From ed46a67b8310664222ea987c49f153f89905221e Mon Sep 17 00:00:00 2001 From: alex anikin <60673011+anikin-xyz@users.noreply.github.com> Date: Thu, 9 Jul 2026 21:51:47 -0700 Subject: [PATCH] fix: de-hardcode the DA name across TOOLS, PULSE, and hooks The author's DA name (kai) was baked into ~15 sites that should read the configured DA name so a fresh install reflects the name the principal chose during setup. - TOOLS (TelosFreshness, DAGrowth, DASchedule, MemoryStatus, MemoryInsights, WorkSweep) + hooks/ReminderRouter: read via getDAName() (hooks/lib/identity). - PULSE modules (work.ts, telegram.ts): resolve the DA name through the same identity/LifeosConfig sources already used in those files; the Agent: label producers now share one source so their labels agree. - BannerRetro: render the DA name (stats.name, defaults to "LifeOS") instead of hardcoded "KAI" block-letter art. - MemoryGraph: drop personal names ("kai", "daniel") from the stopword list. - PULSE.toml [da].primary: neutral placeholder + pointer to the canonical LIFEOS_CONFIG.toml [da].name. --- LifeOS/install/LIFEOS/PULSE/PULSE.toml | 4 +- .../install/LIFEOS/PULSE/modules/telegram.ts | 21 ++++++++-- LifeOS/install/LIFEOS/PULSE/modules/work.ts | 3 +- LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts | 39 ++++++------------- LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts | 5 ++- LifeOS/install/LIFEOS/TOOLS/DASchedule.ts | 5 ++- LifeOS/install/LIFEOS/TOOLS/MemoryGraph.ts | 2 +- LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts | 9 +++-- LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts | 3 +- LifeOS/install/LIFEOS/TOOLS/TelosFreshness.ts | 5 ++- LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts | 9 +++-- LifeOS/install/hooks/ReminderRouter.hook.ts | 3 +- 12 files changed, 57 insertions(+), 51 deletions(-) diff --git a/LifeOS/install/LIFEOS/PULSE/PULSE.toml b/LifeOS/install/LIFEOS/PULSE/PULSE.toml index 32566968b1..adba2178ad 100644 --- a/LifeOS/install/LIFEOS/PULSE/PULSE.toml +++ b/LifeOS/install/LIFEOS/PULSE/PULSE.toml @@ -86,7 +86,9 @@ enabled = true [da] enabled = true -primary = "kai" +# DA display name (placeholder). The canonical DA name lives in +# LIFEOS/USER/CONFIG/LIFEOS_CONFIG.toml [da].name; rename your DA there. +primary = "Aria" heartbeat_schedule = "*/30 * * * *" heartbeat_model = "haiku" heartbeat_cost_ceiling = 0.01 diff --git a/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts b/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts index 708730b7dd..615aab3fdd 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/telegram.ts @@ -102,6 +102,18 @@ const DA_VOICE_ID = ((): string => { return "21m00Tcm4TlvDq8ikWAM" // ElevenLabs "Rachel" — public voice })() +// DA display name — read once at import from LifeosConfig ([da].name in +// LIFEOS/USER/CONFIG/LIFEOS_CONFIG.toml). Used to strip a leading speaker label +// off voice summaries and to name the outbound voice-note file. "LifeOS" +// fallback matches the identity loader's default when config is unavailable. +const DA_NAME = ((): string => { + try { + const n = loadLifeosConfig().da.name + if (n && typeof n === "string" && n.length > 0) return n + } catch { /* fall through to default */ } + return "LifeOS" +})() + // ── Module State ── let bot: Bot | null = null @@ -499,7 +511,8 @@ function tidySummary(raw: string): string { // Strip wrapping quotes s = s.replace(/^["'`]+|["'`]+$/g, "") // Strip leading speaker labels ({{DA_NAME}}:, Summary:, etc.) - s = s.replace(/^(kai|summary|voice|output|response)[:\-—]\s*/i, "") + const daLabel = DA_NAME.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + s = s.replace(new RegExp(`^(${daLabel}|summary|voice|output|response)[:\\-—]\\s*`, "i"), "") // Strip leading list markers s = s.replace(/^[-*•]\s+/, "") // Collapse internal whitespace @@ -528,7 +541,7 @@ function fallbackFirstSentence(text: string): string { * bytes as a Buffer; never writes to disk. Telegram's sendVoice consumes the * Buffer via grammY's InputFile constructor. */ -export async function synthesizeKaiVoice(text: string): Promise { +export async function synthesizeDaVoice(text: string): Promise { const url = `https://api.elevenlabs.io/v1/text-to-speech/${DA_VOICE_ID}?output_format=opus_48000_64` const spokenText = disambiguateHomographs(text) const res = await fetch(url, { @@ -612,11 +625,11 @@ export function sendVoiceSummary( } const tSynth = Date.now() - const buf = await synthesizeKaiVoice(summary) + const buf = await synthesizeDaVoice(summary) const synthLatencyMs = Date.now() - tSynth const tSend = Date.now() - await ctx.api.sendVoice(chatId, new InputFile(buf, "kai-summary.ogg")) + await ctx.api.sendVoice(chatId, new InputFile(buf, `${DA_NAME.toLowerCase().replace(/[^a-z0-9]+/g, "-")}-summary.ogg`)) const sendLatencyMs = Date.now() - tSend lastVoiceSendMs = Date.now() - t0 diff --git a/LifeOS/install/LIFEOS/PULSE/modules/work.ts b/LifeOS/install/LIFEOS/PULSE/modules/work.ts index c8fb9b8d0c..fb1f89aa5e 100644 --- a/LifeOS/install/LIFEOS/PULSE/modules/work.ts +++ b/LifeOS/install/LIFEOS/PULSE/modules/work.ts @@ -24,6 +24,7 @@ import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from "fs"; import { join } from "path"; import { loadWorkConfig, type WorkConfig } from "../../../hooks/lib/work-config"; +import { getDAName } from "../../../hooks/lib/identity"; // Normalize env path vars that Claude Code injects without shell expansion (LifeOS#1404) for (const k of ["LIFEOS_DIR", "LIFEOS_CONFIG_DIR", "PROJECTS_DIR"]) { @@ -317,7 +318,7 @@ function setupTemplate(reason: string): Response { subtype, instructions: [ "Configure the work repo via the privacy-attested CLI: `bun ~/.claude/skills/_ULWORK/Tools/SetWorkRepo.ts `. The CLI calls `gh repo view --json visibility,isPrivate` and refuses to write the config unless the repo is currently private.", - "Ensure the repo has these labels: Type:feature, Type:reminder, Type:research, Type:queue, Status:queued, Status:in-progress, Status:in-review, Status:blocked, Status:done, Priority:P0..P3, Property:internal, Agent:kai, pai-sync.", + `Ensure the repo has these labels: Type:feature, Type:reminder, Type:research, Type:queue, Status:queued, Status:in-progress, Status:in-review, Status:blocked, Status:done, Priority:P0..P3, Property:internal, Agent:${getDAName()}, pai-sync.`, "Restart Pulse so this module re-reads work_repo.json: `bun ~/.claude/LIFEOS/PULSE/manage.sh restart`.", "Run an Algorithm session — ULWorkSync.hook.ts will open the first issue at SessionEnd.", ], diff --git a/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts b/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts index 6caa6807a9..e4457fa9b4 100755 --- a/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts +++ b/LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts @@ -278,24 +278,13 @@ const LIFEOS_CUBE_WIDE = [ " +----------+", ]; -// ═══════════════════════════════════════════════════════════════════════════ -// Block Letter KAI (using block characters) -// ═══════════════════════════════════════════════════════════════════════════ - -const BLOCK_KAI = [ - "█ █ █████ █████", - "█ █ █ █ █ ", - "██ █████ █ ", - "█ █ █ █ █ ", - "█ █ █ █ █████", -]; - -// Smaller block KAI -const BLOCK_KAI_SMALL = [ - "█▀▄ ▄▀█ █", - "█▀▄ █▀█ █", - "▀ ▀ ▀ ▀ █", -]; +// The banner spells the configured DA name (stats.name, which defaults to +// "LifeOS"), rendered letter-spaced at the render sites below. The former +// hardcoded "KAI" block-letter constants were removed so the banner reflects +// whatever DA the principal named during setup. +function daNameBanner(name: string): string { + return name.toUpperCase().split("").join(" "); +} // ═══════════════════════════════════════════════════════════════════════════ // Dynamic Stats & Identity @@ -514,12 +503,10 @@ function createRetroBanner(): string { lines.push(` ${gd}System Status:${RESET} ${g}${progress}${RESET} ${h}75%${RESET}`); // ───────────────────────────────────────────────────────────────────────── - // BLOCK LETTER KAI + // DA NAME (derived from configured DA identity) // ───────────────────────────────────────────────────────────────────────── lines.push(""); - for (const row of BLOCK_KAI_SMALL) { - lines.push(` ${c}${row}${RESET}`); - } + lines.push(` ${c}${daNameBanner(stats.name)}${RESET}`); // ───────────────────────────────────────────────────────────────────────── // GITHUB URL @@ -625,12 +612,8 @@ function createPureASCIIBanner(): string { lines.push(` ${gd}Status:${RESET} ${g}[########....] 75%${RESET}`); lines.push(""); - // Simple block KAI - lines.push(` ${c}# # ### ###${RESET}`); - lines.push(` ${c}# # ### ###${RESET}`); - lines.push(` ${c}## # # ###${RESET}`); - lines.push(` ${c}# # ### ###${RESET}`); - lines.push(` ${c}# # # # ###${RESET}`); + // DA name (derived from configured DA identity) + lines.push(` ${c}${daNameBanner(stats.name)}${RESET}`); lines.push(""); lines.push(` ${gd}----------------------------------------${RESET}`); diff --git a/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts b/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts index 4402c0144c..1cf5be8b0d 100755 --- a/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts @@ -13,6 +13,7 @@ */ import { join } from "path" +import { getDAName } from "../../hooks/lib/identity" const HOME = process.env.HOME ?? "~" const LifeOS = join(HOME, ".claude", "LIFEOS") @@ -40,7 +41,7 @@ interface GrowthEvent { function parsePrimaryDA(content: string): string { const match = content.match(/^primary:\s*(\S+)/m) - return match?.[1] ?? "kai" + return match?.[1] ?? getDAName() } async function readJSONL(path: string): Promise { @@ -212,7 +213,7 @@ async function main() { const args = process.argv.slice(2) const command = args[0] ?? "summary" - let primaryDA = "kai" + let primaryDA = getDAName() try { const registryContent = await Bun.file(REGISTRY_PATH).text() primaryDA = parsePrimaryDA(registryContent) diff --git a/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts b/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts index 6bd0f70c58..57d511269e 100755 --- a/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts +++ b/LifeOS/install/LIFEOS/TOOLS/DASchedule.ts @@ -12,6 +12,7 @@ import { join } from "path" import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from "fs" +import { getDAName } from "../../hooks/lib/identity" const HOME = process.env.HOME ?? "~" const LIFEOS_DIR = join(HOME, ".claude", "LIFEOS") @@ -143,7 +144,7 @@ function addTask(args: Record): void { const task: ScheduledTask = { id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, created_at: new Date().toISOString(), - created_by: args.by ?? "kai", + created_by: args.by ?? getDAName(), description: desc, schedule: hasCron ? { type: "recurring", cron: args.cron, until: args.until } @@ -251,6 +252,6 @@ Options: --model LLM model (for type=prompt, default: haiku) --command Shell command (for type=script) --until Expiry ISO datetime (for recurring) - --by Creator name (default: kai)`) + --by Creator name (default: ${getDAName()})`) break } diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryGraph.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryGraph.ts index a7272cec38..882f3c7840 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryGraph.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryGraph.ts @@ -202,7 +202,7 @@ function ingest(): Raw[] { // ============================================================================ const STOP = new Set(("a an and are as at be by for from has have in into is it its of on or that the to with we our this " + - "you your i me my system build make want need use using via pai memory work isa kai daniel new add get set " + + "you your i me my system build make want need use using via pai memory work isa new add get set " + "not but all any can will should would they them then than over under about across into out up down").split(" ")); function tokenize(text: string): Set { diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts index 8f6afa16c1..af4ead6785 100644 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts @@ -1,6 +1,6 @@ #!/usr/bin/env bun /** - * MemoryInsights — `kai insights` CLI for autonomic memory delta view. + * MemoryInsights — the DA-named `insights` CLI for autonomic memory delta view. * * Visual-freshness ISA, F4 (ISC-30 through ISC-38). * @@ -22,6 +22,7 @@ import { existsSync, readFileSync, statSync } from "node:fs"; import { resolve as pathResolve } from "node:path"; import { homedir } from "node:os"; +import { getDAName } from "../../hooks/lib/identity"; const ROOT = pathResolve(homedir(), ".claude"); const OBS = pathResolve(ROOT, "LIFEOS/MEMORY/OBSERVABILITY"); @@ -41,7 +42,7 @@ function parseArgs(): Args { if (!Number.isNaN(n) && n > 0) days = n; i++; } else if (argv[i] === "--help" || argv[i] === "-h") { - console.log("kai insights — memory delta over the last N day(s)"); + console.log(`${getDAName().toLowerCase()} insights — memory delta over the last N day(s)`); console.log("usage: bun LIFEOS/TOOLS/MemoryInsights.ts [--days N]"); console.log(" --days N window size in days (default: 1)"); process.exit(0); @@ -163,7 +164,7 @@ function main(): void { // Zero-activity short-circuit (ISC-36) const zeroActivity = reviewerRuns.length === 0 && memoryWrites.length === 0 && proposals.length === 0; if (zeroActivity) { - console.log(`kai insights — last ${days} day(s)`); + console.log(`${getDAName().toLowerCase()} insights — last ${days} day(s)`); console.log("═".repeat(60)); console.log(`window: ${fmtClock(sinceMs)} → ${fmtClock(now)}`); console.log(`(no activity in last ${days} day(s))`); @@ -173,7 +174,7 @@ function main(): void { } const out: string[] = []; - out.push(`kai insights — last ${days} day(s)`); + out.push(`${getDAName().toLowerCase()} insights — last ${days} day(s)`); out.push("═".repeat(60)); out.push(`window: ${fmtClock(sinceMs)} → ${fmtClock(now)}`); out.push(""); diff --git a/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts b/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts index f6029b5b19..a4e9a6c70f 100755 --- a/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts +++ b/LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts @@ -31,6 +31,7 @@ import { PENDING_PROPOSALS_PATH, } from "./MemoryTypes"; import { read as readMemory } from "./MemoryWriter"; +import { getDAName } from "../../hooks/lib/identity"; const CLAUDE_ROOT = pathResolve(homedir(), ".claude"); const LIFEOS_DIR = pathJoin(CLAUDE_ROOT, "LIFEOS"); @@ -253,7 +254,7 @@ function relTime(iso: string | null): string { function renderText(r: StatusReport): string { const out: string[] = []; - out.push("kai status — LifeOS memory subsystem"); + out.push(`${getDAName().toLowerCase()} status — LifeOS memory subsystem`); out.push("─".repeat(48)); out.push(""); out.push("Hot-layer memory files:"); diff --git a/LifeOS/install/LIFEOS/TOOLS/TelosFreshness.ts b/LifeOS/install/LIFEOS/TOOLS/TelosFreshness.ts index 81a71ce548..1f9e4befcf 100755 --- a/LifeOS/install/LIFEOS/TOOLS/TelosFreshness.ts +++ b/LifeOS/install/LIFEOS/TOOLS/TelosFreshness.ts @@ -24,6 +24,7 @@ import { readFileSync, writeFileSync, existsSync } from "fs"; import { basename, join } from "path"; +import { getDAName } from "../../hooks/lib/identity"; // Normalize env path vars that Claude Code injects without shell expansion (LifeOS#1404) for (const k of ["LIFEOS_DIR", "LIFEOS_CONFIG_DIR", "PROJECTS_DIR"]) { @@ -552,7 +553,7 @@ function refreshFreshnessCache(): void { */ export function bumpTelosTimestamp( slug?: string, - by: string = "kai", + by: string = getDAName(), path: string = TELOS_PATH, ): { changed: boolean; sectionFound: boolean } { if (!existsSync(path)) return { changed: false, sectionFound: false }; @@ -617,7 +618,7 @@ export function bumpTelosTimestamp( return { changed: true, sectionFound }; } -export function bumpContextTimestamp(filePath: string, by: string = "kai"): { changed: boolean } { +export function bumpContextTimestamp(filePath: string, by: string = getDAName()): { changed: boolean } { if (!existsSync(filePath)) return { changed: false }; const raw = readFileSync(filePath, "utf-8"); diff --git a/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts b/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts index 806c79f4cd..051b176d80 100755 --- a/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts +++ b/LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts @@ -27,6 +27,7 @@ import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync, appendFileSync } from "fs"; import { join } from "path"; import { loadWorkConfig } from "../../hooks/lib/work-config"; +import { getDAName } from "../../hooks/lib/identity"; // Normalize env path vars that Claude Code injects without shell expansion (LifeOS#1404) for (const k of ["LIFEOS_DIR", "LIFEOS_CONFIG_DIR", "PROJECTS_DIR"]) { @@ -255,7 +256,7 @@ async function sweepSessions( "Status:queued", projectProperty(fm.project), "Priority:P3", - "Agent:kai", + `Agent:${getDAName()}`, ], existingLabels); const goalLine = fm.principal_stated_goal ? `\n> 🎯 **Principal stated goal:** ${fm.principal_stated_goal}\n` : ""; const body = [ @@ -378,7 +379,7 @@ async function sweepProjectChecks( "Status:queued", projectProperty(row.name.toLowerCase()), "Priority:P3", - "Agent:kai", + `Agent:${getDAName()}`, "stale", ], existingLabels); const body = [ @@ -443,7 +444,7 @@ async function sweepGoals( "Status:queued", "Property:internal", "Priority:P2", - "Agent:kai", + `Agent:${getDAName()}`, ], existingLabels); const body = [ `## 🎯 TELOS Goal`, @@ -514,7 +515,7 @@ async function sweepBpeCadence( "Status:queued", "Property:internal", "Priority:P3", - "Agent:kai", + `Agent:${getDAName()}`, ], existingLabels); const body = [ `## 🪓 Scheduled BitterPillEngineering pass`, diff --git a/LifeOS/install/hooks/ReminderRouter.hook.ts b/LifeOS/install/hooks/ReminderRouter.hook.ts index 8635ab0418..8f47f87a2b 100755 --- a/LifeOS/install/hooks/ReminderRouter.hook.ts +++ b/LifeOS/install/hooks/ReminderRouter.hook.ts @@ -22,6 +22,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs"; import { createHash } from "crypto"; import { join, dirname } from "path"; import { loadWorkConfig } from "./lib/work-config"; +import { getDAName } from "./lib/identity"; const HOME = process.env.HOME || ""; const STATE_PATH = join(HOME, ".claude", "LIFEOS", "MEMORY", "STATE", "reminder-router-seen.json"); @@ -178,7 +179,7 @@ function buildIssue(match: RouteMatch, prompt: string): { title: string; body: s "Property:internal", "Status:queued", "Priority:P3", - "Agent:kai", + `Agent:${getDAName()}`, "pai-sync", ];