@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.
Files changed (99) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/dist/{asset-feeds-DQ3BUCKA.js → asset-feeds-44M2GV2W.js} +4 -4
  3. package/dist/{asset-feeds-DQ3BUCKA.js.map → asset-feeds-44M2GV2W.js.map} +1 -1
  4. package/dist/asset-manager/index.js +1 -1
  5. package/dist/bridge/drivers/claude-sdk.js +243 -23
  6. package/dist/bridge/drivers/claude-sdk.js.map +1 -1
  7. package/dist/bridge/drivers/codex.js +1 -1
  8. package/dist/bridge/drivers/continue.js +2 -2
  9. package/dist/bridge/drivers/echo.js +1 -1
  10. package/dist/bridge/drivers/gemini.js +3 -3
  11. package/dist/bridge/drivers/goose.js +2 -2
  12. package/dist/bridge/drivers/omp.js +1 -1
  13. package/dist/bridge/drivers/opencode.js +2 -2
  14. package/dist/bridge/drivers/qwen.js +3 -3
  15. package/dist/bridge/index.js +2 -2
  16. package/dist/bridge/src/drivers/claude-sdk.d.ts +84 -6
  17. package/dist/bridge/src/drivers/claude-sdk.d.ts.map +1 -1
  18. package/dist/{chunk-X73J3FFD.js → chunk-2GA2UGQI.js} +6 -6
  19. package/dist/{chunk-X73J3FFD.js.map → chunk-2GA2UGQI.js.map} +1 -1
  20. package/dist/{chunk-AFA7NYN2.js → chunk-32TJ55W5.js} +2 -2
  21. package/dist/{chunk-AFA7NYN2.js.map → chunk-32TJ55W5.js.map} +1 -1
  22. package/dist/{chunk-GVW2REJE.js → chunk-F7S6NDIW.js} +39 -18
  23. package/dist/chunk-F7S6NDIW.js.map +1 -0
  24. package/dist/{chunk-DH4BW6JA.js → chunk-I5TSXDTD.js} +3 -3
  25. package/dist/{chunk-DH4BW6JA.js.map → chunk-I5TSXDTD.js.map} +1 -1
  26. package/dist/{chunk-ZHHZBMZF.js → chunk-IKNQW3AW.js} +3 -3
  27. package/dist/{chunk-ZHHZBMZF.js.map → chunk-IKNQW3AW.js.map} +1 -1
  28. package/dist/{chunk-EWYVONO7.js → chunk-J4I2YPGE.js} +3 -3
  29. package/dist/{chunk-EWYVONO7.js.map → chunk-J4I2YPGE.js.map} +1 -1
  30. package/dist/{chunk-TMWPIQST.js → chunk-J7SEUNPD.js} +2 -2
  31. package/dist/{chunk-TMWPIQST.js.map → chunk-J7SEUNPD.js.map} +1 -1
  32. package/dist/{chunk-JDX4OWRI.js → chunk-JF73DP5W.js} +2 -2
  33. package/dist/{chunk-JDX4OWRI.js.map → chunk-JF73DP5W.js.map} +1 -1
  34. package/dist/{chunk-MKLIIH6W.js → chunk-JTBF4OB2.js} +4 -4
  35. package/dist/{chunk-MKLIIH6W.js.map → chunk-JTBF4OB2.js.map} +1 -1
  36. package/dist/{chunk-QJQL2DGN.js → chunk-NXHRSUKL.js} +4 -4
  37. package/dist/{chunk-QJQL2DGN.js.map → chunk-NXHRSUKL.js.map} +1 -1
  38. package/dist/{chunk-BC5MXJZA.js → chunk-SID35UAK.js} +3 -3
  39. package/dist/{chunk-BC5MXJZA.js.map → chunk-SID35UAK.js.map} +1 -1
  40. package/dist/{chunk-UTMZU56B.js → chunk-VXITGB7G.js} +3 -3
  41. package/dist/{chunk-UTMZU56B.js.map → chunk-VXITGB7G.js.map} +1 -1
  42. package/dist/{chunk-UTPQBYYY.js → chunk-XCG63ZNJ.js} +3 -3
  43. package/dist/{chunk-UTPQBYYY.js.map → chunk-XCG63ZNJ.js.map} +1 -1
  44. package/dist/{chunk-FMGYEYCW.js → chunk-Z7ILXHIT.js} +6 -6
  45. package/dist/{chunk-FMGYEYCW.js.map → chunk-Z7ILXHIT.js.map} +1 -1
  46. package/dist/cli/index.js +29 -29
  47. package/dist/connectors/index.js +3 -3
  48. package/dist/{ensure-sources-2LZWBCWO.js → ensure-sources-QRURV6JB.js} +4 -4
  49. package/dist/{ensure-sources-2LZWBCWO.js.map → ensure-sources-QRURV6JB.js.map} +1 -1
  50. package/dist/factory-assets/connectors/box.js +3 -3
  51. package/dist/factory-assets/connectors/deploy.js +3 -3
  52. package/dist/factory-assets/connectors/devserver.js +3 -3
  53. package/dist/factory-assets/connectors/flow/adapter.js +3 -3
  54. package/dist/factory-assets/connectors/flow/run-flow.js +4 -4
  55. package/dist/factory-assets/connectors/flow.js +3 -3
  56. package/dist/factory-assets/connectors/git.js +3 -3
  57. package/dist/factory-assets/connectors/gmail.js +3 -3
  58. package/dist/factory-assets/connectors/googledrive.js +3 -3
  59. package/dist/factory-assets/connectors/local.js +3 -3
  60. package/dist/factory-assets/connectors/mattermost.js +3 -3
  61. package/dist/factory-assets/connectors/memory.js +3 -3
  62. package/dist/factory-assets/connectors/minio.js +3 -3
  63. package/dist/factory-assets/connectors/postgres.js +3 -3
  64. package/dist/factory-assets/connectors/s3.js +3 -3
  65. package/dist/factory-assets/connectors/sharepoint.js +3 -3
  66. package/dist/factory-assets/connectors/sqlite.js +3 -3
  67. package/dist/factory-assets/connectors/static-server.js +3 -3
  68. package/dist/factory-assets/connectors/tunnel.js +3 -3
  69. package/dist/factory-assets/connectors/webdav.js +3 -3
  70. package/dist/factory-assets/connectors/xstate-store.js +3 -3
  71. package/dist/factory-assets/connectors/xstate.js +3 -3
  72. package/dist/{helpers-SW2GWYGU.js → helpers-TLTVVZRR.js} +4 -4
  73. package/dist/{helpers-SW2GWYGU.js.map → helpers-TLTVVZRR.js.map} +1 -1
  74. package/dist/{library-publish-VCTQFIIW.js → library-publish-6HXFLOXC.js} +6 -6
  75. package/dist/{library-publish-VCTQFIIW.js.map → library-publish-6HXFLOXC.js.map} +1 -1
  76. package/dist/{open-library-SDYW5T7S.js → open-library-FMVJICL5.js} +5 -5
  77. package/dist/{open-library-SDYW5T7S.js.map → open-library-FMVJICL5.js.map} +1 -1
  78. package/dist/{project-C2DGB2V3.js → project-BG3N4I3V.js} +5 -5
  79. package/dist/{project-C2DGB2V3.js.map → project-BG3N4I3V.js.map} +1 -1
  80. package/dist/runner/index.js +6 -6
  81. package/dist/runner/src/compaction/orchestrator.d.ts +4 -0
  82. package/dist/runner/src/compaction/orchestrator.d.ts.map +1 -1
  83. package/dist/runner/src/serve.d.ts.map +1 -1
  84. package/dist/sdk/asset-manager.js +1 -1
  85. package/dist/sdk/bridge.js +2 -2
  86. package/dist/sdk/index.js +6 -6
  87. package/dist/sdk/runner.js +6 -6
  88. package/dist/{setup-4TE4XIDD.js → setup-O2N6PA7I.js} +4 -4
  89. package/dist/{setup-4TE4XIDD.js.map → setup-O2N6PA7I.js.map} +1 -1
  90. package/dist/{source-HAA7QVAP.js → source-JDIBJZ3X.js} +6 -6
  91. package/dist/{source-HAA7QVAP.js.map → source-JDIBJZ3X.js.map} +1 -1
  92. package/dist/{source-manifest-publish-NLIJK22S.js → source-manifest-publish-567T25M6.js} +7 -7
  93. package/dist/{source-manifest-publish-NLIJK22S.js.map → source-manifest-publish-567T25M6.js.map} +1 -1
  94. package/dist/tui/index.js +6 -6
  95. package/dist/types/src/events.d.ts +15 -4
  96. package/dist/types/src/events.d.ts.map +1 -1
  97. package/dist/workspace-plugin/index.js +1 -1
  98. package/package.json +1 -1
  99. 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-AFA7NYN2.js';
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 — may stay alive between turns if streamInput works. */
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 fed via `streamInput()` for efficient multi-turn continuity. Otherwise a new query
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
- const userMsg = this.buildUserMessage(message);
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
- const budget = turnStallMs();
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, budget);
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", { idleMs });
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: message,
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 this.query) {
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.handleConsumerError(err);
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.completeTurn();
1042
- this.query = null;
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: msg.total_cost_usd ?? 0,
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 streamInput. */
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.