agentix-cli 0.139.0 → 0.139.2
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/dist/{agent-PRHZGF6J.js → agent-5CEDGNDW.js} +2 -2
- package/dist/{board-dashboard-GBEBQUFJ.js → board-dashboard-F5GP2X22.js} +2 -2
- package/dist/{builtin-YD7MC572.js → builtin-U5S2XCNJ.js} +2 -2
- package/dist/chunk-3W2BHUGM.js +11 -0
- package/dist/chunk-3W2BHUGM.js.map +1 -0
- package/dist/{chunk-KO6BU3B3.js → chunk-4LNJR5PC.js} +2 -2
- package/dist/{chunk-34XYHSL6.js → chunk-65LN6GQP.js} +230 -229
- package/dist/chunk-65LN6GQP.js.map +1 -0
- package/dist/chunk-6TTGRTDO.js +3 -0
- package/dist/chunk-6TTGRTDO.js.map +1 -0
- package/dist/{chunk-MUC7MMTT.js → chunk-BUKXFZEO.js} +2 -2
- package/dist/{chunk-2BEZU5OF.js → chunk-HWYU6XG2.js} +2 -2
- package/dist/chunk-HWYU6XG2.js.map +1 -0
- package/dist/{chunk-RIYQNADV.js → chunk-LGB6ILTW.js} +3 -3
- package/dist/{chunk-JBYLG3CQ.js → chunk-P4TSZDVO.js} +2 -2
- package/dist/{chunk-HF5EF335.js → chunk-VE3SSOQX.js} +4 -4
- package/dist/chunk-VE3SSOQX.js.map +1 -0
- package/dist/{chunk-XQVL2RYG.js → chunk-WTMZW5X7.js} +2 -2
- package/dist/{chunk-H4NSB54D.js → chunk-XVTD6I34.js} +2 -2
- package/dist/{chunk-U6EJ53DA.js → chunk-YWEGKAE2.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/{enrich-R2LIDLPZ.js → enrich-FPK3OW77.js} +2 -2
- package/dist/{event-levels-WOYYEWSZ.js → event-levels-NBOMTB23.js} +2 -2
- package/dist/{follow-up-tool-QRECR3AH.js → follow-up-tool-M2KCLRQQ.js} +2 -2
- package/dist/index.d.ts +14 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/{program-7VHY5RVZ.js → program-SJT7PIFO.js} +6 -6
- package/dist/program-SJT7PIFO.js.map +1 -0
- package/dist/{registry-B276QJ75.js → registry-Q7VNRLZA.js} +2 -2
- package/dist/{rules-UVUJTVOE.js → rules-5ZZQZLQA.js} +2 -2
- package/dist/send-answer-ZG7UHCVS.js +2 -0
- package/dist/{tool-UPXUWIMM.js → tool-DFPOFXGA.js} +2 -2
- package/dist/{tool-5WCC7SKQ.js → tool-GJ75MWV5.js} +2 -2
- package/dist/wiki-enrich-JNQ2RPWU.js +2 -0
- package/dist/wiki-enrich-JNQ2RPWU.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-2BEZU5OF.js.map +0 -1
- package/dist/chunk-34XYHSL6.js.map +0 -1
- package/dist/chunk-HF5EF335.js.map +0 -1
- package/dist/chunk-Q4XP5TBI.js +0 -11
- package/dist/chunk-Q4XP5TBI.js.map +0 -1
- package/dist/program-7VHY5RVZ.js.map +0 -1
- package/dist/wiki-enrich-7SBCPK2E.js +0 -2
- /package/dist/{agent-PRHZGF6J.js.map → agent-5CEDGNDW.js.map} +0 -0
- /package/dist/{board-dashboard-GBEBQUFJ.js.map → board-dashboard-F5GP2X22.js.map} +0 -0
- /package/dist/{builtin-YD7MC572.js.map → builtin-U5S2XCNJ.js.map} +0 -0
- /package/dist/{chunk-KO6BU3B3.js.map → chunk-4LNJR5PC.js.map} +0 -0
- /package/dist/{chunk-MUC7MMTT.js.map → chunk-BUKXFZEO.js.map} +0 -0
- /package/dist/{chunk-RIYQNADV.js.map → chunk-LGB6ILTW.js.map} +0 -0
- /package/dist/{chunk-JBYLG3CQ.js.map → chunk-P4TSZDVO.js.map} +0 -0
- /package/dist/{chunk-XQVL2RYG.js.map → chunk-WTMZW5X7.js.map} +0 -0
- /package/dist/{chunk-H4NSB54D.js.map → chunk-XVTD6I34.js.map} +0 -0
- /package/dist/{chunk-U6EJ53DA.js.map → chunk-YWEGKAE2.js.map} +0 -0
- /package/dist/{enrich-R2LIDLPZ.js.map → enrich-FPK3OW77.js.map} +0 -0
- /package/dist/{event-levels-WOYYEWSZ.js.map → event-levels-NBOMTB23.js.map} +0 -0
- /package/dist/{follow-up-tool-QRECR3AH.js.map → follow-up-tool-M2KCLRQQ.js.map} +0 -0
- /package/dist/{registry-B276QJ75.js.map → registry-Q7VNRLZA.js.map} +0 -0
- /package/dist/{rules-UVUJTVOE.js.map → rules-5ZZQZLQA.js.map} +0 -0
- /package/dist/{wiki-enrich-7SBCPK2E.js.map → send-answer-ZG7UHCVS.js.map} +0 -0
- /package/dist/{tool-UPXUWIMM.js.map → tool-DFPOFXGA.js.map} +0 -0
- /package/dist/{tool-5WCC7SKQ.js.map → tool-GJ75MWV5.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/actions/builtin/registry.ts","../src/actions/builtin/agent.ts","../src/agents/registry-instance.ts","../src/actions/builtin/channel.ts","../src/actions/builtin/extract.ts","../src/actions/builtin/file.ts","../src/actions/builtin/http.ts","../src/actions/builtin/mesh.ts","../src/a2a/mesh-instance.ts","../src/actions/builtin/rag.ts","../src/rag/lexical-index.ts","../src/actions/builtin/signal.ts","../src/agents/signals/instance.ts","../src/actions/builtin/index.ts"],"sourcesContent":["import type { BuiltinAction, BuiltinActionMetadata } from \"./types\"\n\n// --- Built-in action registry ---\n//\n// Process-global. Built-ins register at module-load time via the\n// barrel in ./index.ts; the daemon never has to wire them up\n// explicitly. Lookup is O(1).\n\nconst _registry = new Map<string, BuiltinAction>()\n\nconst NAME_PATTERN = /^[a-z][a-z0-9_]*(\\.[a-z][a-z0-9_]*)*$/\n\n/**\n * Register a built-in. Idempotent — re-registering the same name\n * overwrites (useful for tests). Throws on malformed names so the\n * dotted-namespace contract stays consistent across the codebase.\n */\nexport function registerBuiltin<I, O>(action: BuiltinAction<I, O>): void {\n if (!NAME_PATTERN.test(action.name)) {\n throw new Error(`built-in action name must be dotted lowercase identifier (got \"${action.name}\")`)\n }\n _registry.set(action.name, action as BuiltinAction)\n}\n\nexport function getBuiltin(name: string): BuiltinAction | undefined {\n return _registry.get(name)\n}\n\n/** List metadata for every registered built-in, sorted by name. The\n * full schemas aren't included here — operators fetch the JSON-schema\n * rendering separately to keep the list response small. */\nexport function listBuiltins(): BuiltinActionMetadata[] {\n const out: BuiltinActionMetadata[] = []\n for (const [name, a] of _registry.entries()) {\n out.push({ name, description: a.description, timeoutMs: a.timeoutMs })\n }\n out.sort((a, b) => a.name.localeCompare(b.name))\n return out\n}\n\n/** Test-only — wipe the registry between cases. Production never clears. */\nexport function _resetBuiltinsForTesting(): void {\n _registry.clear()\n}\n\nconst DEFAULT_TIMEOUT_MS = 60_000\n\n/**\n * Run a built-in. Wraps the handler in:\n * - input validation (Zod parse — throws on bad shape)\n * - per-action timeout (with daemon-default fallback)\n * - output validation (Zod parse — log on mismatch but pass through;\n * output drift shouldn't break callers, just signal)\n *\n * Returns the validated output, or rejects on timeout / handler error.\n * Callers (HTTP route, CLI) catch and translate to error responses.\n */\nexport async function runBuiltin<I, O>(name: string, rawInput: unknown): Promise<O> {\n const action = _registry.get(name) as BuiltinAction<I, O> | undefined\n if (!action) {\n throw new Error(`unknown built-in action: ${name}`)\n }\n const input = action.inputSchema.parse(rawInput) as I\n const timeoutMs = action.timeoutMs ?? DEFAULT_TIMEOUT_MS\n\n const handlerPromise = action.handler(input)\n const timeoutPromise = new Promise<O>((_, reject) => {\n setTimeout(() => reject(new Error(`built-in action ${name} timed out after ${timeoutMs}ms`)), timeoutMs)\n })\n\n const output = await Promise.race([handlerPromise, timeoutPromise])\n\n // Soft validation on output — log mismatches but pass the value\n // through. A handler that drifts from its schema shouldn't break\n // production; surfacing the drift in the daemon log is enough signal.\n const parsed = action.outputSchema.safeParse(output)\n if (!parsed.success) {\n process.stderr.write(`[builtin-action] ${name} output failed schema validation: ${parsed.error.message}\\n`)\n }\n return output\n}\n","import { z } from \"zod\"\nimport { getAgentRegistry } from \"@/agents/registry-instance\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- agent.call ---\n//\n// Same-node sub-agent delegation, typed and traceable. Counterpart to\n// `mesh.delegate`: that one calls a remote peer's agent over HTTP+mesh,\n// this one calls a local agent on the same daemon — the common case for\n// triage/router patterns where one agent classifies + dispatches to a\n// specialized worker on the same machine.\n//\n// Why this exists separately from the daemon's POST /task endpoint:\n// - Going through HTTP for a same-node call is wasteful and harder\n// to trace (the action becomes a Bash + curl + parse-JSON dance,\n// exactly what AGENTX_HANDOFF.md item #1 flagged).\n// - Calling registry.execute() directly captures the call as a\n// structured action step in /traces (action=agent.call,\n// input=<typed>, output=<reply>) instead of opaque shell text.\n// - freshSession defaults to TRUE — the run-3/run-4 stickiness bug\n// was triage's curl reusing a warm pool; this default makes\n// \"safe\" the easy path.\n//\n// Architecturally this is a documented cross-tier seam: a Tier-3\n// procedure reaches into the Tier-1 agent registry via the explicit\n// getAgentRegistry() singleton (see registry-instance.ts and\n// test/tier-discipline.test.ts:DOCUMENTED_SEAMS).\n\nconst agentCallInput = z.object({\n /** Local agent id to dispatch the task to. */\n agentId: z.string().min(1),\n /** The task message — verbatim user message or a synthesized prompt. */\n message: z.string().min(1),\n /** Force a fresh session on the receiving agent. Defaults to TRUE so\n * that triage/router agents don't accidentally inherit a previous\n * visitor's conversation state. Pass `false` only when you genuinely\n * want continuity (rare for delegation). */\n freshSession: z.boolean().default(true),\n /** Optional channel + chatId. Defaults to a synthetic per-call chatId\n * so multiple concurrent delegations don't share a session. */\n channel: z.string().default(\"api\"),\n chatId: z.string().optional(),\n /** Identity of the calling agent — passed through to the receiving\n * agent's context for trace-stitching and (eventually) capability\n * validation. */\n senderAgentId: z.string().optional(),\n /** Per-call timeout. Default 90s — long enough for most agent\n * reasoning; capped at 10min. */\n timeoutMs: z.number().int().min(1).max(600_000).default(90_000),\n})\ntype AgentCallInput = z.infer<typeof agentCallInput>\n\nconst agentCallOutput = z.object({\n agentId: z.string(),\n /** The agent's reply. Same shape as POST /task's `content`. */\n reply: z.string(),\n /** Optional structured error if the underlying agent run failed.\n * Surfaced so callers can branch on auth/billing/tool-required. */\n error: z.string().optional(),\n errorKind: z.string().optional(),\n /** Token usage for cost accounting. */\n usage: z.object({\n inputTokens: z.number().int().default(0),\n outputTokens: z.number().int().default(0),\n cacheReadTokens: z.number().int().default(0),\n cacheCreateTokens: z.number().int().default(0),\n }).default({ inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreateTokens: 0 }),\n durationMs: z.number().int().default(0),\n})\ntype AgentCallOutput = z.infer<typeof agentCallOutput>\n\nexport const agentCall: BuiltinAction<AgentCallInput, AgentCallOutput> = {\n name: \"agent.call\",\n description: \"Dispatch a task to a local agent on the same daemon (fresh session by default — replaces ad-hoc 'Bash curl /task' delegation)\",\n inputSchema: agentCallInput,\n outputSchema: agentCallOutput,\n timeoutMs: 600_000,\n handler: async (input) => {\n const registry = getAgentRegistry()\n if (!registry) {\n throw new Error(\"agent registry not wired (daemon not started or running in a non-daemon process)\")\n }\n // Synthesize a per-call chatId by default so concurrent delegations\n // don't share a session. Caller can override for explicit continuity.\n const chatId = input.chatId ?? `agent-call-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n const started = Date.now()\n // senderAgentId is intentionally NOT placed in context (the\n // standard AgentTask context shape doesn't include it). The receiving\n // agent's prompt can reference {{trigger.input.senderAgentId}} if\n // configured; for trace stitching, the action's invocation already\n // creates a parent step so children land under the same task tree.\n const response = await registry.execute({\n agentId: input.agentId,\n message: input.message,\n freshSession: input.freshSession,\n context: {\n channel: input.channel,\n chatId,\n },\n })\n return {\n agentId: input.agentId,\n reply: response.content ?? \"\",\n error: response.error,\n errorKind: (response as any).errorKind,\n usage: {\n inputTokens: response.usage?.inputTokens ?? 0,\n outputTokens: response.usage?.outputTokens ?? 0,\n cacheReadTokens: (response.usage as any)?.cacheReadTokens ?? 0,\n cacheCreateTokens: (response.usage as any)?.cacheCreateTokens ?? 0,\n },\n durationMs: response.duration ?? (Date.now() - started),\n }\n },\n}\n","import type { AgentRegistry } from \"./registry\"\n\n// --- Singleton accessor for the live AgentRegistry ---\n//\n// Mirrors src/agents/process-registry-instance.ts and src/a2a/mesh-instance.ts.\n// Daemon registers the registry at boot; cross-tier composition seams (e.g.\n// the agent.call built-in action that needs to dispatch a local task without\n// going through HTTP) read it via getAgentRegistry().\n//\n// This is a DOCUMENTED SEAM under the three-tier discipline rule — listed in\n// test/tier-discipline.test.ts:DOCUMENTED_SEAMS so a procedure module can\n// import this without violating \"procedures must not import the agent\n// registry runtime\". The escape hatch is intentional: local delegation\n// reuses the registry's session management + permission resolution + trace\n// recording, which would otherwise need to be duplicated in the action.\n\nlet _registry: AgentRegistry | null = null\n\nexport function getAgentRegistry(): AgentRegistry | null {\n return _registry\n}\n\nexport function setAgentRegistry(r: AgentRegistry | null): void {\n _registry = r\n}\n\n/** Test-only — guarantees a clean slate between cases. */\nexport function resetAgentRegistryForTesting(): void {\n _registry = null\n}\n","import { z } from \"zod\"\nimport { getMessageRouter } from \"@/channels/router-instance\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- channel.reply / channel.label / channel.assign ---\n//\n// Canonical channel-output surface for agents. Replaces the raw-curl\n// playbook in agent skills with a typed action that:\n//\n// - posts through the live MessageRouter -> adapter chain (so the\n// adapter's marker + identity resolution + ledger recording all\n// fire automatically — no per-agent token in workspace files)\n// - dedupes within a 60s window keyed on (channel, chatId, body)\n// via router.sendOutbound's idempotency layer — so the same\n// dedupe protects every caller (HTTP /send, channel.reply,\n// cron notifications, cross-channel routing)\n// - returns the posted message id, or null when dedupe suppressed\n// the post (the messageId from the prior post)\n//\n// Architecturally this is the cleanest decoupling we can offer right\n// now: agent reasoning produces structured actions; the actions touch\n// the world through agentx; agentx owns cross-cutting concerns\n// (dedupe, marker, audit, rate limiting, identity) at the router\n// layer where every send-out converges.\n//\n// `auto_reply_legacy` (separate flag, on the channel config) preserves\n// today's \"router auto-posts response.content\" until every agent skill\n// has migrated to channel.reply. Once migration is complete the flag\n// gets removed and the agent's response.content stops being auto-\n// relayed entirely.\n\n// --- channel.reply ---\n\nconst channelReplyInput = z.object({\n /** Channel name (gitlab | github | telegram | whatsapp | discord | slack). */\n channel: z.string().min(1),\n /** Stable chat id — for gitlab: `org/repo:issue:123` or `org/repo:merge_request:123`.\n * For telegram/whatsapp: the chat or sender id. The format is the same one\n * the agent received in its task context as `chatId`. */\n chatId: z.string().min(1),\n /** Message body. Markdown for gitlab/github/discord; plain for sms/whatsapp. */\n text: z.string().min(1),\n /** Posting identity. When omitted, the channel adapter resolves identity\n * from agentMappings — for gitlab that's the per-agent gitlab token.\n * For telegram/discord it picks an account that's in the chat. */\n agentId: z.string().optional(),\n /** Multi-account adapters (telegram) need this when the chat is reachable\n * from more than one bot. Falls back to the adapter's chatAccountMap. */\n accountId: z.string().optional(),\n /** Optional reply-to message id for threaded channels. */\n replyTo: z.string().optional(),\n /** Custom dedupe key. When provided, replaces the default body-hash\n * scheme — useful for \"I want to overwrite my last status update\"\n * patterns where the body changes but the intent is the same post. */\n idempotencyKey: z.string().optional(),\n /** Optional rich payloads (telegram/whatsapp). URL buttons render as an\n * inline keyboard; poll/media send as their own message. */\n buttons: z.array(z.object({ label: z.string(), url: z.string() })).optional(),\n poll: z.object({ name: z.string(), values: z.array(z.string()), selectableCount: z.number().optional() }).optional(),\n media: z.object({\n type: z.enum([\"image\", \"document\", \"audio\", \"video\"]),\n url: z.string(),\n caption: z.string().optional(),\n }).optional(),\n})\ntype ChannelReplyInput = z.infer<typeof channelReplyInput>\n\nconst channelReplyOutput = z.object({\n /** Adapter-returned id, or null when the adapter couldn't surface one\n * (or the call was suppressed by the router's 60s dedupe window — in\n * which case this returns the prior post's id when known). */\n messageId: z.string().nullable(),\n})\ntype ChannelReplyOutput = z.infer<typeof channelReplyOutput>\n\nexport const channelReply: BuiltinAction<ChannelReplyInput, ChannelReplyOutput> = {\n name: \"channel.reply\",\n description: \"Post a reply to a channel (gitlab/github comment, telegram/whatsapp message, …) through the canonical adapter — auto-marker, auto-dedupe within 60s, identity resolved from agentMappings.\",\n inputSchema: channelReplyInput,\n outputSchema: channelReplyOutput,\n timeoutMs: 30_000,\n handler: async (input) => {\n const router = getMessageRouter()\n if (!router) {\n throw new Error(\"message router not wired (daemon not started or running in a non-daemon process)\")\n }\n // Empty-string idempotencyKey tells the router to use a body-hash\n // dedupe key. Caller can override with their own key for \"overwrite\n // last status\" patterns where the body changes but intent matches.\n const idempotencyKey = input.idempotencyKey ?? \"\"\n const messageId = await router.sendOutbound(\n {\n channel: input.channel,\n chatId: input.chatId,\n text: input.text,\n agentId: input.agentId,\n accountId: input.accountId,\n replyTo: input.replyTo,\n buttons: input.buttons,\n poll: input.poll,\n media: input.media,\n },\n { idempotencyKey, dedupeWindowMs: 60_000 },\n )\n return { messageId: typeof messageId === \"string\" ? messageId : null }\n },\n}\n\n// --- channel.label ---\n//\n// Add and/or remove labels on the inbound entity. GitLab + GitHub both\n// expose this; other channels error politely. Like channel.reply this\n// goes through the live adapter chain (per-agent token, marker, ledger).\n\nconst channelLabelInput = z.object({\n /** Currently supports \"gitlab\"; \"github\" wired when the github adapter\n * surfaces a typed label call (today the adapter-level setLabels is\n * gitlab-only). Pass the channel that owns the entity. */\n channel: z.literal(\"gitlab\"),\n /** GitLab project path — `org/repo`. */\n project: z.string().min(1),\n /** \"issue\" or \"merge_request\". */\n kind: z.enum([\"issue\", \"merge_request\"]),\n /** Numeric iid as a string. */\n iid: z.string().min(1),\n add: z.array(z.string()).default([]),\n remove: z.array(z.string()).default([]),\n /** Posting identity — defaults to channel-adapter resolution. */\n agentId: z.string().optional(),\n})\ntype ChannelLabelInput = z.infer<typeof channelLabelInput>\n\nconst channelLabelOutput = z.object({\n labels: z.array(z.string()),\n})\ntype ChannelLabelOutput = z.infer<typeof channelLabelOutput>\n\nexport const channelLabel: BuiltinAction<ChannelLabelInput, ChannelLabelOutput> = {\n name: \"channel.label\",\n description: \"Add and/or remove labels on a GitLab issue/MR through the adapter (per-agent token, ledger, no raw curl).\",\n inputSchema: channelLabelInput,\n outputSchema: channelLabelOutput,\n timeoutMs: 30_000,\n handler: async (input) => {\n const router = getMessageRouter()\n if (!router) throw new Error(\"message router not wired\")\n const adapter = (router as any).getChannel?.(input.channel) as\n | { setLabels?: (args: { project: string; kind?: \"issue\" | \"merge_request\"; iid: string; add?: string[]; remove?: string[]; agentId?: string }) => Promise<string[] | null> }\n | undefined\n if (!adapter?.setLabels) {\n throw new Error(`channel \"${input.channel}\" does not expose setLabels`)\n }\n const labels = await adapter.setLabels({\n project: input.project,\n kind: input.kind,\n iid: input.iid,\n add: input.add,\n remove: input.remove,\n agentId: input.agentId,\n })\n return { labels: Array.isArray(labels) ? labels : [] }\n },\n}\n\n// --- channel.assign ---\n//\n// Replace the assignees on a GitLab issue or MR. PM uses this to assign\n// the coding agent when flipping Triage→To Do; coders use it to assign\n// reviewers/mergers; QA uses it to assign testers. Same identity model\n// as channel.label — per-agent token, ledger.\n\nconst channelAssignInput = z.object({\n channel: z.literal(\"gitlab\"),\n project: z.string().min(1),\n kind: z.enum([\"issue\", \"merge_request\"]),\n iid: z.string().min(1),\n /** GitLab usernames. Empty array = unassign all. */\n assignees: z.array(z.string()).default([]),\n agentId: z.string().optional(),\n})\ntype ChannelAssignInput = z.infer<typeof channelAssignInput>\n\nconst channelAssignOutput = z.object({\n /** Numeric user_ids assigned (post-resolution from usernames). */\n assigneeIds: z.array(z.number()),\n})\ntype ChannelAssignOutput = z.infer<typeof channelAssignOutput>\n\nexport const channelAssign: BuiltinAction<ChannelAssignInput, ChannelAssignOutput> = {\n name: \"channel.assign\",\n description: \"Set assignees on a GitLab issue or MR (replaces the assignee list). Pass empty array to unassign. Use during Triage→To Do flip to assign the coding agent, or to designate reviewer/merger on an MR.\",\n inputSchema: channelAssignInput,\n outputSchema: channelAssignOutput,\n timeoutMs: 30_000,\n handler: async (input) => {\n const router = getMessageRouter()\n if (!router) throw new Error(\"message router not wired\")\n const adapter = (router as any).getChannel?.(input.channel) as\n | { setAssignees?: (args: { project: string; kind?: \"issue\" | \"merge_request\"; iid: string; assignees: string[]; agentId?: string }) => Promise<number[] | null> }\n | undefined\n if (!adapter?.setAssignees) {\n throw new Error(`channel \"${input.channel}\" does not expose setAssignees`)\n }\n const ids = await adapter.setAssignees({\n project: input.project,\n kind: input.kind,\n iid: input.iid,\n assignees: input.assignees,\n agentId: input.agentId,\n })\n return { assigneeIds: Array.isArray(ids) ? ids : [] }\n },\n}\n\n// --- channel.create_issue ---\n//\n// Create a GitLab issue from inside an agent task. PM uses this to spin\n// off test-case issues from the parent dev issue; CX uses it to file\n// issues from WhatsApp/email client requests. The /relate cross-link\n// happens on a follow-up call — the GitLab API doesn't take it inline.\n\nconst channelCreateIssueInput = z.object({\n channel: z.literal(\"gitlab\"),\n project: z.string().min(1),\n title: z.string().min(1),\n description: z.string().default(\"\"),\n labels: z.array(z.string()).default([]),\n /** Usernames to assign on creation. */\n assignees: z.array(z.string()).default([]),\n agentId: z.string().optional(),\n /** Optional: when set, the new issue is /relate'd to this iid via a\n * follow-up POST to /issues/:src/links — same project. Caller-friendly\n * shortcut so the test-case-create flow is one call, not two. */\n relateToIid: z.string().optional(),\n})\ntype ChannelCreateIssueInput = z.infer<typeof channelCreateIssueInput>\n\nconst channelCreateIssueOutput = z.object({\n iid: z.number(),\n url: z.string(),\n related: z.boolean().default(false),\n})\ntype ChannelCreateIssueOutput = z.infer<typeof channelCreateIssueOutput>\n\nexport const channelCreateIssue: BuiltinAction<ChannelCreateIssueInput, ChannelCreateIssueOutput> = {\n name: \"channel.create_issue\",\n description: \"Create a GitLab issue from an agent (PM test cases, CX client requests). Honors per-agent identity; resolves assignee usernames to ids; optional /relate to a parent issue.\",\n inputSchema: channelCreateIssueInput,\n outputSchema: channelCreateIssueOutput,\n timeoutMs: 60_000,\n handler: async (input) => {\n const router = getMessageRouter()\n if (!router) throw new Error(\"message router not wired\")\n const adapter = (router as any).getChannel?.(input.channel) as\n | {\n createIssue?: (args: {\n project: string; title: string; description?: string;\n labels?: string[]; assignees?: string[]; agentId?: string;\n }) => Promise<{ iid: number; url: string } | null>\n relateIssue?: (args: { project: string; sourceIid: string; targetIid: string; agentId?: string }) => Promise<boolean>\n }\n | undefined\n if (!adapter?.createIssue) {\n throw new Error(`channel \"${input.channel}\" does not expose createIssue`)\n }\n const created = await adapter.createIssue({\n project: input.project,\n title: input.title,\n description: input.description,\n labels: input.labels,\n assignees: input.assignees,\n agentId: input.agentId,\n })\n if (!created) {\n throw new Error(\"createIssue returned null — see daemon log for details\")\n }\n let related = false\n if (input.relateToIid && adapter.relateIssue) {\n related = await adapter.relateIssue({\n project: input.project,\n sourceIid: String(created.iid),\n targetIid: input.relateToIid,\n agentId: input.agentId,\n })\n }\n return { iid: created.iid, url: created.url, related }\n },\n}\n","import { z } from \"zod\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- extract.structured ---\n//\n// Single-call structured extraction: ask Claude for a JSON value\n// matching a caller-supplied JSON-Schema. The agent / workflow gets\n// typed structured data back — no parser needed downstream.\n//\n// Implementation: Anthropic Messages API with a forced tool_use whose\n// input_schema is the caller's schema. Forced tool use guarantees the\n// model's response is the structured value, not free-form text. We\n// extract the tool's `input` and return it verbatim.\n//\n// Why we hit the API directly instead of routing through the\n// claude-code subprocess (or the SDK):\n// - The persistent-process path keeps a turn slot busy for >2s\n// even on warm cache; for a single extraction this is overkill.\n// - The SDK adds a 10MB+ dependency; raw fetch needs zero new deps.\n// - The model + auth are the same — ANTHROPIC_API_KEY env var.\n//\n// Input shape:\n// model: Anthropic model id (\"claude-sonnet-4-6\" / \"claude-haiku-4-5\" / etc.)\n// prompt: User-message text the model extracts from.\n// schema: JSON-Schema-shaped object (NOT a Zod schema) — this is what\n// the model is told to fill in. Caller-defined.\n// maxTokens: Output-token cap, default 1024.\n//\n// Output shape:\n// data: The structured value, exactly as the model returned it.\n// Validated only against \"is an object/array/scalar\" — the\n// caller's schema is the contract; we don't re-validate\n// because that would require shipping a JSON-Schema validator.\n// usage: Token counts for cost accounting.\n// model: Model that actually billed (echoed from the response).\n\nconst extractInput = z.object({\n model: z.string().default(\"claude-haiku-4-5\"),\n prompt: z.string().min(1),\n /** JSON-Schema describing the output shape. Passed to the Claude\n * API as the input_schema of a forced tool_use. */\n schema: z.record(z.string(), z.unknown()),\n /** Optional system prompt prepended to the conversation. */\n systemPrompt: z.string().optional(),\n maxTokens: z.number().int().min(1).max(8_000).default(1024),\n /** Per-call timeout. Default 60s. */\n timeoutMs: z.number().int().min(1).max(120_000).default(60_000),\n})\ntype ExtractInput = z.infer<typeof extractInput>\n\nconst extractOutput = z.object({\n data: z.unknown(),\n model: z.string(),\n usage: z.object({\n inputTokens: z.number().int(),\n outputTokens: z.number().int(),\n }),\n})\ntype ExtractOutput = z.infer<typeof extractOutput>\n\nconst ANTHROPIC_API = \"https://api.anthropic.com/v1/messages\"\nconst ANTHROPIC_VERSION = \"2023-06-01\"\n\nexport const extractStructured: BuiltinAction<ExtractInput, ExtractOutput> = {\n name: \"extract.structured\",\n description: \"Single-call typed extraction: prompt + JSON-Schema → structured data via forced tool_use\",\n inputSchema: extractInput,\n outputSchema: extractOutput,\n timeoutMs: 120_000,\n handler: async (input) => {\n const apiKey = process.env.ANTHROPIC_API_KEY\n if (!apiKey) {\n throw new Error(\"ANTHROPIC_API_KEY env var is not set — extract.structured needs direct API access\")\n }\n\n // Use a forced tool_use with input_schema = the caller's schema.\n // tool_choice: { type: \"tool\", name: \"respond_with_structure\" }\n // makes the model produce a tool_use block as the entire reply,\n // whose `input` field IS the structured value.\n const requestBody: Record<string, unknown> = {\n model: input.model,\n max_tokens: input.maxTokens,\n messages: [{ role: \"user\", content: input.prompt }],\n tools: [{\n name: \"respond_with_structure\",\n description: \"Return a structured value matching the schema\",\n input_schema: input.schema,\n }],\n tool_choice: { type: \"tool\", name: \"respond_with_structure\" },\n }\n if (input.systemPrompt) {\n requestBody.system = input.systemPrompt\n }\n\n const ctrl = new AbortController()\n const t = setTimeout(() => ctrl.abort(), input.timeoutMs)\n let res: Response\n try {\n res = await fetch(ANTHROPIC_API, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_VERSION,\n },\n body: JSON.stringify(requestBody),\n signal: ctrl.signal,\n })\n } finally {\n clearTimeout(t)\n }\n\n if (!res.ok) {\n const errBody = await res.text().catch(() => \"\")\n throw new Error(`anthropic API ${res.status}: ${errBody.slice(0, 500)}`)\n }\n const body: any = await res.json()\n const blocks = Array.isArray(body.content) ? body.content : []\n const toolUse = blocks.find((b: any) => b?.type === \"tool_use\" && b?.name === \"respond_with_structure\")\n if (!toolUse || toolUse.input === undefined) {\n throw new Error(`anthropic API returned no respond_with_structure tool_use (got ${blocks.length} blocks)`)\n }\n\n const usage = body.usage ?? {}\n return {\n data: toolUse.input,\n model: typeof body.model === \"string\" ? body.model : input.model,\n usage: {\n inputTokens: typeof usage.input_tokens === \"number\" ? usage.input_tokens : 0,\n outputTokens: typeof usage.output_tokens === \"number\" ? usage.output_tokens : 0,\n },\n }\n },\n}\n","import { z } from \"zod\"\nimport { existsSync, readFileSync, appendFileSync, mkdirSync, statSync } from \"fs\"\nimport { resolve, dirname, isAbsolute } from \"path\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- Built-in file actions ---\n//\n// file.read_lines — read a text file, return up to maxLines string lines\n// file.write_jsonl — append N JSON objects as newline-delimited records\n//\n// Both are workspace-scoped: paths must resolve under one of the\n// allowed roots set via AGENTX_BUILTIN_FILE_ROOTS env var (or the cwd\n// by default). This is the boundary that keeps an agent from reading\n// /etc/passwd or writing into the daemon's own .agentx state. Path\n// traversal (../..) is rejected via the resolved-path containment\n// check, not regex on the input.\n//\n// Why these two specifically (per improvement plan #6 catalog):\n// - read_lines: typed plumbing for \"give me the last N log lines\"\n// pattern that workflows currently shell out to `tail` for.\n// - write_jsonl: typed plumbing for \"log a structured event into\n// this agent's working file\" — the workflow-as-program framing\n// wants this as a primitive, not as `echo {} >> file`.\n\nconst MAX_FILE_BYTES = 8 * 1024 * 1024 // 8 MB cap on read\n\n/**\n * Resolve and validate a target path against the allowed roots.\n * Returns the absolute path on success; throws otherwise. Allowed\n * roots default to process.cwd() unless the operator overrides via\n * AGENTX_BUILTIN_FILE_ROOTS (colon-separated). This mirrors how\n * Claude Code's own --add-dir flag is applied at spawn.\n */\nfunction resolveAllowed(input: string): string {\n const abs = isAbsolute(input) ? input : resolve(process.cwd(), input)\n const rootsEnv = process.env.AGENTX_BUILTIN_FILE_ROOTS || process.cwd()\n const roots = rootsEnv.split(\":\").map((r) => resolve(r))\n for (const root of roots) {\n if (abs === root || abs.startsWith(root + \"/\")) return abs\n }\n throw new Error(`path \"${input}\" is outside the allowed roots`)\n}\n\nconst readLinesInput = z.object({\n path: z.string().min(1),\n /** Cap the returned line count. Default 200, hard cap 10000. */\n maxLines: z.number().int().min(1).max(10_000).default(200),\n /** When true, return only the LAST maxLines (tail-style). Default false (head). */\n fromEnd: z.boolean().default(false),\n})\ntype ReadLinesInput = z.infer<typeof readLinesInput>\n\nconst readLinesOutput = z.object({\n path: z.string(),\n lines: z.array(z.string()),\n totalLines: z.number().int(),\n truncated: z.boolean(),\n})\ntype ReadLinesOutput = z.infer<typeof readLinesOutput>\n\nexport const fileReadLines: BuiltinAction<ReadLinesInput, ReadLinesOutput> = {\n name: \"file.read_lines\",\n description: \"Read a text file as an array of lines (utf-8, 8MB cap, scoped to allowed roots)\",\n inputSchema: readLinesInput,\n outputSchema: readLinesOutput,\n timeoutMs: 10_000,\n handler: async (input) => {\n const abs = resolveAllowed(input.path)\n if (!existsSync(abs)) throw new Error(`file not found: ${input.path}`)\n const stat = statSync(abs)\n if (!stat.isFile()) throw new Error(`not a regular file: ${input.path}`)\n if (stat.size > MAX_FILE_BYTES) {\n throw new Error(`file too large (${stat.size} > ${MAX_FILE_BYTES} bytes)`)\n }\n const content = readFileSync(abs, \"utf8\")\n // Split on \\n but drop a trailing empty token if the file ended in \\n —\n // the canonical \"logs end with a newline\" case.\n const all = content.split(\"\\n\")\n if (all.length > 0 && all[all.length - 1] === \"\") all.pop()\n const totalLines = all.length\n let lines: string[]\n let truncated: boolean\n if (totalLines <= input.maxLines) {\n lines = all\n truncated = false\n } else {\n truncated = true\n lines = input.fromEnd ? all.slice(totalLines - input.maxLines) : all.slice(0, input.maxLines)\n }\n return { path: abs, lines, totalLines, truncated }\n },\n}\n\nconst writeJsonlInput = z.object({\n path: z.string().min(1),\n /** Records to append, one JSON line each. Each must be a JSON-serializable\n * value; non-serializable inputs are rejected before any FS write. */\n records: z.array(z.unknown()).min(1),\n /** When true, ensure the parent directory exists (mkdir -p). Default false. */\n createDirs: z.boolean().default(false),\n})\ntype WriteJsonlInput = z.infer<typeof writeJsonlInput>\n\nconst writeJsonlOutput = z.object({\n path: z.string(),\n written: z.number().int(),\n bytesAppended: z.number().int(),\n})\ntype WriteJsonlOutput = z.infer<typeof writeJsonlOutput>\n\nexport const fileWriteJsonl: BuiltinAction<WriteJsonlInput, WriteJsonlOutput> = {\n name: \"file.write_jsonl\",\n description: \"Append JSON records to a file as newline-delimited JSON (utf-8, scoped to allowed roots)\",\n inputSchema: writeJsonlInput,\n outputSchema: writeJsonlOutput,\n timeoutMs: 10_000,\n handler: async (input) => {\n const abs = resolveAllowed(input.path)\n\n // Pre-serialize EVERYTHING before opening the file so a bad\n // record can't leave a half-written file behind. Each record\n // becomes one JSON line; embedded newlines in strings are\n // escaped naturally by JSON.stringify.\n const lines: string[] = []\n for (const r of input.records) {\n try {\n lines.push(JSON.stringify(r))\n } catch (e: any) {\n throw new Error(`record ${lines.length} not JSON-serializable: ${e?.message || String(e)}`)\n }\n }\n const payload = lines.join(\"\\n\") + \"\\n\"\n\n if (input.createDirs) {\n mkdirSync(dirname(abs), { recursive: true })\n }\n appendFileSync(abs, payload, \"utf8\")\n return { path: abs, written: lines.length, bytesAppended: Buffer.byteLength(payload, \"utf8\") }\n },\n}\n","import { z } from \"zod\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- Built-in HTTP actions ---\n//\n// http.fetch — typed GET, returns body+status+headers\n// http.post — typed POST with JSON body, same response shape\n//\n// Why bake these in when agents already have Bash + curl: the typed\n// shape lets workflows pipe response data into structured downstream\n// nodes without parsing curl output. Headers and status are\n// first-class. Errors are returned as a result, not thrown — so\n// workflow conditional nodes can branch on them.\n//\n// Body size is capped at 1MB to keep responses bounded; truncate is\n// reported back. Allowed schemes are http and https only — file:// /\n// data:// / etc. would let an agent read arbitrary local resources\n// through this action, which violates the \"agents talk over the\n// network\" boundary.\n\nconst MAX_BODY_BYTES = 1024 * 1024 // 1 MB\n\nconst HEADER_RECORD = z.record(z.string(), z.string())\n\nconst allowedSchemes = [\"http:\", \"https:\"] as const\n\nfunction assertAllowed(url: string): URL {\n let u: URL\n try { u = new URL(url) }\n catch { throw new Error(`invalid url: ${url}`) }\n if (!(allowedSchemes as readonly string[]).includes(u.protocol)) {\n throw new Error(`scheme not allowed (only http/https): ${u.protocol}`)\n }\n return u\n}\n\nconst fetchInput = z.object({\n url: z.string().min(1),\n headers: HEADER_RECORD.optional(),\n /** Per-call timeout in ms. Default 30s. Capped at the registry's\n * 60s ceiling regardless. */\n timeoutMs: z.number().int().min(1).max(60_000).default(30_000),\n})\ntype FetchInput = z.infer<typeof fetchInput>\n\nconst fetchOutput = z.object({\n status: z.number().int(),\n statusText: z.string(),\n headers: HEADER_RECORD,\n body: z.string(),\n truncated: z.boolean(),\n url: z.string(),\n})\ntype FetchOutput = z.infer<typeof fetchOutput>\n\nasync function readCappedText(res: Response): Promise<{ body: string; truncated: boolean }> {\n if (!res.body) return { body: \"\", truncated: false }\n const reader = res.body.getReader()\n const dec = new TextDecoder()\n let total = 0\n let body = \"\"\n let truncated = false\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n if (value) {\n total += value.length\n if (total > MAX_BODY_BYTES) {\n // Keep what we have, mark truncated, stop reading.\n body += dec.decode(value.slice(0, MAX_BODY_BYTES - (total - value.length)), { stream: false })\n truncated = true\n try { await reader.cancel() } catch { /* */ }\n break\n }\n body += dec.decode(value, { stream: true })\n }\n }\n body += dec.decode()\n return { body, truncated }\n}\n\nfunction headersToObject(h: Headers): Record<string, string> {\n const out: Record<string, string> = {}\n h.forEach((v, k) => { out[k] = v })\n return out\n}\n\nexport const httpFetch: BuiltinAction<FetchInput, FetchOutput> = {\n name: \"http.fetch\",\n description: \"GET a URL, return body + status + headers (1MB cap, http/https only)\",\n inputSchema: fetchInput,\n outputSchema: fetchOutput,\n timeoutMs: 35_000,\n handler: async (input) => {\n assertAllowed(input.url)\n const ctrl = new AbortController()\n const t = setTimeout(() => ctrl.abort(), input.timeoutMs)\n try {\n const res = await fetch(input.url, { method: \"GET\", headers: input.headers ?? {}, signal: ctrl.signal })\n const { body, truncated } = await readCappedText(res)\n return {\n status: res.status,\n statusText: res.statusText,\n headers: headersToObject(res.headers),\n body,\n truncated,\n url: res.url,\n }\n } finally {\n clearTimeout(t)\n }\n },\n}\n\nconst postInput = z.object({\n url: z.string().min(1),\n /** JSON body, serialized to string at request time. Caller passes\n * any JSON-serializable value here — strings, objects, arrays. */\n body: z.unknown(),\n headers: HEADER_RECORD.optional(),\n timeoutMs: z.number().int().min(1).max(60_000).default(30_000),\n})\ntype PostInput = z.infer<typeof postInput>\n\nexport const httpPost: BuiltinAction<PostInput, FetchOutput> = {\n name: \"http.post\",\n description: \"POST a JSON body to a URL, return body + status + headers (1MB cap, http/https only)\",\n inputSchema: postInput,\n outputSchema: fetchOutput,\n timeoutMs: 35_000,\n handler: async (input) => {\n assertAllowed(input.url)\n const ctrl = new AbortController()\n const t = setTimeout(() => ctrl.abort(), input.timeoutMs)\n try {\n const headers = { \"Content-Type\": \"application/json\", ...(input.headers ?? {}) }\n const res = await fetch(input.url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(input.body ?? {}),\n signal: ctrl.signal,\n })\n const { body, truncated } = await readCappedText(res)\n return {\n status: res.status,\n statusText: res.statusText,\n headers: headersToObject(res.headers),\n body,\n truncated,\n url: res.url,\n }\n } finally {\n clearTimeout(t)\n }\n },\n}\n","import { z } from \"zod\"\nimport { getMesh } from \"@/a2a/mesh-instance\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- mesh.delegate ---\n//\n// Send a task to a remote agentx peer (mesh node) and return the\n// peer's response text. Same wire as the legacy POST /mesh/task HTTP\n// route, but typed and callable directly from agents and workflows\n// without a Bash shell-out — exactly the \"mesh.delegate (peer call,\n// no shell required)\" line item from improvement plan #6.\n//\n// Why this matters even though POST /mesh/task already exists:\n// - Workflows can pipe the response into a typed downstream node.\n// - Agent-side, the call is one /api/actions/builtin POST instead\n// of \"shell out, parse JSON, hope you wrote the curl right\".\n// - The action interface advertises peer-name + agent-name shape\n// so authoring tools can validate before dispatch.\n\nconst meshDelegateInput = z.object({\n /** Mesh peer name as configured in mesh.peers[].name. */\n peer: z.string().min(1),\n /** Optional remote agent id; falls back to first agent on the peer. */\n agent: z.string().optional(),\n message: z.string().min(1),\n /** Per-call timeout. Default 90s; capped by the registry's 60s\n * ceiling unless the action declares a larger timeoutMs. */\n timeoutMs: z.number().int().min(1).max(600_000).default(90_000),\n /** Identity of the agent making the delegated call. Forwarded to\n * the receiving daemon for route-trace + (eventually) capability\n * validation. Optional during the log-warn rollout. */\n senderAgentId: z.string().optional(),\n /** Force a fresh session on the receiving agent. Defaults to TRUE\n * for cross-agent delegation — without this, a triage agent's\n * delegation reuses whatever conversation the worker pool last\n * served, leaking the previous visitor's context (run-3 / run-4\n * benchmark finding). Pass `false` only when you genuinely want\n * the worker to keep state across delegations. */\n freshSession: z.boolean().default(true),\n})\ntype MeshDelegateInput = z.infer<typeof meshDelegateInput>\n\nconst meshDelegateOutput = z.object({\n peer: z.string(),\n agent: z.string().nullable(),\n response: z.string(),\n})\ntype MeshDelegateOutput = z.infer<typeof meshDelegateOutput>\n\nexport const meshDelegate: BuiltinAction<MeshDelegateInput, MeshDelegateOutput> = {\n name: \"mesh.delegate\",\n description: \"Send a task to a remote mesh peer's agent and return the response text\",\n inputSchema: meshDelegateInput,\n outputSchema: meshDelegateOutput,\n // Allow the action's max well above the registry default so a\n // long peer task isn't capped at 60s.\n timeoutMs: 600_000,\n handler: async (input) => {\n const mesh = getMesh()\n if (!mesh) {\n throw new Error(\"mesh not enabled on this daemon (config.mesh.enabled = false)\")\n }\n const response = await mesh.sendTask(input.peer, input.message, input.agent, {\n timeoutMs: input.timeoutMs,\n senderAgentId: input.senderAgentId,\n freshSession: input.freshSession,\n })\n return {\n peer: input.peer,\n agent: input.agent ?? null,\n response,\n }\n },\n}\n","import type { A2AMesh } from \"./mesh\"\n\n// --- Singleton accessor for the live A2AMesh ---\n//\n// Mirrors src/agents/process-registry-instance.ts. The daemon\n// instantiates A2AMesh in its boot sequence and registers it here so\n// process-global consumers (built-in actions, mesh.delegate) can reach\n// it without changing every call site's signature. Setter is null-able\n// because mesh-disabled deployments still need a valid module.\n\nlet _mesh: A2AMesh | null = null\n\nexport function getMesh(): A2AMesh | null {\n return _mesh\n}\n\nexport function setMesh(m: A2AMesh | null): void {\n _mesh = m\n}\n\n/** Test-only — clears the reference between cases. */\nexport function _resetMeshForTesting(): void {\n _mesh = null\n}\n","import { z } from \"zod\"\nimport { lexicalSearch } from \"@/rag/lexical-index\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- rag.lexical ---\n//\n// Improvement plan #7 — embedding-free retrieval-augmented context.\n// Looks up an agent's lexical (BM25) index built via `agentx rag add`\n// and returns the top hits with score + path + body excerpt. No\n// embedding API call, no key, deterministic.\n\nconst ragLexicalInput = z.object({\n /** Agent whose index to query. Each agent has its own index at\n * .agentx/rag/<agentId>/. */\n agentId: z.string().min(1),\n query: z.string().min(1),\n /** Top-k results. Default 5, hard cap 50. */\n k: z.number().int().min(1).max(50).default(5),\n})\ntype RagLexicalInput = z.infer<typeof ragLexicalInput>\n\nconst ragLexicalOutput = z.object({\n hits: z.array(z.object({\n id: z.string(),\n title: z.string(),\n path: z.string(),\n score: z.number(),\n snippet: z.string(),\n })),\n})\ntype RagLexicalOutput = z.infer<typeof ragLexicalOutput>\n\nexport const ragLexical: BuiltinAction<RagLexicalInput, RagLexicalOutput> = {\n name: \"rag.lexical\",\n description: \"BM25-style lexical search over an agent's pre-built index (no embeddings, deterministic)\",\n inputSchema: ragLexicalInput,\n outputSchema: ragLexicalOutput,\n timeoutMs: 5_000,\n handler: async (input) => {\n const hits = lexicalSearch(input.agentId, input.query, { k: input.k })\n return { hits }\n },\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from \"fs\"\nimport { resolve, basename, relative } from \"path\"\nimport fg from \"fast-glob\"\n// minisearch ships both CJS and ESM; tsup transpiles either way.\nimport MiniSearch from \"minisearch\"\n\n// --- Lexical RAG index per agent ---\n//\n// Improvement plan #7 — embedding-free retrieval-augmented context for\n// agents. BM25-style scoring via the minisearch library (~30KB pure\n// JS, no native deps). Each agent gets its own index at\n// `.agentx/rag/<agentId>/index.json`; `agentx rag add` builds it from\n// a glob, the rag.lexical built-in action queries it.\n//\n// Fields indexed: title (extracted from first H1 or filename) + body\n// (full file text). Title is boosted 2× at search time so a\n// well-titled doc beats a body-only mention. Path is stored as a\n// non-indexed field so query results can cite the source file.\n//\n// Why not use embeddings: zero new keys, deterministic results, fast\n// (sub-ms search even with hundreds of docs), works offline. The\n// trade is no semantic synonymy — but for the kinds of operator\n// docs / wiki entries / playbook lookups agents typically need,\n// keyword recall is enough. Embedding-backed mode is opt-in\n// future work.\n\nexport interface IndexedDoc {\n /** Stable id within an index — usually the workspace-relative path. */\n id: string\n title: string\n body: string\n /** Absolute path on disk. Stored, not indexed. */\n path: string\n /** ISO timestamp when this doc was indexed. */\n indexedAt: string\n}\n\nexport interface RagSearchHit {\n id: string\n title: string\n path: string\n score: number\n /** A short body excerpt around the highest-scoring term. Useful for\n * citation-style \"here's what we found\" responses. */\n snippet: string\n}\n\nconst FIELDS_INDEXED = [\"title\", \"body\"]\nconst FIELDS_STORED = [\"title\", \"body\", \"path\", \"indexedAt\"]\n\nfunction indexFilePath(agentId: string, baseDir: string = \".agentx/rag\"): string {\n return resolve(process.cwd(), baseDir, agentId, \"index.json\")\n}\n\nfunction newSearchEngine(): MiniSearch<IndexedDoc> {\n return new MiniSearch<IndexedDoc>({\n fields: FIELDS_INDEXED,\n storeFields: FIELDS_STORED,\n searchOptions: { boost: { title: 2 }, fuzzy: 0.2, prefix: true },\n })\n}\n\n/**\n * Read a markdown / text file and split into indexable fields. Title\n * comes from the first H1 line (`# ...`); falls back to the filename\n * stem when there isn't one. Body is the full text.\n */\nfunction fileToDoc(absPath: string, idBase: string): IndexedDoc {\n const text = readFileSync(absPath, \"utf8\")\n const h1Match = text.match(/^\\s*#\\s+(.+)$/m)\n const title = h1Match ? h1Match[1].trim() : basename(absPath).replace(/\\.[^.]+$/, \"\")\n return {\n id: idBase,\n title,\n body: text,\n path: absPath,\n indexedAt: new Date().toISOString(),\n }\n}\n\n/**\n * Build an index from one or more globs. Globs resolve from cwd. The\n * resulting index is persisted under .agentx/rag/<agentId>/index.json\n * and returned to the caller for synchronous use.\n */\nexport async function buildIndex(\n agentId: string,\n globs: string[],\n opts: { baseDir?: string; cwd?: string; verbose?: boolean } = {},\n): Promise<{ docs: number; path: string }> {\n const cwd = opts.cwd ?? process.cwd()\n const files = (await fg(globs, { cwd, absolute: true, dot: false }))\n .filter((p) => {\n try { return statSync(p).isFile() } catch { return false }\n })\n\n const ms = newSearchEngine()\n for (const abs of files) {\n const idBase = relative(cwd, abs)\n const doc = fileToDoc(abs, idBase)\n ms.add(doc)\n if (opts.verbose) process.stderr.write(`[rag] indexed ${idBase} (${doc.body.length} bytes)\\n`)\n }\n\n const out = indexFilePath(agentId, opts.baseDir)\n mkdirSync(resolve(out, \"..\"), { recursive: true })\n writeFileSync(out, JSON.stringify(ms.toJSON()), \"utf8\")\n return { docs: files.length, path: out }\n}\n\n/**\n * Load a previously-built index. Returns null when no index exists for\n * this agent (caller can fall back to \"no RAG\" gracefully). Loading is\n * a small JSON parse + minisearch deserialize — sub-millisecond on\n * typical operator-doc indices.\n */\nexport function loadIndex(\n agentId: string,\n opts: { baseDir?: string } = {},\n): MiniSearch<IndexedDoc> | null {\n const path = indexFilePath(agentId, opts.baseDir)\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, \"utf8\")\n return MiniSearch.loadJSON<IndexedDoc>(raw, {\n fields: FIELDS_INDEXED,\n storeFields: FIELDS_STORED,\n searchOptions: { boost: { title: 2 }, fuzzy: 0.2, prefix: true },\n })\n } catch (e: any) {\n process.stderr.write(`[rag] failed to load ${path}: ${e?.message || String(e)}\\n`)\n return null\n }\n}\n\nconst SNIPPET_RADIUS = 80\nconst SNIPPET_MAX = SNIPPET_RADIUS * 2 + 60\n\n/** Extract a snippet around the FIRST occurrence of any of `terms` in\n * `body`. Returns the head of the body when no term hits. */\nfunction makeSnippet(body: string, terms: string[]): string {\n const lc = body.toLowerCase()\n let bestPos = -1\n for (const t of terms) {\n const i = lc.indexOf(t.toLowerCase())\n if (i >= 0 && (bestPos === -1 || i < bestPos)) bestPos = i\n }\n if (bestPos === -1) {\n return body.slice(0, SNIPPET_MAX).replace(/\\s+/g, \" \").trim()\n }\n const start = Math.max(0, bestPos - SNIPPET_RADIUS)\n const end = Math.min(body.length, bestPos + SNIPPET_RADIUS)\n const slice = body.slice(start, end).replace(/\\s+/g, \" \").trim()\n return (start > 0 ? \"…\" : \"\") + slice + (end < body.length ? \"…\" : \"\")\n}\n\nexport interface SearchOpts {\n /** How many top hits to return. Default 5, capped at 50. */\n k?: number\n baseDir?: string\n}\n\n/**\n * Lexical query against an agent's index. Returns at most `k` results,\n * each with a score, the title, the source path, and a snippet around\n * the matched term. Empty array when no index exists or no hits.\n */\nexport function lexicalSearch(\n agentId: string,\n query: string,\n opts: SearchOpts = {},\n): RagSearchHit[] {\n const ms = loadIndex(agentId, { baseDir: opts.baseDir })\n if (!ms) return []\n const k = Math.max(1, Math.min(50, opts.k ?? 5))\n const raw = ms.search(query) as unknown as Array<{\n id: string\n score: number\n title: string\n body: string\n path: string\n terms: string[]\n }>\n return raw.slice(0, k).map((r) => ({\n id: r.id,\n title: r.title,\n path: r.path,\n score: r.score,\n snippet: makeSnippet(r.body || \"\", r.terms || []),\n }))\n}\n\n/** List metadata about an agent's index without loading the full thing. */\nexport function indexInfo(agentId: string, opts: { baseDir?: string } = {}): {\n exists: boolean\n path: string\n docs: number\n} {\n const path = indexFilePath(agentId, opts.baseDir)\n if (!existsSync(path)) return { exists: false, path, docs: 0 }\n try {\n const raw = JSON.parse(readFileSync(path, \"utf8\"))\n // minisearch v7 puts the stored fields under `storedFields` (no\n // leading underscore), keyed by internal numeric doc id. Earlier\n // versions used `_storedFields`. Honour both for robustness across\n // minor minisearch upgrades.\n const sf = raw?.storedFields ?? raw?._storedFields\n if (typeof raw?.documentCount === \"number\") return { exists: true, path, docs: raw.documentCount }\n const docs = sf && typeof sf === \"object\" ? Object.keys(sf).length : 0\n return { exists: true, path, docs }\n } catch {\n return { exists: true, path, docs: 0 }\n }\n}\n","import { z } from \"zod\"\nimport { getSignalService } from \"@/agents/signals/instance\"\nimport type { BuiltinAction } from \"./types\"\n\n// --- signal.resume (#857) ---\n//\n// Resume a task a stop signal paused, from a workflow step: the task runs\n// again in its own chat with its resume plan prepended. The step acts for\n// the owner who wrote the workflow; `by` names it in the event and plan.\n\nconst signalResumeInput = z.object({\n /** The stopped task's id (`agentx signal list`). */\n id: z.string().min(1),\n reason: z.string().max(500).optional(),\n /** Who to name as the resumer. Default \"workflow\". */\n by: z.string().min(1).max(100).default(\"workflow\"),\n})\ntype SignalResumeInput = z.infer<typeof signalResumeInput>\n\nconst signalResumeOutput = z.object({\n ok: z.boolean(),\n id: z.string(),\n agentId: z.string().nullable(),\n error: z.string().optional(),\n})\ntype SignalResumeOutput = z.infer<typeof signalResumeOutput>\n\nexport const signalResume: BuiltinAction<SignalResumeInput, SignalResumeOutput> = {\n name: \"signal.resume\",\n description: \"Resume a task a stop signal paused, from its resume plan, in the same chat\",\n inputSchema: signalResumeInput,\n outputSchema: signalResumeOutput,\n handler: async (input) => {\n const service = getSignalService()\n if (!service) return { ok: false, id: input.id, agentId: null, error: \"signals are not running in this process\" }\n const r = await service.resume({ kind: \"owner\", name: input.by }, input.id, input.reason)\n if (!r.ok) return { ok: false, id: input.id, agentId: service.get(input.id)?.agentId ?? null, error: r.error }\n return { ok: true, id: r.record.id, agentId: r.record.agentId }\n },\n}\n","import type { SignalService } from \"./service\"\n\n// Process-wide handle, set by the daemon once the service exists, so the\n// `signal.resume` built-in action (workflow steps) can reach it.\n\nlet instance: SignalService | undefined\n\nexport function setSignalService(s: SignalService | undefined): void {\n instance = s\n}\n\nexport function getSignalService(): SignalService | undefined {\n return instance\n}\n","// --- Built-in actions barrel ---\n//\n// Importing this module registers every shipped built-in into the\n// process-global registry. The daemon imports it once at startup so\n// callers (HTTP routes, CLI, workflows) can look actions up by name.\n//\n// To add a new built-in: define it in a sibling file, import here,\n// pass it to registerBuiltin(). Keep the imports alphabetical for\n// human review.\n\nimport { registerBuiltin, _resetBuiltinsForTesting as _registryReset } from \"./registry\"\nimport { agentCall } from \"./agent\"\nimport { channelAssign, channelCreateIssue, channelLabel, channelReply } from \"./channel\"\nimport { extractStructured } from \"./extract\"\nimport { fileReadLines, fileWriteJsonl } from \"./file\"\nimport { httpFetch, httpPost } from \"./http\"\nimport { meshDelegate } from \"./mesh\"\nimport { ragLexical } from \"./rag\"\nimport { signalResume } from \"./signal\"\n\n/**\n * Register every shipped built-in. Idempotent — re-registering the\n * same name overwrites with the latest handler (registerBuiltin's\n * own contract), so calling this multiple times is safe and matches\n * the in-memory registry's current state.\n */\nexport function registerAllBuiltins(): void {\n registerBuiltin(agentCall)\n registerBuiltin(channelAssign)\n registerBuiltin(channelCreateIssue)\n registerBuiltin(channelLabel)\n registerBuiltin(channelReply)\n registerBuiltin(extractStructured)\n registerBuiltin(fileReadLines)\n registerBuiltin(fileWriteJsonl)\n registerBuiltin(httpFetch)\n registerBuiltin(httpPost)\n registerBuiltin(meshDelegate)\n registerBuiltin(ragLexical)\n registerBuiltin(signalResume)\n}\n\n/** Test-only — clears the registry. Re-call registerAllBuiltins to repopulate. */\nexport function _resetBuiltinsForTesting(): void {\n _registryReset()\n}\n\nexport { getBuiltin, listBuiltins, runBuiltin } from \"./registry\"\nexport type { BuiltinAction, BuiltinActionMetadata } from \"./types\"\n"],"mappings":"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","names":["_registry","NAME_PATTERN","registerBuiltin","action","getBuiltin","name","listBuiltins","out","a","b","_resetBuiltinsForTesting","DEFAULT_TIMEOUT_MS","runBuiltin","rawInput","input","timeoutMs","handlerPromise","timeoutPromise","_","reject","output","parsed","z","_registry","getAgentRegistry","setAgentRegistry","r","agentCallInput","z","agentCallOutput","agentCall","input","registry","getAgentRegistry","chatId","started","response","z","channelReplyInput","z","channelReplyOutput","channelReply","input","router","getMessageRouter","idempotencyKey","messageId","channelLabelInput","channelLabelOutput","channelLabel","adapter","labels","channelAssignInput","channelAssignOutput","channelAssign","ids","channelCreateIssueInput","channelCreateIssueOutput","channelCreateIssue","created","related","z","extractInput","extractOutput","ANTHROPIC_API","ANTHROPIC_VERSION","extractStructured","input","apiKey","requestBody","ctrl","t","res","errBody","body","blocks","toolUse","b","usage","z","existsSync","readFileSync","appendFileSync","mkdirSync","statSync","resolve","dirname","isAbsolute","MAX_FILE_BYTES","resolveAllowed","input","abs","roots","r","root","readLinesInput","readLinesOutput","fileReadLines","stat","all","totalLines","lines","truncated","writeJsonlInput","writeJsonlOutput","fileWriteJsonl","e","payload","z","MAX_BODY_BYTES","HEADER_RECORD","allowedSchemes","assertAllowed","url","u","fetchInput","fetchOutput","readCappedText","res","reader","dec","total","body","truncated","done","value","headersToObject","h","out","v","k","httpFetch","input","ctrl","postInput","httpPost","headers","z","_mesh","getMesh","setMesh","m","meshDelegateInput","z","meshDelegateOutput","meshDelegate","input","mesh","getMesh","response","z","existsSync","mkdirSync","readFileSync","writeFileSync","statSync","resolve","basename","relative","fg","MiniSearch","FIELDS_INDEXED","FIELDS_STORED","indexFilePath","agentId","baseDir","newSearchEngine","fileToDoc","absPath","idBase","text","h1Match","title","buildIndex","globs","opts","cwd","files","p","ms","abs","doc","out","loadIndex","path","raw","e","SNIPPET_RADIUS","SNIPPET_MAX","makeSnippet","body","terms","lc","bestPos","t","i","start","end","slice","lexicalSearch","query","k","r","indexInfo","sf","docs","ragLexicalInput","z","ragLexicalOutput","ragLexical","input","lexicalSearch","z","instance","setSignalService","s","getSignalService","signalResumeInput","z","signalResumeOutput","signalResume","input","service","getSignalService","r","registerAllBuiltins","registerBuiltin","agentCall","channelAssign","channelCreateIssue","channelLabel","channelReply","extractStructured","fileReadLines","fileWriteJsonl","httpFetch","httpPost","meshDelegate","ragLexical","signalResume","_resetBuiltinsForTesting"]}
|
package/dist/chunk-Q4XP5TBI.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import{a as w,b as f}from"./chunk-SHEUCITK.js";import{randomUUID as E}from"crypto";var y=["ringing","answered","later"],p=`id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,
|
|
2
|
-
ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,
|
|
3
|
-
ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`,A=class{constructor(t){this.db=t;t.exec(`CREATE TABLE IF NOT EXISTS calls (
|
|
4
|
-
id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,
|
|
5
|
-
urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,
|
|
6
|
-
ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,
|
|
7
|
-
ring_again_at INTEGER, note TEXT, summary TEXT);
|
|
8
|
-
CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`);let s=new Set(t.prepare("PRAGMA table_info(calls)").all().map(e=>e.name));s.has("kind")||t.exec("ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'"),s.has("channel")||t.exec("ALTER TABLE calls ADD COLUMN channel TEXT"),s.has("chat_id")||t.exec("ALTER TABLE calls ADD COLUMN chat_id TEXT")}insert(t){this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,
|
|
9
|
-
answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(t.id,t.agentId,t.kind,t.reason,t.urgency,t.status,t.createdAt,t.ringingSince,t.answeredAt,t.endedAt,t.ringAgainAt,t.note,t.summary,t.channel,t.chatId)}get(t){return this.db.prepare(`SELECT ${p} FROM calls WHERE id=?`).get(t)}update(t,s){let e={reason:"reason",urgency:"urgency",status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},n=Object.keys(s).filter(r=>r in e);if(n.length){let r=n.map(i=>`${e[i]}=?`).join(", ");this.db.prepare(`UPDATE calls SET ${r} WHERE id=?`).run(...n.map(i=>s[i]??null),t)}return this.get(t)}transition(t,s,e){let n=Array.isArray(s)?s:[s],r={status:"status",ringingSince:"ringing_since",answeredAt:"answered_at",endedAt:"ended_at",ringAgainAt:"ring_again_at",note:"note",summary:"summary"},i=Object.keys(e).filter(l=>l in r),o=i.map(l=>`${r[l]}=?`).join(", "),u=n.map(()=>"?").join(",");return this.db.prepare(`UPDATE calls SET ${o} WHERE id=? AND status IN (${u})`).run(...i.map(l=>e[l]??null),t,...n).changes?this.get(t):void 0}list(t={}){let s=Math.min(Math.max(t.limit??50,1),200),e=[],n=[];t.status?.length&&(e.push(`status IN (${t.status.map(()=>"?").join(",")})`),n.push(...t.status)),t.kind&&(e.push("kind=?"),n.push(t.kind));let r=e.length?`WHERE ${e.join(" AND ")}`:"";return this.db.prepare(`SELECT ${p} FROM calls ${r} ORDER BY created_at DESC, rowid DESC LIMIT ?`).all(...n,s)}countSince(t,s){return this.db.prepare("SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?").get(t,s).n}};var C=200,S=1e4,I=10,T=240;function L(a=process.env){return a.AGENTX_TASK_ID?{"X-AgentX-Task":a.AGENTX_TASK_ID}:a.AGENTX_CHANNEL&&a.AGENTX_CHAT_ID?{"X-AgentX-Channel":a.AGENTX_CHANNEL,"X-AgentX-Chat":a.AGENTX_CHAT_ID}:{}}function _(a){return`[Call answered] The owner just picked up the voice call you asked for. You called because: "${a.reason}". Speak first: greet them in a few words and say why you called, then let them answer.`}var x="[Call ended] The owner hung up. In two or three plain sentences, summarise what was said and decided on this call, for your records. Reply with the summary only.",k=class{constructor(t){this.deps=t}widgetSeenAt=0;widget={};waiting=new Map;now(){return this.deps.now?.()??Date.now()}name(t){return this.deps.agentName(t)??t}async request(t,s={}){let e=String(t.agentId??"").trim(),n=String(t.reason??"").replace(/\s+/g," ").trim(),r=t.urgency==="urgent"?"urgent":"normal",i=t.kind==="camera"?"camera":"voice";if(t.kind!==void 0&&t.kind!=="voice"&&t.kind!=="camera")return{ok:!1,status:400,error:"kind must be voice or camera"};if(!e||!n)return{ok:!1,status:400,error:"Required: agentId and reason"};if(n.length>C)return{ok:!1,status:413,error:`reason is over ${C} characters; keep it to one line`};if(this.deps.agentName(e)===null)return{ok:!1,status:404,error:`Unknown agent: ${e}`};if(!this.deps.isRunningTurn(e,s))return{ok:!1,status:403,error:`No running turn of ${e} placed this call. An agent calls from inside its own run.`};let o=this.deps.config();if(!o.allow.includes("*")&&!o.allow.includes(e))return{ok:!1,status:403,error:`${e} ${i==="camera"?"may not ask to see through the owner's camera":"may not call the owner"}. Add it to calls.allow in agentx.json (or: agentx call allow ${e}).`};if(await this.sweep(),this.deps.store.list({status:y,limit:200}).some(m=>m.agentId===e))return{ok:!1,status:409,error:`${e} already has a call in progress`};let u=this.now();if(this.deps.store.countSince(e,u-36e5)>=o.maxPerHour)return{ok:!1,status:429,error:`${e} has placed ${o.maxPerHour} calls in the last hour (calls.maxPerHour)`};let d=i==="camera"?this.deps.turnSession?.(e,s)??s:{},l={id:`call-${E().slice(0,8)}`,agentId:e,kind:i,reason:n,urgency:r,status:"ringing",createdAt:u,ringingSince:u,answeredAt:null,endedAt:null,ringAgainAt:null,note:null,summary:null,channel:d.channel&&d.chatId?d.channel:null,chatId:d.channel&&d.chatId?d.chatId:null};this.deps.store.insert(l),this.deps.log?.(`[calls] ${e} asks ${i==="camera"?"to see":"for a call"} (${r}): ${n}`);let g=await this.ring(l),h=this.deps.store.get(l.id);return g!=="waiting"?{ok:!0,call:h,rang:g}:this.waiting.has(l.id)?{ok:!0,call:h,rang:g,busy:this.busyInfo(l)}:{ok:!0,call:h,rang:"widget"}}async cancel(t,s={}){let e=String(t.agentId??"").trim();if(!e)return{ok:!1,status:400,error:"Required: agentId"};if(!this.deps.isRunningTurn(e,s))return{ok:!1,status:403,error:`No running turn of ${e} asked for this. An agent withdraws its own call from inside its run.`};let n=this.deps.store.list({status:["ringing","later"],kind:"voice",limit:200}).find(i=>i.agentId===e);if(!n)return{ok:!1,status:404,error:`${e} has no call waiting or ringing`};let r=this.deps.store.transition(n.id,["ringing","later"],{status:"ended",endedAt:this.now(),ringingSince:null,ringAgainAt:null,note:$});return r?(this.waiting.delete(n.id),this.deps.log?.(`[calls] ${n.id} withdrawn by ${e}`),{ok:!0,call:r}):{ok:!1,status:409,error:`call is ${this.deps.store.get(n.id)?.status}`}}async ring(t){let s=(this.deps.focus??w)();return s.active&&t.urgency!=="urgent"?(this.deps.store.transition(t.id,"ringing",{status:"missed",ringingSince:null,note:f(s)})&&await this.notice(t,this.missedTitle(t)),!1):t.kind==="camera"?(await this.notice(t,`${this.name(t.agentId)} wants to see through your camera`,`Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`),"notify"):this.widgetAlive()?!this.widget.busy&&!this.otherCalls(t).length?"widget":(this.startWaiting(t),await this.callWaiting(t),"waiting"):(await this.notice(t,`${this.name(t.agentId)} is calling`,`Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`),"notify")}async notice(t,s,e){try{await this.deps.alert({title:s,message:e?`${t.reason}
|
|
10
|
-
${e}`:t.reason,urgent:t.urgency==="urgent",from:t.agentId})}catch(n){this.deps.log?.(`[calls] notice for ${t.id} failed: ${n?.message??n}`)}}startWaiting(t){let s=this.now();this.waiting.set(t.id,{since:s,toldAt:s}),this.deps.log?.(`[calls] ${t.id} from ${t.agentId} waits: the widget is busy`)}otherCalls(t){return this.deps.store.list({status:["ringing","answered"],kind:"voice",limit:200}).filter(s=>s.id!==t.id).reverse()}async callWaiting(t){let s=this.deps.config().busy;await this.notice(t,s.title.replaceAll("{agent}",this.name(t.agentId)),s.notice)}busyInfo(t){let s=this.deps.config().busy,{since:e,toldAt:n}=this.waiting.get(t.id)??{since:this.now(),toldAt:this.now()},r=this.otherCalls(t),i=r.find(g=>g.status==="answered"),o=r.find(g=>g.id===this.widget.showing)??r.find(g=>!this.waiting.has(g.id)),u=e+s.maxWaitMinutes*6e4+this.deps.config().ringSeconds*1e3,d=n+s.retryMinutes*6e4,l=e+s.maxWaitMinutes*6e4;return{why:i?"call":o?"ringing":"voice",with:i?this.name(i.agentId):o?this.name(o.agentId):null,remindAt:d<l?d:null,waitsUntil:u}}missedTitle(t){return t.kind==="camera"?`${this.name(t.agentId)} asked to see through your camera`:`Missed call from ${this.name(t.agentId)}`}widgetAlive(){return this.now()-this.widgetSeenAt<S}async ringing(t={}){return this.widgetSeenAt=this.now(),this.widget=t,await this.hold(t),await this.sweep(),this.deps.store.list({status:["ringing"],kind:"voice",limit:20}).reverse()}async hold(t){let s=this.now(),e=this.deps.config().busy,n=[],r=this.deps.store.list({status:["ringing"],kind:"voice",limit:20});for(let i of this.waiting.keys())r.some(o=>o.id===i)||this.waiting.delete(i);for(let i of r){if(i.id===t.showing){this.waiting.delete(i.id)&&this.deps.store.transition(i.id,"ringing",{ringingSince:s});continue}if(!t.busy)continue;let o=this.waiting.get(i.id);if(!(o&&s-o.since>=e.maxWaitMinutes*6e4)&&this.deps.store.transition(i.id,"ringing",{ringingSince:s})){if(!o)this.startWaiting(i);else if(s-o.toldAt>=e.retryMinutes*6e4)o.toldAt=s;else continue;n.push(i)}}for(let i of n)await this.callWaiting(i)}async asking(){return await this.sweep(),this.deps.store.list({status:["ringing"],kind:"camera",limit:20}).reverse()}async sweep(){let t=this.now(),s=this.deps.config().ringSeconds*1e3,e=this.deps.config().maxCallMinutes*6e4;for(let n of this.deps.store.list({status:["ringing","later","answered"],limit:200}))if(n.status==="ringing"&&n.ringingSince!==null&&t-n.ringingSince>=s){if(!this.deps.store.transition(n.id,"ringing",{status:"missed",ringingSince:null,note:this.waiting.has(n.id)?"widget busy":"not answered"}))continue;this.deps.log?.(`[calls] ${n.id} from ${n.agentId} missed`),await this.notice(n,this.missedTitle(n))}else if(n.status==="answered"&&n.answeredAt!==null&&t-n.answeredAt>=e){if(!this.deps.store.transition(n.id,"answered",{status:"ended",endedAt:t,note:"no hang-up"}))continue;this.deps.log?.(`[calls] ${n.id} from ${n.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)}else if(n.status==="later"&&n.ringAgainAt!==null&&t>=n.ringAgainAt){let r=this.deps.store.transition(n.id,"later",{status:"ringing",ringingSince:t,ringAgainAt:null});r&&await this.ring(r)}}answer(t){let s=this.deps.store.get(t);if(!s)return c(t);let e=this.deps.store.transition(t,"ringing",{status:"answered",answeredAt:this.now(),ringingSince:null});return e?(this.deps.log?.(`[calls] ${t} answered (${s.agentId})`),e.kind==="camera"?{ok:!0,call:e}:{ok:!0,call:e,opener:_(e)}):{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}decline(t){if(!this.deps.store.get(t))return c(t);let e=this.deps.store.transition(t,["ringing","later"],{status:"declined",endedAt:this.now(),ringingSince:null,ringAgainAt:null});return e?{ok:!0,call:e}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}`}}later(t,s){if(!this.deps.store.get(t))return c(t);let n=s===void 0?I:Number(s);if(!Number.isInteger(n)||n<1||n>T)return{ok:!1,status:400,error:`minutes must be a whole number from 1 to ${T}`};let r=this.deps.store.transition(t,"ringing",{status:"later",ringingSince:null,ringAgainAt:this.now()+n*6e4});return r?{ok:!0,call:r}:{ok:!1,status:409,error:`call is ${this.deps.store.get(t)?.status}, not ringing`}}hangup(t){let s=this.deps.store.get(t);if(!s)return c(t);let e=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now()});if(!e)return{ok:!1,status:409,error:`call is ${s.status}, not answered`};this.deps.log?.(`[calls] ${t} ended (${s.agentId})`);let n=this.deps.summarize;if(e.kind==="camera"||!this.deps.config().summary||!n)return{ok:!0,call:e};let r=(async()=>{try{let i=(await n(e))?.trim();if(!i)return;let o=this.deps.store.update(t,{summary:i});this.deps.file?.(o,i)}catch(i){this.deps.log?.(`[calls] summary for ${t} failed: ${i?.message??i}`)}})();return{ok:!0,call:e,summarized:r}}endCamera(t,s){let e=this.deps.store.get(t);if(!e||e.kind!=="camera")return!1;let n=this.deps.store.transition(t,"answered",{status:"ended",endedAt:this.now(),note:s??null});return n&&this.deps.log?.(`[calls] ${t} ended (${e.agentId}): ${s??"share over"}`),!!n}get(t){return this.deps.store.get(t)}list(t={}){return this.deps.store.list(t)}},$="withdrawn by the caller";function D(a,t=Date.now()){let s=n=>`${new Date(n).toLocaleTimeString([],{hour:"2-digit",minute:"2-digit",hour12:!1})} (in ${Math.max(1,Math.round((n-t)/6e4))} min)`;return`${a.why==="call"?`The owner is busy in another call${a.with?` (with ${a.with})`:""}.`:a.why==="ringing"?`The owner is busy: another call${a.with?` (from ${a.with})`:""} is ringing.`:"The owner is busy on the voice widget."} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. `+(a.remindAt!==null?`Still busy at ${s(a.remindAt)}, they are told again that you are waiting. `:"")+`It waits until ${s(a.waitsUntil)} at most, then counts as a missed call.`}var c=a=>({ok:!1,status:404,error:`No such call: ${a}`});export{A as a,L as b,x as c,k as d,D as e};
|
|
11
|
-
//# sourceMappingURL=chunk-Q4XP5TBI.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/calls/service.ts","../src/calls/store.ts"],"sourcesContent":["// --- An agent rings the owner (#321) ---\n//\n// An agent that needs the owner directly asks for a call. It rings on the\n// desktop widget (AgentX Voice), which polls `ringing()`. Answering starts\n// the widget's hands-free conversation with that agent, which opens with\n// its reason. When the widget is not running, `notify` stands in. While\n// the widget is busy (a turn, the microphone, another call) the call waits\n// for it: its ring time does not run, and the owner gets a call-waiting\n// notice (#408), again every calls.busy.retryMinutes. The caller is told\n// the owner is busy, with whom, and until when its call waits (#969). It\n// rings by itself once the widget is free, oldest first; the caller may\n// withdraw it meanwhile (`cancel`).\n//\n// Checked before anything rings, in this order:\n// 0. the caller is that agent: a turn of it is running now, named by its\n// task id or by its channel and chat (see CallerProof),\n// 1. the owner allows this agent (calls.allow; empty allows nobody),\n// 2. the agent has no other call in progress,\n// 3. it is under calls.maxPerHour,\n// 4. Focus: a non-urgent call does not ring. It becomes a missed call,\n// and its notice goes through notify, which holds it until Focus\n// ends. Urgent calls ring through, as `notify --urgent` does.\n//\n// A camera ask (#325 phase 3, kind \"camera\") goes through the same checks\n// and the same table. It never rings the widget: the notice goes to the\n// phone through notify, and the phone app polls `asking()` to show a\n// Show / Decline bar. Answering it is the owner tapping Show; the camera\n// opens only then, and the share ends the ask (see camera-api.ts).\n\nimport { randomUUID } from \"crypto\"\nimport { focusLabel, readFocus, type FocusState } from \"@/notify/focus\"\nimport type { CallsConfig } from \"@/daemon/config\"\nimport { LIVE, type Call, type CallKind, type CallStore, type CallUrgency } from \"./store\"\n\nexport const REASON_MAX = 200\n/** The widget polls every 2 s; missing this long means it is not running. */\nexport const WIDGET_FRESH_MS = 10_000\nexport const LATER_DEFAULT_MINUTES = 10\nexport const LATER_MAX_MINUTES = 240\n\n/** What a caller is told when the owner cannot take its call now (#969). */\nexport interface BusyInfo {\n /** \"call\": the owner is in a call; \"ringing\": another call is ringing;\n * \"voice\": AgentX Voice is in a turn of the owner's, or recording. */\n why: \"call\" | \"ringing\" | \"voice\"\n /** The agent the owner is in a call with, or whose call is ringing. */\n with: string | null\n /** When the owner is told again that this call waits, if still busy\n * then; null when the wait ends first. Ms since the epoch. */\n remindAt: number | null\n /** The call stops waiting then: still busy, it counts as missed. */\n waitsUntil: number\n}\n\nexport type CallResult =\n | { ok: true; call: Call }\n | { ok: false; status: number; error: string }\n\n/** What the request says about the turn placing it: the run's\n * AGENTX_TASK_ID, or, for a warm process that has none, its\n * AGENTX_CHANNEL and AGENTX_CHAT_ID. Loopback is not an identity. */\nexport interface CallerProof {\n taskId?: string\n channel?: string\n chatId?: string\n}\n\n/** The proof headers for a call placed from inside an agent's run. */\nexport function callerHeaders(env: NodeJS.ProcessEnv = process.env): Record<string, string> {\n if (env.AGENTX_TASK_ID) return { \"X-AgentX-Task\": env.AGENTX_TASK_ID }\n if (env.AGENTX_CHANNEL && env.AGENTX_CHAT_ID) {\n return { \"X-AgentX-Channel\": env.AGENTX_CHANNEL, \"X-AgentX-Chat\": env.AGENTX_CHAT_ID }\n }\n return {}\n}\n\n/** What the widget says with its poll. A widget from before #408 says nothing. */\nexport interface WidgetState {\n /** It cannot ring a new call now. */\n busy?: boolean\n /** The call ringing on it. */\n showing?: string\n}\n\nexport interface CallDeps {\n store: CallStore\n config: () => CallsConfig\n /** The agent's display name, or null when there is no such agent here. */\n agentName: (agentId: string) => string | null\n /** True when `proof` names a running turn of `agentId`. */\n isRunningTurn: (agentId: string, proof: CallerProof) => boolean\n /** The channel and chat of that running turn, when it has one, so a\n * camera ask is answered where it was asked. Unset: only a proof that\n * names them itself is kept. */\n turnSession?: (agentId: string, proof: CallerProof) => { channel?: string; chatId?: string } | null\n /** Tell the owner through notify. `urgent` passes Focus, like notify --urgent. */\n alert: (notice: { title: string; message: string; urgent: boolean; from: string }) => Promise<void>\n /** Ask the agent, in its voice session, to summarise the call. */\n summarize?: (call: Call) => Promise<string | null>\n /** File a finished summary where the dashboard shows it. */\n file?: (call: Call, summary: string) => void\n focus?: () => FocusState\n now?: () => number\n log?: (m: string) => void\n}\n\n/** What the widget sends through /ask when the owner answers, so the\n * agent speaks first and says why it called. */\nexport function openerPrompt(call: Pick<Call, \"reason\">): string {\n return \"[Call answered] The owner just picked up the voice call you asked for. \" +\n `You called because: \"${call.reason}\". Speak first: greet them in a few words ` +\n \"and say why you called, then let them answer.\"\n}\n\nexport const SUMMARY_PROMPT =\n \"[Call ended] The owner hung up. In two or three plain sentences, summarise what \" +\n \"was said and decided on this call, for your records. Reply with the summary only.\"\n\nexport class CallService {\n private widgetSeenAt = 0\n /** What the widget said with its last poll. */\n private widget: WidgetState = {}\n /** Calls waiting for a busy widget: since when, and when the owner was\n * last told. */\n private waiting = new Map<string, { since: number; toldAt: number }>()\n\n constructor(private deps: CallDeps) {}\n\n private now(): number { return this.deps.now?.() ?? Date.now() }\n private name(agentId: string): string { return this.deps.agentName(agentId) ?? agentId }\n\n /** An agent asks for a call, or to see (kind \"camera\"). 201 ringing, or\n * waiting for a busy owner (`rang: \"waiting\"`, with `busy`); 202 held\n * as missed (Focus). */\n async request(input: { agentId?: unknown; reason?: unknown; urgency?: unknown; kind?: unknown }, proof: CallerProof = {}): Promise<CallResult & { rang?: Rang; busy?: BusyInfo }> {\n const agentId = String(input.agentId ?? \"\").trim()\n const reason = String(input.reason ?? \"\").replace(/\\s+/g, \" \").trim()\n const urgency: CallUrgency = input.urgency === \"urgent\" ? \"urgent\" : \"normal\"\n const kind: CallKind = input.kind === \"camera\" ? \"camera\" : \"voice\"\n if (input.kind !== undefined && input.kind !== \"voice\" && input.kind !== \"camera\") return { ok: false, status: 400, error: \"kind must be voice or camera\" }\n if (!agentId || !reason) return { ok: false, status: 400, error: \"Required: agentId and reason\" }\n if (reason.length > REASON_MAX) return { ok: false, status: 413, error: `reason is over ${REASON_MAX} characters; keep it to one line` }\n if (this.deps.agentName(agentId) === null) return { ok: false, status: 404, error: `Unknown agent: ${agentId}` }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} placed this call. An agent calls from inside its own run.` }\n }\n\n const cfg = this.deps.config()\n if (!cfg.allow.includes(\"*\") && !cfg.allow.includes(agentId)) {\n const verb = kind === \"camera\" ? \"may not ask to see through the owner's camera\" : \"may not call the owner\"\n return { ok: false, status: 403, error: `${agentId} ${verb}. Add it to calls.allow in agentx.json (or: agentx call allow ${agentId}).` }\n }\n await this.sweep()\n if (this.deps.store.list({ status: LIVE, limit: 200 }).some((c) => c.agentId === agentId)) {\n return { ok: false, status: 409, error: `${agentId} already has a call in progress` }\n }\n const now = this.now()\n if (this.deps.store.countSince(agentId, now - 3_600_000) >= cfg.maxPerHour) {\n return { ok: false, status: 429, error: `${agentId} has placed ${cfg.maxPerHour} calls in the last hour (calls.maxPerHour)` }\n }\n\n // Where the ask came from, for kind \"camera\": the turn's own chat, or\n // the one the proof names. A task run has neither.\n const session: { channel?: string; chatId?: string } = kind === \"camera\" ? (this.deps.turnSession?.(agentId, proof) ?? proof) : {}\n const call: Call = {\n id: `call-${randomUUID().slice(0, 8)}`, agentId, kind, reason, urgency, status: \"ringing\", createdAt: now,\n ringingSince: now, answeredAt: null, endedAt: null, ringAgainAt: null, note: null, summary: null,\n channel: session.channel && session.chatId ? session.channel : null,\n chatId: session.channel && session.chatId ? session.chatId : null,\n }\n this.deps.store.insert(call)\n this.deps.log?.(`[calls] ${agentId} asks ${kind === \"camera\" ? \"to see\" : \"for a call\"} (${urgency}): ${reason}`)\n const rang = await this.ring(call)\n const saved = this.deps.store.get(call.id)!\n if (rang !== \"waiting\") return { ok: true, call: saved, rang }\n // The widget may have come free while the owner was being told.\n return this.waiting.has(call.id) ? { ok: true, call: saved, rang, busy: this.busyInfo(call) } : { ok: true, call: saved, rang: \"widget\" }\n }\n\n /** The caller withdraws its own voice call while it waits or rings, for\n * example to send a message instead (#969). */\n async cancel(input: { agentId?: unknown }, proof: CallerProof = {}): Promise<CallResult> {\n const agentId = String(input.agentId ?? \"\").trim()\n if (!agentId) return { ok: false, status: 400, error: \"Required: agentId\" }\n if (!this.deps.isRunningTurn(agentId, proof)) {\n return { ok: false, status: 403, error: `No running turn of ${agentId} asked for this. An agent withdraws its own call from inside its run.` }\n }\n // No sweep first: a call back that is due must not ring just to be withdrawn.\n const mine = this.deps.store.list({ status: [\"ringing\", \"later\"], kind: \"voice\", limit: 200 }).find((c) => c.agentId === agentId)\n if (!mine) return { ok: false, status: 404, error: `${agentId} has no call waiting or ringing` }\n const ended = this.deps.store.transition(mine.id, [\"ringing\", \"later\"], { status: \"ended\", endedAt: this.now(), ringingSince: null, ringAgainAt: null, note: CANCELLED_NOTE })\n if (!ended) return { ok: false, status: 409, error: `call is ${this.deps.store.get(mine.id)?.status}` }\n this.waiting.delete(mine.id)\n this.deps.log?.(`[calls] ${mine.id} withdrawn by ${agentId}`)\n return { ok: true, call: ended }\n }\n\n /** Ring now, wait for a busy widget, or hold as missed in Focus.\n * Returns how it rang. */\n private async ring(call: Call): Promise<Rang> {\n const focus = (this.deps.focus ?? readFocus)()\n if (focus.active && call.urgency !== \"urgent\") {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: focusLabel(focus) })) return false\n await this.notice(call, this.missedTitle(call))\n return false\n }\n // A camera ask is answered on the phone, never on the widget.\n if (call.kind === \"camera\") {\n await this.notice(call, `${this.name(call.agentId)} wants to see through your camera`,\n `Open the phone app and tap Show within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n if (this.widgetAlive()) {\n // Busy by its last poll, or by a call placed since that it will show first.\n if (!this.widget.busy && !this.otherCalls(call).length) return \"widget\"\n this.startWaiting(call)\n await this.callWaiting(call)\n return \"waiting\"\n }\n await this.notice(call, `${this.name(call.agentId)} is calling`,\n `Open AgentX Voice to answer within ${this.deps.config().ringSeconds} s.`)\n return \"notify\"\n }\n\n private async notice(call: Call, title: string, extra?: string): Promise<void> {\n try {\n await this.deps.alert({\n title, message: extra ? `${call.reason}\\n${extra}` : call.reason,\n urgent: call.urgency === \"urgent\", from: call.agentId,\n })\n } catch (e: any) {\n this.deps.log?.(`[calls] notice for ${call.id} failed: ${e?.message ?? e}`)\n }\n }\n\n /** The call starts waiting for the busy widget. */\n private startWaiting(call: Call): void {\n const now = this.now()\n this.waiting.set(call.id, { since: now, toldAt: now })\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} waits: the widget is busy`)\n }\n\n /** Voice calls ringing or answered other than `call`, oldest first. */\n private otherCalls(call: Call): Call[] {\n return this.deps.store.list({ status: [\"ringing\", \"answered\"], kind: \"voice\", limit: 200 }).filter((c) => c.id !== call.id).reverse()\n }\n\n private async callWaiting(call: Call): Promise<void> {\n const busy = this.deps.config().busy\n await this.notice(call, busy.title.replaceAll(\"{agent}\", this.name(call.agentId)), busy.notice)\n }\n\n /** Why the owner cannot take `call` now, and how long it waits. */\n private busyInfo(call: Call): BusyInfo {\n const cfg = this.deps.config().busy\n const { since, toldAt } = this.waiting.get(call.id) ?? { since: this.now(), toldAt: this.now() }\n const others = this.otherCalls(call)\n const talking = others.find((c) => c.status === \"answered\")\n // The call on the pill, or the oldest one it has yet to show.\n const showing = others.find((c) => c.id === this.widget.showing) ?? others.find((c) => !this.waiting.has(c.id))\n // After the wait the ring time runs once more before it is missed.\n const waitsUntil = since + cfg.maxWaitMinutes * 60_000 + this.deps.config().ringSeconds * 1000\n const remindAt = toldAt + cfg.retryMinutes * 60_000\n const stopsWaiting = since + cfg.maxWaitMinutes * 60_000\n return {\n why: talking ? \"call\" : showing ? \"ringing\" : \"voice\",\n with: talking ? this.name(talking.agentId) : showing ? this.name(showing.agentId) : null,\n remindAt: remindAt < stopsWaiting ? remindAt : null,\n waitsUntil,\n }\n }\n\n private missedTitle(call: Call): string {\n return call.kind === \"camera\" ? `${this.name(call.agentId)} asked to see through your camera` : `Missed call from ${this.name(call.agentId)}`\n }\n\n /** True when the widget polled recently. */\n widgetAlive(): boolean {\n return this.now() - this.widgetSeenAt < WIDGET_FRESH_MS\n }\n\n /** The widget's poll: voice calls ringing now, oldest first. Marks it alive. */\n async ringing(widget: WidgetState = {}): Promise<Call[]> {\n this.widgetSeenAt = this.now()\n this.widget = widget\n await this.hold(widget)\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 }).reverse()\n }\n\n /** A busy widget cannot ring: the ring time of each call it is not\n * showing starts over at every poll, for calls.busy.maxWaitMinutes at\n * most. The owner is told that the call waits, and again every\n * calls.busy.retryMinutes. The ring time starts over once more when the\n * widget first shows a call that waited. */\n private async hold(widget: WidgetState): Promise<void> {\n const now = this.now()\n const busy = this.deps.config().busy\n // Told after the loop: a poll that overlaps a slow notice must not\n // carry on from a list and a widget state that are out of date.\n const tell: Call[] = []\n const live = this.deps.store.list({ status: [\"ringing\"], kind: \"voice\", limit: 20 })\n for (const id of this.waiting.keys()) if (!live.some((c) => c.id === id)) this.waiting.delete(id)\n for (const call of live) {\n if (call.id === widget.showing) {\n if (this.waiting.delete(call.id)) this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })\n continue\n }\n if (!widget.busy) continue\n const held = this.waiting.get(call.id)\n if (held && now - held.since >= busy.maxWaitMinutes * 60_000) continue\n if (!this.deps.store.transition(call.id, \"ringing\", { ringingSince: now })) continue\n if (!held) this.startWaiting(call)\n else if (now - held.toldAt >= busy.retryMinutes * 60_000) held.toldAt = now\n else continue\n tell.push(call)\n }\n for (const call of tell) await this.callWaiting(call)\n }\n\n /** The phone app's poll: camera asks waiting for the owner, oldest first.\n * Does not count as the widget being up. */\n async asking(): Promise<Call[]> {\n await this.sweep()\n return this.deps.store.list({ status: [\"ringing\"], kind: \"camera\", limit: 20 }).reverse()\n }\n\n /** Unanswered calls become missed; \"later\" calls that came due ring\n * again; answered calls nobody hung up end after calls.maxCallMinutes. */\n async sweep(): Promise<void> {\n const now = this.now()\n const ringFor = this.deps.config().ringSeconds * 1000\n const talkFor = this.deps.config().maxCallMinutes * 60_000\n for (const call of this.deps.store.list({ status: [\"ringing\", \"later\", \"answered\"], limit: 200 })) {\n // The list was read before any await below: each move re-checks the\n // status in the store, so a call answered meanwhile is left alone.\n if (call.status === \"ringing\" && call.ringingSince !== null && now - call.ringingSince >= ringFor) {\n if (!this.deps.store.transition(call.id, \"ringing\", { status: \"missed\", ringingSince: null, note: this.waiting.has(call.id) ? \"widget busy\" : \"not answered\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} missed`)\n await this.notice(call, this.missedTitle(call))\n } else if (call.status === \"answered\" && call.answeredAt !== null && now - call.answeredAt >= talkFor) {\n if (!this.deps.store.transition(call.id, \"answered\", { status: \"ended\", endedAt: now, note: \"no hang-up\" })) continue\n this.deps.log?.(`[calls] ${call.id} from ${call.agentId} ended: no hang-up in ${this.deps.config().maxCallMinutes} min`)\n } else if (call.status === \"later\" && call.ringAgainAt !== null && now >= call.ringAgainAt) {\n const again = this.deps.store.transition(call.id, \"later\", { status: \"ringing\", ringingSince: now, ringAgainAt: null })\n if (again) await this.ring(again)\n }\n }\n }\n\n /** The owner picked up, or tapped Show. Returns the call and, for a\n * voice call, the opener for /ask. */\n answer(id: string): CallResult & { opener?: string } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const answered = this.deps.store.transition(id, \"ringing\", { status: \"answered\", answeredAt: this.now(), ringingSince: null })\n if (!answered) return { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n this.deps.log?.(`[calls] ${id} answered (${call.agentId})`)\n return answered.kind === \"camera\" ? { ok: true, call: answered } : { ok: true, call: answered, opener: openerPrompt(answered) }\n }\n\n decline(id: string): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const declined = this.deps.store.transition(id, [\"ringing\", \"later\"], { status: \"declined\", endedAt: this.now(), ringingSince: null, ringAgainAt: null })\n return declined ? { ok: true, call: declined } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}` }\n }\n\n /** \"Call back in N min\": it rings again then. */\n later(id: string, minutes?: unknown): CallResult {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const n = minutes === undefined ? LATER_DEFAULT_MINUTES : Number(minutes)\n if (!Number.isInteger(n) || n < 1 || n > LATER_MAX_MINUTES) {\n return { ok: false, status: 400, error: `minutes must be a whole number from 1 to ${LATER_MAX_MINUTES}` }\n }\n const later = this.deps.store.transition(id, \"ringing\", { status: \"later\", ringingSince: null, ringAgainAt: this.now() + n * 60_000 })\n return later ? { ok: true, call: later } : { ok: false, status: 409, error: `call is ${this.deps.store.get(id)?.status}, not ringing` }\n }\n\n /** End an answered call. The summary is written in the background;\n * `summarized` settles when it is filed (tests wait on it). */\n hangup(id: string): CallResult & { summarized?: Promise<void> } {\n const call = this.deps.store.get(id)\n if (!call) return unknown(id)\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now() })\n if (!ended) return { ok: false, status: 409, error: `call is ${call.status}, not answered` }\n this.deps.log?.(`[calls] ${id} ended (${call.agentId})`)\n const summarize = this.deps.summarize\n // What the agent saw is already its answer; only a voice call is summarised.\n if (ended.kind === \"camera\" || !this.deps.config().summary || !summarize) return { ok: true, call: ended }\n const summarized = (async () => {\n try {\n const text = (await summarize(ended))?.trim()\n if (!text) return\n const done = this.deps.store.update(id, { summary: text })!\n this.deps.file?.(done, text)\n } catch (e: any) {\n this.deps.log?.(`[calls] summary for ${id} failed: ${e?.message ?? e}`)\n }\n })()\n return { ok: true, call: ended, summarized }\n }\n\n /** A camera ask ends when the share ends; no summary. Only an answered\n * ask ends this way, so a stop before Show leaves it ringing. */\n endCamera(id: string, note?: string): boolean {\n const call = this.deps.store.get(id)\n if (!call || call.kind !== \"camera\") return false\n const ended = this.deps.store.transition(id, \"answered\", { status: \"ended\", endedAt: this.now(), note: note ?? null })\n if (ended) this.deps.log?.(`[calls] ${id} ended (${call.agentId}): ${note ?? \"share over\"}`)\n return !!ended\n }\n\n get(id: string): Call | undefined { return this.deps.store.get(id) }\n\n list(opts: Parameters<CallStore[\"list\"]>[0] = {}): Call[] { return this.deps.store.list(opts) }\n}\n\n/** How a call reached the owner: on the widget, through notify, waiting\n * for a busy widget, or not at all (held as missed). */\nexport type Rang = \"widget\" | \"notify\" | \"waiting\" | false\n\n/** The note on a call its caller withdrew. */\nexport const CANCELLED_NOTE = \"withdrawn by the caller\"\n\n/** What the caller reads when its call waits for a busy owner. */\nexport function busyText(busy: BusyInfo, now: number = Date.now()): string {\n const at = (ms: number) => `${new Date(ms).toLocaleTimeString([], { hour: \"2-digit\", minute: \"2-digit\", hour12: false })} (in ${Math.max(1, Math.round((ms - now) / 60_000))} min)`\n const why = busy.why === \"call\" ? `The owner is busy in another call${busy.with ? ` (with ${busy.with})` : \"\"}.`\n : busy.why === \"ringing\" ? `The owner is busy: another call${busy.with ? ` (from ${busy.with})` : \"\"} is ringing.`\n : \"The owner is busy on the voice widget.\"\n return `${why} They were told you are calling, and why. Your call waits and rings by itself as soon as they are free: do not call again. ` +\n (busy.remindAt !== null ? `Still busy at ${at(busy.remindAt)}, they are told again that you are waiting. ` : \"\") +\n `It waits until ${at(busy.waitsUntil)} at most, then counts as a missed call.`\n}\n\nconst unknown = (id: string): CallResult => ({ ok: false, status: 404, error: `No such call: ${id}` })\n","// --- Calls an agent placed to the owner, kept in SQLite ---\n//\n// One row per call, so a missed call is still there after a restart and\n// the dashboard can list them. Nothing else reads this table. A camera\n// ask (#325 phase 3: \"show me\") is a call of kind \"camera\": same\n// allowlist, same hourly limit, same lifecycle, answered on the phone\n// instead of the voice widget.\n\nimport type Database from \"better-sqlite3\"\n\nexport type CallStatus = \"ringing\" | \"answered\" | \"ended\" | \"declined\" | \"missed\" | \"later\"\nexport type CallUrgency = \"normal\" | \"urgent\"\n/** \"voice\": a live voice call on the widget. \"camera\": the agent asks to see through the phone camera. */\nexport type CallKind = \"voice\" | \"camera\"\n\nexport interface Call {\n id: string\n agentId: string\n kind: CallKind\n reason: string\n urgency: CallUrgency\n status: CallStatus\n /** The channel and chat the asking turn ran in, so what the agent sees\n * is answered there; null when the turn had none (a task run). */\n channel: string | null\n chatId: string | null\n /** When the agent asked, ms since the epoch. */\n createdAt: number\n /** When it last started ringing: at the request, or when \"later\" came due. */\n ringingSince: number | null\n answeredAt: number | null\n endedAt: number | null\n /** Status \"later\": when it rings again. */\n ringAgainAt: number | null\n /** Why it ended the way it did, in a few words (\"in Focus\", \"not answered\"). */\n note: string | null\n /** The agent's own summary, written after hang-up. */\n summary: string | null\n}\n\n/** Calls still in progress: an agent has at most one of these. */\nexport const LIVE: CallStatus[] = [\"ringing\", \"answered\", \"later\"]\n\nconst COLUMNS = `id, agent_id AS agentId, kind, reason, urgency, status, created_at AS createdAt,\n ringing_since AS ringingSince, answered_at AS answeredAt, ended_at AS endedAt,\n ring_again_at AS ringAgainAt, note, summary, channel, chat_id AS chatId`\n\nexport class CallStore {\n constructor(private db: Database.Database) {\n db.exec(`CREATE TABLE IF NOT EXISTS calls (\n id TEXT PRIMARY KEY, agent_id TEXT NOT NULL, reason TEXT NOT NULL,\n urgency TEXT NOT NULL, status TEXT NOT NULL, created_at INTEGER NOT NULL,\n ringing_since INTEGER, answered_at INTEGER, ended_at INTEGER,\n ring_again_at INTEGER, note TEXT, summary TEXT);\n CREATE INDEX IF NOT EXISTS calls_recent ON calls(created_at DESC);`)\n // Columns added since the table first shipped (#325 phase 3). A row\n // from before is a voice call from a turn whose chat was not kept.\n const have = new Set((db.prepare(\"PRAGMA table_info(calls)\").all() as Array<{ name: string }>).map((c) => c.name))\n if (!have.has(\"kind\")) db.exec(\"ALTER TABLE calls ADD COLUMN kind TEXT NOT NULL DEFAULT 'voice'\")\n if (!have.has(\"channel\")) db.exec(\"ALTER TABLE calls ADD COLUMN channel TEXT\")\n if (!have.has(\"chat_id\")) db.exec(\"ALTER TABLE calls ADD COLUMN chat_id TEXT\")\n }\n\n insert(call: Call): void {\n this.db.prepare(`INSERT INTO calls (id, agent_id, kind, reason, urgency, status, created_at, ringing_since,\n answered_at, ended_at, ring_again_at, note, summary, channel, chat_id) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)`).run(\n call.id, call.agentId, call.kind, call.reason, call.urgency, call.status, call.createdAt,\n call.ringingSince, call.answeredAt, call.endedAt, call.ringAgainAt, call.note, call.summary, call.channel, call.chatId)\n }\n\n get(id: string): Call | undefined {\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls WHERE id=?`).get(id) as Call | undefined\n }\n\n /** Write the changed fields of one call. */\n update(id: string, patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const cols: Record<string, string> = {\n reason: \"reason\", urgency: \"urgency\", status: \"status\", ringingSince: \"ringing_since\",\n answeredAt: \"answered_at\", endedAt: \"ended_at\", ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n if (keys.length) {\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \")\n this.db.prepare(`UPDATE calls SET ${set} WHERE id=?`).run(...keys.map((k) => (patch as any)[k] ?? null), id)\n }\n return this.get(id)\n }\n\n /** Change a call only if it is still `from`: the check and the write are\n * one statement, so two sweeps, or a sweep and the owner answering,\n * cannot both move the same call. Undefined when it had moved on. */\n transition(id: string, from: CallStatus | CallStatus[], patch: Partial<Omit<Call, \"id\" | \"agentId\" | \"createdAt\">>): Call | undefined {\n const statuses = Array.isArray(from) ? from : [from]\n const cols: Record<string, string> = {\n status: \"status\", ringingSince: \"ringing_since\", answeredAt: \"answered_at\", endedAt: \"ended_at\",\n ringAgainAt: \"ring_again_at\", note: \"note\", summary: \"summary\",\n }\n const keys = Object.keys(patch).filter((k) => k in cols)\n const set = keys.map((k) => `${cols[k]}=?`).join(\", \")\n const marks = statuses.map(() => \"?\").join(\",\")\n const changed = this.db.prepare(`UPDATE calls SET ${set} WHERE id=? AND status IN (${marks})`)\n .run(...keys.map((k) => (patch as any)[k] ?? null), id, ...statuses).changes\n return changed ? this.get(id) : undefined\n }\n\n /** Newest first; only these statuses, and only this kind, when given. */\n list(opts: { status?: CallStatus[]; kind?: CallKind; limit?: number } = {}): Call[] {\n const limit = Math.min(Math.max(opts.limit ?? 50, 1), 200)\n const where: string[] = []\n const args: unknown[] = []\n if (opts.status?.length) {\n where.push(`status IN (${opts.status.map(() => \"?\").join(\",\")})`)\n args.push(...opts.status)\n }\n if (opts.kind) { where.push(\"kind=?\"); args.push(opts.kind) }\n const filter = where.length ? `WHERE ${where.join(\" AND \")}` : \"\"\n return this.db.prepare(`SELECT ${COLUMNS} FROM calls ${filter} ORDER BY created_at DESC, rowid DESC LIMIT ?`)\n .all(...args, limit) as Call[]\n }\n\n /** Calls this agent placed after `since` (ms), for the hourly limit. */\n countSince(agentId: string, since: number): number {\n return (this.db.prepare(\"SELECT COUNT(*) AS n FROM calls WHERE agent_id=? AND created_at>?\")\n .get(agentId, since) as { n: number }).n\n }\n}\n"],"mappings":"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","names":["randomUUID","LIVE","COLUMNS","CallStore","db","have","c","call","id","patch","cols","keys","k","set","from","statuses","marks","opts","limit","where","args","filter","agentId","since","REASON_MAX","WIDGET_FRESH_MS","LATER_DEFAULT_MINUTES","LATER_MAX_MINUTES","callerHeaders","env","openerPrompt","call","SUMMARY_PROMPT","CallService","deps","agentId","input","proof","reason","urgency","kind","cfg","LIVE","c","now","session","randomUUID","rang","saved","mine","ended","CANCELLED_NOTE","focus","readFocus","focusLabel","title","extra","e","busy","since","toldAt","others","talking","showing","waitsUntil","remindAt","stopsWaiting","widget","tell","live","id","held","ringFor","talkFor","again","unknown","answered","declined","minutes","later","summarize","summarized","text","done","note","opts","busyText","at","ms"]}
|