@desplega.ai/agent-swarm 1.147.0 → 1.149.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -1
- package/dist/{acp-adapter-9n319wqc.js → acp-adapter-4jncb126.js} +12 -5
- package/dist/{actions-2vxqvpr9.js → actions-31vp3ae7.js} +10 -9
- package/dist/{app-w066xfy0.js → app-1hh67x8w.js} +6 -6
- package/dist/{assistant-kt31dj2h.js → assistant-z66dmz6p.js} +13 -11
- package/dist/{boot-reembed-az5rassp.js → boot-reembed-fqzxxzjh.js} +7 -7
- package/dist/{boot-reembed-j3mm3rfz.js → boot-reembed-wxa8ya0s.js} +8 -8
- package/dist/{boot-scrub-logs-b6h54817.js → boot-scrub-logs-jnyndh79.js} +5 -5
- package/dist/{claude-adapter-w1z04ann.js → claude-adapter-3qpa9w0j.js} +9 -9
- package/dist/{claude-managed-adapter-sactwn31.js → claude-managed-adapter-qvsmh6mv.js} +2 -2
- package/dist/{claude-sdk-session-f0cc1kec.js → claude-sdk-session-2yxgjs5d.js} +9 -9
- package/dist/{cli-y6b6fb76.js → cli-08b07b4r.js} +1 -1
- package/dist/{cli-8fc8de14.js → cli-0z2v4nhw.js} +1 -1
- package/dist/{cli-de14znh8.js → cli-19f354qr.js} +2 -2
- package/dist/{cli-hbv0kq6w.js → cli-2qts1hys.js} +3 -3
- package/dist/{cli-kpv03zhj.js → cli-3apbzgyk.js} +27 -15
- package/dist/{cli-1th1728g.js → cli-3q5ejx9p.js} +2 -2
- package/dist/{cli-d8brzxjb.js → cli-4wtq5jjv.js} +2 -2
- package/dist/{cli-ct0et58h.js → cli-5my3bjsd.js} +1 -1
- package/dist/{cli-cn6mmc7f.js → cli-6664w7y4.js} +1 -1
- package/dist/{cli-yz3djrm0.js → cli-6pqcb4q0.js} +53 -13
- package/dist/{cli-719k9j2c.js → cli-7smrsr25.js} +3 -3
- package/dist/{cli-rw51bq3j.js → cli-9fy6nk6g.js} +1 -1
- package/dist/{cli-zhjmqxzh.js → cli-9hspd3dp.js} +2 -2
- package/dist/{cli-3tgwnf43.js → cli-aae6hz6a.js} +4 -4
- package/dist/{cli-89xfd7f9.js → cli-cr12paw2.js} +5 -5
- package/dist/{cli-b5bq9crk.js → cli-d8ftsp62.js} +8 -8
- package/dist/{cli-4nr988h6.js → cli-e95cx1eb.js} +3 -3
- package/dist/{cli-n8508nre.js → cli-gjhjfeeg.js} +1 -1
- package/dist/{cli-p1d5b073.js → cli-gv88e1vf.js} +1 -1
- package/dist/{cli-rvqz34y5.js → cli-jecc01cb.js} +1 -1
- package/dist/{cli-8v0g1yc8.js → cli-k0cr4kat.js} +1 -1
- package/dist/{cli-mmemxxdc.js → cli-kgz4np97.js} +4 -4
- package/dist/{cli-w9kb3bk1.js → cli-kjwt6hdf.js} +3 -1
- package/dist/{cli-f7dsy61y.js → cli-mnmsfd1w.js} +6 -2
- package/dist/{cli-54wwve05.js → cli-n3dz2942.js} +37 -26
- package/dist/{cli-12fz972k.js → cli-nye12xk5.js} +8 -5
- package/dist/{cli-tzvk9haz.js → cli-phsxnkp6.js} +20 -20
- package/dist/{cli-53s590z8.js → cli-q50ef8g0.js} +1 -1
- package/dist/{cli-f146mn5s.js → cli-qaacms9y.js} +7 -6
- package/dist/{cli-xqcq9y5e.js → cli-r2ap2czm.js} +1 -0
- package/dist/{cli-m8yrsg97.js → cli-sd5pv3b1.js} +3 -3
- package/dist/{cli-0b6y7kxm.js → cli-xm87hzbw.js} +1 -1
- package/dist/{cli-ftht3zzj.js → cli-yab68w40.js} +285 -11
- package/dist/{cli-atcve9y8.js → cli-yb9qhqam.js} +227 -59
- package/dist/{cli-smby5m17.js → cli-ygweqcn4.js} +133 -22
- package/dist/{cli-dttph1bw.js → cli-ykxrnd1q.js} +2 -2
- package/dist/{cli-m9sxbkhm.js → cli-zvz62chv.js} +3 -3
- package/dist/cli.js +16 -14
- package/dist/{codex-adapter-zw4dwsx7.js → codex-adapter-8meawydj.js} +4 -4
- package/dist/{codex-hook-bw8p5nh4.js → codex-hook-hfrt9shr.js} +2 -2
- package/dist/{codex-session-runner-k981s65v.js → codex-session-runner-gjgxyp5j.js} +4 -4
- package/dist/{commands-sykrxx7e.js → commands-hz3nb95f.js} +5 -5
- package/dist/{db-sfbsc8wt.js → db-ggz97zbm.js} +5 -5
- package/dist/{e2b-6hb103d8.js → e2b-p04vmtx0.js} +1 -1
- package/dist/{handlers-0aspcejm.js → handlers-nbzdzn9k.js} +14 -11
- package/dist/{hook-hxccs7m5.js → hook-bks2q1f6.js} +7 -7
- package/dist/{hook-4nvqj2sb.js → hook-kq9wp0ep.js} +8 -8
- package/dist/{http-y06z517d.js → http-hrf77v87.js} +96 -45
- package/dist/{index-tce1rss4.js → index-6nsbg88z.js} +443 -144
- package/dist/{index-x5j894zf.js → index-8hmeg8j1.js} +14 -14
- package/dist/{index-k8znfx0z.js → index-cqtebbhb.js} +13 -13
- package/dist/{index-hssww7kj.js → index-n28pw1zt.js} +15 -15
- package/dist/{keepalive-w733ax66.js → keepalive-dxc12gvg.js} +7 -7
- package/dist/{lead-75bjqa9d.js → lead-yckfn18d.js} +32 -32
- package/dist/{maintenance-w4f1zjck.js → maintenance-6kfb2m5e.js} +8 -8
- package/dist/{oauth-refresh-sweep-wdetky26.js → oauth-refresh-sweep-1x1ac722.js} +6 -6
- package/dist/{onboard-bb5net8s.js → onboard-dcyt1609.js} +4 -3
- package/dist/{opencode-adapter-fef1mrn2.js → opencode-adapter-dce4wskd.js} +2 -2
- package/dist/{otel-impl-0w7c14ap.js → otel-impl-tbk3e5dr.js} +2 -2
- package/dist/{pi-mono-adapter-2has4gnp.js → pi-mono-adapter-zvshk5cj.js} +2 -2
- package/dist/{pricing-refresh-9nzsrgcq.js → pricing-refresh-kaqg567e.js} +7 -7
- package/dist/{rbac-roles-z1jrffbt.js → rbac-roles-ammq6rd2.js} +5 -5
- package/dist/{rbac-roles-r3hqjp09.js → rbac-roles-cf97v1vd.js} +6 -6
- package/dist/{render-v2-vbr89fnb.js → render-v2-80w603vy.js} +6 -6
- package/dist/{seed-pricing-g62hy1pk.js → seed-pricing-zbrn83xv.js} +6 -6
- package/dist/{setup-gnfqnptp.js → setup-fe66kczs.js} +2 -2
- package/dist/{worker-kznxp4qm.js → worker-qmb2d1e2.js} +32 -32
- package/dist/{x-n2phr5vm.js → x-n3e0v3xa.js} +2 -2
- package/openapi.json +46 -4
- package/package.json +3 -1
- package/src/agentmail/handlers.ts +5 -0
- package/src/automation-preflight-alert.ts +41 -0
- package/src/be/automation-preflight.ts +3 -3
- package/src/be/budget-refusal-notify.ts +1 -0
- package/src/be/db/tasks/read.ts +5 -0
- package/src/be/db.ts +132 -2
- package/src/be/migrations/150_deferred_task_waits.sql +18 -0
- package/src/be/migrations/151_repair_scheduled_task_required_params.sql +39 -0
- package/src/be/migrations/152_routing_decisions.sql +44 -0
- package/src/be/scripts/typecheck.ts +33 -22
- package/src/be/seed-scripts/catalog/delegate.ts +5 -1
- package/src/be/seed-skills/bundled-files.generated.json +10 -0
- package/src/be/seed-skills/index.ts +3 -0
- package/src/be/steering.ts +1 -0
- package/src/be/swarm-config-guard.ts +11 -0
- package/src/commands/onboard/steps/post-task.tsx +1 -0
- package/src/github/handlers.ts +9 -0
- package/src/gitlab/handlers.ts +4 -0
- package/src/heartbeat/heartbeat.ts +8 -0
- package/src/http/approval-requests.ts +1 -0
- package/src/http/apps.ts +1 -0
- package/src/http/config.ts +15 -6
- package/src/http/mcp.ts +31 -4
- package/src/http/script-runs.ts +6 -0
- package/src/http/tasks.ts +52 -30
- package/src/integrations/kapso/inbound.ts +1 -0
- package/src/jira/sync.ts +2 -0
- package/src/linear/sync.ts +2 -0
- package/src/prompts/session-templates.ts +29 -15
- package/src/scheduler/deferred-task-waits.ts +123 -0
- package/src/scheduler/schedule-task.ts +42 -0
- package/src/scheduler/scheduler.ts +22 -31
- package/src/script-workflows/workflow-ctx.ts +2 -0
- package/src/scripts-runtime/types/stdlib.d.ts +38 -24
- package/src/scripts-runtime/types/swarm-sdk.d.ts +38 -24
- package/src/server.ts +10 -1
- package/src/slack/actions.ts +1 -0
- package/src/slack/assistant.ts +2 -0
- package/src/slack/handlers.ts +3 -0
- package/src/slack/thread-buffer.ts +2 -0
- package/src/tasks/worker-follow-up.ts +9 -0
- package/src/tests/acp-adapter.test.ts +35 -6
- package/src/tests/acp-session-token.test.ts +67 -0
- package/src/tests/asset-key-api.test.ts +15 -2
- package/src/tests/asset-key-mcp.test.ts +2 -0
- package/src/tests/automation-preflight-alert.test.ts +91 -0
- package/src/tests/claude-worker-parity.test.ts +3 -0
- package/src/tests/config-session-auth.test.ts +143 -0
- package/src/tests/defer-task.test.ts +117 -6
- package/src/tests/deferred-task-wake.test.ts +456 -0
- package/src/tests/heartbeat-reroute-decision.test.ts +1 -0
- package/src/tests/http-api-integration.test.ts +33 -4
- package/src/tests/mcp-input-ergonomics.test.ts +386 -0
- package/src/tests/model-control.test.ts +18 -5
- package/src/tests/opencode-adapter.test.ts +2 -2
- package/src/tests/promote-draft-task-route.test.ts +4 -0
- package/src/tests/prompt-template-session.test.ts +13 -4
- package/src/tests/rbac-wire-e2e.test.ts +2 -2
- package/src/tests/routing-decision-persistence.test.ts +520 -0
- package/src/tests/routing-reason-contract.test.ts +89 -0
- package/src/tests/routing-reason-inventory.test.ts +55 -0
- package/src/tests/schedule-target-type.test.ts +48 -1
- package/src/tests/scheduled-task-required-params-migration.test.ts +182 -0
- package/src/tests/scheduled-tasks.test.ts +9 -5
- package/src/tests/script-connections.test.ts +4 -0
- package/src/tests/script-runs-http.test.ts +31 -0
- package/src/tests/scripts-typecheck.test.ts +11 -0
- package/src/tests/secret-scrubber.test.ts +12 -0
- package/src/tests/send-task-output-schema.test.ts +66 -1
- package/src/tests/send-task-requested-by.test.ts +1 -0
- package/src/tests/send-task-slack-routing-guard.test.ts +2 -0
- package/src/tests/store-progress-blocked-waiting-gate.test.ts +151 -0
- package/src/tests/swarm-tool-result-gate.test.ts +24 -0
- package/src/tests/task-tool-manifest.test.ts +47 -0
- package/src/tests/task-tool-preload-mcp.test.ts +125 -0
- package/src/tests/task-tools-ctx.test.ts +2 -0
- package/src/tests/tool-output-agent-id.test.ts +1 -0
- package/src/tests/ui-followup-lead-delegation.test.ts +6 -1
- package/src/tests/workflow-agent-task.test.ts +24 -1
- package/src/tests/workflow-engine-v2.test.ts +58 -2
- package/src/tools/accept-steer.ts +0 -1
- package/src/tools/defer-task.ts +73 -21
- package/src/tools/memory-get.ts +10 -3
- package/src/tools/memory-rate.ts +19 -7
- package/src/tools/memory-search.ts +2 -1
- package/src/tools/memory-store.ts +25 -5
- package/src/tools/send-task.ts +40 -2
- package/src/tools/skills/skill-publish.ts +1 -0
- package/src/tools/store-progress.ts +78 -4
- package/src/tools/templates.ts +2 -1
- package/src/tools/utils.ts +33 -2
- package/src/types.ts +14 -1
- package/src/utils/acp-session-token.ts +14 -4
- package/src/utils/secret-scrubber.ts +2 -0
- package/src/utils/task-tool-manifest.ts +33 -0
- package/src/workflows/engine.ts +4 -1
- package/src/workflows/executors/agent-task.ts +7 -1
- package/templates/ai-toolbox.manifest.json +6 -1
- package/templates/skills/comms/config.json +18 -0
- package/templates/skills/comms/content.md +74 -0
- package/templates/skills/comms/files/references/ste-rules.md +73 -0
- package/templates/skills/comms/files/references/visual-shapes.md +112 -0
- package/templates/skills/swarm-scripts/SKILL.md +3 -2
- package/templates/skills/swarm-scripts/content.md +3 -2
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
ALTER TABLE agent_tasks ADD COLUMN routing_reason TEXT
|
|
2
|
+
CHECK (
|
|
3
|
+
routing_reason IS NULL OR routing_reason IN (
|
|
4
|
+
'skill',
|
|
5
|
+
'continuity',
|
|
6
|
+
'overflow',
|
|
7
|
+
'human_pinned',
|
|
8
|
+
'reroute_fault'
|
|
9
|
+
)
|
|
10
|
+
);
|
|
11
|
+
|
|
12
|
+
ALTER TABLE agent_tasks ADD COLUMN routing_note TEXT
|
|
13
|
+
CHECK (routing_note IS NULL OR length(routing_note) <= 200);
|
|
14
|
+
|
|
15
|
+
-- Continuity snapshots run while task creation holds BEGIN IMMEDIATE. Keep
|
|
16
|
+
-- each context lookup filter-first and recency-ordered. SQLite stores rowid as
|
|
17
|
+
-- the implicit final index key, so a reverse scan also satisfies the
|
|
18
|
+
-- createdAt DESC, rowid DESC deterministic tie-break.
|
|
19
|
+
CREATE INDEX idx_agent_tasks_routing_pr_recency
|
|
20
|
+
ON agent_tasks(vcsRepo, vcsNumber, createdAt);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX idx_agent_tasks_routing_repo_recency
|
|
23
|
+
ON agent_tasks(vcsRepo, createdAt);
|
|
24
|
+
|
|
25
|
+
CREATE INDEX idx_agent_tasks_routing_slack_thread_recency
|
|
26
|
+
ON agent_tasks(slackChannelId, slackThreadTs, createdAt);
|
|
27
|
+
|
|
28
|
+
CREATE INDEX idx_agent_tasks_routing_agentmail_thread_recency
|
|
29
|
+
ON agent_tasks(agentmailThreadId, createdAt);
|
|
30
|
+
|
|
31
|
+
-- Immutable decision-time telemetry. Agent ids deliberately are not foreign
|
|
32
|
+
-- keys: deleting an agent must not erase which target or candidate was visible
|
|
33
|
+
-- when the task was created.
|
|
34
|
+
CREATE TABLE routing_decisions (
|
|
35
|
+
task_id TEXT PRIMARY KEY REFERENCES agent_tasks(id) ON DELETE CASCADE,
|
|
36
|
+
selected_agent_id TEXT,
|
|
37
|
+
captured_at TEXT NOT NULL,
|
|
38
|
+
worker_statuses TEXT NOT NULL
|
|
39
|
+
CHECK (json_valid(worker_statuses) AND json_type(worker_statuses) = 'array'),
|
|
40
|
+
continuity_candidates TEXT NOT NULL
|
|
41
|
+
CHECK (json_valid(continuity_candidates) AND json_type(continuity_candidates) = 'object'),
|
|
42
|
+
created_by TEXT,
|
|
43
|
+
updated_by TEXT
|
|
44
|
+
);
|
|
@@ -35,6 +35,36 @@ export type ScriptScope = "agent" | "global";
|
|
|
35
35
|
export type ScriptFsMode = "none" | "workspace-rw";
|
|
36
36
|
export type ScriptApiRawOptions = { raw: true };
|
|
37
37
|
export type ScriptApiDefaultOptions = { raw?: false };
|
|
38
|
+
export type RoutingReason = "skill" | "continuity" | "overflow" | "human_pinned" | "reroute_fault";
|
|
39
|
+
export type TaskSendArgs = Record<string, unknown> & {
|
|
40
|
+
task: string;
|
|
41
|
+
routingNote?: string;
|
|
42
|
+
} & (
|
|
43
|
+
| { agentId: string; routingReason: RoutingReason }
|
|
44
|
+
| { agentId?: never; routingReason?: never }
|
|
45
|
+
);
|
|
46
|
+
export type AgentTaskStepConfig = {
|
|
47
|
+
template?: string;
|
|
48
|
+
task?: string;
|
|
49
|
+
agentId?: string;
|
|
50
|
+
routingReason?: RoutingReason;
|
|
51
|
+
routingNote?: string;
|
|
52
|
+
tags?: string[];
|
|
53
|
+
priority?: number;
|
|
54
|
+
offerMode?: boolean;
|
|
55
|
+
dir?: string;
|
|
56
|
+
vcsRepo?: string;
|
|
57
|
+
model?: string;
|
|
58
|
+
parentTaskId?: string;
|
|
59
|
+
requestedByUserId?: string;
|
|
60
|
+
outputSchema?: Record<string, unknown>;
|
|
61
|
+
/** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
|
|
62
|
+
waitForCompletion?: boolean;
|
|
63
|
+
/** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
|
|
64
|
+
timeoutMs?: number;
|
|
65
|
+
/** Throw when the task ends failed/cancelled/superseded (default), or resolve with {taskId,status,error} when false. */
|
|
66
|
+
failOnTaskFailure?: boolean;
|
|
67
|
+
};
|
|
38
68
|
|
|
39
69
|
export interface ScriptApiRawResult {
|
|
40
70
|
ok: boolean;
|
|
@@ -138,7 +168,7 @@ export interface SwarmSdk {
|
|
|
138
168
|
task_list(args?: Record<string, unknown>): Promise<unknown>;
|
|
139
169
|
task_get(args: { taskId: string }): Promise<unknown>;
|
|
140
170
|
task_storeProgress(args: Record<string, unknown>): Promise<unknown>;
|
|
141
|
-
task_defer(args: { taskId: string; delayMs?: number; runAt?: string; summary: string; note: string; checks?: string[] }): Promise<unknown>;
|
|
171
|
+
task_defer(args: { taskId: string; delayMs?: number; runAt?: string; wakeOn?: { event: "task.completed" | "task.failed" | "settled"; taskId: string }; summary: string; note: string; checks?: string[] }): Promise<unknown>;
|
|
142
172
|
task_poll(args?: Record<string, unknown>): Promise<unknown>;
|
|
143
173
|
// --- kv ---
|
|
144
174
|
kv_get<T = unknown>(args: { key: string; namespace?: string }): Promise<KvSdkResponse<KvEntry<T>>>;
|
|
@@ -219,7 +249,7 @@ export interface SwarmSdk {
|
|
|
219
249
|
inject_learning(args: { content: string; name?: string; scope?: "agent" | "swarm"; source?: string; tags?: string[] }): Promise<unknown>;
|
|
220
250
|
|
|
221
251
|
// --- write: tasks ---
|
|
222
|
-
task_send(args:
|
|
252
|
+
task_send(args: TaskSendArgs): Promise<unknown>;
|
|
223
253
|
task_cancel(args: { taskId: string }): Promise<unknown>;
|
|
224
254
|
task_steer(args: { taskId: string; message: string; mode?: "steer" | "queue"; onUnsupported?: "degrade" | "fail" }): Promise<unknown>;
|
|
225
255
|
task_action(args: Record<string, unknown>): Promise<unknown>;
|
|
@@ -374,26 +404,7 @@ export interface ScriptWorkflowSteps {
|
|
|
374
404
|
): Promise<unknown>;
|
|
375
405
|
agentTask(
|
|
376
406
|
label: string,
|
|
377
|
-
config:
|
|
378
|
-
template?: string;
|
|
379
|
-
task?: string;
|
|
380
|
-
agentId?: string;
|
|
381
|
-
tags?: string[];
|
|
382
|
-
priority?: number;
|
|
383
|
-
offerMode?: boolean;
|
|
384
|
-
dir?: string;
|
|
385
|
-
vcsRepo?: string;
|
|
386
|
-
model?: string;
|
|
387
|
-
parentTaskId?: string;
|
|
388
|
-
requestedByUserId?: string;
|
|
389
|
-
outputSchema?: Record<string, unknown>;
|
|
390
|
-
/** Wait for the dispatched task to reach a terminal status before resolving. Default: true. */
|
|
391
|
-
waitForCompletion?: boolean;
|
|
392
|
-
/** Max ms to wait for a terminal status before throwing. Default: 2h. Only used when waitForCompletion is true. */
|
|
393
|
-
timeoutMs?: number;
|
|
394
|
-
/** Throw when the task ends failed/cancelled/superseded (default), or resolve with {taskId,status,error} when false. */
|
|
395
|
-
failOnTaskFailure?: boolean;
|
|
396
|
-
},
|
|
407
|
+
config: AgentTaskStepConfig,
|
|
397
408
|
): Promise<unknown>;
|
|
398
409
|
swarmScript(
|
|
399
410
|
label: string,
|
|
@@ -2,6 +2,8 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const argsSchema = z.object({
|
|
4
4
|
agentName: z.string().describe("Target agent's name (case-insensitive) — resolved to its id"),
|
|
5
|
+
routingReason: z.enum(["skill", "continuity", "overflow", "human_pinned", "reroute_fault"]),
|
|
6
|
+
routingNote: z.string().max(200).optional(),
|
|
5
7
|
task: z.string().describe("Full task prompt for the target agent"),
|
|
6
8
|
parentTaskId: z
|
|
7
9
|
.string()
|
|
@@ -15,7 +17,7 @@ export const argsSchema = z.object({
|
|
|
15
17
|
export default async function delegate(args: any, ctx: any) {
|
|
16
18
|
const parsed = argsSchema.safeParse(args);
|
|
17
19
|
if (!parsed.success) return { ok: false, error: "invalid args: " + parsed.error.message };
|
|
18
|
-
const { agentName, task, parentTaskId, priority, tags } = parsed.data;
|
|
20
|
+
const { agentName, task, routingReason, routingNote, parentTaskId, priority, tags } = parsed.data;
|
|
19
21
|
|
|
20
22
|
const res: any = await ctx.swarm.swarm_get({ includeFull: true });
|
|
21
23
|
const agents: any[] = res?.data?.agents ?? res?.agents ?? [];
|
|
@@ -26,6 +28,8 @@ export default async function delegate(args: any, ctx: any) {
|
|
|
26
28
|
|
|
27
29
|
const sent: any = await ctx.swarm.task_send({
|
|
28
30
|
agentId: agent.id,
|
|
31
|
+
routingReason,
|
|
32
|
+
routingNote,
|
|
29
33
|
task,
|
|
30
34
|
...(parentTaskId ? { parentTaskId } : {}),
|
|
31
35
|
...(priority != null ? { priority } : {}),
|
|
@@ -23,6 +23,16 @@
|
|
|
23
23
|
"content": "---\ndate: [Current date and time with timezone in ISO format]\nauthor: [Author name]\ntopic: \"[Topic]\"\ntags: [brainstorm, relevant-tags]\nstatus: in-progress\nexploration_type: [problem|idea|comparison|workflow|other]\nlast_updated: [Current date in YYYY-MM-DD format]\nlast_updated_by: [Author name]\n---\n\n# [Topic] — Brainstorm\n\n## Context\n\n[Initial framing: what we know, what prompted this exploration, any existing context or constraints]\n\n## Exploration\n\n[Q&A pairs accumulate here during the session]\n\n## Synthesis\n\n### Key Decisions\n- [Filled after exploration — includes \"Deferred: X, defaulting to Y\" entries for consciously parked decisions]\n\n### Open Questions\n- [Fact-shaped questions only (answerable by /research) — undecided decisions don't belong here]\n\n### Constraints Identified\n- [Filled after exploration]\n\n### Core Requirements\n- [Filled after exploration]\n\n## Next Steps\n\n- [Handoff decision: research, plan, or parked]\n"
|
|
24
24
|
}
|
|
25
25
|
],
|
|
26
|
+
"comms": [
|
|
27
|
+
{
|
|
28
|
+
"path": "references/ste-rules.md",
|
|
29
|
+
"content": "# Precise mode — ASD-STE100 rules\n\nCondensed from [asd-ste100-skill](https://github.com/desplega-ai/asd-ste100-skill) (MIT), which encodes the rule categories of ASD-STE100 Issue 9 (Jan 2025).\n\nThis encodes the standard's rule *categories*, not ASD's ~900-word approved dictionary (free to obtain, not free to redistribute). Structural rules are checkable from the description alone — apply them with confidence. Lexical rules depend on the dictionary — apply them as a direction of travel, and never imply dictionary compliance.\n\n## Structural rules — apply these\n\n| Rule | Do | Don't |\n|---|---|---|\n| Active voice | \"The agent deletes the file.\" | \"The file is deleted (by the agent).\" — unless the actor is genuinely unknown or irrelevant |\n| No phrasal verbs | \"Remove the panel.\" / \"Start the job.\" | \"Take off the panel.\" / \"Spin up the job.\" |\n| One instruction per sentence | \"Open the file. Read line 3.\" | \"Open the file and read line 3, then check if it matches.\" |\n| Sentence length | ≤20 words for instructions, ≤25 for descriptions | Long compound/subordinate-clause sentences |\n| No semicolons | Split into separate sentences | Any semicolon at all |\n| Noun clusters | ≤3 words stacked as a noun phrase | 4+ word noun stacks (\"high pressure fuel pump inlet valve assembly\") |\n| No ellipsis | Keep subject, verb, and article explicit | Drop words to save space (\"Files not backed up will be lost\" → ambiguous which files) |\n| Keep modality | \"The request **may have** failed.\" stays \"may have\" | Promote a hedge to a fact, or invent a certainty the source did not state |\n| Paragraph limits | One topic per paragraph, ≤6 sentences | Multi-topic paragraphs |\n| Lists for sequences | Numbered/bulleted list for 3+ steps or conditions | A sequence buried in one prose sentence |\n\n## Lexical rules — direction of travel only\n\n| Rule | Do | Don't |\n|---|---|---|\n| One word, one meaning | Pick one verb for one action and reuse it every time | Rotate synonyms (\"check\"/\"verify\"/\"confirm\") for the same action |\n| One part of speech per word | \"Apply oil to the valve\" (oil = noun) | \"Oil the valve\" (oil = verb) |\n| Verb, not noun | \"Analyze the log.\" | \"Perform an analysis of the log.\" |\n| Domain terms | Keep needed technical terms; define each once if not common English | Jargon never defined |\n\n## Simple tenses — one exception\n\nSTE permits infinitive, imperative, simple present, simple past, simple future, and past participle as adjective. It excludes present perfect: \"we received the report\", not \"we have received the report\". Exception: where the compound form carries information the simple form cannot — current relevance (\"the job has completed\" = output available now), or a hedge like \"may have failed\" — keep it and flag the departure.\n\n## Scan checklist\n\nScan for all six before rewriting. Each is mechanical — you can point at the exact word that breaks it.\n\n1. **Synonym rotation** — the same thing has several names (\"the user\", \"the customer\", \"the client\"). Fix: one name, every time.\n2. **Hedge stacking** — qualifiers pile up until nothing is asserted (\"it is important to note that this may potentially help to improve\"). Fix: state the claim or delete it.\n3. **Nominalization** — an action frozen into a noun (\"perform an analysis of\"). Fix: use the verb.\n4. **Marketing adjectives** — seamless, robust, powerful, blazing-fast. Fix: delete, or replace with the measurement that earns the claim.\n5. **Run-on sentences** — ideas joined by semicolons or em dashes. Fix: one idea per sentence.\n6. **Soft phrasal verbs** — spin up, reach out, dive into, kick off. Fix: the single plain verb (start, contact, read, begin).\n\n## Process\n\n1. Pick the sub-mode (Strict or STE-flavored). Keep the choice internal unless asked.\n2. Read the input once for meaning before rewriting anything.\n3. Walk it sentence by sentence; flag every violation. In STE-flavored, flag lexical rules but do not enforce them.\n4. Rewrite each flagged sentence, preserving the original meaning exactly. If a rewrite would drop necessary precision (a safety condition, scope qualifier, number), keep the longer phrasing and flag it. Check modality before committing — a shorter sentence that upgrades a hedge to a fact is a different claim. Never add a fact the source did not state.\n5. Output the rewritten text alone. If the input already complies, say so — do not force changes.\n\n## Diff table format (on request)\n\nWhen asked to \"show the diff\" / \"which rules did it break\" / \"before/after\":\n\n```markdown\n| Rule violated | Original | Simplified |\n|---|---|---|\n| Present perfect tense | \"We have received your request.\" | \"We received your request.\" |\n| Noun cluster (4+ words) | \"the agent task queue priority handler\" | \"the handler that sets task-queue priority\" |\n\nMode: Strict. 7 violations found.\n```\n\nFollow with one line naming anything deliberately not simplified, and why.\n\n## Boundaries\n\n- Not a certified STE authoring tool — a clarity tool inspired by the standard. For aerospace-grade compliance, check word-by-word against the official dictionary from [asd-ste100.org](https://www.asd-ste100.org/STE_downloads.html).\n- Fixes form, not substance: a hollow paragraph rewritten under these rules is a clean hollow paragraph. Say so instead of polishing it.\n- Stop when the sentence is unambiguous, not when it is shortest.\n"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"path": "references/visual-shapes.md",
|
|
33
|
+
"content": "# Visual mode — example shapes\n\nConcrete examples for each view the Visual mode can produce. Source: the show-me skill (bundled into this skill).\n\nLogic or an algorithm as pseudocode:\n\n```text\non(save)\n if content is unchanged\n return cached result\n write new content\n return fresh result\n```\n\nRuntime control flow as a call tree:\n\n```text\nsubmitForm\n createSession\n persistPrompt\n launchAgent\n navigateToSession\n```\n\nUI structure as a component tree, including state and module boundaries that matter:\n\n```tsx\n<SessionPage> (apps/example/src/routes/session.tsx)\n useSessionEvents()\n <SessionToolbar>\n <RunSkillButton> (packages/ui)\n```\n\nFile responsibility or a broad refactor as a shallow file tree:\n\n```text\nsrc/\n├── commands/ # parses user actions\n├── sessions/ # owns session state\n└── transport/ # sends API requests\n```\n\nComponent interaction, control flow, or data flow with Mermaid:\n\n```mermaid\nsequenceDiagram\n participant User\n participant UI\n participant Daemon\n User->>UI: choose command\n UI->>Daemon: send expanded prompt\n Daemon-->>UI: stream result\n```\n\n`diff` shapes — match the diff to the topic.\n\nComponent change:\n\n```diff\n <SessionPage>\n useSessionEvents()\n <SessionToolbar>\n+ <RunSkillButton />\n <SessionTimeline>\n+ <SkillResultCard />\n```\n\nFile-layout change:\n\n```diff\n src/\n ├── commands/\n+│ └── show-me.ts # expands the slash command\n ├── sessions/\n-└── transport.ts\n+└── transport/\n+ ├── client.ts\n+ └── stream.ts\n```\n\nCall-tree change:\n\n```diff\n submitForm\n createSession\n persistPrompt\n+ expandSkillMention\n launchAgent\n- navigateToSession\n+ navigateToSession\n+ subscribeToEvents\n```\n\nState or control-flow change:\n\n```diff\n on(save)\n- write content\n+ if content is unchanged\n+ return cached result\n+ write new content\n+ invalidate cache\n```\n\nWhole block when most of it is new or a copyable target shape is needed:\n\n```ts\nfunction expandSkill(command: string): string {\n const skillName = command.slice(1)\n return `use the ${skillName} skill`\n}\n```\n"
|
|
34
|
+
}
|
|
35
|
+
],
|
|
26
36
|
"delegate-work": [
|
|
27
37
|
{
|
|
28
38
|
"path": "scripts/codex-exec.sh",
|
|
@@ -50,6 +50,8 @@ import codeReviewingConfig from "../../../templates/skills/code-reviewing/config
|
|
|
50
50
|
import codeReviewingContent from "../../../templates/skills/code-reviewing/content.md" with {
|
|
51
51
|
type: "text",
|
|
52
52
|
};
|
|
53
|
+
import commsConfig from "../../../templates/skills/comms/config.json" with { type: "text" };
|
|
54
|
+
import commsContent from "../../../templates/skills/comms/content.md" with { type: "text" };
|
|
53
55
|
import composioConfig from "../../../templates/skills/composio/config.json" with { type: "text" };
|
|
54
56
|
import composioContent from "../../../templates/skills/composio/content.md" with { type: "text" };
|
|
55
57
|
import composioGmailConfig from "../../../templates/skills/composio-gmail/config.json" with {
|
|
@@ -318,6 +320,7 @@ const BUILT_IN_SKILL_SOURCES = [
|
|
|
318
320
|
{ config: brainstormingConfig, body: brainstormingContent },
|
|
319
321
|
{ config: codeQualityConfig, body: codeQualityContent },
|
|
320
322
|
{ config: codeReviewingConfig, body: codeReviewingContent },
|
|
323
|
+
{ config: commsConfig, body: commsContent },
|
|
321
324
|
{ config: composioConfig, body: composioContent },
|
|
322
325
|
{ config: composioGmailConfig, body: composioGmailContent },
|
|
323
326
|
{ config: composioGoogleCalendarConfig, body: composioGoogleCalendarContent },
|
package/src/be/steering.ts
CHANGED
|
@@ -75,6 +75,7 @@ export async function promoteSteeringToTask(
|
|
|
75
75
|
): Promise<AgentTask> {
|
|
76
76
|
return await createTaskExtended(message.body, {
|
|
77
77
|
agentId: task.agentId,
|
|
78
|
+
routingReason: task.agentId ? "continuity" : undefined,
|
|
78
79
|
creatorAgentId: message.createdByAgentId,
|
|
79
80
|
source: message.source === "script" ? "api" : message.source,
|
|
80
81
|
taskType: "follow-up",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { normalizeSlackReactionShortcode } from "../slack/reaction-shortcode";
|
|
2
2
|
import { ProviderNameSchema } from "../types";
|
|
3
|
+
import { parseTaskToolManifest } from "../utils/task-tool-manifest";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Guards against storing reserved keys in the swarm_config table.
|
|
@@ -163,6 +164,15 @@ function validateFloatRange(
|
|
|
163
164
|
}
|
|
164
165
|
|
|
165
166
|
const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
167
|
+
TASK_TOOL_MANIFESTS: (value) => {
|
|
168
|
+
try {
|
|
169
|
+
if (typeof value !== "string") throw new Error("Expected JSON string");
|
|
170
|
+
parseTaskToolManifest(value);
|
|
171
|
+
return null;
|
|
172
|
+
} catch {
|
|
173
|
+
return "Invalid TASK_TOOL_MANIFESTS (expected JSON with taskTypes and/or schedules maps, each selecting at most 16 known swarm tools)";
|
|
174
|
+
}
|
|
175
|
+
},
|
|
166
176
|
FEEDBACK_ENDPOINT: (value) => {
|
|
167
177
|
if (typeof value !== "string") {
|
|
168
178
|
return "Invalid FEEDBACK_ENDPOINT (must use HTTPS, or HTTP on a loopback host)";
|
|
@@ -252,6 +262,7 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
252
262
|
"HEARTBEAT_PIN_CRASH_RESUME",
|
|
253
263
|
"POOL_AFFINITY_ENFORCEMENT",
|
|
254
264
|
"SCRIPTS_ONLY_MCP",
|
|
265
|
+
"TASK_TOOL_PRELOAD_ENABLED",
|
|
255
266
|
"SLACK_DISABLE",
|
|
256
267
|
"SLACK_RENDER_V2",
|
|
257
268
|
"SLACK_RENDER_V2_DELEGATION",
|
package/src/github/handlers.ts
CHANGED
|
@@ -257,6 +257,7 @@ export async function handlePullRequest(
|
|
|
257
257
|
|
|
258
258
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
259
259
|
agentId: lead?.id ?? "",
|
|
260
|
+
routingReason: lead ? "skill" : undefined,
|
|
260
261
|
source: "github",
|
|
261
262
|
vcsProvider: "github",
|
|
262
263
|
taskType: "github-pr",
|
|
@@ -370,6 +371,7 @@ export async function handlePullRequest(
|
|
|
370
371
|
|
|
371
372
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
372
373
|
agentId: lead?.id ?? "",
|
|
374
|
+
routingReason: lead ? "skill" : undefined,
|
|
373
375
|
source: "github",
|
|
374
376
|
vcsProvider: "github",
|
|
375
377
|
taskType: "github-pr",
|
|
@@ -478,6 +480,7 @@ export async function handlePullRequest(
|
|
|
478
480
|
|
|
479
481
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
480
482
|
agentId: lead?.id ?? "",
|
|
483
|
+
routingReason: lead ? "skill" : undefined,
|
|
481
484
|
source: "github",
|
|
482
485
|
vcsProvider: "github",
|
|
483
486
|
taskType: "github-pr",
|
|
@@ -573,6 +576,7 @@ export async function handlePullRequest(
|
|
|
573
576
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
574
577
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
575
578
|
agentId: lead?.id ?? "",
|
|
579
|
+
routingReason: lead ? "skill" : undefined,
|
|
576
580
|
source: "github",
|
|
577
581
|
vcsProvider: "github",
|
|
578
582
|
taskType: "github-pr",
|
|
@@ -657,6 +661,7 @@ export async function handleIssue(
|
|
|
657
661
|
|
|
658
662
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
659
663
|
agentId: lead?.id ?? "",
|
|
664
|
+
routingReason: lead ? "skill" : undefined,
|
|
660
665
|
source: "github",
|
|
661
666
|
vcsProvider: "github",
|
|
662
667
|
taskType: "github-issue",
|
|
@@ -758,6 +763,7 @@ export async function handleIssue(
|
|
|
758
763
|
|
|
759
764
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
760
765
|
agentId: lead?.id ?? "",
|
|
766
|
+
routingReason: lead ? "skill" : undefined,
|
|
761
767
|
source: "github",
|
|
762
768
|
vcsProvider: "github",
|
|
763
769
|
taskType: "github-issue",
|
|
@@ -835,6 +841,7 @@ export async function handleIssue(
|
|
|
835
841
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
836
842
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
837
843
|
agentId: lead?.id ?? "",
|
|
844
|
+
routingReason: lead ? "skill" : undefined,
|
|
838
845
|
source: "github",
|
|
839
846
|
vcsProvider: "github",
|
|
840
847
|
taskType: "github-issue",
|
|
@@ -946,6 +953,7 @@ export async function handleComment(
|
|
|
946
953
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
947
954
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
948
955
|
agentId: lead?.id ?? "",
|
|
956
|
+
routingReason: lead ? "skill" : undefined,
|
|
949
957
|
source: "github",
|
|
950
958
|
vcsProvider: "github",
|
|
951
959
|
taskType: "github-comment",
|
|
@@ -1260,6 +1268,7 @@ export async function handlePullRequestReview(
|
|
|
1260
1268
|
// Create task (assigned to lead if available, otherwise unassigned)
|
|
1261
1269
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
1262
1270
|
agentId: lead?.id ?? "",
|
|
1271
|
+
routingReason: lead ? "skill" : undefined,
|
|
1263
1272
|
source: "github",
|
|
1264
1273
|
vcsProvider: "github",
|
|
1265
1274
|
taskType: "github-review",
|
package/src/gitlab/handlers.ts
CHANGED
|
@@ -175,6 +175,7 @@ export async function handleMergeRequest(
|
|
|
175
175
|
|
|
176
176
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
177
177
|
agentId: lead?.id ?? null,
|
|
178
|
+
routingReason: lead ? "skill" : undefined,
|
|
178
179
|
source: "gitlab",
|
|
179
180
|
vcsProvider: "gitlab",
|
|
180
181
|
taskType: "gitlab-mr",
|
|
@@ -282,6 +283,7 @@ export async function handleIssue(
|
|
|
282
283
|
|
|
283
284
|
const task = await createTaskWithSiblingAwareness(result.text, {
|
|
284
285
|
agentId: lead?.id ?? null,
|
|
286
|
+
routingReason: lead ? "skill" : undefined,
|
|
285
287
|
source: "gitlab",
|
|
286
288
|
vcsProvider: "gitlab",
|
|
287
289
|
taskType: "gitlab-issue",
|
|
@@ -385,6 +387,7 @@ export async function handleNote(event: NoteEvent): Promise<{ created: boolean;
|
|
|
385
387
|
|
|
386
388
|
const task = await createTaskWithSiblingAwareness(noteResult.text, {
|
|
387
389
|
agentId: lead?.id ?? null,
|
|
390
|
+
routingReason: lead ? "skill" : undefined,
|
|
388
391
|
source: "gitlab",
|
|
389
392
|
vcsProvider: "gitlab",
|
|
390
393
|
taskType: "gitlab-comment",
|
|
@@ -465,6 +468,7 @@ export async function handlePipeline(
|
|
|
465
468
|
|
|
466
469
|
const task = await createTaskWithSiblingAwareness(pipelineResult.text, {
|
|
467
470
|
agentId: lead?.id ?? null,
|
|
471
|
+
routingReason: lead ? "skill" : undefined,
|
|
468
472
|
source: "gitlab",
|
|
469
473
|
vcsProvider: "gitlab",
|
|
470
474
|
taskType: "gitlab-ci",
|
|
@@ -760,6 +760,12 @@ export async function runRebootSweep(): Promise<void> {
|
|
|
760
760
|
const retryTask = await createTaskExtended(task.task, {
|
|
761
761
|
parentTaskId: task.id,
|
|
762
762
|
agentId: preferredAgentId,
|
|
763
|
+
routingReason:
|
|
764
|
+
preferredAgentId === undefined
|
|
765
|
+
? undefined
|
|
766
|
+
: preferredAgentId === task.agentId
|
|
767
|
+
? "continuity"
|
|
768
|
+
: "reroute_fault",
|
|
763
769
|
tags,
|
|
764
770
|
priority: task.priority,
|
|
765
771
|
source: task.source,
|
|
@@ -1368,6 +1374,7 @@ export async function checkHeartbeatChecklist(): Promise<void> {
|
|
|
1368
1374
|
|
|
1369
1375
|
await createTaskExtended(result.text, {
|
|
1370
1376
|
agentId: lead.id,
|
|
1377
|
+
routingReason: "skill",
|
|
1371
1378
|
taskType: "heartbeat-checklist",
|
|
1372
1379
|
tags: ["checklist", "auto-generated"],
|
|
1373
1380
|
priority: 60,
|
|
@@ -1584,6 +1591,7 @@ export async function createBootTriageTask(): Promise<void> {
|
|
|
1584
1591
|
|
|
1585
1592
|
await createTaskExtended(result.text, {
|
|
1586
1593
|
agentId: lead.id,
|
|
1594
|
+
routingReason: "skill",
|
|
1587
1595
|
taskType: "boot-triage",
|
|
1588
1596
|
tags: ["boot", "triage", "auto-generated"],
|
|
1589
1597
|
priority: 70, // Higher than regular checklist (60)
|
|
@@ -385,6 +385,7 @@ export async function handleApprovalRequests(
|
|
|
385
385
|
|
|
386
386
|
await createTaskExtended(taskText, {
|
|
387
387
|
agentId: sourceTask.agentId,
|
|
388
|
+
routingReason: sourceTask.agentId ? "continuity" : undefined,
|
|
388
389
|
parentTaskId: updated.sourceTaskId,
|
|
389
390
|
source: "system",
|
|
390
391
|
taskType: "hitl-follow-up",
|
package/src/http/apps.ts
CHANGED
|
@@ -1413,6 +1413,7 @@ export async function handleApps(
|
|
|
1413
1413
|
const task = await createTaskWithSiblingAwareness(taskPrompt.text, {
|
|
1414
1414
|
source: "api",
|
|
1415
1415
|
agentId: action.agentId ?? lead?.id,
|
|
1416
|
+
routingReason: action.agentId ? "human_pinned" : lead ? "skill" : undefined,
|
|
1416
1417
|
// App-spawned tasks group under the app's asset namespace so a
|
|
1417
1418
|
// swarm-tasks source can pull them back via config.assetKey.
|
|
1418
1419
|
key: normalizeAssetKey(`shared/app:${app.id}/action:${parsed.params.name}/`),
|
package/src/http/config.ts
CHANGED
|
@@ -48,11 +48,19 @@ function singleHeader(req: IncomingMessage, name: string): string | undefined {
|
|
|
48
48
|
return Array.isArray(raw) ? raw[0] : raw;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
async function
|
|
51
|
+
async function requestMayReadSecrets(req: IncomingMessage): Promise<boolean> {
|
|
52
52
|
const auth = getRequestAuth(req);
|
|
53
53
|
if (auth?.kind === "operator") return true;
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
if (auth?.kind === "agent") {
|
|
55
|
+
const agent = await getAgentById(auth.agentId);
|
|
56
|
+
return can({
|
|
57
|
+
principal: { kind: "agent", agentId: auth.agentId, isLead: agent?.isLead ?? false },
|
|
58
|
+
verb: "config.read.secrets",
|
|
59
|
+
resource: { kind: "none" },
|
|
60
|
+
source: "http",
|
|
61
|
+
}).allow;
|
|
62
|
+
}
|
|
63
|
+
// Preserve legacy reads without a user or session-token principal.
|
|
56
64
|
if (auth?.kind !== "user") return true;
|
|
57
65
|
if (!isRbacEnabled()) return true;
|
|
58
66
|
|
|
@@ -61,7 +69,7 @@ async function userMayReadSecrets(req: IncomingMessage): Promise<boolean> {
|
|
|
61
69
|
}
|
|
62
70
|
|
|
63
71
|
async function resolveSecretsRead(req: IncomingMessage, includeSecrets: boolean) {
|
|
64
|
-
if (!includeSecrets || (await
|
|
72
|
+
if (!includeSecrets || (await requestMayReadSecrets(req))) {
|
|
65
73
|
return { effectiveIncludeSecrets: includeSecrets, secretsNote: "" };
|
|
66
74
|
}
|
|
67
75
|
return { effectiveIncludeSecrets: false, secretsNote: SECRETS_FORCE_MASK_NOTE };
|
|
@@ -75,7 +83,8 @@ async function resolveSecretsRead(req: IncomingMessage, includeSecrets: boolean)
|
|
|
75
83
|
* operator/user-before-agent ordering used by fs.ts (Appendix A row 36). This
|
|
76
84
|
* preserves every existing HTTP caller (dashboard, codex-oauth token refresh,
|
|
77
85
|
* devin playbook cache — all operate as operator). Only an agent-context
|
|
78
|
-
* principal
|
|
86
|
+
* principal is gated to lead, using the authenticated session identity ahead
|
|
87
|
+
* of the legacy X-Agent-ID fallback;
|
|
79
88
|
* the real per-agent enforcement lives on the MCP set-config/delete-config
|
|
80
89
|
* tools, where the principal is always an agent.
|
|
81
90
|
*
|
|
@@ -89,7 +98,7 @@ async function ensureConfigAdmin(
|
|
|
89
98
|
): Promise<boolean> {
|
|
90
99
|
const auth = getRequestAuth(req);
|
|
91
100
|
if (auth?.kind === "operator" || auth?.kind === "user") return true;
|
|
92
|
-
const agentId = singleHeader(req, "x-agent-id");
|
|
101
|
+
const agentId = auth?.kind === "agent" ? auth.agentId : singleHeader(req, "x-agent-id");
|
|
93
102
|
const agent = agentId ? await getAgentById(agentId) : undefined;
|
|
94
103
|
const decision = can({
|
|
95
104
|
principal: { kind: "agent", agentId: agentId ?? "", isLead: agent?.isLead ?? false },
|
package/src/http/mcp.ts
CHANGED
|
@@ -2,10 +2,12 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
3
3
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
4
4
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
-
import { getAgentById, getResolvedConfig } from "@/be/db";
|
|
5
|
+
import { getAgentById, getResolvedConfig, getTaskById } from "@/be/db";
|
|
6
6
|
import { createServer } from "@/server";
|
|
7
|
+
import { parseEnvFlag } from "@/utils/env-flag";
|
|
7
8
|
import { getRequestAuth } from "@/utils/request-auth-context";
|
|
8
9
|
import { resolveScriptsOnlyMode } from "@/utils/scripts-only-mode";
|
|
10
|
+
import { parseTaskToolManifest, selectTaskTools } from "@/utils/task-tool-manifest";
|
|
9
11
|
|
|
10
12
|
export type McpTransportActivity = Record<string, number>;
|
|
11
13
|
export type McpSessionAgents = Record<string, string>;
|
|
@@ -196,10 +198,35 @@ export async function handleMcp(
|
|
|
196
198
|
}
|
|
197
199
|
};
|
|
198
200
|
|
|
199
|
-
const
|
|
200
|
-
|
|
201
|
-
|
|
201
|
+
const configs = await getResolvedConfig(agentId);
|
|
202
|
+
const configValue = configs.find((config) => config.key === "SCRIPTS_ONLY_MCP")?.value;
|
|
203
|
+
const preloadEnabled = parseEnvFlag(
|
|
204
|
+
configs.find((config) => config.key === "TASK_TOOL_PRELOAD_ENABLED")?.value ??
|
|
205
|
+
process.env.TASK_TOOL_PRELOAD_ENABLED,
|
|
206
|
+
false,
|
|
207
|
+
);
|
|
208
|
+
let preloadedTools: string[] = [];
|
|
209
|
+
if (preloadEnabled) {
|
|
210
|
+
const taskId = headerValue(req.headers["x-source-task-id"]);
|
|
211
|
+
const task = taskId ? await getTaskById(taskId) : undefined;
|
|
212
|
+
// Never select a manifest using another agent's task. Session-token
|
|
213
|
+
// identity is also checked above, before any MCP session is created.
|
|
214
|
+
if (task?.agentId === agentId) {
|
|
215
|
+
const manifestValue =
|
|
216
|
+
configs.find((config) => config.key === "TASK_TOOL_MANIFESTS")?.value ??
|
|
217
|
+
process.env.TASK_TOOL_MANIFESTS ??
|
|
218
|
+
"{}";
|
|
219
|
+
try {
|
|
220
|
+
preloadedTools = selectTaskTools(parseTaskToolManifest(manifestValue), task);
|
|
221
|
+
} catch {
|
|
222
|
+
// An invalid deployment value must not prevent tool discovery.
|
|
223
|
+
// Avoid logging its contents, which may contain misfiled secrets.
|
|
224
|
+
console.warn("[MCP] Invalid TASK_TOOL_MANIFESTS; using ordinary tool discovery");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
202
228
|
const server = await createServer({
|
|
229
|
+
preloadedTools,
|
|
203
230
|
scriptsOnly: resolveScriptsOnlyMode({
|
|
204
231
|
env: process.env.SCRIPTS_ONLY_MCP,
|
|
205
232
|
configValue,
|
package/src/http/script-runs.ts
CHANGED
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
} from "../script-workflows/supervisor";
|
|
31
31
|
import {
|
|
32
32
|
AgentTaskStatusSchema,
|
|
33
|
+
RoutingReasonSchema,
|
|
33
34
|
ScriptRunJournalEntrySchema,
|
|
34
35
|
ScriptRunListItemSchema,
|
|
35
36
|
ScriptRunSchema,
|
|
@@ -92,6 +93,8 @@ const agentTaskBodySchema = z.object({
|
|
|
92
93
|
template: z.string().optional(),
|
|
93
94
|
task: z.string().optional(),
|
|
94
95
|
agentId: z.string().optional(),
|
|
96
|
+
routingReason: RoutingReasonSchema.optional(),
|
|
97
|
+
routingNote: z.string().max(200).optional(),
|
|
95
98
|
tags: z.array(z.string()).optional(),
|
|
96
99
|
priority: z.number().int().min(0).max(100).optional(),
|
|
97
100
|
offerMode: z.boolean().optional(),
|
|
@@ -619,6 +622,9 @@ export async function handleScriptRuns(
|
|
|
619
622
|
parsed.body.template ?? parsed.body.task ?? parsed.body.stepKey,
|
|
620
623
|
{
|
|
621
624
|
agentId: parsed.body.agentId,
|
|
625
|
+
routingReason:
|
|
626
|
+
parsed.body.routingReason ?? (parsed.body.agentId ? "human_pinned" : undefined),
|
|
627
|
+
routingNote: parsed.body.routingNote,
|
|
622
628
|
tags: parsed.body.tags,
|
|
623
629
|
priority: parsed.body.priority,
|
|
624
630
|
offeredTo: parsed.body.offerMode ? parsed.body.agentId : undefined,
|
package/src/http/tasks.ts
CHANGED
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
ProviderNameSchema,
|
|
60
60
|
ReasoningEffortSchema,
|
|
61
61
|
ResumeReasonSchema,
|
|
62
|
+
RoutingReasonSchema,
|
|
62
63
|
SteeringMessageSchema,
|
|
63
64
|
SteeringSourceSchema,
|
|
64
65
|
SteerModeSchema,
|
|
@@ -216,33 +217,45 @@ const createTask = route({
|
|
|
216
217
|
pattern: ["api", "tasks"],
|
|
217
218
|
summary: "Create a new task",
|
|
218
219
|
tags: ["Tasks"],
|
|
219
|
-
body: z
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
220
|
+
body: z
|
|
221
|
+
.object({
|
|
222
|
+
task: z.string().min(1),
|
|
223
|
+
agentId: z.string().optional(),
|
|
224
|
+
routingReason: RoutingReasonSchema.optional(),
|
|
225
|
+
routingNote: z.string().max(200).optional(),
|
|
226
|
+
taskType: z.string().optional(),
|
|
227
|
+
tags: z.array(z.string()).optional(),
|
|
228
|
+
priority: z.number().int().min(0).max(100).optional(),
|
|
229
|
+
dependsOn: z.array(z.string()).optional(),
|
|
230
|
+
offeredTo: z.string().optional(),
|
|
231
|
+
dir: z.string().optional(),
|
|
232
|
+
parentTaskId: z.string().optional(),
|
|
233
|
+
key: AssetKeySchema.optional(),
|
|
234
|
+
source: AgentTaskSourceSchema.optional(),
|
|
235
|
+
outputSchema: z.record(z.string(), z.unknown()).optional(),
|
|
236
|
+
contextKey: z.string().optional(),
|
|
237
|
+
requestedByUserId: z.string().optional(),
|
|
238
|
+
model: z.string().optional(),
|
|
239
|
+
modelTier: ModelTierSchema.optional(),
|
|
240
|
+
effort: ReasoningEffortSchema.optional(),
|
|
241
|
+
/**
|
|
242
|
+
* Create in `draft` status instead of the normal pending/unassigned/offered
|
|
243
|
+
* status (#1240) — the task exists and is visible to its owner, but is not
|
|
244
|
+
* dispatch-eligible. Used by the UI composer while attachments are still
|
|
245
|
+
* uploading; the caller MUST promote it via `POST /api/tasks/{id}/promote-draft`
|
|
246
|
+
* once the upload batch settles (or it self-promotes on a timeout).
|
|
247
|
+
*/
|
|
248
|
+
draft: z.boolean().optional(),
|
|
249
|
+
})
|
|
250
|
+
.superRefine((body, ctx) => {
|
|
251
|
+
if ((body.agentId !== undefined || body.offeredTo !== undefined) && !body.routingReason) {
|
|
252
|
+
ctx.addIssue({
|
|
253
|
+
code: z.ZodIssueCode.custom,
|
|
254
|
+
message: "routingReason is required when agentId or offeredTo is supplied.",
|
|
255
|
+
path: ["routingReason"],
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}),
|
|
246
259
|
responses: {
|
|
247
260
|
201: { description: "Task created", schema: AgentTaskSchema },
|
|
248
261
|
400: { description: "Validation error" },
|
|
@@ -804,11 +817,12 @@ export async function handleTasks(
|
|
|
804
817
|
if (lead) defaultAgentId = lead.id;
|
|
805
818
|
}
|
|
806
819
|
|
|
820
|
+
const parentTask = parsed.body.parentTaskId
|
|
821
|
+
? await getTaskById(parsed.body.parentTaskId)
|
|
822
|
+
: null;
|
|
807
823
|
let assetKey: string | undefined;
|
|
808
824
|
try {
|
|
809
|
-
const inheritedKey =
|
|
810
|
-
? (await getTaskById(parsed.body.parentTaskId))?.key
|
|
811
|
-
: undefined;
|
|
825
|
+
const inheritedKey = parentTask?.key;
|
|
812
826
|
const requestedKey = parsed.body.key ?? inheritedKey;
|
|
813
827
|
assetKey = requestedKey
|
|
814
828
|
? await authorizeAssetKeyWrite(requestedKey, trustedUserId)
|
|
@@ -825,6 +839,14 @@ export async function handleTasks(
|
|
|
825
839
|
const task = await createTaskWithSiblingAwareness(parsed.body.task, {
|
|
826
840
|
key: assetKey,
|
|
827
841
|
agentId: defaultAgentId,
|
|
842
|
+
routingReason:
|
|
843
|
+
parsed.body.routingReason ??
|
|
844
|
+
(defaultAgentId
|
|
845
|
+
? parentTask?.agentId === defaultAgentId
|
|
846
|
+
? "continuity"
|
|
847
|
+
: "skill"
|
|
848
|
+
: undefined),
|
|
849
|
+
routingNote: parsed.body.routingNote,
|
|
828
850
|
creatorAgentId: myAgentId || undefined,
|
|
829
851
|
taskType: parsed.body.taskType || undefined,
|
|
830
852
|
tags: parsed.body.tags || undefined,
|