@2kw/ai 6.3.0-dev.36 → 6.3.0-dev.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/agent-run.js +15 -6
- package/dist/commands/conversations.js +16 -0
- package/dist/lib/agent-decide.d.ts +3 -2
- package/dist/lib/agent-decide.js +5 -2
- package/dist/lib/agent-run.d.ts +28 -2
- package/dist/lib/agent-run.js +33 -1
- package/dist/lib/approval-prompt.js +31 -1
- package/dist/lib/skills-apply-preview.d.ts +19 -0
- package/dist/lib/skills-apply-preview.js +94 -0
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@ import { isJsonOutput } from "../lib/config.js";
|
|
|
5
5
|
import { BackboneApiError, CliUsageError } from "../lib/errors.js";
|
|
6
6
|
import { withSpinner } from "../lib/output.js";
|
|
7
7
|
import { resolveAgent, splitAgentRef, suggestAgents } from "../lib/agent-lookup.js";
|
|
8
|
-
import { buildRunEnvelope, EXIT_CODES, printRunText, stripControl } from "../lib/agent-run.js";
|
|
8
|
+
import { buildRunEnvelope, EXIT_CODES, MODE_OPTION_HELP, modeItem, parseModeOption, printRunText, stripControl, } from "../lib/agent-run.js";
|
|
9
9
|
import { continueWithDecisions, decisionsFromAnswers, fetchPendingApprovals, planDecisions, } from "../lib/agent-decide.js";
|
|
10
10
|
import { PromptAbortedError, promptApprovals, readlineAsk } from "../lib/approval-prompt.js";
|
|
11
11
|
import { withModelOverride } from "../lib/agent-models.js";
|
|
@@ -90,7 +90,7 @@ async function finishRun(command, client, target, first, opts) {
|
|
|
90
90
|
throw new CliUsageError(`No pending approvals for ${responseId} (already decided or superseded).`);
|
|
91
91
|
}
|
|
92
92
|
const decisions = decisionsFromAnswers(rows, answers);
|
|
93
|
-
result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label, target.model));
|
|
93
|
+
result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label, target.model, target.mode));
|
|
94
94
|
env = buildRunEnvelope(result, agentRef);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
@@ -122,14 +122,21 @@ export function makeAgentRunCommand() {
|
|
|
122
122
|
.option("--conversation <id>", "Continue a conversation")
|
|
123
123
|
.option("--continue <responseId>", "Continue from a previous response")
|
|
124
124
|
.option("--model <model>", "Run this request on another model from the agent's list (sends <agent>#<model>)")
|
|
125
|
+
.option("--mode <mode>", MODE_OPTION_HELP)
|
|
125
126
|
.option("--no-input", "Never prompt for approvals, even on a terminal")
|
|
126
127
|
.option("--raw", "Print the untouched API response")
|
|
127
128
|
.action(async (agentArg, input, opts, command) => {
|
|
128
129
|
await runAction(command, async () => {
|
|
129
130
|
const agentRef = withModelOverride(agentArg, opts.model);
|
|
131
|
+
const mode = parseModeOption(opts.mode);
|
|
130
132
|
const text = resolveRunInput(input, opts.inputFile);
|
|
131
133
|
const client = getClient(command);
|
|
132
|
-
|
|
134
|
+
// Without --mode the body stays exactly as before #656 (D2): input is the bare string.
|
|
135
|
+
const body = {
|
|
136
|
+
model: `agent/${agentRef}`,
|
|
137
|
+
input: mode ? [{ type: "message", role: "user", content: text }, modeItem(mode)] : text,
|
|
138
|
+
stream: false,
|
|
139
|
+
};
|
|
133
140
|
if (opts.conversation)
|
|
134
141
|
body.conversation = opts.conversation;
|
|
135
142
|
if (opts.continue)
|
|
@@ -146,7 +153,7 @@ export function makeAgentRunCommand() {
|
|
|
146
153
|
// The prompt reads stdin and writes to stderr, so those two decide; stdout may be redirected.
|
|
147
154
|
const interactive = !!process.stdin.isTTY && !!process.stderr.isTTY && !isJsonOutput(command) && opts.input !== false && !opts.raw;
|
|
148
155
|
const { name, label, model } = splitAgentRef(agentRef);
|
|
149
|
-
await finishRun(command, client, { name, label, model }, data, {
|
|
156
|
+
await finishRun(command, client, { name, label, model, mode }, data, {
|
|
150
157
|
raw: !!opts.raw,
|
|
151
158
|
interactive,
|
|
152
159
|
});
|
|
@@ -164,9 +171,11 @@ export function makeAgentDecideCommand() {
|
|
|
164
171
|
.option("--reject-all", "Reject every pending approval of the response")
|
|
165
172
|
.option("--reason <text>", "Reason recorded on each decision (the model sees it on rejects)")
|
|
166
173
|
.option("--remember", "Also approve later calls of the same tool in this conversation")
|
|
174
|
+
.option("--mode <mode>", MODE_OPTION_HELP)
|
|
167
175
|
.option("--raw", "Print the untouched API response")
|
|
168
176
|
.action(async (agentRef, opts, command) => {
|
|
169
177
|
await runAction(command, async () => {
|
|
178
|
+
const mode = parseModeOption(opts.mode);
|
|
170
179
|
const client = getClient(command);
|
|
171
180
|
const { name, label, model } = splitAgentRef(agentRef);
|
|
172
181
|
// Dropped silently, an empty override would continue the run on the default model.
|
|
@@ -182,8 +191,8 @@ export function makeAgentDecideCommand() {
|
|
|
182
191
|
reason: opts.reason,
|
|
183
192
|
remember: opts.remember,
|
|
184
193
|
}, opts.response);
|
|
185
|
-
const result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), opts.response, decisions, label, model));
|
|
186
|
-
await finishRun(command, client, { name, label, model }, result, { raw: !!opts.raw, interactive: false });
|
|
194
|
+
const result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), opts.response, decisions, label, model, mode));
|
|
195
|
+
await finishRun(command, client, { name, label, model, mode }, result, { raw: !!opts.raw, interactive: false });
|
|
187
196
|
});
|
|
188
197
|
});
|
|
189
198
|
}
|
|
@@ -98,6 +98,22 @@ export function makeConversationsCommand() {
|
|
|
98
98
|
formatSuccess(`Conversation ${conversationId} deleted.`, command);
|
|
99
99
|
});
|
|
100
100
|
});
|
|
101
|
+
cmd
|
|
102
|
+
.command("cancel")
|
|
103
|
+
.description("Ask a running turn of a conversation to stop")
|
|
104
|
+
.argument("<conversationId>", "Conversation ID")
|
|
105
|
+
.requiredOption("--turn <turnId>", "The turn id the client sent as the Backbone-Turn-Id header")
|
|
106
|
+
.action(async (conversationId, opts, command) => {
|
|
107
|
+
await runAction(command, async () => {
|
|
108
|
+
const client = getClient(command);
|
|
109
|
+
await client.POST("/v1/conversations/{conversationId}/cancel", {
|
|
110
|
+
params: { path: { conversationId } },
|
|
111
|
+
body: { turn_id: opts.turn },
|
|
112
|
+
});
|
|
113
|
+
// Accepted, not performed: the turn's own response says whether it stopped.
|
|
114
|
+
formatSuccess(`Stop requested for turn ${opts.turn} of conversation ${conversationId}.`, command);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
101
117
|
const items = new Command("items").description("List a conversation's items");
|
|
102
118
|
items
|
|
103
119
|
.command("list")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { components } from "../generated/openapi.js";
|
|
2
2
|
import type { ApiClient } from "./agent-lookup.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type ConversationMode, type ResponsesResult } from "./agent-run.js";
|
|
4
4
|
export type ApprovalRow = components["schemas"]["ToolApprovalDTO"];
|
|
5
5
|
export interface DecideOptions {
|
|
6
6
|
approve?: string[];
|
|
@@ -36,6 +36,7 @@ export declare function toApprovalItems(decisions: Decision[]): Record<string, s
|
|
|
36
36
|
* server resolves the continuation's tools, instructions and model from this reference, so both must match
|
|
37
37
|
* the run's or the continuation drops to another version or the agent's default model.
|
|
38
38
|
* Never the echoed `name@<versionNumber>` (it would 404 as a label).
|
|
39
|
+
* With `mode`, the `backbone:mode` item follows the decisions (#656 D7); without it, nothing is added (D2).
|
|
39
40
|
*/
|
|
40
|
-
export declare function continueWithDecisions(client: ApiClient, agentId: string, responseId: string, decisions: Decision[], label?: string, model?: string): Promise<ResponsesResult>;
|
|
41
|
+
export declare function continueWithDecisions(client: ApiClient, agentId: string, responseId: string, decisions: Decision[], label?: string, model?: string, mode?: ConversationMode): Promise<ResponsesResult>;
|
|
41
42
|
//# sourceMappingURL=agent-decide.d.ts.map
|
package/dist/lib/agent-decide.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { modeItem } from "./agent-run.js";
|
|
1
2
|
import { CliUsageError } from "./errors.js";
|
|
2
3
|
import { paginationParams } from "./pagination.js";
|
|
3
4
|
const MAX_PAGES = 50;
|
|
@@ -113,13 +114,15 @@ export function toApprovalItems(decisions) {
|
|
|
113
114
|
* server resolves the continuation's tools, instructions and model from this reference, so both must match
|
|
114
115
|
* the run's or the continuation drops to another version or the agent's default model.
|
|
115
116
|
* Never the echoed `name@<versionNumber>` (it would 404 as a label).
|
|
117
|
+
* With `mode`, the `backbone:mode` item follows the decisions (#656 D7); without it, nothing is added (D2).
|
|
116
118
|
*/
|
|
117
|
-
export async function continueWithDecisions(client, agentId, responseId, decisions, label, model) {
|
|
119
|
+
export async function continueWithDecisions(client, agentId, responseId, decisions, label, model, mode) {
|
|
120
|
+
const approvals = toApprovalItems(decisions);
|
|
118
121
|
const body = {
|
|
119
122
|
model: `agent/${agentId}${label ? `@${label}` : ""}${model ? `#${model}` : ""}`,
|
|
120
123
|
previous_response_id: responseId,
|
|
121
124
|
stream: false,
|
|
122
|
-
input:
|
|
125
|
+
input: mode ? [...approvals, modeItem(mode)] : approvals,
|
|
123
126
|
};
|
|
124
127
|
// The generated body type does not model backbone: extension items, hence the cast.
|
|
125
128
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
package/dist/lib/agent-run.d.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { components } from "../generated/openapi.js";
|
|
1
2
|
type AnyRecord = Record<string, any>;
|
|
2
3
|
export type RunStatus = "completed" | "requires_approval" | "requires_tool_output" | "incomplete";
|
|
4
|
+
/** A built-in SkillsApply approval's preview (#781, spec §6); the generated type, fields all optional. */
|
|
5
|
+
export type SkillsApplyPreview = components["schemas"]["SkillsApplyPreview"];
|
|
3
6
|
export interface PendingApproval {
|
|
4
7
|
approvalId: string;
|
|
5
8
|
callId: string;
|
|
@@ -8,6 +11,8 @@ export interface PendingApproval {
|
|
|
8
11
|
policyClass: string;
|
|
9
12
|
/** Why the automatic approver escalated this request to a human (#634); null when it did not. */
|
|
10
13
|
reason: string | null;
|
|
14
|
+
/** What a built-in SkillsApply change does (#781); null for every other approval. Keyed on presence, not on the tool name (#948). */
|
|
15
|
+
preview: SkillsApplyPreview | null;
|
|
11
16
|
}
|
|
12
17
|
export interface ToolCallSummary {
|
|
13
18
|
tool: string;
|
|
@@ -20,10 +25,30 @@ export interface PendingToolCall {
|
|
|
20
25
|
callId: string;
|
|
21
26
|
arguments: unknown;
|
|
22
27
|
}
|
|
28
|
+
/** The end user's conversation mode (epic &59); the server matches these three values exactly. */
|
|
29
|
+
export type ConversationMode = "plan" | "ask" | "auto";
|
|
30
|
+
export declare const CONVERSATION_MODES: readonly ConversationMode[];
|
|
31
|
+
/** `--mode` help text, in the words of the embedded chat's mode chip (#655). */
|
|
32
|
+
export declare const MODE_OPTION_HELP: string;
|
|
33
|
+
/**
|
|
34
|
+
* The `--mode` value, checked before any request is sent: anything but the three exact values is a
|
|
35
|
+
* usage error (exit 2). Commander's `.choices()` is not used because it exits 1 through `process.exit`.
|
|
36
|
+
*/
|
|
37
|
+
export declare function parseModeOption(raw: string | undefined): ConversationMode | undefined;
|
|
38
|
+
/** The `backbone:mode` input item (S1 D7); always appended last. */
|
|
39
|
+
export declare function modeItem(mode: ConversationMode): {
|
|
40
|
+
type: "backbone:mode";
|
|
41
|
+
mode: ConversationMode;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The mode the response ran under (`conversation_mode`, #656). An absent key (a server before #656)
|
|
45
|
+
* and any value other than the three read as null, which also means "none set".
|
|
46
|
+
*/
|
|
47
|
+
export declare function responseMode(result: ResponsesResult | undefined): ConversationMode | null;
|
|
23
48
|
export interface RunEnvelope {
|
|
24
49
|
status: RunStatus;
|
|
25
|
-
/**
|
|
26
|
-
mode: null;
|
|
50
|
+
/** The conversation mode the request ran under (`conversation_mode`); null when none is set. */
|
|
51
|
+
mode: ConversationMode | null;
|
|
27
52
|
agent: string | null;
|
|
28
53
|
version: number | null;
|
|
29
54
|
responseId: string | null;
|
|
@@ -56,6 +81,7 @@ export interface ResponsesResult {
|
|
|
56
81
|
incomplete_details?: {
|
|
57
82
|
reason?: string;
|
|
58
83
|
} | null;
|
|
84
|
+
conversation_mode?: string | null;
|
|
59
85
|
}
|
|
60
86
|
export declare const EXIT_CODES: Record<RunStatus, number>;
|
|
61
87
|
/** The assistant's text: every `output_text` part of every `message` item, in order. */
|
package/dist/lib/agent-run.js
CHANGED
|
@@ -1,4 +1,35 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { CliUsageError } from "./errors.js";
|
|
3
|
+
export const CONVERSATION_MODES = ["plan", "ask", "auto"];
|
|
4
|
+
/** `--mode` help text, in the words of the embedded chat's mode chip (#655). */
|
|
5
|
+
export const MODE_OPTION_HELP = "Conversation mode: plan (read-only), ask (no automatic approver; calls that need approval wait for you), " +
|
|
6
|
+
"auto (the operator's policy as written). Without it, the conversation keeps its mode";
|
|
7
|
+
function isConversationMode(value) {
|
|
8
|
+
return typeof value === "string" && CONVERSATION_MODES.includes(value);
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The `--mode` value, checked before any request is sent: anything but the three exact values is a
|
|
12
|
+
* usage error (exit 2). Commander's `.choices()` is not used because it exits 1 through `process.exit`.
|
|
13
|
+
*/
|
|
14
|
+
export function parseModeOption(raw) {
|
|
15
|
+
if (raw === undefined)
|
|
16
|
+
return undefined;
|
|
17
|
+
if (isConversationMode(raw))
|
|
18
|
+
return raw;
|
|
19
|
+
throw new CliUsageError(`Invalid --mode '${raw}': use plan, ask or auto.`);
|
|
20
|
+
}
|
|
21
|
+
/** The `backbone:mode` input item (S1 D7); always appended last. */
|
|
22
|
+
export function modeItem(mode) {
|
|
23
|
+
return { type: "backbone:mode", mode };
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The mode the response ran under (`conversation_mode`, #656). An absent key (a server before #656)
|
|
27
|
+
* and any value other than the three read as null, which also means "none set".
|
|
28
|
+
*/
|
|
29
|
+
export function responseMode(result) {
|
|
30
|
+
const raw = result?.conversation_mode;
|
|
31
|
+
return isConversationMode(raw) ? raw : null;
|
|
32
|
+
}
|
|
2
33
|
export const EXIT_CODES = {
|
|
3
34
|
completed: 0,
|
|
4
35
|
requires_approval: 3,
|
|
@@ -80,6 +111,7 @@ export function buildRunEnvelope(result, agentRef) {
|
|
|
80
111
|
arguments: parseArguments(i.arguments),
|
|
81
112
|
policyClass: String(i.policy_class),
|
|
82
113
|
reason: typeof i.reason === "string" && i.reason ? i.reason : null,
|
|
114
|
+
preview: i.preview && typeof i.preview === "object" && !Array.isArray(i.preview) ? i.preview : null,
|
|
83
115
|
}));
|
|
84
116
|
const withheldCallIds = new Set(pendingApprovals.map((a) => a.callId));
|
|
85
117
|
const toolCalls = [];
|
|
@@ -117,7 +149,7 @@ export function buildRunEnvelope(result, agentRef) {
|
|
|
117
149
|
: null;
|
|
118
150
|
return {
|
|
119
151
|
status,
|
|
120
|
-
mode:
|
|
152
|
+
mode: responseMode(result),
|
|
121
153
|
agent,
|
|
122
154
|
version,
|
|
123
155
|
responseId,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createInterface } from "node:readline/promises";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { stripControl } from "./agent-run.js";
|
|
4
|
+
import { REMEMBER_NOTE, previewIsIncomplete, previewLines } from "./skills-apply-preview.js";
|
|
4
5
|
/** Raised when the prompt's input closes (EOF, Ctrl+C, Ctrl+D) before an answer arrives. */
|
|
5
6
|
export class PromptAbortedError extends Error {
|
|
6
7
|
constructor() {
|
|
@@ -54,7 +55,23 @@ export async function promptApprovals(pending, ask, write = (line) => console.er
|
|
|
54
55
|
const destructive = a.policyClass.toLowerCase() === "destructive";
|
|
55
56
|
write(chalk.yellow(`\nApproval ${index + 1}/${pending.length}: ${s(a.tool)} [${s(a.policyClass)}]`));
|
|
56
57
|
// JSON.stringify escapes C0 controls but leaves DEL and C1 raw.
|
|
57
|
-
write(chalk.dim(s(JSON.stringify(a.arguments, null, 2))));
|
|
58
|
+
const request = () => write(chalk.dim(s(JSON.stringify(a.arguments, null, 2))));
|
|
59
|
+
if (a.preview) {
|
|
60
|
+
// A SkillsApply change reads as its diff (#782, spec §10). When the preview leaves changes
|
|
61
|
+
// out, the full arguments follow it: this prompt never runs with --json, so it is the only
|
|
62
|
+
// place the member can read what would persist (spec D7).
|
|
63
|
+
for (const line of previewLines(a.preview))
|
|
64
|
+
write(colourPreviewLine(s(line)));
|
|
65
|
+
if (previewIsIncomplete(a.preview)) {
|
|
66
|
+
write(chalk.dim("Full request (the preview leaves changes out):"));
|
|
67
|
+
request();
|
|
68
|
+
}
|
|
69
|
+
if (!destructive)
|
|
70
|
+
write(chalk.dim(REMEMBER_NOTE));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
request();
|
|
74
|
+
}
|
|
58
75
|
if (a.reason)
|
|
59
76
|
write(chalk.dim(`reason: ${s(a.reason)}`));
|
|
60
77
|
const question = destructive ? "[a]pprove / [r]eject? " : "[a]pprove / [r]eject / approve and [R]emember? ";
|
|
@@ -81,4 +98,17 @@ export async function promptApprovals(pending, ask, write = (line) => console.er
|
|
|
81
98
|
}
|
|
82
99
|
return answers;
|
|
83
100
|
}
|
|
101
|
+
/** Diff lines sit indented six spaces under their file; only those are coloured. */
|
|
102
|
+
function colourPreviewLine(line) {
|
|
103
|
+
if (!line.startsWith(" "))
|
|
104
|
+
return line;
|
|
105
|
+
const body = line.slice(6);
|
|
106
|
+
if (body.startsWith("+"))
|
|
107
|
+
return chalk.green(line);
|
|
108
|
+
if (body.startsWith("-"))
|
|
109
|
+
return chalk.red(line);
|
|
110
|
+
if (body.startsWith("@@"))
|
|
111
|
+
return chalk.cyan(line);
|
|
112
|
+
return chalk.dim(line);
|
|
113
|
+
}
|
|
84
114
|
//# sourceMappingURL=approval-prompt.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SkillsApplyPreview } from "./agent-run.js";
|
|
2
|
+
/** The grant's consequence for a SkillsApply change (spec §6, D13), shown next to "approve and Remember". */
|
|
3
|
+
export declare const REMEMBER_NOTE = "approve and Remember: later skill changes you make in this chat apply without asking";
|
|
4
|
+
/**
|
|
5
|
+
* A SkillsApply preview as plain text, one entry per line (#782, spec §10): per skill where it
|
|
6
|
+
* starts and the labels it moves, each file with its unified diff indented under it, then the
|
|
7
|
+
* followers snapshot and the binding. Lines are unstyled and unstripped; the prompt does both.
|
|
8
|
+
* The generated type marks every field optional and the wire sends nulls, so every read is guarded.
|
|
9
|
+
*/
|
|
10
|
+
export declare function previewLines(preview: SkillsApplyPreview): string[];
|
|
11
|
+
/**
|
|
12
|
+
* Whether the preview leaves part of the change unseen (#782, spec D7): a skill with files left
|
|
13
|
+
* out, a diff that was cut, or an add or edit with no diff at all (abandoned as too large, or a
|
|
14
|
+
* base the server could not read). The prompt then prints the full arguments as well, because a
|
|
15
|
+
* change that persists must be readable at the prompt. A `put` or `delete` carries no diff by
|
|
16
|
+
* design and does not count. Guarded like {@link previewLines}: every field optional, nulls on the wire.
|
|
17
|
+
*/
|
|
18
|
+
export declare function previewIsIncomplete(preview: SkillsApplyPreview): boolean;
|
|
19
|
+
//# sourceMappingURL=skills-apply-preview.d.ts.map
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/** The grant's consequence for a SkillsApply change (spec §6, D13), shown next to "approve and Remember". */
|
|
2
|
+
export const REMEMBER_NOTE = "approve and Remember: later skill changes you make in this chat apply without asking";
|
|
3
|
+
const OP_WORDS = { add: "new file", edit: "edited", delete: "deleted", put: "uploaded file" };
|
|
4
|
+
/**
|
|
5
|
+
* A SkillsApply preview as plain text, one entry per line (#782, spec §10): per skill where it
|
|
6
|
+
* starts and the labels it moves, each file with its unified diff indented under it, then the
|
|
7
|
+
* followers snapshot and the binding. Lines are unstyled and unstripped; the prompt does both.
|
|
8
|
+
* The generated type marks every field optional and the wire sends nulls, so every read is guarded.
|
|
9
|
+
*/
|
|
10
|
+
export function previewLines(preview) {
|
|
11
|
+
const lines = ["Skill changes:"];
|
|
12
|
+
for (const skill of list(preview.skills)) {
|
|
13
|
+
const name = String(skill.name ?? "");
|
|
14
|
+
const from = typeof skill.from === "number" ? skill.from : null;
|
|
15
|
+
const moves = list(skill.moves).map(String);
|
|
16
|
+
const start = from === null ? `${name}: new skill` : `${name} v${from} -> new version`;
|
|
17
|
+
lines.push(` ${start}${moves.length > 0 ? `, moves ${moves.join(", ")}` : ""}`);
|
|
18
|
+
if (typeof skill.fork_of_plugin === "string" && skill.fork_of_plugin) {
|
|
19
|
+
lines.push(` forks the plugin skill from ${skill.fork_of_plugin}: later plugin syncs no longer move latest`);
|
|
20
|
+
}
|
|
21
|
+
for (const file of list(skill.files))
|
|
22
|
+
lines.push(...fileLines(file));
|
|
23
|
+
const omitted = typeof skill.files_omitted === "number" ? skill.files_omitted : 0;
|
|
24
|
+
if (omitted > 0)
|
|
25
|
+
lines.push(` ${omitted} more ${omitted === 1 ? "file" : "files"} not shown`);
|
|
26
|
+
}
|
|
27
|
+
const followers = preview.followers;
|
|
28
|
+
if (followers) {
|
|
29
|
+
const agents = list(followers.agents).map(String);
|
|
30
|
+
const more = typeof followers.more === "number" ? followers.more : 0;
|
|
31
|
+
const when = typeof followers.as_of === "string" ? ` (as of ${followers.as_of})` : "";
|
|
32
|
+
if (agents.length === 0 && more === 0)
|
|
33
|
+
lines.push(`No agent follows these labels${when}`);
|
|
34
|
+
else
|
|
35
|
+
lines.push(`Agents following these labels${when}: ${agents.length > 0 ? agents.join(", ") : `${more} agents`}${agents.length > 0 && more > 0 ? ` and ${more} more` : ""}`);
|
|
36
|
+
}
|
|
37
|
+
const agent = preview.agent;
|
|
38
|
+
const bind = agent ? list(agent.bind).map(String) : [];
|
|
39
|
+
if (agent && bind.length > 0) {
|
|
40
|
+
const base = typeof agent.from === "number" ? ` (new agent version from v${agent.from})` : "";
|
|
41
|
+
lines.push(`Binds ${bind.join(", ")} to this agent${base}`);
|
|
42
|
+
}
|
|
43
|
+
return lines;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Whether the preview leaves part of the change unseen (#782, spec D7): a skill with files left
|
|
47
|
+
* out, a diff that was cut, or an add or edit with no diff at all (abandoned as too large, or a
|
|
48
|
+
* base the server could not read). The prompt then prints the full arguments as well, because a
|
|
49
|
+
* change that persists must be readable at the prompt. A `put` or `delete` carries no diff by
|
|
50
|
+
* design and does not count. Guarded like {@link previewLines}: every field optional, nulls on the wire.
|
|
51
|
+
*/
|
|
52
|
+
export function previewIsIncomplete(preview) {
|
|
53
|
+
for (const skill of list(preview.skills)) {
|
|
54
|
+
if (typeof skill.files_omitted === "number" && skill.files_omitted > 0)
|
|
55
|
+
return true;
|
|
56
|
+
for (const file of list(skill.files)) {
|
|
57
|
+
if (file.truncated === true)
|
|
58
|
+
return true;
|
|
59
|
+
if ((file.op === "add" || file.op === "edit") && typeof file.diff !== "string")
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
function fileLines(file) {
|
|
66
|
+
const facts = [OP_WORDS[String(file.op)] ?? String(file.op)];
|
|
67
|
+
if (typeof file.added === "number" && typeof file.removed === "number")
|
|
68
|
+
facts.push(`+${file.added} -${file.removed}`);
|
|
69
|
+
if (typeof file.bytes === "number")
|
|
70
|
+
facts.push(size(file.bytes));
|
|
71
|
+
const lines = [` ${String(file.path ?? "")}: ${facts.join(", ")}`];
|
|
72
|
+
const diff = typeof file.diff === "string" ? file.diff : null;
|
|
73
|
+
if (diff !== null) {
|
|
74
|
+
for (const line of diff.split("\n"))
|
|
75
|
+
lines.push(` ${line}`);
|
|
76
|
+
if (file.truncated === true)
|
|
77
|
+
lines.push(" (diff cut; the counts cover every changed line)");
|
|
78
|
+
}
|
|
79
|
+
else if (file.truncated === true) {
|
|
80
|
+
lines.push(` too many changes to show: at most +${Number(file.added ?? 0)} -${Number(file.removed ?? 0)} lines`);
|
|
81
|
+
}
|
|
82
|
+
return lines;
|
|
83
|
+
}
|
|
84
|
+
function list(value) {
|
|
85
|
+
return Array.isArray(value) ? value.filter((entry) => entry !== null && entry !== undefined) : [];
|
|
86
|
+
}
|
|
87
|
+
function size(bytes) {
|
|
88
|
+
if (bytes < 1024)
|
|
89
|
+
return `${bytes} B`;
|
|
90
|
+
if (bytes < 1024 * 1024)
|
|
91
|
+
return `${Math.round(bytes / 1024)} KB`;
|
|
92
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=skills-apply-preview.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@2kw/ai",
|
|
3
|
-
"version": "6.3.0-dev.
|
|
3
|
+
"version": "6.3.0-dev.40",
|
|
4
4
|
"description": "CLI for 2kw.ai — schema-driven document extraction, an OpenAI-compatible EU LLM gateway, transcription, prompts, datasets, and experiments from your terminal or agentic workflows. Ships as 2kw, backbone, and bb.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cli",
|