@skein-code/cli 0.3.24 → 0.3.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -220,7 +220,7 @@ function isSqliteBusy(error) {
220
220
  // package.json
221
221
  var package_default = {
222
222
  name: "@skein-code/cli",
223
- version: "0.3.24",
223
+ version: "0.3.25",
224
224
  description: "A context-first, model-agnostic coding agent with an auditable terminal workspace.",
225
225
  type: "module",
226
226
  license: "MIT",
@@ -236,6 +236,11 @@ var package_default = {
236
236
  },
237
237
  skein: {
238
238
  releaseNotes: [
239
+ "Durable sessions now separate a 250k-token context epoch from a 1m-token lifetime ceiling without changing the session id or deleting transcript history",
240
+ "Content-free epoch handoffs preserve Task Contract criteria, unresolved failure circuits, changed files, and verification receipts across long runs",
241
+ "Intent Sufficiency routes clear requests to execution, repository-inferable gaps to inspection, and genuine product choices to one persisted clarification",
242
+ "TUI and headless output expose epoch, lifetime, and needs-input state; queued follow-ups pause and resume after keyboard clarification instead of becoming accidental answers",
243
+ "Successful identical tool calls clear matching historical failure signatures from later epoch handoffs",
239
244
  "Context compaction now rebuilds authoritative task, working-state, verification, permission, failure, and artifact facts outside generated narrative",
240
245
  "Automatic compaction runs only when three predicted prompt reuses produce positive net token savings while explicit compact commands remain available",
241
246
  "Compaction provider usage is included in session totals with separate content-free actual or estimated receipts",
@@ -1993,6 +1998,7 @@ var partialConfigSchema = z2.object({
1993
1998
  }).partial().optional(),
1994
1999
  agent: z2.object({
1995
2000
  maxTurns: z2.number().int().positive().optional(),
2001
+ maxEpochTokens: z2.number().int().positive().optional(),
1996
2002
  maxSessionTokens: z2.number().int().positive().optional(),
1997
2003
  autoVerify: z2.boolean().optional(),
1998
2004
  verifyCommands: z2.array(z2.string()).optional(),
@@ -2072,7 +2078,8 @@ function defaultConfig(workspace = process.cwd()) {
2072
2078
  hooks: {},
2073
2079
  agent: {
2074
2080
  maxTurns: 24,
2075
- maxSessionTokens: 25e4,
2081
+ maxEpochTokens: 25e4,
2082
+ maxSessionTokens: 1e6,
2076
2083
  autoVerify: true,
2077
2084
  verifyCommands: [],
2078
2085
  checkpointBeforeWrite: true
@@ -2436,6 +2443,7 @@ function configSummary(config) {
2436
2443
  workspaceRoots: config.workspaceRoots,
2437
2444
  permissions: config.permissions,
2438
2445
  maxTurns: config.agent.maxTurns,
2446
+ maxEpochTokens: config.agent.maxEpochTokens,
2439
2447
  maxSessionTokens: config.agent.maxSessionTokens,
2440
2448
  autoVerify: config.agent.autoVerify,
2441
2449
  skills: config.skills ? {
@@ -4720,10 +4728,127 @@ function formatContextHits(hits, roots) {
4720
4728
  }
4721
4729
 
4722
4730
  // src/agent/runner.ts
4723
- import { randomUUID as randomUUID13 } from "node:crypto";
4731
+ import { randomUUID as randomUUID15 } from "node:crypto";
4724
4732
 
4725
4733
  // src/context/manager.ts
4734
+ import { randomUUID as randomUUID5 } from "node:crypto";
4735
+
4736
+ // src/context/epochs.ts
4726
4737
  import { randomUUID as randomUUID4 } from "node:crypto";
4738
+ var MAX_EPOCHS = 64;
4739
+ var MAX_HANDOFF_FAILURES = 16;
4740
+ function activeContextEpoch(session) {
4741
+ return ensureContextEpoch(session);
4742
+ }
4743
+ function ensureContextEpoch(session) {
4744
+ const epochs = session.contextEpochs ?? (session.contextEpochs = []);
4745
+ const active = epochs.at(-1);
4746
+ if (active && active.finishedAt === void 0) return active;
4747
+ const next = createEpoch((active?.index ?? 0) + 1);
4748
+ epochs.push(next);
4749
+ trimEpochs(epochs);
4750
+ return next;
4751
+ }
4752
+ function recordContextEpochUsage(session, inputTokens, outputTokens) {
4753
+ const epoch = ensureContextEpoch(session);
4754
+ epoch.usage.inputTokens += boundedTokens(inputTokens);
4755
+ epoch.usage.outputTokens += boundedTokens(outputTokens);
4756
+ return epoch;
4757
+ }
4758
+ function contextEpochTokens(session) {
4759
+ const epoch = ensureContextEpoch(session);
4760
+ return epoch.usage.inputTokens + epoch.usage.outputTokens;
4761
+ }
4762
+ function rotateContextEpoch(session, reason, compaction) {
4763
+ const previous = ensureContextEpoch(session);
4764
+ const createdAt = (/* @__PURE__ */ new Date()).toISOString();
4765
+ const handoff = buildEpochHandoff(session, reason, createdAt, compaction);
4766
+ previous.finishedAt = createdAt;
4767
+ previous.handoff = handoff;
4768
+ const current = createEpoch(previous.index + 1, createdAt);
4769
+ const epochs = session.contextEpochs ?? (session.contextEpochs = []);
4770
+ epochs.push(current);
4771
+ trimEpochs(epochs);
4772
+ return { previous, current, handoff };
4773
+ }
4774
+ function buildEpochHandoff(session, reason, createdAt, compaction) {
4775
+ const failures = unresolvedFailureReceipts(session);
4776
+ const contract = session.taskContract;
4777
+ return {
4778
+ reason,
4779
+ createdAt,
4780
+ ...compaction ? { compactionReceiptId: compaction.id } : {},
4781
+ ...session.compactedThroughMessageId ? { compactedThroughMessageId: session.compactedThroughMessageId } : {},
4782
+ ...contract ? {
4783
+ contract: {
4784
+ state: contract.state,
4785
+ required: contract.acceptanceCriteria.filter((criterion2) => criterion2.required).map((criterion2) => ({
4786
+ id: criterion2.id,
4787
+ status: criterion2.status,
4788
+ evidenceRefs: [...criterion2.evidenceRefs]
4789
+ }))
4790
+ }
4791
+ } : {},
4792
+ unresolvedFailures: failures,
4793
+ changedFiles: [...new Set(session.changedFiles)].slice(-256),
4794
+ checks: session.lastRun?.checks.map((check) => ({ ...check })) ?? []
4795
+ };
4796
+ }
4797
+ function unresolvedFailureReceipts(session) {
4798
+ const unresolved2 = /* @__PURE__ */ new Map();
4799
+ for (const event of session.audit ?? []) {
4800
+ if (event.type !== "tool") continue;
4801
+ if (event.outcome === "failure") {
4802
+ const receipt = failureReceipt(event.metadata?.failure);
4803
+ if (receipt) unresolved2.set(receipt.signature, receipt);
4804
+ continue;
4805
+ }
4806
+ const resolved = event.metadata?.resolvedFailureSignatures;
4807
+ if (!Array.isArray(resolved)) continue;
4808
+ for (const signature of resolved) {
4809
+ if (typeof signature === "string") unresolved2.delete(signature);
4810
+ }
4811
+ }
4812
+ return [...unresolved2.values()].slice(-MAX_HANDOFF_FAILURES);
4813
+ }
4814
+ function failureReceipt(value) {
4815
+ if (!value || typeof value !== "object") return;
4816
+ const candidate = value;
4817
+ const classes = /* @__PURE__ */ new Set([
4818
+ "schema_input",
4819
+ "unknown_tool",
4820
+ "permission_denied",
4821
+ "command_exit",
4822
+ "timeout",
4823
+ "cancelled",
4824
+ "hook",
4825
+ "execution",
4826
+ "no_progress",
4827
+ "contract_required"
4828
+ ]);
4829
+ if (typeof candidate.signature !== "string" || !candidate.signature || typeof candidate.class !== "string" || !classes.has(candidate.class) || typeof candidate.circuitOpen !== "boolean") return;
4830
+ return {
4831
+ signature: candidate.signature.slice(0, 256),
4832
+ class: candidate.class,
4833
+ circuitOpen: candidate.circuitOpen
4834
+ };
4835
+ }
4836
+ function createEpoch(index, startedAt = (/* @__PURE__ */ new Date()).toISOString()) {
4837
+ return {
4838
+ id: randomUUID4(),
4839
+ index,
4840
+ startedAt,
4841
+ usage: { inputTokens: 0, outputTokens: 0 }
4842
+ };
4843
+ }
4844
+ function trimEpochs(epochs) {
4845
+ if (epochs.length > MAX_EPOCHS) epochs.splice(0, epochs.length - MAX_EPOCHS);
4846
+ }
4847
+ function boundedTokens(value) {
4848
+ return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
4849
+ }
4850
+
4851
+ // src/context/manager.ts
4727
4852
  var RECENT_TURN_RESERVE = 3;
4728
4853
  var COMPACTION_HIGH_WATER = 0.78;
4729
4854
  var TOOL_PRESSURE_WATER = 0.28;
@@ -4776,13 +4901,21 @@ var ContextManager = class {
4776
4901
  modelContextTokens ?? Math.min(1e5, this.config.context.maxTokens * 3)
4777
4902
  );
4778
4903
  const compactedMessages = session.compactedThroughMessageId ? Math.max(0, session.messages.findIndex((message2) => message2.id === session.compactedThroughMessageId) + 1) : 0;
4904
+ const epoch = activeContextEpoch(session);
4905
+ const epochBudget = this.config.agent.maxEpochTokens ?? this.config.agent.maxSessionTokens;
4779
4906
  return {
4780
4907
  activeTokens,
4781
4908
  summaryTokens,
4782
4909
  toolTokens: toolTokenCount,
4783
4910
  messageCount: active.length,
4784
4911
  compactedMessages,
4785
- pressure: Math.min(1, (activeTokens + summaryTokens) / contextLimit)
4912
+ pressure: Math.min(1, (activeTokens + summaryTokens) / contextLimit),
4913
+ epochIndex: epoch.index,
4914
+ epochCount: epoch.index,
4915
+ epochTokens: epoch.usage.inputTokens + epoch.usage.outputTokens,
4916
+ epochBudget,
4917
+ lifetimeTokens: session.usage.inputTokens + session.usage.outputTokens,
4918
+ lifetimeBudget: this.config.agent.maxSessionTokens
4786
4919
  };
4787
4920
  }
4788
4921
  shouldCompact(session, tokenBudget) {
@@ -4891,7 +5024,7 @@ function compactionEstimate(session, older, facts, request) {
4891
5024
  }
4892
5025
  function skippedCompaction(session, mode, reason, estimate = emptyCompactionEstimate(session)) {
4893
5026
  const receipt = {
4894
- id: randomUUID4(),
5027
+ id: randomUUID5(),
4895
5028
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
4896
5029
  mode,
4897
5030
  status: "skipped",
@@ -4928,7 +5061,7 @@ function emptyCompactionEstimate(session) {
4928
5061
  function completedCompactionReceipt(mode, omittedMessages, compactedThroughMessageId, estimated, response, summary) {
4929
5062
  const actual = actualUsage(response.usage);
4930
5063
  return {
4931
- id: randomUUID4(),
5064
+ id: randomUUID5(),
4932
5065
  recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
4933
5066
  mode,
4934
5067
  status: "compacted",
@@ -4967,6 +5100,7 @@ function buildCompactionFactsEnvelope(session, throughMessageId) {
4967
5100
  const memory = session.workingMemory;
4968
5101
  const contract = session.taskContract;
4969
5102
  const lastRun = session.lastRun;
5103
+ const epoch = activeContextEpoch(session);
4970
5104
  const directives = olderUserDirectives(session, throughMessageId ?? session.compactedThroughMessageId);
4971
5105
  const permissions = recentPermissionFacts(session.audit ?? []);
4972
5106
  const failures = recentFailureFacts(session.audit ?? []);
@@ -5019,6 +5153,11 @@ ${factList(memory?.relevantFiles ?? [])}
5019
5153
  Session changed files:
5020
5154
  ${factList(session.changedFiles)}
5021
5155
 
5156
+ Context epoch ledger:
5157
+ - Current epoch: ${epoch.index}
5158
+ - Current epoch usage: input=${epoch.usage.inputTokens}; output=${epoch.usage.outputTokens}
5159
+ - Lifetime usage: input=${session.usage.inputTokens}; output=${session.usage.outputTokens}
5160
+
5022
5161
  Last-run verification and residual state:
5023
5162
  ${lastRunLines}
5024
5163
 
@@ -5462,7 +5601,7 @@ function normalizeForMatch(value) {
5462
5601
  }
5463
5602
 
5464
5603
  // src/providers/anthropic.ts
5465
- import { randomUUID as randomUUID5 } from "node:crypto";
5604
+ import { randomUUID as randomUUID6 } from "node:crypto";
5466
5605
 
5467
5606
  // src/providers/provider.ts
5468
5607
  var ProviderError = class extends Error {
@@ -5587,7 +5726,7 @@ var AnthropicProvider = class {
5587
5726
  return {
5588
5727
  content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
5589
5728
  toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
5590
- id: block.id ?? randomUUID5(),
5729
+ id: block.id ?? randomUUID6(),
5591
5730
  name: block.name ?? "unknown",
5592
5731
  arguments: safeJsonArguments(block.input)
5593
5732
  })),
@@ -5661,7 +5800,7 @@ var AnthropicProvider = class {
5661
5800
  }
5662
5801
  if (chunk.type === "content_block_start" && chunk.content_block?.type === "tool_use") {
5663
5802
  calls.set(index, {
5664
- id: chunk.content_block.id ?? randomUUID5(),
5803
+ id: chunk.content_block.id ?? randomUUID6(),
5665
5804
  name: chunk.content_block.name ?? "unknown",
5666
5805
  input: chunk.content_block.input,
5667
5806
  partialJson: ""
@@ -5672,7 +5811,7 @@ var AnthropicProvider = class {
5672
5811
  yield { type: "text_delta", content: chunk.delta.text };
5673
5812
  }
5674
5813
  if (chunk.type === "content_block_delta" && chunk.delta?.type === "input_json_delta") {
5675
- const call = calls.get(index) ?? { id: randomUUID5(), name: "unknown", input: void 0, partialJson: "" };
5814
+ const call = calls.get(index) ?? { id: randomUUID6(), name: "unknown", input: void 0, partialJson: "" };
5676
5815
  call.partialJson += chunk.delta.partial_json ?? "";
5677
5816
  calls.set(index, call);
5678
5817
  }
@@ -5702,7 +5841,7 @@ function normalizeAnthropicResponse(data) {
5702
5841
  return {
5703
5842
  content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
5704
5843
  toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
5705
- id: block.id ?? randomUUID5(),
5844
+ id: block.id ?? randomUUID6(),
5706
5845
  name: block.name ?? "unknown",
5707
5846
  arguments: safeJsonArguments(block.input)
5708
5847
  })),
@@ -5747,7 +5886,7 @@ function toAnthropicMessage(message2) {
5747
5886
  }
5748
5887
 
5749
5888
  // src/providers/gemini.ts
5750
- import { randomUUID as randomUUID6 } from "node:crypto";
5889
+ import { randomUUID as randomUUID7 } from "node:crypto";
5751
5890
  var GeminiProvider = class {
5752
5891
  constructor(config) {
5753
5892
  this.config = config;
@@ -5787,7 +5926,7 @@ var GeminiProvider = class {
5787
5926
  return {
5788
5927
  content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
5789
5928
  toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
5790
- id: randomUUID6(),
5929
+ id: randomUUID7(),
5791
5930
  name: part.functionCall?.name ?? "unknown",
5792
5931
  arguments: safeJsonArguments(part.functionCall?.args)
5793
5932
  })),
@@ -5859,7 +5998,7 @@ var GeminiProvider = class {
5859
5998
  }
5860
5999
  if (part.functionCall) {
5861
6000
  toolCalls.push({
5862
- id: randomUUID6(),
6001
+ id: randomUUID7(),
5863
6002
  name: part.functionCall.name ?? "unknown",
5864
6003
  arguments: safeJsonArguments(part.functionCall.args)
5865
6004
  });
@@ -5888,7 +6027,7 @@ function normalizeGeminiResponse(data) {
5888
6027
  return {
5889
6028
  content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
5890
6029
  toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
5891
- id: randomUUID6(),
6030
+ id: randomUUID7(),
5892
6031
  name: part.functionCall?.name ?? "unknown",
5893
6032
  arguments: safeJsonArguments(part.functionCall?.args)
5894
6033
  })),
@@ -5932,7 +6071,7 @@ function toGeminiMessage(message2) {
5932
6071
  }
5933
6072
 
5934
6073
  // src/providers/openai.ts
5935
- import { randomUUID as randomUUID7 } from "node:crypto";
6074
+ import { randomUUID as randomUUID8 } from "node:crypto";
5936
6075
  var OpenAIProvider = class {
5937
6076
  constructor(config) {
5938
6077
  this.config = config;
@@ -6055,7 +6194,7 @@ var OpenAIProvider = class {
6055
6194
  }
6056
6195
  for (const fragment of delta?.tool_calls ?? []) {
6057
6196
  const index = fragment.index ?? 0;
6058
- const current = calls.get(index) ?? { id: randomUUID7(), name: "", arguments: "" };
6197
+ const current = calls.get(index) ?? { id: randomUUID8(), name: "", arguments: "" };
6059
6198
  if (fragment.id) current.id = fragment.id;
6060
6199
  if (fragment.function?.name) current.name += fragment.function.name;
6061
6200
  if (fragment.function?.arguments) current.arguments += fragment.function.arguments;
@@ -6089,7 +6228,7 @@ function normalizeOpenAIResponse(data) {
6089
6228
  return {
6090
6229
  content: message2.content ?? "",
6091
6230
  toolCalls: (message2.tool_calls ?? []).map((call) => ({
6092
- id: call.id ?? randomUUID7(),
6231
+ id: call.id ?? randomUUID8(),
6093
6232
  name: call.function?.name ?? "unknown",
6094
6233
  arguments: safeJsonArguments(call.function?.arguments)
6095
6234
  })),
@@ -6145,7 +6284,7 @@ function createProvider(config) {
6145
6284
  }
6146
6285
 
6147
6286
  // src/checkpoint/store.ts
6148
- import { createHash as createHash8, randomUUID as randomUUID8 } from "node:crypto";
6287
+ import { createHash as createHash8, randomUUID as randomUUID9 } from "node:crypto";
6149
6288
  import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
6150
6289
  import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
6151
6290
  import { z as z4 } from "zod";
@@ -6181,7 +6320,7 @@ var CheckpointStore = class {
6181
6320
  return this.withManagedLease(() => this.captureUnlocked(sessionId, unique3, options));
6182
6321
  }
6183
6322
  async captureUnlocked(sessionId, unique3, options) {
6184
- const id = `${Date.now().toString(36)}-${randomUUID8()}`;
6323
+ const id = `${Date.now().toString(36)}-${randomUUID9()}`;
6185
6324
  const target = join9(this.directory, sessionId, id);
6186
6325
  const blobDirectory = join9(target, "blobs");
6187
6326
  await this.ensureDirectory();
@@ -6457,7 +6596,7 @@ var HookRunner = class {
6457
6596
  };
6458
6597
 
6459
6598
  // src/session/store.ts
6460
- import { randomUUID as randomUUID9 } from "node:crypto";
6599
+ import { randomUUID as randomUUID10 } from "node:crypto";
6461
6600
  import { constants as constants4 } from "node:fs";
6462
6601
  import {
6463
6602
  chmod as chmod6,
@@ -6683,6 +6822,76 @@ var contextCompactionReceiptSchema = z5.object({
6683
6822
  outputSource: z5.enum(["actual", "estimated", "none"]),
6684
6823
  narrative: z5.enum(["present", "empty", "not-requested"])
6685
6824
  }).strict();
6825
+ var contextEpochSchema = z5.object({
6826
+ id: z5.string().uuid(),
6827
+ index: z5.number().int().positive(),
6828
+ startedAt: z5.string().datetime(),
6829
+ finishedAt: z5.string().datetime().optional(),
6830
+ usage: z5.object({
6831
+ inputTokens: z5.number().int().nonnegative(),
6832
+ outputTokens: z5.number().int().nonnegative()
6833
+ }).strict(),
6834
+ handoff: z5.object({
6835
+ reason: z5.enum(["token_budget", "context_pressure", "manual"]),
6836
+ createdAt: z5.string().datetime(),
6837
+ compactionReceiptId: z5.string().uuid().optional(),
6838
+ compactedThroughMessageId: z5.string().optional(),
6839
+ contract: z5.object({
6840
+ state: z5.enum(["draft", "active", "satisfied", "blocked"]),
6841
+ required: z5.array(z5.object({
6842
+ id: z5.string().min(1).max(128),
6843
+ status: z5.enum(["pending", "satisfied", "blocked"]),
6844
+ evidenceRefs: z5.array(z5.string().min(1).max(256)).max(64)
6845
+ }).strict()).max(64)
6846
+ }).strict().optional(),
6847
+ unresolvedFailures: z5.array(z5.object({
6848
+ signature: z5.string().min(1).max(256),
6849
+ class: z5.enum([
6850
+ "schema_input",
6851
+ "unknown_tool",
6852
+ "permission_denied",
6853
+ "command_exit",
6854
+ "timeout",
6855
+ "cancelled",
6856
+ "hook",
6857
+ "execution",
6858
+ "no_progress",
6859
+ "contract_required"
6860
+ ]),
6861
+ circuitOpen: z5.boolean()
6862
+ }).strict()).max(16),
6863
+ changedFiles: z5.array(z5.string().max(4096)).max(256),
6864
+ checks: z5.array(verificationEvidenceSchema).max(64)
6865
+ }).strict().optional()
6866
+ }).strict();
6867
+ var intentAssessmentSchema = z5.object({
6868
+ version: z5.literal(1),
6869
+ route: z5.enum(["direct_execute", "inspect_then_execute", "needs_input", "permission_required"]),
6870
+ reasons: z5.array(z5.enum([
6871
+ "simple_explicit_request",
6872
+ "workspace_inference_available",
6873
+ "explicit_user_choice_missing",
6874
+ "public_api_compatibility_missing",
6875
+ "runtime_permission_separate",
6876
+ "clarification_resolved"
6877
+ ])).min(1).max(8),
6878
+ assessedAt: z5.string().datetime(),
6879
+ retrievalHits: z5.number().int().nonnegative()
6880
+ }).strict();
6881
+ var pendingInputSchema = z5.object({
6882
+ id: z5.string().uuid(),
6883
+ runId: z5.string().uuid(),
6884
+ createdAt: z5.string().datetime(),
6885
+ originalRequest: z5.string().min(1).max(12e4),
6886
+ question: z5.string().min(1).max(2e3),
6887
+ options: z5.array(z5.object({
6888
+ id: z5.string().regex(/^[a-z][a-z0-9_-]{0,63}$/u),
6889
+ label: z5.string().min(1).max(160),
6890
+ impact: z5.string().min(1).max(500),
6891
+ recommended: z5.boolean()
6892
+ }).strict()).min(2).max(3),
6893
+ reason: z5.enum(["explicit_user_choice_missing", "public_api_compatibility_missing"])
6894
+ }).strict();
6686
6895
  var sessionSchema = z5.object({
6687
6896
  id: sessionIdSchema,
6688
6897
  title: z5.string(),
@@ -6699,6 +6908,9 @@ var sessionSchema = z5.object({
6699
6908
  contextCompactions: z5.number().int().nonnegative().optional(),
6700
6909
  compactedThroughMessageId: z5.string().optional(),
6701
6910
  contextCompactionReceipts: z5.array(contextCompactionReceiptSchema).max(64).optional(),
6911
+ contextEpochs: z5.array(contextEpochSchema).max(64).optional(),
6912
+ intentAssessment: intentAssessmentSchema.optional(),
6913
+ pendingInput: pendingInputSchema.optional(),
6702
6914
  workingMemory: workingMemorySchema.optional(),
6703
6915
  contextSources: z5.array(contextSourceSchema).max(64).optional(),
6704
6916
  toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
@@ -6849,7 +7061,7 @@ var SessionStore = class {
6849
7061
  const backup = this.backupPathFor(session.id);
6850
7062
  await this.assertManagedFile(target);
6851
7063
  await this.assertManagedFile(backup);
6852
- const temporary = join10(this.directory, `.${session.id}.${randomUUID9()}.tmp`);
7064
+ const temporary = join10(this.directory, `.${session.id}.${randomUUID10()}.tmp`);
6853
7065
  const data = `${JSON.stringify(session, null, 2)}
6854
7066
  `;
6855
7067
  const handle = await open2(temporary, "wx", 384);
@@ -6867,7 +7079,7 @@ var SessionStore = class {
6867
7079
  }
6868
7080
  }
6869
7081
  async copyBackup(source, target) {
6870
- const temporary = join10(this.directory, `.${basename7(target)}.${randomUUID9()}.tmp`);
7082
+ const temporary = join10(this.directory, `.${basename7(target)}.${randomUUID10()}.tmp`);
6871
7083
  try {
6872
7084
  await copyFile(source, temporary, constants4.COPYFILE_EXCL);
6873
7085
  await chmod6(temporary, 384);
@@ -6956,7 +7168,7 @@ var SessionStore = class {
6956
7168
  }
6957
7169
  };
6958
7170
  function createSession(options) {
6959
- const id = options.id ?? randomUUID9();
7171
+ const id = options.id ?? randomUUID10();
6960
7172
  validateId(id);
6961
7173
  const now = (/* @__PURE__ */ new Date()).toISOString();
6962
7174
  return {
@@ -6971,6 +7183,12 @@ function createSession(options) {
6971
7183
  tasks: [],
6972
7184
  changedFiles: [],
6973
7185
  audit: [],
7186
+ contextEpochs: [{
7187
+ id: randomUUID10(),
7188
+ index: 1,
7189
+ startedAt: now,
7190
+ usage: { inputTokens: 0, outputTokens: 0 }
7191
+ }],
6974
7192
  usage: { inputTokens: 0, outputTokens: 0 }
6975
7193
  };
6976
7194
  }
@@ -8063,7 +8281,7 @@ function isUnsafeGitOption(argument) {
8063
8281
  "--exclude-from",
8064
8282
  "--output",
8065
8283
  "--directory"
8066
- ].some((option) => argument === option || argument.startsWith(`${option}=`));
8284
+ ].some((option2) => argument === option2 || argument.startsWith(`${option2}=`));
8067
8285
  }
8068
8286
  function firstGitCommand(args) {
8069
8287
  for (const argument of args) {
@@ -8811,7 +9029,7 @@ async function discoverSnapshotFiles(root) {
8811
9029
  }
8812
9030
 
8813
9031
  // src/tools/task.ts
8814
- import { randomUUID as randomUUID10 } from "node:crypto";
9032
+ import { randomUUID as randomUUID11 } from "node:crypto";
8815
9033
  import { z as z14 } from "zod";
8816
9034
  var taskSchema2 = z14.object({
8817
9035
  id: z14.string().min(1).optional(),
@@ -8860,7 +9078,7 @@ var taskTool = {
8860
9078
  case "list":
8861
9079
  break;
8862
9080
  case "add":
8863
- tasks.push({ id: randomUUID10(), title: input2.title, status: input2.status ?? "pending" });
9081
+ tasks.push({ id: randomUUID11(), title: input2.title, status: input2.status ?? "pending" });
8864
9082
  break;
8865
9083
  case "update": {
8866
9084
  const task = tasks.find((item) => item.id === input2.id);
@@ -8877,7 +9095,7 @@ var taskTool = {
8877
9095
  }
8878
9096
  case "replace":
8879
9097
  tasks.splice(0, tasks.length, ...input2.tasks.map((task) => ({
8880
- id: task.id ?? randomUUID10(),
9098
+ id: task.id ?? randomUUID11(),
8881
9099
  title: task.title,
8882
9100
  status: task.status
8883
9101
  })));
@@ -8891,11 +9109,11 @@ var taskTool = {
8891
9109
  };
8892
9110
 
8893
9111
  // src/tools/task-contract.ts
8894
- import { randomUUID as randomUUID12 } from "node:crypto";
9112
+ import { randomUUID as randomUUID13 } from "node:crypto";
8895
9113
  import { z as z15 } from "zod";
8896
9114
 
8897
9115
  // src/agent/task-contract.ts
8898
- import { randomUUID as randomUUID11 } from "node:crypto";
9116
+ import { randomUUID as randomUUID12 } from "node:crypto";
8899
9117
  var EXECUTABLE_INTENTS = /* @__PURE__ */ new Set(["debug", "refactor", "test", "implement"]);
8900
9118
  function shouldUseTaskContract(request, intent, existing) {
8901
9119
  if (existing && existing.state !== "satisfied") return true;
@@ -8961,7 +9179,7 @@ function refreshTaskContractState(contract) {
8961
9179
  }
8962
9180
  function criterion(id, description) {
8963
9181
  return {
8964
- id: `${id}-${randomUUID11().slice(0, 8)}`,
9182
+ id: `${id}-${randomUUID12().slice(0, 8)}`,
8965
9183
  description,
8966
9184
  required: true,
8967
9185
  status: "pending",
@@ -9515,7 +9733,7 @@ var taskContractTool = {
9515
9733
  }
9516
9734
  const now = (/* @__PURE__ */ new Date()).toISOString();
9517
9735
  const criteria = input2.acceptance_criteria.map((item) => ({
9518
- id: item.id ?? `criterion-${randomUUID12().slice(0, 8)}`,
9736
+ id: item.id ?? `criterion-${randomUUID13().slice(0, 8)}`,
9519
9737
  description: item.description,
9520
9738
  required: item.required ?? true,
9521
9739
  status: "pending",
@@ -9983,6 +10201,118 @@ function escapeAttribute3(value) {
9983
10201
  })[character] ?? character);
9984
10202
  }
9985
10203
 
10204
+ // src/agent/intent-sufficiency.ts
10205
+ import { randomUUID as randomUUID14 } from "node:crypto";
10206
+ function assessIntentSufficiency(request, intent, context) {
10207
+ const assessedAt = (/* @__PURE__ */ new Date()).toISOString();
10208
+ const uiChoice = explicitUiChoice(request);
10209
+ if (uiChoice) {
10210
+ const pending = clarification(
10211
+ request,
10212
+ assessedAt,
10213
+ "explicit_user_choice_missing",
10214
+ isChinese(request) ? "\u8FD9\u4E2A\u754C\u9762\u884C\u4E3A\u9700\u8981\u4F60\u7684\u4EA7\u54C1\u504F\u597D\uFF0C\u5E94\u91C7\u7528\u54EA\u4E00\u79CD\uFF1F" : "This UI behavior depends on your product preference. Which should be used?",
10215
+ uiChoice
10216
+ );
10217
+ return {
10218
+ assessment: assessment("needs_input", "explicit_user_choice_missing", assessedAt, context.retrievalHits),
10219
+ pending,
10220
+ directive: "Pause before mutation and ask the single recorded clarification question."
10221
+ };
10222
+ }
10223
+ if (publicApiCompatibilityMissing(request)) {
10224
+ const chinese = isChinese(request);
10225
+ const pending = clarification(
10226
+ request,
10227
+ assessedAt,
10228
+ "public_api_compatibility_missing",
10229
+ chinese ? "\u516C\u5171 API \u7684\u517C\u5BB9\u7B56\u7565\u5C1A\u672A\u6307\u5B9A\uFF0C\u5E94\u8BE5\u91C7\u7528\u54EA\u4E00\u79CD\uFF1F" : "The public API compatibility policy is unspecified. Which policy should be used?",
10230
+ chinese ? [
10231
+ option("backward_compatible", "\u4FDD\u6301\u5411\u540E\u517C\u5BB9", "\u4FDD\u7559\u65E7\u63A5\u53E3\u5E76\u63D0\u4F9B\u5F03\u7528\u6216\u8FC1\u79FB\u8DEF\u5F84\uFF1B\u6539\u52A8\u66F4\u7A33\u59A5\u4F46\u5B9E\u73B0\u8303\u56F4\u8F83\u5927\u3002", true),
10232
+ option("breaking_change", "\u5141\u8BB8\u7834\u574F\u6027\u53D8\u66F4", "\u76F4\u63A5\u91C7\u7528\u65B0\u63A5\u53E3\uFF1B\u8303\u56F4\u66F4\u5C0F\uFF0C\u4F46\u8C03\u7528\u65B9\u5FC5\u987B\u540C\u6B65\u8FC1\u79FB\u3002", false)
10233
+ ] : [
10234
+ option("backward_compatible", "Preserve compatibility", "Keep the old API with a deprecation or migration path; safer but broader.", true),
10235
+ option("breaking_change", "Allow breaking change", "Adopt the new API directly; smaller change but callers must migrate.", false)
10236
+ ]
10237
+ );
10238
+ return {
10239
+ assessment: assessment("needs_input", "public_api_compatibility_missing", assessedAt, context.retrievalHits),
10240
+ pending,
10241
+ directive: "Pause before mutation and ask the single recorded clarification question."
10242
+ };
10243
+ }
10244
+ if (requiresRuntimePermission(request)) {
10245
+ return {
10246
+ assessment: assessment("permission_required", "runtime_permission_separate", assessedAt, context.retrievalHits),
10247
+ directive: "The objective is sufficiently clear. Continue, but keep runtime permission approval separate from clarification."
10248
+ };
10249
+ }
10250
+ if (context.complex && shouldInspectWorkspace(request, intent)) {
10251
+ return {
10252
+ assessment: assessment("inspect_then_execute", "workspace_inference_available", assessedAt, context.retrievalHits),
10253
+ directive: "Do not ask the user for repository facts. Inspect the workspace and current evidence before the first mutation."
10254
+ };
10255
+ }
10256
+ return {
10257
+ assessment: assessment("direct_execute", "simple_explicit_request", assessedAt, context.retrievalHits),
10258
+ directive: "The request is sufficiently clear. Execute without an extra clarification turn."
10259
+ };
10260
+ }
10261
+ function resolvePendingInput(pending, answer) {
10262
+ const normalized = answer.trim();
10263
+ const byIndex = /^([1-3])(?:[.)、:]|$)/u.exec(normalized)?.[1];
10264
+ const selected = pending.options.find((candidate) => candidate.id.toLocaleLowerCase() === normalized.toLocaleLowerCase() || candidate.label.toLocaleLowerCase() === normalized.toLocaleLowerCase()) ?? (byIndex ? pending.options[Number(byIndex) - 1] : void 0);
10265
+ const decision = selected ? `${selected.label}: ${selected.impact}` : compact2(normalized, 2e3);
10266
+ return { answer: compact2(normalized, 2e3), decision };
10267
+ }
10268
+ function assessment(route, reason, assessedAt, retrievalHits) {
10269
+ return { version: 1, route, reasons: [reason], assessedAt, retrievalHits };
10270
+ }
10271
+ function clarification(originalRequest, createdAt, reason, question2, options) {
10272
+ return {
10273
+ id: randomUUID14(),
10274
+ runId: randomUUID14(),
10275
+ createdAt,
10276
+ originalRequest: originalRequest.slice(0, 12e4),
10277
+ question: question2,
10278
+ options,
10279
+ reason
10280
+ };
10281
+ }
10282
+ function explicitUiChoice(value) {
10283
+ const terms = "modal|dialog|drawer|popover|inline|new page|side panel|\u5F39\u7A97|\u6A21\u6001\u6846|\u62BD\u5C49|\u6D6E\u5C42|\u5185\u8054|\u9875\u9762\u5185|\u4FA7\u8FB9\u680F|\u65B0\u9875\u9762";
10284
+ const match = value.match(new RegExp(`\\b(${terms})\\b\\s*(?:or|versus|vs\\.?)\\s*\\b(${terms})\\b`, "iu")) ?? value.match(new RegExp(`(${terms})[\uFF0C\u3001\\s]*(?:\u8FD8\u662F|\u6216\u8005|\u6216)[\uFF0C\u3001\\s]*(${terms})`, "iu"));
10285
+ if (!match?.[1] || !match[2] || match[1].toLocaleLowerCase() === match[2].toLocaleLowerCase()) return;
10286
+ const chinese = isChinese(value);
10287
+ return [
10288
+ option("choice_1", compact2(match[1], 80), chinese ? "\u91C7\u7528\u8BF7\u6C42\u4E2D\u5217\u51FA\u7684\u7B2C\u4E00\u79CD\u4EA4\u4E92\u65B9\u5F0F\u3002" : "Use the first interaction named in the request.", true),
10289
+ option("choice_2", compact2(match[2], 80), chinese ? "\u91C7\u7528\u8BF7\u6C42\u4E2D\u5217\u51FA\u7684\u7B2C\u4E8C\u79CD\u4EA4\u4E92\u65B9\u5F0F\u3002" : "Use the second interaction named in the request.", false)
10290
+ ];
10291
+ }
10292
+ function publicApiCompatibilityMissing(value) {
10293
+ const api = /\b(?:public|external|exported)\s+(?:api|interface|contract)\b|公共\s*(?:api|接口)|公开\s*(?:api|接口)|对外接口/iu;
10294
+ const change = /\b(?:change|replace|remove|rename|migrate|redesign|refactor)\b|修改|替换|删除|移除|重命名|迁移|重构/iu;
10295
+ const policy = /\b(?:backward compatible|backwards compatible|breaking change|semver major|deprecat|compatibility shim|migration path)\b|向后兼容|保持兼容|允许破坏|破坏性变更|主版本|弃用|迁移路径/iu;
10296
+ return api.test(value) && change.test(value) && !policy.test(value);
10297
+ }
10298
+ function requiresRuntimePermission(value) {
10299
+ return /\b(?:push|publish|deploy|release|delete|drop|reset|force[- ]push|send|upload)\b|推送|发布|部署|删除|清空|重置|发送|上传/iu.test(value);
10300
+ }
10301
+ function shouldInspectWorkspace(value, intent) {
10302
+ if (intent === "debug" || intent === "refactor" || intent === "review") return true;
10303
+ const hasConcreteTarget = /(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|`[^`]+`|\b[A-Za-z_$][\w$]*(?:\(\))?\b/u.test(value);
10304
+ return !hasConcreteTarget || /\b(?:existing|current|relevant|appropriate|best)\b|现有|当前|相关|合适|最适合|这个|那个/iu.test(value);
10305
+ }
10306
+ function option(id, label, impact, recommended) {
10307
+ return { id, label, impact, recommended };
10308
+ }
10309
+ function isChinese(value) {
10310
+ return /[\u3400-\u9fff]/u.test(value);
10311
+ }
10312
+ function compact2(value, limit) {
10313
+ return value.trim().replace(/\s+/gu, " ").slice(0, limit);
10314
+ }
10315
+
9986
10316
  // src/agent/tool-recovery.ts
9987
10317
  import { createHash as createHash13 } from "node:crypto";
9988
10318
  var RETRY_BUDGET = {
@@ -9997,6 +10327,7 @@ var RETRY_BUDGET = {
9997
10327
  no_progress: 0,
9998
10328
  contract_required: 2
9999
10329
  };
10330
+ var FAILURE_CLASSES = Object.keys(RETRY_BUDGET);
10000
10331
  var REPAIR_HINT = {
10001
10332
  schema_input: "Correct the arguments to match the tool schema.",
10002
10333
  unknown_tool: "Choose a tool exposed for this turn.",
@@ -10098,6 +10429,9 @@ function classifyToolFailure(result, fallback = "execution") {
10098
10429
  function formatFailureReceipt(receipt) {
10099
10430
  return `Failure: ${receipt.class}; attempt ${receipt.attempt}; ${receipt.remaining} retries remain; circuit ${receipt.circuitOpen ? "open" : "closed"}. Repair: ${receipt.repairHint}`;
10100
10431
  }
10432
+ function resolvableFailureSignatures(call) {
10433
+ return FAILURE_CLASSES.map((failureClass) => failureSignature(call, failureClass));
10434
+ }
10101
10435
  function isRetryable(failureClass) {
10102
10436
  return RETRY_BUDGET[failureClass] > 0;
10103
10437
  }
@@ -10915,16 +11249,35 @@ var AgentRunner = class {
10915
11249
  }
10916
11250
  async run(input2, options = {}) {
10917
11251
  if (this.running) throw new Error("This AgentRunner is already processing a turn.");
10918
- const request = input2.trim();
10919
- if (!request) throw new Error("User input cannot be empty.");
10920
- if (request.length > 12e4) {
11252
+ const submittedRequest = input2.trim();
11253
+ if (!submittedRequest) throw new Error("User input cannot be empty.");
11254
+ if (submittedRequest.length > 12e4) {
10921
11255
  throw new Error("User input is too large; pass a focused request or attach files with @path.");
10922
11256
  }
11257
+ let request = submittedRequest;
10923
11258
  this.running = true;
10924
11259
  this.contextEngine.resetDiagnostics?.();
10925
11260
  const emit = async (event) => {
10926
11261
  await options.onEvent?.(event);
10927
11262
  };
11263
+ const pendingAtStart = this.session.pendingInput;
11264
+ const resolvedInput = pendingAtStart ? resolvePendingInput(pendingAtStart, submittedRequest) : void 0;
11265
+ if (pendingAtStart && resolvedInput) {
11266
+ request = `${pendingAtStart.originalRequest}
11267
+
11268
+ <user-clarification-decision source="explicit-user-input">
11269
+ ${resolvedInput.decision}
11270
+ </user-clarification-decision>`;
11271
+ delete this.session.pendingInput;
11272
+ const constraint = `User clarification (${pendingAtStart.id}): ${resolvedInput.decision}`;
11273
+ if (this.session.taskContract && !this.session.taskContract.constraints.includes(constraint)) {
11274
+ this.session.taskContract.constraints.push(constraint);
11275
+ if (this.session.taskContract.constraints.length > 64) {
11276
+ this.session.taskContract.constraints.splice(0, this.session.taskContract.constraints.length - 64);
11277
+ }
11278
+ this.session.taskContract.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
11279
+ }
11280
+ }
10928
11281
  const changeSequenceAtStart = this.changeSequence;
10929
11282
  const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
10930
11283
  const loadedProgressiveTools = /* @__PURE__ */ new Set();
@@ -10992,11 +11345,12 @@ var AgentRunner = class {
10992
11345
  try {
10993
11346
  throwIfAborted(options.signal);
10994
11347
  await this.reconcileToolArtifacts();
11348
+ ensureContextEpoch(this.session);
10995
11349
  if (this.session.messages.length === 0 && this.session.title === "New session") {
10996
11350
  this.session.title = titleFromInput(request);
10997
11351
  }
10998
11352
  this.contextManager.startTurn(this.session, request);
10999
- const userMessage = message("user", request);
11353
+ const userMessage = message("user", submittedRequest);
11000
11354
  this.activeReuseGate = { requestId: userMessage.id, request, attempted: false };
11001
11355
  this.session.messages.push(userMessage);
11002
11356
  await this.persist();
@@ -11004,11 +11358,48 @@ var AgentRunner = class {
11004
11358
  const turnDirective = buildTurnDirective(request, {
11005
11359
  agents: Boolean(this.config.agents?.enabled)
11006
11360
  });
11361
+ const contractEnabled = shouldUseTaskContract(
11362
+ request,
11363
+ turnDirective.intent,
11364
+ this.session.taskContract
11365
+ );
11007
11366
  const packed = trivialTurn ? emptyPackedContext(selectContextBudget(request, this.config, {
11008
11367
  intent: turnDirective.intent,
11009
11368
  trivial: true
11010
11369
  })) : await this.packContext(request, { intent: turnDirective.intent });
11011
11370
  if (!trivialTurn) await emit({ type: "context", packed });
11371
+ if (contractEnabled && (!this.session.taskContract || this.session.taskContract.state === "satisfied")) {
11372
+ this.session.taskContract = createDraftTaskContract(request, this.session.audit?.at(-1)?.id);
11373
+ await this.persist();
11374
+ await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
11375
+ }
11376
+ let intentDirective;
11377
+ if (pendingAtStart && resolvedInput) {
11378
+ this.session.intentAssessment = {
11379
+ version: 1,
11380
+ route: "direct_execute",
11381
+ reasons: ["clarification_resolved"],
11382
+ assessedAt: (/* @__PURE__ */ new Date()).toISOString(),
11383
+ retrievalHits: packed.hits.length
11384
+ };
11385
+ intentDirective = "The user resolved the recorded clarification. Continue the same logical run and apply that decision without asking again.";
11386
+ await emit({ type: "input_resolved", pendingId: pendingAtStart.id, runId: pendingAtStart.runId, answer: resolvedInput.answer });
11387
+ await emit({ type: "intent", assessment: this.session.intentAssessment });
11388
+ } else {
11389
+ const intent = assessIntentSufficiency(request, turnDirective.intent, {
11390
+ retrievalHits: packed.hits.length,
11391
+ complex: contractEnabled
11392
+ });
11393
+ this.session.intentAssessment = intent.assessment;
11394
+ intentDirective = intent.directive;
11395
+ await emit({ type: "intent", assessment: intent.assessment });
11396
+ if (intent.pending) {
11397
+ this.session.pendingInput = intent.pending;
11398
+ await this.persist();
11399
+ await emit({ type: "needs_input", pending: intent.pending });
11400
+ return finishRun("needs_input");
11401
+ }
11402
+ }
11012
11403
  const mentions = await this.packMentions(request);
11013
11404
  const retrievedContext = trivialTurn && !mentions.length ? "" : buildRetrievedContext(
11014
11405
  packed,
@@ -11036,19 +11427,10 @@ var AgentRunner = class {
11036
11427
  scope: augmentation.memoryScope ?? "session"
11037
11428
  });
11038
11429
  }
11039
- const contractEnabled = shouldUseTaskContract(
11040
- request,
11041
- turnDirective.intent,
11042
- this.session.taskContract
11043
- );
11044
- if (contractEnabled && (!this.session.taskContract || this.session.taskContract.state === "satisfied")) {
11045
- this.session.taskContract = createDraftTaskContract(request, this.session.audit?.at(-1)?.id);
11046
- await this.persist();
11047
- await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
11048
- }
11049
11430
  activeRunContract = contractEnabled ? this.session.taskContract : void 0;
11050
11431
  let verificationAttempted = false;
11051
11432
  const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
11433
+ const epochTokenBudget = this.config.agent.maxEpochTokens ?? this.config.agent.maxSessionTokens;
11052
11434
  const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
11053
11435
  if (this.contextManager.shouldCompact(this.session, contextBudget)) {
11054
11436
  const compacted = await this.compactContext(void 0, options.signal, "automatic");
@@ -11074,11 +11456,15 @@ var AgentRunner = class {
11074
11456
  if (this.session.usage.inputTokens + this.session.usage.outputTokens >= this.config.agent.maxSessionTokens) {
11075
11457
  return finishRun("token_budget");
11076
11458
  }
11459
+ if (contextEpochTokens(this.session) >= epochTokenBudget) {
11460
+ await this.rollContextEpoch("token_budget", options.signal, emit);
11461
+ }
11077
11462
  this.applySteering();
11078
11463
  await emit({ type: "thinking", turn });
11079
11464
  const dynamicPrompt = [
11080
11465
  buildSessionStatePrompt(this.session),
11081
11466
  turnDirective.text,
11467
+ `<intent-sufficiency route="${this.session.intentAssessment?.route ?? "direct_execute"}">${intentDirective}</intent-sufficiency>`,
11082
11468
  this.contextManager.buildShortTermPrompt(this.session),
11083
11469
  pinnedContext,
11084
11470
  options.turnInstructions ?? "",
@@ -11091,7 +11477,9 @@ var AgentRunner = class {
11091
11477
  activeMessages(this.session),
11092
11478
  contextBudget
11093
11479
  );
11094
- const availableTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
11480
+ const availableLifetimeTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
11481
+ const availableEpochTokens = epochTokenBudget - contextEpochTokens(this.session);
11482
+ const availableTokens = Math.min(availableLifetimeTokens, availableEpochTokens);
11095
11483
  const visibleTools = visibleToolDefinitions(
11096
11484
  this.tools,
11097
11485
  options.askMode === true,
@@ -11105,9 +11493,15 @@ var AgentRunner = class {
11105
11493
  loadedProgressiveTools
11106
11494
  );
11107
11495
  const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
11108
- if (availableTokens <= 0 || estimatedInputTokens >= availableTokens) {
11496
+ if (estimatedInputTokens >= availableLifetimeTokens) {
11109
11497
  return finishRun("token_budget");
11110
11498
  }
11499
+ if (availableTokens <= 0 || estimatedInputTokens >= availableEpochTokens) {
11500
+ if (contextEpochTokens(this.session) === 0) return finishRun("token_budget");
11501
+ await this.rollContextEpoch("token_budget", options.signal, emit);
11502
+ turn -= 1;
11503
+ continue;
11504
+ }
11111
11505
  const maxOutputTokens = Math.max(1, Math.min(
11112
11506
  this.config.model.maxTokens ?? 8192,
11113
11507
  availableTokens - estimatedInputTokens
@@ -11129,7 +11523,7 @@ var AgentRunner = class {
11129
11523
  breakdown
11130
11524
  });
11131
11525
  }
11132
- const assistantId = randomUUID13();
11526
+ const assistantId = randomUUID15();
11133
11527
  const response = await this.completeModel(
11134
11528
  messages,
11135
11529
  visibleTools,
@@ -11509,6 +11903,9 @@ ${completeContent}`;
11509
11903
  metadata.failure = receipt;
11510
11904
  } else {
11511
11905
  recovery.recordSuccess(call);
11906
+ const historicalFailures = new Set((this.session.audit ?? []).filter((event) => event.type === "tool" && event.outcome === "failure").map((event) => failureSignatureFromMetadata(event.metadata?.failure)).filter((signature) => signature !== void 0));
11907
+ const resolvedFailureSignatures = resolvableFailureSignatures(call).filter((signature) => historicalFailures.has(signature));
11908
+ if (resolvedFailureSignatures.length) metadata.resolvedFailureSignatures = resolvedFailureSignatures;
11512
11909
  }
11513
11910
  const evidenceProgress = recovery.recordEvidence(call, {
11514
11911
  toolCallId: call.id,
@@ -11622,7 +12019,7 @@ ${completeContent}`;
11622
12019
  }
11623
12020
  appendAudit(event) {
11624
12021
  const audit = this.session.audit ?? (this.session.audit = []);
11625
- audit.push({ id: randomUUID13(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
12022
+ audit.push({ id: randomUUID15(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
11626
12023
  if (audit.length > 5e3) audit.splice(0, audit.length - 5e3);
11627
12024
  }
11628
12025
  async acceptChangedFiles(paths) {
@@ -11643,7 +12040,7 @@ ${completeContent}`;
11643
12040
  const results = [];
11644
12041
  for (const command2 of this.config.agent.verifyCommands) {
11645
12042
  const call = {
11646
- id: `verify-${randomUUID13()}`,
12043
+ id: `verify-${randomUUID15()}`,
11647
12044
  name: "shell",
11648
12045
  arguments: { command: command2, cwd: this.workspace.primaryRoot }
11649
12046
  };
@@ -11691,6 +12088,30 @@ ${completeContent}`;
11691
12088
  await this.persist();
11692
12089
  return result;
11693
12090
  }
12091
+ async rollContextEpoch(reason, signal, emit) {
12092
+ let receipt;
12093
+ const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
12094
+ if (this.contextManager.shouldCompact(this.session, contextBudget)) {
12095
+ const compacted = await this.compactContext(
12096
+ "Create a boundary handoff for the next bounded context epoch.",
12097
+ signal,
12098
+ "automatic"
12099
+ );
12100
+ receipt = compacted.receipt;
12101
+ if (compacted.status === "compacted") await emit({ type: "context_compacted", ...compacted });
12102
+ }
12103
+ const rotated = rotateContextEpoch(this.session, reason, receipt);
12104
+ await this.persist();
12105
+ await emit({
12106
+ type: "context_epoch",
12107
+ index: rotated.current.index,
12108
+ previousIndex: rotated.previous.index,
12109
+ reason,
12110
+ inputTokens: rotated.previous.usage.inputTokens,
12111
+ outputTokens: rotated.previous.usage.outputTokens,
12112
+ handoff: rotated.handoff
12113
+ });
12114
+ }
11694
12115
  getContextStatus() {
11695
12116
  return this.contextManager.status(this.session);
11696
12117
  }
@@ -11722,8 +12143,14 @@ ${completeContent}`;
11722
12143
  toolOutputBudget() {
11723
12144
  const contextWindowTokens = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
11724
12145
  const activeContextTokens = this.contextManager.status(this.session, contextWindowTokens).activeTokens;
11725
- const remainingSessionTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
11726
- return dynamicToolOutputBudget(contextWindowTokens, activeContextTokens, remainingSessionTokens);
12146
+ const remainingLifetimeTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
12147
+ const epochBudget = this.config.agent.maxEpochTokens ?? this.config.agent.maxSessionTokens;
12148
+ const remainingEpochTokens = epochBudget - contextEpochTokens(this.session);
12149
+ return dynamicToolOutputBudget(
12150
+ contextWindowTokens,
12151
+ activeContextTokens,
12152
+ Math.min(remainingLifetimeTokens, remainingEpochTokens)
12153
+ );
11727
12154
  }
11728
12155
  async protectToolResult(result) {
11729
12156
  const metadata = { ...result.metadata ?? {} };
@@ -11779,7 +12206,7 @@ ${completeContent}`;
11779
12206
  };
11780
12207
  function message(role, content, extra = {}) {
11781
12208
  return {
11782
- id: randomUUID13(),
12209
+ id: randomUUID15(),
11783
12210
  role,
11784
12211
  content,
11785
12212
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11894,6 +12321,7 @@ function recordTokenUsage(session, providerUsage, estimatedInputTokens, estimate
11894
12321
  const outputTokens = outputActual ?? estimatedOutputTokens;
11895
12322
  session.usage.inputTokens += inputTokens;
11896
12323
  session.usage.outputTokens += outputTokens;
12324
+ recordContextEpochUsage(session, inputTokens, outputTokens);
11897
12325
  if (inputActual !== void 0) {
11898
12326
  session.usage.actualInputTokens = (session.usage.actualInputTokens ?? 0) + inputActual;
11899
12327
  } else {
@@ -11969,6 +12397,11 @@ ${content}` : content,
11969
12397
  ...failure ? { metadata: { failure } } : {}
11970
12398
  };
11971
12399
  }
12400
+ function failureSignatureFromMetadata(value) {
12401
+ if (!value || typeof value !== "object") return;
12402
+ const signature = value.signature;
12403
+ return typeof signature === "string" && signature ? signature : void 0;
12404
+ }
11972
12405
  function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable, duplicationAvailable, request, loadedProgressiveTools) {
11973
12406
  const eligible = tools.definitions().filter(
11974
12407
  (tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact") && (duplicationAvailable || tool.name !== "duplication_audit")
@@ -12176,7 +12609,7 @@ function integer(value, fallback, min, max) {
12176
12609
  }
12177
12610
 
12178
12611
  // src/agent/delegation.ts
12179
- import { randomUUID as randomUUID15 } from "node:crypto";
12612
+ import { randomUUID as randomUUID17 } from "node:crypto";
12180
12613
  import { join as join18 } from "node:path";
12181
12614
  import { z as z19 } from "zod";
12182
12615
 
@@ -12305,7 +12738,7 @@ function numeric(...values) {
12305
12738
  }
12306
12739
 
12307
12740
  // src/agent/team-store.ts
12308
- import { createHash as createHash16, randomUUID as randomUUID14 } from "node:crypto";
12741
+ import { createHash as createHash16, randomUUID as randomUUID16 } from "node:crypto";
12309
12742
  import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
12310
12743
  import { join as join16, resolve as resolve18 } from "node:path";
12311
12744
  import { z as z18 } from "zod";
@@ -12399,7 +12832,7 @@ var TeamRunStore = class {
12399
12832
  const now = (/* @__PURE__ */ new Date()).toISOString();
12400
12833
  const manifest = manifestSchema2.parse({
12401
12834
  version: 2,
12402
- id: randomUUID14(),
12835
+ id: randomUUID16(),
12403
12836
  workspace: this.workspace,
12404
12837
  objective: input2.objective,
12405
12838
  reviewer: input2.reviewer,
@@ -13205,7 +13638,7 @@ var DelegationManager = class {
13205
13638
  maxReviewRounds: 0
13206
13639
  });
13207
13640
  await emit?.({ type: "team_start", id: board.id, objective: task });
13208
- const writerId = randomUUID15();
13641
+ const writerId = randomUUID17();
13209
13642
  await emit?.({ type: "agent_queued", id: writerId, profile: profile.name, task, phase: "write" });
13210
13643
  const draft = await this.writerLane.createDraft(
13211
13644
  Math.min(this.team.maxWriterPatchBytes ?? 6e4, 12e4),
@@ -13575,7 +14008,7 @@ You are the only writer inside a disposable worktree. Make only the bounded requ
13575
14008
  const reviewer = reviewerOverride ?? this.team.reviewerProfile ?? "reviewer";
13576
14009
  const rounds = Math.max(0, this.team.maxReviewRounds ?? 1);
13577
14010
  const board = await this.teamStore?.create({ objective, reviewer, maxReviewRounds: rounds });
13578
- const runId = board?.id ?? randomUUID15();
14011
+ const runId = board?.id ?? randomUUID17();
13579
14012
  await emit?.({ type: "team_start", id: runId, objective });
13580
14013
  try {
13581
14014
  let results = await this.runBatch(board?.id, tasks, "work", emit, signal);
@@ -13633,7 +14066,7 @@ ${review.summary}`,
13633
14066
  }
13634
14067
  }
13635
14068
  async runBatch(runId, tasks, phase, emit, signal) {
13636
- const scheduled = tasks.map((task) => ({ id: randomUUID15(), task }));
14069
+ const scheduled = tasks.map((task) => ({ id: randomUUID17(), task }));
13637
14070
  for (const item of scheduled) {
13638
14071
  await emit?.({ type: "agent_queued", id: item.id, profile: item.task.profile, task: item.task.task, phase });
13639
14072
  }
@@ -13742,12 +14175,12 @@ Start the response with exactly VERDICT: ACCEPT when the evidence is sufficient,
13742
14175
  });
13743
14176
  }
13744
14177
  async peerMessage(runId, from, to, content, emit) {
13745
- const id = randomUUID15();
14178
+ const id = randomUUID17();
13746
14179
  if (runId) await this.teamStore?.recordMessage(runId, { id, from, to, content });
13747
14180
  await emit?.({ type: "agent_message", id, from, to, content });
13748
14181
  }
13749
14182
  async runOne(task, phase, emit, signal, retryOf, scheduledId) {
13750
- const id = scheduledId ?? randomUUID15();
14183
+ const id = scheduledId ?? randomUUID17();
13751
14184
  const profile = this.options.profiles.get(task.profile);
13752
14185
  const configuredRoute = this.team.routes?.[task.profile];
13753
14186
  const budgetMode = configuredRoute?.budgetMode ?? this.team.budgetMode ?? "observe";
@@ -14660,11 +15093,11 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14660
15093
  `);
14661
15094
  }
14662
15095
  printText(event) {
14663
- const { quiet, compact: compact2 } = this.options;
14664
- if (quiet) return;
15096
+ const { quiet, compact: compact3 } = this.options;
15097
+ if (quiet && event.type !== "needs_input") return;
14665
15098
  switch (event.type) {
14666
15099
  case "thinking":
14667
- if (!compact2) process.stderr.write(this.paint.dim(`${this.glyphs.meta} reasoning ${this.glyphs.separator} turn ${event.turn}
15100
+ if (!compact3) process.stderr.write(this.paint.dim(`${this.glyphs.meta} reasoning ${this.glyphs.separator} turn ${event.turn}
14668
15101
  `));
14669
15102
  break;
14670
15103
  case "context": {
@@ -14676,7 +15109,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14676
15109
  break;
14677
15110
  }
14678
15111
  case "prompt":
14679
- if (!compact2) {
15112
+ if (!compact3) {
14680
15113
  const partition = event.breakdown ? ` ${this.glyphs.separator} stable ${event.breakdown.stableTokens} ${this.glyphs.separator} dynamic ${event.breakdown.dynamicTokens} ${this.glyphs.separator} history ${event.breakdown.conversationTokens} ${this.glyphs.separator} tool results ${event.breakdown.toolResultTokens} ${this.glyphs.separator} retrieved ${event.breakdown.retrievedTokens} ${this.glyphs.separator} tools ${event.breakdown.toolSchemaTokens} ${this.glyphs.separator} output cap ${event.breakdown.outputAllowanceTokens}` : "";
14681
15114
  process.stderr.write(this.paint.dim(
14682
15115
  `${this.glyphs.meta} prompt ${this.glyphs.separator} ${event.intent} ${this.glyphs.separator} ~${event.estimatedTokens} estimated tokens${partition}
@@ -14712,7 +15145,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14712
15145
  }
14713
15146
  break;
14714
15147
  case "tasks":
14715
- if (!compact2) {
15148
+ if (!compact3) {
14716
15149
  const completed = event.tasks.filter((task) => task.status === "completed").length;
14717
15150
  process.stderr.write(this.paint.dim(`${this.glyphs.meta} plan ${this.glyphs.separator} ${completed}/${event.tasks.length} complete
14718
15151
  `));
@@ -14733,6 +15166,22 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14733
15166
  `
14734
15167
  );
14735
15168
  break;
15169
+ case "needs_input":
15170
+ process.stderr.write(this.paint.yellow(`${this.glyphs.meta} ${event.pending.question}
15171
+ `));
15172
+ event.pending.options.forEach((option2, index) => {
15173
+ process.stderr.write(this.paint.dim(
15174
+ ` ${index + 1}. ${option2.label}${option2.recommended ? " (recommended)" : ""} ${this.glyphs.separator} ${option2.impact}
15175
+ `
15176
+ ));
15177
+ });
15178
+ break;
15179
+ case "input_resolved":
15180
+ process.stderr.write(this.paint.dim(
15181
+ `${this.glyphs.meta} clarification resolved ${this.glyphs.separator} ${event.answer}
15182
+ `
15183
+ ));
15184
+ break;
14736
15185
  case "usage":
14737
15186
  case "permission":
14738
15187
  case "skill":
@@ -14743,6 +15192,8 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14743
15192
  case "agent_done":
14744
15193
  case "workflow":
14745
15194
  case "context_compacted":
15195
+ case "context_epoch":
15196
+ case "intent":
14746
15197
  break;
14747
15198
  case "done":
14748
15199
  this.printCompletion(event.completion);
@@ -14815,6 +15266,9 @@ function sessionSummary(session) {
14815
15266
  ...session.lastRun ? { lastRun: session.lastRun } : {},
14816
15267
  ...session.tokenLedger?.length ? { tokenLedger: session.tokenLedger } : {},
14817
15268
  ...session.contextCompactionReceipts?.length ? { contextCompactionReceipts: session.contextCompactionReceipts } : {},
15269
+ ...session.contextEpochs?.length ? { contextEpochs: session.contextEpochs } : {},
15270
+ ...session.intentAssessment ? { intentAssessment: session.intentAssessment } : {},
15271
+ ...session.pendingInput ? { pendingInput: session.pendingInput } : {},
14818
15272
  usage: session.usage
14819
15273
  };
14820
15274
  }
@@ -15507,7 +15961,7 @@ function ToolGlyph({ state, glyphs }) {
15507
15961
  if (state === "cancelled") return /* @__PURE__ */ jsx(Text, { color: theme.warning, children: glyphs.warning });
15508
15962
  return /* @__PURE__ */ jsx(Text, { color: theme.error, children: glyphs.error });
15509
15963
  }
15510
- function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = false, expandedToolId, compact: compact2 = false }) {
15964
+ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = false, expandedToolId, compact: compact3 = false }) {
15511
15965
  const theme = useTheme();
15512
15966
  const glyphs = resolveGlyphs(glyphMode);
15513
15967
  if (!items.length) {
@@ -15515,13 +15969,13 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15515
15969
  }
15516
15970
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
15517
15971
  if (item.kind === "user") {
15518
- return /* @__PURE__ */ jsxs(Box, { marginBottom: compact2 || item.clipped ? 0 : 1, children: [
15972
+ return /* @__PURE__ */ jsxs(Box, { marginBottom: compact3 || item.clipped ? 0 : 1, children: [
15519
15973
  /* @__PURE__ */ jsx(Box, { width: 2, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: glyphs.prompt }) }),
15520
15974
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.textStrong, wrap: "wrap", children: sanitizeTerminalText(item.text) })
15521
15975
  ] }, item.id);
15522
15976
  }
15523
15977
  if (item.kind === "assistant") {
15524
- return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: compact2 || item.clipped ? 0 : 1, children: [
15978
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: compact3 || item.clipped ? 0 : 1, children: [
15525
15979
  /* @__PURE__ */ jsxs(Text, { bold: true, color: theme.accent, children: [
15526
15980
  glyphs.brand,
15527
15981
  " ",
@@ -15537,7 +15991,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15537
15991
  }
15538
15992
  if (item.kind === "context") {
15539
15993
  const spans = item.spans ?? [];
15540
- const spanLimit = compact2 ? 2 : 3;
15994
+ const spanLimit = compact3 ? 2 : 3;
15541
15995
  const innerWidth = Math.max(1, safeWidth(width) - 2);
15542
15996
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
15543
15997
  /* @__PURE__ */ jsx(
@@ -15550,7 +16004,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15550
16004
  labelColor: theme.accent
15551
16005
  }
15552
16006
  ),
15553
- !compact2 && item.budgetReason ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`${glyphs.branchLast} ${sanitizeInlineTerminalText(item.budgetReason)}`, innerWidth) }) : null,
16007
+ !compact3 && item.budgetReason ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`${glyphs.branchLast} ${sanitizeInlineTerminalText(item.budgetReason)}`, innerWidth) }) : null,
15554
16008
  spans.slice(0, spanLimit).map((span, spanIndex) => {
15555
16009
  const lines = span.startLine === span.endLine ? `${span.startLine}` : `${span.startLine}-${span.endLine}`;
15556
16010
  const location = `${compactDisplayPath(sanitizeInlineTerminalText(span.path), 44)}:${lines}`;
@@ -15590,7 +16044,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15590
16044
  const duration = item.durationMs !== void 0 ? formatDuration(item.durationMs) : "";
15591
16045
  const detailText = [detail, duration].filter(Boolean).join(" ");
15592
16046
  const expanded = Boolean(item.output) && (showToolOutput || expandedToolId === item.id);
15593
- const verbose = expanded && item.output ? limitTerminalText(item.output, compact2 ? 24 : 80) : void 0;
16047
+ const verbose = expanded && item.output ? limitTerminalText(item.output, compact3 ? 24 : 80) : void 0;
15594
16048
  const disclosure = item.output ? expanded ? glyphs.expanded : glyphs.collapsed : "";
15595
16049
  const disclosureWidth = disclosure ? displayWidth(disclosure) + 1 : 0;
15596
16050
  const nameLimit = Math.max(1, Math.min(rowWidth - 2 - disclosureWidth, rowWidth < 64 ? rowWidth - 2 - disclosureWidth : 28));
@@ -15722,9 +16176,28 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15722
16176
  item.id
15723
16177
  );
15724
16178
  }
16179
+ if (item.kind === "clarification") {
16180
+ const rowWidth = safeWidth(width);
16181
+ const chinese = /[\u3400-\u9fff]/u.test(item.pending.question);
16182
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
16183
+ /* @__PURE__ */ jsx(Text, { bold: true, color: theme.warning, children: sanitizeTerminalText(item.pending.question) }),
16184
+ item.pending.options.map((option2, optionIndex) => {
16185
+ const label = `${optionIndex + 1}. ${sanitizeInlineTerminalText(option2.label)}${option2.recommended ? chinese ? "\uFF08\u63A8\u8350\uFF09" : " (recommended)" : ""}`;
16186
+ const impact = sanitizeInlineTerminalText(option2.impact);
16187
+ if (rowWidth < 48) {
16188
+ return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
16189
+ /* @__PURE__ */ jsx(Text, { color: option2.recommended ? theme.textStrong : theme.muted, children: truncateDisplay(label, Math.max(1, rowWidth - 2)) }),
16190
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`${glyphs.branchLast} ${impact}`, Math.max(1, (rowWidth - 2) * 2)) })
16191
+ ] }, option2.id);
16192
+ }
16193
+ return /* @__PURE__ */ jsx(Text, { color: option2.recommended ? theme.textStrong : theme.muted, children: truncateDisplay(`${label} ${glyphs.separator} ${impact}`, Math.max(1, rowWidth - 2)) }, option2.id);
16194
+ }),
16195
+ /* @__PURE__ */ jsx(Text, { color: theme.dim, children: chinese ? "\u56DE\u590D\u7F16\u53F7\u3001\u9009\u9879\u540D\u79F0\uFF0C\u6216\u7B80\u77ED\u8BF4\u660E\u4F60\u7684\u51B3\u5B9A\u3002" : "Reply with a number, option label, or a short custom decision." })
16196
+ ] }, item.id);
16197
+ }
15725
16198
  if (item.kind === "list") return /* @__PURE__ */ jsx(ListPanel, { title: item.title, entries: item.entries, width, glyphMode }, item.id);
15726
16199
  if (item.kind === "context-inspector") {
15727
- return /* @__PURE__ */ jsx(ContextInspector, { status: item.status, working: item.working, summary: item.summary, width, compact: compact2, glyphMode }, item.id);
16200
+ return /* @__PURE__ */ jsx(ContextInspector, { status: item.status, working: item.working, summary: item.summary, width, compact: compact3, glyphMode }, item.id);
15728
16201
  }
15729
16202
  if (item.kind === "theme") return /* @__PURE__ */ jsx(ThemePreview, { name: item.name, width, glyphs }, item.id);
15730
16203
  if (item.kind === "banner") {
@@ -15934,7 +16407,7 @@ function TaskRail({ tasks, width = 80, glyphMode = "auto", maxItems }) {
15934
16407
  ] }) : null
15935
16408
  ] });
15936
16409
  }
15937
- function PermissionCard({ call, category, width = 80, glyphMode = "auto", workspace, compact: compact2 = false }) {
16410
+ function PermissionCard({ call, category, width = 80, glyphMode = "auto", workspace, compact: compact3 = false }) {
15938
16411
  const theme = useTheme();
15939
16412
  const glyphs = resolveGlyphs(glyphMode);
15940
16413
  const summary = permissionSummary(call);
@@ -15971,7 +16444,7 @@ function PermissionCard({ call, category, width = 80, glyphMode = "auto", worksp
15971
16444
  rowWidth >= 64 ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx(InlineRow, { parts: shortcuts, width: innerWidth, separator: ` ${glyphs.separator} `, separatorColor: theme.border }) }) : rowWidth >= 28 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
15972
16445
  /* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
15973
16446
  /* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
15974
- ] }) : compact2 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
16447
+ ] }) : compact3 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
15975
16448
  /* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
15976
16449
  /* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
15977
16450
  ] }) : /* @__PURE__ */ jsx(Box, { paddingLeft: 2, flexDirection: "column", children: shortcuts.map((part) => part.color ? /* @__PURE__ */ jsx(Text, { color: part.color, children: truncateDisplay(part.text, innerWidth) }, part.text) : /* @__PURE__ */ jsx(Text, { children: truncateDisplay(part.text, innerWidth) }, part.text)) })
@@ -16232,7 +16705,7 @@ function MeterBar({ segments, total, width, glyphs }) {
16232
16705
  remainder ? /* @__PURE__ */ jsx(Text, { color: theme.border, children: glyphs.meterEmpty.repeat(remainder) }) : null
16233
16706
  ] });
16234
16707
  }
16235
- function ContextInspector({ status, working, summary, width, memory, connections, sources: sources2, compact: compact2 = false, minimal = false, glyphMode = "auto" }) {
16708
+ function ContextInspector({ status, working, summary, width, memory, connections, sources: sources2, compact: compact3 = false, minimal = false, glyphMode = "auto" }) {
16236
16709
  const theme = useTheme();
16237
16710
  const glyphs = resolveGlyphs(glyphMode);
16238
16711
  const hasCompactedContext = status.compactedMessages > 0 || Boolean(summary);
@@ -16253,10 +16726,16 @@ function ContextInspector({ status, working, summary, width, memory, connections
16253
16726
  { label: "summary", detail: hasCompactedContext ? `~${formatTokens(status.summaryTokens)} tokens ${glyphs.separator} ${status.compactedMessages} compacted${summary ? "" : ` ${glyphs.separator} facts`}` : "not created" },
16254
16727
  { label: "long-term", detail: memory ?? `retrieved by relevance ${glyphs.separator} untrusted context` }
16255
16728
  ];
16256
- if (!compact2 && working?.constraints.length) entries.push({ label: `constraints ${working.constraints.length}`, detail: working.constraints.slice(0, 2).join(` ${glyphs.separator} `) });
16257
- if (!compact2 && working?.decisions.length) entries.push({ label: `decisions ${working.decisions.length}`, detail: working.decisions.slice(0, 2).join(` ${glyphs.separator} `) });
16258
- if (!compact2 && working?.openQuestions.length) entries.push({ label: `open ${working.openQuestions.length}`, detail: working.openQuestions.slice(0, 2).join(` ${glyphs.separator} `), tone: "warning" });
16259
- if (!compact2 && working?.relevantFiles.length) entries.push({ label: "relevant files", detail: working.relevantFiles.map((file) => compactDisplayPath(sanitizeInlineTerminalText(file), 28)).join(` ${glyphs.separator} `) });
16729
+ if (status.epochIndex !== void 0 && status.epochCount !== void 0 && status.epochTokens !== void 0 && status.epochBudget !== void 0 && status.lifetimeTokens !== void 0 && status.lifetimeBudget !== void 0) {
16730
+ entries.splice(1, 0, {
16731
+ label: "epoch",
16732
+ detail: `#${status.epochIndex} ${formatTokens(status.epochTokens)}/${formatTokens(status.epochBudget)} ${glyphs.separator} lifetime ${formatTokens(status.lifetimeTokens)}/${formatTokens(status.lifetimeBudget)}`
16733
+ });
16734
+ }
16735
+ if (!compact3 && working?.constraints.length) entries.push({ label: `constraints ${working.constraints.length}`, detail: working.constraints.slice(0, 2).join(` ${glyphs.separator} `) });
16736
+ if (!compact3 && working?.decisions.length) entries.push({ label: `decisions ${working.decisions.length}`, detail: working.decisions.slice(0, 2).join(` ${glyphs.separator} `) });
16737
+ if (!compact3 && working?.openQuestions.length) entries.push({ label: `open ${working.openQuestions.length}`, detail: working.openQuestions.slice(0, 2).join(` ${glyphs.separator} `), tone: "warning" });
16738
+ if (!compact3 && working?.relevantFiles.length) entries.push({ label: "relevant files", detail: working.relevantFiles.map((file) => compactDisplayPath(sanitizeInlineTerminalText(file), 28)).join(` ${glyphs.separator} `) });
16260
16739
  if (sources2?.length) {
16261
16740
  const pinned = sources2.filter((source) => source.state === "pinned");
16262
16741
  const muted = sources2.filter((source) => source.state === "muted");
@@ -16290,7 +16769,7 @@ function ContextInspector({ status, working, summary, width, memory, connections
16290
16769
  /* @__PURE__ */ jsx(MeterBar, { segments, total: meterTotal, width: meterWidth, glyphs })
16291
16770
  ] }) : null
16292
16771
  ] }),
16293
- /* @__PURE__ */ jsx(ListPanel, { title: "", hideTitle: true, entries, width, glyphMode })
16772
+ /* @__PURE__ */ jsx(ListPanel, { title: "", hideTitle: true, entries, width: Math.max(1, rowWidth - 2), glyphMode })
16294
16773
  ] });
16295
16774
  }
16296
16775
  function ThemePreview({ name, width, glyphs }) {
@@ -16377,8 +16856,8 @@ function Banner({ model, engine, workspace, version, width, glyphs }) {
16377
16856
  function UpdateNotice({ current, latest, command: command2, highlights, width, glyphs }) {
16378
16857
  const theme = useTheme();
16379
16858
  const availableWidth = safeWidth(width);
16380
- const compact2 = availableWidth < 48;
16381
- const parts = compact2 ? [
16859
+ const compact3 = availableWidth < 48;
16860
+ const parts = compact3 ? [
16382
16861
  { text: `${glyphs.up} `, color: theme.accent, bold: true },
16383
16862
  { text: `v${current}`, color: theme.dim, bold: false },
16384
16863
  { text: ` ${glyphs.arrow} `, color: theme.muted, bold: false },
@@ -16399,7 +16878,7 @@ function UpdateNotice({ current, latest, command: command2, highlights, width, g
16399
16878
  const bullets = bulletWidth > 0 ? (highlights ?? []).map((line) => truncateDisplay(sanitizeInlineTerminalText(line), bulletWidth)) : [];
16400
16879
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [
16401
16880
  /* @__PURE__ */ jsx(Box, { children: truncated ? /* @__PURE__ */ jsx(Text, { color: theme.muted, children: rendered }) : parts.map((part, index) => /* @__PURE__ */ jsx(Text, { color: part.color, bold: part.bold, children: part.text }, index)) }),
16402
- compact2 ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(` run ${command2}`, availableWidth) }) : null,
16881
+ compact3 ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(` run ${command2}`, availableWidth) }) : null,
16403
16882
  bullets.map((line, index) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: `${bulletPrefix}${line}` }, index))
16404
16883
  ] });
16405
16884
  }
@@ -17307,9 +17786,9 @@ function tailText(value, width, maxRows) {
17307
17786
  return `${marker}
17308
17787
  ${selected.join("\n")}`;
17309
17788
  }
17310
- function estimateTimelineItemRows(item, { width, compact: compact2 = false, showToolOutput = false, expandedToolId }) {
17789
+ function estimateTimelineItemRows(item, { width, compact: compact3 = false, showToolOutput = false, expandedToolId }) {
17311
17790
  const rowWidth = Math.max(1, Math.floor(width));
17312
- const gap = compact2 ? 0 : 1;
17791
+ const gap = compact3 ? 0 : 1;
17313
17792
  if (item.kind === "user") return wrappedRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
17314
17793
  if (item.kind === "assistant") {
17315
17794
  return 1 + richTextRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
@@ -17321,7 +17800,7 @@ function estimateTimelineItemRows(item, { width, compact: compact2 = false, show
17321
17800
  const detail = item.errorDetail || item.detail;
17322
17801
  const detailRows = narrow && detail ? 1 : 0;
17323
17802
  const metaRows = item.meta ? 1 : 0;
17324
- const outputRows = (showToolOutput || item.id === expandedToolId) && item.output ? Math.min(compact2 ? 25 : 81, richTextRows(item.output, Math.max(1, rowWidth - 2))) : 0;
17803
+ const outputRows = (showToolOutput || item.id === expandedToolId) && item.output ? Math.min(compact3 ? 25 : 81, richTextRows(item.output, Math.max(1, rowWidth - 2))) : 0;
17325
17804
  return 1 + detailRows + metaRows + outputRows;
17326
17805
  }
17327
17806
  if (item.kind === "list") {
@@ -17335,7 +17814,7 @@ function estimateTimelineItemRows(item, { width, compact: compact2 = false, show
17335
17814
  if (item.kind === "theme") return 3;
17336
17815
  if (item.kind === "context") {
17337
17816
  const metaRows = rowWidth < 64 ? 2 : 1;
17338
- const spanLimit = compact2 ? 2 : 3;
17817
+ const spanLimit = compact3 ? 2 : 3;
17339
17818
  const spanCount = Math.min(item.spans?.length ?? 0, spanLimit);
17340
17819
  const moreRow = (item.spans?.length ?? 0) > spanLimit ? 1 : 0;
17341
17820
  const degradationRows = item.degradation ? metaRows : 0;
@@ -17565,7 +18044,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17565
18044
  const colorEnabled = config.ui.color && !process.env.NO_COLOR;
17566
18045
  const [theme, setTheme] = useState2(() => resolveThemeWithColor(config.ui.theme, colorEnabled));
17567
18046
  const [themeCatalogRevision, setThemeCatalogRevision] = useState2(0);
17568
- const [compact2, setCompact] = useState2(config.ui.compact);
18047
+ const [compact3, setCompact] = useState2(config.ui.compact);
17569
18048
  const [interactionMode, setInteractionMode] = useState2(planMode ? "plan" : askMode ? "ask" : "build");
17570
18049
  const [input2, setInput] = useState2("");
17571
18050
  const [busy, setBusy] = useState2(false);
@@ -17602,6 +18081,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17602
18081
  const controller = useRef2(void 0);
17603
18082
  const processing = useRef2(false);
17604
18083
  const queued = useRef2([]);
18084
+ const clarificationBacklog = useRef2([]);
17605
18085
  const stopRequested = useRef2(false);
17606
18086
  const startedInitial = useRef2(false);
17607
18087
  const lastSubmitted = useRef2(void 0);
@@ -17842,6 +18322,26 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17842
18322
  append({ id: nextId(), kind: "compaction", messages: event.omittedMessages, tokens: event.summaryTokens });
17843
18323
  refreshSession();
17844
18324
  break;
18325
+ case "context_epoch":
18326
+ append({
18327
+ id: nextId(),
18328
+ kind: "notice",
18329
+ tone: "info",
18330
+ text: `Context epoch ${event.previousIndex} \u2192 ${event.index}${separator}${event.reason.replace("_", " ")}${separator}${event.inputTokens + event.outputTokens} tokens preserved in the lifetime ledger.`
18331
+ });
18332
+ refreshSession();
18333
+ break;
18334
+ case "needs_input":
18335
+ append({ id: nextId(), kind: "clarification", pending: event.pending });
18336
+ refreshSession();
18337
+ break;
18338
+ case "input_resolved":
18339
+ append({ id: nextId(), kind: "notice", tone: "success", text: `Clarification resolved${separator}${event.answer}` });
18340
+ refreshSession();
18341
+ break;
18342
+ case "intent":
18343
+ refreshSession();
18344
+ break;
17845
18345
  case "usage":
17846
18346
  refreshSession();
17847
18347
  break;
@@ -18274,7 +18774,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18274
18774
  }
18275
18775
  if (command2 === "density") {
18276
18776
  const normalized = argument.toLocaleLowerCase();
18277
- const next = normalized === "compact" ? true : normalized === "comfortable" || normalized === "normal" ? false : normalized === "" || normalized === "toggle" ? !compact2 : void 0;
18777
+ const next = normalized === "compact" ? true : normalized === "comfortable" || normalized === "normal" ? false : normalized === "" || normalized === "toggle" ? !compact3 : void 0;
18278
18778
  if (next === void 0) throw new Error("Usage: /density [compact|comfortable]");
18279
18779
  setCompact(next);
18280
18780
  await saveUiPreference({ compact: next });
@@ -18367,7 +18867,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18367
18867
  })));
18368
18868
  return true;
18369
18869
  }
18370
- }, [append, appendList, compact2, config, ellipsis, exit, extensions, interactionMode, refreshSession, requestPermission, runner, separator, showToolOutput, tasks, theme, workflows]);
18870
+ }, [append, appendList, compact3, config, ellipsis, exit, extensions, interactionMode, refreshSession, requestPermission, runner, separator, showToolOutput, tasks, theme, workflows]);
18371
18871
  const submit = useCallback(async (raw, mode = "normal") => {
18372
18872
  const trimmed = raw.trim();
18373
18873
  if (!trimmed) return;
@@ -18382,6 +18882,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18382
18882
  if (processing.current && isExitCommand(trimmed)) {
18383
18883
  stopRequested.current = true;
18384
18884
  queued.current = [];
18885
+ clarificationBacklog.current = [];
18385
18886
  setQueue([]);
18386
18887
  controller.current?.abort();
18387
18888
  exit();
@@ -18469,6 +18970,31 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18469
18970
  const snapshot = snapshotSession(nextSession);
18470
18971
  setSession(snapshot);
18471
18972
  setTasks(snapshot.tasks.map((task) => ({ ...task })));
18973
+ if (snapshot.pendingInput) {
18974
+ const deferred = queued.current.length;
18975
+ clarificationBacklog.current.push(...queued.current);
18976
+ queued.current = [];
18977
+ setQueue([]);
18978
+ if (deferred) append({
18979
+ id: nextId(),
18980
+ kind: "notice",
18981
+ tone: "info",
18982
+ text: `Paused ${deferred} queued follow-up${deferred === 1 ? "" : "s"} for clarification; they will resume after the answer.`
18983
+ });
18984
+ break;
18985
+ }
18986
+ if (clarificationBacklog.current.length) {
18987
+ const resumed = clarificationBacklog.current.length;
18988
+ queued.current = [...clarificationBacklog.current, ...queued.current];
18989
+ clarificationBacklog.current = [];
18990
+ setQueue([...queued.current]);
18991
+ append({
18992
+ id: nextId(),
18993
+ kind: "notice",
18994
+ tone: "success",
18995
+ text: `Clarification resolved; resuming ${resumed} queued follow-up${resumed === 1 ? "" : "s"}.`
18996
+ });
18997
+ }
18472
18998
  } catch (error) {
18473
18999
  const message2 = error instanceof Error ? error.message : String(error);
18474
19000
  if (!abortController.signal.aborted && message2 !== lastEventError.current) {
@@ -18481,8 +19007,9 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18481
19007
  }
18482
19008
  }
18483
19009
  if (abortController.signal.aborted || stopRequested.current) {
18484
- const discarded = queued.current.length;
19010
+ const discarded = queued.current.length + clarificationBacklog.current.length;
18485
19011
  queued.current = [];
19012
+ clarificationBacklog.current = [];
18486
19013
  setQueue([]);
18487
19014
  if (discarded) append({ id: nextId(), kind: "notice", text: `Discarded ${discarded} queued follow-up${discarded === 1 ? "" : "s"}.` });
18488
19015
  break;
@@ -18731,7 +19258,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18731
19258
  const tokenTotal = session.usage.inputTokens + session.usage.outputTokens;
18732
19259
  const contextStatus = runner.getContextStatus();
18733
19260
  const frame = spinnerFrames()[frameIndex % spinnerFrames().length];
18734
- const compactUi = compact2 || terminalHeight < 28;
19261
+ const compactUi = compact3 || terminalHeight < 28;
18735
19262
  const constrainedHeight = terminalHeight < 18;
18736
19263
  const compactComposer = terminalHeight < 18;
18737
19264
  const minimalInspector = terminalHeight < 22;
@@ -18947,6 +19474,9 @@ function initialTimeline(session, banner, setupProblem) {
18947
19474
  text: `Contract ${session.taskContract.state} | ${satisfied}/${required.length} accepted`
18948
19475
  });
18949
19476
  }
19477
+ if (session.pendingInput) {
19478
+ items.push({ id: `pending-input-${session.pendingInput.id}`, kind: "clarification", pending: session.pendingInput });
19479
+ }
18950
19480
  if (setupProblem && items.length <= 1) items.push({ id: nextId(), kind: "notice", tone: "error", text: setupProblem });
18951
19481
  return items;
18952
19482
  }
@@ -18992,6 +19522,35 @@ function snapshotSession(source) {
18992
19522
  relevantFiles: [...source.workingMemory.relevantFiles]
18993
19523
  }
18994
19524
  } : {},
19525
+ ...source.contextEpochs ? {
19526
+ contextEpochs: source.contextEpochs.map((epoch) => ({
19527
+ ...epoch,
19528
+ usage: { ...epoch.usage },
19529
+ ...epoch.handoff ? {
19530
+ handoff: {
19531
+ ...epoch.handoff,
19532
+ ...epoch.handoff.contract ? {
19533
+ contract: {
19534
+ ...epoch.handoff.contract,
19535
+ required: epoch.handoff.contract.required.map((criterion2) => ({
19536
+ ...criterion2,
19537
+ evidenceRefs: [...criterion2.evidenceRefs]
19538
+ }))
19539
+ }
19540
+ } : {},
19541
+ unresolvedFailures: epoch.handoff.unresolvedFailures.map((failure) => ({ ...failure })),
19542
+ changedFiles: [...epoch.handoff.changedFiles],
19543
+ checks: epoch.handoff.checks.map((check) => ({ ...check }))
19544
+ }
19545
+ } : {}
19546
+ }))
19547
+ } : {},
19548
+ ...source.intentAssessment ? {
19549
+ intentAssessment: { ...source.intentAssessment, reasons: [...source.intentAssessment.reasons] }
19550
+ } : {},
19551
+ ...source.pendingInput ? {
19552
+ pendingInput: { ...source.pendingInput, options: source.pendingInput.options.map((option2) => ({ ...option2 })) }
19553
+ } : {},
18995
19554
  usage: { ...source.usage }
18996
19555
  };
18997
19556
  }
@@ -19038,17 +19597,17 @@ function composerAttachments(value) {
19038
19597
  const paths = [...value.matchAll(/(?:^|\s)@([^\s]+)/g)].map((match) => match[1]).filter((path) => Boolean(path));
19039
19598
  return [...new Set(paths)].slice(-3);
19040
19599
  }
19041
- function permissionRows(width, hasCwd, compact2) {
19600
+ function permissionRows(width, hasCwd, compact3) {
19042
19601
  const content = 3 + (hasCwd ? 1 : 0);
19043
19602
  if (width >= 64) return content + 2;
19044
19603
  if (width >= 28) return content + 3;
19045
- if (compact2) return content + 3;
19604
+ if (compact3) return content + 3;
19046
19605
  return content + 5;
19047
19606
  }
19048
- function contextInspectorRows(session, compact2, width, minimal) {
19607
+ function contextInspectorRows(session, compact3, width, minimal) {
19049
19608
  if (minimal) return 2;
19050
19609
  const working = session.workingMemory;
19051
- const entries = 5 + (compact2 ? 0 : (working?.constraints.length ? 1 : 0) + (working?.decisions.length ? 1 : 0) + (working?.openQuestions.length ? 1 : 0) + (working?.relevantFiles.length ? 1 : 0));
19610
+ const entries = 6 + (compact3 ? 0 : (working?.constraints.length ? 1 : 0) + (working?.decisions.length ? 1 : 0) + (working?.openQuestions.length ? 1 : 0) + (working?.relevantFiles.length ? 1 : 0));
19052
19611
  return 2 + entries * (width < 52 ? 2 : 1);
19053
19612
  }
19054
19613
  function contextInspectorStatus(status) {
@@ -19058,7 +19617,13 @@ function contextInspectorStatus(status) {
19058
19617
  activeTokens: status.activeTokens,
19059
19618
  summaryTokens: status.summaryTokens,
19060
19619
  toolTokens: status.toolTokens,
19061
- compactedMessages: status.compactedMessages
19620
+ compactedMessages: status.compactedMessages,
19621
+ epochIndex: status.epochIndex,
19622
+ epochCount: status.epochCount,
19623
+ epochTokens: status.epochTokens,
19624
+ epochBudget: status.epochBudget,
19625
+ lifetimeTokens: status.lifetimeTokens,
19626
+ lifetimeBudget: status.lifetimeBudget
19062
19627
  };
19063
19628
  }
19064
19629
  function toolDetail2(call) {
@@ -19100,14 +19665,14 @@ function WorkspacePreparationView({
19100
19665
  const theme = useTheme();
19101
19666
  const safeWidth2 = Math.max(1, Math.floor(width));
19102
19667
  const innerWidth = Math.max(1, safeWidth2 - (safeWidth2 >= 24 ? 4 : 0));
19103
- const compact2 = safeWidth2 < 48;
19668
+ const compact3 = safeWidth2 < 48;
19104
19669
  const constrained = height < 14;
19105
19670
  const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
19106
19671
  const spinner = (ascii ? [".", "o", "O", "o"] : ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"])[frame % (ascii ? 4 : 6)];
19107
19672
  const separator = ascii ? "|" : "\xB7";
19108
19673
  const brand = ascii ? "*" : PRODUCT_MARK;
19109
19674
  const phase = readiness ? "ready" : error ? "error" : progress.phase;
19110
- const phaseLabel = preparationLabel(phase, progress, readiness, compact2);
19675
+ const phaseLabel = preparationLabel(phase, progress, readiness, compact3);
19111
19676
  const detail = preparationDetail(phase, progress, readiness, error);
19112
19677
  const modelLine = `model ${sanitizeTerminalText(model)}`;
19113
19678
  const workspaceLine = `workspace ${compactDisplayPath(sanitizeTerminalText(workspace), Math.max(1, innerWidth - 10))}`;
@@ -19119,14 +19684,14 @@ function WorkspacePreparationView({
19119
19684
  " ",
19120
19685
  PRODUCT_NAME.toUpperCase()
19121
19686
  ] }),
19122
- !compact2 && !constrained ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
19687
+ !compact3 && !constrained ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
19123
19688
  " ",
19124
19689
  separator,
19125
19690
  " LOCAL CONTEXT"
19126
19691
  ] }) : null
19127
19692
  ] }),
19128
19693
  !constrained ? /* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: truncateDisplay("Ground the workspace before the first request.", innerWidth) }) : null,
19129
- !constrained && !compact2 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(`${modelLine} ${separator} ${workspaceLine}`, innerWidth) }) : !constrained ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
19694
+ !constrained && !compact3 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(`${modelLine} ${separator} ${workspaceLine}`, innerWidth) }) : !constrained ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
19130
19695
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(modelLine, innerWidth) }),
19131
19696
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) })
19132
19697
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) }),
@@ -19140,10 +19705,10 @@ function WorkspacePreparationView({
19140
19705
  step2.glyph,
19141
19706
  " "
19142
19707
  ] }),
19143
- /* @__PURE__ */ jsx4(Text4, { bold: step2.state === "active", color: step2.state === "pending" ? theme.dim : theme.textStrong, children: truncateDisplay(step2.label, compact2 ? 8 : 10) }),
19708
+ /* @__PURE__ */ jsx4(Text4, { bold: step2.state === "active", color: step2.state === "pending" ? theme.dim : theme.textStrong, children: truncateDisplay(step2.label, compact3 ? 8 : 10) }),
19144
19709
  /* @__PURE__ */ jsxs4(Text4, { color: step2.state === "active" ? theme.muted : theme.dim, children: [
19145
19710
  " ",
19146
- truncateDisplay(step2.detail, Math.max(1, innerWidth - displayWidth(step2.glyph) - (compact2 ? 12 : 14)))
19711
+ truncateDisplay(step2.detail, Math.max(1, innerWidth - displayWidth(step2.glyph) - (compact3 ? 12 : 14)))
19147
19712
  ] })
19148
19713
  ] }, step2.id)) }),
19149
19714
  /* @__PURE__ */ jsxs4(Box3, { marginTop: 1, children: [
@@ -19284,9 +19849,9 @@ async function runWorkspacePreparation(engine, config, options = {}) {
19284
19849
  await instance.waitUntilExit();
19285
19850
  return result ?? { status: "cancelled" };
19286
19851
  }
19287
- function preparationLabel(phase, progress, readiness, compact2 = false) {
19852
+ function preparationLabel(phase, progress, readiness, compact3 = false) {
19288
19853
  if (phase === "ready") {
19289
- if (compact2) return "Index verified";
19854
+ if (compact3) return "Index verified";
19290
19855
  return readiness?.rebuilt ? "Workspace index created and verified" : "Workspace index verified";
19291
19856
  }
19292
19857
  if (phase === "error") return "Workspace preparation failed";
@@ -19690,7 +20255,7 @@ function OnboardingFlow({ initialConfig, saveConfig, onFinish }) {
19690
20255
  }, [finish, saveConfig, state]);
19691
20256
  return /* @__PURE__ */ jsx5(OnboardingScreen, { state, dispatch, width, compact: compactHeight });
19692
20257
  }
19693
- function OnboardingScreen({ state, dispatch, width, compact: compact2 = false }) {
20258
+ function OnboardingScreen({ state, dispatch, width, compact: compact3 = false }) {
19694
20259
  const theme = useTheme();
19695
20260
  const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
19696
20261
  const marker = ascii ? ">" : "\u203A";
@@ -19707,14 +20272,14 @@ function OnboardingScreen({ state, dispatch, width, compact: compact2 = false })
19707
20272
  ] }),
19708
20273
  /* @__PURE__ */ jsx5(Text6, { color: theme.border, children: truncateDisplay(stageDivider(ascii, headerWidth), headerWidth) }),
19709
20274
  /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(stage.name, headerWidth) }),
19710
- !compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
20275
+ !compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
19711
20276
  /* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: truncateDisplay(titleForStep(state.step), headerWidth) }),
19712
- !compact2 ? /* @__PURE__ */ jsx5(Text6, { color: theme.muted, wrap: "wrap", children: descriptionForStep(state) }) : null,
20277
+ !compact3 ? /* @__PURE__ */ jsx5(Text6, { color: theme.muted, wrap: "wrap", children: descriptionForStep(state) }) : null,
19713
20278
  summary ? /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(summary, headerWidth) }) : null,
19714
- !compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
19715
- state.step === "method" ? /* @__PURE__ */ jsx5(OptionList, { options: methods, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
19716
- state.step === "official-provider" ? /* @__PURE__ */ jsx5(OptionList, { options: officialProviders, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
19717
- state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
20279
+ !compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
20280
+ state.step === "method" ? /* @__PURE__ */ jsx5(OptionList, { options: methods, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
20281
+ state.step === "official-provider" ? /* @__PURE__ */ jsx5(OptionList, { options: officialProviders, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
20282
+ state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
19718
20283
  inputField ? /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
19719
20284
  /* @__PURE__ */ jsxs5(Box4, { children: [
19720
20285
  /* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: inputField.label }),
@@ -19742,18 +20307,18 @@ function OnboardingScreen({ state, dispatch, width, compact: compact2 = false })
19742
20307
  "! ",
19743
20308
  truncateDisplay(state.error, Math.max(1, headerWidth - 2))
19744
20309
  ] }) : null,
19745
- !compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
20310
+ !compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
19746
20311
  /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: footerForStep(state, headerWidth) })
19747
20312
  ] });
19748
20313
  }
19749
- function OptionList({ options, selected, marker, width, compact: compact2 }) {
20314
+ function OptionList({ options, selected, marker, width, compact: compact3 }) {
19750
20315
  const theme = useTheme();
19751
- return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((option, index) => {
20316
+ return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((option2, index) => {
19752
20317
  const active = index === selected;
19753
20318
  const prefix = active ? `${marker} ` : " ";
19754
20319
  const available = Math.max(1, width - displayWidth(prefix) - (active ? 2 : 0));
19755
- const label = `${prefix}${truncateDisplay(option.label, available)}`;
19756
- return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", marginBottom: compact2 || index === options.length - 1 ? 0 : 1, children: [
20320
+ const label = `${prefix}${truncateDisplay(option2.label, available)}`;
20321
+ return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", marginBottom: compact3 || index === options.length - 1 ? 0 : 1, children: [
19757
20322
  /* @__PURE__ */ jsx5(
19758
20323
  Text6,
19759
20324
  {
@@ -19763,8 +20328,8 @@ function OptionList({ options, selected, marker, width, compact: compact2 }) {
19763
20328
  children: active ? padDisplay(label, width) : label
19764
20329
  }
19765
20330
  ),
19766
- !compact2 && width >= 36 || active ? /* @__PURE__ */ jsx5(Box4, { marginLeft: 2, width: Math.max(1, width - 2), children: /* @__PURE__ */ jsx5(Text6, { color: active ? theme.muted : theme.dim, wrap: "wrap", children: option.detail }) }) : null
19767
- ] }, option.label);
20331
+ !compact3 && width >= 36 || active ? /* @__PURE__ */ jsx5(Box4, { marginLeft: 2, width: Math.max(1, width - 2), children: /* @__PURE__ */ jsx5(Text6, { color: active ? theme.muted : theme.dim, wrap: "wrap", children: option2.detail }) }) : null
20332
+ ] }, option2.label);
19768
20333
  }) });
19769
20334
  }
19770
20335
  function Confirmation({ state, width }) {
@@ -21465,7 +22030,7 @@ process.on("warning", (warning) => {
21465
22030
  var program = new Command();
21466
22031
  program.enablePositionalOptions();
21467
22032
  program.name(PRODUCT_COMMAND).description("A context-first, model-agnostic coding agent with an auditable workspace.").version(package_default.version).showSuggestionAfterError();
21468
- program.argument("[prompt...]", "instruction for the agent").option("-p, --print", "run once and print the result").option("-a, --ask", "retrieval and inspection mode; mutation tools are denied").option("--plan", "read-only planning mode; propose changes without mutating the workspace").option("-q, --quiet", "print only the final response in text mode").addOption(new Option("--output-format <format>", "text, json, or stream-json").choices(["text", "json", "stream-json"]).default("text")).option("--compact", "reduce progress output in print mode").option("--yes", "approve all non-denied tool requests for this run").option("--auto-edit", "approve read/write requests and ask before shell/Git/network").option("--trust-project-config", "allow executable and security-sensitive settings from project config").option("--queue <prompt>", "run an additional prompt after the first one", collect, []).option("-w, --workspace <path>", "primary workspace root", process.cwd()).option("--add-workspace <path>", "additional workspace root", collect, []).option("--config <path>", "explicit config file").option("--provider <provider>", "model provider").option("--model <model>", "model identifier").option("--base-url <url>", "OpenAI-compatible or provider base URL").option("--max-turns <n>", "maximum agent turns").option("--token-budget <n>", "maximum cumulative session tokens").option("--resume [session]", "resume a session by id or prefix").option("-c, --continue", "resume the latest session").option("--no-color", "disable color output").option("--no-checkpoint", "disable pre-mutation checkpoints for this run").action(async (prompts, options) => {
22033
+ program.argument("[prompt...]", "instruction for the agent").option("-p, --print", "run once and print the result").option("-a, --ask", "retrieval and inspection mode; mutation tools are denied").option("--plan", "read-only planning mode; propose changes without mutating the workspace").option("-q, --quiet", "print only the final response in text mode").addOption(new Option("--output-format <format>", "text, json, or stream-json").choices(["text", "json", "stream-json"]).default("text")).option("--compact", "reduce progress output in print mode").option("--yes", "approve all non-denied tool requests for this run").option("--auto-edit", "approve read/write requests and ask before shell/Git/network").option("--trust-project-config", "allow executable and security-sensitive settings from project config").option("--queue <prompt>", "run an additional prompt after the first one", collect, []).option("-w, --workspace <path>", "primary workspace root", process.cwd()).option("--add-workspace <path>", "additional workspace root", collect, []).option("--config <path>", "explicit config file").option("--provider <provider>", "model provider").option("--model <model>", "model identifier").option("--base-url <url>", "OpenAI-compatible or provider base URL").option("--max-turns <n>", "maximum agent turns").option("--epoch-token-budget <n>", "maximum tokens before an internal context handoff").option("--token-budget <n>", "maximum lifetime tokens across the resumed session").option("--resume [session]", "resume a session by id or prefix").option("-c, --continue", "resume the latest session").option("--no-color", "disable color output").option("--no-checkpoint", "disable pre-mutation checkpoints for this run").action(async (prompts, options) => {
21469
22034
  await runChat(prompts, options);
21470
22035
  });
21471
22036
  program.command("init").description("Create a project-local config (preserving an existing .mosaic namespace)").option("-w, --workspace <path>", "workspace root").option("--provider <provider>", "openai, anthropic, gemini, or compatible", "openai").option("--model <model>", "model identifier").option("--base-url <url>", "provider base URL").option("--api-key <key>", "store a provider key in project config (prefer env vars)").option("--index", "build the index after writing config").option("--yes", "use defaults without prompting").action(async (options) => {
@@ -22156,6 +22721,7 @@ async function runChat(prompts, options) {
22156
22721
  requestPermission
22157
22722
  });
22158
22723
  for (const queued of options.queue) {
22724
+ if (session.pendingInput) break;
22159
22725
  session = await runner.run(queued, {
22160
22726
  askMode: options.ask === true || options.plan === true,
22161
22727
  ...options.plan ? { turnInstructions: PLAN_MODE_INSTRUCTIONS } : {},
@@ -22165,6 +22731,7 @@ async function runChat(prompts, options) {
22165
22731
  });
22166
22732
  }
22167
22733
  reporter.finish(session);
22734
+ if (session.pendingInput) process.exitCode = 2;
22168
22735
  } catch (error) {
22169
22736
  reporter.fail(error);
22170
22737
  process.exitCode = 1;
@@ -22333,6 +22900,7 @@ async function runtimeConfig(workspaceInput, options) {
22333
22900
  agent: {
22334
22901
  ...loaded.agent,
22335
22902
  ...options.checkpoint === false ? { checkpointBeforeWrite: false } : {},
22903
+ ...options.epochTokenBudget ? { maxEpochTokens: positiveInt(options.epochTokenBudget, loaded.agent.maxEpochTokens ?? loaded.agent.maxSessionTokens) } : {},
22336
22904
  ...options.tokenBudget ? { maxSessionTokens: positiveInt(options.tokenBudget, loaded.agent.maxSessionTokens) } : {}
22337
22905
  },
22338
22906
  ui: { ...loaded.ui, ...options.color === false ? { color: false } : {} }
@@ -22482,6 +23050,7 @@ function runtimeOptions(options) {
22482
23050
  const provider = options.provider ?? root.provider;
22483
23051
  const model = options.model ?? root.model;
22484
23052
  const baseUrl = options.baseUrl ?? root.baseUrl;
23053
+ const epochTokenBudget = options.epochTokenBudget ?? root.epochTokenBudget;
22485
23054
  const tokenBudget = options.tokenBudget ?? root.tokenBudget;
22486
23055
  return {
22487
23056
  addWorkspace: [...root.addWorkspace ?? [], ...options.addWorkspace ?? []],
@@ -22490,6 +23059,7 @@ function runtimeOptions(options) {
22490
23059
  ...model ? { model } : {},
22491
23060
  ...baseUrl ? { baseUrl } : {},
22492
23061
  ...options.maxTokens ? { maxTokens: options.maxTokens } : {},
23062
+ ...epochTokenBudget ? { epochTokenBudget } : {},
22493
23063
  ...tokenBudget ? { tokenBudget } : {},
22494
23064
  ...root.color !== void 0 ? { color: root.color } : {},
22495
23065
  ...root.checkpoint !== void 0 ? { checkpoint: root.checkpoint } : {},