@skaile/workspaces 3.13.0 → 3.14.1

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 (81) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/dist/{asset-feeds-553UV3SZ.js → asset-feeds-STHKKTW3.js} +4 -4
  3. package/dist/{asset-feeds-553UV3SZ.js.map → asset-feeds-STHKKTW3.js.map} +1 -1
  4. package/dist/asset-manager/index.js +1 -1
  5. package/dist/{chunk-O7BTSMLV.js → chunk-5F33LGZL.js} +3 -3
  6. package/dist/{chunk-O7BTSMLV.js.map → chunk-5F33LGZL.js.map} +1 -1
  7. package/dist/{chunk-PY255PK6.js → chunk-HYFJPR7W.js} +6 -6
  8. package/dist/{chunk-PY255PK6.js.map → chunk-HYFJPR7W.js.map} +1 -1
  9. package/dist/{chunk-YSAYNSML.js → chunk-KKMRT6GU.js} +3 -3
  10. package/dist/chunk-KKMRT6GU.js.map +1 -0
  11. package/dist/{chunk-BCCF5O5X.js → chunk-URB2QZ4W.js} +2 -2
  12. package/dist/{chunk-BCCF5O5X.js.map → chunk-URB2QZ4W.js.map} +1 -1
  13. package/dist/{chunk-SVCJ5WRP.js → chunk-UTW6YKAC.js} +2 -2
  14. package/dist/{chunk-SVCJ5WRP.js.map → chunk-UTW6YKAC.js.map} +1 -1
  15. package/dist/{chunk-I7G3ZNYE.js → chunk-VW6RUT7X.js} +3 -3
  16. package/dist/{chunk-I7G3ZNYE.js.map → chunk-VW6RUT7X.js.map} +1 -1
  17. package/dist/{chunk-4DAERG6K.js → chunk-XICDQVOG.js} +3 -3
  18. package/dist/{chunk-4DAERG6K.js.map → chunk-XICDQVOG.js.map} +1 -1
  19. package/dist/{chunk-Q7EIHMSP.js → chunk-Y6JJHOLR.js} +6 -6
  20. package/dist/{chunk-Q7EIHMSP.js.map → chunk-Y6JJHOLR.js.map} +1 -1
  21. package/dist/{chunk-LIUBSGYL.js → chunk-Z7W4B7P2.js} +3 -3
  22. package/dist/{chunk-LIUBSGYL.js.map → chunk-Z7W4B7P2.js.map} +1 -1
  23. package/dist/{chunk-YIPVMBCG.js → chunk-ZPCHTTD7.js} +348 -37
  24. package/dist/chunk-ZPCHTTD7.js.map +1 -0
  25. package/dist/cli/index.js +26 -26
  26. package/dist/connectors/index.js +1 -1
  27. package/dist/{ensure-sources-IT6FNUI7.js → ensure-sources-BFVKV773.js} +4 -4
  28. package/dist/{ensure-sources-IT6FNUI7.js.map → ensure-sources-BFVKV773.js.map} +1 -1
  29. package/dist/factory-assets/connectors/box.js +1 -1
  30. package/dist/factory-assets/connectors/deploy.js +1 -1
  31. package/dist/factory-assets/connectors/devserver.js +1 -1
  32. package/dist/factory-assets/connectors/flow/adapter.js +1 -1
  33. package/dist/factory-assets/connectors/flow/run-flow.js +2 -2
  34. package/dist/factory-assets/connectors/flow.js +1 -1
  35. package/dist/factory-assets/connectors/git.js +1 -1
  36. package/dist/factory-assets/connectors/gmail.js +1 -1
  37. package/dist/factory-assets/connectors/googledrive.js +1 -1
  38. package/dist/factory-assets/connectors/local.js +1 -1
  39. package/dist/factory-assets/connectors/mattermost.js +1 -1
  40. package/dist/factory-assets/connectors/memory.js +1 -1
  41. package/dist/factory-assets/connectors/minio.js +1 -1
  42. package/dist/factory-assets/connectors/postgres.js +1 -1
  43. package/dist/factory-assets/connectors/s3.js +1 -1
  44. package/dist/factory-assets/connectors/sharepoint.js +1 -1
  45. package/dist/factory-assets/connectors/sqlite.js +1 -1
  46. package/dist/factory-assets/connectors/static-server.js +1 -1
  47. package/dist/factory-assets/connectors/tunnel.js +1 -1
  48. package/dist/factory-assets/connectors/webdav.js +1 -1
  49. package/dist/factory-assets/connectors/xstate-store.js +1 -1
  50. package/dist/factory-assets/connectors/xstate.js +1 -1
  51. package/dist/{helpers-RKVDZOID.js → helpers-CQV2V2SL.js} +4 -4
  52. package/dist/{helpers-RKVDZOID.js.map → helpers-CQV2V2SL.js.map} +1 -1
  53. package/dist/{library-publish-NFD7TLV7.js → library-publish-6FZCXPYU.js} +6 -6
  54. package/dist/{library-publish-NFD7TLV7.js.map → library-publish-6FZCXPYU.js.map} +1 -1
  55. package/dist/{open-library-UHCMZWK5.js → open-library-4EOFZ4QD.js} +5 -5
  56. package/dist/{open-library-UHCMZWK5.js.map → open-library-4EOFZ4QD.js.map} +1 -1
  57. package/dist/{project-MURYBN6B.js → project-J7RSKBAU.js} +5 -5
  58. package/dist/{project-MURYBN6B.js.map → project-J7RSKBAU.js.map} +1 -1
  59. package/dist/runner/index.js +4 -4
  60. package/dist/runner/src/compaction/orchestrator.d.ts +89 -4
  61. package/dist/runner/src/compaction/orchestrator.d.ts.map +1 -1
  62. package/dist/runner/src/compaction/prompt.d.ts +6 -1
  63. package/dist/runner/src/compaction/prompt.d.ts.map +1 -1
  64. package/dist/runner/src/compaction-gate.d.ts +93 -0
  65. package/dist/runner/src/compaction-gate.d.ts.map +1 -0
  66. package/dist/runner/src/serve.d.ts.map +1 -1
  67. package/dist/sdk/asset-manager.js +1 -1
  68. package/dist/sdk/index.js +4 -4
  69. package/dist/sdk/runner.js +4 -4
  70. package/dist/{source-3BDAWE4X.js → source-AJ27AS6D.js} +6 -6
  71. package/dist/{source-3BDAWE4X.js.map → source-AJ27AS6D.js.map} +1 -1
  72. package/dist/{source-manifest-publish-PRC7KC3J.js → source-manifest-publish-UCA2O5UA.js} +7 -7
  73. package/dist/{source-manifest-publish-PRC7KC3J.js.map → source-manifest-publish-UCA2O5UA.js.map} +1 -1
  74. package/dist/tui/index.js +5 -5
  75. package/dist/tui/index.js.map +1 -1
  76. package/dist/types/src/events.d.ts +11 -0
  77. package/dist/types/src/events.d.ts.map +1 -1
  78. package/dist/workspace-plugin/index.js +1 -1
  79. package/package.json +2 -1
  80. package/dist/chunk-YIPVMBCG.js.map +0 -1
  81. package/dist/chunk-YSAYNSML.js.map +0 -1
