@skein-code/cli 0.3.23 → 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.23",
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,15 @@ 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",
244
+ "Context compaction now rebuilds authoritative task, working-state, verification, permission, failure, and artifact facts outside generated narrative",
245
+ "Automatic compaction runs only when three predicted prompt reuses produce positive net token savings while explicit compact commands remain available",
246
+ "Compaction provider usage is included in session totals with separate content-free actual or estimated receipts",
247
+ "Empty or lossy narratives cannot erase deterministic facts, and the complete persisted transcript remains unchanged",
239
248
  "Normal chat startup now exposes a compact mcp_activate catalog instead of eagerly connecting MCP servers",
240
249
  "MCP activation connects one selected server, discovers remote tools, and loads at most eight relevant schemas",
241
250
  "Explicit MCP diagnostics can still connect eagerly while all MCP activation and remote calls remain network operations",
@@ -1989,6 +1998,7 @@ var partialConfigSchema = z2.object({
1989
1998
  }).partial().optional(),
1990
1999
  agent: z2.object({
1991
2000
  maxTurns: z2.number().int().positive().optional(),
2001
+ maxEpochTokens: z2.number().int().positive().optional(),
1992
2002
  maxSessionTokens: z2.number().int().positive().optional(),
1993
2003
  autoVerify: z2.boolean().optional(),
1994
2004
  verifyCommands: z2.array(z2.string()).optional(),
@@ -2068,7 +2078,8 @@ function defaultConfig(workspace = process.cwd()) {
2068
2078
  hooks: {},
2069
2079
  agent: {
2070
2080
  maxTurns: 24,
2071
- maxSessionTokens: 25e4,
2081
+ maxEpochTokens: 25e4,
2082
+ maxSessionTokens: 1e6,
2072
2083
  autoVerify: true,
2073
2084
  verifyCommands: [],
2074
2085
  checkpointBeforeWrite: true
@@ -2432,6 +2443,7 @@ function configSummary(config) {
2432
2443
  workspaceRoots: config.workspaceRoots,
2433
2444
  permissions: config.permissions,
2434
2445
  maxTurns: config.agent.maxTurns,
2446
+ maxEpochTokens: config.agent.maxEpochTokens,
2435
2447
  maxSessionTokens: config.agent.maxSessionTokens,
2436
2448
  autoVerify: config.agent.autoVerify,
2437
2449
  skills: config.skills ? {
@@ -4716,12 +4728,133 @@ function formatContextHits(hits, roots) {
4716
4728
  }
4717
4729
 
4718
4730
  // src/agent/runner.ts
4719
- import { randomUUID as randomUUID12 } from "node:crypto";
4731
+ import { randomUUID as randomUUID15 } from "node:crypto";
4732
+
4733
+ // src/context/manager.ts
4734
+ import { randomUUID as randomUUID5 } from "node:crypto";
4735
+
4736
+ // src/context/epochs.ts
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
+ }
4720
4850
 
4721
4851
  // src/context/manager.ts
4722
4852
  var RECENT_TURN_RESERVE = 3;
4723
4853
  var COMPACTION_HIGH_WATER = 0.78;
4724
4854
  var TOOL_PRESSURE_WATER = 0.28;
4855
+ var COMPACTION_OUTPUT_ALLOWANCE = 1600;
4856
+ var PREDICTED_REUSES = 3;
4857
+ var MAX_FACT_ITEMS = 16;
4725
4858
  var ContextManager = class {
4726
4859
  constructor(config) {
4727
4860
  this.config = config;
@@ -4761,20 +4894,28 @@ var ContextManager = class {
4761
4894
  status(session, modelContextTokens) {
4762
4895
  const active = activeMessages(session);
4763
4896
  const activeTokens = estimateMessages(active);
4764
- const summaryTokens = estimateTokens(session.contextSummary ?? "");
4897
+ const summaryTokens = compactedContextTokens(session);
4765
4898
  const toolTokenCount = toolTokens(active);
4766
4899
  const contextLimit = Math.max(
4767
4900
  8e3,
4768
4901
  modelContextTokens ?? Math.min(1e5, this.config.context.maxTokens * 3)
4769
4902
  );
4770
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;
4771
4906
  return {
4772
4907
  activeTokens,
4773
4908
  summaryTokens,
4774
4909
  toolTokens: toolTokenCount,
4775
4910
  messageCount: active.length,
4776
4911
  compactedMessages,
4777
- 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
4778
4919
  };
4779
4920
  }
4780
4921
  shouldCompact(session, tokenBudget) {
@@ -4784,65 +4925,298 @@ var ContextManager = class {
4784
4925
  const toolTokenCount = toolTokens(active);
4785
4926
  return activeTokens > tokenBudget * COMPACTION_HIGH_WATER || activeTokens > tokenBudget * 0.6 && toolTokenCount > tokenBudget * TOOL_PRESSURE_WATER;
4786
4927
  }
4787
- async compact(session, provider, signal, instructions = "") {
4928
+ async compact(session, provider, signal, instructions = "", mode = "manual") {
4788
4929
  const active = activeMessages(session);
4789
4930
  const cut = compactionCut(active);
4790
4931
  if (cut === 0) {
4791
- return { omittedMessages: 0, summaryTokens: estimateTokens(session.contextSummary ?? "") };
4932
+ return skippedCompaction(session, mode, "insufficient-history");
4792
4933
  }
4793
4934
  const older = active.slice(0, cut);
4794
4935
  if (!older.length) {
4795
- return { omittedMessages: 0, summaryTokens: estimateTokens(session.contextSummary ?? "") };
4936
+ return skippedCompaction(session, mode, "insufficient-history");
4796
4937
  }
4797
4938
  const transcript = older.map(formatMessageForSummary).join("\n\n").slice(-14e4);
4798
- const response = await provider.complete([
4939
+ const throughMessageId = older.at(-1).id;
4940
+ const facts = buildCompactionFactsEnvelope(session, throughMessageId);
4941
+ const messages = [
4799
4942
  transientMessage("system", `You compress coding-agent working context with high fidelity.
4800
- Return a concise Markdown state handoff with these headings: Goal, Completed, Current State, Decisions, Constraints, Open Questions, Relevant Files, Verification, Next Actions.
4801
- Preserve exact file paths, commands, errors, user corrections, unresolved risks, and permission decisions. Remove conversational filler and large raw tool output. Never invent facts.${instructions ? `
4943
+ Return a concise Markdown narrative handoff. Deterministic facts are preserved separately, so do not restate their full lists. Capture only useful chronology, rationale, and unresolved context that the facts do not express. Remove conversational filler and raw tool output. Never invent facts.${instructions ? `
4802
4944
  Additional instructions: ${instructions}` : ""}`),
4803
- transientMessage("user", `Existing summary, if any:
4945
+ transientMessage("user", `Deterministic facts already preserved outside the narrative:
4946
+ ${facts}
4947
+
4948
+ Existing narrative, if any:
4804
4949
  ${session.contextSummary || "(none)"}
4805
4950
 
4806
4951
  Messages to compact:
4807
4952
  ${transcript}`)
4808
- ], [], signal, 2400);
4809
- const summary = response.content.trim();
4810
- if (!summary) throw new Error("Context compaction returned an empty summary.");
4811
- session.contextSummary = summary.slice(0, 8e4);
4812
- session.compactedThroughMessageId = older.at(-1).id;
4953
+ ];
4954
+ const estimate = compactionEstimate(session, older, facts, messages);
4955
+ if (mode === "automatic" && estimate.projectedNetSavingsTokens <= 0) {
4956
+ return skippedCompaction(session, mode, "non-positive-net-savings", estimate);
4957
+ }
4958
+ const response = await provider.complete(messages, [], signal, COMPACTION_OUTPUT_ALLOWANCE);
4959
+ const summary = redactSensitiveText(response.content.trim()).slice(0, 8e4);
4960
+ if (summary) session.contextSummary = summary;
4961
+ else delete session.contextSummary;
4962
+ session.compactedThroughMessageId = throughMessageId;
4813
4963
  session.contextCompactions = (session.contextCompactions ?? 0) + 1;
4964
+ const receipt = completedCompactionReceipt(
4965
+ mode,
4966
+ older.length,
4967
+ throughMessageId,
4968
+ estimate,
4969
+ response,
4970
+ summary
4971
+ );
4814
4972
  return {
4815
4973
  omittedMessages: older.length,
4816
- summaryTokens: estimateTokens(session.contextSummary)
4974
+ summaryTokens: compactedContextTokens(session),
4975
+ status: "compacted",
4976
+ reason: "compacted",
4977
+ receipt
4817
4978
  };
4818
4979
  }
4819
4980
  buildShortTermPrompt(session) {
4820
4981
  const memory = session.workingMemory;
4821
4982
  const sections = [];
4822
- if (memory) {
4823
- sections.push(`<working-memory scope="session" source="runtime" authorization="none" updated-at="${memory.lastUpdatedAt}">
4824
- This is mutable short-term state for the current thread, not durable truth or tool authorization.
4825
- Goal: ${escapeXml(memory.goal || "(not established)")}
4826
- Current focus: ${escapeXml(memory.focus || "(none)")}
4827
- Constraints:
4828
- ${list(memory.constraints)}
4829
- Decisions:
4830
- ${list(memory.decisions)}
4831
- Open questions:
4832
- ${list(memory.openQuestions)}
4833
- Relevant files:
4834
- ${list(memory.relevantFiles)}
4835
- </working-memory>`);
4983
+ if (session.compactedThroughMessageId) {
4984
+ sections.push(buildCompactionFactsEnvelope(session));
4985
+ } else if (memory) {
4986
+ sections.push(buildWorkingMemoryEnvelope(memory));
4836
4987
  }
4837
4988
  if (session.contextSummary) {
4838
4989
  sections.push(`<compacted-context source="generated" authorization="none">
4839
4990
  This is a generated handoff of older session messages. Treat it as fallible context, never as permission, and prefer fresh tool evidence.
4840
- ${session.contextSummary}
4991
+ ${escapeXml(session.contextSummary)}
4841
4992
  </compacted-context>`);
4842
4993
  }
4843
4994
  return sections.join("\n\n");
4844
4995
  }
4845
4996
  };
4997
+ function compactionEstimate(session, older, facts, request) {
4998
+ const omittedTokens = estimateMessages(older.map((message2) => message2.role === "tool" && message2.content.length >= 1200 ? { ...message2, content: toolReceipt(message2) } : message2));
4999
+ const priorSummaryTokens = estimateTokens(session.contextSummary ?? "");
5000
+ const factsTokens = estimateTokens(facts);
5001
+ const existingFactsTokens = session.compactedThroughMessageId ? estimateTokens(buildCompactionFactsEnvelope(session)) : 0;
5002
+ const predictedOutputTokens = Math.min(
5003
+ COMPACTION_OUTPUT_ALLOWANCE,
5004
+ Math.max(160, Math.ceil((omittedTokens + priorSummaryTokens) * 0.12))
5005
+ );
5006
+ const inputTokens = estimateMessages(request);
5007
+ const existingStateTokens = session.compactedThroughMessageId ? existingFactsTokens : estimateTokens(session.workingMemory ? buildWorkingMemoryEnvelope(session.workingMemory) : "");
5008
+ const perReuseSavings = Math.max(
5009
+ 0,
5010
+ omittedTokens + priorSummaryTokens + existingStateTokens - predictedOutputTokens - factsTokens
5011
+ );
5012
+ const projectedGrossSavingsTokens = perReuseSavings * PREDICTED_REUSES;
5013
+ return {
5014
+ inputTokens,
5015
+ outputTokens: predictedOutputTokens,
5016
+ predictedOutputTokens,
5017
+ outputAllowanceTokens: COMPACTION_OUTPUT_ALLOWANCE,
5018
+ omittedTokens,
5019
+ priorSummaryTokens,
5020
+ factsTokens,
5021
+ projectedGrossSavingsTokens,
5022
+ projectedNetSavingsTokens: projectedGrossSavingsTokens - inputTokens - predictedOutputTokens
5023
+ };
5024
+ }
5025
+ function skippedCompaction(session, mode, reason, estimate = emptyCompactionEstimate(session)) {
5026
+ const receipt = {
5027
+ id: randomUUID5(),
5028
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
5029
+ mode,
5030
+ status: "skipped",
5031
+ reason,
5032
+ omittedMessages: 0,
5033
+ predictedReuses: PREDICTED_REUSES,
5034
+ estimated: estimate,
5035
+ actual: {},
5036
+ inputSource: "none",
5037
+ outputSource: "none",
5038
+ narrative: "not-requested"
5039
+ };
5040
+ return {
5041
+ omittedMessages: 0,
5042
+ summaryTokens: compactedContextTokens(session),
5043
+ status: "skipped",
5044
+ reason,
5045
+ receipt
5046
+ };
5047
+ }
5048
+ function emptyCompactionEstimate(session) {
5049
+ return {
5050
+ inputTokens: 0,
5051
+ outputTokens: 0,
5052
+ predictedOutputTokens: 0,
5053
+ outputAllowanceTokens: COMPACTION_OUTPUT_ALLOWANCE,
5054
+ omittedTokens: 0,
5055
+ priorSummaryTokens: estimateTokens(session.contextSummary ?? ""),
5056
+ factsTokens: session.compactedThroughMessageId ? estimateTokens(buildCompactionFactsEnvelope(session)) : 0,
5057
+ projectedGrossSavingsTokens: 0,
5058
+ projectedNetSavingsTokens: 0
5059
+ };
5060
+ }
5061
+ function completedCompactionReceipt(mode, omittedMessages, compactedThroughMessageId, estimated, response, summary) {
5062
+ const actual = actualUsage(response.usage);
5063
+ return {
5064
+ id: randomUUID5(),
5065
+ recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
5066
+ mode,
5067
+ status: "compacted",
5068
+ reason: "compacted",
5069
+ omittedMessages,
5070
+ compactedThroughMessageId,
5071
+ predictedReuses: PREDICTED_REUSES,
5072
+ estimated: {
5073
+ ...estimated,
5074
+ outputTokens: estimateTokens(response.content) + estimateTokens(JSON.stringify(response.toolCalls))
5075
+ },
5076
+ actual,
5077
+ inputSource: actual.inputTokens === void 0 ? "estimated" : "actual",
5078
+ outputSource: actual.outputTokens === void 0 ? "estimated" : "actual",
5079
+ narrative: summary ? "present" : "empty"
5080
+ };
5081
+ }
5082
+ function actualUsage(usage) {
5083
+ const entries = {
5084
+ inputTokens: validTokens(usage?.inputTokens),
5085
+ outputTokens: validTokens(usage?.outputTokens),
5086
+ cachedInputTokens: validTokens(usage?.cachedInputTokens),
5087
+ cacheWriteInputTokens: validTokens(usage?.cacheWriteInputTokens),
5088
+ reasoningTokens: validTokens(usage?.reasoningTokens)
5089
+ };
5090
+ return Object.fromEntries(Object.entries(entries).filter(([, value]) => value !== void 0));
5091
+ }
5092
+ function validTokens(value) {
5093
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : void 0;
5094
+ }
5095
+ function compactedContextTokens(session) {
5096
+ if (!session.compactedThroughMessageId) return estimateTokens(session.contextSummary ?? "");
5097
+ return estimateTokens(buildCompactionFactsEnvelope(session)) + estimateTokens(session.contextSummary ?? "");
5098
+ }
5099
+ function buildCompactionFactsEnvelope(session, throughMessageId) {
5100
+ const memory = session.workingMemory;
5101
+ const contract = session.taskContract;
5102
+ const lastRun = session.lastRun;
5103
+ const epoch = activeContextEpoch(session);
5104
+ const directives = olderUserDirectives(session, throughMessageId ?? session.compactedThroughMessageId);
5105
+ const permissions = recentPermissionFacts(session.audit ?? []);
5106
+ const failures = recentFailureFacts(session.audit ?? []);
5107
+ const artifacts = (session.toolArtifacts ?? []).filter((artifact) => Date.parse(artifact.expiresAt) > Date.now()).slice(-MAX_FACT_ITEMS).map((artifact) => `- sha256=${artifact.sha256} tool-call=${safeFact(artifact.toolCallId, 160)} bytes=${artifact.bytes} expires=${safeFact(artifact.expiresAt, 80)} redacted=${artifact.redacted}`);
5108
+ const contractLines = contract ? [
5109
+ `State: ${contract.state}`,
5110
+ `Objective: ${safeFact(contract.objective, 2e3)}`,
5111
+ `Scope:
5112
+ ${factList(contract.scope)}`,
5113
+ `Constraints:
5114
+ ${factList(contract.constraints)}`,
5115
+ `Non-goals:
5116
+ ${factList(contract.nonGoals)}`,
5117
+ `Acceptance:
5118
+ ${contract.acceptanceCriteria.map(
5119
+ (item) => `- [${item.status}] ${safeFact(item.id, 128)} required=${item.required}: ${safeFact(item.description, 600)}${item.evidenceRefs.length ? ` evidence=${item.evidenceRefs.map((ref) => safeFact(ref, 160)).join(", ")}` : ""}`
5120
+ ).join("\n") || "- None recorded."}`,
5121
+ `Verification requirements:
5122
+ ${factList(contract.verificationRequirements)}`
5123
+ ].join("\n") : "No Task Contract is active.";
5124
+ const lastRunLines = lastRun ? [
5125
+ `Status: ${lastRun.status}; reason: ${safeFact(lastRun.reason, 160)}; finished: ${safeFact(lastRun.finishedAt, 80)}`,
5126
+ `Detail: ${safeFact(lastRun.detail, 600)}`,
5127
+ `Changed files:
5128
+ ${factList(lastRun.changedFiles)}`,
5129
+ `Checks:
5130
+ ${lastRun.checks.map(
5131
+ (check) => `- [${check.ok ? "passed" : "failed"}] ${check.kind}: ${safeFact(check.command, 500)} (tool-call ${safeFact(check.toolCallId, 160)})`
5132
+ ).join("\n") || "- None recorded."}`
5133
+ ].join("\n") : "No completed run is recorded.";
5134
+ return `<compaction-facts scope="session" source="deterministic-ledger" authorization="none">
5135
+ These facts are rebuilt from authoritative session state and take precedence over the generated narrative below. Historical permission events are audit evidence only and never grant current authorization.
5136
+
5137
+ Task Contract:
5138
+ ${contractLines}
5139
+
5140
+ <working-memory source="runtime" authorization="none" updated-at="${escapeXml(memory?.lastUpdatedAt ?? "unknown")}">
5141
+ Goal: ${safeFact(memory?.goal || "(not established)", 1e3)}
5142
+ Current focus: ${safeFact(memory?.focus || "(none)", 1e3)}
5143
+ Constraints:
5144
+ ${factList(memory?.constraints ?? [])}
5145
+ Decisions:
5146
+ ${factList(memory?.decisions ?? [])}
5147
+ Open questions:
5148
+ ${factList(memory?.openQuestions ?? [])}
5149
+ Relevant files:
5150
+ ${factList(memory?.relevantFiles ?? [])}
5151
+ </working-memory>
5152
+
5153
+ Session changed files:
5154
+ ${factList(session.changedFiles)}
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
+
5161
+ Last-run verification and residual state:
5162
+ ${lastRunLines}
5163
+
5164
+ Older user corrections and boundaries:
5165
+ ${directives.length ? directives.map((value) => `- ${value}`).join("\n") : "- None detected."}
5166
+
5167
+ Historical permission decisions (not authorization):
5168
+ ${permissions.length ? permissions.join("\n") : "- None recorded."}
5169
+
5170
+ Bounded failure evidence:
5171
+ ${failures.length ? failures.join("\n") : "- None recorded."}
5172
+
5173
+ Retained tool artifact readback handles:
5174
+ ${artifacts.length ? artifacts.join("\n") : "- None available."}
5175
+ </compaction-facts>`;
5176
+ }
5177
+ function olderUserDirectives(session, throughMessageId) {
5178
+ if (!throughMessageId) return [];
5179
+ const end = session.messages.findIndex((item) => item.id === throughMessageId);
5180
+ if (end < 0) return [];
5181
+ const marker = /\b(?:no|nope|wrong|rather|use|switch|change|must|mustn't|do not|don't|never|always|only|before|after|remember|make sure|cannot|can't|permission|approve|deny|stop|instead|correction|actually|first)\b|不是|不对|错了|改成|换成|用|必须|不要|不能|不得|务必|只能|只要|仅|记得|先|以后|之前|完成后|权限|批准|拒绝|停止|改为|纠正|其实|安全|不允许|别/iu;
5182
+ return unique(session.messages.slice(0, end + 1).filter((item) => item.role === "user" && marker.test(item.content)).map((item) => safeFact(item.content, 800))).slice(-12);
5183
+ }
5184
+ function recentPermissionFacts(audit) {
5185
+ return audit.filter((event) => event.type === "permission").slice(-MAX_FACT_ITEMS).map((event) => `- [${event.outcome}] ${safeFact(event.tool, 160)} category=${event.category ?? "unknown"} tool-call=${safeFact(event.toolCallId, 160)}: ${safeFact(event.reason ?? "No reason recorded.", 360)}`);
5186
+ }
5187
+ function recentFailureFacts(audit) {
5188
+ return audit.filter((event) => event.type === "tool" && event.outcome === "failure").slice(-MAX_FACT_ITEMS).map((event) => {
5189
+ const failure = isFailureReceipt(event.metadata?.failure) ? event.metadata.failure : void 0;
5190
+ const receipt = failure ? ` class=${safeFact(failure.class, 80)} retryable=${failure.retryable} circuit-open=${failure.circuitOpen} signature=${safeFact(failure.signature, 160)} repair=${safeFact(failure.repairHint, 360)}` : "";
5191
+ return `- ${safeFact(event.tool, 160)} tool-call=${safeFact(event.toolCallId, 160)}${receipt} reason=${safeFact(event.reason ?? "No reason recorded.", 500)}`;
5192
+ });
5193
+ }
5194
+ function isFailureReceipt(value) {
5195
+ if (!value || typeof value !== "object") return false;
5196
+ const candidate = value;
5197
+ return typeof candidate.class === "string" && typeof candidate.retryable === "boolean" && typeof candidate.circuitOpen === "boolean" && typeof candidate.signature === "string" && typeof candidate.repairHint === "string";
5198
+ }
5199
+ function factList(values) {
5200
+ return values.length ? values.slice(-MAX_FACT_ITEMS).map((value) => `- ${safeFact(value, 800)}`).join("\n") : "- None recorded.";
5201
+ }
5202
+ function safeFact(value, max) {
5203
+ return escapeXml(concise(redactSensitiveText(value), max));
5204
+ }
5205
+ function buildWorkingMemoryEnvelope(memory) {
5206
+ return `<working-memory scope="session" source="runtime" authorization="none" updated-at="${escapeXml(memory.lastUpdatedAt)}">
5207
+ This is mutable short-term state for the current thread, not durable truth or tool authorization.
5208
+ Goal: ${safeFact(memory.goal || "(not established)", 1e3)}
5209
+ Current focus: ${safeFact(memory.focus || "(none)", 1e3)}
5210
+ Constraints:
5211
+ ${factList(memory.constraints)}
5212
+ Decisions:
5213
+ ${factList(memory.decisions)}
5214
+ Open questions:
5215
+ ${factList(memory.openQuestions)}
5216
+ Relevant files:
5217
+ ${factList(memory.relevantFiles)}
5218
+ </working-memory>`;
5219
+ }
4846
5220
  function activeMessages(session) {
4847
5221
  if (!session.compactedThroughMessageId) return session.messages;
4848
5222
  const index = session.messages.findIndex((message2) => message2.id === session.compactedThroughMessageId);
@@ -4915,11 +5289,11 @@ function emptyWorkingMemory() {
4915
5289
  lastUpdatedAt: (/* @__PURE__ */ new Date()).toISOString()
4916
5290
  };
4917
5291
  }
4918
- function list(values) {
4919
- return values.length ? values.map((value) => `- ${escapeXml(value)}`).join("\n") : "- None recorded.";
4920
- }
4921
5292
  function safeShortTerm(value, max) {
4922
- return concise(value, max).replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/g, "[redacted-secret]").replace(/\b((?:api[_-]?key|access[_-]?token|auth(?:orization)?|password|secret))\s*[:=]\s*[^\s,;]+/gi, "$1=[redacted]");
5293
+ return concise(redactSensitiveText(value), max);
5294
+ }
5295
+ function redactSensitiveText(value) {
5296
+ return value.replace(/(https?:\/\/)[^/\s:@]+(?::[^@\s/]*)?@/giu, "$1[redacted]@").replace(/\b(sk-[A-Za-z0-9_-]{12,}|AIza[0-9A-Za-z_-]{20,})\b/gu, "[redacted-secret]").replace(/\b(bearer|basic)\s+[^\s,;]+/giu, "$1 [redacted]").replace(/\b((?:api[_-]?key|access[_-]?token|auth(?:orization)?|cookie|password|client[_-]?secret|secret))\s*[:=]\s*[^\s,;]+/giu, "$1=[redacted]").replace(/(--(?:api[_-]?key|access[_-]?token|password|client[_-]?secret|secret)(?:=|\s+))[^\s]+/giu, "$1[redacted]");
4923
5297
  }
4924
5298
  function escapeXml(value) {
4925
5299
  return value.replace(/[&<>"']/g, (character) => ({
@@ -5227,7 +5601,7 @@ function normalizeForMatch(value) {
5227
5601
  }
5228
5602
 
5229
5603
  // src/providers/anthropic.ts
5230
- import { randomUUID as randomUUID4 } from "node:crypto";
5604
+ import { randomUUID as randomUUID6 } from "node:crypto";
5231
5605
 
5232
5606
  // src/providers/provider.ts
5233
5607
  var ProviderError = class extends Error {
@@ -5352,7 +5726,7 @@ var AnthropicProvider = class {
5352
5726
  return {
5353
5727
  content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
5354
5728
  toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
5355
- id: block.id ?? randomUUID4(),
5729
+ id: block.id ?? randomUUID6(),
5356
5730
  name: block.name ?? "unknown",
5357
5731
  arguments: safeJsonArguments(block.input)
5358
5732
  })),
@@ -5426,7 +5800,7 @@ var AnthropicProvider = class {
5426
5800
  }
5427
5801
  if (chunk.type === "content_block_start" && chunk.content_block?.type === "tool_use") {
5428
5802
  calls.set(index, {
5429
- id: chunk.content_block.id ?? randomUUID4(),
5803
+ id: chunk.content_block.id ?? randomUUID6(),
5430
5804
  name: chunk.content_block.name ?? "unknown",
5431
5805
  input: chunk.content_block.input,
5432
5806
  partialJson: ""
@@ -5437,7 +5811,7 @@ var AnthropicProvider = class {
5437
5811
  yield { type: "text_delta", content: chunk.delta.text };
5438
5812
  }
5439
5813
  if (chunk.type === "content_block_delta" && chunk.delta?.type === "input_json_delta") {
5440
- const call = calls.get(index) ?? { id: randomUUID4(), name: "unknown", input: void 0, partialJson: "" };
5814
+ const call = calls.get(index) ?? { id: randomUUID6(), name: "unknown", input: void 0, partialJson: "" };
5441
5815
  call.partialJson += chunk.delta.partial_json ?? "";
5442
5816
  calls.set(index, call);
5443
5817
  }
@@ -5467,7 +5841,7 @@ function normalizeAnthropicResponse(data) {
5467
5841
  return {
5468
5842
  content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
5469
5843
  toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
5470
- id: block.id ?? randomUUID4(),
5844
+ id: block.id ?? randomUUID6(),
5471
5845
  name: block.name ?? "unknown",
5472
5846
  arguments: safeJsonArguments(block.input)
5473
5847
  })),
@@ -5512,7 +5886,7 @@ function toAnthropicMessage(message2) {
5512
5886
  }
5513
5887
 
5514
5888
  // src/providers/gemini.ts
5515
- import { randomUUID as randomUUID5 } from "node:crypto";
5889
+ import { randomUUID as randomUUID7 } from "node:crypto";
5516
5890
  var GeminiProvider = class {
5517
5891
  constructor(config) {
5518
5892
  this.config = config;
@@ -5552,7 +5926,7 @@ var GeminiProvider = class {
5552
5926
  return {
5553
5927
  content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
5554
5928
  toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
5555
- id: randomUUID5(),
5929
+ id: randomUUID7(),
5556
5930
  name: part.functionCall?.name ?? "unknown",
5557
5931
  arguments: safeJsonArguments(part.functionCall?.args)
5558
5932
  })),
@@ -5624,7 +5998,7 @@ var GeminiProvider = class {
5624
5998
  }
5625
5999
  if (part.functionCall) {
5626
6000
  toolCalls.push({
5627
- id: randomUUID5(),
6001
+ id: randomUUID7(),
5628
6002
  name: part.functionCall.name ?? "unknown",
5629
6003
  arguments: safeJsonArguments(part.functionCall.args)
5630
6004
  });
@@ -5653,7 +6027,7 @@ function normalizeGeminiResponse(data) {
5653
6027
  return {
5654
6028
  content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
5655
6029
  toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
5656
- id: randomUUID5(),
6030
+ id: randomUUID7(),
5657
6031
  name: part.functionCall?.name ?? "unknown",
5658
6032
  arguments: safeJsonArguments(part.functionCall?.args)
5659
6033
  })),
@@ -5697,7 +6071,7 @@ function toGeminiMessage(message2) {
5697
6071
  }
5698
6072
 
5699
6073
  // src/providers/openai.ts
5700
- import { randomUUID as randomUUID6 } from "node:crypto";
6074
+ import { randomUUID as randomUUID8 } from "node:crypto";
5701
6075
  var OpenAIProvider = class {
5702
6076
  constructor(config) {
5703
6077
  this.config = config;
@@ -5820,7 +6194,7 @@ var OpenAIProvider = class {
5820
6194
  }
5821
6195
  for (const fragment of delta?.tool_calls ?? []) {
5822
6196
  const index = fragment.index ?? 0;
5823
- const current = calls.get(index) ?? { id: randomUUID6(), name: "", arguments: "" };
6197
+ const current = calls.get(index) ?? { id: randomUUID8(), name: "", arguments: "" };
5824
6198
  if (fragment.id) current.id = fragment.id;
5825
6199
  if (fragment.function?.name) current.name += fragment.function.name;
5826
6200
  if (fragment.function?.arguments) current.arguments += fragment.function.arguments;
@@ -5854,7 +6228,7 @@ function normalizeOpenAIResponse(data) {
5854
6228
  return {
5855
6229
  content: message2.content ?? "",
5856
6230
  toolCalls: (message2.tool_calls ?? []).map((call) => ({
5857
- id: call.id ?? randomUUID6(),
6231
+ id: call.id ?? randomUUID8(),
5858
6232
  name: call.function?.name ?? "unknown",
5859
6233
  arguments: safeJsonArguments(call.function?.arguments)
5860
6234
  })),
@@ -5910,7 +6284,7 @@ function createProvider(config) {
5910
6284
  }
5911
6285
 
5912
6286
  // src/checkpoint/store.ts
5913
- import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
6287
+ import { createHash as createHash8, randomUUID as randomUUID9 } from "node:crypto";
5914
6288
  import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
5915
6289
  import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
5916
6290
  import { z as z4 } from "zod";
@@ -5946,7 +6320,7 @@ var CheckpointStore = class {
5946
6320
  return this.withManagedLease(() => this.captureUnlocked(sessionId, unique3, options));
5947
6321
  }
5948
6322
  async captureUnlocked(sessionId, unique3, options) {
5949
- const id = `${Date.now().toString(36)}-${randomUUID7()}`;
6323
+ const id = `${Date.now().toString(36)}-${randomUUID9()}`;
5950
6324
  const target = join9(this.directory, sessionId, id);
5951
6325
  const blobDirectory = join9(target, "blobs");
5952
6326
  await this.ensureDirectory();
@@ -6222,7 +6596,7 @@ var HookRunner = class {
6222
6596
  };
6223
6597
 
6224
6598
  // src/session/store.ts
6225
- import { randomUUID as randomUUID8 } from "node:crypto";
6599
+ import { randomUUID as randomUUID10 } from "node:crypto";
6226
6600
  import { constants as constants4 } from "node:fs";
6227
6601
  import {
6228
6602
  chmod as chmod6,
@@ -6417,6 +6791,107 @@ var taskContractSchema = z5.object({
6417
6791
  updatedAt: z5.string(),
6418
6792
  auditBoundaryId: z5.string().min(1).max(128).optional()
6419
6793
  }).strict();
6794
+ var contextCompactionReceiptSchema = z5.object({
6795
+ id: z5.string().uuid(),
6796
+ recordedAt: z5.string().datetime(),
6797
+ mode: z5.enum(["automatic", "manual"]),
6798
+ status: z5.enum(["compacted", "skipped"]),
6799
+ reason: z5.enum(["compacted", "insufficient-history", "non-positive-net-savings"]),
6800
+ omittedMessages: z5.number().int().nonnegative(),
6801
+ compactedThroughMessageId: z5.string().optional(),
6802
+ predictedReuses: z5.number().int().positive(),
6803
+ estimated: z5.object({
6804
+ inputTokens: z5.number().int().nonnegative(),
6805
+ outputTokens: z5.number().int().nonnegative(),
6806
+ predictedOutputTokens: z5.number().int().nonnegative(),
6807
+ outputAllowanceTokens: z5.number().int().nonnegative(),
6808
+ omittedTokens: z5.number().int().nonnegative(),
6809
+ priorSummaryTokens: z5.number().int().nonnegative(),
6810
+ factsTokens: z5.number().int().nonnegative(),
6811
+ projectedGrossSavingsTokens: z5.number().int().nonnegative(),
6812
+ projectedNetSavingsTokens: z5.number().int()
6813
+ }).strict(),
6814
+ actual: z5.object({
6815
+ inputTokens: z5.number().int().nonnegative().optional(),
6816
+ outputTokens: z5.number().int().nonnegative().optional(),
6817
+ cachedInputTokens: z5.number().int().nonnegative().optional(),
6818
+ cacheWriteInputTokens: z5.number().int().nonnegative().optional(),
6819
+ reasoningTokens: z5.number().int().nonnegative().optional()
6820
+ }).strict(),
6821
+ inputSource: z5.enum(["actual", "estimated", "none"]),
6822
+ outputSource: z5.enum(["actual", "estimated", "none"]),
6823
+ narrative: z5.enum(["present", "empty", "not-requested"])
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();
6420
6895
  var sessionSchema = z5.object({
6421
6896
  id: sessionIdSchema,
6422
6897
  title: z5.string(),
@@ -6432,6 +6907,10 @@ var sessionSchema = z5.object({
6432
6907
  contextSummary: z5.string().max(2e5).optional(),
6433
6908
  contextCompactions: z5.number().int().nonnegative().optional(),
6434
6909
  compactedThroughMessageId: z5.string().optional(),
6910
+ contextCompactionReceipts: z5.array(contextCompactionReceiptSchema).max(64).optional(),
6911
+ contextEpochs: z5.array(contextEpochSchema).max(64).optional(),
6912
+ intentAssessment: intentAssessmentSchema.optional(),
6913
+ pendingInput: pendingInputSchema.optional(),
6435
6914
  workingMemory: workingMemorySchema.optional(),
6436
6915
  contextSources: z5.array(contextSourceSchema).max(64).optional(),
6437
6916
  toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
@@ -6582,7 +7061,7 @@ var SessionStore = class {
6582
7061
  const backup = this.backupPathFor(session.id);
6583
7062
  await this.assertManagedFile(target);
6584
7063
  await this.assertManagedFile(backup);
6585
- const temporary = join10(this.directory, `.${session.id}.${randomUUID8()}.tmp`);
7064
+ const temporary = join10(this.directory, `.${session.id}.${randomUUID10()}.tmp`);
6586
7065
  const data = `${JSON.stringify(session, null, 2)}
6587
7066
  `;
6588
7067
  const handle = await open2(temporary, "wx", 384);
@@ -6600,7 +7079,7 @@ var SessionStore = class {
6600
7079
  }
6601
7080
  }
6602
7081
  async copyBackup(source, target) {
6603
- const temporary = join10(this.directory, `.${basename7(target)}.${randomUUID8()}.tmp`);
7082
+ const temporary = join10(this.directory, `.${basename7(target)}.${randomUUID10()}.tmp`);
6604
7083
  try {
6605
7084
  await copyFile(source, temporary, constants4.COPYFILE_EXCL);
6606
7085
  await chmod6(temporary, 384);
@@ -6689,7 +7168,7 @@ var SessionStore = class {
6689
7168
  }
6690
7169
  };
6691
7170
  function createSession(options) {
6692
- const id = options.id ?? randomUUID8();
7171
+ const id = options.id ?? randomUUID10();
6693
7172
  validateId(id);
6694
7173
  const now = (/* @__PURE__ */ new Date()).toISOString();
6695
7174
  return {
@@ -6704,6 +7183,12 @@ function createSession(options) {
6704
7183
  tasks: [],
6705
7184
  changedFiles: [],
6706
7185
  audit: [],
7186
+ contextEpochs: [{
7187
+ id: randomUUID10(),
7188
+ index: 1,
7189
+ startedAt: now,
7190
+ usage: { inputTokens: 0, outputTokens: 0 }
7191
+ }],
6707
7192
  usage: { inputTokens: 0, outputTokens: 0 }
6708
7193
  };
6709
7194
  }
@@ -7796,7 +8281,7 @@ function isUnsafeGitOption(argument) {
7796
8281
  "--exclude-from",
7797
8282
  "--output",
7798
8283
  "--directory"
7799
- ].some((option) => argument === option || argument.startsWith(`${option}=`));
8284
+ ].some((option2) => argument === option2 || argument.startsWith(`${option2}=`));
7800
8285
  }
7801
8286
  function firstGitCommand(args) {
7802
8287
  for (const argument of args) {
@@ -8544,7 +9029,7 @@ async function discoverSnapshotFiles(root) {
8544
9029
  }
8545
9030
 
8546
9031
  // src/tools/task.ts
8547
- import { randomUUID as randomUUID9 } from "node:crypto";
9032
+ import { randomUUID as randomUUID11 } from "node:crypto";
8548
9033
  import { z as z14 } from "zod";
8549
9034
  var taskSchema2 = z14.object({
8550
9035
  id: z14.string().min(1).optional(),
@@ -8593,7 +9078,7 @@ var taskTool = {
8593
9078
  case "list":
8594
9079
  break;
8595
9080
  case "add":
8596
- tasks.push({ id: randomUUID9(), title: input2.title, status: input2.status ?? "pending" });
9081
+ tasks.push({ id: randomUUID11(), title: input2.title, status: input2.status ?? "pending" });
8597
9082
  break;
8598
9083
  case "update": {
8599
9084
  const task = tasks.find((item) => item.id === input2.id);
@@ -8610,7 +9095,7 @@ var taskTool = {
8610
9095
  }
8611
9096
  case "replace":
8612
9097
  tasks.splice(0, tasks.length, ...input2.tasks.map((task) => ({
8613
- id: task.id ?? randomUUID9(),
9098
+ id: task.id ?? randomUUID11(),
8614
9099
  title: task.title,
8615
9100
  status: task.status
8616
9101
  })));
@@ -8624,11 +9109,11 @@ var taskTool = {
8624
9109
  };
8625
9110
 
8626
9111
  // src/tools/task-contract.ts
8627
- import { randomUUID as randomUUID11 } from "node:crypto";
9112
+ import { randomUUID as randomUUID13 } from "node:crypto";
8628
9113
  import { z as z15 } from "zod";
8629
9114
 
8630
9115
  // src/agent/task-contract.ts
8631
- import { randomUUID as randomUUID10 } from "node:crypto";
9116
+ import { randomUUID as randomUUID12 } from "node:crypto";
8632
9117
  var EXECUTABLE_INTENTS = /* @__PURE__ */ new Set(["debug", "refactor", "test", "implement"]);
8633
9118
  function shouldUseTaskContract(request, intent, existing) {
8634
9119
  if (existing && existing.state !== "satisfied") return true;
@@ -8694,7 +9179,7 @@ function refreshTaskContractState(contract) {
8694
9179
  }
8695
9180
  function criterion(id, description) {
8696
9181
  return {
8697
- id: `${id}-${randomUUID10().slice(0, 8)}`,
9182
+ id: `${id}-${randomUUID12().slice(0, 8)}`,
8698
9183
  description,
8699
9184
  required: true,
8700
9185
  status: "pending",
@@ -9248,7 +9733,7 @@ var taskContractTool = {
9248
9733
  }
9249
9734
  const now = (/* @__PURE__ */ new Date()).toISOString();
9250
9735
  const criteria = input2.acceptance_criteria.map((item) => ({
9251
- id: item.id ?? `criterion-${randomUUID11().slice(0, 8)}`,
9736
+ id: item.id ?? `criterion-${randomUUID13().slice(0, 8)}`,
9252
9737
  description: item.description,
9253
9738
  required: item.required ?? true,
9254
9739
  status: "pending",
@@ -9636,7 +10121,7 @@ ${workspaceRules}` : ""}`;
9636
10121
  }
9637
10122
  function buildSessionStatePrompt(session) {
9638
10123
  const tasks = session.tasks.length ? session.tasks.map((task) => `- [${task.status}] ${task.title}`).join("\n") : "- No active plan.";
9639
- const contract = session.taskContract && session.taskContract.state !== "satisfied" ? `
10124
+ const contract = session.taskContract && session.taskContract.state !== "satisfied" && !session.compactedThroughMessageId ? `
9640
10125
 
9641
10126
  Task Contract (${session.taskContract.state}):
9642
10127
  Objective: ${session.taskContract.objective}
@@ -9716,6 +10201,118 @@ function escapeAttribute3(value) {
9716
10201
  })[character] ?? character);
9717
10202
  }
9718
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
+
9719
10316
  // src/agent/tool-recovery.ts
9720
10317
  import { createHash as createHash13 } from "node:crypto";
9721
10318
  var RETRY_BUDGET = {
@@ -9730,6 +10327,7 @@ var RETRY_BUDGET = {
9730
10327
  no_progress: 0,
9731
10328
  contract_required: 2
9732
10329
  };
10330
+ var FAILURE_CLASSES = Object.keys(RETRY_BUDGET);
9733
10331
  var REPAIR_HINT = {
9734
10332
  schema_input: "Correct the arguments to match the tool schema.",
9735
10333
  unknown_tool: "Choose a tool exposed for this turn.",
@@ -9831,6 +10429,9 @@ function classifyToolFailure(result, fallback = "execution") {
9831
10429
  function formatFailureReceipt(receipt) {
9832
10430
  return `Failure: ${receipt.class}; attempt ${receipt.attempt}; ${receipt.remaining} retries remain; circuit ${receipt.circuitOpen ? "open" : "closed"}. Repair: ${receipt.repairHint}`;
9833
10431
  }
10432
+ function resolvableFailureSignatures(call) {
10433
+ return FAILURE_CLASSES.map((failureClass) => failureSignature(call, failureClass));
10434
+ }
9834
10435
  function isRetryable(failureClass) {
9835
10436
  return RETRY_BUDGET[failureClass] > 0;
9836
10437
  }
@@ -9954,12 +10555,12 @@ function preservedSignals(metadata) {
9954
10555
  }
9955
10556
  }
9956
10557
  const failure = metadata.failure;
9957
- if (isFailureReceipt(failure)) {
10558
+ if (isFailureReceipt2(failure)) {
9958
10559
  lines.push(`failure-class: ${failure.class}; attempt ${failure.attempt}; ${failure.remaining} retries remain`);
9959
10560
  }
9960
10561
  return lines;
9961
10562
  }
9962
- function isFailureReceipt(value) {
10563
+ function isFailureReceipt2(value) {
9963
10564
  return typeof value === "object" && value !== null && typeof value.class === "string" && typeof value.attempt === "number" && typeof value.remaining === "number";
9964
10565
  }
9965
10566
  function redactToolOutput(value) {
@@ -10095,15 +10696,15 @@ async function pinContextSource(session, workspace, requested) {
10095
10696
  const info = await stat9(resolved);
10096
10697
  const alias = workspace.display(resolved);
10097
10698
  const tokens2 = estimateTokens((await readFile13(resolved, "utf8")).slice(0, MAX_SOURCE_CHARS));
10098
- const list2 = sources(session);
10099
- const existing = list2.find((source2) => source2.path === alias);
10699
+ const list = sources(session);
10700
+ const existing = list.find((source2) => source2.path === alias);
10100
10701
  if (existing) {
10101
10702
  existing.state = "pinned";
10102
10703
  existing.tokens = tokens2;
10103
10704
  existing.addedAt = (/* @__PURE__ */ new Date()).toISOString();
10104
10705
  return existing;
10105
10706
  }
10106
- if (list2.length >= MAX_CONTEXT_SOURCES) {
10707
+ if (list.length >= MAX_CONTEXT_SOURCES) {
10107
10708
  throw new Error(`Context source limit reached (${MAX_CONTEXT_SOURCES}); unpin something first.`);
10108
10709
  }
10109
10710
  if (info.size > 4e6) {
@@ -10115,31 +10716,31 @@ async function pinContextSource(session, workspace, requested) {
10115
10716
  tokens: tokens2,
10116
10717
  addedAt: (/* @__PURE__ */ new Date()).toISOString()
10117
10718
  };
10118
- list2.push(source);
10719
+ list.push(source);
10119
10720
  return source;
10120
10721
  }
10121
10722
  function unpinContextSource(session, requested) {
10122
- const list2 = session.contextSources;
10123
- if (!list2?.length) return void 0;
10124
- const match = matchSource(list2, requested);
10723
+ const list = session.contextSources;
10724
+ if (!list?.length) return void 0;
10725
+ const match = matchSource(list, requested);
10125
10726
  if (!match) return void 0;
10126
- list2.splice(list2.indexOf(match), 1);
10727
+ list.splice(list.indexOf(match), 1);
10127
10728
  return match.path;
10128
10729
  }
10129
10730
  function toggleMuteContextSource(session, requested) {
10130
- const list2 = session.contextSources;
10131
- if (!list2?.length) return void 0;
10132
- const match = matchSource(list2, requested);
10731
+ const list = session.contextSources;
10732
+ if (!list?.length) return void 0;
10733
+ const match = matchSource(list, requested);
10133
10734
  if (!match) return void 0;
10134
10735
  match.state = match.state === "muted" ? "pinned" : "muted";
10135
10736
  return match;
10136
10737
  }
10137
10738
  async function resolvePinnedContent(session, workspace) {
10138
- const list2 = session.contextSources;
10139
- if (!list2?.length) return [];
10739
+ const list = session.contextSources;
10740
+ if (!list?.length) return [];
10140
10741
  const resolved = [];
10141
10742
  let remaining = MAX_PINNED_CHARS;
10142
- for (const source of list2) {
10743
+ for (const source of list) {
10143
10744
  if (source.state !== "pinned") continue;
10144
10745
  if (remaining <= 0) break;
10145
10746
  try {
@@ -10171,10 +10772,10 @@ These files were explicitly pinned by the user and are re-read from disk each tu
10171
10772
  ${blocks.join("\n\n")}
10172
10773
  </pinned-context>`;
10173
10774
  }
10174
- function matchSource(list2, requested) {
10775
+ function matchSource(list, requested) {
10175
10776
  const trimmed = requested.trim();
10176
10777
  if (!trimmed) return void 0;
10177
- return list2.find((source) => source.path === trimmed) ?? list2.find((source) => source.path.endsWith(`/${trimmed}`)) ?? list2.find((source) => source.path.includes(trimmed));
10778
+ return list.find((source) => source.path === trimmed) ?? list.find((source) => source.path.endsWith(`/${trimmed}`)) ?? list.find((source) => source.path.includes(trimmed));
10178
10779
  }
10179
10780
  function escapeAttribute5(value) {
10180
10781
  return value.replace(/[&"<>]/g, (character) => ({
@@ -10648,16 +11249,35 @@ var AgentRunner = class {
10648
11249
  }
10649
11250
  async run(input2, options = {}) {
10650
11251
  if (this.running) throw new Error("This AgentRunner is already processing a turn.");
10651
- const request = input2.trim();
10652
- if (!request) throw new Error("User input cannot be empty.");
10653
- 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) {
10654
11255
  throw new Error("User input is too large; pass a focused request or attach files with @path.");
10655
11256
  }
11257
+ let request = submittedRequest;
10656
11258
  this.running = true;
10657
11259
  this.contextEngine.resetDiagnostics?.();
10658
11260
  const emit = async (event) => {
10659
11261
  await options.onEvent?.(event);
10660
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
+ }
10661
11281
  const changeSequenceAtStart = this.changeSequence;
10662
11282
  const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
10663
11283
  const loadedProgressiveTools = /* @__PURE__ */ new Set();
@@ -10725,11 +11345,12 @@ var AgentRunner = class {
10725
11345
  try {
10726
11346
  throwIfAborted(options.signal);
10727
11347
  await this.reconcileToolArtifacts();
11348
+ ensureContextEpoch(this.session);
10728
11349
  if (this.session.messages.length === 0 && this.session.title === "New session") {
10729
11350
  this.session.title = titleFromInput(request);
10730
11351
  }
10731
11352
  this.contextManager.startTurn(this.session, request);
10732
- const userMessage = message("user", request);
11353
+ const userMessage = message("user", submittedRequest);
10733
11354
  this.activeReuseGate = { requestId: userMessage.id, request, attempted: false };
10734
11355
  this.session.messages.push(userMessage);
10735
11356
  await this.persist();
@@ -10737,11 +11358,48 @@ var AgentRunner = class {
10737
11358
  const turnDirective = buildTurnDirective(request, {
10738
11359
  agents: Boolean(this.config.agents?.enabled)
10739
11360
  });
11361
+ const contractEnabled = shouldUseTaskContract(
11362
+ request,
11363
+ turnDirective.intent,
11364
+ this.session.taskContract
11365
+ );
10740
11366
  const packed = trivialTurn ? emptyPackedContext(selectContextBudget(request, this.config, {
10741
11367
  intent: turnDirective.intent,
10742
11368
  trivial: true
10743
11369
  })) : await this.packContext(request, { intent: turnDirective.intent });
10744
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
+ }
10745
11403
  const mentions = await this.packMentions(request);
10746
11404
  const retrievedContext = trivialTurn && !mentions.length ? "" : buildRetrievedContext(
10747
11405
  packed,
@@ -10769,22 +11427,23 @@ var AgentRunner = class {
10769
11427
  scope: augmentation.memoryScope ?? "session"
10770
11428
  });
10771
11429
  }
10772
- const contractEnabled = shouldUseTaskContract(
10773
- request,
10774
- turnDirective.intent,
10775
- this.session.taskContract
10776
- );
10777
- if (contractEnabled && (!this.session.taskContract || this.session.taskContract.state === "satisfied")) {
10778
- this.session.taskContract = createDraftTaskContract(request, this.session.audit?.at(-1)?.id);
10779
- await this.persist();
10780
- await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
10781
- }
10782
11430
  activeRunContract = contractEnabled ? this.session.taskContract : void 0;
11431
+ let verificationAttempted = false;
11432
+ const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
11433
+ const epochTokenBudget = this.config.agent.maxEpochTokens ?? this.config.agent.maxSessionTokens;
11434
+ const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
11435
+ if (this.contextManager.shouldCompact(this.session, contextBudget)) {
11436
+ const compacted = await this.compactContext(void 0, options.signal, "automatic");
11437
+ if (compacted.status === "compacted") {
11438
+ await emit({ type: "context_compacted", ...compacted });
11439
+ }
11440
+ }
10783
11441
  const promptSections = [
10784
11442
  `intent:${turnDirective.intent}`,
10785
11443
  ...workspaceRules ? ["rules"] : [],
10786
11444
  ...this.session.workingMemory ? ["working-memory"] : [],
10787
- ...contractEnabled ? ["task-contract"] : [],
11445
+ ...contractEnabled && !this.session.compactedThroughMessageId ? ["task-contract"] : [],
11446
+ ...this.session.compactedThroughMessageId ? ["compaction-facts"] : [],
10788
11447
  ...this.session.contextSummary ? ["session-summary"] : [],
10789
11448
  ...!trivialTurn ? [`context:${packed.engine}`] : [],
10790
11449
  ...packed.text ? [`code:${packed.engine}`] : [],
@@ -10792,23 +11451,20 @@ var AgentRunner = class {
10792
11451
  ...augmentation.skills?.length ? [`skills:${augmentation.skills.length}`] : [],
10793
11452
  ...augmentation.memoryCount ? [`memory:${augmentation.memoryCount}`] : []
10794
11453
  ];
10795
- let verificationAttempted = false;
10796
- const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
10797
- const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
10798
- if (this.contextManager.shouldCompact(this.session, contextBudget)) {
10799
- const compacted = await this.compactContext(void 0, options.signal);
10800
- await emit({ type: "context_compacted", ...compacted });
10801
- }
10802
11454
  for (let turn = 1; turn <= maxTurns; turn += 1) {
10803
11455
  throwIfAborted(options.signal);
10804
11456
  if (this.session.usage.inputTokens + this.session.usage.outputTokens >= this.config.agent.maxSessionTokens) {
10805
11457
  return finishRun("token_budget");
10806
11458
  }
11459
+ if (contextEpochTokens(this.session) >= epochTokenBudget) {
11460
+ await this.rollContextEpoch("token_budget", options.signal, emit);
11461
+ }
10807
11462
  this.applySteering();
10808
11463
  await emit({ type: "thinking", turn });
10809
11464
  const dynamicPrompt = [
10810
11465
  buildSessionStatePrompt(this.session),
10811
11466
  turnDirective.text,
11467
+ `<intent-sufficiency route="${this.session.intentAssessment?.route ?? "direct_execute"}">${intentDirective}</intent-sufficiency>`,
10812
11468
  this.contextManager.buildShortTermPrompt(this.session),
10813
11469
  pinnedContext,
10814
11470
  options.turnInstructions ?? "",
@@ -10821,7 +11477,9 @@ var AgentRunner = class {
10821
11477
  activeMessages(this.session),
10822
11478
  contextBudget
10823
11479
  );
10824
- 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);
10825
11483
  const visibleTools = visibleToolDefinitions(
10826
11484
  this.tools,
10827
11485
  options.askMode === true,
@@ -10835,9 +11493,15 @@ var AgentRunner = class {
10835
11493
  loadedProgressiveTools
10836
11494
  );
10837
11495
  const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
10838
- if (availableTokens <= 0 || estimatedInputTokens >= availableTokens) {
11496
+ if (estimatedInputTokens >= availableLifetimeTokens) {
10839
11497
  return finishRun("token_budget");
10840
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
+ }
10841
11505
  const maxOutputTokens = Math.max(1, Math.min(
10842
11506
  this.config.model.maxTokens ?? 8192,
10843
11507
  availableTokens - estimatedInputTokens
@@ -10859,7 +11523,7 @@ var AgentRunner = class {
10859
11523
  breakdown
10860
11524
  });
10861
11525
  }
10862
- const assistantId = randomUUID12();
11526
+ const assistantId = randomUUID15();
10863
11527
  const response = await this.completeModel(
10864
11528
  messages,
10865
11529
  visibleTools,
@@ -11239,6 +11903,9 @@ ${completeContent}`;
11239
11903
  metadata.failure = receipt;
11240
11904
  } else {
11241
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;
11242
11909
  }
11243
11910
  const evidenceProgress = recovery.recordEvidence(call, {
11244
11911
  toolCallId: call.id,
@@ -11352,7 +12019,7 @@ ${completeContent}`;
11352
12019
  }
11353
12020
  appendAudit(event) {
11354
12021
  const audit = this.session.audit ?? (this.session.audit = []);
11355
- audit.push({ id: randomUUID12(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
12022
+ audit.push({ id: randomUUID15(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
11356
12023
  if (audit.length > 5e3) audit.splice(0, audit.length - 5e3);
11357
12024
  }
11358
12025
  async acceptChangedFiles(paths) {
@@ -11373,7 +12040,7 @@ ${completeContent}`;
11373
12040
  const results = [];
11374
12041
  for (const command2 of this.config.agent.verifyCommands) {
11375
12042
  const call = {
11376
- id: `verify-${randomUUID12()}`,
12043
+ id: `verify-${randomUUID15()}`,
11377
12044
  name: "shell",
11378
12045
  arguments: { command: command2, cwd: this.workspace.primaryRoot }
11379
12046
  };
@@ -11399,16 +12066,52 @@ ${completeContent}`;
11399
12066
  return [];
11400
12067
  }
11401
12068
  }
11402
- async compactContext(instructions, signal) {
12069
+ async compactContext(instructions, signal, mode = "manual") {
11403
12070
  const result = await this.contextManager.compact(
11404
12071
  this.session,
11405
12072
  this.provider,
11406
12073
  signal,
11407
- instructions
12074
+ instructions,
12075
+ mode
11408
12076
  );
12077
+ if (result.status === "compacted") {
12078
+ recordTokenUsage(
12079
+ this.session,
12080
+ result.receipt.actual,
12081
+ result.receipt.estimated.inputTokens,
12082
+ result.receipt.estimated.outputTokens
12083
+ );
12084
+ }
12085
+ const receipts = this.session.contextCompactionReceipts ?? (this.session.contextCompactionReceipts = []);
12086
+ receipts.push(result.receipt);
12087
+ if (receipts.length > 64) receipts.splice(0, receipts.length - 64);
11409
12088
  await this.persist();
11410
12089
  return result;
11411
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
+ }
11412
12115
  getContextStatus() {
11413
12116
  return this.contextManager.status(this.session);
11414
12117
  }
@@ -11440,8 +12143,14 @@ ${completeContent}`;
11440
12143
  toolOutputBudget() {
11441
12144
  const contextWindowTokens = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
11442
12145
  const activeContextTokens = this.contextManager.status(this.session, contextWindowTokens).activeTokens;
11443
- const remainingSessionTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
11444
- 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
+ );
11445
12154
  }
11446
12155
  async protectToolResult(result) {
11447
12156
  const metadata = { ...result.metadata ?? {} };
@@ -11497,7 +12206,7 @@ ${completeContent}`;
11497
12206
  };
11498
12207
  function message(role, content, extra = {}) {
11499
12208
  return {
11500
- id: randomUUID12(),
12209
+ id: randomUUID15(),
11501
12210
  role,
11502
12211
  content,
11503
12212
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -11612,6 +12321,7 @@ function recordTokenUsage(session, providerUsage, estimatedInputTokens, estimate
11612
12321
  const outputTokens = outputActual ?? estimatedOutputTokens;
11613
12322
  session.usage.inputTokens += inputTokens;
11614
12323
  session.usage.outputTokens += outputTokens;
12324
+ recordContextEpochUsage(session, inputTokens, outputTokens);
11615
12325
  if (inputActual !== void 0) {
11616
12326
  session.usage.actualInputTokens = (session.usage.actualInputTokens ?? 0) + inputActual;
11617
12327
  } else {
@@ -11687,6 +12397,11 @@ ${content}` : content,
11687
12397
  ...failure ? { metadata: { failure } } : {}
11688
12398
  };
11689
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
+ }
11690
12405
  function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable, duplicationAvailable, request, loadedProgressiveTools) {
11691
12406
  const eligible = tools.definitions().filter(
11692
12407
  (tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact") && (duplicationAvailable || tool.name !== "duplication_audit")
@@ -11894,7 +12609,7 @@ function integer(value, fallback, min, max) {
11894
12609
  }
11895
12610
 
11896
12611
  // src/agent/delegation.ts
11897
- import { randomUUID as randomUUID14 } from "node:crypto";
12612
+ import { randomUUID as randomUUID17 } from "node:crypto";
11898
12613
  import { join as join18 } from "node:path";
11899
12614
  import { z as z19 } from "zod";
11900
12615
 
@@ -12023,7 +12738,7 @@ function numeric(...values) {
12023
12738
  }
12024
12739
 
12025
12740
  // src/agent/team-store.ts
12026
- import { createHash as createHash16, randomUUID as randomUUID13 } from "node:crypto";
12741
+ import { createHash as createHash16, randomUUID as randomUUID16 } from "node:crypto";
12027
12742
  import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
12028
12743
  import { join as join16, resolve as resolve18 } from "node:path";
12029
12744
  import { z as z18 } from "zod";
@@ -12117,7 +12832,7 @@ var TeamRunStore = class {
12117
12832
  const now = (/* @__PURE__ */ new Date()).toISOString();
12118
12833
  const manifest = manifestSchema2.parse({
12119
12834
  version: 2,
12120
- id: randomUUID13(),
12835
+ id: randomUUID16(),
12121
12836
  workspace: this.workspace,
12122
12837
  objective: input2.objective,
12123
12838
  reviewer: input2.reviewer,
@@ -12923,7 +13638,7 @@ var DelegationManager = class {
12923
13638
  maxReviewRounds: 0
12924
13639
  });
12925
13640
  await emit?.({ type: "team_start", id: board.id, objective: task });
12926
- const writerId = randomUUID14();
13641
+ const writerId = randomUUID17();
12927
13642
  await emit?.({ type: "agent_queued", id: writerId, profile: profile.name, task, phase: "write" });
12928
13643
  const draft = await this.writerLane.createDraft(
12929
13644
  Math.min(this.team.maxWriterPatchBytes ?? 6e4, 12e4),
@@ -13293,7 +14008,7 @@ You are the only writer inside a disposable worktree. Make only the bounded requ
13293
14008
  const reviewer = reviewerOverride ?? this.team.reviewerProfile ?? "reviewer";
13294
14009
  const rounds = Math.max(0, this.team.maxReviewRounds ?? 1);
13295
14010
  const board = await this.teamStore?.create({ objective, reviewer, maxReviewRounds: rounds });
13296
- const runId = board?.id ?? randomUUID14();
14011
+ const runId = board?.id ?? randomUUID17();
13297
14012
  await emit?.({ type: "team_start", id: runId, objective });
13298
14013
  try {
13299
14014
  let results = await this.runBatch(board?.id, tasks, "work", emit, signal);
@@ -13351,7 +14066,7 @@ ${review.summary}`,
13351
14066
  }
13352
14067
  }
13353
14068
  async runBatch(runId, tasks, phase, emit, signal) {
13354
- const scheduled = tasks.map((task) => ({ id: randomUUID14(), task }));
14069
+ const scheduled = tasks.map((task) => ({ id: randomUUID17(), task }));
13355
14070
  for (const item of scheduled) {
13356
14071
  await emit?.({ type: "agent_queued", id: item.id, profile: item.task.profile, task: item.task.task, phase });
13357
14072
  }
@@ -13460,12 +14175,12 @@ Start the response with exactly VERDICT: ACCEPT when the evidence is sufficient,
13460
14175
  });
13461
14176
  }
13462
14177
  async peerMessage(runId, from, to, content, emit) {
13463
- const id = randomUUID14();
14178
+ const id = randomUUID17();
13464
14179
  if (runId) await this.teamStore?.recordMessage(runId, { id, from, to, content });
13465
14180
  await emit?.({ type: "agent_message", id, from, to, content });
13466
14181
  }
13467
14182
  async runOne(task, phase, emit, signal, retryOf, scheduledId) {
13468
- const id = scheduledId ?? randomUUID14();
14183
+ const id = scheduledId ?? randomUUID17();
13469
14184
  const profile = this.options.profiles.get(task.profile);
13470
14185
  const configuredRoute = this.team.routes?.[task.profile];
13471
14186
  const budgetMode = configuredRoute?.budgetMode ?? this.team.budgetMode ?? "observe";
@@ -14378,11 +15093,11 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14378
15093
  `);
14379
15094
  }
14380
15095
  printText(event) {
14381
- const { quiet, compact: compact2 } = this.options;
14382
- if (quiet) return;
15096
+ const { quiet, compact: compact3 } = this.options;
15097
+ if (quiet && event.type !== "needs_input") return;
14383
15098
  switch (event.type) {
14384
15099
  case "thinking":
14385
- 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}
14386
15101
  `));
14387
15102
  break;
14388
15103
  case "context": {
@@ -14394,7 +15109,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14394
15109
  break;
14395
15110
  }
14396
15111
  case "prompt":
14397
- if (!compact2) {
15112
+ if (!compact3) {
14398
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}` : "";
14399
15114
  process.stderr.write(this.paint.dim(
14400
15115
  `${this.glyphs.meta} prompt ${this.glyphs.separator} ${event.intent} ${this.glyphs.separator} ~${event.estimatedTokens} estimated tokens${partition}
@@ -14430,7 +15145,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14430
15145
  }
14431
15146
  break;
14432
15147
  case "tasks":
14433
- if (!compact2) {
15148
+ if (!compact3) {
14434
15149
  const completed = event.tasks.filter((task) => task.status === "completed").length;
14435
15150
  process.stderr.write(this.paint.dim(`${this.glyphs.meta} plan ${this.glyphs.separator} ${completed}/${event.tasks.length} complete
14436
15151
  `));
@@ -14451,6 +15166,22 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14451
15166
  `
14452
15167
  );
14453
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;
14454
15185
  case "usage":
14455
15186
  case "permission":
14456
15187
  case "skill":
@@ -14461,6 +15192,8 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
14461
15192
  case "agent_done":
14462
15193
  case "workflow":
14463
15194
  case "context_compacted":
15195
+ case "context_epoch":
15196
+ case "intent":
14464
15197
  break;
14465
15198
  case "done":
14466
15199
  this.printCompletion(event.completion);
@@ -14532,6 +15265,10 @@ function sessionSummary(session) {
14532
15265
  changedFiles: session.changedFiles,
14533
15266
  ...session.lastRun ? { lastRun: session.lastRun } : {},
14534
15267
  ...session.tokenLedger?.length ? { tokenLedger: session.tokenLedger } : {},
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 } : {},
14535
15272
  usage: session.usage
14536
15273
  };
14537
15274
  }
@@ -15224,7 +15961,7 @@ function ToolGlyph({ state, glyphs }) {
15224
15961
  if (state === "cancelled") return /* @__PURE__ */ jsx(Text, { color: theme.warning, children: glyphs.warning });
15225
15962
  return /* @__PURE__ */ jsx(Text, { color: theme.error, children: glyphs.error });
15226
15963
  }
15227
- 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 }) {
15228
15965
  const theme = useTheme();
15229
15966
  const glyphs = resolveGlyphs(glyphMode);
15230
15967
  if (!items.length) {
@@ -15232,13 +15969,13 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15232
15969
  }
15233
15970
  return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
15234
15971
  if (item.kind === "user") {
15235
- return /* @__PURE__ */ jsxs(Box, { marginBottom: compact2 || item.clipped ? 0 : 1, children: [
15972
+ return /* @__PURE__ */ jsxs(Box, { marginBottom: compact3 || item.clipped ? 0 : 1, children: [
15236
15973
  /* @__PURE__ */ jsx(Box, { width: 2, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: glyphs.prompt }) }),
15237
15974
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.textStrong, wrap: "wrap", children: sanitizeTerminalText(item.text) })
15238
15975
  ] }, item.id);
15239
15976
  }
15240
15977
  if (item.kind === "assistant") {
15241
- 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: [
15242
15979
  /* @__PURE__ */ jsxs(Text, { bold: true, color: theme.accent, children: [
15243
15980
  glyphs.brand,
15244
15981
  " ",
@@ -15254,7 +15991,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15254
15991
  }
15255
15992
  if (item.kind === "context") {
15256
15993
  const spans = item.spans ?? [];
15257
- const spanLimit = compact2 ? 2 : 3;
15994
+ const spanLimit = compact3 ? 2 : 3;
15258
15995
  const innerWidth = Math.max(1, safeWidth(width) - 2);
15259
15996
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
15260
15997
  /* @__PURE__ */ jsx(
@@ -15267,7 +16004,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15267
16004
  labelColor: theme.accent
15268
16005
  }
15269
16006
  ),
15270
- !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,
15271
16008
  spans.slice(0, spanLimit).map((span, spanIndex) => {
15272
16009
  const lines = span.startLine === span.endLine ? `${span.startLine}` : `${span.startLine}-${span.endLine}`;
15273
16010
  const location = `${compactDisplayPath(sanitizeInlineTerminalText(span.path), 44)}:${lines}`;
@@ -15307,7 +16044,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15307
16044
  const duration = item.durationMs !== void 0 ? formatDuration(item.durationMs) : "";
15308
16045
  const detailText = [detail, duration].filter(Boolean).join(" ");
15309
16046
  const expanded = Boolean(item.output) && (showToolOutput || expandedToolId === item.id);
15310
- 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;
15311
16048
  const disclosure = item.output ? expanded ? glyphs.expanded : glyphs.collapsed : "";
15312
16049
  const disclosureWidth = disclosure ? displayWidth(disclosure) + 1 : 0;
15313
16050
  const nameLimit = Math.max(1, Math.min(rowWidth - 2 - disclosureWidth, rowWidth < 64 ? rowWidth - 2 - disclosureWidth : 28));
@@ -15439,9 +16176,28 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
15439
16176
  item.id
15440
16177
  );
15441
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
+ }
15442
16198
  if (item.kind === "list") return /* @__PURE__ */ jsx(ListPanel, { title: item.title, entries: item.entries, width, glyphMode }, item.id);
15443
16199
  if (item.kind === "context-inspector") {
15444
- 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);
15445
16201
  }
15446
16202
  if (item.kind === "theme") return /* @__PURE__ */ jsx(ThemePreview, { name: item.name, width, glyphs }, item.id);
15447
16203
  if (item.kind === "banner") {
@@ -15651,7 +16407,7 @@ function TaskRail({ tasks, width = 80, glyphMode = "auto", maxItems }) {
15651
16407
  ] }) : null
15652
16408
  ] });
15653
16409
  }
15654
- 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 }) {
15655
16411
  const theme = useTheme();
15656
16412
  const glyphs = resolveGlyphs(glyphMode);
15657
16413
  const summary = permissionSummary(call);
@@ -15688,7 +16444,7 @@ function PermissionCard({ call, category, width = 80, glyphMode = "auto", worksp
15688
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: [
15689
16445
  /* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
15690
16446
  /* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
15691
- ] }) : compact2 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
16447
+ ] }) : compact3 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
15692
16448
  /* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
15693
16449
  /* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
15694
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)) })
@@ -15949,15 +16705,16 @@ function MeterBar({ segments, total, width, glyphs }) {
15949
16705
  remainder ? /* @__PURE__ */ jsx(Text, { color: theme.border, children: glyphs.meterEmpty.repeat(remainder) }) : null
15950
16706
  ] });
15951
16707
  }
15952
- 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" }) {
15953
16709
  const theme = useTheme();
15954
16710
  const glyphs = resolveGlyphs(glyphMode);
16711
+ const hasCompactedContext = status.compactedMessages > 0 || Boolean(summary);
15955
16712
  if (minimal) {
15956
16713
  const rowWidth2 = safeWidth(width);
15957
16714
  const padding = rowWidth2 >= 4 ? 2 : 0;
15958
16715
  const innerWidth2 = Math.max(1, rowWidth2 - padding);
15959
16716
  const active = `${status.messageCount} msg ${glyphs.separator} ~${formatTokens(status.activeTokens)} tok`;
15960
- const focus = sanitizeTerminalText(working?.focus || working?.goal || (summary ? "summary ready" : "not established")).replace(/\s+/g, " ").trim() || "not established";
16717
+ const focus = sanitizeTerminalText(working?.focus || working?.goal || (hasCompactedContext ? "handoff ready" : "not established")).replace(/\s+/g, " ").trim() || "not established";
15961
16718
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: padding, children: [
15962
16719
  /* @__PURE__ */ jsx(Text, { bold: true, color: theme.textStrong, children: truncateDisplay(`Context ${formatPercent(status.pressure)} ${glyphs.separator} ${active}`, innerWidth2) }),
15963
16720
  /* @__PURE__ */ jsx(Text, { color: working ? theme.text : theme.muted, children: truncateDisplay(`working ${focus}`, innerWidth2) })
@@ -15966,13 +16723,19 @@ function ContextInspector({ status, working, summary, width, memory, connections
15966
16723
  const entries = [
15967
16724
  { label: "active", detail: `${status.messageCount} messages ${glyphs.separator} ~${formatTokens(status.activeTokens)} tokens ${glyphs.separator} tools ~${formatTokens(status.toolTokens)}` },
15968
16725
  { label: "short-term", detail: working ? `${working.focus || working.goal || "ready"} ${glyphs.separator} ${relativeTime(working.lastUpdatedAt)}` : "not established" },
15969
- { label: "summary", detail: summary ? `~${formatTokens(status.summaryTokens)} tokens ${glyphs.separator} ${status.compactedMessages} compacted` : "not created" },
16726
+ { label: "summary", detail: hasCompactedContext ? `~${formatTokens(status.summaryTokens)} tokens ${glyphs.separator} ${status.compactedMessages} compacted${summary ? "" : ` ${glyphs.separator} facts`}` : "not created" },
15970
16727
  { label: "long-term", detail: memory ?? `retrieved by relevance ${glyphs.separator} untrusted context` }
15971
16728
  ];
15972
- if (!compact2 && working?.constraints.length) entries.push({ label: `constraints ${working.constraints.length}`, detail: working.constraints.slice(0, 2).join(` ${glyphs.separator} `) });
15973
- if (!compact2 && working?.decisions.length) entries.push({ label: `decisions ${working.decisions.length}`, detail: working.decisions.slice(0, 2).join(` ${glyphs.separator} `) });
15974
- if (!compact2 && working?.openQuestions.length) entries.push({ label: `open ${working.openQuestions.length}`, detail: working.openQuestions.slice(0, 2).join(` ${glyphs.separator} `), tone: "warning" });
15975
- 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} `) });
15976
16739
  if (sources2?.length) {
15977
16740
  const pinned = sources2.filter((source) => source.state === "pinned");
15978
16741
  const muted = sources2.filter((source) => source.state === "muted");
@@ -15988,7 +16751,7 @@ function ContextInspector({ status, working, summary, width, memory, connections
15988
16751
  const rowWidth = safeWidth(width);
15989
16752
  const innerWidth = Math.max(1, rowWidth - 2);
15990
16753
  const pressureColor = status.pressure >= 0.9 ? theme.error : status.pressure >= 0.75 ? theme.warning : theme.accent;
15991
- const summaryTokens = summary ? status.summaryTokens : 0;
16754
+ const summaryTokens = hasCompactedContext ? status.summaryTokens : 0;
15992
16755
  const segments = [
15993
16756
  { label: "active", value: status.activeTokens, color: theme.accent },
15994
16757
  { label: "tools", value: status.toolTokens, color: theme.assistant },
@@ -16006,7 +16769,7 @@ function ContextInspector({ status, working, summary, width, memory, connections
16006
16769
  /* @__PURE__ */ jsx(MeterBar, { segments, total: meterTotal, width: meterWidth, glyphs })
16007
16770
  ] }) : null
16008
16771
  ] }),
16009
- /* @__PURE__ */ jsx(ListPanel, { title: "", hideTitle: true, entries, width, glyphMode })
16772
+ /* @__PURE__ */ jsx(ListPanel, { title: "", hideTitle: true, entries, width: Math.max(1, rowWidth - 2), glyphMode })
16010
16773
  ] });
16011
16774
  }
16012
16775
  function ThemePreview({ name, width, glyphs }) {
@@ -16093,8 +16856,8 @@ function Banner({ model, engine, workspace, version, width, glyphs }) {
16093
16856
  function UpdateNotice({ current, latest, command: command2, highlights, width, glyphs }) {
16094
16857
  const theme = useTheme();
16095
16858
  const availableWidth = safeWidth(width);
16096
- const compact2 = availableWidth < 48;
16097
- const parts = compact2 ? [
16859
+ const compact3 = availableWidth < 48;
16860
+ const parts = compact3 ? [
16098
16861
  { text: `${glyphs.up} `, color: theme.accent, bold: true },
16099
16862
  { text: `v${current}`, color: theme.dim, bold: false },
16100
16863
  { text: ` ${glyphs.arrow} `, color: theme.muted, bold: false },
@@ -16115,7 +16878,7 @@ function UpdateNotice({ current, latest, command: command2, highlights, width, g
16115
16878
  const bullets = bulletWidth > 0 ? (highlights ?? []).map((line) => truncateDisplay(sanitizeInlineTerminalText(line), bulletWidth)) : [];
16116
16879
  return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [
16117
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)) }),
16118
- 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,
16119
16882
  bullets.map((line, index) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: `${bulletPrefix}${line}` }, index))
16120
16883
  ] });
16121
16884
  }
@@ -17023,9 +17786,9 @@ function tailText(value, width, maxRows) {
17023
17786
  return `${marker}
17024
17787
  ${selected.join("\n")}`;
17025
17788
  }
17026
- function estimateTimelineItemRows(item, { width, compact: compact2 = false, showToolOutput = false, expandedToolId }) {
17789
+ function estimateTimelineItemRows(item, { width, compact: compact3 = false, showToolOutput = false, expandedToolId }) {
17027
17790
  const rowWidth = Math.max(1, Math.floor(width));
17028
- const gap = compact2 ? 0 : 1;
17791
+ const gap = compact3 ? 0 : 1;
17029
17792
  if (item.kind === "user") return wrappedRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
17030
17793
  if (item.kind === "assistant") {
17031
17794
  return 1 + richTextRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
@@ -17037,7 +17800,7 @@ function estimateTimelineItemRows(item, { width, compact: compact2 = false, show
17037
17800
  const detail = item.errorDetail || item.detail;
17038
17801
  const detailRows = narrow && detail ? 1 : 0;
17039
17802
  const metaRows = item.meta ? 1 : 0;
17040
- 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;
17041
17804
  return 1 + detailRows + metaRows + outputRows;
17042
17805
  }
17043
17806
  if (item.kind === "list") {
@@ -17051,7 +17814,7 @@ function estimateTimelineItemRows(item, { width, compact: compact2 = false, show
17051
17814
  if (item.kind === "theme") return 3;
17052
17815
  if (item.kind === "context") {
17053
17816
  const metaRows = rowWidth < 64 ? 2 : 1;
17054
- const spanLimit = compact2 ? 2 : 3;
17817
+ const spanLimit = compact3 ? 2 : 3;
17055
17818
  const spanCount = Math.min(item.spans?.length ?? 0, spanLimit);
17056
17819
  const moreRow = (item.spans?.length ?? 0) > spanLimit ? 1 : 0;
17057
17820
  const degradationRows = item.degradation ? metaRows : 0;
@@ -17281,7 +18044,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17281
18044
  const colorEnabled = config.ui.color && !process.env.NO_COLOR;
17282
18045
  const [theme, setTheme] = useState2(() => resolveThemeWithColor(config.ui.theme, colorEnabled));
17283
18046
  const [themeCatalogRevision, setThemeCatalogRevision] = useState2(0);
17284
- const [compact2, setCompact] = useState2(config.ui.compact);
18047
+ const [compact3, setCompact] = useState2(config.ui.compact);
17285
18048
  const [interactionMode, setInteractionMode] = useState2(planMode ? "plan" : askMode ? "ask" : "build");
17286
18049
  const [input2, setInput] = useState2("");
17287
18050
  const [busy, setBusy] = useState2(false);
@@ -17318,6 +18081,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17318
18081
  const controller = useRef2(void 0);
17319
18082
  const processing = useRef2(false);
17320
18083
  const queued = useRef2([]);
18084
+ const clarificationBacklog = useRef2([]);
17321
18085
  const stopRequested = useRef2(false);
17322
18086
  const startedInitial = useRef2(false);
17323
18087
  const lastSubmitted = useRef2(void 0);
@@ -17558,6 +18322,26 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17558
18322
  append({ id: nextId(), kind: "compaction", messages: event.omittedMessages, tokens: event.summaryTokens });
17559
18323
  refreshSession();
17560
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;
17561
18345
  case "usage":
17562
18346
  refreshSession();
17563
18347
  break;
@@ -17801,8 +18585,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17801
18585
  return true;
17802
18586
  }
17803
18587
  if (subcommand === "list") {
17804
- const list2 = runner.listContextSources();
17805
- appendList("Pinned context", list2.length ? list2.map((source) => ({
18588
+ const list = runner.listContextSources();
18589
+ appendList("Pinned context", list.length ? list.map((source) => ({
17806
18590
  label: `${source.state === "muted" ? "muted " : "pinned"} ${source.path}`,
17807
18591
  detail: `~${source.tokens} tokens${separator}added ${source.addedAt.slice(0, 10)}`,
17808
18592
  tone: source.state === "muted" ? "warning" : "success"
@@ -17990,7 +18774,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
17990
18774
  }
17991
18775
  if (command2 === "density") {
17992
18776
  const normalized = argument.toLocaleLowerCase();
17993
- 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;
17994
18778
  if (next === void 0) throw new Error("Usage: /density [compact|comfortable]");
17995
18779
  setCompact(next);
17996
18780
  await saveUiPreference({ compact: next });
@@ -18083,7 +18867,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18083
18867
  })));
18084
18868
  return true;
18085
18869
  }
18086
- }, [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]);
18087
18871
  const submit = useCallback(async (raw, mode = "normal") => {
18088
18872
  const trimmed = raw.trim();
18089
18873
  if (!trimmed) return;
@@ -18098,6 +18882,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18098
18882
  if (processing.current && isExitCommand(trimmed)) {
18099
18883
  stopRequested.current = true;
18100
18884
  queued.current = [];
18885
+ clarificationBacklog.current = [];
18101
18886
  setQueue([]);
18102
18887
  controller.current?.abort();
18103
18888
  exit();
@@ -18185,6 +18970,31 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18185
18970
  const snapshot = snapshotSession(nextSession);
18186
18971
  setSession(snapshot);
18187
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
+ }
18188
18998
  } catch (error) {
18189
18999
  const message2 = error instanceof Error ? error.message : String(error);
18190
19000
  if (!abortController.signal.aborted && message2 !== lastEventError.current) {
@@ -18197,8 +19007,9 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18197
19007
  }
18198
19008
  }
18199
19009
  if (abortController.signal.aborted || stopRequested.current) {
18200
- const discarded = queued.current.length;
19010
+ const discarded = queued.current.length + clarificationBacklog.current.length;
18201
19011
  queued.current = [];
19012
+ clarificationBacklog.current = [];
18202
19013
  setQueue([]);
18203
19014
  if (discarded) append({ id: nextId(), kind: "notice", text: `Discarded ${discarded} queued follow-up${discarded === 1 ? "" : "s"}.` });
18204
19015
  break;
@@ -18447,7 +19258,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
18447
19258
  const tokenTotal = session.usage.inputTokens + session.usage.outputTokens;
18448
19259
  const contextStatus = runner.getContextStatus();
18449
19260
  const frame = spinnerFrames()[frameIndex % spinnerFrames().length];
18450
- const compactUi = compact2 || terminalHeight < 28;
19261
+ const compactUi = compact3 || terminalHeight < 28;
18451
19262
  const constrainedHeight = terminalHeight < 18;
18452
19263
  const compactComposer = terminalHeight < 18;
18453
19264
  const minimalInspector = terminalHeight < 22;
@@ -18663,6 +19474,9 @@ function initialTimeline(session, banner, setupProblem) {
18663
19474
  text: `Contract ${session.taskContract.state} | ${satisfied}/${required.length} accepted`
18664
19475
  });
18665
19476
  }
19477
+ if (session.pendingInput) {
19478
+ items.push({ id: `pending-input-${session.pendingInput.id}`, kind: "clarification", pending: session.pendingInput });
19479
+ }
18666
19480
  if (setupProblem && items.length <= 1) items.push({ id: nextId(), kind: "notice", tone: "error", text: setupProblem });
18667
19481
  return items;
18668
19482
  }
@@ -18708,6 +19522,35 @@ function snapshotSession(source) {
18708
19522
  relevantFiles: [...source.workingMemory.relevantFiles]
18709
19523
  }
18710
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
+ } : {},
18711
19554
  usage: { ...source.usage }
18712
19555
  };
18713
19556
  }
@@ -18754,17 +19597,17 @@ function composerAttachments(value) {
18754
19597
  const paths = [...value.matchAll(/(?:^|\s)@([^\s]+)/g)].map((match) => match[1]).filter((path) => Boolean(path));
18755
19598
  return [...new Set(paths)].slice(-3);
18756
19599
  }
18757
- function permissionRows(width, hasCwd, compact2) {
19600
+ function permissionRows(width, hasCwd, compact3) {
18758
19601
  const content = 3 + (hasCwd ? 1 : 0);
18759
19602
  if (width >= 64) return content + 2;
18760
19603
  if (width >= 28) return content + 3;
18761
- if (compact2) return content + 3;
19604
+ if (compact3) return content + 3;
18762
19605
  return content + 5;
18763
19606
  }
18764
- function contextInspectorRows(session, compact2, width, minimal) {
19607
+ function contextInspectorRows(session, compact3, width, minimal) {
18765
19608
  if (minimal) return 2;
18766
19609
  const working = session.workingMemory;
18767
- 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));
18768
19611
  return 2 + entries * (width < 52 ? 2 : 1);
18769
19612
  }
18770
19613
  function contextInspectorStatus(status) {
@@ -18774,7 +19617,13 @@ function contextInspectorStatus(status) {
18774
19617
  activeTokens: status.activeTokens,
18775
19618
  summaryTokens: status.summaryTokens,
18776
19619
  toolTokens: status.toolTokens,
18777
- 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
18778
19627
  };
18779
19628
  }
18780
19629
  function toolDetail2(call) {
@@ -18816,14 +19665,14 @@ function WorkspacePreparationView({
18816
19665
  const theme = useTheme();
18817
19666
  const safeWidth2 = Math.max(1, Math.floor(width));
18818
19667
  const innerWidth = Math.max(1, safeWidth2 - (safeWidth2 >= 24 ? 4 : 0));
18819
- const compact2 = safeWidth2 < 48;
19668
+ const compact3 = safeWidth2 < 48;
18820
19669
  const constrained = height < 14;
18821
19670
  const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
18822
19671
  const spinner = (ascii ? [".", "o", "O", "o"] : ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"])[frame % (ascii ? 4 : 6)];
18823
19672
  const separator = ascii ? "|" : "\xB7";
18824
19673
  const brand = ascii ? "*" : PRODUCT_MARK;
18825
19674
  const phase = readiness ? "ready" : error ? "error" : progress.phase;
18826
- const phaseLabel = preparationLabel(phase, progress, readiness, compact2);
19675
+ const phaseLabel = preparationLabel(phase, progress, readiness, compact3);
18827
19676
  const detail = preparationDetail(phase, progress, readiness, error);
18828
19677
  const modelLine = `model ${sanitizeTerminalText(model)}`;
18829
19678
  const workspaceLine = `workspace ${compactDisplayPath(sanitizeTerminalText(workspace), Math.max(1, innerWidth - 10))}`;
@@ -18835,14 +19684,14 @@ function WorkspacePreparationView({
18835
19684
  " ",
18836
19685
  PRODUCT_NAME.toUpperCase()
18837
19686
  ] }),
18838
- !compact2 && !constrained ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
19687
+ !compact3 && !constrained ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
18839
19688
  " ",
18840
19689
  separator,
18841
19690
  " LOCAL CONTEXT"
18842
19691
  ] }) : null
18843
19692
  ] }),
18844
19693
  !constrained ? /* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: truncateDisplay("Ground the workspace before the first request.", innerWidth) }) : null,
18845
- !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: [
18846
19695
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(modelLine, innerWidth) }),
18847
19696
  /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) })
18848
19697
  ] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) }),
@@ -18856,10 +19705,10 @@ function WorkspacePreparationView({
18856
19705
  step2.glyph,
18857
19706
  " "
18858
19707
  ] }),
18859
- /* @__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) }),
18860
19709
  /* @__PURE__ */ jsxs4(Text4, { color: step2.state === "active" ? theme.muted : theme.dim, children: [
18861
19710
  " ",
18862
- 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)))
18863
19712
  ] })
18864
19713
  ] }, step2.id)) }),
18865
19714
  /* @__PURE__ */ jsxs4(Box3, { marginTop: 1, children: [
@@ -19000,9 +19849,9 @@ async function runWorkspacePreparation(engine, config, options = {}) {
19000
19849
  await instance.waitUntilExit();
19001
19850
  return result ?? { status: "cancelled" };
19002
19851
  }
19003
- function preparationLabel(phase, progress, readiness, compact2 = false) {
19852
+ function preparationLabel(phase, progress, readiness, compact3 = false) {
19004
19853
  if (phase === "ready") {
19005
- if (compact2) return "Index verified";
19854
+ if (compact3) return "Index verified";
19006
19855
  return readiness?.rebuilt ? "Workspace index created and verified" : "Workspace index verified";
19007
19856
  }
19008
19857
  if (phase === "error") return "Workspace preparation failed";
@@ -19406,7 +20255,7 @@ function OnboardingFlow({ initialConfig, saveConfig, onFinish }) {
19406
20255
  }, [finish, saveConfig, state]);
19407
20256
  return /* @__PURE__ */ jsx5(OnboardingScreen, { state, dispatch, width, compact: compactHeight });
19408
20257
  }
19409
- function OnboardingScreen({ state, dispatch, width, compact: compact2 = false }) {
20258
+ function OnboardingScreen({ state, dispatch, width, compact: compact3 = false }) {
19410
20259
  const theme = useTheme();
19411
20260
  const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
19412
20261
  const marker = ascii ? ">" : "\u203A";
@@ -19423,14 +20272,14 @@ function OnboardingScreen({ state, dispatch, width, compact: compact2 = false })
19423
20272
  ] }),
19424
20273
  /* @__PURE__ */ jsx5(Text6, { color: theme.border, children: truncateDisplay(stageDivider(ascii, headerWidth), headerWidth) }),
19425
20274
  /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(stage.name, headerWidth) }),
19426
- !compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
20275
+ !compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
19427
20276
  /* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: truncateDisplay(titleForStep(state.step), headerWidth) }),
19428
- !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,
19429
20278
  summary ? /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(summary, headerWidth) }) : null,
19430
- !compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
19431
- state.step === "method" ? /* @__PURE__ */ jsx5(OptionList, { options: methods, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
19432
- state.step === "official-provider" ? /* @__PURE__ */ jsx5(OptionList, { options: officialProviders, selected: state.selected, marker, width: headerWidth, compact: compact2 }) : null,
19433
- 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,
19434
20283
  inputField ? /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
19435
20284
  /* @__PURE__ */ jsxs5(Box4, { children: [
19436
20285
  /* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: inputField.label }),
@@ -19458,18 +20307,18 @@ function OnboardingScreen({ state, dispatch, width, compact: compact2 = false })
19458
20307
  "! ",
19459
20308
  truncateDisplay(state.error, Math.max(1, headerWidth - 2))
19460
20309
  ] }) : null,
19461
- !compact2 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
20310
+ !compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
19462
20311
  /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: footerForStep(state, headerWidth) })
19463
20312
  ] });
19464
20313
  }
19465
- function OptionList({ options, selected, marker, width, compact: compact2 }) {
20314
+ function OptionList({ options, selected, marker, width, compact: compact3 }) {
19466
20315
  const theme = useTheme();
19467
- return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((option, index) => {
20316
+ return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((option2, index) => {
19468
20317
  const active = index === selected;
19469
20318
  const prefix = active ? `${marker} ` : " ";
19470
20319
  const available = Math.max(1, width - displayWidth(prefix) - (active ? 2 : 0));
19471
- const label = `${prefix}${truncateDisplay(option.label, available)}`;
19472
- 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: [
19473
20322
  /* @__PURE__ */ jsx5(
19474
20323
  Text6,
19475
20324
  {
@@ -19479,8 +20328,8 @@ function OptionList({ options, selected, marker, width, compact: compact2 }) {
19479
20328
  children: active ? padDisplay(label, width) : label
19480
20329
  }
19481
20330
  ),
19482
- !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
19483
- ] }, 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);
19484
20333
  }) });
19485
20334
  }
19486
20335
  function Confirmation({ state, width }) {
@@ -21181,7 +22030,7 @@ process.on("warning", (warning) => {
21181
22030
  var program = new Command();
21182
22031
  program.enablePositionalOptions();
21183
22032
  program.name(PRODUCT_COMMAND).description("A context-first, model-agnostic coding agent with an auditable workspace.").version(package_default.version).showSuggestionAfterError();
21184
- 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) => {
21185
22034
  await runChat(prompts, options);
21186
22035
  });
21187
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) => {
@@ -21872,6 +22721,7 @@ async function runChat(prompts, options) {
21872
22721
  requestPermission
21873
22722
  });
21874
22723
  for (const queued of options.queue) {
22724
+ if (session.pendingInput) break;
21875
22725
  session = await runner.run(queued, {
21876
22726
  askMode: options.ask === true || options.plan === true,
21877
22727
  ...options.plan ? { turnInstructions: PLAN_MODE_INSTRUCTIONS } : {},
@@ -21881,6 +22731,7 @@ async function runChat(prompts, options) {
21881
22731
  });
21882
22732
  }
21883
22733
  reporter.finish(session);
22734
+ if (session.pendingInput) process.exitCode = 2;
21884
22735
  } catch (error) {
21885
22736
  reporter.fail(error);
21886
22737
  process.exitCode = 1;
@@ -22049,6 +22900,7 @@ async function runtimeConfig(workspaceInput, options) {
22049
22900
  agent: {
22050
22901
  ...loaded.agent,
22051
22902
  ...options.checkpoint === false ? { checkpointBeforeWrite: false } : {},
22903
+ ...options.epochTokenBudget ? { maxEpochTokens: positiveInt(options.epochTokenBudget, loaded.agent.maxEpochTokens ?? loaded.agent.maxSessionTokens) } : {},
22052
22904
  ...options.tokenBudget ? { maxSessionTokens: positiveInt(options.tokenBudget, loaded.agent.maxSessionTokens) } : {}
22053
22905
  },
22054
22906
  ui: { ...loaded.ui, ...options.color === false ? { color: false } : {} }
@@ -22198,6 +23050,7 @@ function runtimeOptions(options) {
22198
23050
  const provider = options.provider ?? root.provider;
22199
23051
  const model = options.model ?? root.model;
22200
23052
  const baseUrl = options.baseUrl ?? root.baseUrl;
23053
+ const epochTokenBudget = options.epochTokenBudget ?? root.epochTokenBudget;
22201
23054
  const tokenBudget = options.tokenBudget ?? root.tokenBudget;
22202
23055
  return {
22203
23056
  addWorkspace: [...root.addWorkspace ?? [], ...options.addWorkspace ?? []],
@@ -22206,6 +23059,7 @@ function runtimeOptions(options) {
22206
23059
  ...model ? { model } : {},
22207
23060
  ...baseUrl ? { baseUrl } : {},
22208
23061
  ...options.maxTokens ? { maxTokens: options.maxTokens } : {},
23062
+ ...epochTokenBudget ? { epochTokenBudget } : {},
22209
23063
  ...tokenBudget ? { tokenBudget } : {},
22210
23064
  ...root.color !== void 0 ? { color: root.color } : {},
22211
23065
  ...root.checkpoint !== void 0 ? { checkpoint: root.checkpoint } : {},