@2kw/ai 6.2.0-dev.23 → 6.2.0-dev.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.
Files changed (39) hide show
  1. package/README.md +2 -2
  2. package/dist/agent-config/export.d.ts +31 -0
  3. package/dist/agent-config/export.js +87 -0
  4. package/dist/agent-config/file.d.ts +20 -0
  5. package/dist/agent-config/file.js +137 -0
  6. package/dist/agent-config/plan.d.ts +33 -0
  7. package/dist/agent-config/plan.js +100 -0
  8. package/dist/agent-config/schema.d.ts +420 -0
  9. package/dist/agent-config/schema.js +222 -0
  10. package/dist/agent-config/template.d.ts +6 -0
  11. package/dist/agent-config/template.js +59 -0
  12. package/dist/agent-config/validate.d.ts +29 -0
  13. package/dist/agent-config/validate.js +123 -0
  14. package/dist/commands/agent-apply.d.ts +9 -0
  15. package/dist/commands/agent-apply.js +176 -0
  16. package/dist/commands/agent-export.d.ts +3 -0
  17. package/dist/commands/agent-export.js +100 -0
  18. package/dist/commands/agent-init.d.ts +4 -0
  19. package/dist/commands/agent-init.js +54 -0
  20. package/dist/commands/agent-run.d.ts +5 -0
  21. package/dist/commands/agent-run.js +190 -0
  22. package/dist/commands/agent-versions.js +12 -7
  23. package/dist/commands/agents.js +24 -9
  24. package/dist/commands/ai.d.ts +5 -37
  25. package/dist/commands/ai.js +29 -136
  26. package/dist/lib/agent-decide.d.ts +41 -0
  27. package/dist/lib/agent-decide.js +129 -0
  28. package/dist/lib/agent-lookup.d.ts +15 -0
  29. package/dist/lib/agent-lookup.js +71 -0
  30. package/dist/lib/agent-models.d.ts +28 -0
  31. package/dist/lib/agent-models.js +53 -0
  32. package/dist/lib/agent-run.d.ts +83 -0
  33. package/dist/lib/agent-run.js +170 -0
  34. package/dist/lib/approval-prompt.d.ts +18 -0
  35. package/dist/lib/approval-prompt.js +84 -0
  36. package/dist/lib/client.js +6 -2
  37. package/dist/lib/errors.d.ts +12 -0
  38. package/dist/lib/errors.js +46 -2
  39. package/package.json +5 -2