@@ -1,9 +1,9 @@
1
- import { WorkspacePlugin } from './chunk-SVCJ5WRP.js';
1
+ import { WorkspacePlugin } from './chunk-UTW6YKAC.js';
2
2
  import { assembleSystemPrompt, buildCapabilitiesPromptSection } from './chunk-W3UDISS2.js';
3
3
  import { WebSocketServerTransport } from './chunk-WQ7DE5UC.js';
4
- import { resolveFlowPath } from './chunk-I7G3ZNYE.js';
4
+ import { resolveFlowPath } from './chunk-VW6RUT7X.js';
5
5
  import { findAiAssetsRoot, deployCatalogEntry, undeployCatalogEntry } from './chunk-GHWJHOYX.js';
6
- import { registerBuiltinConnectors, buildConnectorPromptSection, findMissingPackages, installNpmPackages, ConnectorManager, ConnectorStartupError, buildSdkConnectorTools, driveOrchestratorTurn, SessionNodeExecutor, SessionSubpromptRunner, SessionInlineSubFlowRunner, buildSdkFlowTools } from './chunk-YSAYNSML.js';
6
+ import { registerBuiltinConnectors, buildConnectorPromptSection, findMissingPackages, installNpmPackages, ConnectorManager, ConnectorStartupError, buildSdkConnectorTools, driveOrchestratorTurn, SessionNodeExecutor, SessionSubpromptRunner, SessionInlineSubFlowRunner, buildSdkFlowTools } from './chunk-KKMRT6GU.js';
7
7
  import { loadConnectorDeclarations, SecretProviderChain, PreMintedSecretProvider, InMemorySecretProvider, resolvedConfigToDeclarations, isKnownSecretRef, deriveSingleMaterializedConnectorDeclaration } from './chunk-3UU3D2Q3.js';
8
8
  import { EventNormalizer } from './chunk-2A7BV6WT.js';
9
9
  import { SUPPORTED_CLOUDS, probeResumeTranscript, CLOUD_SECRET_ENV_KEYS } from './chunk-RUFTB7QN.js';
@@ -3123,6 +3123,108 @@ function cleanupCloudCredentialFiles(projectDir) {
3123
3123
  }
3124
3124
  }
3125
3125
 
