@parall/claude-agent 1.63.0 → 1.64.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dispatch.d.ts +16 -2
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +47 -13
- package/dist/index.js +22 -4
- package/dist/process-pump.d.ts.map +1 -1
- package/dist/process-pump.js +3 -0
- package/package.json +4 -4
- package/src/dispatch.ts +56 -14
- package/src/index.ts +24 -3
- package/src/process-pump.ts +3 -0
package/dist/dispatch.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CleanupForkOpts, CompactOpts, CompactResult, DispatchAdapter, DispatchInputLifecycle, DispatchOpts, ForkOpts, GatewayLogger, RuntimeActivityEvent, RuntimeBusyState, RuntimeEvent } from '@parall/agent-core';
|
|
1
|
+
import type { CleanupForkOpts, CompactOpts, CompactResult, DiscardedInjectionOutcome, DispatchAdapter, DispatchInputLifecycle, DispatchOpts, ForkOpts, GatewayLogger, RuntimeActivityEvent, RuntimeBusyState, RuntimeEvent } from '@parall/agent-core';
|
|
2
2
|
import type { ClaudeAgentConfig } from './config.js';
|
|
3
3
|
import { ClaudeInputRegistry } from './input-lifecycle.js';
|
|
4
4
|
import { ClaudeProcessPump } from './process-pump.js';
|
|
@@ -60,6 +60,7 @@ export type ProcessState = {
|
|
|
60
60
|
export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
61
61
|
private readonly opts;
|
|
62
62
|
readonly inputLifecycleMode: "explicit";
|
|
63
|
+
readonly inputSettlementMode: "receipt";
|
|
63
64
|
private readonly processes;
|
|
64
65
|
/** Retired by abortDispatch while the CLI still had its own work: busy until idle, then terminated. */
|
|
65
66
|
private readonly retiring;
|
|
@@ -88,7 +89,19 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
88
89
|
abortDispatch(sessionKey: string): void;
|
|
89
90
|
hasPendingInjections(sessionKey: string): boolean;
|
|
90
91
|
hasUnsettledInjections(sessionKey: string): boolean;
|
|
91
|
-
|
|
92
|
+
/**
|
|
93
|
+
* The gateway discarded the buffered copy of an injection without a
|
|
94
|
+
* dispatch(). A settled injection only owes bookkeeping. One that has NOT
|
|
95
|
+
* settled is an orphan: nothing will ever drain it, and the CLI does not
|
|
96
|
+
* start a queued input on its own once the turn it was steered into has
|
|
97
|
+
* ended — it works its queue only when the next stdin frame arrives, which
|
|
98
|
+
* would be the batch re-delivering this very member (prod 2026-09-03: lane
|
|
99
|
+
* dethroned after 10 idle minutes, then "refusing duplicate stdin write"
|
|
100
|
+
* on every retry). Withdraw it: kill the process (the session survives via
|
|
101
|
+
* --resume), report the input failed so the server releases the member,
|
|
102
|
+
* and let the gateway run it as a real turn.
|
|
103
|
+
*/
|
|
104
|
+
acknowledgeDiscardedInjection(sessionKey: string, deliveryKey: string): Promise<DiscardedInjectionOutcome>;
|
|
92
105
|
dispatch({ event, bodyForAgent, sessionKey, context, inputLifecycle, noteActivity, }: DispatchOpts): AsyncIterable<RuntimeEvent>;
|
|
93
106
|
getBranchPoint(_sessionKey: string): string | undefined;
|
|
94
107
|
getSessionHistoryPath(sessionKey: string): string | undefined;
|
|
@@ -125,6 +138,7 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
125
138
|
private maybeApplyRestart;
|
|
126
139
|
private ensureProcess;
|
|
127
140
|
private spawnProcess;
|
|
141
|
+
/** Resolves once every live input's failed report has settled (best effort). */
|
|
128
142
|
private killProcess;
|
|
129
143
|
private endStdin;
|
|
130
144
|
private terminateHandle;
|
package/dist/dispatch.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,WAAW,EACX,aAAa,EACb,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,QAAQ,EACR,aAAa,EACb,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACb,MAAM,oBAAoB,CAAC;AAQ5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AACrD,OAAO,EAA4B,mBAAmB,EAAE,MAAM,sBAAsB,CAAC;AAErF,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,KAAK,EAAE,mBAAmB,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG/C,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,KAAK,wBAAwB,GAAG,IAAI,CAClC,iBAAiB,EACf,gBAAgB,GAChB,aAAa,GACb,cAAc,GACd,oBAAoB,GACpB,WAAW,GACX,YAAY,GACZ,iBAAiB,GACjB,OAAO,GACP,gBAAgB,GAChB,cAAc,CACjB,GAAG;IACF,cAAc,EAAE,oBAAoB,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,kFAAkF;IAClF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yBAAyB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC3D,iDAAiD;IACjD,wBAAwB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1D;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iBAAiB;IACjB,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB,CAAC;AAqBF;;;;;;;;;GASG;AACH,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,IAAI,EAAE,iBAAiB,CAAC;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,OAAO,CAAC;IACtB,MAAM,EAAE,mBAAmB,CAAC;IAC5B,GAAG,CAAC,EAAE,aAAa,CAAC;CACrB,CAAC;AAEF,qBAAa,iBAAkB,YAAW,eAAe;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
|
package/dist/dispatch.js
CHANGED
|
@@ -33,6 +33,7 @@ function killWin32Tree(pid) {
|
|
|
33
33
|
export class ClaudeCodeAdapter {
|
|
34
34
|
opts;
|
|
35
35
|
inputLifecycleMode = 'explicit';
|
|
36
|
+
inputSettlementMode = 'receipt';
|
|
36
37
|
processes = new Map();
|
|
37
38
|
/** Retired by abortDispatch while the CLI still had its own work: busy until idle, then terminated. */
|
|
38
39
|
retiring = new Set();
|
|
@@ -169,16 +170,44 @@ export class ClaudeCodeAdapter {
|
|
|
169
170
|
return false;
|
|
170
171
|
return state.inputs.hasUnsettledInjections();
|
|
171
172
|
}
|
|
172
|
-
|
|
173
|
+
/**
|
|
174
|
+
* The gateway discarded the buffered copy of an injection without a
|
|
175
|
+
* dispatch(). A settled injection only owes bookkeeping. One that has NOT
|
|
176
|
+
* settled is an orphan: nothing will ever drain it, and the CLI does not
|
|
177
|
+
* start a queued input on its own once the turn it was steered into has
|
|
178
|
+
* ended — it works its queue only when the next stdin frame arrives, which
|
|
179
|
+
* would be the batch re-delivering this very member (prod 2026-09-03: lane
|
|
180
|
+
* dethroned after 10 idle minutes, then "refusing duplicate stdin write"
|
|
181
|
+
* on every retry). Withdraw it: kill the process (the session survives via
|
|
182
|
+
* --resume), report the input failed so the server releases the member,
|
|
183
|
+
* and let the gateway run it as a real turn.
|
|
184
|
+
*/
|
|
185
|
+
async acknowledgeDiscardedInjection(sessionKey, deliveryKey) {
|
|
173
186
|
const state = this.processes.get(sessionKey);
|
|
174
187
|
if (!state)
|
|
175
|
-
return;
|
|
188
|
+
return 'discarded';
|
|
189
|
+
const delivery = state.inputs.getByKey(deliveryKey);
|
|
190
|
+
if (delivery && !delivery.terminal) {
|
|
191
|
+
state.log?.warn?.(`withdrawing orphaned Claude input ${delivery.commandUuid} (${delivery.reportedState ?? 'queued'}) on ${sessionKey}: its buffered copy was discarded before the CLI settled it${state.pump.hasOwnWork() ? '; the runtime-initiated turn dies with the process' : ''}`);
|
|
192
|
+
await this.killProcess(sessionKey, state);
|
|
193
|
+
state.inputs.discardBookkeeping(deliveryKey);
|
|
194
|
+
// 'cancelled' only when the server confirmed the failed receipt: the
|
|
195
|
+
// member is released and un-folded, so the gateway can re-fold and run
|
|
196
|
+
// it locally. A lost receipt (reportedState never reached 'failed') or
|
|
197
|
+
// a 'settled' answer (a reply effect already resolved the member)
|
|
198
|
+
// leaves nothing to re-run here — the lane complete releases an unread
|
|
199
|
+
// member for re-drive instead.
|
|
200
|
+
const confirmed = delivery.reportedState === 'failed' && delivery.terminal === 'failed';
|
|
201
|
+
if (!confirmed) {
|
|
202
|
+
state.log?.warn?.(`withdrawal of Claude input ${delivery.commandUuid} was not confirmed by the server (${delivery.terminal}); leaving the member to the lane complete`);
|
|
203
|
+
}
|
|
204
|
+
return confirmed ? 'cancelled' : 'discarded';
|
|
205
|
+
}
|
|
176
206
|
state.inputs.discardBookkeeping(deliveryKey);
|
|
177
207
|
// The discarded copy may have been the last thing a lazy restart was
|
|
178
|
-
// waiting on.
|
|
179
|
-
// still be RUNNING (non-terminal): killing the process now would fail a
|
|
180
|
-
// live input, so the restart also waits for every injection to settle.
|
|
208
|
+
// waiting on.
|
|
181
209
|
this.maybeApplyRestart(sessionKey, state);
|
|
210
|
+
return 'discarded';
|
|
182
211
|
}
|
|
183
212
|
async *dispatch({ event, bodyForAgent, sessionKey, context, inputLifecycle, noteActivity, }) {
|
|
184
213
|
const deliveryKey = inputLifecycle?.deliveryKey ?? event.dispatchEventId ?? event.messageId;
|
|
@@ -246,13 +275,13 @@ export class ClaudeCodeAdapter {
|
|
|
246
275
|
cleanupFork({ fork }) {
|
|
247
276
|
const state = this.processes.get(fork.sessionKey);
|
|
248
277
|
if (state) {
|
|
249
|
-
this.killProcess(fork.sessionKey, state);
|
|
278
|
+
void this.killProcess(fork.sessionKey, state);
|
|
250
279
|
}
|
|
251
280
|
this.opts.sessionManager.cleanupFork(fork.sessionKey);
|
|
252
281
|
}
|
|
253
282
|
resetProcesses() {
|
|
254
283
|
for (const [sessionKey, state] of this.processes) {
|
|
255
|
-
this.killProcess(sessionKey, state);
|
|
284
|
+
void this.killProcess(sessionKey, state);
|
|
256
285
|
}
|
|
257
286
|
this.processes.clear();
|
|
258
287
|
const retired = [...this.retiring];
|
|
@@ -303,7 +332,7 @@ export class ClaudeCodeAdapter {
|
|
|
303
332
|
await state.inputs.failBestEffort(delivery, log, `Claude stdin write failed: ${String(err)}`);
|
|
304
333
|
state.inputs.remove(delivery);
|
|
305
334
|
yield { type: 'error', message: `Claude stdin write failed: ${String(err)}` };
|
|
306
|
-
this.killProcess(sessionKey, state);
|
|
335
|
+
void this.killProcess(sessionKey, state);
|
|
307
336
|
return;
|
|
308
337
|
}
|
|
309
338
|
try {
|
|
@@ -318,7 +347,9 @@ export class ClaudeCodeAdapter {
|
|
|
318
347
|
return runClaudeCompact({
|
|
319
348
|
ensureRuntimeCapability: (log) => this.ensureRuntimeCapability(log),
|
|
320
349
|
ensureProcess: (sessionKey, log) => this.ensureProcess(sessionKey, log),
|
|
321
|
-
killProcess: (sessionKey, state) =>
|
|
350
|
+
killProcess: (sessionKey, state) => {
|
|
351
|
+
void this.killProcess(sessionKey, state);
|
|
352
|
+
},
|
|
322
353
|
writeUserMessage: (handle, text, uuid) => this.writeUserMessage(handle, text, uuid),
|
|
323
354
|
applyPendingRestart: (sessionKey, state) => this.maybeApplyRestart(sessionKey, state),
|
|
324
355
|
}, opts);
|
|
@@ -451,7 +482,7 @@ export class ClaudeCodeAdapter {
|
|
|
451
482
|
if (outstanding.total > 0) {
|
|
452
483
|
state.log?.warn?.(`restarting ${sessionKey} with ${outstanding.total} live background task(s); they die with the process`);
|
|
453
484
|
}
|
|
454
|
-
this.killProcess(sessionKey, state);
|
|
485
|
+
void this.killProcess(sessionKey, state);
|
|
455
486
|
}
|
|
456
487
|
ensureProcess(sessionKey, log) {
|
|
457
488
|
if (this.shuttingDown) {
|
|
@@ -466,7 +497,7 @@ export class ClaudeCodeAdapter {
|
|
|
466
497
|
log?.info?.(`deferring lazy restart of ${sessionKey}: runtime-initiated turn in progress`);
|
|
467
498
|
return existing;
|
|
468
499
|
}
|
|
469
|
-
this.killProcess(sessionKey, existing);
|
|
500
|
+
void this.killProcess(sessionKey, existing);
|
|
470
501
|
}
|
|
471
502
|
else if (alive) {
|
|
472
503
|
return existing;
|
|
@@ -501,7 +532,7 @@ export class ClaudeCodeAdapter {
|
|
|
501
532
|
onFatal: () => {
|
|
502
533
|
const current = this.processes.get(sessionKey);
|
|
503
534
|
if (current === state)
|
|
504
|
-
this.killProcess(sessionKey, state);
|
|
535
|
+
void this.killProcess(sessionKey, state);
|
|
505
536
|
},
|
|
506
537
|
onEof: () => {
|
|
507
538
|
state.done = true;
|
|
@@ -564,10 +595,12 @@ export class ClaudeCodeAdapter {
|
|
|
564
595
|
});
|
|
565
596
|
return { proc, exitPromise, stderrChunks };
|
|
566
597
|
}
|
|
598
|
+
/** Resolves once every live input's failed report has settled (best effort). */
|
|
567
599
|
killProcess(sessionKey, state) {
|
|
600
|
+
const reports = [];
|
|
568
601
|
for (const delivery of state.inputs.values()) {
|
|
569
602
|
if (!delivery.terminal)
|
|
570
|
-
|
|
603
|
+
reports.push(state.inputs.failBestEffort(delivery, state.log));
|
|
571
604
|
}
|
|
572
605
|
state.done = true;
|
|
573
606
|
const current = this.processes.get(sessionKey);
|
|
@@ -576,6 +609,7 @@ export class ClaudeCodeAdapter {
|
|
|
576
609
|
}
|
|
577
610
|
state.pump.close('killed', 'Claude process terminated by the bridge');
|
|
578
611
|
this.terminateHandle(state.handle);
|
|
612
|
+
return Promise.all(reports).then(() => undefined);
|
|
579
613
|
}
|
|
580
614
|
endStdin(handle) {
|
|
581
615
|
try {
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as os from 'node:os';
|
|
3
|
-
import { capabilityBinDir, childLogger, clearAllProviderCreds, configureHttpKeepAlive, createLogger, createOtelLogger, createPlatformConfigManager, deriveModelIsPin, identityFromMe, initAgentTelemetry, llmSource, resolveServiceVersion, materializeChannelCapabilities, ParallAgentGateway, parseDispatchDeadlineMs, parseForkDeadlineMs, parseProviderConfig, parseShutdownDeadlineMs, resolveRuntimeContextWindow, resolveRuntimeMaxTokens, resolveRuntimeModel, traceContextHeaders, } from '@parall/agent-core';
|
|
3
|
+
import { classifyStartupFailure, capabilityBinDir, childLogger, clearAllProviderCreds, configureHttpKeepAlive, createLogger, createOtelLogger, createPlatformConfigManager, deriveModelIsPin, identityFromMe, initAgentTelemetry, llmSource, resolveServiceVersion, materializeChannelCapabilities, ParallAgentGateway, parseDispatchDeadlineMs, parseForkDeadlineMs, parseProviderConfig, parseShutdownDeadlineMs, resolveRuntimeContextWindow, resolveRuntimeMaxTokens, resolveRuntimeModel, traceContextHeaders, newRuntimeInstanceId, } from '@parall/agent-core';
|
|
4
4
|
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
5
5
|
import { buildClaudeRuntimeKey, contextFilePathForSession, dispatchContextDirPath, resolveClaudeAgentConfig, resolveWsUrl, sessionStateFilePathForRuntime, stepIdFilePathForSession, } from './config.js';
|
|
6
6
|
import { ClaudeCodeAdapter } from './dispatch.js';
|
|
@@ -84,8 +84,12 @@ async function main() {
|
|
|
84
84
|
const sessionStateFilePath = sessionStateFilePathForRuntime(config.stateDir, runtimeKey);
|
|
85
85
|
const sessionManager = new ClaudeSessionManager(runtimeKey, sessionStateFilePath, agentLog);
|
|
86
86
|
const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
87
|
+
// This process's runtime instance: rides the WS ticket and every claim so
|
|
88
|
+
// the server owns turns by process identity instead of a lease clock
|
|
89
|
+
// (agent-dispatch-idempotency-design.md#instance-ownership).
|
|
90
|
+
const instanceId = newRuntimeInstanceId();
|
|
87
91
|
const ws = new ParallWs({
|
|
88
|
-
getTicket: () => client.getWsTicket(),
|
|
92
|
+
getTicket: () => client.getWsTicket({ instance_id: instanceId }),
|
|
89
93
|
wsUrl: resolvedWsUrl,
|
|
90
94
|
});
|
|
91
95
|
const configMgr = createPlatformConfigManager({
|
|
@@ -237,6 +241,7 @@ async function main() {
|
|
|
237
241
|
accountId: agentUserId,
|
|
238
242
|
client,
|
|
239
243
|
ws,
|
|
244
|
+
instanceId,
|
|
240
245
|
connectionLabel: resolvedWsUrl,
|
|
241
246
|
config: {
|
|
242
247
|
parall_url: config.apiUrl,
|
|
@@ -300,6 +305,19 @@ async function main() {
|
|
|
300
305
|
}
|
|
301
306
|
}
|
|
302
307
|
main().catch((err) => {
|
|
303
|
-
|
|
304
|
-
|
|
308
|
+
// A credential the server rejects during bootstrap is the same terminal
|
|
309
|
+
// failure the gateway reports as auth-invalid: exit with the re-mint code
|
|
310
|
+
// so the daemon mints a fresh launch credential instead of restarting
|
|
311
|
+
// this process with the same dead key.
|
|
312
|
+
const { exitCode, authRejection } = classifyStartupFailure(err);
|
|
313
|
+
if (authRejection) {
|
|
314
|
+
activeLog.error(`AUTH-INVALID at startup status=${authRejection.status}` +
|
|
315
|
+
`${authRejection.code ? ` code=${authRejection.code}` : ''} — ` +
|
|
316
|
+
`the server rejected this runtime's credential (${authRejection.message}); ` +
|
|
317
|
+
`exiting with code ${exitCode} so the supervisor can re-mint the launch credential.`);
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
activeLog.error(`fatal: ${String(err)}`);
|
|
321
|
+
}
|
|
322
|
+
process.exitCode = exitCode;
|
|
305
323
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"process-pump.d.ts","sourceRoot":"","sources":["../src/process-pump.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"process-pump.d.ts","sourceRoot":"","sources":["../src/process-pump.ts"],"names":[],"mappings":"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"}
|
package/dist/process-pump.js
CHANGED
|
@@ -321,6 +321,9 @@ export class ClaudeProcessPump {
|
|
|
321
321
|
kind: 'runtime',
|
|
322
322
|
event: { type: 'error', message: `Claude input lifecycle update failed: ${String(err)}` },
|
|
323
323
|
});
|
|
324
|
+
// failBestEffort may unregister this delivery before onFatal closes
|
|
325
|
+
// the process's remaining sinks. Its waiting drain still needs EOF.
|
|
326
|
+
delivery.sink.close();
|
|
324
327
|
this.opts.hooks.onFatal();
|
|
325
328
|
return;
|
|
326
329
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/claude-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.64.0",
|
|
4
4
|
"description": "Claude Code bridge runtime for self-hosted Parall agents",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -25,9 +25,9 @@
|
|
|
25
25
|
"src"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@parall/agent-core": "1.
|
|
29
|
-
"@parall/cli": "1.
|
|
30
|
-
"@parall/sdk": "1.
|
|
28
|
+
"@parall/agent-core": "1.64.0",
|
|
29
|
+
"@parall/cli": "1.64.0",
|
|
30
|
+
"@parall/sdk": "1.64.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|
package/src/dispatch.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type {
|
|
|
6
6
|
CleanupForkOpts,
|
|
7
7
|
CompactOpts,
|
|
8
8
|
CompactResult,
|
|
9
|
+
DiscardedInjectionOutcome,
|
|
9
10
|
DispatchAdapter,
|
|
10
11
|
DispatchInputLifecycle,
|
|
11
12
|
DispatchOpts,
|
|
@@ -119,6 +120,7 @@ export type ProcessState = {
|
|
|
119
120
|
|
|
120
121
|
export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
121
122
|
readonly inputLifecycleMode = 'explicit' as const;
|
|
123
|
+
readonly inputSettlementMode = 'receipt' as const;
|
|
122
124
|
private readonly processes = new Map<string, ProcessState>();
|
|
123
125
|
/** Retired by abortDispatch while the CLI still had its own work: busy until idle, then terminated. */
|
|
124
126
|
private readonly retiring = new Set<ProcessState>();
|
|
@@ -260,15 +262,50 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
260
262
|
return state.inputs.hasUnsettledInjections();
|
|
261
263
|
}
|
|
262
264
|
|
|
263
|
-
|
|
265
|
+
/**
|
|
266
|
+
* The gateway discarded the buffered copy of an injection without a
|
|
267
|
+
* dispatch(). A settled injection only owes bookkeeping. One that has NOT
|
|
268
|
+
* settled is an orphan: nothing will ever drain it, and the CLI does not
|
|
269
|
+
* start a queued input on its own once the turn it was steered into has
|
|
270
|
+
* ended — it works its queue only when the next stdin frame arrives, which
|
|
271
|
+
* would be the batch re-delivering this very member (prod 2026-09-03: lane
|
|
272
|
+
* dethroned after 10 idle minutes, then "refusing duplicate stdin write"
|
|
273
|
+
* on every retry). Withdraw it: kill the process (the session survives via
|
|
274
|
+
* --resume), report the input failed so the server releases the member,
|
|
275
|
+
* and let the gateway run it as a real turn.
|
|
276
|
+
*/
|
|
277
|
+
async acknowledgeDiscardedInjection(
|
|
278
|
+
sessionKey: string,
|
|
279
|
+
deliveryKey: string,
|
|
280
|
+
): Promise<DiscardedInjectionOutcome> {
|
|
264
281
|
const state = this.processes.get(sessionKey);
|
|
265
|
-
if (!state) return;
|
|
282
|
+
if (!state) return 'discarded';
|
|
283
|
+
const delivery = state.inputs.getByKey(deliveryKey);
|
|
284
|
+
if (delivery && !delivery.terminal) {
|
|
285
|
+
state.log?.warn?.(
|
|
286
|
+
`withdrawing orphaned Claude input ${delivery.commandUuid} (${delivery.reportedState ?? 'queued'}) on ${sessionKey}: its buffered copy was discarded before the CLI settled it${state.pump.hasOwnWork() ? '; the runtime-initiated turn dies with the process' : ''}`,
|
|
287
|
+
);
|
|
288
|
+
await this.killProcess(sessionKey, state);
|
|
289
|
+
state.inputs.discardBookkeeping(deliveryKey);
|
|
290
|
+
// 'cancelled' only when the server confirmed the failed receipt: the
|
|
291
|
+
// member is released and un-folded, so the gateway can re-fold and run
|
|
292
|
+
// it locally. A lost receipt (reportedState never reached 'failed') or
|
|
293
|
+
// a 'settled' answer (a reply effect already resolved the member)
|
|
294
|
+
// leaves nothing to re-run here — the lane complete releases an unread
|
|
295
|
+
// member for re-drive instead.
|
|
296
|
+
const confirmed = delivery.reportedState === 'failed' && delivery.terminal === 'failed';
|
|
297
|
+
if (!confirmed) {
|
|
298
|
+
state.log?.warn?.(
|
|
299
|
+
`withdrawal of Claude input ${delivery.commandUuid} was not confirmed by the server (${delivery.terminal}); leaving the member to the lane complete`,
|
|
300
|
+
);
|
|
301
|
+
}
|
|
302
|
+
return confirmed ? 'cancelled' : 'discarded';
|
|
303
|
+
}
|
|
266
304
|
state.inputs.discardBookkeeping(deliveryKey);
|
|
267
305
|
// The discarded copy may have been the last thing a lazy restart was
|
|
268
|
-
// waiting on.
|
|
269
|
-
// still be RUNNING (non-terminal): killing the process now would fail a
|
|
270
|
-
// live input, so the restart also waits for every injection to settle.
|
|
306
|
+
// waiting on.
|
|
271
307
|
this.maybeApplyRestart(sessionKey, state);
|
|
308
|
+
return 'discarded';
|
|
272
309
|
}
|
|
273
310
|
|
|
274
311
|
async *dispatch({
|
|
@@ -360,14 +397,14 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
360
397
|
cleanupFork({ fork }: CleanupForkOpts) {
|
|
361
398
|
const state = this.processes.get(fork.sessionKey);
|
|
362
399
|
if (state) {
|
|
363
|
-
this.killProcess(fork.sessionKey, state);
|
|
400
|
+
void this.killProcess(fork.sessionKey, state);
|
|
364
401
|
}
|
|
365
402
|
this.opts.sessionManager.cleanupFork(fork.sessionKey);
|
|
366
403
|
}
|
|
367
404
|
|
|
368
405
|
resetProcesses(): void {
|
|
369
406
|
for (const [sessionKey, state] of this.processes) {
|
|
370
|
-
this.killProcess(sessionKey, state);
|
|
407
|
+
void this.killProcess(sessionKey, state);
|
|
371
408
|
}
|
|
372
409
|
this.processes.clear();
|
|
373
410
|
const retired = [...this.retiring];
|
|
@@ -425,7 +462,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
425
462
|
await state.inputs.failBestEffort(delivery, log, `Claude stdin write failed: ${String(err)}`);
|
|
426
463
|
state.inputs.remove(delivery);
|
|
427
464
|
yield { type: 'error', message: `Claude stdin write failed: ${String(err)}` };
|
|
428
|
-
this.killProcess(sessionKey, state);
|
|
465
|
+
void this.killProcess(sessionKey, state);
|
|
429
466
|
return;
|
|
430
467
|
}
|
|
431
468
|
|
|
@@ -442,7 +479,9 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
442
479
|
{
|
|
443
480
|
ensureRuntimeCapability: (log) => this.ensureRuntimeCapability(log),
|
|
444
481
|
ensureProcess: (sessionKey, log) => this.ensureProcess(sessionKey, log),
|
|
445
|
-
killProcess: (sessionKey, state) =>
|
|
482
|
+
killProcess: (sessionKey, state) => {
|
|
483
|
+
void this.killProcess(sessionKey, state);
|
|
484
|
+
},
|
|
446
485
|
writeUserMessage: (handle, text, uuid) => this.writeUserMessage(handle, text, uuid),
|
|
447
486
|
applyPendingRestart: (sessionKey, state) => this.maybeApplyRestart(sessionKey, state),
|
|
448
487
|
},
|
|
@@ -583,7 +622,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
583
622
|
`restarting ${sessionKey} with ${outstanding.total} live background task(s); they die with the process`,
|
|
584
623
|
);
|
|
585
624
|
}
|
|
586
|
-
this.killProcess(sessionKey, state);
|
|
625
|
+
void this.killProcess(sessionKey, state);
|
|
587
626
|
}
|
|
588
627
|
|
|
589
628
|
private ensureProcess(sessionKey: string, log: GatewayLogger | undefined): ProcessState {
|
|
@@ -602,7 +641,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
602
641
|
);
|
|
603
642
|
return existing;
|
|
604
643
|
}
|
|
605
|
-
this.killProcess(sessionKey, existing);
|
|
644
|
+
void this.killProcess(sessionKey, existing);
|
|
606
645
|
} else if (alive) {
|
|
607
646
|
return existing;
|
|
608
647
|
} else {
|
|
@@ -635,7 +674,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
635
674
|
},
|
|
636
675
|
onFatal: () => {
|
|
637
676
|
const current = this.processes.get(sessionKey);
|
|
638
|
-
if (current === state) this.killProcess(sessionKey, state);
|
|
677
|
+
if (current === state) void this.killProcess(sessionKey, state);
|
|
639
678
|
},
|
|
640
679
|
onEof: () => {
|
|
641
680
|
state.done = true;
|
|
@@ -723,9 +762,11 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
723
762
|
return { proc, exitPromise, stderrChunks };
|
|
724
763
|
}
|
|
725
764
|
|
|
726
|
-
|
|
765
|
+
/** Resolves once every live input's failed report has settled (best effort). */
|
|
766
|
+
private killProcess(sessionKey: string, state: ProcessState): Promise<void> {
|
|
767
|
+
const reports: Promise<void>[] = [];
|
|
727
768
|
for (const delivery of state.inputs.values()) {
|
|
728
|
-
if (!delivery.terminal)
|
|
769
|
+
if (!delivery.terminal) reports.push(state.inputs.failBestEffort(delivery, state.log));
|
|
729
770
|
}
|
|
730
771
|
state.done = true;
|
|
731
772
|
const current = this.processes.get(sessionKey);
|
|
@@ -734,6 +775,7 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
734
775
|
}
|
|
735
776
|
state.pump.close('killed', 'Claude process terminated by the bridge');
|
|
736
777
|
this.terminateHandle(state.handle);
|
|
778
|
+
return Promise.all(reports).then(() => undefined);
|
|
737
779
|
}
|
|
738
780
|
|
|
739
781
|
private endStdin(handle: ClaudeProcessHandle): void {
|
package/src/index.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import * as os from 'node:os';
|
|
4
4
|
import {
|
|
5
|
+
classifyStartupFailure,
|
|
5
6
|
capabilityBinDir,
|
|
6
7
|
childLogger,
|
|
7
8
|
clearAllProviderCreds,
|
|
@@ -24,6 +25,7 @@ import {
|
|
|
24
25
|
resolveRuntimeMaxTokens,
|
|
25
26
|
resolveRuntimeModel,
|
|
26
27
|
traceContextHeaders,
|
|
28
|
+
newRuntimeInstanceId,
|
|
27
29
|
} from '@parall/agent-core';
|
|
28
30
|
import { ApiError, ParallClient, ParallWs } from '@parall/sdk';
|
|
29
31
|
import {
|
|
@@ -124,8 +126,12 @@ async function main() {
|
|
|
124
126
|
const sessionManager = new ClaudeSessionManager(runtimeKey, sessionStateFilePath, agentLog);
|
|
125
127
|
|
|
126
128
|
const resolvedWsUrl = resolveWsUrl(config.apiUrl, config.wsUrl, config.swimlaneName);
|
|
129
|
+
// This process's runtime instance: rides the WS ticket and every claim so
|
|
130
|
+
// the server owns turns by process identity instead of a lease clock
|
|
131
|
+
// (agent-dispatch-idempotency-design.md#instance-ownership).
|
|
132
|
+
const instanceId = newRuntimeInstanceId();
|
|
127
133
|
const ws = new ParallWs({
|
|
128
|
-
getTicket: () => client.getWsTicket(),
|
|
134
|
+
getTicket: () => client.getWsTicket({ instance_id: instanceId }),
|
|
129
135
|
wsUrl: resolvedWsUrl,
|
|
130
136
|
});
|
|
131
137
|
|
|
@@ -299,6 +305,7 @@ async function main() {
|
|
|
299
305
|
accountId: agentUserId,
|
|
300
306
|
client,
|
|
301
307
|
ws,
|
|
308
|
+
instanceId,
|
|
302
309
|
connectionLabel: resolvedWsUrl,
|
|
303
310
|
config: {
|
|
304
311
|
parall_url: config.apiUrl,
|
|
@@ -365,6 +372,20 @@ async function main() {
|
|
|
365
372
|
}
|
|
366
373
|
|
|
367
374
|
main().catch((err) => {
|
|
368
|
-
|
|
369
|
-
|
|
375
|
+
// A credential the server rejects during bootstrap is the same terminal
|
|
376
|
+
// failure the gateway reports as auth-invalid: exit with the re-mint code
|
|
377
|
+
// so the daemon mints a fresh launch credential instead of restarting
|
|
378
|
+
// this process with the same dead key.
|
|
379
|
+
const { exitCode, authRejection } = classifyStartupFailure(err);
|
|
380
|
+
if (authRejection) {
|
|
381
|
+
activeLog.error(
|
|
382
|
+
`AUTH-INVALID at startup status=${authRejection.status}` +
|
|
383
|
+
`${authRejection.code ? ` code=${authRejection.code}` : ''} — ` +
|
|
384
|
+
`the server rejected this runtime's credential (${authRejection.message}); ` +
|
|
385
|
+
`exiting with code ${exitCode} so the supervisor can re-mint the launch credential.`,
|
|
386
|
+
);
|
|
387
|
+
} else {
|
|
388
|
+
activeLog.error(`fatal: ${String(err)}`);
|
|
389
|
+
}
|
|
390
|
+
process.exitCode = exitCode;
|
|
370
391
|
});
|
package/src/process-pump.ts
CHANGED
|
@@ -361,6 +361,9 @@ export class ClaudeProcessPump {
|
|
|
361
361
|
kind: 'runtime',
|
|
362
362
|
event: { type: 'error', message: `Claude input lifecycle update failed: ${String(err)}` },
|
|
363
363
|
});
|
|
364
|
+
// failBestEffort may unregister this delivery before onFatal closes
|
|
365
|
+
// the process's remaining sinks. Its waiting drain still needs EOF.
|
|
366
|
+
delivery.sink.close();
|
|
364
367
|
this.opts.hooks.onFatal();
|
|
365
368
|
return;
|
|
366
369
|
}
|