agent-relay-server 0.125.0 → 0.127.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.
Files changed (156) hide show
  1. package/docs/openapi.json +1 -1
  2. package/package.json +9 -5
  3. package/public/assets/{MessageBubble-BYeYFteG.js → MessageBubble-D2cnxyTy.js} +2 -2
  4. package/public/assets/{MessageBubble-BYeYFteG.js.map → MessageBubble-D2cnxyTy.js.map} +1 -1
  5. package/public/assets/{PlanGraphPanel-iAXvkZKW.js → PlanGraphPanel-Cnxt-x7R.js} +2 -2
  6. package/public/assets/{PlanGraphPanel-iAXvkZKW.js.map → PlanGraphPanel-Cnxt-x7R.js.map} +1 -1
  7. package/public/assets/{activity-Du9SRJt5.js → activity-BEm1BQB1.js} +2 -2
  8. package/public/assets/{activity-Du9SRJt5.js.map → activity-BEm1BQB1.js.map} +1 -1
  9. package/public/assets/{agent-profiles-DXDQnrlH.js → agent-profiles-C8V_oCiM.js} +2 -2
  10. package/public/assets/{agent-profiles-DXDQnrlH.js.map → agent-profiles-C8V_oCiM.js.map} +1 -1
  11. package/public/assets/{agent-type-icon-B3i0rndK.js → agent-type-icon-BmBR-9CH.js} +2 -2
  12. package/public/assets/{agent-type-icon-B3i0rndK.js.map → agent-type-icon-BmBR-9CH.js.map} +1 -1
  13. package/public/assets/{agents-CWOdKrxP.js → agents-D8mc9X1j.js} +2 -2
  14. package/public/assets/{agents-CWOdKrxP.js.map → agents-D8mc9X1j.js.map} +1 -1
  15. package/public/assets/{analytics-YcY59Hv9.js → analytics-BZtRycyL.js} +2 -2
  16. package/public/assets/{analytics-YcY59Hv9.js.map → analytics-BZtRycyL.js.map} +1 -1
  17. package/public/assets/{automation-CItTse7F.js → automation-BEhJZkNQ.js} +2 -2
  18. package/public/assets/{automation-CItTse7F.js.map → automation-BEhJZkNQ.js.map} +1 -1
  19. package/public/assets/{badge-XEru6wto.js → badge-BCvp7LK3.js} +2 -2
  20. package/public/assets/{badge-XEru6wto.js.map → badge-BCvp7LK3.js.map} +1 -1
  21. package/public/assets/{branch-state-badge-QjTktTYj.js → branch-state-badge-Cscjqf-E.js} +2 -2
  22. package/public/assets/{branch-state-badge-QjTktTYj.js.map → branch-state-badge-Cscjqf-E.js.map} +1 -1
  23. package/public/assets/{button-C3CQSr4p.js → button-DyHjsHY3.js} +2 -2
  24. package/public/assets/{button-C3CQSr4p.js.map → button-DyHjsHY3.js.map} +1 -1
  25. package/public/assets/{card-C5XTIPvq.js → card-BoqYxpVW.js} +2 -2
  26. package/public/assets/{card-C5XTIPvq.js.map → card-BoqYxpVW.js.map} +1 -1
  27. package/public/assets/{channels-BSFTsbhT.js → channels-DAtOnA6o.js} +2 -2
  28. package/public/assets/{channels-BSFTsbhT.js.map → channels-DAtOnA6o.js.map} +1 -1
  29. package/public/assets/chat-BAH9bEPx.js +2 -0
  30. package/public/assets/chat-BAH9bEPx.js.map +1 -0
  31. package/public/assets/{connectors-C7d5dlYz.js → connectors-B2grC-hl.js} +2 -2
  32. package/public/assets/{connectors-C7d5dlYz.js.map → connectors-B2grC-hl.js.map} +1 -1
  33. package/public/assets/{copy-button-Bil2CjzT.js → copy-button-C67FUG3E.js} +2 -2
  34. package/public/assets/{copy-button-Bil2CjzT.js.map → copy-button-C67FUG3E.js.map} +1 -1
  35. package/public/assets/{dist-V3wu3BHw.js → dist-BD4Xc13v.js} +2 -2
  36. package/public/assets/{dist-V3wu3BHw.js.map → dist-BD4Xc13v.js.map} +1 -1
  37. package/public/assets/{dist-C5z05Vkz.js → dist-BFDfbfNC.js} +2 -2
  38. package/public/assets/{dist-C5z05Vkz.js.map → dist-BFDfbfNC.js.map} +1 -1
  39. package/public/assets/{dist-CjyfQThq.js → dist-BJKxqIQd.js} +2 -2
  40. package/public/assets/{dist-CjyfQThq.js.map → dist-BJKxqIQd.js.map} +1 -1
  41. package/public/assets/{dist-B8tD9ZcR.js → dist-BNB3fTO-.js} +2 -2
  42. package/public/assets/{dist-B8tD9ZcR.js.map → dist-BNB3fTO-.js.map} +1 -1
  43. package/public/assets/{dist-CFtFIhu3.js → dist-CHKra2JV.js} +2 -2
  44. package/public/assets/{dist-CFtFIhu3.js.map → dist-CHKra2JV.js.map} +1 -1
  45. package/public/assets/{dist-r8wyUTkK.js → dist-CQMQ5iku.js} +2 -2
  46. package/public/assets/{dist-r8wyUTkK.js.map → dist-CQMQ5iku.js.map} +1 -1
  47. package/public/assets/{dist-DKmTXfpP.js → dist-C_VOBSaK.js} +2 -2
  48. package/public/assets/{dist-DKmTXfpP.js.map → dist-C_VOBSaK.js.map} +1 -1
  49. package/public/assets/{dist-CGaHpxnN.js → dist-D5w04z0l.js} +2 -2
  50. package/public/assets/{dist-CGaHpxnN.js.map → dist-D5w04z0l.js.map} +1 -1
  51. package/public/assets/{dist-4VwhnLtj.js → dist-DG75bvPp.js} +2 -2
  52. package/public/assets/{dist-4VwhnLtj.js.map → dist-DG75bvPp.js.map} +1 -1
  53. package/public/assets/{dist-CrUl2aQp.js → dist-G8YBD-wp.js} +2 -2
  54. package/public/assets/{dist-CrUl2aQp.js.map → dist-G8YBD-wp.js.map} +1 -1
  55. package/public/assets/{dist-BJaV2BD4.js → dist-f6MW74eX.js} +2 -2
  56. package/public/assets/{dist-BJaV2BD4.js.map → dist-f6MW74eX.js.map} +1 -1
  57. package/public/assets/{es2015-5DTJe35n.js → es2015-a6fysEVz.js} +2 -2
  58. package/public/assets/{es2015-5DTJe35n.js.map → es2015-a6fysEVz.js.map} +1 -1
  59. package/public/assets/formatted-body-Rv9RDPsQ.js +3 -0
  60. package/public/assets/{formatted-body-BuLfVMeD.js.map → formatted-body-Rv9RDPsQ.js.map} +1 -1
  61. package/public/assets/{formatted-body-impl-DO-8giS6.js → formatted-body-impl-XFU4tPeT.js} +2 -2
  62. package/public/assets/{formatted-body-impl-DO-8giS6.js.map → formatted-body-impl-XFU4tPeT.js.map} +1 -1
  63. package/public/assets/{index-De1tR7J4.js → index-Bhy_jnX4.js} +6 -6
  64. package/public/assets/{index-De1tR7J4.js.map → index-Bhy_jnX4.js.map} +1 -1
  65. package/public/assets/index-DHzXsQot.css +2 -0
  66. package/public/assets/{input-CXMbJ848.js → input-Rc4oWv4q.js} +2 -2
  67. package/public/assets/{input-CXMbJ848.js.map → input-Rc4oWv4q.js.map} +1 -1
  68. package/public/assets/{insights-Ci3VuA5Y.js → insights-DpXPAEwH.js} +2 -2
  69. package/public/assets/{insights-Ci3VuA5Y.js.map → insights-DpXPAEwH.js.map} +1 -1
  70. package/public/assets/{integrations-BboxWQQD.js → integrations-9c6IdRU4.js} +2 -2
  71. package/public/assets/{integrations-BboxWQQD.js.map → integrations-9c6IdRU4.js.map} +1 -1
  72. package/public/assets/{lib-BcUW1l-W.js → lib-BZ6WPg2t.js} +2 -2
  73. package/public/assets/{lib-BcUW1l-W.js.map → lib-BZ6WPg2t.js.map} +1 -1
  74. package/public/assets/lucide-react-NXHPIMsX.js +10 -0
  75. package/public/assets/lucide-react-NXHPIMsX.js.map +1 -0
  76. package/public/assets/{maintenance-CO1miJIJ.js → maintenance-BrkqfGwx.js} +2 -2
  77. package/public/assets/{maintenance-CO1miJIJ.js.map → maintenance-BrkqfGwx.js.map} +1 -1
  78. package/public/assets/{managed-agents-r4oYK3Fm.js → managed-agents-CLANxOAN.js} +2 -2
  79. package/public/assets/{managed-agents-r4oYK3Fm.js.map → managed-agents-CLANxOAN.js.map} +1 -1
  80. package/public/assets/{markdown-preview-impl-5CCM9iyL.js → markdown-preview-impl-CcZFkjn-.js} +2 -2
  81. package/public/assets/{markdown-preview-impl-5CCM9iyL.js.map → markdown-preview-impl-CcZFkjn-.js.map} +1 -1
  82. package/public/assets/{memory-BJJS07W-.js → memory-BoQw8q-i.js} +2 -2
  83. package/public/assets/{memory-BJJS07W-.js.map → memory-BoQw8q-i.js.map} +1 -1
  84. package/public/assets/{messages-RT_dfEsL.js → messages-DfKNuph2.js} +2 -2
  85. package/public/assets/{messages-RT_dfEsL.js.map → messages-DfKNuph2.js.map} +1 -1
  86. package/public/assets/{orchestrators-8hyl8hW1.js → orchestrators-sQh8e7-V.js} +2 -2
  87. package/public/assets/{orchestrators-8hyl8hW1.js.map → orchestrators-sQh8e7-V.js.map} +1 -1
  88. package/public/assets/{overview-CUJ9QmW_.js → overview-B0z8ZNtp.js} +2 -2
  89. package/public/assets/{overview-CUJ9QmW_.js.map → overview-B0z8ZNtp.js.map} +1 -1
  90. package/public/assets/{pairs-CzkzMGZq.js → pairs-B3FhGmn-.js} +2 -2
  91. package/public/assets/{pairs-CzkzMGZq.js.map → pairs-B3FhGmn-.js.map} +1 -1
  92. package/public/assets/{projects-CAuLWBU5.js → projects-iDkxAGBW.js} +2 -2
  93. package/public/assets/{projects-CAuLWBU5.js.map → projects-iDkxAGBW.js.map} +1 -1
  94. package/public/assets/{prompt-settings-BnB90R7F.js → prompt-settings-CCj_s008.js} +2 -2
  95. package/public/assets/{prompt-settings-BnB90R7F.js.map → prompt-settings-CCj_s008.js.map} +1 -1
  96. package/public/assets/{react-dom-BQICbUuF.js → react-dom-8yaD9iJG.js} +2 -2
  97. package/public/assets/{react-dom-BQICbUuF.js.map → react-dom-8yaD9iJG.js.map} +1 -1
  98. package/public/assets/{registry-B8TInqru.js → registry-mD_y06Ru.js} +2 -2
  99. package/public/assets/{registry-B8TInqru.js.map → registry-mD_y06Ru.js.map} +1 -1
  100. package/public/assets/{security-DG_VVZUW.js → security-CTSA9niL.js} +2 -2
  101. package/public/assets/{security-DG_VVZUW.js.map → security-CTSA9niL.js.map} +1 -1
  102. package/public/assets/{select-BJ8G38_V.js → select-gEQEHCIf.js} +2 -2
  103. package/public/assets/{select-BJ8G38_V.js.map → select-gEQEHCIf.js.map} +1 -1
  104. package/public/assets/{settings-CK_T9z5w.js → settings-ByAR-TYj.js} +2 -2
  105. package/public/assets/{settings-CK_T9z5w.js.map → settings-ByAR-TYj.js.map} +1 -1
  106. package/public/assets/store-D-Frk_EC.js +9 -0
  107. package/public/assets/store-D-Frk_EC.js.map +1 -0
  108. package/public/assets/{task-chip-BHM-e3I8.js → task-chip-CeujtXYT.js} +2 -2
  109. package/public/assets/{task-chip-BHM-e3I8.js.map → task-chip-CeujtXYT.js.map} +1 -1
  110. package/public/assets/{tasks-Briui9Mp.js → tasks-CZyJX881.js} +2 -2
  111. package/public/assets/{tasks-Briui9Mp.js.map → tasks-CZyJX881.js.map} +1 -1
  112. package/public/assets/{teams-Ck4EVV3R.js → teams-BrL4sEL7.js} +2 -2
  113. package/public/assets/{teams-Ck4EVV3R.js.map → teams-BrL4sEL7.js.map} +1 -1
  114. package/public/assets/{terminal-viewer-impl-66ZjVN1v.js → terminal-viewer-impl-LjIph8ql.js} +2 -2
  115. package/public/assets/{terminal-viewer-impl-66ZjVN1v.js.map → terminal-viewer-impl-LjIph8ql.js.map} +1 -1
  116. package/public/assets/types-BqeSkWrv.js.map +1 -1
  117. package/public/assets/{use-keyboard-viewport-CAa7IQTm.js → use-keyboard-viewport-cGEmrGE7.js} +2 -2
  118. package/public/assets/{use-keyboard-viewport-CAa7IQTm.js.map → use-keyboard-viewport-cGEmrGE7.js.map} +1 -1
  119. package/public/assets/{user-avatar-BOy9o2xK.js → user-avatar-qbo-B3dQ.js} +2 -2
  120. package/public/assets/{user-avatar-BOy9o2xK.js.map → user-avatar-qbo-B3dQ.js.map} +1 -1
  121. package/public/assets/{work-queue-CnghFRG2.js → work-queue-CSu5kRsw.js} +2 -2
  122. package/public/assets/{work-queue-CnghFRG2.js.map → work-queue-CSu5kRsw.js.map} +1 -1
  123. package/public/assets/{workspaces-BzmQnzSm.js → workspaces-CoDz-iZd.js} +2 -2
  124. package/public/assets/{workspaces-BzmQnzSm.js.map → workspaces-CoDz-iZd.js.map} +1 -1
  125. package/public/index.html +24 -24
  126. package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
  127. package/runner/plugins/claude/monitors/relay-monitor.provisioned.mjs +2 -2
  128. package/runner/src/adapters/codex-agent-message-capture.ts +38 -8
  129. package/runner/src/relay-mcp-proxy.ts +1 -3
  130. package/runner/src/runner-core.ts +33 -13
  131. package/runner/src/runner-helpers.ts +31 -0
  132. package/src/db/index.ts +1 -0
  133. package/src/db/messages.ts +38 -2
  134. package/src/db/task-deliverables.ts +206 -0
  135. package/src/db/task-entity.ts +4 -8
  136. package/src/mcp/index.ts +4 -4
  137. package/src/mcp/tools-workspace.ts +3 -36
  138. package/src/mcp-task-deliver-tools.ts +143 -0
  139. package/src/notification-rules.ts +1 -0
  140. package/src/notification-types.ts +2 -0
  141. package/src/prompt-resolver.ts +6 -6
  142. package/src/routes/agent-sessions.ts +10 -7
  143. package/src/routes/orchestrator.ts +5 -2
  144. package/src/routes/settings.ts +31 -2
  145. package/src/services/restart-instructions.ts +36 -0
  146. package/src/services/task-delivery.ts +275 -0
  147. package/src/settings/registry.ts +14 -3
  148. package/src/workspace-phase.ts +5 -5
  149. package/public/assets/chat-CRzFSFWe.js +0 -2
  150. package/public/assets/chat-CRzFSFWe.js.map +0 -1
  151. package/public/assets/formatted-body-BuLfVMeD.js +0 -3
  152. package/public/assets/index-C9X_0QBt.css +0 -2
  153. package/public/assets/lucide-react-CoU_Xts2.js +0 -10
  154. package/public/assets/lucide-react-CoU_Xts2.js.map +0 -1
  155. package/public/assets/store-Dl9Z8YUS.js +0 -9
  156. 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-De1tR7J4.js"></script>
