@skein-code/cli 0.3.24 → 0.3.26
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/README.md +39 -2
- package/dist/cli.js +1114 -169
- package/dist/cli.js.map +1 -1
- package/docs/ARCHITECTURE.md +32 -11
- package/docs/NEXT_STEPS.md +50 -3
- package/docs/headless-output.schema.json +36 -0
- package/examples/config.yaml +2 -1
- package/package.json +12 -1
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.
|
|
223
|
+
version: "0.3.26",
|
|
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,17 @@ var package_default = {
|
|
|
236
236
|
},
|
|
237
237
|
skein: {
|
|
238
238
|
releaseNotes: [
|
|
239
|
+
"First-run setup now states that the primary agent needs API credentials and that provider subscriptions or signed-in coding CLIs are separate delegated tools",
|
|
240
|
+
"Permission prompts show the policy reason, redacted target, working directory, category risk, and explicit once, session, deny, and stop choices",
|
|
241
|
+
"The Recovery Center joins last-run state, failure repair hints, changed files, checkpoints, diff, audit, rollback, retry, and safe session resume",
|
|
242
|
+
"Read-only review commands pin working-tree, commit, or branch scope and inject a content-free redacted evidence bundle without storing it in the user transcript",
|
|
243
|
+
"JSON and JSONL terminal records now follow the published headless v1 schema with stable completed, verification, input, blocked, cancellation, turn, token, and error exit codes",
|
|
244
|
+
"Short terminal viewports clip long lists without overflow and pending clarifications cannot be consumed by recovery commands",
|
|
245
|
+
"Durable sessions now separate a 250k-token context epoch from a 1m-token lifetime ceiling without changing the session id or deleting transcript history",
|
|
246
|
+
"Content-free epoch handoffs preserve Task Contract criteria, unresolved failure circuits, changed files, and verification receipts across long runs",
|
|
247
|
+
"Intent Sufficiency routes clear requests to execution, repository-inferable gaps to inspection, and genuine product choices to one persisted clarification",
|
|
248
|
+
"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",
|
|
249
|
+
"Successful identical tool calls clear matching historical failure signatures from later epoch handoffs",
|
|
239
250
|
"Context compaction now rebuilds authoritative task, working-state, verification, permission, failure, and artifact facts outside generated narrative",
|
|
240
251
|
"Automatic compaction runs only when three predicted prompt reuses produce positive net token savings while explicit compact commands remain available",
|
|
241
252
|
"Compaction provider usage is included in session totals with separate content-free actual or estimated receipts",
|
|
@@ -1993,6 +2004,7 @@ var partialConfigSchema = z2.object({
|
|
|
1993
2004
|
}).partial().optional(),
|
|
1994
2005
|
agent: z2.object({
|
|
1995
2006
|
maxTurns: z2.number().int().positive().optional(),
|
|
2007
|
+
maxEpochTokens: z2.number().int().positive().optional(),
|
|
1996
2008
|
maxSessionTokens: z2.number().int().positive().optional(),
|
|
1997
2009
|
autoVerify: z2.boolean().optional(),
|
|
1998
2010
|
verifyCommands: z2.array(z2.string()).optional(),
|
|
@@ -2072,7 +2084,8 @@ function defaultConfig(workspace = process.cwd()) {
|
|
|
2072
2084
|
hooks: {},
|
|
2073
2085
|
agent: {
|
|
2074
2086
|
maxTurns: 24,
|
|
2075
|
-
|
|
2087
|
+
maxEpochTokens: 25e4,
|
|
2088
|
+
maxSessionTokens: 1e6,
|
|
2076
2089
|
autoVerify: true,
|
|
2077
2090
|
verifyCommands: [],
|
|
2078
2091
|
checkpointBeforeWrite: true
|
|
@@ -2436,6 +2449,7 @@ function configSummary(config) {
|
|
|
2436
2449
|
workspaceRoots: config.workspaceRoots,
|
|
2437
2450
|
permissions: config.permissions,
|
|
2438
2451
|
maxTurns: config.agent.maxTurns,
|
|
2452
|
+
maxEpochTokens: config.agent.maxEpochTokens,
|
|
2439
2453
|
maxSessionTokens: config.agent.maxSessionTokens,
|
|
2440
2454
|
autoVerify: config.agent.autoVerify,
|
|
2441
2455
|
skills: config.skills ? {
|
|
@@ -4720,10 +4734,127 @@ function formatContextHits(hits, roots) {
|
|
|
4720
4734
|
}
|
|
4721
4735
|
|
|
4722
4736
|
// src/agent/runner.ts
|
|
4723
|
-
import { randomUUID as
|
|
4737
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
4724
4738
|
|
|
4725
4739
|
// src/context/manager.ts
|
|
4740
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
4741
|
+
|
|
4742
|
+
// src/context/epochs.ts
|
|
4726
4743
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
4744
|
+
var MAX_EPOCHS = 64;
|
|
4745
|
+
var MAX_HANDOFF_FAILURES = 16;
|
|
4746
|
+
function activeContextEpoch(session) {
|
|
4747
|
+
return ensureContextEpoch(session);
|
|
4748
|
+
}
|
|
4749
|
+
function ensureContextEpoch(session) {
|
|
4750
|
+
const epochs = session.contextEpochs ?? (session.contextEpochs = []);
|
|
4751
|
+
const active = epochs.at(-1);
|
|
4752
|
+
if (active && active.finishedAt === void 0) return active;
|
|
4753
|
+
const next = createEpoch((active?.index ?? 0) + 1);
|
|
4754
|
+
epochs.push(next);
|
|
4755
|
+
trimEpochs(epochs);
|
|
4756
|
+
return next;
|
|
4757
|
+
}
|
|
4758
|
+
function recordContextEpochUsage(session, inputTokens, outputTokens) {
|
|
4759
|
+
const epoch = ensureContextEpoch(session);
|
|
4760
|
+
epoch.usage.inputTokens += boundedTokens(inputTokens);
|
|
4761
|
+
epoch.usage.outputTokens += boundedTokens(outputTokens);
|
|
4762
|
+
return epoch;
|
|
4763
|
+
}
|
|
4764
|
+
function contextEpochTokens(session) {
|
|
4765
|
+
const epoch = ensureContextEpoch(session);
|
|
4766
|
+
return epoch.usage.inputTokens + epoch.usage.outputTokens;
|
|
4767
|
+
}
|
|
4768
|
+
function rotateContextEpoch(session, reason, compaction) {
|
|
4769
|
+
const previous = ensureContextEpoch(session);
|
|
4770
|
+
const createdAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
4771
|
+
const handoff = buildEpochHandoff(session, reason, createdAt, compaction);
|
|
4772
|
+
previous.finishedAt = createdAt;
|
|
4773
|
+
previous.handoff = handoff;
|
|
4774
|
+
const current = createEpoch(previous.index + 1, createdAt);
|
|
4775
|
+
const epochs = session.contextEpochs ?? (session.contextEpochs = []);
|
|
4776
|
+
epochs.push(current);
|
|
4777
|
+
trimEpochs(epochs);
|
|
4778
|
+
return { previous, current, handoff };
|
|
4779
|
+
}
|
|
4780
|
+
function buildEpochHandoff(session, reason, createdAt, compaction) {
|
|
4781
|
+
const failures = unresolvedFailureReceipts(session);
|
|
4782
|
+
const contract = session.taskContract;
|
|
4783
|
+
return {
|
|
4784
|
+
reason,
|
|
4785
|
+
createdAt,
|
|
4786
|
+
...compaction ? { compactionReceiptId: compaction.id } : {},
|
|
4787
|
+
...session.compactedThroughMessageId ? { compactedThroughMessageId: session.compactedThroughMessageId } : {},
|
|
4788
|
+
...contract ? {
|
|
4789
|
+
contract: {
|
|
4790
|
+
state: contract.state,
|
|
4791
|
+
required: contract.acceptanceCriteria.filter((criterion2) => criterion2.required).map((criterion2) => ({
|
|
4792
|
+
id: criterion2.id,
|
|
4793
|
+
status: criterion2.status,
|
|
4794
|
+
evidenceRefs: [...criterion2.evidenceRefs]
|
|
4795
|
+
}))
|
|
4796
|
+
}
|
|
4797
|
+
} : {},
|
|
4798
|
+
unresolvedFailures: failures,
|
|
4799
|
+
changedFiles: [...new Set(session.changedFiles)].slice(-256),
|
|
4800
|
+
checks: session.lastRun?.checks.map((check) => ({ ...check })) ?? []
|
|
4801
|
+
};
|
|
4802
|
+
}
|
|
4803
|
+
function unresolvedFailureReceipts(session) {
|
|
4804
|
+
const unresolved2 = /* @__PURE__ */ new Map();
|
|
4805
|
+
for (const event of session.audit ?? []) {
|
|
4806
|
+
if (event.type !== "tool") continue;
|
|
4807
|
+
if (event.outcome === "failure") {
|
|
4808
|
+
const receipt = failureReceipt(event.metadata?.failure);
|
|
4809
|
+
if (receipt) unresolved2.set(receipt.signature, receipt);
|
|
4810
|
+
continue;
|
|
4811
|
+
}
|
|
4812
|
+
const resolved = event.metadata?.resolvedFailureSignatures;
|
|
4813
|
+
if (!Array.isArray(resolved)) continue;
|
|
4814
|
+
for (const signature of resolved) {
|
|
4815
|
+
if (typeof signature === "string") unresolved2.delete(signature);
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
return [...unresolved2.values()].slice(-MAX_HANDOFF_FAILURES);
|
|
4819
|
+
}
|
|
4820
|
+
function failureReceipt(value) {
|
|
4821
|
+
if (!value || typeof value !== "object") return;
|
|
4822
|
+
const candidate = value;
|
|
4823
|
+
const classes = /* @__PURE__ */ new Set([
|
|
4824
|
+
"schema_input",
|
|
4825
|
+
"unknown_tool",
|
|
4826
|
+
"permission_denied",
|
|
4827
|
+
"command_exit",
|
|
4828
|
+
"timeout",
|
|
4829
|
+
"cancelled",
|
|
4830
|
+
"hook",
|
|
4831
|
+
"execution",
|
|
4832
|
+
"no_progress",
|
|
4833
|
+
"contract_required"
|
|
4834
|
+
]);
|
|
4835
|
+
if (typeof candidate.signature !== "string" || !candidate.signature || typeof candidate.class !== "string" || !classes.has(candidate.class) || typeof candidate.circuitOpen !== "boolean") return;
|
|
4836
|
+
return {
|
|
4837
|
+
signature: candidate.signature.slice(0, 256),
|
|
4838
|
+
class: candidate.class,
|
|
4839
|
+
circuitOpen: candidate.circuitOpen
|
|
4840
|
+
};
|
|
4841
|
+
}
|
|
4842
|
+
function createEpoch(index, startedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
4843
|
+
return {
|
|
4844
|
+
id: randomUUID4(),
|
|
4845
|
+
index,
|
|
4846
|
+
startedAt,
|
|
4847
|
+
usage: { inputTokens: 0, outputTokens: 0 }
|
|
4848
|
+
};
|
|
4849
|
+
}
|
|
4850
|
+
function trimEpochs(epochs) {
|
|
4851
|
+
if (epochs.length > MAX_EPOCHS) epochs.splice(0, epochs.length - MAX_EPOCHS);
|
|
4852
|
+
}
|
|
4853
|
+
function boundedTokens(value) {
|
|
4854
|
+
return Number.isFinite(value) && value > 0 ? Math.floor(value) : 0;
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4857
|
+
// src/context/manager.ts
|
|
4727
4858
|
var RECENT_TURN_RESERVE = 3;
|
|
4728
4859
|
var COMPACTION_HIGH_WATER = 0.78;
|
|
4729
4860
|
var TOOL_PRESSURE_WATER = 0.28;
|
|
@@ -4776,13 +4907,21 @@ var ContextManager = class {
|
|
|
4776
4907
|
modelContextTokens ?? Math.min(1e5, this.config.context.maxTokens * 3)
|
|
4777
4908
|
);
|
|
4778
4909
|
const compactedMessages = session.compactedThroughMessageId ? Math.max(0, session.messages.findIndex((message2) => message2.id === session.compactedThroughMessageId) + 1) : 0;
|
|
4910
|
+
const epoch = activeContextEpoch(session);
|
|
4911
|
+
const epochBudget = this.config.agent.maxEpochTokens ?? this.config.agent.maxSessionTokens;
|
|
4779
4912
|
return {
|
|
4780
4913
|
activeTokens,
|
|
4781
4914
|
summaryTokens,
|
|
4782
4915
|
toolTokens: toolTokenCount,
|
|
4783
4916
|
messageCount: active.length,
|
|
4784
4917
|
compactedMessages,
|
|
4785
|
-
pressure: Math.min(1, (activeTokens + summaryTokens) / contextLimit)
|
|
4918
|
+
pressure: Math.min(1, (activeTokens + summaryTokens) / contextLimit),
|
|
4919
|
+
epochIndex: epoch.index,
|
|
4920
|
+
epochCount: epoch.index,
|
|
4921
|
+
epochTokens: epoch.usage.inputTokens + epoch.usage.outputTokens,
|
|
4922
|
+
epochBudget,
|
|
4923
|
+
lifetimeTokens: session.usage.inputTokens + session.usage.outputTokens,
|
|
4924
|
+
lifetimeBudget: this.config.agent.maxSessionTokens
|
|
4786
4925
|
};
|
|
4787
4926
|
}
|
|
4788
4927
|
shouldCompact(session, tokenBudget) {
|
|
@@ -4891,7 +5030,7 @@ function compactionEstimate(session, older, facts, request) {
|
|
|
4891
5030
|
}
|
|
4892
5031
|
function skippedCompaction(session, mode, reason, estimate = emptyCompactionEstimate(session)) {
|
|
4893
5032
|
const receipt = {
|
|
4894
|
-
id:
|
|
5033
|
+
id: randomUUID5(),
|
|
4895
5034
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4896
5035
|
mode,
|
|
4897
5036
|
status: "skipped",
|
|
@@ -4928,7 +5067,7 @@ function emptyCompactionEstimate(session) {
|
|
|
4928
5067
|
function completedCompactionReceipt(mode, omittedMessages, compactedThroughMessageId, estimated, response, summary) {
|
|
4929
5068
|
const actual = actualUsage(response.usage);
|
|
4930
5069
|
return {
|
|
4931
|
-
id:
|
|
5070
|
+
id: randomUUID5(),
|
|
4932
5071
|
recordedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4933
5072
|
mode,
|
|
4934
5073
|
status: "compacted",
|
|
@@ -4967,6 +5106,7 @@ function buildCompactionFactsEnvelope(session, throughMessageId) {
|
|
|
4967
5106
|
const memory = session.workingMemory;
|
|
4968
5107
|
const contract = session.taskContract;
|
|
4969
5108
|
const lastRun = session.lastRun;
|
|
5109
|
+
const epoch = activeContextEpoch(session);
|
|
4970
5110
|
const directives = olderUserDirectives(session, throughMessageId ?? session.compactedThroughMessageId);
|
|
4971
5111
|
const permissions = recentPermissionFacts(session.audit ?? []);
|
|
4972
5112
|
const failures = recentFailureFacts(session.audit ?? []);
|
|
@@ -5019,6 +5159,11 @@ ${factList(memory?.relevantFiles ?? [])}
|
|
|
5019
5159
|
Session changed files:
|
|
5020
5160
|
${factList(session.changedFiles)}
|
|
5021
5161
|
|
|
5162
|
+
Context epoch ledger:
|
|
5163
|
+
- Current epoch: ${epoch.index}
|
|
5164
|
+
- Current epoch usage: input=${epoch.usage.inputTokens}; output=${epoch.usage.outputTokens}
|
|
5165
|
+
- Lifetime usage: input=${session.usage.inputTokens}; output=${session.usage.outputTokens}
|
|
5166
|
+
|
|
5022
5167
|
Last-run verification and residual state:
|
|
5023
5168
|
${lastRunLines}
|
|
5024
5169
|
|
|
@@ -5462,7 +5607,7 @@ function normalizeForMatch(value) {
|
|
|
5462
5607
|
}
|
|
5463
5608
|
|
|
5464
5609
|
// src/providers/anthropic.ts
|
|
5465
|
-
import { randomUUID as
|
|
5610
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
5466
5611
|
|
|
5467
5612
|
// src/providers/provider.ts
|
|
5468
5613
|
var ProviderError = class extends Error {
|
|
@@ -5587,7 +5732,7 @@ var AnthropicProvider = class {
|
|
|
5587
5732
|
return {
|
|
5588
5733
|
content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
|
|
5589
5734
|
toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
|
|
5590
|
-
id: block.id ??
|
|
5735
|
+
id: block.id ?? randomUUID6(),
|
|
5591
5736
|
name: block.name ?? "unknown",
|
|
5592
5737
|
arguments: safeJsonArguments(block.input)
|
|
5593
5738
|
})),
|
|
@@ -5661,7 +5806,7 @@ var AnthropicProvider = class {
|
|
|
5661
5806
|
}
|
|
5662
5807
|
if (chunk.type === "content_block_start" && chunk.content_block?.type === "tool_use") {
|
|
5663
5808
|
calls.set(index, {
|
|
5664
|
-
id: chunk.content_block.id ??
|
|
5809
|
+
id: chunk.content_block.id ?? randomUUID6(),
|
|
5665
5810
|
name: chunk.content_block.name ?? "unknown",
|
|
5666
5811
|
input: chunk.content_block.input,
|
|
5667
5812
|
partialJson: ""
|
|
@@ -5672,7 +5817,7 @@ var AnthropicProvider = class {
|
|
|
5672
5817
|
yield { type: "text_delta", content: chunk.delta.text };
|
|
5673
5818
|
}
|
|
5674
5819
|
if (chunk.type === "content_block_delta" && chunk.delta?.type === "input_json_delta") {
|
|
5675
|
-
const call = calls.get(index) ?? { id:
|
|
5820
|
+
const call = calls.get(index) ?? { id: randomUUID6(), name: "unknown", input: void 0, partialJson: "" };
|
|
5676
5821
|
call.partialJson += chunk.delta.partial_json ?? "";
|
|
5677
5822
|
calls.set(index, call);
|
|
5678
5823
|
}
|
|
@@ -5702,7 +5847,7 @@ function normalizeAnthropicResponse(data) {
|
|
|
5702
5847
|
return {
|
|
5703
5848
|
content: blocks.filter((block) => block.type === "text").map((block) => block.text ?? "").join("\n"),
|
|
5704
5849
|
toolCalls: blocks.filter((block) => block.type === "tool_use").map((block) => ({
|
|
5705
|
-
id: block.id ??
|
|
5850
|
+
id: block.id ?? randomUUID6(),
|
|
5706
5851
|
name: block.name ?? "unknown",
|
|
5707
5852
|
arguments: safeJsonArguments(block.input)
|
|
5708
5853
|
})),
|
|
@@ -5747,7 +5892,7 @@ function toAnthropicMessage(message2) {
|
|
|
5747
5892
|
}
|
|
5748
5893
|
|
|
5749
5894
|
// src/providers/gemini.ts
|
|
5750
|
-
import { randomUUID as
|
|
5895
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
5751
5896
|
var GeminiProvider = class {
|
|
5752
5897
|
constructor(config) {
|
|
5753
5898
|
this.config = config;
|
|
@@ -5787,7 +5932,7 @@ var GeminiProvider = class {
|
|
|
5787
5932
|
return {
|
|
5788
5933
|
content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
|
|
5789
5934
|
toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
|
|
5790
|
-
id:
|
|
5935
|
+
id: randomUUID7(),
|
|
5791
5936
|
name: part.functionCall?.name ?? "unknown",
|
|
5792
5937
|
arguments: safeJsonArguments(part.functionCall?.args)
|
|
5793
5938
|
})),
|
|
@@ -5859,7 +6004,7 @@ var GeminiProvider = class {
|
|
|
5859
6004
|
}
|
|
5860
6005
|
if (part.functionCall) {
|
|
5861
6006
|
toolCalls.push({
|
|
5862
|
-
id:
|
|
6007
|
+
id: randomUUID7(),
|
|
5863
6008
|
name: part.functionCall.name ?? "unknown",
|
|
5864
6009
|
arguments: safeJsonArguments(part.functionCall.args)
|
|
5865
6010
|
});
|
|
@@ -5888,7 +6033,7 @@ function normalizeGeminiResponse(data) {
|
|
|
5888
6033
|
return {
|
|
5889
6034
|
content: parts.map((part) => part.text ?? "").filter(Boolean).join("\n"),
|
|
5890
6035
|
toolCalls: parts.filter((part) => part.functionCall).map((part) => ({
|
|
5891
|
-
id:
|
|
6036
|
+
id: randomUUID7(),
|
|
5892
6037
|
name: part.functionCall?.name ?? "unknown",
|
|
5893
6038
|
arguments: safeJsonArguments(part.functionCall?.args)
|
|
5894
6039
|
})),
|
|
@@ -5932,7 +6077,7 @@ function toGeminiMessage(message2) {
|
|
|
5932
6077
|
}
|
|
5933
6078
|
|
|
5934
6079
|
// src/providers/openai.ts
|
|
5935
|
-
import { randomUUID as
|
|
6080
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
5936
6081
|
var OpenAIProvider = class {
|
|
5937
6082
|
constructor(config) {
|
|
5938
6083
|
this.config = config;
|
|
@@ -6055,7 +6200,7 @@ var OpenAIProvider = class {
|
|
|
6055
6200
|
}
|
|
6056
6201
|
for (const fragment of delta?.tool_calls ?? []) {
|
|
6057
6202
|
const index = fragment.index ?? 0;
|
|
6058
|
-
const current = calls.get(index) ?? { id:
|
|
6203
|
+
const current = calls.get(index) ?? { id: randomUUID8(), name: "", arguments: "" };
|
|
6059
6204
|
if (fragment.id) current.id = fragment.id;
|
|
6060
6205
|
if (fragment.function?.name) current.name += fragment.function.name;
|
|
6061
6206
|
if (fragment.function?.arguments) current.arguments += fragment.function.arguments;
|
|
@@ -6089,7 +6234,7 @@ function normalizeOpenAIResponse(data) {
|
|
|
6089
6234
|
return {
|
|
6090
6235
|
content: message2.content ?? "",
|
|
6091
6236
|
toolCalls: (message2.tool_calls ?? []).map((call) => ({
|
|
6092
|
-
id: call.id ??
|
|
6237
|
+
id: call.id ?? randomUUID8(),
|
|
6093
6238
|
name: call.function?.name ?? "unknown",
|
|
6094
6239
|
arguments: safeJsonArguments(call.function?.arguments)
|
|
6095
6240
|
})),
|
|
@@ -6145,7 +6290,7 @@ function createProvider(config) {
|
|
|
6145
6290
|
}
|
|
6146
6291
|
|
|
6147
6292
|
// src/checkpoint/store.ts
|
|
6148
|
-
import { createHash as createHash8, randomUUID as
|
|
6293
|
+
import { createHash as createHash8, randomUUID as randomUUID9 } from "node:crypto";
|
|
6149
6294
|
import { lstat as lstat10, mkdir as mkdir7, readFile as readFile5, readdir as readdir2, unlink as unlink2 } from "node:fs/promises";
|
|
6150
6295
|
import { basename as basename6, dirname as dirname8, join as join9, relative as relative6, resolve as resolve12 } from "node:path";
|
|
6151
6296
|
import { z as z4 } from "zod";
|
|
@@ -6181,7 +6326,7 @@ var CheckpointStore = class {
|
|
|
6181
6326
|
return this.withManagedLease(() => this.captureUnlocked(sessionId, unique3, options));
|
|
6182
6327
|
}
|
|
6183
6328
|
async captureUnlocked(sessionId, unique3, options) {
|
|
6184
|
-
const id = `${Date.now().toString(36)}-${
|
|
6329
|
+
const id = `${Date.now().toString(36)}-${randomUUID9()}`;
|
|
6185
6330
|
const target = join9(this.directory, sessionId, id);
|
|
6186
6331
|
const blobDirectory = join9(target, "blobs");
|
|
6187
6332
|
await this.ensureDirectory();
|
|
@@ -6457,7 +6602,7 @@ var HookRunner = class {
|
|
|
6457
6602
|
};
|
|
6458
6603
|
|
|
6459
6604
|
// src/session/store.ts
|
|
6460
|
-
import { randomUUID as
|
|
6605
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
6461
6606
|
import { constants as constants4 } from "node:fs";
|
|
6462
6607
|
import {
|
|
6463
6608
|
chmod as chmod6,
|
|
@@ -6683,6 +6828,76 @@ var contextCompactionReceiptSchema = z5.object({
|
|
|
6683
6828
|
outputSource: z5.enum(["actual", "estimated", "none"]),
|
|
6684
6829
|
narrative: z5.enum(["present", "empty", "not-requested"])
|
|
6685
6830
|
}).strict();
|
|
6831
|
+
var contextEpochSchema = z5.object({
|
|
6832
|
+
id: z5.string().uuid(),
|
|
6833
|
+
index: z5.number().int().positive(),
|
|
6834
|
+
startedAt: z5.string().datetime(),
|
|
6835
|
+
finishedAt: z5.string().datetime().optional(),
|
|
6836
|
+
usage: z5.object({
|
|
6837
|
+
inputTokens: z5.number().int().nonnegative(),
|
|
6838
|
+
outputTokens: z5.number().int().nonnegative()
|
|
6839
|
+
}).strict(),
|
|
6840
|
+
handoff: z5.object({
|
|
6841
|
+
reason: z5.enum(["token_budget", "context_pressure", "manual"]),
|
|
6842
|
+
createdAt: z5.string().datetime(),
|
|
6843
|
+
compactionReceiptId: z5.string().uuid().optional(),
|
|
6844
|
+
compactedThroughMessageId: z5.string().optional(),
|
|
6845
|
+
contract: z5.object({
|
|
6846
|
+
state: z5.enum(["draft", "active", "satisfied", "blocked"]),
|
|
6847
|
+
required: z5.array(z5.object({
|
|
6848
|
+
id: z5.string().min(1).max(128),
|
|
6849
|
+
status: z5.enum(["pending", "satisfied", "blocked"]),
|
|
6850
|
+
evidenceRefs: z5.array(z5.string().min(1).max(256)).max(64)
|
|
6851
|
+
}).strict()).max(64)
|
|
6852
|
+
}).strict().optional(),
|
|
6853
|
+
unresolvedFailures: z5.array(z5.object({
|
|
6854
|
+
signature: z5.string().min(1).max(256),
|
|
6855
|
+
class: z5.enum([
|
|
6856
|
+
"schema_input",
|
|
6857
|
+
"unknown_tool",
|
|
6858
|
+
"permission_denied",
|
|
6859
|
+
"command_exit",
|
|
6860
|
+
"timeout",
|
|
6861
|
+
"cancelled",
|
|
6862
|
+
"hook",
|
|
6863
|
+
"execution",
|
|
6864
|
+
"no_progress",
|
|
6865
|
+
"contract_required"
|
|
6866
|
+
]),
|
|
6867
|
+
circuitOpen: z5.boolean()
|
|
6868
|
+
}).strict()).max(16),
|
|
6869
|
+
changedFiles: z5.array(z5.string().max(4096)).max(256),
|
|
6870
|
+
checks: z5.array(verificationEvidenceSchema).max(64)
|
|
6871
|
+
}).strict().optional()
|
|
6872
|
+
}).strict();
|
|
6873
|
+
var intentAssessmentSchema = z5.object({
|
|
6874
|
+
version: z5.literal(1),
|
|
6875
|
+
route: z5.enum(["direct_execute", "inspect_then_execute", "needs_input", "permission_required"]),
|
|
6876
|
+
reasons: z5.array(z5.enum([
|
|
6877
|
+
"simple_explicit_request",
|
|
6878
|
+
"workspace_inference_available",
|
|
6879
|
+
"explicit_user_choice_missing",
|
|
6880
|
+
"public_api_compatibility_missing",
|
|
6881
|
+
"runtime_permission_separate",
|
|
6882
|
+
"clarification_resolved"
|
|
6883
|
+
])).min(1).max(8),
|
|
6884
|
+
assessedAt: z5.string().datetime(),
|
|
6885
|
+
retrievalHits: z5.number().int().nonnegative()
|
|
6886
|
+
}).strict();
|
|
6887
|
+
var pendingInputSchema = z5.object({
|
|
6888
|
+
id: z5.string().uuid(),
|
|
6889
|
+
runId: z5.string().uuid(),
|
|
6890
|
+
createdAt: z5.string().datetime(),
|
|
6891
|
+
originalRequest: z5.string().min(1).max(12e4),
|
|
6892
|
+
question: z5.string().min(1).max(2e3),
|
|
6893
|
+
options: z5.array(z5.object({
|
|
6894
|
+
id: z5.string().regex(/^[a-z][a-z0-9_-]{0,63}$/u),
|
|
6895
|
+
label: z5.string().min(1).max(160),
|
|
6896
|
+
impact: z5.string().min(1).max(500),
|
|
6897
|
+
recommended: z5.boolean()
|
|
6898
|
+
}).strict()).min(2).max(3),
|
|
6899
|
+
reason: z5.enum(["explicit_user_choice_missing", "public_api_compatibility_missing"])
|
|
6900
|
+
}).strict();
|
|
6686
6901
|
var sessionSchema = z5.object({
|
|
6687
6902
|
id: sessionIdSchema,
|
|
6688
6903
|
title: z5.string(),
|
|
@@ -6699,6 +6914,9 @@ var sessionSchema = z5.object({
|
|
|
6699
6914
|
contextCompactions: z5.number().int().nonnegative().optional(),
|
|
6700
6915
|
compactedThroughMessageId: z5.string().optional(),
|
|
6701
6916
|
contextCompactionReceipts: z5.array(contextCompactionReceiptSchema).max(64).optional(),
|
|
6917
|
+
contextEpochs: z5.array(contextEpochSchema).max(64).optional(),
|
|
6918
|
+
intentAssessment: intentAssessmentSchema.optional(),
|
|
6919
|
+
pendingInput: pendingInputSchema.optional(),
|
|
6702
6920
|
workingMemory: workingMemorySchema.optional(),
|
|
6703
6921
|
contextSources: z5.array(contextSourceSchema).max(64).optional(),
|
|
6704
6922
|
toolArtifacts: z5.array(toolArtifactSchema).max(200).optional(),
|
|
@@ -6849,7 +7067,7 @@ var SessionStore = class {
|
|
|
6849
7067
|
const backup = this.backupPathFor(session.id);
|
|
6850
7068
|
await this.assertManagedFile(target);
|
|
6851
7069
|
await this.assertManagedFile(backup);
|
|
6852
|
-
const temporary = join10(this.directory, `.${session.id}.${
|
|
7070
|
+
const temporary = join10(this.directory, `.${session.id}.${randomUUID10()}.tmp`);
|
|
6853
7071
|
const data = `${JSON.stringify(session, null, 2)}
|
|
6854
7072
|
`;
|
|
6855
7073
|
const handle = await open2(temporary, "wx", 384);
|
|
@@ -6867,7 +7085,7 @@ var SessionStore = class {
|
|
|
6867
7085
|
}
|
|
6868
7086
|
}
|
|
6869
7087
|
async copyBackup(source, target) {
|
|
6870
|
-
const temporary = join10(this.directory, `.${basename7(target)}.${
|
|
7088
|
+
const temporary = join10(this.directory, `.${basename7(target)}.${randomUUID10()}.tmp`);
|
|
6871
7089
|
try {
|
|
6872
7090
|
await copyFile(source, temporary, constants4.COPYFILE_EXCL);
|
|
6873
7091
|
await chmod6(temporary, 384);
|
|
@@ -6956,7 +7174,7 @@ var SessionStore = class {
|
|
|
6956
7174
|
}
|
|
6957
7175
|
};
|
|
6958
7176
|
function createSession(options) {
|
|
6959
|
-
const id = options.id ??
|
|
7177
|
+
const id = options.id ?? randomUUID10();
|
|
6960
7178
|
validateId(id);
|
|
6961
7179
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
6962
7180
|
return {
|
|
@@ -6971,6 +7189,12 @@ function createSession(options) {
|
|
|
6971
7189
|
tasks: [],
|
|
6972
7190
|
changedFiles: [],
|
|
6973
7191
|
audit: [],
|
|
7192
|
+
contextEpochs: [{
|
|
7193
|
+
id: randomUUID10(),
|
|
7194
|
+
index: 1,
|
|
7195
|
+
startedAt: now,
|
|
7196
|
+
usage: { inputTokens: 0, outputTokens: 0 }
|
|
7197
|
+
}],
|
|
6974
7198
|
usage: { inputTokens: 0, outputTokens: 0 }
|
|
6975
7199
|
};
|
|
6976
7200
|
}
|
|
@@ -8063,7 +8287,7 @@ function isUnsafeGitOption(argument) {
|
|
|
8063
8287
|
"--exclude-from",
|
|
8064
8288
|
"--output",
|
|
8065
8289
|
"--directory"
|
|
8066
|
-
].some((
|
|
8290
|
+
].some((option2) => argument === option2 || argument.startsWith(`${option2}=`));
|
|
8067
8291
|
}
|
|
8068
8292
|
function firstGitCommand(args) {
|
|
8069
8293
|
for (const argument of args) {
|
|
@@ -8811,7 +9035,7 @@ async function discoverSnapshotFiles(root) {
|
|
|
8811
9035
|
}
|
|
8812
9036
|
|
|
8813
9037
|
// src/tools/task.ts
|
|
8814
|
-
import { randomUUID as
|
|
9038
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
8815
9039
|
import { z as z14 } from "zod";
|
|
8816
9040
|
var taskSchema2 = z14.object({
|
|
8817
9041
|
id: z14.string().min(1).optional(),
|
|
@@ -8860,7 +9084,7 @@ var taskTool = {
|
|
|
8860
9084
|
case "list":
|
|
8861
9085
|
break;
|
|
8862
9086
|
case "add":
|
|
8863
|
-
tasks.push({ id:
|
|
9087
|
+
tasks.push({ id: randomUUID11(), title: input2.title, status: input2.status ?? "pending" });
|
|
8864
9088
|
break;
|
|
8865
9089
|
case "update": {
|
|
8866
9090
|
const task = tasks.find((item) => item.id === input2.id);
|
|
@@ -8877,7 +9101,7 @@ var taskTool = {
|
|
|
8877
9101
|
}
|
|
8878
9102
|
case "replace":
|
|
8879
9103
|
tasks.splice(0, tasks.length, ...input2.tasks.map((task) => ({
|
|
8880
|
-
id: task.id ??
|
|
9104
|
+
id: task.id ?? randomUUID11(),
|
|
8881
9105
|
title: task.title,
|
|
8882
9106
|
status: task.status
|
|
8883
9107
|
})));
|
|
@@ -8891,11 +9115,11 @@ var taskTool = {
|
|
|
8891
9115
|
};
|
|
8892
9116
|
|
|
8893
9117
|
// src/tools/task-contract.ts
|
|
8894
|
-
import { randomUUID as
|
|
9118
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
8895
9119
|
import { z as z15 } from "zod";
|
|
8896
9120
|
|
|
8897
9121
|
// src/agent/task-contract.ts
|
|
8898
|
-
import { randomUUID as
|
|
9122
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
8899
9123
|
var EXECUTABLE_INTENTS = /* @__PURE__ */ new Set(["debug", "refactor", "test", "implement"]);
|
|
8900
9124
|
function shouldUseTaskContract(request, intent, existing) {
|
|
8901
9125
|
if (existing && existing.state !== "satisfied") return true;
|
|
@@ -8961,7 +9185,7 @@ function refreshTaskContractState(contract) {
|
|
|
8961
9185
|
}
|
|
8962
9186
|
function criterion(id, description) {
|
|
8963
9187
|
return {
|
|
8964
|
-
id: `${id}-${
|
|
9188
|
+
id: `${id}-${randomUUID12().slice(0, 8)}`,
|
|
8965
9189
|
description,
|
|
8966
9190
|
required: true,
|
|
8967
9191
|
status: "pending",
|
|
@@ -9515,7 +9739,7 @@ var taskContractTool = {
|
|
|
9515
9739
|
}
|
|
9516
9740
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9517
9741
|
const criteria = input2.acceptance_criteria.map((item) => ({
|
|
9518
|
-
id: item.id ?? `criterion-${
|
|
9742
|
+
id: item.id ?? `criterion-${randomUUID13().slice(0, 8)}`,
|
|
9519
9743
|
description: item.description,
|
|
9520
9744
|
required: item.required ?? true,
|
|
9521
9745
|
status: "pending",
|
|
@@ -9983,6 +10207,118 @@ function escapeAttribute3(value) {
|
|
|
9983
10207
|
})[character] ?? character);
|
|
9984
10208
|
}
|
|
9985
10209
|
|
|
10210
|
+
// src/agent/intent-sufficiency.ts
|
|
10211
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
10212
|
+
function assessIntentSufficiency(request, intent, context) {
|
|
10213
|
+
const assessedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10214
|
+
const uiChoice = explicitUiChoice(request);
|
|
10215
|
+
if (uiChoice) {
|
|
10216
|
+
const pending = clarification(
|
|
10217
|
+
request,
|
|
10218
|
+
assessedAt,
|
|
10219
|
+
"explicit_user_choice_missing",
|
|
10220
|
+
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?",
|
|
10221
|
+
uiChoice
|
|
10222
|
+
);
|
|
10223
|
+
return {
|
|
10224
|
+
assessment: assessment("needs_input", "explicit_user_choice_missing", assessedAt, context.retrievalHits),
|
|
10225
|
+
pending,
|
|
10226
|
+
directive: "Pause before mutation and ask the single recorded clarification question."
|
|
10227
|
+
};
|
|
10228
|
+
}
|
|
10229
|
+
if (publicApiCompatibilityMissing(request)) {
|
|
10230
|
+
const chinese = isChinese(request);
|
|
10231
|
+
const pending = clarification(
|
|
10232
|
+
request,
|
|
10233
|
+
assessedAt,
|
|
10234
|
+
"public_api_compatibility_missing",
|
|
10235
|
+
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?",
|
|
10236
|
+
chinese ? [
|
|
10237
|
+
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),
|
|
10238
|
+
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)
|
|
10239
|
+
] : [
|
|
10240
|
+
option("backward_compatible", "Preserve compatibility", "Keep the old API with a deprecation or migration path; safer but broader.", true),
|
|
10241
|
+
option("breaking_change", "Allow breaking change", "Adopt the new API directly; smaller change but callers must migrate.", false)
|
|
10242
|
+
]
|
|
10243
|
+
);
|
|
10244
|
+
return {
|
|
10245
|
+
assessment: assessment("needs_input", "public_api_compatibility_missing", assessedAt, context.retrievalHits),
|
|
10246
|
+
pending,
|
|
10247
|
+
directive: "Pause before mutation and ask the single recorded clarification question."
|
|
10248
|
+
};
|
|
10249
|
+
}
|
|
10250
|
+
if (requiresRuntimePermission(request)) {
|
|
10251
|
+
return {
|
|
10252
|
+
assessment: assessment("permission_required", "runtime_permission_separate", assessedAt, context.retrievalHits),
|
|
10253
|
+
directive: "The objective is sufficiently clear. Continue, but keep runtime permission approval separate from clarification."
|
|
10254
|
+
};
|
|
10255
|
+
}
|
|
10256
|
+
if (context.complex && shouldInspectWorkspace(request, intent)) {
|
|
10257
|
+
return {
|
|
10258
|
+
assessment: assessment("inspect_then_execute", "workspace_inference_available", assessedAt, context.retrievalHits),
|
|
10259
|
+
directive: "Do not ask the user for repository facts. Inspect the workspace and current evidence before the first mutation."
|
|
10260
|
+
};
|
|
10261
|
+
}
|
|
10262
|
+
return {
|
|
10263
|
+
assessment: assessment("direct_execute", "simple_explicit_request", assessedAt, context.retrievalHits),
|
|
10264
|
+
directive: "The request is sufficiently clear. Execute without an extra clarification turn."
|
|
10265
|
+
};
|
|
10266
|
+
}
|
|
10267
|
+
function resolvePendingInput(pending, answer) {
|
|
10268
|
+
const normalized = answer.trim();
|
|
10269
|
+
const byIndex = /^([1-3])(?:[.)、:]|$)/u.exec(normalized)?.[1];
|
|
10270
|
+
const selected = pending.options.find((candidate) => candidate.id.toLocaleLowerCase() === normalized.toLocaleLowerCase() || candidate.label.toLocaleLowerCase() === normalized.toLocaleLowerCase()) ?? (byIndex ? pending.options[Number(byIndex) - 1] : void 0);
|
|
10271
|
+
const decision = selected ? `${selected.label}: ${selected.impact}` : compact2(normalized, 2e3);
|
|
10272
|
+
return { answer: compact2(normalized, 2e3), decision };
|
|
10273
|
+
}
|
|
10274
|
+
function assessment(route, reason, assessedAt, retrievalHits) {
|
|
10275
|
+
return { version: 1, route, reasons: [reason], assessedAt, retrievalHits };
|
|
10276
|
+
}
|
|
10277
|
+
function clarification(originalRequest, createdAt, reason, question2, options) {
|
|
10278
|
+
return {
|
|
10279
|
+
id: randomUUID14(),
|
|
10280
|
+
runId: randomUUID14(),
|
|
10281
|
+
createdAt,
|
|
10282
|
+
originalRequest: originalRequest.slice(0, 12e4),
|
|
10283
|
+
question: question2,
|
|
10284
|
+
options,
|
|
10285
|
+
reason
|
|
10286
|
+
};
|
|
10287
|
+
}
|
|
10288
|
+
function explicitUiChoice(value) {
|
|
10289
|
+
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";
|
|
10290
|
+
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"));
|
|
10291
|
+
if (!match?.[1] || !match[2] || match[1].toLocaleLowerCase() === match[2].toLocaleLowerCase()) return;
|
|
10292
|
+
const chinese = isChinese(value);
|
|
10293
|
+
return [
|
|
10294
|
+
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),
|
|
10295
|
+
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)
|
|
10296
|
+
];
|
|
10297
|
+
}
|
|
10298
|
+
function publicApiCompatibilityMissing(value) {
|
|
10299
|
+
const api = /\b(?:public|external|exported)\s+(?:api|interface|contract)\b|公共\s*(?:api|接口)|公开\s*(?:api|接口)|对外接口/iu;
|
|
10300
|
+
const change = /\b(?:change|replace|remove|rename|migrate|redesign|refactor)\b|修改|替换|删除|移除|重命名|迁移|重构/iu;
|
|
10301
|
+
const policy = /\b(?:backward compatible|backwards compatible|breaking change|semver major|deprecat|compatibility shim|migration path)\b|向后兼容|保持兼容|允许破坏|破坏性变更|主版本|弃用|迁移路径/iu;
|
|
10302
|
+
return api.test(value) && change.test(value) && !policy.test(value);
|
|
10303
|
+
}
|
|
10304
|
+
function requiresRuntimePermission(value) {
|
|
10305
|
+
return /\b(?:push|publish|deploy|release|delete|drop|reset|force[- ]push|send|upload)\b|推送|发布|部署|删除|清空|重置|发送|上传/iu.test(value);
|
|
10306
|
+
}
|
|
10307
|
+
function shouldInspectWorkspace(value, intent) {
|
|
10308
|
+
if (intent === "debug" || intent === "refactor" || intent === "review") return true;
|
|
10309
|
+
const hasConcreteTarget = /(?:^|\s)(?:[\w.-]+\/)+[\w.-]+|`[^`]+`|\b[A-Za-z_$][\w$]*(?:\(\))?\b/u.test(value);
|
|
10310
|
+
return !hasConcreteTarget || /\b(?:existing|current|relevant|appropriate|best)\b|现有|当前|相关|合适|最适合|这个|那个/iu.test(value);
|
|
10311
|
+
}
|
|
10312
|
+
function option(id, label, impact, recommended) {
|
|
10313
|
+
return { id, label, impact, recommended };
|
|
10314
|
+
}
|
|
10315
|
+
function isChinese(value) {
|
|
10316
|
+
return /[\u3400-\u9fff]/u.test(value);
|
|
10317
|
+
}
|
|
10318
|
+
function compact2(value, limit) {
|
|
10319
|
+
return value.trim().replace(/\s+/gu, " ").slice(0, limit);
|
|
10320
|
+
}
|
|
10321
|
+
|
|
9986
10322
|
// src/agent/tool-recovery.ts
|
|
9987
10323
|
import { createHash as createHash13 } from "node:crypto";
|
|
9988
10324
|
var RETRY_BUDGET = {
|
|
@@ -9997,6 +10333,7 @@ var RETRY_BUDGET = {
|
|
|
9997
10333
|
no_progress: 0,
|
|
9998
10334
|
contract_required: 2
|
|
9999
10335
|
};
|
|
10336
|
+
var FAILURE_CLASSES = Object.keys(RETRY_BUDGET);
|
|
10000
10337
|
var REPAIR_HINT = {
|
|
10001
10338
|
schema_input: "Correct the arguments to match the tool schema.",
|
|
10002
10339
|
unknown_tool: "Choose a tool exposed for this turn.",
|
|
@@ -10098,6 +10435,9 @@ function classifyToolFailure(result, fallback = "execution") {
|
|
|
10098
10435
|
function formatFailureReceipt(receipt) {
|
|
10099
10436
|
return `Failure: ${receipt.class}; attempt ${receipt.attempt}; ${receipt.remaining} retries remain; circuit ${receipt.circuitOpen ? "open" : "closed"}. Repair: ${receipt.repairHint}`;
|
|
10100
10437
|
}
|
|
10438
|
+
function resolvableFailureSignatures(call) {
|
|
10439
|
+
return FAILURE_CLASSES.map((failureClass) => failureSignature(call, failureClass));
|
|
10440
|
+
}
|
|
10101
10441
|
function isRetryable(failureClass) {
|
|
10102
10442
|
return RETRY_BUDGET[failureClass] > 0;
|
|
10103
10443
|
}
|
|
@@ -10915,16 +11255,35 @@ var AgentRunner = class {
|
|
|
10915
11255
|
}
|
|
10916
11256
|
async run(input2, options = {}) {
|
|
10917
11257
|
if (this.running) throw new Error("This AgentRunner is already processing a turn.");
|
|
10918
|
-
const
|
|
10919
|
-
if (!
|
|
10920
|
-
if (
|
|
11258
|
+
const submittedRequest = input2.trim();
|
|
11259
|
+
if (!submittedRequest) throw new Error("User input cannot be empty.");
|
|
11260
|
+
if (submittedRequest.length > 12e4) {
|
|
10921
11261
|
throw new Error("User input is too large; pass a focused request or attach files with @path.");
|
|
10922
11262
|
}
|
|
11263
|
+
let request = submittedRequest;
|
|
10923
11264
|
this.running = true;
|
|
10924
11265
|
this.contextEngine.resetDiagnostics?.();
|
|
10925
11266
|
const emit = async (event) => {
|
|
10926
11267
|
await options.onEvent?.(event);
|
|
10927
11268
|
};
|
|
11269
|
+
const pendingAtStart = this.session.pendingInput;
|
|
11270
|
+
const resolvedInput = pendingAtStart ? resolvePendingInput(pendingAtStart, submittedRequest) : void 0;
|
|
11271
|
+
if (pendingAtStart && resolvedInput) {
|
|
11272
|
+
request = `${pendingAtStart.originalRequest}
|
|
11273
|
+
|
|
11274
|
+
<user-clarification-decision source="explicit-user-input">
|
|
11275
|
+
${resolvedInput.decision}
|
|
11276
|
+
</user-clarification-decision>`;
|
|
11277
|
+
delete this.session.pendingInput;
|
|
11278
|
+
const constraint = `User clarification (${pendingAtStart.id}): ${resolvedInput.decision}`;
|
|
11279
|
+
if (this.session.taskContract && !this.session.taskContract.constraints.includes(constraint)) {
|
|
11280
|
+
this.session.taskContract.constraints.push(constraint);
|
|
11281
|
+
if (this.session.taskContract.constraints.length > 64) {
|
|
11282
|
+
this.session.taskContract.constraints.splice(0, this.session.taskContract.constraints.length - 64);
|
|
11283
|
+
}
|
|
11284
|
+
this.session.taskContract.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
11285
|
+
}
|
|
11286
|
+
}
|
|
10928
11287
|
const changeSequenceAtStart = this.changeSequence;
|
|
10929
11288
|
const runStartedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
10930
11289
|
const loadedProgressiveTools = /* @__PURE__ */ new Set();
|
|
@@ -10992,11 +11351,12 @@ var AgentRunner = class {
|
|
|
10992
11351
|
try {
|
|
10993
11352
|
throwIfAborted(options.signal);
|
|
10994
11353
|
await this.reconcileToolArtifacts();
|
|
11354
|
+
ensureContextEpoch(this.session);
|
|
10995
11355
|
if (this.session.messages.length === 0 && this.session.title === "New session") {
|
|
10996
11356
|
this.session.title = titleFromInput(request);
|
|
10997
11357
|
}
|
|
10998
11358
|
this.contextManager.startTurn(this.session, request);
|
|
10999
|
-
const userMessage = message("user",
|
|
11359
|
+
const userMessage = message("user", submittedRequest);
|
|
11000
11360
|
this.activeReuseGate = { requestId: userMessage.id, request, attempted: false };
|
|
11001
11361
|
this.session.messages.push(userMessage);
|
|
11002
11362
|
await this.persist();
|
|
@@ -11004,11 +11364,48 @@ var AgentRunner = class {
|
|
|
11004
11364
|
const turnDirective = buildTurnDirective(request, {
|
|
11005
11365
|
agents: Boolean(this.config.agents?.enabled)
|
|
11006
11366
|
});
|
|
11367
|
+
const contractEnabled = shouldUseTaskContract(
|
|
11368
|
+
request,
|
|
11369
|
+
turnDirective.intent,
|
|
11370
|
+
this.session.taskContract
|
|
11371
|
+
);
|
|
11007
11372
|
const packed = trivialTurn ? emptyPackedContext(selectContextBudget(request, this.config, {
|
|
11008
11373
|
intent: turnDirective.intent,
|
|
11009
11374
|
trivial: true
|
|
11010
11375
|
})) : await this.packContext(request, { intent: turnDirective.intent });
|
|
11011
11376
|
if (!trivialTurn) await emit({ type: "context", packed });
|
|
11377
|
+
if (contractEnabled && (!this.session.taskContract || this.session.taskContract.state === "satisfied")) {
|
|
11378
|
+
this.session.taskContract = createDraftTaskContract(request, this.session.audit?.at(-1)?.id);
|
|
11379
|
+
await this.persist();
|
|
11380
|
+
await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
|
|
11381
|
+
}
|
|
11382
|
+
let intentDirective;
|
|
11383
|
+
if (pendingAtStart && resolvedInput) {
|
|
11384
|
+
this.session.intentAssessment = {
|
|
11385
|
+
version: 1,
|
|
11386
|
+
route: "direct_execute",
|
|
11387
|
+
reasons: ["clarification_resolved"],
|
|
11388
|
+
assessedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11389
|
+
retrievalHits: packed.hits.length
|
|
11390
|
+
};
|
|
11391
|
+
intentDirective = "The user resolved the recorded clarification. Continue the same logical run and apply that decision without asking again.";
|
|
11392
|
+
await emit({ type: "input_resolved", pendingId: pendingAtStart.id, runId: pendingAtStart.runId, answer: resolvedInput.answer });
|
|
11393
|
+
await emit({ type: "intent", assessment: this.session.intentAssessment });
|
|
11394
|
+
} else {
|
|
11395
|
+
const intent = assessIntentSufficiency(request, turnDirective.intent, {
|
|
11396
|
+
retrievalHits: packed.hits.length,
|
|
11397
|
+
complex: contractEnabled
|
|
11398
|
+
});
|
|
11399
|
+
this.session.intentAssessment = intent.assessment;
|
|
11400
|
+
intentDirective = intent.directive;
|
|
11401
|
+
await emit({ type: "intent", assessment: intent.assessment });
|
|
11402
|
+
if (intent.pending) {
|
|
11403
|
+
this.session.pendingInput = intent.pending;
|
|
11404
|
+
await this.persist();
|
|
11405
|
+
await emit({ type: "needs_input", pending: intent.pending });
|
|
11406
|
+
return finishRun("needs_input");
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11012
11409
|
const mentions = await this.packMentions(request);
|
|
11013
11410
|
const retrievedContext = trivialTurn && !mentions.length ? "" : buildRetrievedContext(
|
|
11014
11411
|
packed,
|
|
@@ -11036,19 +11433,10 @@ var AgentRunner = class {
|
|
|
11036
11433
|
scope: augmentation.memoryScope ?? "session"
|
|
11037
11434
|
});
|
|
11038
11435
|
}
|
|
11039
|
-
const contractEnabled = shouldUseTaskContract(
|
|
11040
|
-
request,
|
|
11041
|
-
turnDirective.intent,
|
|
11042
|
-
this.session.taskContract
|
|
11043
|
-
);
|
|
11044
|
-
if (contractEnabled && (!this.session.taskContract || this.session.taskContract.state === "satisfied")) {
|
|
11045
|
-
this.session.taskContract = createDraftTaskContract(request, this.session.audit?.at(-1)?.id);
|
|
11046
|
-
await this.persist();
|
|
11047
|
-
await emit({ type: "contract", contract: structuredClone(this.session.taskContract) });
|
|
11048
|
-
}
|
|
11049
11436
|
activeRunContract = contractEnabled ? this.session.taskContract : void 0;
|
|
11050
11437
|
let verificationAttempted = false;
|
|
11051
11438
|
const maxTurns = options.maxTurns ?? this.config.agent.maxTurns;
|
|
11439
|
+
const epochTokenBudget = this.config.agent.maxEpochTokens ?? this.config.agent.maxSessionTokens;
|
|
11052
11440
|
const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
|
|
11053
11441
|
if (this.contextManager.shouldCompact(this.session, contextBudget)) {
|
|
11054
11442
|
const compacted = await this.compactContext(void 0, options.signal, "automatic");
|
|
@@ -11074,11 +11462,15 @@ var AgentRunner = class {
|
|
|
11074
11462
|
if (this.session.usage.inputTokens + this.session.usage.outputTokens >= this.config.agent.maxSessionTokens) {
|
|
11075
11463
|
return finishRun("token_budget");
|
|
11076
11464
|
}
|
|
11465
|
+
if (contextEpochTokens(this.session) >= epochTokenBudget) {
|
|
11466
|
+
await this.rollContextEpoch("token_budget", options.signal, emit);
|
|
11467
|
+
}
|
|
11077
11468
|
this.applySteering();
|
|
11078
11469
|
await emit({ type: "thinking", turn });
|
|
11079
11470
|
const dynamicPrompt = [
|
|
11080
11471
|
buildSessionStatePrompt(this.session),
|
|
11081
11472
|
turnDirective.text,
|
|
11473
|
+
`<intent-sufficiency route="${this.session.intentAssessment?.route ?? "direct_execute"}">${intentDirective}</intent-sufficiency>`,
|
|
11082
11474
|
this.contextManager.buildShortTermPrompt(this.session),
|
|
11083
11475
|
pinnedContext,
|
|
11084
11476
|
options.turnInstructions ?? "",
|
|
@@ -11091,7 +11483,9 @@ var AgentRunner = class {
|
|
|
11091
11483
|
activeMessages(this.session),
|
|
11092
11484
|
contextBudget
|
|
11093
11485
|
);
|
|
11094
|
-
const
|
|
11486
|
+
const availableLifetimeTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
|
|
11487
|
+
const availableEpochTokens = epochTokenBudget - contextEpochTokens(this.session);
|
|
11488
|
+
const availableTokens = Math.min(availableLifetimeTokens, availableEpochTokens);
|
|
11095
11489
|
const visibleTools = visibleToolDefinitions(
|
|
11096
11490
|
this.tools,
|
|
11097
11491
|
options.askMode === true,
|
|
@@ -11105,9 +11499,15 @@ var AgentRunner = class {
|
|
|
11105
11499
|
loadedProgressiveTools
|
|
11106
11500
|
);
|
|
11107
11501
|
const estimatedInputTokens = estimateMessages2(messages) + estimateToolDefinitions(visibleTools);
|
|
11108
|
-
if (
|
|
11502
|
+
if (estimatedInputTokens >= availableLifetimeTokens) {
|
|
11109
11503
|
return finishRun("token_budget");
|
|
11110
11504
|
}
|
|
11505
|
+
if (availableTokens <= 0 || estimatedInputTokens >= availableEpochTokens) {
|
|
11506
|
+
if (contextEpochTokens(this.session) === 0) return finishRun("token_budget");
|
|
11507
|
+
await this.rollContextEpoch("token_budget", options.signal, emit);
|
|
11508
|
+
turn -= 1;
|
|
11509
|
+
continue;
|
|
11510
|
+
}
|
|
11111
11511
|
const maxOutputTokens = Math.max(1, Math.min(
|
|
11112
11512
|
this.config.model.maxTokens ?? 8192,
|
|
11113
11513
|
availableTokens - estimatedInputTokens
|
|
@@ -11129,7 +11529,7 @@ var AgentRunner = class {
|
|
|
11129
11529
|
breakdown
|
|
11130
11530
|
});
|
|
11131
11531
|
}
|
|
11132
|
-
const assistantId =
|
|
11532
|
+
const assistantId = randomUUID15();
|
|
11133
11533
|
const response = await this.completeModel(
|
|
11134
11534
|
messages,
|
|
11135
11535
|
visibleTools,
|
|
@@ -11509,6 +11909,9 @@ ${completeContent}`;
|
|
|
11509
11909
|
metadata.failure = receipt;
|
|
11510
11910
|
} else {
|
|
11511
11911
|
recovery.recordSuccess(call);
|
|
11912
|
+
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));
|
|
11913
|
+
const resolvedFailureSignatures = resolvableFailureSignatures(call).filter((signature) => historicalFailures.has(signature));
|
|
11914
|
+
if (resolvedFailureSignatures.length) metadata.resolvedFailureSignatures = resolvedFailureSignatures;
|
|
11512
11915
|
}
|
|
11513
11916
|
const evidenceProgress = recovery.recordEvidence(call, {
|
|
11514
11917
|
toolCallId: call.id,
|
|
@@ -11578,13 +11981,13 @@ ${completeContent}`;
|
|
|
11578
11981
|
this.recordPermission(call, category, "allow", "Approved for this session.");
|
|
11579
11982
|
return true;
|
|
11580
11983
|
}
|
|
11581
|
-
await emit({ type: "permission", call, category });
|
|
11984
|
+
await emit({ type: "permission", call, category, reason: decision.reason });
|
|
11582
11985
|
if (!options.requestPermission) {
|
|
11583
11986
|
this.recordPermission(call, category, "deny", "No permission handler was available.");
|
|
11584
11987
|
return false;
|
|
11585
11988
|
}
|
|
11586
11989
|
try {
|
|
11587
|
-
const grant = await options.requestPermission(call, category);
|
|
11990
|
+
const grant = await options.requestPermission(call, category, decision.reason);
|
|
11588
11991
|
const allowed = grant === true || grant === "session";
|
|
11589
11992
|
if (grant === "session") this.sessionApprovals.add(approvalKey);
|
|
11590
11993
|
this.recordPermission(
|
|
@@ -11599,13 +12002,13 @@ ${completeContent}`;
|
|
|
11599
12002
|
return false;
|
|
11600
12003
|
}
|
|
11601
12004
|
}
|
|
11602
|
-
recordPermission(call, category,
|
|
12005
|
+
recordPermission(call, category, outcome2, reason) {
|
|
11603
12006
|
this.appendAudit({
|
|
11604
12007
|
type: "permission",
|
|
11605
12008
|
toolCallId: call.id,
|
|
11606
12009
|
tool: call.name,
|
|
11607
12010
|
category,
|
|
11608
|
-
outcome,
|
|
12011
|
+
outcome: outcome2,
|
|
11609
12012
|
reason
|
|
11610
12013
|
});
|
|
11611
12014
|
}
|
|
@@ -11622,7 +12025,7 @@ ${completeContent}`;
|
|
|
11622
12025
|
}
|
|
11623
12026
|
appendAudit(event) {
|
|
11624
12027
|
const audit = this.session.audit ?? (this.session.audit = []);
|
|
11625
|
-
audit.push({ id:
|
|
12028
|
+
audit.push({ id: randomUUID15(), createdAt: (/* @__PURE__ */ new Date()).toISOString(), ...event });
|
|
11626
12029
|
if (audit.length > 5e3) audit.splice(0, audit.length - 5e3);
|
|
11627
12030
|
}
|
|
11628
12031
|
async acceptChangedFiles(paths) {
|
|
@@ -11643,7 +12046,7 @@ ${completeContent}`;
|
|
|
11643
12046
|
const results = [];
|
|
11644
12047
|
for (const command2 of this.config.agent.verifyCommands) {
|
|
11645
12048
|
const call = {
|
|
11646
|
-
id: `verify-${
|
|
12049
|
+
id: `verify-${randomUUID15()}`,
|
|
11647
12050
|
name: "shell",
|
|
11648
12051
|
arguments: { command: command2, cwd: this.workspace.primaryRoot }
|
|
11649
12052
|
};
|
|
@@ -11691,6 +12094,30 @@ ${completeContent}`;
|
|
|
11691
12094
|
await this.persist();
|
|
11692
12095
|
return result;
|
|
11693
12096
|
}
|
|
12097
|
+
async rollContextEpoch(reason, signal, emit) {
|
|
12098
|
+
let receipt;
|
|
12099
|
+
const contextBudget = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
|
|
12100
|
+
if (this.contextManager.shouldCompact(this.session, contextBudget)) {
|
|
12101
|
+
const compacted = await this.compactContext(
|
|
12102
|
+
"Create a boundary handoff for the next bounded context epoch.",
|
|
12103
|
+
signal,
|
|
12104
|
+
"automatic"
|
|
12105
|
+
);
|
|
12106
|
+
receipt = compacted.receipt;
|
|
12107
|
+
if (compacted.status === "compacted") await emit({ type: "context_compacted", ...compacted });
|
|
12108
|
+
}
|
|
12109
|
+
const rotated = rotateContextEpoch(this.session, reason, receipt);
|
|
12110
|
+
await this.persist();
|
|
12111
|
+
await emit({
|
|
12112
|
+
type: "context_epoch",
|
|
12113
|
+
index: rotated.current.index,
|
|
12114
|
+
previousIndex: rotated.previous.index,
|
|
12115
|
+
reason,
|
|
12116
|
+
inputTokens: rotated.previous.usage.inputTokens,
|
|
12117
|
+
outputTokens: rotated.previous.usage.outputTokens,
|
|
12118
|
+
handoff: rotated.handoff
|
|
12119
|
+
});
|
|
12120
|
+
}
|
|
11694
12121
|
getContextStatus() {
|
|
11695
12122
|
return this.contextManager.status(this.session);
|
|
11696
12123
|
}
|
|
@@ -11722,8 +12149,14 @@ ${completeContent}`;
|
|
|
11722
12149
|
toolOutputBudget() {
|
|
11723
12150
|
const contextWindowTokens = Math.max(24e3, Math.min(1e5, this.config.context.maxTokens * 3));
|
|
11724
12151
|
const activeContextTokens = this.contextManager.status(this.session, contextWindowTokens).activeTokens;
|
|
11725
|
-
const
|
|
11726
|
-
|
|
12152
|
+
const remainingLifetimeTokens = this.config.agent.maxSessionTokens - (this.session.usage.inputTokens + this.session.usage.outputTokens);
|
|
12153
|
+
const epochBudget = this.config.agent.maxEpochTokens ?? this.config.agent.maxSessionTokens;
|
|
12154
|
+
const remainingEpochTokens = epochBudget - contextEpochTokens(this.session);
|
|
12155
|
+
return dynamicToolOutputBudget(
|
|
12156
|
+
contextWindowTokens,
|
|
12157
|
+
activeContextTokens,
|
|
12158
|
+
Math.min(remainingLifetimeTokens, remainingEpochTokens)
|
|
12159
|
+
);
|
|
11727
12160
|
}
|
|
11728
12161
|
async protectToolResult(result) {
|
|
11729
12162
|
const metadata = { ...result.metadata ?? {} };
|
|
@@ -11779,7 +12212,7 @@ ${completeContent}`;
|
|
|
11779
12212
|
};
|
|
11780
12213
|
function message(role, content, extra = {}) {
|
|
11781
12214
|
return {
|
|
11782
|
-
id:
|
|
12215
|
+
id: randomUUID15(),
|
|
11783
12216
|
role,
|
|
11784
12217
|
content,
|
|
11785
12218
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -11894,6 +12327,7 @@ function recordTokenUsage(session, providerUsage, estimatedInputTokens, estimate
|
|
|
11894
12327
|
const outputTokens = outputActual ?? estimatedOutputTokens;
|
|
11895
12328
|
session.usage.inputTokens += inputTokens;
|
|
11896
12329
|
session.usage.outputTokens += outputTokens;
|
|
12330
|
+
recordContextEpochUsage(session, inputTokens, outputTokens);
|
|
11897
12331
|
if (inputActual !== void 0) {
|
|
11898
12332
|
session.usage.actualInputTokens = (session.usage.actualInputTokens ?? 0) + inputActual;
|
|
11899
12333
|
} else {
|
|
@@ -11969,6 +12403,11 @@ ${content}` : content,
|
|
|
11969
12403
|
...failure ? { metadata: { failure } } : {}
|
|
11970
12404
|
};
|
|
11971
12405
|
}
|
|
12406
|
+
function failureSignatureFromMetadata(value) {
|
|
12407
|
+
if (!value || typeof value !== "object") return;
|
|
12408
|
+
const signature = value.signature;
|
|
12409
|
+
return typeof signature === "string" && signature ? signature : void 0;
|
|
12410
|
+
}
|
|
11972
12411
|
function visibleToolDefinitions(tools, askMode, contractEnabled, artifactReadAvailable, duplicationAvailable, request, loadedProgressiveTools) {
|
|
11973
12412
|
const eligible = tools.definitions().filter(
|
|
11974
12413
|
(tool) => (!askMode || tool.category === "read") && (contractEnabled || tool.name !== "task_contract") && (artifactReadAvailable || tool.name !== "read_tool_artifact") && (duplicationAvailable || tool.name !== "duplication_audit")
|
|
@@ -12176,7 +12615,7 @@ function integer(value, fallback, min, max) {
|
|
|
12176
12615
|
}
|
|
12177
12616
|
|
|
12178
12617
|
// src/agent/delegation.ts
|
|
12179
|
-
import { randomUUID as
|
|
12618
|
+
import { randomUUID as randomUUID17 } from "node:crypto";
|
|
12180
12619
|
import { join as join18 } from "node:path";
|
|
12181
12620
|
import { z as z19 } from "zod";
|
|
12182
12621
|
|
|
@@ -12305,7 +12744,7 @@ function numeric(...values) {
|
|
|
12305
12744
|
}
|
|
12306
12745
|
|
|
12307
12746
|
// src/agent/team-store.ts
|
|
12308
|
-
import { createHash as createHash16, randomUUID as
|
|
12747
|
+
import { createHash as createHash16, randomUUID as randomUUID16 } from "node:crypto";
|
|
12309
12748
|
import { lstat as lstat18, readFile as readFile17, readdir as readdir7, rm as rm3 } from "node:fs/promises";
|
|
12310
12749
|
import { join as join16, resolve as resolve18 } from "node:path";
|
|
12311
12750
|
import { z as z18 } from "zod";
|
|
@@ -12399,7 +12838,7 @@ var TeamRunStore = class {
|
|
|
12399
12838
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
12400
12839
|
const manifest = manifestSchema2.parse({
|
|
12401
12840
|
version: 2,
|
|
12402
|
-
id:
|
|
12841
|
+
id: randomUUID16(),
|
|
12403
12842
|
workspace: this.workspace,
|
|
12404
12843
|
objective: input2.objective,
|
|
12405
12844
|
reviewer: input2.reviewer,
|
|
@@ -13205,7 +13644,7 @@ var DelegationManager = class {
|
|
|
13205
13644
|
maxReviewRounds: 0
|
|
13206
13645
|
});
|
|
13207
13646
|
await emit?.({ type: "team_start", id: board.id, objective: task });
|
|
13208
|
-
const writerId =
|
|
13647
|
+
const writerId = randomUUID17();
|
|
13209
13648
|
await emit?.({ type: "agent_queued", id: writerId, profile: profile.name, task, phase: "write" });
|
|
13210
13649
|
const draft = await this.writerLane.createDraft(
|
|
13211
13650
|
Math.min(this.team.maxWriterPatchBytes ?? 6e4, 12e4),
|
|
@@ -13216,18 +13655,18 @@ var DelegationManager = class {
|
|
|
13216
13655
|
await this.recordAgent(board.id, writer, "write");
|
|
13217
13656
|
const writerFailed = !writer.ok || !draft.patch || !draft.worktreeCleaned || signal?.aborted;
|
|
13218
13657
|
if (writerFailed) {
|
|
13219
|
-
const
|
|
13658
|
+
const outcome2 = writer.termination === "cancelled" || signal?.aborted ? "cancelled" : "failed";
|
|
13220
13659
|
await this.teamStore.recordWriterLane(board.id, {
|
|
13221
13660
|
profile: profile.name,
|
|
13222
13661
|
reviewer: reviewer.name,
|
|
13223
13662
|
baseCommit: draft.baseCommit,
|
|
13224
|
-
outcome,
|
|
13663
|
+
outcome: outcome2,
|
|
13225
13664
|
patch: draft.patch,
|
|
13226
13665
|
files: draft.files,
|
|
13227
13666
|
worktreeCleaned: draft.worktreeCleaned
|
|
13228
13667
|
});
|
|
13229
13668
|
await this.teamStore.complete(board.id, { accepted: false, reviewRounds: 0, failed: true });
|
|
13230
|
-
const status2 =
|
|
13669
|
+
const status2 = outcome2 === "cancelled" ? "cancelled" : "failed";
|
|
13231
13670
|
const detail2 = !draft.worktreeCleaned ? "Writer worktree cleanup could not be verified; integration is blocked." : !draft.patch ? "Writer returned no patch." : writer.summary;
|
|
13232
13671
|
await emit?.({ type: "writer_lane", id: board.id, status: status2, detail: detail2, files: draft.files });
|
|
13233
13672
|
await emit?.({ type: "team_done", id: board.id, accepted: false, reviewRounds: 0 });
|
|
@@ -13575,7 +14014,7 @@ You are the only writer inside a disposable worktree. Make only the bounded requ
|
|
|
13575
14014
|
const reviewer = reviewerOverride ?? this.team.reviewerProfile ?? "reviewer";
|
|
13576
14015
|
const rounds = Math.max(0, this.team.maxReviewRounds ?? 1);
|
|
13577
14016
|
const board = await this.teamStore?.create({ objective, reviewer, maxReviewRounds: rounds });
|
|
13578
|
-
const runId = board?.id ??
|
|
14017
|
+
const runId = board?.id ?? randomUUID17();
|
|
13579
14018
|
await emit?.({ type: "team_start", id: runId, objective });
|
|
13580
14019
|
try {
|
|
13581
14020
|
let results = await this.runBatch(board?.id, tasks, "work", emit, signal);
|
|
@@ -13633,7 +14072,7 @@ ${review.summary}`,
|
|
|
13633
14072
|
}
|
|
13634
14073
|
}
|
|
13635
14074
|
async runBatch(runId, tasks, phase, emit, signal) {
|
|
13636
|
-
const scheduled = tasks.map((task) => ({ id:
|
|
14075
|
+
const scheduled = tasks.map((task) => ({ id: randomUUID17(), task }));
|
|
13637
14076
|
for (const item of scheduled) {
|
|
13638
14077
|
await emit?.({ type: "agent_queued", id: item.id, profile: item.task.profile, task: item.task.task, phase });
|
|
13639
14078
|
}
|
|
@@ -13742,12 +14181,12 @@ Start the response with exactly VERDICT: ACCEPT when the evidence is sufficient,
|
|
|
13742
14181
|
});
|
|
13743
14182
|
}
|
|
13744
14183
|
async peerMessage(runId, from, to, content, emit) {
|
|
13745
|
-
const id =
|
|
14184
|
+
const id = randomUUID17();
|
|
13746
14185
|
if (runId) await this.teamStore?.recordMessage(runId, { id, from, to, content });
|
|
13747
14186
|
await emit?.({ type: "agent_message", id, from, to, content });
|
|
13748
14187
|
}
|
|
13749
14188
|
async runOne(task, phase, emit, signal, retryOf, scheduledId) {
|
|
13750
|
-
const id = scheduledId ??
|
|
14189
|
+
const id = scheduledId ?? randomUUID17();
|
|
13751
14190
|
const profile = this.options.profiles.get(task.profile);
|
|
13752
14191
|
const configuredRoute = this.team.routes?.[task.profile];
|
|
13753
14192
|
const budgetMode = configuredRoute?.budgetMode ?? this.team.budgetMode ?? "observe";
|
|
@@ -14576,6 +15015,61 @@ function keyEnvironmentName(provider) {
|
|
|
14576
15015
|
// src/cli/output.ts
|
|
14577
15016
|
import { createInterface } from "node:readline/promises";
|
|
14578
15017
|
import chalk2, { Chalk } from "chalk";
|
|
15018
|
+
|
|
15019
|
+
// src/cli/headless-contract.ts
|
|
15020
|
+
var HEADLESS_SCHEMA_VERSION = 1;
|
|
15021
|
+
var HEADLESS_EXIT_CODES = {
|
|
15022
|
+
completed: 0,
|
|
15023
|
+
error: 1,
|
|
15024
|
+
needsInput: 2,
|
|
15025
|
+
unverified: 3,
|
|
15026
|
+
verificationFailed: 4,
|
|
15027
|
+
blocked: 5,
|
|
15028
|
+
cancelled: 6,
|
|
15029
|
+
maxTurns: 7,
|
|
15030
|
+
tokenBudget: 8
|
|
15031
|
+
};
|
|
15032
|
+
function resolveHeadlessOutcome(input2) {
|
|
15033
|
+
if (input2.error !== void 0 || input2.reason === "error") {
|
|
15034
|
+
return outcome("error", HEADLESS_EXIT_CODES.error, input2.reason ?? "error");
|
|
15035
|
+
}
|
|
15036
|
+
if (input2.reason === "needs_input") {
|
|
15037
|
+
return outcome("needs_input", HEADLESS_EXIT_CODES.needsInput, input2.reason);
|
|
15038
|
+
}
|
|
15039
|
+
if (input2.reason === "blocked" || input2.completion?.acceptance?.state === "blocked") {
|
|
15040
|
+
return outcome("blocked", HEADLESS_EXIT_CODES.blocked, input2.reason ?? "blocked");
|
|
15041
|
+
}
|
|
15042
|
+
if (input2.reason === "aborted" || input2.reason === "cancelled") {
|
|
15043
|
+
return outcome("cancelled", HEADLESS_EXIT_CODES.cancelled, input2.reason);
|
|
15044
|
+
}
|
|
15045
|
+
if (input2.reason === "max_turns") {
|
|
15046
|
+
return outcome("max_turns", HEADLESS_EXIT_CODES.maxTurns, input2.reason);
|
|
15047
|
+
}
|
|
15048
|
+
if (input2.reason === "token_budget") {
|
|
15049
|
+
return outcome("token_budget", HEADLESS_EXIT_CODES.tokenBudget, input2.reason);
|
|
15050
|
+
}
|
|
15051
|
+
if (input2.reason === "verification_failed" || input2.completion?.status === "verification_failed") {
|
|
15052
|
+
return outcome("verification_failed", HEADLESS_EXIT_CODES.verificationFailed, input2.reason ?? "verification_failed");
|
|
15053
|
+
}
|
|
15054
|
+
if (input2.reason === "unverified" || input2.completion?.status === "unverified") {
|
|
15055
|
+
return outcome("unverified", HEADLESS_EXIT_CODES.unverified, input2.reason ?? "unverified");
|
|
15056
|
+
}
|
|
15057
|
+
if (input2.completion?.status === "verified") {
|
|
15058
|
+
return outcome("verified", HEADLESS_EXIT_CODES.completed, input2.reason ?? "completed");
|
|
15059
|
+
}
|
|
15060
|
+
return outcome("completed", HEADLESS_EXIT_CODES.completed, input2.reason ?? "completed");
|
|
15061
|
+
}
|
|
15062
|
+
function outcome(status, exitCode, reason) {
|
|
15063
|
+
return {
|
|
15064
|
+
schemaVersion: HEADLESS_SCHEMA_VERSION,
|
|
15065
|
+
ok: exitCode === HEADLESS_EXIT_CODES.completed,
|
|
15066
|
+
status,
|
|
15067
|
+
exitCode,
|
|
15068
|
+
reason
|
|
15069
|
+
};
|
|
15070
|
+
}
|
|
15071
|
+
|
|
15072
|
+
// src/cli/output.ts
|
|
14579
15073
|
var HeadlessReporter = class {
|
|
14580
15074
|
constructor(options) {
|
|
14581
15075
|
this.options = options;
|
|
@@ -14585,7 +15079,6 @@ var HeadlessReporter = class {
|
|
|
14585
15079
|
options;
|
|
14586
15080
|
finalResponse = "";
|
|
14587
15081
|
tools = [];
|
|
14588
|
-
eventError;
|
|
14589
15082
|
context;
|
|
14590
15083
|
streamedAssistant = false;
|
|
14591
15084
|
completion;
|
|
@@ -14595,7 +15088,6 @@ var HeadlessReporter = class {
|
|
|
14595
15088
|
onEvent = (event) => {
|
|
14596
15089
|
if (event.type === "assistant") this.finalResponse = event.content;
|
|
14597
15090
|
if (event.type === "tool_result") this.tools.push(event.result);
|
|
14598
|
-
if (event.type === "error") this.eventError = event.error.message;
|
|
14599
15091
|
if (event.type === "done") {
|
|
14600
15092
|
this.doneReason = event.reason;
|
|
14601
15093
|
this.completion = event.completion;
|
|
@@ -14613,26 +15105,33 @@ var HeadlessReporter = class {
|
|
|
14613
15105
|
this.printText(event);
|
|
14614
15106
|
};
|
|
14615
15107
|
finish(session) {
|
|
15108
|
+
const completion = this.completion ?? session.lastRun;
|
|
15109
|
+
const reason = this.doneReason ?? session.lastRun?.reason;
|
|
15110
|
+
const outcome2 = resolveHeadlessOutcome({
|
|
15111
|
+
...reason ? { reason } : {},
|
|
15112
|
+
...completion ? { completion } : {}
|
|
15113
|
+
});
|
|
14616
15114
|
if (this.options.format === "stream-json") {
|
|
14617
15115
|
process.stdout.write(`${JSON.stringify({
|
|
14618
15116
|
type: "session",
|
|
15117
|
+
...outcome2,
|
|
14619
15118
|
session: sessionSummary(session)
|
|
14620
15119
|
})}
|
|
14621
15120
|
`);
|
|
14622
|
-
return;
|
|
15121
|
+
return outcome2;
|
|
14623
15122
|
}
|
|
14624
15123
|
if (this.options.format === "json") {
|
|
14625
15124
|
process.stdout.write(`${JSON.stringify({
|
|
14626
|
-
|
|
15125
|
+
type: "result",
|
|
15126
|
+
...outcome2,
|
|
14627
15127
|
response: this.finalResponse,
|
|
14628
15128
|
session: sessionSummary(session),
|
|
14629
|
-
...
|
|
14630
|
-
...this.completion ? { completion: this.completion } : {},
|
|
15129
|
+
...completion ? { completion } : {},
|
|
14631
15130
|
...this.context ? { context: this.context } : {},
|
|
14632
15131
|
tools: this.tools
|
|
14633
15132
|
}, null, 2)}
|
|
14634
15133
|
`);
|
|
14635
|
-
return;
|
|
15134
|
+
return outcome2;
|
|
14636
15135
|
}
|
|
14637
15136
|
if (this.options.quiet && this.finalResponse.trim()) {
|
|
14638
15137
|
process.stdout.write(`${this.finalResponse.trim()}
|
|
@@ -14647,24 +15146,41 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
14647
15146
|
`
|
|
14648
15147
|
));
|
|
14649
15148
|
}
|
|
15149
|
+
return outcome2;
|
|
14650
15150
|
}
|
|
14651
|
-
fail(error) {
|
|
15151
|
+
fail(error, session) {
|
|
14652
15152
|
const message2 = error instanceof Error ? error.message : String(error);
|
|
14653
|
-
|
|
14654
|
-
if (this.options.format === "
|
|
14655
|
-
process.stdout.write(`${JSON.stringify({
|
|
15153
|
+
const outcome2 = resolveHeadlessOutcome({ reason: "error", error });
|
|
15154
|
+
if (this.options.format === "stream-json") {
|
|
15155
|
+
process.stdout.write(`${JSON.stringify({
|
|
15156
|
+
type: "final",
|
|
15157
|
+
...outcome2,
|
|
15158
|
+
error: message2,
|
|
15159
|
+
...session ? { session: sessionSummary(session) } : {}
|
|
15160
|
+
})}
|
|
14656
15161
|
`);
|
|
14657
|
-
return;
|
|
15162
|
+
return outcome2;
|
|
15163
|
+
}
|
|
15164
|
+
if (this.options.format === "json") {
|
|
15165
|
+
process.stdout.write(`${JSON.stringify({
|
|
15166
|
+
type: "result",
|
|
15167
|
+
...outcome2,
|
|
15168
|
+
error: message2,
|
|
15169
|
+
...session ? { session: sessionSummary(session) } : {}
|
|
15170
|
+
}, null, 2)}
|
|
15171
|
+
`);
|
|
15172
|
+
return outcome2;
|
|
14658
15173
|
}
|
|
14659
15174
|
process.stderr.write(`${this.paint.red(this.glyphs.error)} ${message2}
|
|
14660
15175
|
`);
|
|
15176
|
+
return outcome2;
|
|
14661
15177
|
}
|
|
14662
15178
|
printText(event) {
|
|
14663
|
-
const { quiet, compact:
|
|
14664
|
-
if (quiet) return;
|
|
15179
|
+
const { quiet, compact: compact3 } = this.options;
|
|
15180
|
+
if (quiet && event.type !== "needs_input") return;
|
|
14665
15181
|
switch (event.type) {
|
|
14666
15182
|
case "thinking":
|
|
14667
|
-
if (!
|
|
15183
|
+
if (!compact3) process.stderr.write(this.paint.dim(`${this.glyphs.meta} reasoning ${this.glyphs.separator} turn ${event.turn}
|
|
14668
15184
|
`));
|
|
14669
15185
|
break;
|
|
14670
15186
|
case "context": {
|
|
@@ -14676,7 +15192,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
14676
15192
|
break;
|
|
14677
15193
|
}
|
|
14678
15194
|
case "prompt":
|
|
14679
|
-
if (!
|
|
15195
|
+
if (!compact3) {
|
|
14680
15196
|
const partition = event.breakdown ? ` ${this.glyphs.separator} stable ${event.breakdown.stableTokens} ${this.glyphs.separator} dynamic ${event.breakdown.dynamicTokens} ${this.glyphs.separator} history ${event.breakdown.conversationTokens} ${this.glyphs.separator} tool results ${event.breakdown.toolResultTokens} ${this.glyphs.separator} retrieved ${event.breakdown.retrievedTokens} ${this.glyphs.separator} tools ${event.breakdown.toolSchemaTokens} ${this.glyphs.separator} output cap ${event.breakdown.outputAllowanceTokens}` : "";
|
|
14681
15197
|
process.stderr.write(this.paint.dim(
|
|
14682
15198
|
`${this.glyphs.meta} prompt ${this.glyphs.separator} ${event.intent} ${this.glyphs.separator} ~${event.estimatedTokens} estimated tokens${partition}
|
|
@@ -14712,7 +15228,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
14712
15228
|
}
|
|
14713
15229
|
break;
|
|
14714
15230
|
case "tasks":
|
|
14715
|
-
if (!
|
|
15231
|
+
if (!compact3) {
|
|
14716
15232
|
const completed = event.tasks.filter((task) => task.status === "completed").length;
|
|
14717
15233
|
process.stderr.write(this.paint.dim(`${this.glyphs.meta} plan ${this.glyphs.separator} ${completed}/${event.tasks.length} complete
|
|
14718
15234
|
`));
|
|
@@ -14733,6 +15249,22 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
14733
15249
|
`
|
|
14734
15250
|
);
|
|
14735
15251
|
break;
|
|
15252
|
+
case "needs_input":
|
|
15253
|
+
process.stderr.write(this.paint.yellow(`${this.glyphs.meta} ${event.pending.question}
|
|
15254
|
+
`));
|
|
15255
|
+
event.pending.options.forEach((option2, index) => {
|
|
15256
|
+
process.stderr.write(this.paint.dim(
|
|
15257
|
+
` ${index + 1}. ${option2.label}${option2.recommended ? " (recommended)" : ""} ${this.glyphs.separator} ${option2.impact}
|
|
15258
|
+
`
|
|
15259
|
+
));
|
|
15260
|
+
});
|
|
15261
|
+
break;
|
|
15262
|
+
case "input_resolved":
|
|
15263
|
+
process.stderr.write(this.paint.dim(
|
|
15264
|
+
`${this.glyphs.meta} clarification resolved ${this.glyphs.separator} ${event.answer}
|
|
15265
|
+
`
|
|
15266
|
+
));
|
|
15267
|
+
break;
|
|
14736
15268
|
case "usage":
|
|
14737
15269
|
case "permission":
|
|
14738
15270
|
case "skill":
|
|
@@ -14743,9 +15275,12 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
14743
15275
|
case "agent_done":
|
|
14744
15276
|
case "workflow":
|
|
14745
15277
|
case "context_compacted":
|
|
15278
|
+
case "context_epoch":
|
|
15279
|
+
case "intent":
|
|
14746
15280
|
break;
|
|
14747
15281
|
case "done":
|
|
14748
15282
|
this.printCompletion(event.completion);
|
|
15283
|
+
this.printStopReason(event.reason);
|
|
14749
15284
|
break;
|
|
14750
15285
|
case "error":
|
|
14751
15286
|
break;
|
|
@@ -14776,8 +15311,20 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
14776
15311
|
`
|
|
14777
15312
|
));
|
|
14778
15313
|
}
|
|
15314
|
+
printStopReason(reason) {
|
|
15315
|
+
if (reason === "aborted") {
|
|
15316
|
+
process.stderr.write(this.paint.yellow(`${this.glyphs.warning} cancelled ${this.glyphs.separator} resume with --resume after inspecting the saved session
|
|
15317
|
+
`));
|
|
15318
|
+
} else if (reason === "max_turns") {
|
|
15319
|
+
process.stderr.write(this.paint.yellow(`${this.glyphs.warning} turn limit reached ${this.glyphs.separator} resume with --resume and a larger --max-turns value
|
|
15320
|
+
`));
|
|
15321
|
+
} else if (reason === "token_budget") {
|
|
15322
|
+
process.stderr.write(this.paint.yellow(`${this.glyphs.warning} token budget reached ${this.glyphs.separator} inspect the saved session before resuming with a larger --token-budget
|
|
15323
|
+
`));
|
|
15324
|
+
}
|
|
15325
|
+
}
|
|
14779
15326
|
};
|
|
14780
|
-
async function askConsolePermission(call, category, color = !process.env.NO_COLOR) {
|
|
15327
|
+
async function askConsolePermission(call, category, color = !process.env.NO_COLOR, reason = `${category} tools require approval.`) {
|
|
14781
15328
|
if (!process.stdin.isTTY || !process.stderr.isTTY) return false;
|
|
14782
15329
|
const paint = color ? chalk2 : new Chalk({ level: 0 });
|
|
14783
15330
|
const glyphs = resolveCliGlyphs();
|
|
@@ -14785,6 +15332,10 @@ async function askConsolePermission(call, category, color = !process.env.NO_COLO
|
|
|
14785
15332
|
${paint.yellow("Permission required")} ${paint.dim(`(${category})`)}
|
|
14786
15333
|
`);
|
|
14787
15334
|
process.stderr.write(`${paint.bold(call.name)}${formatToolDetail(call, paint)}
|
|
15335
|
+
`);
|
|
15336
|
+
process.stderr.write(`${paint.dim(`Reason: ${reason}`)}
|
|
15337
|
+
`);
|
|
15338
|
+
process.stderr.write(`${paint.yellow(`Risk: ${permissionRisk(category)}`)}
|
|
14788
15339
|
`);
|
|
14789
15340
|
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
14790
15341
|
try {
|
|
@@ -14794,6 +15345,13 @@ ${paint.yellow("Permission required")} ${paint.dim(`(${category})`)}
|
|
|
14794
15345
|
readline.close();
|
|
14795
15346
|
}
|
|
14796
15347
|
}
|
|
15348
|
+
function permissionRisk(category) {
|
|
15349
|
+
if (category === "read") return "workspace content may enter model context";
|
|
15350
|
+
if (category === "write") return "workspace files may be created, replaced, or deleted";
|
|
15351
|
+
if (category === "shell") return "a local process may read or change workspace state";
|
|
15352
|
+
if (category === "git") return "repository state or remotes may change";
|
|
15353
|
+
return "data may leave this machine or remote state may change";
|
|
15354
|
+
}
|
|
14797
15355
|
function eventToJson(event) {
|
|
14798
15356
|
if (event.type === "error") {
|
|
14799
15357
|
return { type: event.type, error: event.error.message };
|
|
@@ -14815,6 +15373,9 @@ function sessionSummary(session) {
|
|
|
14815
15373
|
...session.lastRun ? { lastRun: session.lastRun } : {},
|
|
14816
15374
|
...session.tokenLedger?.length ? { tokenLedger: session.tokenLedger } : {},
|
|
14817
15375
|
...session.contextCompactionReceipts?.length ? { contextCompactionReceipts: session.contextCompactionReceipts } : {},
|
|
15376
|
+
...session.contextEpochs?.length ? { contextEpochs: session.contextEpochs } : {},
|
|
15377
|
+
...session.intentAssessment ? { intentAssessment: session.intentAssessment } : {},
|
|
15378
|
+
...session.pendingInput ? { pendingInput: session.pendingInput } : {},
|
|
14818
15379
|
usage: session.usage
|
|
14819
15380
|
};
|
|
14820
15381
|
}
|
|
@@ -14896,7 +15457,7 @@ function commandNames(command2) {
|
|
|
14896
15457
|
// src/ui/tui.tsx
|
|
14897
15458
|
import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
14898
15459
|
import { Box as Box2, render as render2, Text as Text3, useApp, useInput as useInput2, useWindowSize } from "ink";
|
|
14899
|
-
import { relative as
|
|
15460
|
+
import { relative as relative10 } from "node:path";
|
|
14900
15461
|
|
|
14901
15462
|
// src/ui/components.tsx
|
|
14902
15463
|
import React2 from "react";
|
|
@@ -14920,6 +15481,8 @@ var commandDefinitions = [
|
|
|
14920
15481
|
command("permissions", "Inspect the active permission policy"),
|
|
14921
15482
|
command("changes", "List files changed in the active session"),
|
|
14922
15483
|
command("diff", "Open the current workspace diff in the transcript"),
|
|
15484
|
+
command("review", "Run a read-only review with a fixed, redacted scope", "/review [working-tree|commit <ref>|branch <base-ref>]"),
|
|
15485
|
+
command("recover", "Open recovery actions for the last incomplete run", "/recover [retry|resume|diff|rollback|audit]"),
|
|
14923
15486
|
command("checkpoints", "List recoverable pre-mutation snapshots"),
|
|
14924
15487
|
command("audit", "Review the hash-chained tool and permission timeline"),
|
|
14925
15488
|
command("rollback", "Restore workspace files from a checkpoint", "/rollback [checkpoint-id]"),
|
|
@@ -14976,6 +15539,28 @@ function commandSuggestions(input2, options = {}) {
|
|
|
14976
15539
|
description: "Show the secure shared-connection setup command"
|
|
14977
15540
|
}].filter((item) => item.label.includes(argument.trim().toLocaleLowerCase()));
|
|
14978
15541
|
}
|
|
15542
|
+
if (firstSpace >= 0 && commandName === "review") {
|
|
15543
|
+
const query = argument.trim().toLocaleLowerCase();
|
|
15544
|
+
return [
|
|
15545
|
+
{ value: "/review working-tree", label: "working-tree", description: "Review only the current working tree" },
|
|
15546
|
+
{ value: "/review commit ", label: "commit", description: "Review exactly one Git commit" },
|
|
15547
|
+
{ value: "/review branch ", label: "branch", description: "Review the current branch against one base ref" }
|
|
15548
|
+
].filter((item) => item.label.includes(query) || item.value.slice("/review ".length).startsWith(query));
|
|
15549
|
+
}
|
|
15550
|
+
if (firstSpace >= 0 && commandName === "recover") {
|
|
15551
|
+
const query = argument.trim().toLocaleLowerCase();
|
|
15552
|
+
return [
|
|
15553
|
+
{ name: "retry", description: "Repair and retry the latest failed operation once" },
|
|
15554
|
+
{ name: "resume", description: "Continue the most recent incomplete logical run" },
|
|
15555
|
+
{ name: "diff", description: "Inspect the current workspace patch" },
|
|
15556
|
+
{ name: "audit", description: "Review permission and tool evidence" },
|
|
15557
|
+
{ name: "rollback", description: "Choose a checkpoint to restore" }
|
|
15558
|
+
].filter((item) => item.name.includes(query)).map((item) => ({
|
|
15559
|
+
value: `/recover ${item.name}`,
|
|
15560
|
+
label: item.name,
|
|
15561
|
+
description: item.description
|
|
15562
|
+
}));
|
|
15563
|
+
}
|
|
14979
15564
|
if (firstSpace >= 0 && commandName === "memory") {
|
|
14980
15565
|
const query = argument.trim().toLocaleLowerCase();
|
|
14981
15566
|
return [
|
|
@@ -15507,7 +16092,7 @@ function ToolGlyph({ state, glyphs }) {
|
|
|
15507
16092
|
if (state === "cancelled") return /* @__PURE__ */ jsx(Text, { color: theme.warning, children: glyphs.warning });
|
|
15508
16093
|
return /* @__PURE__ */ jsx(Text, { color: theme.error, children: glyphs.error });
|
|
15509
16094
|
}
|
|
15510
|
-
function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = false, expandedToolId, compact:
|
|
16095
|
+
function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = false, expandedToolId, compact: compact3 = false }) {
|
|
15511
16096
|
const theme = useTheme();
|
|
15512
16097
|
const glyphs = resolveGlyphs(glyphMode);
|
|
15513
16098
|
if (!items.length) {
|
|
@@ -15515,13 +16100,13 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
15515
16100
|
}
|
|
15516
16101
|
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", children: items.map((item, index) => {
|
|
15517
16102
|
if (item.kind === "user") {
|
|
15518
|
-
return /* @__PURE__ */ jsxs(Box, { marginBottom:
|
|
16103
|
+
return /* @__PURE__ */ jsxs(Box, { marginBottom: compact3 || item.clipped ? 0 : 1, children: [
|
|
15519
16104
|
/* @__PURE__ */ jsx(Box, { width: 2, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.accent, children: glyphs.prompt }) }),
|
|
15520
16105
|
/* @__PURE__ */ jsx(Text, { bold: true, color: theme.textStrong, wrap: "wrap", children: sanitizeTerminalText(item.text) })
|
|
15521
16106
|
] }, item.id);
|
|
15522
16107
|
}
|
|
15523
16108
|
if (item.kind === "assistant") {
|
|
15524
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom:
|
|
16109
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginBottom: compact3 || item.clipped ? 0 : 1, children: [
|
|
15525
16110
|
/* @__PURE__ */ jsxs(Text, { bold: true, color: theme.accent, children: [
|
|
15526
16111
|
glyphs.brand,
|
|
15527
16112
|
" ",
|
|
@@ -15537,7 +16122,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
15537
16122
|
}
|
|
15538
16123
|
if (item.kind === "context") {
|
|
15539
16124
|
const spans = item.spans ?? [];
|
|
15540
|
-
const spanLimit =
|
|
16125
|
+
const spanLimit = compact3 ? 2 : 3;
|
|
15541
16126
|
const innerWidth = Math.max(1, safeWidth(width) - 2);
|
|
15542
16127
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
15543
16128
|
/* @__PURE__ */ jsx(
|
|
@@ -15550,7 +16135,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
15550
16135
|
labelColor: theme.accent
|
|
15551
16136
|
}
|
|
15552
16137
|
),
|
|
15553
|
-
!
|
|
16138
|
+
!compact3 && item.budgetReason ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`${glyphs.branchLast} ${sanitizeInlineTerminalText(item.budgetReason)}`, innerWidth) }) : null,
|
|
15554
16139
|
spans.slice(0, spanLimit).map((span, spanIndex) => {
|
|
15555
16140
|
const lines = span.startLine === span.endLine ? `${span.startLine}` : `${span.startLine}-${span.endLine}`;
|
|
15556
16141
|
const location = `${compactDisplayPath(sanitizeInlineTerminalText(span.path), 44)}:${lines}`;
|
|
@@ -15590,7 +16175,7 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
15590
16175
|
const duration = item.durationMs !== void 0 ? formatDuration(item.durationMs) : "";
|
|
15591
16176
|
const detailText = [detail, duration].filter(Boolean).join(" ");
|
|
15592
16177
|
const expanded = Boolean(item.output) && (showToolOutput || expandedToolId === item.id);
|
|
15593
|
-
const verbose = expanded && item.output ? limitTerminalText(item.output,
|
|
16178
|
+
const verbose = expanded && item.output ? limitTerminalText(item.output, compact3 ? 24 : 80) : void 0;
|
|
15594
16179
|
const disclosure = item.output ? expanded ? glyphs.expanded : glyphs.collapsed : "";
|
|
15595
16180
|
const disclosureWidth = disclosure ? displayWidth(disclosure) + 1 : 0;
|
|
15596
16181
|
const nameLimit = Math.max(1, Math.min(rowWidth - 2 - disclosureWidth, rowWidth < 64 ? rowWidth - 2 - disclosureWidth : 28));
|
|
@@ -15722,9 +16307,28 @@ function Timeline({ items, width = 80, glyphMode = "auto", showToolOutput = fals
|
|
|
15722
16307
|
item.id
|
|
15723
16308
|
);
|
|
15724
16309
|
}
|
|
16310
|
+
if (item.kind === "clarification") {
|
|
16311
|
+
const rowWidth = safeWidth(width);
|
|
16312
|
+
const chinese = /[\u3400-\u9fff]/u.test(item.pending.question);
|
|
16313
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", paddingLeft: 2, marginBottom: 1, children: [
|
|
16314
|
+
/* @__PURE__ */ jsx(Text, { bold: true, color: theme.warning, children: sanitizeTerminalText(item.pending.question) }),
|
|
16315
|
+
item.pending.options.map((option2, optionIndex) => {
|
|
16316
|
+
const label = `${optionIndex + 1}. ${sanitizeInlineTerminalText(option2.label)}${option2.recommended ? chinese ? "\uFF08\u63A8\u8350\uFF09" : " (recommended)" : ""}`;
|
|
16317
|
+
const impact = sanitizeInlineTerminalText(option2.impact);
|
|
16318
|
+
if (rowWidth < 48) {
|
|
16319
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
16320
|
+
/* @__PURE__ */ jsx(Text, { color: option2.recommended ? theme.textStrong : theme.muted, children: truncateDisplay(label, Math.max(1, rowWidth - 2)) }),
|
|
16321
|
+
/* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(`${glyphs.branchLast} ${impact}`, Math.max(1, (rowWidth - 2) * 2)) })
|
|
16322
|
+
] }, option2.id);
|
|
16323
|
+
}
|
|
16324
|
+
return /* @__PURE__ */ jsx(Text, { color: option2.recommended ? theme.textStrong : theme.muted, children: truncateDisplay(`${label} ${glyphs.separator} ${impact}`, Math.max(1, rowWidth - 2)) }, option2.id);
|
|
16325
|
+
}),
|
|
16326
|
+
/* @__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." })
|
|
16327
|
+
] }, item.id);
|
|
16328
|
+
}
|
|
15725
16329
|
if (item.kind === "list") return /* @__PURE__ */ jsx(ListPanel, { title: item.title, entries: item.entries, width, glyphMode }, item.id);
|
|
15726
16330
|
if (item.kind === "context-inspector") {
|
|
15727
|
-
return /* @__PURE__ */ jsx(ContextInspector, { status: item.status, working: item.working, summary: item.summary, width, compact:
|
|
16331
|
+
return /* @__PURE__ */ jsx(ContextInspector, { status: item.status, working: item.working, summary: item.summary, width, compact: compact3, glyphMode }, item.id);
|
|
15728
16332
|
}
|
|
15729
16333
|
if (item.kind === "theme") return /* @__PURE__ */ jsx(ThemePreview, { name: item.name, width, glyphs }, item.id);
|
|
15730
16334
|
if (item.kind === "banner") {
|
|
@@ -15934,7 +16538,7 @@ function TaskRail({ tasks, width = 80, glyphMode = "auto", maxItems }) {
|
|
|
15934
16538
|
] }) : null
|
|
15935
16539
|
] });
|
|
15936
16540
|
}
|
|
15937
|
-
function PermissionCard({ call, category, width = 80, glyphMode = "auto", workspace, compact:
|
|
16541
|
+
function PermissionCard({ call, category, reason, width = 80, glyphMode = "auto", workspace, compact: compact3 = false }) {
|
|
15938
16542
|
const theme = useTheme();
|
|
15939
16543
|
const glyphs = resolveGlyphs(glyphMode);
|
|
15940
16544
|
const summary = permissionSummary(call);
|
|
@@ -15942,7 +16546,9 @@ function PermissionCard({ call, category, width = 80, glyphMode = "auto", worksp
|
|
|
15942
16546
|
const innerWidth = Math.max(1, rowWidth - 2);
|
|
15943
16547
|
const title = truncateDisplay(`Permission required ${glyphs.separator} ${category}`, innerWidth);
|
|
15944
16548
|
const tool = truncateDisplay(`tool ${sanitizeInlineTerminalText(call.name)}`, innerWidth);
|
|
15945
|
-
const summaryLine = truncateDisplay(
|
|
16549
|
+
const summaryLine = truncateDisplay(`target ${summary.label} ${summary.value}`, innerWidth);
|
|
16550
|
+
const reasonLine = truncateDisplay(`reason ${redactPermissionText(reason ?? `${category} tools require approval.`)}`, innerWidth);
|
|
16551
|
+
const riskLine = truncateDisplay(`risk ${permissionRisk2(category)}`, innerWidth);
|
|
15946
16552
|
const argumentCwd = typeof call.arguments.cwd === "string" ? call.arguments.cwd : void 0;
|
|
15947
16553
|
const cwd = sanitizeInlineTerminalText(argumentCwd || workspace || "");
|
|
15948
16554
|
const shortcuts = [
|
|
@@ -15967,11 +16573,13 @@ function PermissionCard({ call, category, width = 80, glyphMode = "auto", worksp
|
|
|
15967
16573
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.warning, children: title }) }),
|
|
15968
16574
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.muted, children: tool }) }),
|
|
15969
16575
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.text, children: summaryLine }) }),
|
|
16576
|
+
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.muted, children: reasonLine }) }),
|
|
16577
|
+
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.warning, children: riskLine }) }),
|
|
15970
16578
|
cwd ? /* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.muted, children: truncateDisplay(`cwd ${compactDisplayPath(cwd, Math.max(1, innerWidth - 4))}`, innerWidth) }) }) : null,
|
|
15971
16579
|
rowWidth >= 64 ? /* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx(InlineRow, { parts: shortcuts, width: innerWidth, separator: ` ${glyphs.separator} `, separatorColor: theme.border }) }) : rowWidth >= 28 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
|
|
15972
16580
|
/* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
|
|
15973
16581
|
/* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
|
|
15974
|
-
] }) :
|
|
16582
|
+
] }) : compact3 ? /* @__PURE__ */ jsxs(Box, { paddingLeft: 2, flexDirection: "column", children: [
|
|
15975
16583
|
/* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
|
|
15976
16584
|
/* @__PURE__ */ jsx(InlineRow, { parts: compactNarrowShortcuts.slice(2), width: innerWidth, separator: " ", separatorColor: theme.border })
|
|
15977
16585
|
] }) : /* @__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)) })
|
|
@@ -15987,6 +16595,13 @@ function PermissionLine({ marker, children }) {
|
|
|
15987
16595
|
children
|
|
15988
16596
|
] });
|
|
15989
16597
|
}
|
|
16598
|
+
function permissionRisk2(category) {
|
|
16599
|
+
if (category === "read") return "workspace content may enter model context";
|
|
16600
|
+
if (category === "write") return "workspace files may be created, replaced, or deleted";
|
|
16601
|
+
if (category === "shell") return "a local process may read or change workspace state";
|
|
16602
|
+
if (category === "git") return "repository state or remotes may change";
|
|
16603
|
+
return "data may leave this machine or remote state may change";
|
|
16604
|
+
}
|
|
15990
16605
|
function PromptBar({ busy, value, placeholder, width = 80, mode = "chat", queueCount = 0, queuePreview, attachments = [], glyphMode = "auto", children }) {
|
|
15991
16606
|
const theme = useTheme();
|
|
15992
16607
|
const glyphs = resolveGlyphs(glyphMode);
|
|
@@ -16232,7 +16847,7 @@ function MeterBar({ segments, total, width, glyphs }) {
|
|
|
16232
16847
|
remainder ? /* @__PURE__ */ jsx(Text, { color: theme.border, children: glyphs.meterEmpty.repeat(remainder) }) : null
|
|
16233
16848
|
] });
|
|
16234
16849
|
}
|
|
16235
|
-
function ContextInspector({ status, working, summary, width, memory, connections, sources: sources2, compact:
|
|
16850
|
+
function ContextInspector({ status, working, summary, width, memory, connections, sources: sources2, compact: compact3 = false, minimal = false, glyphMode = "auto" }) {
|
|
16236
16851
|
const theme = useTheme();
|
|
16237
16852
|
const glyphs = resolveGlyphs(glyphMode);
|
|
16238
16853
|
const hasCompactedContext = status.compactedMessages > 0 || Boolean(summary);
|
|
@@ -16253,10 +16868,16 @@ function ContextInspector({ status, working, summary, width, memory, connections
|
|
|
16253
16868
|
{ label: "summary", detail: hasCompactedContext ? `~${formatTokens(status.summaryTokens)} tokens ${glyphs.separator} ${status.compactedMessages} compacted${summary ? "" : ` ${glyphs.separator} facts`}` : "not created" },
|
|
16254
16869
|
{ label: "long-term", detail: memory ?? `retrieved by relevance ${glyphs.separator} untrusted context` }
|
|
16255
16870
|
];
|
|
16256
|
-
if (
|
|
16257
|
-
|
|
16258
|
-
|
|
16259
|
-
|
|
16871
|
+
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) {
|
|
16872
|
+
entries.splice(1, 0, {
|
|
16873
|
+
label: "epoch",
|
|
16874
|
+
detail: `#${status.epochIndex} ${formatTokens(status.epochTokens)}/${formatTokens(status.epochBudget)} ${glyphs.separator} lifetime ${formatTokens(status.lifetimeTokens)}/${formatTokens(status.lifetimeBudget)}`
|
|
16875
|
+
});
|
|
16876
|
+
}
|
|
16877
|
+
if (!compact3 && working?.constraints.length) entries.push({ label: `constraints ${working.constraints.length}`, detail: working.constraints.slice(0, 2).join(` ${glyphs.separator} `) });
|
|
16878
|
+
if (!compact3 && working?.decisions.length) entries.push({ label: `decisions ${working.decisions.length}`, detail: working.decisions.slice(0, 2).join(` ${glyphs.separator} `) });
|
|
16879
|
+
if (!compact3 && working?.openQuestions.length) entries.push({ label: `open ${working.openQuestions.length}`, detail: working.openQuestions.slice(0, 2).join(` ${glyphs.separator} `), tone: "warning" });
|
|
16880
|
+
if (!compact3 && working?.relevantFiles.length) entries.push({ label: "relevant files", detail: working.relevantFiles.map((file) => compactDisplayPath(sanitizeInlineTerminalText(file), 28)).join(` ${glyphs.separator} `) });
|
|
16260
16881
|
if (sources2?.length) {
|
|
16261
16882
|
const pinned = sources2.filter((source) => source.state === "pinned");
|
|
16262
16883
|
const muted = sources2.filter((source) => source.state === "muted");
|
|
@@ -16290,7 +16911,7 @@ function ContextInspector({ status, working, summary, width, memory, connections
|
|
|
16290
16911
|
/* @__PURE__ */ jsx(MeterBar, { segments, total: meterTotal, width: meterWidth, glyphs })
|
|
16291
16912
|
] }) : null
|
|
16292
16913
|
] }),
|
|
16293
|
-
/* @__PURE__ */ jsx(ListPanel, { title: "", hideTitle: true, entries, width, glyphMode })
|
|
16914
|
+
/* @__PURE__ */ jsx(ListPanel, { title: "", hideTitle: true, entries, width: Math.max(1, rowWidth - 2), glyphMode })
|
|
16294
16915
|
] });
|
|
16295
16916
|
}
|
|
16296
16917
|
function ThemePreview({ name, width, glyphs }) {
|
|
@@ -16377,8 +16998,8 @@ function Banner({ model, engine, workspace, version, width, glyphs }) {
|
|
|
16377
16998
|
function UpdateNotice({ current, latest, command: command2, highlights, width, glyphs }) {
|
|
16378
16999
|
const theme = useTheme();
|
|
16379
17000
|
const availableWidth = safeWidth(width);
|
|
16380
|
-
const
|
|
16381
|
-
const parts =
|
|
17001
|
+
const compact3 = availableWidth < 48;
|
|
17002
|
+
const parts = compact3 ? [
|
|
16382
17003
|
{ text: `${glyphs.up} `, color: theme.accent, bold: true },
|
|
16383
17004
|
{ text: `v${current}`, color: theme.dim, bold: false },
|
|
16384
17005
|
{ text: ` ${glyphs.arrow} `, color: theme.muted, bold: false },
|
|
@@ -16399,7 +17020,7 @@ function UpdateNotice({ current, latest, command: command2, highlights, width, g
|
|
|
16399
17020
|
const bullets = bulletWidth > 0 ? (highlights ?? []).map((line) => truncateDisplay(sanitizeInlineTerminalText(line), bulletWidth)) : [];
|
|
16400
17021
|
return /* @__PURE__ */ jsxs(Box, { marginBottom: 1, flexDirection: "column", children: [
|
|
16401
17022
|
/* @__PURE__ */ jsx(Box, { children: truncated ? /* @__PURE__ */ jsx(Text, { color: theme.muted, children: rendered }) : parts.map((part, index) => /* @__PURE__ */ jsx(Text, { color: part.color, bold: part.bold, children: part.text }, index)) }),
|
|
16402
|
-
|
|
17023
|
+
compact3 ? /* @__PURE__ */ jsx(Text, { color: theme.dim, children: truncateDisplay(` run ${command2}`, availableWidth) }) : null,
|
|
16403
17024
|
bullets.map((line, index) => /* @__PURE__ */ jsx(Text, { color: theme.dim, children: `${bulletPrefix}${line}` }, index))
|
|
16404
17025
|
] });
|
|
16405
17026
|
}
|
|
@@ -17265,6 +17886,40 @@ function clipTimelineItem(item, options) {
|
|
|
17265
17886
|
if (item.kind === "notice") {
|
|
17266
17887
|
return { ...item, text: tailText(item.text, width, options.rows) };
|
|
17267
17888
|
}
|
|
17889
|
+
if (item.kind === "list") {
|
|
17890
|
+
if (options.rows <= 1) return { id: item.id, kind: "notice", text: truncateDisplay(item.title, width) };
|
|
17891
|
+
const available = Math.max(1, options.rows - 2);
|
|
17892
|
+
const entries = [];
|
|
17893
|
+
let used = 0;
|
|
17894
|
+
let firstIncluded = item.entries.length;
|
|
17895
|
+
for (let index = item.entries.length - 1; index >= 0; index -= 1) {
|
|
17896
|
+
const entry = item.entries[index];
|
|
17897
|
+
const rows = 1 + (entry.detail ? 1 : 0);
|
|
17898
|
+
if (entries.length && used + rows > available) break;
|
|
17899
|
+
if (!entries.length && rows > available) {
|
|
17900
|
+
const { detail: _detail, ...compactEntry } = entry;
|
|
17901
|
+
entries.unshift(compactEntry);
|
|
17902
|
+
firstIncluded = index;
|
|
17903
|
+
used = 1;
|
|
17904
|
+
break;
|
|
17905
|
+
}
|
|
17906
|
+
entries.unshift(entry);
|
|
17907
|
+
firstIncluded = index;
|
|
17908
|
+
used += rows;
|
|
17909
|
+
}
|
|
17910
|
+
if (firstIncluded > 0) {
|
|
17911
|
+
while (entries.length > 1 && used >= available) {
|
|
17912
|
+
const removed = entries.shift();
|
|
17913
|
+
if (!removed) break;
|
|
17914
|
+
firstIncluded += 1;
|
|
17915
|
+
used -= 1 + (removed.detail ? 1 : 0);
|
|
17916
|
+
}
|
|
17917
|
+
if (used < available) {
|
|
17918
|
+
entries.unshift({ label: `${terminalEllipsis()} ${firstIncluded} earlier entries hidden` });
|
|
17919
|
+
}
|
|
17920
|
+
}
|
|
17921
|
+
return { ...item, entries };
|
|
17922
|
+
}
|
|
17268
17923
|
if (item.kind === "update") {
|
|
17269
17924
|
const baseRows = width < 48 ? 3 : 2;
|
|
17270
17925
|
if (options.rows < baseRows) {
|
|
@@ -17307,9 +17962,9 @@ function tailText(value, width, maxRows) {
|
|
|
17307
17962
|
return `${marker}
|
|
17308
17963
|
${selected.join("\n")}`;
|
|
17309
17964
|
}
|
|
17310
|
-
function estimateTimelineItemRows(item, { width, compact:
|
|
17965
|
+
function estimateTimelineItemRows(item, { width, compact: compact3 = false, showToolOutput = false, expandedToolId }) {
|
|
17311
17966
|
const rowWidth = Math.max(1, Math.floor(width));
|
|
17312
|
-
const gap =
|
|
17967
|
+
const gap = compact3 ? 0 : 1;
|
|
17313
17968
|
if (item.kind === "user") return wrappedRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
|
|
17314
17969
|
if (item.kind === "assistant") {
|
|
17315
17970
|
return 1 + richTextRows(item.text, Math.max(1, rowWidth - 2)) + (item.clipped ? 0 : gap);
|
|
@@ -17321,7 +17976,7 @@ function estimateTimelineItemRows(item, { width, compact: compact2 = false, show
|
|
|
17321
17976
|
const detail = item.errorDetail || item.detail;
|
|
17322
17977
|
const detailRows = narrow && detail ? 1 : 0;
|
|
17323
17978
|
const metaRows = item.meta ? 1 : 0;
|
|
17324
|
-
const outputRows = (showToolOutput || item.id === expandedToolId) && item.output ? Math.min(
|
|
17979
|
+
const outputRows = (showToolOutput || item.id === expandedToolId) && item.output ? Math.min(compact3 ? 25 : 81, richTextRows(item.output, Math.max(1, rowWidth - 2))) : 0;
|
|
17325
17980
|
return 1 + detailRows + metaRows + outputRows;
|
|
17326
17981
|
}
|
|
17327
17982
|
if (item.kind === "list") {
|
|
@@ -17335,7 +17990,7 @@ function estimateTimelineItemRows(item, { width, compact: compact2 = false, show
|
|
|
17335
17990
|
if (item.kind === "theme") return 3;
|
|
17336
17991
|
if (item.kind === "context") {
|
|
17337
17992
|
const metaRows = rowWidth < 64 ? 2 : 1;
|
|
17338
|
-
const spanLimit =
|
|
17993
|
+
const spanLimit = compact3 ? 2 : 3;
|
|
17339
17994
|
const spanCount = Math.min(item.spans?.length ?? 0, spanLimit);
|
|
17340
17995
|
const moreRow = (item.spans?.length ?? 0) > spanLimit ? 1 : 0;
|
|
17341
17996
|
const degradationRows = item.degradation ? metaRows : 0;
|
|
@@ -17362,6 +18017,91 @@ function wrappedRows(value, width) {
|
|
|
17362
18017
|
return sanitizeTerminalText(value).split("\n").reduce((rows, line) => rows + Math.max(1, Math.ceil(displayWidth(line) / safeWidth2)), 0);
|
|
17363
18018
|
}
|
|
17364
18019
|
|
|
18020
|
+
// src/ui/review-bundle.ts
|
|
18021
|
+
import { relative as relative9 } from "node:path";
|
|
18022
|
+
var failureClasses = /* @__PURE__ */ new Set([
|
|
18023
|
+
"schema_input",
|
|
18024
|
+
"unknown_tool",
|
|
18025
|
+
"permission_denied",
|
|
18026
|
+
"command_exit",
|
|
18027
|
+
"timeout",
|
|
18028
|
+
"cancelled",
|
|
18029
|
+
"hook",
|
|
18030
|
+
"execution",
|
|
18031
|
+
"no_progress",
|
|
18032
|
+
"contract_required"
|
|
18033
|
+
]);
|
|
18034
|
+
function parseReviewScope(argument) {
|
|
18035
|
+
const value = argument.trim();
|
|
18036
|
+
if (!value || value === "working-tree") return { kind: "working-tree" };
|
|
18037
|
+
const [kind, ref, ...extra] = value.split(/\s+/u);
|
|
18038
|
+
if (kind !== "commit" && kind !== "branch" || !ref || extra.length) {
|
|
18039
|
+
throw new Error("Usage: /review [working-tree|commit <ref>|branch <base-ref>]");
|
|
18040
|
+
}
|
|
18041
|
+
if (!/^[A-Za-z0-9._/@{}~^+-]{1,200}$/u.test(ref) || ref.startsWith("-")) {
|
|
18042
|
+
throw new Error("Review refs must be a single Git revision without control characters or leading dashes.");
|
|
18043
|
+
}
|
|
18044
|
+
return { kind, ref };
|
|
18045
|
+
}
|
|
18046
|
+
function buildRedactedReviewBundle(session, workspaceRoot, scope) {
|
|
18047
|
+
const lastRun = session.lastRun;
|
|
18048
|
+
const failures = (session.audit ?? []).filter((event) => event.type === "tool" && event.outcome === "failure").slice(-8).map((event) => {
|
|
18049
|
+
const receipt = failureReceipt2(event.metadata?.failure);
|
|
18050
|
+
return {
|
|
18051
|
+
tool: event.tool,
|
|
18052
|
+
...event.category ? { category: event.category } : {},
|
|
18053
|
+
...receipt?.class ? { class: receipt.class } : {},
|
|
18054
|
+
...receipt?.retryable !== void 0 ? { retryable: receipt.retryable } : {},
|
|
18055
|
+
...receipt?.circuitOpen !== void 0 ? { circuitOpen: receipt.circuitOpen } : {}
|
|
18056
|
+
};
|
|
18057
|
+
});
|
|
18058
|
+
return {
|
|
18059
|
+
version: 1,
|
|
18060
|
+
redacted: true,
|
|
18061
|
+
sessionId: session.id,
|
|
18062
|
+
scope,
|
|
18063
|
+
changedFiles: [...new Set(session.changedFiles.map((path) => relative9(workspaceRoot, path) || "."))].sort(),
|
|
18064
|
+
...lastRun ? {
|
|
18065
|
+
lastRun: {
|
|
18066
|
+
status: lastRun.status,
|
|
18067
|
+
reason: lastRun.reason,
|
|
18068
|
+
checks: lastRun.checks.map((check) => ({ tool: check.tool, kind: check.kind, ok: check.ok })),
|
|
18069
|
+
...lastRun.acceptance ? { acceptance: {
|
|
18070
|
+
state: lastRun.acceptance.state,
|
|
18071
|
+
satisfied: lastRun.acceptance.satisfied,
|
|
18072
|
+
pending: lastRun.acceptance.pending,
|
|
18073
|
+
blocked: lastRun.acceptance.blocked
|
|
18074
|
+
} } : {}
|
|
18075
|
+
}
|
|
18076
|
+
} : {},
|
|
18077
|
+
failures
|
|
18078
|
+
};
|
|
18079
|
+
}
|
|
18080
|
+
function reviewRequest(scope) {
|
|
18081
|
+
if (scope.kind === "working-tree") return "Review the current working tree changes.";
|
|
18082
|
+
if (scope.kind === "commit") return `Review commit ${scope.ref}.`;
|
|
18083
|
+
return `Review the current branch against ${scope.ref}.`;
|
|
18084
|
+
}
|
|
18085
|
+
function reviewTurnInstructions(bundle) {
|
|
18086
|
+
return `A read-only review is active. Keep the review scope fixed to the bundle below. Do not mutate files, run write-capable tools, or widen the scope. Report actionable findings first with file and line evidence. Never reproduce credentials or secret values. The bundle is deliberately content-free and redacted.
|
|
18087
|
+
|
|
18088
|
+
<redacted-review-bundle>
|
|
18089
|
+
${JSON.stringify(bundle, null, 2)}
|
|
18090
|
+
</redacted-review-bundle>`;
|
|
18091
|
+
}
|
|
18092
|
+
function failureReceipt2(value) {
|
|
18093
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
18094
|
+
const record = value;
|
|
18095
|
+
return {
|
|
18096
|
+
...isToolFailureClass(record.class) ? { class: record.class } : {},
|
|
18097
|
+
...typeof record.retryable === "boolean" ? { retryable: record.retryable } : {},
|
|
18098
|
+
...typeof record.circuitOpen === "boolean" ? { circuitOpen: record.circuitOpen } : {}
|
|
18099
|
+
};
|
|
18100
|
+
}
|
|
18101
|
+
function isToolFailureClass(value) {
|
|
18102
|
+
return typeof value === "string" && failureClasses.has(value);
|
|
18103
|
+
}
|
|
18104
|
+
|
|
17365
18105
|
// src/ui/timeline-reducers.ts
|
|
17366
18106
|
var itemCounter = 0;
|
|
17367
18107
|
var nextId = () => `ui-${Date.now()}-${itemCounter++}`;
|
|
@@ -17565,7 +18305,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17565
18305
|
const colorEnabled = config.ui.color && !process.env.NO_COLOR;
|
|
17566
18306
|
const [theme, setTheme] = useState2(() => resolveThemeWithColor(config.ui.theme, colorEnabled));
|
|
17567
18307
|
const [themeCatalogRevision, setThemeCatalogRevision] = useState2(0);
|
|
17568
|
-
const [
|
|
18308
|
+
const [compact3, setCompact] = useState2(config.ui.compact);
|
|
17569
18309
|
const [interactionMode, setInteractionMode] = useState2(planMode ? "plan" : askMode ? "ask" : "build");
|
|
17570
18310
|
const [input2, setInput] = useState2("");
|
|
17571
18311
|
const [busy, setBusy] = useState2(false);
|
|
@@ -17602,6 +18342,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17602
18342
|
const controller = useRef2(void 0);
|
|
17603
18343
|
const processing = useRef2(false);
|
|
17604
18344
|
const queued = useRef2([]);
|
|
18345
|
+
const clarificationBacklog = useRef2([]);
|
|
17605
18346
|
const stopRequested = useRef2(false);
|
|
17606
18347
|
const startedInitial = useRef2(false);
|
|
17607
18348
|
const lastSubmitted = useRef2(void 0);
|
|
@@ -17704,8 +18445,13 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17704
18445
|
const timer = setInterval(() => setFrameIndex((value) => (value + 1) % spinnerFrames().length), 120);
|
|
17705
18446
|
return () => clearInterval(timer);
|
|
17706
18447
|
}, [busy]);
|
|
17707
|
-
const requestPermission = useCallback((call, category) => {
|
|
17708
|
-
return new Promise((resolve26) => setPermission({
|
|
18448
|
+
const requestPermission = useCallback((call, category, reason) => {
|
|
18449
|
+
return new Promise((resolve26) => setPermission({
|
|
18450
|
+
call,
|
|
18451
|
+
category,
|
|
18452
|
+
...reason ? { reason } : {},
|
|
18453
|
+
resolve: resolve26
|
|
18454
|
+
}));
|
|
17709
18455
|
}, []);
|
|
17710
18456
|
const onEvent = useCallback((event) => {
|
|
17711
18457
|
switch (event.type) {
|
|
@@ -17725,7 +18471,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17725
18471
|
...event.packed.budgetReason ? { budgetReason: event.packed.budgetReason } : {},
|
|
17726
18472
|
truncated: event.packed.truncated,
|
|
17727
18473
|
spans: event.packed.hits.slice(0, 5).map((hit) => ({
|
|
17728
|
-
path:
|
|
18474
|
+
path: relative10(runner.workspace.primaryRoot, hit.path) || hit.path,
|
|
17729
18475
|
startLine: hit.startLine,
|
|
17730
18476
|
endLine: hit.endLine,
|
|
17731
18477
|
score: hit.score,
|
|
@@ -17829,7 +18575,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17829
18575
|
id: nextId(),
|
|
17830
18576
|
kind: "notice",
|
|
17831
18577
|
tone: event.status === "ready" || event.status === "integrated" ? "success" : "error",
|
|
17832
|
-
text: `Writer ${event.id.slice(0, 8)} ${event.status}${separator}${event.detail}`
|
|
18578
|
+
text: `Writer ${event.id.slice(0, 8)} ${event.status}${separator}${event.detail}${event.status === "conflict" || event.status === "failed" || event.status === "cancelled" ? `${separator}Run /recover before retrying or restoring.` : ""}`
|
|
17833
18579
|
});
|
|
17834
18580
|
break;
|
|
17835
18581
|
case "agent_done":
|
|
@@ -17842,13 +18588,33 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17842
18588
|
append({ id: nextId(), kind: "compaction", messages: event.omittedMessages, tokens: event.summaryTokens });
|
|
17843
18589
|
refreshSession();
|
|
17844
18590
|
break;
|
|
18591
|
+
case "context_epoch":
|
|
18592
|
+
append({
|
|
18593
|
+
id: nextId(),
|
|
18594
|
+
kind: "notice",
|
|
18595
|
+
tone: "info",
|
|
18596
|
+
text: `Context epoch ${event.previousIndex} \u2192 ${event.index}${separator}${event.reason.replace("_", " ")}${separator}${event.inputTokens + event.outputTokens} tokens preserved in the lifetime ledger.`
|
|
18597
|
+
});
|
|
18598
|
+
refreshSession();
|
|
18599
|
+
break;
|
|
18600
|
+
case "needs_input":
|
|
18601
|
+
append({ id: nextId(), kind: "clarification", pending: event.pending });
|
|
18602
|
+
refreshSession();
|
|
18603
|
+
break;
|
|
18604
|
+
case "input_resolved":
|
|
18605
|
+
append({ id: nextId(), kind: "notice", tone: "success", text: `Clarification resolved${separator}${event.answer}` });
|
|
18606
|
+
refreshSession();
|
|
18607
|
+
break;
|
|
18608
|
+
case "intent":
|
|
18609
|
+
refreshSession();
|
|
18610
|
+
break;
|
|
17845
18611
|
case "usage":
|
|
17846
18612
|
refreshSession();
|
|
17847
18613
|
break;
|
|
17848
18614
|
case "error":
|
|
17849
18615
|
lastEventError.current = event.error.message;
|
|
17850
18616
|
setTimeline(endStreamingAssistants);
|
|
17851
|
-
append({ id: nextId(), kind: "notice", tone: "error", text: event.error.message });
|
|
18617
|
+
append({ id: nextId(), kind: "notice", tone: "error", text: `${event.error.message}${separator}Run /recover to inspect evidence, changes, and safe next actions.` });
|
|
17852
18618
|
setActivity(void 0);
|
|
17853
18619
|
break;
|
|
17854
18620
|
case "done":
|
|
@@ -17872,7 +18638,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17872
18638
|
id: nextId(),
|
|
17873
18639
|
kind: "notice",
|
|
17874
18640
|
tone: event.reason === "aborted" ? "info" : "error",
|
|
17875
|
-
text: event.reason === "aborted" ? "Run interrupted." : event.reason === "max_turns" ? "Stopped at the configured turn limit." : event.reason === "token_budget" ? "Stopped at the configured token budget." : event.reason
|
|
18641
|
+
text: event.reason === "aborted" ? "Run interrupted. Use /recover to inspect changes or resume safely." : event.reason === "max_turns" ? "Stopped at the configured turn limit. Use /recover resume after adjusting the limit." : event.reason === "token_budget" ? "Stopped at the configured token budget. Use /recover to inspect state before resuming with a larger budget." : event.reason
|
|
17876
18642
|
});
|
|
17877
18643
|
}
|
|
17878
18644
|
break;
|
|
@@ -17886,9 +18652,16 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17886
18652
|
const runLocalCommand = useCallback(async (value) => {
|
|
17887
18653
|
if (!value.startsWith("/")) return false;
|
|
17888
18654
|
const [rawCommand = "", ...rest] = value.slice(1).trim().split(/\s+/);
|
|
17889
|
-
|
|
17890
|
-
|
|
18655
|
+
let command2 = rawCommand.toLocaleLowerCase();
|
|
18656
|
+
let argument = rest.join(" ").trim();
|
|
17891
18657
|
if (!command2) return true;
|
|
18658
|
+
if (command2 === "recover") {
|
|
18659
|
+
const [action = "", ...actionRest] = argument.split(/\s+/u);
|
|
18660
|
+
if (action === "diff" || action === "audit" || action === "rollback") {
|
|
18661
|
+
command2 = action;
|
|
18662
|
+
argument = actionRest.join(" ").trim();
|
|
18663
|
+
}
|
|
18664
|
+
}
|
|
17892
18665
|
if (command2 === "exit" || command2 === "quit") {
|
|
17893
18666
|
exit();
|
|
17894
18667
|
return true;
|
|
@@ -17970,10 +18743,77 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
17970
18743
|
});
|
|
17971
18744
|
return true;
|
|
17972
18745
|
}
|
|
18746
|
+
if (command2 === "review") {
|
|
18747
|
+
const scope = parseReviewScope(argument);
|
|
18748
|
+
const bundle = buildRedactedReviewBundle(runner.getSession(), runner.workspace.primaryRoot, scope);
|
|
18749
|
+
return {
|
|
18750
|
+
kind: "agent",
|
|
18751
|
+
display: `/review ${scope.kind}${scope.kind === "working-tree" ? "" : ` ${scope.ref}`}`,
|
|
18752
|
+
runInput: reviewRequest(scope),
|
|
18753
|
+
turnInstructions: reviewTurnInstructions(bundle),
|
|
18754
|
+
readOnly: true
|
|
18755
|
+
};
|
|
18756
|
+
}
|
|
18757
|
+
if (command2 === "recover") {
|
|
18758
|
+
const action = argument.toLocaleLowerCase();
|
|
18759
|
+
const currentSession = runner.getSession();
|
|
18760
|
+
const lastRun = currentSession.lastRun;
|
|
18761
|
+
const lastFailure = (currentSession.audit ?? []).findLast((event) => event.type === "tool" && event.outcome === "failure");
|
|
18762
|
+
const failure = recoveryFailure(lastFailure?.metadata?.failure);
|
|
18763
|
+
if (action === "retry") {
|
|
18764
|
+
if (currentSession.pendingInput) throw new Error("Answer the pending clarification before retrying an operation.");
|
|
18765
|
+
if (!lastFailure) throw new Error("No failed operation is available to retry.");
|
|
18766
|
+
return {
|
|
18767
|
+
kind: "agent",
|
|
18768
|
+
display: "/recover retry",
|
|
18769
|
+
runInput: `Retry the most recent failed ${lastFailure.tool} operation after inspecting the current workspace state.`,
|
|
18770
|
+
turnInstructions: "Resume the existing session. Use the recorded failure receipt and current files as authority. Apply its repair hint before one targeted retry; do not replay a circuit-open or non-retryable operation unchanged."
|
|
18771
|
+
};
|
|
18772
|
+
}
|
|
18773
|
+
if (action === "resume") {
|
|
18774
|
+
if (currentSession.pendingInput) throw new Error("Answer the pending clarification in the composer to resume the same logical run.");
|
|
18775
|
+
if (!lastRun || lastRun.reason === "completed") throw new Error("The latest run is already complete.");
|
|
18776
|
+
return {
|
|
18777
|
+
kind: "agent",
|
|
18778
|
+
display: "/recover resume",
|
|
18779
|
+
runInput: "Resume the most recent incomplete run from its persisted state.",
|
|
18780
|
+
turnInstructions: "Resume the existing session from its Task Contract, changed-file set, last-run receipt, and unresolved failures. Inspect current state before acting, keep prior successful evidence, and run only missing verification."
|
|
18781
|
+
};
|
|
18782
|
+
}
|
|
18783
|
+
if (action) throw new Error("Usage: /recover [retry|resume|diff|rollback|audit]");
|
|
18784
|
+
const checkpoints = await runner.checkpointStore.list(currentSession.id);
|
|
18785
|
+
const latestCheckpoint = checkpoints[0];
|
|
18786
|
+
appendList("Recovery Center", [
|
|
18787
|
+
{
|
|
18788
|
+
label: `Last run ${lastRun?.status ?? "none"}${lastRun ? `${separator}${lastRun.reason}` : ""}`,
|
|
18789
|
+
detail: lastRun?.detail ?? "No completed or interrupted run has been recorded.",
|
|
18790
|
+
tone: lastRun?.status === "verified" || lastRun?.status === "no_changes" ? "success" : lastRun ? "warning" : "normal"
|
|
18791
|
+
},
|
|
18792
|
+
...lastFailure ? [{
|
|
18793
|
+
label: `Failure ${lastFailure.tool}${failure?.class ? `${separator}${failure.class}` : ""}`,
|
|
18794
|
+
detail: failure?.repairHint ?? "Inspect the audit timeline before retrying.",
|
|
18795
|
+
tone: "error"
|
|
18796
|
+
}] : [],
|
|
18797
|
+
{
|
|
18798
|
+
label: `Workspace ${currentSession.changedFiles.length} changed file${currentSession.changedFiles.length === 1 ? "" : "s"}`,
|
|
18799
|
+
detail: currentSession.changedFiles.length ? "/diff inspects the current patch." : "No tracked session changes."
|
|
18800
|
+
},
|
|
18801
|
+
{
|
|
18802
|
+
label: `Checkpoint ${latestCheckpoint ? latestCheckpoint.id.slice(0, 12) : "none"}`,
|
|
18803
|
+
detail: latestCheckpoint ? `${latestCheckpoint.reason}${separator}${latestCheckpoint.entries.length} files${separator}/recover rollback` : "No pre-mutation snapshot is available for this session."
|
|
18804
|
+
},
|
|
18805
|
+
{ label: "/recover retry", detail: lastFailure ? "Apply the repair hint, then retry the latest failure once." : "Unavailable until an operation fails." },
|
|
18806
|
+
{ label: "/recover resume", detail: currentSession.pendingInput ? "Unavailable: answer the pending clarification in the composer." : lastRun && lastRun.reason !== "completed" ? "Continue the incomplete logical run." : "No incomplete run to resume." },
|
|
18807
|
+
{ label: "/recover diff", detail: "Inspect the current workspace patch." },
|
|
18808
|
+
{ label: "/recover audit", detail: "Review permission and tool evidence." },
|
|
18809
|
+
{ label: "/recover rollback", detail: "Choose a checkpoint to restore." }
|
|
18810
|
+
]);
|
|
18811
|
+
return true;
|
|
18812
|
+
}
|
|
17973
18813
|
if (command2 === "changes") {
|
|
17974
18814
|
const changed = runner.getSession().changedFiles;
|
|
17975
18815
|
appendList("Changed files", changed.length ? changed.map((path) => ({
|
|
17976
|
-
label:
|
|
18816
|
+
label: relative10(runner.workspace.primaryRoot, path) || ".",
|
|
17977
18817
|
detail: path
|
|
17978
18818
|
})) : [{ label: "No recorded changes." }]);
|
|
17979
18819
|
return true;
|
|
@@ -18126,7 +18966,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18126
18966
|
const skills = extensions?.listSkills() ?? [];
|
|
18127
18967
|
appendList("Skills", skills.length ? skills.map((skill) => ({
|
|
18128
18968
|
label: `${skill.name} ${skill.scope}${skill.trusted ? "" : `${separator}untrusted`}`,
|
|
18129
|
-
detail: `${skill.description}${separator}${
|
|
18969
|
+
detail: `${skill.description}${separator}${relative10(runner.workspace.primaryRoot, skill.path) || skill.path}`,
|
|
18130
18970
|
tone: skill.trusted ? "normal" : "warning"
|
|
18131
18971
|
})) : [{ label: "No skills discovered.", detail: "Add SKILL.md playbooks under .agents/skills, .claude/skills, or a configured directory." }]);
|
|
18132
18972
|
return true;
|
|
@@ -18274,7 +19114,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18274
19114
|
}
|
|
18275
19115
|
if (command2 === "density") {
|
|
18276
19116
|
const normalized = argument.toLocaleLowerCase();
|
|
18277
|
-
const next = normalized === "compact" ? true : normalized === "comfortable" || normalized === "normal" ? false : normalized === "" || normalized === "toggle" ? !
|
|
19117
|
+
const next = normalized === "compact" ? true : normalized === "comfortable" || normalized === "normal" ? false : normalized === "" || normalized === "toggle" ? !compact3 : void 0;
|
|
18278
19118
|
if (next === void 0) throw new Error("Usage: /density [compact|comfortable]");
|
|
18279
19119
|
setCompact(next);
|
|
18280
19120
|
await saveUiPreference({ compact: next });
|
|
@@ -18367,7 +19207,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18367
19207
|
})));
|
|
18368
19208
|
return true;
|
|
18369
19209
|
}
|
|
18370
|
-
}, [append, appendList,
|
|
19210
|
+
}, [append, appendList, compact3, config, ellipsis, exit, extensions, interactionMode, refreshSession, requestPermission, runner, separator, showToolOutput, tasks, theme, workflows]);
|
|
18371
19211
|
const submit = useCallback(async (raw, mode = "normal") => {
|
|
18372
19212
|
const trimmed = raw.trim();
|
|
18373
19213
|
if (!trimmed) return;
|
|
@@ -18382,6 +19222,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18382
19222
|
if (processing.current && isExitCommand(trimmed)) {
|
|
18383
19223
|
stopRequested.current = true;
|
|
18384
19224
|
queued.current = [];
|
|
19225
|
+
clarificationBacklog.current = [];
|
|
18385
19226
|
setQueue([]);
|
|
18386
19227
|
controller.current?.abort();
|
|
18387
19228
|
exit();
|
|
@@ -18455,7 +19296,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18455
19296
|
lastEventError.current = void 0;
|
|
18456
19297
|
try {
|
|
18457
19298
|
const nextSession = await runner.run(current.runInput, {
|
|
18458
|
-
askMode: interactionMode !== "build",
|
|
19299
|
+
askMode: current.readOnly === true || interactionMode !== "build",
|
|
18459
19300
|
signal: abortController.signal,
|
|
18460
19301
|
...current.turnInstructions || interactionMode === "plan" ? {
|
|
18461
19302
|
turnInstructions: [
|
|
@@ -18469,6 +19310,31 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18469
19310
|
const snapshot = snapshotSession(nextSession);
|
|
18470
19311
|
setSession(snapshot);
|
|
18471
19312
|
setTasks(snapshot.tasks.map((task) => ({ ...task })));
|
|
19313
|
+
if (snapshot.pendingInput) {
|
|
19314
|
+
const deferred = queued.current.length;
|
|
19315
|
+
clarificationBacklog.current.push(...queued.current);
|
|
19316
|
+
queued.current = [];
|
|
19317
|
+
setQueue([]);
|
|
19318
|
+
if (deferred) append({
|
|
19319
|
+
id: nextId(),
|
|
19320
|
+
kind: "notice",
|
|
19321
|
+
tone: "info",
|
|
19322
|
+
text: `Paused ${deferred} queued follow-up${deferred === 1 ? "" : "s"} for clarification; they will resume after the answer.`
|
|
19323
|
+
});
|
|
19324
|
+
break;
|
|
19325
|
+
}
|
|
19326
|
+
if (clarificationBacklog.current.length) {
|
|
19327
|
+
const resumed = clarificationBacklog.current.length;
|
|
19328
|
+
queued.current = [...clarificationBacklog.current, ...queued.current];
|
|
19329
|
+
clarificationBacklog.current = [];
|
|
19330
|
+
setQueue([...queued.current]);
|
|
19331
|
+
append({
|
|
19332
|
+
id: nextId(),
|
|
19333
|
+
kind: "notice",
|
|
19334
|
+
tone: "success",
|
|
19335
|
+
text: `Clarification resolved; resuming ${resumed} queued follow-up${resumed === 1 ? "" : "s"}.`
|
|
19336
|
+
});
|
|
19337
|
+
}
|
|
18472
19338
|
} catch (error) {
|
|
18473
19339
|
const message2 = error instanceof Error ? error.message : String(error);
|
|
18474
19340
|
if (!abortController.signal.aborted && message2 !== lastEventError.current) {
|
|
@@ -18481,8 +19347,9 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18481
19347
|
}
|
|
18482
19348
|
}
|
|
18483
19349
|
if (abortController.signal.aborted || stopRequested.current) {
|
|
18484
|
-
const discarded = queued.current.length;
|
|
19350
|
+
const discarded = queued.current.length + clarificationBacklog.current.length;
|
|
18485
19351
|
queued.current = [];
|
|
19352
|
+
clarificationBacklog.current = [];
|
|
18486
19353
|
setQueue([]);
|
|
18487
19354
|
if (discarded) append({ id: nextId(), kind: "notice", text: `Discarded ${discarded} queued follow-up${discarded === 1 ? "" : "s"}.` });
|
|
18488
19355
|
break;
|
|
@@ -18499,10 +19366,10 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18499
19366
|
}, [append, exit, interactionMode, onEvent, requestPermission, runLocalCommand, runner]);
|
|
18500
19367
|
const submitFromComposer = useCallback((raw, mode = "normal") => {
|
|
18501
19368
|
if (historySearch) {
|
|
18502
|
-
const
|
|
19369
|
+
const selected2 = resolveHistorySearch(historySearch, "select");
|
|
18503
19370
|
setHistorySearch(void 0);
|
|
18504
19371
|
setHistoryIndex(-1);
|
|
18505
|
-
void submit(
|
|
19372
|
+
void submit(selected2, mode === "normal" && processing.current ? "steer" : mode);
|
|
18506
19373
|
return;
|
|
18507
19374
|
}
|
|
18508
19375
|
if (suggestionMode === "mention" && selectedSuggestion) {
|
|
@@ -18513,7 +19380,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18513
19380
|
return;
|
|
18514
19381
|
}
|
|
18515
19382
|
}
|
|
18516
|
-
const
|
|
19383
|
+
const selected = selectedSuggestion;
|
|
19384
|
+
const suggestion = selected && !(raw.startsWith(selected.value) && raw.slice(selected.value.length).trim()) ? selected : void 0;
|
|
18517
19385
|
const normalized = raw.trimEnd();
|
|
18518
19386
|
if (suggestion && raw.startsWith("/") && suggestion.value !== raw && suggestion.value.endsWith(" ") && suggestion.label !== normalized) {
|
|
18519
19387
|
setInput(suggestion.value);
|
|
@@ -18552,6 +19420,14 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18552
19420
|
text: `Allowed ${call.name} for this exact ${category} target during the session.`
|
|
18553
19421
|
});
|
|
18554
19422
|
}
|
|
19423
|
+
if (grant === false) {
|
|
19424
|
+
append({
|
|
19425
|
+
id: nextId(),
|
|
19426
|
+
kind: "notice",
|
|
19427
|
+
tone: "info",
|
|
19428
|
+
text: `Denied ${call.name}; the requested ${category} action was not run. Use /permissions to inspect policy or /recover to review the run.`
|
|
19429
|
+
});
|
|
19430
|
+
}
|
|
18555
19431
|
if (stop) {
|
|
18556
19432
|
requestRunStop();
|
|
18557
19433
|
}
|
|
@@ -18731,7 +19607,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18731
19607
|
const tokenTotal = session.usage.inputTokens + session.usage.outputTokens;
|
|
18732
19608
|
const contextStatus = runner.getContextStatus();
|
|
18733
19609
|
const frame = spinnerFrames()[frameIndex % spinnerFrames().length];
|
|
18734
|
-
const compactUi =
|
|
19610
|
+
const compactUi = compact3 || terminalHeight < 28;
|
|
18735
19611
|
const constrainedHeight = terminalHeight < 18;
|
|
18736
19612
|
const compactComposer = terminalHeight < 18;
|
|
18737
19613
|
const minimalInspector = terminalHeight < 22;
|
|
@@ -18895,7 +19771,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18895
19771
|
)
|
|
18896
19772
|
}
|
|
18897
19773
|
)
|
|
18898
|
-
] }) : /* @__PURE__ */ jsx3(PermissionCard, { call: permission.call, category: permission.category, workspace: runner.workspace.primaryRoot, width: contentWidth, glyphMode, compact: constrainedHeight }),
|
|
19774
|
+
] }) : /* @__PURE__ */ jsx3(PermissionCard, { call: permission.call, category: permission.category, ...permission.reason ? { reason: permission.reason } : {}, workspace: runner.workspace.primaryRoot, width: contentWidth, glyphMode, compact: constrainedHeight }),
|
|
18899
19775
|
showFooter ? /* @__PURE__ */ jsx3(
|
|
18900
19776
|
Footer,
|
|
18901
19777
|
{
|
|
@@ -18947,6 +19823,9 @@ function initialTimeline(session, banner, setupProblem) {
|
|
|
18947
19823
|
text: `Contract ${session.taskContract.state} | ${satisfied}/${required.length} accepted`
|
|
18948
19824
|
});
|
|
18949
19825
|
}
|
|
19826
|
+
if (session.pendingInput) {
|
|
19827
|
+
items.push({ id: `pending-input-${session.pendingInput.id}`, kind: "clarification", pending: session.pendingInput });
|
|
19828
|
+
}
|
|
18950
19829
|
if (setupProblem && items.length <= 1) items.push({ id: nextId(), kind: "notice", tone: "error", text: setupProblem });
|
|
18951
19830
|
return items;
|
|
18952
19831
|
}
|
|
@@ -18992,6 +19871,35 @@ function snapshotSession(source) {
|
|
|
18992
19871
|
relevantFiles: [...source.workingMemory.relevantFiles]
|
|
18993
19872
|
}
|
|
18994
19873
|
} : {},
|
|
19874
|
+
...source.contextEpochs ? {
|
|
19875
|
+
contextEpochs: source.contextEpochs.map((epoch) => ({
|
|
19876
|
+
...epoch,
|
|
19877
|
+
usage: { ...epoch.usage },
|
|
19878
|
+
...epoch.handoff ? {
|
|
19879
|
+
handoff: {
|
|
19880
|
+
...epoch.handoff,
|
|
19881
|
+
...epoch.handoff.contract ? {
|
|
19882
|
+
contract: {
|
|
19883
|
+
...epoch.handoff.contract,
|
|
19884
|
+
required: epoch.handoff.contract.required.map((criterion2) => ({
|
|
19885
|
+
...criterion2,
|
|
19886
|
+
evidenceRefs: [...criterion2.evidenceRefs]
|
|
19887
|
+
}))
|
|
19888
|
+
}
|
|
19889
|
+
} : {},
|
|
19890
|
+
unresolvedFailures: epoch.handoff.unresolvedFailures.map((failure) => ({ ...failure })),
|
|
19891
|
+
changedFiles: [...epoch.handoff.changedFiles],
|
|
19892
|
+
checks: epoch.handoff.checks.map((check) => ({ ...check }))
|
|
19893
|
+
}
|
|
19894
|
+
} : {}
|
|
19895
|
+
}))
|
|
19896
|
+
} : {},
|
|
19897
|
+
...source.intentAssessment ? {
|
|
19898
|
+
intentAssessment: { ...source.intentAssessment, reasons: [...source.intentAssessment.reasons] }
|
|
19899
|
+
} : {},
|
|
19900
|
+
...source.pendingInput ? {
|
|
19901
|
+
pendingInput: { ...source.pendingInput, options: source.pendingInput.options.map((option2) => ({ ...option2 })) }
|
|
19902
|
+
} : {},
|
|
18995
19903
|
usage: { ...source.usage }
|
|
18996
19904
|
};
|
|
18997
19905
|
}
|
|
@@ -19003,6 +19911,14 @@ function cloneValue(value) {
|
|
|
19003
19911
|
if (value && typeof value === "object") return cloneRecord(value);
|
|
19004
19912
|
return value;
|
|
19005
19913
|
}
|
|
19914
|
+
function recoveryFailure(value) {
|
|
19915
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
19916
|
+
const receipt = value;
|
|
19917
|
+
return {
|
|
19918
|
+
...typeof receipt.class === "string" ? { class: sanitizeTerminalText(receipt.class).slice(0, 40) } : {},
|
|
19919
|
+
...typeof receipt.repairHint === "string" ? { repairHint: sanitizeTerminalText(receipt.repairHint).replace(/\s+/gu, " ").slice(0, 240) } : {}
|
|
19920
|
+
};
|
|
19921
|
+
}
|
|
19006
19922
|
function isExitCommand(value) {
|
|
19007
19923
|
const command2 = localCommandName(value);
|
|
19008
19924
|
return command2 === "exit" || command2 === "quit";
|
|
@@ -19020,6 +19936,9 @@ function shouldDeferLocalCommand(value) {
|
|
|
19020
19936
|
"remember",
|
|
19021
19937
|
"diff",
|
|
19022
19938
|
"checkpoints",
|
|
19939
|
+
"audit",
|
|
19940
|
+
"rollback",
|
|
19941
|
+
"recover",
|
|
19023
19942
|
"workflow",
|
|
19024
19943
|
"exit",
|
|
19025
19944
|
"quit"
|
|
@@ -19038,17 +19957,17 @@ function composerAttachments(value) {
|
|
|
19038
19957
|
const paths = [...value.matchAll(/(?:^|\s)@([^\s]+)/g)].map((match) => match[1]).filter((path) => Boolean(path));
|
|
19039
19958
|
return [...new Set(paths)].slice(-3);
|
|
19040
19959
|
}
|
|
19041
|
-
function permissionRows(width, hasCwd,
|
|
19042
|
-
const content =
|
|
19960
|
+
function permissionRows(width, hasCwd, compact3) {
|
|
19961
|
+
const content = 5 + (hasCwd ? 1 : 0);
|
|
19043
19962
|
if (width >= 64) return content + 2;
|
|
19044
19963
|
if (width >= 28) return content + 3;
|
|
19045
|
-
if (
|
|
19964
|
+
if (compact3) return content + 3;
|
|
19046
19965
|
return content + 5;
|
|
19047
19966
|
}
|
|
19048
|
-
function contextInspectorRows(session,
|
|
19967
|
+
function contextInspectorRows(session, compact3, width, minimal) {
|
|
19049
19968
|
if (minimal) return 2;
|
|
19050
19969
|
const working = session.workingMemory;
|
|
19051
|
-
const entries =
|
|
19970
|
+
const entries = 6 + (compact3 ? 0 : (working?.constraints.length ? 1 : 0) + (working?.decisions.length ? 1 : 0) + (working?.openQuestions.length ? 1 : 0) + (working?.relevantFiles.length ? 1 : 0));
|
|
19052
19971
|
return 2 + entries * (width < 52 ? 2 : 1);
|
|
19053
19972
|
}
|
|
19054
19973
|
function contextInspectorStatus(status) {
|
|
@@ -19058,7 +19977,13 @@ function contextInspectorStatus(status) {
|
|
|
19058
19977
|
activeTokens: status.activeTokens,
|
|
19059
19978
|
summaryTokens: status.summaryTokens,
|
|
19060
19979
|
toolTokens: status.toolTokens,
|
|
19061
|
-
compactedMessages: status.compactedMessages
|
|
19980
|
+
compactedMessages: status.compactedMessages,
|
|
19981
|
+
epochIndex: status.epochIndex,
|
|
19982
|
+
epochCount: status.epochCount,
|
|
19983
|
+
epochTokens: status.epochTokens,
|
|
19984
|
+
epochBudget: status.epochBudget,
|
|
19985
|
+
lifetimeTokens: status.lifetimeTokens,
|
|
19986
|
+
lifetimeBudget: status.lifetimeBudget
|
|
19062
19987
|
};
|
|
19063
19988
|
}
|
|
19064
19989
|
function toolDetail2(call) {
|
|
@@ -19100,14 +20025,14 @@ function WorkspacePreparationView({
|
|
|
19100
20025
|
const theme = useTheme();
|
|
19101
20026
|
const safeWidth2 = Math.max(1, Math.floor(width));
|
|
19102
20027
|
const innerWidth = Math.max(1, safeWidth2 - (safeWidth2 >= 24 ? 4 : 0));
|
|
19103
|
-
const
|
|
20028
|
+
const compact3 = safeWidth2 < 48;
|
|
19104
20029
|
const constrained = height < 14;
|
|
19105
20030
|
const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
|
|
19106
20031
|
const spinner = (ascii ? [".", "o", "O", "o"] : ["\u25DC", "\u25E0", "\u25DD", "\u25DE", "\u25E1", "\u25DF"])[frame % (ascii ? 4 : 6)];
|
|
19107
20032
|
const separator = ascii ? "|" : "\xB7";
|
|
19108
20033
|
const brand = ascii ? "*" : PRODUCT_MARK;
|
|
19109
20034
|
const phase = readiness ? "ready" : error ? "error" : progress.phase;
|
|
19110
|
-
const phaseLabel = preparationLabel(phase, progress, readiness,
|
|
20035
|
+
const phaseLabel = preparationLabel(phase, progress, readiness, compact3);
|
|
19111
20036
|
const detail = preparationDetail(phase, progress, readiness, error);
|
|
19112
20037
|
const modelLine = `model ${sanitizeTerminalText(model)}`;
|
|
19113
20038
|
const workspaceLine = `workspace ${compactDisplayPath(sanitizeTerminalText(workspace), Math.max(1, innerWidth - 10))}`;
|
|
@@ -19119,14 +20044,14 @@ function WorkspacePreparationView({
|
|
|
19119
20044
|
" ",
|
|
19120
20045
|
PRODUCT_NAME.toUpperCase()
|
|
19121
20046
|
] }),
|
|
19122
|
-
!
|
|
20047
|
+
!compact3 && !constrained ? /* @__PURE__ */ jsxs4(Text4, { color: theme.dim, children: [
|
|
19123
20048
|
" ",
|
|
19124
20049
|
separator,
|
|
19125
20050
|
" LOCAL CONTEXT"
|
|
19126
20051
|
] }) : null
|
|
19127
20052
|
] }),
|
|
19128
20053
|
!constrained ? /* @__PURE__ */ jsx4(Text4, { color: theme.muted, children: truncateDisplay("Ground the workspace before the first request.", innerWidth) }) : null,
|
|
19129
|
-
!constrained && !
|
|
20054
|
+
!constrained && !compact3 ? /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(`${modelLine} ${separator} ${workspaceLine}`, innerWidth) }) : !constrained ? /* @__PURE__ */ jsxs4(Fragment3, { children: [
|
|
19130
20055
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(modelLine, innerWidth) }),
|
|
19131
20056
|
/* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) })
|
|
19132
20057
|
] }) : /* @__PURE__ */ jsx4(Text4, { color: theme.dim, children: truncateDisplay(workspaceLine, innerWidth) }),
|
|
@@ -19140,10 +20065,10 @@ function WorkspacePreparationView({
|
|
|
19140
20065
|
step2.glyph,
|
|
19141
20066
|
" "
|
|
19142
20067
|
] }),
|
|
19143
|
-
/* @__PURE__ */ jsx4(Text4, { bold: step2.state === "active", color: step2.state === "pending" ? theme.dim : theme.textStrong, children: truncateDisplay(step2.label,
|
|
20068
|
+
/* @__PURE__ */ jsx4(Text4, { bold: step2.state === "active", color: step2.state === "pending" ? theme.dim : theme.textStrong, children: truncateDisplay(step2.label, compact3 ? 8 : 10) }),
|
|
19144
20069
|
/* @__PURE__ */ jsxs4(Text4, { color: step2.state === "active" ? theme.muted : theme.dim, children: [
|
|
19145
20070
|
" ",
|
|
19146
|
-
truncateDisplay(step2.detail, Math.max(1, innerWidth - displayWidth(step2.glyph) - (
|
|
20071
|
+
truncateDisplay(step2.detail, Math.max(1, innerWidth - displayWidth(step2.glyph) - (compact3 ? 12 : 14)))
|
|
19147
20072
|
] })
|
|
19148
20073
|
] }, step2.id)) }),
|
|
19149
20074
|
/* @__PURE__ */ jsxs4(Box3, { marginTop: 1, children: [
|
|
@@ -19284,9 +20209,9 @@ async function runWorkspacePreparation(engine, config, options = {}) {
|
|
|
19284
20209
|
await instance.waitUntilExit();
|
|
19285
20210
|
return result ?? { status: "cancelled" };
|
|
19286
20211
|
}
|
|
19287
|
-
function preparationLabel(phase, progress, readiness,
|
|
20212
|
+
function preparationLabel(phase, progress, readiness, compact3 = false) {
|
|
19288
20213
|
if (phase === "ready") {
|
|
19289
|
-
if (
|
|
20214
|
+
if (compact3) return "Index verified";
|
|
19290
20215
|
return readiness?.rebuilt ? "Workspace index created and verified" : "Workspace index verified";
|
|
19291
20216
|
}
|
|
19292
20217
|
if (phase === "error") return "Workspace preparation failed";
|
|
@@ -19690,7 +20615,7 @@ function OnboardingFlow({ initialConfig, saveConfig, onFinish }) {
|
|
|
19690
20615
|
}, [finish, saveConfig, state]);
|
|
19691
20616
|
return /* @__PURE__ */ jsx5(OnboardingScreen, { state, dispatch, width, compact: compactHeight });
|
|
19692
20617
|
}
|
|
19693
|
-
function OnboardingScreen({ state, dispatch, width, compact:
|
|
20618
|
+
function OnboardingScreen({ state, dispatch, width, compact: compact3 = false }) {
|
|
19694
20619
|
const theme = useTheme();
|
|
19695
20620
|
const ascii = process.env.SKEIN_GLYPHS === "ascii" || process.env.MOSAIC_GLYPHS === "ascii";
|
|
19696
20621
|
const marker = ascii ? ">" : "\u203A";
|
|
@@ -19707,14 +20632,14 @@ function OnboardingScreen({ state, dispatch, width, compact: compact2 = false })
|
|
|
19707
20632
|
] }),
|
|
19708
20633
|
/* @__PURE__ */ jsx5(Text6, { color: theme.border, children: truncateDisplay(stageDivider(ascii, headerWidth), headerWidth) }),
|
|
19709
20634
|
/* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(stage.name, headerWidth) }),
|
|
19710
|
-
!
|
|
20635
|
+
!compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
19711
20636
|
/* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: truncateDisplay(titleForStep(state.step), headerWidth) }),
|
|
19712
|
-
!
|
|
20637
|
+
!compact3 ? /* @__PURE__ */ jsx5(Text6, { color: theme.muted, wrap: "wrap", children: descriptionForStep(state) }) : null,
|
|
19713
20638
|
summary ? /* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: truncateDisplay(summary, headerWidth) }) : null,
|
|
19714
|
-
!
|
|
19715
|
-
state.step === "method" ? /* @__PURE__ */ jsx5(OptionList, { options: methods, selected: state.selected, marker, width: headerWidth, compact:
|
|
19716
|
-
state.step === "official-provider" ? /* @__PURE__ */ jsx5(OptionList, { options: officialProviders, selected: state.selected, marker, width: headerWidth, compact:
|
|
19717
|
-
state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols, selected: state.selected, marker, width: headerWidth, compact:
|
|
20639
|
+
!compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
20640
|
+
state.step === "method" ? /* @__PURE__ */ jsx5(OptionList, { options: methods, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
|
|
20641
|
+
state.step === "official-provider" ? /* @__PURE__ */ jsx5(OptionList, { options: officialProviders, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
|
|
20642
|
+
state.step === "relay-protocol" ? /* @__PURE__ */ jsx5(OptionList, { options: relayProtocols, selected: state.selected, marker, width: headerWidth, compact: compact3 }) : null,
|
|
19718
20643
|
inputField ? /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", children: [
|
|
19719
20644
|
/* @__PURE__ */ jsxs5(Box4, { children: [
|
|
19720
20645
|
/* @__PURE__ */ jsx5(Text6, { color: theme.textStrong, bold: true, children: inputField.label }),
|
|
@@ -19742,18 +20667,18 @@ function OnboardingScreen({ state, dispatch, width, compact: compact2 = false })
|
|
|
19742
20667
|
"! ",
|
|
19743
20668
|
truncateDisplay(state.error, Math.max(1, headerWidth - 2))
|
|
19744
20669
|
] }) : null,
|
|
19745
|
-
!
|
|
20670
|
+
!compact3 ? /* @__PURE__ */ jsx5(Box4, { height: 1 }) : null,
|
|
19746
20671
|
/* @__PURE__ */ jsx5(Text6, { color: theme.dim, children: footerForStep(state, headerWidth) })
|
|
19747
20672
|
] });
|
|
19748
20673
|
}
|
|
19749
|
-
function OptionList({ options, selected, marker, width, compact:
|
|
20674
|
+
function OptionList({ options, selected, marker, width, compact: compact3 }) {
|
|
19750
20675
|
const theme = useTheme();
|
|
19751
|
-
return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((
|
|
20676
|
+
return /* @__PURE__ */ jsx5(Box4, { flexDirection: "column", children: options.map((option2, index) => {
|
|
19752
20677
|
const active = index === selected;
|
|
19753
20678
|
const prefix = active ? `${marker} ` : " ";
|
|
19754
20679
|
const available = Math.max(1, width - displayWidth(prefix) - (active ? 2 : 0));
|
|
19755
|
-
const label = `${prefix}${truncateDisplay(
|
|
19756
|
-
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", marginBottom:
|
|
20680
|
+
const label = `${prefix}${truncateDisplay(option2.label, available)}`;
|
|
20681
|
+
return /* @__PURE__ */ jsxs5(Box4, { flexDirection: "column", marginBottom: compact3 || index === options.length - 1 ? 0 : 1, children: [
|
|
19757
20682
|
/* @__PURE__ */ jsx5(
|
|
19758
20683
|
Text6,
|
|
19759
20684
|
{
|
|
@@ -19763,8 +20688,8 @@ function OptionList({ options, selected, marker, width, compact: compact2 }) {
|
|
|
19763
20688
|
children: active ? padDisplay(label, width) : label
|
|
19764
20689
|
}
|
|
19765
20690
|
),
|
|
19766
|
-
!
|
|
19767
|
-
] },
|
|
20691
|
+
!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
|
|
20692
|
+
] }, option2.label);
|
|
19768
20693
|
}) });
|
|
19769
20694
|
}
|
|
19770
20695
|
function Confirmation({ state, width }) {
|
|
@@ -19827,7 +20752,7 @@ function titleForStep(step2) {
|
|
|
19827
20752
|
return "Saving configuration";
|
|
19828
20753
|
}
|
|
19829
20754
|
function descriptionForStep(state) {
|
|
19830
|
-
if (state.step === "method") return "
|
|
20755
|
+
if (state.step === "method") return "Skein's primary agent needs an API credential. OpenAI, Anthropic, and Gemini subscription logins are not API keys; signed-in coding CLIs are separate delegated tools.";
|
|
19831
20756
|
if (state.step === "official-provider") return "Use the API credential issued by the selected provider.";
|
|
19832
20757
|
if (state.step === "relay-protocol") return "The protocol is explicit so requests and credentials are never guessed.";
|
|
19833
20758
|
if (state.step === "endpoint") return "Remote endpoints require HTTPS. Loopback development servers may use HTTP.";
|
|
@@ -21465,7 +22390,7 @@ process.on("warning", (warning) => {
|
|
|
21465
22390
|
var program = new Command();
|
|
21466
22391
|
program.enablePositionalOptions();
|
|
21467
22392
|
program.name(PRODUCT_COMMAND).description("A context-first, model-agnostic coding agent with an auditable workspace.").version(package_default.version).showSuggestionAfterError();
|
|
21468
|
-
program.argument("[prompt...]", "instruction for the agent").option("-p, --print", "run once and print the result").option("-a, --ask", "retrieval and inspection mode; mutation tools are denied").option("--plan", "read-only planning mode; propose changes without mutating the workspace").option("-q, --quiet", "print only the final response in text mode").addOption(new Option("--output-format <format>", "text, json, or stream-json").choices(["text", "json", "stream-json"]).default("text")).option("--compact", "reduce progress output in print mode").option("--yes", "approve all non-denied tool requests for this run").option("--auto-edit", "approve read/write requests and ask before shell/Git/network").option("--trust-project-config", "allow executable and security-sensitive settings from project config").option("--queue <prompt>", "run an additional prompt after the first one", collect, []).option("-w, --workspace <path>", "primary workspace root", process.cwd()).option("--add-workspace <path>", "additional workspace root", collect, []).option("--config <path>", "explicit config file").option("--provider <provider>", "model provider").option("--model <model>", "model identifier").option("--base-url <url>", "OpenAI-compatible or provider base URL").option("--max-turns <n>", "maximum agent turns").option("--token-budget <n>", "maximum
|
|
22393
|
+
program.argument("[prompt...]", "instruction for the agent").option("-p, --print", "run once and print the result").option("-a, --ask", "retrieval and inspection mode; mutation tools are denied").option("--plan", "read-only planning mode; propose changes without mutating the workspace").option("-q, --quiet", "print only the final response in text mode").addOption(new Option("--output-format <format>", "text, json, or stream-json").choices(["text", "json", "stream-json"]).default("text")).option("--compact", "reduce progress output in print mode").option("--yes", "approve all non-denied tool requests for this run").option("--auto-edit", "approve read/write requests and ask before shell/Git/network").option("--trust-project-config", "allow executable and security-sensitive settings from project config").option("--queue <prompt>", "run an additional prompt after the first one", collect, []).option("-w, --workspace <path>", "primary workspace root", process.cwd()).option("--add-workspace <path>", "additional workspace root", collect, []).option("--config <path>", "explicit config file").option("--provider <provider>", "model provider").option("--model <model>", "model identifier").option("--base-url <url>", "OpenAI-compatible or provider base URL").option("--max-turns <n>", "maximum agent turns").option("--epoch-token-budget <n>", "maximum tokens before an internal context handoff").option("--token-budget <n>", "maximum lifetime tokens across the resumed session").option("--resume [session]", "resume a session by id or prefix").option("-c, --continue", "resume the latest session").option("--no-color", "disable color output").option("--no-checkpoint", "disable pre-mutation checkpoints for this run").action(async (prompts, options) => {
|
|
21469
22394
|
await runChat(prompts, options);
|
|
21470
22395
|
});
|
|
21471
22396
|
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) => {
|
|
@@ -22071,14 +22996,26 @@ async function runCli() {
|
|
|
22071
22996
|
try {
|
|
22072
22997
|
await program.parseAsync(process.argv);
|
|
22073
22998
|
} catch (error) {
|
|
22074
|
-
const
|
|
22075
|
-
|
|
22999
|
+
const format = structuredOutputFormat(process.argv);
|
|
23000
|
+
if (format) {
|
|
23001
|
+
const reporter = new HeadlessReporter({ format, color: false });
|
|
23002
|
+
process.exitCode = reporter.fail(error).exitCode;
|
|
23003
|
+
} else {
|
|
23004
|
+
const message2 = error instanceof Error ? error.message : String(error);
|
|
23005
|
+
process.stderr.write(`${chalk4.red(cliGlyphs.error)} ${message2}
|
|
22076
23006
|
`);
|
|
22077
|
-
|
|
23007
|
+
process.exitCode = 1;
|
|
23008
|
+
}
|
|
22078
23009
|
} finally {
|
|
22079
23010
|
releaseCliNamespaceLeases(cliNamespaceLeases);
|
|
22080
23011
|
}
|
|
22081
23012
|
}
|
|
23013
|
+
function structuredOutputFormat(argv) {
|
|
23014
|
+
const inline = argv.find((argument) => argument.startsWith("--output-format="))?.slice("--output-format=".length);
|
|
23015
|
+
const index = argv.indexOf("--output-format");
|
|
23016
|
+
const value = inline ?? (index >= 0 ? argv[index + 1] : void 0);
|
|
23017
|
+
return value === "json" || value === "stream-json" ? value : void 0;
|
|
23018
|
+
}
|
|
22082
23019
|
async function runChat(prompts, options) {
|
|
22083
23020
|
const shouldPrint = options.print === true || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
22084
23021
|
if (options.ask && options.plan) throw new Error("--ask and --plan cannot be used together.");
|
|
@@ -22146,7 +23083,8 @@ async function runChat(prompts, options) {
|
|
|
22146
23083
|
color: (options.color ?? config.ui.color) && !process.env.NO_COLOR
|
|
22147
23084
|
});
|
|
22148
23085
|
const colorOutput = (options.color ?? config.ui.color) && !process.env.NO_COLOR;
|
|
22149
|
-
const requestPermission = options.yes ? async () => true : options.autoEdit ? async (_call, category) => category === "read" || category === "write" ? true : askConsolePermission(_call, category, colorOutput) : async (call, category) => askConsolePermission(call, category, colorOutput);
|
|
23086
|
+
const requestPermission = options.yes ? async () => true : options.autoEdit ? async (_call, category, reason) => category === "read" || category === "write" ? true : askConsolePermission(_call, category, colorOutput, reason) : async (call, category, reason) => askConsolePermission(call, category, colorOutput, reason);
|
|
23087
|
+
let extensionsClosed = false;
|
|
22150
23088
|
try {
|
|
22151
23089
|
let session = await runner.run(firstPrompt, {
|
|
22152
23090
|
askMode: options.ask === true || options.plan === true,
|
|
@@ -22156,6 +23094,7 @@ async function runChat(prompts, options) {
|
|
|
22156
23094
|
requestPermission
|
|
22157
23095
|
});
|
|
22158
23096
|
for (const queued of options.queue) {
|
|
23097
|
+
if (session.pendingInput) break;
|
|
22159
23098
|
session = await runner.run(queued, {
|
|
22160
23099
|
askMode: options.ask === true || options.plan === true,
|
|
22161
23100
|
...options.plan ? { turnInstructions: PLAN_MODE_INSTRUCTIONS } : {},
|
|
@@ -22164,12 +23103,15 @@ async function runChat(prompts, options) {
|
|
|
22164
23103
|
requestPermission
|
|
22165
23104
|
});
|
|
22166
23105
|
}
|
|
22167
|
-
|
|
23106
|
+
await extensions.close();
|
|
23107
|
+
extensionsClosed = true;
|
|
23108
|
+
const outcome2 = reporter.finish(session);
|
|
23109
|
+
process.exitCode = outcome2.exitCode;
|
|
22168
23110
|
} catch (error) {
|
|
22169
|
-
reporter.fail(error);
|
|
22170
|
-
process.exitCode =
|
|
23111
|
+
const outcome2 = reporter.fail(error, runner.getSession());
|
|
23112
|
+
process.exitCode = outcome2.exitCode;
|
|
22171
23113
|
} finally {
|
|
22172
|
-
await extensions.close();
|
|
23114
|
+
if (!extensionsClosed) await extensions.close().catch(() => void 0);
|
|
22173
23115
|
}
|
|
22174
23116
|
}
|
|
22175
23117
|
async function openMemoryStore(config) {
|
|
@@ -22333,6 +23275,7 @@ async function runtimeConfig(workspaceInput, options) {
|
|
|
22333
23275
|
agent: {
|
|
22334
23276
|
...loaded.agent,
|
|
22335
23277
|
...options.checkpoint === false ? { checkpointBeforeWrite: false } : {},
|
|
23278
|
+
...options.epochTokenBudget ? { maxEpochTokens: positiveInt(options.epochTokenBudget, loaded.agent.maxEpochTokens ?? loaded.agent.maxSessionTokens) } : {},
|
|
22336
23279
|
...options.tokenBudget ? { maxSessionTokens: positiveInt(options.tokenBudget, loaded.agent.maxSessionTokens) } : {}
|
|
22337
23280
|
},
|
|
22338
23281
|
ui: { ...loaded.ui, ...options.color === false ? { color: false } : {} }
|
|
@@ -22482,6 +23425,7 @@ function runtimeOptions(options) {
|
|
|
22482
23425
|
const provider = options.provider ?? root.provider;
|
|
22483
23426
|
const model = options.model ?? root.model;
|
|
22484
23427
|
const baseUrl = options.baseUrl ?? root.baseUrl;
|
|
23428
|
+
const epochTokenBudget = options.epochTokenBudget ?? root.epochTokenBudget;
|
|
22485
23429
|
const tokenBudget = options.tokenBudget ?? root.tokenBudget;
|
|
22486
23430
|
return {
|
|
22487
23431
|
addWorkspace: [...root.addWorkspace ?? [], ...options.addWorkspace ?? []],
|
|
@@ -22490,6 +23434,7 @@ function runtimeOptions(options) {
|
|
|
22490
23434
|
...model ? { model } : {},
|
|
22491
23435
|
...baseUrl ? { baseUrl } : {},
|
|
22492
23436
|
...options.maxTokens ? { maxTokens: options.maxTokens } : {},
|
|
23437
|
+
...epochTokenBudget ? { epochTokenBudget } : {},
|
|
22493
23438
|
...tokenBudget ? { tokenBudget } : {},
|
|
22494
23439
|
...root.color !== void 0 ? { color: root.color } : {},
|
|
22495
23440
|
...root.checkpoint !== void 0 ? { checkpoint: root.checkpoint } : {},
|