agent-relay-server 0.120.2 → 0.120.3
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 +3 -3
- package/public/assets/{MessageBubble-DazQ9Rn-.js → MessageBubble-Cqi3XsZ7.js} +2 -2
- package/public/assets/{MessageBubble-DazQ9Rn-.js.map → MessageBubble-Cqi3XsZ7.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-DpTiGCkz.js → PlanGraphPanel-D6Um05oW.js} +2 -2
- package/public/assets/{PlanGraphPanel-DpTiGCkz.js.map → PlanGraphPanel-D6Um05oW.js.map} +1 -1
- package/public/assets/{activity-DReCE2mK.js → activity-B0LsQHEz.js} +2 -2
- package/public/assets/{activity-DReCE2mK.js.map → activity-B0LsQHEz.js.map} +1 -1
- package/public/assets/{agent-profiles-D3ba8Xpe.js → agent-profiles-O--pe5Ic.js} +2 -2
- package/public/assets/{agent-profiles-D3ba8Xpe.js.map → agent-profiles-O--pe5Ic.js.map} +1 -1
- package/public/assets/{agent-type-icon-BEz393ra.js → agent-type-icon-CeEoMouY.js} +2 -2
- package/public/assets/{agent-type-icon-BEz393ra.js.map → agent-type-icon-CeEoMouY.js.map} +1 -1
- package/public/assets/{agents-BcsIxadm.js → agents-Ii6XlVwR.js} +2 -2
- package/public/assets/{agents-BcsIxadm.js.map → agents-Ii6XlVwR.js.map} +1 -1
- package/public/assets/{analytics-0lOlkaib.js → analytics-BCzdYWRT.js} +2 -2
- package/public/assets/{analytics-0lOlkaib.js.map → analytics-BCzdYWRT.js.map} +1 -1
- package/public/assets/{automation-D66mthBn.js → automation-Crr3Xzui.js} +2 -2
- package/public/assets/{automation-D66mthBn.js.map → automation-Crr3Xzui.js.map} +1 -1
- package/public/assets/{badge-BhUZ1cAb.js → badge-DCIT3Irj.js} +2 -2
- package/public/assets/{badge-BhUZ1cAb.js.map → badge-DCIT3Irj.js.map} +1 -1
- package/public/assets/{branch-state-badge-NH-l2Eh3.js → branch-state-badge-Bhw2rloy.js} +2 -2
- package/public/assets/{branch-state-badge-NH-l2Eh3.js.map → branch-state-badge-Bhw2rloy.js.map} +1 -1
- package/public/assets/{button-C-V4oij1.js → button-CzdTkRIf.js} +2 -2
- package/public/assets/{button-C-V4oij1.js.map → button-CzdTkRIf.js.map} +1 -1
- package/public/assets/{card-Br1JLjTH.js → card-C3BGuVC6.js} +2 -2
- package/public/assets/{card-Br1JLjTH.js.map → card-C3BGuVC6.js.map} +1 -1
- package/public/assets/{channels-BlHLMQVk.js → channels-CKs9rvsn.js} +2 -2
- package/public/assets/{channels-BlHLMQVk.js.map → channels-CKs9rvsn.js.map} +1 -1
- package/public/assets/{chat-rwffRsPX.js → chat-BCwEUj5R.js} +2 -2
- package/public/assets/{chat-rwffRsPX.js.map → chat-BCwEUj5R.js.map} +1 -1
- package/public/assets/{connectors-CdsiaLD3.js → connectors-D_kQR_vw.js} +2 -2
- package/public/assets/{connectors-CdsiaLD3.js.map → connectors-D_kQR_vw.js.map} +1 -1
- package/public/assets/{copy-button-BStzXgtd.js → copy-button-B3o1jpLu.js} +2 -2
- package/public/assets/{copy-button-BStzXgtd.js.map → copy-button-B3o1jpLu.js.map} +1 -1
- package/public/assets/{dist-ChDX_t12.js → dist-B-T7lg0a.js} +2 -2
- package/public/assets/{dist-ChDX_t12.js.map → dist-B-T7lg0a.js.map} +1 -1
- package/public/assets/{dist-DV83j9tm.js → dist-B3e4B9pg.js} +2 -2
- package/public/assets/{dist-DV83j9tm.js.map → dist-B3e4B9pg.js.map} +1 -1
- package/public/assets/{dist-y03IwOrd.js → dist-B6woQ9Tb.js} +2 -2
- package/public/assets/{dist-y03IwOrd.js.map → dist-B6woQ9Tb.js.map} +1 -1
- package/public/assets/{dist-CQ0Z-TDi.js → dist-B9G83DXv.js} +2 -2
- package/public/assets/{dist-CQ0Z-TDi.js.map → dist-B9G83DXv.js.map} +1 -1
- package/public/assets/{dist-Cs8X0P1d.js → dist-BIjlN-xB.js} +2 -2
- package/public/assets/{dist-Cs8X0P1d.js.map → dist-BIjlN-xB.js.map} +1 -1
- package/public/assets/{dist-DyNJNwpi.js → dist-BInckSJC.js} +2 -2
- package/public/assets/{dist-DyNJNwpi.js.map → dist-BInckSJC.js.map} +1 -1
- package/public/assets/{dist-CmFeEXMe.js → dist-BlzVGRhh.js} +2 -2
- package/public/assets/{dist-CmFeEXMe.js.map → dist-BlzVGRhh.js.map} +1 -1
- package/public/assets/{dist-CDZZxxJq.js → dist-C7aTlosb.js} +2 -2
- package/public/assets/{dist-CDZZxxJq.js.map → dist-C7aTlosb.js.map} +1 -1
- package/public/assets/{dist-D2jvo5oA.js → dist-DGfp5dLz.js} +2 -2
- package/public/assets/{dist-D2jvo5oA.js.map → dist-DGfp5dLz.js.map} +1 -1
- package/public/assets/{dist-mm7c7nBt.js → dist-DT-OV6sQ.js} +2 -2
- package/public/assets/{dist-mm7c7nBt.js.map → dist-DT-OV6sQ.js.map} +1 -1
- package/public/assets/{dist-CBMWGhWc.js → dist-OUPTEP6q.js} +2 -2
- package/public/assets/{dist-CBMWGhWc.js.map → dist-OUPTEP6q.js.map} +1 -1
- package/public/assets/{es2015-VnZ2kVIz.js → es2015-CnKXYKpf.js} +2 -2
- package/public/assets/{es2015-VnZ2kVIz.js.map → es2015-CnKXYKpf.js.map} +1 -1
- package/public/assets/{formatted-body-impl-D_ZJQVNk.js → formatted-body-impl-CSRGuMcM.js} +2 -2
- package/public/assets/{formatted-body-impl-D_ZJQVNk.js.map → formatted-body-impl-CSRGuMcM.js.map} +1 -1
- package/public/assets/formatted-body-q_qzt1L-.js +3 -0
- package/public/assets/{formatted-body-C1AprxyU.js.map → formatted-body-q_qzt1L-.js.map} +1 -1
- package/public/assets/{index-DfPouNiV.js → index-Ceiwd02X.js} +6 -6
- package/public/assets/{index-DfPouNiV.js.map → index-Ceiwd02X.js.map} +1 -1
- package/public/assets/{input-CtLT7WXi.js → input-B6Q0NWCS.js} +2 -2
- package/public/assets/{input-CtLT7WXi.js.map → input-B6Q0NWCS.js.map} +1 -1
- package/public/assets/{insights-BJf1vwrf.js → insights-DUhXqZTZ.js} +2 -2
- package/public/assets/{insights-BJf1vwrf.js.map → insights-DUhXqZTZ.js.map} +1 -1
- package/public/assets/{integrations-BZSHasnY.js → integrations-lbg5CjtE.js} +2 -2
- package/public/assets/{integrations-BZSHasnY.js.map → integrations-lbg5CjtE.js.map} +1 -1
- package/public/assets/{lib-ByLQqcD1.js → lib-BHsWPMKB.js} +2 -2
- package/public/assets/{lib-ByLQqcD1.js.map → lib-BHsWPMKB.js.map} +1 -1
- package/public/assets/lucide-react-DuhlF2Sj.js +10 -0
- package/public/assets/lucide-react-DuhlF2Sj.js.map +1 -0
- package/public/assets/{maintenance-uEI1lArh.js → maintenance-CQgneSVf.js} +2 -2
- package/public/assets/{maintenance-uEI1lArh.js.map → maintenance-CQgneSVf.js.map} +1 -1
- package/public/assets/{managed-agents-mX3hHpOP.js → managed-agents-DsnYf6fm.js} +2 -2
- package/public/assets/{managed-agents-mX3hHpOP.js.map → managed-agents-DsnYf6fm.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-ClNwMC48.js → markdown-preview-impl-C1ZtGJp6.js} +2 -2
- package/public/assets/{markdown-preview-impl-ClNwMC48.js.map → markdown-preview-impl-C1ZtGJp6.js.map} +1 -1
- package/public/assets/{memory-OfusEzGh.js → memory-SSLUTOI2.js} +2 -2
- package/public/assets/{memory-OfusEzGh.js.map → memory-SSLUTOI2.js.map} +1 -1
- package/public/assets/{messages-DsQGj0gU.js → messages-BY26OP4K.js} +2 -2
- package/public/assets/{messages-DsQGj0gU.js.map → messages-BY26OP4K.js.map} +1 -1
- package/public/assets/{orchestrators-BwWyl4mB.js → orchestrators-OY7yPzVI.js} +2 -2
- package/public/assets/{orchestrators-BwWyl4mB.js.map → orchestrators-OY7yPzVI.js.map} +1 -1
- package/public/assets/{overview-CED1SeTY.js → overview-Dj0u1tPG.js} +2 -2
- package/public/assets/{overview-CED1SeTY.js.map → overview-Dj0u1tPG.js.map} +1 -1
- package/public/assets/{pairs-BNu6ZWg4.js → pairs-CrUR50ou.js} +2 -2
- package/public/assets/{pairs-BNu6ZWg4.js.map → pairs-CrUR50ou.js.map} +1 -1
- package/public/assets/{projects-BmopRiUD.js → projects-B_AtxHtH.js} +2 -2
- package/public/assets/{projects-BmopRiUD.js.map → projects-B_AtxHtH.js.map} +1 -1
- package/public/assets/{prompt-settings-Ci2n1cFt.js → prompt-settings-CPtYSban.js} +2 -2
- package/public/assets/{prompt-settings-Ci2n1cFt.js.map → prompt-settings-CPtYSban.js.map} +1 -1
- package/public/assets/{react-dom-cLkVFBsS.js → react-dom-DWBmcawP.js} +2 -2
- package/public/assets/{react-dom-cLkVFBsS.js.map → react-dom-DWBmcawP.js.map} +1 -1
- package/public/assets/{registry-DwXnDIql.js → registry-BgUALWn5.js} +2 -2
- package/public/assets/{registry-DwXnDIql.js.map → registry-BgUALWn5.js.map} +1 -1
- package/public/assets/{security-AW5EbdOm.js → security-5lHfGk7g.js} +2 -2
- package/public/assets/{security-AW5EbdOm.js.map → security-5lHfGk7g.js.map} +1 -1
- package/public/assets/{select-CEeWX4hp.js → select-Cgx4QSLj.js} +2 -2
- package/public/assets/{select-CEeWX4hp.js.map → select-Cgx4QSLj.js.map} +1 -1
- package/public/assets/{settings-DYUdGz4G.js → settings-DFGgxyun.js} +2 -2
- package/public/assets/{settings-DYUdGz4G.js.map → settings-DFGgxyun.js.map} +1 -1
- package/public/assets/{store-BaslPLIP.js → store-BM4zM89B.js} +3 -3
- package/public/assets/store-BM4zM89B.js.map +1 -0
- package/public/assets/{task-chip-kxk4eNm1.js → task-chip-DXfvXBDx.js} +2 -2
- package/public/assets/{task-chip-kxk4eNm1.js.map → task-chip-DXfvXBDx.js.map} +1 -1
- package/public/assets/{tasks-DxCsrY4g.js → tasks-BvH5rluu.js} +2 -2
- package/public/assets/{tasks-DxCsrY4g.js.map → tasks-BvH5rluu.js.map} +1 -1
- package/public/assets/{teams-CF7vi-oM.js → teams-VZOIOdoE.js} +2 -2
- package/public/assets/{teams-CF7vi-oM.js.map → teams-VZOIOdoE.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-Dy3vqOnW.js → terminal-viewer-impl-DYY3Wmoe.js} +2 -2
- package/public/assets/{terminal-viewer-impl-Dy3vqOnW.js.map → terminal-viewer-impl-DYY3Wmoe.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-B06XtRUZ.js → use-keyboard-viewport-CP109w9y.js} +2 -2
- package/public/assets/{use-keyboard-viewport-B06XtRUZ.js.map → use-keyboard-viewport-CP109w9y.js.map} +1 -1
- package/public/assets/{user-avatar-DuwCFrER.js → user-avatar-Dw6mzWhv.js} +2 -2
- package/public/assets/{user-avatar-DuwCFrER.js.map → user-avatar-Dw6mzWhv.js.map} +1 -1
- package/public/assets/{work-queue-BPaGXrCE.js → work-queue-B6Xk7QNe.js} +2 -2
- package/public/assets/{work-queue-BPaGXrCE.js.map → work-queue-B6Xk7QNe.js.map} +1 -1
- package/public/assets/{workspaces-BLEXgnlj.js → workspaces-CPIzAHAe.js} +2 -2
- package/public/assets/{workspaces-BLEXgnlj.js.map → workspaces-CPIzAHAe.js.map} +1 -1
- package/public/index.html +23 -23
- package/runner/plugins/claude/.claude-plugin/plugin.json +1 -1
- package/src/branch-landed.ts +3 -7
- package/src/bus.ts +2 -2
- package/src/db/connection.ts +36 -12
- package/src/db/maintenance-worker.ts +23 -11
- package/src/db/message-reads.ts +17 -1
- package/src/maintenance/jobs.ts +18 -12
- package/src/maintenance/scheduler.ts +8 -3
- package/src/maintenance/types.ts +5 -1
- package/src/routes/sse.ts +2 -1
- package/src/services/git-process.ts +61 -0
- package/src/services/git-remote.ts +68 -17
- package/src/services/task-lifecycle.ts +2 -6
- package/src/sse.ts +110 -44
- package/public/assets/formatted-body-C1AprxyU.js +0 -3
- package/public/assets/lucide-react-Dc3ZwCBi.js +0 -9
- package/public/assets/lucide-react-Dc3ZwCBi.js.map +0 -1
- package/public/assets/store-BaslPLIP.js.map +0 -1
package/public/index.html
CHANGED
|
@@ -12,34 +12,34 @@
|
|
|
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-Ceiwd02X.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-DuhlF2Sj.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-B-T7lg0a.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-BIjlN-xB.js">
|
|
21
|
+
<link rel="modulepreload" crossorigin href="./assets/react-dom-DWBmcawP.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-B9G83DXv.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-DT-OV6sQ.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-C7aTlosb.js">
|
|
25
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-B3e4B9pg.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-BInckSJC.js">
|
|
28
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-DGfp5dLz.js">
|
|
29
|
+
<link rel="modulepreload" crossorigin href="./assets/es2015-CnKXYKpf.js">
|
|
30
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-BlzVGRhh.js">
|
|
31
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-OUPTEP6q.js">
|
|
32
|
+
<link rel="modulepreload" crossorigin href="./assets/dist-B6woQ9Tb.js">
|
|
33
33
|
<link rel="modulepreload" crossorigin href="./assets/types-uVOXgvMT.js">
|
|
34
34
|
<link rel="modulepreload" crossorigin href="./assets/display-exQDWum3.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-BM4zM89B.js">
|
|
36
|
+
<link rel="modulepreload" crossorigin href="./assets/agent-type-icon-CeEoMouY.js">
|
|
37
|
+
<link rel="modulepreload" crossorigin href="./assets/button-CzdTkRIf.js">
|
|
38
|
+
<link rel="modulepreload" crossorigin href="./assets/copy-button-B3o1jpLu.js">
|
|
39
|
+
<link rel="modulepreload" crossorigin href="./assets/formatted-body-q_qzt1L-.js">
|
|
40
|
+
<link rel="modulepreload" crossorigin href="./assets/task-chip-DXfvXBDx.js">
|
|
41
|
+
<link rel="modulepreload" crossorigin href="./assets/badge-DCIT3Irj.js">
|
|
42
|
+
<link rel="modulepreload" crossorigin href="./assets/input-B6Q0NWCS.js">
|
|
43
43
|
<link rel="modulepreload" crossorigin href="./assets/themes-DjSwmAZN.js">
|
|
44
44
|
<link rel="stylesheet" crossorigin href="./assets/index-DcmG5gLR.css">
|
|
45
45
|
</head>
|
package/src/branch-landed.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { resolveCoordinatorAgentId } from "./steward-identity";
|
|
|
8
8
|
import { stewardFallbackTarget } from "./config";
|
|
9
9
|
import { handleLandedIssueLifecycle } from "./services/issue-lifecycle";
|
|
10
10
|
import { handleLandedTaskLifecycle } from "./services/task-lifecycle";
|
|
11
|
+
import { runGit } from "./services/git-process";
|
|
11
12
|
import type { WorkspaceMergePreview, WorkspaceRecord } from "./types";
|
|
12
13
|
|
|
13
14
|
// #444 — branch.landed is the first rule-driven type: the on-main "merged" fan-out audience comes
|
|
@@ -67,14 +68,9 @@ async function landedCommitSubjects(input: BranchLandedInput, log: BranchLandedL
|
|
|
67
68
|
const tipSha = cleanRef(input.mergedSha);
|
|
68
69
|
if (!repoRoot || !baseSha || !tipSha || baseSha === tipSha) return undefined;
|
|
69
70
|
try {
|
|
70
|
-
const
|
|
71
|
-
stdin: "ignore",
|
|
72
|
-
stdout: "pipe",
|
|
73
|
-
stderr: "pipe",
|
|
74
|
-
});
|
|
75
|
-
const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
|
|
71
|
+
const { stdout, stderr, exitCode, timedOut } = await runGit(["-C", repoRoot, "log", "--format=%B%x00", `${baseSha}..${tipSha}`]);
|
|
76
72
|
if (exitCode !== 0) {
|
|
77
|
-
const error = stderr.trim() || `exit ${exitCode}`;
|
|
73
|
+
const error = timedOut ? "timed out" : stderr.trim() || `exit ${exitCode}`;
|
|
78
74
|
log(`issue lifecycle commit range scan failed for ${input.workspace.id} (${baseSha.slice(0, 12)}..${tipSha.slice(0, 12)}): ${error}; falling back to tip subject`);
|
|
79
75
|
return undefined;
|
|
80
76
|
}
|
package/src/bus.ts
CHANGED
|
@@ -528,6 +528,7 @@ function fanoutAgent(agentId: string, cache: Map<string, AgentCard | null>): Age
|
|
|
528
528
|
|
|
529
529
|
function connectionWantsEvent(conn: BusConnection, event: RelayEvent, agentCache: Map<string, AgentCard | null>): boolean {
|
|
530
530
|
if (!matchesSubscription(conn.subscriptions, event.type)) return false;
|
|
531
|
+
if (event.type === "agent.status" && conn.agentId) return event.subject === conn.agentId || (typeof event.data.id === "string" && event.data.id === conn.agentId);
|
|
531
532
|
if (event.type === "message.new" && conn.agentId) {
|
|
532
533
|
return messageMatchesAgent(event.data as unknown as Message, conn.agentId, fanoutAgent(conn.agentId, agentCache));
|
|
533
534
|
}
|
|
@@ -538,8 +539,7 @@ function connectionWantsEvent(conn: BusConnection, event: RelayEvent, agentCache
|
|
|
538
539
|
if (event.type.startsWith("command.")) {
|
|
539
540
|
const command = isRecord(event.data.command) ? event.data.command : undefined;
|
|
540
541
|
if (!command) return true;
|
|
541
|
-
const target = typeof command.target === "string" ? command.target : "";
|
|
542
|
-
const source = typeof command.source === "string" ? command.source : "";
|
|
542
|
+
const target = typeof command.target === "string" ? command.target : "", source = typeof command.source === "string" ? command.source : "";
|
|
543
543
|
return target === conn.componentId || target === conn.agentId || source === conn.componentId || source === conn.agentId;
|
|
544
544
|
}
|
|
545
545
|
return true;
|
package/src/db/connection.ts
CHANGED
|
@@ -74,24 +74,48 @@ export function runDbMaintenance(options: { vacuum?: boolean } = {}): {
|
|
|
74
74
|
return { analyzed: true, checkpointed: true, vacuumed };
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
export async function
|
|
77
|
+
export async function runDbMaintenanceWorker<T>(operation: string, args: string[], signal?: AbortSignal): Promise<T> {
|
|
78
|
+
if (!dbPath || dbPath === ":memory:") {
|
|
79
|
+
throw new Error("database worker requires a file-backed database");
|
|
80
|
+
}
|
|
81
|
+
if (signal?.aborted) throw new Error("database worker aborted");
|
|
82
|
+
const proc = Bun.spawn([process.execPath, join(import.meta.dir, "maintenance-worker.ts"), operation, dbPath, ...args], {
|
|
83
|
+
stdin: "ignore",
|
|
84
|
+
stdout: "pipe",
|
|
85
|
+
stderr: "pipe",
|
|
86
|
+
});
|
|
87
|
+
let killTimer: Timer | undefined;
|
|
88
|
+
const abort = () => {
|
|
89
|
+
proc.kill("SIGTERM");
|
|
90
|
+
killTimer = setTimeout(() => proc.kill("SIGKILL"), 2_000);
|
|
91
|
+
};
|
|
92
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
93
|
+
try {
|
|
94
|
+
const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
|
|
95
|
+
if (signal?.aborted) throw new Error("database worker aborted");
|
|
96
|
+
if (exitCode !== 0) {
|
|
97
|
+
const detail = stderr.trim() || stdout.trim() || `exit ${exitCode}`;
|
|
98
|
+
throw new Error(`database worker ${operation} failed: ${detail}`);
|
|
99
|
+
}
|
|
100
|
+
return JSON.parse(stdout) as T;
|
|
101
|
+
} finally {
|
|
102
|
+
signal?.removeEventListener("abort", abort);
|
|
103
|
+
if (killTimer) clearTimeout(killTimer);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function runDbMaintenanceAsync(options: { vacuum?: boolean; signal?: AbortSignal } = {}): Promise<{
|
|
78
108
|
analyzed: boolean;
|
|
79
109
|
checkpointed: boolean;
|
|
80
110
|
vacuumed: boolean;
|
|
81
111
|
offThread: boolean;
|
|
82
112
|
}> {
|
|
83
113
|
if (!dbPath || dbPath === ":memory:") return { ...runDbMaintenance(options), offThread: false };
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const [stdout, stderr, exitCode] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text(), proc.exited]);
|
|
90
|
-
if (exitCode !== 0) {
|
|
91
|
-
const detail = stderr.trim() || stdout.trim() || `exit ${exitCode}`;
|
|
92
|
-
throw new Error(`db maintenance worker failed: ${detail}`);
|
|
93
|
-
}
|
|
94
|
-
const result = JSON.parse(stdout) as { analyzed: boolean; checkpointed: boolean; vacuumed: boolean };
|
|
114
|
+
const result = await runDbMaintenanceWorker<{ analyzed: boolean; checkpointed: boolean; vacuumed: boolean }>(
|
|
115
|
+
"db-maintenance",
|
|
116
|
+
[options.vacuum ? "vacuum" : "no-vacuum"],
|
|
117
|
+
options.signal,
|
|
118
|
+
);
|
|
95
119
|
return { ...result, offThread: true };
|
|
96
120
|
}
|
|
97
121
|
|
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
import { Database } from "bun:sqlite";
|
|
2
2
|
import { applyConnectionPragmas } from "./connection";
|
|
3
3
|
|
|
4
|
-
const [, , dbPath,
|
|
4
|
+
const [, , operation, dbPath, arg] = Bun.argv;
|
|
5
5
|
|
|
6
|
-
if (!dbPath) {
|
|
7
|
-
console.error("db path
|
|
6
|
+
if (!operation || !dbPath) {
|
|
7
|
+
console.error("operation and db path are required");
|
|
8
8
|
process.exit(2);
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
const db = new Database(dbPath, { create: true });
|
|
12
12
|
try {
|
|
13
13
|
applyConnectionPragmas(db);
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
if (operation === "db-maintenance") {
|
|
15
|
+
db.run("ANALYZE");
|
|
16
|
+
db.run("PRAGMA optimize");
|
|
17
|
+
db.run("PRAGMA wal_checkpoint(TRUNCATE)");
|
|
18
|
+
let vacuumed = false;
|
|
19
|
+
if (arg === "vacuum") {
|
|
20
|
+
db.run("VACUUM");
|
|
21
|
+
vacuumed = true;
|
|
22
|
+
}
|
|
23
|
+
console.log(JSON.stringify({ analyzed: true, checkpointed: true, vacuumed }));
|
|
24
|
+
} else if (operation === "old-message-prune") {
|
|
25
|
+
const cutoff = Number(arg);
|
|
26
|
+
if (!Number.isFinite(cutoff)) throw new Error("old-message-prune requires a cutoff timestamp");
|
|
27
|
+
const prunedMessages = db.transaction(() => {
|
|
28
|
+
db.query("UPDATE messages SET reply_to = NULL WHERE reply_to IN (SELECT id FROM messages WHERE created_at < ?)").run(cutoff);
|
|
29
|
+
return db.query("DELETE FROM messages WHERE created_at < ?").run(cutoff).changes;
|
|
30
|
+
})();
|
|
31
|
+
console.log(JSON.stringify({ prunedMessages }));
|
|
32
|
+
} else {
|
|
33
|
+
throw new Error(`unknown maintenance operation: ${operation}`);
|
|
21
34
|
}
|
|
22
|
-
console.log(JSON.stringify({ analyzed: true, checkpointed: true, vacuumed }));
|
|
23
35
|
} finally {
|
|
24
36
|
db.close();
|
|
25
37
|
}
|
package/src/db/message-reads.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { matchAgents } from "../agent-ref";
|
|
|
17
17
|
import { getAgent } from "./agents.ts";
|
|
18
18
|
import { sendMessageWithResult } from "./messages.ts";
|
|
19
19
|
import { deleteArtifactLinksForEntity } from "./artifacts.ts";
|
|
20
|
-
import { getDb, timedQuery } from "./connection.ts";
|
|
20
|
+
import { getDb, getDbPath, runDbMaintenanceWorker, timedQuery } from "./connection.ts";
|
|
21
21
|
import { legacyChannelTargets, isChannelAgentId } from "./delivery.ts";
|
|
22
22
|
import { messageTaskClosed } from "./claim-sql.ts";
|
|
23
23
|
import { MSG_SELECT, rowToMessage } from "./mappers.ts";
|
|
@@ -630,6 +630,10 @@ export function getLatestMessageId(): number {
|
|
|
630
630
|
|
|
631
631
|
export function pruneOldMessages(maxAgeMs: number): number {
|
|
632
632
|
const cutoff = Date.now() - maxAgeMs;
|
|
633
|
+
return pruneOldMessagesBefore(cutoff);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
function pruneOldMessagesBefore(cutoff: number): number {
|
|
633
637
|
return getDb().transaction(() => {
|
|
634
638
|
getDb()
|
|
635
639
|
.query("UPDATE messages SET reply_to = NULL WHERE reply_to IN (SELECT id FROM messages WHERE created_at < ?)")
|
|
@@ -639,3 +643,15 @@ export function pruneOldMessages(maxAgeMs: number): number {
|
|
|
639
643
|
.run(cutoff).changes;
|
|
640
644
|
})();
|
|
641
645
|
}
|
|
646
|
+
|
|
647
|
+
export async function pruneOldMessagesAsync(maxAgeMs: number, options: { signal?: AbortSignal } = {}): Promise<{
|
|
648
|
+
prunedMessages: number;
|
|
649
|
+
offThread: boolean;
|
|
650
|
+
}> {
|
|
651
|
+
const cutoff = Date.now() - maxAgeMs;
|
|
652
|
+
if (!getDbPath() || getDbPath() === ":memory:") {
|
|
653
|
+
return { prunedMessages: pruneOldMessagesBefore(cutoff), offThread: false };
|
|
654
|
+
}
|
|
655
|
+
const result = await runDbMaintenanceWorker<{ prunedMessages: number }>("old-message-prune", [String(cutoff)], options.signal);
|
|
656
|
+
return { ...result, offThread: true };
|
|
657
|
+
}
|
package/src/maintenance/jobs.ts
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
listStaleQueuedDirectTargets,
|
|
14
14
|
pruneOrphanedSharedWorkspaces,
|
|
15
15
|
pruneOfflineAgents,
|
|
16
|
-
|
|
16
|
+
pruneOldMessagesAsync,
|
|
17
17
|
pruneProviderQuotaHistory,
|
|
18
18
|
reapStaleAgents,
|
|
19
19
|
reapStaleOrchestrators,
|
|
@@ -72,6 +72,10 @@ import type { MaintenanceJobDefinition } from "./types";
|
|
|
72
72
|
|
|
73
73
|
let dbMaintenanceRuns = 0;
|
|
74
74
|
|
|
75
|
+
function isFleetIdleForVacuum(): boolean {
|
|
76
|
+
return !listAgents({ status: "busy" }).some((agent) => agent.kind === "provider" && agent.id !== "user" && agent.id !== "system");
|
|
77
|
+
}
|
|
78
|
+
|
|
75
79
|
function isManagedBusyWatchCandidate(agent: ReturnType<typeof listAgents>[number]): boolean {
|
|
76
80
|
if (agent.kind !== "provider") return false;
|
|
77
81
|
if (agent.status !== "busy") return false;
|
|
@@ -315,17 +319,17 @@ export const definitions: MaintenanceJobDefinition[] = [
|
|
|
315
319
|
description: "Stop spawned sessions whose relay agent is offline/gone but whose process the orchestrator still reports running, after a grace period for self-heal.",
|
|
316
320
|
intervalMs: ORPHAN_REAPER_INTERVAL_MS,
|
|
317
321
|
runOnStart: false,
|
|
318
|
-
handler: reapOrphanedSessions,
|
|
322
|
+
handler: () => reapOrphanedSessions(),
|
|
319
323
|
},
|
|
320
|
-
{ id: "transient-agent-reaper", title: "Transient agent reaper", description: "Cleanly shut down transient spawned agents once they are idle and any isolated branch work has landed.", intervalMs: REAP_INTERVAL_MS, runOnStart: false, handler: reapTransientAgents },
|
|
321
|
-
{ id: "rate-limit-resume", title: "Rate-limit resume", description: "Auto-resume agents held on a provider usage/rate-limit stall once their reset window has passed (#286).", intervalMs: REAP_INTERVAL_MS, runOnStart: false, handler: resumeRateLimitedAgents },
|
|
324
|
+
{ id: "transient-agent-reaper", title: "Transient agent reaper", description: "Cleanly shut down transient spawned agents once they are idle and any isolated branch work has landed.", intervalMs: REAP_INTERVAL_MS, runOnStart: false, handler: () => reapTransientAgents() },
|
|
325
|
+
{ id: "rate-limit-resume", title: "Rate-limit resume", description: "Auto-resume agents held on a provider usage/rate-limit stall once their reset window has passed (#286).", intervalMs: REAP_INTERVAL_MS, runOnStart: false, handler: () => resumeRateLimitedAgents() },
|
|
322
326
|
{
|
|
323
327
|
id: "stuck-busy-watchdog",
|
|
324
328
|
title: "Stuck busy watchdog",
|
|
325
329
|
description: "Warn when a managed provider stays busy past the threshold without provider progress (#53).",
|
|
326
330
|
intervalMs: REAP_INTERVAL_MS,
|
|
327
331
|
runOnStart: false,
|
|
328
|
-
handler: runStuckBusyWatchdog,
|
|
332
|
+
handler: () => runStuckBusyWatchdog(),
|
|
329
333
|
},
|
|
330
334
|
{
|
|
331
335
|
id: "team-reap",
|
|
@@ -341,7 +345,7 @@ export const definitions: MaintenanceJobDefinition[] = [
|
|
|
341
345
|
description: "Gently ping a team's current coordinator once when the team has had no busy members for the configured threshold.",
|
|
342
346
|
intervalMs: REAP_INTERVAL_MS,
|
|
343
347
|
runOnStart: false,
|
|
344
|
-
handler: runTeamIdleWatchdog,
|
|
348
|
+
handler: () => runTeamIdleWatchdog(),
|
|
345
349
|
},
|
|
346
350
|
{
|
|
347
351
|
id: "orchestrator-reaper",
|
|
@@ -373,9 +377,9 @@ export const definitions: MaintenanceJobDefinition[] = [
|
|
|
373
377
|
description: "Delete messages outside the configured retention window.",
|
|
374
378
|
intervalMs: DAY_MS,
|
|
375
379
|
runOnStart: false,
|
|
376
|
-
handler() {
|
|
380
|
+
handler(context) {
|
|
377
381
|
const retentionDays = Number(process.env.RETENTION_DAYS) || 30;
|
|
378
|
-
return
|
|
382
|
+
return pruneOldMessagesAsync(retentionDays * DAY_MS, { signal: context?.signal });
|
|
379
383
|
},
|
|
380
384
|
},
|
|
381
385
|
{
|
|
@@ -385,10 +389,12 @@ export const definitions: MaintenanceJobDefinition[] = [
|
|
|
385
389
|
intervalMs: DB_MAINTENANCE_INTERVAL_MS,
|
|
386
390
|
runOnStart: false,
|
|
387
391
|
timeoutMs: 5 * 60 * 1000,
|
|
388
|
-
async handler() {
|
|
392
|
+
async handler(context) {
|
|
389
393
|
dbMaintenanceRuns += 1;
|
|
390
|
-
const
|
|
391
|
-
|
|
394
|
+
const vacuumDue = DB_VACUUM_EVERY > 0 && dbMaintenanceRuns % DB_VACUUM_EVERY === 0;
|
|
395
|
+
const vacuum = vacuumDue && isFleetIdleForVacuum();
|
|
396
|
+
const result = await runDbMaintenanceAsync({ vacuum, signal: context?.signal });
|
|
397
|
+
return vacuumDue && !vacuum ? { ...result, vacuumSkipped: "fleet-busy" } : result;
|
|
392
398
|
},
|
|
393
399
|
},
|
|
394
400
|
{
|
|
@@ -492,7 +498,7 @@ export const definitions: MaintenanceJobDefinition[] = [
|
|
|
492
498
|
intervalMs: 60 * 1000,
|
|
493
499
|
runOnStart: false,
|
|
494
500
|
timeoutMs: 10 * 1000,
|
|
495
|
-
handler: runAutoMergeWatchdog,
|
|
501
|
+
handler: () => runAutoMergeWatchdog(),
|
|
496
502
|
},
|
|
497
503
|
{
|
|
498
504
|
id: "workspace-idle-refresh",
|
|
@@ -118,8 +118,9 @@ async function runJob(definition: MaintenanceJobDefinition, options: { force?: b
|
|
|
118
118
|
return { id: definition.id, status: "skipped", startedAt, finishedAt: Date.now(), durationMs: 0, result: { reason: "not due or already running", state } };
|
|
119
119
|
}
|
|
120
120
|
|
|
121
|
+
const abortController = new AbortController();
|
|
121
122
|
try {
|
|
122
|
-
const result = await withTimeout(Promise.resolve(definition.handler()), timeoutMs);
|
|
123
|
+
const result = await withTimeout(Promise.resolve(definition.handler({ signal: abortController.signal })), timeoutMs, abortController);
|
|
123
124
|
const finishedAt = Date.now();
|
|
124
125
|
const durationMs = finishedAt - startedAt;
|
|
125
126
|
db.query(`
|
|
@@ -145,10 +146,14 @@ async function runJob(definition: MaintenanceJobDefinition, options: { force?: b
|
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
148
|
-
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {
|
|
149
|
+
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, abortController?: AbortController): Promise<T> {
|
|
149
150
|
let timeout: Timer | undefined;
|
|
150
151
|
const timeoutPromise = new Promise<never>((_, reject) => {
|
|
151
|
-
timeout = setTimeout(() =>
|
|
152
|
+
timeout = setTimeout(() => {
|
|
153
|
+
const error = new Error(`maintenance job timed out after ${timeoutMs}ms`);
|
|
154
|
+
abortController?.abort(error);
|
|
155
|
+
reject(error);
|
|
156
|
+
}, timeoutMs);
|
|
152
157
|
});
|
|
153
158
|
try {
|
|
154
159
|
return await Promise.race([promise, timeoutPromise]);
|
package/src/maintenance/types.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import type { MaintenanceJob } from "../types";
|
|
2
2
|
|
|
3
|
+
export interface MaintenanceJobContext {
|
|
4
|
+
signal: AbortSignal;
|
|
5
|
+
}
|
|
6
|
+
|
|
3
7
|
export interface MaintenanceJobDefinition {
|
|
4
8
|
id: string;
|
|
5
9
|
title: string;
|
|
@@ -7,7 +11,7 @@ export interface MaintenanceJobDefinition {
|
|
|
7
11
|
intervalMs: number;
|
|
8
12
|
timeoutMs?: number;
|
|
9
13
|
runOnStart?: boolean;
|
|
10
|
-
handler: () => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
14
|
+
handler: (context?: MaintenanceJobContext) => Promise<Record<string, unknown>> | Record<string, unknown>;
|
|
11
15
|
}
|
|
12
16
|
|
|
13
17
|
export interface MaintenanceJobRow {
|
package/src/routes/sse.ts
CHANGED
|
@@ -5,5 +5,6 @@ import { type Handler } from "./_shared";
|
|
|
5
5
|
export const getEvents: Handler = (req) => {
|
|
6
6
|
const url = new URL(req.url);
|
|
7
7
|
const agentId = url.searchParams.get("for") || null;
|
|
8
|
-
|
|
8
|
+
const lastEventId = Number(req.headers.get("last-event-id") ?? "");
|
|
9
|
+
return createSSEStream(agentId, Number.isSafeInteger(lastEventId) && lastEventId > 0 ? lastEventId : 0);
|
|
9
10
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export const LAND_GIT_TIMEOUT_MS = 2_000;
|
|
2
|
+
|
|
3
|
+
export interface GitProcessResult {
|
|
4
|
+
stdout: string;
|
|
5
|
+
stderr: string;
|
|
6
|
+
exitCode: number;
|
|
7
|
+
timedOut: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function runGit(args: string[], timeoutMs = LAND_GIT_TIMEOUT_MS): Promise<GitProcessResult> {
|
|
11
|
+
const proc = Bun.spawn(["git", ...args], {
|
|
12
|
+
stdin: "ignore",
|
|
13
|
+
stdout: "pipe",
|
|
14
|
+
stderr: "pipe",
|
|
15
|
+
});
|
|
16
|
+
const stdoutPromise = streamText(proc.stdout);
|
|
17
|
+
const stderrPromise = streamText(proc.stderr);
|
|
18
|
+
const timedOut = await waitForExitOrTimeout(proc, timeoutMs);
|
|
19
|
+
const [stdout, stderr] = timedOut
|
|
20
|
+
? await Promise.all([settleText(stdoutPromise), settleText(stderrPromise)])
|
|
21
|
+
: await Promise.all([stdoutPromise, stderrPromise]);
|
|
22
|
+
return { stdout, stderr, exitCode: proc.exitCode ?? 1, timedOut };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function streamText(stream: ReadableStream<Uint8Array> | null): Promise<string> {
|
|
26
|
+
if (!stream) return "";
|
|
27
|
+
try {
|
|
28
|
+
return await new Response(stream).text();
|
|
29
|
+
} catch {
|
|
30
|
+
return "";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function settleText(promise: Promise<string>): Promise<string> {
|
|
35
|
+
return Promise.race([promise, new Promise<string>((resolve) => setTimeout(() => resolve(""), 25))]);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function waitForExitOrTimeout(proc: Bun.Subprocess<"ignore", "pipe", "pipe">, timeoutMs: number | undefined): Promise<boolean> {
|
|
39
|
+
if (!timeoutMs || timeoutMs <= 0) {
|
|
40
|
+
await proc.exited;
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
let timedOut = false;
|
|
44
|
+
let kill: ReturnType<typeof setTimeout> | undefined;
|
|
45
|
+
const timeout = setTimeout(() => {
|
|
46
|
+
timedOut = true;
|
|
47
|
+
try { proc.kill("SIGTERM"); } catch {}
|
|
48
|
+
kill = setTimeout(() => {
|
|
49
|
+
try { proc.kill("SIGKILL"); } catch {}
|
|
50
|
+
}, 1_000);
|
|
51
|
+
kill.unref?.();
|
|
52
|
+
}, timeoutMs);
|
|
53
|
+
timeout.unref?.();
|
|
54
|
+
try {
|
|
55
|
+
await proc.exited;
|
|
56
|
+
} finally {
|
|
57
|
+
clearTimeout(timeout);
|
|
58
|
+
if (kill) clearTimeout(kill);
|
|
59
|
+
}
|
|
60
|
+
return timedOut;
|
|
61
|
+
}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
// not drift: issue-lifecycle land-close (which repo a bare `#NNN` in a commit subject
|
|
4
4
|
// closes) AND project auto-seed (a project's `issueRepo` for chat #NNN linkify). Promoted
|
|
5
5
|
// out of issue-lifecycle.ts where it was private — see the DRY rule in CLAUDE.md.
|
|
6
|
+
import { runGit } from "./git-process";
|
|
7
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
8
|
+
import { dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
9
|
|
|
7
10
|
/**
|
|
8
11
|
* `owner/repo` out of any git remote URL form: `git@github.com:owner/repo.git`,
|
|
@@ -24,12 +27,7 @@ export async function remoteUrlFromGitRemoteAsync(repoRoot: string | undefined):
|
|
|
24
27
|
if (remoteUrlCache.has(repoRoot)) return remoteUrlCache.get(repoRoot);
|
|
25
28
|
let url: string | undefined;
|
|
26
29
|
try {
|
|
27
|
-
const
|
|
28
|
-
stdin: "ignore",
|
|
29
|
-
stdout: "pipe",
|
|
30
|
-
stderr: "ignore",
|
|
31
|
-
});
|
|
32
|
-
const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
30
|
+
const { stdout, exitCode } = await runGit(["-C", repoRoot, "remote", "get-url", "origin"]);
|
|
33
31
|
if (exitCode === 0) url = stdout.trim() || undefined;
|
|
34
32
|
} catch {
|
|
35
33
|
url = undefined;
|
|
@@ -42,17 +40,7 @@ export function remoteUrlFromGitRemote(repoRoot: string | undefined): string | u
|
|
|
42
40
|
if (!repoRoot) return undefined;
|
|
43
41
|
if (!remoteUrlCache) remoteUrlCache = new Map();
|
|
44
42
|
if (remoteUrlCache.has(repoRoot)) return remoteUrlCache.get(repoRoot);
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const proc = Bun.spawnSync(["git", "-C", repoRoot, "remote", "get-url", "origin"], {
|
|
48
|
-
stdin: "ignore",
|
|
49
|
-
stdout: "pipe",
|
|
50
|
-
stderr: "ignore",
|
|
51
|
-
});
|
|
52
|
-
if (proc.exitCode === 0) url = Buffer.from(proc.stdout).toString("utf8").trim() || undefined;
|
|
53
|
-
} catch {
|
|
54
|
-
url = undefined;
|
|
55
|
-
}
|
|
43
|
+
const url = remoteUrlFromGitConfig(repoRoot);
|
|
56
44
|
remoteUrlCache.set(repoRoot, url);
|
|
57
45
|
return url;
|
|
58
46
|
}
|
|
@@ -84,3 +72,66 @@ export function __resetRemoteRepoCacheForTests(): void {
|
|
|
84
72
|
remoteRepoCache = undefined;
|
|
85
73
|
remoteUrlCache = undefined;
|
|
86
74
|
}
|
|
75
|
+
|
|
76
|
+
function remoteUrlFromGitConfig(repoRoot: string): string | undefined {
|
|
77
|
+
const gitDir = findGitDir(repoRoot);
|
|
78
|
+
if (!gitDir) return undefined;
|
|
79
|
+
return originUrlFromConfig(join(gitDir, "config")) ?? originUrlFromConfig(join(commonGitDir(gitDir) ?? gitDir, "config"));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function findGitDir(start: string): string | undefined {
|
|
83
|
+
let dir = resolve(start);
|
|
84
|
+
while (true) {
|
|
85
|
+
const marker = join(dir, ".git");
|
|
86
|
+
try {
|
|
87
|
+
const stat = statSync(marker);
|
|
88
|
+
if (stat.isDirectory()) return marker;
|
|
89
|
+
if (stat.isFile()) return gitDirFromFile(marker, dir);
|
|
90
|
+
} catch {}
|
|
91
|
+
const parent = dirname(dir);
|
|
92
|
+
if (parent === dir) return undefined;
|
|
93
|
+
dir = parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function gitDirFromFile(marker: string, worktreeRoot: string): string | undefined {
|
|
98
|
+
try {
|
|
99
|
+
const content = readFileSync(marker, "utf8").trim();
|
|
100
|
+
const match = content.match(/^gitdir:\s*(.+)$/i);
|
|
101
|
+
if (!match?.[1]) return undefined;
|
|
102
|
+
return isAbsolute(match[1]) ? match[1] : resolve(worktreeRoot, match[1]);
|
|
103
|
+
} catch {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function commonGitDir(gitDir: string): string | undefined {
|
|
109
|
+
const commonDirFile = join(gitDir, "commondir");
|
|
110
|
+
if (!existsSync(commonDirFile)) return undefined;
|
|
111
|
+
try {
|
|
112
|
+
const common = readFileSync(commonDirFile, "utf8").trim();
|
|
113
|
+
if (!common) return undefined;
|
|
114
|
+
return isAbsolute(common) ? common : resolve(gitDir, common);
|
|
115
|
+
} catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function originUrlFromConfig(configPath: string): string | undefined {
|
|
121
|
+
let inOrigin = false;
|
|
122
|
+
try {
|
|
123
|
+
for (const line of readFileSync(configPath, "utf8").split(/\r?\n/)) {
|
|
124
|
+
const section = line.match(/^\s*\[(.+)]\s*$/);
|
|
125
|
+
if (section) {
|
|
126
|
+
inOrigin = /^remote\s+"origin"$/.test(section[1]!.trim());
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (!inOrigin) continue;
|
|
130
|
+
const url = line.match(/^\s*url\s*=\s*(.+?)\s*$/);
|
|
131
|
+
if (url?.[1]) return url[1].trim() || undefined;
|
|
132
|
+
}
|
|
133
|
+
} catch {
|
|
134
|
+
return undefined;
|
|
135
|
+
}
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
@@ -38,6 +38,7 @@ import { routeNotification } from "../notification-router";
|
|
|
38
38
|
import { resolveCoordinatorAgentId } from "../steward-identity";
|
|
39
39
|
import { isAgentExitDoneWhen, stageSatisfiesDoneWhen } from "./task-done-when";
|
|
40
40
|
import { emitTaskSemanticNotification, type TaskSemanticTransition } from "./task-semantic-events";
|
|
41
|
+
import { runGit } from "./git-process";
|
|
41
42
|
import type { IssueRef, TaskDeliverableStage, TaskGateState, WorkspaceRecord } from "../types";
|
|
42
43
|
import type { IssueTrackerAdapter } from "../issue-tracker";
|
|
43
44
|
|
|
@@ -107,12 +108,7 @@ async function landedDiffSummary(input: LandedTaskLifecycleInput, log: LogFn): P
|
|
|
107
108
|
const tipSha = input.mergedSha?.trim();
|
|
108
109
|
if (!repoRoot || !baseSha || !tipSha || baseSha === tipSha) return undefined;
|
|
109
110
|
try {
|
|
110
|
-
const
|
|
111
|
-
stdin: "ignore",
|
|
112
|
-
stdout: "pipe",
|
|
113
|
-
stderr: "pipe",
|
|
114
|
-
});
|
|
115
|
-
const [stdout, exitCode] = await Promise.all([new Response(proc.stdout).text(), proc.exited]);
|
|
111
|
+
const { stdout, exitCode } = await runGit(["-C", repoRoot, "diff", "--shortstat", `${baseSha}..${tipSha}`]);
|
|
116
112
|
if (exitCode !== 0) return undefined;
|
|
117
113
|
const out = stdout.trim();
|
|
118
114
|
return out || undefined;
|