@2kw/ai 7.4.0-dev.9 → 8.0.0-dev.1

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.
@@ -571,7 +571,7 @@ export declare const agentSchema: {
571
571
  };
572
572
  readonly additionalProperties: {
573
573
  readonly type: "string";
574
- readonly enum: readonly ["read", "write", "destructive", "approve", "auto", "deny"];
574
+ readonly enum: readonly ["read", "write", "destructive", "approve", "deny"];
575
575
  };
576
576
  };
577
577
  };
@@ -105,8 +105,8 @@ export const agentSchema = {
105
105
  properties: {
106
106
  readOnlyHint: { type: "boolean" },
107
107
  destructiveHint: { type: "boolean" },
108
- // Optional. With destructiveHint: false it declares the tool harmless, so a write under
109
- // `auto` may reach the approver (#938).
108
+ // Optional. With destructiveHint: false it declares the tool harmless, so a write that needs
109
+ // approval may be decided by the automatic approver in the Auto mode (#938, #1645).
110
110
  openWorldHint: { type: "boolean" },
111
111
  },
112
112
  },
@@ -290,17 +290,17 @@ export const agentSchema = {
290
290
  type: "object",
291
291
  additionalProperties: false,
292
292
  properties: {
293
- read: oneOfLowercase("allow", "auto", "approve", "block", "deny"),
294
- write: oneOfLowercase("auto", "approve", "block", "deny"),
295
- destructive: oneOfLowercase("block", "deny"),
293
+ read: oneOfLowercase("allow", "approve", "block", "deny"),
294
+ write: oneOfLowercase("approve", "block", "deny"),
295
+ destructive: oneOfLowercase("approve", "block", "deny"),
296
296
  },
297
297
  },
298
298
  tools: {
299
- // Value is a policy class or an action. Actions (approve, auto, deny) are exact lowercase;
299
+ // Value is a policy class or an action. Actions (approve, deny) are exact lowercase;
300
300
  // classes (read, write, destructive) are case-insensitive and lowercased by the validator.
301
301
  type: "object",
302
302
  propertyNames: { pattern: "\\S" },
303
- additionalProperties: { type: "string", enum: ["read", "write", "destructive", "approve", "auto", "deny"] },
303
+ additionalProperties: { type: "string", enum: ["read", "write", "destructive", "approve", "deny"] },
304
304
  },
305
305
  },
306
306
  },
