@parall/claude-agent 1.58.2 → 1.59.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 +2 -0
- package/dist/dispatch.d.ts.map +1 -1
- package/dist/dispatch.js +27 -2
- package/dist/input-lifecycle.d.ts +21 -0
- package/dist/input-lifecycle.d.ts.map +1 -1
- package/dist/input-lifecycle.js +39 -0
- package/package.json +4 -4
- package/src/dispatch.ts +33 -2
- package/src/input-lifecycle.ts +36 -0
package/dist/dispatch.d.ts
CHANGED
|
@@ -57,6 +57,8 @@ export declare class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
57
57
|
enqueueDuringDispatch(sessionKey: string, body: string, inputLifecycle?: DispatchInputLifecycle): boolean;
|
|
58
58
|
abortDispatch(sessionKey: string): void;
|
|
59
59
|
hasPendingInjections(sessionKey: string): boolean;
|
|
60
|
+
hasUnsettledInjections(sessionKey: string): boolean;
|
|
61
|
+
acknowledgeDiscardedInjection(sessionKey: string, deliveryKey: string): void;
|
|
60
62
|
dispatch({ event, bodyForAgent, sessionKey, context, inputLifecycle, noteActivity, }: DispatchOpts): AsyncIterable<RuntimeEvent>;
|
|
61
63
|
getBranchPoint(_sessionKey: string): string | undefined;
|
|
62
64
|
getSessionHistoryPath(sessionKey: string): string | undefined;
|
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,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,QAAQ,EAER,YAAY,EACb,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOrD,OAAO,KAAK,EAAuB,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;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAqCF,qBAAa,iBAAkB,YAAW,eAAe;IAW3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IAVjC,QAAQ,CAAC,kBAAkB,EAAG,UAAU,CAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,qBAAqB,CAAC,CAAsB;IACpD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAqB;gBAEV,IAAI,EAAE,wBAAwB;IAM3D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAErC;IACD,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IACD,YAAY,CAAC,MAAM,EAAE;QACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GAAG,IAAI;IAqBR,qBAAqB,IAAI,IAAI;IAM7B,qBAAqB,CACnB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,sBAAsB,GACtC,OAAO;IA2BV,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAcvC,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;
|
|
1
|
+
{"version":3,"file":"dispatch.d.ts","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,eAAe,EACf,eAAe,EACf,sBAAsB,EACtB,YAAY,EACZ,QAAQ,EAER,YAAY,EACb,MAAM,oBAAoB,CAAC;AAK5B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOrD,OAAO,KAAK,EAAuB,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;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B,CAAC;AAqCF,qBAAa,iBAAkB,YAAW,eAAe;IAW3C,OAAO,CAAC,QAAQ,CAAC,IAAI;IAVjC,QAAQ,CAAC,kBAAkB,EAAG,UAAU,CAAU;IAClD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAmC;IAC7D,OAAO,CAAC,eAAe,CAAC,CAAgB;IACxC,OAAO,CAAC,qBAAqB,CAAC,CAAsB;IACpD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,UAAU,CAAqB;gBAEV,IAAI,EAAE,wBAAwB;IAM3D,IAAI,YAAY,IAAI,MAAM,GAAG,SAAS,CAErC;IACD,IAAI,aAAa,IAAI,MAAM,GAAG,SAAS,CAEtC;IACD,YAAY,CAAC,MAAM,EAAE;QACnB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACvB,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC9B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,GAAG,IAAI;IAqBR,qBAAqB,IAAI,IAAI;IAM7B,qBAAqB,CACnB,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,cAAc,CAAC,EAAE,sBAAsB,GACtC,OAAO;IA2BV,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAcvC,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAMjD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAMnD,6BAA6B,CAAC,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI;IAiBrE,QAAQ,CAAC,EACd,KAAK,EACL,YAAY,EACZ,UAAU,EACV,OAAO,EACP,cAAc,EACd,YAAY,GACb,EAAE,YAAY,GAAG,aAAa,CAAC,YAAY,CAAC;IAqE7C,cAAc,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIvD,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAc7D,WAAW,CAAC,EAAE,UAAU,EAAE,EAAE,QAAQ;IAIpC,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,eAAe;IAQrC,cAAc,IAAI,IAAI;IAOhB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;YAUhB,OAAO;IAuCtB,OAAO,CAAC,uBAAuB;IAa/B;;;;;OAKG;YACW,sBAAsB;IAwCpC;;;;;OAKG;YACY,eAAe;IAgM9B,OAAO,CAAC,aAAa;IAmCrB,OAAO,CAAC,YAAY;IA8DpB,OAAO,CAAC,WAAW;IAYnB,OAAO,CAAC,eAAe;IAiBvB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,gBAAgB;IAaxB,OAAO,CAAC,SAAS;IAwCjB,OAAO,CAAC,uBAAuB;CAmBhC"}
|
package/dist/dispatch.js
CHANGED
|
@@ -131,6 +131,27 @@ export class ClaudeCodeAdapter {
|
|
|
131
131
|
return false;
|
|
132
132
|
return state.inputs.hasPendingInjections();
|
|
133
133
|
}
|
|
134
|
+
hasUnsettledInjections(sessionKey) {
|
|
135
|
+
const state = this.processes.get(sessionKey);
|
|
136
|
+
if (!state)
|
|
137
|
+
return false;
|
|
138
|
+
return state.inputs.hasUnsettledInjections();
|
|
139
|
+
}
|
|
140
|
+
acknowledgeDiscardedInjection(sessionKey, deliveryKey) {
|
|
141
|
+
const state = this.processes.get(sessionKey);
|
|
142
|
+
if (!state)
|
|
143
|
+
return;
|
|
144
|
+
state.inputs.discardBookkeeping(deliveryKey);
|
|
145
|
+
// The discarded copy may have been the last thing a lazy restart was
|
|
146
|
+
// waiting on. Unlike the consume path's tail, the discarded delivery may
|
|
147
|
+
// still be RUNNING (non-terminal): killing the process now would fail a
|
|
148
|
+
// live input, so the restart also waits for every injection to settle.
|
|
149
|
+
if (state.needsRestart &&
|
|
150
|
+
!state.inputs.hasPendingInjections() &&
|
|
151
|
+
!state.inputs.hasUnsettledInjections()) {
|
|
152
|
+
this.killProcess(sessionKey, state);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
134
155
|
async *dispatch({ event, bodyForAgent, sessionKey, context, inputLifecycle, noteActivity, }) {
|
|
135
156
|
const deliveryKey = inputLifecycle?.deliveryKey ?? event.dispatchEventId ?? event.messageId;
|
|
136
157
|
const existingState = this.processes.get(sessionKey);
|
|
@@ -150,7 +171,9 @@ export class ClaudeCodeAdapter {
|
|
|
150
171
|
finally {
|
|
151
172
|
existingState.inputs.remove(injected);
|
|
152
173
|
}
|
|
153
|
-
if (existingState.needsRestart &&
|
|
174
|
+
if (existingState.needsRestart &&
|
|
175
|
+
!existingState.inputs.hasPendingInjections() &&
|
|
176
|
+
!existingState.inputs.hasUnsettledInjections()) {
|
|
154
177
|
this.killProcess(sessionKey, existingState);
|
|
155
178
|
}
|
|
156
179
|
return;
|
|
@@ -475,7 +498,9 @@ export class ClaudeCodeAdapter {
|
|
|
475
498
|
if (lastResultMeta) {
|
|
476
499
|
yield classifyClaudeTurn(lastResultMeta, noticeTexts);
|
|
477
500
|
}
|
|
478
|
-
if (state.needsRestart &&
|
|
501
|
+
if (state.needsRestart &&
|
|
502
|
+
!state.inputs.hasPendingInjections() &&
|
|
503
|
+
!state.inputs.hasUnsettledInjections()) {
|
|
479
504
|
this.killProcess(sessionKey, state);
|
|
480
505
|
}
|
|
481
506
|
}
|
|
@@ -30,8 +30,29 @@ export declare class ClaudeInputRegistry {
|
|
|
30
30
|
findOverlapping(dispatchEventIds: string[]): ClaudeInputDelivery | undefined;
|
|
31
31
|
values(): IterableIterator<ClaudeInputDelivery>;
|
|
32
32
|
hasPendingInjections(): boolean;
|
|
33
|
+
/**
|
|
34
|
+
* Injected inputs whose lifecycle has NOT reached a terminal state. A
|
|
35
|
+
* terminal delivery still counts as pending (its buffered copy owes frame
|
|
36
|
+
* boundary bookkeeping) but owes the server nothing — the lane complete
|
|
37
|
+
* defers on this predicate, never on hasPendingInjections, so a bookkeeping
|
|
38
|
+
* copy that never drains cannot hold the lane open past its lease. Drained
|
|
39
|
+
* is deliberately NOT part of this predicate: a discarded bookkeeping copy
|
|
40
|
+
* (drained, non-terminal) is still a running input — completing its lane
|
|
41
|
+
* would release the member for redrive mid-processing.
|
|
42
|
+
*/
|
|
43
|
+
hasUnsettledInjections(): boolean;
|
|
33
44
|
register(deliveryKey: string, lifecycle: DispatchInputLifecycle | undefined, injected: boolean): ClaudeInputDelivery;
|
|
34
45
|
remove(delivery: ClaudeInputDelivery): void;
|
|
46
|
+
/**
|
|
47
|
+
* The buffered copy backing this delivery was discarded without a
|
|
48
|
+
* dispatch (already rendered into a frame the model saw), so the normal
|
|
49
|
+
* bookkeeping drain never comes. Its frame-boundary obligation is void:
|
|
50
|
+
* mark it drained so nothing waits on it, and drop it once terminal. A
|
|
51
|
+
* still-running input keeps its registration — byCommand must keep routing
|
|
52
|
+
* its lifecycle frames — and is dropped when its terminal state arrives
|
|
53
|
+
* (see apply/fail).
|
|
54
|
+
*/
|
|
55
|
+
discardBookkeeping(deliveryKey: string): void;
|
|
35
56
|
apply(delivery: ClaudeInputDelivery, state: 'queued' | 'started' | 'completed' | 'cancelled' | 'discarded'): Promise<void>;
|
|
36
57
|
fail(delivery: ClaudeInputDelivery): Promise<void>;
|
|
37
58
|
failBestEffort(delivery: ClaudeInputDelivery, log?: GatewayLogger): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"input-lifecycle.d.ts","sourceRoot":"","sources":["../src/input-lifecycle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEnG,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC9C,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0C;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;IAEpE,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAI9D,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIlE,eAAe,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,mBAAmB,GAAG,SAAS;IAU5E,MAAM,IAAI,gBAAgB,CAAC,mBAAmB,CAAC;IAI/C,oBAAoB,IAAI,OAAO;IAI/B,QAAQ,CACN,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,sBAAsB,GAAG,SAAS,EAC7C,QAAQ,EAAE,OAAO,GAChB,mBAAmB;IAiBtB,MAAM,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"input-lifecycle.d.ts","sourceRoot":"","sources":["../src/input-lifecycle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,sBAAsB,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAEnG,MAAM,MAAM,mBAAmB,GAAG;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC9C,aAAa,CAAC,EAAE,iBAAiB,CAAC;IAClC,YAAY,EAAE,OAAO,CAAC;IACtB;;;;;;OAMG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B,CAAC;AAEF;;;;GAIG;AACH,qBAAa,mBAAmB;IAC9B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA0C;IAChE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAA0C;IAEpE,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAI9D,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,mBAAmB,GAAG,SAAS;IAIlE,eAAe,CAAC,gBAAgB,EAAE,MAAM,EAAE,GAAG,mBAAmB,GAAG,SAAS;IAU5E,MAAM,IAAI,gBAAgB,CAAC,mBAAmB,CAAC;IAI/C,oBAAoB,IAAI,OAAO;IAI/B;;;;;;;;;OASG;IACH,sBAAsB,IAAI,OAAO;IAIjC,QAAQ,CACN,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,sBAAsB,GAAG,SAAS,EAC7C,QAAQ,EAAE,OAAO,GAChB,mBAAmB;IAiBtB,MAAM,CAAC,QAAQ,EAAE,mBAAmB,GAAG,IAAI;IAS3C;;;;;;;;OAQG;IACH,kBAAkB,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAOvC,KAAK,CACT,QAAQ,EAAE,mBAAmB,EAC7B,KAAK,EAAE,QAAQ,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,GAAG,WAAW,GACpE,OAAO,CAAC,IAAI,CAAC;IA0BV,IAAI,CAAC,QAAQ,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAmBlD,cAAc,CAAC,QAAQ,EAAE,mBAAmB,EAAE,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjF,iBAAiB,CAAC,GAAG,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;CAO5D"}
|
package/dist/input-lifecycle.js
CHANGED
|
@@ -28,6 +28,19 @@ export class ClaudeInputRegistry {
|
|
|
28
28
|
hasPendingInjections() {
|
|
29
29
|
return [...this.byKey.values()].some((delivery) => delivery.injected && !delivery.drained);
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Injected inputs whose lifecycle has NOT reached a terminal state. A
|
|
33
|
+
* terminal delivery still counts as pending (its buffered copy owes frame
|
|
34
|
+
* boundary bookkeeping) but owes the server nothing — the lane complete
|
|
35
|
+
* defers on this predicate, never on hasPendingInjections, so a bookkeeping
|
|
36
|
+
* copy that never drains cannot hold the lane open past its lease. Drained
|
|
37
|
+
* is deliberately NOT part of this predicate: a discarded bookkeeping copy
|
|
38
|
+
* (drained, non-terminal) is still a running input — completing its lane
|
|
39
|
+
* would release the member for redrive mid-processing.
|
|
40
|
+
*/
|
|
41
|
+
hasUnsettledInjections() {
|
|
42
|
+
return [...this.byKey.values()].some((delivery) => delivery.injected && !delivery.terminal);
|
|
43
|
+
}
|
|
31
44
|
register(deliveryKey, lifecycle, injected) {
|
|
32
45
|
if (this.byKey.has(deliveryKey)) {
|
|
33
46
|
throw new Error(`duplicate Claude delivery key ${deliveryKey}`);
|
|
@@ -52,6 +65,23 @@ export class ClaudeInputRegistry {
|
|
|
52
65
|
this.byCommand.delete(delivery.commandUuid);
|
|
53
66
|
}
|
|
54
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* The buffered copy backing this delivery was discarded without a
|
|
70
|
+
* dispatch (already rendered into a frame the model saw), so the normal
|
|
71
|
+
* bookkeeping drain never comes. Its frame-boundary obligation is void:
|
|
72
|
+
* mark it drained so nothing waits on it, and drop it once terminal. A
|
|
73
|
+
* still-running input keeps its registration — byCommand must keep routing
|
|
74
|
+
* its lifecycle frames — and is dropped when its terminal state arrives
|
|
75
|
+
* (see apply/fail).
|
|
76
|
+
*/
|
|
77
|
+
discardBookkeeping(deliveryKey) {
|
|
78
|
+
const delivery = this.byKey.get(deliveryKey);
|
|
79
|
+
if (!delivery)
|
|
80
|
+
return;
|
|
81
|
+
delivery.drained = true;
|
|
82
|
+
if (delivery.terminal)
|
|
83
|
+
this.remove(delivery);
|
|
84
|
+
}
|
|
55
85
|
async apply(delivery, state) {
|
|
56
86
|
if (delivery.terminal || state === 'queued')
|
|
57
87
|
return;
|
|
@@ -71,12 +101,19 @@ export class ClaudeInputRegistry {
|
|
|
71
101
|
delivery.reportedState = 'completed';
|
|
72
102
|
}
|
|
73
103
|
delivery.terminal = 'completed';
|
|
104
|
+
// A discarded bookkeeping copy (drained without a dispatch) has no
|
|
105
|
+
// drain left to remove this entry — drop it at its terminal so it can
|
|
106
|
+
// never shadow a redelivered batch in findOverlapping.
|
|
107
|
+
if (delivery.drained)
|
|
108
|
+
this.remove(delivery);
|
|
74
109
|
}
|
|
75
110
|
async fail(delivery) {
|
|
76
111
|
if (delivery.terminal)
|
|
77
112
|
return;
|
|
78
113
|
if (delivery.suppressFailReport) {
|
|
79
114
|
delivery.terminal = 'settled';
|
|
115
|
+
if (delivery.drained)
|
|
116
|
+
this.remove(delivery);
|
|
80
117
|
return;
|
|
81
118
|
}
|
|
82
119
|
try {
|
|
@@ -88,6 +125,8 @@ export class ClaudeInputRegistry {
|
|
|
88
125
|
}
|
|
89
126
|
finally {
|
|
90
127
|
delivery.terminal ??= 'failed';
|
|
128
|
+
if (delivery.drained)
|
|
129
|
+
this.remove(delivery);
|
|
91
130
|
}
|
|
92
131
|
}
|
|
93
132
|
async failBestEffort(delivery, log) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parall/claude-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.59.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.59.0",
|
|
29
|
+
"@parall/cli": "1.59.0",
|
|
30
|
+
"@parall/sdk": "1.59.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@types/node": "^22.0.0",
|
package/src/dispatch.ts
CHANGED
|
@@ -212,6 +212,29 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
212
212
|
return state.inputs.hasPendingInjections();
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
hasUnsettledInjections(sessionKey: string): boolean {
|
|
216
|
+
const state = this.processes.get(sessionKey);
|
|
217
|
+
if (!state) return false;
|
|
218
|
+
return state.inputs.hasUnsettledInjections();
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
acknowledgeDiscardedInjection(sessionKey: string, deliveryKey: string): void {
|
|
222
|
+
const state = this.processes.get(sessionKey);
|
|
223
|
+
if (!state) return;
|
|
224
|
+
state.inputs.discardBookkeeping(deliveryKey);
|
|
225
|
+
// The discarded copy may have been the last thing a lazy restart was
|
|
226
|
+
// waiting on. Unlike the consume path's tail, the discarded delivery may
|
|
227
|
+
// still be RUNNING (non-terminal): killing the process now would fail a
|
|
228
|
+
// live input, so the restart also waits for every injection to settle.
|
|
229
|
+
if (
|
|
230
|
+
state.needsRestart &&
|
|
231
|
+
!state.inputs.hasPendingInjections() &&
|
|
232
|
+
!state.inputs.hasUnsettledInjections()
|
|
233
|
+
) {
|
|
234
|
+
this.killProcess(sessionKey, state);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
215
238
|
async *dispatch({
|
|
216
239
|
event,
|
|
217
240
|
bodyForAgent,
|
|
@@ -242,7 +265,11 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
242
265
|
} finally {
|
|
243
266
|
existingState.inputs.remove(injected);
|
|
244
267
|
}
|
|
245
|
-
if (
|
|
268
|
+
if (
|
|
269
|
+
existingState.needsRestart &&
|
|
270
|
+
!existingState.inputs.hasPendingInjections() &&
|
|
271
|
+
!existingState.inputs.hasUnsettledInjections()
|
|
272
|
+
) {
|
|
246
273
|
this.killProcess(sessionKey, existingState);
|
|
247
274
|
}
|
|
248
275
|
return;
|
|
@@ -618,7 +645,11 @@ export class ClaudeCodeAdapter implements DispatchAdapter {
|
|
|
618
645
|
yield classifyClaudeTurn(lastResultMeta, noticeTexts);
|
|
619
646
|
}
|
|
620
647
|
|
|
621
|
-
if (
|
|
648
|
+
if (
|
|
649
|
+
state.needsRestart &&
|
|
650
|
+
!state.inputs.hasPendingInjections() &&
|
|
651
|
+
!state.inputs.hasUnsettledInjections()
|
|
652
|
+
) {
|
|
622
653
|
this.killProcess(sessionKey, state);
|
|
623
654
|
}
|
|
624
655
|
}
|
package/src/input-lifecycle.ts
CHANGED
|
@@ -55,6 +55,20 @@ export class ClaudeInputRegistry {
|
|
|
55
55
|
return [...this.byKey.values()].some((delivery) => delivery.injected && !delivery.drained);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Injected inputs whose lifecycle has NOT reached a terminal state. A
|
|
60
|
+
* terminal delivery still counts as pending (its buffered copy owes frame
|
|
61
|
+
* boundary bookkeeping) but owes the server nothing — the lane complete
|
|
62
|
+
* defers on this predicate, never on hasPendingInjections, so a bookkeeping
|
|
63
|
+
* copy that never drains cannot hold the lane open past its lease. Drained
|
|
64
|
+
* is deliberately NOT part of this predicate: a discarded bookkeeping copy
|
|
65
|
+
* (drained, non-terminal) is still a running input — completing its lane
|
|
66
|
+
* would release the member for redrive mid-processing.
|
|
67
|
+
*/
|
|
68
|
+
hasUnsettledInjections(): boolean {
|
|
69
|
+
return [...this.byKey.values()].some((delivery) => delivery.injected && !delivery.terminal);
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
register(
|
|
59
73
|
deliveryKey: string,
|
|
60
74
|
lifecycle: DispatchInputLifecycle | undefined,
|
|
@@ -85,6 +99,22 @@ export class ClaudeInputRegistry {
|
|
|
85
99
|
}
|
|
86
100
|
}
|
|
87
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The buffered copy backing this delivery was discarded without a
|
|
104
|
+
* dispatch (already rendered into a frame the model saw), so the normal
|
|
105
|
+
* bookkeeping drain never comes. Its frame-boundary obligation is void:
|
|
106
|
+
* mark it drained so nothing waits on it, and drop it once terminal. A
|
|
107
|
+
* still-running input keeps its registration — byCommand must keep routing
|
|
108
|
+
* its lifecycle frames — and is dropped when its terminal state arrives
|
|
109
|
+
* (see apply/fail).
|
|
110
|
+
*/
|
|
111
|
+
discardBookkeeping(deliveryKey: string): void {
|
|
112
|
+
const delivery = this.byKey.get(deliveryKey);
|
|
113
|
+
if (!delivery) return;
|
|
114
|
+
delivery.drained = true;
|
|
115
|
+
if (delivery.terminal) this.remove(delivery);
|
|
116
|
+
}
|
|
117
|
+
|
|
88
118
|
async apply(
|
|
89
119
|
delivery: ClaudeInputDelivery,
|
|
90
120
|
state: 'queued' | 'started' | 'completed' | 'cancelled' | 'discarded',
|
|
@@ -108,12 +138,17 @@ export class ClaudeInputRegistry {
|
|
|
108
138
|
delivery.reportedState = 'completed';
|
|
109
139
|
}
|
|
110
140
|
delivery.terminal = 'completed';
|
|
141
|
+
// A discarded bookkeeping copy (drained without a dispatch) has no
|
|
142
|
+
// drain left to remove this entry — drop it at its terminal so it can
|
|
143
|
+
// never shadow a redelivered batch in findOverlapping.
|
|
144
|
+
if (delivery.drained) this.remove(delivery);
|
|
111
145
|
}
|
|
112
146
|
|
|
113
147
|
async fail(delivery: ClaudeInputDelivery): Promise<void> {
|
|
114
148
|
if (delivery.terminal) return;
|
|
115
149
|
if (delivery.suppressFailReport) {
|
|
116
150
|
delivery.terminal = 'settled';
|
|
151
|
+
if (delivery.drained) this.remove(delivery);
|
|
117
152
|
return;
|
|
118
153
|
}
|
|
119
154
|
try {
|
|
@@ -124,6 +159,7 @@ export class ClaudeInputRegistry {
|
|
|
124
159
|
}
|
|
125
160
|
} finally {
|
|
126
161
|
delivery.terminal ??= 'failed';
|
|
162
|
+
if (delivery.drained) this.remove(delivery);
|
|
127
163
|
}
|
|
128
164
|
}
|
|
129
165
|
|