agentix-cli 0.75.2 → 0.76.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -6
- package/dist/{App-N2ZWRJKV.js → App-NCTXMA2P.js} +2 -2
- package/dist/{ChatApp-PGQKQBHF.js → ChatApp-CQIQEKZ2.js} +2 -2
- package/dist/{agent-B46X7Y6V.js → agent-5GQLISLJ.js} +2 -2
- package/dist/{board-dashboard-W64VXH6Y.js → board-dashboard-E4CXJUK5.js} +5 -4
- package/dist/board-dashboard-E4CXJUK5.js.map +1 -0
- package/dist/{chunk-6CCJMHBA.js → chunk-2KOWJNQJ.js} +2 -2
- package/dist/{chunk-TS4JCBSP.js → chunk-676OIOHG.js} +30 -30
- package/dist/chunk-676OIOHG.js.map +1 -0
- package/dist/{chunk-NWL5KPBJ.js → chunk-72P53PGX.js} +2 -2
- package/dist/{chunk-663BLK67.js → chunk-7GC42WAK.js} +32 -32
- package/dist/chunk-7GC42WAK.js.map +1 -0
- package/dist/{chunk-S5JGQKON.js → chunk-A4ACRRRP.js} +2 -2
- package/dist/{chunk-JGEHN4VE.js → chunk-B556VD7A.js} +2 -2
- package/dist/{chunk-PUU2ESI2.js → chunk-CW42OBFL.js} +2 -2
- package/dist/{chunk-Q2FFZX7Y.js → chunk-EAMSULHF.js} +2 -2
- package/dist/{chunk-KIXYU2DD.js → chunk-GT6ZDLXU.js} +197 -189
- package/dist/chunk-GT6ZDLXU.js.map +1 -0
- package/dist/{chunk-UJ5JLS6Z.js → chunk-IYLQBBON.js} +2 -2
- package/dist/{chunk-CUXYJNWT.js → chunk-LQ3PEMBH.js} +2 -2
- package/dist/{chunk-55RM7V4N.js → chunk-MODPTVA6.js} +2 -2
- package/dist/chunk-N3Y3NORE.js +7 -0
- package/dist/chunk-N3Y3NORE.js.map +1 -0
- package/dist/{chunk-PBUJBWOF.js → chunk-P27P7GFY.js} +5 -5
- package/dist/{chunk-KHEI4VNM.js → chunk-PDSCFOC4.js} +2 -2
- package/dist/{chunk-F2RIMHW5.js → chunk-PK6EBDBU.js} +2 -2
- package/dist/{chunk-2HMI5PJG.js → chunk-QGTQ4PIL.js} +2 -2
- package/dist/chunk-R5BXA7TL.js +3 -0
- package/dist/chunk-R5BXA7TL.js.map +1 -0
- package/dist/{chunk-BYJ3A55Y.js → chunk-S2GAEH2F.js} +2 -2
- package/dist/{chunk-IR2Q63IV.js → chunk-SC6Q5M4C.js} +2 -2
- package/dist/{chunk-RGTHA2EI.js → chunk-SY7ENNNK.js} +2 -2
- package/dist/{chunk-CXZ4YDHJ.js → chunk-SZZSUW42.js} +2 -2
- package/dist/{chunk-CXZ4YDHJ.js.map → chunk-SZZSUW42.js.map} +1 -1
- package/dist/{chunk-7ZO2UZRI.js → chunk-URINHGJF.js} +233 -233
- package/dist/chunk-URINHGJF.js.map +1 -0
- package/dist/{chunk-CAVAMWI5.js → chunk-ZPM7SSJM.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-HLCKITQJ.js +2 -0
- package/dist/{config-A23CU2XT.js → config-QLCPWIJN.js} +2 -2
- package/dist/config-mutator-BCTMKRPT.js +2 -0
- package/dist/doctor-74P5CWVW.js +2 -0
- package/dist/index.d.ts +60 -0
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/program-ONWCNXKS.js +2 -0
- package/dist/{promote-PMMUU4G2.js → promote-VFL66JD2.js} +2 -2
- package/dist/{query-CB6FV6JU.js → query-MCCWN6XA.js} +2 -2
- package/dist/{registry-A2LPURNS.js → registry-43X3DQTI.js} +2 -2
- package/dist/{request-planner-44WOTYCY.js → request-planner-GSGBUGNG.js} +2 -2
- package/dist/{review-candidates-BZLS37MN.js → review-candidates-4Z6QTMN2.js} +2 -2
- package/dist/{routing-CPLPRA2E.js → routing-T66LO3JY.js} +2 -2
- package/dist/schedule-tool-ORNG6HMN.js +2 -0
- package/dist/scheduler-VIWJITKR.js +2 -0
- package/dist/{seat-CBAZAYGY.js → seat-4H7TTV5X.js} +2 -2
- package/dist/{select-4CW3CXBU.js → select-YCVNOR5Q.js} +2 -2
- package/dist/{set-enabled-QNSLVBGT.js → set-enabled-6UGUPYW4.js} +2 -2
- package/dist/tool-EID3ZKQK.js +4 -0
- package/dist/tool-EID3ZKQK.js.map +1 -0
- package/dist/wiki-SHHIVZPT.js +2 -0
- package/package.json +2 -2
- package/dist/board-dashboard-W64VXH6Y.js.map +0 -1
- package/dist/chunk-663BLK67.js.map +0 -1
- package/dist/chunk-7ZO2UZRI.js.map +0 -1
- package/dist/chunk-GZAGG3ES.js +0 -7
- package/dist/chunk-GZAGG3ES.js.map +0 -1
- package/dist/chunk-KIXYU2DD.js.map +0 -1
- package/dist/chunk-TS4JCBSP.js.map +0 -1
- package/dist/client-5DVNJ4VZ.js +0 -2
- package/dist/config-mutator-WDZ6F6Q4.js +0 -2
- package/dist/doctor-MVV47YVK.js +0 -2
- package/dist/program-3DBXC6DX.js +0 -2
- package/dist/schedule-tool-4I3QUXJK.js +0 -2
- package/dist/scheduler-LJ32FB5N.js +0 -2
- package/dist/tool-544FGIJK.js +0 -2
- package/dist/tool-544FGIJK.js.map +0 -1
- package/dist/wiki-4LKOLCP4.js +0 -2
- /package/dist/{App-N2ZWRJKV.js.map → App-NCTXMA2P.js.map} +0 -0
- /package/dist/{ChatApp-PGQKQBHF.js.map → ChatApp-CQIQEKZ2.js.map} +0 -0
- /package/dist/{agent-B46X7Y6V.js.map → agent-5GQLISLJ.js.map} +0 -0
- /package/dist/{chunk-6CCJMHBA.js.map → chunk-2KOWJNQJ.js.map} +0 -0
- /package/dist/{chunk-NWL5KPBJ.js.map → chunk-72P53PGX.js.map} +0 -0
- /package/dist/{chunk-S5JGQKON.js.map → chunk-A4ACRRRP.js.map} +0 -0
- /package/dist/{chunk-JGEHN4VE.js.map → chunk-B556VD7A.js.map} +0 -0
- /package/dist/{chunk-PUU2ESI2.js.map → chunk-CW42OBFL.js.map} +0 -0
- /package/dist/{chunk-Q2FFZX7Y.js.map → chunk-EAMSULHF.js.map} +0 -0
- /package/dist/{chunk-UJ5JLS6Z.js.map → chunk-IYLQBBON.js.map} +0 -0
- /package/dist/{chunk-CUXYJNWT.js.map → chunk-LQ3PEMBH.js.map} +0 -0
- /package/dist/{chunk-55RM7V4N.js.map → chunk-MODPTVA6.js.map} +0 -0
- /package/dist/{chunk-PBUJBWOF.js.map → chunk-P27P7GFY.js.map} +0 -0
- /package/dist/{chunk-KHEI4VNM.js.map → chunk-PDSCFOC4.js.map} +0 -0
- /package/dist/{chunk-F2RIMHW5.js.map → chunk-PK6EBDBU.js.map} +0 -0
- /package/dist/{chunk-2HMI5PJG.js.map → chunk-QGTQ4PIL.js.map} +0 -0
- /package/dist/{chunk-BYJ3A55Y.js.map → chunk-S2GAEH2F.js.map} +0 -0
- /package/dist/{chunk-IR2Q63IV.js.map → chunk-SC6Q5M4C.js.map} +0 -0
- /package/dist/{chunk-RGTHA2EI.js.map → chunk-SY7ENNNK.js.map} +0 -0
- /package/dist/{chunk-CAVAMWI5.js.map → chunk-ZPM7SSJM.js.map} +0 -0
- /package/dist/{client-5DVNJ4VZ.js.map → client-HLCKITQJ.js.map} +0 -0
- /package/dist/{config-A23CU2XT.js.map → config-QLCPWIJN.js.map} +0 -0
- /package/dist/{config-mutator-WDZ6F6Q4.js.map → config-mutator-BCTMKRPT.js.map} +0 -0
- /package/dist/{doctor-MVV47YVK.js.map → doctor-74P5CWVW.js.map} +0 -0
- /package/dist/{program-3DBXC6DX.js.map → program-ONWCNXKS.js.map} +0 -0
- /package/dist/{promote-PMMUU4G2.js.map → promote-VFL66JD2.js.map} +0 -0
- /package/dist/{query-CB6FV6JU.js.map → query-MCCWN6XA.js.map} +0 -0
- /package/dist/{registry-A2LPURNS.js.map → registry-43X3DQTI.js.map} +0 -0
- /package/dist/{request-planner-44WOTYCY.js.map → request-planner-GSGBUGNG.js.map} +0 -0
- /package/dist/{review-candidates-BZLS37MN.js.map → review-candidates-4Z6QTMN2.js.map} +0 -0
- /package/dist/{routing-CPLPRA2E.js.map → routing-T66LO3JY.js.map} +0 -0
- /package/dist/{schedule-tool-4I3QUXJK.js.map → schedule-tool-ORNG6HMN.js.map} +0 -0
- /package/dist/{scheduler-LJ32FB5N.js.map → scheduler-VIWJITKR.js.map} +0 -0
- /package/dist/{seat-CBAZAYGY.js.map → seat-4H7TTV5X.js.map} +0 -0
- /package/dist/{select-4CW3CXBU.js.map → select-YCVNOR5Q.js.map} +0 -0
- /package/dist/{set-enabled-QNSLVBGT.js.map → set-enabled-6UGUPYW4.js.map} +0 -0
- /package/dist/{wiki-4LKOLCP4.js.map → wiki-SHHIVZPT.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/daemon/workflow-health.ts","../src/daemon/routines.ts","../src/daemon/token-store.ts","../src/approvals/inbox.ts","../src/whatsapp-triage/drafts.ts","../src/whatsapp-triage/wacli.ts","../src/approvals/cards.ts","../src/approvals/state.ts","../src/daemon/mesh-auth.ts","../src/daemon/browser-origin.ts","../src/voice/voice-health.ts","../src/voice/system-voices.ts","../src/voice/siri.ts","../src/voice/language.ts","../src/voice/agent-voice.ts","../src/business/work-pool.ts","../src/business/backlog-store.ts","../src/business/backlog-sync.ts","../src/business/clients.ts","../src/channels/ui-directive.ts","../src/channels/webrtc-signal.ts","../src/daemon/app-files-api.ts","../src/voice/speaker.ts","../src/voice/transcribe.ts","../src/voice/speakable.ts","../src/daemon/voice-io-api.ts","../src/daemon/file-ops.ts","../src/computer-use/capture-settings.ts"],"sourcesContent":["// --- Is the automation still running? -------------------------------------\n//\n// A workflow that stops firing produces nothing: no error, no run, no page\n// entry. On this fleet 16 workflows produced 759 runs and then went quiet for\n// three days without anyone noticing, which is the failure this module exists\n// to make visible. Health is therefore about ABSENCE first and failure second.\n//\n// Cost note: RunStore.get() replays a whole run file. Polling that for every\n// run on every dashboard refresh is not affordable, so we read each file's\n// first line (the snapshot, which carries workflowId) and last line (the\n// terminal status), and cache per file keyed on mtime. Runs are append-only,\n// so after the first scan almost nothing is re-read.\n\nimport { existsSync, readdirSync, readFileSync, statSync } from \"fs\"\nimport { resolve } from \"path\"\n\nexport interface RunSummary { workflowId: string; status: string; at: number }\n\n/** Absence first: a workflow that used to fire and no longer does is the\n * headline, because nothing else in the product will ever mention it. */\nexport type WorkflowState = \"never\" | \"failing\" | \"dormant\" | \"active\" | \"quiet\"\n\nexport interface WorkflowHealth {\n id: string\n name: string\n state: WorkflowState\n /** Last run of any status, ms since epoch. Null when it has never run. */\n lastRunAt: number | null\n recent: number\n prior: number\n failed: number\n /** Runs parked at a checkpoint. These are waiting on a human, not broken. */\n paused: number\n}\n\nexport function workflowHealth(\n workflows: Array<{ id: string; name?: string }>,\n runs: RunSummary[],\n now = Date.now(),\n windowDays = 7,\n): WorkflowHealth[] {\n // One day in ms, inlined: workflowHealth is stringified and shipped to the\n // workflows page, where a module-scope constant would be unbound.\n const span = windowDays * 86_400_000\n const byId = new Map<string, RunSummary[]>()\n for (const r of runs) {\n const list = byId.get(r.workflowId)\n if (list) list.push(r)\n else byId.set(r.workflowId, [r])\n }\n return workflows.map(w => {\n const mine = byId.get(w.id) ?? []\n const recent = mine.filter(r => now - r.at <= span)\n const prior = mine.filter(r => now - r.at > span && now - r.at <= span * 2)\n const failed = recent.filter(r => r.status === \"failed\").length\n const paused = mine.filter(r => r.status === \"paused\").length\n const lastRunAt = mine.length ? Math.max(...mine.map(r => r.at)) : null\n // Order matters: a workflow that has stopped firing is reported as dormant\n // even when its last few runs failed — the silence is the bigger signal.\n const state: WorkflowState =\n !mine.length ? \"never\"\n : prior.length > 0 && recent.length === 0 ? \"dormant\"\n : recent.length > 0 && failed * 2 >= recent.length ? \"failing\"\n : recent.length > 0 ? \"active\"\n : \"quiet\"\n return { id: w.id, name: w.name || w.id, state, lastRunAt, recent: recent.length, prior: prior.length, failed, paused }\n }).sort((a, b) => {\n // Inlined rather than a module constant: workflowHealth is stringified\n // and shipped to the workflows page, where module scope does not travel.\n // A hoisted RANK only failed with two or more workflows, because Array\n // .sort never calls the comparator on a single-element list.\n const rank: Record<string, number> = { dormant: 0, failing: 1, active: 2, quiet: 3, never: 4 }\n return rank[a.state] - rank[b.state] || (b.lastRunAt ?? 0) - (a.lastRunAt ?? 0)\n })\n}\n\n\nconst cache = new Map<string, { mtime: number; summary: RunSummary | null }>()\n\n/** Read workflowId + terminal status per run, re-reading only changed files. */\nexport function scanRuns(runsDir: string, limit = 1000): RunSummary[] {\n if (!existsSync(runsDir)) return []\n const files = readdirSync(runsDir)\n .filter(f => f.endsWith(\".jsonl\"))\n .map(f => { const p = resolve(runsDir, f); return { path: p, mtime: statSync(p).mtimeMs } })\n .sort((a, b) => b.mtime - a.mtime)\n .slice(0, limit)\n const live = new Set(files.map(f => f.path))\n for (const key of cache.keys()) if (!live.has(key)) cache.delete(key)\n const out: RunSummary[] = []\n for (const { path, mtime } of files) {\n const hit = cache.get(path)\n if (hit && hit.mtime === mtime) { if (hit.summary) out.push(hit.summary); continue }\n const summary = readSummary(path, mtime)\n cache.set(path, { mtime, summary })\n if (summary) out.push(summary)\n }\n return out\n}\n\nfunction readSummary(path: string, mtime: number): RunSummary | null {\n try {\n const lines = readFileSync(path, \"utf-8\").split(\"\\n\").filter(Boolean)\n if (!lines.length) return null\n const head = JSON.parse(lines[0]) as { run?: { workflowId?: string; status?: string } }\n const workflowId = head.run?.workflowId\n if (!workflowId) return null\n // The last line is either a status-bearing exec entry or another snapshot;\n // fall back to the snapshot's own status when it carries neither.\n let status = head.run?.status ?? \"unknown\"\n for (let i = lines.length - 1; i >= 0; i--) {\n const tail = JSON.parse(lines[i]) as { status?: string; run?: { status?: string } }\n const s = tail.status ?? tail.run?.status\n if (s) { status = s; break }\n }\n return { workflowId, status, at: mtime }\n } catch { return null }\n}\n","// --- Routines: everything that runs on its own, in one list -----------------\n//\n// Automation lives in two places: time-based `crons.*` jobs and workflows\n// whose trigger node is `trigger.cron` or `trigger.hook`. This module is a\n// read model over both — no new schema — merged into one bounded row shape\n// with a staleness verdict, so obsolete or failing automation surfaces on\n// the mesh overview instead of going unnoticed.\n//\n// Pure: callers pass the job list, the persisted cron attempts (the source\n// of truth; scheduler counters reset on reload), the workflow definitions\n// and the workflow run summaries. Nothing here reads prompts or responses,\n// and every string that leaves it is length-capped.\n//\n// Routine ids are stable and namespaced by source — `cron:<jobId>` and\n// `workflow:<workflowId>` — so later per-routine actions can address a row\n// without knowing which subsystem owns it.\n\nimport type { CronRunHistoryItem } from \"@/crons/run-history\"\nimport { getNextCronDate } from \"@/crons/scheduler\"\nimport { workflowHealth, type RunSummary } from \"./workflow-health\"\n\nexport type RoutineKind = \"schedule\" | \"event\"\nexport type RoutineSource = \"cron\" | \"workflow\"\nexport type RoutineOutcome = \"success\" | \"failed\" | \"timeout\" | \"canceled\" | \"running\" | \"paused\" | \"unknown\"\nexport type RoutineFlagReason = \"failing\" | \"overdue\" | \"dormant\" | \"never-ran\" | \"disabled-long\"\nexport type RoutineAttention = \"critical\" | \"warning\" | \"info\"\n\nexport interface RoutineFlag { reason: RoutineFlagReason; detail: string }\n\nexport interface Routine {\n id: string\n source: RoutineSource\n kind: RoutineKind\n name: string\n trigger: {\n /** Cron expression for schedules. */\n schedule?: string\n timezone?: string\n /** `on:*` event for event routines. */\n event?: string\n /** Compact `key=value` renderings of the trigger filters. */\n filters?: string[]\n }\n agent: string | null\n enabled: boolean\n /** Cron: enabled/disabled. Workflow: active/disabled/quarantined. */\n state: string\n nextRunAt: string | null\n lastRun: { at: string; status: RoutineOutcome; summary?: string } | null\n lastSuccessAt: string | null\n /** Terminal failures in a row, newest first, over the sampled runs. */\n consecutiveFailures: number\n /** Runs inspected to reach the verdict (bounded, not a lifetime total). */\n sampledRuns: number\n flags: RoutineFlag[]\n attention: RoutineAttention | null\n}\n\nexport interface RoutineCronJob {\n id: string\n enabled: boolean\n schedule: string\n timezone?: string\n agent: string\n nextRun?: Date | string\n}\n\nexport interface RoutineWorkflow {\n id: string\n title?: string\n state?: string\n ownerAgent?: string\n project?: string\n updated?: string\n created?: string\n nodes: Array<{ id: string; type: string; config?: Record<string, unknown> }>\n}\n\nexport interface RoutineThresholds {\n /** Consecutive failures that flag a routine as failing. */\n failStreak: number\n /** Days a routine may sit disabled before it reads as obsolete. */\n disabledDays: number\n /** Scheduled fires that may pass without an attempt before it is overdue. */\n missedFires: number\n /** Slack added to the overdue deadline for slow starts and restarts. */\n graceMs: number\n}\n\nexport const DEFAULT_ROUTINE_THRESHOLDS: RoutineThresholds = {\n failStreak: 3,\n disabledDays: 30,\n missedFires: 2,\n graceMs: 15 * 60_000,\n}\n\n/** Hard caps: the list is served on an unauthenticated GET and polled. */\nexport const ROUTINE_LIMITS = { routines: 200, summary: 160, filters: 6, filterValue: 60, text: 120 }\n\nexport type NextFire = (expression: string, after: Date, timezone: string) => Date | null\n\nconst defaultNextFire: NextFire = (expression, after, timezone) => {\n try { return getNextCronDate(expression, after, timezone) } catch { return null }\n}\n\nexport interface BuildRoutinesInput {\n crons: RoutineCronJob[]\n /** Newest-first persisted attempts per cron job id. */\n cronRuns: Map<string, CronRunHistoryItem[]>\n workflows: RoutineWorkflow[]\n workflowRuns: RunSummary[]\n now?: number\n thresholds?: Partial<RoutineThresholds>\n nextFire?: NextFire\n}\n\nconst DAY = 86_400_000\n\nexport function buildRoutines(input: BuildRoutinesInput): Routine[] {\n const now = input.now ?? Date.now()\n const t = { ...DEFAULT_ROUTINE_THRESHOLDS, ...input.thresholds }\n const nextFire = input.nextFire ?? defaultNextFire\n const out: Routine[] = []\n\n for (const job of input.crons) {\n const runs = input.cronRuns.get(job.id) ?? []\n const timezone = job.timezone || \"UTC\"\n const outcomes = runs.map((r) => ({ at: Date.parse(r.startedAt), status: r.status as RoutineOutcome }))\n const latest = runs[0]\n const routine: Routine = {\n id: `cron:${job.id}`,\n source: \"cron\",\n kind: \"schedule\",\n name: cap(job.id, ROUTINE_LIMITS.text),\n trigger: { schedule: cap(job.schedule, ROUTINE_LIMITS.text), timezone },\n agent: job.agent || null,\n enabled: job.enabled,\n state: job.enabled ? \"enabled\" : \"disabled\",\n nextRunAt: job.enabled ? isoOrNull(job.nextRun) ?? isoOrNull(nextFire(job.schedule, new Date(now), timezone)) : null,\n lastRun: latest\n ? { at: latest.startedAt, status: latest.status, summary: capOpt(latest.status === \"success\" ? undefined : latest.errorSummary, ROUTINE_LIMITS.summary) }\n : null,\n lastSuccessAt: runs.find((r) => r.status === \"success\")?.startedAt ?? null,\n consecutiveFailures: failStreak(outcomes),\n sampledRuns: runs.length,\n flags: [],\n attention: null,\n }\n routine.flags = scheduleFlags(routine, outcomes, job.schedule, timezone, null, now, t, nextFire)\n out.push(finish(routine))\n }\n\n const health = new Map(workflowHealth(\n input.workflows.map((w) => ({ id: w.id, name: w.title })), input.workflowRuns, now,\n ).map((h) => [h.id, h] as const))\n const runsByWorkflow = new Map<string, RunSummary[]>()\n for (const r of input.workflowRuns) {\n const list = runsByWorkflow.get(r.workflowId)\n if (list) list.push(r)\n else runsByWorkflow.set(r.workflowId, [r])\n }\n\n for (const wf of input.workflows) {\n const trigger = wf.nodes.find((n) => n.type.startsWith(\"trigger.\"))\n if (!trigger || (trigger.type !== \"trigger.cron\" && trigger.type !== \"trigger.hook\")) continue\n const cfg = (trigger.config ?? {}) as Record<string, unknown>\n const state = wf.state || \"active\"\n const enabled = state === \"active\"\n const runs = (runsByWorkflow.get(wf.id) ?? []).slice().sort((a, b) => b.at - a.at)\n const outcomes = runs.map((r) => ({ at: r.at, status: workflowOutcome(r.status) }))\n const latest = outcomes[0]\n const isCron = trigger.type === \"trigger.cron\"\n const spec = typeof cfg.spec === \"string\" ? cfg.spec : \"\"\n const timezone = typeof cfg.timezone === \"string\" ? cfg.timezone : \"UTC\"\n const routine: Routine = {\n id: `workflow:${wf.id}`,\n source: \"workflow\",\n kind: isCron ? \"schedule\" : \"event\",\n name: cap(wf.title || wf.id, ROUTINE_LIMITS.text),\n trigger: isCron\n ? { schedule: cap(spec, ROUTINE_LIMITS.text), timezone }\n : { event: cap(typeof cfg.event === \"string\" ? cfg.event : \"\", ROUTINE_LIMITS.text), filters: describeFilters(cfg.filter, wf) },\n agent: workflowAgent(wf),\n enabled,\n state,\n nextRunAt: isCron && enabled && spec ? isoOrNull(nextFire(spec, new Date(now), timezone)) : null,\n lastRun: latest ? { at: new Date(latest.at).toISOString(), status: latest.status } : null,\n lastSuccessAt: (() => { const s = outcomes.find((o) => o.status === \"success\"); return s ? new Date(s.at).toISOString() : null })(),\n consecutiveFailures: failStreak(outcomes),\n sampledRuns: runs.length,\n flags: [],\n attention: null,\n }\n const changedAt = Date.parse(wf.updated || wf.created || \"\")\n routine.flags = isCron\n ? scheduleFlags(routine, outcomes, spec, timezone, Number.isFinite(changedAt) ? changedAt : null, now, t, nextFire)\n : eventFlags(routine, health.get(wf.id)?.state === \"dormant\", Number.isFinite(changedAt) ? changedAt : null, now, t)\n out.push(finish(routine))\n }\n\n return out.sort(compareRoutines).slice(0, ROUTINE_LIMITS.routines)\n}\n\n/** Terminal failures in a row from the newest run. In-flight and parked\n * runs are skipped; a success or cancel ends the streak. */\nexport function failStreak(outcomes: Array<{ status: RoutineOutcome }>): number {\n let n = 0\n for (const o of outcomes) {\n if (o.status === \"running\" || o.status === \"paused\" || o.status === \"unknown\") continue\n if (o.status === \"failed\" || o.status === \"timeout\") n++\n else break\n }\n return n\n}\n\nfunction scheduleFlags(\n r: Routine,\n outcomes: Array<{ at: number; status: RoutineOutcome }>,\n spec: string,\n timezone: string,\n changedAt: number | null,\n now: number,\n t: RoutineThresholds,\n nextFire: NextFire,\n): RoutineFlag[] {\n const flags: RoutineFlag[] = []\n const lastAt = outcomes.length ? outcomes[0].at : null\n if (!r.enabled) {\n const disabled = disabledFlag(lastAt, changedAt, now, t)\n if (disabled) flags.push(disabled)\n return flags\n }\n if (r.consecutiveFailures >= t.failStreak) {\n flags.push({ reason: \"failing\", detail: `${r.consecutiveFailures} failed attempts in a row` })\n }\n if (lastAt === null) {\n flags.push({ reason: \"never-ran\", detail: \"No persisted attempt\" })\n return flags\n }\n // Walk the schedule forward from the last attempt: if `missedFires`\n // expected fires have already passed (plus grace), the job has gone quiet.\n let cursor = new Date(lastAt)\n let deadline: Date | null = null\n for (let i = 0; i < t.missedFires; i++) {\n deadline = nextFire(spec, cursor, timezone)\n if (!deadline) break\n cursor = deadline\n }\n if (deadline && now > deadline.getTime() + t.graceMs) {\n flags.push({ reason: \"overdue\", detail: `No attempt since ${new Date(lastAt).toISOString()}; ${t.missedFires} scheduled fires missed` })\n }\n return flags\n}\n\nfunction eventFlags(\n r: Routine,\n dormant: boolean,\n changedAt: number | null,\n now: number,\n t: RoutineThresholds,\n): RoutineFlag[] {\n const flags: RoutineFlag[] = []\n const lastAt = r.lastRun ? Date.parse(r.lastRun.at) : null\n if (!r.enabled) {\n const disabled = disabledFlag(lastAt, changedAt, now, t)\n if (disabled) flags.push(disabled)\n return flags\n }\n if (r.consecutiveFailures >= t.failStreak) {\n flags.push({ reason: \"failing\", detail: `${r.consecutiveFailures} failed runs in a row` })\n }\n if (lastAt === null) flags.push({ reason: \"never-ran\", detail: \"No run recorded for this trigger\" })\n else if (dormant) flags.push({ reason: \"dormant\", detail: \"Fired in the prior week, silent in the last one\" })\n return flags\n}\n\nfunction disabledFlag(lastAt: number | null, changedAt: number | null, now: number, t: RoutineThresholds): RoutineFlag | null {\n // Crons carry no \"disabled since\" timestamp, so the last attempt (or the\n // workflow's last edit) is the best available proxy for how long it sat.\n const since = Math.max(lastAt ?? 0, changedAt ?? 0)\n if (since && now - since < t.disabledDays * DAY) return null\n return {\n reason: \"disabled-long\",\n detail: since ? `Disabled; no activity for ${Math.floor((now - since) / DAY)} days` : \"Disabled and never ran\",\n }\n}\n\nfunction finish(r: Routine): Routine {\n const reasons = new Set(r.flags.map((f) => f.reason))\n r.attention = reasons.has(\"failing\") ? \"critical\"\n : reasons.has(\"overdue\") || reasons.has(\"dormant\") || reasons.has(\"never-ran\") ? \"warning\"\n : reasons.has(\"disabled-long\") ? \"info\"\n : null\n return r\n}\n\nconst ATTENTION_RANK: Record<string, number> = { critical: 0, warning: 1, info: 2 }\n\nfunction compareRoutines(a: Routine, b: Routine): number {\n const ra = a.attention ? ATTENTION_RANK[a.attention] : 3\n const rb = b.attention ? ATTENTION_RANK[b.attention] : 3\n return ra - rb || Number(b.enabled) - Number(a.enabled) || a.name.localeCompare(b.name)\n}\n\n/** Map a workflow run's last recorded status onto the routine vocabulary.\n * The tail line may be a node execution entry (`ok`), not a run status. */\nexport function workflowOutcome(status: string): RoutineOutcome {\n switch (status) {\n case \"completed\": case \"ok\": return \"success\"\n case \"failed\": return \"failed\"\n case \"timeout\": return \"timeout\"\n case \"canceled\": return \"canceled\"\n case \"running\": return \"running\"\n case \"paused\": return \"paused\"\n default: return \"unknown\"\n }\n}\n\nfunction workflowAgent(wf: RoutineWorkflow): string | null {\n if (wf.ownerAgent) return cap(wf.ownerAgent, ROUTINE_LIMITS.text)\n const node = wf.nodes.find((n) => n.type === \"agent\" && typeof n.config?.agentId === \"string\" && n.config.agentId)\n return node ? cap(String(node.config!.agentId), ROUTINE_LIMITS.text) : null\n}\n\n/** `filter.action = [\"open\",\"reopen\"]` → `action=open,reopen`. The workflow's\n * project scope is a filter too: it silently drops other projects' events. */\nexport function describeFilters(filter: unknown, wf?: { project?: string }): string[] {\n const out: string[] = []\n const project = wf?.project\n if (project) out.push(cap(`project=${project}`, ROUTINE_LIMITS.filterValue))\n if (filter && typeof filter === \"object\" && !Array.isArray(filter)) {\n for (const [key, value] of Object.entries(filter as Record<string, unknown>)) {\n const values = Array.isArray(value) ? value.map(String) : value == null ? [] : [String(value)]\n if (!values.length) continue\n out.push(cap(`${key}=${values.join(\",\")}`, ROUTINE_LIMITS.filterValue))\n }\n }\n return out.slice(0, ROUTINE_LIMITS.filters)\n}\n\nfunction isoOrNull(v: Date | string | null | undefined): string | null {\n if (!v) return null\n const d = v instanceof Date ? v : new Date(v)\n return Number.isFinite(d.getTime()) ? d.toISOString() : null\n}\n\nfunction cap(value: string, limit: number): string {\n return value.length <= limit ? value : value.slice(0, limit - 3) + \"...\"\n}\n\nfunction capOpt(value: string | undefined, limit: number): string | undefined {\n return value === undefined ? undefined : cap(value, limit)\n}\n","import { createHash, randomBytes } from \"crypto\"\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\n\n// --- Scoped API tokens ---\n//\n// Tokens let external services (other agentx instances, webhook senders, custom\n// apps) talk to this daemon with explicit scopes instead of sharing a single\n// all-powerful dashboard token.\n//\n// We only ever store the sha256 of each token + its printable prefix. The full\n// token is shown exactly once, at creation time, and the operator copies it.\n// Format: agx_live_<hex(32)> — prefix lets secret-scanners catch leaks.\n//\n// Scopes (string equality; \"agent:*\" grants any agent):\n// dashboard:read — /api/live, /api/agents, /api/task/history\n// dashboard:write — /api/admin/*, config mutation endpoints\n// agent:<id> — /api/public/agents/<id>/messages\n// agent:* — any agent\n// mesh:peer — cross-node mesh (forward-compatible — not enforced yet)\n// app — the phone app (/app, /api/app/*) and nothing else;\n// minted per device by `agentx app pair`\n\nexport const TOKEN_PREFIX = \"agx_live_\"\nconst DEFAULT_FILE = \".agentx/tokens.json\"\n\nexport interface TokenRecord {\n id: string // short public id (shown in lists)\n name: string\n prefix: string // full printable prefix (TOKEN_PREFIX + first-4 chars of body)\n hash: string // sha256 of the full token\n scopes: string[]\n createdAt: string\n expiresAt?: string\n revokedAt?: string\n lastUsedAt?: string\n}\n\nexport class TokenStore {\n private file: string\n\n constructor(baseDir: string = process.cwd(), fileRel: string = DEFAULT_FILE) {\n this.file = resolve(baseDir, fileRel)\n }\n\n private load(): TokenRecord[] {\n if (!existsSync(this.file)) return []\n try { return JSON.parse(readFileSync(this.file, \"utf-8\")) as TokenRecord[] } catch { return [] }\n }\n\n private save(records: TokenRecord[]): void {\n mkdirSync(dirname(this.file), { recursive: true })\n writeFileSync(this.file, JSON.stringify(records, null, 2) + \"\\n\", \"utf-8\")\n }\n\n /**\n * Mint a new token. Returns the full token string AND the record.\n * The full token is the only place the secret is ever exposed — the caller\n * must surface it to the operator immediately; after this call the store only\n * knows the hash.\n */\n create(input: {\n name: string\n scopes: string[]\n expiresInDays?: number\n }): { token: string; record: TokenRecord } {\n if (!input.name?.trim()) throw new Error(\"Token name is required.\")\n if (!Array.isArray(input.scopes) || input.scopes.length === 0) {\n throw new Error(\"At least one scope is required.\")\n }\n for (const s of input.scopes) {\n if (!isValidScope(s)) throw new Error(`Invalid scope: ${s}`)\n }\n const body = randomBytes(32).toString(\"hex\")\n const token = TOKEN_PREFIX + body\n const record: TokenRecord = {\n id: \"tok_\" + randomBytes(4).toString(\"hex\"),\n name: input.name.trim(),\n prefix: TOKEN_PREFIX + body.slice(0, 4),\n hash: hashToken(token),\n scopes: [...input.scopes],\n createdAt: new Date().toISOString(),\n expiresAt: input.expiresInDays\n ? new Date(Date.now() + input.expiresInDays * 86400 * 1000).toISOString()\n : undefined,\n }\n const records = this.load()\n records.push(record)\n this.save(records)\n return { token, record }\n }\n\n list(): TokenRecord[] {\n return this.load()\n }\n\n revoke(id: string): TokenRecord | null {\n const records = this.load()\n const rec = records.find((r) => r.id === id)\n if (!rec) return null\n rec.revokedAt = new Date().toISOString()\n this.save(records)\n return rec\n }\n\n /** True when the token with this id exists and is neither revoked nor expired. */\n isActive(id: string): boolean {\n const rec = this.load().find((r) => r.id === id)\n return !!rec && !rec.revokedAt && !(rec.expiresAt && Date.parse(rec.expiresAt) < Date.now())\n }\n\n /**\n * Look up a token string. Returns the record on success, null on unknown /\n * revoked / expired. Updates lastUsedAt opportunistically (write-on-success).\n */\n verify(token: string): TokenRecord | null {\n if (!token || !token.startsWith(TOKEN_PREFIX)) return null\n const hash = hashToken(token)\n const records = this.load()\n const rec = records.find((r) => r.hash === hash)\n if (!rec) return null\n if (rec.revokedAt) return null\n if (rec.expiresAt && Date.parse(rec.expiresAt) < Date.now()) return null\n // Throttle lastUsedAt updates to at most once per minute to avoid disk churn.\n const now = Date.now()\n const last = rec.lastUsedAt ? Date.parse(rec.lastUsedAt) : 0\n if (now - last > 60_000) {\n rec.lastUsedAt = new Date(now).toISOString()\n try { this.save(records) } catch { /* best-effort */ }\n }\n return rec\n }\n}\n\n/**\n * Check whether a record has a given required scope. \"agent:<id>\" required\n * matches either itself or \"agent:*\". \"dashboard:write\" implicitly grants\n * \"dashboard:read\" for convenience.\n */\nexport function recordHasScope(record: TokenRecord, required: string): boolean {\n if (record.scopes.includes(required)) return true\n if (required.startsWith(\"agent:\") && record.scopes.includes(\"agent:*\")) return true\n if (required === \"dashboard:read\" && record.scopes.includes(\"dashboard:write\")) return true\n return false\n}\n\nfunction isValidScope(s: string): boolean {\n return (\n s === \"dashboard:read\" ||\n s === \"dashboard:write\" ||\n s === \"agent:*\" ||\n /^agent:[a-z0-9][a-z0-9_-]*$/.test(s) ||\n s === \"mesh:peer\" ||\n s === \"app\"\n )\n}\n\nexport function hashToken(token: string): string {\n return \"sha256:\" + createHash(\"sha256\").update(token).digest(\"hex\")\n}\n\n/**\n * Extract a bearer token from the request headers. Falls back to the\n * `?token=` query param so EventSource (which can't set headers in most\n * browsers) can still authenticate.\n */\nexport function extractToken(req: { headers: Record<string, string | string[] | undefined>; url?: string }): string | null {\n const auth = req.headers[\"authorization\"]\n const authHeader = Array.isArray(auth) ? auth[0] : auth\n if (authHeader && authHeader.toLowerCase().startsWith(\"bearer \")) {\n return authHeader.slice(7).trim()\n }\n if (req.url) {\n try {\n const u = new URL(req.url, \"http://localhost\")\n const tok = u.searchParams.get(\"token\")\n if (tok) return tok\n } catch { /* */ }\n }\n return null\n}\n","import { existsSync, readdirSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { MemoryStore } from \"@/agents/memory-store\"\nimport { approveSchedule, formatFireTime, humanizeCron, nextFireTime, rejectSchedule } from \"@/crons/schedule-ops\"\nimport { listProposals, readProposal, type PromotionProposal } from \"@/wiki/proposals\"\nimport { approveProposal, rejectProposal } from \"@/wiki/promote\"\nimport { decideDraft, listDrafts, readDraft, type SendReply } from \"@/whatsapp-triage/drafts\"\nimport { decideCard, listCards, readCard, type DecisionCard, type IfSilent } from \"./cards\"\nimport { readInboxState, snooze } from \"./state\"\n\n// --- The Approvals inbox: one list over every pending decision ---\n//\n// A read model, not a store. Each source keeps its own records and its own\n// approve/reject; the inbox lists them in one shape and hands a verdict to\n// the source's existing function:\n//\n// card decision cards agents raise (cards.ts)\n// schedule schedules an agent asked to create or delete (crons/schedule-ops)\n// memory facts from outside sources held before agents may use them\n// wiki lessons proposed for the shared wiki (wiki/proposals, wiki/promote)\n// whatsapp replies an agent drafted for a watched WhatsApp chat\n// (whatsapp-triage/drafts); yes sends it through wacli\n//\n// Only operator surfaces call decide(): the `agentx approvals` CLI and the\n// dashboard's /api/admin/approvals. The daemon's agent-facing API and the\n// MCP tool can create cards and read, never decide.\n\nexport type ApprovalKind = \"card\" | \"schedule\" | \"memory\" | \"wiki\" | \"whatsapp\"\nexport const APPROVAL_KINDS: readonly ApprovalKind[] = [\"card\", \"schedule\", \"memory\", \"wiki\", \"whatsapp\"]\n\nexport type InboxAction = \"yes\" | \"no\" | \"later\"\n\n/** One pending decision, in bounded summary form. */\nexport interface InboxItem {\n /** `<kind>:<ref>`: what the CLI and the dashboard pass back. */\n key: string\n kind: ApprovalKind\n title: string\n /** The yes/no question. */\n ask: string\n /** What yes and no do, in words. */\n yes: string\n no: string\n recommend?: string\n if_silent?: IfSilent\n expires?: string\n source?: string\n raised_by: string\n created_at: string\n /** A short excerpt of what is being decided. */\n detail?: string\n /** The source's own command for the full record. */\n more?: string\n /** Cards: the ready-made answers on offer; yes must pick one. */\n choices?: string[]\n snoozed_until?: string\n}\n\nexport interface InboxContext {\n /** Directory holding agentx.json and .agentx/. */\n root: string\n configPath?: string\n wikiDir?: string\n /** Hot-reload the daemon after a schedule decision. Default true. */\n reload?: boolean\n now?: number\n /** Sends an approved WhatsApp reply. Default: wacli. Tests swap it. */\n sendWhatsApp?: SendReply\n}\n\nexport const DETAIL_MAX = 280\n\nfunction clip(s: string, n = DETAIL_MAX): string {\n const flat = s.replace(/\\s+/g, \" \").trim()\n return flat.length > n ? `${flat.slice(0, n - 1)}…` : flat\n}\n\nfunction configPathFor(ctx: InboxContext): string {\n if (ctx.configPath) return resolve(ctx.configPath)\n const candidates = [resolve(ctx.root, \"agentx.json\"), resolve(ctx.root, \".agentx\", \"config.json\")]\n return candidates.find((c) => existsSync(c)) ?? candidates[0]\n}\n\nfunction wikiDirFor(ctx: InboxContext): string {\n return ctx.wikiDir ? resolve(ctx.wikiDir) : resolve(ctx.root, \".agentx\", \"wiki\")\n}\n\n// ── Sources ──────────────────────────────────────────────────────────\n\nfunction cardItems(ctx: InboxContext): InboxItem[] {\n return listCards(ctx.root, \"pending\").map(cardItem)\n}\n\nfunction cardItem(c: DecisionCard): InboxItem {\n return {\n key: `card:${c.id}`,\n kind: \"card\",\n title: c.title,\n ask: c.ask,\n yes: `tell ${c.raised_by} yes`,\n no: `tell ${c.raised_by} no`,\n recommend: c.recommend,\n if_silent: c.if_silent,\n expires: c.expires,\n ...(c.source ? { source: c.source } : {}),\n raised_by: c.raised_by,\n created_at: c.created_at,\n ...(c.choices ? { choices: c.choices, more: `agentx approvals popup card:${c.id}` } : {}),\n ...(c.draft ? { detail: clip(`Suggested message: ${c.draft}`) } : {}),\n }\n}\n\nfunction scheduleItems(ctx: InboxContext): InboxItem[] {\n const path = configPathFor(ctx)\n if (!existsSync(path)) return []\n let cfg: any\n try { cfg = JSON.parse(readFileSync(path, \"utf-8\")) } catch { return [] }\n const now = new Date(ctx.now ?? Date.now())\n const out: InboxItem[] = []\n for (const [id, job] of Object.entries<any>(cfg?.crons ?? {})) {\n const approval = job?.approval\n if (!approval || (approval.action !== \"create\" && approval.action !== \"delete\")) continue\n const tz = job.timezone || \"UTC\"\n const human = job.schedule ? humanizeCron(job.schedule) : \"\"\n const when = job.schedule ? `${human.charAt(0).toLowerCase()}${human.slice(1)} (${job.schedule})` : \"no schedule\"\n const what = String(job.prompt ?? job.command ?? \"\")\n const creating = approval.action === \"create\"\n out.push({\n key: `schedule:${id}`,\n kind: \"schedule\",\n title: creating ? `New schedule \"${id}\" for ${job.agent ?? \"an agent\"}` : `Delete schedule \"${id}\"`,\n ask: creating ? `Let ${job.agent ?? \"the agent\"} run this ${when}?` : `Remove the schedule \"${id}\" (${when})?`,\n yes: creating ? \"enable it\" : \"remove it\",\n no: creating ? \"drop the request\" : \"keep the schedule\",\n raised_by: String(approval.requestedBy ?? \"unknown\"),\n created_at: String(approval.requestedAt ?? \"\"),\n detail: clip(`${what}${creating && job.schedule ? ` · first run ${formatFireTime(nextFireTime(job.schedule, tz, now), tz)}` : \"\"}`),\n more: \"agentx schedule list\",\n })\n }\n return out\n}\n\nfunction memoryAgents(root: string): string[] {\n const dir = resolve(root, \".agentx\", \"memory\")\n if (!existsSync(dir)) return []\n return readdirSync(dir).filter((f) => f.endsWith(\".jsonl\")).map((f) => f.slice(0, -6)).sort()\n}\n\nfunction memoryItems(ctx: InboxContext): InboxItem[] {\n const agents = memoryAgents(ctx.root)\n if (agents.length === 0) return []\n const store = new MemoryStore(ctx.root)\n const out: InboxItem[] = []\n for (const agent of agents) {\n for (const f of store.held(agent)) {\n out.push({\n key: `memory:${agent}/${f.id}`,\n kind: \"memory\",\n title: `Fact ${agent} learned from ${f.source.channel}`,\n ask: `Let ${agent} use this fact in its prompts?`,\n yes: \"use it\",\n no: \"keep it out for good\",\n raised_by: agent,\n created_at: f.createdAt,\n detail: clip(f.content),\n more: `agentx memory facts held --agent ${agent}`,\n })\n }\n }\n return out\n}\n\n/** One line on what backs a wiki proposal. A lesson drawn from a\n * recurring failure says so, with the failure, before the article text:\n * the reviewer is judging a fix for something that keeps breaking. */\nexport function proposalBacking(e: PromotionProposal[\"evidence\"]): string {\n const failures = e.sources.filter((s) => s.failure)\n if (failures.length) {\n return failures.map((s) =>\n `Recurring failure: ${s.agentId}'s ${s.failure!.tool} fails with \"${s.failure!.errorClass}\" in ${s.occurrences ?? 1} sessions (${s.failure!.runs} runs).`,\n ).join(\" \")\n }\n return `${e.sources.length} source(s)${e.occurrences > 1 ? `, seen in ${e.occurrences} sessions` : \"\"}.`\n}\n\nfunction wikiItems(ctx: InboxContext): InboxItem[] {\n return listProposals(wikiDirFor(ctx), \"pending\").map((p) => {\n const e = p.evidence\n return {\n key: `wiki:${p.id}`,\n kind: \"wiki\" as const,\n title: p.article.title,\n ask: p.replaces ? `Update \"${p.article.path}\" in the shared wiki?` : `Add \"${p.article.path}\" to the shared wiki?`,\n yes: \"write the article\",\n no: \"decline it\",\n raised_by: e.agents.join(\", \") || \"wiki promote\",\n created_at: p.createdAt,\n detail: clip(`${proposalBacking(e)} ${p.article.content}`),\n more: `agentx wiki proposals show ${p.id}`,\n }\n })\n}\n\nfunction whatsappItems(ctx: InboxContext): InboxItem[] {\n return listDrafts(ctx.root, \"pending\").map((d) => ({\n key: `whatsapp:${d.id}`,\n kind: \"whatsapp\" as const,\n title: `WhatsApp reply to ${d.chat_name || d.to}`,\n // The whole draft: the owner approves exactly what will be sent.\n ask: `Send this to ${d.chat_name || d.to}? \"${d.text}\"`,\n yes: \"send it on WhatsApp\",\n no: \"drop the draft\",\n raised_by: d.agent,\n created_at: d.created_at,\n detail: clip(`${d.triage}: ${d.summary}`),\n more: \"agentx whatsapp triage log\",\n }))\n}\n\nconst SOURCES: Record<ApprovalKind, (ctx: InboxContext) => InboxItem[]> = {\n card: cardItems,\n schedule: scheduleItems,\n memory: memoryItems,\n wiki: wikiItems,\n whatsapp: whatsappItems,\n}\n\n// ── Read model ───────────────────────────────────────────────────────\n\nexport interface InboxListing {\n items: InboxItem[]\n /** Hidden because the operator said \"later\". */\n snoozed: number\n /** Sources that couldn't be read, so an empty list isn't mistaken for \"nothing waiting\". */\n errors: Array<{ kind: ApprovalKind; error: string }>\n}\n\n/** Soonest expiry first; then oldest first. */\nexport function byUrgency(a: InboxItem, b: InboxItem): number {\n const ea = a.expires ? Date.parse(a.expires) : Infinity\n const eb = b.expires ? Date.parse(b.expires) : Infinity\n if (ea !== eb) return ea - eb\n return (Date.parse(a.created_at) || 0) - (Date.parse(b.created_at) || 0)\n}\n\nexport function listInbox(ctx: InboxContext, opts: { includeSnoozed?: boolean; kinds?: ApprovalKind[] } = {}): InboxListing {\n const now = ctx.now ?? Date.now()\n const { snoozed } = readInboxState(ctx.root)\n const items: InboxItem[] = []\n const errors: InboxListing[\"errors\"] = []\n let hidden = 0\n for (const kind of opts.kinds ?? APPROVAL_KINDS) {\n let found: InboxItem[]\n try { found = SOURCES[kind](ctx) } catch (e: any) {\n errors.push({ kind, error: String(e?.message ?? e).slice(0, 200) })\n continue\n }\n for (const item of found) {\n const until = snoozed[item.key]\n if (until && Date.parse(until) > now) {\n if (!opts.includeSnoozed) { hidden++; continue }\n item.snoozed_until = until\n }\n items.push(item)\n }\n }\n items.sort(byUrgency)\n return { items, snoozed: hidden, errors }\n}\n\n// ── Decide ───────────────────────────────────────────────────────────\n\nexport interface DecideOptions {\n /** Wiki: approve even if the article changed since the proposal. */\n force?: boolean\n /** Kept with the decision where the source records one. */\n note?: string\n /** \"later\": how long to put it off. */\n laterHours?: number\n by?: string\n /** Cards with choices: which one (1-based number or the label). */\n choice?: string | number\n /** Cards with a draft: the message as the operator edited it. */\n text?: string\n}\n\nexport type DecideResult = { ok: true; message: string } | { ok: false; error: string }\n\nexport function parseKey(key: string): { kind: ApprovalKind; ref: string } | null {\n const i = key.indexOf(\":\")\n if (i <= 0) return null\n const kind = key.slice(0, i) as ApprovalKind\n const ref = key.slice(i + 1)\n if (!APPROVAL_KINDS.includes(kind) || !ref) return null\n return { kind, ref }\n}\n\n/** True while the item is still waiting in its source. */\nfunction stillPending(ctx: InboxContext, kind: ApprovalKind, ref: string): boolean {\n switch (kind) {\n case \"card\": return readCard(ctx.root, ref)?.status === \"pending\"\n case \"wiki\": return readProposal(wikiDirFor(ctx), ref)?.status === \"pending\"\n case \"whatsapp\": return readDraft(ctx.root, ref)?.status === \"pending\"\n default: return SOURCES[kind](ctx).some((i) => i.key === `${kind}:${ref}`)\n }\n}\n\n/**\n * Apply the operator's answer through the source's own approve/reject.\n * Operator surfaces only: never expose this to agents.\n */\nexport async function decide(ctx: InboxContext, key: string, action: InboxAction, opts: DecideOptions = {}): Promise<DecideResult> {\n const parsed = parseKey(key)\n if (!parsed) return { ok: false, error: `unknown item \"${key}\": use a key from \\`agentx approvals list\\`` }\n const { kind, ref } = parsed\n const now = ctx.now ?? Date.now()\n const by = opts.by ?? \"operator\"\n\n if (action === \"later\") {\n if (!stillPending(ctx, kind, ref)) return { ok: false, error: `nothing waiting for \"${key}\"` }\n const hours = opts.laterHours && opts.laterHours > 0 ? opts.laterHours : 24\n const until = new Date(now + hours * 3_600_000)\n snooze(ctx.root, key, until, now)\n return { ok: true, message: `${key} put off until ${until.toISOString()}` }\n }\n\n const yes = action === \"yes\"\n switch (kind) {\n case \"card\": {\n const r = decideCard(ctx.root, ref, yes ? \"yes\" : \"no\", { by, note: opts.note, now, choice: opts.choice, text: opts.text })\n if (!r.ok) return r\n return { ok: true, message: `${key}: ${r.card.raised_by} will be told ${yes ? \"yes\" : \"no\"}${r.card.choice ? ` (${r.card.choice})` : \"\"}` }\n }\n case \"schedule\": {\n const r = await (yes ? approveSchedule : rejectSchedule)(ref, { configPath: configPathFor(ctx), reload: ctx.reload })\n return r.success ? { ok: true, message: r.message } : { ok: false, error: r.message }\n }\n case \"memory\": {\n const slash = ref.indexOf(\"/\")\n if (slash <= 0) return { ok: false, error: `memory keys look like memory:<agent>/<fact-id>` }\n const agent = ref.slice(0, slash)\n const id = ref.slice(slash + 1)\n if (!memoryAgents(ctx.root).includes(agent)) return { ok: false, error: `no memory for agent \"${agent}\"` }\n const store = new MemoryStore(ctx.root)\n if (!store.held(agent).some((f) => f.id === id)) return { ok: false, error: `fact \"${id}\" isn't waiting for review` }\n store.review(agent, id, yes ? \"approved\" : \"rejected\", by)\n return { ok: true, message: `${key} ${yes ? \"approved: the agent may use it\" : \"rejected: it stays out of prompts\"}` }\n }\n case \"wiki\": {\n const dir = wikiDirFor(ctx)\n const r = yes\n ? approveProposal(dir, ref, { by, force: opts.force, now })\n : rejectProposal(dir, ref, { by, reason: opts.note, now })\n if (!r.ok) return r\n return { ok: true, message: yes ? `${r.proposal.article.path} written to the shared wiki` : `${key} rejected` }\n }\n case \"whatsapp\": {\n const r = await decideDraft(ctx.root, ref, yes ? \"yes\" : \"no\", { by, now, send: ctx.sendWhatsApp })\n if (!r.ok) return r\n return { ok: true, message: yes ? `${key}: sent to ${r.draft.chat_name || r.draft.to}` : `${key}: dropped, nothing sent` }\n }\n }\n}\n","import { randomBytes } from \"crypto\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport type { WacliSettings } from \"./config\"\nimport type { TriageClass } from \"./store\"\nimport { sendText } from \"./wacli\"\n\n// --- Draft WhatsApp replies waiting for the owner ---\n//\n// The agent never sends to a watched contact. Its draft is saved here and\n// listed in the Approvals inbox as `whatsapp:<id>`. Only the owner's yes\n// (CLI or dashboard, see approvals/inbox.ts) sends it through wacli.\n//\n// One JSON file per draft under .agentx/whatsapp-triage/drafts/, because\n// the CLI, the dashboard and the daemon are separate processes. Deciding\n// first renames the file away, so two clicks on \"yes\" send once.\n\nexport type DraftStatus = \"pending\" | \"sent\" | \"rejected\" | \"failed\"\n\nexport interface ReplyDraft {\n id: string\n status: DraftStatus\n created_at: string\n agent: string\n rule_id: string\n /** The chat the reply goes to. */\n to: string\n chat_name?: string\n text: string\n triage: TriageClass\n summary: string\n wacli: WacliSettings\n decided_by?: string\n decided_at?: string\n error?: string\n}\n\nexport type SendReply = (draft: ReplyDraft) => Promise<{ ok: true } | { ok: false; error: string }>\n\nexport const DRAFT_MAX = 2000\n\nexport const sendWithWacli: SendReply = (d) => sendText(d.wacli, d.to, d.text)\n\nexport function draftsDir(root: string): string {\n return resolve(root, \".agentx\", \"whatsapp-triage\", \"drafts\")\n}\n\nconst validId = (id: string): boolean => /^[a-z0-9-]{8,64}$/.test(id)\nconst fileFor = (root: string, id: string): string => resolve(draftsDir(root), `${id}.json`)\n\nfunction save(root: string, draft: ReplyDraft): void {\n mkdirSync(draftsDir(root), { recursive: true })\n const path = fileFor(root, draft.id)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(draft, null, 2) + \"\\n\", { mode: 0o600 })\n renameSync(tmp, path)\n}\n\nexport function createDraft(\n root: string,\n input: Omit<ReplyDraft, \"id\" | \"status\" | \"created_at\">,\n now = Date.now(),\n): ReplyDraft {\n const text = input.text.trim().slice(0, DRAFT_MAX)\n if (!text) throw new Error(\"empty draft\")\n const draft: ReplyDraft = {\n ...input,\n text,\n id: `${now.toString(36)}-${randomBytes(4).toString(\"hex\")}`,\n status: \"pending\",\n created_at: new Date(now).toISOString(),\n }\n save(root, draft)\n return draft\n}\n\nexport function readDraft(root: string, id: string): ReplyDraft | null {\n if (!validId(id)) return null\n try { return JSON.parse(readFileSync(fileFor(root, id), \"utf-8\")) as ReplyDraft } catch { return null }\n}\n\nexport function listDrafts(root: string, status?: DraftStatus): ReplyDraft[] {\n const dir = draftsDir(root)\n if (!existsSync(dir)) return []\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".json\"))\n .map((f) => readDraft(root, f.slice(0, -5)))\n .filter((d): d is ReplyDraft => !!d && (!status || d.status === status))\n .sort((a, b) => a.created_at.localeCompare(b.created_at))\n}\n\n/**\n * The owner's answer. Yes sends the draft through wacli, no drops it.\n * Operator surfaces only: never reachable by an agent.\n */\nexport async function decideDraft(\n root: string,\n id: string,\n verdict: \"yes\" | \"no\",\n opts: { by?: string; now?: number; send?: SendReply } = {},\n): Promise<{ ok: true; draft: ReplyDraft } | { ok: false; error: string }> {\n const draft = readDraft(root, id)\n if (!draft || draft.status !== \"pending\") return { ok: false, error: `no reply draft \"${id}\" is waiting` }\n // Claim it: only one caller's rename succeeds.\n const claim = `${fileFor(root, id)}.${process.pid}.${randomBytes(3).toString(\"hex\")}.claim`\n try { renameSync(fileFor(root, id), claim) } catch {\n return { ok: false, error: `reply draft \"${id}\" is already being decided` }\n }\n const decided: ReplyDraft = {\n ...draft,\n decided_by: opts.by ?? \"operator\",\n decided_at: new Date(opts.now ?? Date.now()).toISOString(),\n }\n if (verdict === \"no\") {\n decided.status = \"rejected\"\n } else {\n const r = await (opts.send ?? sendWithWacli)(draft).catch((e: any) => ({ ok: false as const, error: String(e?.message ?? e) }))\n if (r.ok) decided.status = \"sent\"\n else { decided.status = \"failed\"; decided.error = r.error }\n }\n save(root, decided)\n try { unlinkSync(claim) } catch { /* already gone */ }\n return decided.status === \"failed\" ? { ok: false, error: decided.error ?? \"send failed\" } : { ok: true, draft: decided }\n}\n","import { run } from \"@/wiki/facts/exec\"\nimport type { WacliSettings } from \"./config\"\n\n// --- The two wacli commands triage uses ---\n//\n// execFile, never a shell: the reply text and the ids come from chat\n// messages. While `wacli sync --follow` holds the store, `send text` is\n// handed to that process, and `--read-only media download` takes no lock.\n\nexport type Runner = typeof run\n\nfunction base(settings: WacliSettings): string[] {\n return [\n ...(settings.account ? [\"--account\", settings.account] : []),\n ...(settings.store ? [\"--store\", settings.store] : []),\n ]\n}\n\nconst firstLine = (s: string): string => s.trim().split(\"\\n\").pop()?.slice(0, 300) || \"no output\"\n\nexport async function sendText(\n settings: WacliSettings,\n to: string,\n text: string,\n exec: Runner = run,\n): Promise<{ ok: true } | { ok: false; error: string }> {\n const r = await exec(settings.bin, [...base(settings), \"send\", \"text\", \"--to\", to, \"--message\", text, \"--no-preview\"], { timeoutMs: 60_000 })\n return r.code === 0 ? { ok: true } : { ok: false, error: `wacli send failed: ${firstLine(r.stderr || r.stdout)}` }\n}\n\n/** Downloads one message's media to `output`. False when wacli couldn't. */\nexport async function downloadMedia(\n settings: WacliSettings,\n chat: string,\n id: string,\n output: string,\n exec: Runner = run,\n): Promise<boolean> {\n const r = await exec(settings.bin, [...base(settings), \"--read-only\", \"media\", \"download\", \"--chat\", chat, \"--id\", id, \"--output\", output], { timeoutMs: 120_000 })\n return r.code === 0\n}\n","import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { answerLines, buildChoices, resolveAnswer, type CardChoices } from \"./choices\"\n\n// --- Decision cards: what an agent asks the operator ---\n//\n// An agent that needs a yes/no from the operator writes a card instead of\n// asking in chat. A card says what it is, the question, what the agent\n// recommends, and what happens if nobody answers by `expires`. Nothing\n// waits forever: past `expires` the daemon applies `if_silent` (sweep.ts).\n//\n// Storage: one JSON file per card under `.agentx/approvals/`, written\n// atomically, like wiki proposals. The CLI, the dashboard and the daemon\n// are separate processes and all read the same files.\n//\n// Field names match the API (`if_silent`, `raised_by`), so a stored card is\n// exactly what `POST /approvals` accepted.\n\nexport type IfSilent = \"discard\" | \"keep\" | \"pause\" | \"approve\"\nexport const IF_SILENT_VALUES: readonly IfSilent[] = [\"discard\", \"keep\", \"pause\", \"approve\"]\n\nexport type CardStatus = \"pending\" | \"decided\" | \"expired\"\nexport type Verdict = \"yes\" | \"no\"\n\n/** Where the agent was asked, so the verdict can be taken back there. */\nexport interface ReplyTarget {\n channel: string\n chatId: string\n accountId?: string\n}\n\nexport interface DecisionCard extends CardChoices {\n id: string\n title: string\n ask: string\n recommend: string\n if_silent: IfSilent\n /** ISO time the default applies. Always set. */\n expires: string\n /** Link to the draft, PR or issue. */\n source?: string\n raised_by: string\n created_at: string\n reply?: ReplyTarget\n status: CardStatus\n /** The operator's answer (status \"decided\"). */\n verdict?: Verdict\n /** What applied on expiry (status \"expired\"). */\n outcome?: IfSilent\n decided_by?: string\n decided_at?: string\n note?: string\n /** The option the operator picked, when the card offered choices. */\n choice?: string\n /** The message the operator approved, after any edit. */\n text?: string\n /** Set once the raising agent has been told the result. */\n agent_notified_at?: string\n}\n\nexport const CARD_LIMITS = {\n title: 120,\n ask: 300,\n recommend: 300,\n source: 500,\n note: 500,\n /** Pending cards one agent may have open at a time. */\n pendingPerAgent: 25,\n} as const\n\nexport interface CardSettings {\n defaultExpiryDays: number\n maxExpiryDays: number\n}\n\nexport const DEFAULT_CARD_SETTINGS: CardSettings = { defaultExpiryDays: 3, maxExpiryDays: 30 }\n\nexport function approvalsDir(root: string): string {\n return resolve(root, \".agentx\", \"approvals\")\n}\n\nconst CARD_ID_RE = /^[a-z0-9][a-z0-9-]{0,80}$/\n\nexport function isValidCardId(id: string): boolean {\n return CARD_ID_RE.test(id)\n}\n\nfunction fileFor(root: string, id: string): string {\n if (!isValidCardId(id)) throw new Error(`invalid card id: ${id}`)\n return resolve(approvalsDir(root), `${id}.json`)\n}\n\nexport function newCardId(now: number, title: string): string {\n const day = new Date(now).toISOString().slice(0, 10)\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, \"-\").replace(/^-+|-+$/g, \"\").slice(0, 40)\n return `${day}-${slug || \"card\"}-${Math.random().toString(36).slice(2, 6)}`\n}\n\nexport function saveCard(root: string, card: DecisionCard): void {\n mkdirSync(approvalsDir(root), { recursive: true })\n const path = fileFor(root, card.id)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(card, null, 2) + \"\\n\")\n renameSync(tmp, path)\n}\n\nexport function readCard(root: string, id: string): DecisionCard | null {\n if (!isValidCardId(id)) return null\n try {\n return JSON.parse(readFileSync(fileFor(root, id), \"utf-8\")) as DecisionCard\n } catch {\n return null\n }\n}\n\n/** Cards, oldest first; only `status` ones when given. */\nexport function listCards(root: string, status?: CardStatus): DecisionCard[] {\n const dir = approvalsDir(root)\n if (!existsSync(dir)) return []\n const out: DecisionCard[] = []\n for (const f of readdirSync(dir).filter((f) => f.endsWith(\".json\") && !f.startsWith(\"_\")).sort()) {\n const c = readCard(root, f.slice(0, -5))\n if (c && (!status || c.status === status)) out.push(c)\n }\n return out\n}\n\nfunction oneLine(v: unknown): string {\n return typeof v === \"string\" ? v.replace(/\\s+/g, \" \").trim() : \"\"\n}\n\n/**\n * `expires` as given by an agent: an ISO date or time, or a relative\n * \"12h\" / \"3d\". Unset: the default. Always capped at maxExpiryDays, so a\n * card can't be parked for a year.\n */\nexport function resolveExpiry(input: unknown, now: number, settings: CardSettings): { ok: true; at: string } | { ok: false; error: string } {\n const max = now + settings.maxExpiryDays * 86_400_000\n let at: number\n const raw = oneLine(input)\n if (!raw) {\n at = now + settings.defaultExpiryDays * 86_400_000\n } else {\n const rel = /^(\\d+)\\s*([hd])$/i.exec(raw)\n if (rel) {\n at = now + Number(rel[1]) * (rel[2].toLowerCase() === \"d\" ? 86_400_000 : 3_600_000)\n } else {\n at = Date.parse(raw)\n if (Number.isNaN(at)) return { ok: false, error: `expires must be an ISO date or time, or like \"12h\" / \"3d\" (got \"${raw}\")` }\n }\n }\n if (at <= now) return { ok: false, error: \"expires must be in the future\" }\n return { ok: true, at: new Date(Math.min(at, max)).toISOString() }\n}\n\nexport interface CardInput {\n title?: unknown\n ask?: unknown\n recommend?: unknown\n if_silent?: unknown\n expires?: unknown\n source?: unknown\n raised_by?: unknown\n reply?: unknown\n choices?: unknown\n draft?: unknown\n say?: unknown\n}\n\n/** Validate what an agent sent and build a pending card. Never saves. */\nexport function buildCard(\n input: CardInput,\n opts: { now?: number; settings?: CardSettings } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const now = opts.now ?? Date.now()\n const settings = opts.settings ?? DEFAULT_CARD_SETTINGS\n const title = oneLine(input.title)\n const ask = oneLine(input.ask)\n const recommend = oneLine(input.recommend)\n const raisedBy = oneLine(input.raised_by)\n if (!title) return { ok: false, error: \"title is required\" }\n if (!ask) return { ok: false, error: \"ask is required: the yes/no question\" }\n if (!recommend) return { ok: false, error: \"recommend is required: your advice and why, in one line\" }\n if (!raisedBy) return { ok: false, error: \"raised_by is required: the agent id\" }\n if (title.length > CARD_LIMITS.title) return { ok: false, error: `title is longer than ${CARD_LIMITS.title} characters` }\n if (ask.length > CARD_LIMITS.ask) return { ok: false, error: `ask is longer than ${CARD_LIMITS.ask} characters` }\n if (recommend.length > CARD_LIMITS.recommend) return { ok: false, error: `recommend is longer than ${CARD_LIMITS.recommend} characters` }\n const ifSilent = oneLine(input.if_silent).toLowerCase()\n if (!IF_SILENT_VALUES.includes(ifSilent as IfSilent)) {\n return { ok: false, error: `if_silent must be one of ${IF_SILENT_VALUES.join(\", \")}` }\n }\n const expiry = resolveExpiry(input.expires, now, settings)\n if (!expiry.ok) return expiry\n const source = oneLine(input.source)\n if (source.length > CARD_LIMITS.source) return { ok: false, error: `source is longer than ${CARD_LIMITS.source} characters` }\n if (source && /^[a-z][a-z0-9+.-]*:/i.test(source) && !/^https?:\\/\\//i.test(source)) {\n return { ok: false, error: \"source must be an http(s) link or a plain reference\" }\n }\n const extras = buildChoices(input)\n if (!extras.ok) return extras\n let reply: ReplyTarget | undefined\n const r = input.reply as Record<string, unknown> | undefined\n if (r && typeof r === \"object\" && oneLine(r.channel) && oneLine(r.chatId)) {\n reply = { channel: oneLine(r.channel), chatId: oneLine(r.chatId), ...(oneLine(r.accountId) ? { accountId: oneLine(r.accountId) } : {}) }\n }\n return {\n ok: true,\n card: {\n id: newCardId(now, title),\n title,\n ask,\n recommend,\n if_silent: ifSilent as IfSilent,\n expires: expiry.at,\n ...(source ? { source } : {}),\n raised_by: raisedBy,\n created_at: new Date(now).toISOString(),\n ...(reply ? { reply } : {}),\n ...extras.value,\n status: \"pending\",\n },\n }\n}\n\n/** Validate, enforce the per-agent cap, and save. */\nexport function createCard(\n root: string,\n input: CardInput,\n opts: { now?: number; settings?: CardSettings } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const built = buildCard(input, opts)\n if (!built.ok) return built\n const open = listCards(root, \"pending\").filter((c) => c.raised_by === built.card.raised_by).length\n if (open >= CARD_LIMITS.pendingPerAgent) {\n return { ok: false, error: `${built.card.raised_by} already has ${open} cards waiting; wait for decisions before raising more` }\n }\n saveCard(root, built.card)\n return built\n}\n\n/** Record the operator's answer. Refuses anything no longer pending. */\nexport function decideCard(\n root: string,\n id: string,\n verdict: Verdict,\n opts: { by?: string; note?: string; now?: number; choice?: string | number; text?: string } = {},\n): { ok: true; card: DecisionCard } | { ok: false; error: string } {\n const card = readCard(root, id)\n if (!card) return { ok: false, error: `no card \"${id}\"` }\n if (card.status !== \"pending\") return { ok: false, error: `card \"${id}\" is already ${card.status}` }\n const answer = resolveAnswer(card, verdict, opts)\n if (!answer.ok) return answer\n const note = oneLine(opts.note).slice(0, CARD_LIMITS.note)\n const decided: DecisionCard = {\n ...card,\n status: \"decided\",\n verdict,\n decided_by: opts.by ?? \"operator\",\n decided_at: new Date(opts.now ?? Date.now()).toISOString(),\n ...(note ? { note } : {}),\n ...(answer.choice ? { choice: answer.choice } : {}),\n ...(answer.text ? { text: answer.text } : {}),\n }\n saveCard(root, decided)\n return { ok: true, card: decided }\n}\n\n/** Apply `if_silent` to every pending card past its expiry. */\nexport function expireCards(root: string, now: number = Date.now()): DecisionCard[] {\n const out: DecisionCard[] = []\n for (const card of listCards(root, \"pending\")) {\n if (Date.parse(card.expires) > now) continue\n const expired: DecisionCard = {\n ...card,\n status: \"expired\",\n outcome: card.if_silent,\n decided_by: \"expiry\",\n decided_at: new Date(now).toISOString(),\n }\n saveCard(root, expired)\n out.push(expired)\n }\n return out\n}\n\n/** Decided or expired cards whose agent hasn't been told yet. */\nexport function cardsAwaitingAgentNotice(root: string): DecisionCard[] {\n return listCards(root).filter((c) => c.status !== \"pending\" && !c.agent_notified_at)\n}\n\nexport function markAgentNotified(root: string, id: string, now: number = Date.now()): void {\n const card = readCard(root, id)\n if (!card || card.agent_notified_at) return\n saveCard(root, { ...card, agent_notified_at: new Date(now).toISOString() })\n}\n\n/** What the raising agent is told. Plain text, one short message. */\nexport function verdictMessage(card: DecisionCard): string {\n const result = card.status === \"decided\"\n ? (card.verdict === \"yes\" ? \"The operator said YES.\" : \"The operator said NO.\")\n : `Nobody answered before it expired, so the default applied: ${card.outcome ?? card.if_silent}.`\n const lines = [\n `[Approval result] Your decision card \"${card.title}\" (${card.id}) is closed.`,\n `Question: ${card.ask}`,\n result,\n ]\n if (card.status === \"decided\" && card.verdict === \"yes\") lines.push(...answerLines(card))\n if (card.note) lines.push(`Operator note: ${card.note}`)\n if (card.source) lines.push(`Source: ${card.source}`)\n if (card.reply) lines.push(`You raised it from ${card.reply.channel} chat ${card.reply.chatId}; reply there if the requester should know.`)\n lines.push(\"Act on this result now. Do not raise the same card again.\")\n return lines.join(\"\\n\")\n}\n","import { mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { approvalsDir } from \"./cards\"\n\n// --- Inbox view state: \"later\" and the digest date ---\n//\n// Not a decision store. Every decision stays with its source (the card\n// file, the schedule in agentx.json, the memory fact, the wiki proposal).\n// This file only remembers which inbox items the operator put off until\n// when, the last day a digest went out, and which cards the Mac popup\n// has already shown.\n\nexport interface InboxState {\n /** Inbox key → ISO time it comes back. */\n snoozed: Record<string, string>\n /** Local date (YYYY-MM-DD) of the last digest sent. */\n lastDigest?: string\n /** Inbox key → ISO time the Mac popup showed it. Shown once, not every minute. */\n popped?: Record<string, string>\n}\n\nfunction stateFile(root: string): string {\n return resolve(approvalsDir(root), \"_state.json\")\n}\n\nexport function readInboxState(root: string): InboxState {\n try {\n const raw = JSON.parse(readFileSync(stateFile(root), \"utf-8\"))\n return {\n snoozed: raw && typeof raw.snoozed === \"object\" && raw.snoozed ? raw.snoozed : {},\n ...(typeof raw?.lastDigest === \"string\" ? { lastDigest: raw.lastDigest } : {}),\n ...(raw?.popped && typeof raw.popped === \"object\" ? { popped: raw.popped } : {}),\n }\n } catch {\n return { snoozed: {} }\n }\n}\n\nexport function writeInboxState(root: string, state: InboxState): void {\n mkdirSync(approvalsDir(root), { recursive: true })\n const path = stateFile(root)\n const tmp = `${path}.${process.pid}.tmp`\n writeFileSync(tmp, JSON.stringify(state, null, 2) + \"\\n\")\n renameSync(tmp, path)\n}\n\n/** Put an item off until `until`. Drops snoozes that have run out. */\nexport function snooze(root: string, key: string, until: Date, now: number = Date.now()): void {\n const state = readInboxState(root)\n const snoozed: Record<string, string> = {}\n for (const [k, v] of Object.entries(state.snoozed)) if (Date.parse(v) > now) snoozed[k] = v\n snoozed[key] = until.toISOString()\n writeInboxState(root, { ...state, snoozed })\n}\n\nexport function recordDigest(root: string, localDate: string): void {\n writeInboxState(root, { ...readInboxState(root), lastDigest: localDate })\n}\n\n/** Record that the popup showed `key`. Forgets keys no longer waiting,\n * so the file stays as small as the inbox. */\nexport function recordPopped(root: string, key: string, waiting: string[], now: number = Date.now()): void {\n const state = readInboxState(root)\n const keep = new Set(waiting)\n const popped: Record<string, string> = {}\n for (const [k, v] of Object.entries(state.popped ?? {})) if (keep.has(k)) popped[k] = v\n popped[key] = new Date(now).toISOString()\n writeInboxState(root, { ...state, popped })\n}\n","// --- Inbound auth decision for the daemon's mesh surface ---\n//\n// Pure decision logic, kept out of the daemon class so it can be unit\n// tested. The daemon wraps this with logging and the 401 response.\n//\n// Model: mesh peers have always sent `Authorization: Bearer <token>`\n// (a2a/mesh.ts sendTask), so verifying it server-side is compatible with\n// existing paired meshes. Loopback callers (CLI, same-host dashboard,\n// local tools) are exempt. Installs with no token configured anywhere\n// pass with a warning — that grace path is scheduled for removal.\n\nexport interface MeshAuthRequest {\n /** req.socket.remoteAddress */\n remoteAddress: string\n /** Raw Authorization header value (\"\" when absent). */\n authorizationHeader: string\n /** All tokens this node accepts: MESH_TOKEN, peer tokens, dashboard.token. */\n acceptedTokens: ReadonlySet<string>\n /** AGENTX_MESH_AUTH=off escape hatch. */\n enforcementDisabled?: boolean\n /** A browser says the request comes from another origin's page (see\n * browser-origin.ts). Such a page is not the operator, even though the\n * browser that sends it sits on loopback. */\n foreignBrowser?: boolean\n}\n\nexport type MeshAuthDecision =\n | { allowed: true; reason: \"disabled\" | \"loopback\" | \"token\" | \"no-tokens-configured\" }\n | { allowed: false; reason: \"missing-or-invalid-token\" | \"foreign-browser-origin\" }\n\nconst LOOPBACK = new Set([\"127.0.0.1\", \"::1\", \"::ffff:127.0.0.1\"])\n\n/** Routes gated for every method, reads included. Agent memory is\n * injected into every future session of its agent, so an off-box write\n * is a persistent prompt injection and an off-box read can leak what an\n * agent noted down. Agents reach it with `curl localhost` from their own\n * Bash, which the loopback exemption keeps working. */\nexport function isMeshGatedPath(path: string): boolean {\n return path === \"/api/memory\" || path.startsWith(\"/api/memory/\") ||\n // Approvals list held memory facts and draft wiki articles, and a card\n // written from off-box would ask the operator in an agent's name.\n path === \"/approvals\" || path.startsWith(\"/approvals/\") ||\n // Recent events name agents, chats and errors across the node.\n path === \"/events/recent\" || /^\\/agents\\/[^/]+\\/events$/.test(path) ||\n // Delegations name which agents are working for which others.\n path === \"/a2a/delegations\" || path.startsWith(\"/a2a/delegations/\") ||\n // Traces carry each task's full prompt and final answer.\n path === \"/traces\" || path.startsWith(\"/traces/\") ||\n // Past voice exchanges: what was asked and answered out loud. A\n // replay under it makes this host speak.\n path === \"/voice/history\" || path.startsWith(\"/voice/history/\") ||\n // The phone app's voice: runs speech to text on this host and spends\n // its ElevenLabs quota (voice-io-api.ts).\n path === \"/voice/transcribe\" || path === \"/voice/speak\" ||\n // Files agents declared for the phone app: whatever an agent saved in\n // its workspace (app-files-api.ts).\n path === \"/app-files\" ||\n // Calls agents place to the owner (calls-api.ts): the reasons are\n // agent-written text, and a call makes this host ring and speak.\n path === \"/calls\" || path.startsWith(\"/calls/\") ||\n // An agent watching the phone camera (camera-api.ts): a look runs the\n // agent's turn, and a snapshot writes a picture of the owner's\n // surroundings to disk.\n path === \"/webrtc/camera\" || path.startsWith(\"/webrtc/camera/\")\n}\n\n/** Control POSTs that act as this daemon: reload its config, switch a\n * schedule on or off, stop or steer a running task, kill an agent process,\n * send a message on one of its channels, or announce to the mesh. The CLI, TUI and same-host dashboard reach them over\n * loopback; an off-box caller needs a mesh token. */\nexport function isControlPost(path: string): boolean {\n return path === \"/reload\" || path === \"/api/processes/kill\" ||\n path === \"/send\" || path === \"/send/agent\" || path === \"/send/contact\" ||\n // A note to the whole mesh, published in this node's name.\n path === \"/mesh/announce\" ||\n // AgentX Voice's settings window: rewrites agentx.json, and speaks.\n path === \"/voice/settings\" || path === \"/voice/preview\" ||\n /^\\/api\\/tasks\\/[^/]+\\/(cancel|followup)$/.test(path) ||\n /^\\/crons\\/[^/]+\\/enabled$/.test(path)\n}\n\n/** True when the socket peer is on this host. Used by loopback-only\n * endpoints (e.g. the guard hook) that must never be reachable off-box. */\nexport function isLoopback(remoteAddress: string): boolean {\n return LOOPBACK.has(remoteAddress)\n}\n\nexport function decideMeshAuth(req: MeshAuthRequest): MeshAuthDecision {\n // Checked before every exemption: loopback, the no-token grace path and\n // the off switch all exist for the operator's own tools, not for pages.\n if (req.foreignBrowser) return { allowed: false, reason: \"foreign-browser-origin\" }\n if (req.enforcementDisabled) return { allowed: true, reason: \"disabled\" }\n if (LOOPBACK.has(req.remoteAddress)) return { allowed: true, reason: \"loopback\" }\n if (req.acceptedTokens.size === 0) return { allowed: true, reason: \"no-tokens-configured\" }\n\n const header = req.authorizationHeader || \"\"\n const token = header.startsWith(\"Bearer \") ? header.slice(7).trim() : \"\"\n if (token && req.acceptedTokens.has(token)) return { allowed: true, reason: \"token\" }\n\n return { allowed: false, reason: \"missing-or-invalid-token\" }\n}\n\n/** Config shape this module needs. Structural so callers can pass a full\n * DaemonConfig without this module importing it. */\nexport interface MeshTokenSources {\n mesh?: { peers?: Array<{ token?: string }> }\n dashboard?: { token?: string }\n}\n\n/**\n * Every token accepted on a mesh WRITE path.\n *\n * MESH_TOKEN and per-peer tokens only. `dashboard.token` is deliberately\n * NOT included: it is the most widely handled secret in the config — it\n * sits in dashboard settings and travels alongside peer entries — and\n * accepting it here made it sufficient to POST /task, /channel/send, and\n * the peer-identity forwards that make the daemon act as its own GitLab\n * and GitHub bot.\n *\n * A dashboard on the same host reaches the daemon over loopback, which\n * decideMeshAuth exempts before tokens are consulted. A dashboard on a\n * different host needs a real mesh credential instead.\n */\nexport function collectAcceptedMeshTokens(\n config: MeshTokenSources,\n env: Record<string, string | undefined> = process.env,\n): Set<string> {\n const accepted = new Set<string>()\n if (env.MESH_TOKEN) accepted.add(env.MESH_TOKEN)\n for (const p of config.mesh?.peers || []) if (p.token) accepted.add(p.token)\n return accepted\n}\n\n/**\n * Bearer token the dashboard sends to `target` (a normalized node URL).\n *\n * The primary daemon gets dashboard.token and a configured\n * dashboard.daemons[] entry gets its own token. Without one (a peer the\n * dashboard only learned about through /mesh, or a primary with no\n * dashboard.token) it falls back to MESH_TOKEN, the credential every node\n * in the mesh accepts.\n */\nexport function dashboardTokenForNode(\n dashboard: { daemonUrl: string; token?: string; daemons: Array<{ url: string; token?: string }> },\n target: string,\n env: Record<string, string | undefined> = process.env,\n): string | undefined {\n const norm = (u: string) => u.replace(/\\/+$/, \"\")\n const configured = target === norm(dashboard.daemonUrl)\n ? dashboard.token\n : dashboard.daemons.find((d) => norm(d.url) === target)?.token\n return configured || env.MESH_TOKEN || undefined\n}\n","import type { IncomingHttpHeaders } from \"http\"\n\n// --- Browser-origin guard for the daemon and dashboard HTTP servers ---\n//\n// Both servers trust loopback callers, and a browser on the same machine is\n// a loopback caller. So any web page open in that browser could make it call\n// these servers: send an agent a task, write agent memory, post to a channel.\n// Neither server has a cross-origin browser client (dashboard pages call the\n// dashboard; daemon pages call the daemon; everything else is curl, the CLI,\n// native apps or server-to-server, none of which send Origin), so a request\n// the browser marks as coming from another origin is not trusted.\n//\n// Pure functions, unit tested; the servers add the 403 and the logging.\n\nexport type BrowserOrigin =\n /** No browser origin marker: curl, CLI, native apps, servers. */\n | \"none\"\n /** A page served by this same server. */\n | \"same-origin\"\n /** A page from any other origin, including another port on this host. */\n | \"foreign\"\n\nfunction first(v: string | string[] | undefined): string {\n return (Array.isArray(v) ? v[0] : v)?.trim() ?? \"\"\n}\n\nfunction originHost(origin: string): string | null {\n try {\n return new URL(origin).host.toLowerCase()\n } catch {\n // Opaque origins (\"null\": sandboxed iframes, file://) never match.\n return null\n }\n}\n\n/** Extra trusted origins, for a dashboard served through a reverse proxy\n * under another host name. Comma-separated full origins. */\nexport function allowedOriginsFromEnv(env: NodeJS.ProcessEnv = process.env): Set<string> {\n return new Set(\n (env.AGENTX_ALLOWED_ORIGINS || \"\")\n .split(\",\")\n .map((s) => s.trim().replace(/\\/+$/, \"\").toLowerCase())\n .filter(Boolean),\n )\n}\n\nexport function classifyBrowserRequest(\n headers: IncomingHttpHeaders,\n extraAllowed: ReadonlySet<string> = allowedOriginsFromEnv(),\n): BrowserOrigin {\n const origin = first(headers.origin)\n if (origin) {\n if (extraAllowed.has(origin.replace(/\\/+$/, \"\").toLowerCase())) return \"same-origin\"\n const from = originHost(origin)\n const hosts = [first(headers.host), first(headers[\"x-forwarded-host\"])]\n .filter(Boolean)\n .map((h) => h.toLowerCase())\n return from && hosts.includes(from) ? \"same-origin\" : \"foreign\"\n }\n // Browsers omit Origin on plain GET navigations and image loads, but\n // modern ones still say where the request came from. localhost on\n // another port is \"same-site\", and it is still someone else's page.\n const site = first(headers[\"sec-fetch-site\"]).toLowerCase()\n if (site === \"cross-site\" || site === \"same-site\") return \"foreign\"\n return \"none\"\n}\n\n/** Methods that change state, plus the CORS preflight that precedes a\n * cross-origin write. A foreign page gets none of these. */\nexport function isStateChangingOrPreflight(method: string | undefined): boolean {\n const m = (method || \"GET\").toUpperCase()\n return m !== \"GET\" && m !== \"HEAD\"\n}\n","// --- Configured voices that are not installed, and telling the owner ---\n//\n// macOS can take voices away: it purges the downloaded Siri and Premium\n// voices when the disk runs low. An agent whose voice is gone still\n// speaks, with a stand-in (agent-voice.ts), and only a log line said so.\n// Here the daemon checks every configured voice, tells the owner ONCE per\n// missing voice (at startup, or as soon as a line finds it gone), and says\n// when it is back. The state lives in a small file, so a restart does not\n// repeat the notice, and so the dashboard and `agentx voice list` can show\n// it.\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { dirname, join } from \"path\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport { OS_DEFAULT, label, localSystemVoices, type VoiceSettings } from \"./agent-voice\"\nimport { findVoice, forgetWarnings, type SystemVoice } from \"./system-voices\"\nimport { parseSiriId, SIRI_PREFIX } from \"./siri\"\n\n/** Where to get a voice back, as System Settings words it. */\nexport const REINSTALL_HINT = \"System Settings › Accessibility › Spoken Content › System voice › Manage Voices\"\n\nexport interface MissingVoice {\n /** The name as configured, e.g. \"siri:nora\" or a voice id. */\n voice: string\n /** Who names it: agent ids, or \"voice.system\" for the global default. */\n agents: string[]\n /** What each of those agents speaks with instead. */\n speaksWith: Record<string, string>\n /** When it was first found missing (ISO). */\n since: string\n /** When the owner was told (ISO); absent until a notice went out. */\n notifiedAt?: string\n}\n\nexport interface VoiceHealthState {\n checkedAt: string\n missing: MissingVoice[]\n}\n\nexport const voiceHealthPath = (home = homedir()) => join(home, \".agentx\", \"voice\", \"health.json\")\n\n/** Whether a configured name is installed as itself: a Siri voice counts\n * only when that Siri voice is there, not a system voice of its name. */\nexport function isInstalled(name: string, installed: SystemVoice[], locale: string): boolean {\n const q = name.trim()\n if (!q || q.toLowerCase() === OS_DEFAULT) return true\n if (q.toLowerCase().startsWith(SIRI_PREFIX)) {\n const base = q.slice(SIRI_PREFIX.length).trim().toLowerCase()\n return installed.some((v) => v.siri && v.name.toLowerCase() === base)\n }\n return findVoice(q, installed, locale) !== null\n}\n\ntype Config = Pick<DaemonConfig, \"agents\"> & { voice?: VoiceSettings }\n\n/** Every configured voice name, with who names it and in which language. */\nfunction configuredNames(config: Config): Array<{ owner: string; name: string; locale: string; perLanguage: boolean }> {\n const locale = config.voice?.locale ?? \"en\"\n const out: Array<{ owner: string; name: string; locale: string; perLanguage: boolean }> = []\n const add = (owner: string, choice: string | Record<string, string> | undefined) => {\n if (typeof choice === \"string\") out.push({ owner, name: choice, locale, perLanguage: false })\n else for (const [lang, name] of Object.entries(choice ?? {})) out.push({ owner, name, locale: lang, perLanguage: true })\n }\n add(\"voice.system\", config.voice?.system)\n for (const [id, a] of Object.entries(config.agents)) {\n if ((a.voice?.provider ?? config.voice?.provider ?? \"system\") !== \"system\" && config.voice?.fallback === \"none\") continue\n add(id, a.voice?.system)\n }\n return out\n}\n\n/**\n * The configured voices that are not installed, one entry per voice. An\n * empty `installed` means the list could not be read (or not macOS): no\n * verdict, so nothing is reported missing.\n */\nexport function findMissingVoices(config: Config, installed: SystemVoice[]): Array<Omit<MissingVoice, \"since\" | \"notifiedAt\">> {\n if (!installed.length) return []\n const byVoice = new Map<string, Omit<MissingVoice, \"since\" | \"notifiedAt\">>()\n let cast: Map<string, SystemVoice | null> | undefined\n for (const { owner, name, locale, perLanguage } of configuredNames(config)) {\n if (isInstalled(name, installed, locale)) continue\n cast ??= localSystemVoices(config.agents, config.voice, installed)\n const entry = byVoice.get(name) ?? { voice: name, agents: [], speaksWith: {} }\n if (!entry.agents.includes(owner)) entry.agents.push(owner)\n const now = cast.get(owner)\n // A per-language voice's stand-in is not the agent's default voice.\n if (owner !== \"voice.system\" && !perLanguage) entry.speaksWith[owner] = now ? label(now) : \"the OS default voice\"\n byVoice.set(name, entry)\n }\n return [...byVoice.values()]\n}\n\n/** \"Siri Nora (en-US)\" for a Siri id, \"Siri Nora\" for siri:nora, else as is. */\nexport function voiceDisplayName(name: string): string {\n const siri = parseSiriId(name)\n if (siri) return `Siri ${siri.name[0].toUpperCase()}${siri.name.slice(1)} (${siri.locale})`\n if (name.toLowerCase().startsWith(SIRI_PREFIX)) {\n const base = name.slice(SIRI_PREFIX.length).trim()\n return `Siri ${base[0]?.toUpperCase() ?? \"\"}${base.slice(1)}`\n }\n return name\n}\n\n/** The notice: which voices are gone, who they belong to, what speaks\n * now, and how to get them back. */\nexport function missingNotice(missing: MissingVoice[]): { title: string; message: string } {\n const lines = missing.map((m) => {\n const who = m.agents.map((a) => (m.speaksWith[a] ? `${a} (now ${m.speaksWith[a]})` : a)).join(\", \")\n return `• ${voiceDisplayName(m.voice)}: ${who}`\n })\n return {\n title: missing.length === 1 ? \"AgentX: a voice is not installed\" : `AgentX: ${missing.length} voices are not installed`,\n message: `${lines.join(\"\\n\")}\\nReinstall in ${REINSTALL_HINT}. Agents switch back on their own once it is installed.`,\n }\n}\n\nexport function readVoiceHealth(file = voiceHealthPath()): VoiceHealthState | null {\n if (!existsSync(file)) return null\n try {\n const s = JSON.parse(readFileSync(file, \"utf8\"))\n return s && Array.isArray(s.missing) ? s : null\n } catch {\n return null // a torn or hand-edited file is rebuilt by the next check\n }\n}\n\nfunction writeVoiceHealth(file: string, state: VoiceHealthState): void {\n mkdirSync(dirname(file), { recursive: true })\n writeFileSync(`${file}.tmp`, JSON.stringify(state, null, 2) + \"\\n\")\n renameSync(`${file}.tmp`, file)\n}\n\nexport interface VoiceHealthDeps {\n /** Tell the owner; resolves once sent (or held for Focus). */\n notify: (title: string, message: string) => Promise<void>\n log: (msg: string) => void\n file?: string\n}\n\n/** Checks configured voices, notifies once per missing voice, and logs\n * when one comes back. One check at a time; calls meanwhile are dropped. */\nexport class VoiceHealth {\n private running = false\n private missingNow = false\n\n constructor(private deps: VoiceHealthDeps) {}\n\n /** Whether the last check found a voice missing. */\n hasMissing(): boolean { return this.missingNow }\n\n async check(config: Config, installed: SystemVoice[], now = new Date()): Promise<VoiceHealthState | null> {\n if (this.running || !installed.length) return null\n this.running = true\n try {\n const file = this.deps.file ?? voiceHealthPath()\n const before = new Map((readVoiceHealth(file)?.missing ?? []).map((m) => [m.voice, m]))\n const found = findMissingVoices(config, installed)\n const missing: MissingVoice[] = found.map((m) => ({ ...m, since: before.get(m.voice)?.since ?? now.toISOString(), ...(before.get(m.voice)?.notifiedAt ? { notifiedAt: before.get(m.voice)!.notifiedAt } : {}) }))\n const back = [...before.keys()].filter((v) => !found.some((m) => m.voice === v))\n for (const v of back) this.deps.log(`[voice] ${voiceDisplayName(v)} is installed again; its agents speak with it again`)\n // Keys are \"<owner>:<name>\", and findVoice's Siri stand-in warns as the name itself.\n if (back.length) forgetWarnings((k) => back.some((v) => k === v || k.endsWith(`:${v}`)))\n const untold = missing.filter((m) => !m.notifiedAt)\n if (untold.length) {\n const { title, message } = missingNotice(untold)\n try {\n await this.deps.notify(title, message)\n for (const m of untold) m.notifiedAt = now.toISOString()\n this.deps.log(`[voice] told the owner: ${untold.map((m) => voiceDisplayName(m.voice)).join(\", \")} not installed`)\n } catch (e: any) {\n // Left untold, so the next check tries again.\n this.deps.log(`[voice] could not send the missing-voice notice: ${e?.message ?? e}`)\n }\n }\n this.missingNow = missing.length > 0\n const state = { checkedAt: now.toISOString(), missing }\n // Only on a change: this runs every minute, often on a nearly full disk.\n if (!existsSync(file) || JSON.stringify(missing) !== JSON.stringify([...before.values()])) writeVoiceHealth(file, state)\n return state\n } finally {\n this.running = false\n }\n }\n}\n","// --- The free voices that ship with macOS ---\n//\n// Every Mac has a few dozen voices installed and can download Premium and\n// Enhanced ones for nothing (System Settings → Accessibility → Spoken\n// Content → Manage Voices). They are the default, so voice works with no\n// account and no key; ElevenLabs is an opt-in upgrade.\n//\n// `say -v '?'` lists names but neither quality nor gender, so the list\n// comes from AVSpeechSynthesisVoice through JXA instead. Two traps shape\n// what is offered:\n// - `say -v <unknown>` exits 0 and quietly uses the default voice, so a\n// configured name is always checked against this list first.\n// - The compact Siri voices in that list cannot be used by `say -v`, and\n// the novelty voices (Bells, Zarvox…) are jokes, not voices for an\n// assistant. Both are left out. The neural Siri voices come from their\n// assets instead (./siri.ts) and are named \"siri:<name>\".\n\nimport { execFile, execFileSync } from \"child_process\"\nimport { listSiriVoices, SIRI_PREFIX } from \"./siri\"\n\nexport type Gender = \"female\" | \"male\" | \"neutral\" | null\n\nexport interface SystemVoice {\n /** What `say -v` takes; unambiguous across languages. */\n id: string\n name: string\n /** BCP 47, e.g. \"en-GB\". */\n locale: string\n quality: \"premium\" | \"enhanced\" | \"standard\"\n gender: Gender\n /** A Siri voice: spoken by switching the OS default, only when named. */\n siri?: boolean\n}\n\nconst JXA = `ObjC.import(\"AVFoundation\")\nconst vs = $.AVSpeechSynthesisVoice.speechVoices, out = []\nfor (let i = 0; i < vs.count; i++) {\n const v = vs.objectAtIndex(i)\n out.push([v.identifier.js, v.name.js, v.language.js, Number(v.quality), Number(v.gender)].join(\"\\\\t\"))\n}\nout.join(\"\\\\n\")`\n\n/** The classic voices worth keeping; the rest of that family are novelties. */\nconst CLASSIC = new Set([\"Fred\", \"Kathy\", \"Ralph\", \"Junior\"])\n/** AVSpeech reports no gender for these families. */\nconst GENDER: Record<string, Gender> = {\n Flo: \"female\", Sandy: \"female\", Shelley: \"female\", Grandma: \"female\", Kathy: \"female\",\n Eddy: \"male\", Reed: \"male\", Rocko: \"male\", Grandpa: \"male\", Fred: \"male\", Ralph: \"male\", Junior: \"male\",\n}\n\n/** Parse the JXA output: id, name, language, quality (1–3), gender (0–2). */\nexport function parseVoiceList(out: string): SystemVoice[] {\n const voices: SystemVoice[] = []\n for (const line of out.split(\"\\n\")) {\n const [id, name, locale, q, g] = line.split(\"\\t\")\n if (!id || !name || !locale) continue\n if (id.includes(\".siri_\")) continue\n if (id.startsWith(\"com.apple.speech.synthesis.voice.\") && !CLASSIC.has(name)) continue\n voices.push({\n id, name, locale,\n quality: q === \"3\" ? \"premium\" : q === \"2\" ? \"enhanced\" : \"standard\",\n gender: g === \"1\" ? \"male\" : g === \"2\" ? \"female\" : GENDER[name] ?? null,\n })\n }\n return voices\n}\n\n// --- Installed voices, cached ---\n//\n// Listing takes a few hundred milliseconds, so it runs once and is then\n// refreshed in the background: a Premium voice downloaded while the daemon\n// runs is picked up within REFRESH_MS without stalling a spoken turn.\n\nconst REFRESH_MS = 10 * 60 * 1000\n/** While a configured voice is missing, look again this often, so a\n * reinstalled voice is picked up without a restart. */\nexport const MISSING_REFRESH_MS = 60 * 1000\nlet cached: { at: number; voices: SystemVoice[] } | null = null\nlet refreshing = false\n\nexport function listSystemVoices(now = Date.now(), maxAgeMs = REFRESH_MS): SystemVoice[] {\n if (process.platform !== \"darwin\") return []\n if (!cached) {\n try {\n cached = { at: now, voices: [...parseVoiceList(execFileSync(\"osascript\", [\"-l\", \"JavaScript\", \"-e\", JXA], { encoding: \"utf8\", timeout: 10_000 })), ...listSiriVoices()] }\n } catch (e: any) {\n voiceLog(`[voice] could not list system voices: ${String(e?.message ?? e).split(\"\\n\")[0]}`)\n cached = { at: now, voices: listSiriVoices() }\n }\n } else if (now - cached.at > maxAgeMs && !refreshing) {\n refreshing = true\n execFile(\"osascript\", [\"-l\", \"JavaScript\", \"-e\", JXA], { timeout: 10_000 }, (err, out) => {\n refreshing = false\n if (!err) cached = { at: Date.now(), voices: [...parseVoiceList(String(out)), ...listSiriVoices()] }\n })\n }\n return cached.voices\n}\n\n// --- Logging, once per problem ---\n\nlet voiceLog: (msg: string) => void = (m) => process.stderr.write(m + \"\\n\")\nconst warned = new Set<string>()\n\n/** The daemon routes voice warnings into its own log. */\nexport function setVoiceLog(fn: (msg: string) => void): void { voiceLog = fn }\n\nexport function warnOnce(key: string, msg: string): void {\n if (warned.has(key)) return\n warned.add(key)\n voiceLog(msg)\n}\n\n/** Warn again about these problems the next time they happen: a voice\n * that came back and then went missing again is news. */\nexport function forgetWarnings(match: (key: string) => boolean): void {\n for (const k of warned) if (match(k)) warned.delete(k)\n}\n\n/** Called when a configured voice is found missing while choosing one to\n * speak with, so the daemon can tell the owner (./voice-health.ts). */\nlet onMissing: (name: string) => void = () => {}\nexport function setMissingVoiceHook(fn: (name: string) => void): void { onMissing = fn }\nexport function reportMissing(name: string): void {\n try { onMissing(name) } catch (e: any) { warnOnce(\"missing-hook\", `[voice] missing-voice check failed: ${e?.message ?? e}`) }\n}\n\n// --- Choosing voices ---\n\nconst QUALITY = { premium: 3, enhanced: 2, standard: 1 } as const\n\n/** Character voices: fine for fun, last choice for an assistant. */\nconst CHARACTER = new Set([\"Grandma\", \"Grandpa\"])\n\n/** Better first: quality tier, then modern over the older families. */\nexport function rank(v: SystemVoice): number {\n const family = v.id.startsWith(\"com.apple.voice.\") ? 2\n : v.id.startsWith(\"com.apple.eloquence.\") && !CHARACTER.has(v.name) ? 1 : 0\n return QUALITY[v.quality] * 10 + family\n}\n\nconst language = (locale: string) => locale.toLowerCase().split(/[-_]/)[0]\n\n/** The current macOS voices (com.apple.voice.*), as opposed to Eloquence,\n * the classic voices and the novelties. */\nconst modern = (v: SystemVoice) => v.id.startsWith(\"com.apple.voice.\")\n\n/**\n * What stands in for a configured voice that is not installed: in its\n * language, of its gender when known, of the best quality there is\n * (premium, then enhanced, then standard). Eloquence and the classic\n * voices only when no current macOS voice speaks the language. Among the\n * best, a voice no one in `taken` uses yet. Null when nothing installed\n * speaks the language.\n */\nexport function fallbackVoice(installed: SystemVoice[], locale: string, gender: Gender | undefined, taken: Set<string> = new Set()): SystemVoice | null {\n let pool = candidates(installed.filter((v) => !v.siri && language(v.locale) === language(locale)), locale)\n if (pool.some(modern)) pool = pool.filter(modern)\n if (gender === \"female\" || gender === \"male\") {\n const same = pool.filter((v) => v.gender === gender)\n if (same.length) pool = same\n }\n if (!pool.length) return null\n const best = pool.filter((v) => v.quality === pool[0].quality)\n const used = new Set([...taken].map((n) => n.toLowerCase()))\n return best.find((v) => !used.has(v.name.toLowerCase())) ?? best[0]\n}\n\n/** Voices in the configured language (e.g. \"en\", \"fr-FR\"), best first; all\n * of them if none match, so a mislabelled locale still speaks. */\nexport function candidates(installed: SystemVoice[], locale = \"en\"): SystemVoice[] {\n const exact = locale.toLowerCase().replace(\"_\", \"-\")\n const lang = language(locale)\n const pool = installed.filter((v) => language(v.locale) === lang)\n return (pool.length ? pool : installed).slice().sort((a, b) =>\n rank(b) - rank(a)\n || Number(b.locale.toLowerCase() === exact) - Number(a.locale.toLowerCase() === exact)\n || a.name.localeCompare(b.name) || a.locale.localeCompare(b.locale))\n}\n\n/**\n * The installed voice a configured name means, or null.\n *\n * Accepts an identifier, a plain name (\"Daniel\"), a tier suffix as System\n * Settings shows it (\"Ava (Premium)\"), or `say`'s language suffix\n * (\"Eddy (English (US))\"). A name several locales share resolves to the\n * best one in the configured language. A Siri voice is named \"siri:aaron\";\n * when its asset is missing, the system voice of that name stands in.\n */\nexport function findVoice(name: string, installed: SystemVoice[], locale = \"en\"): SystemVoice | null {\n const q = name.trim()\n if (!q) return null\n const byId = installed.find((v) => v.id === q)\n if (byId) return byId\n if (q.toLowerCase().startsWith(SIRI_PREFIX)) {\n const base = q.slice(SIRI_PREFIX.length).trim().toLowerCase()\n const siri = candidates(installed.filter((v) => v.siri && v.name.toLowerCase() === base), locale)[0]\n if (siri || !base) return siri ?? null\n // Only a stand-in in the same language: \"siri:nora\" must not become\n // the Norwegian Nora.\n const standIn = findVoice(base, installed.filter((v) => language(v.locale) === language(locale)), locale)\n if (standIn) warnOnce(`siri:${base}`, `[voice] Siri voice \"${base}\" is not installed; speaking with ${standIn.name} (${standIn.locale}) instead.`)\n return standIn\n }\n installed = installed.filter((v) => !v.siri)\n const tier = /^(.+?)\\s*\\((premium|enhanced)\\)$/i.exec(q)\n const base = (tier ? tier[1] : q.replace(/\\s*\\(.*\\)\\s*$/, \"\")).trim().toLowerCase()\n const named = candidates(installed.filter((v) => v.name.toLowerCase() === base), locale)\n const wanted = tier ? named.find((v) => v.quality === tier[2].toLowerCase()) : undefined\n return wanted ?? named[0] ?? null\n}\n\nexport interface CastEntry {\n id: string\n gender?: Gender\n}\n\n/**\n * A distinct voice for each entry, in order: the best remaining voice of\n * the entry's gender, or alternating female and male when it has none, so\n * two agents in a row never sound alike. Distinct means a different NAME:\n * Eddy in en-GB and Eddy in en-US are the same speaker. Names in `taken`\n * (pinned by other agents) are used only once every other voice is; with\n * more agents than voices, voices repeat. A known gender wins over being\n * distinct: once that gender's voices are all used, one is repeated\n * rather than giving the agent a voice of the other gender.\n */\nexport function castVoices(entries: CastEntry[], pool: SystemVoice[], taken: Set<string> = new Set()): Map<string, SystemVoice> {\n const cast = new Map<string, SystemVoice>()\n if (!pool.length) return cast\n const used = new Set([...taken].map((n) => n.toLowerCase()))\n let turn: \"female\" | \"male\" = \"female\"\n for (const e of entries) {\n const known = e.gender === \"female\" || e.gender === \"male\"\n const want = known ? e.gender : turn\n const free = pool.filter((v) => !used.has(v.name.toLowerCase()))\n const pick = free.find((v) => v.gender === want)\n ?? (known ? pool.find((v) => v.gender === want) : undefined)\n ?? free[0] ?? pool[cast.size % pool.length]\n cast.set(e.id, pick)\n used.add(pick.name.toLowerCase())\n if (!known) turn = turn === \"female\" ? \"male\" : \"female\"\n }\n return cast\n}\n","// --- Siri voices, spoken by switching the OS default voice ---\n//\n// `say -v` cannot use the Siri neural voices, but `say` with no -v reads\n// the Spoken Content default (com.apple.Accessibility\n// SpokenContentDefaultVoiceSelectionsByLanguage) afresh on every run. So a\n// Siri line is: take the lock, save the user's selection, write the Siri\n// voice under the system language (the only key `say` reads, whatever\n// language the text is in), run `say`, put the saved selection back.\n//\n// The pref is global, so every `say` that follows it goes through the one\n// script below, from the daemon and from the Mac voice app alike: they\n// share the lock and never race. A run killed mid-line leaves its saved\n// selection behind; the next run restores it before anything else.\n\nimport { execFileSync } from \"child_process\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { join } from \"path\"\nimport type { SystemVoice } from \"./system-voices\"\n\n/** How a config names a Siri voice: \"siri:aaron\". */\nexport const SIRI_PREFIX = \"siri:\"\nconst ID_PREFIX = \"com.apple.ttsbundle.gryphon-neural_\"\n\nexport const isSiriVoice = (id: string | null | undefined): id is string => !!id && id.startsWith(ID_PREFIX)\n\n// --- Can this host switch voices at all? ---\n//\n// Only on macOS, and only when the pieces are really there, not because\n// of a version number: `say`, `defaults` and `plutil`, and the Spoken\n// Content pref domain. Anywhere else (Linux, an older macOS) Siri voices\n// are simply not offered, a configured siri:<name> falls back like any\n// voice that is not installed, and no pref is ever written.\n\nexport interface SiriHost {\n platform?: NodeJS.Platform\n exists?: (path: string) => boolean\n /** True when the com.apple.Accessibility pref domain can be read. */\n prefDomain?: () => boolean\n}\n\nconst TOOLS = [\"/usr/bin/say\", \"/usr/bin/defaults\", \"/usr/bin/plutil\"]\n\nconst readPrefDomain = () => {\n try {\n execFileSync(\"/usr/bin/defaults\", [\"read\", \"com.apple.Accessibility\"], { stdio: \"ignore\", timeout: 5_000 })\n return true\n } catch {\n return false\n }\n}\n\nlet supported: boolean | undefined\n\n/** Whether Siri voices can be spoken here; checked once per process. */\nexport function siriSupported(host: SiriHost = {}): boolean {\n const probe = host.platform !== undefined || host.exists !== undefined || host.prefDomain !== undefined\n if (!probe && supported !== undefined) return supported\n const { platform = process.platform, exists = existsSync, prefDomain = readPrefDomain } = host\n const ok = platform === \"darwin\" && TOOLS.every((t) => exists(t)) && prefDomain()\n if (!probe) supported = ok\n return ok\n}\n\n/** Where MobileAsset keeps the downloaded Siri voices. */\nconst ASSET_DIRS = [\n \"/System/Library/AssetsV2/com_apple_MobileAsset_Trial_Siri_SiriTextToSpeech/purpose_auto\",\n \"/System/Library/AssetsV2/com_apple_MobileAsset_UAF_Siri_TextToSpeech/purpose_auto\",\n]\n\n/** The assets name no gender; these are the voices System Settings offers. */\nconst GENDER: Record<string, SystemVoice[\"gender\"]> = {\n aaron: \"male\", damon: \"male\", daniel: \"male\", pierre: \"male\", samer: \"male\", arthur: \"male\", gordon: \"male\", martin: \"male\",\n nora: \"female\", simone: \"female\", marie: \"female\", soha: \"female\", martha: \"female\", catherine: \"female\", helena: \"female\", nicky: \"female\",\n}\n\n/** A Siri voice's gender, known even when its asset is gone. */\nexport const siriGender = (name: string): SystemVoice[\"gender\"] => GENDER[name.toLowerCase()] ?? null\n\n/** Name and locale from a Siri voice id, e.g. \"nora\" and \"en-US\". */\nexport function parseSiriId(id: string): { name: string; locale: string } | null {\n const m = /^com\\.apple\\.ttsbundle\\.gryphon-neural_([a-z]+)_([a-z]{2}-[A-Z]{2})_premium$/.exec(id)\n return m ? { name: m[1], locale: m[2] } : null\n}\n\n/** A voice asset's specifier, e.g. \"com.apple.siri.tts.voice.en_US.aaron.neural.premium-en_US-iPhone\".\n * Codenames (\"fr-FR-D\") are not voices System Settings offers, so they are skipped. */\nconst SPECIFIER = /com\\.apple\\.siri\\.tts\\.voice\\.([a-z]{2})_([A-Z]{2})\\.([a-z]+)\\.neural\\.premium/g\n\n/** The Siri voices named in asset Info.plists, one per name and locale. */\nexport function parseSiriAssets(plists: string[]): SystemVoice[] {\n const seen = new Map<string, SystemVoice>()\n for (const text of plists) {\n for (const [, lang, region, name] of text.matchAll(SPECIFIER)) {\n const locale = `${lang}-${region}`\n const id = `${ID_PREFIX}${name}_${locale}_premium`\n if (seen.has(id)) continue\n seen.set(id, {\n id, name: name[0].toUpperCase() + name.slice(1), locale,\n quality: \"premium\", gender: GENDER[name] ?? null, siri: true,\n })\n }\n }\n return [...seen.values()]\n}\n\n/** Installed Siri voices. Binary plists keep strings as plain ASCII, so the\n * specifier is found without converting each file. */\nexport function listSiriVoices(dirs = ASSET_DIRS, host: SiriHost = {}): SystemVoice[] {\n if (!siriSupported(host)) return []\n const plists: string[] = []\n for (const dir of dirs) {\n let assets: string[]\n try { assets = readdirSync(dir) } catch { continue }\n for (const a of assets) {\n try { plists.push(readFileSync(join(dir, a, \"Info.plist\"), \"latin1\")) } catch { /* not an asset */ }\n }\n }\n return parseSiriAssets(plists)\n}\n\n// --- Speaking ---\n\n/** Text on stdin; $1 is a voice id: a Siri id switches the OS default for\n * the line, any other id goes to `say -v`, none speaks the OS default as\n * it stands. Every line holds one lock, so none talk over each other.\n * The text is read before the lock, a watchdog bounds each line, lines\n * queued past AGENTX_SAY_STALE_S (30 s) are dropped, and `--stop`\n * silences the line speaking and drops every queued one. */\nexport const SIRI_SAY = `#!/bin/sh\n# Written by agentx; shared by the daemon and AgentX Voice. Do not edit.\n# siri-say.sh [voice] < text speak one line. A Siri voice id switches\n# the OS default for it; any other id is\n# passed to say -v.\n# siri-say.sh --stop silence the line speaking, drop the queue.\n# siri-say.sh --restore put back a voice a killed line left switched.\n# AGENTX_SAY_RATE=<words a minute> sets the speed; unset, say's own.\nD=com.apple.Accessibility K=SpokenContentDefaultVoiceSelectionsByLanguage\ndir=\"$HOME/.agentx/voice\" lock=\"$HOME/.agentx/voice/siri.lock\" saved=\"$HOME/.agentx/voice/siri-saved.plist\"\nstops=\"$dir/stop\"\nread_s=\\${AGENTX_SAY_READ_S:-5} stale_s=\\${AGENTX_SAY_STALE_S:-30} max_s=\\${AGENTX_SAY_MAX_S:-300}\nassets=\\${AGENTX_SIRI_ASSET_DIRS:-\"${ASSET_DIRS.join(\" \")}\"}\nif [ \"$1\" = --stop ]; then\n mkdir -p \"$dir\" || exit 1\n # Queued lines see this change and drop. The owner's trap stops say,\n # restores the voice, and frees the lock.\n echo \"$$ $(date +%s)\" > \"$stops\"\n owner=$(cat \"$lock/pid\" 2>/dev/null)\n [ -n \"$owner\" ] && kill \"$owner\" 2>/dev/null\n exit 0\nfi\nmode=speak\n[ \"$1\" = --restore ] && { mode=restore; set --; }\ncase \"$1\" in com.apple.ttsbundle.gryphon-neural_*) siri=$1 voice= ;; *) siri= voice=$1 ;; esac\nrate=\\${AGENTX_SAY_RATE:-}\ncase \"$rate\" in *[!0-9]*) rate= ;; esac\ncase \"$siri\" in *[!A-Za-z0-9._-]*) echo \"bad voice id\" >&2; exit 2;; esac\n# Not macOS, or no pref tools: speak as is and never touch a pref.\nif [ \"$(uname)\" != Darwin ] || ! command -v defaults >/dev/null || ! command -v plutil >/dev/null; then\n [ \"$mode\" = restore ] && exit 0\n if [ -n \"$voice\" ]; then exec say \\${rate:+-r \"$rate\"} -v \"$voice\"; else exec say \\${rate:+-r \"$rate\"}; fi\nfi\nmkdir -p \"$dir\" || exit 1\nmine=$(cat \"$stops\" 2>/dev/null)\nstopped() { [ \"$(cat \"$stops\" 2>/dev/null)\" != \"$mine\" ]; }\ntxt=$(mktemp \"\\${TMPDIR:-/tmp}/agentx-say.XXXXXX\") || exit 1\nsay_pid= dog=\nrestore() {\n [ -f \"$saved\" ] || return 0\n if [ -s \"$saved\" ]; then defaults write $D $K \"$(cat \"$saved\")\"; else defaults delete $D $K 2>/dev/null; fi\n rm -f \"$saved\"\n}\nfinish() {\n [ -n \"$say_pid\" ] && kill \"$say_pid\" 2>/dev/null\n [ -n \"$dog\" ] && { pkill -P \"$dog\" 2>/dev/null; kill \"$dog\" 2>/dev/null; }\n restore; rm -rf \"$lock\"; rm -f \"$txt\"; exit \"$1\"\n}\ndrop() { rm -f \"$txt\"; exit 75; }\n# A Siri voice whose asset macOS purged: switching to it makes the OS\n# speak a fallback of its own (Eloquence Flo), so leave the pref alone.\nif [ -n \"$siri\" ]; then\n s=\\${siri#com.apple.ttsbundle.gryphon-neural_} ; name=\\${s%%_*} ; s=\\${s#*_} ; loc=$(printf %s \"\\${s%%_*}\" | tr - _)\n found=\n for a in $assets; do\n grep -aqs \"com.apple.siri.tts.voice.$loc.$name.neural.premium\" \"$a\"/*/Info.plist && { found=1; break; }\n done\n [ -z \"$found\" ] && { echo \"Siri voice $siri is not installed; speaking with the OS default\" >&2; siri=; }\nfi\n# The whole text first, before the lock: a writer that never closes stdin\n# must not hold up every other speaker.\n[ \"$mode\" = restore ] && exec 0</dev/null\nexec 3<&0 0</dev/null\ncat <&3 > \"$txt\" & cat_pid=$!\nexec 3<&-\nn=0\nwhile kill -0 \"$cat_pid\" 2>/dev/null; do\n if [ $n -ge $((read_s * 10)) ]; then\n kill \"$cat_pid\" 2>/dev/null; echo \"stdin never closed; nothing said\" >&2; rm -f \"$txt\"; exit 3\n fi\n sleep 0.1; n=$((n + 1))\ndone\n# One speaker at a time. A lock whose owner died is taken over. A line\n# that waits past stale_s, or that a stop overtook, is dropped unsaid.\nn=0\nwhile ! mkdir \"$lock\" 2>/dev/null; do\n stopped && drop\n if [ $n -ge $((stale_s * 10)) ]; then echo \"waited \\${stale_s}s for the speaker; dropped\" >&2; drop; fi\n owner=$(cat \"$lock/pid\" 2>/dev/null)\n if [ -n \"$owner\" ] && ! kill -0 \"$owner\" 2>/dev/null; then rm -rf \"$lock\"\n elif [ -z \"$owner\" ] && [ -n \"$(find \"$lock\" -maxdepth 0 -mmin +1 2>/dev/null)\" ]; then rm -rf \"$lock\"\n else sleep 0.1; n=$((n + 1)); fi\ndone\necho $$ > \"$lock/pid\"\ntrap 'finish 143' TERM INT HUP\nstopped && finish 75\n[ \"$mode\" = restore ] && finish 0\nrestore\nif [ -n \"$siri\" ]; then\n lang=$(defaults read -g AppleLanguages 2>/dev/null | sed -n '2s/^[^A-Za-z]*\\\\([A-Za-z]*\\\\).*/\\\\1/p')\n defaults export $D - 2>/dev/null | plutil -extract $K xml1 -o \"$saved.tmp\" - 2>/dev/null || : > \"$saved.tmp\"\n mv \"$saved.tmp\" \"$saved\"\n defaults write $D $K -array \"\\${lang:-en}\" \"{ _type = \\\\\"Speech.VoiceSelection\\\\\"; _version = 0; voiceId = \\\\\"$siri\\\\\"; }\"\nfi\n# A watchdog: no line runs past its length's worth of speech (a hung say\n# held the lock for minutes), capped at max_s.\nlimit=$((5 + $(wc -w < \"$txt\") * 6 / 10))\n[ $limit -gt $max_s ] && limit=$max_s\nif [ -n \"$voice\" ]; then say \\${rate:+-r \"$rate\"} -v \"$voice\" -f \"$txt\" & else say \\${rate:+-r \"$rate\"} -f \"$txt\" & fi\nsay_pid=$!\n( sleep \"$limit\"; echo \"say ran past \\${limit}s; stopped\" >&2; kill \"$say_pid\" 2>/dev/null ) & dog=$!\nwait \"$say_pid\"\nstatus=$?\nsay_pid=\nfinish $status\n`\n\n/** Where the script lives; AgentX Voice runs the same file. */\nexport const siriSayPath = (home = homedir()) => join(home, \".agentx\", \"voice\", \"siri-say.sh\")\n\n/** Write the script if it is missing or out of date; returns its path. */\nexport function ensureSiriSay(home = homedir()): string {\n const path = siriSayPath(home)\n if (existsSync(path) && readFileSync(path, \"utf8\") === SIRI_SAY) return path\n mkdirSync(join(home, \".agentx\", \"voice\"), { recursive: true })\n writeFileSync(`${path}.tmp`, SIRI_SAY, { mode: 0o755 })\n renameSync(`${path}.tmp`, path)\n return path\n}\n","// --- Which language a spoken line is in: English, French or Arabic ---\n//\n// An agent may hold one system voice per language, so a French answer is\n// read by a French voice. Replies are short and the choice is between\n// three very different languages, so a script check and a handful of\n// common words are enough; a model would be slower and no more useful.\n\nexport type Language = \"en\" | \"fr\" | \"ar\"\n\nconst EN = new Set(\"the and is are was were i you he she we they it this that to of in for with not but yes what have has will would can your my\".split(\" \"))\nconst FR = new Set(\"le la les des du de un une et est sont je tu il elle nous vous ils elles ce cette c'est pas pour que qui avec dans sur au aux mais très oui non merci bonjour votre mon\".split(\" \"))\nconst ACCENT = /[éèêëàâçùûîïôœ]/\n\n/** The line's language, or null when nothing in it says which. */\nexport function detectLanguage(text: string): Language | null {\n const arabic = (text.match(/[-ۿ]/g) ?? []).length\n const latin = (text.match(/[a-zA-ZÀ-ÿ]/g) ?? []).length\n if (arabic && arabic >= latin) return \"ar\"\n let en = 0, fr = 0\n for (const w of text.toLowerCase().replace(/[’]/g, \"'\").match(/[a-zà-ÿœ']+/g) ?? []) {\n if (EN.has(w)) en++\n if (FR.has(w) || w.startsWith(\"l'\") || w.startsWith(\"d'\") || w.startsWith(\"qu'\")) fr++\n else if (ACCENT.test(w)) fr += 0.5\n }\n if (en === fr) return null\n return en > fr ? \"en\" : \"fr\"\n}\n","// --- Per-agent voices, and when an agent should introduce itself ---\n//\n// One global voice made every agent sound like the same person, so a\n// voice conversation could not tell you who was answering. Each agent may\n// carry a `voice` block in agentx.json; anything it leaves out falls back\n// to the global `voice` settings. Out of the box every agent speaks with\n// a free macOS voice of its own; ElevenLabs is opt-in (voice.provider).\n\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport type { TalkSpeaker } from \"./talk\"\nimport type { VoiceRef } from \"./speaker\"\nimport { castVoices, candidates, fallbackVoice, findVoice, listSystemVoices, reportMissing, warnOnce, type CastEntry, type SystemVoice } from \"./system-voices\"\nimport { parseSiriId, siriGender, SIRI_PREFIX } from \"./siri\"\nimport { detectLanguage } from \"./language\"\n\ntype AgentConfig = DaemonConfig[\"agents\"][string]\ntype Agents = DaemonConfig[\"agents\"]\nexport type VoiceSettings = Partial<DaemonConfig[\"voice\"]>\nexport type VoiceProvider = DaemonConfig[\"voice\"][\"provider\"]\n/** A configured system voice: one name, or one per language. */\nexport type SystemChoice = NonNullable<DaemonConfig[\"voice\"][\"system\"]>\n\n/** The name that means the OS default voice: `say` with no -v follows\n * System Settings → Spoken Content, the only way apps get a Siri voice. */\nexport const OS_DEFAULT = \"system\"\nconst isOsDefault = (name: string) => name.trim().toLowerCase() === OS_DEFAULT\n\n/** ElevenLabs \"Rachel\" — the voice every agent used before this existed. */\nexport const DEFAULT_VOICE_ID = \"21m00Tcm4TlvDq8ikWAM\"\n\nexport interface AgentVoice {\n agentId: string\n name: string\n /** Which engine speaks: the free system voices, or ElevenLabs. */\n provider: VoiceProvider\n /** Null when the agent has none: the client applies its own default. */\n elevenlabsVoiceId: string | null\n /** The macOS voice identifier for `say -v`; null for the OS default. */\n systemVoice: string | null\n /** Its readable name, e.g. \"Daniel (en-GB)\". */\n systemVoiceName: string | null\n /** A voice per language (\"fr\", \"ar\"; null for the OS default), used for\n * lines in that language. Empty when one voice speaks every language. */\n systemByLanguage: Record<string, SystemVoice | null>\n /** When ElevenLabs cannot speak, use the system voice instead. */\n fallback: boolean\n gender: \"female\" | \"male\" | \"neutral\" | null\n style: string | null\n intro: string\n /** Speaking speed, 1 = normal; absent when unset. */\n rate?: number\n /** Place in the speaking queue; absent when unset (normal). */\n priority?: \"high\" | \"normal\" | \"low\"\n}\n\n/**\n * How an agent sounds. The provider is the agent's, else the global one\n * (system by default). The system voice is the agent's, else the global\n * default, else one assigned to it alone; a name that is not installed\n * is skipped with one warning.\n */\nexport function resolveAgentVoice(\n agentId: string,\n agents: Agents,\n settings: VoiceSettings = {},\n installed: SystemVoice[] = listSystemVoices(),\n): AgentVoice {\n const agent: Partial<AgentConfig> | undefined = agents[agentId]\n const name = agent?.name || agentId\n const v = agent?.voice\n const sys = localSystemVoices(agents, settings, installed).get(agentId) ?? null\n const systemByLanguage = languageVoices(agentId, v?.system, v?.gender, settings, installed)\n return {\n agentId,\n name,\n provider: v?.provider ?? settings.provider ?? \"system\",\n elevenlabsVoiceId: v?.elevenlabsVoiceId || null,\n systemVoice: sys?.id ?? null,\n systemVoiceName: sys ? label(sys) : null,\n systemByLanguage,\n fallback: (settings.fallback ?? \"system\") === \"system\",\n gender: v?.gender ?? null,\n style: v?.style || null,\n intro: v?.intro || deriveIntro(name, agent?.systemPrompt),\n ...(v?.rate ? { rate: v.rate } : {}),\n ...(v?.priority ? { priority: v.priority } : {}),\n }\n}\n\nexport const label = (v: SystemVoice) =>\n `${v.name}${v.siri ? \" (Siri)\" : v.quality === \"standard\" ? \"\" : ` (${v.quality[0].toUpperCase()}${v.quality.slice(1)})`} ${v.locale}`\n\n/** A configured name, checked: the installed voice, or null and a warning. */\nexport function checkedVoice(owner: string, name: string | undefined, installed: SystemVoice[], locale: string): SystemVoice | null {\n if (!name || !installed.length) return null\n const v = findVoice(name, installed, locale)\n if (!v) {\n warnOnce(`${owner}:${name}`, `[voice] ${owner}: system voice \"${name}\" is not installed; using the next choice. Run \\`agentx voice list\\` to see installed voices.`)\n reportMissing(name)\n }\n return v\n}\n\n/** What a configured name tells about the voice it meant, even when that\n * voice is gone: its language and gender. */\nexport function voiceTraits(name: string, locale: string): { locale: string; gender: SystemVoice[\"gender\"] } {\n const q = name.trim()\n if (q.toLowerCase().startsWith(SIRI_PREFIX)) return { locale, gender: siriGender(q.slice(SIRI_PREFIX.length).trim()) }\n const siri = parseSiriId(q)\n if (siri) return { locale: siri.locale, gender: siriGender(siri.name) }\n const system = /^com\\.apple\\.voice\\.[a-z]+\\.([a-z]{2}-[A-Z]{2})\\./.exec(q)\n return { locale: system?.[1] ?? locale, gender: null }\n}\n\n/** Someone to cast: their configured voice and gender, if any, and what\n * to speak with when that voice is not installed. */\nexport interface VoiceWish { id: string; system?: SystemChoice; gender?: SystemVoice[\"gender\"]; fallbacks?: string[] }\n\nconst language = (locale: string) => locale.toLowerCase().split(/[-_]/)[0]\n\n/** The configured name for one language: a plain name serves them all. */\nconst nameFor = (choice: SystemChoice | undefined, lang: string) =>\n typeof choice === \"string\" ? choice : choice?.[language(lang)]\n\n/** A configured name: the installed voice, null for the OS default, or\n * undefined when unset or not installed (with a warning). */\nfunction configured(owner: string, name: string | undefined, installed: SystemVoice[], locale: string): SystemVoice | null | undefined {\n if (name === undefined) return undefined\n if (isOsDefault(name)) return null\n return checkedVoice(owner, name, installed, locale) ?? undefined\n}\n\n/** The global default suits an agent unless both genders are known and differ. */\nconst fitsGender = (v: SystemVoice | null, gender: SystemVoice[\"gender\"] | undefined) =>\n !v || !v.gender || v.gender === \"neutral\" || !gender || gender === \"neutral\" || v.gender === gender\n\n/**\n * A system voice for each wish, in order: its own if installed, else the\n * global default if it is of the wish's gender, else a distinct one of its\n * own. Null means the OS default voice. Names in `taken` are already\n * spoken for (e.g. by local agents, when casting mesh agents).\n */\nexport function castSystemVoices(wishes: VoiceWish[], settings: VoiceSettings, installed: SystemVoice[], taken: Set<string> = new Set()): Map<string, SystemVoice | null> {\n const locale = settings.locale ?? \"en\"\n const out = new Map<string, SystemVoice | null>()\n const open: CastEntry[] = []\n const fallback = configured(\"voice.system\", nameFor(settings.system, locale), installed, locale)\n const inUse = () => new Set([...taken, ...[...out.values()].flatMap((v) => (v ? [v.name] : []))])\n for (const w of wishes) {\n const name = nameFor(w.system, locale)\n const own = configured(w.id, name, installed, locale)\n const pick = own !== undefined ? own\n : name !== undefined && installed.length ? standIn(w, name, locale, fallback, installed, inUse())\n : fallback !== undefined && fitsGender(fallback, w.gender) ? fallback : undefined\n if (pick !== undefined) out.set(w.id, pick)\n else open.push({ id: w.id, gender: w.gender ?? null })\n }\n const names = inUse()\n // Siri voices switch a pref the user owns, so only an agent that names one gets one.\n for (const [id, v] of castVoices(open, candidates(installed.filter((v) => !v.siri), locale), names)) out.set(id, v)\n return out\n}\n\n/**\n * What speaks for an agent whose configured voice is not installed: the\n * first installed name in its `fallbacks`, else the global default when\n * it suits the agent's gender, else the best installed voice in the\n * missing voice's language and of its gender (fallbackVoice).\n */\nfunction standIn(w: VoiceWish, name: string, locale: string, fallback: SystemVoice | null | undefined, installed: SystemVoice[], taken: Set<string>): SystemVoice | null | undefined {\n for (const f of w.fallbacks ?? []) {\n const v = configured(`${w.id}.fallbacks`, f, installed, locale)\n if (v !== undefined) return v\n }\n if (fallback !== undefined && fitsGender(fallback, w.gender)) return fallback\n const traits = voiceTraits(name, locale)\n return fallbackVoice(installed, traits.locale, w.gender ?? traits.gender, taken) ?? undefined\n}\n\n/**\n * The voice per language for lines not in the default one: the agent's\n * own list over the global one. A plain name on the agent speaks every\n * language, so there is nothing to switch to.\n */\nexport function languageVoices(\n owner: string, own: SystemChoice | undefined, gender: SystemVoice[\"gender\"] | undefined,\n settings: VoiceSettings, installed: SystemVoice[],\n): Record<string, SystemVoice | null> {\n if (typeof own === \"string\") return {}\n const out: Record<string, SystemVoice | null> = {}\n const add = (who: string, list: SystemChoice | undefined, suits: (v: SystemVoice | null) => boolean) => {\n if (!list || typeof list === \"string\") return\n for (const [lang, name] of Object.entries(list)) {\n let v = configured(`${who}.${lang}`, name, installed, lang)\n // Missing: the best installed voice in that language stands in.\n if (v === undefined && installed.length) v = fallbackVoice(installed, lang, gender ?? voiceTraits(name, lang).gender) ?? undefined\n if (v !== undefined && suits(v)) out[language(lang)] = v\n }\n }\n add(\"voice.system\", settings.system, (v) => fitsGender(v, gender))\n add(owner, own, () => true)\n return out\n}\n\n/** The voice for one line: the voice of its language when the agent has\n * one, else its default voice. */\nexport function voiceForText(v: AgentVoice, text?: string | null): AgentVoice {\n const lang = text ? detectLanguage(text) : null\n if (!lang || !(lang in v.systemByLanguage)) return v\n const sys = v.systemByLanguage[lang]\n return { ...v, systemVoice: sys?.id ?? null, systemVoiceName: sys ? label(sys) : null }\n}\n\n/** Every local agent's system voice, assigned in config order, so adding\n * an agent at the end never changes anyone else's voice. */\nexport function localSystemVoices(agents: Agents, settings: VoiceSettings = {}, installed: SystemVoice[] = listSystemVoices()): Map<string, SystemVoice | null> {\n const wishes = Object.entries(agents).map(([id, a]) => ({ id, system: a.voice?.system, gender: a.voice?.gender, fallbacks: a.voice?.fallbacks }))\n return castSystemVoices(wishes, settings, installed)\n}\n\n/** What the speech engine needs; `explicit` is a one-off ElevenLabs id. */\nexport function voiceRef(v: AgentVoice, explicit?: string | null): VoiceRef {\n const ref: VoiceRef = { provider: v.provider, elevenlabs: pickVoiceId(explicit, v.elevenlabsVoiceId), system: v.systemVoice, fallback: v.fallback }\n const langs = Object.entries(v.systemByLanguage ?? {})\n if (langs.length) ref.languages = Object.fromEntries(langs.map(([l, sys]) => [l, sys?.id ?? null]))\n if (v.rate && v.rate !== 1) ref.rate = v.rate\n if (v.priority && v.priority !== \"normal\") ref.priority = v.priority\n return ref\n}\n\n/** The voice id to synthesise with: explicit, then agent, then env, then Rachel. */\nexport function pickVoiceId(explicit?: string | null, agentVoiceId?: string | null): string {\n return explicit || agentVoiceId || process.env.AGENTX_VOICE_ID || DEFAULT_VOICE_ID\n}\n\n/**\n * \"Hello, this is Nadia, the marketing agent for Acme.\" from a system\n * prompt that opens \"You are Nadia, the marketing agent for Acme (…).\".\n *\n * Only the first sentence is used, because that is where every persona\n * says who it is; the rest is instructions, which make terrible speech.\n */\nexport function deriveIntro(name: string, systemPrompt?: string): string {\n const first = (systemPrompt ?? \"\").split(/(?<=\\.)\\s/)[0] ?? \"\"\n const m = /^You are (.+?)\\.?$/i.exec(first.trim())\n if (!m) return `Hello, this is ${name}.`\n // Parentheticals (\"(acme.com)\") read badly aloud.\n const who = m[1].replace(/\\s*\\([^)]*\\)/g, \"\").trim()\n const [lead, ...rest] = who.split(/,\\s*/)\n const role = rest.join(\", \")\n // \"You are Nadia, the marketing agent\" — lead is the name.\n if (role && lead.toLowerCase() === name.toLowerCase()) return `Hello, this is ${name}, ${role}.`\n // \"You are the Accountant agent for Acme\" — the name is already in it.\n // Not for \"a taxonomy/graph agent\": a generic kind still needs the name.\n if (!/^an? /i.test(who) && who.toLowerCase().includes(name.toLowerCase())) return `Hello, this is ${who}.`\n return `Hello, this is ${name}, ${who}.`\n}\n\n// --- Introductions ---\n\n/** Silence longer than this makes the next reply a fresh introduction. */\nexport const INTRO_GAP_MS = 8 * 60 * 60 * 1000\nconst MAX_TRACKED = 500\n\n/**\n * Remembers who has already spoken in which voice session.\n *\n * In memory on purpose: a daemon restart re-introducing everyone once is\n * harmless, and persisting \"who said hello\" is not worth a file.\n */\nexport class VoiceIntroTracker {\n private lastSpoke = new Map<string, number>()\n\n constructor(private gapMs = INTRO_GAP_MS) {}\n\n needsIntro(session: string, agentId: string, now = Date.now()): boolean {\n const last = this.lastSpoke.get(key(session, agentId))\n return last === undefined || now - last > this.gapMs\n }\n\n /** Call only after the agent actually answered, or a failed turn would\n * swallow the introduction. */\n spoke(session: string, agentId: string, now = Date.now()): void {\n const k = key(session, agentId)\n // Re-insert so Map order stays oldest-first for eviction.\n this.lastSpoke.delete(k)\n this.lastSpoke.set(k, now)\n if (this.lastSpoke.size > MAX_TRACKED) {\n this.lastSpoke.delete(this.lastSpoke.keys().next().value as string)\n }\n }\n}\n\nconst key = (session: string, agentId: string) => `${session}\\u0000${agentId}`\n\n/** The system-prompt line that makes the agent introduce itself, or not. */\nexport function introInstruction(voice: AgentVoice, introduce: boolean): string {\n const style = voice.style ? ` Your speaking manner: ${voice.style}.` : \"\"\n if (introduce) {\n return (\n \"[VOICE INTRO] This is your first spoken exchange with this person in a while. \" +\n `Open with one short sentence introducing yourself in your own manner, along the lines of \"${voice.intro}\", ` +\n `then answer.${style}`\n )\n }\n return (\n \"[VOICE CASUAL] You have already introduced yourself in this voice conversation. \" +\n \"Do not introduce yourself or greet again; talk casually, like a colleague in the same office.\" +\n style\n )\n}\n\n/** An agent as a talk participant: who it is, how it sounds, whether it\n * still owes the listener an introduction. */\nexport function talkSpeaker(agentId: string, agents: DaemonConfig[\"agents\"], introduce: boolean, settings: VoiceSettings = {}): TalkSpeaker {\n const agent = agents[agentId]\n const voice = resolveAgentVoice(agentId, agents, settings)\n // The first paragraph is where a persona says who it is; the rest is\n // task instructions, which do not belong in small talk.\n const persona = (agent?.systemPrompt ?? \"\").split(/\\n\\s*\\n/)[0].slice(0, 600).trim()\n return {\n agentId,\n name: voice.name,\n voice: voiceRef(voice),\n persona: persona || `You are ${voice.name}.`,\n introLine: introInstruction(voice, introduce),\n }\n}\n\n/** Rides in the system append of every voice turn, never in the message,\n * or the session would record the scaffolding as the person's words. */\nexport const VOICE_MODE_INSTRUCTION =\n \"[VOICE MODE] This question arrived by voice and your reply will be spoken aloud by a \" +\n \"TTS engine. Answer in two or three short sentences. Plain language, no markdown, no \" +\n \"code blocks, no bullet points, no URLs. Speak conversationally.\"\n\n/** What a mesh peer sends in `context.voice` when a remote Mac speaks for\n * one of this node's agents. Only these fields cross the mesh, never\n * free-form system text. */\nexport interface RemoteVoiceTurn { introduce?: boolean; intro?: string; style?: string }\n\n/** The system append for a voice turn that arrived over the mesh, or\n * undefined when the task is not a voice turn. */\nexport function remoteVoiceAppend(context: unknown): string | undefined {\n const ctx = context as { channel?: unknown; voice?: RemoteVoiceTurn } | undefined\n if (ctx?.channel !== \"voice\" || !ctx.voice || typeof ctx.voice !== \"object\") return undefined\n const clip = (s: unknown) => (typeof s === \"string\" ? s.replace(/\\s+/g, \" \").slice(0, 200) : \"\")\n const intro = clip(ctx.voice.intro)\n const voice: AgentVoice = {\n agentId: \"\", name: \"\", provider: \"system\", elevenlabsVoiceId: null,\n systemVoice: null, systemVoiceName: null, systemByLanguage: {}, fallback: true, gender: null,\n style: clip(ctx.voice.style) || null,\n intro: intro || \"Hello.\",\n }\n return `${VOICE_MODE_INSTRUCTION}\\n${introInstruction(voice, ctx.voice.introduce === true && !!intro)}`\n}\n","import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from \"fs\"\nimport { resolve, join } from \"path\"\nimport type { BusinessConfig, BusinessWorkSource } from \"./config\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport { BacklogStore } from \"./backlog-store\"\nimport { syncBacklogItemToSource, resolveAssigneesForSource, type BacklogPatch } from \"./backlog-sync\"\n\n// --- Work pool: pluggable source of tasks for the business layer ---\n\nexport interface WorkItem {\n id: string // stable id for claim/report (e.g. \"backlog:line-12\", \"gitlab:proj:issue:42\")\n title: string\n description?: string\n assignee?: string // agentId\n estimatedSeconds?: number\n url?: string\n priority?: number // lower = higher priority\n /** Board stage / kanban column id. Was a closed enum back when boards\n * had a fixed schema; widened to `string` because per-project boards\n * can declare arbitrary column ids. The well-known names below remain\n * the standard set most boards use. */\n stage?: string // typically: \"triage\" | \"todo\" | \"doing\" | \"onhold\" | \"review\" | \"done\"\n /** Raw labels from the source (GitLab). Empty array if source doesn't support labels. */\n labels?: string[]\n /** Label name+color when the source returns colored labels (GitLab with_labels_details). */\n labelDetails?: Array<{ name: string; color: string; text_color?: string }>\n /** ISO timestamp of last update on the source. */\n updatedAt?: string\n /** Open/closed state from the source. */\n state?: \"opened\" | \"closed\"\n /** Optional milestone title (GitLab: issue.milestone.title). */\n milestone?: string\n /** GitLab assignees (0..n). `assignee` above is the agentId alias for assignees[0]. */\n assignees?: Array<{ username: string; name?: string; avatarUrl?: string }>\n}\n\n/** Member of a source (GitLab project members) — used for assignee pickers. */\nexport interface WorkMember {\n username: string\n name?: string\n avatarUrl?: string\n}\n\n/** Milestone on a source — used for milestone pickers. */\nexport interface WorkMilestone {\n id: number | string\n title: string\n state?: \"active\" | \"closed\"\n dueDate?: string\n}\n\n/** Full issue detail — a superset of WorkItem for the detail view. */\nexport interface WorkDetail extends WorkItem {\n /** Raw body, may be markdown. */\n descriptionHtml?: string\n /** ISO timestamp of creation. */\n createdAt?: string\n /** Author (issue reporter). */\n author?: { username: string; name?: string; avatarUrl?: string }\n /** Full labels list (all of them). */\n allLabels?: string[]\n}\n\nexport interface WorkReport {\n status: \"in-progress\" | \"done\" | \"blocked\"\n note?: string\n timeSeconds?: number\n blocker?: string\n}\n\n/** What a WorkSource can do beyond the minimal listOpen/claim/report triple. */\nexport interface WorkSourceCapabilities {\n /** Enumerate items beyond assignee filter — required for a board view. */\n listAll: boolean\n /** Create new items (e.g. POST GitLab issue). */\n create: boolean\n /** Arbitrary column transitions (label add/remove pair). */\n transition: boolean\n /** Surfaces labels[] on returned items. */\n labels: boolean\n /** Server-side text search. */\n search: boolean\n}\n\nexport interface WorkCreateInput {\n title: string\n description?: string\n /** agentId */\n assignee?: string\n /** Raw GitLab usernames — bypass agentId resolution for UI-driven creates. */\n assigneeUsernames?: string[]\n labels?: string[]\n /** Milestone title (resolved to id). */\n milestoneTitle?: string\n /** For sources that span multiple projects (gitlab): \"group/project\". */\n projectHint?: string\n}\n\nexport interface ListAllOpts {\n sinceDays?: number\n labels?: string[]\n /** Exclude items carrying any of these labels (client-side filter). */\n notLabels?: string[]\n /** Exclude items whose labels carry this scoped prefix (e.g. \"Status\"). */\n withoutScopedPrefix?: string\n search?: string\n assignee?: string\n /** GitLab issue state. Defaults to \"opened\" (boards hide closed issues). */\n state?: \"opened\" | \"closed\" | \"all\"\n /** Milestone filter (GitLab \"milestone\" query param). */\n milestone?: string\n}\n\nexport interface WorkSource {\n type: string\n capabilities: WorkSourceCapabilities\n listOpen(agentId: string): Promise<WorkItem[]>\n claim(agentId: string, itemId: string): Promise<void>\n report(itemId: string, update: WorkReport): Promise<void>\n /** Optional — gated by capabilities.listAll. */\n listAll?(opts: ListAllOpts): Promise<WorkItem[]>\n /** Optional — gated by capabilities.create. */\n create?(input: WorkCreateInput): Promise<WorkItem>\n /**\n * Optional — gated by capabilities.transition. Add `toLabel` and remove\n * `fromLabel` (if provided) from the item. Used by the kanban board on drag.\n */\n transition?(itemId: string, toLabel: string, fromLabel?: string): Promise<void>\n}\n\n// ---------- BacklogWorkSource: GFM checklist file ----------\n// Format in .agentx/backlog.md (or configured path):\n//\n// - [ ] @alice Implement login form [time: 2h]\n// - [x] @alice Write unit tests (done)\n// - [ ] @bob Design pricing page [time: 1h30m]\n//\n// Line number is the stable id.\n\nconst CHECK_RE = /^(\\s*)-\\s*\\[( |x|X)\\]\\s*(.*)$/\nconst MENTION_RE = /@([a-z0-9_-]+)/i\nconst TIME_RE = /\\[time:\\s*([0-9hm\\s]+)\\]/i\n\nfunction parseDuration(s: string): number {\n // \"2h30m\" -> 9000 s. \"1h\" -> 3600. \"45m\" -> 2700.\n const hMatch = s.match(/(\\d+)\\s*h/i)\n const mMatch = s.match(/(\\d+)\\s*m/i)\n return (hMatch ? parseInt(hMatch[1], 10) * 3600 : 0) + (mMatch ? parseInt(mMatch[1], 10) * 60 : 0)\n}\n\nexport class BacklogWorkSource implements WorkSource {\n type = \"backlog\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: false, transition: false, labels: true, search: false,\n }\n private store: BacklogStore\n\n constructor(\n private path: string,\n private daemon?: DaemonConfig,\n private log: (...args: unknown[]) => void = () => {},\n ) {\n this.store = new BacklogStore(this.path)\n }\n\n /** Expose the store so the CLI and sync-back can share state. */\n getStore(): BacklogStore {\n return this.store\n }\n\n private readMd(): string[] {\n const full = resolve(process.cwd(), this.path)\n if (!existsSync(full)) return []\n return readFileSync(full, \"utf-8\").split(\"\\n\")\n }\n\n private writeMd(lines: string[]): void {\n const full = resolve(process.cwd(), this.path)\n writeFileSync(full, lines.join(\"\\n\"))\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n if (this.store.exists()) {\n const items = this.store.list().filter((it) =>\n it.status !== \"done\" &&\n (!it.assignee || it.assignee.toLowerCase() === agentId.toLowerCase()),\n )\n return items.map((it) => this.toWorkItem(it))\n }\n // Legacy markdown-checklist fallback (unchanged behavior).\n const lines = this.readMd()\n const items: WorkItem[] = []\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m) continue\n const checked = m[2].toLowerCase() === \"x\"\n if (checked) continue\n const body = m[3]\n const mention = body.match(MENTION_RE)\n if (mention && mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n const timeMatch = body.match(TIME_RE)\n const title = body.replace(MENTION_RE, \"\").replace(TIME_RE, \"\").trim()\n items.push({\n id: `backlog:${i}`,\n title: title || `Task at line ${i + 1}`,\n assignee: mention ? mention[1] : undefined,\n estimatedSeconds: timeMatch ? parseDuration(timeMatch[1]) : undefined,\n priority: i,\n })\n }\n return items\n }\n\n async listAll(_opts: ListAllOpts = {}): Promise<WorkItem[]> {\n if (!this.store.exists()) return []\n return this.store.list().map((it) => this.toWorkItem(it))\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n if (!this.store.exists()) return\n const item = this.store.findById(itemId)\n if (!item) return\n const updated = this.store.update(itemId, {\n assignee: item.assignee || agentId,\n status: \"doing\",\n })\n if (updated.source && this.daemon) {\n const usernames = resolveAssigneesForSource([updated.assignee || agentId], updated.source, this.daemon)\n void syncBacklogItemToSource(updated, {\n assigneeUsernames: usernames,\n addLabels: [\"Doing\"],\n removeLabels: [\"To Do\"],\n }, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] claim failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // Structured store path.\n if (this.store.exists() && this.store.findById(itemId)) {\n const patch: Partial<import(\"./backlog-store\").BacklogItem> = {}\n if (update.status === \"done\") patch.status = \"done\"\n else if (update.status === \"blocked\") {\n patch.status = \"blocked\"\n patch.blocker = update.blocker || update.note || undefined\n } else if (update.status === \"in-progress\") patch.status = \"doing\"\n if (update.timeSeconds) patch.estimatedSeconds = update.timeSeconds\n const item = this.store.update(itemId, patch)\n\n if (item.source && this.daemon) {\n const sourcePatch: BacklogPatch = {}\n if (update.status === \"done\") {\n sourcePatch.addLabels = [\"Done\"]\n sourcePatch.removeLabels = [\"Doing\"]\n } else if (update.status === \"blocked\") {\n sourcePatch.addLabels = [\"Blocked\"]\n }\n if (update.note) sourcePatch.note = update.note\n if (Object.keys(sourcePatch).length) {\n void syncBacklogItemToSource(item, sourcePatch, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] report failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n return\n }\n\n // Legacy markdown path (line-number ids).\n if (!itemId.startsWith(\"backlog:\")) throw new Error(`not a backlog id: ${itemId}`)\n const lineNo = parseInt(itemId.slice(\"backlog:\".length), 10)\n const lines = this.readMd()\n const line = lines[lineNo]\n if (!line) throw new Error(`backlog line ${lineNo} missing`)\n const m = line.match(CHECK_RE)\n if (!m) throw new Error(`backlog line ${lineNo} not a checkbox`)\n\n if (update.status === \"done\") {\n lines[lineNo] = line.replace(/- \\[ \\]/, \"- [x]\")\n const note = update.timeSeconds\n ? ` <!-- done by agent, ${Math.round(update.timeSeconds / 60)}m -->`\n : \" <!-- done by agent -->\"\n lines.splice(lineNo + 1, 0, note)\n } else if (update.status === \"blocked\") {\n lines.splice(lineNo + 1, 0, ` <!-- BLOCKED: ${update.blocker || update.note || \"?\"} -->`)\n } else if (update.note) {\n lines.splice(lineNo + 1, 0, ` <!-- ${update.note.replace(/-->/g, \"— >\")} -->`)\n }\n this.writeMd(lines)\n }\n\n private toWorkItem(it: import(\"./backlog-store\").BacklogItem): WorkItem {\n const stage = ({ todo: \"todo\", doing: \"doing\", blocked: \"onhold\", done: \"done\" } as const)[it.status]\n return {\n id: it.id,\n title: it.title,\n description: it.description,\n assignee: it.assignee,\n estimatedSeconds: it.estimatedSeconds,\n priority: it.priority,\n labels: it.labels,\n stage,\n milestone: it.milestone,\n url: it.source?.url,\n updatedAt: it.updatedAt,\n state: it.status === \"done\" ? \"closed\" : \"opened\",\n }\n }\n}\n\n// ---------- WikiWorkSource: scan markdown files for @mention checkboxes ----------\n\nexport class WikiWorkSource implements WorkSource {\n type = \"wiki\"\n capabilities: WorkSourceCapabilities = {\n listAll: false, create: false, transition: false, labels: false, search: false,\n }\n constructor(private root: string, private glob: string) {}\n\n private walk(dir: string, out: string[]): void {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const st = statSync(full)\n if (st.isDirectory()) {\n this.walk(full, out)\n } else if (entry.endsWith(\".md\")) {\n out.push(full)\n }\n }\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const files: string[] = []\n this.walk(resolve(process.cwd(), this.root), files)\n const items: WorkItem[] = []\n for (const file of files) {\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m || m[2].toLowerCase() === \"x\") continue\n const mention = m[3].match(MENTION_RE)\n if (!mention || mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n items.push({\n id: `wiki:${file}:${i}`,\n title: m[3].replace(MENTION_RE, \"\").trim(),\n assignee: mention[1],\n priority: i,\n })\n }\n }\n return items\n }\n\n async claim(): Promise<void> { /* no-op */ }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // wiki:<file>:<line>\n const parts = itemId.split(\":\")\n const lineNo = parseInt(parts[parts.length - 1], 10)\n const file = parts.slice(1, -1).join(\":\")\n if (!existsSync(file)) return\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n if (update.status === \"done\" && lines[lineNo]) {\n lines[lineNo] = lines[lineNo].replace(/- \\[ \\]/, \"- [x]\")\n writeFileSync(file, lines.join(\"\\n\"))\n }\n }\n}\n\n// ---------- GitLabWorkSource: assignee-filtered issues via GitLab API ----------\n\nexport class GitLabWorkSource implements WorkSource {\n type = \"gitlab\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: true, transition: true, labels: true, search: true,\n }\n\n constructor(\n private host: string,\n private token: string,\n private projects: string[],\n /** Map from agentId -> GitLab username(s) */\n private agentUsernames: Record<string, string[]>,\n private log: (...args: unknown[]) => void,\n ) {}\n\n /** Reverse lookup: GitLab username -> agentId. Used when resolving board items\n * back to an agent (for the reconciler and card display). */\n private usernameToAgent(username: string): string | undefined {\n for (const [agentId, names] of Object.entries(this.agentUsernames)) {\n if (names.some((n) => n.toLowerCase() === username.toLowerCase())) return agentId\n }\n return undefined\n }\n\n private mapIssue(issue: any, project: string): WorkItem {\n const ns = issue.references?.full?.split(\"#\")[0] || project\n // `labels` may be a string[] or, with with_labels_details=true, an object[].\n let labels: string[] = []\n let labelDetails: Array<{ name: string; color: string; text_color?: string }> | undefined\n if (Array.isArray(issue.labels)) {\n if (issue.labels.length && typeof issue.labels[0] === \"object\") {\n labelDetails = issue.labels.map((l: any) => ({\n name: l.name, color: l.color, text_color: l.text_color,\n }))\n labels = labelDetails!.map((l) => l.name)\n } else {\n labels = issue.labels\n }\n }\n const rawAssignees: any[] = Array.isArray(issue.assignees) && issue.assignees.length\n ? issue.assignees\n : (issue.assignee ? [issue.assignee] : [])\n const assignees = rawAssignees.map((a) => ({\n username: a.username, name: a.name, avatarUrl: a.avatar_url,\n }))\n const firstUsername = assignees[0]?.username\n return {\n id: `gitlab:${ns}:issue:${issue.iid}`,\n title: issue.title,\n description: issue.description || undefined,\n // Prefer configured agentId mapping; fall back to the raw GitLab username\n // so cards still render an assignee even without agentMappings configured.\n assignee: firstUsername ? (this.usernameToAgent(firstUsername) || firstUsername) : undefined,\n assignees,\n url: issue.web_url,\n priority: issue.weight ?? 99,\n estimatedSeconds: issue.time_stats?.time_estimate || undefined,\n labels,\n labelDetails,\n updatedAt: issue.updated_at,\n state: issue.state === \"closed\" ? \"closed\" : \"opened\",\n milestone: issue.milestone?.title || undefined,\n }\n }\n\n private async api(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetch(`${this.host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": this.token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n if (!res.ok) throw new Error(`GitLab ${res.status}: ${await res.text().catch(() => \"\")}`)\n return res.json()\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const usernames = this.agentUsernames[agentId] || []\n if (!usernames.length) return []\n\n const items: WorkItem[] = []\n const projectList = this.projects.length\n ? this.projects\n : [\"__ALL__\"] // falls back to a global issues query\n\n for (const username of usernames) {\n for (const project of projectList) {\n try {\n const q = `assignee_username=${encodeURIComponent(username)}&state=opened&scope=all&per_page=20`\n const path = project === \"__ALL__\"\n ? `/issues?${q}`\n : `/projects/${encodeURIComponent(project)}/issues?${q}`\n const issues = await this.api(path)\n for (const issue of issues) {\n const item = this.mapIssue(issue, project)\n // Force assignee to the agent we queried for (listOpen is per-agent).\n item.assignee = agentId\n items.push(item)\n }\n } catch (e: any) {\n this.log(`[business] GitLab listOpen failed for ${username} on ${project}: ${e.message}`)\n }\n }\n }\n return items\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n // itemId: \"gitlab:<project>:issue:<iid>\"\n const { project, iid, type } = this.parseId(itemId)\n // Transition labels: add \"Doing\", remove \"To Do\"\n const path = `/projects/${encodeURIComponent(project)}/${type === \"issue\" ? \"issues\" : \"merge_requests\"}/${iid}?add_labels=Doing&remove_labels=To Do`\n try {\n await this.api(path, { method: \"PUT\" })\n } catch (e: any) {\n this.log(`[business] claim label update failed: ${e.message}`)\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n\n // Time tracking\n if (update.timeSeconds && update.timeSeconds > 0) {\n const mins = Math.max(1, Math.round(update.timeSeconds / 60))\n const dur = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? ` ${mins % 60}m` : \"\"}` : `${mins}m`\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/add_spent_time?duration=${encodeURIComponent(dur)}`,\n { method: \"POST\" },\n )\n } catch (e: any) {\n this.log(`[business] add_spent_time failed: ${e.message}`)\n }\n }\n\n // Status → labels\n if (update.status === \"done\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Done&remove_labels=Doing`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] done label update failed: ${e.message}`)\n }\n } else if (update.status === \"blocked\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Blocked`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] blocked label update failed: ${e.message}`)\n }\n }\n\n // Note/comment\n if (update.note) {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/notes`,\n { method: \"POST\", body: JSON.stringify({ body: update.note }) },\n )\n } catch (e: any) {\n this.log(`[business] report note failed: ${e.message}`)\n }\n }\n }\n\n async listAll(opts: ListAllOpts = {}): Promise<WorkItem[]> {\n const sinceDays = opts.sinceDays ?? 30\n const updatedAfter = new Date(Date.now() - sinceDays * 24 * 3600_000).toISOString()\n\n const params = new URLSearchParams()\n const state = opts.state ?? \"opened\"\n params.set(\"state\", state)\n // `updated_after` makes sense for the Closed column (bounded window) and for\n // keeping the open list recent; for \"opened\" we pass it too but GitLab treats\n // the window loosely (any issue updated since counts, regardless of open-age).\n params.set(\"updated_after\", updatedAfter)\n params.set(\"scope\", \"all\")\n params.set(\"per_page\", \"100\")\n params.set(\"with_labels_details\", \"true\")\n params.set(\"order_by\", \"updated_at\")\n params.set(\"sort\", \"desc\")\n if (opts.labels?.length) params.set(\"labels\", opts.labels.join(\",\"))\n if (opts.milestone) params.set(\"milestone\", opts.milestone)\n if (opts.search) params.set(\"search\", opts.search)\n\n const projectList = this.projects.length ? this.projects : [\"__ALL__\"]\n const items: WorkItem[] = []\n for (const project of projectList) {\n try {\n const path = project === \"__ALL__\"\n ? `/issues?${params.toString()}`\n : `/projects/${encodeURIComponent(project)}/issues?${params.toString()}`\n const issues = await this.api(path)\n for (const issue of issues) items.push(this.mapIssue(issue, project))\n } catch (e: any) {\n this.log(`[business] GitLab listAll failed on ${project}: ${e.message}`)\n }\n }\n\n // Client-side negative-label filter (GitLab API supports `not[labels]` only\n // via the `not[]` nested query form, which isn't convenient via URLSearchParams).\n let result = items\n if (opts.notLabels?.length) {\n const deny = new Set(opts.notLabels)\n result = result.filter((i) => !(i.labels || []).some((l) => deny.has(l)))\n }\n if (opts.withoutScopedPrefix) {\n const prefix = opts.withoutScopedPrefix + \"::\"\n result = result.filter((i) => !(i.labels || []).some((l) => l.startsWith(prefix)))\n }\n return result\n }\n\n /** Close or reopen an issue via state_event. */\n async setState(itemId: string, to: \"close\" | \"reopen\"): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?state_event=${to}`,\n { method: \"PUT\" },\n )\n }\n\n /** Fetch a single issue's full detail. */\n async getItem(itemId: string): Promise<WorkDetail | null> {\n const { project, iid } = this.parseId(itemId)\n try {\n const issue = await this.api(\n `/projects/${encodeURIComponent(project)}/issues/${iid}?with_labels_details=true`,\n )\n const base = this.mapIssue(issue, project)\n return {\n ...base,\n descriptionHtml: issue.description_html || undefined,\n createdAt: issue.created_at,\n author: issue.author ? {\n username: issue.author.username, name: issue.author.name, avatarUrl: issue.author.avatar_url,\n } : undefined,\n allLabels: Array.isArray(issue.labels)\n ? (typeof issue.labels[0] === \"object\" ? issue.labels.map((l: any) => l.name) : issue.labels)\n : [],\n }\n } catch (e: any) {\n this.log(`[board] GitLab getItem failed for ${itemId}: ${e.message}`)\n return null\n }\n }\n\n /** Update fields on an issue. Accepts string usernames for assignees. */\n async updateItem(\n itemId: string,\n patch: {\n title?: string\n description?: string\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n assigneeUsernames?: string[]\n milestoneTitle?: string | null\n },\n ): Promise<WorkItem> {\n const { project, iid } = this.parseId(itemId)\n\n // Resolve assignee usernames → user ids (parallel).\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames) {\n const results = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch { return null }\n }))\n assigneeIds = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id (null clears the milestone).\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestoneId = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${patch.milestoneTitle}\": ${e.message}`)\n }\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues/${iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n /** List project members (direct + inherited). Deduped by username across projects. */\n async listMembers(): Promise<WorkMember[]> {\n const byUsername = new Map<string, WorkMember>()\n for (const project of this.projects) {\n try {\n const members = await this.api(\n `/projects/${encodeURIComponent(project)}/members/all?per_page=100`,\n )\n for (const m of members) {\n if (m.username && !byUsername.has(m.username)) {\n byUsername.set(m.username, {\n username: m.username, name: m.name, avatarUrl: m.avatar_url,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMembers failed for ${project}: ${e.message}`)\n }\n }\n return [...byUsername.values()].sort((a, b) => a.username.localeCompare(b.username))\n }\n\n /** List milestones (active first, then closed). Deduped by title across projects. */\n async listMilestones(): Promise<WorkMilestone[]> {\n const byTitle = new Map<string, WorkMilestone>()\n for (const project of this.projects) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?per_page=100`,\n )\n for (const m of ms) {\n if (m.title && !byTitle.has(m.title)) {\n byTitle.set(m.title, {\n id: m.id, title: m.title, state: m.state, dueDate: m.due_date,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMilestones failed for ${project}: ${e.message}`)\n }\n }\n const all = [...byTitle.values()]\n all.sort((a, b) => {\n if (a.state !== b.state) return a.state === \"active\" ? -1 : 1\n return a.title.localeCompare(b.title)\n })\n return all\n }\n\n async create(input: WorkCreateInput): Promise<WorkItem> {\n const project = input.projectHint || this.projects[0]\n if (!project) throw new Error(\"GitLabWorkSource.create requires projectHint or configured projects\")\n\n // Resolve assignee usernames → user ids. Prefer explicit `assigneeUsernames`,\n // fall back to agentId → configured mapping for legacy callers.\n let assignee_ids: number[] | undefined\n const usernames = input.assigneeUsernames?.length\n ? input.assigneeUsernames\n : (input.assignee ? (this.agentUsernames[input.assignee] || []).slice(0, 1) : [])\n if (usernames.length) {\n const results = await Promise.all(usernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch (e: any) {\n this.log(`[board] user lookup failed for ${u}: ${e.message}`)\n return null\n }\n }))\n assignee_ids = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestone_id: number | undefined\n if (input.milestoneTitle) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(input.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestone_id = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${input.milestoneTitle}\": ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {\n title: input.title,\n description: input.description,\n labels: (input.labels || []).join(\",\"),\n }\n if (assignee_ids?.length) body.assignee_ids = assignee_ids\n if (milestone_id !== undefined) body.milestone_id = milestone_id\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues`, {\n method: \"POST\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n async transition(itemId: string, toLabel: string, fromLabel?: string): Promise<void> {\n // Legacy single add/remove — boards use transitionMany.\n await this.transitionMany(itemId, toLabel ? [toLabel] : [], fromLabel ? [fromLabel] : [])\n }\n\n /** Multi-label transition; used by board drag-drop (scoped labels produce pairs). */\n async transitionMany(itemId: string, add: string[], remove: string[]): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n const params = new URLSearchParams()\n if (add.length) params.set(\"add_labels\", add.join(\",\"))\n if (remove.length) params.set(\"remove_labels\", remove.join(\",\"))\n if (!params.toString()) return\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?${params.toString()}`,\n { method: \"PUT\" },\n )\n }\n\n private parseId(itemId: string): { project: string; type: \"issue\" | \"mr\"; iid: string } {\n // \"gitlab:<project>:issue:<iid>\" (project may contain a slash → it's joined)\n const body = itemId.replace(/^gitlab:/, \"\")\n const parts = body.split(\":\")\n const iid = parts.pop() as string\n const type = parts.pop() === \"mr\" ? \"mr\" : \"issue\"\n const project = parts.join(\":\")\n return { project, type, iid }\n }\n}\n\n// ---------- Factory ----------\n\nexport function createWorkSource(\n business: BusinessConfig,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): WorkSource {\n const ws: BusinessWorkSource = business.workSource\n switch (ws.type) {\n case \"backlog\":\n return new BacklogWorkSource(ws.path, daemon, log)\n\n case \"wiki\":\n return new WikiWorkSource(ws.path, ws.glob)\n\n case \"gitlab\": {\n const gl = daemon.channels.gitlab\n if (!gl?.token) {\n throw new Error(\"business.workSource.type=gitlab requires channels.gitlab.token\")\n }\n const agentUsernames: Record<string, string[]> = {}\n for (const mapping of gl.agentMappings) {\n if (mapping.gitlabUsernames.length) {\n agentUsernames[mapping.agentId] = mapping.gitlabUsernames\n }\n }\n const projects = ws.projects.length\n ? ws.projects\n : gl.routes.map((r) => r.project)\n return new GitLabWorkSource(gl.host, gl.token, projects, agentUsernames, log)\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\n\n// --- Structured backlog store (Plan B + import) ---\n//\n// Replaces the legacy `.agentx/backlog.md` GFM checklist as the canonical\n// store when `.agentx/backlog.json` is present. The markdown view is\n// regenerated on every save so humans still get a readable diff.\n//\n// Why structured: importing issues from gitlab/github needs a stable\n// linkage back to the source (project + iid + url) so mutations\n// (assignee, labels, status, milestone) can be pushed upstream by the\n// sync-back layer. Encoding that in markdown comments is brittle.\n\nconst sourceSchema = z.object({\n type: z.enum([\"gitlab\", \"github\"]),\n host: z.string().optional(),\n project: z.string(),\n iid: z.number().int().positive(),\n url: z.string(),\n})\n\nconst itemSchema = z.object({\n id: z.string(),\n title: z.string(),\n description: z.string().optional(),\n assignee: z.string().optional(),\n labels: z.array(z.string()).default([]),\n milestone: z.string().optional(),\n status: z.enum([\"todo\", \"doing\", \"blocked\", \"done\"]).default(\"todo\"),\n priority: z.number().int().optional(),\n estimatedSeconds: z.number().int().optional(),\n source: sourceSchema.optional(),\n importedAt: z.string().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n blocker: z.string().optional(),\n doneNote: z.string().optional(),\n})\n\nconst fileSchema = z.object({\n version: z.literal(1),\n items: z.array(itemSchema).default([]),\n})\n\nexport type BacklogSource = z.infer<typeof sourceSchema>\nexport type BacklogItem = z.infer<typeof itemSchema>\nexport type BacklogFile = z.infer<typeof fileSchema>\n\nexport class BacklogStore {\n constructor(private path: string) {}\n\n /** Absolute path of the JSON store. */\n jsonPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.md$/i, \".json\")\n }\n\n /** Absolute path of the markdown view (alongside the JSON store). */\n mdPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.json$/i, \".md\")\n }\n\n exists(): boolean {\n return existsSync(this.jsonPath())\n }\n\n load(): BacklogFile {\n const p = this.jsonPath()\n if (!existsSync(p)) return { version: 1, items: [] }\n const raw = readFileSync(p, \"utf-8\")\n try {\n return fileSchema.parse(JSON.parse(raw))\n } catch (e: any) {\n throw new Error(`Backlog file invalid (${p}): ${e.message}`)\n }\n }\n\n save(file: BacklogFile): void {\n const jsonP = this.jsonPath()\n mkdirSync(dirname(jsonP), { recursive: true })\n writeFileSync(jsonP, JSON.stringify(file, null, 2) + \"\\n\")\n writeFileSync(this.mdPath(), this.toMarkdown(file))\n }\n\n list(): BacklogItem[] {\n return this.load().items\n }\n\n findById(id: string): BacklogItem | undefined {\n return this.list().find((i) => i.id === id)\n }\n\n /** Add an item; rejects duplicates by id. Returns the saved item. */\n add(input: Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }): BacklogItem {\n const file = this.load()\n if (file.items.some((i) => i.id === input.id)) {\n throw new Error(`backlog item ${input.id} already exists`)\n }\n const now = new Date().toISOString()\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n this.save(file)\n return item\n }\n\n /** Add many; skips items whose id already exists, returns the inserted set. */\n addMany(inputs: Array<Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }>): BacklogItem[] {\n const file = this.load()\n const existing = new Set(file.items.map((i) => i.id))\n const now = new Date().toISOString()\n const added: BacklogItem[] = []\n for (const input of inputs) {\n if (existing.has(input.id)) continue\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n added.push(item)\n existing.add(item.id)\n }\n if (added.length) this.save(file)\n return added\n }\n\n update(id: string, patch: Partial<BacklogItem>): BacklogItem {\n const file = this.load()\n const idx = file.items.findIndex((i) => i.id === id)\n if (idx < 0) throw new Error(`backlog item ${id} not found`)\n const updated: BacklogItem = {\n ...file.items[idx],\n ...patch,\n id: file.items[idx].id,\n createdAt: file.items[idx].createdAt,\n updatedAt: new Date().toISOString(),\n }\n file.items[idx] = updated\n this.save(file)\n return updated\n }\n\n remove(id: string): boolean {\n const file = this.load()\n const before = file.items.length\n file.items = file.items.filter((i) => i.id !== id)\n if (file.items.length === before) return false\n this.save(file)\n return true\n }\n\n /** Render a human-readable markdown view (generated, not authoritative). */\n toMarkdown(file: BacklogFile): string {\n const lines: string[] = [\n \"# Backlog\",\n \"\",\n \"_Generated from `backlog.json`. Do not edit by hand — use `agentx backlog` commands._\",\n \"\",\n ]\n const buckets: Record<BacklogItem[\"status\"], BacklogItem[]> = {\n doing: [], blocked: [], todo: [], done: [],\n }\n for (const item of file.items) buckets[item.status].push(item)\n const sections: Array<[BacklogItem[\"status\"], string]> = [\n [\"doing\", \"Doing\"], [\"blocked\", \"Blocked\"], [\"todo\", \"To Do\"], [\"done\", \"Done\"],\n ]\n for (const [key, label] of sections) {\n if (!buckets[key].length) continue\n lines.push(`## ${label}`, \"\")\n for (const it of buckets[key]) {\n const check = key === \"done\" ? \"x\" : \" \"\n const mention = it.assignee ? `@${it.assignee} ` : \"\"\n const time = it.estimatedSeconds\n ? ` [time: ${Math.round(it.estimatedSeconds / 60)}m]`\n : \"\"\n const src = it.source ? ` (${it.source.type}: ${it.source.project}#${it.source.iid})` : \"\"\n lines.push(`- [${check}] ${mention}${it.title}${time}${src}`)\n if (it.source?.url) lines.push(` - <${it.source.url}>`)\n if (it.labels.length) lines.push(` - labels: ${it.labels.join(\", \")}`)\n if (it.milestone) lines.push(` - milestone: ${it.milestone}`)\n if (it.blocker) lines.push(` - BLOCKED: ${it.blocker}`)\n }\n lines.push(\"\")\n }\n return lines.join(\"\\n\")\n }\n}\n\n/** Generate a stable BacklogItem.id for a fresh manual item. */\nexport function manualBacklogId(): string {\n return `manual:${randomUUID()}`\n}\n\n/** Build the canonical id for an imported source issue. */\nexport function sourceBacklogId(source: BacklogSource): string {\n return `${source.type}:${source.project}:${source.iid}`\n}\n","import type { BacklogItem, BacklogSource } from \"./backlog-store\"\nimport type { DaemonConfig } from \"@/daemon/config\"\n\n// --- Sync-back layer: backlog item mutations → upstream issue ---\n//\n// When a backlog item carries a `source` ref (gitlab or github), changes\n// applied via the backlog work-source need to flow back to the original\n// issue: assignees, labels, title, description, milestone, open/close.\n// Otherwise the imported view drifts from upstream and humans browsing\n// the source see stale state.\n//\n// This module is intentionally stateless — callers pass the patch they\n// just applied to the local item; we forward the same shape to the\n// matching upstream API.\n\nexport interface BacklogPatch {\n title?: string\n description?: string\n /** Replace labels wholesale. Use addLabels/removeLabels for incremental. */\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n /** Username on the source (NOT agentId — caller resolves the mapping). */\n assigneeUsernames?: string[]\n /** Title of the milestone, or empty string to clear. */\n milestoneTitle?: string | null\n /** Open/close transition. */\n state?: \"open\" | \"closed\"\n /** Optional comment to post on the upstream issue. */\n note?: string\n}\n\nexport interface SyncBackResult {\n ok: boolean\n url?: string\n error?: string\n /** Each upstream call we attempted, in order. */\n attempts: Array<{ kind: string; ok: boolean; status?: number; error?: string }>\n}\n\n/** Resolve agentIds in a patch to upstream usernames using the daemon mappings. */\nexport function resolveAssigneesForSource(\n agentIds: string[],\n source: BacklogSource,\n daemon: DaemonConfig,\n): string[] {\n const out: string[] = []\n if (source.type === \"gitlab\") {\n const mappings = daemon.channels.gitlab?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.gitlabUsernames?.length) out.push(m.gitlabUsernames[0])\n else out.push(agentId)\n }\n } else if (source.type === \"github\") {\n const mappings = daemon.channels.github?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.githubUsernames?.length) out.push(m.githubUsernames[0])\n else out.push(agentId)\n }\n }\n return out\n}\n\n/** Push a backlog patch to the upstream source referenced by the item. */\nexport async function syncBacklogItemToSource(\n item: BacklogItem,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void = () => {},\n): Promise<SyncBackResult> {\n if (!item.source) return { ok: true, attempts: [] }\n if (item.source.type === \"gitlab\") return syncToGitLab(item.source, patch, daemon, log)\n if (item.source.type === \"github\") return syncToGitHub(item.source, patch, daemon, log)\n return { ok: false, error: `unknown source.type ${(item.source as any).type}`, attempts: [] }\n}\n\n// ---------- GitLab ----------\n\nasync function syncToGitLab(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gl = daemon.channels.gitlab\n const token = gl?.token\n const host = gl?.host || (source.host ? `https://${source.host}` : undefined)\n if (!token || !host) {\n return { ok: false, error: \"gitlab token/host not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`${host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n }\n\n const projectEnc = encodeURIComponent(source.project)\n\n // Resolve assignee usernames → ids (parallel) — only when we need them.\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames !== undefined) {\n const ids = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const res = await fetch(`${host}/api/v4/users?username=${encodeURIComponent(u)}`, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (!res.ok) return null\n const arr: any = await res.json()\n return Array.isArray(arr) && arr[0]?.id ? arr[0].id : null\n } catch { return null }\n }))\n assigneeIds = ids.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const res = await api(`/projects/${projectEnc}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`)\n if (res.ok) {\n const arr: any = await res.json()\n if (Array.isArray(arr) && arr[0]?.id) milestoneId = arr[0].id\n }\n } catch (e: any) {\n log(`[backlog-sync] milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // Build the PUT body.\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n if (patch.state) body.state_event = patch.state === \"closed\" ? \"close\" : \"reopen\"\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PUT issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PUT issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PUT issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}/notes`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST note\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST note\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n\n// ---------- GitHub ----------\n\nasync function syncToGitHub(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gh = daemon.channels.github\n const token = gh?.token\n if (!token) {\n return { ok: false, error: \"github token not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`https://api.github.com${path}`, {\n ...init,\n headers: {\n \"Authorization\": `Bearer ${token}`,\n \"Accept\": \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n ...(init.headers || {}),\n },\n })\n }\n\n const repo = source.project // GitHub IDs use \"owner/repo\" verbatim in the URL path.\n\n // Resolve milestone title → number.\n let milestoneNumber: number | null | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneNumber = null\n } else {\n try {\n const res = await api(`/repos/${repo}/milestones?state=all&per_page=100`)\n if (res.ok) {\n const arr: any = await res.json()\n const found = Array.isArray(arr) ? arr.find((m: any) => m.title === patch.milestoneTitle) : null\n if (found?.number) milestoneNumber = found.number\n }\n } catch (e: any) {\n log(`[backlog-sync] github milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // GitHub doesn't support add/remove labels in the issue PATCH the way\n // GitLab does — labels is a wholesale set. If only addLabels/removeLabels\n // were provided we read+merge.\n let nextLabels: string[] | undefined\n if (patch.labels !== undefined) {\n nextLabels = patch.labels\n } else if (patch.addLabels?.length || patch.removeLabels?.length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`)\n if (res.ok) {\n const issue: any = await res.json()\n const current: string[] = Array.isArray(issue.labels)\n ? issue.labels.map((l: any) => (typeof l === \"string\" ? l : l.name))\n : []\n const adds = new Set([...current, ...(patch.addLabels ?? [])])\n for (const r of patch.removeLabels ?? []) adds.delete(r)\n nextLabels = [...adds]\n }\n } catch (e: any) {\n log(`[backlog-sync] github label fetch failed: ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.body = patch.description\n if (nextLabels !== undefined) body.labels = nextLabels\n if (patch.assigneeUsernames !== undefined) body.assignees = patch.assigneeUsernames\n if (milestoneNumber !== undefined) body.milestone = milestoneNumber\n if (patch.state) body.state = patch.state\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`, {\n method: \"PATCH\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PATCH issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PATCH issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PATCH issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}/comments`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST comment\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST comment\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n","// --- Who is the work for? -------------------------------------------------\n//\n// The principal behind a piece of work is a CLIENT. That concept already\n// existed, spread across `business.projects[].client`, `business.contactMap[]`\n// and the `orgChart` reportsTo chain, resolved privately inside the activity\n// graph panel. This module is the single implementation both that panel and\n// the session monitor call, plus the policy layer the monitor needs.\n//\n// Clients are DERIVED, never declared: one appears the moment a project or a\n// contact mentions it. `business.clients` only carries overrides — a display\n// name, whether it is a paying client or your own house, how fast a delay\n// costs you, and what agents may do for it without asking. A client with no\n// entry there still resolves; it just gets defaults.\n\nexport interface ContactRule {\n channel?: string\n chatId?: string\n username?: string\n senderId?: string\n client: string\n project?: string\n displayName?: string\n}\nexport interface ProjectRule { id: string; pm?: string; client?: string }\nexport interface ClientPolicy {\n name?: string\n kind?: \"client\" | \"internal\" | \"own\"\n respondWithin?: string\n standing?: string[]\n}\nexport interface BusinessShape {\n projects?: ProjectRule[]\n contactMap?: ContactRule[]\n orgChart?: Record<string, { reportsTo?: string }>\n clients?: Record<string, ClientPolicy>\n}\nexport interface ResolvedClient {\n id: string\n name: string\n kind: \"client\" | \"internal\" | \"own\"\n /** Minutes before a wait starts costing; undefined = no external clock. */\n respondWithinMinutes?: number\n standing: string[]\n /** True when nothing in config mentions this client — derived only. */\n declared: boolean\n}\n\nexport const UNMAPPED = \"unmapped\"\n\n/** Explicit `business.projects[].client` wins; otherwise the leading segment\n * of the id. Works for \"owner/repo\" on any forge and for bare internal ids. */\nexport function clientFromProject(project: string | null | undefined, projects: ProjectRule[] = []): string {\n if (!project) return UNMAPPED\n const hit = projects.find(p => p.id === project)\n if (hit?.client) return hit.client\n const slash = project.indexOf(\"/\")\n return slash > 0 ? project.slice(0, slash) : project\n}\n\n/** agent id -> client, from project PMs then transitively down reportsTo.\n * A mesh dispatch carries no project, so without this it is unattributable. */\nexport function agentClients(business: BusinessShape): Map<string, string> {\n const out = new Map<string, string>()\n const projects = business.projects ?? []\n const orgChart = business.orgChart ?? {}\n for (const p of projects) {\n if (p.pm) if (!out.has(p.pm)) out.set(p.pm, clientFromProject(p.id, projects))\n }\n const climb = (agentId: string, seen = new Set<string>()): string | undefined => {\n if (out.has(agentId)) return out.get(agentId)\n if (seen.has(agentId)) return undefined\n seen.add(agentId)\n const up = orgChart[agentId]?.reportsTo\n if (!up) return undefined\n const c = climb(up, seen)\n if (c) out.set(agentId, c)\n return c\n }\n for (const agentId of Object.keys(orgChart)) climb(agentId)\n return out\n}\n\n/** chatId > username > senderId > channel-default. First match wins. */\nexport function matchContact(\n channel: string | undefined,\n who: { chatId?: string; username?: string; senderId?: string },\n contactMap: ContactRule[] = [],\n): ContactRule | undefined {\n if (!contactMap.length) return undefined\n const pool = contactMap.filter(m => !m.channel || m.channel === channel)\n return pool.find(m => m.chatId && who.chatId && m.chatId === who.chatId)\n ?? pool.find(m => m.username && who.username && m.username === who.username)\n ?? pool.find(m => m.senderId && who.senderId && m.senderId === who.senderId)\n ?? pool.find(m => m.channel && !m.chatId && !m.username && !m.senderId)\n}\n\nexport interface WorkRef { agentId?: string; channel?: string; project?: string; chatId?: string; username?: string }\n\n/** The one entry point. Conversation rules beat project shape, which beats\n * the agent's place in the org, because a rule someone wrote by hand is\n * always more deliberate than a heuristic. */\nexport function resolveClient(ref: WorkRef, business: BusinessShape): string {\n const contact = matchContact(ref.channel, { chatId: ref.chatId, username: ref.username }, business.contactMap)\n if (contact) return contact.client\n if (ref.project) return clientFromProject(ref.project, business.projects)\n if (ref.agentId) {\n const byAgent = agentClients(business).get(ref.agentId)\n if (byAgent) return byAgent\n }\n return UNMAPPED\n}\n\n/** A monitor session id is \"<agent>:<channel>:<target...>\". Forge targets\n * carry the project, chat targets carry the chat id. */\nexport function parseWorkRef(sessionId: string, agentId?: string): WorkRef {\n const parts = String(sessionId || \"\").split(\":\")\n if (parts.length < 3) return { agentId }\n const [agent, channel, ...rest] = parts\n const target = rest.join(\":\")\n const ref: WorkRef = { agentId: agentId || agent, channel }\n if (channel === \"gitlab\" || channel === \"github\") {\n // \"owner/repo:issue:94\" and \"owner/repo:pipeline:10897\" -> \"owner/repo\"\n ref.project = target.split(\":\")[0]\n } else if (channel === \"cron\" || channel === \"api\") {\n // Nothing addressable: fall through to the agent's own client.\n } else {\n ref.chatId = target.split(\"@\")[0]\n }\n return ref\n}\n\nconst KIND_DEFAULT = (id: string): \"client\" | \"internal\" | \"own\" => (id === UNMAPPED ? \"internal\" : \"client\")\n\nexport function clientPolicy(id: string, business: BusinessShape): ResolvedClient {\n const p = business.clients?.[id]\n return {\n id,\n name: p?.name || id,\n kind: p?.kind ?? KIND_DEFAULT(id),\n respondWithinMinutes: parseDuration(p?.respondWithin),\n standing: p?.standing ?? [],\n declared: Boolean(p),\n }\n}\n\n/** Every client the config can produce: from projects, contacts and overrides. */\nexport function listClients(business: BusinessShape): ResolvedClient[] {\n const ids = new Set<string>()\n for (const p of business.projects ?? []) ids.add(clientFromProject(p.id, business.projects))\n for (const c of business.contactMap ?? []) ids.add(c.client)\n for (const id of Object.keys(business.clients ?? {})) ids.add(id)\n return [...ids].sort().map(id => clientPolicy(id, business))\n}\n\n/** \"4h\", \"90m\", \"2d\" -> minutes. Anything else is treated as no clock, so a\n * typo can never silently become an aggressive deadline. */\nexport function parseDuration(v: string | undefined): number | undefined {\n const m = /^(\\d+)\\s*([mhd])$/.exec(String(v ?? \"\").trim())\n if (!m) return undefined\n const n = Number(m[1])\n return m[2] === \"m\" ? n : m[2] === \"h\" ? n * 60 : n * 1440\n}\n\n/** Whether an agent may act unattended for this client. Deny by default, and\n * never for a paying client regardless of what is listed — an outward-facing\n * mistake on someone else's repo is not ours to make on a timer. */\nexport function mayProceedUnattended(client: ResolvedClient, capability: string): boolean {\n if (client.kind === \"client\") return false\n return client.standing.includes(\"*\") || client.standing.includes(capability)\n}\n","// --- In-band rich-message directive (`agentx:ui`) ---\n//\n// Agents emit rich messages (buttons, polls, media) by appending a fenced\n// block to their reply:\n//\n// ```agentx:ui\n// { \"buttons\": [{ \"label\": \"Docs\", \"url\": \"https://…\" }],\n// \"poll\": { \"question\": \"Deploy now?\", \"options\": [\"Yes\", \"No\"] } }\n// ```\n//\n// A render layer lifts the JSON into an OutgoingMessage's buttons/poll/media\n// and strips the block from the visible text. Fail-safe by design: a missing\n// or malformed block leaves the text untouched and yields no directive, so a\n// half-written or invalid fence just renders as ordinary text.\n//\n// Chat channels render URL buttons. The phone app also renders quick-reply\n// chips (`\"quickReplies\": [\"Yes\", \"No\"]`) and reply buttons\n// (`{ \"label\": …, \"reply\": … }`): a tap sends that text as the user's next\n// message, exactly as if it had been typed. Other channels ignore them. A\n// `{ \"action\": … }` button (a callback that runs without a user message) is\n// parsed but skipped until the inbound callback_query plumbing lands (Phase 2).\n\nexport interface UiButton {\n label: string\n url: string\n}\n\n/** A button whose tap sends `reply` as the user's next message (phone app). */\nexport interface UiReply {\n label: string\n reply: string\n}\n\nexport interface UiPoll {\n question: string\n options: string[]\n multiple?: boolean\n}\n\nexport interface UiMedia {\n type: \"image\" | \"document\" | \"audio\" | \"video\"\n url: string\n caption?: string\n}\n\nexport interface UiDirective {\n buttons?: UiButton[]\n poll?: UiPoll\n media?: UiMedia\n /** Chips whose text is sent as the user's next message when tapped. */\n quickReplies?: string[]\n replies?: UiReply[]\n /** Labels of `action` (callback) buttons dropped because Phase 2 isn't wired\n * yet — surfaced so the caller can log them. */\n skippedActions?: string[]\n}\n\n/** The opening fence, matched case-insensitively at a line start. */\nconst FENCE_OPEN = /(^|\\n)[ \\t]*```[ \\t]*agentx:ui[ \\t]*\\r?\\n/i\n\n/** A complete fenced block: ```agentx:ui\\n<json>\\n``` (capturing the JSON). */\nconst FENCE_FULL = /(^|\\n)[ \\t]*```[ \\t]*agentx:ui[ \\t]*\\r?\\n([\\s\\S]*?)\\r?\\n[ \\t]*```[ \\t]*(?=\\n|$)/gi\n\nexport interface ExtractResult {\n cleanText: string\n ui?: UiDirective\n}\n\n/**\n * Extract the LAST complete `agentx:ui` block from `text`, returning the text\n * with that block removed plus the parsed directive. Never throws: on no\n * block, invalid JSON, or an empty directive, returns the original text and no\n * `ui`.\n */\nexport function extractUiDirective(text: string): ExtractResult {\n if (!text || !text.includes(\"agentx:ui\")) return { cleanText: text }\n\n // Find all complete blocks; the last one wins (agents sometimes narrate an\n // example fence earlier in the reply).\n const matches = [...text.matchAll(FENCE_FULL)]\n if (matches.length === 0) return { cleanText: text }\n const match = matches[matches.length - 1]\n const json = match[2]\n\n const ui = parseDirective(json)\n if (!ui) return { cleanText: text } // malformed → leave text intact\n\n // Remove exactly the matched block, preserving the leading newline group.\n const start = match.index ?? 0\n const leading = match[1] ?? \"\"\n const cleanText = (text.slice(0, start) + leading + text.slice(start + match[0].length)).trim()\n return { cleanText, ui }\n}\n\n/**\n * During streaming, hide a partial or complete directive so a half-written\n * fence never flashes as raw text. Cuts from the first `agentx:ui` opening\n * fence to the end of the string. Cheap and lossy-forward-only — the final\n * `extractUiDirective` does the authoritative parse.\n */\nexport function stripUiDirectiveForPreview(text: string): string {\n if (!text || !text.includes(\"agentx:ui\")) return text\n const m = FENCE_OPEN.exec(text)\n if (!m) return text\n return text.slice(0, m.index).trimEnd()\n}\n\n/** Parse + validate the directive JSON. Returns null on any problem or when\n * nothing renderable survives. */\nfunction parseDirective(json: string): UiDirective | null {\n let raw: any\n try {\n raw = JSON.parse(json)\n } catch {\n return null\n }\n if (!raw || typeof raw !== \"object\") return null\n\n const ui: UiDirective = {}\n\n if (Array.isArray(raw.buttons)) {\n const buttons: UiButton[] = []\n const replies: UiReply[] = []\n const skippedActions: string[] = []\n for (const b of raw.buttons) {\n if (!b || typeof b.label !== \"string\") continue\n if (typeof b.url === \"string\" && /^https?:\\/\\//i.test(b.url)) {\n buttons.push({ label: b.label, url: b.url })\n } else if (typeof b.reply === \"string\" && b.reply.trim()) {\n replies.push({ label: b.label, reply: b.reply.trim() })\n } else if (b.action != null) {\n skippedActions.push(b.label) // Phase 2\n }\n }\n if (buttons.length) ui.buttons = buttons\n if (replies.length) ui.replies = replies\n if (skippedActions.length) ui.skippedActions = skippedActions\n }\n\n if (raw.poll && typeof raw.poll === \"object\") {\n const q = raw.poll.question\n const opts = raw.poll.options\n if (typeof q === \"string\" && q.trim() && Array.isArray(opts) && opts.filter((o: unknown) => typeof o === \"string\").length >= 2) {\n ui.poll = {\n question: q,\n options: opts.filter((o: unknown) => typeof o === \"string\"),\n multiple: raw.poll.multiple === true,\n }\n }\n }\n\n if (raw.media && typeof raw.media === \"object\") {\n const { type, url, caption } = raw.media\n if ([\"image\", \"document\", \"audio\", \"video\"].includes(type) && typeof url === \"string\" && url) {\n ui.media = { type, url, ...(typeof caption === \"string\" ? { caption } : {}) }\n }\n }\n\n if (Array.isArray(raw.quickReplies)) {\n const chips = raw.quickReplies.filter((q: unknown) => typeof q === \"string\" && q.trim()).map((q: string) => q.trim())\n if (chips.length) ui.quickReplies = chips\n }\n\n // Nothing renderable (and no skipped actions worth reporting) → treat as\n // \"no directive\" so the block is left in the text rather than silently\n // vanishing.\n if (!ui.buttons && !ui.poll && !ui.media && !ui.quickReplies && !ui.replies && !ui.skippedActions) return null\n return ui\n}\n","import type { ServerResponse } from \"http\"\n\n// --- WebRTC signal broker ---\n//\n// AgentX itself never carries audio/video frames. This broker only relays the\n// control-plane messages (SDP offer/answer + ICE candidates + hangup) between a\n// local browser and a remote peer's browser — the media connection is negotiated\n// directly between the two browsers via WebRTC (SRTP over UDP).\n//\n// Flow:\n// Alice's browser -- SSE + POST --> agentx-A -- HTTP /webrtc/signal --> agentx-B -- SSE --> Bob's browser\n//\n// Subscribers index by (callId, recipient). A call is two-sided so there are\n// typically two subscribers per callId: one on each daemon.\n\nexport type WebRtcSignalKind = \"offer\" | \"answer\" | \"ice\" | \"hangup\" | \"ring\"\n\nexport interface WebRtcSignal {\n kind: WebRtcSignalKind\n callId: string\n /** Agent/peer name the signal is FROM (which daemon sent it). */\n from: string\n /** Agent/peer name the signal is FOR (which local subscriber should receive). */\n to: string\n /** SDP blob for offer/answer. */\n sdp?: string\n /** ICE candidate payload. */\n candidate?: {\n candidate: string\n sdpMid: string | null\n sdpMLineIndex: number | null\n usernameFragment?: string | null\n }\n /** Optional short reason for hangup/ring. */\n reason?: string\n}\n\n/** A subscriber may be either an SSE-backed browser (we write the raw event\n * stream into its ServerResponse) OR an in-process consumer like the\n * WebRtcBot (we just call its callback). One Set, two delivery shapes. */\ntype SubscriberSink =\n | { kind: \"sse\"; res: ServerResponse }\n | { kind: \"callback\"; deliver: (signal: WebRtcSignal) => void }\n\ninterface Subscriber {\n callId: string\n recipient: string\n sink: SubscriberSink\n}\n\ninterface BufferedSignal {\n signal: WebRtcSignal\n expiresAt: number\n}\n\n/** How long (ms) to hold a signal that arrived with no matching subscriber.\n * 30s comfortably covers the \"caller offered before callee joined\" race\n * without keeping stale ICE around long enough to confuse a later call. */\nconst BUFFER_TTL_MS = 30_000\n\nexport interface WebRtcSignalForwarder {\n /** POST the signal to the named mesh peer's /webrtc/signal endpoint.\n * Returns true on 2xx. */\n (peerName: string, signal: WebRtcSignal): Promise<boolean>\n}\n\nexport interface RingHandler {\n /** Invoked when a ring signal arrives from a remote peer. The broker\n * dedups by callId so this fires at most once per call over a short TTL. */\n (signal: WebRtcSignal): void | Promise<void>\n}\n\n/** The \"someone is calling\" message a ring sends to ringNotify targets. A\n * phone sharing its camera (#325) gets a watch-only link: that page opens\n * no camera or microphone and sends the offer itself. */\nexport function ringNotice(signal: WebRtcSignal, urlBase: string): string {\n const link = `${urlBase}/call?to=${encodeURIComponent(signal.from)}&callId=${encodeURIComponent(signal.callId)}`\n return signal.reason === \"camera\"\n ? `📷 ${signal.from} is sharing a phone camera — tap to watch: ${link}&watch=1`\n : `📞 ${signal.from} is calling — tap to join: ${link}`\n}\n\n/** Dedup window for ring notifications. A single call shouldn't ping the\n * callee's channels twice, but two distinct calls on the same day must\n * each notify. Five minutes comfortably covers retry-after-missed-call\n * without muting legitimate repeat dials. */\nconst RING_DEDUP_TTL_MS = 5 * 60_000\n\n/** Normalize a peer/node name for tolerant matching: lowercase and strip any\n * non-alphanumeric characters. This papers over the fact that a node's\n * self-reported `node.name` and the name other peers use for it in their\n * `mesh.peers[].name` list frequently disagree on case, spaces, or hyphens\n * (\"HQ-Local\" vs \"hq-local\" vs \"hq_local\"). Signaling is\n * looser than agent dispatch because a single call is a tight two-party\n * negotiation where ambiguity is vanishingly unlikely. */\nexport function normalizeName(name: string): string {\n return name.toLowerCase().replace(/[^a-z0-9]/g, \"\")\n}\n\nexport class WebRtcSignalBroker {\n private subs: Set<Subscriber> = new Set()\n private log: (...args: unknown[]) => void\n private forwarder?: WebRtcSignalForwarder\n /** Local peer/agent name — used when a browser client identifies as \"us\". */\n private localName: string\n /** Peer names allowed to initiate *inbound* calls. Empty means allow all.\n * Stored pre-normalized so every comparison is apples-to-apples. */\n private allowedCallers: Set<string>\n /** Signals that arrived before their matching subscriber showed up.\n * Keyed by `${callId}|${normalizedRecipient}`; flushed on subscribe. */\n private pending: Map<string, BufferedSignal[]> = new Map()\n private cleanupTimer?: ReturnType<typeof setInterval>\n /** Ring notification callback (set by daemon wiring). */\n private ringHandler?: RingHandler\n /** Recent ring callIds, for dedup. Value is the expiry timestamp. */\n private recentRings: Map<string, number> = new Map()\n\n constructor(\n localName: string,\n allowedCallers: string[] = [],\n log: (...args: unknown[]) => void = console.error.bind(console, \"[webrtc]\"),\n ) {\n this.localName = localName\n this.allowedCallers = new Set(allowedCallers.map(normalizeName))\n this.log = log\n // Sweep expired buffered signals every 10s.\n this.cleanupTimer = setInterval(() => this.cleanupExpired(), 10_000)\n }\n\n private pendingKey(callId: string, recipient: string): string {\n return `${callId}|${normalizeName(recipient)}`\n }\n\n private cleanupExpired(): void {\n const now = Date.now()\n for (const [key, bucket] of this.pending) {\n const kept = bucket.filter(b => b.expiresAt > now)\n if (kept.length === 0) this.pending.delete(key)\n else if (kept.length !== bucket.length) this.pending.set(key, kept)\n }\n for (const [callId, expiry] of this.recentRings) {\n if (expiry <= now) this.recentRings.delete(callId)\n }\n }\n\n setForwarder(fn: WebRtcSignalForwarder): void {\n this.forwarder = fn\n }\n\n setRingHandler(fn: RingHandler): void {\n this.ringHandler = fn\n }\n\n /** Register an SSE subscriber (a browser tab). Returns an unsubscribe fn. */\n subscribe(callId: string, recipient: string, res: ServerResponse): () => void {\n return this.addSubscriber(callId, recipient, { kind: \"sse\", res })\n }\n\n /** Register an in-process subscriber (a server-side bot). The callback fires\n * for every signal addressed to `recipient` on `callId`. Returns unsubscribe. */\n subscribeInternal(callId: string, recipient: string, deliver: (signal: WebRtcSignal) => void): () => void {\n return this.addSubscriber(callId, recipient, { kind: \"callback\", deliver })\n }\n\n private addSubscriber(callId: string, recipient: string, sink: SubscriberSink): () => void {\n const sub: Subscriber = { callId, recipient, sink }\n this.subs.add(sub)\n const tag = sink.kind === \"sse\" ? \"sse\" : \"internal\"\n this.log(`subscribe[${tag}] call=${callId} as=${recipient} (${this.subs.size} total)`)\n\n // Flush any signals that arrived before this subscriber. Common race: the\n // caller offers before the callee opens its page, or SSE connects slightly\n // after the POST that sent the offer.\n const key = this.pendingKey(callId, recipient)\n const buffered = this.pending.get(key)\n if (buffered && buffered.length) {\n const now = Date.now()\n const fresh = buffered.filter(b => b.expiresAt > now)\n for (const { signal } of fresh) {\n this.deliverTo(sub, signal)\n }\n this.pending.delete(key)\n if (fresh.length) this.log(`flushed ${fresh.length} buffered signal(s) to new subscriber call=${callId} as=${recipient}`)\n }\n\n return () => {\n this.subs.delete(sub)\n this.log(`unsubscribe[${tag}] call=${callId} as=${recipient} (${this.subs.size} remaining)`)\n }\n }\n\n /** Deliver a single signal to a subscriber, regardless of sink kind.\n * Returns false if the sink looks dead (caller should drop the sub). */\n private deliverTo(sub: Subscriber, signal: WebRtcSignal): boolean {\n try {\n if (sub.sink.kind === \"sse\") {\n sub.sink.res.write(`event: signal\\ndata: ${JSON.stringify(signal)}\\n\\n`)\n } else {\n sub.sink.deliver(signal)\n }\n return true\n } catch {\n return false\n }\n }\n\n /** Called when a browser POSTs a signal. Routes locally if the target is\n * this node OR matches a local in-process subscriber (e.g. the AI bot\n * peer); otherwise forwards through the mesh. */\n async handleOutgoing(signal: WebRtcSignal): Promise<{ ok: boolean; error?: string }> {\n // Local fan-out: addressed to ourselves (loopback) OR addressed to a\n // peer we're hosting in-process (the bot subscribes via callback, not via\n // mesh — its name like \"bot:atlas\" isn't in mesh.peers[]).\n const wantRecipient = normalizeName(signal.to)\n const hasLocalSub = wantRecipient === normalizeName(this.localName) || this.hasSubscriberFor(signal.callId, wantRecipient)\n if (hasLocalSub) {\n this.fanOut(signal)\n return { ok: true }\n }\n if (!this.forwarder) {\n return { ok: false, error: \"mesh forwarder not wired\" }\n }\n const ok = await this.forwarder(signal.to, signal)\n return ok ? { ok } : { ok: false, error: `forward to \"${signal.to}\" failed` }\n }\n\n private hasSubscriberFor(callId: string, normalizedRecipient: string): boolean {\n for (const sub of this.subs) {\n if (sub.callId === callId && normalizeName(sub.recipient) === normalizedRecipient) return true\n }\n return false\n }\n\n /** Called when the daemon receives a signal from a remote peer via /webrtc/signal. */\n handleIncoming(signal: WebRtcSignal): { ok: boolean; error?: string } {\n // Enforce allowedCallers on the *first* signal of a call (offer or ring).\n // Answers/ICE/hangup for an already-established call go through — the\n // authoritative gate is the offer.\n if ((signal.kind === \"offer\" || signal.kind === \"ring\") && this.allowedCallers.size > 0) {\n if (!this.allowedCallers.has(normalizeName(signal.from))) {\n this.log(`reject inbound ${signal.kind} from \"${signal.from}\": not in allowedCallers`)\n return { ok: false, error: \"caller not allowed\" }\n }\n }\n\n // Rings are purely for out-of-band notification — don't fan out over SSE\n // or buffer (they'd be noise on the callee's page). Dedup by callId so\n // the same call only pings the callee's channels once.\n if (signal.kind === \"ring\") {\n const now = Date.now()\n const expiry = this.recentRings.get(signal.callId)\n if (expiry && expiry > now) {\n this.log(`dedup ring for call=${signal.callId} (already notified)`)\n return { ok: true }\n }\n this.recentRings.set(signal.callId, now + RING_DEDUP_TTL_MS)\n if (this.ringHandler) {\n try { void this.ringHandler(signal) }\n catch (e: any) { this.log(`ringHandler threw: ${e.message}`) }\n } else {\n this.log(`ring received for call=${signal.callId} but no handler wired`)\n }\n return { ok: true }\n }\n\n const delivered = this.fanOut(signal)\n if (delivered === 0) {\n // Callee's page isn't up yet. Buffer the signal so the subscribe() call\n // that lands within BUFFER_TTL_MS replays it. Keeps the flow robust to\n // normal race conditions (SSE reconnects, slow first paint) without\n // forcing the caller to implement retry.\n const key = this.pendingKey(signal.callId, signal.to)\n const bucket = this.pending.get(key) || []\n bucket.push({ signal, expiresAt: Date.now() + BUFFER_TTL_MS })\n this.pending.set(key, bucket)\n this.log(`buffered ${signal.kind} for call=${signal.callId} to=${signal.to} (no subscriber yet, ${bucket.length} pending)`)\n }\n return { ok: true }\n }\n\n private fanOut(signal: WebRtcSignal): number {\n let delivered = 0\n const wantRecipient = normalizeName(signal.to)\n for (const sub of this.subs) {\n if (sub.callId !== signal.callId) continue\n if (normalizeName(sub.recipient) !== wantRecipient) continue\n if (this.deliverTo(sub, signal)) delivered++\n else this.subs.delete(sub)\n }\n return delivered\n }\n\n /** Active subscriber count — for /mesh or admin diagnostics. */\n get subscriberCount(): number {\n return this.subs.size\n }\n\n /** Close every open SSE stream (daemon shutdown). Internal subscribers\n * are simply dropped — they're owned by the bot manager which has its own\n * shutdown path. */\n shutdown(): void {\n if (this.cleanupTimer) clearInterval(this.cleanupTimer)\n for (const sub of this.subs) {\n if (sub.sink.kind === \"sse\") {\n try { sub.sink.res.end() } catch { /* already closed */ }\n }\n }\n this.subs.clear()\n this.pending.clear()\n }\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\nimport { closeSync, createReadStream, fstatSync, openSync, realpathSync } from \"fs\"\nimport { isAbsolute, resolve, sep } from \"path\"\nimport { ARTIFACT_LIMITS, artifactType } from \"@/utils/artifact-sentinel\"\nimport { OUTBOX_DIR } from \"@/utils/app-outbox\"\n\n// --- GET /app-files: one file an agent declared, for the phone app ---\n//\n// The daemon half of /api/app/files/:id (app-files.ts). The dashboard keeps\n// which files each answer declared and which phone may see them; it asks\n// the daemon that ran the agent for the file by agent and declared path.\n// Mesh-gated (isMeshGatedPath): a same-host dashboard comes over loopback,\n// a dashboard on another computer with a mesh token.\n//\n// Whatever the path says, only a regular file whose real location (links\n// followed) is inside the agent's workspace is served, of an allowed type\n// and size, and never as something a browser would run.\n\nexport const APP_FILES_PATH = \"/app-files\"\n\nconst AGENT_RE = /^[A-Za-z0-9_.:-]{1,64}$/\n\nexport interface AppFilesApiDeps {\n /** The agent's workspace as configured, or null for an unknown agent. */\n workspaceOf: (agentId: string) => string | null\n /** Relative workspaces resolve against it (the daemon's cwd). */\n cwd?: string\n /** Refusals are logged, so the agent's owner (and the agent, reading the\n * daemon log) learns why a file never reached the phone. */\n log?: (message: string) => void\n}\n\n/** Where a declared path really is, or why it may not be served. */\nexport function resolveArtifact(\n workspace: string, file: string, cwd = process.cwd(),\n): { ok: true; path: string; mime: string; kind: string } | { ok: false; status: number; error: string } {\n if (!file || file.length > ARTIFACT_LIMITS.pathChars || file.includes(\"\\0\")) return { ok: false, status: 400, error: \"bad file path\" }\n if (file.split(/[\\\\/]/).includes(\"..\")) return { ok: false, status: 403, error: `the path leaves the workspace: copy the file into ${OUTBOX_DIR}/ first` }\n if (!artifactType(file)) return { ok: false, status: 415, error: \"this type of file is not served\" }\n let root: string\n try { root = realpathSync(resolve(cwd, workspace)) } catch { return { ok: false, status: 404, error: \"the agent's workspace is missing\" } }\n let real: string\n try { real = realpathSync(isAbsolute(file) ? file : resolve(root, file)) } catch { return { ok: false, status: 404, error: \"file not found\" } }\n if (!real.startsWith(root + sep)) return { ok: false, status: 403, error: `the file is outside the agent's workspace: copy it into ${OUTBOX_DIR}/ first` }\n // A link inside the workspace may point at a file of another type.\n const type = artifactType(real)\n if (!type) return { ok: false, status: 415, error: \"this type of file is not served\" }\n return { ok: true, path: real, ...type }\n}\n\n/** `inline` for pictures, sound and video; everything else downloads. */\nexport function contentDisposition(name: string, kind: string): string {\n const ascii = name.replace(/[^\\x20-\\x7e]/g, \"_\").replace(/[\"\\\\;]/g, \"_\").slice(0, 120) || \"file\"\n return `${kind === \"file\" ? \"attachment\" : \"inline\"}; filename=\"${ascii}\"; filename*=UTF-8''${encodeURIComponent(name.slice(0, 120))}`\n}\n\n/** Headers every served file carries, on the daemon and the dashboard. */\nexport function fileHeaders(mime: string, name: string, kind: string): Record<string, string> {\n return {\n \"Content-Type\": mime,\n \"Content-Disposition\": contentDisposition(name, kind),\n \"X-Content-Type-Options\": \"nosniff\",\n // Opened on its own, the file runs nothing (an SVG or HTML-looking text).\n \"Content-Security-Policy\": \"default-src 'none'; img-src 'self'; media-src 'self'; sandbox\",\n \"Cache-Control\": \"private, max-age=3600\",\n \"Accept-Ranges\": \"bytes\",\n }\n}\n\n/** One `bytes=a-b` range within `size`, or null for the whole file. A range\n * that can't be met is \"unsatisfiable\". */\nexport function parseRange(header: string | undefined, size: number): { start: number; end: number } | null | \"unsatisfiable\" {\n const m = /^bytes=(\\d*)-(\\d*)$/.exec(String(header || \"\").trim())\n if (!m || (!m[1] && !m[2])) return null\n let start: number, end: number\n if (!m[1]) { start = Math.max(0, size - Number(m[2])); end = size - 1 }\n else { start = Number(m[1]); end = m[2] ? Math.min(Number(m[2]), size - 1) : size - 1 }\n if (start > end || start >= size) return \"unsatisfiable\"\n return { start, end }\n}\n\nexport function handleAppFilesApi(req: IncomingMessage, res: ServerResponse, url: URL, deps: AppFilesApiDeps): void {\n const json = (status: number, body: unknown) => {\n res.writeHead(status, { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\", \"X-Content-Type-Options\": \"nosniff\" })\n res.end(JSON.stringify(body))\n }\n if (req.method !== \"GET\" && req.method !== \"HEAD\") return json(405, { error: \"GET only\" })\n const agent = url.searchParams.get(\"agent\") || \"\"\n const file = url.searchParams.get(\"file\") || \"\"\n if (!AGENT_RE.test(agent)) return json(400, { error: \"bad agent id\" })\n const workspace = deps.workspaceOf(agent)\n if (!workspace) return json(404, { error: `no agent \"${agent}\" on this computer` })\n const found = resolveArtifact(workspace, file, deps.cwd)\n if (!found.ok) {\n deps.log?.(`[app-files] refused ${JSON.stringify(file.slice(0, 200))} from ${agent}: ${found.error}`)\n return json(found.status, { error: found.error })\n }\n\n let fd: number\n try { fd = openSync(found.path, \"r\") } catch { return json(404, { error: \"file not found\" }) }\n let size: number\n try {\n // Checked on the open file, so it can't be swapped after the check.\n const st = fstatSync(fd)\n if (!st.isFile()) { closeSync(fd); return json(404, { error: \"not a file\" }) }\n size = st.size\n } catch { closeSync(fd); return json(404, { error: \"file not found\" }) }\n if (size > ARTIFACT_LIMITS.bytes) { closeSync(fd); return json(413, { error: `the file is larger than ${ARTIFACT_LIMITS.bytes / 1024 / 1024} MB` }) }\n\n const name = found.path.split(sep).pop() || \"file\"\n const headers = fileHeaders(found.mime, name, found.kind)\n const range = parseRange(req.headers.range, size)\n if (range === \"unsatisfiable\") {\n closeSync(fd)\n res.writeHead(416, { ...headers, \"Content-Range\": `bytes */${size}` })\n res.end()\n return\n }\n const { start, end } = range ?? { start: 0, end: size - 1 }\n res.writeHead(range ? 206 : 200, {\n ...headers,\n \"Content-Length\": String(size ? end - start + 1 : 0),\n ...(range ? { \"Content-Range\": `bytes ${start}-${end}/${size}` } : {}),\n })\n if (req.method === \"HEAD\" || size === 0) { closeSync(fd); res.end(); return }\n const stream = createReadStream(\"\", { fd, start, end, autoClose: true })\n stream.on(\"error\", () => res.destroy())\n res.on(\"close\", () => stream.destroy())\n stream.pipe(res)\n}\n","// --- Speaking out loud on the daemon's host, one line at a time ---\n//\n// With the system provider (the default) a line is spoken by `say` in the\n// agent's macOS voice and nothing leaves the machine. With ElevenLabs, each\n// line is sent the moment it is known, so its audio is usually ready\n// before the line ahead of it finishes playing. The queue that orders the\n// lines is SpeechOut (speaking-queue.ts): every voice on the daemon shares\n// one, so two voices never talk over each other.\n\nimport { spawn, type ChildProcess } from \"child_process\"\nimport { readFileSync, writeFileSync } from \"fs\"\nimport { tmpdir } from \"os\"\nimport { join } from \"path\"\nimport { warnOnce } from \"./system-voices\"\nimport { ensureSiriSay, isSiriVoice, siriSupported, type SiriHost } from \"./siri\"\nimport { detectLanguage } from \"./language\"\nimport type { SpeechKind } from \"./speaking-queue\"\n\n/** Which engine speaks a line, and in which voice. */\nexport interface VoiceRef {\n provider: \"system\" | \"elevenlabs\"\n /** ElevenLabs voice id; only used by the elevenlabs provider. */\n elevenlabs: string\n /** macOS voice identifier; null for the OS default voice. */\n system: string | null\n /** Identifiers per language (\"fr\", \"ar\"), for lines in that language. */\n languages?: Record<string, string | null>\n /** When ElevenLabs cannot speak, fall back to the system voice. */\n fallback: boolean\n /** Speaking speed, 1 (normal) when unset. */\n rate?: number\n /** Place in the speaking queue; normal when unset. */\n priority?: \"high\" | \"normal\" | \"low\"\n}\n\n/** Words a minute for `say` at this rate; its own default is about 175. */\nexport const sayRate = (rate?: number): number | null =>\n rate && rate !== 1 ? Math.round(175 * rate) : null\n\n/** ElevenLabs' speed setting, which only accepts 0.7 to 1.2. */\nexport const elevenLabsSpeed = (rate?: number): number | null =>\n rate && rate !== 1 ? Math.min(1.2, Math.max(0.7, rate)) : null\n\nexport interface Utterance {\n voice: VoiceRef\n text: string\n /** Called when this line starts playing. */\n onStart?: () => void\n /** Who says it and why; shown in the speaking queue. */\n agentId?: string\n kind?: SpeechKind\n}\n\n/** Audio for a line: an mp3 file, or null to speak it with the system voice. */\nexport type Synth = (u: Utterance, signal: AbortSignal) => Promise<string | null>\n/** Start playing; the returned process exits when playback ends. */\nexport type Play = (file: string | null, u: Utterance) => ChildProcess\n\nexport interface SpeechEvents {\n onStart?: (u: Utterance, at: number) => void\n onEnd?: (u: Utterance, at: number, completed: boolean) => void\n}\n\nexport function elevenLabsKey(): string | null {\n if (process.env.ELEVENLABS_API_KEY) return process.env.ELEVENLABS_API_KEY\n for (const p of [`${process.env.HOME}/.elevenlabs/key`, `${process.env.HOME}/.agentx/elevenlabs-key.txt`]) {\n try {\n const v = readFileSync(p, \"utf8\").trim()\n if (v) return v\n } catch { /* next */ }\n }\n return null\n}\n\n/** ElevenLabs Flash: the lowest-latency model, fine for short lines. */\nexport function elevenLabsSynth(key: string | null = elevenLabsKey()): Synth {\n let n = 0\n return async (u, signal) => {\n if (u.voice.provider === \"system\") return null\n if (!key) {\n if (u.voice.fallback) return null\n throw new Error(\"ElevenLabs is the voice provider but no key is set\")\n }\n try {\n const res = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${u.voice.elevenlabs}/stream?output_format=mp3_44100_128`, {\n method: \"POST\",\n headers: { \"xi-api-key\": key, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n text: u.text, model_id: \"eleven_flash_v2_5\",\n ...(elevenLabsSpeed(u.voice.rate) ? { voice_settings: { speed: elevenLabsSpeed(u.voice.rate) } } : {}),\n }),\n signal,\n })\n if (!res.ok) throw new Error(`ElevenLabs ${res.status}: ${(await res.text()).slice(0, 160)}`)\n const file = join(tmpdir(), `agentx-say-${process.pid}-${++n}.mp3`)\n writeFileSync(file, Buffer.from(await res.arrayBuffer()))\n return file\n } catch (e: any) {\n if (signal.aborted || !u.voice.fallback) throw e\n warnOnce(`elevenlabs:${String(e?.message ?? e).slice(0, 40)}`, `[voice] ElevenLabs failed (${String(e?.message ?? e).slice(0, 160)}); speaking with the system voice`)\n return null\n }\n }\n}\n\n/** The system voice for a line: its language's voice, else the default. */\nexport function systemVoiceFor(v: VoiceRef, text: string): string | null {\n const lang = v.languages && detectLanguage(text)\n return lang && v.languages && lang in v.languages ? v.languages[lang] : v.system\n}\n\n/** The `say` arguments for a line. A line that opens with \"-\" must not be\n * read as an option, so the text always comes from stdin. A Siri voice\n * cannot be named to `say`; it speaks as the OS default. */\nexport const sayArgs = (v: VoiceRef, text = \"\"): string[] => {\n const id = systemVoiceFor(v, text)\n const wpm = sayRate(v.rate)\n return [...(id && !isSiriVoice(id) ? [\"-v\", id] : []), ...(wpm ? [\"-r\", String(wpm)] : [])]\n}\n\n/** The command that speaks a line. On macOS every line goes through the\n * shared script: it switches the default for a Siri voice, holds the one\n * speaker lock, bounds the line, and is what `stopAllSpeakers` reaches. */\nexport function sayCommand(v: VoiceRef, text: string, siriSay: string | null): [string, string[]] {\n if (!siriSay) return [\"say\", sayArgs(v, text)]\n const id = systemVoiceFor(v, text)\n return [\"/bin/sh\", [siriSay, ...(id ? [id] : [])]]\n}\n\n/** How long a line may play before it is taken for hung: its length's\n * worth of speech with room to spare, capped. The shared script applies\n * the same bound to its own `say`. */\nexport function speakLimitMs(text: string): number {\n const words = text.split(/\\s+/).filter(Boolean).length\n return Math.min(300, 5 + Math.floor((words * 6) / 10)) * 1000\n}\n\n/** Silence every speaker on this host, AgentX Voice's lines included:\n * the line holding the shared script's lock stops (the voice is\n * restored) and every queued line is dropped. Never throws. */\nexport function stopAllSpeakers(script: string | null = siriSayScript()): void {\n if (!script) return\n try {\n const p = spawn(\"/bin/sh\", [script, \"--stop\"], { stdio: \"ignore\" })\n p.on(\"error\", () => {})\n } catch { /* nothing to stop */ }\n}\n\n/** Put back the Spoken Content voice a line killed outright (SIGKILL, a\n * crash) left switched, instead of waiting for the next line to do it.\n * Takes the shared lock like any line. Never throws. */\nexport function restoreSpokenVoice(script: string | null = siriSayScript()): void {\n if (!script) return\n try {\n const p = spawn(\"/bin/sh\", [script, \"--restore\"], { stdio: \"ignore\" })\n p.on(\"error\", () => {})\n } catch { /* the next line restores it */ }\n}\n\n/** The shared script's path on a Mac that can switch voices, written if\n * needed; null elsewhere, or when it cannot be written (plain `say` still\n * speaks). Nothing is written on a host that cannot switch. */\nexport function siriSayScript(host: SiriHost = {}, home?: string): string | null {\n if (!siriSupported(host)) return null\n try {\n return ensureSiriSay(home)\n } catch (e: any) {\n warnOnce(\"siri-say\", `[voice] could not write the Siri voice script (${String(e?.message ?? e).split(\"\\n\")[0]}); Siri voices will not switch`)\n return null\n }\n}\n\n/** afplay on macOS, mpg123 elsewhere; `say` when there is no audio file. */\nexport const systemPlay: Play = (file, u) => {\n if (!file) {\n const [cmd, args] = sayCommand(u.voice, u.text, siriSayScript())\n // The shared script takes the rate from its environment.\n const wpm = sayRate(u.voice.rate)\n const env = wpm ? { ...process.env, AGENTX_SAY_RATE: String(wpm) } : process.env\n const p = spawn(cmd, args, { stdio: [\"pipe\", \"ignore\", \"ignore\"], env })\n p.stdin?.on(\"error\", () => {})\n p.stdin?.end(u.text)\n return p\n }\n // The file is the queue's to delete: a paused line plays it again.\n return process.platform === \"darwin\"\n ? spawn(\"afplay\", [file], { stdio: \"ignore\" })\n : spawn(\"mpg123\", [\"-q\", file], { stdio: \"ignore\" })\n}\n","import { spawn } from \"child_process\"\nimport { accessSync, constants, readFileSync } from \"fs\"\nimport { homedir } from \"os\"\nimport { basename, delimiter, dirname, extname, join } from \"path\"\n\n// --- Speech to text for the phone app (and anything else holding a file) ---\n//\n// The same engine order as AgentX Voice on the Mac (Speech.swift): ElevenLabs\n// Scribe when a key is set, then a Whisper that runs on this computer. The\n// phone records whatever its browser can (webm/opus on Android and desktop,\n// mp4/aac on iPhone), so the local engines need ffmpeg to read it: without\n// ffmpeg they are not offered at all.\n//\n// Nothing here logs audio, a transcript or a file path; only which engine ran\n// and why one failed.\n\nexport type SttSetting = \"auto\" | \"elevenlabs\" | \"local\"\nexport type SttEngine = \"elevenlabs\" | \"mlx-whisper\" | \"whisper\"\n\n/** Bounds on one recording, checked by the dashboard and by the daemon.\n * The phone records at `bitsPerSecond`, so 2 minutes is under 1 MB; the\n * byte cap is 2 minutes at about twice that, room for a browser that\n * ignores the requested rate and for the container. The cap bounds bytes,\n * not minutes: a low-bitrate file holds far more than 2 minutes in 2 MB,\n * so only measuring it (with ffmpeg) bounds the length. */\nexport const AUDIO_LIMITS = {\n ms: 2 * 60 * 1000,\n bitsPerSecond: 64_000,\n bytes: 2 * 1024 * 1024,\n} as const\n\n/** The recording's length as the phone states it (X-Audio-Duration-Ms).\n * Required: a missing or malformed value is refused, as is one over the\n * cap (with 2 s of slack for the recorder's own rounding). */\nexport function checkDurationHeader(header: string | string[] | undefined): { status: 400 | 413; error: string } | null {\n const raw = Array.isArray(header) ? header[0] : header\n if (!raw || !/^\\d{1,9}$/.test(raw.trim())) return { status: 400, error: \"Say how long the recording is (X-Audio-Duration-Ms).\" }\n return Number(raw) > AUDIO_LIMITS.ms + 2000 ? { status: 413, error: tooLongMessage() } : null\n}\n\nexport const tooLongMessage = (): string => `The recording is longer than ${AUDIO_LIMITS.ms / 60000} minutes.`\n\nconst AUDIO_TYPES: Record<string, string> = {\n \"audio/webm\": \"webm\", \"video/webm\": \"webm\", \"audio/ogg\": \"ogg\",\n \"audio/mp4\": \"m4a\", \"video/mp4\": \"m4a\", \"audio/x-m4a\": \"m4a\", \"audio/aac\": \"aac\",\n \"audio/mpeg\": \"mp3\", \"audio/wav\": \"wav\", \"audio/x-wav\": \"wav\", \"audio/wave\": \"wav\",\n}\n\n/** The file extension for a recording's Content-Type, or null when it is\n * not audio this route takes. Codec parameters are ignored. */\nexport function audioExt(contentType: string | undefined): string | null {\n const base = String(contentType || \"\").split(\";\")[0].trim().toLowerCase()\n return AUDIO_TYPES[base] ?? null\n}\n\n/** What this computer has for speech to text. */\nexport interface SttHost {\n key: string | null\n mlx: string | null\n whisper: string | null\n ffmpeg: string | null\n}\n\n/** The engines to try, in order. Empty: voice input is not set up here. */\nexport function sttEngines(setting: SttSetting, host: SttHost): SttEngine[] {\n const out: SttEngine[] = []\n if (setting !== \"local\" && host.key) out.push(\"elevenlabs\")\n if (host.ffmpeg) {\n if (host.mlx) out.push(\"mlx-whisper\")\n else if (host.whisper) out.push(\"whisper\")\n }\n return out\n}\n\n/** Why voice input can't work on this computer, in words for the phone. */\nexport function sttSetupHint(setting: SttSetting, host: SttHost): string {\n if (setting === \"local\" && host.key && !host.ffmpeg) return \"Speech to text is set to local, and this computer has no ffmpeg.\"\n if ((host.mlx || host.whisper) && !host.ffmpeg) return \"This computer has Whisper but no ffmpeg to read the recording.\"\n return \"Add an ElevenLabs key on this computer, or install mlx-whisper and ffmpeg.\"\n}\n\n/** Looks for an executable: an explicit path, then the usual install\n * places, then PATH. A launchd daemon's PATH is short, hence the list. */\nexport function findBinary(name: string, explicit?: string | null, pathEnv = process.env.PATH ?? \"\"): string | null {\n const home = homedir()\n const dirs = [join(home, \".local\", \"bin\"), \"/opt/homebrew/bin\", \"/usr/local/bin\", ...pathEnv.split(delimiter).filter(Boolean)]\n for (const p of [explicit, ...dirs.map((d) => join(d, name))]) {\n if (!p) continue\n try { accessSync(p, constants.X_OK); return p } catch { /* next */ }\n }\n return null\n}\n\nexport function detectSttHost(key: string | null): SttHost {\n return {\n key,\n mlx: findBinary(\"mlx_whisper\", process.env.AGENTX_MLX_WHISPER),\n whisper: findBinary(\"whisper\", process.env.AGENTX_WHISPER),\n ffmpeg: findFfmpeg(),\n }\n}\n\n/** The ffmpeg this process would use, from its own PATH (a launchd or\n * systemd service's PATH is not the shell's). */\nexport const findFfmpeg = (): string | null => findBinary(\"ffmpeg\", process.env.AGENTX_FFMPEG)\n\n/** Seconds of audio in a file, measured, not taken from the phone or from\n * the container's stated duration (which the sender writes): ffmpeg\n * decodes it. Decoding stops a little past the cap, so a long file costs\n * no more than a legal one. Null when ffmpeg is not here or can't read it. */\nexport async function measureSeconds(file: string, ffmpeg: string | null): Promise<number | null> {\n return ffmpeg ? decodeSeconds(file, ffmpeg) : null\n}\n\n/** Decodes up to the cap plus 5 s and reads ffmpeg's last \"time=\". */\nexport async function decodeSeconds(file: string, ffmpeg: string): Promise<number | null> {\n const limit = String(AUDIO_LIMITS.ms / 1000 + 5)\n try {\n const err = await runStderr(ffmpeg, [\"-nostdin\", \"-hide_banner\", \"-t\", limit, \"-i\", file, \"-f\", \"null\", \"-\"], 30_000)\n const times = [...err.matchAll(/time=(\\d+):(\\d+):(\\d+(?:\\.\\d+)?)/g)]\n const m = times[times.length - 1]\n return m ? Number(m[1]) * 3600 + Number(m[2]) * 60 + Number(m[3]) : null\n } catch { return null }\n}\n\n/** ElevenLabs Scribe. The recording goes as it is: Scribe reads webm and mp4. */\nexport async function elevenLabsScribe(file: string, mime: string, key: string, signal?: AbortSignal): Promise<string> {\n const fd = new FormData()\n fd.set(\"model_id\", process.env.AGENTX_STT_MODEL || \"scribe_v1\")\n fd.set(\"file\", new Blob([readFileSync(file)], { type: mime }), basename(file))\n const r = await fetch(\"https://api.elevenlabs.io/v1/speech-to-text\", {\n method: \"POST\", headers: { \"xi-api-key\": key }, body: fd, signal: signal ?? AbortSignal.timeout(60_000),\n })\n if (!r.ok) throw new Error(`ElevenLabs ${r.status}: ${(await r.text().catch(() => \"\")).slice(0, 160)}`)\n const j = await r.json() as { text?: unknown }\n if (typeof j.text !== \"string\") throw new Error(\"ElevenLabs returned no text\")\n return j.text.trim()\n}\n\n/** A Whisper CLI on this computer. Both write <name>.txt into `outDir`;\n * stdout is never taken as the words (mlx_whisper prints \"Skipping …\" there\n * and exits 0 when it fails). */\nexport async function localWhisper(engine: \"mlx-whisper\" | \"whisper\", bin: string, file: string, ffmpeg: string, outDir: string): Promise<string> {\n const args = engine === \"mlx-whisper\"\n ? [\"--model\", process.env.AGENTX_MLX_MODEL || \"mlx-community/whisper-large-v3-turbo\", \"--output-format\", \"txt\", \"--output-dir\", outDir, file]\n : [file, \"--model\", process.env.AGENTX_WHISPER_MODEL || \"base\", \"--output_format\", \"txt\", \"--output_dir\", outDir, \"--fp16\", \"False\"]\n // Both read the recording through ffmpeg, found by PATH.\n const env = { ...process.env, PATH: `${dirname(ffmpeg)}${delimiter}${process.env.PATH ?? \"\"}` }\n await run(bin, args, 120_000, env)\n try {\n return readFileSync(join(outDir, basename(file, extname(file)) + \".txt\"), \"utf8\").trim()\n } catch {\n throw new Error(`${engine} wrote no transcript`)\n }\n}\n\n/** Runs to the end and resolves with the tail of stderr (ffmpeg's progress). */\nfunction runStderr(cmd: string, args: string[], timeoutMs: number): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"ignore\", \"pipe\"] })\n let err = \"\"\n const timer = setTimeout(() => { child.kill(\"SIGKILL\"); reject(new Error(`${basename(cmd)} took too long`)) }, timeoutMs)\n child.stderr.on(\"data\", (d) => { err = (err + d).slice(-8192) })\n child.on(\"error\", (e) => { clearTimeout(timer); reject(e) })\n child.on(\"exit\", (code) => { clearTimeout(timer); code === 0 ? resolve(err) : reject(new Error(`${basename(cmd)} exited ${code}`)) })\n })\n}\n\nfunction run(cmd: string, args: string[], timeoutMs: number, env: NodeJS.ProcessEnv = process.env): Promise<string> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { stdio: [\"ignore\", \"pipe\", \"pipe\"], env })\n let out = \"\"\n let err = \"\"\n const timer = setTimeout(() => { child.kill(\"SIGKILL\"); reject(new Error(`${basename(cmd)} took longer than ${timeoutMs / 1000}s`)) }, timeoutMs)\n child.stdout.on(\"data\", (d) => { if (out.length < 4096) out += d })\n child.stderr.on(\"data\", (d) => { if (err.length < 4096) err += d })\n child.on(\"error\", (e) => { clearTimeout(timer); reject(e) })\n child.on(\"exit\", (code) => {\n clearTimeout(timer)\n if (code === 0) resolve(out)\n else reject(new Error(`${basename(cmd)} exited ${code}: ${err.trim().split(\"\\n\").pop()?.slice(0, 160) ?? \"\"}`))\n })\n })\n}\n","// --- Written answer to spoken answer ---\n//\n// Shared by the daemon's voice replies (/ask, talk, voice history) and the\n// phone app's spoken answers (voice-io-api.ts). Moved out of index.ts\n// unchanged, with the length cap as a parameter.\n\n/**\n * Convert text to TTS-friendly speech.\n * Strips markdown, expands technical terms, removes code, handles symbols.\n */\nexport function toSpeakable(text: string, max = 800): string {\n let s = text\n\n // Remove code blocks entirely (don't read code aloud)\n s = s.replace(/```[\\s\\S]*?```/g, \". \")\n s = s.replace(/`[^`]*`/g, \"\") // inline code\n\n // Remove markdown syntax characters\n s = s.replace(/\\*\\*(.*?)\\*\\*/g, \"$1\") // bold\n s = s.replace(/__(.*?)__/g, \"$1\") // bold\n s = s.replace(/\\*([^*]+)\\*/g, \"$1\") // italic\n s = s.replace(/_([^_]+)_/g, \"$1\") // italic\n s = s.replace(/~~(.*?)~~/g, \"$1\") // strikethrough\n\n // Links: keep text, drop URL\n s = s.replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n\n // Images: describe\n s = s.replace(/!\\[([^\\]]*)\\]\\([^)]+\\)/g, \"image$1 \")\n\n // Headers: remove # markers\n s = s.replace(/^#{1,6}\\s+/gm, \"\")\n\n // List bullets: convert to pauses\n s = s.replace(/^[\\s]*[-*+]\\s+/gm, \". \")\n s = s.replace(/^[\\s]*\\d+\\.\\s+/gm, \". \")\n\n // Horizontal rules\n s = s.replace(/^[-=_]{3,}$/gm, \"\")\n\n // Blockquotes\n s = s.replace(/^>\\s*/gm, \"\")\n\n // URLs in plain text: replace with \"link\"\n s = s.replace(/https?:\\/\\/\\S+/g, \"link\")\n\n // File paths: keep readable\n s = s.replace(/\\/\\w+(\\/\\w+)+/g, (match) => match.split(\"/\").filter(Boolean).join(\" slash \"))\n\n // Technical symbols → words\n s = s.replace(/&/g, \" and \")\n s = s.replace(/@/g, \" at \")\n s = s.replace(/=>/g, \" returns \")\n s = s.replace(/->/g, \" to \")\n s = s.replace(/\\|/g, \" or \")\n\n // Common abbreviations\n s = s.replace(/\\be\\.g\\./gi, \"for example\")\n s = s.replace(/\\bi\\.e\\./gi, \"that is\")\n s = s.replace(/\\betc\\./gi, \"etcetera\")\n s = s.replace(/\\bvs\\.?\\b/gi, \"versus\")\n\n // Collapse whitespace\n s = s.replace(/\\n{2,}/g, \". \")\n s = s.replace(/\\n/g, \" \")\n s = s.replace(/\\s+/g, \" \")\n s = s.replace(/\\.\\s*\\./g, \".\")\n s = s.replace(/\\s+([.,!?])/g, \"$1\")\n\n // Strip leading/trailing whitespace and punctuation\n s = s.trim().replace(/^[.,:;]+\\s*/, \"\")\n\n // Cap for voice UX: 800 characters unless the caller asks otherwise\n if (s.length > max) {\n s = s.slice(0, max).replace(/\\s+\\S*$/, \"\") + \"...\"\n }\n\n return s\n}\n","import type { IncomingMessage, ServerResponse } from \"http\"\nimport { mkdtemp, rm, writeFile } from \"fs/promises\"\nimport { tmpdir } from \"os\"\nimport { join } from \"path\"\nimport { extractUiDirective } from \"@/channels/ui-directive\"\nimport { toSpeakable } from \"@/voice/speakable\"\nimport { extractArtifacts } from \"@/utils/artifact-sentinel\"\nimport { elevenLabsSpeed, type VoiceRef } from \"@/voice/speaker\"\nimport {\n AUDIO_LIMITS, audioExt, checkDurationHeader, elevenLabsScribe, localWhisper, measureSeconds, sttEngines, sttSetupHint,\n tooLongMessage, type SttEngine, type SttHost, type SttSetting,\n} from \"@/voice/transcribe\"\nimport { resolveAgentVoice, voiceRef } from \"@/voice/agent-voice\"\nimport type { MeshVoices } from \"@/voice/mesh-voice\"\nimport type { DaemonConfig } from \"./config\"\n\n// --- Voice in and out for the phone app: POST /voice/transcribe, /voice/speak ---\n//\n// The phone app's dashboard (app-voice.ts) checks the device token and\n// forwards here with the daemon token, because this process holds the\n// ElevenLabs key, the agents' voices and the `voice.stt` setting. Both\n// routes are mesh-gated for every caller off this box (isMeshGatedPath):\n// they spend the ElevenLabs quota and run Whisper on this computer.\n//\n// Audio is never logged and never kept: a recording lives in a private temp\n// folder for the length of the request and is deleted after it.\n\nexport const SPEAK_MAX_CHARS = 1500\nconst SPEAK_BODY_MAX = 64 * 1024\n/** Transcriptions running at once on this host; more wait for none, they\n * get 429. Whisper is heavy, and one phone rarely needs two. */\nexport const MAX_TRANSCRIBING = 2\nlet transcribing = 0\n\nexport function isVoiceIoPath(path: string): boolean {\n return path === \"/voice/transcribe\" || path === \"/voice/speak\"\n}\n\nexport interface VoiceIoDeps {\n stt: () => SttSetting\n host: () => SttHost\n /** The agent's voice; `peer` names the mesh peer for an agent over there. */\n voiceOf: (agentId: string, peer?: string) => VoiceRef | null\n elevenLabsKey: () => string | null\n log: (line: string) => void\n /** Tests swap the engines and the synthesiser. */\n engines?: Partial<Record<SttEngine, (file: string, mime: string, host: SttHost, dir: string) => Promise<string>>>\n measure?: (file: string, ffmpeg: string | null) => Promise<number | null>\n /** voice.allowUnmeasured: take recordings this host can't measure. */\n allowUnmeasured?: () => boolean\n /** This node's name, for the phone's setup hint. */\n nodeName?: string\n synth?: (key: string, voice: VoiceRef, text: string) => Promise<Buffer>\n}\n\nexport async function handleVoiceIo(req: IncomingMessage, res: ServerResponse, path: string, deps: VoiceIoDeps): Promise<void> {\n if (req.method !== \"POST\") return json(res, 405, { error: \"POST\" })\n if (path === \"/voice/transcribe\") return transcribe(req, res, deps)\n return speak(req, res, deps)\n}\n\nasync function transcribe(req: IncomingMessage, res: ServerResponse, deps: VoiceIoDeps): Promise<void> {\n const mime = String(req.headers[\"content-type\"] || \"\")\n const ext = audioExt(mime)\n if (!ext) return json(res, 415, { error: \"Send the recording as audio (webm, mp4, ogg, mp3 or wav).\" })\n const stated = checkDurationHeader(req.headers[\"x-audio-duration-ms\"])\n if (stated) { req.resume(); return json(res, stated.status, { error: stated.error }) }\n const audio = await readRaw(req, AUDIO_LIMITS.bytes)\n if (!audio) return json(res, 413, { error: tooBigMessage() })\n if (!audio.length) return json(res, 400, { error: \"The recording is empty.\" })\n if (transcribing >= MAX_TRANSCRIBING) return json(res, 429, { error: \"This computer is already writing down other recordings. Try again in a moment.\" })\n\n const setting = deps.stt()\n const host = deps.host()\n const engines = sttEngines(setting, host)\n // Without ffmpeg the length can't be measured, and the byte cap bounds\n // bytes, not minutes: refuse unless the owner opted out (#233).\n if (!host.ffmpeg && !deps.allowUnmeasured?.()) {\n const where = deps.nodeName ? `on ${deps.nodeName}` : \"on this computer\"\n const more = engines.length ? \"\" : \" \" + sttSetupHint(setting, host)\n return json(res, 503, { error: \"Voice input isn't set up on this computer.\", hint: `Install ffmpeg ${where} for voice input from the phone.${more}`, setup: false })\n }\n if (!engines.length) {\n return json(res, 503, { error: \"Voice input isn't set up on this computer.\", hint: sttSetupHint(setting, host), setup: false })\n }\n // The recording is deleted before the phone gets its answer.\n transcribing++\n let reply: [number, unknown]\n try {\n const dir = await mkdtemp(join(tmpdir(), \"agentx-phone-voice-\"))\n try {\n reply = await transcribeIn(dir, `speech.${ext}`, audio, mime, host, engines, deps)\n } finally {\n await rm(dir, { recursive: true, force: true }).catch(() => {})\n }\n } finally {\n transcribing--\n }\n json(res, reply[0], reply[1])\n}\n\nasync function transcribeIn(dir: string, name: string, audio: Buffer, mime: string, host: SttHost, engines: SttEngine[], deps: VoiceIoDeps): Promise<[number, unknown]> {\n const file = join(dir, name)\n await writeFile(file, audio, { mode: 0o600 })\n // The real length where this host can measure it, whatever the header\n // said. With ffmpeg, a recording it can't read is refused rather than\n // passed on unmeasured. Without ffmpeg (only with voice.allowUnmeasured)\n // just the byte cap applies, which bounds bytes, not minutes.\n if (host.ffmpeg) {\n const secs = await (deps.measure ?? measureSeconds)(file, host.ffmpeg)\n if (secs == null) return [422, { error: \"This computer couldn't read the recording. Try again.\" }]\n if (secs * 1000 > AUDIO_LIMITS.ms + 2000) return [413, { error: tooLongMessage() }]\n }\n const failures: string[] = []\n for (const engine of engines) {\n try {\n const text = await runEngine(engine, file, mime, host, dir, deps)\n deps.log(`[voice] phone recording transcribed with ${engine} (${Math.round(audio.length / 1024)} KB)`)\n if (!text) return [422, { error: \"No words were heard. Hold the orb and speak, then let go.\", engine }]\n return [200, { text, engine }]\n } catch (e: any) {\n const why = String(e?.message ?? e).split(\"\\n\")[0].slice(0, 200)\n failures.push(`${engine}: ${why}`)\n deps.log(`[voice] phone transcription with ${engine} failed: ${why}`)\n }\n }\n return [502, { error: \"Speech to text failed on this computer.\", detail: failures.join(\"; \") }]\n}\n\nfunction runEngine(engine: SttEngine, file: string, mime: string, host: SttHost, dir: string, deps: VoiceIoDeps): Promise<string> {\n const custom = deps.engines?.[engine]\n if (custom) return custom(file, mime, host, dir)\n if (engine === \"elevenlabs\") return elevenLabsScribe(file, mime, host.key!)\n const bin = engine === \"mlx-whisper\" ? host.mlx! : host.whisper!\n return localWhisper(engine, bin, file, host.ffmpeg!, dir)\n}\n\nasync function speak(req: IncomingMessage, res: ServerResponse, deps: VoiceIoDeps): Promise<void> {\n const raw = await readRaw(req, SPEAK_BODY_MAX)\n if (!raw) return json(res, 413, { error: \"request too large\" })\n let body: any\n try { body = JSON.parse(raw.toString(\"utf8\") || \"{}\") } catch { return json(res, 400, { error: \"expected JSON\" }) }\n const agent = typeof body?.agent === \"string\" ? body.agent : \"\"\n const peer = typeof body?.peer === \"string\" && body.peer ? body.peer : undefined\n if (!agent) return json(res, 400, { error: \"agent is required\" })\n const text = speakableAnswer(typeof body?.text === \"string\" ? body.text : \"\")\n if (!text) return json(res, 422, { error: \"Nothing in this answer can be said aloud.\" })\n\n const voice = deps.voiceOf(agent, peer)\n if (!voice) return json(res, 404, { error: `Unknown agent: ${agent}` })\n // No ElevenLabs voice for this agent: the phone speaks the same cleaned\n // text with its own voice. That is the normal case for system-voice\n // agents, so it is a 200, not an error.\n const browser = (why: string, status = 200) => json(res, status, { fallback: \"browser\", reason: why, text })\n if (voice.provider !== \"elevenlabs\") return browser(\"This agent speaks with a system voice, which only plays on the computer.\")\n const key = deps.elevenLabsKey()\n if (!key) return browser(\"No ElevenLabs key is set on this computer.\")\n try {\n const audio = await (deps.synth ?? elevenLabsAudio)(key, voice, text)\n res.writeHead(200, { \"Content-Type\": \"audio/mpeg\", \"Content-Length\": audio.length, \"Cache-Control\": \"no-store\", \"X-Spoken-Chars\": String(text.length) })\n res.end(audio)\n } catch (e: any) {\n deps.log(`[voice] phone answer synthesis failed: ${String(e?.message ?? e).split(\"\\n\")[0].slice(0, 160)}`)\n return browser(\"ElevenLabs could not speak this answer.\", 502)\n }\n}\n\n/** An answer as it is said: no agentx:ui block, no declared files, no\n * pictures, no markdown, capped. Pictures and files are only ever shown. */\nexport function speakableAnswer(text: string): string {\n const clean = extractUiDirective(extractArtifacts(text).text).cleanText\n .replace(/!\\[[^\\]\\n]*\\]\\([^)\\n]*\\)/g, \"\")\n return toSpeakable(clean, SPEAK_MAX_CHARS)\n}\n\n/** One answer as mp3, in the agent's ElevenLabs voice. */\nasync function elevenLabsAudio(key: string, voice: VoiceRef, text: string): Promise<Buffer> {\n const speed = elevenLabsSpeed(voice.rate)\n const r = await fetch(`https://api.elevenlabs.io/v1/text-to-speech/${encodeURIComponent(voice.elevenlabs)}?output_format=mp3_44100_128`, {\n method: \"POST\",\n headers: { \"xi-api-key\": key, \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ text, model_id: \"eleven_flash_v2_5\", ...(speed ? { voice_settings: { speed } } : {}) }),\n signal: AbortSignal.timeout(60_000),\n })\n if (!r.ok) throw new Error(`ElevenLabs ${r.status}: ${(await r.text().catch(() => \"\")).slice(0, 160)}`)\n return Buffer.from(await r.arrayBuffer())\n}\n\nexport const tooBigMessage = (): string =>\n `The recording is larger than ${AUDIO_LIMITS.bytes / 1024 / 1024} MB, more than ${AUDIO_LIMITS.ms / 60000} minutes of speech.`\n\n/** The voice for `agentId`: a local agent's when there is no peer, else the\n * agent of that id on that mesh peer exactly. Never another peer's agent\n * that happens to share the id. */\nexport function resolveVoice(\n agentId: string, peer: string | undefined,\n config: Pick<DaemonConfig, \"agents\" | \"voice\">, mesh: Pick<MeshVoices, \"speakerOn\" | \"speaker\">,\n): VoiceRef | null {\n if (peer) return mesh.speakerOn(peer, agentId, false)?.voice ?? null\n const agents = config.agents ?? {}\n return agents[agentId] ? voiceRef(resolveAgentVoice(agentId, agents, config.voice)) : null\n}\n\n/** The whole body, or null past `limit` (a declared length says so at\n * once). Past the limit the rest is read and dropped, not kept, so the\n * answer still reaches the sender. */\nexport function readRaw(req: IncomingMessage, limit: number): Promise<Buffer | null> {\n if (Number(req.headers[\"content-length\"]) > limit) { req.resume(); return Promise.resolve(null) }\n return new Promise((resolve, reject) => {\n const chunks: Buffer[] = []\n let size = 0\n req.on(\"data\", (chunk: Buffer) => {\n if (size > limit) return\n size += chunk.length\n if (size > limit) { chunks.length = 0; resolve(null); return }\n chunks.push(chunk)\n })\n req.on(\"end\", () => { if (size <= limit) resolve(Buffer.concat(chunks)) })\n req.on(\"error\", reject)\n })\n}\n\nfunction json(res: ServerResponse, status: number, body: unknown): void {\n res.writeHead(status, { \"Content-Type\": \"application/json\", \"Cache-Control\": \"no-store\" })\n res.end(JSON.stringify(body))\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync } from \"fs\"\nimport { resolve, relative, dirname, join, extname, basename } from \"path\"\n\n// --- Scoped file operations for the admin panel ---\n//\n// Lets the dashboard read/write identity files (CLAUDE.md / SOUL.md /\n// IDENTITY.md) and skill files (.claude/skills/<slug>/SKILL.md) WITHIN an\n// agent's workspace without ever touching the rest of the filesystem.\n// Every public function resolves the requested path against the agent's\n// workspace and refuses anything that would escape via .. symlinks, etc.\n\nconst MAX_FILE_BYTES = 200 * 1024 // 200 KB — plenty for CLAUDE.md / skills.\nconst ALLOWED_EXTENSIONS = new Set([\".md\", \".markdown\", \".txt\"])\n/** Extensions the skill-tree viewer is allowed to read/write. Broader than\n * identity editing — skills include shell scripts, lockfiles, configs. Kept\n * as an allow-list so a malicious prompt can't touch an arbitrary binary. */\nconst SKILL_EDITABLE_EXTENSIONS = new Set([\n \".md\", \".markdown\", \".txt\", \".json\", \".jsonc\", \".yaml\", \".yml\", \".toml\",\n \".sh\", \".bash\", \".zsh\", \".py\", \".js\", \".mjs\", \".ts\", \".tsx\", \".jsx\",\n \".css\", \".html\", \".lock\", \".cfg\", \".ini\", \".env\", \".gitignore\",\n])\n/** Depth limit when walking a skill dir tree — defensive against loops. */\nconst SKILL_TREE_MAX_DEPTH = 6\nconst IDENTITY_FILENAMES = new Set([\n \"CLAUDE.md\", \"SOUL.md\", \"IDENTITY.md\", \"PERSONA.md\", \"AGENT.md\", \"BOOTSTRAP.md\",\n])\n\nexport interface FileOverview {\n identity: Array<{\n path: string // relative to workspace, e.g. \"CLAUDE.md\"\n title: string\n size: number\n exists: boolean // canonical files can be listed as templates even if missing\n }>\n skills: Array<{\n slug: string // dir name under .claude/skills/\n title: string // first # heading in SKILL.md or fallback to slug\n size: number\n path: string // relative to workspace, e.g. \".claude/skills/foo/SKILL.md\"\n }>\n workspace: string // absolute, for display only\n}\n\n/**\n * Resolve a user-supplied path to an absolute path strictly within `workspace`.\n * Throws if the result would escape. Returns the normalised absolute path.\n */\nfunction resolveInWorkspace(workspace: string, relPath: string): string {\n if (!workspace) throw new Error(\"Agent workspace is not configured.\")\n const wsAbs = resolve(workspace)\n if (!existsSync(wsAbs)) {\n // Create the workspace lazily — a fresh agent scaffolded via the wizard\n // may have an empty directory created but not yet with file structure.\n mkdirSync(wsAbs, { recursive: true })\n }\n // Reject absolute-path attempts outright.\n if (!relPath || relPath.startsWith(\"/\") || relPath.match(/^[a-zA-Z]:/)) {\n throw new Error(`Path must be relative to the workspace: ${relPath}`)\n }\n const target = resolve(wsAbs, relPath)\n const rel = relative(wsAbs, target)\n // rel === \"\" means target IS the workspace dir — fine for list ops.\n // rel starts with \"..\" iff target is outside the workspace.\n if (rel.startsWith(\"..\")) {\n throw new Error(`Path escapes the workspace: ${relPath}`)\n }\n return target\n}\n\nfunction assertEditable(absPath: string): void {\n const ext = extname(absPath).toLowerCase()\n if (!ALLOWED_EXTENSIONS.has(ext)) {\n throw new Error(`Only text files (.md, .markdown, .txt) can be edited from the admin panel (got ${ext}).`)\n }\n}\n\nfunction extractTitle(markdown: string, fallback: string): string {\n const line = markdown.split(/\\r?\\n/).find((l) => l.trim().startsWith(\"#\"))\n if (!line) return fallback\n return line.replace(/^#+\\s*/, \"\").trim().slice(0, 120) || fallback\n}\n\n/**\n * List the identity files + skills for an agent. Identity \"canonical\" names\n * that don't exist are still reported (exists: false) so the UI can offer to\n * create them with a sensible default.\n */\nexport function listAgentFiles(workspace: string): FileOverview {\n const wsAbs = resolveInWorkspace(workspace, \".\")\n const identity: FileOverview[\"identity\"] = []\n const seen = new Set<string>()\n\n // Every .md in the workspace root is identity material; canonical names\n // sort first.\n let rootEntries: string[] = []\n try { rootEntries = readdirSync(wsAbs) } catch { /* empty */ }\n for (const name of IDENTITY_FILENAMES) {\n const abs = join(wsAbs, name)\n if (existsSync(abs) && statSync(abs).isFile()) {\n const size = statSync(abs).size\n const content = size > 0 && size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size, exists: true })\n seen.add(name)\n } else {\n identity.push({ path: name, title: name, size: 0, exists: false })\n seen.add(name)\n }\n }\n for (const name of rootEntries) {\n if (seen.has(name)) continue\n const abs = join(wsAbs, name)\n try {\n const st = statSync(abs)\n if (!st.isFile()) continue\n if (!ALLOWED_EXTENSIONS.has(extname(name).toLowerCase())) continue\n const content = st.size > 0 && st.size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size: st.size, exists: true })\n } catch { /* skip unreadable */ }\n }\n\n // Skills live under .claude/skills/<slug>/SKILL.md\n const skills: FileOverview[\"skills\"] = []\n const skillsRoot = join(wsAbs, \".claude\", \"skills\")\n if (existsSync(skillsRoot)) {\n try {\n for (const slug of readdirSync(skillsRoot)) {\n const dir = join(skillsRoot, slug)\n let st\n try { st = statSync(dir) } catch { continue }\n if (!st.isDirectory()) continue\n const file = join(dir, \"SKILL.md\")\n if (!existsSync(file)) continue\n const stf = statSync(file)\n const content = stf.size < MAX_FILE_BYTES ? safeRead(file) : \"\"\n skills.push({\n slug,\n title: extractTitle(content, slug),\n size: stf.size,\n path: `.claude/skills/${slug}/SKILL.md`,\n })\n }\n } catch { /* */ }\n }\n skills.sort((a, b) => a.slug.localeCompare(b.slug))\n\n return { identity, skills, workspace: wsAbs }\n}\n\nfunction safeRead(abs: string): string {\n try { return readFileSync(abs, \"utf-8\") } catch { return \"\" }\n}\n\n/**\n * Read a single file within the workspace. Throws if the path is outside the\n * workspace, has a disallowed extension, or doesn't exist.\n */\nexport function readAgentFile(workspace: string, relPath: string): { path: string; content: string } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n if (!existsSync(abs)) return { path: relPath, content: \"\" }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File is too large to edit in the admin panel (> ${MAX_FILE_BYTES / 1024} KB).`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/**\n * Write a file. Creates parent dirs as needed. Rejects extensions / size\n * outside the allow-list the same way readAgentFile does.\n */\nexport function writeAgentFile(workspace: string, relPath: string, content: string): { path: string; bytes: number } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/**\n * Create a new skill scaffold at .claude/skills/<slug>/SKILL.md.\n */\nexport function createAgentSkill(\n workspace: string,\n slug: string,\n opts: { title?: string; content?: string } = {},\n): { slug: string; path: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) {\n throw new Error(\"Skill slug must be lowercase (letters, digits, -, _) and start with a letter or digit.\")\n }\n const relPath = `.claude/skills/${slug}/SKILL.md`\n const abs = resolveInWorkspace(workspace, relPath)\n if (existsSync(abs)) throw new Error(`Skill \"${slug}\" already exists.`)\n mkdirSync(dirname(abs), { recursive: true })\n const body = opts.content?.trim()\n ? opts.content\n : defaultSkillTemplate(slug, opts.title)\n writeFileSync(abs, body)\n return { slug, path: relPath }\n}\n\n/**\n * Remove a skill directory (.claude/skills/<slug>). Scoped to the workspace\n * so we can never touch anything else.\n */\nexport function deleteAgentSkill(workspace: string, slug: string): { slug: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(abs)) throw new Error(`Skill \"${slug}\" not found.`)\n const st = statSync(abs)\n if (!st.isDirectory()) throw new Error(`Skill path is not a directory: ${slug}`)\n rmSync(abs, { recursive: true, force: true })\n return { slug }\n}\n\n// --- Skill directory tree + per-file I/O ---\n//\n// A skill is `.claude/skills/<slug>/` — usually SKILL.md plus scripts,\n// lockfiles, configs. The admin UI lets the operator browse the directory\n// tree, edit any text file, and dispatch \"install deps\" through the agent.\n\nexport interface SkillTreeEntry {\n /** Path relative to the skill dir root. Directories end with \"/\". */\n path: string\n kind: \"file\" | \"dir\"\n size: number\n editable: boolean // false for binaries or disallowed extensions\n}\n\nexport interface SkillDepsHint {\n /** Which install command the operator should run (or have an agent run). */\n manager: \"npm\" | \"pnpm\" | \"yarn\" | \"pip\" | \"bundle\" | null\n /** Filename that triggered the detection, relative to skill dir. */\n file: string | null\n /** The exact command we'd recommend. */\n command: string | null\n}\n\n/** List every file in `.claude/skills/<slug>/`, recursively. Scoped so the\n * caller can't escape the skill dir via symlinks or \"..\" segments. */\nexport function listSkillTree(workspace: string, slug: string): SkillTreeEntry[] {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const out: SkillTreeEntry[] = []\n walk(skillRoot, \"\", 0, out)\n out.sort((a, b) => {\n // dirs first, then alphabetical\n if (a.kind !== b.kind) return a.kind === \"dir\" ? -1 : 1\n return a.path.localeCompare(b.path)\n })\n return out\n\n function walk(absDir: string, relDir: string, depth: number, acc: SkillTreeEntry[]): void {\n if (depth > SKILL_TREE_MAX_DEPTH) return\n let entries: string[] = []\n try { entries = readdirSync(absDir) } catch { return }\n for (const name of entries) {\n if (name === \"node_modules\" || name === \".git\") continue\n const abs = join(absDir, name)\n const rel = relDir ? `${relDir}/${name}` : name\n let st\n try { st = statSync(abs) } catch { continue }\n if (st.isDirectory()) {\n acc.push({ path: `${rel}/`, kind: \"dir\", size: 0, editable: false })\n walk(abs, rel, depth + 1, acc)\n } else if (st.isFile()) {\n const ext = extname(name).toLowerCase()\n // name-only allowlist for dotfiles like .env / .gitignore (no extension)\n const allowedByName = SKILL_EDITABLE_EXTENSIONS.has(ext) ||\n SKILL_EDITABLE_EXTENSIONS.has(name.toLowerCase())\n acc.push({\n path: rel,\n kind: \"file\",\n size: st.size,\n editable: allowedByName && st.size < MAX_FILE_BYTES,\n })\n }\n }\n }\n}\n\n/** Read any file in a skill dir. Respects the skill-editable allowlist. */\nexport function readSkillFile(workspace: string, slug: string, relPath: string): { path: string; content: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n if (!existsSync(abs) || !statSync(abs).isFile()) {\n throw new Error(`File not found: ${relPath}`)\n }\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File too large to edit (> ${MAX_FILE_BYTES / 1024} KB): ${relPath}`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/** Write any allowed file inside a skill dir. Creates parents. */\nexport function writeSkillFile(\n workspace: string,\n slug: string,\n relPath: string,\n content: string,\n): { path: string; bytes: number } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/** Detect which dependency manager a skill needs based on the files present. */\nexport function detectSkillDeps(workspace: string, slug: string): SkillDepsHint {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const check = (name: string): boolean => existsSync(join(skillRoot, name))\n if (check(\"pnpm-lock.yaml\")) return { manager: \"pnpm\", file: \"pnpm-lock.yaml\", command: \"pnpm install\" }\n if (check(\"yarn.lock\")) return { manager: \"yarn\", file: \"yarn.lock\", command: \"yarn install\" }\n if (check(\"package.json\")) return { manager: \"npm\", file: \"package.json\", command: \"npm install\" }\n if (check(\"requirements.txt\")) return { manager: \"pip\", file: \"requirements.txt\", command: \"pip install -r requirements.txt\" }\n if (check(\"Gemfile\")) return { manager: \"bundle\", file: \"Gemfile\", command: \"bundle install\" }\n return { manager: null, file: null, command: null }\n}\n\nfunction defaultSkillTemplate(slug: string, title?: string): string {\n const displayTitle = title?.trim() || slug\n return `# ${displayTitle}\n\nDescribe what this skill does and when the agent should use it. Keep it under ~5 lines — the agent reads this to decide whether to invoke the skill.\n\n## When to use\n- Trigger condition 1\n- Trigger condition 2\n\n## How to use\nDocument the arguments, commands, or files the skill expects. Example:\n\n\\`\\`\\`bash\n# Example invocation\nclaude-code ${basename(slug)} --arg value\n\\`\\`\\`\n`\n}\n","import { loadDaemonConfig, type DaemonConfig } from \"@/daemon/config\"\n\n// The `screen` block of agentx.json and the one write rule for it, shared\n// by `agentx screen config` and the dashboard so both refuse the same\n// values.\n\nexport type ScreenSettings = DaemonConfig[\"screen\"]\ntype Rect = ScreenSettings[\"regions\"][string]\n\nexport const DEFAULT_SCREEN: ScreenSettings = {\n maxPixels: 1_200_000,\n timeoutMs: 5_000,\n intervalMs: 100,\n changeThreshold: 0.015,\n stableMs: 400,\n regions: {},\n buffer: { enabled: false, seconds: 10, fps: 2, region: \"screen\", maxPixels: 300_000 },\n}\n\n/** Fill gaps in a partial `screen` block with the defaults. */\nexport function screenSettings(cfg: Partial<ScreenSettings> | undefined): ScreenSettings {\n return {\n ...DEFAULT_SCREEN,\n ...(cfg ?? {}),\n regions: { ...(cfg?.regions ?? {}) },\n buffer: { ...DEFAULT_SCREEN.buffer, ...(cfg?.buffer ?? {}) },\n }\n}\n\n/** The `screen` block from agentx.json. A missing or invalid file means\n * the defaults: a capture should never fail over a settings file. */\nexport function readScreenSettings(configPath?: string): ScreenSettings {\n try { return screenSettings(loadDaemonConfig(configPath).screen) } catch { return screenSettings(undefined) }\n}\n\nconst REGION_NAME =/^[a-z][\\w-]*$/\n\n/** Regions the helper knows without configuration; `screen.regions` adds\n * more and may override these. */\nexport const BUILT_IN_REGIONS = [\"window\", \"screen\", \"menubar\", \"notifications\"]\n\nfunction num(v: unknown, what: string, ok: (n: number) => boolean, rule: string): number {\n const n = Number(v)\n if (v === \"\" || v === null || !Number.isFinite(n) || !ok(n)) throw new Error(`${what} must be ${rule}`)\n return n\n}\nconst positiveInt = (v: unknown, what: string) => num(v, what, (n) => Number.isInteger(n) && n > 0, \"a whole number above 0\")\n\n/** \"x,y,w,h\" or {x, y, width, height} → a rect, or an error. */\nexport function parseRect(v: unknown): Rect {\n const r = typeof v === \"string\" ? v.split(\",\").map(Number) : [(v as any)?.x, (v as any)?.y, (v as any)?.width, (v as any)?.height].map(Number)\n if (r.length !== 4 || !r.every(Number.isFinite) || r[2] <= 0 || r[3] <= 0) throw new Error(\"a region is x,y,width,height in screen points, with width and height above 0\")\n return { x: r[0], y: r[1], width: r[2], height: r[3] }\n}\n\n/**\n * Apply a change to a stored `screen` block. Returns the new block;\n * throws on a value that would not load. `regions` entries set to null\n * are removed.\n */\nexport function patchScreen(current: Partial<ScreenSettings> | undefined, patch: Record<string, any>): Partial<ScreenSettings> {\n const next: Record<string, any> = { ...(current ?? {}) }\n if (\"maxPixels\" in patch) next.maxPixels = positiveInt(patch.maxPixels, \"maxPixels\")\n if (\"timeoutMs\" in patch) next.timeoutMs = positiveInt(patch.timeoutMs, \"timeoutMs\")\n if (\"intervalMs\" in patch) next.intervalMs = positiveInt(patch.intervalMs, \"intervalMs\")\n if (\"stableMs\" in patch) next.stableMs = num(patch.stableMs, \"stableMs\", (n) => Number.isInteger(n) && n >= 0, \"a whole number, 0 or more\")\n if (\"changeThreshold\" in patch) next.changeThreshold = num(patch.changeThreshold, \"changeThreshold\", (n) => n >= 0 && n <= 1, \"between 0 and 1\")\n if (patch.regions && typeof patch.regions === \"object\") {\n const regions = { ...(next.regions ?? {}) }\n for (const [name, rect] of Object.entries(patch.regions)) {\n if (!REGION_NAME.test(name)) throw new Error(`region name \"${name}\" must be lowercase letters, digits, - or _`)\n if (rect === null) delete regions[name]\n else regions[name] = parseRect(rect)\n }\n next.regions = regions\n }\n if (patch.buffer && typeof patch.buffer === \"object\") {\n const b = patch.buffer\n const buffer = { ...(next.buffer ?? {}) }\n if (\"enabled\" in b) buffer.enabled = Boolean(b.enabled)\n if (\"seconds\" in b) buffer.seconds = num(b.seconds, \"buffer.seconds\", (n) => n > 0 && n <= 120, \"above 0 and at most 120\")\n if (\"fps\" in b) buffer.fps = num(b.fps, \"buffer.fps\", (n) => n > 0 && n <= 10, \"above 0 and at most 10\")\n if (\"maxPixels\" in b) buffer.maxPixels = positiveInt(b.maxPixels, \"buffer.maxPixels\")\n if (\"region\" in b) buffer.region = String(b.region).trim()\n next.buffer = buffer\n }\n const watched = next.buffer?.region\n if (watched !== undefined && !BUILT_IN_REGIONS.includes(watched) && !next.regions?.[watched]) {\n try { parseRect(watched) } catch {\n throw new Error(`buffer.region \"${watched}\" is not a region — use ${[...BUILT_IN_REGIONS, ...Object.keys(next.regions ?? {})].join(\", \")}, or x,y,w,h`)\n }\n }\n return next\n}\n"],"mappings":"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","names":["existsSync","readdirSync","readFileSync","statSync","resolve","workflowHealth","workflows","runs","now","windowDays","span","byId","r","list","w","mine","recent","prior","failed","paused","lastRunAt","state","a","b","rank","cache","scanRuns","runsDir","limit","files","f","p","live","key","out","path","mtime","hit","summary","readSummary","lines","head","workflowId","status","i","tail","s","DEFAULT_ROUTINE_THRESHOLDS","ROUTINE_LIMITS","defaultNextFire","expression","after","timezone","getNextCronDate","DAY","buildRoutines","input","now","t","nextFire","out","job","runs","outcomes","r","latest","routine","cap","isoOrNull","capOpt","failStreak","scheduleFlags","finish","health","workflowHealth","w","h","runsByWorkflow","list","wf","trigger","n","cfg","state","enabled","a","b","workflowOutcome","isCron","spec","describeFilters","workflowAgent","s","o","changedAt","eventFlags","compareRoutines","flags","lastAt","disabled","disabledFlag","cursor","deadline","i","dormant","since","reasons","f","ATTENTION_RANK","ra","rb","status","node","filter","project","key","value","values","v","d","limit","createHash","randomBytes","existsSync","mkdirSync","readFileSync","writeFileSync","resolve","dirname","TOKEN_PREFIX","DEFAULT_FILE","TokenStore","baseDir","fileRel","records","input","s","isValidScope","body","token","record","hashToken","id","rec","r","hash","now","last","recordHasScope","required","extractToken","req","auth","authHeader","tok","existsSync","readdirSync","readFileSync","resolve","randomBytes","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","unlinkSync","writeFileSync","resolve","base","settings","firstLine","s","sendText","to","text","exec","run","downloadMedia","chat","id","output","DRAFT_MAX","sendWithWacli","d","sendText","draftsDir","root","resolve","validId","id","fileFor","save","draft","mkdirSync","path","tmp","writeFileSync","renameSync","createDraft","input","now","text","randomBytes","readDraft","readFileSync","listDrafts","status","dir","existsSync","readdirSync","f","a","b","decideDraft","verdict","opts","claim","decided","r","e","unlinkSync","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","writeFileSync","resolve","IF_SILENT_VALUES","CARD_LIMITS","DEFAULT_CARD_SETTINGS","approvalsDir","root","resolve","CARD_ID_RE","isValidCardId","id","fileFor","newCardId","now","title","day","slug","saveCard","card","mkdirSync","path","tmp","writeFileSync","renameSync","readCard","readFileSync","listCards","status","dir","existsSync","out","f","readdirSync","c","oneLine","v","resolveExpiry","input","settings","max","at","raw","rel","buildCard","opts","ask","recommend","raisedBy","ifSilent","expiry","source","extras","buildChoices","reply","r","createCard","built","open","decideCard","verdict","answer","resolveAnswer","note","decided","expireCards","expired","cardsAwaitingAgentNotice","markAgentNotified","verdictMessage","result","lines","answerLines","mkdirSync","readFileSync","renameSync","writeFileSync","resolve","stateFile","root","resolve","approvalsDir","readInboxState","raw","readFileSync","writeInboxState","state","mkdirSync","path","tmp","writeFileSync","renameSync","snooze","key","until","now","snoozed","k","v","recordDigest","localDate","recordPopped","waiting","keep","popped","APPROVAL_KINDS","DETAIL_MAX","clip","s","n","flat","configPathFor","ctx","resolve","candidates","c","existsSync","wikiDirFor","cardItems","listCards","cardItem","scheduleItems","path","cfg","readFileSync","now","out","id","job","approval","tz","human","humanizeCron","when","what","creating","formatFireTime","nextFireTime","memoryAgents","root","dir","readdirSync","f","memoryItems","agents","store","MemoryStore","agent","proposalBacking","e","failures","wikiItems","listProposals","p","whatsappItems","listDrafts","d","SOURCES","byUrgency","a","b","ea","eb","listInbox","opts","snoozed","readInboxState","items","errors","hidden","kind","found","item","until","parseKey","key","i","ref","stillPending","readCard","readProposal","readDraft","decide","action","parsed","by","hours","snooze","yes","r","decideCard","approveSchedule","rejectSchedule","slash","approveProposal","rejectProposal","decideDraft","LOOPBACK","isMeshGatedPath","path","isControlPost","isLoopback","remoteAddress","decideMeshAuth","req","header","token","collectAcceptedMeshTokens","config","env","accepted","p","dashboardTokenForNode","dashboard","target","norm","u","d","first","v","originHost","origin","allowedOriginsFromEnv","env","s","classifyBrowserRequest","headers","extraAllowed","from","hosts","h","site","isStateChangingOrPreflight","method","m","existsSync","mkdirSync","readFileSync","renameSync","writeFileSync","homedir","dirname","join","execFile","execFileSync","execFileSync","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","writeFileSync","homedir","join","SIRI_PREFIX","ID_PREFIX","isSiriVoice","id","TOOLS","readPrefDomain","supported","siriSupported","host","probe","platform","exists","prefDomain","ok","t","ASSET_DIRS","GENDER","siriGender","name","parseSiriId","m","SPECIFIER","parseSiriAssets","plists","seen","text","lang","region","locale","listSiriVoices","dirs","dir","assets","a","SIRI_SAY","siriSayPath","home","ensureSiriSay","path","JXA","CLASSIC","GENDER","parseVoiceList","out","voices","line","id","name","locale","q","g","REFRESH_MS","MISSING_REFRESH_MS","cached","refreshing","listSystemVoices","now","maxAgeMs","execFile","err","listSiriVoices","execFileSync","e","voiceLog","m","warned","setVoiceLog","fn","warnOnce","key","msg","forgetWarnings","match","k","onMissing","setMissingVoiceHook","reportMissing","QUALITY","CHARACTER","rank","v","family","language","modern","fallbackVoice","installed","gender","taken","pool","candidates","same","best","used","n","exact","lang","a","b","findVoice","byId","SIRI_PREFIX","base","siri","standIn","tier","named","castVoices","entries","cast","turn","known","want","free","pick","EN","FR","ACCENT","detectLanguage","text","arabic","latin","en","fr","w","OS_DEFAULT","isOsDefault","name","DEFAULT_VOICE_ID","resolveAgentVoice","agentId","agents","settings","installed","listSystemVoices","agent","v","sys","localSystemVoices","systemByLanguage","languageVoices","label","deriveIntro","checkedVoice","owner","locale","findVoice","warnOnce","reportMissing","voiceTraits","q","SIRI_PREFIX","siriGender","siri","parseSiriId","language","nameFor","choice","lang","configured","fitsGender","gender","castSystemVoices","wishes","taken","out","open","fallback","inUse","w","own","pick","standIn","names","id","castVoices","candidates","f","traits","fallbackVoice","add","who","list","suits","voiceForText","text","detectLanguage","a","voiceRef","explicit","ref","pickVoiceId","langs","l","agentVoiceId","systemPrompt","first","m","lead","rest","role","INTRO_GAP_MS","MAX_TRACKED","VoiceIntroTracker","gapMs","session","now","last","key","k","introInstruction","voice","introduce","style","talkSpeaker","persona","VOICE_MODE_INSTRUCTION","remoteVoiceAppend","context","ctx","clip","s","intro","REINSTALL_HINT","voiceHealthPath","home","homedir","join","isInstalled","name","installed","locale","q","OS_DEFAULT","SIRI_PREFIX","base","v","findVoice","configuredNames","config","out","add","owner","choice","lang","id","a","findMissingVoices","byVoice","cast","perLanguage","localSystemVoices","entry","now","label","voiceDisplayName","siri","parseSiriId","missingNotice","missing","lines","m","who","readVoiceHealth","file","existsSync","s","readFileSync","writeVoiceHealth","state","mkdirSync","dirname","writeFileSync","renameSync","VoiceHealth","deps","before","found","back","forgetWarnings","k","untold","title","message","e","readFileSync","writeFileSync","existsSync","readdirSync","statSync","resolve","join","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","resolve","z","sourceSchema","itemSchema","fileSchema","BacklogStore","path","resolve","existsSync","p","raw","readFileSync","e","file","jsonP","mkdirSync","dirname","writeFileSync","id","i","input","now","item","inputs","existing","added","patch","idx","updated","before","lines","buckets","sections","key","label","it","check","mention","time","src","sourceBacklogId","source","resolveAssigneesForSource","agentIds","source","daemon","out","mappings","agentId","m","x","syncBacklogItemToSource","item","patch","log","syncToGitLab","syncToGitHub","gl","token","host","attempts","api","path","init","projectEnc","assigneeIds","u","res","arr","milestoneId","e","body","text","a","repo","milestoneNumber","found","nextLabels","issue","current","l","adds","r","CHECK_RE","MENTION_RE","TIME_RE","parseDuration","s","hMatch","mMatch","BacklogWorkSource","path","daemon","log","BacklogStore","full","resolve","existsSync","readFileSync","lines","writeFileSync","agentId","it","items","i","m","body","mention","timeMatch","title","_opts","itemId","item","updated","usernames","resolveAssigneesForSource","syncBacklogItemToSource","e","update","patch","sourcePatch","lineNo","line","note","stage","WikiWorkSource","root","glob","dir","out","entry","readdirSync","join","statSync","files","file","parts","GitLabWorkSource","host","token","projects","agentUsernames","username","names","n","issue","project","ns","labels","labelDetails","l","assignees","a","firstUsername","init","res","projectList","q","issues","iid","type","seg","mins","dur","opts","sinceDays","updatedAfter","params","state","result","deny","prefix","to","assigneeIds","users","x","milestoneId","ms","byUsername","members","b","byTitle","all","input","assignee_ids","u","milestone_id","toLabel","fromLabel","add","remove","createWorkSource","business","ws","gl","mapping","r","UNMAPPED","clientFromProject","project","projects","hit","p","slash","agentClients","business","out","orgChart","climb","agentId","seen","up","c","matchContact","channel","who","contactMap","pool","m","resolveClient","ref","contact","byAgent","parseWorkRef","sessionId","parts","agent","rest","target","KIND_DEFAULT","id","clientPolicy","parseDuration","listClients","ids","v","FENCE_OPEN","FENCE_FULL","extractUiDirective","text","matches","match","json","ui","parseDirective","start","leading","stripUiDirectiveForPreview","m","raw","buttons","replies","skippedActions","b","q","opts","o","type","url","caption","chips","ringNotice","signal","urlBase","link","RING_DEDUP_TTL_MS","normalizeName","name","WebRtcSignalBroker","localName","allowedCallers","log","callId","recipient","now","key","bucket","kept","b","expiry","fn","res","deliver","sink","sub","tag","buffered","fresh","wantRecipient","ok","normalizedRecipient","e","delivered","closeSync","createReadStream","fstatSync","openSync","realpathSync","isAbsolute","resolve","sep","APP_FILES_PATH","AGENT_RE","resolveArtifact","workspace","file","cwd","ARTIFACT_LIMITS","OUTBOX_DIR","artifactType","root","realpathSync","resolve","real","isAbsolute","sep","type","contentDisposition","name","kind","ascii","fileHeaders","mime","parseRange","header","size","m","start","end","handleAppFilesApi","req","res","url","deps","json","status","body","agent","found","fd","openSync","st","fstatSync","closeSync","headers","range","stream","createReadStream","spawn","readFileSync","writeFileSync","tmpdir","join","sayRate","rate","elevenLabsSpeed","elevenLabsKey","p","v","readFileSync","elevenLabsSynth","key","n","u","signal","res","file","join","tmpdir","writeFileSync","e","warnOnce","systemVoiceFor","text","lang","detectLanguage","sayArgs","id","wpm","isSiriVoice","sayCommand","siriSay","speakLimitMs","words","stopAllSpeakers","script","siriSayScript","spawn","restoreSpokenVoice","host","home","siriSupported","ensureSiriSay","systemPlay","cmd","args","env","spawn","accessSync","constants","readFileSync","homedir","basename","delimiter","dirname","extname","join","AUDIO_LIMITS","checkDurationHeader","header","raw","tooLongMessage","AUDIO_TYPES","audioExt","contentType","base","sttEngines","setting","host","out","sttSetupHint","findBinary","name","explicit","pathEnv","home","dirs","p","d","detectSttHost","key","findFfmpeg","measureSeconds","file","ffmpeg","decodeSeconds","limit","times","runStderr","m","elevenLabsScribe","mime","signal","fd","r","j","localWhisper","engine","bin","outDir","args","env","run","cmd","timeoutMs","resolve","reject","child","err","timer","e","code","toSpeakable","text","max","s","match","mkdtemp","rm","writeFile","tmpdir","join","SPEAK_MAX_CHARS","SPEAK_BODY_MAX","MAX_TRANSCRIBING","transcribing","isVoiceIoPath","path","handleVoiceIo","req","res","deps","json","transcribe","speak","mime","ext","audioExt","stated","checkDurationHeader","audio","readRaw","AUDIO_LIMITS","tooBigMessage","setting","host","engines","sttEngines","where","more","sttSetupHint","reply","dir","mkdtemp","join","tmpdir","transcribeIn","rm","name","file","writeFile","secs","measureSeconds","tooLongMessage","failures","engine","text","runEngine","e","why","custom","elevenLabsScribe","bin","localWhisper","raw","body","agent","peer","speakableAnswer","voice","browser","status","key","elevenLabsAudio","clean","extractUiDirective","extractArtifacts","toSpeakable","speed","elevenLabsSpeed","resolveVoice","agentId","config","mesh","agents","voiceRef","resolveAgentVoice","limit","resolve","reject","chunks","size","chunk","existsSync","readFileSync","writeFileSync","mkdirSync","readdirSync","rmSync","statSync","resolve","relative","dirname","join","extname","basename","MAX_FILE_BYTES","ALLOWED_EXTENSIONS","SKILL_EDITABLE_EXTENSIONS","SKILL_TREE_MAX_DEPTH","IDENTITY_FILENAMES","resolveInWorkspace","workspace","relPath","wsAbs","target","assertEditable","absPath","ext","extractTitle","markdown","fallback","line","l","listAgentFiles","identity","seen","rootEntries","name","abs","size","content","safeRead","st","skills","skillsRoot","slug","dir","file","stf","b","readAgentFile","writeAgentFile","buf","createAgentSkill","opts","body","defaultSkillTemplate","deleteAgentSkill","listSkillTree","skillRoot","out","walk","a","absDir","relDir","depth","acc","entries","rel","allowedByName","readSkillFile","writeSkillFile","detectSkillDeps","check","title","init_config","DEFAULT_SCREEN","screenSettings","cfg","readScreenSettings","configPath","loadDaemonConfig","REGION_NAME","BUILT_IN_REGIONS","num","v","what","ok","rule","n","positiveInt","parseRect","r","patchScreen","current","patch","next","regions","name","rect","b","buffer","watched"]}
|