Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion LifeOS/install/LIFEOS/PULSE/PULSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
21 changes: 17 additions & 4 deletions LifeOS/install/LIFEOS/PULSE/modules/telegram.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Buffer> {
export async function synthesizeDaVoice(text: string): Promise<Buffer> {
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, {
Expand Down Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion LifeOS/install/LIFEOS/PULSE/modules/work.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]) {
Expand Down Expand Up @@ -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 <owner/repo>`. 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.",
],
Expand Down
39 changes: 11 additions & 28 deletions LifeOS/install/LIFEOS/TOOLS/BannerRetro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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}`);
Expand Down
5 changes: 3 additions & 2 deletions LifeOS/install/LIFEOS/TOOLS/DAGrowth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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<T>(path: string): Promise<T[]> {
Expand Down Expand Up @@ -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)
Expand Down
5 changes: 3 additions & 2 deletions LifeOS/install/LIFEOS/TOOLS/DASchedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -143,7 +144,7 @@ function addTask(args: Record<string, string>): 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 }
Expand Down Expand Up @@ -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
}
2 changes: 1 addition & 1 deletion LifeOS/install/LIFEOS/TOOLS/MemoryGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string> {
Expand Down
9 changes: 5 additions & 4 deletions LifeOS/install/LIFEOS/TOOLS/MemoryInsights.ts
Original file line number Diff line number Diff line change
@@ -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).
*
Expand All @@ -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");
Expand All @@ -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);
Expand Down Expand Up @@ -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))`);
Expand All @@ -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("");
Expand Down
3 changes: 2 additions & 1 deletion LifeOS/install/LIFEOS/TOOLS/MemoryStatus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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:");
Expand Down
5 changes: 3 additions & 2 deletions LifeOS/install/LIFEOS/TOOLS/TelosFreshness.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]) {
Expand Down Expand Up @@ -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 };
Expand Down Expand Up @@ -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");
Expand Down
9 changes: 5 additions & 4 deletions LifeOS/install/LIFEOS/TOOLS/WorkSweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"]) {
Expand Down Expand Up @@ -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 = [
Expand Down Expand Up @@ -378,7 +379,7 @@ async function sweepProjectChecks(
"Status:queued",
projectProperty(row.name.toLowerCase()),
"Priority:P3",
"Agent:kai",
`Agent:${getDAName()}`,
"stale",
], existingLabels);
const body = [
Expand Down Expand Up @@ -443,7 +444,7 @@ async function sweepGoals(
"Status:queued",
"Property:internal",
"Priority:P2",
"Agent:kai",
`Agent:${getDAName()}`,
], existingLabels);
const body = [
`## 🎯 TELOS Goal`,
Expand Down Expand Up @@ -514,7 +515,7 @@ async function sweepBpeCadence(
"Status:queued",
"Property:internal",
"Priority:P3",
"Agent:kai",
`Agent:${getDAName()}`,
], existingLabels);
const body = [
`## 🪓 Scheduled BitterPillEngineering pass`,
Expand Down
3 changes: 2 additions & 1 deletion LifeOS/install/hooks/ReminderRouter.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -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",
];

Expand Down