@desplega.ai/agent-swarm 2.5.2 → 2.5.4
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/{acp-adapter-csh0zz3b.js → acp-adapter-ek38cwsv.js} +5 -5
- package/dist/{actions-6xx079d1.js → actions-92mepq03.js} +19 -19
- package/dist/{amp-adapter-8n6f0hz8.js → amp-adapter-9gaxg015.js} +4 -4
- package/dist/{app-ft6r6v23.js → app-ctn8gsgx.js} +3 -3
- package/dist/{assistant-vt2cyfzd.js → assistant-zyr5kz47.js} +21 -21
- package/dist/{boot-reembed-tgrfy27t.js → boot-reembed-dwzfttdr.js} +1 -1
- package/dist/{boot-reembed-r3e808ky.js → boot-reembed-w75maga2.js} +1 -1
- package/dist/{boot-scrub-sweep-masdwxkm.js → boot-scrub-sweep-e9kq43kf.js} +3 -3
- package/dist/{boot-seeds-0n82qc7q.js → boot-seeds-x28v3fhb.js} +4 -4
- package/dist/{claude-adapter-s7j47k06.js → claude-adapter-f0afqnne.js} +5 -5
- package/dist/{claude-managed-adapter-gt6pa35d.js → claude-managed-adapter-x81a7r65.js} +3 -3
- package/dist/{claude-managed-setup-3hzrgrnk.js → claude-managed-setup-bqzwjd9x.js} +2 -2
- package/dist/{claude-sdk-session-0zrdzyx2.js → claude-sdk-session-7b7whkaq.js} +9307 -9254
- package/dist/{cli-a5ebc95n.js → cli-0shx9j93.js} +16 -13
- package/dist/{cli-4qkg9968.js → cli-19202kew.js} +4 -4
- package/dist/{cli-cyfgxnvb.js → cli-1r5ad7j5.js} +19 -17
- package/dist/{cli-jmcjbjkt.js → cli-25n3mv5d.js} +9 -7
- package/dist/cli-2655k16q.js +140 -0
- package/dist/{cli-wadzapwb.js → cli-277c31dw.js} +1 -1
- package/dist/{cli-n6kmqngw.js → cli-4b2z6n0m.js} +29 -7
- package/dist/{cli-7ccf7akh.js → cli-5bnsc0e1.js} +9 -7
- package/dist/{cli-qrjv7t18.js → cli-621ce7kw.js} +1 -1
- package/dist/{cli-xzge46sj.js → cli-64mh9mf3.js} +20 -6
- package/dist/{cli-b6tnjdvx.js → cli-6a1xg1p7.js} +6 -2
- package/dist/{cli-ydg6ep51.js → cli-6n8axjv1.js} +1 -1
- package/dist/{cli-e91vshb1.js → cli-7p4jxgtt.js} +5 -5
- package/dist/{cli-f3pfn4x3.js → cli-9j129brq.js} +9 -3
- package/dist/{cli-xw9q173h.js → cli-9s05422w.js} +1 -1
- package/dist/{cli-15xd0j7w.js → cli-abs8ndtr.js} +12 -12
- package/dist/{cli-h5d22pfs.js → cli-asq5ydm4.js} +219 -309
- package/dist/{cli-w9etnnxk.js → cli-bf8ma3f1.js} +120 -50
- package/dist/{cli-0vgy5wfw.js → cli-bvpgr9ny.js} +2 -2
- package/dist/{cli-3e9vs93g.js → cli-c61mbdds.js} +4 -4
- package/dist/{cli-e5bxnjps.js → cli-c952c7ac.js} +5 -5
- package/dist/{cli-p859hcc8.js → cli-dew6x255.js} +1 -1
- package/dist/{cli-tx00c0h6.js → cli-dqygeeh5.js} +17 -1
- package/dist/{cli-gpv60278.js → cli-e42vss7b.js} +2 -2
- package/dist/{cli-b63d08rs.js → cli-eftzyh9y.js} +1 -1
- package/dist/{cli-fgn7yg5r.js → cli-ethgy7ha.js} +1 -1
- package/dist/{cli-86zpfje5.js → cli-ff1tm9n9.js} +1 -1
- package/dist/{cli-cf63dnxj.js → cli-g9vd9ece.js} +37 -12
- package/dist/{cli-j3dwd4kz.js → cli-gbfxp48k.js} +770 -126
- package/dist/{cli-n2918sed.js → cli-gn5af0x5.js} +21 -21
- package/dist/{cli-d172svcw.js → cli-gwhjmh8g.js} +4 -4
- package/dist/{cli-stxdrqs2.js → cli-hh5we352.js} +24 -5
- package/dist/{cli-0ebey9hg.js → cli-j6rktc91.js} +1 -1
- package/dist/{cli-csh8fnbg.js → cli-jb30dzvk.js} +473 -58
- package/dist/{cli-vaxw3bg0.js → cli-jbnn4vxb.js} +3 -3
- package/dist/{cli-ha22h0eg.js → cli-k4tjsdx9.js} +45 -40
- package/dist/{cli-f55b6ky0.js → cli-m4ecdxvz.js} +40 -13
- package/dist/{cli-mntxtkhn.js → cli-mz9afxxf.js} +2 -1
- package/dist/{cli-ta8bd7th.js → cli-pzatvw94.js} +1 -1
- package/dist/{cli-3qv8naq8.js → cli-rr7zx19j.js} +1591 -353
- package/dist/{cli-b1tv22wr.js → cli-s682zgqz.js} +1 -1
- package/dist/{cli-bpbcpd8b.js → cli-sbwsxvtd.js} +1 -1
- package/dist/{cli-0xk505e8.js → cli-sxgtf5fw.js} +1 -1
- package/dist/{cli-6y43v79h.js → cli-tsma7p8v.js} +1 -1
- package/dist/{cli-edtm741g.js → cli-wghjkr0e.js} +5 -5
- package/dist/{cli-aaqnqpyw.js → cli-wx2nj19m.js} +30 -11
- package/dist/{cli-y3dzwyy3.js → cli-x0f2v69c.js} +180 -60
- package/dist/{cli-kcnwn85g.js → cli-xtrz6bzs.js} +7 -7
- package/dist/{cli-14v1sxxy.js → cli-xzfq4at3.js} +7 -7
- package/dist/cli.js +20 -20
- package/dist/{codex-adapter-brh2tcxr.js → codex-adapter-wch12q99.js} +3 -3
- package/dist/{codex-hook-e2he7aca.js → codex-hook-brpnycn5.js} +1 -1
- package/dist/{codex-session-runner-0dnj21qs.js → codex-session-runner-41pfcr95.js} +2 -2
- package/dist/{commands-1cnvseqx.js → commands-9849dkj6.js} +2 -2
- package/dist/{controller-kc7s9xhn.js → controller-ja0sd95p.js} +29 -3
- package/dist/{controllers-connect-jzx7t4bn.js → controllers-connect-xp05dj6x.js} +20 -20
- package/dist/{cursor-adapter-qpwp5sfg.js → cursor-adapter-623qxqew.js} +1 -2
- package/dist/{db-4vq88jkc.js → db-47vj40dy.js} +3 -3
- package/dist/{db-v5ytg5pw.js → db-w759qmx0.js} +2 -2
- package/dist/{dispatcher-trtcrqae.js → dispatcher-7b8e4z2j.js} +17 -17
- package/dist/{docker-nfy2gc8t.js → docker-s84hjs91.js} +2 -2
- package/dist/{dsh-adapter-f20xwn1z.js → dsh-adapter-2ezw3xea.js} +2 -3
- package/dist/{external-channel-n9bymc2n.js → external-channel-e2s028da.js} +2 -2
- package/dist/{grok-adapter-4kpmmnr6.js → grok-adapter-ys7gaqee.js} +6 -6
- package/dist/{handlers-ptsraqa8.js → handlers-wy1dr6ja.js} +20 -20
- package/dist/{heartbeat-60qtxtjf.js → heartbeat-w3mntghj.js} +20 -20
- package/dist/{hook-rbktcstg.js → hook-3jjywnpb.js} +9 -3
- package/dist/{hook-ngtvh729.js → hook-g7kvv6x9.js} +3 -2
- package/dist/{http-z8xy72gp.js → http-tkp7d4kb.js} +2984 -2483
- package/dist/{index-r70q0xjx.js → index-1ef1myq2.js} +17 -17
- package/dist/{index-a9nsavhx.js → index-3n8m9ea2.js} +1 -1
- package/dist/{index-cbxkmfc5.js → index-9pn1pfkf.js} +1 -1
- package/dist/{index-s193j5fh.js → index-nmm79qgr.js} +20 -20
- package/dist/{index-6srwvd5b.js → index-p4xfz37z.js} +1 -1
- package/dist/{index-06qkmcx6.js → index-q7m26kvs.js} +33 -23
- package/dist/{index-1cscynkb.js → index-xdaahthb.js} +17 -17
- package/dist/{keepalive-a9srcphj.js → keepalive-v4ta8wgg.js} +3 -3
- package/dist/{lead-3devneqy.js → lead-sawnyqxk.js} +35 -35
- package/dist/{maintenance-0er16h2y.js → maintenance-84n1ex21.js} +1 -1
- package/dist/{oauth-refresh-sweep-b65jcyw2.js → oauth-refresh-sweep-vjnnandw.js} +2 -2
- package/dist/{onboard-wqywz5py.js → onboard-sfmpe4np.js} +2 -2
- package/dist/{opencode-adapter-e56086m1.js → opencode-adapter-jmpst4q4.js} +4 -4
- package/dist/{otel-impl-vk32s884.js → otel-impl-v6cc0zve.js} +1 -1
- package/dist/{pi-mono-adapter-tzps8f38.js → pi-mono-adapter-fk8fkcf6.js} +33 -6
- package/dist/{pricing-refresh-t27anv4z.js → pricing-refresh-fsk1e1zc.js} +3 -3
- package/dist/{rbac-roles-452665y0.js → rbac-roles-prmkdnkw.js} +2 -2
- package/dist/{rbac-roles-bmcgsxjn.js → rbac-roles-zxmxqa39.js} +1 -1
- package/dist/{render-v2-0ttsk2j4.js → render-v2-pjwxvnkw.js} +3 -3
- package/dist/{resume-a26kvr41.js → resume-frdtgfa0.js} +9 -5
- package/dist/{session-access-dgh9nkwk.js → session-access-fpez4y5q.js} +4 -4
- package/dist/{setup-9vc9qahm.js → setup-mq6m3m8q.js} +1 -1
- package/dist/{work-objects-vn8nkxwn.js → work-objects-kfahhjsq.js} +20 -20
- package/dist/{worker-1x1h8vnm.js → worker-v5e5rxj3.js} +35 -35
- package/openapi.json +3078 -2424
- package/package.json +7 -7
- package/plugin/commands/create-pr.md +2 -2
- package/plugin/pi-skills/create-pr/SKILL.md +2 -2
- package/src/be/approval-config-question.ts +389 -0
- package/src/be/automation-preflight.ts +37 -3
- package/src/be/db/approvals.ts +48 -0
- package/src/be/db/session-tree.ts +73 -15
- package/src/be/db/skill-search.ts +87 -0
- package/src/be/db/tasks/read.ts +29 -2
- package/src/be/db/tasks/write.ts +15 -5
- package/src/be/db/workflow-idempotency.ts +183 -0
- package/src/be/db/workflow-runs.ts +366 -8
- package/src/be/db.ts +92 -22
- package/src/be/migrations/239_skills_fts.sql +40 -0
- package/src/be/migrations/240_workflow_run_parent_step_attempt.sql +37 -0
- package/src/be/migrations/241_skills_meta_fts.sql +39 -0
- package/src/be/migrations/242_workflow_idempotency_keys.sql +40 -0
- package/src/be/sealed-json.ts +1 -0
- package/src/be/seed-scripts/catalog/compound-insights.inline.ts +2 -2
- package/src/be/seed-scripts/catalog/compound-insights.ts +2 -2
- package/src/be/seed-skills/bundled-files.generated.json +1 -1
- package/src/be/sql-connections.ts +7 -1
- package/src/be/swarm-config-guard.ts +25 -0
- package/src/be/users.ts +9 -0
- package/src/commands/controller.ts +33 -0
- package/src/commands/runner.ts +6 -2
- package/src/extensions/apply-task-create.ts +57 -11
- package/src/extensions/catalog.generated.json +4 -4
- package/src/extensions/contract-types.generated.ts +1 -1
- package/src/extensions/contract.ts +25 -0
- package/src/extensions/dispatcher.ts +4 -0
- package/src/extensions/post-bridge.ts +8 -0
- package/src/hooks/context-bound-note.ts +196 -0
- package/src/hooks/hook.ts +12 -0
- package/src/http/approval-requests.ts +209 -38
- package/src/http/config.ts +70 -53
- package/src/http/idempotency-key.ts +117 -0
- package/src/http/sessions.ts +27 -2
- package/src/http/workflows.ts +129 -24
- package/src/integrations/chat-sdk/inbound.ts +5 -4
- package/src/prompts/context-bound-note.ts +48 -0
- package/src/prompts/session-templates.ts +25 -1
- package/src/providers/amp-adapter.ts +30 -8
- package/src/providers/claude-adapter.ts +29 -4
- package/src/providers/claude-managed-adapter.ts +26 -0
- package/src/providers/claude-sdk-session.ts +10 -0
- package/src/providers/claude-session-events.ts +13 -0
- package/src/providers/codex-adapter.ts +12 -0
- package/src/providers/dsh-adapter.ts +1 -1
- package/src/providers/opencode-adapter.ts +18 -0
- package/src/providers/pi-mono-adapter.ts +35 -0
- package/src/runtime-providers/docker.ts +6 -2
- package/src/scripts-runtime/types/swarm-extension.d.ts +35 -0
- package/src/tests/amp-adapter.test.ts +97 -0
- package/src/tests/approval-config-question.test.ts +702 -0
- package/src/tests/approval-requests.test.ts +10 -7
- package/src/tests/approval-sweeps.test.ts +12 -6
- package/src/tests/base-prompt.test.ts +1 -1
- package/src/tests/chat-sdk-inbound.test.ts +20 -0
- package/src/tests/claude-hook-output.test.ts +79 -1
- package/src/tests/claude-managed-adapter.test.ts +275 -0
- package/src/tests/codex-adapter.test.ts +163 -0
- package/src/tests/context-bound-note.test.ts +269 -0
- package/src/tests/dsh-adapter.test.ts +1 -1
- package/src/tests/entrypoint-gitlab-credential-helper.test.ts +273 -0
- package/src/tests/extensions-example-comms-adherence-gate.test.ts +321 -12
- package/src/tests/extensions-pre-task.test.ts +273 -0
- package/src/tests/fetch-resolved-env.test.ts +17 -0
- package/src/tests/fixtures/extensions/record-blocks/hooks.ts +10 -0
- package/src/tests/fixtures/extensions/record-blocks/manifest.json +7 -0
- package/src/tests/fixtures/fake-amp.ts +22 -8
- package/src/tests/fixtures/held-template-render.ts +42 -0
- package/src/tests/fixtures/workflow-idempotency-trigger.child.ts +73 -0
- package/src/tests/list-endpoint-slimming.test.ts +33 -0
- package/src/tests/migration-240-workflow-run-parent-step-attempt.test.ts +55 -0
- package/src/tests/openapi-route-headers.test.ts +2 -0
- package/src/tests/opencode-adapter.test.ts +134 -0
- package/src/tests/pi-mono-adapter.test.ts +105 -0
- package/src/tests/prompt-template-session.test.ts +4 -3
- package/src/tests/runner-push-delivery.test.ts +25 -0
- package/src/tests/runtime-controller-liveness.test.ts +133 -0
- package/src/tests/runtime-providers-docker.test.ts +2 -0
- package/src/tests/script-connections.test.ts +9 -0
- package/src/tests/script-run-config-scope.test.ts +91 -3
- package/src/tests/session-logs.test.ts +70 -0
- package/src/tests/sessions-tree.test.ts +205 -1
- package/src/tests/skill-search-long-content.test.ts +193 -0
- package/src/tests/skill-search.test.ts +173 -22
- package/src/tests/workflow-cascade-cancel.test.ts +1169 -0
- package/src/tests/workflow-foreach-supersede-retry.test.ts +488 -0
- package/src/tests/workflow-idempotency.test.ts +915 -0
- package/src/tests/workflow-sub-workflow.test.ts +538 -1
- package/src/tools/request-human-input.ts +27 -5
- package/src/tools/workflows/cancel-workflow-run.ts +1 -1
- package/src/types.ts +7 -0
- package/src/utils/context-window.ts +8 -6
- package/src/workflows/approval-cancel.ts +15 -13
- package/src/workflows/approval-notifications.ts +15 -4
- package/src/workflows/checkpoint.ts +72 -35
- package/src/workflows/engine.ts +262 -93
- package/src/workflows/executors/base.ts +6 -0
- package/src/workflows/executors/foreach.ts +2 -2
- package/src/workflows/executors/human-in-the-loop.ts +25 -1
- package/src/workflows/executors/sub-workflow.ts +45 -16
- package/src/workflows/foreach-driver.ts +10 -3
- package/src/workflows/foreach-join.ts +29 -6
- package/src/workflows/recovery.ts +68 -1
- package/src/workflows/resume.ts +228 -77
- package/src/workflows/retry-poller.ts +74 -7
- package/src/workflows/task-step-routing.ts +29 -7
- package/templates/ai-toolbox.manifest.json +8 -8
- package/templates/community/open-prompt-showreel/PLAYBOOK.md +5 -6
- package/templates/community/open-prompt-showreel/lead-prompt.md +1 -1
- package/templates/community/open-prompt-showreel/skills/open-prompt-showreel/SKILL.md +4 -4
- package/templates/extensions/comms-adherence-gate/README.md +38 -13
- package/templates/extensions/comms-adherence-gate/hooks.ts +272 -69
- package/templates/extensions/comms-adherence-gate/manifest.json +2 -2
- package/templates/skills/agent-browser/SKILL.md +4 -4
- package/templates/skills/agent-browser/content.md +4 -4
- package/templates/skills/code-quality/config.json +1 -1
- package/templates/skills/code-quality/content.md +2 -2
- package/templates/skills/delegate-work/content.md +29 -25
- package/templates/skills/delegate-work/files/scripts/codex-exec.sh +5 -5
- package/templates/skills/qa/content.md +2 -2
- package/templates/skills/researching/content.md +2 -0
- package/templates/skills/turso-interaction/content.md +1 -1
- package/templates/skills/work-on-task/SKILL.md +1 -1
- package/templates/skills/work-on-task/content.md +1 -1
- package/templates/skills/workflow-iterate/content.md +8 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- Full-text index for ranked skill search (searchSkills / skill-search tool).
|
|
2
|
+
-- Same shape as memory_fts: an id column FTS5 does not index, the searchable
|
|
3
|
+
-- text columns, and the porter tokenizer. Column order matters: searchSkills
|
|
4
|
+
-- passes bm25() weights positionally (skill_id, name, description, content).
|
|
5
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS skills_fts USING fts5(
|
|
6
|
+
skill_id UNINDEXED,
|
|
7
|
+
name,
|
|
8
|
+
description,
|
|
9
|
+
content,
|
|
10
|
+
tokenize='porter unicode61'
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
INSERT INTO skills_fts(skill_id, name, description, content)
|
|
14
|
+
SELECT id, name, description, content FROM skills
|
|
15
|
+
WHERE id NOT IN (SELECT skill_id FROM skills_fts WHERE skill_id IS NOT NULL);
|
|
16
|
+
|
|
17
|
+
-- Triggers keep the index in sync on every write path, including FK cascade
|
|
18
|
+
-- deletes. A later migration that rebuilds `skills` (CREATE new + DROP +
|
|
19
|
+
-- RENAME) drops these triggers with the old table and must recreate them;
|
|
20
|
+
-- src/tests/skill-search.test.ts asserts they exist on the migrated schema.
|
|
21
|
+
CREATE TRIGGER IF NOT EXISTS skills_fts_insert
|
|
22
|
+
AFTER INSERT ON skills
|
|
23
|
+
BEGIN
|
|
24
|
+
INSERT INTO skills_fts(skill_id, name, description, content)
|
|
25
|
+
VALUES (NEW.id, NEW.name, NEW.description, NEW.content);
|
|
26
|
+
END;
|
|
27
|
+
|
|
28
|
+
CREATE TRIGGER IF NOT EXISTS skills_fts_update
|
|
29
|
+
AFTER UPDATE OF id, name, description, content ON skills
|
|
30
|
+
BEGIN
|
|
31
|
+
DELETE FROM skills_fts WHERE skill_id = OLD.id;
|
|
32
|
+
INSERT INTO skills_fts(skill_id, name, description, content)
|
|
33
|
+
VALUES (NEW.id, NEW.name, NEW.description, NEW.content);
|
|
34
|
+
END;
|
|
35
|
+
|
|
36
|
+
CREATE TRIGGER IF NOT EXISTS skills_fts_delete
|
|
37
|
+
AFTER DELETE ON skills
|
|
38
|
+
BEGIN
|
|
39
|
+
DELETE FROM skills_fts WHERE skill_id = OLD.id;
|
|
40
|
+
END;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
-- Key a child workflow run by its parent step AND that step's attempt.
|
|
2
|
+
--
|
|
3
|
+
-- The retry poller re-executes a failed step under the same step id, with
|
|
4
|
+
-- `retryCount` bumped. Keyed by step id alone, the retried `sub-workflow`
|
|
5
|
+
-- step reconnected to the failed child of the previous attempt and failed
|
|
6
|
+
-- again without starting a new one.
|
|
7
|
+
|
|
8
|
+
ALTER TABLE workflow_runs ADD COLUMN parentStepAttempt INTEGER NOT NULL DEFAULT 0;
|
|
9
|
+
|
|
10
|
+
-- Before this migration a step had at most one child, created on whichever
|
|
11
|
+
-- attempt first got that far (a retry after a synchronous failure, such as a
|
|
12
|
+
-- disabled child workflow, creates it on attempt 1 or later), and every later
|
|
13
|
+
-- attempt reconnected to it. Hand that child to the attempt that settles or
|
|
14
|
+
-- re-executes it now, or recovery would look up the step's current attempt,
|
|
15
|
+
-- find no child and fail the parent as "deleted":
|
|
16
|
+
-- - a step queued for a retry (`failed` with `nextRetryAt`) starts its next
|
|
17
|
+
-- attempt at `retryCount`, which must start a new child, so the existing
|
|
18
|
+
-- child belongs to the previous attempt;
|
|
19
|
+
-- - any other step (running, waiting, terminal) owns it at `retryCount`.
|
|
20
|
+
-- A child whose step is gone keeps attempt 0.
|
|
21
|
+
UPDATE workflow_runs
|
|
22
|
+
SET parentStepAttempt = (
|
|
23
|
+
SELECT CASE
|
|
24
|
+
WHEN s.status = 'failed' AND s.nextRetryAt IS NOT NULL
|
|
25
|
+
THEN MAX(s.retryCount - 1, 0)
|
|
26
|
+
ELSE s.retryCount
|
|
27
|
+
END
|
|
28
|
+
FROM workflow_run_steps s
|
|
29
|
+
WHERE s.id = workflow_runs.parentStepId
|
|
30
|
+
)
|
|
31
|
+
WHERE parentStepId IS NOT NULL
|
|
32
|
+
AND EXISTS (SELECT 1 FROM workflow_run_steps s WHERE s.id = workflow_runs.parentStepId);
|
|
33
|
+
|
|
34
|
+
DROP INDEX IF EXISTS idx_workflow_runs_parentStepId;
|
|
35
|
+
|
|
36
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workflow_runs_parentStep_attempt
|
|
37
|
+
ON workflow_runs(parentStepId, parentStepAttempt) WHERE parentStepId IS NOT NULL;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
-- Name + description index for ranked skill search. skills_fts (239) scores
|
|
2
|
+
-- the whole row, and FTS5 bm25 length-normalizes over every column, so a
|
|
3
|
+
-- long SKILL.md body buries a name or description hit. This index holds only
|
|
4
|
+
-- the short fields; searchSkillRowsRanked adds its score to skills_fts's.
|
|
5
|
+
-- Column order matters: bm25() weights are positional (skill_id, name,
|
|
6
|
+
-- description).
|
|
7
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS skills_meta_fts USING fts5(
|
|
8
|
+
skill_id UNINDEXED,
|
|
9
|
+
name,
|
|
10
|
+
description,
|
|
11
|
+
tokenize='porter unicode61'
|
|
12
|
+
);
|
|
13
|
+
|
|
14
|
+
INSERT INTO skills_meta_fts(skill_id, name, description)
|
|
15
|
+
SELECT id, name, description FROM skills
|
|
16
|
+
WHERE id NOT IN (SELECT skill_id FROM skills_meta_fts WHERE skill_id IS NOT NULL);
|
|
17
|
+
|
|
18
|
+
-- Same sync rules as skills_fts: a later migration that rebuilds `skills`
|
|
19
|
+
-- must recreate these triggers; src/tests/skill-search.test.ts asserts them.
|
|
20
|
+
CREATE TRIGGER IF NOT EXISTS skills_meta_fts_insert
|
|
21
|
+
AFTER INSERT ON skills
|
|
22
|
+
BEGIN
|
|
23
|
+
INSERT INTO skills_meta_fts(skill_id, name, description)
|
|
24
|
+
VALUES (NEW.id, NEW.name, NEW.description);
|
|
25
|
+
END;
|
|
26
|
+
|
|
27
|
+
CREATE TRIGGER IF NOT EXISTS skills_meta_fts_update
|
|
28
|
+
AFTER UPDATE OF id, name, description ON skills
|
|
29
|
+
BEGIN
|
|
30
|
+
DELETE FROM skills_meta_fts WHERE skill_id = OLD.id;
|
|
31
|
+
INSERT INTO skills_meta_fts(skill_id, name, description)
|
|
32
|
+
VALUES (NEW.id, NEW.name, NEW.description);
|
|
33
|
+
END;
|
|
34
|
+
|
|
35
|
+
CREATE TRIGGER IF NOT EXISTS skills_meta_fts_delete
|
|
36
|
+
AFTER DELETE ON skills
|
|
37
|
+
BEGIN
|
|
38
|
+
DELETE FROM skills_meta_fts WHERE skill_id = OLD.id;
|
|
39
|
+
END;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
-- Idempotency-Key records for POST /api/workflows and POST /api/workflows/{id}/trigger.
|
|
2
|
+
--
|
|
3
|
+
-- One row is one operation the server accepted under a client key. It commits
|
|
4
|
+
-- in the same transaction as the workflow (create) or the run and its initial
|
|
5
|
+
-- state (trigger), so a retry after a lost response, a restart or a concurrent
|
|
6
|
+
-- duplicate finds it and gets the original result instead of a second
|
|
7
|
+
-- workflow or run.
|
|
8
|
+
--
|
|
9
|
+
-- The scope is (principal, operation, targetId, keyHash): the caller's stable
|
|
10
|
+
-- identity (never the bearer value), the operation, the triggered workflow id
|
|
11
|
+
-- ('' for create) and a SHA-256 of the client key. requestFingerprint is a
|
|
12
|
+
-- SHA-256 of the canonical request; a different request under the same key is
|
|
13
|
+
-- a conflict. responseBody is the original response, sealed (sealJson). When
|
|
14
|
+
-- the workflow is deleted, responseBody is cleared and resourceDeletedAt set:
|
|
15
|
+
-- the key stays taken and is never accepted again.
|
|
16
|
+
--
|
|
17
|
+
-- No FK to workflows: a row outlives the workflow as that tombstone.
|
|
18
|
+
|
|
19
|
+
CREATE TABLE IF NOT EXISTS workflow_idempotency_keys (
|
|
20
|
+
id TEXT PRIMARY KEY,
|
|
21
|
+
principal TEXT NOT NULL,
|
|
22
|
+
operation TEXT NOT NULL CHECK (operation IN ('workflow.create', 'workflow.trigger')),
|
|
23
|
+
targetId TEXT NOT NULL DEFAULT '',
|
|
24
|
+
keyHash TEXT NOT NULL,
|
|
25
|
+
requestFingerprint TEXT NOT NULL,
|
|
26
|
+
workflowId TEXT NOT NULL,
|
|
27
|
+
runId TEXT,
|
|
28
|
+
responseStatus INTEGER NOT NULL,
|
|
29
|
+
responseBody TEXT,
|
|
30
|
+
resourceDeletedAt TEXT,
|
|
31
|
+
createdAt TEXT NOT NULL,
|
|
32
|
+
created_by TEXT,
|
|
33
|
+
updated_by TEXT
|
|
34
|
+
);
|
|
35
|
+
|
|
36
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_workflow_idempotency_keys_scope
|
|
37
|
+
ON workflow_idempotency_keys(principal, operation, targetId, keyHash);
|
|
38
|
+
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_idempotency_keys_workflowId
|
|
40
|
+
ON workflow_idempotency_keys(workflowId);
|
package/src/be/sealed-json.ts
CHANGED
|
@@ -30,6 +30,7 @@ export const SEALED_REPLAY_COLUMNS: readonly { table: string; column: string }[]
|
|
|
30
30
|
{ table: "workflow_run_steps", column: "output_replay" },
|
|
31
31
|
{ table: "workflow_runs", column: "context_replay" },
|
|
32
32
|
{ table: "approval_requests", column: "responses" },
|
|
33
|
+
{ table: "workflow_idempotency_keys", column: "responseBody" },
|
|
33
34
|
];
|
|
34
35
|
|
|
35
36
|
/** SQL `EXISTS` arms matching a sealed value in each `SEALED_REPLAY_COLUMNS` entry. */
|
|
@@ -574,14 +574,14 @@ export default async function compoundInsights(args: any, ctx: any) {
|
|
|
574
574
|
const rows = rowsToObjects(
|
|
575
575
|
await ctx.swarm.db_query({
|
|
576
576
|
sql: `WITH raw AS (
|
|
577
|
-
SELECT sessionId, iteration, lineNumber, content,
|
|
577
|
+
SELECT rowid AS seq, sessionId, iteration, lineNumber, content,
|
|
578
578
|
json_extract(content, '$.tool_name') as jsonToolName
|
|
579
579
|
FROM session_logs
|
|
580
580
|
WHERE createdAt > ${w}
|
|
581
581
|
AND (content LIKE '%"type":"tool_use"%' OR json_extract(content, '$.tool_name') IS NOT NULL)
|
|
582
582
|
)
|
|
583
583
|
SELECT sessionId, iteration, lineNumber, jsonToolName, content
|
|
584
|
-
FROM raw ORDER BY sessionId, iteration,
|
|
584
|
+
FROM raw ORDER BY sessionId, iteration, seq LIMIT 100`,
|
|
585
585
|
}),
|
|
586
586
|
);
|
|
587
587
|
const bySession = new Map<string, string[]>();
|
|
@@ -574,14 +574,14 @@ export default async function compoundInsights(args: any, ctx: any) {
|
|
|
574
574
|
const rows = rowsToObjects(
|
|
575
575
|
await ctx.swarm.db_query({
|
|
576
576
|
sql: `WITH raw AS (
|
|
577
|
-
SELECT sessionId, iteration, lineNumber, content,
|
|
577
|
+
SELECT rowid AS seq, sessionId, iteration, lineNumber, content,
|
|
578
578
|
json_extract(content, '$.tool_name') as jsonToolName
|
|
579
579
|
FROM session_logs
|
|
580
580
|
WHERE createdAt > ${w}
|
|
581
581
|
AND (content LIKE '%"type":"tool_use"%' OR json_extract(content, '$.tool_name') IS NOT NULL)
|
|
582
582
|
)
|
|
583
583
|
SELECT sessionId, iteration, lineNumber, jsonToolName, content
|
|
584
|
-
FROM raw ORDER BY sessionId, iteration,
|
|
584
|
+
FROM raw ORDER BY sessionId, iteration, seq LIMIT 100`,
|
|
585
585
|
}),
|
|
586
586
|
);
|
|
587
587
|
const bySession = new Map<string, string[]>();
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"delegate-work": [
|
|
19
19
|
{
|
|
20
20
|
"path": "scripts/codex-exec.sh",
|
|
21
|
-
"content": "#!/usr/bin/env bash\n#\n# codex-exec.sh — invoke `codex exec` (non-interactive) for delegated\n# implementation work. This is the SINGLE SOURCE OF TRUTH for the invocation\n# flags. The delegate-work skill calls only this script; its routing matrix\n# decides WHICH gpt-
|
|
21
|
+
"content": "#!/usr/bin/env bash\n#\n# codex-exec.sh — invoke `codex exec` (non-interactive) for delegated\n# implementation work. This is the SINGLE SOURCE OF TRUTH for the invocation\n# flags. The delegate-work skill calls only this script; its routing matrix\n# decides WHICH gpt-6 variant (-m) and effort (-e) each task gets:\n# gpt-6-luna = trivial/mechanical gpt-6.1-sol = default (medium) to hard (high/xhigh)\n# gpt-6-astra = escalation only effort: medium|high|xhigh|max (never ultra)\n#\n# Usage:\n# codex-exec.sh [-m <model>] [-e <effort>] -C <workdir> -o <last-message-file> [-l <log-file>] \"PROMPT\"\n# printf '%s' \"$PROMPT\" | codex-exec.sh -C <workdir> -o <file> # prompt via stdin (preferred)\n#\n# Notes:\n# * -C sets codex's working root (the git worktree, for parallel isolation).\n# * -o writes codex's final message (its report) to a file you can read back.\n# * Reads the prompt from stdin when no positional PROMPT is given.\n# * In `exec` mode approvals are already \"never\", so nothing hangs waiting for\n# a human. The sandbox mode below bounds what codex may WRITE.\n#\n# Sandbox default: `workspace-write` — codex may freely edit files inside its\n# working root (the worktree) and read elsewhere on disk (so it can read the\n# plan by absolute path), but cannot write outside the workspace. This keeps\n# parallel worktree runs from clobbering each other or the main tree.\n#\n# Env overrides (flags -m/-e take precedence):\n# CODEX_MODEL default: gpt-6.1-sol gpt-6.1-sol | gpt-6-astra | gpt-6-luna\n# CODEX_EFFORT default: high reasoning effort (never \"fast\"/low)\n# CODEX_SANDBOX default: workspace-write (passed to codex's -s option)\n# CODEX_BYPASS set to 1 to use --dangerously-bypass-approvals-and-sandbox\n# (fully unsandboxed — opt in only when a phase must write\n# outside its worktree; overrides CODEX_SANDBOX)\nset -euo pipefail\n\nWORKDIR=\".\"\nOUTFILE=\"\"\nLOGFILE=\"\"\nPROMPT=\"\"\nMODEL_FLAG=\"\"\nEFFORT_FLAG=\"\"\n\nwhile [[ $# -gt 0 ]]; do\n case \"$1\" in\n -m|--model) MODEL_FLAG=\"$2\"; shift 2 ;;\n -e|--effort) EFFORT_FLAG=\"$2\"; shift 2 ;;\n -C|--cd) WORKDIR=\"$2\"; shift 2 ;;\n -o|--out) OUTFILE=\"$2\"; shift 2 ;;\n -l|--log) LOGFILE=\"$2\"; shift 2 ;;\n --) shift; PROMPT=\"${1:-}\"; break ;;\n -*) echo \"codex-exec.sh: unknown flag: $1\" >&2; exit 2 ;;\n *) PROMPT=\"$1\"; shift ;;\n esac\ndone\n\nMODEL=\"${MODEL_FLAG:-${CODEX_MODEL:-gpt-6.1-sol}}\"\nEFFORT=\"${EFFORT_FLAG:-${CODEX_EFFORT:-high}}\"\nSANDBOX=\"${CODEX_SANDBOX:-workspace-write}\"\n\nargs=( exec\n -m \"$MODEL\"\n -c \"model_reasoning_effort=\\\"$EFFORT\\\"\"\n -C \"$WORKDIR\"\n)\nif [[ \"${CODEX_BYPASS:-0}\" == \"1\" ]]; then\n args+=( --dangerously-bypass-approvals-and-sandbox )\nelse\n args+=( -s \"$SANDBOX\" )\nfi\n[[ -n \"$OUTFILE\" ]] && args+=( -o \"$OUTFILE\" )\n\n# Prompt source: positional arg if present, otherwise stdin (`-`).\nif [[ -n \"$PROMPT\" ]]; then\n set -- \"$PROMPT\"\nelse\n set -- \"-\"\nfi\n\nif [[ -n \"$LOGFILE\" ]]; then\n codex \"${args[@]}\" \"$@\" 2>&1 | tee \"$LOGFILE\"\nelse\n codex \"${args[@]}\" \"$@\"\nfi\n"
|
|
22
22
|
}
|
|
23
23
|
],
|
|
24
24
|
"design-docs": [
|
|
@@ -51,7 +51,13 @@ export async function resolveSqlDsn(
|
|
|
51
51
|
// so the parameterless case goes through the tagged-template path instead. Never use `.simple()`.
|
|
52
52
|
// The statement runs as a server-side cursor fetched in batches, so the API never holds more than
|
|
53
53
|
// one batch past the caps. DECLARE only accepts SELECT, VALUES, TABLE and WITH ... SELECT.
|
|
54
|
-
|
|
54
|
+
// Bun >= 1.4.4 rejects an enumerable `raw`, so shape it like a real (frozen) template object.
|
|
55
|
+
const asTemplate = (text: string): TemplateStringsArray =>
|
|
56
|
+
Object.freeze(
|
|
57
|
+
Object.defineProperty([text], "raw", { value: Object.freeze([text]) }) as string[] & {
|
|
58
|
+
raw: readonly string[];
|
|
59
|
+
},
|
|
60
|
+
);
|
|
55
61
|
|
|
56
62
|
const defaultExecutor: SqlExecutor = async (dsn, query, params, take) => {
|
|
57
63
|
// Bun.SQL global, not `import { SQL } from "bun"`: the node-target CLI bundle cannot import "bun".
|
|
@@ -610,6 +610,8 @@ const VALIDATED_KEYS: Record<string, ConfigValidator> = {
|
|
|
610
610
|
// (~80k chars) it risks the SIGTERM-143 context-saturation failure mode
|
|
611
611
|
// the cap exists to prevent (see context-preamble.ts).
|
|
612
612
|
...boundedIntegerValidators(["CONTEXT_PREAMBLE_MAX_TOKENS"], 100, 20000),
|
|
613
|
+
// 0 turns the one-time context-bound note off; no model window reaches 10M.
|
|
614
|
+
...boundedIntegerValidators(["CONTEXT_BOUND_NOTE_TOKENS"], 0, 10_000_000),
|
|
613
615
|
MEMORY_MIN_SIMILARITY: (value) =>
|
|
614
616
|
validateFloatRange("MEMORY_MIN_SIMILARITY", value, 0, 1, "between 0 and 1 inclusive"),
|
|
615
617
|
MEMORY_ACCESS_BOOST_MAX: (value) =>
|
|
@@ -628,6 +630,29 @@ export function validateConfigValue(key: string, value: unknown): string | null
|
|
|
628
630
|
return validator ? validator(value) : null;
|
|
629
631
|
}
|
|
630
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Every check `PUT /api/config` runs before a write: scope and scopeId shape,
|
|
635
|
+
* reserved and internal keys, the per-key value validator and the env-export
|
|
636
|
+
* rule. Shared with HITL `config` questions so the two writers cannot drift.
|
|
637
|
+
*/
|
|
638
|
+
export function validateConfigEntry(entry: {
|
|
639
|
+
scope: "global" | "agent" | "repo";
|
|
640
|
+
scopeId?: string | null;
|
|
641
|
+
key: string;
|
|
642
|
+
value: unknown;
|
|
643
|
+
exportToEnv?: boolean;
|
|
644
|
+
}): string | null {
|
|
645
|
+
if (entry.scope === "global" && entry.scopeId) return "Global scope must not have scopeId";
|
|
646
|
+
if ((entry.scope === "agent" || entry.scope === "repo") && !entry.scopeId) {
|
|
647
|
+
return "Agent/repo scope requires scopeId";
|
|
648
|
+
}
|
|
649
|
+
if (isReservedConfigKey(entry.key)) return reservedKeyError(entry.key).message;
|
|
650
|
+
if (isInternalConfigKey(entry.key)) return internalConfigKeyError(entry.key).message;
|
|
651
|
+
return (
|
|
652
|
+
validateConfigValue(entry.key, entry.value) ?? validateExportToEnv(entry.key, entry.exportToEnv)
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
631
656
|
/**
|
|
632
657
|
* Rejects `exportToEnv: true` for keys only the API server reads. Omitting the
|
|
633
658
|
* flag is fine: the write path stores false for those keys.
|
package/src/be/users.ts
CHANGED
|
@@ -632,6 +632,15 @@ export async function mintScriptRunToken(
|
|
|
632
632
|
return await insertSessionToken(agentId, null, null, ttlMs, null, undefined, null, null, true);
|
|
633
633
|
}
|
|
634
634
|
|
|
635
|
+
/** Whether a session token was minted by `mintScriptRunToken`. */
|
|
636
|
+
export async function isScriptRunToken(tokenId: string): Promise<boolean> {
|
|
637
|
+
const row = await getDbClient().get<{ scriptRun: number }>(
|
|
638
|
+
"SELECT scriptRun FROM session_tokens WHERE id = ?",
|
|
639
|
+
[tokenId],
|
|
640
|
+
);
|
|
641
|
+
return row?.scriptRun === 1;
|
|
642
|
+
}
|
|
643
|
+
|
|
635
644
|
export async function insertSessionToken(
|
|
636
645
|
agentId: string,
|
|
637
646
|
taskId: string | null,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createRuntimeProvider } from "../runtime-providers";
|
|
2
2
|
import { BuiltinController } from "../runtime-providers/controller";
|
|
3
3
|
import {
|
|
4
|
+
CONTROLLER_PING_INTERVAL_MS,
|
|
4
5
|
CONTROLLER_PROTOCOL_VERSION,
|
|
5
6
|
type ControllerCommand,
|
|
6
7
|
ControllerServerMessageSchema,
|
|
@@ -37,13 +38,21 @@ async function executeCommand(
|
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// A network cut never closes the socket on this side, so the client watches inbound
|
|
42
|
+
// traffic. The API pings every CONTROLLER_PING_INTERVAL_MS and the client pings on
|
|
43
|
+
// each check; Bun surfaces both as `ping` / `pong` events. Three ping intervals
|
|
44
|
+
// without any frame means the link is half-open.
|
|
45
|
+
export const CONTROLLER_LIVENESS_TIMEOUT_MS = 3 * CONTROLLER_PING_INTERVAL_MS;
|
|
46
|
+
|
|
40
47
|
export function startControllerClient(options: {
|
|
41
48
|
apiUrl: string;
|
|
42
49
|
token: string;
|
|
43
50
|
provider: RuntimeProvider;
|
|
44
51
|
target: RuntimeTargetConfig;
|
|
45
52
|
onFatal?: () => void;
|
|
53
|
+
livenessTimeoutMs?: number;
|
|
46
54
|
}): { stop: () => void } {
|
|
55
|
+
const livenessTimeoutMs = options.livenessTimeoutMs ?? CONTROLLER_LIVENESS_TIMEOUT_MS;
|
|
47
56
|
const url = new URL("/api/controllers/connect", options.apiUrl);
|
|
48
57
|
if (url.protocol !== "http:" && url.protocol !== "https:")
|
|
49
58
|
throw new Error("Controller API URL must use HTTP or HTTPS");
|
|
@@ -52,6 +61,7 @@ export function startControllerClient(options: {
|
|
|
52
61
|
let stopped = false;
|
|
53
62
|
let socket: WebSocket | undefined;
|
|
54
63
|
let reconnect: ReturnType<typeof setTimeout> | undefined;
|
|
64
|
+
let liveness: ReturnType<typeof setInterval> | undefined;
|
|
55
65
|
let attempts = 0;
|
|
56
66
|
const log = (message: string) => console.log(scrubSecrets(`[runtime-controller] ${message}`));
|
|
57
67
|
|
|
@@ -63,7 +73,28 @@ export function startControllerClient(options: {
|
|
|
63
73
|
socket = ws;
|
|
64
74
|
let controller: BuiltinController | undefined;
|
|
65
75
|
let connectedAt = 0;
|
|
76
|
+
let lastTrafficAt = Date.now();
|
|
77
|
+
const markAlive = () => {
|
|
78
|
+
lastTrafficAt = Date.now();
|
|
79
|
+
};
|
|
80
|
+
ws.addEventListener("ping", markAlive);
|
|
81
|
+
ws.addEventListener("pong", markAlive);
|
|
82
|
+
clearInterval(liveness);
|
|
83
|
+
liveness = setInterval(
|
|
84
|
+
() => {
|
|
85
|
+
if (socket !== ws) return;
|
|
86
|
+
if (Date.now() - lastTrafficAt > livenessTimeoutMs) {
|
|
87
|
+
log(`No traffic for ${livenessTimeoutMs}ms. Dropping the connection`);
|
|
88
|
+
clearInterval(liveness);
|
|
89
|
+
ws.terminate();
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (ws.readyState === WebSocket.OPEN) ws.ping();
|
|
93
|
+
},
|
|
94
|
+
Math.ceil(livenessTimeoutMs / 3),
|
|
95
|
+
);
|
|
66
96
|
ws.onmessage = (event) => {
|
|
97
|
+
markAlive();
|
|
67
98
|
void (async () => {
|
|
68
99
|
const message = ControllerServerMessageSchema.parse(JSON.parse(String(event.data)));
|
|
69
100
|
if (message.type === "welcome") {
|
|
@@ -106,6 +137,7 @@ export function startControllerClient(options: {
|
|
|
106
137
|
ws.onerror = () => log("Connection failed");
|
|
107
138
|
ws.onclose = (event) => {
|
|
108
139
|
if (stopped || socket !== ws) return;
|
|
140
|
+
clearInterval(liveness);
|
|
109
141
|
if (event.code === 1008) {
|
|
110
142
|
stopped = true;
|
|
111
143
|
log("Controller access rejected or token revoked. Mint a token and restart this process.");
|
|
@@ -129,6 +161,7 @@ export function startControllerClient(options: {
|
|
|
129
161
|
stop: () => {
|
|
130
162
|
stopped = true;
|
|
131
163
|
if (reconnect) clearTimeout(reconnect);
|
|
164
|
+
clearInterval(liveness);
|
|
132
165
|
socket?.close(1000, "Controller stopped");
|
|
133
166
|
},
|
|
134
167
|
};
|
package/src/commands/runner.ts
CHANGED
|
@@ -3352,6 +3352,8 @@ export interface PollOptions {
|
|
|
3352
3352
|
pollTimeout: number;
|
|
3353
3353
|
/** Push scheduling fetches once, then returns control to the runner. */
|
|
3354
3354
|
singlePoll?: boolean;
|
|
3355
|
+
/** Stop an open poll window before its next claim request. */
|
|
3356
|
+
shouldStop?: () => boolean;
|
|
3355
3357
|
/** Distinguishes a failed fetch from an empty successful response. */
|
|
3356
3358
|
onPollFailure?: () => void;
|
|
3357
3359
|
since?: string; // Optional: for filtering finished tasks
|
|
@@ -3669,6 +3671,7 @@ async function pollForTriggerOnce(opts: PollOptions): Promise<Trigger | null> {
|
|
|
3669
3671
|
}
|
|
3670
3672
|
|
|
3671
3673
|
while (Date.now() - startTime < opts.pollTimeout) {
|
|
3674
|
+
if (opts.shouldStop?.()) return null;
|
|
3672
3675
|
try {
|
|
3673
3676
|
// Build URL with optional since parameter
|
|
3674
3677
|
let url = `${opts.apiUrl}/api/poll`;
|
|
@@ -7099,7 +7102,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
7099
7102
|
}
|
|
7100
7103
|
}
|
|
7101
7104
|
|
|
7102
|
-
// Only poll if we have capacity and the API is
|
|
7105
|
+
// Only poll if we have capacity and neither the API nor worker is draining
|
|
7103
7106
|
const schedule = pushDelivery.schedule({
|
|
7104
7107
|
mode: state.deliveryMode ?? "poll",
|
|
7105
7108
|
push: deliveryChecks.push,
|
|
@@ -7108,7 +7111,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
7108
7111
|
exitAfterTasks === null
|
|
7109
7112
|
? state.maxConcurrent
|
|
7110
7113
|
: Math.min(state.maxConcurrent, Math.max(0, exitAfterTasks - state.tasksProcessed)),
|
|
7111
|
-
draining: state.apiDraining,
|
|
7114
|
+
draining: state.apiDraining || shuttingDown,
|
|
7112
7115
|
lastPushPingAt,
|
|
7113
7116
|
pollInterval: PollIntervalMs,
|
|
7114
7117
|
});
|
|
@@ -7146,6 +7149,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
|
|
|
7146
7149
|
runtimeInstanceId,
|
|
7147
7150
|
pollTimeout: effectiveTimeout,
|
|
7148
7151
|
singlePoll: schedule.singlePoll,
|
|
7152
|
+
shouldStop: () => shuttingDown,
|
|
7149
7153
|
onPollFailure,
|
|
7150
7154
|
harnessProvider: state.harnessProvider,
|
|
7151
7155
|
onApiDrainSignal: (draining) => applyApiDrainSignal(state, role, draining),
|
|
@@ -2,7 +2,8 @@ import { explicitModelErrorForAgent } from "../be/model-validation";
|
|
|
2
2
|
import type { RequestInfo } from "../tools/utils";
|
|
3
3
|
import { type CreateTaskOptions, CreateTaskOptionsSchema } from "../types";
|
|
4
4
|
import { scrubSecrets } from "../utils/secret-scrubber";
|
|
5
|
-
import
|
|
5
|
+
import { workflowEventBus } from "../workflows/event-bus";
|
|
6
|
+
import type { TaskBlockedEvent, TaskCreateModify, TaskCreateOrigin } from "./contract";
|
|
6
7
|
import { dispatchPre, extensionIdForAgent, listRegistered } from "./dispatcher";
|
|
7
8
|
|
|
8
9
|
const MODIFIABLE_OPTION_KEYS = [
|
|
@@ -69,6 +70,14 @@ function warnDroppedKey(extensionName: string, key: string): void {
|
|
|
69
70
|
);
|
|
70
71
|
}
|
|
71
72
|
|
|
73
|
+
function scrubFields<T extends object>(fields: T): T {
|
|
74
|
+
return Object.fromEntries(
|
|
75
|
+
Object.entries(fields)
|
|
76
|
+
.filter(([, value]) => value !== undefined)
|
|
77
|
+
.map(([key, value]) => [key, typeof value === "string" ? scrubSecrets(value) : value]),
|
|
78
|
+
) as T;
|
|
79
|
+
}
|
|
80
|
+
|
|
72
81
|
export async function applyPreTaskCreate(
|
|
73
82
|
args: ApplyPreTaskCreateArgs,
|
|
74
83
|
): Promise<ApplyPreTaskCreateResult> {
|
|
@@ -80,6 +89,48 @@ export async function applyPreTaskCreate(
|
|
|
80
89
|
? (`extension:${creatingExtension.record.name}` as const)
|
|
81
90
|
: args.origin;
|
|
82
91
|
|
|
92
|
+
const blocked = (
|
|
93
|
+
reason: string,
|
|
94
|
+
extension: TaskBlockedEvent["extension"],
|
|
95
|
+
): ApplyPreTaskCreateResult => {
|
|
96
|
+
const { options, requestInfo } = args;
|
|
97
|
+
const event: TaskBlockedEvent = {
|
|
98
|
+
extension,
|
|
99
|
+
reason: scrubSecrets(reason),
|
|
100
|
+
origin,
|
|
101
|
+
source: options.source,
|
|
102
|
+
requester: scrubFields({
|
|
103
|
+
userId: options.requestedByUserId,
|
|
104
|
+
agentId: requestInfo?.agentId ?? options.creatorAgentId,
|
|
105
|
+
slackUserId: options.slackUserId,
|
|
106
|
+
}),
|
|
107
|
+
context: scrubFields({
|
|
108
|
+
contextKey: options.contextKey ?? requestInfo?.contextKey,
|
|
109
|
+
scheduleId: options.scheduleId,
|
|
110
|
+
workflowRunId: options.workflowRunId,
|
|
111
|
+
workflowRunStepId: options.workflowRunStepId,
|
|
112
|
+
parentTaskId: options.parentTaskId,
|
|
113
|
+
vcsRepo: options.vcsRepo,
|
|
114
|
+
vcsNumber: options.vcsNumber,
|
|
115
|
+
slackChannelId: options.slackChannelId,
|
|
116
|
+
slackThreadTs: options.slackThreadTs,
|
|
117
|
+
}),
|
|
118
|
+
description: scrubSecrets(args.description).slice(0, 500),
|
|
119
|
+
};
|
|
120
|
+
try {
|
|
121
|
+
workflowEventBus.emit("task.create_blocked", {
|
|
122
|
+
...event,
|
|
123
|
+
skipExtensionId: creatingExtensionId,
|
|
124
|
+
});
|
|
125
|
+
} catch (error) {
|
|
126
|
+
console.error(
|
|
127
|
+
"[extensions] task.create_blocked event not emitted:",
|
|
128
|
+
scrubSecrets(error instanceof Error ? error.message : String(error)),
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
return { kind: "blocked", reason: event.reason, extension };
|
|
132
|
+
};
|
|
133
|
+
|
|
83
134
|
// The last extension that rewrote `model` or `agentId`. The final pair is re-checked
|
|
84
135
|
// against the assignee's harness, because the caller validated the pre-hook pair only.
|
|
85
136
|
let modelEditor: { id: string; name: string } | undefined;
|
|
@@ -125,11 +176,7 @@ export async function applyPreTaskCreate(
|
|
|
125
176
|
);
|
|
126
177
|
|
|
127
178
|
if (result.action === "block") {
|
|
128
|
-
return
|
|
129
|
-
kind: "blocked",
|
|
130
|
-
reason: result.reason,
|
|
131
|
-
extension: result.extension,
|
|
132
|
-
};
|
|
179
|
+
return blocked(result.reason, result.extension);
|
|
133
180
|
}
|
|
134
181
|
if (result.action === "continue") {
|
|
135
182
|
return { kind: "proceed", description: args.description, options: args.options };
|
|
@@ -146,11 +193,10 @@ export async function applyPreTaskCreate(
|
|
|
146
193
|
agentId: options.agentId,
|
|
147
194
|
});
|
|
148
195
|
if (modelError) {
|
|
149
|
-
return
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
};
|
|
196
|
+
return blocked(
|
|
197
|
+
`pre.task.create rewrite from extension "${modelEditor.name}" is invalid: ${modelError}`,
|
|
198
|
+
modelEditor,
|
|
199
|
+
);
|
|
154
200
|
}
|
|
155
201
|
}
|
|
156
202
|
return {
|