@skaile/workspaces 3.7.0 → 3.7.2
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/CHANGELOG.md +34 -0
- package/dist/{asset-feeds-DQ3BUCKA.js → asset-feeds-44M2GV2W.js} +4 -4
- package/dist/{asset-feeds-DQ3BUCKA.js.map → asset-feeds-44M2GV2W.js.map} +1 -1
- package/dist/asset-manager/index.js +1 -1
- package/dist/bridge/drivers/claude-sdk.js +243 -23
- package/dist/bridge/drivers/claude-sdk.js.map +1 -1
- package/dist/bridge/drivers/codex.js +1 -1
- package/dist/bridge/drivers/continue.js +2 -2
- package/dist/bridge/drivers/echo.js +1 -1
- package/dist/bridge/drivers/gemini.js +3 -3
- package/dist/bridge/drivers/goose.js +2 -2
- package/dist/bridge/drivers/omp.js +1 -1
- package/dist/bridge/drivers/opencode.js +2 -2
- package/dist/bridge/drivers/qwen.js +3 -3
- package/dist/bridge/index.js +2 -2
- package/dist/bridge/src/drivers/claude-sdk.d.ts +84 -6
- package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
- package/dist/{chunk-X73J3FFD.js → chunk-2GA2UGQI.js} +6 -6
- package/dist/{chunk-X73J3FFD.js.map → chunk-2GA2UGQI.js.map} +1 -1
- package/dist/{chunk-AFA7NYN2.js → chunk-32TJ55W5.js} +2 -2
- package/dist/{chunk-AFA7NYN2.js.map → chunk-32TJ55W5.js.map} +1 -1
- package/dist/{chunk-GVW2REJE.js → chunk-F7S6NDIW.js} +39 -18
- package/dist/chunk-F7S6NDIW.js.map +1 -0
- package/dist/{chunk-DH4BW6JA.js → chunk-I5TSXDTD.js} +3 -3
- package/dist/{chunk-DH4BW6JA.js.map → chunk-I5TSXDTD.js.map} +1 -1
- package/dist/{chunk-ZHHZBMZF.js → chunk-IKNQW3AW.js} +3 -3
- package/dist/{chunk-ZHHZBMZF.js.map → chunk-IKNQW3AW.js.map} +1 -1
- package/dist/{chunk-EWYVONO7.js → chunk-J4I2YPGE.js} +3 -3
- package/dist/{chunk-EWYVONO7.js.map → chunk-J4I2YPGE.js.map} +1 -1
- package/dist/{chunk-TMWPIQST.js → chunk-J7SEUNPD.js} +2 -2
- package/dist/{chunk-TMWPIQST.js.map → chunk-J7SEUNPD.js.map} +1 -1
- package/dist/{chunk-JDX4OWRI.js → chunk-JF73DP5W.js} +2 -2
- package/dist/{chunk-JDX4OWRI.js.map → chunk-JF73DP5W.js.map} +1 -1
- package/dist/{chunk-MKLIIH6W.js → chunk-JTBF4OB2.js} +4 -4
- package/dist/{chunk-MKLIIH6W.js.map → chunk-JTBF4OB2.js.map} +1 -1
- package/dist/{chunk-QJQL2DGN.js → chunk-NXHRSUKL.js} +4 -4
- package/dist/{chunk-QJQL2DGN.js.map → chunk-NXHRSUKL.js.map} +1 -1
- package/dist/{chunk-BC5MXJZA.js → chunk-SID35UAK.js} +3 -3
- package/dist/{chunk-BC5MXJZA.js.map → chunk-SID35UAK.js.map} +1 -1
- package/dist/{chunk-UTMZU56B.js → chunk-VXITGB7G.js} +3 -3
- package/dist/{chunk-UTMZU56B.js.map → chunk-VXITGB7G.js.map} +1 -1
- package/dist/{chunk-UTPQBYYY.js → chunk-XCG63ZNJ.js} +3 -3
- package/dist/{chunk-UTPQBYYY.js.map → chunk-XCG63ZNJ.js.map} +1 -1
- package/dist/{chunk-FMGYEYCW.js → chunk-Z7ILXHIT.js} +6 -6
- package/dist/{chunk-FMGYEYCW.js.map → chunk-Z7ILXHIT.js.map} +1 -1
- package/dist/cli/index.js +29 -29
- package/dist/connectors/index.js +3 -3
- package/dist/{ensure-sources-2LZWBCWO.js → ensure-sources-QRURV6JB.js} +4 -4
- package/dist/{ensure-sources-2LZWBCWO.js.map → ensure-sources-QRURV6JB.js.map} +1 -1
- package/dist/factory-assets/connectors/box.js +3 -3
- package/dist/factory-assets/connectors/deploy.js +3 -3
- package/dist/factory-assets/connectors/devserver.js +3 -3
- package/dist/factory-assets/connectors/flow/adapter.js +3 -3
- package/dist/factory-assets/connectors/flow/run-flow.js +4 -4
- package/dist/factory-assets/connectors/flow.js +3 -3
- package/dist/factory-assets/connectors/git.js +3 -3
- package/dist/factory-assets/connectors/gmail.js +3 -3
- package/dist/factory-assets/connectors/googledrive.js +3 -3
- package/dist/factory-assets/connectors/local.js +3 -3
- package/dist/factory-assets/connectors/mattermost.js +3 -3
- package/dist/factory-assets/connectors/memory.js +3 -3
- package/dist/factory-assets/connectors/minio.js +3 -3
- package/dist/factory-assets/connectors/postgres.js +3 -3
- package/dist/factory-assets/connectors/s3.js +3 -3
- package/dist/factory-assets/connectors/sharepoint.js +3 -3
- package/dist/factory-assets/connectors/sqlite.js +3 -3
- package/dist/factory-assets/connectors/static-server.js +3 -3
- package/dist/factory-assets/connectors/tunnel.js +3 -3
- package/dist/factory-assets/connectors/webdav.js +3 -3
- package/dist/factory-assets/connectors/xstate-store.js +3 -3
- package/dist/factory-assets/connectors/xstate.js +3 -3
- package/dist/{helpers-SW2GWYGU.js → helpers-TLTVVZRR.js} +4 -4
- package/dist/{helpers-SW2GWYGU.js.map → helpers-TLTVVZRR.js.map} +1 -1
- package/dist/{library-publish-VCTQFIIW.js → library-publish-6HXFLOXC.js} +6 -6
- package/dist/{library-publish-VCTQFIIW.js.map → library-publish-6HXFLOXC.js.map} +1 -1
- package/dist/{open-library-SDYW5T7S.js → open-library-FMVJICL5.js} +5 -5
- package/dist/{open-library-SDYW5T7S.js.map → open-library-FMVJICL5.js.map} +1 -1
- package/dist/{project-C2DGB2V3.js → project-BG3N4I3V.js} +5 -5
- package/dist/{project-C2DGB2V3.js.map → project-BG3N4I3V.js.map} +1 -1
- package/dist/runner/index.js +6 -6
- package/dist/runner/src/compaction/orchestrator.d.ts +4 -0
- package/dist/runner/src/compaction/orchestrator.d.ts.map +1 -1
- package/dist/runner/src/serve.d.ts.map +1 -1
- package/dist/sdk/asset-manager.js +1 -1
- package/dist/sdk/bridge.js +2 -2
- package/dist/sdk/index.js +6 -6
- package/dist/sdk/runner.js +6 -6
- package/dist/{setup-4TE4XIDD.js → setup-O2N6PA7I.js} +4 -4
- package/dist/{setup-4TE4XIDD.js.map → setup-O2N6PA7I.js.map} +1 -1
- package/dist/{source-HAA7QVAP.js → source-JDIBJZ3X.js} +6 -6
- package/dist/{source-HAA7QVAP.js.map → source-JDIBJZ3X.js.map} +1 -1
- package/dist/{source-manifest-publish-NLIJK22S.js → source-manifest-publish-567T25M6.js} +7 -7
- package/dist/{source-manifest-publish-NLIJK22S.js.map → source-manifest-publish-567T25M6.js.map} +1 -1
- package/dist/tui/index.js +6 -6
- package/dist/types/src/events.d.ts +15 -4
- package/dist/types/src/events.d.ts.map +1 -1
- package/dist/workspace-plugin/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-GVW2REJE.js.map +0 -1
|
@@ -2,7 +2,7 @@ import { scrubPoisonedTranscript, resolveClaudeConfigDir, SUPPORTED_CLOUDS, buil
|
|
|
2
2
|
import { dispatchCapability } from '../../chunk-RRVQAE5D.js';
|
|
3
3
|
import { fetchProviderModels } from '../../chunk-V34ZEXSE.js';
|
|
4
4
|
import { AuthError, classifyClaudeSdkError, reclassifyAuthAsAccountUnavailable, AccountUnavailableError } from '../../chunk-GAH4YRCI.js';
|
|
5
|
-
import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-
|
|
5
|
+
import { BUILTIN_DRIVER_CATALOG, AgentDriver, getBridgeLogger } from '../../chunk-32TJ55W5.js';
|
|
6
6
|
import '../../chunk-6E6PKKAD.js';
|
|
7
7
|
import '../../chunk-AZPXCHDK.js';
|
|
8
8
|
import '../../chunk-F3HNW354.js';
|
|
@@ -159,13 +159,62 @@ if (typeof globalThis.require === "undefined") {
|
|
|
159
159
|
}
|
|
160
160
|
}
|
|
161
161
|
function freshRetryBudget() {
|
|
162
|
-
return { staleResume: false, poison: false, auth: false };
|
|
162
|
+
return { staleResume: false, poison: false, auth: false, queryEnded: false };
|
|
163
163
|
}
|
|
164
|
+
var QueryEndedBeforeTurnError = class extends Error {
|
|
165
|
+
constructor(message) {
|
|
166
|
+
super(message);
|
|
167
|
+
this.name = "QueryEndedBeforeTurnError";
|
|
168
|
+
}
|
|
169
|
+
};
|
|
164
170
|
var DEFAULT_TURN_STALL_MS = 30 * 6e4;
|
|
165
171
|
function turnStallMs() {
|
|
166
172
|
const raw = Number(process.env.SKAILE_TURN_STALL_MS);
|
|
167
173
|
return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_TURN_STALL_MS;
|
|
168
174
|
}
|
|
175
|
+
var DEFAULT_FIRST_MESSAGE_STALL_MS = 5 * 6e4;
|
|
176
|
+
function firstMessageStallMs() {
|
|
177
|
+
const raw = Number(process.env.SKAILE_FIRST_MESSAGE_STALL_MS);
|
|
178
|
+
const value = Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_FIRST_MESSAGE_STALL_MS;
|
|
179
|
+
return Math.min(value, turnStallMs());
|
|
180
|
+
}
|
|
181
|
+
var RECYCLE_TURNS_FRACTION = 0.8;
|
|
182
|
+
var MIN_RECYCLE_HEADROOM = 5;
|
|
183
|
+
var PushableInputStream = class {
|
|
184
|
+
queue = [];
|
|
185
|
+
wake = null;
|
|
186
|
+
done = false;
|
|
187
|
+
consumed = false;
|
|
188
|
+
/** `true` once {@link end} ran — pushes are no longer possible. */
|
|
189
|
+
get ended() {
|
|
190
|
+
return this.done;
|
|
191
|
+
}
|
|
192
|
+
push(message) {
|
|
193
|
+
if (this.done) throw new Error("input stream already ended");
|
|
194
|
+
this.queue.push(message);
|
|
195
|
+
this.wake?.();
|
|
196
|
+
}
|
|
197
|
+
/** Finish the iterable; the SDK responds by closing the CLI's stdin. Idempotent. */
|
|
198
|
+
end() {
|
|
199
|
+
if (this.done) return;
|
|
200
|
+
this.done = true;
|
|
201
|
+
this.wake?.();
|
|
202
|
+
}
|
|
203
|
+
async *[Symbol.asyncIterator]() {
|
|
204
|
+
if (this.consumed) throw new Error("input stream already has a consumer");
|
|
205
|
+
this.consumed = true;
|
|
206
|
+
while (true) {
|
|
207
|
+
while (this.queue.length > 0) {
|
|
208
|
+
yield this.queue.shift();
|
|
209
|
+
}
|
|
210
|
+
if (this.done) return;
|
|
211
|
+
await new Promise((resolve) => {
|
|
212
|
+
this.wake = resolve;
|
|
213
|
+
});
|
|
214
|
+
this.wake = null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
};
|
|
169
218
|
var ABORT_GRACE_MS = 3e3;
|
|
170
219
|
var claudeBinaryCache;
|
|
171
220
|
var claudeBinaryResolved = false;
|
|
@@ -191,8 +240,26 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
191
240
|
emittedSanitizedLen = 0;
|
|
192
241
|
/** Set once the streaming text of the current message classifies as a self-healable auth render. */
|
|
193
242
|
suppressingAuthStream = false;
|
|
194
|
-
/** Active query —
|
|
243
|
+
/** Active query — stays alive between turns (streaming input mode). */
|
|
195
244
|
query = null;
|
|
245
|
+
/**
|
|
246
|
+
* The live query's prompt stream. Created in {@link startQuery}, fed one user
|
|
247
|
+
* message per turn, and ended wherever the query is dropped — an ended stream
|
|
248
|
+
* with a live query means the CLI's stdin is closed and the query can never
|
|
249
|
+
* accept another turn.
|
|
250
|
+
*/
|
|
251
|
+
inputStream = null;
|
|
252
|
+
/**
|
|
253
|
+
* `true` once any SDK message arrived after the current turn's watchdog was
|
|
254
|
+
* armed — flips the stall budget from the first-message tier to the mid-turn
|
|
255
|
+
* ceiling. See {@link armTurnStallWatchdog}.
|
|
256
|
+
*/
|
|
257
|
+
firstSdkMessageSeen = false;
|
|
258
|
+
/**
|
|
259
|
+
* `num_turns` reported by the most recent result message. Feeds
|
|
260
|
+
* {@link shouldRecycleQuery}; reset on every fresh spawn.
|
|
261
|
+
*/
|
|
262
|
+
lastNumTurns = null;
|
|
196
263
|
/**
|
|
197
264
|
* OS pid of the CLI subprocess backing `query`, captured via
|
|
198
265
|
* `spawnClaudeCodeProcess` in {@link startQuery}. Feeds
|
|
@@ -251,6 +318,22 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
251
318
|
_lastContextTokens = null;
|
|
252
319
|
/** Context window size from the most recent result's modelUsage. */
|
|
253
320
|
_contextWindow = null;
|
|
321
|
+
/**
|
|
322
|
+
* The SDK's `total_cost_usd` from the most recently seen result message —
|
|
323
|
+
* cumulative over the whole SDK run/session, not per-turn. Diffed against
|
|
324
|
+
* the next result's total to derive the per-turn delta.
|
|
325
|
+
*/
|
|
326
|
+
_lastCumulativeCostUsd = 0;
|
|
327
|
+
/** Per-turn cost delta derived from `total_cost_usd`, emitted as the `result` event's `costUsd`. */
|
|
328
|
+
_lastTurnCostUsd = 0;
|
|
329
|
+
/**
|
|
330
|
+
* Bumped by every `startQuery()` spawn. Lets `captureResultUsage` detect a
|
|
331
|
+
* fresh CLI process deterministically, instead of only inferring it from
|
|
332
|
+
* `total_cost_usd` happening to decrease.
|
|
333
|
+
*/
|
|
334
|
+
_querySpawnGeneration = 0;
|
|
335
|
+
/** Spawn generation `_lastCumulativeCostUsd` was captured under. */
|
|
336
|
+
_costTrackingGeneration = -1;
|
|
254
337
|
/**
|
|
255
338
|
* `true` when the active credential is a Claude Code OAuth subscription
|
|
256
339
|
* (no API key in config) rather than a raw `ANTHROPIC_API_KEY`. Set on
|
|
@@ -309,8 +392,8 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
309
392
|
* Sends a user message to the Claude Agent SDK and resolves when the turn completes.
|
|
310
393
|
*
|
|
311
394
|
* If a live SDK query already exists (from a prior turn in this session) the message
|
|
312
|
-
* is
|
|
313
|
-
* is started applying the resume chain described in the class-level remarks.
|
|
395
|
+
* is pushed into its persistent prompt stream (streaming input mode). Otherwise a new
|
|
396
|
+
* query is started applying the resume chain described in the class-level remarks.
|
|
314
397
|
*
|
|
315
398
|
* @param message - Plain-text user prompt.
|
|
316
399
|
* @throws {Error} When the SDK reports a fatal error that cannot be auto-recovered.
|
|
@@ -339,7 +422,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
339
422
|
if (!this.sdk) await this.start();
|
|
340
423
|
this.authSelfHealUsed = _budget.auth;
|
|
341
424
|
let retrying = false;
|
|
342
|
-
const isFirstAttempt = !_budget.staleResume && !_budget.poison && !_budget.auth;
|
|
425
|
+
const isFirstAttempt = !_budget.staleResume && !_budget.poison && !_budget.auth && !_budget.queryEnded;
|
|
343
426
|
if (isFirstAttempt) {
|
|
344
427
|
this._lastTokens = null;
|
|
345
428
|
this._lastContextTokens = null;
|
|
@@ -349,14 +432,16 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
349
432
|
this.emittedSanitizedLen = 0;
|
|
350
433
|
this.suppressingAuthStream = false;
|
|
351
434
|
this.turnCompleted = false;
|
|
435
|
+
if (this.query && (this.inputStream == null || this.inputStream.ended || this.shouldRecycleQuery())) {
|
|
436
|
+
this.recycleQueryForNextTurn();
|
|
437
|
+
}
|
|
352
438
|
const turnPromise = new Promise((resolve, reject) => {
|
|
353
439
|
this.turnResolve = resolve;
|
|
354
440
|
this.turnReject = reject;
|
|
355
441
|
});
|
|
356
442
|
try {
|
|
357
|
-
if (this.query) {
|
|
358
|
-
|
|
359
|
-
this.query.streamInput(this.singleItemIterable(userMsg));
|
|
443
|
+
if (this.query && this.inputStream) {
|
|
444
|
+
this.inputStream.push(this.buildUserMessage(message));
|
|
360
445
|
} else {
|
|
361
446
|
await this.startQuery(message);
|
|
362
447
|
}
|
|
@@ -370,6 +455,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
370
455
|
try {
|
|
371
456
|
await turnPromise;
|
|
372
457
|
} catch (err) {
|
|
458
|
+
if (!_budget.queryEnded && err instanceof QueryEndedBeforeTurnError) {
|
|
459
|
+
this.log.warn("turn was enqueued into an ending query; replaying on a fresh one");
|
|
460
|
+
retrying = true;
|
|
461
|
+
return this.runPrompt(message, { ..._budget, queryEnded: true });
|
|
462
|
+
}
|
|
373
463
|
const errMsg = err instanceof Error ? err.message : String(err);
|
|
374
464
|
const isStaleResume = !_budget.staleResume && /No conversation found with session ID/i.test(errMsg);
|
|
375
465
|
if (isStaleResume) {
|
|
@@ -406,13 +496,22 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
406
496
|
* Arm the per-turn inactivity watchdog. Self-rescheduling rather than a single
|
|
407
497
|
* absolute timer so any mid-turn SDK message pushes the deadline out; the
|
|
408
498
|
* timer is `unref`'d so an armed watchdog never holds the process open.
|
|
499
|
+
*
|
|
500
|
+
* Two budget tiers: until the first SDK message of the turn arrives the
|
|
501
|
+
* (small) {@link firstMessageStallMs} budget applies — silence there means
|
|
502
|
+
* the query is deaf, not busy. Once anything arrives, the (large)
|
|
503
|
+
* {@link turnStallMs} ceiling takes over, because a long-running tool call
|
|
504
|
+
* legitimately produces no SDK messages. The budget is re-read every tick, so
|
|
505
|
+
* the first message flips tiers without re-arming.
|
|
409
506
|
*/
|
|
410
507
|
armTurnStallWatchdog() {
|
|
411
508
|
this.disarmTurnStallWatchdog();
|
|
412
509
|
if (this.turnCompleted || this.turnReject == null) return;
|
|
413
|
-
|
|
510
|
+
this.firstSdkMessageSeen = false;
|
|
511
|
+
const currentBudget = () => this.firstSdkMessageSeen ? turnStallMs() : firstMessageStallMs();
|
|
414
512
|
const tick = () => {
|
|
415
513
|
if (this.turnCompleted || this.turnReject == null) return;
|
|
514
|
+
const budget = currentBudget();
|
|
416
515
|
if (this.hasPendingQuestion()) {
|
|
417
516
|
this.turnStallTimer = setTimeout(tick, budget);
|
|
418
517
|
this.turnStallTimer.unref?.();
|
|
@@ -427,7 +526,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
427
526
|
this.onTurnStalled(idleMs);
|
|
428
527
|
};
|
|
429
528
|
this.lastSdkMessageAt = Date.now();
|
|
430
|
-
this.turnStallTimer = setTimeout(tick,
|
|
529
|
+
this.turnStallTimer = setTimeout(tick, currentBudget());
|
|
431
530
|
this.turnStallTimer.unref?.();
|
|
432
531
|
}
|
|
433
532
|
disarmTurnStallWatchdog() {
|
|
@@ -443,7 +542,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
443
542
|
*/
|
|
444
543
|
onTurnStalled(idleMs) {
|
|
445
544
|
const idle = idleMs < 6e4 ? `${Math.round(idleMs / 1e3)}s` : `${Math.round(idleMs / 6e4)}m`;
|
|
446
|
-
this.log.error("turn stalled \u2014 no SDK message received; resetting the query", {
|
|
545
|
+
this.log.error("turn stalled \u2014 no SDK message received; resetting the query", {
|
|
546
|
+
idleMs,
|
|
547
|
+
firstMessageOfTurnSeen: this.firstSdkMessageSeen
|
|
548
|
+
});
|
|
447
549
|
const descendants = this.snapshotDescendantPids();
|
|
448
550
|
this.hardResetQuery();
|
|
449
551
|
this.killPids(descendants);
|
|
@@ -465,6 +567,62 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
465
567
|
* abort signal) runs, then {@link killPids} after.
|
|
466
568
|
*/
|
|
467
569
|
hardResetQuery() {
|
|
570
|
+
this.endInputStream();
|
|
571
|
+
if (this.query) {
|
|
572
|
+
try {
|
|
573
|
+
this.query.close();
|
|
574
|
+
} catch {
|
|
575
|
+
}
|
|
576
|
+
this.query = null;
|
|
577
|
+
}
|
|
578
|
+
this.abortController?.abort();
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* End the persistent prompt stream (the SDK responds by closing the CLI's
|
|
582
|
+
* stdin) and forget it. Called wherever the query is dropped so a stale
|
|
583
|
+
* stream can never be pushed into. Safe to call with no stream.
|
|
584
|
+
*/
|
|
585
|
+
endInputStream() {
|
|
586
|
+
if (this.inputStream) {
|
|
587
|
+
this.inputStream.end();
|
|
588
|
+
this.inputStream = null;
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* `maxTurns` counts agentic turns, but whether the CLI resets that count per
|
|
593
|
+
* user turn or accumulates it across a resident streaming session is
|
|
594
|
+
* undocumented. This guard is correct under either semantics: once the last
|
|
595
|
+
* result's reported `num_turns` is within {@link RECYCLE_TURNS_FRACTION}
|
|
596
|
+
* headroom of the ceiling, the resident query is dropped at the next turn
|
|
597
|
+
* boundary and the prompt respawns with `--resume` (which restarts the
|
|
598
|
+
* count). Under per-turn semantics this fires only when a single prompt
|
|
599
|
+
* nearly exhausted the budget by itself — where a recycle is harmless.
|
|
600
|
+
*/
|
|
601
|
+
shouldRecycleQuery() {
|
|
602
|
+
if (this.lastNumTurns == null) return false;
|
|
603
|
+
const max = this.config.maxTurns ?? 15;
|
|
604
|
+
const threshold = Math.min(
|
|
605
|
+
Math.max(1, Math.floor(max * RECYCLE_TURNS_FRACTION)),
|
|
606
|
+
Math.max(1, max - MIN_RECYCLE_HEADROOM)
|
|
607
|
+
);
|
|
608
|
+
return this.lastNumTurns >= threshold;
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Gracefully drop the resident query between turns so the next prompt starts
|
|
612
|
+
* a fresh CLI resuming the same session. Deliberately does NOT sweep the
|
|
613
|
+
* CLI's descendant processes: a between-turns recycle mirrors the legacy
|
|
614
|
+
* spawn-per-turn behavior, where the CLI's background tasks (`run_in_background`
|
|
615
|
+
* dev servers) outlive the turn boundary. Note they become orphans no later
|
|
616
|
+
* sweep can reach — snapshotDescendantPids walks from `cliProcessPid`, which
|
|
617
|
+
* the next spawn overwrites — exactly as in legacy spawn-per-turn.
|
|
618
|
+
*/
|
|
619
|
+
recycleQueryForNextTurn() {
|
|
620
|
+
this.log.info("recycling resident query before the next turn", {
|
|
621
|
+
reportedTurns: this.lastNumTurns,
|
|
622
|
+
maxTurns: this.config.maxTurns ?? 15,
|
|
623
|
+
inputStreamEnded: this.inputStream?.ended ?? null
|
|
624
|
+
});
|
|
625
|
+
this.endInputStream();
|
|
468
626
|
if (this.query) {
|
|
469
627
|
try {
|
|
470
628
|
this.query.close();
|
|
@@ -550,6 +708,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
550
708
|
this.sessionId = "";
|
|
551
709
|
this.hasSession = false;
|
|
552
710
|
this.query = null;
|
|
711
|
+
this.endInputStream();
|
|
553
712
|
this.config.resumeSessionId = void 0;
|
|
554
713
|
this.turnResolve = null;
|
|
555
714
|
this.turnReject = null;
|
|
@@ -596,6 +755,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
596
755
|
fatal: false
|
|
597
756
|
});
|
|
598
757
|
this.query = null;
|
|
758
|
+
this.endInputStream();
|
|
599
759
|
this.turnResolve = null;
|
|
600
760
|
this.turnReject = null;
|
|
601
761
|
this.running = false;
|
|
@@ -636,6 +796,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
636
796
|
if (refreshed) {
|
|
637
797
|
this.log.info("credentials refreshed; retrying prompt");
|
|
638
798
|
this.query = null;
|
|
799
|
+
this.endInputStream();
|
|
639
800
|
this.turnResolve = null;
|
|
640
801
|
this.turnReject = null;
|
|
641
802
|
this.running = false;
|
|
@@ -719,6 +880,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
719
880
|
return "default";
|
|
720
881
|
}
|
|
721
882
|
async startQuery(message) {
|
|
883
|
+
this._querySpawnGeneration++;
|
|
722
884
|
this.abortController = new AbortController();
|
|
723
885
|
await this.preventivelyScrubTranscript();
|
|
724
886
|
const cloud = this.resolveCloud();
|
|
@@ -838,8 +1000,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
838
1000
|
options.env = { ...baseEnv, ...this.config.env ?? {}, ANTHROPIC_API_KEY: apiKey };
|
|
839
1001
|
}
|
|
840
1002
|
const { query } = this.sdk;
|
|
1003
|
+
this.inputStream = new PushableInputStream();
|
|
1004
|
+
this.inputStream.push(this.buildUserMessage(message));
|
|
1005
|
+
this.lastNumTurns = null;
|
|
841
1006
|
const queryArgs = {
|
|
842
|
-
prompt:
|
|
1007
|
+
prompt: this.inputStream,
|
|
843
1008
|
options
|
|
844
1009
|
};
|
|
845
1010
|
if (apiKey) queryArgs.apiKey = apiKey;
|
|
@@ -1023,11 +1188,14 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1023
1188
|
* If it ends unexpectedly, query is nulled so the next prompt() restarts.
|
|
1024
1189
|
*/
|
|
1025
1190
|
async consumeMessages() {
|
|
1191
|
+
const servedQuery = this.query;
|
|
1026
1192
|
void this.fetchSlashCommands();
|
|
1027
1193
|
try {
|
|
1028
|
-
for await (const msg of
|
|
1194
|
+
for await (const msg of servedQuery) {
|
|
1195
|
+
if (this.query !== servedQuery) break;
|
|
1029
1196
|
if (this.abortController?.signal.aborted) break;
|
|
1030
1197
|
this.lastSdkMessageAt = Date.now();
|
|
1198
|
+
this.firstSdkMessageSeen = true;
|
|
1031
1199
|
this.captureSessionId(msg);
|
|
1032
1200
|
this.handleSdkMessage(msg);
|
|
1033
1201
|
if (msg.type === "result" && !msg.is_error) {
|
|
@@ -1035,11 +1203,29 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1035
1203
|
}
|
|
1036
1204
|
}
|
|
1037
1205
|
} catch (err) {
|
|
1038
|
-
this.
|
|
1206
|
+
if (this.query === servedQuery) {
|
|
1207
|
+
this.handleConsumerError(err);
|
|
1208
|
+
} else {
|
|
1209
|
+
this.log.debug("stale consumer error after query replacement; ignoring", {
|
|
1210
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1211
|
+
});
|
|
1212
|
+
}
|
|
1039
1213
|
} finally {
|
|
1040
1214
|
this.log.debug("query generator ended");
|
|
1041
|
-
this.
|
|
1042
|
-
|
|
1215
|
+
if (this.query === servedQuery) {
|
|
1216
|
+
this.query = null;
|
|
1217
|
+
this.endInputStream();
|
|
1218
|
+
const neverDispatched = !this.turnCompleted && this.turnReject != null && !this.firstSdkMessageSeen && !this.abortController?.signal.aborted;
|
|
1219
|
+
if (neverDispatched) {
|
|
1220
|
+
this.rejectTurnSilently(
|
|
1221
|
+
new QueryEndedBeforeTurnError(
|
|
1222
|
+
"The model process ended before this turn was dispatched."
|
|
1223
|
+
)
|
|
1224
|
+
);
|
|
1225
|
+
} else {
|
|
1226
|
+
this.completeTurn();
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1043
1229
|
}
|
|
1044
1230
|
}
|
|
1045
1231
|
/** Capture the SDK session id from a message (first one assigns hasSession). */
|
|
@@ -1102,6 +1288,27 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1102
1288
|
});
|
|
1103
1289
|
}
|
|
1104
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Reject the current turn WITHOUT emitting an `agent-event: error`.
|
|
1293
|
+
*
|
|
1294
|
+
* The one legitimate use is a turn that is about to be replayed: the host
|
|
1295
|
+
* must not see an error for a failure the driver is itself recovering from
|
|
1296
|
+
* (the same reasoning as `failTurn`'s auth self-heal deferral). Ordering
|
|
1297
|
+
* mirrors {@link failTurn} — settle the promise before touching anything
|
|
1298
|
+
* else — so a turn can never be left pending.
|
|
1299
|
+
*
|
|
1300
|
+
* Anything a caller should actually learn about goes through `failTurn`.
|
|
1301
|
+
*/
|
|
1302
|
+
rejectTurnSilently(err) {
|
|
1303
|
+
this.disarmTurnStallWatchdog();
|
|
1304
|
+
this.turnCompleted = true;
|
|
1305
|
+
if (this.turnReject) {
|
|
1306
|
+
const reject = this.turnReject;
|
|
1307
|
+
this.turnResolve = null;
|
|
1308
|
+
this.turnReject = null;
|
|
1309
|
+
reject(err);
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1105
1312
|
/**
|
|
1106
1313
|
* Signal the current turn as failed. When a structured `detail` is
|
|
1107
1314
|
* supplied, the AgentEvent carries the category + hint so downstream
|
|
@@ -1302,6 +1509,8 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1302
1509
|
* tokens — surface all four to consumers that want a complete picture.
|
|
1303
1510
|
*/
|
|
1304
1511
|
captureResultUsage(msg) {
|
|
1512
|
+
const numTurns = msg.num_turns;
|
|
1513
|
+
if (typeof numTurns === "number") this.lastNumTurns = numTurns;
|
|
1305
1514
|
const usage = msg.usage;
|
|
1306
1515
|
if (usage) {
|
|
1307
1516
|
const tokens = {};
|
|
@@ -1318,6 +1527,11 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1318
1527
|
const first = Object.values(modelUsage)[0];
|
|
1319
1528
|
if (first?.contextWindow) this._contextWindow = first.contextWindow;
|
|
1320
1529
|
}
|
|
1530
|
+
const totalCostUsd = msg.total_cost_usd ?? 0;
|
|
1531
|
+
const freshCostSession = this._querySpawnGeneration !== this._costTrackingGeneration || totalCostUsd < this._lastCumulativeCostUsd;
|
|
1532
|
+
this._lastTurnCostUsd = freshCostSession ? totalCostUsd : totalCostUsd - this._lastCumulativeCostUsd;
|
|
1533
|
+
this._lastCumulativeCostUsd = totalCostUsd;
|
|
1534
|
+
this._costTrackingGeneration = this._querySpawnGeneration;
|
|
1321
1535
|
}
|
|
1322
1536
|
/** Emit the message_end (if any) + success result event for a clean turn. */
|
|
1323
1537
|
handleSuccessResult(msg) {
|
|
@@ -1333,7 +1547,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1333
1547
|
type: "result",
|
|
1334
1548
|
subtype: msg.subtype,
|
|
1335
1549
|
summary: result ?? "Task completed",
|
|
1336
|
-
costUsd:
|
|
1550
|
+
costUsd: this._lastTurnCostUsd,
|
|
1337
1551
|
...this._lastTokens ? { tokens: this._lastTokens } : {}
|
|
1338
1552
|
});
|
|
1339
1553
|
}
|
|
@@ -1354,6 +1568,10 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1354
1568
|
subtype: msg.subtype,
|
|
1355
1569
|
errors
|
|
1356
1570
|
});
|
|
1571
|
+
const awaitingFollowUpThrow = looseMsg.is_error === true && errors.length === 0;
|
|
1572
|
+
if (!this.turnCompleted && !awaitingFollowUpThrow) {
|
|
1573
|
+
this.completeTurn();
|
|
1574
|
+
}
|
|
1357
1575
|
}
|
|
1358
1576
|
/**
|
|
1359
1577
|
* Classify + route an `is_error` result payload: auth-shaped errors throw
|
|
@@ -1391,7 +1609,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1391
1609
|
// ---------------------------------------------------------------------------
|
|
1392
1610
|
// Helpers
|
|
1393
1611
|
// ---------------------------------------------------------------------------
|
|
1394
|
-
/** Build an SDKUserMessage for
|
|
1612
|
+
/** Build an SDKUserMessage for the persistent prompt stream. */
|
|
1395
1613
|
buildUserMessage(text) {
|
|
1396
1614
|
return {
|
|
1397
1615
|
type: "user",
|
|
@@ -1403,10 +1621,6 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1403
1621
|
session_id: this.sessionId
|
|
1404
1622
|
};
|
|
1405
1623
|
}
|
|
1406
|
-
/** Wrap a single value as an AsyncIterable that yields once. */
|
|
1407
|
-
async *singleItemIterable(item) {
|
|
1408
|
-
yield item;
|
|
1409
|
-
}
|
|
1410
1624
|
mapAssistantMessage(msg) {
|
|
1411
1625
|
const contentBlocks = [];
|
|
1412
1626
|
const toolCalls = [];
|
|
@@ -1467,6 +1681,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1467
1681
|
*/
|
|
1468
1682
|
async abort() {
|
|
1469
1683
|
this.denyPendingQuestion("turn aborted before the question was answered");
|
|
1684
|
+
if (!this.running && this.query) return;
|
|
1470
1685
|
const abortedQuery = this.query;
|
|
1471
1686
|
const abortedController = this.abortController;
|
|
1472
1687
|
const descendants = this.snapshotDescendantPids();
|
|
@@ -1507,6 +1722,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1507
1722
|
kill() {
|
|
1508
1723
|
this.denyPendingQuestion("session terminated before the question was answered");
|
|
1509
1724
|
const descendants = this.snapshotDescendantPids();
|
|
1725
|
+
this.endInputStream();
|
|
1510
1726
|
if (this.query) {
|
|
1511
1727
|
try {
|
|
1512
1728
|
this.query.close();
|
|
@@ -1539,6 +1755,7 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1539
1755
|
async resetSession() {
|
|
1540
1756
|
this.denyPendingQuestion("session reset before the question was answered");
|
|
1541
1757
|
const descendants = this.snapshotDescendantPids();
|
|
1758
|
+
this.endInputStream();
|
|
1542
1759
|
if (this.query) {
|
|
1543
1760
|
try {
|
|
1544
1761
|
this.query.close();
|
|
@@ -1553,6 +1770,9 @@ var ClaudeSdkDriver = class extends AgentDriver {
|
|
|
1553
1770
|
this._lastTokens = null;
|
|
1554
1771
|
this._lastContextTokens = null;
|
|
1555
1772
|
this._contextWindow = null;
|
|
1773
|
+
this._lastCumulativeCostUsd = 0;
|
|
1774
|
+
this._lastTurnCostUsd = 0;
|
|
1775
|
+
this._costTrackingGeneration = -1;
|
|
1556
1776
|
}
|
|
1557
1777
|
/**
|
|
1558
1778
|
* Build allowedTools / disallowedTools options from AgentConfig.tools.
|