3126
+ // runner/src/compaction-gate.ts
3127
+ var CompactionGate = class {
3128
+ /**
3129
+ * Every compaction currently holding the driver.
3130
+ *
3131
+ * A set rather than a boolean because holds can overlap: a driver swap
3132
+ * rebuilds the orchestrator, and the new instance carries its own
3133
+ * `_isCompacting`, so it can start a compaction while the previous
3134
+ * instance's call is still unwinding. Keyed by handle so the older one
3135
+ * settling cannot open the gate under the newer one — the same identity
3136
+ * check `DriverSwapGate.beginSwap` makes for swaps.
3137
+ */
3138
+ held = /* @__PURE__ */ new Set();
3139
+ /** Resolves when the last hold is released. Null whenever the driver is free. */
3140
+ opened = null;
3141
+ release = null;
3142
+ /**
3143
+ * Mark a compaction as occupying the driver. The returned handle is the only
3144
+ * thing that can release it.
3145
+ */
3146
+ begin() {
3147
+ const hold = /* @__PURE__ */ Symbol("compaction");
3148
+ if (this.held.size === 0) {
3149
+ this.opened = new Promise((resolve4) => {
3150
+ this.release = resolve4;
3151
+ });
3152
+ }
3153
+ this.held.add(hold);
3154
+ return hold;
3155
+ }
3156
+ /**
3157
+ * Release one hold, opening the gate once none remain. Unknown and null
3158
+ * handles are ignored, so a settle signal that fires without a matching
3159
+ * `begin()` is harmless.
3160
+ *
3161
+ * Call this from a signal the orchestrator emits unconditionally. A `begin()`
3162
+ * whose `settle()` can be skipped holds user turns forever, which is a worse
3163
+ * failure than the discarded reply this gate exists to prevent.
3164
+ */
3165
+ settle(hold) {
3166
+ if (hold == null || !this.held.delete(hold)) return;
3167
+ if (this.held.size > 0) return;
3168
+ const release = this.release;
3169
+ this.opened = null;
3170
+ this.release = null;
3171
+ release?.();
3172
+ }
3173
+ /**
3174
+ * Wait for the driver to be free.
3175
+ *
3176
+ * Returns `idle` when nothing was in flight, `waited` when every compaction
3177
+ * settled within the bound, and `timeout` when they did not. A caller that
3178
+ * gets `timeout` must surface it rather than dispatch: a compaction still
3179
+ * holding the driver past this bound is stuck, and dispatching into it
3180
+ * reproduces the silent loss.
3181
+ *
3182
+ * Re-checks in a loop, as `DriverSwapGate.awaitSwap` does, so a compaction
3183
+ * armed while this turn was already waiting is also awaited. `timeoutMs`
3184
+ * bounds the whole wait, not each iteration.
3185
+ */
3186
+ async awaitIdle(timeoutMs) {
3187
+ if (this.held.size === 0) return "idle";
3188
+ const deadline = Date.now() + timeoutMs;
3189
+ let waited = false;
3190
+ while (this.held.size > 0) {
3191
+ const opened = this.opened;
3192
+ if (!opened) break;
3193
+ const remaining = deadline - Date.now();
3194
+ if (remaining <= 0) return "timeout";
3195
+ if (await raceDeadline(opened, remaining) === "timeout") return "timeout";
3196
+ waited = true;
3197
+ }
3198
+ return waited ? "waited" : "idle";
3199
+ }
3200
+ };
3201
+ async function raceDeadline(opened, ms) {
3202
+ let timer;
3203
+ const expired = new Promise((resolve4) => {
3204
+ timer = setTimeout(() => resolve4("timeout"), ms);
3205
+ timer.unref?.();
3206
+ });
3207
+ try {
3208
+ return await Promise.race([opened.then(() => "opened"), expired]);
3209
+ } finally {
3210
+ if (timer) clearTimeout(timer);
3211
+ }
3212
+ }
3213
+ var DEFAULT_COMPACTION_GATE_TIMEOUT_MS = 60 * 6e4;
3214
+ function compactionGateTimeoutMs() {
3215
+ const raw = Number(process.env.SKAILE_COMPACTION_GATE_TIMEOUT_MS);
3216
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_COMPACTION_GATE_TIMEOUT_MS;
3217
+ }
3218
+ var COMPACTION_GATE_TIMEOUT_MESSAGE = "The session is still compacting its history and could not take this message. Send it again in a moment.";
3219
+ async function guardTurnAgainstCompaction(deps) {
3220
+ const outcome = await deps.gate.awaitIdle(deps.timeoutMs ?? compactionGateTimeoutMs());
3221
+ if (outcome === "waited") deps.log(`[serve] ${deps.what} released after compaction settled`);
3222
+ if (outcome !== "timeout") return true;
3223
+ deps.log(`[serve] ${deps.what} abandoned: a compaction has held the driver past its bound`);
3224
+ deps.sendEvent({ type: "error", message: COMPACTION_GATE_TIMEOUT_MESSAGE, fatal: false });
3225
+ return false;
3226
+ }
3227
+
3126
3228
  // runner/src/driver-swap-gate.ts
3127
3229
  var DriverSwapGate = class {
3128
3230
  /** Resolves when the in-flight driver recreate settles (resolve or reject). */
@@ -3244,6 +3346,14 @@ What still needs to be done or is waiting on user input.
3244
3346
 
3245
3347
  ### Context
3246
3348
  User preferences, constraints, or instructions that must carry forward.`;
3349
+ var RE_COMPACTION_MERGE_INSTRUCTION = `This conversation has already been compacted once. The summary below replaced
3350
+ everything that came before it, and your reply will in turn replace this
3351
+ summary -- anything you leave out is lost for good.
3352
+
3353
+ Your reply must be the COMPLETE merged summary in the section format above:
3354
+ restate everything from the previous summary that still matters, then fold in
3355
+ what has happened since. Do NOT reply with a delta, a diff, a list of changes,
3356
+ or a note that the previous summary still applies.`;
3247
3357
  function buildCompactionPrompt(opts) {
3248
3358
  const base = opts.agentPrompt ?? DEFAULT_COMPACTION_PROMPT;
3249
3359
  const parts = [base];
@@ -3256,6 +3366,19 @@ function buildCompactionPrompt(opts) {
3256
3366
  ${allDirectives.map((d) => `- ${d}`).join("\n")}`
3257
3367
  );
3258
3368
  }
