@code-yeongyu/senpi 2026.10.16-2 → 2026.10.16-4

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 (154) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/bundle/chunks/{chunk-QQUHMCOM.js → chunk-2NO5ZOYE.js} +6 -6
  3. package/dist/bundle/chunks/chunk-74YFRVGQ.js +2 -0
  4. package/dist/bundle/chunks/{chunk-4R4PRTF5.js → chunk-CJRE5LLR.js} +1 -1
  5. package/dist/bundle/chunks/{chunk-Q7RD3NOC.js → chunk-GY5O7NIQ.js} +1 -1
  6. package/dist/bundle/chunks/{chunk-KRAABTZY.js → chunk-JD4CV3GH.js} +2 -2
  7. package/dist/bundle/chunks/chunk-K2EVQGGT.js +31 -0
  8. package/dist/bundle/chunks/{chunk-SSEGIXRL.js → chunk-KUQ3K33L.js} +1 -1
  9. package/dist/bundle/chunks/{chunk-V5JFWWGJ.js → chunk-KWC3MCLP.js} +1 -1
  10. package/dist/bundle/chunks/chunk-MLL7AGVM.js +3 -0
  11. package/dist/bundle/chunks/chunk-PAJKEG2L.js +8 -0
  12. package/dist/bundle/chunks/{chunk-DPRV3FR7.js → chunk-PWHAOWNH.js} +1 -1
  13. package/dist/bundle/chunks/chunk-WL4Y6XN6.js +18 -0
  14. package/dist/bundle/chunks/{chunk-VY43SSYL.js → chunk-XCRGEMY2.js} +144 -134
  15. package/dist/bundle/chunks/{cli-main-JDL52ADK.js → cli-main-I6JF2GPS.js} +1 -1
  16. package/dist/bundle/chunks/google-generative-ai-AI6PAUPA.js +2 -0
  17. package/dist/bundle/chunks/google-vertex-USNRIFF7.js +2 -0
  18. package/dist/bundle/chunks/google-vertex-adc-JZURL4XM.js +2 -0
  19. package/dist/bundle/chunks/{help-fast-path-SVCMHCYG.js → help-fast-path-APSHUDY6.js} +1 -1
  20. package/dist/bundle/chunks/{host-session-command-46PTZGIB.js → host-session-command-BUJUJHDA.js} +1 -1
  21. package/dist/bundle/chunks/{interactive-mode-7IM7YJYH.js → interactive-mode-4Q3TIXOZ.js} +1 -1
  22. package/dist/bundle/chunks/{multi-session-host-3MEEUMYU.js → multi-session-host-XEZB2XVR.js} +6 -5
  23. package/dist/bundle/chunks/{openai-codex-responses-T26WW2RE.js → openai-codex-responses-NV27Q6VV.js} +1 -1
  24. package/dist/bundle/chunks/{package-manager-cli-UCHXVBQ2.js → package-manager-cli-YWZHOZOY.js} +1 -1
  25. package/dist/bundle/chunks/{rotation-stream-QX7YSKDU.js → rotation-stream-HY3JPQCX.js} +1 -1
  26. package/dist/bundle/chunks/{rpc-mode-D4BJKWFL.js → rpc-mode-SLWXVXXE.js} +1 -1
  27. package/dist/bundle/chunks/{schedule-command-2RHSWBQO.js → schedule-command-JRWZGEDU.js} +1 -1
  28. package/dist/bundle/chunks/{session-control-endpoint-NBSRSAJS.js → session-control-endpoint-E2ZSEWXW.js} +1 -1
  29. package/dist/bundle/chunks/session-picker-5IZY7E3G.js +2 -0
  30. package/dist/bundle/chunks/session-worker.js +266 -255
  31. package/dist/bundle/cli.js +2 -2
  32. package/dist/bundle/index.js +1 -1
  33. package/dist/bundle/rpc-entry.js +1 -1
  34. package/dist/bundle/runtime-manifest.json +1 -1
  35. package/dist/core/agent-session-runtime.d.ts +15 -0
  36. package/dist/core/agent-session-runtime.js +53 -2
  37. package/dist/core/agent-session.d.ts +28 -0
  38. package/dist/core/agent-session.js +88 -3
  39. package/dist/core/context-files.d.ts +21 -0
  40. package/dist/core/context-files.js +175 -0
  41. package/dist/core/diagnostics.d.ts +1 -0
  42. package/dist/core/dynamic-prompt/intent-gate.d.ts +3 -0
  43. package/dist/core/dynamic-prompt/intent-gate.js +4 -3
  44. package/dist/core/dynamic-prompt/policies.d.ts +4 -1
  45. package/dist/core/dynamic-prompt/policies.js +3 -2
  46. package/dist/core/dynamic-prompt/working-task.d.ts +4 -1
  47. package/dist/core/dynamic-prompt/working-task.js +3 -2
  48. package/dist/core/dynamic-prompt/workstation.d.ts +4 -3
  49. package/dist/core/dynamic-prompt/workstation.js +1 -0
  50. package/dist/core/extensions/builtin/anthropic-subscription/account-command.js +26 -3
  51. package/dist/core/extensions/builtin/anthropic-subscription/account-management.d.ts +5 -0
  52. package/dist/core/extensions/builtin/anthropic-subscription/account-management.js +40 -0
  53. package/dist/core/extensions/builtin/anthropic-subscription/accounts.d.ts +3 -0
  54. package/dist/core/extensions/builtin/anthropic-subscription/accounts.js +1 -1
  55. package/dist/core/extensions/builtin/anthropic-subscription/affinity.js +1 -1
  56. package/dist/core/extensions/builtin/anthropic-subscription/auth-lane.js +1 -0
  57. package/dist/core/extensions/builtin/anthropic-subscription/failover.d.ts +10 -5
  58. package/dist/core/extensions/builtin/anthropic-subscription/failover.js +158 -17
  59. package/dist/core/extensions/builtin/anthropic-subscription/usage-limit-block.d.ts +13 -0
  60. package/dist/core/extensions/builtin/anthropic-subscription/usage-limit-block.js +89 -0
  61. package/dist/core/extensions/builtin/goal/index.js +18 -8
  62. package/dist/core/extensions/builtin/goal/monitor-continuation.js +7 -0
  63. package/dist/core/extensions/builtin/goal/reload-reengagement.js +5 -0
  64. package/dist/core/extensions/builtin/index.js +3 -0
  65. package/dist/core/extensions/builtin/permission-system/always-scope.d.ts +34 -0
  66. package/dist/core/extensions/builtin/permission-system/always-scope.js +77 -0
  67. package/dist/core/extensions/builtin/permission-system/index.js +3 -1
  68. package/dist/core/extensions/builtin/permission-system/parsers.d.ts +1 -1
  69. package/dist/core/extensions/builtin/permission-system/parsers.js +24 -3
  70. package/dist/core/extensions/builtin/permission-system/prompt.js +5 -1
  71. package/dist/core/extensions/builtin/permission-system/service.js +1 -1
  72. package/dist/core/extensions/builtin/permission-system/types.d.ts +7 -0
  73. package/dist/core/extensions/builtin/prompt-preset/contract.js +6 -1
  74. package/dist/core/extensions/builtin/prompt-preset/gemini-4-argon.d.ts +7 -0
  75. package/dist/core/extensions/builtin/prompt-preset/gemini-4-argon.js +83 -0
  76. package/dist/core/extensions/builtin/prompt-preset/matchers.d.ts +2 -0
  77. package/dist/core/extensions/builtin/prompt-preset/matchers.js +12 -0
  78. package/dist/core/extensions/builtin/prompt-preset/presets.js +3 -0
  79. package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -1
  80. package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -0
  81. package/dist/core/extensions/builtin/vertex-model-garden/auth.d.ts +16 -0
  82. package/dist/core/extensions/builtin/vertex-model-garden/auth.js +19 -0
  83. package/dist/core/extensions/builtin/vertex-model-garden/index.d.ts +6 -0
  84. package/dist/core/extensions/builtin/vertex-model-garden/index.js +27 -0
  85. package/dist/core/extensions/builtin/vertex-model-garden/models.d.ts +3 -0
  86. package/dist/core/extensions/builtin/vertex-model-garden/models.js +34 -0
  87. package/dist/core/extensions/builtin/vertex-model-garden/transport.d.ts +14 -0
  88. package/dist/core/extensions/builtin/vertex-model-garden/transport.js +35 -0
  89. package/dist/core/extensions/types.d.ts +32 -1
  90. package/dist/core/package-manager.js +4 -32
  91. package/dist/core/project-trust.d.ts +8 -0
  92. package/dist/core/project-trust.js +12 -0
  93. package/dist/core/provider-api-key-auth.d.ts +9 -1
  94. package/dist/core/provider-api-key-auth.js +34 -0
  95. package/dist/core/provider-composer.d.ts +23 -0
  96. package/dist/core/resource-loader.d.ts +5 -7
  97. package/dist/core/resource-loader.js +11 -84
  98. package/dist/core/retry-fallback/circuit-probes.d.ts +1 -0
  99. package/dist/core/retry-fallback/circuit-probes.js +3 -0
  100. package/dist/core/retry-fallback/circuit.d.ts +5 -0
  101. package/dist/core/retry-fallback/circuit.js +27 -4
  102. package/dist/core/retry-fallback/controller.d.ts +9 -4
  103. package/dist/core/retry-fallback/controller.js +21 -4
  104. package/dist/core/settings-manager.d.ts +19 -0
  105. package/dist/core/settings-manager.js +27 -1
  106. package/dist/core/settings-shapes.d.ts +4 -0
  107. package/dist/core/skills-prompt.d.ts +18 -0
  108. package/dist/core/skills-prompt.js +131 -0
  109. package/dist/core/skills.d.ts +1 -9
  110. package/dist/core/skills.js +1 -59
  111. package/dist/core/trust-manager.d.ts +13 -4
  112. package/dist/core/trust-manager.js +52 -21
  113. package/dist/core/turn-deadline.d.ts +92 -0
  114. package/dist/core/turn-deadline.js +179 -0
  115. package/dist/main.js +23 -4
  116. package/dist/modes/interactive/interactive-mode.js +10 -3
  117. package/dist/modes/rpc/abort-grace.d.ts +9 -0
  118. package/dist/modes/rpc/abort-grace.js +11 -0
  119. package/dist/modes/rpc/client-admission-record.d.ts +1 -0
  120. package/dist/modes/rpc/client-admissions.d.ts +6 -3
  121. package/dist/modes/rpc/client-admissions.js +9 -4
  122. package/dist/modes/rpc/connection-handler.js +8 -3
  123. package/dist/modes/rpc/effect-commands.d.ts +1 -3
  124. package/dist/modes/rpc/effect-commands.js +4 -5
  125. package/dist/modes/rpc/effect-outcome-copy.d.ts +4 -4
  126. package/dist/modes/rpc/effect-outcome-copy.js +4 -4
  127. package/dist/modes/rpc/effect-record.d.ts +2 -0
  128. package/dist/modes/rpc/effect-record.js +2 -0
  129. package/dist/modes/rpc/effect-status.js +1 -1
  130. package/dist/modes/rpc/host-handover-parked.d.ts +6 -0
  131. package/dist/modes/rpc/host-handover-parked.js +1 -0
  132. package/dist/modes/rpc/host-handover-reopen.js +11 -2
  133. package/dist/modes/rpc/rpc-client.d.ts +28 -2
  134. package/dist/modes/rpc/rpc-client.js +24 -11
  135. package/dist/modes/rpc/rpc-request-deadline.d.ts +12 -0
  136. package/dist/modes/rpc/rpc-request-deadline.js +15 -0
  137. package/dist/modes/rpc/session-command-router.js +7 -4
  138. package/dist/modes/rpc/session-registry-attach.js +6 -1
  139. package/dist/modes/rpc/session-registry-claims.d.ts +22 -2
  140. package/dist/modes/rpc/session-registry-claims.js +68 -0
  141. package/dist/modes/rpc/session-registry-opening.d.ts +23 -0
  142. package/dist/modes/rpc/session-registry-opening.js +57 -0
  143. package/dist/modes/rpc/session-registry.d.ts +1 -0
  144. package/dist/modes/rpc/session-registry.js +48 -60
  145. package/dist/modes/rpc/session-unload.js +4 -1
  146. package/docs/rpc.md +10 -3
  147. package/docs/settings.md +16 -1
  148. package/package.json +6 -6
  149. package/dist/bundle/chunks/chunk-6MEETY7N.js +0 -8
  150. package/dist/bundle/chunks/chunk-7ASXJ4AD.js +0 -47
  151. package/dist/bundle/chunks/chunk-TEGEPQIA.js +0 -3
  152. package/dist/bundle/chunks/google-generative-ai-YNIOL5FT.js +0 -2
  153. package/dist/bundle/chunks/google-vertex-CF6AYVXL.js +0 -2
  154. package/dist/bundle/chunks/session-picker-6TSBGWOA.js +0 -2