15
+ <script type="module" crossorigin src="./assets/index-Bhy_jnX4.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-CoU_Xts2.js">
19
- <link rel="modulepreload" crossorigin href="./assets/dist-C5z05Vkz.js">
20
- <link rel="modulepreload" crossorigin href="./assets/dist-B8tD9ZcR.js">
21
- <link rel="modulepreload" crossorigin href="./assets/react-dom-BQICbUuF.js">
22
- <link rel="modulepreload" crossorigin href="./assets/dist-V3wu3BHw.js">
23
- <link rel="modulepreload" crossorigin href="./assets/dist-CFtFIhu3.js">
24
- <link rel="modulepreload" crossorigin href="./assets/dist-CGaHpxnN.js">
25
- <link rel="modulepreload" crossorigin href="./assets/dist-DKmTXfpP.js">
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-BJaV2BD4.js">
28
- <link rel="modulepreload" crossorigin href="./assets/dist-CjyfQThq.js">
29
- <link rel="modulepreload" crossorigin href="./assets/es2015-5DTJe35n.js">
30
- <link rel="modulepreload" crossorigin href="./assets/dist-r8wyUTkK.js">
31
- <link rel="modulepreload" crossorigin href="./assets/dist-4VwhnLtj.js">
32
- <link rel="modulepreload" crossorigin href="./assets/dist-CrUl2aQp.js">
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-Dl9Z8YUS.js">
36
- <link rel="modulepreload" crossorigin href="./assets/agent-type-icon-B3i0rndK.js">
37
- <link rel="modulepreload" crossorigin href="./assets/button-C3CQSr4p.js">
38
- <link rel="modulepreload" crossorigin href="./assets/copy-button-Bil2CjzT.js">
39
- <link rel="modulepreload" crossorigin href="./assets/formatted-body-BuLfVMeD.js">
40
- <link rel="modulepreload" crossorigin href="./assets/task-chip-BHM-e3I8.js">
41
- <link rel="modulepreload" crossorigin href="./assets/badge-XEru6wto.js">
42
- <link rel="modulepreload" crossorigin href="./assets/input-CXMbJ848.js">
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-Rv9RDPsQ.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-C9X_0QBt.css">
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>
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agent-relay-runner",
3
3
  "description": "Thin Agent Relay runner bridge for Claude Code",
