@desplega.ai/agent-swarm 1.124.0 → 1.125.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{actions-z3d0fk7z.js → actions-3aky6er5.js} +6 -5
- package/dist/{app-q2rc9tdr.js → app-xbt4n4mt.js} +4 -3
- package/dist/{assistant-3stjq7fz.js → assistant-ncftbs9t.js} +12 -10
- package/dist/{boot-reembed-v6r1df32.js → boot-reembed-00cxm1mj.js} +5 -4
- package/dist/{boot-reembed-5867whnb.js → boot-reembed-5xxzva12.js} +4 -3
- package/dist/{boot-scrub-logs-8b8jegk3.js → boot-scrub-logs-9cmcq6ez.js} +3 -2
- package/dist/{cli-0925phzv.js → cli-0w3mk6r2.js} +1 -1
- package/dist/{cli-zxb72ctk.js → cli-3x5aphbt.js} +2 -2
- package/dist/{cli-exfs8aym.js → cli-3y8xyp0b.js} +7 -1
- package/dist/{cli-2yr6edr7.js → cli-4xft7caa.js} +4 -4
- package/dist/{cli-kk58tg33.js → cli-5a4nmfrn.js} +12 -10
- package/dist/{cli-5jn6wnd9.js → cli-88r31vx9.js} +2 -2
- package/dist/{cli-sqv6c9b8.js → cli-8bkwn1wv.js} +11 -9
- package/dist/{cli-p0zgv8pj.js → cli-8vzddbs7.js} +2 -1
- package/dist/{cli-qf7acvyr.js → cli-94796kxr.js} +2 -2
- package/dist/{cli-wn3m6q54.js → cli-dbmh9yqn.js} +7 -5
- package/dist/{cli-p0ec4vv6.js → cli-ff8b5cbe.js} +1 -1
- package/dist/{cli-y43n987v.js → cli-gbx122ge.js} +1 -1
- package/dist/{cli-ykthjw29.js → cli-jcavqvh7.js} +20 -24
- package/dist/cli-k1py8264.js +1129 -0
- package/dist/{cli-qwsagsmg.js → cli-k5c6jfzn.js} +1 -1
- package/dist/{cli-gkvf8d83.js → cli-n02497b2.js} +5 -3
- package/dist/{cli-vm20sh8d.js → cli-nqq9tthd.js} +9 -7
- package/dist/{cli-jp4mdj9d.js → cli-pqtrhrss.js} +2 -2
- package/dist/{cli-gzjsamwc.js → cli-q92pr3rt.js} +1 -1
- package/dist/{cli-mmk5vagg.js → cli-qzy21svh.js} +2 -2
- package/dist/{cli-dq2ssyf0.js → cli-refygp3c.js} +5 -5
- package/dist/{cli-perb20ma.js → cli-tar44ekp.js} +1 -1
- package/dist/{cli-h89g0qrd.js → cli-v292k2fv.js} +1 -1
- package/dist/{cli-mcez7z4p.js → cli-vvbwy286.js} +1 -1
- package/dist/{cli-x268say8.js → cli-wbcsjpyv.js} +366 -68
- package/dist/cli-wdrrgv4m.js +22 -0
- package/dist/{cli-3z4q09tn.js → cli-yr11vvst.js} +32 -1127
- package/dist/{cli-92bqgbgy.js → cli-zhp3t49e.js} +4 -2
- package/dist/cli.js +23 -10
- package/dist/{codex-adapter-vcseqpwf.js → codex-adapter-mqczt8xv.js} +1 -1
- package/dist/codex-hook-emq42pfq.js +106 -0
- package/dist/{codex-session-runner-jec9e9t1.js → codex-session-runner-mrwz1h50.js} +1 -1
- package/dist/{commands-sda76yn8.js → commands-7dbx98fp.js} +5 -3
- package/dist/{db-vn9e4rqj.js → db-r5705rk8.js} +7 -2
- package/dist/{handlers-xfja18ac.js → handlers-8kj038es.js} +12 -10
- package/dist/{hook-2medrp47.js → hook-2590fnyk.js} +1 -1
- package/dist/{http-937pyjfc.js → http-8yej35d0.js} +77 -55
- package/dist/{index-vah60vxp.js → index-hy73j39b.js} +8 -7
- package/dist/{index-knqczmnm.js → index-kgay2v5w.js} +11 -10
- package/dist/{index-gw4en7jg.js → index-v3n4hb3w.js} +10 -9
- package/dist/{index-2bqd2q9v.js → index-x1n3tybc.js} +12 -11
- package/dist/{keepalive-20rd6hxh.js → keepalive-q8yg6d6r.js} +6 -5
- package/dist/{lead-7w2k3a49.js → lead-yxtt169p.js} +23 -21
- package/dist/{maintenance-p3x918yw.js → maintenance-70cskw5t.js} +5 -4
- package/dist/{oauth-refresh-sweep-shm0nzm4.js → oauth-refresh-sweep-90h9rzx8.js} +5 -4
- package/dist/{onboard-py9dnf54.js → onboard-20reqz6j.js} +2 -2
- package/dist/{otel-impl-7yjkfg68.js → otel-impl-vja8f9mm.js} +1 -1
- package/dist/{pricing-refresh-3snb6370.js → pricing-refresh-34mbexgm.js} +5 -4
- package/dist/{rbac-roles-gqxdp8st.js → rbac-roles-s1qnewkt.js} +5 -4
- package/dist/{rbac-roles-5e72bbpk.js → rbac-roles-sa59eyp4.js} +4 -3
- package/dist/{seed-pricing-bg42d7h2.js → seed-pricing-p5fnj0qd.js} +4 -3
- package/dist/{setup-nzk3kbbq.js → setup-p9yq3mb2.js} +2 -2
- package/dist/{worker-0p1jdzkm.js → worker-21zrq8cd.js} +23 -21
- package/openapi.json +1 -1
- package/package.json +2 -1
- package/src/be/db.ts +30 -0
- package/src/cli.tsx +12 -0
- package/src/commands/runner.ts +11 -23
- package/src/hooks/codex-hook.ts +157 -0
- package/src/http/kv.ts +37 -8
- package/src/http/mcp-bridge.ts +3 -2
- package/src/kv-overflow.ts +20 -0
- package/src/prompts/steering-delivery.ts +32 -0
- package/src/providers/codex-adapter.ts +20 -4
- package/src/providers/types.ts +8 -0
- package/src/script-workflows/harness.ts +5 -1
- package/src/script-workflows/workflow-ctx.ts +6 -7
- package/src/scripts-runtime/eval-harness.ts +11 -2
- package/src/scripts-runtime/extract-args-schema.ts +5 -1
- package/src/scripts-runtime/response-limit.ts +50 -0
- package/src/scripts-runtime/swarm-sdk.ts +3 -4
- package/src/tests/claude-adapter-binary.test.ts +64 -32
- package/src/tests/codex-steering-hook.test.ts +224 -0
- package/src/tests/kv-http.test.ts +35 -1
- package/src/tests/kv-storage.test.ts +45 -0
- package/src/tests/kv-tool.test.ts +51 -0
- package/src/tests/run-bun-tests.test.ts +171 -0
- package/src/tests/scripts-mcp-e2e.test.ts +105 -1
- package/src/tests/scripts-runtime.test.ts +33 -0
- package/src/tests/sdk-allowlist.test.ts +27 -1
- package/src/tests/slack-read-boundaries.test.ts +229 -0
- package/src/tests/steering-core.test.ts +19 -19
- package/src/tests/steering-transport.test.ts +21 -3
- package/src/tests/store-progress-attachments-handler.test.ts +45 -10
- package/src/tests/swarm-tool-result-gate.test.ts +300 -21
- package/src/tests/tool-output-agent-id.test.ts +9 -3
- package/src/tests/tool-registrar-no-input.test.ts +6 -0
- package/src/tools/kv/kv-get.ts +10 -0
- package/src/tools/kv/kv-list.ts +7 -0
- package/src/tools/kv/kv-read-auth.ts +13 -0
- package/src/tools/kv/kv-write-auth.ts +4 -0
- package/src/tools/steer-task.ts +1 -1
- package/src/tools/store-progress.ts +24 -7
- package/src/tools/utils.ts +403 -43
- package/src/types.ts +7 -1
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
forceRefreshAuthorizationOrThrow
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-v292k2fv.js";
|
|
4
4
|
import {
|
|
5
5
|
getAuthorizationById,
|
|
6
6
|
listAuthorizationSweepRows
|
|
7
|
-
} from "./cli-
|
|
8
|
-
import"./cli-
|
|
7
|
+
} from "./cli-vvbwy286.js";
|
|
8
|
+
import"./cli-yr11vvst.js";
|
|
9
|
+
import"./cli-k1py8264.js";
|
|
9
10
|
import"./cli-z2zcxes1.js";
|
|
10
|
-
import"./cli-
|
|
11
|
+
import"./cli-8vzddbs7.js";
|
|
11
12
|
import {
|
|
12
13
|
init_secret_scrubber,
|
|
13
14
|
scrubSecrets
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
createDefaultMcpJson,
|
|
4
4
|
createDefaultSettingsLocal,
|
|
5
5
|
createHooksConfig
|
|
6
|
-
} from "./cli-
|
|
6
|
+
} from "./cli-tar44ekp.js";
|
|
7
7
|
import {
|
|
8
8
|
require_jsx_dev_runtime
|
|
9
9
|
} from "./cli-wx82tw1k.js";
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
import {
|
|
14
14
|
init_package,
|
|
15
15
|
package_default
|
|
16
|
-
} from "./cli-
|
|
16
|
+
} from "./cli-8vzddbs7.js";
|
|
17
17
|
import {
|
|
18
18
|
Box_default,
|
|
19
19
|
Select,
|
|
@@ -2,11 +2,12 @@ import {
|
|
|
2
2
|
PRICING_REFRESH_INTERVAL_MS,
|
|
3
3
|
refreshPricingFromModelsDev,
|
|
4
4
|
startPricingRefreshLoop
|
|
5
|
-
} from "./cli-
|
|
6
|
-
import"./cli-
|
|
7
|
-
import"./cli-
|
|
5
|
+
} from "./cli-94796kxr.js";
|
|
6
|
+
import"./cli-q92pr3rt.js";
|
|
7
|
+
import"./cli-yr11vvst.js";
|
|
8
|
+
import"./cli-k1py8264.js";
|
|
8
9
|
import"./cli-z2zcxes1.js";
|
|
9
|
-
import"./cli-
|
|
10
|
+
import"./cli-8vzddbs7.js";
|
|
10
11
|
import"./cli-4j2mrzag.js";
|
|
11
12
|
import"./cli-bw80ck94.js";
|
|
12
13
|
import"./cli-xz9aq0rf.js";
|
|
@@ -7,11 +7,12 @@ import {
|
|
|
7
7
|
getUserGrant,
|
|
8
8
|
listUserRoles,
|
|
9
9
|
runRbacCliCommand
|
|
10
|
-
} from "./cli-
|
|
11
|
-
import"./cli-
|
|
12
|
-
import"./cli-
|
|
10
|
+
} from "./cli-3x5aphbt.js";
|
|
11
|
+
import"./cli-gbx122ge.js";
|
|
12
|
+
import"./cli-yr11vvst.js";
|
|
13
|
+
import"./cli-k1py8264.js";
|
|
13
14
|
import"./cli-z2zcxes1.js";
|
|
14
|
-
import"./cli-
|
|
15
|
+
import"./cli-8vzddbs7.js";
|
|
15
16
|
import"./cli-4j2mrzag.js";
|
|
16
17
|
import"./cli-bw80ck94.js";
|
|
17
18
|
import"./cli-xz9aq0rf.js";
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PermissionVerbSchema
|
|
3
|
-
} from "./cli-
|
|
3
|
+
} from "./cli-gbx122ge.js";
|
|
4
4
|
import {
|
|
5
5
|
getDb,
|
|
6
6
|
init_db
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-yr11vvst.js";
|
|
8
|
+
import"./cli-k1py8264.js";
|
|
8
9
|
import"./cli-z2zcxes1.js";
|
|
9
|
-
import"./cli-
|
|
10
|
+
import"./cli-8vzddbs7.js";
|
|
10
11
|
import"./cli-4j2mrzag.js";
|
|
11
12
|
import"./cli-bw80ck94.js";
|
|
12
13
|
import"./cli-xz9aq0rf.js";
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
buildModelsDevSeedRows,
|
|
3
3
|
seedPricingFromModelsDev
|
|
4
|
-
} from "./cli-
|
|
5
|
-
import"./cli-
|
|
4
|
+
} from "./cli-q92pr3rt.js";
|
|
5
|
+
import"./cli-yr11vvst.js";
|
|
6
|
+
import"./cli-k1py8264.js";
|
|
6
7
|
import"./cli-z2zcxes1.js";
|
|
7
|
-
import"./cli-
|
|
8
|
+
import"./cli-8vzddbs7.js";
|
|
8
9
|
import"./cli-4j2mrzag.js";
|
|
9
10
|
import"./cli-bw80ck94.js";
|
|
10
11
|
import"./cli-xz9aq0rf.js";
|
|
@@ -4,14 +4,14 @@ import {
|
|
|
4
4
|
createDefaultMcpJson,
|
|
5
5
|
createDefaultSettingsLocal,
|
|
6
6
|
createHooksConfig
|
|
7
|
-
} from "./cli-
|
|
7
|
+
} from "./cli-tar44ekp.js";
|
|
8
8
|
import {
|
|
9
9
|
require_jsx_dev_runtime
|
|
10
10
|
} from "./cli-wx82tw1k.js";
|
|
11
11
|
import {
|
|
12
12
|
getApiKey
|
|
13
13
|
} from "./cli-f14fvzag.js";
|
|
14
|
-
import"./cli-
|
|
14
|
+
import"./cli-8vzddbs7.js";
|
|
15
15
|
import {
|
|
16
16
|
Box_default,
|
|
17
17
|
Spinner,
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
runAgent
|
|
3
|
-
} from "./cli-
|
|
4
|
-
import"./cli-
|
|
3
|
+
} from "./cli-jcavqvh7.js";
|
|
4
|
+
import"./cli-wdrrgv4m.js";
|
|
5
|
+
import"./cli-3y8xyp0b.js";
|
|
5
6
|
import"./cli-jj1arm18.js";
|
|
6
7
|
import"./cli-07er7wrs.js";
|
|
7
8
|
import"./cli-w5r4wfm5.js";
|
|
@@ -14,7 +15,7 @@ import"./cli-d6n0thxw.js";
|
|
|
14
15
|
import"./cli-v1mwc5v2.js";
|
|
15
16
|
import {
|
|
16
17
|
getEnabledCapabilities
|
|
17
|
-
} from "./cli-
|
|
18
|
+
} from "./cli-wbcsjpyv.js";
|
|
18
19
|
import"./cli-1z7048dy.js";
|
|
19
20
|
import"./cli-gz9sbyk3.js";
|
|
20
21
|
import"./cli-ftyf58xj.js";
|
|
@@ -25,36 +26,37 @@ import"./cli-tng3131n.js";
|
|
|
25
26
|
import"./cli-zm7njwzd.js";
|
|
26
27
|
import"./cli-cw70hymf.js";
|
|
27
28
|
import"./cli-e61fs2hp.js";
|
|
28
|
-
import"./cli-
|
|
29
|
-
import"./cli-
|
|
30
|
-
import"./cli-
|
|
31
|
-
import"./cli-
|
|
32
|
-
import"./cli-
|
|
33
|
-
import"./cli-
|
|
34
|
-
import"./cli-
|
|
29
|
+
import"./cli-4xft7caa.js";
|
|
30
|
+
import"./cli-94796kxr.js";
|
|
31
|
+
import"./cli-q92pr3rt.js";
|
|
32
|
+
import"./cli-3x5aphbt.js";
|
|
33
|
+
import"./cli-gbx122ge.js";
|
|
34
|
+
import"./cli-n02497b2.js";
|
|
35
|
+
import"./cli-5a4nmfrn.js";
|
|
35
36
|
import"./cli-f14fvzag.js";
|
|
36
37
|
import"./cli-3dpj7atn.js";
|
|
37
|
-
import"./cli-
|
|
38
|
-
import"./cli-
|
|
38
|
+
import"./cli-v292k2fv.js";
|
|
39
|
+
import"./cli-dbmh9yqn.js";
|
|
39
40
|
import"./cli-jkhwmmgg.js";
|
|
40
|
-
import"./cli-
|
|
41
|
-
import"./cli-
|
|
41
|
+
import"./cli-qzy21svh.js";
|
|
42
|
+
import"./cli-ff8b5cbe.js";
|
|
42
43
|
import"./cli-q25ejbyn.js";
|
|
43
|
-
import"./cli-
|
|
44
|
+
import"./cli-vvbwy286.js";
|
|
44
45
|
import"./cli-fad8m16k.js";
|
|
45
46
|
import"./cli-7zd05xb4.js";
|
|
46
|
-
import"./cli-
|
|
47
|
-
import"./cli-
|
|
48
|
-
import"./cli-
|
|
49
|
-
import"./cli-
|
|
47
|
+
import"./cli-zhp3t49e.js";
|
|
48
|
+
import"./cli-0w3mk6r2.js";
|
|
49
|
+
import"./cli-k5c6jfzn.js";
|
|
50
|
+
import"./cli-refygp3c.js";
|
|
50
51
|
import"./cli-rkndnn90.js";
|
|
51
52
|
import"./cli-b0p7rfnd.js";
|
|
52
53
|
import"./cli-5ncdb7ff.js";
|
|
53
54
|
import"./cli-wspgs9bt.js";
|
|
54
55
|
import"./cli-dh55d5fg.js";
|
|
55
|
-
import"./cli-
|
|
56
|
+
import"./cli-yr11vvst.js";
|
|
57
|
+
import"./cli-k1py8264.js";
|
|
56
58
|
import"./cli-z2zcxes1.js";
|
|
57
|
-
import"./cli-
|
|
59
|
+
import"./cli-8vzddbs7.js";
|
|
58
60
|
import"./cli-4j2mrzag.js";
|
|
59
61
|
import"./cli-bw80ck94.js";
|
|
60
62
|
import"./cli-xz9aq0rf.js";
|
package/openapi.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"openapi": "3.1.0",
|
|
3
3
|
"info": {
|
|
4
4
|
"title": "Agent Swarm API",
|
|
5
|
-
"version": "1.
|
|
5
|
+
"version": "1.125.0",
|
|
6
6
|
"description": "Multi-agent orchestration API for Claude Code, Codex, and Gemini CLI. Enables task distribution, agent communication, and service discovery.\n\nMCP tools are documented separately in [MCP.md](./MCP.md)."
|
|
7
7
|
},
|
|
8
8
|
"servers": [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@desplega.ai/agent-swarm",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.125.0",
|
|
4
4
|
"description": "Multi-agent orchestration for Claude Code, Codex, Gemini CLI, and other AI coding assistants",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "desplega.sh <contact@desplega.sh>",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"mono:test": "turbo run test",
|
|
66
66
|
"mono:lint": "turbo run lint",
|
|
67
67
|
"mono:dev": "turbo run dev",
|
|
68
|
+
"test:root": "bash scripts/run-bun-tests.sh",
|
|
68
69
|
"prepack": "bun run build:cli",
|
|
69
70
|
"tsc:check": "bun tsc --noEmit",
|
|
70
71
|
"check:db-boundary": "bash scripts/check-db-boundary.sh",
|
package/src/be/db.ts
CHANGED
|
@@ -13657,6 +13657,36 @@ export function getKv(namespace: string, key: string): KvEntry | null {
|
|
|
13657
13657
|
return decodeKvRow(row);
|
|
13658
13658
|
}
|
|
13659
13659
|
|
|
13660
|
+
/** Delete expired entries in one namespace. Used by internal TTL-backed stores
|
|
13661
|
+
* that need proactive cleanup rather than waiting for a point read. */
|
|
13662
|
+
export function sweepExpiredKv(namespace: string, now = Date.now()): number {
|
|
13663
|
+
const result = getDb()
|
|
13664
|
+
.prepare<unknown, [string, number]>(
|
|
13665
|
+
`DELETE FROM kv_entries
|
|
13666
|
+
WHERE namespace = ?
|
|
13667
|
+
AND expires_at IS NOT NULL
|
|
13668
|
+
AND expires_at <= ?`,
|
|
13669
|
+
)
|
|
13670
|
+
.run(namespace, now);
|
|
13671
|
+
return result.changes;
|
|
13672
|
+
}
|
|
13673
|
+
|
|
13674
|
+
/** Delete expired entries across a namespace family (`prefix` and
|
|
13675
|
+
* `prefix:*`). Used by per-agent internal stores whose inactive owners may
|
|
13676
|
+
* never return to trigger a namespace-local sweep. */
|
|
13677
|
+
export function sweepExpiredKvPrefix(prefix: string, now = Date.now()): number {
|
|
13678
|
+
const escaped = prefix.replace(/[\\%_]/g, "\\$&");
|
|
13679
|
+
const result = getDb()
|
|
13680
|
+
.prepare<unknown, [string, string, number]>(
|
|
13681
|
+
`DELETE FROM kv_entries
|
|
13682
|
+
WHERE (namespace = ? OR namespace LIKE ? ESCAPE '\\')
|
|
13683
|
+
AND expires_at IS NOT NULL
|
|
13684
|
+
AND expires_at <= ?`,
|
|
13685
|
+
)
|
|
13686
|
+
.run(prefix, `${escaped}:%`, now);
|
|
13687
|
+
return result.changes;
|
|
13688
|
+
}
|
|
13689
|
+
|
|
13660
13690
|
/**
|
|
13661
13691
|
* Upsert a KV entry. Caller passes the decoded value + valueType; we encode
|
|
13662
13692
|
* before storing. `expiresAt` is unix-ms (NULL means no expiry).
|
package/src/cli.tsx
CHANGED
|
@@ -244,6 +244,13 @@ const COMMAND_HELP: Record<
|
|
|
244
244
|
options: " -h, --help Show this help",
|
|
245
245
|
examples: ` ${binName} hook`,
|
|
246
246
|
},
|
|
247
|
+
"codex-hook": {
|
|
248
|
+
usage: `${binName} codex-hook`,
|
|
249
|
+
description:
|
|
250
|
+
"Handle Codex CLI hook events from stdin (steering delivery).\nUsed internally by the codex hooks registered in the worker image.",
|
|
251
|
+
options: " -h, --help Show this help",
|
|
252
|
+
examples: ` ${binName} codex-hook`,
|
|
253
|
+
},
|
|
247
254
|
artifact: {
|
|
248
255
|
usage: `${binName} artifact <subcommand> [options]`,
|
|
249
256
|
description: "Manage agent artifacts (serve, list, etc.).",
|
|
@@ -383,6 +390,7 @@ function printHelp(command?: string) {
|
|
|
383
390
|
["api", "Start the API + MCP HTTP server"],
|
|
384
391
|
["claude", "Run Claude CLI"],
|
|
385
392
|
["hook", "Handle Claude Code hook events (stdin)"],
|
|
393
|
+
["codex-hook", "Handle Codex CLI hook events (stdin, steering delivery)"],
|
|
386
394
|
["artifact", "Manage agent artifacts"],
|
|
387
395
|
["x", "Execute external command routes"],
|
|
388
396
|
["scripts", "Reusable scripts maintenance"],
|
|
@@ -672,6 +680,10 @@ if (args.showHelp || args.command === "help" || args.command === undefined) {
|
|
|
672
680
|
} else if (args.command === "hook") {
|
|
673
681
|
const { runHook } = await import("./commands/hook.ts");
|
|
674
682
|
await runHook();
|
|
683
|
+
} else if (args.command === "codex-hook") {
|
|
684
|
+
const { handleCodexHook } = await import("./hooks/codex-hook.ts");
|
|
685
|
+
await handleCodexHook();
|
|
686
|
+
process.exit(0);
|
|
675
687
|
} else if (args.command === "artifact") {
|
|
676
688
|
// Pass all args after "artifact" directly
|
|
677
689
|
const artifactArgs = process.argv.slice(process.argv.indexOf("artifact") + 1);
|
package/src/commands/runner.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
} from "../prompts/defaults.ts";
|
|
22
22
|
import { renderMemoriesPrompt } from "../prompts/memories.ts";
|
|
23
23
|
import { configureHttpResolver, resolveTemplateAsync } from "../prompts/resolver.ts";
|
|
24
|
+
import { renderSteeringDelivery } from "../prompts/steering-delivery.ts";
|
|
24
25
|
import { authJsonToCredentialSelection } from "../providers/codex-oauth/auth-json.js";
|
|
25
26
|
import { materializeCodexAuthJson } from "../providers/codex-oauth/auth-json-fs.js";
|
|
26
27
|
import { loadAllCodexOAuthSlots } from "../providers/codex-oauth/storage.js";
|
|
@@ -486,29 +487,10 @@ export function createSteeringDispatchState(): SteeringDispatchState {
|
|
|
486
487
|
};
|
|
487
488
|
}
|
|
488
489
|
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
* can never acknowledge it, and the row sits at `delivered` forever.
|
|
494
|
-
*
|
|
495
|
-
* Falls back to the bare body if template resolution fails: delivering an
|
|
496
|
-
* un-acknowledgeable message beats not delivering one at all.
|
|
497
|
-
*/
|
|
498
|
-
export async function renderSteeringDelivery(
|
|
499
|
-
steeringMessageId: string,
|
|
500
|
-
body: string,
|
|
501
|
-
): Promise<string> {
|
|
502
|
-
try {
|
|
503
|
-
const result = await resolveTemplateAsync("system.agent.steering.delivery", {
|
|
504
|
-
steeringMessageId,
|
|
505
|
-
body,
|
|
506
|
-
});
|
|
507
|
-
return result.skipped || !result.text.trim() ? body : result.text;
|
|
508
|
-
} catch {
|
|
509
|
-
return body;
|
|
510
|
-
}
|
|
511
|
-
}
|
|
490
|
+
// Moved to src/prompts/steering-delivery.ts so the codex-hook can render the
|
|
491
|
+
// same envelope without importing the whole runner. Re-exported for existing
|
|
492
|
+
// consumers (steering-transport tests).
|
|
493
|
+
export { renderSteeringDelivery };
|
|
512
494
|
|
|
513
495
|
export async function pollAndDispatchSteering(
|
|
514
496
|
config: ApiConfig,
|
|
@@ -518,6 +500,12 @@ export async function pollAndDispatchSteering(
|
|
|
518
500
|
fetchImpl: typeof fetch = fetch,
|
|
519
501
|
): Promise<void> {
|
|
520
502
|
if (!isSteeringEnabled()) return;
|
|
503
|
+
// Harness-side delivery (codex hooks): the hook polls pending rows and
|
|
504
|
+
// injects them itself. Dispatching here would race it into a false
|
|
505
|
+
// "Provider session does not support live steering" undeliverable →
|
|
506
|
+
// premature promotion. Leave the rows pending; the terminal sweep still
|
|
507
|
+
// promotes anything the hook never delivered.
|
|
508
|
+
if (session.steeringDeliveredExternally) return;
|
|
521
509
|
|
|
522
510
|
const headers = {
|
|
523
511
|
Authorization: `Bearer ${config.apiKey}`,
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Codex lifecycle hook — harness-side steering delivery.
|
|
5
|
+
*
|
|
6
|
+
* Codex has no in-process steering channel: `@openai/codex-sdk` drives
|
|
7
|
+
* `codex exec` with stdin written once and closed, and the app-server's
|
|
8
|
+
* native `turn/steer` is not reachable through the SDK (issue #1034). So
|
|
9
|
+
* delivery happens here instead: this hook runs inside the codex lifecycle
|
|
10
|
+
* (registered for SessionStart / PostToolUse / Stop via the managed
|
|
11
|
+
* `/etc/codex/requirements.toml` in the worker image), polls the API for
|
|
12
|
+
* pending steering rows, marks them `delivered`, and injects the rendered
|
|
13
|
+
* envelope into the model's context:
|
|
14
|
+
*
|
|
15
|
+
* - SessionStart / PostToolUse → `hookSpecificOutput.additionalContext`
|
|
16
|
+
* (verified to reach the model at codex-cli 0.146.0; PreToolUse drops
|
|
17
|
+
* additionalContext upstream, so it is deliberately not registered).
|
|
18
|
+
* - Stop → `{"decision":"block","reason":...}` so a session about to end
|
|
19
|
+
* still receives the message and continues to act on it.
|
|
20
|
+
*
|
|
21
|
+
* One-shot guarantee: a message is included in hook output only after its
|
|
22
|
+
* `/delivered` POST succeeded, so a message is injected at most once. A
|
|
23
|
+
* failed POST leaves the row `pending` for the next lifecycle event; rows a
|
|
24
|
+
* dying session never picks up are promoted by the server's terminal sweep.
|
|
25
|
+
* The runner's dispatch poll skips codex sessions entirely
|
|
26
|
+
* (`ProviderSession.steeringDeliveredExternally`).
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { renderSteeringDelivery } from "../prompts/steering-delivery.ts";
|
|
30
|
+
import type { SteeringMessage } from "../types";
|
|
31
|
+
import { getApiKey } from "../utils/api-key";
|
|
32
|
+
import { getMcpBaseUrl } from "../utils/constants";
|
|
33
|
+
import { isSteeringEnabled } from "../utils/steering-enabled";
|
|
34
|
+
|
|
35
|
+
const FETCH_TIMEOUT_MS = 5_000;
|
|
36
|
+
|
|
37
|
+
export interface CodexHookMessage {
|
|
38
|
+
hook_event_name?: string;
|
|
39
|
+
stop_hook_active?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface CodexHookConfig {
|
|
43
|
+
apiUrl: string;
|
|
44
|
+
apiKey: string;
|
|
45
|
+
agentId: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function resolveCodexHookConfig(
|
|
49
|
+
env: Record<string, string | undefined> = process.env,
|
|
50
|
+
): CodexHookConfig | null {
|
|
51
|
+
const agentId = env.AGENT_ID;
|
|
52
|
+
const apiKey = getApiKey(env);
|
|
53
|
+
const apiUrl = getMcpBaseUrl();
|
|
54
|
+
if (!agentId || !apiKey || !apiUrl) return null;
|
|
55
|
+
return { apiUrl, apiKey, agentId };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Fetch the agent's pending steering rows, mark each `delivered`, and return
|
|
60
|
+
* the rendered envelopes of the ones whose delivered-POST succeeded.
|
|
61
|
+
*
|
|
62
|
+
* Any error yields an empty result — a hook must never take the harness down
|
|
63
|
+
* over steering, and `pending` rows are retried on the next lifecycle event.
|
|
64
|
+
*/
|
|
65
|
+
export async function collectDeliverableSteering(
|
|
66
|
+
config: CodexHookConfig,
|
|
67
|
+
fetchImpl: typeof fetch = fetch,
|
|
68
|
+
): Promise<string[]> {
|
|
69
|
+
const headers = {
|
|
70
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
71
|
+
"X-Agent-ID": config.agentId,
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let messages: SteeringMessage[];
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetchImpl(`${config.apiUrl}/api/steering-messages`, {
|
|
77
|
+
headers,
|
|
78
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) return [];
|
|
81
|
+
const data = (await response.json()) as { messages?: SteeringMessage[] };
|
|
82
|
+
messages = (data.messages ?? []).filter((message) => message.status === "pending");
|
|
83
|
+
} catch {
|
|
84
|
+
return [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const delivered: string[] = [];
|
|
88
|
+
for (const message of messages) {
|
|
89
|
+
// Mark delivered BEFORE injecting: if the POST fails the row stays
|
|
90
|
+
// pending and is retried later; the inverse order could inject the same
|
|
91
|
+
// message on every tool call ("no polluting").
|
|
92
|
+
try {
|
|
93
|
+
const report = await fetchImpl(
|
|
94
|
+
`${config.apiUrl}/api/steering-messages/${encodeURIComponent(message.id)}/delivered`,
|
|
95
|
+
{
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
98
|
+
body: JSON.stringify({ mode: "queue" }),
|
|
99
|
+
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
100
|
+
},
|
|
101
|
+
);
|
|
102
|
+
if (!report.ok) continue;
|
|
103
|
+
} catch {
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
delivered.push(await renderSteeringDelivery(message.id, message.body));
|
|
107
|
+
}
|
|
108
|
+
return delivered;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Handle one codex hook event. Returns the JSON object to print to stdout
|
|
113
|
+
* (codex reads hook responses from stdout), or null for no output.
|
|
114
|
+
*/
|
|
115
|
+
export async function handleCodexHookEvent(
|
|
116
|
+
msg: CodexHookMessage,
|
|
117
|
+
config: CodexHookConfig | null,
|
|
118
|
+
env: Record<string, string | undefined> = process.env,
|
|
119
|
+
fetchImpl: typeof fetch = fetch,
|
|
120
|
+
): Promise<Record<string, unknown> | null> {
|
|
121
|
+
const event = msg.hook_event_name;
|
|
122
|
+
if (!event || !config || !isSteeringEnabled(env)) return null;
|
|
123
|
+
if (event !== "SessionStart" && event !== "PostToolUse" && event !== "Stop") return null;
|
|
124
|
+
|
|
125
|
+
const envelopes = await collectDeliverableSteering(config, fetchImpl);
|
|
126
|
+
if (envelopes.length === 0) return null;
|
|
127
|
+
const additionalContext = envelopes.join("\n\n");
|
|
128
|
+
|
|
129
|
+
if (event === "Stop") {
|
|
130
|
+
// Delivered rows are marked before this block fires, so the next Stop
|
|
131
|
+
// finds nothing pending and lets the session end — no block loop.
|
|
132
|
+
return {
|
|
133
|
+
decision: "block",
|
|
134
|
+
reason: `Steering message(s) arrived before you finished:\n\n${additionalContext}`,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
hookSpecificOutput: { hookEventName: event, additionalContext },
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** stdin/stdout entry point used by the `codex-hook` CLI command. */
|
|
144
|
+
export async function handleCodexHook(): Promise<void> {
|
|
145
|
+
let msg: CodexHookMessage;
|
|
146
|
+
try {
|
|
147
|
+
msg = (await Bun.stdin.json()) as CodexHookMessage;
|
|
148
|
+
} catch {
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
try {
|
|
152
|
+
const output = await handleCodexHookEvent(msg, resolveCodexHookConfig());
|
|
153
|
+
if (output) console.log(JSON.stringify(output));
|
|
154
|
+
} catch {
|
|
155
|
+
// Never fail the harness over steering delivery.
|
|
156
|
+
}
|
|
157
|
+
}
|
package/src/http/kv.ts
CHANGED
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
listKv,
|
|
12
12
|
upsertKv,
|
|
13
13
|
} from "../be/db";
|
|
14
|
+
import { mcpOverflowAuthError } from "../kv-overflow";
|
|
14
15
|
import { can } from "../rbac";
|
|
15
16
|
import { agentContextKey, pageContextKey } from "../tasks/context-key";
|
|
16
17
|
import { KvKeySchema, KvNamespaceSchema, KvValueTypeSchema } from "../types";
|
|
@@ -321,6 +322,10 @@ function authorizeWrite(
|
|
|
321
322
|
namespace: string,
|
|
322
323
|
ctx: AuthCtx,
|
|
323
324
|
): { status: number; message: string } | null {
|
|
325
|
+
const overflowAuthErr = mcpOverflowAuthError(namespace, ctx.callerAgentId);
|
|
326
|
+
if (overflowAuthErr) {
|
|
327
|
+
return { status: 403, message: overflowAuthErr };
|
|
328
|
+
}
|
|
324
329
|
if (namespace.startsWith("task:page:")) {
|
|
325
330
|
if (!ctx.hasPageHeader) {
|
|
326
331
|
return { status: 403, message: "task:page:* writes require a page-proxy request" };
|
|
@@ -349,6 +354,14 @@ function authorizeWrite(
|
|
|
349
354
|
return null;
|
|
350
355
|
}
|
|
351
356
|
|
|
357
|
+
function authorizeRead(
|
|
358
|
+
namespace: string,
|
|
359
|
+
ctx: AuthCtx,
|
|
360
|
+
): { status: number; message: string } | null {
|
|
361
|
+
const overflowAuthErr = mcpOverflowAuthError(namespace, ctx.callerAgentId);
|
|
362
|
+
return overflowAuthErr ? { status: 403, message: overflowAuthErr } : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
352
365
|
function encodeValueOrError(
|
|
353
366
|
res: ServerResponse,
|
|
354
367
|
value: unknown,
|
|
@@ -435,7 +448,7 @@ export async function handleKv(
|
|
|
435
448
|
if (!ns) return true;
|
|
436
449
|
const key = decodeKvSegment(res, parsed.params.key, "key");
|
|
437
450
|
if (!key) return true;
|
|
438
|
-
return sendGet(res, ns, key);
|
|
451
|
+
return sendGet(req, res, ns, key);
|
|
439
452
|
}
|
|
440
453
|
if (putKvExplicit.match(req.method, pathSegments)) {
|
|
441
454
|
if (enforceContentLengthCap(req, res, MAX_KV_BODY_BYTES) === BODY_TOO_LARGE) return true;
|
|
@@ -461,7 +474,7 @@ export async function handleKv(
|
|
|
461
474
|
if (!parsed) return true;
|
|
462
475
|
const ns = decodeKvSegment(res, parsed.params.namespace, "namespace");
|
|
463
476
|
if (!ns) return true;
|
|
464
|
-
return sendList(res, ns, parsed.query);
|
|
477
|
+
return sendList(req, res, ns, parsed.query);
|
|
465
478
|
}
|
|
466
479
|
|
|
467
480
|
// ── Header-resolved variants ──────────────────────────────────────────────
|
|
@@ -475,7 +488,7 @@ export async function handleKv(
|
|
|
475
488
|
}
|
|
476
489
|
const key = decodeKvSegment(res, parsed.params.key, "key");
|
|
477
490
|
if (!key) return true;
|
|
478
|
-
return sendGet(res, ns, key);
|
|
491
|
+
return sendGet(req, res, ns, key);
|
|
479
492
|
}
|
|
480
493
|
if (putKvHeader.match(req.method, pathSegments)) {
|
|
481
494
|
if (enforceContentLengthCap(req, res, MAX_KV_BODY_BYTES) === BODY_TOO_LARGE) return true;
|
|
@@ -510,16 +523,16 @@ export async function handleKv(
|
|
|
510
523
|
jsonError(res, "namespace is required (pass X-Source-Task-Id or X-Agent-ID)", 400);
|
|
511
524
|
return true;
|
|
512
525
|
}
|
|
513
|
-
return sendList(res, ns, parsed.query);
|
|
526
|
+
return sendList(req, res, ns, parsed.query);
|
|
514
527
|
}
|
|
515
528
|
|
|
516
529
|
return false;
|
|
517
530
|
}
|
|
518
531
|
|
|
519
532
|
/**
|
|
520
|
-
* Reads
|
|
521
|
-
*
|
|
522
|
-
*
|
|
533
|
+
* Reads resolve from headers. Ordinary KV namespaces remain cross-agent
|
|
534
|
+
* readable by design; `sendGet`/`sendList` separately enforce private
|
|
535
|
+
* ownership for the internal `mcp:overflow:<agentId>` namespace family.
|
|
523
536
|
*/
|
|
524
537
|
function resolveNamespaceForRead(req: IncomingMessage): string | null {
|
|
525
538
|
return resolveNamespaceFromHeaders(req);
|
|
@@ -591,7 +604,17 @@ async function handleIncr(
|
|
|
591
604
|
return true;
|
|
592
605
|
}
|
|
593
606
|
|
|
594
|
-
function sendGet(
|
|
607
|
+
function sendGet(
|
|
608
|
+
req: IncomingMessage,
|
|
609
|
+
res: ServerResponse,
|
|
610
|
+
namespace: string,
|
|
611
|
+
key: string,
|
|
612
|
+
): boolean {
|
|
613
|
+
const authErr = authorizeRead(namespace, buildAuthCtx(req));
|
|
614
|
+
if (authErr) {
|
|
615
|
+
jsonError(res, authErr.message, authErr.status);
|
|
616
|
+
return true;
|
|
617
|
+
}
|
|
595
618
|
const entry = getKv(namespace, key);
|
|
596
619
|
if (!entry) {
|
|
597
620
|
jsonError(res, "not found", 404);
|
|
@@ -661,10 +684,16 @@ function sendDelete(
|
|
|
661
684
|
}
|
|
662
685
|
|
|
663
686
|
function sendList(
|
|
687
|
+
req: IncomingMessage,
|
|
664
688
|
res: ServerResponse,
|
|
665
689
|
namespace: string,
|
|
666
690
|
query: z.infer<typeof kvListQuerySchema>,
|
|
667
691
|
): boolean {
|
|
692
|
+
const authErr = authorizeRead(namespace, buildAuthCtx(req));
|
|
693
|
+
if (authErr) {
|
|
694
|
+
jsonError(res, authErr.message, authErr.status);
|
|
695
|
+
return true;
|
|
696
|
+
}
|
|
668
697
|
const limit = Math.min(query.limit ?? 100, MAX_KV_LIST_LIMIT);
|
|
669
698
|
const offset = query.offset ?? 0;
|
|
670
699
|
const prefix = query.prefix && query.prefix.length > 0 ? query.prefix : undefined;
|
package/src/http/mcp-bridge.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { createServer } from "@/server";
|
|
5
5
|
import { isMcpToolAllowedForScripts } from "../scripts-runtime/sdk-allowlist";
|
|
6
|
+
import { markScriptSdkRequestOrigin } from "../tools/utils";
|
|
6
7
|
import { route } from "./route-def";
|
|
7
8
|
import { json, jsonError } from "./utils";
|
|
8
9
|
|
|
@@ -83,7 +84,7 @@ export async function handleMcpBridge(
|
|
|
83
84
|
? req.headers["x-source-task-id"][0]
|
|
84
85
|
: (req.headers["x-source-task-id"] as string | undefined);
|
|
85
86
|
|
|
86
|
-
const extra = {
|
|
87
|
+
const extra = markScriptSdkRequestOrigin({
|
|
87
88
|
sessionId: "mcp-bridge",
|
|
88
89
|
requestInfo: {
|
|
89
90
|
headers: {
|
|
@@ -91,7 +92,7 @@ export async function handleMcpBridge(
|
|
|
91
92
|
...(sourceTaskId ? { "x-source-task-id": sourceTaskId } : {}),
|
|
92
93
|
},
|
|
93
94
|
},
|
|
94
|
-
};
|
|
95
|
+
});
|
|
95
96
|
|
|
96
97
|
try {
|
|
97
98
|
const result = tool.inputSchema
|