@dxos/assistant-toolkit 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/chunk-DelegationStrategy.mjs +5 -4
- package/dist/lib/chunk-DelegationStrategy.mjs.map +1 -1
- package/dist/lib/chunk-relay.mjs +1 -1
- package/dist/lib/chunk-relay.mjs.map +1 -1
- package/dist/types/src/supervisor/delegation-strategy.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -26
- package/src/skills/agent/operations/relay.ts +1 -1
- package/src/supervisor/delegation-strategy.ts +4 -4
|
@@ -8,8 +8,9 @@ import * as Exit from "effect/Exit";
|
|
|
8
8
|
import { AiContext } from "@dxos/assistant";
|
|
9
9
|
import * as Agent from "@dxos/assistant/Agent";
|
|
10
10
|
import * as Chat from "@dxos/assistant/Chat";
|
|
11
|
-
import {
|
|
11
|
+
import { OperationProcess } from "@dxos/compute-runtime";
|
|
12
12
|
import * as Instructions from "@dxos/compute/Instructions";
|
|
13
|
+
import * as Process from "@dxos/compute/Process";
|
|
13
14
|
import { Database, Feed, Filter, Obj, Query, Ref } from "@dxos/echo";
|
|
14
15
|
import * as EffectEx from "@dxos/effect/EffectEx";
|
|
15
16
|
import { EID, EntityId } from "@dxos/keys";
|
|
@@ -77,7 +78,7 @@ var sweepOrphanedTasks = (chat, activeIds) => Effect.gen(function* () {
|
|
|
77
78
|
}, {
|
|
78
79
|
"~LogMeta": "~LogMeta",
|
|
79
80
|
F: __dxlog_file,
|
|
80
|
-
L:
|
|
81
|
+
L: 115,
|
|
81
82
|
S: this
|
|
82
83
|
});
|
|
83
84
|
Obj.update(task, (task) => {
|
|
@@ -101,7 +102,7 @@ var failHeldTasks = (chat, cause) => Effect.gen(function* () {
|
|
|
101
102
|
}, {
|
|
102
103
|
"~LogMeta": "~LogMeta",
|
|
103
104
|
F: __dxlog_file,
|
|
104
|
-
L:
|
|
105
|
+
L: 139,
|
|
105
106
|
S: this
|
|
106
107
|
});
|
|
107
108
|
for (const task of held) Task.setStatus(task, "failed", {
|
|
@@ -154,7 +155,7 @@ var makeDelegationStrategy = () => ({
|
|
|
154
155
|
delegations.push({
|
|
155
156
|
id: task.id,
|
|
156
157
|
spawn: Effect.gen(function* () {
|
|
157
|
-
return (yield* (
|
|
158
|
+
return (yield* Process.spawn(OperationProcess.make(RunInstructions), {
|
|
158
159
|
instructions: Ref.make(instructions),
|
|
159
160
|
input: {}
|
|
160
161
|
})).pid;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-DelegationStrategy.mjs","names":[],"sources":["../../src/supervisor/delegation-strategy.ts","../../src/DelegationStrategy.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Cause from 'effect/Cause';\nimport * as Effect from 'effect/Effect';\nimport * as Exit from 'effect/Exit';\n\nimport { type Delegation, type DelegationStrategy } from '@dxos/agent-runtime';\nimport { AiContext } from '@dxos/assistant';\nimport * as Agent from '@dxos/assistant/Agent';\nimport * as Chat from '@dxos/assistant/Chat';\nimport { ProcessManager } from '@dxos/compute-runtime';\nimport * as Instructions from '@dxos/compute/Instructions';\nimport { Database, Feed, Filter, Obj, Query, Ref } from '@dxos/echo';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { EID, EntityId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { Message, Task } from '@dxos/types';\nimport { trim } from '@dxos/util';\n\nimport { ToolkitError } from '../errors.ts';\nimport * as DelegationSkill from '../skills/delegation/DelegationSkill.ts';\nimport * as AgentOperation from '../types/AgentOperation.ts';\n\n/**\n * Normalizes an LLM-reported artifact reference (bare entity id or full ECHO URI) to a\n * fully-qualified ref in the current space. Not resolved here — it resolves lazily when read.\n */\nconst resolveArtifactRef = (id: string): Effect.Effect<Ref.Ref<Obj.Unknown>, Error, Database.Service> =>\n Effect.gen(function* () {\n const parsed = EID.tryParse(id);\n const candidate = (parsed ? EID.getEntityId(parsed) : undefined) ?? id;\n if (!EntityId.isValid(candidate)) {\n // Malformed LLM-reported id: fail so the caller's `orElseSucceed` drops it.\n return yield* Effect.fail(new ToolkitError({ message: `Invalid artifact id: ${id}` }));\n }\n const { db } = yield* Database.Service;\n return db.makeRef<Obj.Unknown>(EID.make({ spaceId: db.spaceId, entityId: candidate }));\n });\n\n/**\n * Renders a sub-agent result for inclusion in a notification message.\n */\nconst formatResult = (value: unknown): string => (typeof value === 'string' ? value : JSON.stringify(value));\n\n/**\n * Extracts artifact ids a sub-agent reported in its result (see the synthesized instructions\n * instructions). Tolerates the result being a string, or an object with `artifactIds`/`artifactId`.\n */\nconst extractArtifactIds = (value: unknown): string[] => {\n if (typeof value !== 'object' || value === null) {\n return [];\n }\n\n const record = value as { artifactIds?: unknown; artifactId?: unknown };\n const ids = Array.isArray(record.artifactIds)\n ? record.artifactIds\n : typeof record.artifactId === 'string'\n ? [record.artifactId]\n : [];\n return ids.filter((id): id is string => typeof id === 'string');\n};\n\n/**\n * A task handed to a sub-agent by the delegation verbs, which assign `{ role: 'assistant' }` and\n * nothing else. An assistant assignee carrying a `subject` names a concrete session that owns the\n * task — the conversation's own agent starting it from the planning tool, or a remote session —\n * so it is that session's work, not a spawn request for the supervisor.\n */\nexport const isSubAgentTask = (task: Task.Task): boolean =>\n task.assignee?.role === 'assistant' && task.assignee.subject === undefined;\n\n/**\n * The durable agent tasks awaiting a sub-agent for this conversation: queued (`todo`) tasks of\n * the chat's checklist whose assignee is an agent, all of whose dependencies are done. Ordinary\n * (unassigned) tasks are never spawned — delegation happens only through the delegation verbs.\n * `started` always means a live process (set at spawn), so a started agent task with no process\n * is an orphan — {@link sweepOrphanedTasks}.\n */\nconst findPendingTasks = (\n chat: Chat.Chat,\n activeIds: ReadonlySet<string>,\n): Effect.Effect<Task.Task[], never, Database.Service> =>\n Effect.gen(function* () {\n const tasks = yield* Chat.loadTasks(chat);\n // The chat's `tasks` array is flat, so a delegated sub-task is found without descending.\n return tasks.filter(\n (task) =>\n isSubAgentTask(task) &&\n (task.status ?? 'todo') === 'todo' &&\n !activeIds.has(task.id) &&\n Task.isTaskReady(tasks, task),\n );\n });\n\n/**\n * Fails agent tasks stuck in `started` with no live process — a crashed or never-exited\n * sub-agent (e.g. the host reloaded mid-run) must not wedge the task or the delegation loop.\n */\nconst sweepOrphanedTasks = (\n chat: Chat.Chat,\n activeIds: ReadonlySet<string>,\n): Effect.Effect<void, never, Database.Service> =>\n Effect.gen(function* () {\n const tasks = yield* Chat.loadTasks(chat);\n const orphans = tasks.filter(\n (task) => isSubAgentTask(task) && task.status === 'started' && !activeIds.has(task.id),\n );\n if (orphans.length === 0) {\n return;\n }\n for (const task of orphans) {\n log.warn('orphaned delegated task failed', { taskId: task.id, title: task.title });\n Obj.update(task, (task) => {\n task.status = 'failed';\n });\n }\n yield* Database.flush();\n });\n\n/**\n * Fails the tasks the conversation's own agent holds when its turn fails — a model error ends the\n * process before any reconcile, so a task left `started` would read as underway indefinitely.\n * Sub-agent tasks are left to their own process's exit, which may still succeed.\n */\nconst failHeldTasks = (chat: Chat.Chat, cause: Cause.Cause<unknown>): Effect.Effect<void, never, Database.Service> =>\n Effect.gen(function* () {\n const held = (yield* Chat.loadTasks(chat)).filter((task) => Task.isAgentWorking(task) && !isSubAgentTask(task));\n if (held.length === 0) {\n return;\n }\n\n // The reader sees the error message; the full cause goes to the log.\n const reason = Cause.prettyErrors(cause)\n .map((error) => error.message)\n .join('; ');\n log.warn('agent turn failed; failing held tasks', { chatId: chat.id, cause: Cause.pretty(cause) });\n for (const task of held) {\n Task.setStatus(task, 'failed', {\n actor: { role: 'assistant', subject: Ref.make(chat) },\n description: `The agent's request failed${reason ? `: ${reason}` : '.'}`,\n });\n }\n yield* Database.flush();\n });\n\n/**\n * Supervisor behaviour for the conversational agent: after each turn, every queued agent task\n * whose dependencies are done is run by a sub-agent (a synthesized minimal `Routine` executed via\n * `RunInstructions`), marked started at spawn; on exit the task is marked done/failed and a\n * templated message is posted back to the conversation, whose turn re-runs this reconcile — so a\n * batch of delegated tasks drains in dependency order without further prompting.\n */\nexport const makeDelegationStrategy = (): DelegationStrategy => ({\n reconcile: (chat, activeIds) =>\n Effect.gen(function* () {\n yield* sweepOrphanedTasks(chat, activeIds);\n const pending = yield* findPendingTasks(chat, activeIds);\n if (pending.length === 0) {\n return [];\n }\n\n // Sub-agents inherit the supervisor's bound skills (so they have the same tools/\n // capabilities), minus the delegation skill itself — otherwise a sub-agent could\n // recursively delegate. Resolved from the conversation's AiContext bindings.\n const feed = yield* Database.load(chat.feed).pipe(Effect.orDie);\n const inheritedSkills = yield* Effect.gen(function* () {\n const runtime = yield* Effect.context<Database.Service>();\n const binder = yield* EffectEx.acquireReleaseResource(() => new AiContext.Binder({ feed, runtime }));\n return binder.getSkills().filter((skill) => Obj.getMeta(skill).key !== DelegationSkill.key);\n }).pipe(Effect.scoped);\n const skills = inheritedSkills.map((skill) => Ref.make(skill));\n\n const delegations: Delegation[] = [];\n for (const task of pending) {\n // Synthesize a minimal instructions whose goal is the task; the sub-agent runs it via RunInstructions\n // with the inherited skills bound.\n const instructions = yield* Database.add(\n Instructions.make({\n name: task.title,\n text: trim`\n Complete the following task and report the result concisely.\n\n If you create any documents or artifacts, call completeJob with a JSON object of the\n form { \"summary\": string, \"artifactIds\": string[] }, where artifactIds are the exact\n ids returned by the tools that created them. Otherwise return a short summary string.\n\n Task: ${task.title}\n `,\n skills,\n }),\n );\n\n // Started is stamped when the delegation is handed to the runtime — never by the\n // delegation verbs — so `started` always means a spawning/live sub-agent and an orphaned\n // `started` is detectable (see the sweep).\n Obj.update(task, (task) => {\n task.status = 'started';\n });\n\n delegations.push({\n id: task.id,\n spawn: Effect.gen(function* () {\n const invoker = yield* ProcessManager.ProcessOperationInvoker.Service;\n // The task ↔ process mapping lives runtime-side (the supervisor's activeIds keyed by\n // task id) — nothing is stamped on the durable task.\n const fiber = yield* invoker.invokeFiber(AgentOperation.RunInstructions, {\n instructions: Ref.make(instructions),\n input: {},\n });\n return fiber.pid;\n }),\n });\n }\n yield* Database.flush();\n return delegations;\n }),\n\n onTurnFailed: failHeldTasks,\n\n onComplete: (chat, id, exit) =>\n Effect.gen(function* () {\n const agent = yield* Agent.loadForChat(chat);\n\n // Resolve the durable task by its id and record the outcome directly on it — the task set\n // is the working surface, so there is no separate mirror to reconcile.\n let title = id;\n const tasks = yield* Database.query(Query.select(Filter.id(id))).run.pipe(Effect.orElseSucceed(() => []));\n const task = tasks.find((candidate): candidate is Task.Task => Obj.instanceOf(Task.Task, candidate));\n if (task) {\n Obj.update(task, (task) => {\n task.status = Exit.isSuccess(exit) ? 'done' : 'failed';\n });\n title = task.title;\n }\n\n // Surface any artifacts the sub-agent produced as inline reference blocks in the notification\n // message, so follow-up turns can reference them (durable filing belongs to the project's\n // collection — the agent stores no artifacts).\n const artifactRefs: Ref.Ref<Obj.Unknown>[] = [];\n if (agent && Exit.isSuccess(exit)) {\n for (const artifactId of extractArtifactIds(exit.value)) {\n const ref = yield* resolveArtifactRef(artifactId).pipe(Effect.orElseSucceed(() => undefined));\n if (ref) {\n artifactRefs.push(ref);\n }\n }\n }\n\n // The full cause (with stack traces) goes to the log; the conversation gets the error\n // messages only, so delegation errors stay debuggable without dumping a stack in the chat.\n if (Exit.isFailure(exit)) {\n log.warn('sub-agent failed', { taskId: id, title, cause: Cause.pretty(exit.cause) });\n }\n const failureSummary = Exit.isFailure(exit)\n ? Cause.prettyErrors(exit.cause)\n .map((error) => error.message)\n .join('; ')\n : undefined;\n\n const text = Exit.isSuccess(exit)\n ? `The sub-agent completed \"${title}\".${artifactRefs.length === 0 ? ` ${formatResult(exit.value)}` : ''}`\n : `The sub-agent failed to complete \"${title}\": ${failureSummary || 'unknown error'}`;\n\n // Embed each produced artifact as a `reference` block — the chat renders these as a dx-anchor\n // tag with an inline object preview, rather than a raw `echo://` URI in the text.\n const blocks = [\n { _tag: 'text' as const, text },\n ...artifactRefs.map((reference) => ({ _tag: 'reference' as const, reference })),\n ];\n\n const feed = yield* Database.load(chat.feed).pipe(Effect.orDie);\n yield* Feed.append(feed, [Message.make({ sender: 'assistant', blocks })]);\n }),\n});\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nexport { isSubAgentTask, makeDelegationStrategy as make } from './supervisor/delegation-strategy.ts';\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"chunk-DelegationStrategy.mjs","names":[],"sources":["../../src/supervisor/delegation-strategy.ts","../../src/DelegationStrategy.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as Cause from 'effect/Cause';\nimport * as Effect from 'effect/Effect';\nimport * as Exit from 'effect/Exit';\n\nimport { type Delegation, type DelegationStrategy } from '@dxos/agent-runtime';\nimport { AiContext } from '@dxos/assistant';\nimport * as Agent from '@dxos/assistant/Agent';\nimport * as Chat from '@dxos/assistant/Chat';\nimport { OperationProcess } from '@dxos/compute-runtime';\nimport * as Instructions from '@dxos/compute/Instructions';\nimport * as Process from '@dxos/compute/Process';\nimport { Database, Feed, Filter, Obj, Query, Ref } from '@dxos/echo';\nimport * as EffectEx from '@dxos/effect/EffectEx';\nimport { EID, EntityId } from '@dxos/keys';\nimport { log } from '@dxos/log';\nimport { Message, Task } from '@dxos/types';\nimport { trim } from '@dxos/util';\n\nimport { ToolkitError } from '../errors.ts';\nimport * as DelegationSkill from '../skills/delegation/DelegationSkill.ts';\nimport * as AgentOperation from '../types/AgentOperation.ts';\n\n/**\n * Normalizes an LLM-reported artifact reference (bare entity id or full ECHO URI) to a\n * fully-qualified ref in the current space. Not resolved here — it resolves lazily when read.\n */\nconst resolveArtifactRef = (id: string): Effect.Effect<Ref.Ref<Obj.Unknown>, Error, Database.Service> =>\n Effect.gen(function* () {\n const parsed = EID.tryParse(id);\n const candidate = (parsed ? EID.getEntityId(parsed) : undefined) ?? id;\n if (!EntityId.isValid(candidate)) {\n // Malformed LLM-reported id: fail so the caller's `orElseSucceed` drops it.\n return yield* Effect.fail(new ToolkitError({ message: `Invalid artifact id: ${id}` }));\n }\n const { db } = yield* Database.Service;\n return db.makeRef<Obj.Unknown>(EID.make({ spaceId: db.spaceId, entityId: candidate }));\n });\n\n/**\n * Renders a sub-agent result for inclusion in a notification message.\n */\nconst formatResult = (value: unknown): string => (typeof value === 'string' ? value : JSON.stringify(value));\n\n/**\n * Extracts artifact ids a sub-agent reported in its result (see the synthesized instructions\n * instructions). Tolerates the result being a string, or an object with `artifactIds`/`artifactId`.\n */\nconst extractArtifactIds = (value: unknown): string[] => {\n if (typeof value !== 'object' || value === null) {\n return [];\n }\n\n const record = value as { artifactIds?: unknown; artifactId?: unknown };\n const ids = Array.isArray(record.artifactIds)\n ? record.artifactIds\n : typeof record.artifactId === 'string'\n ? [record.artifactId]\n : [];\n return ids.filter((id): id is string => typeof id === 'string');\n};\n\n/**\n * A task handed to a sub-agent by the delegation verbs, which assign `{ role: 'assistant' }` and\n * nothing else. An assistant assignee carrying a `subject` names a concrete session that owns the\n * task — the conversation's own agent starting it from the planning tool, or a remote session —\n * so it is that session's work, not a spawn request for the supervisor.\n */\nexport const isSubAgentTask = (task: Task.Task): boolean =>\n task.assignee?.role === 'assistant' && task.assignee.subject === undefined;\n\n/**\n * The durable agent tasks awaiting a sub-agent for this conversation: queued (`todo`) tasks of\n * the chat's checklist whose assignee is an agent, all of whose dependencies are done. Ordinary\n * (unassigned) tasks are never spawned — delegation happens only through the delegation verbs.\n * `started` always means a live process (set at spawn), so a started agent task with no process\n * is an orphan — {@link sweepOrphanedTasks}.\n */\nconst findPendingTasks = (\n chat: Chat.Chat,\n activeIds: ReadonlySet<string>,\n): Effect.Effect<Task.Task[], never, Database.Service> =>\n Effect.gen(function* () {\n const tasks = yield* Chat.loadTasks(chat);\n // The chat's `tasks` array is flat, so a delegated sub-task is found without descending.\n return tasks.filter(\n (task) =>\n isSubAgentTask(task) &&\n (task.status ?? 'todo') === 'todo' &&\n !activeIds.has(task.id) &&\n Task.isTaskReady(tasks, task),\n );\n });\n\n/**\n * Fails agent tasks stuck in `started` with no live process — a crashed or never-exited\n * sub-agent (e.g. the host reloaded mid-run) must not wedge the task or the delegation loop.\n */\nconst sweepOrphanedTasks = (\n chat: Chat.Chat,\n activeIds: ReadonlySet<string>,\n): Effect.Effect<void, never, Database.Service> =>\n Effect.gen(function* () {\n const tasks = yield* Chat.loadTasks(chat);\n const orphans = tasks.filter(\n (task) => isSubAgentTask(task) && task.status === 'started' && !activeIds.has(task.id),\n );\n if (orphans.length === 0) {\n return;\n }\n for (const task of orphans) {\n log.warn('orphaned delegated task failed', { taskId: task.id, title: task.title });\n Obj.update(task, (task) => {\n task.status = 'failed';\n });\n }\n yield* Database.flush();\n });\n\n/**\n * Fails the tasks the conversation's own agent holds when its turn fails — a model error ends the\n * process before any reconcile, so a task left `started` would read as underway indefinitely.\n * Sub-agent tasks are left to their own process's exit, which may still succeed.\n */\nconst failHeldTasks = (chat: Chat.Chat, cause: Cause.Cause<unknown>): Effect.Effect<void, never, Database.Service> =>\n Effect.gen(function* () {\n const held = (yield* Chat.loadTasks(chat)).filter((task) => Task.isAgentWorking(task) && !isSubAgentTask(task));\n if (held.length === 0) {\n return;\n }\n\n // The reader sees the error message; the full cause goes to the log.\n const reason = Cause.prettyErrors(cause)\n .map((error) => error.message)\n .join('; ');\n log.warn('agent turn failed; failing held tasks', { chatId: chat.id, cause: Cause.pretty(cause) });\n for (const task of held) {\n Task.setStatus(task, 'failed', {\n actor: { role: 'assistant', subject: Ref.make(chat) },\n description: `The agent's request failed${reason ? `: ${reason}` : '.'}`,\n });\n }\n yield* Database.flush();\n });\n\n/**\n * Supervisor behaviour for the conversational agent: after each turn, every queued agent task\n * whose dependencies are done is run by a sub-agent (a synthesized minimal `Routine` executed via\n * `RunInstructions`), marked started at spawn; on exit the task is marked done/failed and a\n * templated message is posted back to the conversation, whose turn re-runs this reconcile — so a\n * batch of delegated tasks drains in dependency order without further prompting.\n */\nexport const makeDelegationStrategy = (): DelegationStrategy => ({\n reconcile: (chat, activeIds) =>\n Effect.gen(function* () {\n yield* sweepOrphanedTasks(chat, activeIds);\n const pending = yield* findPendingTasks(chat, activeIds);\n if (pending.length === 0) {\n return [];\n }\n\n // Sub-agents inherit the supervisor's bound skills (so they have the same tools/\n // capabilities), minus the delegation skill itself — otherwise a sub-agent could\n // recursively delegate. Resolved from the conversation's AiContext bindings.\n const feed = yield* Database.load(chat.feed).pipe(Effect.orDie);\n const inheritedSkills = yield* Effect.gen(function* () {\n const runtime = yield* Effect.context<Database.Service>();\n const binder = yield* EffectEx.acquireReleaseResource(() => new AiContext.Binder({ feed, runtime }));\n return binder.getSkills().filter((skill) => Obj.getMeta(skill).key !== DelegationSkill.key);\n }).pipe(Effect.scoped);\n const skills = inheritedSkills.map((skill) => Ref.make(skill));\n\n const delegations: Delegation[] = [];\n for (const task of pending) {\n // Synthesize a minimal instructions whose goal is the task; the sub-agent runs it via RunInstructions\n // with the inherited skills bound.\n const instructions = yield* Database.add(\n Instructions.make({\n name: task.title,\n text: trim`\n Complete the following task and report the result concisely.\n\n If you create any documents or artifacts, call completeJob with a JSON object of the\n form { \"summary\": string, \"artifactIds\": string[] }, where artifactIds are the exact\n ids returned by the tools that created them. Otherwise return a short summary string.\n\n Task: ${task.title}\n `,\n skills,\n }),\n );\n\n // Started is stamped when the delegation is handed to the runtime — never by the\n // delegation verbs — so `started` always means a spawning/live sub-agent and an orphaned\n // `started` is detectable (see the sweep).\n Obj.update(task, (task) => {\n task.status = 'started';\n });\n\n delegations.push({\n id: task.id,\n spawn: Effect.gen(function* () {\n // The task ↔ process mapping lives runtime-side (the supervisor's activeIds keyed by\n // task id) — nothing is stamped on the durable task.\n const handle = yield* Process.spawn(OperationProcess.make(AgentOperation.RunInstructions), {\n instructions: Ref.make(instructions),\n input: {},\n });\n return handle.pid;\n }),\n });\n }\n yield* Database.flush();\n return delegations;\n }),\n\n onTurnFailed: failHeldTasks,\n\n onComplete: (chat, id, exit) =>\n Effect.gen(function* () {\n const agent = yield* Agent.loadForChat(chat);\n\n // Resolve the durable task by its id and record the outcome directly on it — the task set\n // is the working surface, so there is no separate mirror to reconcile.\n let title = id;\n const tasks = yield* Database.query(Query.select(Filter.id(id))).run.pipe(Effect.orElseSucceed(() => []));\n const task = tasks.find((candidate): candidate is Task.Task => Obj.instanceOf(Task.Task, candidate));\n if (task) {\n Obj.update(task, (task) => {\n task.status = Exit.isSuccess(exit) ? 'done' : 'failed';\n });\n title = task.title;\n }\n\n // Surface any artifacts the sub-agent produced as inline reference blocks in the notification\n // message, so follow-up turns can reference them (durable filing belongs to the project's\n // collection — the agent stores no artifacts).\n const artifactRefs: Ref.Ref<Obj.Unknown>[] = [];\n if (agent && Exit.isSuccess(exit)) {\n for (const artifactId of extractArtifactIds(exit.value)) {\n const ref = yield* resolveArtifactRef(artifactId).pipe(Effect.orElseSucceed(() => undefined));\n if (ref) {\n artifactRefs.push(ref);\n }\n }\n }\n\n // The full cause (with stack traces) goes to the log; the conversation gets the error\n // messages only, so delegation errors stay debuggable without dumping a stack in the chat.\n if (Exit.isFailure(exit)) {\n log.warn('sub-agent failed', { taskId: id, title, cause: Cause.pretty(exit.cause) });\n }\n const failureSummary = Exit.isFailure(exit)\n ? Cause.prettyErrors(exit.cause)\n .map((error) => error.message)\n .join('; ')\n : undefined;\n\n const text = Exit.isSuccess(exit)\n ? `The sub-agent completed \"${title}\".${artifactRefs.length === 0 ? ` ${formatResult(exit.value)}` : ''}`\n : `The sub-agent failed to complete \"${title}\": ${failureSummary || 'unknown error'}`;\n\n // Embed each produced artifact as a `reference` block — the chat renders these as a dx-anchor\n // tag with an inline object preview, rather than a raw `echo://` URI in the text.\n const blocks = [\n { _tag: 'text' as const, text },\n ...artifactRefs.map((reference) => ({ _tag: 'reference' as const, reference })),\n ];\n\n const feed = yield* Database.load(chat.feed).pipe(Effect.orDie);\n yield* Feed.append(feed, [Message.make({ sender: 'assistant', blocks })]);\n }),\n});\n","//\n// Copyright 2026 DXOS.org\n//\n\n// @import-as-namespace\n\nexport { isSubAgentTask, makeDelegationStrategy as make } from './supervisor/delegation-strategy.ts';\n"],"mappings":"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"}
|
package/dist/lib/chunk-relay.mjs
CHANGED
|
@@ -33,7 +33,7 @@ var handler = Relay.pipe(Operation.withHandler(Effect.fnUntraced(function* ({ ch
|
|
|
33
33
|
return true;
|
|
34
34
|
}))))) return;
|
|
35
35
|
}
|
|
36
|
-
const session = yield* AgentService.getSession(chat, { location: chat
|
|
36
|
+
const session = yield* AgentService.getSession(chat, { location: Agent.chatLocation(chat) });
|
|
37
37
|
const content = prompt ?? JSON.stringify(event);
|
|
38
38
|
yield* session.submitPrompt([{
|
|
39
39
|
_tag: "text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chunk-relay.mjs","names":[],"sources":["../../src/skills/agent/operations/relay.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as LanguageModel from 'effect/ai/LanguageModel';\nimport * as Prompt from 'effect/ai/Prompt';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { AiService } from '@dxos/ai';\nimport * as Agent from '@dxos/assistant/Agent';\nimport * as Chat from '@dxos/assistant/Chat';\nimport * as AgentService from '@dxos/compute/AgentService';\nimport * as Operation from '@dxos/compute/Operation';\nimport { Database, Obj } from '@dxos/echo';\nimport { log } from '@dxos/log';\nimport { trim } from '@dxos/util';\n\nimport { Relay } from './definitions.ts';\n\n/**\n * The relay pattern (plugin-projects PLAN.md phase C): one trigger per subscribed feed runs this\n * relay, which qualifies the event with a cheap model and, when relevant, forwards it onto the\n * chat's durable session (the process input queue) — multiplexing and filtering in one construct,\n * with no intermediate staging feed.\n */\nconst handler: Operation.WithHandler<typeof Relay> = Relay.pipe(\n Operation.withHandler(\n Effect.fnUntraced(\n function* ({ chat: chatRef, event, prompt, qualify }) {\n if (!event && !prompt) {\n return yield* Effect.die(new Error('Relay requires an event or a prompt.'));\n }\n const chat = yield* Database.load(chatRef).pipe(\n Effect.catchTag('EntityNotFoundError', () => Effect.die(new Error('Unable to load relay chat.'))),\n );\n\n if (event && (qualify ?? true)) {\n // Fail open (matching the prompt's \"if unsure, return true\"): a malformed model reply\n // forwards the event rather than silently dropping it.\n const relevant = yield* qualifyEvent(chat, event).pipe(\n Effect.retry({ times: 1 }),\n Effect.catch((error) =>\n Effect.sync(() => {\n log.warn('relay qualification failed; forwarding event', { error });\n return true;\n }),\n ),\n );\n if (!relevant) {\n return;\n }\n }\n\n // The durable session is bound to the chat, so it recovers its steering (and its queue)\n // from the chat itself on every rehydration.\n const session = yield* AgentService.getSession(chat, { location: chat
|
|
1
|
+
{"version":3,"file":"chunk-relay.mjs","names":[],"sources":["../../src/skills/agent/operations/relay.ts"],"sourcesContent":["//\n// Copyright 2026 DXOS.org\n//\n\nimport * as LanguageModel from 'effect/ai/LanguageModel';\nimport * as Prompt from 'effect/ai/Prompt';\nimport * as Effect from 'effect/Effect';\nimport * as Schema from 'effect/Schema';\n\nimport { AiService } from '@dxos/ai';\nimport * as Agent from '@dxos/assistant/Agent';\nimport * as Chat from '@dxos/assistant/Chat';\nimport * as AgentService from '@dxos/compute/AgentService';\nimport * as Operation from '@dxos/compute/Operation';\nimport { Database, Obj } from '@dxos/echo';\nimport { log } from '@dxos/log';\nimport { trim } from '@dxos/util';\n\nimport { Relay } from './definitions.ts';\n\n/**\n * The relay pattern (plugin-projects PLAN.md phase C): one trigger per subscribed feed runs this\n * relay, which qualifies the event with a cheap model and, when relevant, forwards it onto the\n * chat's durable session (the process input queue) — multiplexing and filtering in one construct,\n * with no intermediate staging feed.\n */\nconst handler: Operation.WithHandler<typeof Relay> = Relay.pipe(\n Operation.withHandler(\n Effect.fnUntraced(\n function* ({ chat: chatRef, event, prompt, qualify }) {\n if (!event && !prompt) {\n return yield* Effect.die(new Error('Relay requires an event or a prompt.'));\n }\n const chat = yield* Database.load(chatRef).pipe(\n Effect.catchTag('EntityNotFoundError', () => Effect.die(new Error('Unable to load relay chat.'))),\n );\n\n if (event && (qualify ?? true)) {\n // Fail open (matching the prompt's \"if unsure, return true\"): a malformed model reply\n // forwards the event rather than silently dropping it.\n const relevant = yield* qualifyEvent(chat, event).pipe(\n Effect.retry({ times: 1 }),\n Effect.catch((error) =>\n Effect.sync(() => {\n log.warn('relay qualification failed; forwarding event', { error });\n return true;\n }),\n ),\n );\n if (!relevant) {\n return;\n }\n }\n\n // The durable session is bound to the chat, so it recovers its steering (and its queue)\n // from the chat itself on every rehydration.\n const session = yield* AgentService.getSession(chat, { location: Agent.chatLocation(chat) });\n const content = prompt ?? JSON.stringify(event);\n yield* session.submitPrompt([{ _tag: 'text', text: content, disposition: 'synthetic' }]);\n },\n Effect.provide(AiService.languageModel('com.anthropic.model.claude-sonnet-5.default')),\n ),\n ),\n Operation.opaqueHandler,\n);\n\n/** Cheap-model relevance check, ported from the retired Qualifier's prompt (chat-centric per phase B). */\nconst qualifyEvent = (chat: Chat.Chat, event: unknown) =>\n Effect.gen(function* () {\n const agent = yield* Agent.loadForChat(chat);\n const instructions = chat.instructions\n ? yield* Database.load(chat.instructions).pipe(Effect.orElseSucceed(() => undefined))\n : undefined;\n const instructionsText = instructions\n ? yield* Database.load(instructions.text).pipe(\n Effect.map((doc) => doc.content),\n Effect.catchTag('EntityNotFoundError', () => Effect.succeed('')),\n )\n : '';\n const checklistText = yield* Chat.formatChecklist(chat);\n\n const { value } = yield* Effect.scoped(\n LanguageModel.generateObject({\n schema: Schema.Struct({\n isRelevant: Schema.Boolean,\n }),\n prompt: Prompt.fromMessages([\n Prompt.systemMessage({\n content: trim`\n You are a qualifying agent that determines if the event is relevant to the agent.\n Respond with true if the event is relevant to the agent, false otherwise.\n If you are not sure, return true.\n\n The qualified events will be forwarded to the larger agent that will process them.\n\n <agent id=\"${agent ? Obj.getURI(agent) : 'unknown'}\" name=\"${agent?.name ?? ''}\">\n <instructions>\n ${instructionsText}\n </instructions>\n <checklist>\n ${checklistText}\n </checklist>\n </agent>\n `,\n }),\n Prompt.userMessage({\n content: [\n Prompt.makePart('text', {\n text: trim`\n <event>\n ${JSON.stringify(event, null, 2)}\n </event>\n `,\n }),\n ],\n }),\n ]),\n }),\n );\n\n return value.isRelevant;\n });\n\nexport default handler;\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA0BA,IAAM,UAA+C,MAAM,KACzD,UAAU,YACR,OAAO,WACL,WAAW,EAAE,MAAM,SAAS,OAAO,QAAQ,WAAW;CACpD,IAAI,CAAC,SAAS,CAAC,QACb,OAAO,OAAO,OAAO,oBAAI,IAAI,MAAM,sCAAsC,CAAC;CAE5E,MAAM,OAAO,OAAO,SAAS,KAAK,OAAO,CAAC,CAAC,KACzC,OAAO,SAAS,6BAA6B,OAAO,oBAAI,IAAI,MAAM,4BAA4B,CAAC,CAAC,CAClG;CAEA,IAAI,UAAU,WAAW,OAYnB;MAAA,EAAC,OATmB,aAAa,MAAM,KAAK,CAAC,CAAC,KAChD,OAAO,MAAM,EAAE,OAAO,EAAE,CAAC,GACzB,OAAO,OAAO,UACZ,OAAO,WAAW;GAChB,IAAI,KAAK,gDAAgD,EAAE,MAAM,GAAA;IAAA,YAAA;IAAA,GAAA;IAAA,GAAA;IAAA,GAAA;GAAA,CAAC;GAClE,OAAO;EACT,CAAC,CACH,CACF,IAEE;CAAA;CAMJ,MAAM,UAAU,OAAO,aAAa,WAAW,MAAM,EAAE,UAAU,MAAM,aAAa,IAAI,EAAE,CAAC;CAC3F,MAAM,UAAU,UAAU,KAAK,UAAU,KAAK;CAC9C,OAAO,QAAQ,aAAa,CAAC;EAAE,MAAM;EAAQ,MAAM;EAAS,aAAa;CAAY,CAAC,CAAC;AACzF,GACA,OAAO,QAAQ,UAAU,cAAc,6CAA6C,CAAC,CACvF,CACF,GACA,UAAU,aACZ;;AAGA,IAAM,gBAAgB,MAAiB,UACrC,OAAO,IAAI,aAAa;CACtB,MAAM,QAAQ,OAAO,MAAM,YAAY,IAAI;CAC3C,MAAM,eAAe,KAAK,eACtB,OAAO,SAAS,KAAK,KAAK,YAAY,CAAC,CAAC,KAAK,OAAO,oBAAoB,KAAA,CAAS,CAAC,IAClF,KAAA;CACJ,MAAM,mBAAmB,eACrB,OAAO,SAAS,KAAK,aAAa,IAAI,CAAC,CAAC,KACtC,OAAO,KAAK,QAAQ,IAAI,OAAO,GAC/B,OAAO,SAAS,6BAA6B,OAAO,QAAQ,EAAE,CAAC,CACjE,IACA;CACJ,MAAM,gBAAgB,OAAO,KAAK,gBAAgB,IAAI;CAEtD,MAAM,EAAE,UAAU,OAAO,OAAO,OAC9B,cAAc,eAAe;EAC3B,QAAQ,OAAO,OAAO,EACpB,YAAY,OAAO,QACrB,CAAC;EACD,QAAQ,OAAO,aAAa,CAC1B,OAAO,cAAc,EACnB,SAAS,IAAI;;;;;;;2BAOE,QAAQ,IAAI,OAAO,KAAK,IAAI,UAAU,UAAU,OAAO,QAAQ,GAAG;;kBAE3E,iBAAgB;;;oBAGd,cAAa;;;cAIvB,CAAC,GACD,OAAO,YAAY,EACjB,SAAS,CACP,OAAO,SAAS,QAAQ,EACtB,MAAM,IAAI;;sBAEJ,KAAK,UAAU,OAAO,MAAM,CAAC,EAAC;;kBAGtC,CAAC,CACH,EACF,CAAC,CACH,CAAC;CACH,CAAC,CACH;CAEA,OAAO,MAAM;AACf,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"delegation-strategy.d.ts","sourceRoot":"","sources":["../../../../src/supervisor/delegation-strategy.ts"],"names":[],"mappings":"AAQA,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"delegation-strategy.d.ts","sourceRoot":"","sources":["../../../../src/supervisor/delegation-strategy.ts"],"names":[],"mappings":"AAQA,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAW/E,OAAO,EAAW,IAAI,EAAE,MAAM,aAAa,CAAC;AA8C5C;;;;;GAKG;AACH,eAAO,MAAM,cAAc,SAAU,IAAI,CAAC,IAAI,KAAG,OAC2B,CAAC;AA4E7E;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,QAAO,kBAwHxC,CAAC"}
|