3369
+ if (opts.previousSummary) {
3370
+ parts.push(
3371
+ `
3372
+
3373
+ ## Previous Summary
3374
+
3375
+ <previous_summary>
3376
+ ${opts.previousSummary}
3377
+ </previous_summary>
3378
+
3379
+ ${RE_COMPACTION_MERGE_INSTRUCTION}`
3380
+ );
3381
+ }
3259
3382
  if (opts.focus) {
3260
3383
  parts.push(`
3261
3384
 
@@ -3287,6 +3410,31 @@ var TokenThresholdStrategy = class {
3287
3410
  var MIN_SUMMARY_CHARS = 200;
3288
3411
  var MAX_COMPRESSION_RATIO = 0.9;
3289
3412
  var DEFAULT_CONTEXT_WINDOW = 2e5;
3413
+ var ONE_MILLION_CONTEXT_WINDOW = 1e6;
3414
+ var ONE_MILLION_ALIAS_PATTERN = /\[1m\]|-1m$/i;
3415
+ var DEFAULT_COMPACTION_TIMEOUT_MS = 10 * 6e4;
3416
+ function compactionTimeoutMs() {
3417
+ const raw = Number(process.env.SKAILE_COMPACTION_TIMEOUT_MS);
3418
+ return Number.isFinite(raw) && raw > 0 ? raw : DEFAULT_COMPACTION_TIMEOUT_MS;
3419
+ }
3420
+ var RETRYABLE_ERROR_CODES = /* @__PURE__ */ new Set(["timeout", "unknown"]);
3421
+ var RETRY_FOCUS_DIRECTIVE = `The previous attempt to write this summary did not finish in time, most likely
3422
+ because it ran long. Be economical: compress aggressively, and drop low-value
3423
+ detail rather than exceeding a few hundred words.
3424
+
3425
+ Cover exactly the same ground as before -- if a previous summary appears above,
3426
+ still restate everything in it that still matters. The saving comes from
3427
+ tighter prose, never from leaving material out.`;
3428
+ var ABORT_SETTLE_GRACE_MS = 5e3;
3429
+ var MAX_CARRIED_SUMMARY_CHARS = 24e3;
3430
+ var MAX_FAILURE_DIAGNOSTIC_CHARS = 4e3;
3431
+ function truncateTo(text, max) {
3432
+ if (text.length <= max) return text;
3433
+ return `${text.slice(0, max)}... [truncated]`;
3434
+ }
3435
+ function truncateDiagnostic(text) {
3436
+ return truncateTo(text, MAX_FAILURE_DIAGNOSTIC_CHARS);
3437
+ }
3290
3438
  var RATE_LIMIT_TEXT_MAX_CHARS = 1500;
3291
3439
  var RATE_LIMIT_TEXT_PATTERNS = [
3292
3440
  /you('?| ha)ve hit your (rate |usage )?limit/i,
@@ -3303,6 +3451,12 @@ function contextTokens(usage) {
3303
3451
  const cacheCreation = usage?.cacheCreationTokens ?? 0;
3304
3452
  return Math.max(input, cacheRead) + cacheCreation;
3305
3453
  }
3454
+ function resolveContextWindow(driverWindow, model) {
3455
+ if (model && ONE_MILLION_ALIAS_PATTERN.test(model)) {
3456
+ return Math.max(driverWindow ?? 0, ONE_MILLION_CONTEXT_WINDOW);
3457
+ }
3458
+ return driverWindow || DEFAULT_CONTEXT_WINDOW;
3459
+ }
3306
3460
  function detectRateLimitInSummary(text) {
3307
3461
  if (text.length > RATE_LIMIT_TEXT_MAX_CHARS) return false;
3308
3462
  return RATE_LIMIT_TEXT_PATTERNS.some((p) => p.test(text));
@@ -3322,8 +3476,18 @@ var CompactionOrchestrator = class {
3322
3476
  config;
3323
3477
  opts;
3324
3478
  _isCompacting = false;
3479
+ /** Whether {@link CompactionOrchestratorOptions.onCompactionStart} has fired for the current call. */
3480
+ _startFired = false;
3481
+ /** Whether {@link CompactionOrchestratorOptions.onCompactionEnd} has fired for the current call. */
3482
+ _endFired = false;
3325
3483
  lastCompactionSeq = null;
3326
3484
  lastCompactionTime = null;
3485
+ /**
3486
+ * Summary of the last successful compaction, carried into the next one.
3487
+ * Per-instance, in step with `lastCompactionSeq`: a driver rebuild resets
3488
+ * both, so the first compaction after one is not treated as a re-compaction.
3489
+ */
3490
+ lastSummary = null;
3327
3491
  currentSeq = 0;
3328
3492
  messagesSinceCompaction = 0;
3329
3493
  constructor(opts) {
@@ -3353,7 +3517,10 @@ var CompactionOrchestrator = class {
3353
3517
  shouldCompact() {
3354
3518
  if (!this.config.enabled) return false;
3355
3519
  const usage = this.driver.getTokenUsage();
3356
- const contextWindow = this.driver.getContextWindow() || DEFAULT_CONTEXT_WINDOW;
3520
+ const contextWindow = resolveContextWindow(
3521
+ this.driver.getContextWindow(),
3522
+ this.driver.getModel() ?? null
3523
+ );
3357
3524
  const estimatedTokens = this.contextFill(usage);
3358
3525
  if (estimatedTokens <= 0) return false;
3359
3526
  return this.strategy.shouldCompact({
@@ -3367,14 +3534,45 @@ var CompactionOrchestrator = class {
3367
3534
  async compact(trigger, focus) {
3368
3535
  if (this._isCompacting) return null;
3369
3536
  this._isCompacting = true;
3537
+ this._startFired = false;
3538
+ this._endFired = false;
3370
3539
  try {
3371
3540
  return await this._doCompact(trigger, focus);
3372
3541
  } finally {
3373
3542
  this._isCompacting = false;
3543
+ try {
3544
+ this._endCompaction();
3545
+ } catch (err) {
3546
+ this.opts.onLog?.(
3547
+ `[compaction] onCompactionEnd threw while unwinding: ${err instanceof Error ? err.message : String(err)}`
3548
+ );
3549
+ } finally {
3550
+ this.opts.onCompactionSettled?.();
3551
+ }
3374
3552
  }
3375
3553
  }
3376
- async _doCompact(trigger, focus) {
3377
- const tokensBefore = this.contextFill(this.driver.getTokenUsage());
3554
+ /**
3555
+ * Fire {@link CompactionOrchestratorOptions.onCompactionEnd} once per
3556
+ * compaction. The host stops suppressing forwarded events here, so a second
3557
+ * call would emit a stray idle status over the injection turn that follows
3558
+ * it on the success path.
3559
+ */
3560
+ _endCompaction() {
3561
+ if (!this._startFired || this._endFired) return;
3562
+ this._endFired = true;
3563
+ this.opts.onCompactionEnd?.();
3564
+ }
3565
+ /**
3566
+ * One compaction attempt, plus at most one narrowed retry.
3567
+ *
3568
+ * @param retryOf Set only by the retry path. It suppresses a second
3569
+ * `onCompactionStart` (the retry continues the same compaction phase),
3570
+ * guarantees the retry can never itself retry, and carries the first
3571
+ * attempt's context reading forward.
3572
+ */
3573
+ async _doCompact(trigger, focus, retryOf) {
3574
+ const isRetry = retryOf !== void 0;
3575
+ const tokensBefore = retryOf?.tokensBefore ?? this.contextFill(this.driver.getTokenUsage());
3378
3576
  const driverSessionId = this.driver.runtimeSessionId ?? null;
3379
3577
  const driverModel = this.driver.getModel() ?? null;
3380
3578
  const driverProvider = this.driver.driverInfo?.id ?? null;
@@ -3391,25 +3589,40 @@ var CompactionOrchestrator = class {
3391
3589
  modelAtCompaction: driverModel,
3392
3590
  capabilitySignatureAtCompaction
3393
3591
  };
3394
- this.opts.onCompactionStart?.(trigger);
3395
- this.opts.onLog?.(`[compaction] Starting (trigger=${trigger}, tokens=${tokensBefore})`);
3592
+ if (!isRetry) {
3593
+ this._startFired = true;
3594
+ this.opts.onCompactionStart?.(trigger);
3595
+ }
3596
+ this.opts.onLog?.(
3597
+ `[compaction] ${isRetry ? "Retrying" : "Starting"} (trigger=${trigger}, tokens=${tokensBefore})`
3598
+ );
3396
3599
  const prompt = buildCompactionPrompt({
3397
3600
  agentPrompt: this.opts.agentCompactionPrompt,
3398
3601
  skillDirectives: this.opts.skillDirectives,
3399
3602
  mcpDirectives: this.opts.mcpDirectives,
3400
- focus
3603
+ focus,
3604
+ // A re-compaction's reply replaces the previous summary, so the model has
3605
+ // to see it — otherwise it answers with a delta that fails validation.
3606
+ previousSummary: coversFromSeq > 0 ? this._carriedSummary() : void 0
3401
3607
  });
3402
- const { summaryText, promptError, elapsedMs } = await this._runCompactionPrompt(prompt);
3608
+ const { summaryText, promptError, elapsedMs, timedOut } = await this._runCompactionPrompt(prompt);
3403
3609
  const attempt = { ...attemptBase, elapsedMs };
3404
3610
  if (promptError !== null) {
3405
- this._emitThrownFailure(attempt, promptError);
3611
+ const errorCode = this._emitThrownFailure(attempt, promptError, timedOut);
3612
+ this._beginCooldown();
3613
+ if (this._shouldRetry(trigger, errorCode, focus, isRetry)) {
3614
+ return await this._doCompact(trigger, RETRY_FOCUS_DIRECTIVE, { tokensBefore });
3615
+ }
3616
+ this._endCompaction();
3406
3617
  return null;
3407
3618
  }
3408
3619
  const actualUsage = this.driver.getTokenUsage();
3409
3620
  const tokensAfter = actualUsage?.outputTokens ?? Math.ceil(summaryText.length / 4);
3410
3621
  const validation = validateCompactionOutput(summaryText, tokensBefore, tokensAfter);
3411
3622
  if (!validation.ok) {
3412
- this._emitValidationFailure(attempt, validation.errorCode, tokensAfter);
3623
+ this._emitValidationFailure(attempt, validation.errorCode, tokensAfter, summaryText);
3624
+ this._beginCooldown();
3625
+ this._endCompaction();
3413
3626
  return null;
3414
3627
  }
3415
3628
  return await this._completeCompaction(
@@ -3421,6 +3634,40 @@ var CompactionOrchestrator = class {
3421
3634
  coversFromSeq
3422
3635
  );
3423
3636
  }
3637
+ /**
3638
+ * The previous summary as it goes into a re-compaction prompt, capped so the
3639
+ * prompt cannot grow without bound. Truncating loses context the model is
3640
+ * being asked to restate, so it is logged when it happens.
3641
+ */
3642
+ _carriedSummary() {
3643
+ const summary = this.lastSummary;
3644
+ if (!summary) return void 0;
3645
+ if (summary.length <= MAX_CARRIED_SUMMARY_CHARS) return summary;
3646
+ this.opts.onLog?.(
3647
+ `[compaction] Previous summary truncated to ${MAX_CARRIED_SUMMARY_CHARS} of ${summary.length} chars`
3648
+ );
3649
+ return truncateTo(summary, MAX_CARRIED_SUMMARY_CHARS);
3650
+ }
3651
+ /**
3652
+ * Start the anti-thrash cooldown after a failed attempt.
3653
+ *
3654
+ * `TokenThresholdStrategy` skips its cooldown gate entirely while
3655
+ * `lastCompactionTime` is null, and only success used to set it — so any
3656
+ * failing session re-ran a full-context compaction on every single turn.
3657
+ */
3658
+ _beginCooldown() {
3659
+ this.lastCompactionTime = Date.now();
3660
+ }
3661
+ /**
3662
+ * Whether a failed attempt earns the one narrowed retry.
3663
+ *
3664
+ * Only automatic (threshold) compactions retry: a `manual` or `hibernate`
3665
+ * caller owns the decision to try again, and a caller-supplied `focus` would
3666
+ * be discarded by the retry's own directive.
3667
+ */
3668
+ _shouldRetry(trigger, errorCode, focus, isRetry) {
3669
+ return !isRetry && trigger === "threshold" && focus === void 0 && RETRYABLE_ERROR_CODES.has(errorCode);
3670
+ }
3424
3671
  /**
3425
3672
  * Drive the compaction `prompt()` call, capturing summary text from the
3426
3673
  * driver's raw bridge-level events and any thrown error. The listener is
@@ -3429,6 +3676,10 @@ var CompactionOrchestrator = class {
3429
3676
  * `elapsedMs` is the wall-clock cost of the `prompt()` call. It is the only
3430
3677
  * thing that separates a genuinely empty model response from a turn that was
3431
3678
  * settled by something other than the model, which resolves in milliseconds.
3679
+ *
3680
+ * The call is bounded by {@link compactionTimeoutMs}; on expiry the turn is
3681
+ * aborted and `timedOut` is returned so the caller can record a classified
3682
+ * `timeout` rather than wait out the driver's much longer stall watchdog.
3432
3683
  */
3433
3684
  async _runCompactionPrompt(prompt) {
3434
3685
  let summaryText = "";
@@ -3464,58 +3715,103 @@ var CompactionOrchestrator = class {
3464
3715
  }
3465
3716
  };
3466
3717
  let promptError = null;
3718
+ let timedOut = false;
3719
+ let timer;
3467
3720
  const startedAt = Date.now();
3721
+ const timeoutMs = compactionTimeoutMs();
3468
3722
  try {
3469
3723
  this.driver.on("agent-event", captureHandler);
3470
- await this.driver.prompt(prompt);
3724
+ const running = this.driver.prompt(prompt);
3725
+ const expiry = new Promise((resolve4) => {
3726
+ timer = setTimeout(() => resolve4("expired"), timeoutMs);
3727
+ });
3728
+ const outcome = await Promise.race([running.then(() => "settled"), expiry]);
3729
+ if (outcome === "expired") {
3730
+ timedOut = true;
3731
+ const settled = running.then(
3732
+ () => void 0,
3733
+ () => void 0
3734
+ );
3735
+ promptError = new Error(
3736
+ `Compaction prompt did not complete within ${timeoutMs}ms; the turn was aborted.`
3737
+ );
3738
+ const aborted = this.driver.abort().catch((abortErr) => {
3739
+ this.opts.onLog?.(`[compaction] abort after timeout failed: ${String(abortErr)}`);
3740
+ });
3741
+ let settleTimer;
3742
+ const graceExpired = new Promise((resolve4) => {
3743
+ settleTimer = setTimeout(resolve4, ABORT_SETTLE_GRACE_MS);
3744
+ settleTimer.unref?.();
3745
+ });
3746
+ await Promise.race([Promise.all([aborted, settled]), graceExpired]);
3747
+ clearTimeout(settleTimer);
3748
+ }
3471
3749
  } catch (err) {
3472
3750
  promptError = err;
3473
3751
  } finally {
3752
+ clearTimeout(timer);
3474
3753
  this.driver.removeListener("agent-event", captureHandler);
3475
3754
  }
3476
- return { summaryText, promptError, elapsedMs: Date.now() - startedAt };
3755
+ return { summaryText, promptError, elapsedMs: Date.now() - startedAt, timedOut };
3477
3756
  }
3478
3757
  /**
3479
3758
  * Record a `failed` attempt for a thrown `prompt()` error: classify the
3480
- * error, map it to an error code, log, emit the attempt event, and signal
3481
- * compaction end.
3759
+ * error, map it to an error code, log the raw text, and emit the attempt
3760
+ * event. Signalling compaction end is the caller's job — a retry continues
3761
+ * the same phase.
3762
+ *
3763
+ * @param timedOut Set when the bound in {@link _runCompactionPrompt} expired.
3764
+ * The synthetic message carries none of the classifier's keywords, so
3765
+ * round-tripping it would report the opaque `unknown` this bound replaces.
3766
+ * @returns The error code recorded, so the caller can decide about a retry.
3482
3767
  */
3483
- _emitThrownFailure(attemptBase, promptError) {
3484
- const classified = classifyClaudeSdkError(
3485
- promptError instanceof Error ? promptError.message : String(promptError)
3486
- );
3487
- const errorCode = classified.category === "rate_limit" ? "rate_limit" : classified.category === "auth" ? "auth" : classified.category === "network" ? "timeout" : classified.category === "validation" ? "parse_error" : classified.category;
3768
+ _emitThrownFailure(attemptBase, promptError, timedOut = false) {
3769
+ const rawMessage = promptError instanceof Error ? promptError.message : String(promptError);
3770
+ const classified = classifyClaudeSdkError(rawMessage);
3771
+ const classifiedCode = classified.category === "rate_limit" ? "rate_limit" : classified.category === "auth" ? "auth" : classified.category === "network" ? "timeout" : classified.category === "validation" ? "parse_error" : classified.category;
3772
+ const errorCode = timedOut ? "timeout" : classifiedCode;
3773
+ const diagnostic = truncateDiagnostic(classified.message);
3488
3774
  this.opts.onLog?.(`[compaction] Failed: ${classified.message} (${errorCode})`);
3489
3775
  this.opts.onCompactionAttempt?.({
3776
+ // Spread first: `status`/`validatedAt` below are load-bearing (see the
3777
+ // summary comment) and must not be override-able from the base object.
3778
+ ...attemptBase,
3490
3779
  type: "compaction_attempt",
3491
3780
  status: "failed",
3492
3781
  errorCode,
3493
- summary: "",
3782
+ errorMessage: diagnostic,
3783
+ // Mirrored so the platform's existing `summary` column explains the
3784
+ // failure with no schema change. Safe: a failed attempt is never
3785
+ // restored — restoration needs `success` plus a `validatedAt` stamp.
3786
+ summary: diagnostic,
3494
3787
  outputTokens: 0,
3495
- validatedAt: null,
3496
- ...attemptBase
3788
+ validatedAt: null
3497
3789
  });
3498
- this.opts.onCompactionEnd?.();
3790
+ return errorCode;
3499
3791
  }
3500
3792
  /**
3501
3793
  * Record a `failed` attempt for output that passed `prompt()` but failed
3502
- * structural validation. Logs, emits the attempt event, and signals
3503
- * compaction end.
3794
+ * structural validation. The rejected text is persisted so the next
3795
+ * occurrence is diagnosable from the row alone; signalling compaction end is
3796
+ * the caller's job.
3504
3797
  */
3505
- _emitValidationFailure(attemptBase, errorCode, tokensAfter) {
3798
+ _emitValidationFailure(attemptBase, errorCode, tokensAfter, rejectedText) {
3506
3799
  this.opts.onLog?.(
3507
3800
  `[compaction] Output failed validation (${errorCode}); recording failed attempt`
3508
3801
  );
3509
3802
  this.opts.onCompactionAttempt?.({
3803
+ // Spread first — see _emitThrownFailure.
3804
+ ...attemptBase,
3510
3805
  type: "compaction_attempt",
3511
3806
  status: "failed",
3512
3807
  errorCode,
3513
- summary: "",
3808
+ errorMessage: `Compaction output rejected by validation (${errorCode}): ${rejectedText.length} chars, ${tokensAfter} output tokens`,
3809
+ // The rejected text itself, not a summary — see _emitThrownFailure on
3810
+ // why persisting it here cannot leak into a restored session.
3811
+ summary: truncateDiagnostic(rejectedText),
3514
3812
  outputTokens: tokensAfter,
3515
- validatedAt: null,
3516
- ...attemptBase
3813
+ validatedAt: null
3517
3814
  });
3518
- this.opts.onCompactionEnd?.();
3519
3815
  }
3520
3816
  /**
3521
3817
  * Success path: emit the attempt + snapshot events, update internal state,
@@ -3533,19 +3829,20 @@ var CompactionOrchestrator = class {
3533
3829
  tokensAfter
3534
3830
  };
3535
3831
  this.opts.onCompactionAttempt?.({
3832
+ ...attemptBase,
3536
3833
  type: "compaction_attempt",
3537
3834
  status: "success",
3538
3835
  errorCode: null,
3539
3836
  summary: summaryText,
3540
3837
  outputTokens: tokensAfter,
3541
- validatedAt,
3542
- ...attemptBase
3838
+ validatedAt
3543
3839
  });
3544
3840
  this.opts.onSnapshot?.(snapshot);
3545
3841
  this.lastCompactionSeq = this.currentSeq + 1;
3546
3842
  this.lastCompactionTime = Date.now();
3843
+ this.lastSummary = summaryText;
3547
3844
  this.messagesSinceCompaction = 0;
3548
- this.opts.onCompactionEnd?.();
3845
+ this._endCompaction();
3549
3846
  if (trigger !== "hibernate") {
3550
3847
  this.opts.onLog?.("[compaction] Resetting driver session");
3551
3848
  await this.driver.resetSession();
@@ -5571,6 +5868,7 @@ async function startAgentServer(opts) {
5571
5868
  let driverStarted = false;
5572
5869
  let driverStartInFlight = null;
5573
5870
  const driverSwapGate = new DriverSwapGate();
5871
+ const compactionGate = new CompactionGate();
5574
5872
  let driverRestartChain = Promise.resolve();
5575
5873
  let driver = secretsMode === "env" ? agentSession.driver : void 0;
5576
5874
  let secretsProvisioned = secretsMode === "env";
@@ -5595,15 +5893,19 @@ async function startAgentServer(opts) {
5595
5893
  let eventSeq = 0;
5596
5894
  let lastEmittedRuntimeSession;
5597
5895
  let compacting = false;
5896
+ let suppressedBy = null;
5598
5897
  let compactionOrchestrator = null;
5599
5898
  const compactionConfig = wsConfig.compaction;
5600
5899
  function rebuildCompactionOrchestrator() {
5601
5900
  if (!driver) return;
5901
+ let gateHold = null;
5602
5902
  compactionOrchestrator = new CompactionOrchestrator({
5603
5903
  driver,
5604
5904
  config: compactionConfig,
5605
5905
  onCompactionStart: (trigger) => {
5606
5906
  compacting = true;
5907
+ gateHold = compactionGate.begin();
5908
+ suppressedBy = gateHold;
5607
5909
  sendEvent({ type: "status", phase: "compacting" });
5608
5910
  log(`[serve] compaction started (trigger=${trigger})`);
5609
5911
  },
@@ -5614,9 +5916,15 @@ async function startAgentServer(opts) {
5614
5916
  sendEvent(event);
5615
5917
  },
5616
5918
  onCompactionEnd: () => {
5919
+ if (suppressedBy !== gateHold) return;
5617
5920
  compacting = false;
5921
+ suppressedBy = null;
5618
5922
  sendEvent({ type: "status", phase: "idle" });
5619
5923
  },
5924
+ onCompactionSettled: () => {
5925
+ compactionGate.settle(gateHold);
5926
+ gateHold = null;
5927
+ },
5620
5928
  onLog: (line) => log(`[serve] ${line}`),
5621
5929
  // Phase 3: stamp every CompactionAttemptEvent with the registry's
5622
5930
  // current signature so wake-time tier-1 selection has the input it
@@ -6421,6 +6729,7 @@ async function startAgentServer(opts) {
6421
6729
  rejectSessionReady(err instanceof Error ? err : new Error(errMsg));
6422
6730
  });
6423
6731
  }
6732
+ const awaitCompactionIdle = (what) => guardTurnAgainstCompaction({ gate: compactionGate, what, log, sendEvent });
6424
6733
  async function handlePrompt(cmd) {
6425
6734
  if (cmd.messageId) sendEvent({ type: "prompt_ack", messageId: cmd.messageId });
6426
6735
  if (!await awaitSessionReadyOrFail()) return;
@@ -6435,6 +6744,7 @@ async function startAgentServer(opts) {
6435
6744
  }
6436
6745
  log(`[serve] prompt: ${cmd.prompt.slice(0, 80)}...`);
6437
6746
  await driverSwapGate.awaitSwap();
6747
+ if (!await awaitCompactionIdle("prompt")) return;
6438
6748
  await restageMaterializedSkills(true);
6439
6749
  const activeFlow = getOnlyActiveFlow();
6440
6750
  if (activeFlow) {
@@ -6487,6 +6797,7 @@ async function startAgentServer(opts) {
6487
6797
  log(`[serve] reply: ${cmd.answer.slice(0, 80)}...`);
6488
6798
  await driverSwapGate.awaitSwap();
6489
6799
  if (driver.hasPendingQuestion() && driver.answerQuestion(cmd.answer, cmd.question)) return;
6800
+ if (!await awaitCompactionIdle("reply")) return;
6490
6801
  const endTurn = driverSwapGate.beginTurn();
6491
6802
  try {
6492
6803
  await driver.prompt(cmd.answer);
@@ -7028,5 +7339,5 @@ function touchSession(state) {
7028
7339
  }
7029
7340
 
7030
7341
  export { CLAUDE_CODE_CREDENTIALS_KEY, COMPILE_MANIFEST_FILENAME, CapabilityRegistry, DEFAULT_CAPABILITY_CALL_TIMEOUT_MS, DEFAULT_COALESCE_MS, DEFAULT_FLOW_MUTATE_CONNECTOR_WAIT_MS, MOUNT_LIST_MAX_DEPTH, MOUNT_LIST_MAX_ENTRIES, MOUNT_READ_DEADLINE_FACTOR, MOUNT_TREE_OP_DEADLINE_FACTOR, MarkdownStreamer, PreInitRingSink, agentDefinitionExists, bootstrapCapabilityRegistry, bootstrapRunnerLogStore, buildAgentResources, buildClientCapabilityHandler, buildConnectorTokenMediator, buildContextSection, buildEnvironmentSection, buildResourcesAvailablePayload, buildStoreSnapshotReplay, builtinCapabilities, capabilityLogInstance, clearPreInitRingSink, clearSession, compileComposition, computeCapabilitySignature, connectorRefreshKind, createAgentSession, createFlowConnectorReadiness, createSessionStimulusBus, defineCapability, deleteSession, dispatchRunnerCapabilityInvocation, emitSystemPromptComposed, ensureClaudeSettingsDisablesConnectors, ensureGitConfigInclude, executeConnectorMutate, extractClaudeAiOauthExpiresAt, getPreInitRingSink, handleMountResourceRequest, handleResourceRequest, hasNonTerminalFlow, hostOpResumeStimulus, installPreInitRingSink, isTerminalFlowStatus, listSessions, loadAgentManifest, loadCompileManifest, loadCompileManifestFromDir, loadSession, loadSessionById, mcpAuthSecretKey, newSession, nulTailPaddingLength, pickRepointedManager, planFlowMutate, reconcilePendingDecisions, registerActiveFlow, registerCompositionCapabilities, rejectCapabilityOnApprovalDeny, resetRunnerLogStore, resolveAgentComposition, resolveAgentMixins, resolveBinding, resolveCapabilityCallTimeoutMs, resolveCapabilityResult, resolveComposition, resolveFlowMutateConnectorWaitMs, resolveMixin, runAgentChat, saveSession, setCurrentSession, setUpFlowConnector, startAgentServer, stimulusFromMetas, touchSession, validateInputResponse, writeClaudeCodeCredentialsFile };
7031
- //# sourceMappingURL=chunk-YIPVMBCG.js.map
7032
- //# sourceMappingURL=chunk-YIPVMBCG.js.map
7342
+ //# sourceMappingURL=chunk-ZPCHTTD7.js.map
7343
+ //# sourceMappingURL=chunk-ZPCHTTD7.js.map