4
- "version": "0.125.0",
4
+ "version": "0.127.0",
5
5
  "agentRelayContracts": {
6
6
  "providerPluginProtocol": 1
7
7
  }
@@ -56,8 +56,8 @@ var RUNNER_PROMPT_TEMPLATE_DEFAULTS = [
56
56
  category: "runner",
57
57
  title: "Runner Workspace Lifecycle Note",
58
58
  description: "Lifecycle note injected by the runner for isolated workspaces.",
59
- template: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, run `agent-relay workspace ready` \u2014 Relay rebases onto the latest {{baseRef}}, lands your work, and pushes for you. If the installed `agent-relay` binary is stale and says the workspace command is unknown, run the repo-local fallback: `bun src/index.ts workspace ready`.", "After `ready`, the status becomes `review_requested` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after `ready` and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
60
- defaultTemplate: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, run `agent-relay workspace ready` \u2014 Relay rebases onto the latest {{baseRef}}, lands your work, and pushes for you. If the installed `agent-relay` binary is stale and says the workspace command is unknown, run the repo-local fallback: `bun src/index.ts workspace ready`.", "After `ready`, the status becomes `review_requested` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after `ready` and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
59
+ template: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, call `relay_task_deliver` with disposition `complete`, a summary, commits, and passing `typecheck` + `test` gates. Relay records the typed delivery on the Task, then rebases onto the latest {{baseRef}}, lands your work, and pushes for you.", "After `relay_task_deliver` accepts a complete delivery, the workspace status becomes `ready` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after delivery and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
60
+ defaultTemplate: lines("[agent-relay] Isolated workspace: you are in a git worktree on branch {{branch}}, based on {{baseRef}} \u2014 NOT the main checkout. Other agents may work in parallel and land to {{baseRef}}, so {{baseRef}} will move under you. That is expected; don't fight it.", "Do NOT push this branch yourself \u2014 not with `git push`, not with `tl push` or any other push wrapper, and do not manually rebase or merge. A steward may be auto-rebasing this branch in the background; pushing concurrently races it and can leave the worktree mid-rebase. Just commit your work here. When the task is done, call `relay_task_deliver` with disposition `complete`, a summary, commits, and passing `typecheck` + `test` gates. Relay records the typed delivery on the Task, then rebases onto the latest {{baseRef}}, lands your work, and pushes for you.", "After `relay_task_deliver` accepts a complete delivery, the workspace status becomes `ready` \u2014 this is the NORMAL, healthy hand-off state, NOT an escalation or a stall. Relay auto-merges clean rebases roughly every 2 minutes; a steward agent is spawned (after a short delay) ONLY if it can't land deterministically, so seeing no steward means it's working, not stuck. Stop your turn after delivery and go idle; do NOT wait or poll the steward queue. Relay wakes you with `landed-success` when your branch lands and refreshes, or `landed-failure` if landing fatally fails. On success you'll be on a fresh rebased branch whose name gains a `--N` suffix \u2014 expected, keep working there. Never `cd` into the main checkout, and never merge/push/resolve conflicts yourself \u2014 Relay and the steward own all of that. `agent-relay workspace status` anytime shows your current state and the exact next step."),
61
61
  variables: ["branch", "baseRef"]
62
62
  },