@@ -24,7 +24,8 @@ export function makeAgentPolicyCommand() {
24
24
  .option("--tool <name>", "Answer for one tool instead of every configured tool")
25
25
  .option("--installation <id>", "Resolve the module tool catalog of this installation")
26
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)")
27
+ "ask (a person decides every call that needs approval), auto (the automatic approver decides reads and " +
28
+ "writes the server marks safe). Without it, the agent's default mode")
28
29
  .action(async (agentRef, opts, command) => {
29
30
  await runAction(command, async () => {
30
31
  const mode = parseModeOption(opts.mode);
@@ -49,11 +50,14 @@ export function makeAgentPolicyCommand() {
49
50
  // model, so they belong above the table rather than in a footnote.
50
51
  const auto = data?.autoApproval;
51
52
  const parts = [
52
- `auto-approval ${auto?.enabled ? "on" : "off (every auto pauses for a human)"}`,
53
- `judge ${auto?.judgeConfigured ? "configured" : "not configured (every candidate escalates)"}`,
53
+ `auto-approval ${auto?.enabled ? "on" : "off (every call the approver would decide pauses for a human)"}`,
54
+ `approver ${auto?.judgeConfigured ? "configured" : "not configured (every candidate escalates)"}`,
54
55
  ];
55
- if (mode)
56
- parts.push(`mode ${mode}`);
56
+ if (auto?.instructionsExceedJudgeBound) {
57
+ parts.push("instructions longer than the approver reads (every candidate escalates)");
58
+ }
59
+ if (data?.effectiveMode)
60
+ parts.push(`mode ${data.effectiveMode} (${data.effectiveModeSource})`);
57
61
  if (data?.catalogResolvedAt)
58
62
  parts.push(`catalog resolved ${data.catalogResolvedAt}`);
59
63
  console.log(chalk.dim(parts.join(", ")));
@@ -97,7 +97,6 @@ export function makeDatasetVersionsCommand() {
97
97
  const versionId = opts.versionId ?? (await resolveLatestVersionId(client, opts.dataset));
98
98
  await client.DELETE("/v1/datasets/{id}/versions/{versionId}/items/{itemId}", {
99
99
  params: {
100
- // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
101
100
  path: { id: opts.dataset, versionId, itemId },
102
101
  },
103
102
  });
@@ -120,7 +119,6 @@ export function makeDatasetVersionsCommand() {
120
119
  body.expectedOutput = JSON.parse(opts.expectedOutput);
121
120
  const { data } = await client.PATCH("/v1/datasets/{id}/versions/{versionId}/items/{itemId}/expected-output", {
122
121
  params: {
123
- // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
124
122
  path: { id: opts.dataset, versionId, itemId },
125
123
  },
126
124
  body: body,
@@ -138,7 +138,6 @@ export function makeDatasetsCommand() {
138
138
  body.metadata = JSON.parse(opts.metadata);
139
139
  const { data } = await client.POST("/v1/datasets/{id}/versions/{versionId}/items", {
140
140
  params: {
141
- // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
142
141
  path: { id: opts.dataset, versionId },
143
142
  },
144
143
  body: body,
@@ -158,7 +157,6 @@ export function makeDatasetsCommand() {
158
157
  const items = JSON.parse(opts.items);
159
158
  const { data } = await client.POST("/v1/datasets/{id}/versions/{versionId}/items/bulk", {
160
159
  params: {
161
- // OpenAPI spec omits `id` in declared path params though `{id}` is in the URL template.
162
160
  path: { id: opts.dataset, versionId },
163
161
  },
164
162
  body: items,
@@ -335,7 +335,7 @@ export function makeExperimentsCommand() {
335
335
  },
336
336
  });
337
337
  formatPage(data, command, [
338
- "datasetItemId", "durationMs", "inputTokens", "outputTokens", "estimatedCost", "error",
338
+ "datasetItemId", "durationMs", "inputTokens", "outputTokens", "error",
339
339
  ]);
340
340
  });
341
341
  });
@@ -418,7 +418,7 @@ export function makeExperimentsCommand() {
418
418
  // Only when something was skipped, so a clean run keeps its narrow table (#693).
419
419
  const showSkipped = aggregates.some((agg) => skippedOf(agg) !== "");
420
420
  const aggTable = new Table({
421
- head: ["Variant", "Items", "Avg Duration", "Avg Tokens", "Total Cost", ...(showSkipped ? ["Skipped"] : [])].map((h) => chalk.cyan(h)),
421
+ head: ["Variant", "Items", "Avg Duration", "Avg Tokens", ...(showSkipped ? ["Skipped"] : [])].map((h) => chalk.cyan(h)),
422
422
  wordWrap: true,
423
423
  });
424
424
  for (const agg of aggregates) {
@@ -427,7 +427,6 @@ export function makeExperimentsCommand() {
427
427
  agg.itemCount ?? "—",
428
428
  agg.avgDurationMs != null ? `${Math.round(agg.avgDurationMs)}ms` : "—",
429
429
  agg.avgTokens != null ? Math.round(agg.avgTokens) : "—",
430
- agg.totalCost != null ? `$${agg.totalCost}` : "—",
431
430
  ...(showSkipped ? [skippedOf(agg) || "—"] : []),
432
431
  ]);
433
432
  }
@@ -5,8 +5,9 @@ import { CliUsageError } from "./errors.js";
5
5
  import { previewIsIncomplete, previewLines } from "./skills-apply-preview.js";
6
6
  export const CONVERSATION_MODES = ["plan", "ask", "auto"];
7
7
  /** `--mode` help text, in the words of the embedded chat's mode chip (#655). */
8
- export const MODE_OPTION_HELP = "Conversation mode: plan (read-only), ask (no automatic approver; calls that need approval wait for you), " +
9
- "auto (the operator's policy as written). Without it, the conversation keeps its mode";
8
+ export const MODE_OPTION_HELP = "Conversation mode: plan (read-only), ask (calls that need approval wait for you), " +
9
+ "auto (the automatic approver decides reads and writes the server marks safe; the rest wait for you). " +
10
+ "Without it, the conversation keeps its mode, else the agent's default; a mode the agent does not offer is refused";
10
11
  function isConversationMode(value) {
11
12
  return typeof value === "string" && CONVERSATION_MODES.includes(value);
12
13
  }
@@ -102,6 +102,7 @@ const CODE_HINTS = {
102
102
  "Check pendingToolCalls and pendingApprovals in the run envelope.",
103
103
  unknown_tool_output: "That call id was not released by this response. Use the responseId of the latest envelope.",
104
104
  conversation_agent_mismatch: "This conversation belongs to another agent. Start a new conversation or run the agent that owns it.",
105
+ conversation_mode_not_offered: "The agent does not offer that mode. Use one of the modes the message lists (plan is always offered), or leave --mode out for the agent's default.",
105
106
  provider_quota_exhausted: "The model provider account is out of credits or at its spend limit. Add credits or raise the limit with the provider; retrying will not help.",
106
107
  };
107
108
  /** The most specific hint for an API error: gateway code, then known messages, then the status table. */
@@ -117,6 +118,9 @@ export function hintFor(err) {
117
118
  }
118
119
  if (err.code && Object.hasOwn(CODE_HINTS, err.code))
119
120
  return CODE_HINTS[err.code];
121
+ // The REST layer (the explain view) spells the same refusal as an errorCode.
122
+ if (err.errorCode === "CONVERSATION_MODE_NOT_OFFERED")
123
+ return CODE_HINTS.conversation_mode_not_offered;
120
124
  // The gateway answers `agent_not_published` (code), the REST layer AGENT_NOT_PUBLISHED (errorCode).
121
125
  if (err.code === "agent_not_published" || err.errorCode === "AGENT_NOT_PUBLISHED") {
122
126
  return NOT_PUBLISHED_HINT;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2kw/ai",
3
- "version": "7.4.0-dev.9",
3
+ "version": "8.0.0-dev.1",
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",