@axiom-lattice/gateway 4.7.0 → 4.8.1
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/{OrphanRecoveryRuntime-MQDEJX3M.mjs → OrphanRecoveryRuntime-BEFILJMU.mjs} +2 -2
- package/dist/{chunk-2POW27EI.mjs → chunk-7CG57LEF.mjs} +2 -2
- package/dist/{chunk-WKNZ7GBC.mjs → chunk-EXPERMZR.mjs} +1 -1
- package/dist/chunk-EXPERMZR.mjs.map +1 -0
- package/dist/{chunk-52SUWMOX.mjs → chunk-JUA3VJE7.mjs} +3 -2
- package/dist/chunk-JUA3VJE7.mjs.map +1 -0
- package/dist/{chunk-TJAUXMYJ.mjs → chunk-URXZPI2F.mjs} +8 -1
- package/dist/chunk-URXZPI2F.mjs.map +1 -0
- package/dist/{chunk-KDQKTU3N.mjs → chunk-ZBTG3T7K.mjs} +44 -8
- package/dist/chunk-ZBTG3T7K.mjs.map +1 -0
- package/dist/embed/{embed-page-AgentDetailSideApp-2C2ZR62Q.js → embed-page-AgentDetailSideApp-R2RYOFBU.js} +1 -1
- package/dist/embed/{embed-page-AgentGovernanceWorkbench-GG4ONSTS.js → embed-page-AgentGovernanceWorkbench-DYEK37IB.js} +1 -1
- package/dist/embed/{embed-page-MessageGraph-WMYTVUUC.js → embed-page-MessageGraph-GQKF6ERQ.js} +1 -1
- package/dist/embed/{embed-page-ReactChart-7XCMDMH6.js → embed-page-ReactChart-2J36M2FN.js} +1 -1
- package/dist/embed/{embed-page-SkillFlow-XMMHEHQL.js → embed-page-SkillFlow-TSBZ4V2P.js} +1 -1
- package/dist/embed/{embed-page-TasksSideApp-4ZJ4Q4CL.js → embed-page-TasksSideApp-IEKDM5AK.js} +1 -1
- package/dist/embed/{embed-page-TeamWorkspace-H5OOEN3K.js → embed-page-TeamWorkspace-7IBW4J2Z.js} +1 -1
- package/dist/embed/{embed-page-TopologyRuntimeView-QMXWERVC.js → embed-page-TopologyRuntimeView-TVYZRWMY.js} +1 -1
- package/dist/embed/{embed-page-WorkflowAutomationView-L2OEGCA5.js → embed-page-WorkflowAutomationView-SS6TZVER.js} +1 -1
- package/dist/embed/{embed-page-chunk-SUMYB6ZA.js → embed-page-chunk-626XAYL6.js} +1 -1
- package/dist/embed/embed-page-chunk-CFUFMRE6.js +6985 -0
- package/dist/embed/{embed-page-chunk-OHDEKFTU.js → embed-page-chunk-FRXM6EXJ.js} +2 -2
- package/dist/embed/{embed-page-chunk-2G54EIW2.js → embed-page-chunk-NCP3KPCF.js} +1 -1
- package/dist/embed/{embed-page-chunk-N2ISCAS7.js → embed-page-chunk-SG4KQEFI.js} +1 -1
- package/dist/embed/{embed-page-chunk-CZE4IEB3.js → embed-page-chunk-TJGWHX6G.js} +1 -1
- package/dist/embed/embed-page-chunk-YPNSOZYW.js +8 -0
- package/dist/embed/embed-page.js +1 -1
- package/dist/index.d.mts +146 -17
- package/dist/index.d.ts +146 -17
- package/dist/index.js +1190 -328
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +649 -97
- package/dist/index.mjs.map +1 -1
- package/dist/{open-STFMNRO5.mjs → open-UVKMONVE.mjs} +5 -4
- package/dist/open-UVKMONVE.mjs.map +1 -0
- package/dist/{open-files-NPNQQ6TY.mjs → open-files-X3HXVEEM.mjs} +4 -4
- package/dist/{open-session-IHDGAXL3.mjs → open-session-2Y7XJJW2.mjs} +3 -3
- package/dist/{resources-DXPXAXEZ.mjs → resources-2L3KSUUU.mjs} +2 -2
- package/dist/sandboxOpenCredentialProvider-USIU7Z7H.mjs +275 -0
- package/dist/sandboxOpenCredentialProvider-USIU7Z7H.mjs.map +1 -0
- package/dist/{sandboxProvider-I3P66VXT.mjs → sandboxProvider-DUYWHYJN.mjs} +2 -2
- package/package.json +14 -8
- package/dist/chunk-52SUWMOX.mjs.map +0 -1
- package/dist/chunk-KDQKTU3N.mjs.map +0 -1
- package/dist/chunk-TJAUXMYJ.mjs.map +0 -1
- package/dist/chunk-WKNZ7GBC.mjs.map +0 -1
- package/dist/embed/embed-page-chunk-HEMNMC4X.js +0 -7
- package/dist/embed/embed-page-chunk-XIXSRYJY.js +0 -6986
- package/dist/open-STFMNRO5.mjs.map +0 -1
- /package/dist/{OrphanRecoveryRuntime-MQDEJX3M.mjs.map → OrphanRecoveryRuntime-BEFILJMU.mjs.map} +0 -0
- /package/dist/{chunk-2POW27EI.mjs.map → chunk-7CG57LEF.mjs.map} +0 -0
- /package/dist/{open-files-NPNQQ6TY.mjs.map → open-files-X3HXVEEM.mjs.map} +0 -0
- /package/dist/{open-session-IHDGAXL3.mjs.map → open-session-2Y7XJJW2.mjs.map} +0 -0
- /package/dist/{resources-DXPXAXEZ.mjs.map → resources-2L3KSUUU.mjs.map} +0 -0
- /package/dist/{sandboxProvider-I3P66VXT.mjs.map → sandboxProvider-DUYWHYJN.mjs.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/services/collaboration/OrphanRecoveryRuntime.ts","../src/services/collaboration/TopCallerRecovery.ts","../src/services/collaboration/projectTaskScope.ts"],"sourcesContent":["/**\n * Shared orphan-recovery runtime: the startup sweep and the on-demand\n * single-task entry point share one dependency shape and one lifecycle.\n *\n * @remarks\n * `recoverAll()` is the behavior-preserving move of the gateway startup block\n * (reconcile every tenant's orphaned ordinary tasks, then backfill the\n * top-level caller). `recoverTask()` is the A-lite on-demand entry: no HTTP\n * route, just a programmatic call used by the `task:recover` CLI/script.\n */\nimport type { TaskStore, TaskWorkItemStore, TenantStore } from \"@axiom-lattice/protocols\";\nimport {\n createTaskLifecycleService,\n reconcileAllTenantTaskRuns,\n taskRunRegistry,\n type TaskLifecycleService,\n} from \"@axiom-lattice/core\";\nimport {\n recoverTask as recoverSingleTask,\n runOrphanRecovery,\n type RecoverTaskResult,\n type TopCallerRecoveryDeps,\n} from \"./TopCallerRecovery\";\nimport { isProjectLifecycleTask } from \"./projectTaskScope\";\n\n/** Dependencies shared by the startup sweep and the single-task entry. */\nexport interface OrphanRecoveryRuntimeDeps {\n /** Task store used to enumerate tenants' orphans and read one task by id. */\n taskStore: TaskStore;\n /** Work-item store used to build the lifecycle when `lifecycle` is omitted. */\n workItemStore: TaskWorkItemStore;\n /** Tenant store supplying `getAllTenants()` for the startup sweep. */\n tenantStore: Pick<TenantStore, \"getAllTenants\">;\n /** Lifecycle used to converge orphans (built from the stores when omitted). */\n lifecycle?: Pick<TaskLifecycleService, \"failTask\">;\n /** Resolve a caller's agent client + config from the resolved top caller. */\n callerFor: TopCallerRecoveryDeps[\"callerFor\"];\n /** Resume the continuation (tests can call client.invoke(null, config)). */\n resume: TopCallerRecoveryDeps[\"resume\"];\n /** Structured logger. */\n log: { info(msg: string, ctx: Record<string, unknown>): void; warn(msg: string, ctx: Record<string, unknown>): void };\n}\n\n/** Entry points returned by {@link createOrphanRecoveryRuntime}. */\nexport interface OrphanRecoveryRuntime {\n /** Startup/interval sweep over every tenant's orphaned ordinary tasks. */\n recoverAll(): Promise<{ recovered: string[]; skipped: string[] }>;\n /**\n * On-demand recovery of exactly one task within one tenant.\n *\n * @remarks Offline/maintenance: run when the Gateway is stopped, or from the\n * Gateway process. The `live` guard reads the process-local task registry and\n * cannot see a run held by another process. Governed Project tasks are refused\n * (`skipped:project_task`) so this never bypasses their managed runtime.\n */\n recoverTask(input: { tenantId: string; taskId: string }): Promise<RecoverTaskResult>;\n}\n\n/**\n * Builds the shared orphan-recovery runtime.\n *\n * @param deps Task/work-item/tenant stores, optional lifecycle, caller\n * resolver, resume hook, and logger.\n * @returns `recoverAll()` (startup sweep) and `recoverTask()` (on-demand).\n * @remarks\n * Governed Project tasks are excluded from `recoverAll()` (they have their own\n * observer-based reconciliation) and from `recoverTask()`\n * (`skipped:project_task`), so the on-demand entry can never bypass the managed\n * Project runtime. `recoverTask()` delegates to {@link recoverSingleTask} with\n * the shared lifecycle and the process-local live-run registry, so it never\n * converges a task a live run still holds — in-process only.\n *\n * @example\n * ```ts\n * const runtime = createOrphanRecoveryRuntime({ taskStore, workItemStore, tenantStore, callerFor, resume, log });\n * await runtime.recoverAll();\n * const outcome = await runtime.recoverTask({ tenantId, taskId });\n * ```\n */\nexport function createOrphanRecoveryRuntime(\n deps: OrphanRecoveryRuntimeDeps,\n): OrphanRecoveryRuntime {\n const lifecycle = deps.lifecycle ?? createTaskLifecycleService({\n taskStore: deps.taskStore,\n workItemStore: deps.workItemStore,\n });\n\n return {\n async recoverAll() {\n return runOrphanRecovery({\n reconcile: () => reconcileAllTenantTaskRuns({\n actor: \"system:orphan-recovery\",\n tenantStore: {\n listTenants: async () => (await deps.tenantStore.getAllTenants()).map((tenant) => ({ id: tenant.id })),\n },\n taskStore: deps.taskStore,\n lifecycle,\n include: (t) => !isProjectLifecycleTask(t),\n }),\n callerFor: deps.callerFor,\n resume: deps.resume,\n log: deps.log,\n });\n },\n\n async recoverTask(input) {\n const task = await deps.taskStore.getById(input.tenantId, input.taskId);\n if (!task) return { kind: \"skipped\", reason: \"not_found\" };\n if (isProjectLifecycleTask(task)) {\n return { kind: \"skipped\", reason: \"project_task\" };\n }\n const orphans = await deps.taskStore.list({ tenantId: input.tenantId, status: \"in_progress\" });\n return recoverSingleTask(task, {\n lifecycle,\n registry: taskRunRegistry,\n orphans,\n callerFor: deps.callerFor,\n resume: deps.resume,\n });\n },\n };\n}\n","/**\n * Collapse recovery: backfill the top-level caller of converged orphan tasks.\n *\n * @remarks Only direct children of the top-level caller are backfilled; middle\n * task runs are frozen by the collapse design. Four guards: A skip a caller\n * with a pending interrupt (HITL), B skip live tasks, C derive the tool node\n * name from state, D use the task's real terminal state.\n */\nimport { ToolMessage } from \"@langchain/core/messages\";\nimport type { TaskItem } from \"@axiom-lattice/protocols\";\nimport {\n reconcileStaleTaskRuns,\n taskRunRegistry,\n type TaskLifecycleService,\n type TaskRunRegistry,\n} from \"@axiom-lattice/core\";\n\n/** Minimal caller-graph client surface used by recovery. */\nexport interface RecoveryCallerClient {\n getState(config: unknown): Promise<unknown>;\n updateState(config: unknown, values: unknown, asNode?: string): Promise<unknown>;\n invoke(input: unknown, config: unknown): Promise<unknown>;\n}\n\nexport interface TopCallerRecoveryDeps {\n /**\n * Resolve a caller's agent client + config from the resolved top caller.\n *\n * @param top The resolved top-level caller thread + assistant.\n * @param tenantId The orphan task's tenant, used to scope the caller client.\n */\n callerFor(\n top: { threadId: string; assistantId?: string },\n tenantId: string,\n ): Promise<{ client: RecoveryCallerClient; config: unknown; agent?: unknown } | undefined>;\n /** Live-run registry (defaults to the shared Core singleton). */\n registry?: { isLive(taskId: string): boolean };\n /** Resume the continuation (tests can call client.invoke(null, config)). */\n resume(client: RecoveryCallerClient, config: unknown, agent?: unknown): Promise<void>;\n}\n\n/** Dependencies for an on-demand single-task recovery ({@link recoverTask}). */\nexport interface RecoverTaskDeps {\n /** Lifecycle used to converge the orphan to `failed(no_executor)`. */\n lifecycle: Pick<TaskLifecycleService, \"failTask\">;\n /** Live-run registry (defaults to the shared Core singleton). */\n registry?: TaskRunRegistry;\n /**\n * The tenant's other orphan candidates (normally every `in_progress` task),\n * used to walk the caller chain up to the true top-level caller.\n *\n * @remarks\n * Without this set {@link resolveTopCaller} can only see the target itself,\n * so a nested middle run would be mistaken for a direct child of its\n * immediate (frozen) caller. Defaults to `[converged]` when omitted.\n */\n orphans?: TaskItem[];\n /** Resolve a caller's agent client + config from the resolved top caller. */\n callerFor: TopCallerRecoveryDeps[\"callerFor\"];\n /** Resume the continuation (tests can call client.invoke(null, config)). */\n resume: TopCallerRecoveryDeps[\"resume\"];\n}\n\n/**\n * Outcome of an on-demand single-task recovery attempt.\n *\n * @remarks\n * - `recovered`: the task was converged and its top-level caller was resumed.\n * - `skipped`: nothing was changed; `reason` names the guard (for example\n * `not_in_progress`, `live`, `middle`, `project_task`, `no_top_caller`,\n * `not_found`).\n * - `converged_no_call`: the task was converged to `failed`, but the caller had\n * no pending `task` call left to resolve, so no continuation was resumed.\n */\nexport type RecoverTaskResult =\n | { kind: \"recovered\" }\n | { kind: \"skipped\"; reason: string }\n | { kind: \"converged_no_call\" };\n\n/** A `task` tool call that still has no tool result. */\ninterface PendingTaskCall {\n id: string;\n taskId?: string;\n}\n\n/** Guard A: a caller waiting for a human is never touched. */\nfunction hasPendingInterrupts(state: Record<string, unknown>): boolean {\n return ((state.tasks as Array<{ interrupts?: unknown[] }> | undefined) ?? [])\n .some((t) => (t.interrupts?.length ?? 0) > 0);\n}\n\n/** Collects every pending `task` tool call, keyed by its `args.taskId` when present. */\nfunction pendingTaskCalls(state: Record<string, unknown>): PendingTaskCall[] {\n const messages = ((state.values as { messages?: Array<Record<string, unknown>> } | undefined)?.messages) ?? [];\n const resolved = new Set(\n messages\n .filter((m) => (m as { tool_call_id?: string }).tool_call_id)\n .map((m) => (m as { tool_call_id: string }).tool_call_id),\n );\n const calls: PendingTaskCall[] = [];\n for (const m of messages) {\n const typed = m as { getType?: () => string };\n if (typeof typed.getType !== \"function\" || typed.getType() !== \"ai\") continue;\n const toolCalls = (m as {\n tool_calls?: Array<{ id?: string; name?: string; args?: { taskId?: unknown } }>;\n }).tool_calls;\n if (!Array.isArray(toolCalls)) continue;\n for (const c of toolCalls) {\n if (c.name !== \"task\" || !c.id || resolved.has(c.id)) continue;\n const taskId = typeof c.args?.taskId === \"string\" ? c.args.taskId : undefined;\n calls.push({ id: c.id, ...(taskId ? { taskId } : {}) });\n }\n }\n return calls;\n}\n\n/** Guard D: the outcome follows the task's real terminal state. */\nfunction outcome(task: TaskItem): string {\n if (task.status === \"completed\") {\n return JSON.stringify({ success: true, taskId: task.id, result: task.result ?? \"\" });\n }\n if (task.status === \"failed\") {\n return JSON.stringify({ success: false, code: \"TASK_FAILED\", retryable: false, taskId: task.id, error: task.failureReason ?? \"\" });\n }\n return JSON.stringify({\n success: false,\n code: \"TASK_INTERRUPTED\",\n retryable: false,\n taskId: task.id,\n error: \"Run ended before completion; the task is interrupted and awaiting a decision.\",\n });\n}\n\n/** Per-task recovery outcome. */\ninterface RecoveryEntry {\n id: string;\n reason: string;\n}\n\n/**\n * Backfills a batch of converged direct-child tasks that share one top caller.\n *\n * @param tasks The direct-child orphans whose top caller is `top`.\n * @param top The resolved top-level caller.\n * @param deps Caller resolver, live-run registry, and resume hook.\n * @returns One `\"recovered\"` or `\"skipped:<reason>\"` entry per task.\n * @remarks\n * Reads the caller state once, matches each task to its pending `task` tool\n * call by `args.taskId`, injects every matched result in a single\n * `updateState`, then resumes once.\n */\nasync function backfillTopCallerGroup(\n tasks: TaskItem[],\n top: { threadId: string; assistantId?: string },\n deps: TopCallerRecoveryDeps,\n): Promise<RecoveryEntry[]> {\n const registry = deps.registry ?? taskRunRegistry;\n const results: RecoveryEntry[] = [];\n const eligible: TaskItem[] = [];\n for (const task of tasks) {\n if (!task.context?.caller_thread_id) {\n results.push({ id: task.id, reason: \"skipped:no_caller_link\" });\n continue;\n }\n if (registry.isLive(task.id)) { // Guard B\n results.push({ id: task.id, reason: \"skipped:live\" });\n continue;\n }\n eligible.push(task);\n }\n if (eligible.length === 0) return results;\n\n const target = await deps.callerFor(top, eligible[0].tenantId);\n if (!target) {\n return [...results, ...eligible.map((task) => ({ id: task.id, reason: \"skipped:no_caller_client\" }))];\n }\n const state = (await target.client.getState(target.config)) as Record<string, unknown>;\n if (hasPendingInterrupts(state)) { // Guard A\n return [...results, ...eligible.map((task) => ({ id: task.id, reason: \"skipped:hitl\" }))];\n }\n const nodeName = ((state.tasks as Array<{ name?: string }> | undefined) ?? [])[0]?.name; // Guard C\n if (!nodeName) {\n return [...results, ...eligible.map((task) => ({ id: task.id, reason: \"skipped:no_pending_node\" }))];\n }\n\n const calls = pendingTaskCalls(state);\n const matched: Array<{ task: TaskItem; call: PendingTaskCall }> = [];\n for (const task of eligible) {\n const call = calls.find((candidate) => candidate.taskId === task.id);\n if (!call) {\n results.push({ id: task.id, reason: \"skipped:no_pending_task_call\" });\n continue;\n }\n matched.push({ task, call });\n }\n if (matched.length === 0) return results;\n\n await target.client.updateState(target.config, {\n messages: matched.map(({ task, call }) =>\n new ToolMessage({ content: outcome(task), tool_call_id: call.id, name: \"task\" })),\n }, nodeName); // Guard D via outcome()\n await deps.resume(target.client, target.config, target.agent);\n return [...results, ...matched.map(({ task }) => ({ id: task.id, reason: \"recovered\" }))];\n}\n\n/**\n * Backfills one converged direct-child task's top-level caller.\n *\n * @param task The converged orphan task (must carry `context.caller_thread_id`).\n * @param top The resolved top-level caller.\n * @param deps Caller resolver, live-run registry, and resume hook.\n * @returns `\"recovered\"` or a `\"skipped:<reason>\"` string.\n */\nexport async function recoverTopCaller(\n task: TaskItem,\n top: { threadId: string; assistantId?: string },\n deps: TopCallerRecoveryDeps,\n): Promise<string> {\n if (!task.context?.caller_thread_id) return \"skipped:no_caller_link\";\n // Only direct children may be backfilled; a middle run is frozen by design.\n if (task.context.caller_thread_id !== top.threadId) return \"skipped:middle\";\n const [first] = await backfillTopCallerGroup([task], top, deps);\n return first?.reason ?? \"skipped:no_pending_task_call\";\n}\n\n/**\n * Recovers a single orphan task on demand, reusing the collapse logic.\n *\n * @param task The candidate task (any status).\n * @param deps Lifecycle, live-run registry, orphan candidate set (for the\n * caller-chain walk), caller resolver, and resume hook.\n * @returns A discriminated outcome: `recovered`, `skipped:<reason>`, or\n * `converged_no_call` when the task was converged but the caller had no\n * pending `task` call to resolve.\n * @remarks\n * Mirrors {@link runOrphanRecovery} for exactly one task: anything not\n * `in_progress`, or still held by a live run, is skipped; the rest is converged\n * to `failed` (`no_executor`, ADR-143) and its **top-level** caller is\n * backfilled. `deps.orphans` must carry the tenant's other `in_progress`\n * candidates so a nested middle run is correctly skipped (`middle`) instead of\n * being mistaken for a direct child of its frozen immediate caller.\n *\n * This is an **offline/maintenance** entry point (the `task:recover` CLI): run\n * it when the Gateway is stopped, or from within the Gateway process. The\n * `live` guard reads the process-local {@link taskRunRegistry}, so it is\n * meaningful only in-process and cannot detect a run held by another process.\n *\n * The convergence actor is `system:on-demand-recovery` (distinct from the\n * startup sweep's `system:orphan-recovery`), so on-demand convergences are\n * auditable in the task work-item trail. No HTTP route is added and recovery\n * semantics are unchanged.\n */\nexport async function recoverTask(\n task: TaskItem,\n deps: RecoverTaskDeps,\n): Promise<RecoverTaskResult> {\n if (task.status !== \"in_progress\") {\n return { kind: \"skipped\", reason: \"not_in_progress\" };\n }\n const registry = deps.registry ?? taskRunRegistry;\n if (registry.isLive(task.id)) { // Guard B\n return { kind: \"skipped\", reason: \"live\" };\n }\n\n const { convergedTasks } = await reconcileStaleTaskRuns({\n tasks: [task],\n actor: \"system:on-demand-recovery\",\n lifecycle: deps.lifecycle,\n ...(deps.registry ? { registry: deps.registry } : {}),\n });\n const converged = convergedTasks[0];\n if (!converged) {\n return { kind: \"skipped\", reason: \"convergence_failed\" };\n }\n\n const top = resolveTopCaller(converged, deps.orphans ?? [converged]);\n if (!top) {\n return { kind: \"skipped\", reason: \"no_top_caller\" };\n }\n // Only direct children may be backfilled; a middle run is frozen by design.\n if (converged.context?.caller_thread_id !== top.threadId) {\n return { kind: \"skipped\", reason: \"middle\" };\n }\n\n const reason = await recoverTopCaller(converged, top, {\n callerFor: deps.callerFor,\n resume: deps.resume,\n ...(deps.registry ? { registry: deps.registry } : {}),\n });\n if (reason === \"recovered\") return { kind: \"recovered\" };\n if (reason === \"skipped:no_pending_task_call\") return { kind: \"converged_no_call\" };\n return { kind: \"skipped\", reason: reason.replace(/^skipped:/, \"\") };\n}\n\n/** Walks caller links up to the thread that is not any orphan's run thread. */\nexport function resolveTopCaller(\n task: TaskItem,\n orphans: TaskItem[],\n): { threadId: string; assistantId?: string } | undefined {\n const tenantId = task.tenantId;\n const runThreadToTask = new Map(\n orphans.map((orphan) => [`${orphan.tenantId}:${orphan.context?.thread_id}`, orphan]),\n );\n let threadId = task.context?.caller_thread_id as string | undefined;\n let assistantId = task.context?.caller_assistant_id as string | undefined;\n const seen = new Set<string>();\n while (threadId) {\n const scopeKey = `${tenantId}:${threadId}`;\n if (!runThreadToTask.has(scopeKey) || seen.has(scopeKey)) break;\n seen.add(scopeKey);\n const parent = runThreadToTask.get(scopeKey)!;\n threadId = parent.context?.caller_thread_id as string | undefined;\n assistantId = parent.context?.caller_assistant_id as string | undefined;\n }\n return threadId ? { threadId, ...(assistantId ? { assistantId } : {}) } : undefined;\n}\n\n/**\n * Runs the whole collapse recovery once: reconcile orphan tasks, then backfill\n * the top-level caller of every direct-child orphan, grouped per top caller.\n * Best-effort; a failing group is contained and never aborts the others.\n *\n * @param deps Reconcile source, caller resolution, resume, and logger.\n * @returns The recovered and skipped `taskId:reason` entries.\n */\nexport async function runOrphanRecovery(deps: {\n reconcile: () => Promise<{ convergedTasks: TaskItem[] }>;\n callerFor: TopCallerRecoveryDeps[\"callerFor\"];\n resume: TopCallerRecoveryDeps[\"resume\"];\n log: { info(msg: string, ctx: Record<string, unknown>): void; warn(msg: string, ctx: Record<string, unknown>): void };\n}): Promise<{ recovered: string[]; skipped: string[] }> {\n let convergedTasks: TaskItem[];\n try {\n ({ convergedTasks } = await deps.reconcile());\n } catch (error) {\n deps.log.warn(\"Task orphan recovery reconcile failed\", {\n error: error instanceof Error ? error.message : String(error),\n });\n return { recovered: [], skipped: [\"reconcile:error\"] };\n }\n const orphans = convergedTasks;\n const recovered: string[] = [];\n const skipped: string[] = [];\n\n const groups = new Map<string, { top: { threadId: string; assistantId?: string }; tasks: TaskItem[] }>();\n for (const task of orphans) {\n const top = resolveTopCaller(task, orphans);\n if (!top) { skipped.push(`${task.id}:skipped:no_top_caller`); continue; }\n // Only direct children are backfilled; middle task runs are frozen by design.\n if (task.context?.caller_thread_id !== top.threadId) {\n skipped.push(`${task.id}:skipped:middle`);\n continue;\n }\n const key = `${task.tenantId}:${top.threadId}`;\n const group = groups.get(key);\n if (group) group.tasks.push(task);\n else groups.set(key, { top, tasks: [task] });\n }\n\n for (const { top, tasks } of groups.values()) {\n try {\n const results = await backfillTopCallerGroup(tasks, top, {\n callerFor: deps.callerFor,\n resume: deps.resume,\n });\n for (const { id, reason } of results) {\n (reason === \"recovered\" ? recovered : skipped).push(`${id}:${reason}`);\n }\n } catch (error) {\n for (const task of tasks) skipped.push(`${task.id}:error`);\n deps.log.warn(\"Task orphan recovery group failed\", {\n threadId: top.threadId,\n error: error instanceof Error ? error.message : String(error),\n });\n }\n }\n\n deps.log.info(\"Task orphan recovery complete\", { recovered, skipped });\n return { recovered, skipped };\n}\n","import type {\n Project,\n ProjectBotMembership,\n ProjectRoom,\n TaskItem,\n} from \"@axiom-lattice/protocols\";\n\n/**\n * Whether a task belongs to the Project lifecycle scope enforced by this host.\n *\n * Host-owned business predicate (ADR-118): Core no longer interprets\n * `context.source`; the gateway decides which tasks its Project runtime owns.\n *\n * @param task Task snapshot whose durable scope and host-stamped origin are inspected.\n * @returns True for Project Room, Project Task, or human `user` origins with an explicit scope.\n *\n * @remarks ADR-118 §6: classification is **explicit scope presence + host-stamped\n * `context.source`** — there is no `\"default\"` sentinel. A source of\n * `project_room`/`project_task` is stamped only by the host's trusted run context,\n * which always carries a real workspace/project id. `user` marks UI/human-created\n * Project tasks, which share the same durable Project queue runtime.\n */\nexport function isProjectLifecycleTask(task: TaskItem): task is TaskItem & {\n workspaceId: string;\n projectId: string;\n} {\n const source = task.context?.source;\n return typeof task.workspaceId === \"string\" && task.workspaceId.length > 0\n && typeof task.projectId === \"string\" && task.projectId.length > 0\n && (source === \"project_room\" || source === \"project_task\" || source === \"user\");\n}\n\n/**\n * Whether a Project's public (general) kind is exempt from roster authority.\n *\n * @param project Project row being inspected, or `null`/`undefined` when absent.\n * @returns True only for `kind === \"public\"`.\n *\n * @remarks The public project kind is the workspace-wide general space: it has no\n * authored roster, so any available agent may own its tasks. Every other kind\n * (`business`, `training`, `personal`, legacy omitted) keeps strict roster\n * validation. The decision is a project property, not a task-source property.\n */\nexport function isRosterExemptProject(project: Pick<Project, \"kind\"> | null | undefined): boolean {\n return project?.kind === \"public\";\n}\n\n/**\n * Resolves the owner membership that authorizes a Project task's execution thread.\n *\n * @param task Exact Project task whose owner is being resolved.\n * @param project Exact Project row; `kind` decides roster exemption.\n * @param room Exact Project main room the owner executes in.\n * @param membership Roster lookup result for `task.ownerId`, if any.\n * @returns The active exact-scope roster membership, or a synthesized membership\n * for a public-project task whose owner is not an active roster bot, or\n * `undefined` when the owner is not an active roster bot and the project is not\n * public.\n *\n * @remarks Public-project tasks may use any available agent as owner, so the\n * Project runtime synthesizes a stable, task-scoped membership descriptor instead\n * of requiring a roster row. Roster authority is unchanged everywhere else.\n */\nexport function resolveTaskOwnerMembership(\n task: TaskItem,\n project: Pick<Project, \"kind\"> | null | undefined,\n room: ProjectRoom,\n membership: ProjectBotMembership | undefined,\n): ProjectBotMembership | undefined {\n const active = membership && membership.status === \"active\"\n && membership.tenantId === task.tenantId && membership.workspaceId === task.workspaceId\n && membership.projectId === task.projectId && membership.assistantId === task.ownerId\n && membership.roomId === room.id;\n if (active) return membership;\n if (!isRosterExemptProject(project)) return undefined;\n return syntheticPublicProjectOwnerMembership(task, room);\n}\n\n/**\n * Builds the stable, task-scoped owner membership used for a public-project task\n * whose owner agent has no active Project roster row.\n *\n * @param task Exact public-project task.\n * @param room Exact Project main room.\n * @returns A descriptor-safe `active` membership matching the task's exact scope\n * and owner, with a deterministic synthetic id so subsequent `ensure`/recovery\n * reads resolve the same canonical Task Thread metadata.\n */\nexport function syntheticPublicProjectOwnerMembership(task: TaskItem, room: ProjectRoom): ProjectBotMembership {\n const threadId = typeof task.context?.thread_id === \"string\" && task.context.thread_id.length > 0\n ? task.context.thread_id\n : task.ownerId;\n return {\n id: `public-project-owner:${task.projectId}:${task.ownerId}`,\n tenantId: task.tenantId,\n workspaceId: task.workspaceId!,\n projectId: task.projectId!,\n roomId: room.id,\n assistantId: task.ownerId,\n role: \"specialist\",\n title: task.ownerId,\n mentionName: task.ownerId,\n status: \"active\",\n roomThreadId: threadId,\n joinedAt: task.createdAt,\n updatedAt: task.updatedAt,\n };\n}\n"],"mappings":"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","names":["taskRunRegistry","taskRunRegistry"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/services/open/providers/sandboxProvider.ts"],"sourcesContent":["/**\n * Built-in sandbox provider for the Open surface: project-scoped file\n * read/list and shell execution with self-binding for sandbox tokens.\n *\n * API keys that hold sandbox-domain grants resolve the sandbox by the\n * credential's tenant + project (their project's sandbox). Sandbox tokens\n * with self-binding are resolved by the token's sandboxName field.\n */\n\nimport { z } from \"zod\";\nimport type { OpenExecutionContext, OpenExecutionResult } from \"@axiom-lattice/protocols\";\nimport { sandboxLatticeManager } from \"@axiom-lattice/core\";\nimport type { OpenProviderDeclaration } from \"../openLattice\";\n\nconst MAX_OUTPUT_BYTES = 64000;\n\n/** Resolve the credential-addressed project sandbox (self-binding). */\nexport async function resolveContextSandbox(ctx: OpenExecutionContext) {\n return sandboxLatticeManager.getSandboxFromConfig({\n assistant_id: \"\",\n thread_id: \"\",\n tenantId: ctx.tenantId,\n workspaceId: ctx.workspaceId ?? \"\",\n projectId: ctx.projectId,\n });\n}\n\nexport function createOpenSandboxProvider(): OpenProviderDeclaration {\n return {\n domain: \"sandbox\",\n label: \"Sandbox\",\n source: \"builtin\",\n scopeKind: \"project\",\n actions: [\n {\n name: \"files_read\",\n label: \"Read files\",\n description: \"Read a file from the project sandbox.\",\n schema: { path: z.string().min(1) },\n annotations: { readOnlyHint: true, destructiveHint: false },\n },\n {\n name: \"files_list\",\n label: \"List files\",\n description: \"List files under a directory in the project sandbox.\",\n schema: { path: z.string(), recursive: z.boolean().optional() },\n annotations: { readOnlyHint: true, destructiveHint: false },\n },\n {\n name: \"files_write\",\n label: \"Write files\",\n description: \"Write content to a file in the project sandbox.\",\n schema: { path: z.string().min(1), content: z.string() },\n annotations: { readOnlyHint: false, destructiveHint: true },\n },\n {\n name: \"shell_exec\",\n label: \"Run shell commands\",\n description:\n \"Execute a shell command in the project sandbox. Returns stdout (up to 64KB) and exit code.\",\n schema: {\n command: z.string().min(1),\n exec_dir: z.string().optional(),\n timeout: z.number().int().min(1000).optional(),\n },\n annotations: { readOnlyHint: false, destructiveHint: true },\n },\n ],\n execute: async (\n actionName: string,\n args: unknown,\n ctx: OpenExecutionContext,\n ): Promise<OpenExecutionResult> => {\n // Self-binding is inherent: the sandbox is resolved from the credential's\n // tenant/workspace/project triple (minted server-side, unforgeable), so a\n // sandbox token can only ever reach the sandbox it was issued for.\n const sandbox = await resolveContextSandbox(ctx);\n\n switch (actionName) {\n case \"files_read\": {\n const { path } = args as { path: string };\n const file = await sandbox.file.readFile(path);\n return { content: [{ type: \"text\", text: file.content }] };\n }\n case \"files_list\": {\n const { path, recursive } = args as { path: string; recursive?: boolean };\n const listing = await sandbox.file.listPath(path, { recursive });\n return {\n content: [{ type: \"text\", text: JSON.stringify(listing.files) }],\n };\n }\n case \"files_write\": {\n const { path, content } = args as { path: string; content: string };\n await sandbox.file.writeFile(path, content);\n return { content: [{ type: \"text\", text: `wrote ${path}` }] };\n }\n case \"shell_exec\": {\n const { command, exec_dir, timeout } = args as {\n command: string;\n exec_dir?: string;\n timeout?: number;\n };\n const result = await sandbox.shell.execCommand({\n command,\n exec_dir,\n timeout: timeout ?? 120_000,\n });\n const truncated =\n result.output.length > MAX_OUTPUT_BYTES\n ? result.output.slice(0, MAX_OUTPUT_BYTES) +\n `\\n...[truncated ${result.output.length - MAX_OUTPUT_BYTES} bytes]`\n : result.output;\n return {\n content: [\n {\n type: \"text\",\n text: `exit_code: ${result.exit_code}\\n${truncated}`,\n },\n ],\n };\n }\n default:\n return {\n content: [\n { type: \"text\", text: `Unknown sandbox action: ${actionName}` },\n ],\n isError: true,\n };\n }\n },\n };\n}"],"mappings":";AASA,SAAS,SAAS;AAElB,SAAS,6BAA6B;AAGtC,IAAM,mBAAmB;AAGzB,eAAsB,sBAAsB,KAA2B;AACrE,SAAO,sBAAsB,qBAAqB;AAAA,IAChD,cAAc;AAAA,IACd,WAAW;AAAA,IACX,UAAU,IAAI;AAAA,IACd,aAAa,IAAI,eAAe;AAAA,IAChC,WAAW,IAAI;AAAA,EACjB,CAAC;AACH;AAEO,SAAS,4BAAqD;AACnE,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,SAAS;AAAA,MACP;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;AAAA,QAClC,aAAa,EAAE,cAAc,MAAM,iBAAiB,MAAM;AAAA,MAC5D;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAG,WAAW,EAAE,QAAQ,EAAE,SAAS,EAAE;AAAA,QAC9D,aAAa,EAAE,cAAc,MAAM,iBAAiB,MAAM;AAAA,MAC5D;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aAAa;AAAA,QACb,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,GAAG,SAAS,EAAE,OAAO,EAAE;AAAA,QACvD,aAAa,EAAE,cAAc,OAAO,iBAAiB,KAAK;AAAA,MAC5D;AAAA,MACA;AAAA,QACE,MAAM;AAAA,QACN,OAAO;AAAA,QACP,aACE;AAAA,QACF,QAAQ;AAAA,UACN,SAAS,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,UACzB,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,UAC9B,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,GAAI,EAAE,SAAS;AAAA,QAC/C;AAAA,QACA,aAAa,EAAE,cAAc,OAAO,iBAAiB,KAAK;AAAA,MAC5D;AAAA,IACF;AAAA,IACA,SAAS,OACP,YACA,MACA,QACiC;AAIjC,YAAM,UAAU,MAAM,sBAAsB,GAAG;AAE/C,cAAQ,YAAY;AAAA,QAClB,KAAK,cAAc;AACjB,gBAAM,EAAE,KAAK,IAAI;AACjB,gBAAM,OAAO,MAAM,QAAQ,KAAK,SAAS,IAAI;AAC7C,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,QAAQ,CAAC,EAAE;AAAA,QAC3D;AAAA,QACA,KAAK,cAAc;AACjB,gBAAM,EAAE,MAAM,UAAU,IAAI;AAC5B,gBAAM,UAAU,MAAM,QAAQ,KAAK,SAAS,MAAM,EAAE,UAAU,CAAC;AAC/D,iBAAO;AAAA,YACL,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,KAAK,UAAU,QAAQ,KAAK,EAAE,CAAC;AAAA,UACjE;AAAA,QACF;AAAA,QACA,KAAK,eAAe;AAClB,gBAAM,EAAE,MAAM,QAAQ,IAAI;AAC1B,gBAAM,QAAQ,KAAK,UAAU,MAAM,OAAO;AAC1C,iBAAO,EAAE,SAAS,CAAC,EAAE,MAAM,QAAQ,MAAM,SAAS,IAAI,GAAG,CAAC,EAAE;AAAA,QAC9D;AAAA,QACA,KAAK,cAAc;AACjB,gBAAM,EAAE,SAAS,UAAU,QAAQ,IAAI;AAKvC,gBAAM,SAAS,MAAM,QAAQ,MAAM,YAAY;AAAA,YAC7C;AAAA,YACA;AAAA,YACA,SAAS,WAAW;AAAA,UACtB,CAAC;AACD,gBAAM,YACJ,OAAO,OAAO,SAAS,mBACnB,OAAO,OAAO,MAAM,GAAG,gBAAgB,IACvC;AAAA,gBAAmB,OAAO,OAAO,SAAS,gBAAgB,YAC1D,OAAO;AACb,iBAAO;AAAA,YACL,SAAS;AAAA,cACP;AAAA,gBACE,MAAM;AAAA,gBACN,MAAM,cAAc,OAAO,SAAS;AAAA,EAAK,SAAS;AAAA,cACpD;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,QACA;AACE,iBAAO;AAAA,YACL,SAAS;AAAA,cACP,EAAE,MAAM,QAAQ,MAAM,2BAA2B,UAAU,GAAG;AAAA,YAChE;AAAA,YACA,SAAS;AAAA,UACX;AAAA,MACJ;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/services/open/openToken.ts"],"sourcesContent":["/**\n * Open-surface stateless bearer tokens (sandbox / browser tokens).\n *\n * Reuses the HMAC-SHA256 scheme from createConsoleSessionToken\n * (base64url payload + signature), with a dedicated secret so a leaked\n * console session does not yield open tokens.\n */\n\nimport { randomBytes, createHmac, timingSafeEqual } from \"crypto\";\n\nexport interface SandboxTokenPayload {\n kind: \"sandbox_token\";\n tenantId: string;\n workspaceId: string;\n projectId: string;\n sandboxName: string;\n grants: unknown;\n exp: number;\n}\n\nexport interface BrowserTokenPayload {\n kind: \"browser_token\";\n tenantId: string;\n projectId: string;\n workspaceId?: string;\n shareId?: string;\n /** Internal-preview variant (viewfile-injected, no share). */\n preview?: boolean;\n grants: unknown;\n exp: number;\n}\n\nexport type OpenTokenPayload = SandboxTokenPayload | BrowserTokenPayload;\n\nfunction secret(): string {\n return (\n process.env.OPEN_TOKEN_SECRET ||\n process.env.AUTH_TOKEN_SECRET ||\n \"open-token-dev-secret-fallback\"\n );\n}\n\n/** Sign an open-token payload (15-min TTL default). */\nexport function signOpenToken(\n payload:\n | (Omit<SandboxTokenPayload, \"exp\"> & { exp?: number })\n | (Omit<BrowserTokenPayload, \"exp\"> & { exp?: number }),\n): string {\n const data = { ...payload, exp: payload.exp ?? Date.now() + 15 * 60 * 1000 };\n const encoded = Buffer.from(JSON.stringify(data)).toString(\"base64url\");\n const sig = createHmac(\"sha256\", secret()).update(encoded).digest(\"base64url\");\n return `${encoded}.${sig}`;\n}\n\n/** Verify a signed open bearer token; null if missing, expired, or tampered. */\nexport function verifyOpenToken(bearer: string | undefined): OpenTokenPayload | null {\n if (!bearer) return null;\n const parts = bearer.split(\".\");\n if (parts.length !== 2) return null;\n const [encoded, sigStr] = parts;\n if (!/^[A-Za-z0-9_-]{43}$/.test(sigStr)) return null;\n try {\n const actual = Buffer.from(sigStr, \"base64url\");\n if (actual.toString(\"base64url\") !== sigStr) return null;\n const expected = createHmac(\"sha256\", secret()).update(encoded).digest();\n if (actual.length !== expected.length || !timingSafeEqual(actual, expected)) return null;\n const payload: unknown = JSON.parse(Buffer.from(encoded, \"base64url\").toString(\"utf8\"));\n if (typeof payload !== \"object\" || payload === null) return null;\n const p = payload as Record<string, unknown>;\n if (\n typeof p.exp !== \"number\" ||\n p.exp < Date.now() ||\n (p.kind !== \"sandbox_token\" && p.kind !== \"browser_token\") ||\n typeof p.tenantId !== \"string\" ||\n typeof p.projectId !== \"string\" ||\n typeof p.grants !== \"object\"\n ) {\n return null;\n }\n return p as unknown as OpenTokenPayload;\n } catch {\n return null;\n }\n}"],"mappings":";;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAQA,SAAsB,YAAY,uBAAuB;AA0BzD,SAAS,SAAiB;AACxB,SACE,QAAQ,IAAI,qBACZ,QAAQ,IAAI,qBACZ;AAEJ;AAGO,SAAS,cACd,SAGQ;AACR,QAAM,OAAO,EAAE,GAAG,SAAS,KAAK,QAAQ,OAAO,KAAK,IAAI,IAAI,KAAK,KAAK,IAAK;AAC3E,QAAM,UAAU,OAAO,KAAK,KAAK,UAAU,IAAI,CAAC,EAAE,SAAS,WAAW;AACtE,QAAM,MAAM,WAAW,UAAU,OAAO,CAAC,EAAE,OAAO,OAAO,EAAE,OAAO,WAAW;AAC7E,SAAO,GAAG,OAAO,IAAI,GAAG;AAC1B;AAGO,SAAS,gBAAgB,QAAqD;AACnF,MAAI,CAAC,OAAQ,QAAO;AACpB,QAAM,QAAQ,OAAO,MAAM,GAAG;AAC9B,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,CAAC,SAAS,MAAM,IAAI;AAC1B,MAAI,CAAC,sBAAsB,KAAK,MAAM,EAAG,QAAO;AAChD,MAAI;AACF,UAAM,SAAS,OAAO,KAAK,QAAQ,WAAW;AAC9C,QAAI,OAAO,SAAS,WAAW,MAAM,OAAQ,QAAO;AACpD,UAAM,WAAW,WAAW,UAAU,OAAO,CAAC,EAAE,OAAO,OAAO,EAAE,OAAO;AACvE,QAAI,OAAO,WAAW,SAAS,UAAU,CAAC,gBAAgB,QAAQ,QAAQ,EAAG,QAAO;AACpF,UAAM,UAAmB,KAAK,MAAM,OAAO,KAAK,SAAS,WAAW,EAAE,SAAS,MAAM,CAAC;AACtF,QAAI,OAAO,YAAY,YAAY,YAAY,KAAM,QAAO;AAC5D,UAAM,IAAI;AACV,QACE,OAAO,EAAE,QAAQ,YACjB,EAAE,MAAM,KAAK,IAAI,KAChB,EAAE,SAAS,mBAAmB,EAAE,SAAS,mBAC1C,OAAO,EAAE,aAAa,YACtB,OAAO,EAAE,cAAc,YACvB,OAAO,EAAE,WAAW,UACpB;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAnFA;AAAA;AAAA;AAAA;AAAA;","names":[]}
|