@loicngr/kobo 1.11.36 → 1.11.38
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/AGENTS.md +18 -1
- package/CHANGELOG.md +8 -0
- package/dist/server/index.js +20 -24
- package/dist/server/routes/dev-server.js +2 -1
- package/dist/server/routes/pull-requests.js +5 -4
- package/dist/server/routes/workspaces.js +161 -109
- package/dist/server/services/agent/engines/claude-code/engine.js +11 -0
- package/dist/server/services/agent/engines/codex/client.js +1 -0
- package/dist/server/services/agent/engines/codex/engine.js +47 -10
- package/dist/server/services/agent/engines/codex/event-mapper.js +8 -2
- package/dist/server/services/agent/engines/codex/jsonrpc/peer.js +21 -8
- package/dist/server/services/agent/engines/codex/jsonrpc/transport.js +30 -3
- package/dist/server/services/agent/event-router.js +1 -1
- package/dist/server/services/agent/orchestrator.js +219 -78
- package/dist/server/services/agent/session-controller.js +12 -2
- package/dist/server/services/auto-loop-service.js +60 -6
- package/dist/server/services/change-source-branch-service.js +36 -4
- package/dist/server/services/cron-service.js +19 -0
- package/dist/server/services/db-backup-service.js +30 -0
- package/dist/server/services/dev-server-service.js +167 -38
- package/dist/server/services/image-service.js +25 -28
- package/dist/server/services/pr-checkout-service.js +34 -5
- package/dist/server/services/pr-watcher-service.js +5 -3
- package/dist/server/services/quota-backoff-service.js +25 -8
- package/dist/server/services/wakeup-service.js +23 -3
- package/dist/server/services/websocket-service.js +10 -2
- package/dist/server/services/workspace-service.js +8 -4
- package/dist/server/services/worktree-purge-service.js +3 -7
- package/dist/server/utils/agent-stop-result.js +12 -0
- package/dist/server/utils/git-ops.js +8 -1
- package/dist/server/utils/git-worktree-fingerprint.js +86 -0
- package/dist/server/utils/safe-path.js +21 -0
- package/dist/server/utils/script-runner.js +32 -9
- package/dist/server/utils/workspace-lifecycle-guard.js +27 -0
- package/package.json +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-BzzvcOmq.js +8 -0
- package/src/client/dist/pwa/assets/{ActivityFeed-Bakgvay6.css → ActivityFeed-Chsw1Mh7.css} +1 -1
- package/src/client/dist/pwa/assets/{ChangelogPage-BFGYQ6eY.js → ChangelogPage-D8563008.js} +1 -1
- package/src/client/dist/pwa/assets/ComparisonPanel-Bd0X08sg.js +2 -0
- package/src/client/dist/pwa/assets/ComparisonPanel-CRRaDs2D.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-A3pVradx.css +1 -0
- package/src/client/dist/pwa/assets/CreatePage-Bon8qIka.js +2 -0
- package/src/client/dist/pwa/assets/DashboardPage-D8g7_r4S.css +1 -0
- package/src/client/dist/pwa/assets/DashboardPage-DYxdxEul.js +1 -0
- package/src/client/dist/pwa/assets/{DiffViewer-DSlSAIiW.js → DiffViewer-BpsLNxnj.js} +3 -3
- package/src/client/dist/pwa/assets/{DocumentsPanel-C1eXTw_Q.js → DocumentsPanel-BJsJ0fNs.js} +1 -1
- package/src/client/dist/pwa/assets/{DrawerToggleButton-BxYpUb4y.js → DrawerToggleButton-BVLO5K5d.js} +1 -1
- package/src/client/dist/pwa/assets/{GitPanel-DnBWSdVp.js → GitPanel-Cal_wzC0.js} +3 -3
- package/src/client/dist/pwa/assets/{HealthPage-rX1TiaP9.js → HealthPage-RFZS7Lao.js} +1 -1
- package/src/client/dist/pwa/assets/{MainLayout-Ceuh5uJj.js → MainLayout-vvyiRcql.js} +2 -2
- package/src/client/dist/pwa/assets/{QBtnDropdown-K7syiyyH.js → QBtnDropdown-X8oNU5Mb.js} +1 -1
- package/src/client/dist/pwa/assets/{QDrawer-DapzE8DH.js → QDrawer-Cij17skX.js} +1 -1
- package/src/client/dist/pwa/assets/{QSelect-Bodml89k.js → QSelect-xd-AlGo_.js} +1 -1
- package/src/client/dist/pwa/assets/{QTooltip-K-9OdDfq.js → QTooltip-CwcCGSWq.js} +1 -1
- package/src/client/dist/pwa/assets/{SchedulePanel-CfumTzAa.js → SchedulePanel-CXRUsz6G.js} +1 -1
- package/src/client/dist/pwa/assets/{SearchPage-AhZYS-dS.js → SearchPage-DopbgLxh.js} +1 -1
- package/src/client/dist/pwa/assets/{SettingsPage-BoHXWvKn.js → SettingsPage-Jx6TnOzR.js} +1 -1
- package/src/client/dist/pwa/assets/{SplitWorkspacePage-3wK11mcu.js → SplitWorkspacePage-D7lGFRKs.js} +1 -1
- package/src/client/dist/pwa/assets/{TerminalPanel-CKg0BeNK.js → TerminalPanel-DI8DHb9e.js} +1 -1
- package/src/client/dist/pwa/assets/{TouchPan-D6ctyPYf.js → TouchPan-B3nrkCY8.js} +1 -1
- package/src/client/dist/pwa/assets/{TourReplayButton-Dn4Dy_kH.js → TourReplayButton-DU1UShMY.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspaceAttentionLabels-DnoabsfK.js → WorkspaceAttentionLabels-C-yN5IX8.js} +1 -1
- package/src/client/dist/pwa/assets/{WorkspacePage-DYQrfeYw.css → WorkspacePage-BAC6u9OP.css} +1 -1
- package/src/client/dist/pwa/assets/WorkspacePage-DKRpdpPg.js +4 -0
- package/src/client/dist/pwa/assets/{browser-DHpSUK8-.js → browser-f9MHQjfi.js} +1 -1
- package/src/client/dist/pwa/assets/{build-path-tree-DtEjsZlh.js → build-path-tree-BWBzP8os.js} +1 -1
- package/src/client/dist/pwa/assets/{de-Dt_m-H83.js → de-QkDLrcGq.js} +1 -1
- package/src/client/dist/pwa/assets/{documents-B2dg1wRf.js → documents-C2cLnEBQ.js} +1 -1
- package/src/client/dist/pwa/assets/{editor.api-Cd1D35oj.js → editor.api-sw146MWN.js} +1 -1
- package/src/client/dist/pwa/assets/{efforts-IB1vPjJQ.js → efforts-C5ZZgCH4.js} +1 -1
- package/src/client/dist/pwa/assets/{engineFeatures-DYSyDEJe.js → engineFeatures-CAVMDawq.js} +1 -1
- package/src/client/dist/pwa/assets/{es-DFg25DCS.js → es-BgQFKlo8.js} +1 -1
- package/src/client/dist/pwa/assets/{expand-template-CNAHiesL.js → expand-template-B6xAlAaM.js} +1 -1
- package/src/client/dist/pwa/assets/{format-DidwgbbX.js → format-BwWP6Aay.js} +1 -1
- package/src/client/dist/pwa/assets/{formatters-BjzyUHlu.js → formatters-DyWtO-nR.js} +1 -1
- package/src/client/dist/pwa/assets/{fr-CQ1slpol.js → fr-DAdLWjNM.js} +1 -1
- package/src/client/dist/pwa/assets/{freemarker2-jNGWawVw.js → freemarker2-DZ5Hm8UH.js} +1 -1
- package/src/client/dist/pwa/assets/{handlebars-DfSqClrW.js → handlebars-BRUGpTA7.js} +1 -1
- package/src/client/dist/pwa/assets/{html-ByLX-n8T.js → html-DNOayNNs.js} +1 -1
- package/src/client/dist/pwa/assets/i18n-CBez_vip.js +1 -0
- package/src/client/dist/pwa/assets/index-VL3fI9a3.js +20 -0
- package/src/client/dist/pwa/assets/{it-w09rfaP7.js → it-Bumaewp1.js} +1 -1
- package/src/client/dist/pwa/assets/{javascript-Dja1fHfv.js → javascript-C0tDQU3z.js} +1 -1
- package/src/client/dist/pwa/assets/{liquid-D315hW6C.js → liquid-CN46O6OF.js} +1 -1
- package/src/client/dist/pwa/assets/{mdx-B5yUwpzv.js → mdx-DM-lrr8Q.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco-contributions-NfxrU0r3.js → monaco-contributions-CkN7y-8c.js} +1 -1
- package/src/client/dist/pwa/assets/{monaco.contribution-D1hXPkDZ.js → monaco.contribution-2BcAOLjc.js} +2 -2
- package/src/client/dist/pwa/assets/{network-auth-CARVtbNe.js → network-auth-KYS4snQH.js} +1 -1
- package/src/client/dist/pwa/assets/{notify-theme-CKC3-EAZ.js → notify-theme-a91Tcr6o.js} +1 -1
- package/src/client/dist/pwa/assets/{pending-diff-open-BlLrQaW7.js → pending-diff-open-DOtELZfB.js} +1 -1
- package/src/client/dist/pwa/assets/{permissionModes-DMzFEQhr.js → permissionModes-C-9Crj-c.js} +1 -1
- package/src/client/dist/pwa/assets/{project-color-CFjdIj6W.js → project-color-BRe4oyNx.js} +1 -1
- package/src/client/dist/pwa/assets/{python-Dc6mnEip.js → python-BRpJG6sZ.js} +1 -1
- package/src/client/dist/pwa/assets/{razor-DArVFBGO.js → razor-CehCmvHl.js} +1 -1
- package/src/client/dist/pwa/assets/{render-chat-markdown-BTPAjU2g.js → render-chat-markdown-DlbQLZ02.js} +1 -1
- package/src/client/dist/pwa/assets/{typescript-COE1vIyB.js → typescript-Dt_8mVuj.js} +1 -1
- package/src/client/dist/pwa/assets/{use-tours-C8x2V1w5.js → use-tours-CSVM_54K.js} +1 -1
- package/src/client/dist/pwa/assets/{workspace-templates-DHyA_GEW.js → workspace-templates-GCObcjO6.js} +1 -1
- package/src/client/dist/pwa/assets/{xml-eYjr4cGR.js → xml-_gOwdzB4.js} +1 -1
- package/src/client/dist/pwa/assets/{yaml-UooOcssF.js → yaml-BxeBqpGf.js} +1 -1
- package/src/client/dist/pwa/index.html +1 -1
- package/src/client/dist/pwa/sw.js +1 -1
- package/src/client/dist/pwa/assets/ActivityFeed-BPx-xhFY.js +0 -8
- package/src/client/dist/pwa/assets/ComparisonPanel-C6DfjpZZ.js +0 -2
- package/src/client/dist/pwa/assets/ComparisonPanel-x1iuICgd.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-DxOyY65u.css +0 -1
- package/src/client/dist/pwa/assets/CreatePage-ypEzkAKv.js +0 -2
- package/src/client/dist/pwa/assets/DashboardPage-DsVRjzzy.js +0 -1
- package/src/client/dist/pwa/assets/DashboardPage-DsWn5cWl.css +0 -1
- package/src/client/dist/pwa/assets/WorkspacePage-BI7CW2yT.js +0 -4
- package/src/client/dist/pwa/assets/i18n-CnjXQjxy.js +0 -1
- package/src/client/dist/pwa/assets/index-DcwcFDrZ.js +0 -20
|
@@ -353,6 +353,7 @@ export function createClaudeCodeEngine() {
|
|
|
353
353
|
return;
|
|
354
354
|
console.warn(`[claude-engine] Compaction still reported active ${COMPACTION_STALL_TIMEOUT_MS}ms after it started — resuming the liveness deadline.`);
|
|
355
355
|
isCompacting = false;
|
|
356
|
+
safeEmit({ kind: 'session:compacting', active: false });
|
|
356
357
|
reevaluateLivenessPause();
|
|
357
358
|
if (activeSubagentTaskIds.size > 0 && !subagentStallTimer) {
|
|
358
359
|
// Resume declined because a subagent is still tracked — but the
|
|
@@ -468,6 +469,16 @@ export function createClaudeCodeEngine() {
|
|
|
468
469
|
clearCompactionStallTimer();
|
|
469
470
|
}
|
|
470
471
|
}
|
|
472
|
+
// Actual foreground output proves work resumed even if the SDK
|
|
473
|
+
// omitted its trailing status/compact_boundary message. Tool results
|
|
474
|
+
// and background progress can drain during compaction, so exclude them.
|
|
475
|
+
if (isCompacting &&
|
|
476
|
+
!('parent_tool_use_id' in msg && msg.parent_tool_use_id != null) &&
|
|
477
|
+
events.some((ev) => ev.kind === 'message:text' || ev.kind === 'message:thinking' || ev.kind === 'tool:call')) {
|
|
478
|
+
isCompacting = false;
|
|
479
|
+
clearCompactionStallTimer();
|
|
480
|
+
safeEmit({ kind: 'session:compacting', active: false });
|
|
481
|
+
}
|
|
471
482
|
for (const ev of events) {
|
|
472
483
|
if (ev.kind === 'tool:call')
|
|
473
484
|
pendingToolCallIds.add(ev.toolCallId);
|
|
@@ -11,6 +11,7 @@ import { buildResponseForResolve, handleServerRequest } from './server-requests.
|
|
|
11
11
|
import { spawnAppServer } from './spawn.js';
|
|
12
12
|
/** Long enough for normal tool work, short enough to recover a lost turn event. */
|
|
13
13
|
export const CODEX_TURN_IDLE_TIMEOUT_MS = 120_000;
|
|
14
|
+
export const CODEX_TOOL_IDLE_TIMEOUT_MS = 30 * 60_000;
|
|
14
15
|
export const CODEX_GRACEFUL_INTERRUPT_TIMEOUT_MS = 3_000;
|
|
15
16
|
/** Grace given to a SIGTERM before the child is killed outright. */
|
|
16
17
|
const CODEX_FORCE_KILL_TIMEOUT_MS = 3_000;
|
|
@@ -24,6 +25,35 @@ export const CODEX_SUBAGENT_STALL_TIMEOUT_MS = 10 * 60_000;
|
|
|
24
25
|
function wait(ms) {
|
|
25
26
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
26
27
|
}
|
|
28
|
+
/** A sent signal is only a request; resolve true exclusively on confirmed exit. */
|
|
29
|
+
function signalAndWaitForExit(child, signal, timeoutMs) {
|
|
30
|
+
if (child.exitCode !== null || child.signalCode !== null || child.pid === undefined)
|
|
31
|
+
return Promise.resolve(true);
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const cleanup = () => {
|
|
34
|
+
clearTimeout(timer);
|
|
35
|
+
child.removeListener('exit', onExit);
|
|
36
|
+
};
|
|
37
|
+
const onExit = () => {
|
|
38
|
+
cleanup();
|
|
39
|
+
resolve(true);
|
|
40
|
+
};
|
|
41
|
+
const timer = setTimeout(() => {
|
|
42
|
+
cleanup();
|
|
43
|
+
resolve(false);
|
|
44
|
+
}, timeoutMs);
|
|
45
|
+
timer.unref?.();
|
|
46
|
+
// Install before signalling so an immediate exit cannot be missed.
|
|
47
|
+
child.once('exit', onExit);
|
|
48
|
+
try {
|
|
49
|
+
child.kill(signal);
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
cleanup();
|
|
53
|
+
reject(err);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
27
57
|
class CodexTurnTimeoutError extends Error {
|
|
28
58
|
constructor() {
|
|
29
59
|
super('Codex stopped reporting activity for this turn');
|
|
@@ -77,7 +107,17 @@ export function createCodexEngine() {
|
|
|
77
107
|
}
|
|
78
108
|
};
|
|
79
109
|
const streamingBatcher = createStreamingBatcher(emitDirect);
|
|
80
|
-
const
|
|
110
|
+
const pendingToolCalls = new Set();
|
|
111
|
+
const safeEmit = (ev) => {
|
|
112
|
+
if (ev.kind === 'tool:call')
|
|
113
|
+
pendingToolCalls.add(ev.toolCallId);
|
|
114
|
+
if (ev.kind === 'tool:result')
|
|
115
|
+
pendingToolCalls.delete(ev.toolCallId);
|
|
116
|
+
if (ev.kind === 'tool:call' || ev.kind === 'tool:result') {
|
|
117
|
+
turnLiveness.setTimeoutMs(pendingToolCalls.size ? CODEX_TOOL_IDLE_TIMEOUT_MS : CODEX_TURN_IDLE_TIMEOUT_MS);
|
|
118
|
+
}
|
|
119
|
+
streamingBatcher.push(ev);
|
|
120
|
+
};
|
|
81
121
|
let rejectChildFailure;
|
|
82
122
|
const childFailurePromise = new Promise((_resolve, reject) => {
|
|
83
123
|
rejectChildFailure = reject;
|
|
@@ -324,6 +364,7 @@ export function createCodexEngine() {
|
|
|
324
364
|
autoApprove: (toolName, payload) => isWorkspacePermissionAllowed(options.workspaceId, { engine: 'codex', toolName, payload }),
|
|
325
365
|
});
|
|
326
366
|
},
|
|
367
|
+
onDisconnect: rejectChildFailure,
|
|
327
368
|
onError(err) {
|
|
328
369
|
console.error('[codex] JSON-RPC transport error:', err);
|
|
329
370
|
rejectTurnDone(err);
|
|
@@ -428,6 +469,7 @@ export function createCodexEngine() {
|
|
|
428
469
|
}
|
|
429
470
|
}
|
|
430
471
|
pendingByCallId.clear();
|
|
472
|
+
pendingToolCalls.clear();
|
|
431
473
|
client.close();
|
|
432
474
|
try {
|
|
433
475
|
child.kill('SIGTERM');
|
|
@@ -515,15 +557,10 @@ export function createCodexEngine() {
|
|
|
515
557
|
// stop, the archive, the delete and Kōbō's own shutdown, holding the
|
|
516
558
|
// worktree and its share of the model quota, with nothing left in
|
|
517
559
|
// this process tracking it.
|
|
518
|
-
if (
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
new
|
|
522
|
-
wait(CODEX_FORCE_KILL_TIMEOUT_MS).then(() => false),
|
|
523
|
-
]);
|
|
524
|
-
if (!exited) {
|
|
525
|
-
console.warn('[codex] app-server ignored SIGTERM — sending SIGKILL');
|
|
526
|
-
child.kill('SIGKILL');
|
|
560
|
+
if (!(await signalAndWaitForExit(child, 'SIGTERM', CODEX_FORCE_KILL_TIMEOUT_MS))) {
|
|
561
|
+
console.warn('[codex] app-server ignored SIGTERM — sending SIGKILL');
|
|
562
|
+
if (!(await signalAndWaitForExit(child, 'SIGKILL', CODEX_FORCE_KILL_TIMEOUT_MS))) {
|
|
563
|
+
throw new Error('Codex app-server did not exit after SIGKILL');
|
|
527
564
|
}
|
|
528
565
|
}
|
|
529
566
|
},
|
|
@@ -179,7 +179,7 @@ export function handleItemStarted(item, state) {
|
|
|
179
179
|
return events;
|
|
180
180
|
}
|
|
181
181
|
if (item.type === 'contextCompaction') {
|
|
182
|
-
events.push({ kind: 'session:
|
|
182
|
+
events.push({ kind: 'session:compacting', active: true });
|
|
183
183
|
return events;
|
|
184
184
|
}
|
|
185
185
|
return events;
|
|
@@ -189,6 +189,8 @@ export function handleItemStarted(item, state) {
|
|
|
189
189
|
*/
|
|
190
190
|
export function handleItemCompleted(item, state) {
|
|
191
191
|
const events = [];
|
|
192
|
+
if (item.type === 'contextCompaction')
|
|
193
|
+
return [{ kind: 'session:compacted' }];
|
|
192
194
|
if (item.type === 'agentMessage') {
|
|
193
195
|
const text = item.text ?? '';
|
|
194
196
|
const messageId = scopedId(state, item.id);
|
|
@@ -302,13 +304,17 @@ export function handleItemCompleted(item, state) {
|
|
|
302
304
|
}
|
|
303
305
|
if (item.type === 'plan') {
|
|
304
306
|
// Codex's `plan` is a markdown blob, not a structured list — parse bullets.
|
|
307
|
+
const toolCallId = scopedId(state, item.id);
|
|
305
308
|
events.push({
|
|
306
309
|
kind: 'tool:call',
|
|
307
310
|
messageId: '',
|
|
308
|
-
toolCallId
|
|
311
|
+
toolCallId,
|
|
309
312
|
name: 'TodoWrite',
|
|
310
313
|
input: { todos: parseCodexPlanText(item.text) },
|
|
311
314
|
});
|
|
315
|
+
// The plan has already completed. Close the synthetic update so both the
|
|
316
|
+
// tool card and the engine's idle watchdog see no outstanding operation.
|
|
317
|
+
events.push({ kind: 'tool:result', toolCallId, output: null, isError: false });
|
|
312
318
|
return events;
|
|
313
319
|
}
|
|
314
320
|
if (item.type === 'error') {
|
|
@@ -19,6 +19,7 @@ export class JsonRpcTimeoutError extends Error {
|
|
|
19
19
|
}
|
|
20
20
|
export function createJsonRpcPeer(opts) {
|
|
21
21
|
let nextId = 1;
|
|
22
|
+
let closedError;
|
|
22
23
|
const pending = new Map();
|
|
23
24
|
/** Remove a pending slot and clear its deadline in one place. */
|
|
24
25
|
const takePending = (id) => {
|
|
@@ -30,10 +31,21 @@ export function createJsonRpcPeer(opts) {
|
|
|
30
31
|
pending.delete(id);
|
|
31
32
|
return slot;
|
|
32
33
|
};
|
|
34
|
+
const rejectPending = (err) => {
|
|
35
|
+
if (closedError)
|
|
36
|
+
return;
|
|
37
|
+
closedError = err;
|
|
38
|
+
for (const id of pending.keys())
|
|
39
|
+
takePending(id)?.reject(err);
|
|
40
|
+
};
|
|
33
41
|
const transport = createJsonRpcTransport({
|
|
34
42
|
stdin: opts.stdin,
|
|
35
43
|
stdout: opts.stdout,
|
|
36
44
|
onError: opts.onError ?? (() => { }),
|
|
45
|
+
onDisconnect(err) {
|
|
46
|
+
rejectPending(err);
|
|
47
|
+
opts.onDisconnect?.(err);
|
|
48
|
+
},
|
|
37
49
|
onMessage(msg) {
|
|
38
50
|
// Response to one of our requests
|
|
39
51
|
if (msg.id != null && (msg.result !== undefined || msg.error !== undefined)) {
|
|
@@ -61,6 +73,8 @@ export function createJsonRpcPeer(opts) {
|
|
|
61
73
|
});
|
|
62
74
|
return {
|
|
63
75
|
request(method, params, timeoutMs) {
|
|
76
|
+
if (closedError)
|
|
77
|
+
return Promise.reject(closedError);
|
|
64
78
|
const id = nextId++;
|
|
65
79
|
const deadlineMs = timeoutMs ?? opts.defaultRequestTimeoutMs ?? DEFAULT_JSONRPC_REQUEST_TIMEOUT_MS;
|
|
66
80
|
return new Promise((resolve, reject) => {
|
|
@@ -73,7 +87,12 @@ export function createJsonRpcPeer(opts) {
|
|
|
73
87
|
slot.timer.unref?.();
|
|
74
88
|
}
|
|
75
89
|
pending.set(id, slot);
|
|
76
|
-
|
|
90
|
+
try {
|
|
91
|
+
transport.send({ jsonrpc: '2.0', id, method, params });
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
takePending(id)?.reject(err instanceof Error ? err : new Error(String(err)));
|
|
95
|
+
}
|
|
77
96
|
});
|
|
78
97
|
},
|
|
79
98
|
notify(method, params) {
|
|
@@ -86,14 +105,8 @@ export function createJsonRpcPeer(opts) {
|
|
|
86
105
|
transport.send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
87
106
|
},
|
|
88
107
|
close() {
|
|
108
|
+
rejectPending(new Error('peer closed'));
|
|
89
109
|
transport.close();
|
|
90
|
-
const err = new Error('peer closed');
|
|
91
|
-
for (const slot of pending.values()) {
|
|
92
|
-
if (slot.timer)
|
|
93
|
-
clearTimeout(slot.timer);
|
|
94
|
-
slot.reject(err);
|
|
95
|
-
}
|
|
96
|
-
pending.clear();
|
|
97
110
|
},
|
|
98
111
|
};
|
|
99
112
|
}
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
export function createJsonRpcTransport(opts) {
|
|
2
2
|
let buffer = '';
|
|
3
|
+
let closed = false;
|
|
4
|
+
const fail = (err) => {
|
|
5
|
+
if (closed)
|
|
6
|
+
return;
|
|
7
|
+
closed = true;
|
|
8
|
+
opts.onDisconnect?.(err);
|
|
9
|
+
opts.onError(err);
|
|
10
|
+
};
|
|
11
|
+
// Keep listeners installed through teardown: late pipe errors must stay handled.
|
|
12
|
+
opts.stdin.on('error', fail);
|
|
13
|
+
opts.stdout.on('error', fail);
|
|
14
|
+
opts.stdout.on('end', () => fail(new Error('JSON-RPC transport closed')));
|
|
3
15
|
opts.stdout.setEncoding?.('utf-8');
|
|
4
16
|
opts.stdout.on('data', (chunk) => {
|
|
5
17
|
buffer += chunk;
|
|
@@ -19,13 +31,28 @@ export function createJsonRpcTransport(opts) {
|
|
|
19
31
|
}
|
|
20
32
|
}
|
|
21
33
|
});
|
|
22
|
-
opts.stdout.on('error', opts.onError);
|
|
23
34
|
return {
|
|
24
35
|
send(msg) {
|
|
25
|
-
|
|
36
|
+
if (closed)
|
|
37
|
+
throw new Error('JSON-RPC transport closed');
|
|
38
|
+
try {
|
|
39
|
+
opts.stdin.write(`${JSON.stringify(msg)}\n`);
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
fail(err instanceof Error ? err : new Error(String(err)));
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
26
45
|
},
|
|
27
46
|
close() {
|
|
28
|
-
|
|
47
|
+
if (closed)
|
|
48
|
+
return;
|
|
49
|
+
closed = true;
|
|
50
|
+
try {
|
|
51
|
+
opts.stdin.end();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
/* Already disconnected. */
|
|
55
|
+
}
|
|
29
56
|
},
|
|
30
57
|
};
|
|
31
58
|
}
|
|
@@ -3,7 +3,7 @@ export function routeEvent(workspaceId, agentSessionId, event) {
|
|
|
3
3
|
// The compaction "in progress" signal is a transient live indicator — deliver
|
|
4
4
|
// it once and never persist/replay it. The persistent trace is `session:compacted`.
|
|
5
5
|
if (event.kind === 'session:compacting') {
|
|
6
|
-
emitEphemeral(workspaceId, 'agent:event', event);
|
|
6
|
+
emitEphemeral(workspaceId, 'agent:event', event, agentSessionId);
|
|
7
7
|
return;
|
|
8
8
|
}
|
|
9
9
|
if (event.kind === 'mcp:status' && event.status !== 'error') {
|