@@ -122,6 +122,7 @@ import { composeFilesystemPolicies } from "./tools/filesystem-policy.js";
122
122
  import { createAllToolDefinitions } from "./tools/index.js";
123
123
  import { createToolDefinitionFromAgentTool } from "./tools/tool-definition-wrapper.js";
124
124
  import { TranscriptWriteFailures } from "./transcript-write-failures.js";
125
+ import { AgentDeadlineTracker, resolveTurnDeadlineSettings, TURN_DEADLINE_ENTRY_TYPE, } from "./turn-deadline.js";
125
126
  import { commandShapedName, findUnknownCommand } from "./unknown-command.js";
126
127
  import { addUsageToTotals, combineUsage, createUsageTotals } from "./usage-totals.js";
127
128
  import { keepsTextBlocksVerbatim, userTextContent } from "./user-text-blocks.js";
@@ -324,6 +325,8 @@ export class AgentSession {
324
325
  this._continueWithQueuedInput = false;
325
326
  /** True while a reload's `session_start` handlers run; a nested reload would retire the runner they run on. */
326
327
  this._sessionStartDispatching = false;
328
+ /** Waiters for the moment no prompt start is pending and no session start is dispatching (applyProjectTrustChange). */
329
+ this._admissionIdleWaiters = [];
327
330
  this._engineTurnStarts = [];
328
331
  this._engineTurnPauseRecorded = false;
329
332
  this._nextInputId = 0;
@@ -659,6 +662,10 @@ export class AgentSession {
659
662
  activeToolNames: this._initialActiveToolNames,
660
663
  includeAllExtensionTools: true,
661
664
  });
