@2kw/ai 6.2.2 → 6.3.0-dev.105
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 +3 -0
- package/dist/agent-config/schema.d.ts +6 -3
- package/dist/agent-config/schema.js +13 -4
- package/dist/agent-config/template.js +1 -1
- package/dist/commands/agent-apply.js +1 -1
- package/dist/commands/agent-policy.d.ts +2 -1
- package/dist/commands/agent-policy.js +9 -2
- package/dist/commands/agent-run.d.ts +5 -0
- package/dist/commands/agent-run.js +37 -17
- package/dist/commands/agents.js +33 -6
- package/dist/commands/ai.js +3 -9
- package/dist/commands/auth.js +6 -1
- package/dist/commands/billing.js +5 -3
- package/dist/commands/config.d.ts +1 -1
- package/dist/commands/config.js +16 -2
- package/dist/commands/connectors.d.ts +10 -0
- package/dist/commands/connectors.js +97 -0
- package/dist/commands/conversations.js +16 -0
- package/dist/commands/datasets.js +17 -15
- package/dist/commands/experiments.js +36 -26
- package/dist/commands/files.js +10 -28
- package/dist/commands/installations.js +1 -1
- package/dist/commands/knowledge-documents.js +24 -32
- package/dist/commands/memory.d.ts +10 -0
- package/dist/commands/memory.js +132 -0
- package/dist/commands/prompts.js +9 -8
- package/dist/commands/relays.d.ts +3 -0
- package/dist/commands/relays.js +135 -0
- package/dist/commands/schemas.js +15 -6
- package/dist/commands/settings.d.ts +13 -0
- package/dist/commands/settings.js +80 -0
- package/dist/commands/skill-versions.js +27 -1
- package/dist/commands/skills.js +31 -1
- package/dist/commands/tracing.js +5 -12
- package/dist/index.js +8 -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 +49 -3
- package/dist/lib/agent-run.js +152 -9
- package/dist/lib/approval-prompt.js +31 -1
- package/dist/lib/client.d.ts +8 -0
- package/dist/lib/client.js +20 -1
- package/dist/lib/config.d.ts +16 -0
- package/dist/lib/config.js +40 -1
- package/dist/lib/errors.d.ts +6 -0
- package/dist/lib/errors.js +9 -0
- package/dist/lib/overlay.d.ts +10 -0
- package/dist/lib/overlay.js +20 -0
- package/dist/lib/skills-apply-preview.d.ts +19 -0
- package/dist/lib/skills-apply-preview.js +94 -0
- package/dist/lib/tracing-settings.d.ts +26 -0
- package/dist/lib/tracing-settings.js +25 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,6 +88,8 @@ kubectl-style contexts switch between organizations and environments:
|
|
|
88
88
|
2kw context create staging # Create a new context
|
|
89
89
|
```
|
|
90
90
|
|
|
91
|
+
`2kw config set memory true` makes an API-key context send `X-Backbone-Memory: enabled` on `ai respond` and `agents run` calls, which opts those runs into agent memory; `AI_2KW_MEMORY=true|false` overrides it per shell. Runs from a browser-login (session) context are always eligible for agent memory regardless of this setting, so `memory false` only affects API-key contexts.
|
|
92
|
+
|
|
91
93
|
## Commands
|
|
92
94
|
|
|
93
95
|
| Command | Description |
|
|
@@ -106,6 +108,7 @@ kubectl-style contexts switch between organizations and environments:
|
|
|
106
108
|
| `evaluators` | Manage evaluator templates |
|
|
107
109
|
| `scores` | Record and inspect quality scores |
|
|
108
110
|
| `tracing` | Inspect request traces and tracing settings |
|
|
111
|
+
| `memory` | Your agent memory files (list, read, write, delete, forget); member totals and erasure for admins |
|
|
109
112
|
| `providers` | Manage BYOK AI providers |
|
|
110
113
|
| `analytics` | Usage analytics: spend, quality, providers, errors |
|
|
111
114
|
| `billing` | Check subscription tier and usage limits |
|
|
@@ -83,8 +83,8 @@ export declare const agentSchema: {
|
|
|
83
83
|
readonly properties: {
|
|
84
84
|
readonly name: {
|
|
85
85
|
readonly type: "string";
|
|
86
|
-
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)
|
|
87
|
-
readonly maxLength:
|
|
86
|
+
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$";
|
|
87
|
+
readonly maxLength: 201;
|
|
88
88
|
};
|
|
89
89
|
readonly ref: {
|
|
90
90
|
readonly type: "string";
|
|
@@ -106,6 +106,9 @@ export declare const agentSchema: {
|
|
|
106
106
|
readonly destructiveHint: {
|
|
107
107
|
readonly type: "boolean";
|
|
108
108
|
};
|
|
109
|
+
readonly openWorldHint: {
|
|
110
|
+
readonly type: "boolean";
|
|
111
|
+
};
|
|
109
112
|
};
|
|
110
113
|
};
|
|
111
114
|
readonly fileSearchTool: {
|
|
@@ -213,7 +216,7 @@ export declare const agentSchema: {
|
|
|
213
216
|
readonly uniqueItems: true;
|
|
214
217
|
readonly items: {
|
|
215
218
|
readonly type: "string";
|
|
216
|
-
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*(@[A-Za-z0-9._-]+)?$";
|
|
219
|
+
readonly pattern: "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?(@[A-Za-z0-9._-]+)?$";
|
|
217
220
|
};
|
|
218
221
|
};
|
|
219
222
|
};
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* Kept as a TS constant: package.json `files` publishes only dist JS/d.ts.
|
|
6
6
|
*/
|
|
7
7
|
export const AGENT_SCHEMA_ID = "https://docs.2kw.ai/schemas/agent.v1.json";
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
// #788: a skill name is a slug, or <plugin>:<skill> for a skill a plugin import created.
|
|
9
|
+
// Mirrors ValidationLimits.SKILL_NAME_PATTERN; each part is at most 100 characters, 201 in all.
|
|
10
|
+
const SKILL_NAME = "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?$";
|
|
11
|
+
const SKILL_REF = "^[a-z0-9]+(-[a-z0-9]+)*(:[a-z0-9]+(-[a-z0-9]+)*)?(@[A-Za-z0-9._-]+)?$";
|
|
12
|
+
const SKILL_NAME_MAX = 201;
|
|
10
13
|
const MODEL_REF = {
|
|
11
14
|
type: "string",
|
|
12
15
|
minLength: 1,
|
|
@@ -74,7 +77,7 @@ export const agentSchema = {
|
|
|
74
77
|
additionalProperties: false,
|
|
75
78
|
required: ["name"],
|
|
76
79
|
properties: {
|
|
77
|
-
name: { type: "string", pattern: SKILL_NAME, maxLength:
|
|
80
|
+
name: { type: "string", pattern: SKILL_NAME, maxLength: SKILL_NAME_MAX },
|
|
78
81
|
ref: { type: "string", pattern: "\\S", description: "Label name or version number; default latest." },
|
|
79
82
|
},
|
|
80
83
|
},
|
|
@@ -84,7 +87,13 @@ export const agentSchema = {
|
|
|
84
87
|
annotations: {
|
|
85
88
|
type: "object",
|
|
86
89
|
required: ["readOnlyHint", "destructiveHint"],
|
|
87
|
-
properties: {
|
|
90
|
+
properties: {
|
|
91
|
+
readOnlyHint: { type: "boolean" },
|
|
92
|
+
destructiveHint: { type: "boolean" },
|
|
93
|
+
// Optional. With destructiveHint: false it declares the tool harmless, so a write under
|
|
94
|
+
// `auto` may reach the approver (#938).
|
|
95
|
+
openWorldHint: { type: "boolean" },
|
|
96
|
+
},
|
|
88
97
|
},
|
|
89
98
|
fileSearchTool: {
|
|
90
99
|
type: "object",
|
|
@@ -43,7 +43,7 @@ tools: []
|
|
|
43
43
|
# properties:
|
|
44
44
|
# order: { type: string }
|
|
45
45
|
# required: [order]
|
|
46
|
-
# annotations: { readOnlyHint: true, destructiveHint: false }
|
|
46
|
+
# annotations: { readOnlyHint: true, destructiveHint: false, openWorldHint: false }
|
|
47
47
|
# Approval policy. Defaults: read allow, write approve, destructive block. Rules can only tighten.
|
|
48
48
|
# hitlPolicy:
|
|
49
49
|
# version: 1
|
|
@@ -30,7 +30,7 @@ export function versionBody(config, changeDescription) {
|
|
|
30
30
|
/**
|
|
31
31
|
* The complete agent DTO for POST /v1/agents and PUT /v1/agents/{id}. The backend's PUT maps every DTO field
|
|
32
32
|
* onto the row without skipping nulls (#624), so an absent key clears the column: `skills` (NOT NULL) must
|
|
33
|
-
* always be sent
|
|
33
|
+
* always be sent. `latestVersionId` is server-managed and ignored in a body (#996); it is still sent. Nullable
|
|
34
34
|
* columns (instructions, options, hitl_policy) are omitted when the file omits them, which clears them to
|
|
35
35
|
* SQL NULL exactly as createVersion does; an explicit JSON null would arrive as a NullNode instead.
|
|
36
36
|
*/
|
|
@@ -5,7 +5,8 @@ import { Command } from "commander";
|
|
|
5
5
|
*
|
|
6
6
|
* Answers the question an operator otherwise has to answer by running the agent
|
|
7
7
|
* and reading the transcript. The version defaults to the agent's latest, which
|
|
8
|
-
* is the one an unlabelled reference resolves to.
|
|
8
|
+
* is the one an unlabelled reference resolves to. `--mode` answers for a conversation in that
|
|
9
|
+
* mode (#1037); a contributing mode shows in the matched rules as `conversation_mode.<mode>`.
|
|
9
10
|
*/
|
|
10
11
|
export declare function makeAgentPolicyCommand(): Command;
|
|
11
12
|
//# sourceMappingURL=agent-policy.d.ts.map
|
|
@@ -4,6 +4,7 @@ import { getClient, runAction } from "../lib/client.js";
|
|
|
4
4
|
import { isJsonOutput } from "../lib/config.js";
|
|
5
5
|
import { formatDetail, formatList } from "../lib/output.js";
|
|
6
6
|
import { resolveAgent } from "../lib/agent-lookup.js";
|
|
7
|
+
import { parseModeOption } from "../lib/agent-run.js";
|
|
7
8
|
import { CliUsageError } from "../lib/errors.js";
|
|
8
9
|
import { getLatestVersion } from "./agent-apply.js";
|
|
9
10
|
/**
|
|
@@ -12,7 +13,8 @@ import { getLatestVersion } from "./agent-apply.js";
|
|
|
12
13
|
*
|
|
13
14
|
* Answers the question an operator otherwise has to answer by running the agent
|
|
14
15
|
* and reading the transcript. The version defaults to the agent's latest, which
|
|
15
|
-
* is the one an unlabelled reference resolves to.
|
|
16
|
+
* is the one an unlabelled reference resolves to. `--mode` answers for a conversation in that
|
|
17
|
+
* mode (#1037); a contributing mode shows in the matched rules as `conversation_mode.<mode>`.
|
|
16
18
|
*/
|
|
17
19
|
export function makeAgentPolicyCommand() {
|
|
18
20
|
return new Command("policy")
|
|
@@ -21,8 +23,11 @@ export function makeAgentPolicyCommand() {
|
|
|
21
23
|
.option("--version <id>", "Agent version ID (default: the agent's latest version)")
|
|
22
24
|
.option("--tool <name>", "Answer for one tool instead of every configured tool")
|
|
23
25
|
.option("--installation <id>", "Resolve the module tool catalog of this installation")
|
|
26
|
+
.option("--mode <mode>", "Answer as a conversation in this mode would be gated: plan (every call that is not read-only is refused), " +
|
|
27
|
+
"ask (a person decides where the policy would ask the judge), auto (the policy as written)")
|
|
24
28
|
.action(async (agentRef, opts, command) => {
|
|
25
29
|
await runAction(command, async () => {
|
|
30
|
+
const mode = parseModeOption(opts.mode);
|
|
26
31
|
const client = getClient(command);
|
|
27
32
|
const agent = await resolveAgent(client, agentRef);
|
|
28
33
|
const agentId = agent.id;
|
|
@@ -33,7 +38,7 @@ export function makeAgentPolicyCommand() {
|
|
|
33
38
|
const { data } = await client.GET("/v1/agents/{agentId}/versions/{versionId}/policy", {
|
|
34
39
|
params: {
|
|
35
40
|
path: { agentId, versionId },
|
|
36
|
-
query: { tool: opts.tool, installation: opts.installation },
|
|
41
|
+
query: { tool: opts.tool, installation: opts.installation, mode },
|
|
37
42
|
},
|
|
38
43
|
});
|
|
39
44
|
if (isJsonOutput(command)) {
|
|
@@ -47,6 +52,8 @@ export function makeAgentPolicyCommand() {
|
|
|
47
52
|
`auto-approval ${auto?.enabled ? "on" : "off (every auto pauses for a human)"}`,
|
|
48
53
|
`judge ${auto?.judgeConfigured ? "configured" : "not configured (every candidate escalates)"}`,
|
|
49
54
|
];
|
|
55
|
+
if (mode)
|
|
56
|
+
parts.push(`mode ${mode}`);
|
|
50
57
|
if (data?.catalogResolvedAt)
|
|
51
58
|
parts.push(`catalog resolved ${data.catalogResolvedAt}`);
|
|
52
59
|
console.log(chalk.dim(parts.join(", ")));
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
export declare function resolveRunInput(arg: string | undefined, inputFile: string | undefined, readStdin?: () => string | undefined): string;
|
|
3
|
+
/**
|
|
4
|
+
* The task text from the argument, then the file, then stdin; `undefined` when none is given. A
|
|
5
|
+
* `--continue` run may have none: a connect pause is continued with an empty input (#807).
|
|
6
|
+
*/
|
|
7
|
+
export declare function readRunInput(arg: string | undefined, inputFile: string | undefined, readStdin?: () => string | undefined): string | undefined;
|
|
3
8
|
export declare function makeAgentRunCommand(): Command;
|
|
4
9
|
export declare function makeAgentDecideCommand(): Command;
|
|
5
10
|
//# sourceMappingURL=agent-run.d.ts.map
|
|
@@ -1,16 +1,27 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { getClient, runAction } from "../lib/client.js";
|
|
4
|
-
import { isJsonOutput } from "../lib/config.js";
|
|
4
|
+
import { chatUrlFor, isJsonOutput, resolveConfig } 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";
|
|
12
12
|
import { readStdinInput } from "./ai.js";
|
|
13
13
|
export function resolveRunInput(arg, inputFile, readStdin = readStdinInput) {
|
|
14
|
+
const text = readRunInput(arg, inputFile, readStdin);
|
|
15
|
+
if (text === undefined) {
|
|
16
|
+
throw new CliUsageError("Input is required: pass it as an argument, with --input-file, or via stdin.");
|
|
17
|
+
}
|
|
18
|
+
return text;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The task text from the argument, then the file, then stdin; `undefined` when none is given. A
|
|
22
|
+
* `--continue` run may have none: a connect pause is continued with an empty input (#807).
|
|
23
|
+
*/
|
|
24
|
+
export function readRunInput(arg, inputFile, readStdin = readStdinInput) {
|
|
14
25
|
if (arg)
|
|
15
26
|
return arg;
|
|
16
27
|
if (inputFile) {
|
|
@@ -29,10 +40,7 @@ export function resolveRunInput(arg, inputFile, readStdin = readStdinInput) {
|
|
|
29
40
|
throw new CliUsageError(`Input file ${inputFile} is empty.`);
|
|
30
41
|
return text;
|
|
31
42
|
}
|
|
32
|
-
|
|
33
|
-
if (piped)
|
|
34
|
-
return piped;
|
|
35
|
-
throw new CliUsageError("Input is required: pass it as an argument, with --input-file, or via stdin.");
|
|
43
|
+
return readStdin() || undefined;
|
|
36
44
|
}
|
|
37
45
|
async function withAgentSuggestions(client, agentRef, err) {
|
|
38
46
|
if (!(err instanceof BackboneApiError) || err.status !== 404 || !err.message.startsWith("Agent not found")) {
|
|
@@ -56,8 +64,9 @@ async function withAgentSuggestions(client, agentRef, err) {
|
|
|
56
64
|
*/
|
|
57
65
|
async function finishRun(command, client, target, first, opts) {
|
|
58
66
|
const agentRef = `${target.name}${target.label ? `@${target.label}` : ""}${target.model ? `#${target.model}` : ""}`;
|
|
67
|
+
const chatUrl = chatUrlFor(resolveConfig(command).baseUrl);
|
|
59
68
|
let result = first;
|
|
60
|
-
let env = buildRunEnvelope(result, agentRef);
|
|
69
|
+
let env = buildRunEnvelope(result, agentRef, chatUrl);
|
|
61
70
|
if (opts.interactive && env.status === "requires_approval") {
|
|
62
71
|
try {
|
|
63
72
|
const agent = await resolveAgent(client, target.name);
|
|
@@ -90,8 +99,8 @@ async function finishRun(command, client, target, first, opts) {
|
|
|
90
99
|
throw new CliUsageError(`No pending approvals for ${responseId} (already decided or superseded).`);
|
|
91
100
|
}
|
|
92
101
|
const decisions = decisionsFromAnswers(rows, answers);
|
|
93
|
-
result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label, target.model));
|
|
94
|
-
env = buildRunEnvelope(result, agentRef);
|
|
102
|
+
result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), responseId, decisions, target.label, target.model, target.mode));
|
|
103
|
+
env = buildRunEnvelope(result, agentRef, chatUrl);
|
|
95
104
|
}
|
|
96
105
|
}
|
|
97
106
|
catch (err) {
|
|
@@ -115,21 +124,30 @@ async function finishRun(command, client, target, first, opts) {
|
|
|
115
124
|
}
|
|
116
125
|
export function makeAgentRunCommand() {
|
|
117
126
|
return new Command("run")
|
|
118
|
-
.description("Run an agent on a task (exit 3 = paused for approval, 4 = needs client tool output, 5 = incomplete)")
|
|
127
|
+
.description("Run an agent on a task (exit 3 = paused for approval, 4 = needs client tool output or a connector connected in chat, 5 = incomplete)")
|
|
119
128
|
.argument("<agent>", "Agent name or id, optionally with @label and #model")
|
|
120
|
-
.argument("[input]", "Task text (or use --input-file, or pipe it on stdin)")
|
|
129
|
+
.argument("[input]", "Task text (or use --input-file, or pipe it on stdin); optional with --continue")
|
|
121
130
|
.option("--input-file <path>", "Read the task text from a file")
|
|
122
131
|
.option("--conversation <id>", "Continue a conversation")
|
|
123
|
-
.option("--continue <responseId>", "Continue from a previous response")
|
|
132
|
+
.option("--continue <responseId>", "Continue from a previous response, with or without new input")
|
|
124
133
|
.option("--model <model>", "Run this request on another model from the agent's list (sends <agent>#<model>)")
|
|
134
|
+
.option("--mode <mode>", MODE_OPTION_HELP)
|
|
125
135
|
.option("--no-input", "Never prompt for approvals, even on a terminal")
|
|
126
136
|
.option("--raw", "Print the untouched API response")
|
|
127
137
|
.action(async (agentArg, input, opts, command) => {
|
|
128
138
|
await runAction(command, async () => {
|
|
129
139
|
const agentRef = withModelOverride(agentArg, opts.model);
|
|
130
|
-
const
|
|
140
|
+
const mode = parseModeOption(opts.mode);
|
|
141
|
+
// A continuation may send no new turn (#807): the connect pause's `next` is exactly that command.
|
|
142
|
+
const text = opts.continue ? readRunInput(input, opts.inputFile) : resolveRunInput(input, opts.inputFile);
|
|
131
143
|
const client = getClient(command);
|
|
132
|
-
const
|
|
144
|
+
const turn = text === undefined ? [] : [{ type: "message", role: "user", content: text }];
|
|
145
|
+
// Without --mode the body stays exactly as before #656 (D2): input is the bare string, or [] without one.
|
|
146
|
+
const body = {
|
|
147
|
+
model: `agent/${agentRef}`,
|
|
148
|
+
input: mode ? [...turn, modeItem(mode)] : text ?? [],
|
|
149
|
+
stream: false,
|
|
150
|
+
};
|
|
133
151
|
if (opts.conversation)
|
|
134
152
|
body.conversation = opts.conversation;
|
|
135
153
|
if (opts.continue)
|
|
@@ -146,7 +164,7 @@ export function makeAgentRunCommand() {
|
|
|
146
164
|
// The prompt reads stdin and writes to stderr, so those two decide; stdout may be redirected.
|
|
147
165
|
const interactive = !!process.stdin.isTTY && !!process.stderr.isTTY && !isJsonOutput(command) && opts.input !== false && !opts.raw;
|
|
148
166
|
const { name, label, model } = splitAgentRef(agentRef);
|
|
149
|
-
await finishRun(command, client, { name, label, model }, data, {
|
|
167
|
+
await finishRun(command, client, { name, label, model, mode }, data, {
|
|
150
168
|
raw: !!opts.raw,
|
|
151
169
|
interactive,
|
|
152
170
|
});
|
|
@@ -164,9 +182,11 @@ export function makeAgentDecideCommand() {
|
|
|
164
182
|
.option("--reject-all", "Reject every pending approval of the response")
|
|
165
183
|
.option("--reason <text>", "Reason recorded on each decision (the model sees it on rejects)")
|
|
166
184
|
.option("--remember", "Also approve later calls of the same tool in this conversation")
|
|
185
|
+
.option("--mode <mode>", MODE_OPTION_HELP)
|
|
167
186
|
.option("--raw", "Print the untouched API response")
|
|
168
187
|
.action(async (agentRef, opts, command) => {
|
|
169
188
|
await runAction(command, async () => {
|
|
189
|
+
const mode = parseModeOption(opts.mode);
|
|
170
190
|
const client = getClient(command);
|
|
171
191
|
const { name, label, model } = splitAgentRef(agentRef);
|
|
172
192
|
// Dropped silently, an empty override would continue the run on the default model.
|
|
@@ -182,8 +202,8 @@ export function makeAgentDecideCommand() {
|
|
|
182
202
|
reason: opts.reason,
|
|
183
203
|
remember: opts.remember,
|
|
184
204
|
}, 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 });
|
|
205
|
+
const result = await withSpinner("Continuing...", () => continueWithDecisions(client, String(agent.id), opts.response, decisions, label, model, mode));
|
|
206
|
+
await finishRun(command, client, { name, label, model, mode }, result, { raw: !!opts.raw, interactive: false });
|
|
187
207
|
});
|
|
188
208
|
});
|
|
189
209
|
}
|
package/dist/commands/agents.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { getClient, runAction } from "../lib/client.js";
|
|
3
|
-
import { formatPage, formatDetail, formatSuccess } from "../lib/output.js";
|
|
3
|
+
import { formatPage, formatDetail, formatList, formatSuccess } from "../lib/output.js";
|
|
4
4
|
import { addPaginationOptions, paginationParams } from "../lib/pagination.js";
|
|
5
5
|
import { makeAgentVersionsCommand } from "./agent-versions.js";
|
|
6
6
|
import { makeAgentLabelsCommand } from "./agent-labels.js";
|
|
@@ -11,6 +11,7 @@ import { makeAgentPolicyCommand } from "./agent-policy.js";
|
|
|
11
11
|
import { makeAgentInitCommand, makeAgentSchemaCommand } from "./agent-init.js";
|
|
12
12
|
import { maskSecrets } from "../agent-config/file.js";
|
|
13
13
|
import { collectModel, MODEL_HELP, MODELS_HELP, modelFieldsFromOptions } from "../lib/agent-models.js";
|
|
14
|
+
import { assertNameNotBlank } from "../lib/overlay.js";
|
|
14
15
|
export function makeAgentsCommand() {
|
|
15
16
|
const cmd = new Command("agents").description("Manage agents");
|
|
16
17
|
const list = new Command("list").description("List agents");
|
|
@@ -101,13 +102,25 @@ export function makeAgentsCommand() {
|
|
|
101
102
|
.option("--hitl-policy <json>", "New human-in-the-loop policy as JSON")
|
|
102
103
|
.action(async (id, opts, command) => {
|
|
103
104
|
await runAction(command, async () => {
|
|
105
|
+
let name = opts.name;
|
|
106
|
+
let description = opts.description;
|
|
107
|
+
assertNameNotBlank(name);
|
|
104
108
|
const client = getClient(command);
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
// PUT /v1/agents/{id} keeps the configuration fields a body omits, but name is required and
|
|
110
|
+
// description is written as sent, so an omitted description would be cleared (#1195).
|
|
111
|
+
if (name === undefined || description === undefined) {
|
|
112
|
+
const { data: current } = await client.GET("/v1/agents/{id}", {
|
|
113
|
+
params: { path: { id } },
|
|
114
|
+
});
|
|
115
|
+
if (!current)
|
|
116
|
+
throw new Error(`Agent ${id} could not be read.`);
|
|
117
|
+
name ??= current.name;
|
|
118
|
+
description ??= current.description;
|
|
119
|
+
}
|
|
120
|
+
const body = { name };
|
|
108
121
|
Object.assign(body, modelFieldsFromOptions(opts, false));
|
|
109
|
-
if (
|
|
110
|
-
body.description =
|
|
122
|
+
if (description !== undefined)
|
|
123
|
+
body.description = description;
|
|
111
124
|
if (opts.instructions !== undefined)
|
|
112
125
|
body.instructions = opts.instructions;
|
|
113
126
|
if (opts.options)
|
|
@@ -172,6 +185,20 @@ export function makeAgentsCommand() {
|
|
|
172
185
|
});
|
|
173
186
|
});
|
|
174
187
|
cmd.addCommand(toolCatalogs);
|
|
188
|
+
// The skills the latest published version binds (#827) -- readable by a chat-only USER key,
|
|
189
|
+
// which cannot read the version itself. No published version answers [] rather than 404.
|
|
190
|
+
const skills = new Command("skills").description("List the skills an agent's latest published version binds");
|
|
191
|
+
skills.requiredOption("--agent <id>", "Agent ID");
|
|
192
|
+
skills.action(async (opts, command) => {
|
|
193
|
+
await runAction(command, async () => {
|
|
194
|
+
const client = getClient(command);
|
|
195
|
+
const { data } = await client.GET("/v1/agents/{agentId}/skills", {
|
|
196
|
+
params: { path: { agentId: opts.agent } },
|
|
197
|
+
});
|
|
198
|
+
formatList(data, command, ["name", "description", "versionNumber", "ref", "pluginName"]);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
201
|
+
cmd.addCommand(skills);
|
|
175
202
|
cmd.addCommand(makeAgentRunCommand());
|
|
176
203
|
cmd.addCommand(makeAgentDecideCommand());
|
|
177
204
|
cmd.addCommand(makeAgentApplyCommand());
|
package/dist/commands/ai.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { getClient, runAction } from "../lib/client.js";
|
|
4
|
-
import { isJsonOutput } from "../lib/config.js";
|
|
4
|
+
import { chatUrlFor, isJsonOutput, resolveConfig } from "../lib/config.js";
|
|
5
5
|
import { CliUsageError } from "../lib/errors.js";
|
|
6
6
|
import { withModelOverride } from "../lib/agent-models.js";
|
|
7
7
|
import { formatList, withSpinner } from "../lib/output.js";
|
|
@@ -46,7 +46,7 @@ function printResponseResult(result, command, agentRef) {
|
|
|
46
46
|
console.log(JSON.stringify(result, null, 2));
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
printRunText(buildRunEnvelope(result, agentRef));
|
|
49
|
+
printRunText(buildRunEnvelope(result, agentRef, chatUrlFor(resolveConfig(command).baseUrl)));
|
|
50
50
|
if (result?.usage) {
|
|
51
51
|
const usage = result.usage;
|
|
52
52
|
console.log(chalk.dim(`[${result.model} | ${usage.input_tokens} in / ${usage.output_tokens} out / ${usage.total_tokens} total tokens]`));
|
|
@@ -125,13 +125,7 @@ export function makeAiCommand() {
|
|
|
125
125
|
if (opts.conversation)
|
|
126
126
|
body.conversation = opts.conversation;
|
|
127
127
|
const client = getClient(command);
|
|
128
|
-
const { data } = await withSpinner("Generating response...", () =>
|
|
129
|
-
// The wire `input` field also accepts a bare string (shorthand for
|
|
130
|
-
// a single user message; see ResponseItemInputDeserializer), which
|
|
131
|
-
// the generated type does not model — hence the cast, same as the
|
|
132
|
-
// untyped convert.ts source-conversion calls.
|
|
133
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
134
|
-
client.POST("/v1/responses", { body }));
|
|
128
|
+
const { data } = await withSpinner("Generating response...", () => client.POST("/v1/responses", { body }));
|
|
135
129
|
printResponseResult(data, command, opts.agent);
|
|
136
130
|
});
|
|
137
131
|
});
|
package/dist/commands/auth.js
CHANGED
|
@@ -160,7 +160,12 @@ async function manualLogin(opts) {
|
|
|
160
160
|
console.error(chalk.red("API key is required."));
|
|
161
161
|
process.exit(1);
|
|
162
162
|
}
|
|
163
|
-
|
|
163
|
+
// Carry the memory opt-in over: re-entering a key should not silently turn it off (#721).
|
|
164
|
+
setContext(contextName, {
|
|
165
|
+
apiKey,
|
|
166
|
+
baseUrl,
|
|
167
|
+
...(currentCtx?.memory !== undefined ? { memory: currentCtx.memory } : {}),
|
|
168
|
+
});
|
|
164
169
|
setActiveContext(contextName);
|
|
165
170
|
console.log(chalk.green("Credentials saved successfully."));
|
|
166
171
|
console.log(chalk.dim(`Config stored at: ${store.path}`));
|
package/dist/commands/billing.js
CHANGED
|
@@ -49,10 +49,12 @@ export function makeBillingCommand() {
|
|
|
49
49
|
}
|
|
50
50
|
console.log();
|
|
51
51
|
}
|
|
52
|
+
// A null maximum is "unlimited" (schemas and prompts in every tier since #971), not 0.
|
|
53
|
+
const max = (value) => (value == null ? "unlimited" : String(value));
|
|
52
54
|
console.log(chalk.bold("Usage:"));
|
|
53
|
-
console.log(` Schemas: ${data?.currentSchemas ?? 0} / ${data?.maxSchemas
|
|
54
|
-
console.log(` Prompts: ${data?.currentPrompts ?? 0} / ${data?.maxPrompts
|
|
55
|
-
console.log(` Team members: ${data?.currentTeamMembers ?? 0} / ${data?.maxTeamMembers
|
|
55
|
+
console.log(` Schemas: ${data?.currentSchemas ?? 0} / ${max(data?.maxSchemas)}`);
|
|
56
|
+
console.log(` Prompts: ${data?.currentPrompts ?? 0} / ${max(data?.maxPrompts)}`);
|
|
57
|
+
console.log(` Team members: ${data?.currentTeamMembers ?? 0} / ${max(data?.maxTeamMembers)}`);
|
|
56
58
|
console.log();
|
|
57
59
|
console.log(chalk.bold("Features:"));
|
|
58
60
|
console.log(` BYOK: ${data?.byokEnabled ? chalk.green("yes") : chalk.dim("no")}`);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
|
-
declare const ALLOWED_KEYS: readonly ["apiKey", "baseUrl"];
|
|
2
|
+
declare const ALLOWED_KEYS: readonly ["apiKey", "baseUrl", "memory"];
|
|
3
3
|
type ConfigKey = (typeof ALLOWED_KEYS)[number];
|
|
4
4
|
/**
|
|
5
5
|
* Write one config key onto the active context.
|
package/dist/commands/config.js
CHANGED
|
@@ -2,7 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { store, isJsonOutput, getActiveContext, getActiveContextName, getContextCount, setContext, DEFAULT_BASE_URL, } from "../lib/config.js";
|
|
4
4
|
import { maskApiKey } from "../lib/redact.js";
|
|
5
|
-
const ALLOWED_KEYS = ["apiKey", "baseUrl"];
|
|
5
|
+
const ALLOWED_KEYS = ["apiKey", "baseUrl", "memory"];
|
|
6
6
|
const KEY_ALIASES = {
|
|
7
7
|
"api-key": "apiKey",
|
|
8
8
|
"base-url": "baseUrl",
|
|
@@ -43,6 +43,14 @@ export function applyConfigSet(key, value) {
|
|
|
43
43
|
setContext(contextName, entry);
|
|
44
44
|
return { clearedSession };
|
|
45
45
|
}
|
|
46
|
+
if (key === "memory") {
|
|
47
|
+
if (value !== "true" && value !== "false") {
|
|
48
|
+
throw new Error('memory takes true or false');
|
|
49
|
+
}
|
|
50
|
+
entry.memory = value === "true";
|
|
51
|
+
setContext(contextName, entry);
|
|
52
|
+
return { clearedSession: false };
|
|
53
|
+
}
|
|
46
54
|
entry[key] = value;
|
|
47
55
|
setContext(contextName, entry);
|
|
48
56
|
return { clearedSession: false };
|
|
@@ -82,7 +90,12 @@ export function makeConfigCommand() {
|
|
|
82
90
|
const validKey = validateKey(key);
|
|
83
91
|
const ctx = getActiveContext();
|
|
84
92
|
const raw = ctx?.[validKey];
|
|
85
|
-
|
|
93
|
+
// A stored "" apiKey stays "" (shown as "(not set)"); only a real key is masked.
|
|
94
|
+
const value = raw === undefined
|
|
95
|
+
? undefined
|
|
96
|
+
: validKey === "apiKey" && typeof raw === "string"
|
|
97
|
+
? raw && maskApiKey(raw)
|
|
98
|
+
: String(raw);
|
|
86
99
|
if (isJsonOutput(command)) {
|
|
87
100
|
console.log(JSON.stringify({ key: validKey, value: value ?? null }));
|
|
88
101
|
}
|
|
@@ -103,6 +116,7 @@ export function makeConfigCommand() {
|
|
|
103
116
|
const values = {
|
|
104
117
|
apiKey: ctx?.apiKey ? maskApiKey(ctx.apiKey) : undefined,
|
|
105
118
|
baseUrl: ctx?.baseUrl,
|
|
119
|
+
memory: ctx?.memory === undefined ? undefined : String(ctx.memory),
|
|
106
120
|
};
|
|
107
121
|
if (isJsonOutput(command)) {
|
|
108
122
|
console.log(JSON.stringify(values, null, 2));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
/**
|
|
3
|
+
* Manage the connector settings an organization decides centrally (#805).
|
|
4
|
+
*
|
|
5
|
+
* Only the approved public hosts live here: which MCP servers this organization's agents
|
|
6
|
+
* may reach directly. The connectors themselves are declared per agent, in the agent
|
|
7
|
+
* version's `tools`.
|
|
8
|
+
*/
|
|
9
|
+
export declare function makeConnectorsCommand(): Command;
|
|
10
|
+
//# sourceMappingURL=connectors.d.ts.map
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { getClient, runAction } from "../lib/client.js";
|
|
3
|
+
import { CliUsageError } from "../lib/errors.js";
|
|
4
|
+
import { formatDetail, formatList, formatSuccess } from "../lib/output.js";
|
|
5
|
+
/**
|
|
6
|
+
* These three endpoints take a member sign-in only (plan ruling R10): approving a
|
|
7
|
+
* destination is the human gate against exfiltration, so an API key — however admin it is —
|
|
8
|
+
* is refused. Said in every description, because `--help` is where a reader looks before
|
|
9
|
+
* the 403.
|
|
10
|
+
*/
|
|
11
|
+
const SESSION_ONLY = "Signed-in admins only: run 2kw auth login and sign in through the browser; an API key is refused";
|
|
12
|
+
/** The backend's limit, so a too-long host fails here instead of after a round trip. */
|
|
13
|
+
const MAX_HOST_LENGTH = 253;
|
|
14
|
+
const MIN_PORT = 1;
|
|
15
|
+
const MAX_PORT = 65535;
|
|
16
|
+
/**
|
|
17
|
+
* Parse `--port` strictly, and only when one was given.
|
|
18
|
+
*
|
|
19
|
+
* Commander's house pattern is a bare `parseInt`, which turns `--port abc` into `NaN`,
|
|
20
|
+
* `JSON.stringify` turns that into `null`, and the backend reads a null port as "not
|
|
21
|
+
* given" and approves 443. Silently approving a port nobody asked for is not a wart worth
|
|
22
|
+
* inheriting on a trust decision.
|
|
23
|
+
*
|
|
24
|
+
* Validated inside the action rather than as commander's option parser, so the refusal
|
|
25
|
+
* travels through `runAction` and exits 2 like every other usage error.
|
|
26
|
+
*/
|
|
27
|
+
function parsePort(value) {
|
|
28
|
+
if (value === undefined) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
// Digits only: `Number` alone accepts " 8443 ", "0x20FB" and "8.443e3".
|
|
32
|
+
const port = /^\d{1,5}$/.test(value) ? Number(value) : NaN;
|
|
33
|
+
if (!Number.isInteger(port) || port < MIN_PORT || port > MAX_PORT) {
|
|
34
|
+
throw new CliUsageError(`--port must be a whole number between ${MIN_PORT} and ${MAX_PORT}, not '${value}'`);
|
|
35
|
+
}
|
|
36
|
+
return port;
|
|
37
|
+
}
|
|
38
|
+
function requireHost(host) {
|
|
39
|
+
if (host.length > MAX_HOST_LENGTH) {
|
|
40
|
+
throw new CliUsageError(`A host name is at most ${MAX_HOST_LENGTH} characters`);
|
|
41
|
+
}
|
|
42
|
+
return host;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Manage the connector settings an organization decides centrally (#805).
|
|
46
|
+
*
|
|
47
|
+
* Only the approved public hosts live here: which MCP servers this organization's agents
|
|
48
|
+
* may reach directly. The connectors themselves are declared per agent, in the agent
|
|
49
|
+
* version's `tools`.
|
|
50
|
+
*/
|
|
51
|
+
export function makeConnectorsCommand() {
|
|
52
|
+
const cmd = new Command("connectors").description("Manage MCP connector settings");
|
|
53
|
+
const publicHosts = new Command("public-hosts").description(`Approve the public MCP hosts this organization's agents may reach. ${SESSION_ONLY}`);
|
|
54
|
+
publicHosts
|
|
55
|
+
.command("list")
|
|
56
|
+
.description(`List the approved public MCP hosts. ${SESSION_ONLY}`)
|
|
57
|
+
.action(async (_opts, command) => {
|
|
58
|
+
await runAction(command, async () => {
|
|
59
|
+
const client = getClient(command);
|
|
60
|
+
const { data } = await client.GET("/v1/connectors/public-hosts");
|
|
61
|
+
formatList(data, command, [
|
|
62
|
+
"id",
|
|
63
|
+
"host",
|
|
64
|
+
"port",
|
|
65
|
+
"createdBy",
|
|
66
|
+
"createdAt",
|
|
67
|
+
]);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
publicHosts
|
|
71
|
+
.command("add")
|
|
72
|
+
.description(`Approve a host — every path and every account on it becomes reachable. ${SESSION_ONLY}`)
|
|
73
|
+
.argument("<host>", "Plain host name: no scheme, path, port, wildcard or IP literal")
|
|
74
|
+
.option("--port <n>", "Port to approve; 443 when omitted")
|
|
75
|
+
.action(async (host, opts, command) => {
|
|
76
|
+
await runAction(command, async () => {
|
|
77
|
+
const body = { host: requireHost(host), port: parsePort(opts.port) };
|
|
78
|
+
const client = getClient(command);
|
|
79
|
+
const { data } = await client.POST("/v1/connectors/public-hosts", { body });
|
|
80
|
+
formatDetail(data, command);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
publicHosts
|
|
84
|
+
.command("remove")
|
|
85
|
+
.description(`Withdraw an approved host; it takes effect on the next request. ${SESSION_ONLY}`)
|
|
86
|
+
.argument("<id>", "Approved host ID, from `connectors public-hosts list`")
|
|
87
|
+
.action(async (id, _opts, command) => {
|
|
88
|
+
await runAction(command, async () => {
|
|
89
|
+
const client = getClient(command);
|
|
90
|
+
await client.DELETE("/v1/connectors/public-hosts/{id}", { params: { path: { id } } });
|
|
91
|
+
formatSuccess(`Host ${id} withdrawn.`, command);
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
cmd.addCommand(publicHosts);
|
|
95
|
+
return cmd;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=connectors.js.map
|
|
@@ -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")
|