@@ -0,0 +1,41 @@
1
+ import type { components } from "../generated/openapi.js";
2
+ import type { ApiClient } from "./agent-lookup.js";
3
+ import type { ResponsesResult } from "./agent-run.js";
4
+ export type ApprovalRow = components["schemas"]["ToolApprovalDTO"];
5
+ export interface DecideOptions {
6
+ approve?: string[];
7
+ reject?: string[];
8
+ approveAll?: boolean;
9
+ rejectAll?: boolean;
10
+ reason?: string;
11
+ remember?: boolean;
12
+ }
13
+ export interface Decision {
14
+ approvalId: string;
15
+ decision: "approve" | "reject";
16
+ hmac: string;
17
+ reason?: string;
18
+ remember?: "conversation";
19
+ }
20
+ export interface PromptAnswer {
21
+ decision: "approve" | "reject";
22
+ reason?: string;
23
+ remember?: boolean;
24
+ }
25
+ export declare function fetchPendingApprovals(client: ApiClient, agentId: string, responseId: string): Promise<ApprovalRow[]>;
26
+ /**
27
+ * The backend refuses a continuation that leaves any pending approval of the paused
28
+ * response undecided (400 incomplete_tool_outputs), so the whole set is checked here first.
29
+ */
30
+ export declare function planDecisions(pending: ApprovalRow[], opts: DecideOptions, responseId: string): Decision[];
31
+ export declare function decisionsFromAnswers(pending: ApprovalRow[], answers: Map<string, PromptAnswer>): Decision[];
32
+ /** Same item shape as n8n Decide Approval (#660). */
33
+ export declare function toApprovalItems(decisions: Decision[]): Record<string, string>[];
34
+ /**
35
+ * Continues on `agent/<agentId>[@<label>][#<model>]` with the label and model override the run used: the
36
+ * server resolves the continuation's tools, instructions and model from this reference, so both must match
37
+ * the run's or the continuation drops to another version or the agent's default model.
38
+ * Never the echoed `name@<versionNumber>` (it would 404 as a label).
39
+ */
40
+ export declare function continueWithDecisions(client: ApiClient, agentId: string, responseId: string, decisions: Decision[], label?: string, model?: string): Promise<ResponsesResult>;
41
+ //# sourceMappingURL=agent-decide.d.ts.map
@@ -0,0 +1,129 @@
1
+ import { CliUsageError } from "./errors.js";
2
+ import { paginationParams } from "./pagination.js";
3
+ const MAX_PAGES = 50;
4
+ export async function fetchPendingApprovals(client, agentId, responseId) {
5
+ const rows = [];
6
+ for (let page = 0; page < MAX_PAGES; page++) {
7
+ const { data } = await client.GET("/v1/agents/{agentId}/approvals", {
8
+ params: {
9
+ path: { agentId },
10
+ query: { status: "pending", ...paginationParams({ page: String(page), size: "100" }) },
11
+ },
12
+ });
13
+ const content = data?.content ?? [];
14
+ rows.push(...content.filter((r) => r.responseId === responseId));
15
+ if (data?.last !== false || content.length === 0)
16
+ break;
17
+ }
18
+ return rows;
19
+ }
20
+ function isDestructive(row) {
21
+ return (row.policyClass ?? "").toLowerCase() === "destructive";
22
+ }
23
+ function hmacOf(row) {
24
+ if (!row.hmac)
25
+ throw new CliUsageError(`Approval ${row.id} carries no hmac and cannot be decided from the CLI.`);
26
+ return row.hmac;
27
+ }
28
+ /**
29
+ * The backend refuses a continuation that leaves any pending approval of the paused
30
+ * response undecided (400 incomplete_tool_outputs), so the whole set is checked here first.
31
+ */
32
+ export function planDecisions(pending, opts, responseId) {
33
+ if (pending.length === 0) {
34
+ throw new CliUsageError(`No pending approvals for ${responseId} (already decided or superseded).`);
35
+ }
36
+ const approve = opts.approve ?? [];
37
+ const reject = opts.reject ?? [];
38
+ const usesAll = !!opts.approveAll || !!opts.rejectAll;
39
+ if ((opts.approveAll && opts.rejectAll) || (usesAll && (approve.length > 0 || reject.length > 0))) {
40
+ throw new CliUsageError("Use either --approve-all / --reject-all or explicit --approve / --reject ids, not both.");
41
+ }
42
+ if (!usesAll && approve.length === 0 && reject.length === 0) {
43
+ throw new CliUsageError("Choose a decision: --approve <ids...>, --reject <ids...>, --approve-all or --reject-all.");
44
+ }
45
+ const choice = new Map();
46
+ if (usesAll) {
47
+ for (const r of pending)
48
+ choice.set(String(r.id), opts.approveAll ? "approve" : "reject");
49
+ }
50
+ else {
51
+ const seen = new Set();
52
+ const twice = new Set();
53
+ for (const [ids, d] of [[approve, "approve"], [reject, "reject"]]) {
54
+ for (const id of ids) {
55
+ if (seen.has(id))
56
+ twice.add(id);
57
+ seen.add(id);
58
+ choice.set(id, d);
59
+ }
60
+ }
61
+ if (twice.size)
62
+ throw new CliUsageError(`Approvals decided more than once: ${[...twice].join(", ")}`);
63
+ const pendingIds = new Set(pending.map((r) => String(r.id)));
64
+ const foreign = [...seen].filter((id) => !pendingIds.has(id));
65
+ if (foreign.length)
66
+ throw new CliUsageError(`Not pending on ${responseId}: ${foreign.join(", ")}`);
67
+ const undecided = [...pendingIds].filter((id) => !seen.has(id));
68
+ if (undecided.length) {
69
+ throw new CliUsageError(`Every pending approval of a response must be decided in one call. Undecided: ${undecided.join(", ")}`);
70
+ }
71
+ }
72
+ if (opts.remember && pending.some((r) => choice.get(String(r.id)) === "approve" && isDestructive(r))) {
73
+ throw new CliUsageError("--remember cannot be used on a destructive tool call.");
74
+ }
75
+ return pending.map((r) => {
76
+ const decision = choice.get(String(r.id));
77
+ return {
78
+ approvalId: String(r.id),
79
+ decision,
80
+ hmac: hmacOf(r),
81
+ ...(opts.reason ? { reason: opts.reason } : {}),
82
+ ...(opts.remember && decision === "approve" ? { remember: "conversation" } : {}),
83
+ };
84
+ });
85
+ }
86
+ export function decisionsFromAnswers(pending, answers) {
87
+ return pending.map((r) => {
88
+ const answer = answers.get(String(r.id));
89
+ if (!answer)
90
+ throw new CliUsageError(`No answer for approval ${r.id}.`);
91
+ return {
92
+ approvalId: String(r.id),
93
+ decision: answer.decision,
94
+ hmac: hmacOf(r),
95
+ ...(answer.reason ? { reason: answer.reason } : {}),
96
+ ...(answer.remember && answer.decision === "approve" && !isDestructive(r) ? { remember: "conversation" } : {}),
97
+ };
98
+ });
99
+ }
100
+ /** Same item shape as n8n Decide Approval (#660). */
101
+ export function toApprovalItems(decisions) {
102
+ return decisions.map((d) => ({
103
+ type: "backbone:approval_response",
104
+ approval_id: d.approvalId,
105
+ decision: d.decision,
106
+ hmac: d.hmac,
107
+ ...(d.reason ? { reason: d.reason } : {}),
108
+ ...(d.remember ? { remember: d.remember } : {}),
109
+ }));
110
+ }
111
+ /**
112
+ * Continues on `agent/<agentId>[@<label>][#<model>]` with the label and model override the run used: the
113
+ * server resolves the continuation's tools, instructions and model from this reference, so both must match
114
+ * the run's or the continuation drops to another version or the agent's default model.
115
+ * Never the echoed `name@<versionNumber>` (it would 404 as a label).
116
+ */
117
+ export async function continueWithDecisions(client, agentId, responseId, decisions, label, model) {
118
+ const body = {
119
+ model: `agent/${agentId}${label ? `@${label}` : ""}${model ? `#${model}` : ""}`,
120
+ previous_response_id: responseId,
121
+ stream: false,
122
+ input: toApprovalItems(decisions),
123
+ };
124
+ // The generated body type does not model backbone: extension items, hence the cast.
125
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
126
+ const { data } = await client.POST("/v1/responses", { body });
127
+ return data;
128
+ }
129
+ //# sourceMappingURL=agent-decide.js.map
@@ -0,0 +1,15 @@
1
+ import type { components } from "../generated/openapi.js";
2
+ import type { getClient } from "./client.js";
3
+ export type ApiClient = ReturnType<typeof getClient>;
4
+ export type AgentDTO = components["schemas"]["AgentDTO"];
5
+ /** `name[@label][#model]`, split like the server: the model at the last `#`, then the label at the last `@`. */
6
+ export declare function splitAgentRef(ref: string): {
7
+ name: string;
8
+ label: string | undefined;
9
+ model: string | undefined;
10
+ };
11
+ /** Exact, case-sensitive name match. The REST API only offers a substring `search`. */
12
+ export declare function findAgentByName(client: ApiClient, name: string): Promise<AgentDTO | undefined>;
13
+ export declare function suggestAgents(client: ApiClient, name: string, limit?: number): Promise<string[]>;
14
+ export declare function resolveAgent(client: ApiClient, ref: string): Promise<AgentDTO>;
15
+ //# sourceMappingURL=agent-lookup.d.ts.map
@@ -0,0 +1,71 @@
1
+ import { BackboneApiError, CliUsageError } from "./errors.js";
2
+ import { paginationParams } from "./pagination.js";
3
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
+ const PAGE_SIZE = 100;
5
+ const MAX_PAGES = 50;
6
+ /** `name[@label][#model]`, split like the server: the model at the last `#`, then the label at the last `@`. */
7
+ export function splitAgentRef(ref) {
8
+ const hash = ref.lastIndexOf("#");
9
+ const model = hash >= 0 ? ref.slice(hash + 1) : undefined;
10
+ const base = hash >= 0 ? ref.slice(0, hash) : ref;
11
+ const at = base.lastIndexOf("@");
12
+ if (at <= 0)
13
+ return { name: base, label: undefined, model };
14
+ return { name: base.slice(0, at), label: base.slice(at + 1), model };
15
+ }
16
+ /** Exact, case-sensitive name match. The REST API only offers a substring `search`. */
17
+ export async function findAgentByName(client, name) {
18
+ for (let page = 0; page < MAX_PAGES; page++) {
19
+ const { data } = await client.GET("/v1/agents", {
20
+ // A stable sort: the controller has none by default, and offset paging over
21
+ // an unordered result can skip rows between pages.
22
+ params: {
23
+ query: {
24
+ search: name,
25
+ ...paginationParams({ page: String(page), size: String(PAGE_SIZE), sort: ["id,asc"] }),
26
+ },
27
+ },
28
+ });
29
+ const content = data?.content ?? [];
30
+ const hit = content.find((a) => a.name === name);
31
+ if (hit)
32
+ return hit;
33
+ if (data?.last !== false || content.length === 0)
34
+ return undefined;
35
+ }
36
+ return undefined;
37
+ }
38
+ export async function suggestAgents(client, name, limit = 5) {
39
+ try {
40
+ const { data } = await client.GET("/v1/agents", {
41
+ params: { query: { search: name, ...paginationParams({ page: "0", size: String(limit) }) } },
42
+ });
43
+ return (data?.content ?? []).map((a) => a.name).slice(0, limit);
44
+ }
45
+ catch {
46
+ return [];
47
+ }
48
+ }
49
+ export async function resolveAgent(client, ref) {
50
+ if (UUID.test(ref)) {
51
+ try {
52
+ const { data } = await client.GET("/v1/agents/{id}", { params: { path: { id: ref } } });
53
+ if (data)
54
+ return data;
55
+ }
56
+ catch (err) {
57
+ // An unknown id is a usage error like an unknown name; anything else is a real failure.
58
+ if (!(err instanceof BackboneApiError && err.status === 404))
59
+ throw err;
60
+ }
61
+ }
62
+ else {
63
+ const found = await findAgentByName(client, ref);
64
+ if (found)
65
+ return found;
66
+ }
67
+ const suggestions = await suggestAgents(client, ref);
68
+ const hint = suggestions.length ? ` Did you mean: ${suggestions.join(", ")}` : "";
69
+ throw new CliUsageError(`Agent '${ref}' not found.${hint}`);
70
+ }
71
+ //# sourceMappingURL=agent-lookup.js.map
@@ -0,0 +1,28 @@
1
+ /**
2
+ * The ordered model list of an agent or agent version (#591): the first entry is the default,
3
+ * the rest are the models a request may switch to with `agent/<ref>#<model>`. The backend owns
4
+ * the write rules (1-10 entries, unique, each configured for the org); the CLI only turns flags
5
+ * into the body fields. Refs: #626.
6
+ */
7
+ export declare const MODEL_HELP = "Model (provider/model or a platform model); repeat for an ordered list, the first is the default";
8
+ export declare const MODELS_HELP = "Ordered model list, comma-separated (e.g. openai/gpt-4o,gpt-4.1-mini); the first is the default";
9
+ /** Commander collector for a repeatable `-m, --model <model>` flag. */
10
+ export declare function collectModel(value: string, previous: string[] | undefined): string[];
11
+ /**
12
+ * The `model` / `models` body fields from `--model` (repeatable) and `--models a,b`. One `--model`
13
+ * stays the scalar shorthand; a repeated `--model` or any `--models` sends the ordered list.
14
+ * Returns `{}` when neither flag was given; `required` turns that into a usage error.
15
+ */
16
+ export declare function modelFieldsFromOptions(opts: {
17
+ model?: string[] | string;
18
+ models?: string;
19
+ }, required: boolean): {
20
+ model?: string;
21
+ models?: string[];
22
+ };
23
+ /**
24
+ * An agent reference with a per-request model switch: `ref[@label]#<model>`. The model must be one
25
+ * of the resolved version's `models`; the server answers 400 otherwise.
26
+ */
27
+ export declare function withModelOverride(agentRef: string, model: string | undefined): string;
28
+ //# sourceMappingURL=agent-models.d.ts.map
@@ -0,0 +1,53 @@
1
+ import { CliUsageError } from "./errors.js";
2
+ /**
3
+ * The ordered model list of an agent or agent version (#591): the first entry is the default,
4
+ * the rest are the models a request may switch to with `agent/<ref>#<model>`. The backend owns
5
+ * the write rules (1-10 entries, unique, each configured for the org); the CLI only turns flags
6
+ * into the body fields. Refs: #626.
7
+ */
8
+ export const MODEL_HELP = "Model (provider/model or a platform model); repeat for an ordered list, the first is the default";
9
+ export const MODELS_HELP = "Ordered model list, comma-separated (e.g. openai/gpt-4o,gpt-4.1-mini); the first is the default";
10
+ /** Commander collector for a repeatable `-m, --model <model>` flag. */
11
+ export function collectModel(value, previous) {
12
+ return [...(previous ?? []), value];
13
+ }
14
+ /**
15
+ * The `model` / `models` body fields from `--model` (repeatable) and `--models a,b`. One `--model`
16
+ * stays the scalar shorthand; a repeated `--model` or any `--models` sends the ordered list.
17
+ * Returns `{}` when neither flag was given; `required` turns that into a usage error.
18
+ */
19
+ export function modelFieldsFromOptions(opts, required) {
20
+ const repeated = opts.model === undefined ? [] : Array.isArray(opts.model) ? opts.model : [opts.model];
21
+ if (opts.models !== undefined && repeated.length > 0) {
22
+ throw new CliUsageError("Use either --model (repeatable) or --models <a,b,...>, not both.");
23
+ }
24
+ if (opts.models !== undefined) {
25
+ const list = opts.models.split(",").map((m) => m.trim());
26
+ if (list.length === 0 || list.some((m) => m === "")) {
27
+ throw new CliUsageError(`--models needs a comma-separated list of model ids, got '${opts.models}'.`);
28
+ }
29
+ return { models: list };
30
+ }
31
+ if (repeated.length === 1)
32
+ return { model: repeated[0] };
33
+ if (repeated.length > 1)
34
+ return { models: repeated };
35
+ if (required)
36
+ throw new CliUsageError("A model is required: pass --model <model> (repeatable) or --models <a,b,...>.");
37
+ return {};
38
+ }
39
+ /**
40
+ * An agent reference with a per-request model switch: `ref[@label]#<model>`. The model must be one
41
+ * of the resolved version's `models`; the server answers 400 otherwise.
42
+ */
43
+ export function withModelOverride(agentRef, model) {
44
+ if (model === undefined)
45
+ return agentRef;
46
+ if (model.trim() === "")
47
+ throw new CliUsageError("--model needs a model id.");
48
+ if (agentRef.includes("#")) {
49
+ throw new CliUsageError(`Give the model switch once: '${agentRef}' already names one after '#'; drop --model or the suffix.`);
50
+ }
51
+ return `${agentRef}#${model}`;
52
+ }
53
+ //# sourceMappingURL=agent-models.js.map
@@ -0,0 +1,83 @@
1
+ type AnyRecord = Record<string, any>;
2
+ export type RunStatus = "completed" | "requires_approval" | "requires_tool_output" | "incomplete";
3
+ export interface PendingApproval {
4
+ approvalId: string;
5
+ callId: string;
6
+ tool: string;
7
+ arguments: unknown;
8
+ policyClass: string;
9
+ /** Why the automatic approver escalated this request to a human (#634); null when it did not. */
10
+ reason: string | null;
11
+ }
12
+ export interface ToolCallSummary {
13
+ tool: string;
14
+ callId: string;
15
+ /** `incomplete` when the server-side tool run failed (its `function_call_output` carries that status). */
16
+ status: "completed" | "incomplete";
17
+ }
18
+ export interface PendingToolCall {
19
+ tool: string;
20
+ callId: string;
21
+ arguments: unknown;
22
+ }
23
+ export interface RunEnvelope {
24
+ status: RunStatus;
25
+ /** Reserved for the server-side conversation mode (#656); always null until then. */
26
+ mode: null;
27
+ agent: string | null;
28
+ version: number | null;
29
+ responseId: string | null;
30
+ conversationId: string | null;
31
+ text: string;
32
+ toolCalls: ToolCallSummary[];
33
+ pendingApprovals: PendingApproval[];
34
+ pendingToolCalls: PendingToolCall[];
35
+ incompleteReason: string | null;
36
+ usage: {
37
+ inputTokens: number;
38
+ outputTokens: number;
39
+ } | null;
40
+ next: string | null;
41
+ }
42
+ /** Loose wire shape of a `POST /v1/responses` result; the OpenAPI spec types it as `object`. */
43
+ export interface ResponsesResult {
44
+ id?: string;
45
+ status?: string;
46
+ model?: string;
47
+ output?: AnyRecord[];
48
+ usage?: {
49
+ input_tokens?: number;
50
+ output_tokens?: number;
51
+ total_tokens?: number;
52
+ };
53
+ conversation?: {
54
+ id?: string;
55
+ } | null;
56
+ incomplete_details?: {
57
+ reason?: string;
58
+ } | null;
59
+ }
60
+ export declare const EXIT_CODES: Record<RunStatus, number>;
61
+ /** The assistant's text: every `output_text` part of every `message` item, in order. */
62
+ export declare function extractResponseText(result: ResponsesResult | undefined): string;
63
+ /**
64
+ * The response echoes `model` as `agent/{name}@{versionNumber}` (all digits), optionally
65
+ * followed by a `#<model>` override. Only a trailing all-digit `@N` is read as a version;
66
+ * any other `@` suffix stays part of the name.
67
+ */
68
+ export declare function parseAgentModel(model?: string): {
69
+ agent: string | null;
70
+ version: number | null;
71
+ };
72
+ /**
73
+ * Removes terminal control characters from model-, judge- or server-controlled text before it is
74
+ * printed, so an escape sequence in an answer cannot hide text, rewrite the screen or set the
75
+ * clipboard. Newlines and tabs stay. JSON output is never passed through this.
76
+ */
77
+ export declare function stripControl(s: string): string;
78
+ export declare function shellQuote(value: string): string;
79
+ export declare function buildRunEnvelope(result: ResponsesResult | undefined, agentRef?: string): RunEnvelope;
80
+ /** Human-readable rendering of an envelope on stdout. Every value printed is passed through {@link stripControl}. */
81
+ export declare function printRunText(env: RunEnvelope): void;
82
+ export {};
83
+ //# sourceMappingURL=agent-run.d.ts.map
@@ -0,0 +1,170 @@
1
+ import chalk from "chalk";
2
+ export const EXIT_CODES = {
3
+ completed: 0,
4
+ requires_approval: 3,
5
+ requires_tool_output: 4,
6
+ incomplete: 5,
7
+ };
8
+ /** The assistant's text: every `output_text` part of every `message` item, in order. */
9
+ export function extractResponseText(result) {
10
+ const parts = [];
11
+ for (const item of result?.output ?? []) {
12
+ if (item.type !== "message")
13
+ continue;
14
+ for (const part of item.content ?? []) {
15
+ if (part.type === "output_text" && part.text)
16
+ parts.push(part.text);
17
+ }
18
+ }
19
+ return parts.join("\n");
20
+ }
21
+ /**
22
+ * The response echoes `model` as `agent/{name}@{versionNumber}` (all digits), optionally
23
+ * followed by a `#<model>` override. Only a trailing all-digit `@N` is read as a version;
24
+ * any other `@` suffix stays part of the name.
25
+ */
26
+ export function parseAgentModel(model) {
27
+ // Strip the `#<model>` override (from the last `#`, as the server splits it) before matching.
28
+ const hash = (model ?? "").lastIndexOf("#");
29
+ const ref = hash >= 0 ? (model ?? "").slice(0, hash) : model ?? "";
30
+ const withVersion = /^agent\/(.+)@(\d+)$/.exec(ref);
31
+ if (withVersion)
32
+ return { agent: withVersion[1], version: Number(withVersion[2]) };
33
+ const bare = /^agent\/(.+)$/.exec(ref);
34
+ if (bare)
35
+ return { agent: bare[1], version: null };
36
+ return { agent: null, version: null };
37
+ }
38
+ function parseArguments(raw) {
39
+ if (typeof raw !== "string")
40
+ return raw ?? {};
41
+ try {
42
+ return JSON.parse(raw);
43
+ }
44
+ catch {
45
+ return raw;
46
+ }
47
+ }
48
+ /** C0 controls except tab (\x09) and newline (\x0a), DEL, and C1 controls. */
49
+ const CONTROL_CHARS = /[\x00-\x08\x0b-\x1f\x7f-\x9f]/g;
50
+ /**
51
+ * Removes terminal control characters from model-, judge- or server-controlled text before it is
52
+ * printed, so an escape sequence in an answer cannot hide text, rewrite the screen or set the
53
+ * clipboard. Newlines and tabs stay. JSON output is never passed through this.
54
+ */
55
+ export function stripControl(s) {
56
+ return s.replace(CONTROL_CHARS, "");
57
+ }
58
+ export function shellQuote(value) {
59
+ if (/^[A-Za-z0-9._@/:-]+$/.test(value))
60
+ return value;
61
+ return `'${value.replace(/'/g, `'\\''`)}'`;
62
+ }
63
+ export function buildRunEnvelope(result, agentRef) {
64
+ const output = result?.output ?? [];
65
+ const { agent, version } = parseAgentModel(result?.model);
66
+ // call_id → status of its tool output; a failed server-side tool run is marked `incomplete`.
67
+ const outputStatuses = new Map(output
68
+ .filter((i) => i.type === "function_call_output")
69
+ .map((i) => [
70
+ String(i.call_id),
71
+ i.status === "incomplete" ? "incomplete" : "completed",
72
+ ]));
73
+ const pendingApprovals = output
74
+ // Only an open request is pending; judge- or grant-decided requests (#634, #629) carry another status.
75
+ .filter((i) => i.type === "backbone:approval_request" && (i.status === undefined || i.status === "in_progress"))
76
+ .map((i) => ({
77
+ approvalId: String(i.id),
78
+ callId: String(i.call_id),
79
+ tool: String(i.tool),
80
+ arguments: parseArguments(i.arguments),
81
+ policyClass: String(i.policy_class),
82
+ reason: typeof i.reason === "string" && i.reason ? i.reason : null,
83
+ }));
84
+ const withheldCallIds = new Set(pendingApprovals.map((a) => a.callId));
85
+ const toolCalls = [];
86
+ const pendingToolCalls = [];
87
+ for (const item of output) {
88
+ if (item.type !== "function_call")
89
+ continue;
90
+ const callId = String(item.call_id);
91
+ const outputStatus = outputStatuses.get(callId);
92
+ if (outputStatus) {
93
+ toolCalls.push({ tool: String(item.name), callId, status: outputStatus });
94
+ }
95
+ else if (!withheldCallIds.has(callId)) {
96
+ pendingToolCalls.push({ tool: String(item.name), callId, arguments: parseArguments(item.arguments) });
97
+ }
98
+ }
99
+ let status;
100
+ switch (result?.status) {
101
+ case "completed":
102
+ status = "completed";
103
+ break;
104
+ case "incomplete":
105
+ status = "incomplete";
106
+ break;
107
+ case "requires_action":
108
+ status = pendingToolCalls.length > 0 || pendingApprovals.length === 0 ? "requires_tool_output" : "requires_approval";
109
+ break;
110
+ default:
111
+ throw new Error(`Unexpected response status: ${result?.status}`);
112
+ }
113
+ const responseId = result?.id ?? null;
114
+ const ref = agentRef ?? agent;
115
+ const next = status === "requires_approval" && ref && responseId
116
+ ? `2kw agents decide ${shellQuote(ref)} --response ${responseId} --approve-all`
117
+ : null;
118
+ return {
119
+ status,
120
+ mode: null,
121
+ agent,
122
+ version,
123
+ responseId,
124
+ conversationId: result?.conversation?.id ?? null,
125
+ text: extractResponseText(result),
126
+ toolCalls,
127
+ pendingApprovals,
128
+ pendingToolCalls,
129
+ incompleteReason: result?.incomplete_details?.reason ?? null,
130
+ usage: result?.usage
131
+ ? { inputTokens: result.usage.input_tokens ?? 0, outputTokens: result.usage.output_tokens ?? 0 }
132
+ : null,
133
+ next,
134
+ };
135
+ }
136
+ /** Human-readable rendering of an envelope on stdout. Every value printed is passed through {@link stripControl}. */
137
+ export function printRunText(env) {
138
+ const s = stripControl;
139
+ if (env.text)
140
+ console.log(s(env.text));
141
+ const head = [
142
+ env.agent ? `${env.agent}${env.version !== null ? `@${env.version}` : ""}` : null,
143
+ env.responseId,
144
+ env.conversationId,
145
+ ].filter(Boolean);
146
+ if (head.length)
147
+ console.log(chalk.dim(`\n── ${s(head.join(" · "))}`));
148
+ if (env.toolCalls.length)
149
+ console.log(chalk.dim(` tools: ${env.toolCalls.map((c) => `${s(c.tool)} ${c.status === "incomplete" ? "✗" : "✓"}`).join(" ")}`));
150
+ if (env.status === "requires_approval") {
151
+ console.log(chalk.yellow("\nPaused for approval:"));
152
+ env.pendingApprovals.forEach((a, i) => {
153
+ console.log(` ${i + 1}. ${s(a.tool)} [${s(a.policyClass)}] ${s(a.approvalId)}`);
154
+ // JSON.stringify escapes C0 controls but leaves DEL and C1 raw.
155
+ console.log(chalk.dim(` ${s(JSON.stringify(a.arguments))}`));
156
+ if (a.reason)
157
+ console.log(chalk.dim(` reason: ${s(a.reason)}`));
158
+ });
159
+ if (env.next)
160
+ console.log(`\nDecide with:\n ${s(env.next)}`);
161
+ }
162
+ else if (env.status === "requires_tool_output") {
163
+ console.log(chalk.yellow("\nPaused: the agent needs client-side tool output this CLI cannot provide:"));
164
+ env.pendingToolCalls.forEach((c) => console.log(` - ${s(c.tool)} (${s(c.callId)})`));
165
+ }
166
+ else if (env.status === "incomplete") {
167
+ console.log(chalk.yellow(`\nIncomplete: ${s(env.incompleteReason ?? "unknown reason")}`));
168
+ }
169
+ }
170
+ //# sourceMappingURL=agent-run.js.map
@@ -0,0 +1,18 @@
1
+ import type { PromptAnswer } from "./agent-decide.js";
2
+ import { type PendingApproval } from "./agent-run.js";
3
+ export type Ask = (question: string) => Promise<string>;
4
+ /** Raised when the prompt's input closes (EOF, Ctrl+C, Ctrl+D) before an answer arrives. */
5
+ export declare class PromptAbortedError extends Error {
6
+ constructor();
7
+ }
8
+ /**
9
+ * Prompts go to stderr so stdout stays the agent's answer. A closed input never
10
+ * leaves a question hanging: every pending and later `ask` rejects with
11
+ * {@link PromptAbortedError}.
12
+ */
13
+ export declare function readlineAsk(input?: NodeJS.ReadableStream, output?: NodeJS.WritableStream): {
14
+ ask: Ask;
15
+ close: () => void;
16
+ };
17
+ export declare function promptApprovals(pending: PendingApproval[], ask: Ask, write?: (line: string) => void): Promise<Map<string, PromptAnswer>>;
18
+ //# sourceMappingURL=approval-prompt.d.ts.map