665
+ this._deadlineTracker = new AgentDeadlineTracker(this, () => resolveTurnDeadlineSettings({
666
+ turn_inactivity_ms: this.settingsManager.getTurnInactivityMs(),
667
+ turn_max_runtime_ms: this.settingsManager.getTurnMaxRuntimeMs(),
668
+ }));
662
669
  // Last, so a construction that throws never leaves a hold only dispose() could release.
663
670
  this._fallbackCircuitsLease = acquireFallbackCircuits(this._agentDir);
664
671
  }
@@ -1788,6 +1795,7 @@ export class AgentSession {
1788
1795
  }
1789
1796
  }
1790
1797
  this.releaseSettledSessionMemory();
1798
+ this._deadlineTracker.disarm();
1791
1799
  this._emit({ type: "agent_idle" });
1792
1800
  }
1793
1801
  async _promptAgent(messages, deferredTurnClaim, onQueuedInput) {
@@ -1795,6 +1803,8 @@ export class AgentSession {
1795
1803
  if (!this._isAgentRunActive)
1796
1804
  this.externalAdmission.beginTurn();
1797
1805
  this._isAgentRunActive = true;
1806
+ // A new turn admitted: arm the inactivity lease and the hard max-runtime cap.
1807
+ this._deadlineTracker.arm();
1798
1808
  // A new prompt replaces any retry that compaction before it scheduled.
1799
1809
  this._failedResponse = undefined;
1800
1810
  this._recordSelection();
@@ -2188,6 +2198,9 @@ export class AgentSession {
2188
2198
  ? withProviderFailure({
2189
2199
  ...event,
2190
2200
  willRetry: agentEndWillRetry,
2201
+ ...(this._deadlineTracker.current !== undefined
2202
+ ? { aborted: true, abortSource: "system", deadline: this._deadlineTracker.current }
2203
+ : {}),
2191
2204
  ...(abortCause === undefined ? {} : { abortCause }),
2192
2205
  })
2193
2206
  : event.type === "message_update"
@@ -2245,7 +2258,7 @@ export class AgentSession {
2245
2258
  assistantMsg.stopReason !== "aborted" &&
2246
2259
  !isClassifierRefusal(assistantMsg);
2247
2260
  if (succeeded)
2248
- this._retryFallback.probes.accept(`${assistantMsg.provider}/${assistantMsg.model}`);
2261
+ this._retryFallback.noteServed(`${assistantMsg.provider}/${assistantMsg.model}`);
2249
2262
  if (succeeded && assistantMsg.stopReason !== "length") {
2250
2263
  this._overflowRecoveryRungs = 0;
2251
2264
  }
@@ -2755,6 +2768,7 @@ export class AgentSession {
2755
2768
  }
2756
2769
  }
2757
2770
  else if (event.type === "tool_execution_start") {
2771
+ this._deadlineTracker.renew();
2758
2772
  const extensionEvent = {
2759
2773
  type: "tool_execution_start",
2760
2774
  toolCallId: event.toolCallId,
@@ -2764,6 +2778,7 @@ export class AgentSession {
2764
2778
  await this._extensionRunner.emit(extensionEvent);
2765
2779
  }
2766
2780
  else if (event.type === "tool_execution_update") {
2781
+ this._deadlineTracker.renew();
2767
2782
  const extensionEvent = {
2768
2783
  type: "tool_execution_update",
2769
2784
  toolCallId: event.toolCallId,
@@ -2774,6 +2789,7 @@ export class AgentSession {
2774
2789
  await this._extensionRunner.emit(extensionEvent);
2775
2790
  }
2776
2791
  else if (event.type === "tool_execution_end") {
2792
+ this._deadlineTracker.renew();
2777
2793
  const extensionEvent = {
2778
2794
  type: "tool_execution_end",
2779
2795
  toolCallId: event.toolCallId,
@@ -2838,6 +2854,7 @@ export class AgentSession {
2838
2854
  */
2839
2855
  dispose() {
2840
2856
  this._disposed = true;
2857
+ this._deadlineTracker.disarm();
2841
2858
  for (const dispose of this._toolContextDisposers)
2842
2859
  dispose();
2843
2860
  try {
@@ -3008,6 +3025,10 @@ export class AgentSession {
3008
3025
  get isStreaming() {
3009
3026
  return this._isAgentRunActive;
3010
3027
  }
3028
+ /** True while a provider retry is in flight; the deadline lease treats it as turn progress. */
3029
+ get isRetryInFlight() {
3030
+ return this._retryPromise !== undefined;
3031
+ }
3011
3032
  /** Whether the session has no active agent run, retry, or queued continuation. */
3012
3033
  get isIdle() {
3013
3034
  return !this._isAgentRunActive;
@@ -3043,6 +3064,27 @@ export class AgentSession {
3043
3064
  get currentAbortSource() {
3044
3065
  return this._abortProvenance.currentSource;
3045
3066
  }
3067
+ /** The deadline that stopped the current turn, when the lease fired (senpi#3227). */
3068
+ get currentDeadline() {
3069
+ return this._deadlineTracker.current;
3070
+ }
3071
+ /** Adapter for the deadline owner interface: a deadline stop that could not complete. */
3072
+ _warnDeadline(event, detail) {
3073
+ this._sessionLogger.warn(event, { detail });
3074
+ }
3075
+ /** Adapter for the deadline owner interface: end the turn through the normal system abort. */
3076
+ async abortTurnForDeadline() {
3077
+ await this._abortActiveAgentAndRetry("system");
3078
+ }
3079
+ /** Record a deadline stop durably so the goal engine holds off until admitted user input. */
3080
+ _recordDeadlineStop(deadline) {
3081
+ this._emitEntryAppended(this.sessionManager.appendCustomEntry(TURN_DEADLINE_ENTRY_TYPE, {
3082
+ kind: deadline.kind,
3083
+ limitMs: deadline.limitMs,
3084
+ elapsedMs: deadline.elapsedMs,
3085
+ at: Date.now(),
3086
+ }));
3087
+ }
3046
3088
  /**
3047
3089
  * Get the names of currently active tools.
3048
3090
  * Returns the names of tools currently set on the agent. Tools with `search` or `eval` exposure
@@ -3557,8 +3599,10 @@ export class AgentSession {
3557
3599
  submittedByCommand: options?.source === "extension",
3558
3600
  });
3559
3601
  try {
3560
- if (options?.source !== "extension")
3602
+ if (options?.source !== "extension") {
3561
3603
  emitUserActivity(this, "prompt");
3604
+ this._deadlineTracker.clearHold();
3605
+ }
3562
3606
  await this._prompt(text, {
3563
3607
  ...options,
3564
3608
  promptDisposition: (disposition) => {
@@ -3643,6 +3687,7 @@ export class AgentSession {
3643
3687
  return;
3644
3688
  claim.started = started;
3645
3689
  this._promptStartPending = false;
3690
+ this._notifyAdmissionIdle();
3646
3691
  if (!started && claim.joined === true && !this.isStreaming)
3647
3692
  this._drainJoinedInput();
3648
3693
  };
@@ -4209,8 +4254,10 @@ export class AgentSession {
4209
4254
  * `input` handlers, skill/template expansion, then the recovery-ordered enqueue.
4210
4255
  */
4211
4256
  async _queueUserInput(text, images, behavior, options) {
4212
- if (options?.source !== "extension")
4257
+ if (options?.source !== "extension") {
4213
4258
  emitUserActivity(this, "prompt");
4259
+ this._deadlineTracker.clearHold();
4260
+ }
4214
4261
  // Check for extension commands (cannot be queued)
4215
4262
  if (text.startsWith("/")) {
4216
4263
  this._throwIfExtensionCommand(text);
@@ -8058,6 +8105,34 @@ export class AgentSession {
8058
8105
  finishReloadWork();
8059
8106
  }
8060
8107
  }
8108
+ /**
8109
+ * Loads or unloads the project's code after the session's project trust changed (Full access left or
8110
+ * re-entered, senpi#3420): rebuilds the runtime exactly like reload(), but nothing can cancel it. No
8111
+ * `session_before_reload` handler runs (a project extension must not keep itself loaded); a prompt
8112
+ * that is being admitted, or a session start in dispatch, is waited out instead of cancelling, and
8113
+ * prompts admitted after that wait for the rebuilt runtime. A reload already in flight sees the trust
8114
+ * epoch move and loads again before it installs anything.
8115
+ */
8116
+ async applyProjectTrustChange() {
8117
+ while (this._promptStartPending || this._sessionStartDispatching) {
8118
+ await new Promise((resolve) => this._admissionIdleWaiters.push(resolve));
8119
+ }
8120
+ const finishReloadWork = this._sessionWorkBarrier.begin();
8121
+ try {
8122
+ await this._rebuildRuntimeForReload();
8123
+ }
8124
+ finally {
8125
+ finishReloadWork();
8126
+ }
8127
+ }
8128
+ _notifyAdmissionIdle() {
8129
+ if (this._promptStartPending || this._sessionStartDispatching)
8130
+ return;
8131
+ const waiters = this._admissionIdleWaiters;
8132
+ this._admissionIdleWaiters = [];
8133
+ for (const resolve of waiters)
8134
+ resolve();
8135
+ }
8061
8136
  async _rebuildRuntimeForReload(options) {
8062
8137
  resetTimings("reload");
8063
8138
  const oldExtensionRunner = this._extensionRunner;
@@ -8076,6 +8151,7 @@ export class AgentSession {
8076
8151
  });
8077
8152
  time("shutdown", "reload");
8078
8153
  const previousDefaultTools = new Set(this._usesDefaultTools ? (this.settingsManager.getDefaultTools() ?? DEFAULT_TOOL_NAMES) : []);
8154
+ let trustEpoch = this.settingsManager.getTrustEpoch();
8079
8155
  await this.settingsManager.reload();
8080
8156
  this._delegatedCompactionKey = undefined;
8081
8157
  // Activate tools newly added to defaultTools. Removed ones stay active, and tools disabled
@@ -8114,6 +8190,14 @@ export class AgentSession {
8114
8190
  await this._resourceLoader.reload({
8115
8191
  settingsAlreadyReloadedFor: this.settingsManager,
8116
8192
  });
8193
+ // The project trust changed while this rebuild ran (senpi#3420): what it loaded matches the old
8194
+ // trust, so it loads again under the current one. Nothing is installed until a load matches the
8195
+ // trust at install time; the check and the install below run in one synchronous step.
8196
+ while (this.settingsManager.getTrustEpoch() !== trustEpoch) {
8197
+ trustEpoch = this.settingsManager.getTrustEpoch();
8198
+ await this.settingsManager.reload();
8199
+ await this._resourceLoader.reload({ settingsAlreadyReloadedFor: this.settingsManager });
8200
+ }
8117
8201
  time("resources", "reload");
8118
8202
  try {
8119
8203
  this._buildRuntime({
@@ -8167,6 +8251,7 @@ export class AgentSession {
8167
8251
  finally {
8168
8252
  this._sessionStartDispatching = false;
8169
8253
  settleSessionStart();
8254
+ this._notifyAdmissionIdle();
8170
8255
  }
8171
8256
  }
8172
8257
  time("lifecycle", "reload");
@@ -0,0 +1,21 @@
1
+ import type { ResourceDiagnostic } from "./diagnostics.ts";
2
+ export interface ContextFile {
3
+ path: string;
4
+ content: string;
5
+ }
6
+ /** Preserve the public array API while returning diagnostics to the owning mode. */
7
+ export type ContextFiles = ContextFile[] & {
8
+ readonly diagnostics: ResourceDiagnostic[];
9
+ };
10
+ /** Size limits for AGENTS.md / CLAUDE.md context files, which ride along on every model request. */
11
+ export interface ContextFileLimits {
12
+ maxBytesPerFile?: number;
13
+ maxTotalBytes?: number;
14
+ }
15
+ export declare const DEFAULT_CONTEXT_FILE_MAX_BYTES: number;
16
+ export declare const DEFAULT_CONTEXT_FILES_MAX_TOTAL_BYTES: number;
17
+ export declare function loadProjectContextFiles(options: {
18
+ cwd: string;
19
+ agentDir: string;
20
+ } & ContextFileLimits): ContextFiles;
21
+ //# sourceMappingURL=context-files.d.ts.map
@@ -0,0 +1,175 @@
1
+ import { existsSync, readFileSync, statSync } from "node:fs";
2
+ import { basename, dirname, join, sep } from "node:path";
3
+ import chalk from "chalk";
4
+ import { canonicalizePath, resolvePath } from "../utils/paths.js";
5
+ import { stripBom } from "../utils/text.js";
6
+ import { findGitPaths } from "./footer-data-provider.js";
7
+ export const DEFAULT_CONTEXT_FILE_MAX_BYTES = 64 * 1024;
8
+ export const DEFAULT_CONTEXT_FILES_MAX_TOTAL_BYTES = 128 * 1024;
9
+ const MIN_USEFUL_BYTES = 1024;
10
+ function loadContextFileFromDir(dir) {
11
+ const candidates = ["AGENTS.override.md", "AGENTS.md", "AGENTS.MD", "CLAUDE.md", "CLAUDE.MD"];
12
+ for (const filename of candidates) {
13
+ const filePath = join(dir, filename);
14
+ if (existsSync(filePath)) {
15
+ try {
16
+ if (!statSync(filePath).isFile()) {
17
+ continue;
18
+ }
19
+ return {
20
+ path: filePath,
21
+ content: stripBom(readFileSync(filePath, "utf-8")),
22
+ };
23
+ }
24
+ catch (error) {
25
+ console.error(chalk.yellow(`Warning: Could not read ${filePath}: ${error}`));
26
+ }
27
+ }
28
+ }
29
+ return null;
30
+ }
31
+ /**
32
+ * The main repo's context file that a nested linked worktree's own copy shadows: both
33
+ * occupy the same logical repository scope, so loading both applies that context twice. Returns
34
+ * undefined when nothing is shadowed, leaving normal ancestor inheritance alone.
35
+ *
36
+ * Returned canonicalized (realpath), because `git worktree add` writes the `.git`
37
+ * file's `gitdir:` target in realpath form while cwd may still be symlinked
38
+ * (macOS `/tmp` -> `/private/tmp`).
39
+ */
40
+ function findShadowedContextFile(cwd) {
41
+ const gitPaths = findGitPaths(cwd);
42
+ if (!gitPaths)
43
+ return undefined;
44
+ const commonGitDir = canonicalizePath(gitPaths.commonGitDir);
45
+ const worktreeRoot = canonicalizePath(gitPaths.repoDir);
46
+ const mainRepoRoot = dirname(commonGitDir);
47
+ // False for an ordinary repo, where the two are the same dir, and for a sibling
48
+ // worktree (`git worktree add ../feat`), whose main repo is not an ancestor.
49
+ if (!worktreeRoot.startsWith(`${mainRepoRoot}${sep}`))
50
+ return undefined;
51
+ // dirname of the common git dir is the main worktree root only when that dir is
52
+ // itself checked out from the same repo. In a bare layout (`proj/.bare` +
53
+ // `proj/main`) it is just the directory holding `.bare`, which tracks nothing; a
54
+ // submodule's gitdir has no `commondir`, so it lands under `.git/modules`.
55
+ if (canonicalizePath(join(mainRepoRoot, ".git")) !== commonGitDir)
56
+ return undefined;
57
+ const worktreeContextFile = loadContextFileFromDir(worktreeRoot);
58
+ return worktreeContextFile ? join(mainRepoRoot, basename(worktreeContextFile.path)) : undefined;
59
+ }
60
+ function kib(bytes) {
61
+ const value = bytes / 1024;
62
+ return Number.isInteger(value) ? `${value} KiB` : `${value.toFixed(1)} KiB`;
63
+ }
64
+ function utf8Prefix(content, maxBytes) {
65
+ if (maxBytes <= 0)
66
+ return "";
67
+ const bytes = Buffer.from(content, "utf8");
68
+ let end = Math.min(maxBytes, bytes.length);
69
+ while (end > 0 && end < bytes.length && ((bytes[end] ?? 0) & 0xc0) === 0x80)
70
+ end--;
71
+ const newline = bytes.lastIndexOf(10, end - 1);
72
+ if (newline >= maxBytes * 0.75)
73
+ end = newline + 1;
74
+ return bytes.subarray(0, end).toString("utf8");
75
+ }
76
+ function positiveLimit(value, fallback) {
77
+ return typeof value === "number" && Number.isFinite(value) && value > 0 ? Math.floor(value) : fallback;
78
+ }
79
+ /**
80
+ * Bound what the context files add to every request. Files are given budget nearest-first
81
+ * (the cwd's file before its ancestors, the global file last), so the most specific
82
+ * instructions survive; every cut or skipped file is named in a warning.
83
+ */
84
+ function applyContextFileLimits(files, limits) {
85
+ const perFile = positiveLimit(limits.maxBytesPerFile, DEFAULT_CONTEXT_FILE_MAX_BYTES);
86
+ const total = positiveLimit(limits.maxTotalBytes, DEFAULT_CONTEXT_FILES_MAX_TOTAL_BYTES);
87
+ const limitText = `limit ${kib(perFile)} per file, ${kib(total)} total; raise contextFiles.maxBytesPerFile / contextFiles.maxTotalBytes in settings to load more`;
88
+ const kept = [];
89
+ const diagnostics = [];
90
+ let remaining = total;
91
+ for (const file of [...files].reverse()) {
92
+ const size = Buffer.byteLength(file.content, "utf8");
93
+ const allowed = Math.min(perFile, remaining);
94
+ if (size <= allowed) {
95
+ kept.unshift(file);
96
+ remaining -= size;
97
+ continue;
98
+ }
99
+ if (allowed < MIN_USEFUL_BYTES) {
100
+ const reason = perFile < MIN_USEFUL_BYTES ? "the per-file limit is too small" : "nearer context files used the budget";
101
+ diagnostics.push({
102
+ type: "warning",
103
+ category: "size-limit",
104
+ path: file.path,
105
+ message: `Context file (${kib(size)}) was not loaded: ${reason} (${limitText}).`,
106
+ });
107
+ continue;
108
+ }
109
+ const note = `\n\n[This file was truncated from ${kib(size)}.]`;
110
+ const contentBudget = allowed - Buffer.byteLength(note);
111
+ let prefix = utf8Prefix(file.content, contentBudget);
112
+ let content;
113
+ while (true) {
114
+ let openFence;
115
+ for (const match of prefix.matchAll(/^[ \t]*(`{3,}|~{3,})([^\n]*)$/gm)) {
116
+ const marker = match[1];
117
+ if (!marker)
118
+ continue;
119
+ if (openFence === undefined)
120
+ openFence = marker;
121
+ else if (marker[0] === openFence[0] && marker.length >= openFence.length && match[2]?.trim() === "") {
122
+ openFence = undefined;
123
+ }
124
+ }
125
+ const closingFence = openFence ? `${prefix.endsWith("\n") ? "" : "\n"}${openFence}\n` : "";
126
+ content = `${prefix}${closingFence}${note}`;
127
+ if (Buffer.byteLength(content) <= allowed)
128
+ break;
129
+ // A longer closing marker can expose another opener when the prefix shrinks.
130
+ prefix = utf8Prefix(prefix, Math.max(0, contentBudget - Buffer.byteLength(closingFence)));
131
+ }
132
+ const loaded = Buffer.byteLength(prefix, "utf8");
133
+ diagnostics.push({
134
+ type: "warning",
135
+ category: "size-limit",
136
+ path: file.path,
137
+ message: `Context file is ${kib(size)}; only the first ${kib(loaded)} was loaded (${limitText}).`,
138
+ });
139
+ kept.unshift({
140
+ path: file.path,
141
+ content,
142
+ });
143
+ remaining -= Buffer.byteLength(content);
144
+ }
145
+ return Object.assign(kept, { diagnostics });
146
+ }
147
+ export function loadProjectContextFiles(options) {
148
+ const resolvedCwd = resolvePath(options.cwd);
149
+ const resolvedAgentDir = resolvePath(options.agentDir);
150
+ const contextFiles = [];
151
+ const seenPaths = new Set();
152
+ const globalContext = loadContextFileFromDir(resolvedAgentDir);
153
+ if (globalContext) {
154
+ contextFiles.push(globalContext);
155
+ seenPaths.add(globalContext.path);
156
+ }
157
+ const ancestorContextFiles = [];
158
+ const shadowedContextFile = findShadowedContextFile(resolvedCwd);
159
+ let currentDir = resolvedCwd;
160
+ while (true) {
161
+ const contextFile = loadContextFileFromDir(currentDir);
162
+ const isShadowed = shadowedContextFile !== undefined && canonicalizePath(contextFile?.path ?? "") === shadowedContextFile;
163
+ if (contextFile && !isShadowed && !seenPaths.has(contextFile.path)) {
164
+ ancestorContextFiles.unshift(contextFile);
165
+ seenPaths.add(contextFile.path);
166
+ }
167
+ const parentDir = dirname(currentDir);
168
+ if (parentDir === currentDir)
169
+ break;
170
+ currentDir = parentDir;
171
+ }
172
+ contextFiles.push(...ancestorContextFiles);
173
+ return applyContextFileLimits(contextFiles, options);
174
+ }
175
+ //# sourceMappingURL=context-files.js.map
@@ -8,6 +8,7 @@ export interface ResourceCollision {
8
8
  }
9
9
  export interface ResourceDiagnostic {
10
10
  type: "warning" | "error" | "collision";
11
+ category?: "size-limit";
11
12
  message: string;
12
13
  path?: string;
13
14
  collision?: ResourceCollision;
@@ -1,6 +1,9 @@
1
1
  import type { AvailableTool, PromptSurface } from "./types.ts";
2
+ export declare const DEFAULT_ROUTING_LINE_RULE = "The line keeps your reading transparent";
2
3
  export declare function buildIntentGate(config: {
3
4
  tools: AvailableTool[];
4
5
  surface?: PromptSurface;
6
+ /** Terminal-only: the clause before "only the user's explicit request commits you"; presets replace it. */
7
+ routingLineRule?: string;
5
8
  }): string;
6
9
  //# sourceMappingURL=intent-gate.d.ts.map
@@ -6,16 +6,17 @@ function buildKeyTriggers(tools) {
6
6
  }
7
7
  return `\nSpecialized search available this turn: ${triggerTools}. Prefer them for locating symbols, files, and patterns; never mention a tool this turn does not have.\n`;
8
8
  }
9
- const TERMINAL_ROUTING = `Open every turn with one short routing line:
9
+ export const DEFAULT_ROUTING_LINE_RULE = "The line keeps your reading transparent";
10
+ const terminalRouting = (routingLineRule) => `Open every turn with one short routing line:
10
11
 
11
12
  I read this as [intent] - [plan]. I'll stop when [the observable condition that ends this turn].
12
13
 
13
- The line keeps your reading transparent; only the user's explicit request commits you to implementation. Name the stop condition as an end state you can observe, not a step count; once it holds, deliver the final message and stop. Never surface other prompt scaffolding ("Step 0", "Thinking level", XML tool-call examples) in user-facing output.`;
14
+ ${routingLineRule}; only the user's explicit request commits you to implementation. Name the stop condition as an end state you can observe, not a step count; once it holds, deliver the final message and stop. Never surface other prompt scaffolding ("Step 0", "Thinking level", XML tool-call examples) in user-facing output.`;
14
15
  const APP_ROUTING = `Only the user's explicit request commits you to implementation. Before acting, decide the stop condition - an end state you can observe, not a step count; once it holds, deliver the final message and stop. Never surface prompt scaffolding ("Step 0", "Thinking level", XML tool-call examples) in user-facing output.`;
15
16
  export function buildIntentGate(config) {
16
17
  return `## Intent Gate
17
18
 
18
- ${(config.surface ?? "terminal") === "terminal" ? TERMINAL_ROUTING : APP_ROUTING}
19
+ ${(config.surface ?? "terminal") === "terminal" ? terminalRouting(config.routingLineRule ?? DEFAULT_ROUTING_LINE_RULE) : APP_ROUTING}
19
20
  ${buildKeyTriggers(config.tools)}
20
21
  Route by true intent, not surface form:
21
22
  - Information asks (explain, look into, investigate): read the code, report the answer or findings - no edits, no fixes yet.
@@ -1,2 +1,5 @@
1
- export declare function buildPoliciesSection(): string;
1
+ export declare const DEFAULT_ERROR_RULE = "Never silently swallow errors; never shotgun-debug with unrelated edits or blind retries.";
2
+ export declare function buildPoliciesSection(options?: {
3
+ errorRule?: string;
4
+ }): string;
2
5
  //# sourceMappingURL=policies.d.ts.map
@@ -1,11 +1,12 @@
1
- export function buildPoliciesSection() {
1
+ export const DEFAULT_ERROR_RULE = "Never silently swallow errors; never shotgun-debug with unrelated edits or blind retries.";
2
+ export function buildPoliciesSection(options = {}) {
2
3
  return `## Policies
3
4
 
4
5
  ### Hard Blocks
5
6
  - Never create a git commit unless the user explicitly requested it.
6
7
  - Never present unread code or unrun commands as verified fact.
7
8
  - Never suppress type errors, lint warnings, or test failures, and never delete or skip failing tests to go green.
8
- - Never silently swallow errors; never shotgun-debug with unrelated edits or blind retries.
9
+ - ${options.errorRule ?? DEFAULT_ERROR_RULE}
9
10
  - Never present partial work as complete or deliver a stub, placeholder, or no-op as the feature; say what is done, what is not, and why you stopped.`;
10
11
  }
11
12
  //# sourceMappingURL=policies.js.map
@@ -1,2 +1,5 @@
1
- export declare function buildWorkingTaskSection(): string;
1
+ export declare const DEFAULT_WORKING_TASK_OPENING = "Fire independent tool calls as one parallel wave - reads, searches, listings, diagnostics - and bias toward breadth when context is thin: pull in anything even loosely relevant now instead of serially later. Wasted reads cost almost nothing; stale assumptions cost the turn. Edits and result-dependent calls go one at a time, each compared with the state you meant to produce; when the result must be seen rather than read, render after each change and look before the next. Never fill missing parameters with placeholders.";
2
+ export declare function buildWorkingTaskSection(options?: {
3
+ opening?: string;
4
+ }): string;
2
5
  //# sourceMappingURL=working-task.d.ts.map
@@ -1,7 +1,8 @@
1
- export function buildWorkingTaskSection() {
1
+ export const DEFAULT_WORKING_TASK_OPENING = "Fire independent tool calls as one parallel wave - reads, searches, listings, diagnostics - and bias toward breadth when context is thin: pull in anything even loosely relevant now instead of serially later. Wasted reads cost almost nothing; stale assumptions cost the turn. Edits and result-dependent calls go one at a time, each compared with the state you meant to produce; when the result must be seen rather than read, render after each change and look before the next. Never fill missing parameters with placeholders.";
2
+ export function buildWorkingTaskSection(options = {}) {
2
3
  return `## Working the Task
3
4
 
4
- Fire independent tool calls as one parallel wave - reads, searches, listings, diagnostics - and bias toward breadth when context is thin: pull in anything even loosely relevant now instead of serially later. Wasted reads cost almost nothing; stale assumptions cost the turn. Edits and result-dependent calls go one at a time, each compared with the state you meant to produce; when the result must be seen rather than read, render after each change and look before the next. Never fill missing parameters with placeholders.
5
+ ${options.opening ?? DEFAULT_WORKING_TASK_OPENING}
5
6
 
6
7
  Memory of file contents is unreliable - read before claiming, re-read before editing. Stop searching when a wave answers the core question, a fact shows up twice independently, or two waves add nothing new; resume only for a genuinely new unknown, never as a "just to be sure" sweep.
7
8
 
@@ -18,10 +18,11 @@ export interface WorkstationFacts {
18
18
  * Families follow the prompt-preset conventions: Claude/GLM take direct
19
19
  * imperatives in a tagged block, GPT (codex) takes terse bounded rules, Kimi
20
20
  * takes positive operational constraints (all-caps directives make K2.x
21
- * overthink), and `default` is the maximum-emphasis fallback for unmatched
22
- * models.
21
+ * overthink), the `gemini` dialect takes one plain sentence pair (the
22
+ * provider's guidance: direct instructions, no persuasive emphasis), and `default` is
23
+ * the maximum-emphasis fallback for unmatched models.
23
24
  */
24
- export type WorkstationDialect = "default" | "claude" | "codex" | "kimi";
25
+ export type WorkstationDialect = "default" | "claude" | "codex" | "kimi" | "gemini";
25
26
  /** Collect host facts once per process; every call after the first is free. */
26
27
  export declare function collectWorkstationFacts(): WorkstationFacts;
27
28
  export interface BuildWorkstationSectionOptions {
@@ -62,6 +62,7 @@ ${executors} on THIS workstation. Match commands, paths, package managers, and p
62
62
  </execution_context>`,
63
63
  codex: (executors) => `${executors} on this workstation; match commands, paths, and parallelism to it. Written code may target other platforms — executed code runs here.`,
64
64
  kimi: (executors) => `${executors} on this workstation — choose commands, paths, and parallelism that fit it. When writing code for another target platform, keep the code portable; only the execution happens here.`,
65
+ gemini: (executors) => `${executors} on this workstation; match commands, paths, package managers, and parallel pool sizes to it. Code you write may target another machine; code you run always runs here.`,
65
66
  default: (executors) => `**EXECUTION HAPPENS HERE.** ${executors} on THIS machine — you MUST match commands, paths, package managers, and parallel pool sizes to the workstation above. Code you WRITE may target a different machine; anything you RUN executes HERE.`,
66
67
  };
67
68
  /** Render the `<workstation>` facts block plus the dialect-tuned execution-context instruction. */
@@ -3,7 +3,7 @@ import { describeModelBlocks } from "../../../credential-pool/model-scope.js";
3
3
  import { accountDisplayNameCommand, promptAccountDisplayName } from "../account-display-name.js";
4
4
  import { createExtensionLoginInteraction, LOGIN_CANCELLED_MESSAGE } from "../oauth-login-interaction.js";
5
5
  import { emitProviderAccountsChanged } from "./account-events.js";
6
- import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, pinProviderAccount, removeProviderAccount } from "./account-management.js";
6
+ import { ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, pinProviderAccount, removeProviderAccount, unblockProviderAccount, } from "./account-management.js";
7
7
  import { emptyCredential, listAccounts } from "./accounts.js";
8
8
  import { selectAccount } from "./affinity.js";
9
9
  import { loadAnthropicSubscriptionProviderSettingsFromDisk, } from "./settings.js";
@@ -65,7 +65,7 @@ export function registerClaudeAccountCommand(pi, deps = {}) {
65
65
  });
66
66
  pi.registerCommand("claude-account", {
67
67
  description: "List and manage Anthropic Subscription accounts.",
68
- argumentHint: "[add | remove <id> | pin <id> | unpin | rename <id> <display name...> | clear-name <id>]",
68
+ argumentHint: "[add | remove <id> | pin <id> | unpin | unblock <id> | rename <id> <display name...> | clear-name <id>]",
69
69
  requiresArguments: false,
70
70
  handler: async (rawArgs, ctx) => {
71
71
  if (await accountDisplayNameCommand(ctx, ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, rawArgs))
@@ -92,7 +92,11 @@ export function registerClaudeAccountCommand(pi, deps = {}) {
92
92
  await unpinAccount(ctx);
93
93
  return;
94
94
  }
95
- ctx.ui.notify("Usage: /claude-account [add | remove <id> | pin <id> | unpin | rename <id> <display name...> | clear-name <id>]", "error");
95
+ if (action === "unblock") {
96
+ await unblockNamedAccount(ctx, args[1], environment);
97
+ return;
98
+ }
99
+ ctx.ui.notify("Usage: /claude-account [add | remove <id> | pin <id> | unpin | unblock <id> | rename <id> <display name...> | clear-name <id>]", "error");
96
100
  },
97
101
  });
98
102
  }
@@ -194,6 +198,25 @@ async function pinNamedAccount(ctx, name, environment) {
194
198
  ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
195
199
  }
196
200
  }
201
+ async function unblockNamedAccount(ctx, name, environment) {
202
+ if (!name) {
203
+ ctx.ui.notify("Usage: /claude-account unblock <name>", "error");
204
+ return;
205
+ }
206
+ if (!readAccounts(ctx, environment).accounts.some((account) => account.name === name)) {
207
+ return accountError(ctx, name);
208
+ }
209
+ try {
210
+ const env = new Proxy({}, { get: (_target, key) => environment(String(key)) });
211
+ const lifted = await unblockProviderAccount(ctx.modelRegistry.authStorage, ANTHROPIC_SUBSCRIPTION_PROVIDER_ID, name, env);
212
+ ctx.ui.notify(lifted
213
+ ? `Unblocked Anthropic Subscription account: ${name}. The next turn may use it.`
214
+ : `Anthropic Subscription account '${name}' has no usage block to lift.`, "info");
215
+ }
216
+ catch (error) {
217
+ ctx.ui.notify(error instanceof Error ? error.message : String(error), "error");
218
+ }
219
+ }
197
220
  async function unpinAccount(ctx) {
198
221
  const credential = asCredential(ctx.modelRegistry.authStorage.get(ANTHROPIC_SUBSCRIPTION_PROVIDER_ID));
199
222
  if (!credential?.pinned) {
@@ -11,4 +11,9 @@ export type ProviderAccountSummary = {
11
11
  export declare function getProviderAccounts(storage: AuthStorage, provider: string, env?: NodeJS.ProcessEnv): ProviderAccountSummary[];
12
12
  export declare function pinProviderAccount(storage: AuthStorage, provider: string, name: string | null, env?: NodeJS.ProcessEnv): Promise<void>;
13
13
  export declare function removeProviderAccount(storage: AuthStorage, provider: string, name: string): Promise<void>;
14
+ /**
15
+ * Lifts a usage or rate-limit block the user knows is wrong (extra usage bought, a misread reset).
16
+ * Auth blocks stay: only a login or the re-probe can lift those. Returns whether a block was lifted.
17
+ */
18
+ export declare function unblockProviderAccount(storage: AuthStorage, provider: string, name: string, env?: NodeJS.ProcessEnv): Promise<boolean>;
14
19
  //# sourceMappingURL=account-management.d.ts.map