agent-relay-server 0.125.0 → 0.126.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/docs/openapi.json +1 -1
- package/package.json +9 -5
- package/public/assets/{MessageBubble-BYeYFteG.js → MessageBubble-B6YgulnX.js} +2 -2
- package/public/assets/{MessageBubble-BYeYFteG.js.map → MessageBubble-B6YgulnX.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-iAXvkZKW.js → PlanGraphPanel--DNxHn1T.js} +2 -2
- package/public/assets/{PlanGraphPanel-iAXvkZKW.js.map → PlanGraphPanel--DNxHn1T.js.map} +1 -1
- package/public/assets/{activity-Du9SRJt5.js → activity-Fo5aLYD6.js} +2 -2
- package/public/assets/{activity-Du9SRJt5.js.map → activity-Fo5aLYD6.js.map} +1 -1
- package/public/assets/{agent-profiles-DXDQnrlH.js → agent-profiles-C3p0xclG.js} +2 -2
- package/public/assets/{agent-profiles-DXDQnrlH.js.map → agent-profiles-C3p0xclG.js.map} +1 -1
- package/public/assets/{agent-type-icon-B3i0rndK.js → agent-type-icon-BmBR-9CH.js} +2 -2
- package/public/assets/{agent-type-icon-B3i0rndK.js.map → agent-type-icon-BmBR-9CH.js.map} +1 -1
- package/public/assets/{agents-CWOdKrxP.js → agents-_GBQyF5b.js} +2 -2
- package/public/assets/{agents-CWOdKrxP.js.map → agents-_GBQyF5b.js.map} +1 -1
- package/public/assets/{analytics-YcY59Hv9.js → analytics-BZtRycyL.js} +2 -2
- package/public/assets/{analytics-YcY59Hv9.js.map → analytics-BZtRycyL.js.map} +1 -1
- package/public/assets/{automation-CItTse7F.js → automation-Cf_Ewi9f.js} +2 -2
- package/public/assets/{automation-CItTse7F.js.map → automation-Cf_Ewi9f.js.map} +1 -1
- package/public/assets/{badge-XEru6wto.js → badge-BCvp7LK3.js} +2 -2
- package/public/assets/{badge-XEru6wto.js.map → badge-BCvp7LK3.js.map} +1 -1
- package/public/assets/{branch-state-badge-QjTktTYj.js → branch-state-badge-Cscjqf-E.js} +2 -2
- package/public/assets/{branch-state-badge-QjTktTYj.js.map → branch-state-badge-Cscjqf-E.js.map} +1 -1
- package/public/assets/{button-C3CQSr4p.js → button-DyHjsHY3.js} +2 -2
- package/public/assets/{button-C3CQSr4p.js.map → button-DyHjsHY3.js.map} +1 -1
- package/public/assets/{card-C5XTIPvq.js → card-BoqYxpVW.js} +2 -2
- package/public/assets/{card-C5XTIPvq.js.map → card-BoqYxpVW.js.map} +1 -1
- package/public/assets/{channels-BSFTsbhT.js → channels-DAtOnA6o.js} +2 -2
- package/public/assets/{channels-BSFTsbhT.js.map → channels-DAtOnA6o.js.map} +1 -1
- package/public/assets/chat-BdbIWZpQ.js +2 -0
- package/public/assets/chat-BdbIWZpQ.js.map +1 -0
- package/public/assets/{connectors-C7d5dlYz.js → connectors-B2grC-hl.js} +2 -2
- package/public/assets/{connectors-C7d5dlYz.js.map → connectors-B2grC-hl.js.map} +1 -1
- package/public/assets/{copy-button-Bil2CjzT.js → copy-button-C67FUG3E.js} +2 -2
- package/public/assets/{copy-button-Bil2CjzT.js.map → copy-button-C67FUG3E.js.map} +1 -1
- package/public/assets/{dist-V3wu3BHw.js → dist-BD4Xc13v.js} +2 -2
- package/public/assets/{dist-V3wu3BHw.js.map → dist-BD4Xc13v.js.map} +1 -1
- package/public/assets/{dist-C5z05Vkz.js → dist-BFDfbfNC.js} +2 -2
- package/public/assets/{dist-C5z05Vkz.js.map → dist-BFDfbfNC.js.map} +1 -1
- package/public/assets/{dist-CjyfQThq.js → dist-BJKxqIQd.js} +2 -2
- package/public/assets/{dist-CjyfQThq.js.map → dist-BJKxqIQd.js.map} +1 -1
- package/public/assets/{dist-B8tD9ZcR.js → dist-BNB3fTO-.js} +2 -2
- package/public/assets/{dist-B8tD9ZcR.js.map → dist-BNB3fTO-.js.map} +1 -1
- package/public/assets/{dist-CFtFIhu3.js → dist-CHKra2JV.js} +2 -2
- package/public/assets/{dist-CFtFIhu3.js.map → dist-CHKra2JV.js.map} +1 -1
- package/public/assets/{dist-r8wyUTkK.js → dist-CQMQ5iku.js} +2 -2
- package/public/assets/{dist-r8wyUTkK.js.map → dist-CQMQ5iku.js.map} +1 -1
- package/public/assets/{dist-DKmTXfpP.js → dist-C_VOBSaK.js} +2 -2
- package/public/assets/{dist-DKmTXfpP.js.map → dist-C_VOBSaK.js.map} +1 -1
- package/public/assets/{dist-CGaHpxnN.js → dist-D5w04z0l.js} +2 -2
- package/public/assets/{dist-CGaHpxnN.js.map → dist-D5w04z0l.js.map} +1 -1
- package/public/assets/{dist-4VwhnLtj.js → dist-DG75bvPp.js} +2 -2
- package/public/assets/{dist-4VwhnLtj.js.map → dist-DG75bvPp.js.map} +1 -1
- package/public/assets/{dist-CrUl2aQp.js → dist-G8YBD-wp.js} +2 -2
- package/public/assets/{dist-CrUl2aQp.js.map → dist-G8YBD-wp.js.map} +1 -1
- package/public/assets/{dist-BJaV2BD4.js → dist-f6MW74eX.js} +2 -2
- package/public/assets/{dist-BJaV2BD4.js.map → dist-f6MW74eX.js.map} +1 -1
- package/public/assets/{es2015-5DTJe35n.js → es2015-a6fysEVz.js} +2 -2
- package/public/assets/{es2015-5DTJe35n.js.map → es2015-a6fysEVz.js.map} +1 -1
- package/public/assets/{formatted-body-impl-DO-8giS6.js → formatted-body-impl-gOLBKeyW.js} +2 -2
- package/public/assets/{formatted-body-impl-DO-8giS6.js.map → formatted-body-impl-gOLBKeyW.js.map} +1 -1
- package/public/assets/formatted-body-jjhFXTap.js +3 -0
- package/public/assets/{formatted-body-BuLfVMeD.js.map → formatted-body-jjhFXTap.js.map} +1 -1
- package/public/assets/{index-De1tR7J4.js → index-CgH1BZwq.js} +6 -6
- package/public/assets/{index-De1tR7J4.js.map → index-CgH1BZwq.js.map} +1 -1
- package/public/assets/index-DHzXsQot.css +2 -0
- package/public/assets/{input-CXMbJ848.js → input-Rc4oWv4q.js} +2 -2
- package/public/assets/{input-CXMbJ848.js.map → input-Rc4oWv4q.js.map} +1 -1
- package/public/assets/{insights-Ci3VuA5Y.js → insights-CTW85An9.js} +2 -2
- package/public/assets/{insights-Ci3VuA5Y.js.map → insights-CTW85An9.js.map} +1 -1
- package/public/assets/{integrations-BboxWQQD.js → integrations-9c6IdRU4.js} +2 -2
- package/public/assets/{integrations-BboxWQQD.js.map → integrations-9c6IdRU4.js.map} +1 -1
- package/public/assets/{lib-BcUW1l-W.js → lib-BZ6WPg2t.js} +2 -2
- package/public/assets/{lib-BcUW1l-W.js.map → lib-BZ6WPg2t.js.map} +1 -1
- package/public/assets/lucide-react-NXHPIMsX.js +10 -0
- package/public/assets/lucide-react-NXHPIMsX.js.map +1 -0
- package/public/assets/{maintenance-CO1miJIJ.js → maintenance-D5PEygZz.js} +2 -2
- package/public/assets/{maintenance-CO1miJIJ.js.map → maintenance-D5PEygZz.js.map} +1 -1
- package/public/assets/{managed-agents-r4oYK3Fm.js → managed-agents-pkpAzZy8.js} +2 -2
- package/public/assets/{managed-agents-r4oYK3Fm.js.map → managed-agents-pkpAzZy8.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-5CCM9iyL.js → markdown-preview-impl-DMzFE-Lo.js} +2 -2
- package/public/assets/{markdown-preview-impl-5CCM9iyL.js.map → markdown-preview-impl-DMzFE-Lo.js.map} +1 -1
- package/public/assets/{memory-BJJS07W-.js → memory-BnquVYQz.js} +2 -2
- package/public/assets/{memory-BJJS07W-.js.map → memory-BnquVYQz.js.map} +1 -1
- package/public/assets/{messages-RT_dfEsL.js → messages-DgxLlndC.js} +2 -2
- package/public/assets/{messages-RT_dfEsL.js.map → messages-DgxLlndC.js.map} +1 -1
- package/public/assets/{orchestrators-8hyl8hW1.js → orchestrators-BaWxeAlK.js} +2 -2
- package/public/assets/{orchestrators-8hyl8hW1.js.map → orchestrators-BaWxeAlK.js.map} +1 -1
- package/public/assets/{overview-CUJ9QmW_.js → overview-Kvs-j2gg.js} +2 -2
- package/public/assets/{overview-CUJ9QmW_.js.map → overview-Kvs-j2gg.js.map} +1 -1
- package/public/assets/{pairs-CzkzMGZq.js → pairs-eDzbqcER.js} +2 -2
- package/public/assets/{pairs-CzkzMGZq.js.map → pairs-eDzbqcER.js.map} +1 -1
- package/public/assets/{projects-CAuLWBU5.js → projects-iDkxAGBW.js} +2 -2
- package/public/assets/{projects-CAuLWBU5.js.map → projects-iDkxAGBW.js.map} +1 -1
- package/public/assets/{prompt-settings-BnB90R7F.js → prompt-settings-CjcqBG_r.js} +2 -2
- package/public/assets/{prompt-settings-BnB90R7F.js.map → prompt-settings-CjcqBG_r.js.map} +1 -1
- package/public/assets/{react-dom-BQICbUuF.js → react-dom-8yaD9iJG.js} +2 -2
- package/public/assets/{react-dom-BQICbUuF.js.map → react-dom-8yaD9iJG.js.map} +1 -1
- package/public/assets/{registry-B8TInqru.js → registry-C1DSnjjg.js} +2 -2
- package/public/assets/{registry-B8TInqru.js.map → registry-C1DSnjjg.js.map} +1 -1
- package/public/assets/{security-DG_VVZUW.js → security-VbEe_jwm.js} +2 -2
- package/public/assets/{security-DG_VVZUW.js.map → security-VbEe_jwm.js.map} +1 -1
- package/public/assets/{select-BJ8G38_V.js → select-CVfwgfKl.js} +2 -2
- package/public/assets/{select-BJ8G38_V.js.map → select-CVfwgfKl.js.map} +1 -1
- package/public/assets/{settings-CK_T9z5w.js → settings-ujA9hZW1.js} +2 -2
- package/public/assets/{settings-CK_T9z5w.js.map → settings-ujA9hZW1.js.map} +1 -1
- package/public/assets/store-D-Frk_EC.js +9 -0
- package/public/assets/store-D-Frk_EC.js.map +1 -0
- package/public/assets/{task-chip-BHM-e3I8.js → task-chip-CeujtXYT.js} +2 -2
- package/public/assets/{task-chip-BHM-e3I8.js.map → task-chip-CeujtXYT.js.map} +1 -1
- package/public/assets/{tasks-Briui9Mp.js → tasks-BtbKaa-b.js} +2 -2
- package/public/assets/{tasks-Briui9Mp.js.map → tasks-BtbKaa-b.js.map} +1 -1
- package/public/assets/{teams-Ck4EVV3R.js → teams-CrVDQ99v.js} +2 -2
- package/public/assets/{teams-Ck4EVV3R.js.map → teams-CrVDQ99v.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-66ZjVN1v.js → terminal-viewer-impl-Aays_e7-.js} +2 -2
- package/public/assets/{terminal-viewer-impl-66ZjVN1v.js.map → terminal-viewer-impl-Aays_e7-.js.map} +1 -1
- package/public/assets/types-BqeSkWrv.js.map +1 -1
- package/public/assets/{use-keyboard-viewport-CAa7IQTm.js → use-keyboard-viewport-cGEmrGE7.js} +2 -2
- package/public/assets/{use-keyboard-viewport-CAa7IQTm.js.map → use-keyboard-viewport-cGEmrGE7.js.map} +1 -1
- package/public/assets/{user-avatar-BOy9o2xK.js → user-avatar-qbo-B3dQ.js} +2 -2
- package/public/assets/{user-avatar-BOy9o2xK.js.map → user-avatar-qbo-B3dQ.js.map} +1 -1
- package/public/assets/{work-queue-CnghFRG2.js → work-queue-DDSGZO4Z.js} +2 -2
- package/public/assets/{work-queue-CnghFRG2.js.map → work-queue-DDSGZO4Z.js.map} +1 -1
- package/public/assets/{workspaces-BzmQnzSm.js → workspaces-CoDz-iZd.js} +2 -2
- package/public/assets/{workspaces-BzmQnzSm.js.map → workspaces-CoDz-iZd.js.map} +1 -1
- package/public/index.html +24 -24
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/runner/src/adapters/codex-agent-message-capture.ts +38 -8
- package/runner/src/runner-core.ts +33 -13
- package/runner/src/runner-helpers.ts +31 -0
- package/src/db/messages.ts +38 -2
- package/src/routes/agent-sessions.ts +10 -7
- package/src/routes/orchestrator.ts +5 -2
- package/src/routes/settings.ts +31 -2
- package/src/services/restart-instructions.ts +36 -0
- package/src/settings/registry.ts +14 -3
- package/public/assets/chat-CRzFSFWe.js +0 -2
- package/public/assets/chat-CRzFSFWe.js.map +0 -1
- package/public/assets/formatted-body-BuLfVMeD.js +0 -3
- package/public/assets/index-C9X_0QBt.css +0 -2
- package/public/assets/lucide-react-CoU_Xts2.js +0 -10
- package/public/assets/lucide-react-CoU_Xts2.js.map +0 -1
- package/public/assets/store-Dl9Z8YUS.js +0 -9
- package/public/assets/store-Dl9Z8YUS.js.map +0 -1
package/public/index.html
CHANGED
|
@@ -12,36 +12,36 @@
|
|
|
12
12
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='6' fill='%2309090b'/%3E%3Ccircle cx='16' cy='16' r='4.5' fill='%2358a6ff'/%3E%3Ccircle cx='6' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='8' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='6' cy='24' r='2.5' fill='%233fb950'/%3E%3Ccircle cx='26' cy='24' r='2.5' fill='%233fb950'/%3E%3Cline x1='8' y1='9.5' x2='13' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='9.5' x2='19' y2='14' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='8' y1='22.5' x2='13' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3Cline x1='24' y1='22.5' x2='19' y2='18' stroke='%2330363d' stroke-width='1.5'/%3E%3C/svg%3E">
|
|
13
13
|
<link rel="manifest" href="manifest.webmanifest">
|
|
14
14
|
<link rel="apple-touch-icon" href="icons/agent-relay-192.png">
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-CgH1BZwq.js"></script>
|
|
16
16
|
<link rel="modulepreload" crossorigin href="./assets/chunk-CilyBKbf.js">
|
|
17
17
|
<link rel="modulepreload" crossorigin href="./assets/preload-helper-C_JMBXql.js">
|
|
18
|
-
<link rel="modulepreload" crossorigin href="./assets/lucide-react-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
21
|
-
<link rel="modulepreload" crossorigin href="./assets/react-dom-
|
|
22
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
23
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
25
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
18
|
+
<link rel="modulepreload" crossorigin href="./assets/lucide-react-NXHPIMsX.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-BFDfbfNC.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-BNB3fTO-.js">
|
|
21
|
+
<link rel="modulepreload" crossorigin href="./assets/react-dom-8yaD9iJG.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-BD4Xc13v.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-CHKra2JV.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-D5w04z0l.js">
|
|
25
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-C_VOBSaK.js">
|
|
26
26
|
<link rel="modulepreload" crossorigin href="./assets/dist-BiLuYU6G.js">
|
|
27
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
28
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
29
|
-
<link rel="modulepreload" crossorigin href="./assets/es2015-
|
|
30
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
31
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
32
|
-
<link rel="modulepreload" crossorigin href="./assets/dist-
|
|
27
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-f6MW74eX.js">
|
|
28
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-BJKxqIQd.js">
|
|
29
|
+
<link rel="modulepreload" crossorigin href="./assets/es2015-a6fysEVz.js">
|
|
30
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-CQMQ5iku.js">
|
|
31
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-DG75bvPp.js">
|
|
32
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-G8YBD-wp.js">
|
|
33
33
|
<link rel="modulepreload" crossorigin href="./assets/types-BqeSkWrv.js">
|
|
34
34
|
<link rel="modulepreload" crossorigin href="./assets/display-aTnwsqQh.js">
|
|
35
|
-
<link rel="modulepreload" crossorigin href="./assets/store-
|
|
36
|
-
<link rel="modulepreload" crossorigin href="./assets/agent-type-icon-
|
|
37
|
-
<link rel="modulepreload" crossorigin href="./assets/button-
|
|
38
|
-
<link rel="modulepreload" crossorigin href="./assets/copy-button-
|
|
39
|
-
<link rel="modulepreload" crossorigin href="./assets/formatted-body-
|
|
40
|
-
<link rel="modulepreload" crossorigin href="./assets/task-chip-
|
|
41
|
-
<link rel="modulepreload" crossorigin href="./assets/badge-
|
|
42
|
-
<link rel="modulepreload" crossorigin href="./assets/input-
|
|
35
|
+
<link rel="modulepreload" crossorigin href="./assets/store-D-Frk_EC.js">
|
|
36
|
+
<link rel="modulepreload" crossorigin href="./assets/agent-type-icon-BmBR-9CH.js">
|
|
37
|
+
<link rel="modulepreload" crossorigin href="./assets/button-DyHjsHY3.js">
|
|
38
|
+
<link rel="modulepreload" crossorigin href="./assets/copy-button-C67FUG3E.js">
|
|
39
|
+
<link rel="modulepreload" crossorigin href="./assets/formatted-body-jjhFXTap.js">
|
|
40
|
+
<link rel="modulepreload" crossorigin href="./assets/task-chip-CeujtXYT.js">
|
|
41
|
+
<link rel="modulepreload" crossorigin href="./assets/badge-BCvp7LK3.js">
|
|
42
|
+
<link rel="modulepreload" crossorigin href="./assets/input-Rc4oWv4q.js">
|
|
43
43
|
<link rel="modulepreload" crossorigin href="./assets/themes-DjSwmAZN.js">
|
|
44
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
44
|
+
<link rel="stylesheet" crossorigin href="./assets/index-DHzXsQot.css">
|
|
45
45
|
</head>
|
|
46
46
|
<body class="bg-background text-foreground antialiased">
|
|
47
47
|
<div id="root"></div>
|
|
@@ -45,17 +45,16 @@ export class CodexAgentMessageCapture {
|
|
|
45
45
|
this.currentSegment = [];
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
// Deterministic turn-final response: the
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// whichever segment they land in, but is not the sole guard against leading narration.
|
|
48
|
+
// Deterministic turn-final response: prefer the trailing structured report span, not a generic
|
|
49
|
+
// post-handoff coda. The original #1079/#1086 trailing-run rule correctly kept early narration
|
|
50
|
+
// out, but it over-trimmed Codex turns shaped "summary -> workspace ready -> Landed..." and made
|
|
51
|
+
// report-up deliver only the coda (#1136). We still drop leading narration; we just let structured
|
|
52
|
+
// report markers beat a terse final status line.
|
|
54
53
|
finish(emit: EmitAgentMessage): string | undefined {
|
|
55
54
|
this.closeSegment(emit);
|
|
56
|
-
const trailingSegment = this.segments
|
|
55
|
+
const trailingSegment = selectFinalResponseSegments(this.segments);
|
|
57
56
|
this.segments = [];
|
|
58
|
-
const text = trailingSegment ? segmentBody(
|
|
57
|
+
const text = trailingSegment ? trailingSegment.map(segmentBody).join("\n\n").trim() : undefined;
|
|
59
58
|
return truncateCapturedResponse(text);
|
|
60
59
|
}
|
|
61
60
|
|
|
@@ -105,6 +104,37 @@ function segmentBody(segment: ResponseEntry[]): string {
|
|
|
105
104
|
return segment.map((entry) => entry.body).join("\n\n").trim();
|
|
106
105
|
}
|
|
107
106
|
|
|
107
|
+
function selectFinalResponseSegments(segments: ResponseEntry[][]): ResponseEntry[][] | undefined {
|
|
108
|
+
const bodies = segments.map(segmentBody).filter(Boolean);
|
|
109
|
+
if (!bodies.length) return undefined;
|
|
110
|
+
let end = bodies.length - 1;
|
|
111
|
+
while (end > 0 && isGenericFinalCoda(bodies[end]!) && bodies.slice(0, end).some(isSubstantiveReportSegment)) end--;
|
|
112
|
+
const firstReport = bodies.slice(0, end + 1).findIndex(isReportBoundarySegment);
|
|
113
|
+
if (firstReport >= 0) return segments.slice(firstReport, end + 1);
|
|
114
|
+
for (let i = end; i >= 0; i--) {
|
|
115
|
+
if (isSubstantiveReportSegment(bodies[i]!)) return [segments[i]!];
|
|
116
|
+
}
|
|
117
|
+
return [segments[end]!];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const REPORT_BOUNDARY = /(^|\n)(#{1,6}\s|\*\*(summary|what changed|rca|fix|tests?|gates?|verification|risk)\b|(?:summary|what changed|rca|fix|tests?|gates?|verification|risk):)/i;
|
|
121
|
+
const REPORT_STRUCTURE = /(^|\n)\s*[-*]\s+\S|`[^`]+`|\b(typecheck|bun test|tests? pass(?:ed)?|gates? green|rca|verified|implemented)\b/i;
|
|
122
|
+
const GENERIC_FINAL_CODA = /^(?:done|landed(?: on main)?|committed|merged|ready|no further action|nothing further|all set)\b|(?:\bno further action needed\b|\bnothing further needed\b)$/i;
|
|
123
|
+
|
|
124
|
+
function isReportBoundarySegment(body: string): boolean {
|
|
125
|
+
return REPORT_BOUNDARY.test(body.trim());
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function isSubstantiveReportSegment(body: string): boolean {
|
|
129
|
+
const trimmed = body.trim();
|
|
130
|
+
return !isGenericFinalCoda(trimmed) && (trimmed.length >= 120 || isReportBoundarySegment(trimmed) || REPORT_STRUCTURE.test(trimmed));
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function isGenericFinalCoda(body: string): boolean {
|
|
134
|
+
const trimmed = body.trim();
|
|
135
|
+
return trimmed.length <= 180 && GENERIC_FINAL_CODA.test(trimmed) && !isReportBoundarySegment(trimmed);
|
|
136
|
+
}
|
|
137
|
+
|
|
108
138
|
function truncateCapturedResponse(text: string | undefined): string | undefined {
|
|
109
139
|
if (!text || text.length <= MAX_CAPTURED_RESPONSE_CHARS) return text;
|
|
110
140
|
return `${text.slice(0, MAX_CAPTURED_RESPONSE_CHARS).trimEnd()}\n\n${TRUNCATION_MARKER_PREFIX} showing first ${MAX_CAPTURED_RESPONSE_CHARS} of ${text.length} chars]`;
|
|
@@ -51,7 +51,7 @@ import {
|
|
|
51
51
|
runnerAgentStatus,
|
|
52
52
|
runnerBusErrorAction,
|
|
53
53
|
runnerMessageMatches,
|
|
54
|
-
runnerMessageShouldDirtyObligations,
|
|
54
|
+
runnerMessageShouldDirtyObligations, restartOptionsFromCommand,
|
|
55
55
|
runnerShouldResolveProviderExit,
|
|
56
56
|
runnerShouldRestartUnexpectedProviderExit,
|
|
57
57
|
runtimeProviderCapabilities,
|
|
@@ -60,7 +60,7 @@ import {
|
|
|
60
60
|
shouldLogDeliveryFailure,
|
|
61
61
|
stripRunnerClaimedGuidance,
|
|
62
62
|
taskIdFromMessage,
|
|
63
|
-
type ProbeModelInfo,
|
|
63
|
+
type ProbeModelInfo, type RestartProviderOptions,
|
|
64
64
|
} from "./runner-helpers";
|
|
65
65
|
|
|
66
66
|
// Pre-destroy work is best-effort and must never hang teardown. Capping it keeps a slow
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
// short so a wedged/down server can't stall an operator-requested shutdown for long; a
|
|
70
70
|
// row that still can't land is logged, not silently dropped.
|
|
71
71
|
const OUTBOX_FLUSH_TIMEOUT_MS = 3_000;
|
|
72
|
+
const STALE_PROVIDER_TURN_BUSY_MS = 10 * 60_000;
|
|
72
73
|
|
|
73
74
|
interface RunnerOptions {
|
|
74
75
|
provider: string;
|
|
@@ -535,7 +536,7 @@ export class AgentRunner {
|
|
|
535
536
|
return mode === "isolated";
|
|
536
537
|
}
|
|
537
538
|
|
|
538
|
-
private async spawnProvider(opts:
|
|
539
|
+
private async spawnProvider(opts: RestartProviderOptions = {}): Promise<ManagedProcess> {
|
|
539
540
|
this.providerSessionId = crypto.randomUUID();
|
|
540
541
|
this.lastTranscriptPath = undefined;
|
|
541
542
|
const includeProviderGlobals = profileUsesProviderHostGlobals(this.options);
|
|
@@ -586,7 +587,7 @@ export class AgentRunner {
|
|
|
586
587
|
...(this.options.profile ? { profile: this.options.profile } : {}),
|
|
587
588
|
...(this.options.agentProfile ? { agentProfile: this.options.agentProfile } : {}),
|
|
588
589
|
...(this.options.prompt && !opts.resumeId && !opts.suppressPrompt ? { prompt: this.options.prompt } : {}),
|
|
589
|
-
...(this.options.systemPromptAppend ? { systemPromptAppend: this.options.systemPromptAppend } : {}),
|
|
590
|
+
...((opts.systemPromptAppend ?? this.options.systemPromptAppend) ? { systemPromptAppend: opts.systemPromptAppend ?? this.options.systemPromptAppend } : {}),
|
|
590
591
|
...(this.options.tmuxSession ? { tmuxSession: this.options.tmuxSession } : {}),
|
|
591
592
|
providerArgs: opts.resumeId ? [...this.options.providerArgs, "--resume", opts.resumeId] : this.options.providerArgs,
|
|
592
593
|
providerConfig: this.options.providerConfig,
|
|
@@ -604,7 +605,7 @@ export class AgentRunner {
|
|
|
604
605
|
};
|
|
605
606
|
const launchSeededPrompt = this.options.adapter.seedsInitialPromptAtLaunch && !opts.resumeId && !opts.suppressPrompt ? this.options.prompt?.trim() : ""; if (launchSeededPrompt) this.recordInjectedPrompt(launchSeededPrompt);
|
|
606
607
|
const managedProcess = await this.options.adapter.spawn(config);
|
|
607
|
-
const injectionEvents = runnerLaunchInjectionSessionEvents({ carried: this.options.relayInjectionEvents, provider: this.options.provider, config, providerConfig: this.options.providerConfig, agentId: this.agentId, providerSessionId: this.providerSessionId });
|
|
608
|
+
const injectionEvents = runnerLaunchInjectionSessionEvents({ carried: opts.relayInjectionEvents ?? this.options.relayInjectionEvents, provider: this.options.provider, config, providerConfig: this.options.providerConfig, agentId: this.agentId, providerSessionId: this.providerSessionId });
|
|
608
609
|
for (const event of injectionEvents) {
|
|
609
610
|
this.publishSessionEvent(event);
|
|
610
611
|
}
|
|
@@ -777,7 +778,7 @@ export class AgentRunner {
|
|
|
777
778
|
else this.lifecycleAction = undefined;
|
|
778
779
|
this.publishStatus();
|
|
779
780
|
let providerResult: Record<string, unknown> | void = undefined;
|
|
780
|
-
if (type === "agent.restart") await this.restartProvider();
|
|
781
|
+
if (type === "agent.restart") await this.restartProvider(restartOptionsFromCommand(params));
|
|
781
782
|
else if (type === "agent.reconnect") this.publishStatus();
|
|
782
783
|
else if (type === "agent.compact") {
|
|
783
784
|
if (!this.options.adapter.compact || !this.process) throw new Error("provider does not support compact");
|
|
@@ -927,7 +928,7 @@ export class AgentRunner {
|
|
|
927
928
|
return attachmentText ? `${body}\n\n${attachmentText}` : body;
|
|
928
929
|
}
|
|
929
930
|
|
|
930
|
-
private async restartProvider(opts:
|
|
931
|
+
private async restartProvider(opts: RestartProviderOptions = {}): Promise<void> {
|
|
931
932
|
this.restartInProgress = true;
|
|
932
933
|
try {
|
|
933
934
|
if (this.process) {
|
|
@@ -1204,7 +1205,7 @@ export class AgentRunner {
|
|
|
1204
1205
|
const providerSessionId = typeof update === "string" ? undefined : update.providerSessionId;
|
|
1205
1206
|
if (providerSessionId && providerSessionId !== this.providerSessionId) return;
|
|
1206
1207
|
const reason = typeof update === "string" ? "provider-turn" : update.reason ?? "provider-turn";
|
|
1207
|
-
|
|
1208
|
+
let id = typeof update === "string" ? reason : update.id ?? reason;
|
|
1208
1209
|
if (typeof update !== "string" && update.timeline) {
|
|
1209
1210
|
this.pendingTimelineEvent = {
|
|
1210
1211
|
status: update.timeline.status,
|
|
@@ -1300,8 +1301,10 @@ export class AgentRunner {
|
|
|
1300
1301
|
this.compactionMidTurn = false;
|
|
1301
1302
|
this.sessionLog(`turn started (turn ${this.currentTurnId})`);
|
|
1302
1303
|
}
|
|
1304
|
+
id = this.currentTurnId;
|
|
1303
1305
|
this.busyReconciler.arm();
|
|
1304
1306
|
} else if (status === "idle" && reason === "provider-turn") {
|
|
1307
|
+
id = typeof update !== "string" && update.id ? update.id : this.currentTurnId ?? reason;
|
|
1305
1308
|
if (this.currentTurnId) { this.completedProviderTurns += 1; this.sessionLog(`turn ended via provider idle (turn ${this.currentTurnId})`); }
|
|
1306
1309
|
this.currentTurnId = undefined;
|
|
1307
1310
|
this.currentTurnStartedAt = undefined;
|
|
@@ -1346,7 +1349,8 @@ export class AgentRunner {
|
|
|
1346
1349
|
}
|
|
1347
1350
|
} else if (status === "idle") {
|
|
1348
1351
|
this.claims.clearTerminalStatus();
|
|
1349
|
-
this.claims.
|
|
1352
|
+
if (reason === "provider-turn") this.claims.clearWorkKind("provider-turn");
|
|
1353
|
+
else this.claims.finishWork(reason, id);
|
|
1350
1354
|
if (reason === "provider-turn") void this.completeObservedTaskClaims();
|
|
1351
1355
|
}
|
|
1352
1356
|
else if (status === "offline" || status === "error") this.claims.setTerminalStatus(status);
|
|
@@ -2007,15 +2011,25 @@ export class AgentRunner {
|
|
|
2007
2011
|
});
|
|
2008
2012
|
}
|
|
2009
2013
|
|
|
2014
|
+
private isStaleProviderTurnOnlyBusy(
|
|
2015
|
+
status: SemanticStatus,
|
|
2016
|
+
activeWork: Array<{ kind: string }>,
|
|
2017
|
+
busyReasons: string[],
|
|
2018
|
+
liveness: { hasLiveWork: boolean; lastProgressAt: number },
|
|
2019
|
+
): boolean {
|
|
2020
|
+
if (status !== "busy") return false;
|
|
2021
|
+
if (activeWork.length === 0 || activeWork.some((work) => work.kind !== "provider-turn")) return false;
|
|
2022
|
+
if (busyReasons.some((reason) => reason !== "provider-turn")) return false;
|
|
2023
|
+
if (liveness.hasLiveWork || !liveness.lastProgressAt) return false;
|
|
2024
|
+
return Date.now() - liveness.lastProgressAt >= STALE_PROVIDER_TURN_BUSY_MS;
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2010
2027
|
private publishStatus(): void {
|
|
2011
2028
|
this.claims.expire();
|
|
2012
2029
|
const status = this.claims.currentStatus();
|
|
2013
|
-
const agentStatus = runnerAgentStatus(status);
|
|
2014
2030
|
const activeWork = this.claims.activeWork();
|
|
2015
2031
|
const activeSubagents = activeWork.filter((item) => item.kind === "subagent");
|
|
2016
2032
|
const terminalFailure = this.terminalFailure;
|
|
2017
|
-
const providerState = terminalFailure?.providerState ?? this.rateLimitHold ?? providerStateFromActiveWork(activeWork);
|
|
2018
|
-
this.bus.setSemanticStatus(status === "offline" || status === "error" ? "idle" : status);
|
|
2019
2033
|
const timelineEvent = this.pendingTimelineEvent;
|
|
2020
2034
|
this.pendingTimelineEvent = undefined;
|
|
2021
2035
|
// §4.1 Runner↔provider liveness contract (#725 / #746): fold the raw transport/timeline/
|
|
@@ -2031,6 +2045,12 @@ export class AgentRunner {
|
|
|
2031
2045
|
const liveness = this.options.adapter.liveness
|
|
2032
2046
|
? this.options.adapter.liveness(livenessInputs)
|
|
2033
2047
|
: computeLivenessSignal(livenessInputs);
|
|
2048
|
+
const busyReasons = this.claims.reasons();
|
|
2049
|
+
const staleProviderTurnBusy = this.isStaleProviderTurnOnlyBusy(status, activeWork, busyReasons, liveness);
|
|
2050
|
+
const effectiveStatus = staleProviderTurnBusy ? "idle" : status;
|
|
2051
|
+
const agentStatus = runnerAgentStatus(effectiveStatus);
|
|
2052
|
+
const providerState = terminalFailure?.providerState ?? this.rateLimitHold ?? (staleProviderTurnBusy ? null : providerStateFromActiveWork(activeWork));
|
|
2053
|
+
this.bus.setSemanticStatus(effectiveStatus === "offline" || effectiveStatus === "error" ? "idle" : effectiveStatus);
|
|
2034
2054
|
this.bus.status({
|
|
2035
2055
|
agentStatus,
|
|
2036
2056
|
ready: agentStatus !== "offline" && !this.stopped,
|
|
@@ -2062,7 +2082,7 @@ export class AgentRunner {
|
|
|
2062
2082
|
? { lastError: `Claude provider exited; resume id ${this.terminalProviderExit.claudeResumeId} captured for manual recovery` }
|
|
2063
2083
|
: { lastError: "Claude provider exited; manual intervention required" }),
|
|
2064
2084
|
} : {}),
|
|
2065
|
-
busyReasons:
|
|
2085
|
+
busyReasons: staleProviderTurnBusy ? busyReasons.filter((reason) => reason !== "provider-turn") : busyReasons,
|
|
2066
2086
|
completedProviderTurns: this.completedProviderTurns,
|
|
2067
2087
|
activeWork,
|
|
2068
2088
|
activeSubagents,
|
|
@@ -5,6 +5,7 @@ import { getManifest } from "agent-relay-providers";
|
|
|
5
5
|
import type { BooleanCapabilityDefault, ProviderCapabilityCondition } from "agent-relay-providers";
|
|
6
6
|
import { targetMatchesAgentIdentity } from "agent-relay-sdk/agent-target";
|
|
7
7
|
import type { ProviderAdapter, ProviderConfig, RunnerSpawnConfig, SemanticStatus } from "./adapter";
|
|
8
|
+
import type { RunnerRelayInjectionEvent } from "./relay-injection-events";
|
|
8
9
|
import { agentProfileProjectionReport } from "./profile-projection";
|
|
9
10
|
import { assembleLaunch } from "./launch-assembly";
|
|
10
11
|
|
|
@@ -27,6 +28,13 @@ export interface ProbeModelInfo {
|
|
|
27
28
|
effort?: string;
|
|
28
29
|
}
|
|
29
30
|
|
|
31
|
+
export interface RestartProviderOptions {
|
|
32
|
+
resumeId?: string;
|
|
33
|
+
suppressPrompt?: boolean;
|
|
34
|
+
systemPromptAppend?: string;
|
|
35
|
+
relayInjectionEvents?: RunnerRelayInjectionEvent[];
|
|
36
|
+
}
|
|
37
|
+
|
|
30
38
|
export function runnerMessageMatches(message: Pick<Message, "to" | "resolvedToAgent">, target: {
|
|
31
39
|
agentId: string;
|
|
32
40
|
label?: string;
|
|
@@ -62,6 +70,29 @@ export function stripRunnerClaimedGuidance(body: string): string {
|
|
|
62
70
|
return body.replace(RUNNER_CLAIMED_GUIDANCE_RE, "");
|
|
63
71
|
}
|
|
64
72
|
|
|
73
|
+
const RELAY_INJECTION_CATEGORIES = new Set(["memory", "instructions", "tools", "comms", "knowledge", "context"]);
|
|
74
|
+
|
|
75
|
+
function relayInjectionEventsFromParam(value: unknown): RunnerRelayInjectionEvent[] | undefined {
|
|
76
|
+
if (!Array.isArray(value)) return undefined;
|
|
77
|
+
const events = value.flatMap((item): RunnerRelayInjectionEvent[] => {
|
|
78
|
+
if (!item || typeof item !== "object" || Array.isArray(item)) return [];
|
|
79
|
+
const record = item as Record<string, unknown>;
|
|
80
|
+
const detail = record.detail;
|
|
81
|
+
if (typeof record.category !== "string" || !RELAY_INJECTION_CATEGORIES.has(record.category)) return [];
|
|
82
|
+
if (typeof record.summary !== "string" || !record.summary.trim()) return [];
|
|
83
|
+
if (!detail || typeof detail !== "object" || Array.isArray(detail)) return [];
|
|
84
|
+
return [{ category: record.category as RunnerRelayInjectionEvent["category"], summary: record.summary, detail: detail as Record<string, unknown> }];
|
|
85
|
+
});
|
|
86
|
+
return events.length ? events : undefined;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function restartOptionsFromCommand(params: Record<string, unknown>): RestartProviderOptions {
|
|
90
|
+
return {
|
|
91
|
+
systemPromptAppend: typeof params.systemPromptAppend === "string" ? params.systemPromptAppend : undefined,
|
|
92
|
+
relayInjectionEvents: relayInjectionEventsFromParam(params.relayInjectionEvents),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
65
96
|
// Bound a runner's INITIAL relay registration. `connect` resolves on the first `registered`
|
|
66
97
|
// frame and otherwise loops forever, so a token the relay rejects (401 at the WS upgrade)
|
|
67
98
|
// hangs here silently. Race it against `timeoutMs`; on timeout, surface a fatal reason, tear
|
package/src/db/messages.ts
CHANGED
|
@@ -479,7 +479,7 @@ export function flushWorkFinalReports(childId: string, source: MessageWorkFinali
|
|
|
479
479
|
const recencyCutoff = markedAt - LINEAGE_FINAL_GROUP_RECENCY_MS;
|
|
480
480
|
const forwarded: Array<{ message: Message; created: boolean }> = [];
|
|
481
481
|
const finalizedOriginals: Message[] = [];
|
|
482
|
-
|
|
482
|
+
const held: Array<{ original: Message; session: Record<string, unknown> }> = [];
|
|
483
483
|
for (const messageId of queue) {
|
|
484
484
|
const original = getMessage(messageId);
|
|
485
485
|
if (!original) continue;
|
|
@@ -488,17 +488,53 @@ export function flushWorkFinalReports(childId: string, source: MessageWorkFinali
|
|
|
488
488
|
if ((original.occurredAt ?? original.createdAt) < recencyCutoff) continue;
|
|
489
489
|
const session = sessionMeta(original);
|
|
490
490
|
if (!session) continue;
|
|
491
|
+
held.push({ original, session });
|
|
492
|
+
}
|
|
493
|
+
const selected = source === "edge-fallback" ? selectEdgeFallbackLineageReports(held) : held;
|
|
494
|
+
let groupOrder = 0;
|
|
495
|
+
for (const { original, session } of selected) {
|
|
491
496
|
const finality: MessageWorkFinality = { windowId, source, groupOrder, markedAt };
|
|
492
497
|
const result = forwardLineageStreamCapture(original, session, "responses", child, parent, finality);
|
|
493
498
|
if (!result) continue;
|
|
494
499
|
groupOrder++;
|
|
495
500
|
forwarded.push(result);
|
|
496
|
-
const finalizedOriginal = setMessageWorkFinality(
|
|
501
|
+
const finalizedOriginal = setMessageWorkFinality(original.id, finality);
|
|
497
502
|
if (finalizedOriginal) finalizedOriginals.push(finalizedOriginal);
|
|
498
503
|
}
|
|
499
504
|
return { forwarded, finalizedOriginals };
|
|
500
505
|
}
|
|
501
506
|
|
|
507
|
+
function selectEdgeFallbackLineageReports<T extends { original: Message }>(held: T[]): T[] {
|
|
508
|
+
if (held.length <= 1) return held;
|
|
509
|
+
let bestIndex = -1;
|
|
510
|
+
let bestScore = -Infinity;
|
|
511
|
+
for (let i = 0; i < held.length; i++) {
|
|
512
|
+
const score = edgeFallbackReportScore(held[i]!.original.body);
|
|
513
|
+
if (score > bestScore || (score === bestScore && i > bestIndex)) {
|
|
514
|
+
bestScore = score;
|
|
515
|
+
bestIndex = i;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return bestScore > 0 && bestIndex >= 0 ? [held[bestIndex]!] : [held.at(-1)!];
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const EDGE_FALLBACK_REPORT_BOUNDARY = /(^|\n)(#{1,6}\s|\*\*(summary|what changed|rca|fix|tests?|gates?|verification|risk)\b|(?:summary|what changed|rca|fix|tests?|gates?|verification|risk):)/i;
|
|
522
|
+
const EDGE_FALLBACK_REPORT_STRUCTURE = /(^|\n)\s*[-*]\s+\S|`[^`]+`|\b(typecheck|bun test|tests? pass(?:ed)?|gates? green|rca|verified|implemented)\b/i;
|
|
523
|
+
const EDGE_FALLBACK_GENERIC_CODA = /^(?:done|landed(?: on main)?|committed|merged|ready|no further action|nothing further|all set)\b|(?:\bno further action needed\b|\bnothing further needed\b)$/i;
|
|
524
|
+
const EDGE_FALLBACK_NARRATION = /^(?:i(?:'|’)ll|i(?:'|’)m|let me|checking|running|reading|found\b|now\b)/i;
|
|
525
|
+
|
|
526
|
+
function edgeFallbackReportScore(body: string): number {
|
|
527
|
+
const trimmed = body.trim();
|
|
528
|
+
if (!trimmed) return -10;
|
|
529
|
+
if (trimmed.length <= 180 && EDGE_FALLBACK_GENERIC_CODA.test(trimmed) && !EDGE_FALLBACK_REPORT_BOUNDARY.test(trimmed)) return -5;
|
|
530
|
+
let score = 0;
|
|
531
|
+
if (trimmed.length >= 120) score += 2;
|
|
532
|
+
if (EDGE_FALLBACK_REPORT_BOUNDARY.test(trimmed)) score += 6;
|
|
533
|
+
if (EDGE_FALLBACK_REPORT_STRUCTURE.test(trimmed)) score += 3;
|
|
534
|
+
if (EDGE_FALLBACK_NARRATION.test(trimmed)) score -= 3;
|
|
535
|
+
return score;
|
|
536
|
+
}
|
|
537
|
+
|
|
502
538
|
/**
|
|
503
539
|
* #1040/#1041 — the persisted turn-final response captures needed to rebuild the in-memory held
|
|
504
540
|
* group after a relay restart. Bounded by a recency window (a report is only ever held while its
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { RELAY_TOKEN_HEADER, isRecord, isSpawnProvider } from "agent-relay-sdk";
|
|
3
3
|
import { VALID_AGENT_ACTIONS, agentControlActionIcon, auditEvent, authAuditMetadata, authorizeAgentRoute, authorizeRoute, canViewAgentRoute, cleanAttachmentRefs, dashboardAttribution, emitCommand, error, json, metaString, parseBody, requestUserAddress, requestUserId, spawnRequestId, type AgentControlAction, type Handler } from "./_shared";
|
|
4
4
|
import { arbitrateDrive, currentDriver, drivingMessage, releaseDrive, type DriveArbitration } from "../agent-drive-arbiter";
|
|
5
|
-
import { ValidationError, getAgent, getAgentTimeline, getOrchestrator, markReady, mergeAgentMeta,
|
|
5
|
+
import { ValidationError, getAgent, getAgentTimeline, getOrchestrator, markReady, mergeAgentMeta, sendMessage, withResolvedProvisioning } from "../db";
|
|
6
6
|
import { announcePresence, viewersForAgent } from "../dashboard-presence";
|
|
7
7
|
import { buildManagedSpawnParams } from "../managed-policy";
|
|
8
8
|
import { buildSpawnCommand, generateSpawnRequestId, resolveSpawnModelParams } from "../spawn-command";
|
|
@@ -19,8 +19,8 @@ import { ShutdownAuthError, ShutdownTargetError, shutdownAgent } from "../servic
|
|
|
19
19
|
import { type AgentCard, type SpawnApprovalMode } from "../types";
|
|
20
20
|
import { type ProviderEffort } from "agent-relay-sdk/provider-catalog";
|
|
21
21
|
import { getManifest } from "agent-relay-providers";
|
|
22
|
-
import { composeProfileSpawnInstructions } from "../project-instructions";
|
|
23
22
|
import { relayToken } from "../config";
|
|
23
|
+
import { restartInstructionParamsForAgent } from "../services/restart-instructions";
|
|
24
24
|
|
|
25
25
|
// 409 when a second human tries to drive an agent another user is already driving (#394). The
|
|
26
26
|
// blocked driver learns who holds the wheel so the UI can show "X is driving" + offer "request".
|
|
@@ -133,6 +133,7 @@ export function restartSpawnParamsForAgent(
|
|
|
133
133
|
const agentProfile = resolvedProfile ? applyDefaultSharedMcp(resolvedProfile) : undefined;
|
|
134
134
|
const workspaceMode = metaString(agent.meta, "workspaceMode") ?? "inherit";
|
|
135
135
|
const resolvedModel = resolveSpawnModelParams(provider, model, effort, { onError: "passthrough", skipDefaultWhenEmpty: true });
|
|
136
|
+
const projectedInstructions = restartInstructionParamsForAgent(agent, { spawnRequestId, cwd }) ?? {};
|
|
136
137
|
// #1052-v2 — a dashboard-spawned agent has NO spawn policy, so its restart skips the
|
|
137
138
|
// `buildManagedSpawnParams` policy branch above and lands here. Re-project the profile role
|
|
138
139
|
// charter / project knowledge / scoped memory (the SAME projection a fresh spawn gets) so the
|
|
@@ -152,8 +153,6 @@ export function restartSpawnParamsForAgent(
|
|
|
152
153
|
// params — the SAME field a fresh agent.spawn stashes it in, see spawn-agent.ts). The reader then
|
|
153
154
|
// recovers it from either the original agent.spawn row OR a later agent.restart row, so each
|
|
154
155
|
// generation has its own recoverable copy and the brief follows the agent across every rotation.
|
|
155
|
-
const callerAppend = readSpawnCallerSystemPromptAppend(spawnRequestId);
|
|
156
|
-
const composed = composeProfileSpawnInstructions({ cwd, operatorId: resolveAgentOwnership(agent.id).ownerUserId, ...(profileName ? { profileName } : {}), ...(agentProfile ? { profile: agentProfile } : {}), ...(callerAppend ? { callerSystemPromptAppend: callerAppend } : {}) });
|
|
157
156
|
const params = buildSpawnCommand({
|
|
158
157
|
provider,
|
|
159
158
|
modelParams: resolvedModel,
|
|
@@ -168,12 +167,12 @@ export function restartSpawnParamsForAgent(
|
|
|
168
167
|
approvalMode: approvalMode ?? "guarded",
|
|
169
168
|
permissionMode: approvalMode ?? "guarded",
|
|
170
169
|
providerArgs: providerArgs.length ? providerArgs : undefined,
|
|
171
|
-
...(
|
|
170
|
+
...(typeof projectedInstructions.systemPromptAppend === "string" ? { systemPromptAppend: projectedInstructions.systemPromptAppend } : {}),
|
|
172
171
|
// #1067 — re-persist the RAW brief (pre-composition) under this restart's fresh spawnRequestId so
|
|
173
172
|
// the NEXT restart, whose predecessor id is this fresh id, can recover it. Without this the brief
|
|
174
173
|
// survives only one restart (the original agent.spawn row is keyed on the very first id).
|
|
175
|
-
...(
|
|
176
|
-
...(
|
|
174
|
+
...(typeof projectedInstructions.callerSystemPromptAppend === "string" ? { callerSystemPromptAppend: projectedInstructions.callerSystemPromptAppend } : {}),
|
|
175
|
+
...(Array.isArray(projectedInstructions.relayInjectionEvents) ? { relayInjectionEvents: projectedInstructions.relayInjectionEvents } : {}),
|
|
177
176
|
policyName: policyName || undefined,
|
|
178
177
|
headless: true,
|
|
179
178
|
spawnRequestId: requestId,
|
|
@@ -298,6 +297,9 @@ export const postAgentAction: Handler = async (req, params) => {
|
|
|
298
297
|
const metaPolicyName = typeof agent.meta?.policyName === "string" ? agent.meta.policyName : undefined;
|
|
299
298
|
const metaSpawnRequestId = typeof agent.meta?.spawnRequestId === "string" ? agent.meta.spawnRequestId : undefined;
|
|
300
299
|
const resumeId = action === "resume" ? latestResumeIdForAgent(agent) : undefined;
|
|
300
|
+
const restartInstructions = action === "restart" || action === "resume"
|
|
301
|
+
? restartInstructionParamsForAgent(agent, { spawnRequestId: metaSpawnRequestId, cwd: metaString(agent.meta, "cwd") ?? orchestrator?.baseDir })
|
|
302
|
+
: undefined;
|
|
301
303
|
if (action === "resume" && !orchestrator) return error("no online orchestrator available to resume agent", 409);
|
|
302
304
|
if (action === "resume" && !resumeId) return error("no resume id recorded for agent", 422);
|
|
303
305
|
const denied = authorizeRoute(req, {
|
|
@@ -319,6 +321,7 @@ export const postAgentAction: Handler = async (req, params) => {
|
|
|
319
321
|
...(metaPolicyName ? { policyName: metaPolicyName } : {}),
|
|
320
322
|
...(metaSpawnRequestId ? { spawnRequestId: metaSpawnRequestId } : {}),
|
|
321
323
|
...(action === "restart" || action === "resume" ? { restartSpawn: restartSpawnParamsForAgent(agent, orchestrator, metaPolicyName, metaSpawnRequestId, { resumeId }) } : {}),
|
|
324
|
+
...(restartInstructions ?? {}),
|
|
322
325
|
...(action === "compact" ? compactMemoryParams(agent) : {}),
|
|
323
326
|
...(resumeId ? { claudeResumeId: resumeId } : {}),
|
|
324
327
|
requestedBy: "dashboard",
|
|
@@ -4,7 +4,7 @@ import { CONTRACT_VERSIONS, parseRuntimeCapabilities, parseRuntimeContracts, par
|
|
|
4
4
|
import { notifyAgentOffline } from "../agent-lifecycle-events";
|
|
5
5
|
import { diagnoseAgentDeath } from "../agent-death-diagnosis";
|
|
6
6
|
import { VERSION } from "../config";
|
|
7
|
-
import { ValidationError, deleteOrchestrator, getOrchestrator, listOrchestrators, orchestratorHeartbeat, recordAgentExitDiagnostics, setOrchestratorUpgradeState, updateManagedAgents, upsertOrchestrator } from "../db";
|
|
7
|
+
import { ValidationError, deleteOrchestrator, getAgent, getOrchestrator, listOrchestrators, orchestratorHeartbeat, recordAgentExitDiagnostics, setOrchestratorUpgradeState, updateManagedAgents, upsertOrchestrator } from "../db";
|
|
8
8
|
import { auditEvent, authAuditMetadata, authorizeRoute, cleanJsonArray, cleanSafeNumber, dashboardAttribution, emitCommand, error, isRootCredentialRequest, json, parseBody, spawnRequestId, type Handler } from "./_shared";
|
|
9
9
|
import { cleanMeta, cleanString, cleanStringArray, cleanWorkspaceMetadata, optionalEnum } from "../validation";
|
|
10
10
|
import { createCommand, updateCommand } from "../commands-db";
|
|
@@ -13,6 +13,7 @@ import { getLifecycleManager } from "../lifecycle-manager";
|
|
|
13
13
|
import { issueOrchestratorRuntimeToken } from "../runtime-tokens";
|
|
14
14
|
import { isOrchestratorRegistrationAuthorized } from "../security";
|
|
15
15
|
import { type ManagedAgent, type ManagedSessionExitDiagnostics, type OrchestratorRuntimeInput, type RegisterOrchestratorInput, type SpawnApprovalMode, type SpawnProvider } from "../types";
|
|
16
|
+
import { restartSpawnParamsForAgent } from "./agent-sessions";
|
|
16
17
|
|
|
17
18
|
function cleanProtocolVersion(value: unknown): number | undefined {
|
|
18
19
|
if (value === undefined) return undefined;
|
|
@@ -533,13 +534,15 @@ export const postOrchestratorAction: Handler = async (req, params) => {
|
|
|
533
534
|
resource: { orchestratorId: orch.id, agentId, policyName, spawnRequestId },
|
|
534
535
|
});
|
|
535
536
|
if (denied) return denied;
|
|
537
|
+
const agent = action === "restart" && agentId ? getAgent(agentId) : undefined;
|
|
538
|
+
const restartSpawn = agent ? restartSpawnParamsForAgent(agent, orch, policyName, spawnRequestId) : undefined;
|
|
536
539
|
|
|
537
540
|
const command = createCommand({
|
|
538
541
|
type: action === "restart" ? "agent.restart" : "agent.shutdown",
|
|
539
542
|
source: "system",
|
|
540
543
|
target: orch.agentId,
|
|
541
544
|
correlationId: spawnRequestId,
|
|
542
|
-
params: { action, agentId, policyName, spawnRequestId, sessionName, tmuxSession, reason, requestedBy: "dashboard", requestedAt: Date.now(), orchestratorId: orch.id },
|
|
545
|
+
params: { action, agentId, policyName, spawnRequestId, sessionName, tmuxSession, reason, ...(restartSpawn ? { restartSpawn } : {}), requestedBy: "dashboard", requestedAt: Date.now(), orchestratorId: orch.id },
|
|
543
546
|
});
|
|
544
547
|
emitCommand(command);
|
|
545
548
|
auditEvent({
|
package/src/routes/settings.ts
CHANGED
|
@@ -10,7 +10,7 @@ import { isRequestAuthorizedFor } from "../security";
|
|
|
10
10
|
import { cleanString } from "../validation";
|
|
11
11
|
import { emitConfigChanged } from "../sse";
|
|
12
12
|
import { error, json, normalizeConfigPathParam, parseBody, requestUserId, type Handler } from "./_shared";
|
|
13
|
-
import { DEFAULT_USER_ID, isRecord, type ConfigEntry, type SettingDescriptor, type SettingEntry } from "agent-relay-sdk";
|
|
13
|
+
import { DEFAULT_USER_ID, isRecord, type ConfigEntry, type JSONSchema, type SettingDescriptor, type SettingEntry } from "agent-relay-sdk";
|
|
14
14
|
|
|
15
15
|
type SettingEnvelope = SettingEntry;
|
|
16
16
|
|
|
@@ -91,7 +91,10 @@ async function putSetting(req: Request, namespaceRaw: string | undefined, keyRaw
|
|
|
91
91
|
if (!validation.valid) return json({ error: "invalid setting value", details: validation.errors }, 400);
|
|
92
92
|
if (descriptor.tier === "user") {
|
|
93
93
|
const userId = requestUserId(req);
|
|
94
|
-
const
|
|
94
|
+
const valueToStore = descriptor.mergeMapKeys && isRecord(parsed.body.value)
|
|
95
|
+
? mergeMapKeysIntoExisting(getUserSetting(userId, namespace, key)?.value, parsed.body.value, descriptor.schema)
|
|
96
|
+
: parsed.body.value;
|
|
97
|
+
const userEntry = setUserSetting(userId, namespace, key, valueToStore, updatedBy);
|
|
95
98
|
emitConfigChanged(userEntry.namespace, userEntry.key, userEntry.version);
|
|
96
99
|
return json(settingEnvelope(req, descriptor, userEntry), userEntry.version === 1 ? 201 : 200);
|
|
97
100
|
}
|
|
@@ -156,3 +159,29 @@ function mergeSettingDefaults(defaults: unknown, value: unknown): unknown {
|
|
|
156
159
|
}
|
|
157
160
|
return merged;
|
|
158
161
|
}
|
|
162
|
+
|
|
163
|
+
// A `mergeMapKeys` descriptor (#1124) is a dictionary keyed by caller-chosen ids (e.g. one row
|
|
164
|
+
// per conversation). A PUT only ever knows its own device's changed keys, never every other
|
|
165
|
+
// device's, so overwriting the whole stored value like a normal setting would erase them — this
|
|
166
|
+
// merges just the incoming keys on top of whatever is already stored instead. The merged result
|
|
167
|
+
// is then capped at the schema's `maxProperties` (when set) so per-key merges from unboundedly
|
|
168
|
+
// many devices can't grow the row forever; the entry with the OLDEST `updatedAt` is evicted first
|
|
169
|
+
// (missing/non-numeric `updatedAt`, e.g. a pre-#1124 row, sorts as oldest: 0).
|
|
170
|
+
function mergeMapKeysIntoExisting(existing: unknown, incoming: Record<string, unknown>, schema: JSONSchema): Record<string, unknown> {
|
|
171
|
+
const merged: Record<string, unknown> = isRecord(existing) ? { ...existing } : {};
|
|
172
|
+
for (const [key, item] of Object.entries(incoming)) merged[key] = item;
|
|
173
|
+
|
|
174
|
+
const cap = typeof schema.maxProperties === "number" ? schema.maxProperties : undefined;
|
|
175
|
+
const entries = Object.entries(merged);
|
|
176
|
+
if (cap !== undefined && entries.length > cap) {
|
|
177
|
+
entries.sort((a, b) => entryUpdatedAt(a[1]) - entryUpdatedAt(b[1]));
|
|
178
|
+
const overflow = entries.length - cap;
|
|
179
|
+
for (let i = 0; i < overflow; i += 1) delete merged[entries[i]![0]];
|
|
180
|
+
}
|
|
181
|
+
return merged;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function entryUpdatedAt(value: unknown): number {
|
|
185
|
+
if (isRecord(value) && typeof value.updatedAt === "number" && Number.isFinite(value.updatedAt)) return value.updatedAt;
|
|
186
|
+
return 0;
|
|
187
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { isSpawnProvider } from "agent-relay-sdk";
|
|
2
|
+
import { getAgentProfile } from "../config-store";
|
|
3
|
+
import { readSpawnCallerSystemPromptAppend, resolveAgentOwnership, withResolvedProvisioning } from "../db";
|
|
4
|
+
import { composeProfileSpawnInstructions } from "../project-instructions";
|
|
5
|
+
import { applyDefaultSharedMcp } from "../shared-mcp";
|
|
6
|
+
import type { AgentCard } from "../types";
|
|
7
|
+
|
|
8
|
+
function metaString(meta: Record<string, unknown> | undefined, key: string): string | undefined {
|
|
9
|
+
const value = meta?.[key];
|
|
10
|
+
return typeof value === "string" ? value : undefined;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function restartInstructionParamsForAgent(
|
|
14
|
+
agent: AgentCard,
|
|
15
|
+
opts: { spawnRequestId?: string; cwd?: string } = {},
|
|
16
|
+
): Record<string, unknown> | undefined {
|
|
17
|
+
const provider = metaString(agent.meta, "provider");
|
|
18
|
+
if (!isSpawnProvider(provider)) return undefined;
|
|
19
|
+
const cwd = opts.cwd ?? metaString(agent.meta, "cwd");
|
|
20
|
+
const profileName = metaString(agent.meta, "profile");
|
|
21
|
+
const resolvedProfile = withResolvedProvisioning(profileName ? getAgentProfile(profileName)?.value : undefined, provider);
|
|
22
|
+
const agentProfile = resolvedProfile ? applyDefaultSharedMcp(resolvedProfile) : undefined;
|
|
23
|
+
const callerAppend = readSpawnCallerSystemPromptAppend(opts.spawnRequestId);
|
|
24
|
+
const composed = composeProfileSpawnInstructions({
|
|
25
|
+
...(cwd ? { cwd } : {}),
|
|
26
|
+
operatorId: resolveAgentOwnership(agent.id).ownerUserId,
|
|
27
|
+
...(profileName ? { profileName } : {}),
|
|
28
|
+
...(agentProfile ? { profile: agentProfile } : {}),
|
|
29
|
+
...(callerAppend ? { callerSystemPromptAppend: callerAppend } : {}),
|
|
30
|
+
});
|
|
31
|
+
return {
|
|
32
|
+
...(composed.systemPromptAppend ? { systemPromptAppend: composed.systemPromptAppend } : {}),
|
|
33
|
+
...(callerAppend ? { callerSystemPromptAppend: callerAppend } : {}),
|
|
34
|
+
...(composed.relayInjectionEvents.length ? { relayInjectionEvents: composed.relayInjectionEvents } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|