@skein-code/cli 0.3.25 → 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 +21 -1
- package/dist/cli.js +425 -50
- package/dist/cli.js.map +1 -1
- package/docs/NEXT_STEPS.md +26 -3
- package/docs/headless-output.schema.json +36 -0
- package/package.json +7 -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,12 @@ 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",
|
|
239
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",
|
|
240
246
|
"Content-free epoch handoffs preserve Task Contract criteria, unresolved failure circuits, changed files, and verification receipts across long runs",
|
|
241
247
|
"Intent Sufficiency routes clear requests to execution, repository-inferable gaps to inspection, and genuine product choices to one persisted clarification",
|
|
@@ -11975,13 +11981,13 @@ ${completeContent}`;
|
|
|
11975
11981
|
this.recordPermission(call, category, "allow", "Approved for this session.");
|
|
11976
11982
|
return true;
|
|
11977
11983
|
}
|
|
11978
|
-
await emit({ type: "permission", call, category });
|
|
11984
|
+
await emit({ type: "permission", call, category, reason: decision.reason });
|
|
11979
11985
|
if (!options.requestPermission) {
|
|
11980
11986
|
this.recordPermission(call, category, "deny", "No permission handler was available.");
|
|
11981
11987
|
return false;
|
|
11982
11988
|
}
|
|
11983
11989
|
try {
|
|
11984
|
-
const grant = await options.requestPermission(call, category);
|
|
11990
|
+
const grant = await options.requestPermission(call, category, decision.reason);
|
|
11985
11991
|
const allowed = grant === true || grant === "session";
|
|
11986
11992
|
if (grant === "session") this.sessionApprovals.add(approvalKey);
|
|
11987
11993
|
this.recordPermission(
|
|
@@ -11996,13 +12002,13 @@ ${completeContent}`;
|
|
|
11996
12002
|
return false;
|
|
11997
12003
|
}
|
|
11998
12004
|
}
|
|
11999
|
-
recordPermission(call, category,
|
|
12005
|
+
recordPermission(call, category, outcome2, reason) {
|
|
12000
12006
|
this.appendAudit({
|
|
12001
12007
|
type: "permission",
|
|
12002
12008
|
toolCallId: call.id,
|
|
12003
12009
|
tool: call.name,
|
|
12004
12010
|
category,
|
|
12005
|
-
outcome,
|
|
12011
|
+
outcome: outcome2,
|
|
12006
12012
|
reason
|
|
12007
12013
|
});
|
|
12008
12014
|
}
|
|
@@ -13649,18 +13655,18 @@ var DelegationManager = class {
|
|
|
13649
13655
|
await this.recordAgent(board.id, writer, "write");
|
|
13650
13656
|
const writerFailed = !writer.ok || !draft.patch || !draft.worktreeCleaned || signal?.aborted;
|
|
13651
13657
|
if (writerFailed) {
|
|
13652
|
-
const
|
|
13658
|
+
const outcome2 = writer.termination === "cancelled" || signal?.aborted ? "cancelled" : "failed";
|
|
13653
13659
|
await this.teamStore.recordWriterLane(board.id, {
|
|
13654
13660
|
profile: profile.name,
|
|
13655
13661
|
reviewer: reviewer.name,
|
|
13656
13662
|
baseCommit: draft.baseCommit,
|
|
13657
|
-
outcome,
|
|
13663
|
+
outcome: outcome2,
|
|
13658
13664
|
patch: draft.patch,
|
|
13659
13665
|
files: draft.files,
|
|
13660
13666
|
worktreeCleaned: draft.worktreeCleaned
|
|
13661
13667
|
});
|
|
13662
13668
|
await this.teamStore.complete(board.id, { accepted: false, reviewRounds: 0, failed: true });
|
|
13663
|
-
const status2 =
|
|
13669
|
+
const status2 = outcome2 === "cancelled" ? "cancelled" : "failed";
|
|
13664
13670
|
const detail2 = !draft.worktreeCleaned ? "Writer worktree cleanup could not be verified; integration is blocked." : !draft.patch ? "Writer returned no patch." : writer.summary;
|
|
13665
13671
|
await emit?.({ type: "writer_lane", id: board.id, status: status2, detail: detail2, files: draft.files });
|
|
13666
13672
|
await emit?.({ type: "team_done", id: board.id, accepted: false, reviewRounds: 0 });
|
|
@@ -15009,6 +15015,61 @@ function keyEnvironmentName(provider) {
|
|
|
15009
15015
|
// src/cli/output.ts
|
|
15010
15016
|
import { createInterface } from "node:readline/promises";
|
|
15011
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
|
|
15012
15073
|
var HeadlessReporter = class {
|
|
15013
15074
|
constructor(options) {
|
|
15014
15075
|
this.options = options;
|
|
@@ -15018,7 +15079,6 @@ var HeadlessReporter = class {
|
|
|
15018
15079
|
options;
|
|
15019
15080
|
finalResponse = "";
|
|
15020
15081
|
tools = [];
|
|
15021
|
-
eventError;
|
|
15022
15082
|
context;
|
|
15023
15083
|
streamedAssistant = false;
|
|
15024
15084
|
completion;
|
|
@@ -15028,7 +15088,6 @@ var HeadlessReporter = class {
|
|
|
15028
15088
|
onEvent = (event) => {
|
|
15029
15089
|
if (event.type === "assistant") this.finalResponse = event.content;
|
|
15030
15090
|
if (event.type === "tool_result") this.tools.push(event.result);
|
|
15031
|
-
if (event.type === "error") this.eventError = event.error.message;
|
|
15032
15091
|
if (event.type === "done") {
|
|
15033
15092
|
this.doneReason = event.reason;
|
|
15034
15093
|
this.completion = event.completion;
|
|
@@ -15046,26 +15105,33 @@ var HeadlessReporter = class {
|
|
|
15046
15105
|
this.printText(event);
|
|
15047
15106
|
};
|
|
15048
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
|
+
});
|
|
15049
15114
|
if (this.options.format === "stream-json") {
|
|
15050
15115
|
process.stdout.write(`${JSON.stringify({
|
|
15051
15116
|
type: "session",
|
|
15117
|
+
...outcome2,
|
|
15052
15118
|
session: sessionSummary(session)
|
|
15053
15119
|
})}
|
|
15054
15120
|
`);
|
|
15055
|
-
return;
|
|
15121
|
+
return outcome2;
|
|
15056
15122
|
}
|
|
15057
15123
|
if (this.options.format === "json") {
|
|
15058
15124
|
process.stdout.write(`${JSON.stringify({
|
|
15059
|
-
|
|
15125
|
+
type: "result",
|
|
15126
|
+
...outcome2,
|
|
15060
15127
|
response: this.finalResponse,
|
|
15061
15128
|
session: sessionSummary(session),
|
|
15062
|
-
...
|
|
15063
|
-
...this.completion ? { completion: this.completion } : {},
|
|
15129
|
+
...completion ? { completion } : {},
|
|
15064
15130
|
...this.context ? { context: this.context } : {},
|
|
15065
15131
|
tools: this.tools
|
|
15066
15132
|
}, null, 2)}
|
|
15067
15133
|
`);
|
|
15068
|
-
return;
|
|
15134
|
+
return outcome2;
|
|
15069
15135
|
}
|
|
15070
15136
|
if (this.options.quiet && this.finalResponse.trim()) {
|
|
15071
15137
|
process.stdout.write(`${this.finalResponse.trim()}
|
|
@@ -15080,17 +15146,34 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
15080
15146
|
`
|
|
15081
15147
|
));
|
|
15082
15148
|
}
|
|
15149
|
+
return outcome2;
|
|
15083
15150
|
}
|
|
15084
|
-
fail(error) {
|
|
15151
|
+
fail(error, session) {
|
|
15085
15152
|
const message2 = error instanceof Error ? error.message : String(error);
|
|
15086
|
-
|
|
15087
|
-
if (this.options.format === "
|
|
15088
|
-
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
|
+
})}
|
|
15089
15161
|
`);
|
|
15090
|
-
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;
|
|
15091
15173
|
}
|
|
15092
15174
|
process.stderr.write(`${this.paint.red(this.glyphs.error)} ${message2}
|
|
15093
15175
|
`);
|
|
15176
|
+
return outcome2;
|
|
15094
15177
|
}
|
|
15095
15178
|
printText(event) {
|
|
15096
15179
|
const { quiet, compact: compact3 } = this.options;
|
|
@@ -15197,6 +15280,7 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
15197
15280
|
break;
|
|
15198
15281
|
case "done":
|
|
15199
15282
|
this.printCompletion(event.completion);
|
|
15283
|
+
this.printStopReason(event.reason);
|
|
15200
15284
|
break;
|
|
15201
15285
|
case "error":
|
|
15202
15286
|
break;
|
|
@@ -15227,8 +15311,20 @@ ${this.glyphs.meta} ${session.changedFiles.length} changed files ${this.glyphs.s
|
|
|
15227
15311
|
`
|
|
15228
15312
|
));
|
|
15229
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
|
+
}
|
|
15230
15326
|
};
|
|
15231
|
-
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.`) {
|
|
15232
15328
|
if (!process.stdin.isTTY || !process.stderr.isTTY) return false;
|
|
15233
15329
|
const paint = color ? chalk2 : new Chalk({ level: 0 });
|
|
15234
15330
|
const glyphs = resolveCliGlyphs();
|
|
@@ -15236,6 +15332,10 @@ async function askConsolePermission(call, category, color = !process.env.NO_COLO
|
|
|
15236
15332
|
${paint.yellow("Permission required")} ${paint.dim(`(${category})`)}
|
|
15237
15333
|
`);
|
|
15238
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)}`)}
|
|
15239
15339
|
`);
|
|
15240
15340
|
const readline = createInterface({ input: process.stdin, output: process.stderr });
|
|
15241
15341
|
try {
|
|
@@ -15245,6 +15345,13 @@ ${paint.yellow("Permission required")} ${paint.dim(`(${category})`)}
|
|
|
15245
15345
|
readline.close();
|
|
15246
15346
|
}
|
|
15247
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
|
+
}
|
|
15248
15355
|
function eventToJson(event) {
|
|
15249
15356
|
if (event.type === "error") {
|
|
15250
15357
|
return { type: event.type, error: event.error.message };
|
|
@@ -15350,7 +15457,7 @@ function commandNames(command2) {
|
|
|
15350
15457
|
// src/ui/tui.tsx
|
|
15351
15458
|
import { useCallback, useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
15352
15459
|
import { Box as Box2, render as render2, Text as Text3, useApp, useInput as useInput2, useWindowSize } from "ink";
|
|
15353
|
-
import { relative as
|
|
15460
|
+
import { relative as relative10 } from "node:path";
|
|
15354
15461
|
|
|
15355
15462
|
// src/ui/components.tsx
|
|
15356
15463
|
import React2 from "react";
|
|
@@ -15374,6 +15481,8 @@ var commandDefinitions = [
|
|
|
15374
15481
|
command("permissions", "Inspect the active permission policy"),
|
|
15375
15482
|
command("changes", "List files changed in the active session"),
|
|
15376
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]"),
|
|
15377
15486
|
command("checkpoints", "List recoverable pre-mutation snapshots"),
|
|
15378
15487
|
command("audit", "Review the hash-chained tool and permission timeline"),
|
|
15379
15488
|
command("rollback", "Restore workspace files from a checkpoint", "/rollback [checkpoint-id]"),
|
|
@@ -15430,6 +15539,28 @@ function commandSuggestions(input2, options = {}) {
|
|
|
15430
15539
|
description: "Show the secure shared-connection setup command"
|
|
15431
15540
|
}].filter((item) => item.label.includes(argument.trim().toLocaleLowerCase()));
|
|
15432
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
|
+
}
|
|
15433
15564
|
if (firstSpace >= 0 && commandName === "memory") {
|
|
15434
15565
|
const query = argument.trim().toLocaleLowerCase();
|
|
15435
15566
|
return [
|
|
@@ -16407,7 +16538,7 @@ function TaskRail({ tasks, width = 80, glyphMode = "auto", maxItems }) {
|
|
|
16407
16538
|
] }) : null
|
|
16408
16539
|
] });
|
|
16409
16540
|
}
|
|
16410
|
-
function PermissionCard({ call, category, width = 80, glyphMode = "auto", workspace, compact: compact3 = false }) {
|
|
16541
|
+
function PermissionCard({ call, category, reason, width = 80, glyphMode = "auto", workspace, compact: compact3 = false }) {
|
|
16411
16542
|
const theme = useTheme();
|
|
16412
16543
|
const glyphs = resolveGlyphs(glyphMode);
|
|
16413
16544
|
const summary = permissionSummary(call);
|
|
@@ -16415,7 +16546,9 @@ function PermissionCard({ call, category, width = 80, glyphMode = "auto", worksp
|
|
|
16415
16546
|
const innerWidth = Math.max(1, rowWidth - 2);
|
|
16416
16547
|
const title = truncateDisplay(`Permission required ${glyphs.separator} ${category}`, innerWidth);
|
|
16417
16548
|
const tool = truncateDisplay(`tool ${sanitizeInlineTerminalText(call.name)}`, innerWidth);
|
|
16418
|
-
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);
|
|
16419
16552
|
const argumentCwd = typeof call.arguments.cwd === "string" ? call.arguments.cwd : void 0;
|
|
16420
16553
|
const cwd = sanitizeInlineTerminalText(argumentCwd || workspace || "");
|
|
16421
16554
|
const shortcuts = [
|
|
@@ -16440,6 +16573,8 @@ function PermissionCard({ call, category, width = 80, glyphMode = "auto", worksp
|
|
|
16440
16573
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { bold: true, color: theme.warning, children: title }) }),
|
|
16441
16574
|
/* @__PURE__ */ jsx(PermissionLine, { marker, children: /* @__PURE__ */ jsx(Text, { color: theme.muted, children: tool }) }),
|
|
16442
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 }) }),
|
|
16443
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,
|
|
16444
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: [
|
|
16445
16580
|
/* @__PURE__ */ jsx(InlineRow, { parts: shortcuts.slice(0, 2), width: innerWidth, separator: " ", separatorColor: theme.border }),
|
|
@@ -16460,6 +16595,13 @@ function PermissionLine({ marker, children }) {
|
|
|
16460
16595
|
children
|
|
16461
16596
|
] });
|
|
16462
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
|
+
}
|
|
16463
16605
|
function PromptBar({ busy, value, placeholder, width = 80, mode = "chat", queueCount = 0, queuePreview, attachments = [], glyphMode = "auto", children }) {
|
|
16464
16606
|
const theme = useTheme();
|
|
16465
16607
|
const glyphs = resolveGlyphs(glyphMode);
|
|
@@ -17744,6 +17886,40 @@ function clipTimelineItem(item, options) {
|
|
|
17744
17886
|
if (item.kind === "notice") {
|
|
17745
17887
|
return { ...item, text: tailText(item.text, width, options.rows) };
|
|
17746
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
|
+
}
|
|
17747
17923
|
if (item.kind === "update") {
|
|
17748
17924
|
const baseRows = width < 48 ? 3 : 2;
|
|
17749
17925
|
if (options.rows < baseRows) {
|
|
@@ -17841,6 +18017,91 @@ function wrappedRows(value, width) {
|
|
|
17841
18017
|
return sanitizeTerminalText(value).split("\n").reduce((rows, line) => rows + Math.max(1, Math.ceil(displayWidth(line) / safeWidth2)), 0);
|
|
17842
18018
|
}
|
|
17843
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
|
+
|
|
17844
18105
|
// src/ui/timeline-reducers.ts
|
|
17845
18106
|
var itemCounter = 0;
|
|
17846
18107
|
var nextId = () => `ui-${Date.now()}-${itemCounter++}`;
|
|
@@ -18184,8 +18445,13 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18184
18445
|
const timer = setInterval(() => setFrameIndex((value) => (value + 1) % spinnerFrames().length), 120);
|
|
18185
18446
|
return () => clearInterval(timer);
|
|
18186
18447
|
}, [busy]);
|
|
18187
|
-
const requestPermission = useCallback((call, category) => {
|
|
18188
|
-
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
|
+
}));
|
|
18189
18455
|
}, []);
|
|
18190
18456
|
const onEvent = useCallback((event) => {
|
|
18191
18457
|
switch (event.type) {
|
|
@@ -18205,7 +18471,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18205
18471
|
...event.packed.budgetReason ? { budgetReason: event.packed.budgetReason } : {},
|
|
18206
18472
|
truncated: event.packed.truncated,
|
|
18207
18473
|
spans: event.packed.hits.slice(0, 5).map((hit) => ({
|
|
18208
|
-
path:
|
|
18474
|
+
path: relative10(runner.workspace.primaryRoot, hit.path) || hit.path,
|
|
18209
18475
|
startLine: hit.startLine,
|
|
18210
18476
|
endLine: hit.endLine,
|
|
18211
18477
|
score: hit.score,
|
|
@@ -18309,7 +18575,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18309
18575
|
id: nextId(),
|
|
18310
18576
|
kind: "notice",
|
|
18311
18577
|
tone: event.status === "ready" || event.status === "integrated" ? "success" : "error",
|
|
18312
|
-
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.` : ""}`
|
|
18313
18579
|
});
|
|
18314
18580
|
break;
|
|
18315
18581
|
case "agent_done":
|
|
@@ -18348,7 +18614,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18348
18614
|
case "error":
|
|
18349
18615
|
lastEventError.current = event.error.message;
|
|
18350
18616
|
setTimeline(endStreamingAssistants);
|
|
18351
|
-
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.` });
|
|
18352
18618
|
setActivity(void 0);
|
|
18353
18619
|
break;
|
|
18354
18620
|
case "done":
|
|
@@ -18372,7 +18638,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18372
18638
|
id: nextId(),
|
|
18373
18639
|
kind: "notice",
|
|
18374
18640
|
tone: event.reason === "aborted" ? "info" : "error",
|
|
18375
|
-
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
|
|
18376
18642
|
});
|
|
18377
18643
|
}
|
|
18378
18644
|
break;
|
|
@@ -18386,9 +18652,16 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18386
18652
|
const runLocalCommand = useCallback(async (value) => {
|
|
18387
18653
|
if (!value.startsWith("/")) return false;
|
|
18388
18654
|
const [rawCommand = "", ...rest] = value.slice(1).trim().split(/\s+/);
|
|
18389
|
-
|
|
18390
|
-
|
|
18655
|
+
let command2 = rawCommand.toLocaleLowerCase();
|
|
18656
|
+
let argument = rest.join(" ").trim();
|
|
18391
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
|
+
}
|
|
18392
18665
|
if (command2 === "exit" || command2 === "quit") {
|
|
18393
18666
|
exit();
|
|
18394
18667
|
return true;
|
|
@@ -18470,10 +18743,77 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18470
18743
|
});
|
|
18471
18744
|
return true;
|
|
18472
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
|
+
}
|
|
18473
18813
|
if (command2 === "changes") {
|
|
18474
18814
|
const changed = runner.getSession().changedFiles;
|
|
18475
18815
|
appendList("Changed files", changed.length ? changed.map((path) => ({
|
|
18476
|
-
label:
|
|
18816
|
+
label: relative10(runner.workspace.primaryRoot, path) || ".",
|
|
18477
18817
|
detail: path
|
|
18478
18818
|
})) : [{ label: "No recorded changes." }]);
|
|
18479
18819
|
return true;
|
|
@@ -18626,7 +18966,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18626
18966
|
const skills = extensions?.listSkills() ?? [];
|
|
18627
18967
|
appendList("Skills", skills.length ? skills.map((skill) => ({
|
|
18628
18968
|
label: `${skill.name} ${skill.scope}${skill.trusted ? "" : `${separator}untrusted`}`,
|
|
18629
|
-
detail: `${skill.description}${separator}${
|
|
18969
|
+
detail: `${skill.description}${separator}${relative10(runner.workspace.primaryRoot, skill.path) || skill.path}`,
|
|
18630
18970
|
tone: skill.trusted ? "normal" : "warning"
|
|
18631
18971
|
})) : [{ label: "No skills discovered.", detail: "Add SKILL.md playbooks under .agents/skills, .claude/skills, or a configured directory." }]);
|
|
18632
18972
|
return true;
|
|
@@ -18956,7 +19296,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
18956
19296
|
lastEventError.current = void 0;
|
|
18957
19297
|
try {
|
|
18958
19298
|
const nextSession = await runner.run(current.runInput, {
|
|
18959
|
-
askMode: interactionMode !== "build",
|
|
19299
|
+
askMode: current.readOnly === true || interactionMode !== "build",
|
|
18960
19300
|
signal: abortController.signal,
|
|
18961
19301
|
...current.turnInstructions || interactionMode === "plan" ? {
|
|
18962
19302
|
turnInstructions: [
|
|
@@ -19026,10 +19366,10 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
19026
19366
|
}, [append, exit, interactionMode, onEvent, requestPermission, runLocalCommand, runner]);
|
|
19027
19367
|
const submitFromComposer = useCallback((raw, mode = "normal") => {
|
|
19028
19368
|
if (historySearch) {
|
|
19029
|
-
const
|
|
19369
|
+
const selected2 = resolveHistorySearch(historySearch, "select");
|
|
19030
19370
|
setHistorySearch(void 0);
|
|
19031
19371
|
setHistoryIndex(-1);
|
|
19032
|
-
void submit(
|
|
19372
|
+
void submit(selected2, mode === "normal" && processing.current ? "steer" : mode);
|
|
19033
19373
|
return;
|
|
19034
19374
|
}
|
|
19035
19375
|
if (suggestionMode === "mention" && selectedSuggestion) {
|
|
@@ -19040,7 +19380,8 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
19040
19380
|
return;
|
|
19041
19381
|
}
|
|
19042
19382
|
}
|
|
19043
|
-
const
|
|
19383
|
+
const selected = selectedSuggestion;
|
|
19384
|
+
const suggestion = selected && !(raw.startsWith(selected.value) && raw.slice(selected.value.length).trim()) ? selected : void 0;
|
|
19044
19385
|
const normalized = raw.trimEnd();
|
|
19045
19386
|
if (suggestion && raw.startsWith("/") && suggestion.value !== raw && suggestion.value.endsWith(" ") && suggestion.label !== normalized) {
|
|
19046
19387
|
setInput(suggestion.value);
|
|
@@ -19079,6 +19420,14 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
19079
19420
|
text: `Allowed ${call.name} for this exact ${category} target during the session.`
|
|
19080
19421
|
});
|
|
19081
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
|
+
}
|
|
19082
19431
|
if (stop) {
|
|
19083
19432
|
requestRunStop();
|
|
19084
19433
|
}
|
|
@@ -19422,7 +19771,7 @@ function SkeinApp({ runner, config, extensions, initialPrompt, askMode = false,
|
|
|
19422
19771
|
)
|
|
19423
19772
|
}
|
|
19424
19773
|
)
|
|
19425
|
-
] }) : /* @__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 }),
|
|
19426
19775
|
showFooter ? /* @__PURE__ */ jsx3(
|
|
19427
19776
|
Footer,
|
|
19428
19777
|
{
|
|
@@ -19562,6 +19911,14 @@ function cloneValue(value) {
|
|
|
19562
19911
|
if (value && typeof value === "object") return cloneRecord(value);
|
|
19563
19912
|
return value;
|
|
19564
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
|
+
}
|
|
19565
19922
|
function isExitCommand(value) {
|
|
19566
19923
|
const command2 = localCommandName(value);
|
|
19567
19924
|
return command2 === "exit" || command2 === "quit";
|
|
@@ -19579,6 +19936,9 @@ function shouldDeferLocalCommand(value) {
|
|
|
19579
19936
|
"remember",
|
|
19580
19937
|
"diff",
|
|
19581
19938
|
"checkpoints",
|
|
19939
|
+
"audit",
|
|
19940
|
+
"rollback",
|
|
19941
|
+
"recover",
|
|
19582
19942
|
"workflow",
|
|
19583
19943
|
"exit",
|
|
19584
19944
|
"quit"
|
|
@@ -19598,7 +19958,7 @@ function composerAttachments(value) {
|
|
|
19598
19958
|
return [...new Set(paths)].slice(-3);
|
|
19599
19959
|
}
|
|
19600
19960
|
function permissionRows(width, hasCwd, compact3) {
|
|
19601
|
-
const content =
|
|
19961
|
+
const content = 5 + (hasCwd ? 1 : 0);
|
|
19602
19962
|
if (width >= 64) return content + 2;
|
|
19603
19963
|
if (width >= 28) return content + 3;
|
|
19604
19964
|
if (compact3) return content + 3;
|
|
@@ -20392,7 +20752,7 @@ function titleForStep(step2) {
|
|
|
20392
20752
|
return "Saving configuration";
|
|
20393
20753
|
}
|
|
20394
20754
|
function descriptionForStep(state) {
|
|
20395
|
-
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.";
|
|
20396
20756
|
if (state.step === "official-provider") return "Use the API credential issued by the selected provider.";
|
|
20397
20757
|
if (state.step === "relay-protocol") return "The protocol is explicit so requests and credentials are never guessed.";
|
|
20398
20758
|
if (state.step === "endpoint") return "Remote endpoints require HTTPS. Loopback development servers may use HTTP.";
|
|
@@ -22636,14 +22996,26 @@ async function runCli() {
|
|
|
22636
22996
|
try {
|
|
22637
22997
|
await program.parseAsync(process.argv);
|
|
22638
22998
|
} catch (error) {
|
|
22639
|
-
const
|
|
22640
|
-
|
|
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}
|
|
22641
23006
|
`);
|
|
22642
|
-
|
|
23007
|
+
process.exitCode = 1;
|
|
23008
|
+
}
|
|
22643
23009
|
} finally {
|
|
22644
23010
|
releaseCliNamespaceLeases(cliNamespaceLeases);
|
|
22645
23011
|
}
|
|
22646
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
|
+
}
|
|
22647
23019
|
async function runChat(prompts, options) {
|
|
22648
23020
|
const shouldPrint = options.print === true || !process.stdin.isTTY || !process.stdout.isTTY;
|
|
22649
23021
|
if (options.ask && options.plan) throw new Error("--ask and --plan cannot be used together.");
|
|
@@ -22711,7 +23083,8 @@ async function runChat(prompts, options) {
|
|
|
22711
23083
|
color: (options.color ?? config.ui.color) && !process.env.NO_COLOR
|
|
22712
23084
|
});
|
|
22713
23085
|
const colorOutput = (options.color ?? config.ui.color) && !process.env.NO_COLOR;
|
|
22714
|
-
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;
|
|
22715
23088
|
try {
|
|
22716
23089
|
let session = await runner.run(firstPrompt, {
|
|
22717
23090
|
askMode: options.ask === true || options.plan === true,
|
|
@@ -22730,13 +23103,15 @@ async function runChat(prompts, options) {
|
|
|
22730
23103
|
requestPermission
|
|
22731
23104
|
});
|
|
22732
23105
|
}
|
|
22733
|
-
|
|
22734
|
-
|
|
23106
|
+
await extensions.close();
|
|
23107
|
+
extensionsClosed = true;
|
|
23108
|
+
const outcome2 = reporter.finish(session);
|
|
23109
|
+
process.exitCode = outcome2.exitCode;
|
|
22735
23110
|
} catch (error) {
|
|
22736
|
-
reporter.fail(error);
|
|
22737
|
-
process.exitCode =
|
|
23111
|
+
const outcome2 = reporter.fail(error, runner.getSession());
|
|
23112
|
+
process.exitCode = outcome2.exitCode;
|
|
22738
23113
|
} finally {
|
|
22739
|
-
await extensions.close();
|
|
23114
|
+
if (!extensionsClosed) await extensions.close().catch(() => void 0);
|
|
22740
23115
|
}
|
|
22741
23116
|
}
|
|
22742
23117
|
async function openMemoryStore(config) {
|