63
63
  {
@@ -45,17 +45,16 @@ export class CodexAgentMessageCapture {
45
45
  this.currentSegment = [];
46
46
  }
47
47
 
48
- // Deterministic turn-final response: the TRAILING agentMessage run only all items after
49
- // the LAST non-message item (tool call, file change, reasoning...) in the turn (design
50
- // #1079 §3.2). Earlier agentMessage segments are mid-work narration, already mirrored live
51
- // via `emit`, and must not be re-injected into the final (#1083 Finding 2). Codex's own
52
- // phase:"commentary" tag (see completeItem above) still trims commentary items out of
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.at(-1);
55
+ const trailingSegment = selectFinalResponseSegments(this.segments);
57
56
  this.segments = [];
58
- const text = trailingSegment ? segmentBody(trailingSegment) : undefined;
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]`;
@@ -43,7 +43,7 @@ const SSE_KEEPALIVE_MS = 25_000;
43
43
  const DEFAULT_BUFFERABLE_TOOLS = new Set<string>([
44
44
  "relay_send_message",
45
45
  "relay_reply",
46
- "relay_workspace_ready",
46
+ "relay_task_deliver",
47
47
  ]);
48
48
 
49
49
  // Tools surfaced ONLY to an agent that owns a live isolated worktree. For any other agent the
@@ -51,12 +51,10 @@ const DEFAULT_BUFFERABLE_TOOLS = new Set<string>([
51
51
  // would return them). This is the context the coarse token scope can't express (#214/#215).
52
52
  const WORKTREE_ONLY_TOOLS = new Set<string>([
53
53
  "relay_workspace_status",
54
- "relay_workspace_ready",
55
54
  "relay_workspace_deps",
56
55
  "relay_workspace_list",
57
56
  "relay_workspace_claim",
58
57
  "relay_workspace_release",
59
- "relay_workspace_land",
60
58
  ]);
61
59
 
62
60
  interface ProxyContext {
@@ -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: { resumeId?: string; suppressPrompt?: boolean } = {}): Promise<ManagedProcess> {
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: { resumeId?: string; suppressPrompt?: boolean } = {}): Promise<void> {
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
- const id = typeof update === "string" ? reason : update.id ?? reason;
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.finishWork(reason, id);
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: this.claims.reasons(),
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/index.ts CHANGED
@@ -10,6 +10,7 @@ export * from "./agents.ts";
10
10
  export * from "./agent-search.ts";
11
11
  export * from "./tasks.ts";
12
12
  export * from "./task-entity.ts";
13
+ export * from "./task-deliverables.ts";
13
14
  export * from "./task-memory.ts";
14
15
  export * from "./task-comment-through.ts";
15
16
  export * from "./integrations.ts";
@@ -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
- let groupOrder = 0;
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(messageId, finality);
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