@odla-ai/cli 0.40.2 → 0.41.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.
package/bin/odla-ai.js CHANGED
@@ -1,20 +1,22 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync } from "node:fs";
2
+ import { existsSync, readFileSync, statSync } from "node:fs";
3
3
  import { spawnSync } from "node:child_process";
4
4
  import { dirname, join } from "node:path";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import { workspaceBuildOrder } from "./workspace-build.js";
7
+ import { firstStaleWorkspace, staleBuildNotice } from "./stale-build.js";
7
8
 
8
9
  const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..");
9
10
  const builtEntry = join(packageRoot, "dist", "bin.js");
11
+ const workspaceRoot = join(packageRoot, "..", "..");
12
+ const rootManifestPath = join(workspaceRoot, "package.json");
13
+ const rootManifest = existsSync(rootManifestPath)
14
+ ? JSON.parse(readFileSync(rootManifestPath, "utf8"))
15
+ : null;
16
+ const inWorkspace = rootManifest?.name === "odla-ai" && rootManifest.private === true;
10
17
 
11
18
  if (!existsSync(builtEntry)) {
12
- const workspaceRoot = join(packageRoot, "..", "..");
13
- const rootManifestPath = join(workspaceRoot, "package.json");
14
- const rootManifest = existsSync(rootManifestPath)
15
- ? JSON.parse(readFileSync(rootManifestPath, "utf8"))
16
- : null;
17
- if (rootManifest?.name !== "odla-ai" || rootManifest.private !== true) {
19
+ if (!inWorkspace) {
18
20
  throw new Error("odla-ai CLI build is missing; reinstall @odla-ai/cli from npm");
19
21
  }
20
22
 
@@ -28,6 +30,14 @@ if (!existsSync(builtEntry)) {
28
30
  );
29
31
  if (child.error) throw child.error;
30
32
  if (child.status !== 0) process.exit(child.status ?? 1);
33
+ } else if (inWorkspace) {
34
+ // stderr, never stdout: every --json command stays parseable.
35
+ const stale = firstStaleWorkspace(
36
+ workspaceRoot,
37
+ workspaceBuildOrder(workspaceRoot),
38
+ statSync(builtEntry).mtimeMs,
39
+ );
40
+ if (stale) console.error(staleBuildNotice(stale));
31
41
  }
32
42
 
33
43
  await import(pathToFileURL(builtEntry).href);
@@ -0,0 +1,16 @@
1
+ /** A workspace whose sources are newer than the built entry about to run. */
2
+ export interface StaleWorkspace {
3
+ workspace: string;
4
+ file: string;
5
+ }
6
+
7
+ /** The first workspace in `workspaceNames` whose `src` is newer than
8
+ * `builtAtMs`, or null when the build is current or the tree is unreadable. */
9
+ export declare function firstStaleWorkspace(
10
+ root: string,
11
+ workspaceNames: readonly string[],
12
+ builtAtMs: number,
13
+ ): StaleWorkspace | null;
14
+
15
+ /** The one-line stderr notice naming the stale workspace and the way out. */
16
+ export declare function staleBuildNotice(stale: StaleWorkspace): string;
@@ -0,0 +1,77 @@
1
+ // A workspace checkout runs the CLI through a symlink into packages/cli, so
2
+ // `npx odla-ai` answers from whatever was last built — which can be a day older
3
+ // than the source sitting next to it, with nothing saying so. That is not a
4
+ // cosmetic problem: an agent reads the stale output, believes it, and reasons
5
+ // from a surface that no longer exists. The launcher already handles "not built
6
+ // at all"; this handles "built, but behind".
7
+ import { existsSync, readdirSync, statSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { workspacePackages } from "./workspace-build.js";
10
+
11
+ /** Directories that never hold sources worth comparing. */
12
+ const SKIP = new Set(["node_modules", "dist", "js", ".turbo", ".wrangler"]);
13
+
14
+ /** First file under `dir` modified after `builtAtMs`, or null. Depth-first and
15
+ * early-exiting: the answer is "is anything newer", not "what is newest". */
16
+ function firstNewerFile(dir, builtAtMs) {
17
+ let entries;
18
+ try {
19
+ entries = readdirSync(dir, { withFileTypes: true });
20
+ } catch {
21
+ return null;
22
+ }
23
+ for (const entry of entries) {
24
+ if (entry.name.startsWith(".") || SKIP.has(entry.name)) continue;
25
+ const path = join(dir, entry.name);
26
+ if (entry.isDirectory()) {
27
+ const found = firstNewerFile(path, builtAtMs);
28
+ if (found) return found;
29
+ continue;
30
+ }
31
+ try {
32
+ if (statSync(path).mtimeMs > builtAtMs) return path;
33
+ } catch {
34
+ // A file that vanished mid-walk is not evidence of staleness.
35
+ }
36
+ }
37
+ return null;
38
+ }
39
+
40
+ /**
41
+ * The first workspace whose sources are newer than the built entry about to be
42
+ * run, or null when the build is current.
43
+ *
44
+ * Checks the CLI's whole internal dependency closure, not just its own `src`:
45
+ * a stale `@odla-ai/db` breaks the CLI just as thoroughly, and reports itself
46
+ * as a missing export rather than as a stale build.
47
+ *
48
+ * Returns null rather than throwing for anything unreadable — a diagnostic
49
+ * must never be the reason a command fails.
50
+ */
51
+ export function firstStaleWorkspace(root, workspaceNames, builtAtMs) {
52
+ try {
53
+ const packages = workspacePackages(root);
54
+ for (const name of workspaceNames) {
55
+ const entry = packages.get(name);
56
+ if (!entry) continue;
57
+ // A published install ships `dist` and no `src`, so its absence is how we
58
+ // know this is not a source checkout rather than a stale one.
59
+ const source = join(entry.dir, "src");
60
+ if (!existsSync(source)) continue;
61
+ const file = firstNewerFile(source, builtAtMs);
62
+ if (file) return { workspace: name, file };
63
+ }
64
+ } catch {
65
+ return null;
66
+ }
67
+ return null;
68
+ }
69
+
70
+ /** The one line a stale workspace build is worth, and what ends it. */
71
+ export function staleBuildNotice(stale) {
72
+ return (
73
+ `odla-ai: running a stale build — ${stale.workspace} source is newer than ` +
74
+ "the built CLI, so this output can describe code that no longer exists. " +
75
+ "Run `npm run build`."
76
+ );
77
+ }
@@ -1,2 +1,7 @@
1
1
  /** Return buildable internal dependencies in dependency-first order. */
2
2
  export declare function workspaceBuildOrder(root: string): string[];
3
+
4
+ /** Every workspace in the checkout, by package name, with its directory. */
5
+ export declare function workspacePackages(
6
+ root: string,
7
+ ): Map<string, { dir: string; manifest: Record<string, unknown> }>;
@@ -3,8 +3,10 @@ import { join } from "node:path";
3
3
 
4
4
  const manifestAt = (path) => JSON.parse(readFileSync(path, "utf8"));
5
5
 
6
- /** Return buildable internal dependencies in dependency-first order. */
7
- export function workspaceBuildOrder(root) {
6
+ /** Every workspace in the checkout, by package name, with the directory it
7
+ * lives in. Shared so the launcher's build order and its staleness check agree
8
+ * on what a workspace is and where its sources are. */
9
+ export function workspacePackages(root) {
8
10
  const rootManifest = manifestAt(join(root, "package.json"));
9
11
  const workspaces = new Map();
10
12
  for (const pattern of rootManifest.workspaces ?? []) {
@@ -13,12 +15,22 @@ export function workspaceBuildOrder(root) {
13
15
  if (!existsSync(parent)) continue;
14
16
  for (const entry of readdirSync(parent, { withFileTypes: true })) {
15
17
  if (!entry.isDirectory()) continue;
16
- const manifestPath = join(parent, entry.name, "package.json");
18
+ const dir = join(parent, entry.name);
19
+ const manifestPath = join(dir, "package.json");
17
20
  if (!existsSync(manifestPath)) continue;
18
21
  const manifest = manifestAt(manifestPath);
19
- if (manifest.name) workspaces.set(manifest.name, manifest);
22
+ if (manifest.name) workspaces.set(manifest.name, { dir, manifest });
20
23
  }
21
24
  }
25
+ return workspaces;
26
+ }
27
+
28
+ /** Return buildable internal dependencies in dependency-first order. */
29
+ export function workspaceBuildOrder(root) {
30
+ const packages = workspacePackages(root);
31
+ const workspaces = new Map(
32
+ [...packages].map(([name, entry]) => [name, entry.manifest]),
33
+ );
22
34
 
23
35
  const visited = new Set();
24
36
  const order = [];
package/dist/bin.cjs CHANGED
@@ -7915,7 +7915,7 @@ var init_code2 = __esm({
7915
7915
  }
7916
7916
  });
7917
7917
 
7918
- // ../harness/dist/chunk-L2T3LPEF.js
7918
+ // ../harness/dist/chunk-5HX5LWTG.js
7919
7919
  async function digestStagedWorkspace(root, limits) {
7920
7920
  const files = [];
7921
7921
  const walk = async (directory) => {
@@ -9013,6 +9013,7 @@ async function runCodeAgent(options) {
9013
9013
  }
9014
9014
  async function runCodeAgentAttempt(options) {
9015
9015
  try {
9016
+ const surface = options.surface ?? "v2";
9016
9017
  const { run } = await runCodeAgent({
9017
9018
  inference: options.inference,
9018
9019
  broker: options.broker,
@@ -9022,17 +9023,31 @@ async function runCodeAgentAttempt(options) {
9022
9023
  // The brokered route resolves the real model from platform policy; this
9023
9024
  // id only labels the request the control plane is about to rewrite.
9024
9025
  model: "brokered",
9025
- surface: options.surface ?? "v2",
9026
+ surface,
9026
9027
  ...options.maxSteps === void 0 ? {} : { maxSteps: options.maxSteps },
9027
9028
  ...options.budget ? { budget: options.budget } : {},
9028
9029
  ...options.signal ? { signal: options.signal } : {},
9029
9030
  ...options.onToolCall ? { onToolCall: options.onToolCall } : {}
9030
9031
  });
9032
+ let finalText = run.finalText.trim();
9033
+ if (!finalText && run.stoppedReason !== "refusal") {
9034
+ const closing = await options.inference.chat({
9035
+ model: "brokered",
9036
+ system: `${SYSTEM_PROMPT_FOR[surface]}
9037
+
9038
+ Finish with a concise, non-empty answer to the owner. Do not call tools or promise future work.`,
9039
+ messages: [...run.messages, { role: "user", content: "Give the owner the closing answer now, grounded in the repository evidence and tool results above." }],
9040
+ maxTokens: 16384,
9041
+ ...options.signal ? { signal: options.signal } : {}
9042
+ });
9043
+ finalText = (0, import_ai5.extractText)(closing.content).trim();
9044
+ }
9045
+ const missingClosing = !finalText && run.stoppedReason !== "refusal";
9031
9046
  return {
9032
- status: run.stoppedReason === "refusal" ? "failed" : "completed",
9033
- finalText: run.finalText,
9047
+ status: run.stoppedReason === "refusal" || missingClosing ? "failed" : "completed",
9048
+ finalText,
9034
9049
  stoppedReason: run.stoppedReason,
9035
- ...run.stoppedReason === "refusal" ? { error: run.finalText || "the agent refused the task" } : {}
9050
+ ...run.stoppedReason === "refusal" ? { error: finalText || "the agent refused the task" } : missingClosing ? { error: "the Code agent did not produce a closing answer" } : {}
9036
9051
  };
9037
9052
  } catch (cause) {
9038
9053
  const error = (cause instanceof Error ? cause.message : String(cause)).slice(0, 2e3);
@@ -9040,31 +9055,7 @@ async function runCodeAgentAttempt(options) {
9040
9055
  }
9041
9056
  }
9042
9057
  async function handleCodeRuntimeInference(input) {
9043
- const { command, metadata: metadata2, request: request3, state: state2 } = input;
9044
- if (state2.tokens >= metadata2.maxTokensPerInteraction) {
9045
- if (!state2.noticeEmitted) {
9046
- state2.noticeEmitted = true;
9047
- await input.event({
9048
- type: "message",
9049
- actor: "system",
9050
- body: `The agent paused at the ${metadata2.maxTokensPerInteraction.toLocaleString("en-US")}-token per-interaction limit. Send a new instruction to continue.`
9051
- }).catch(() => void 0);
9052
- }
9053
- return {
9054
- protocolVersion: HARNESS_PROTOCOL_VERSION,
9055
- type: "inference.response",
9056
- requestId: request3.requestId,
9057
- response: {
9058
- id: `budget:${command.commandId}`,
9059
- provider: "openai",
9060
- model: "interaction-budget",
9061
- role: "assistant",
9062
- content: [{ type: "text", text: "Pause now. The owner-set token limit for this interaction has been reached." }],
9063
- stopReason: "end_turn",
9064
- usage: { inputTokens: 0, outputTokens: 0 }
9065
- }
9066
- };
9067
- }
9058
+ const { command, request: request3, state: state2 } = input;
9068
9059
  const startedAt = Date.now();
9069
9060
  const response2 = await input.control.infer(command.sessionId, {
9070
9061
  requestId: request3.requestId,
@@ -9084,7 +9075,6 @@ async function handleCodeRuntimeInference(input) {
9084
9075
  durationMs: Date.now() - startedAt,
9085
9076
  interactionId: command.commandId,
9086
9077
  interactionTokens: state2.tokens,
9087
- interactionMaxTokens: metadata2.maxTokensPerInteraction,
9088
9078
  ...costUsd === void 0 ? {} : { costUsd },
9089
9079
  ...state2.costKnown ? { interactionCostUsd: state2.costUsd } : {}
9090
9080
  }).catch(() => void 0);
@@ -9914,9 +9904,9 @@ async function appendCodeRuntimeEvent(control, command, event, refs) {
9914
9904
  const bounded = event.type === "message" ? { ...event, body: event.body.trim().slice(0, 2e4) || `${event.actor} event` } : event;
9915
9905
  await control.appendSessionEvent(command.sessionId, eventId, bounded);
9916
9906
  }
9917
- var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
9918
- var init_chunk_L2T3LPEF = __esm({
9919
- "../harness/dist/chunk-L2T3LPEF.js"() {
9907
+ var import_crypto, import_promises5, import_path5, import_child_process4, import_promises6, import_path6, import_child_process5, import_process2, import_crypto2, import_crypto3, import_fs2, import_promises7, import_path7, import_promises8, import_os3, import_path8, import_ai4, import_ai5, import_promises9, import_path9, import_promises10, import_promises11, import_path10, import_crypto4, CODE_RUNTIME_PROTOCOL_VERSION, CodeRuntimeReconciler, CodeRuntimeControlError, record5, invalid, RESERVED, SECRET, PATH, FORBIDDEN, ARTIFACT_PATH, PRIVATE_ARTIFACT_PART, SHA3, DIGEST3, ID3, RULE, DEFAULT_PREFIXES, DEFAULT_SUFFIXES, message, CodeRuntimeCheckpointManager, record22, SOURCE_LIMITS, RESERVED2, SECRET2, SOURCE_MAX_FILES, SOURCE_MAX_BYTES, SOURCE_SET_MAX_BYTES, V1_SYSTEM_PROMPT, V2_SYSTEM_PROMPT, V3_SYSTEM_PROMPT, SYSTEM_PROMPT_FOR, DEFAULT_MAX_FILES, DEFAULT_MAX_RESULTS, DEFAULT_MAX_FILE_BYTES, DESTINATIONS, READ, LIST, SEARCH, GRAPH, PATCH, RECIPE, cache, shortId, GRAPH_TOOLS, MAX_MEMORY_BODY, POSITIVE, digestRuntimeValue, runtimeErrorMessage, CodePiRuntimeEngine;
9908
+ var init_chunk_5HX5LWTG = __esm({
9909
+ "../harness/dist/chunk-5HX5LWTG.js"() {
9920
9910
  "use strict";
9921
9911
  init_cjs_shims();
9922
9912
  init_chunk_K76I2TCQ();
@@ -9941,6 +9931,7 @@ var init_chunk_L2T3LPEF = __esm({
9941
9931
  import_os3 = require("os");
9942
9932
  import_path8 = require("path");
9943
9933
  import_ai4 = require("@odla-ai/ai");
9934
+ import_ai5 = require("@odla-ai/ai");
9944
9935
  import_promises9 = require("fs/promises");
9945
9936
  import_path9 = require("path");
9946
9937
  init_dist();
@@ -10337,7 +10328,7 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10337
10328
  recipeAuthorization: this.options.recipeAuthorization
10338
10329
  }, lease, metadata2.role));
10339
10330
  const startedAt = Date.now();
10340
- const interaction = { tokens: 0, noticeEmitted: false, costUsd: 0, costKnown: true };
10331
+ const interaction = { tokens: 0, costUsd: 0, costKnown: true };
10341
10332
  const inference = createCodeRuntimeInference({
10342
10333
  command,
10343
10334
  metadata: metadata2,
@@ -10352,31 +10343,30 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
10352
10343
  lease,
10353
10344
  workspaceDir: active.workspace.workspaceDir,
10354
10345
  prompt: metadata2.prompt,
10355
- signal: active.abort.signal,
10356
- // The owner's per-interaction allowance, enforced by runAgent against
10357
- // INCREMENTAL usage. The control plane still reserves against the same
10358
- // ceiling, but this is what stops the loop cleanly at the boundary rather
10359
- // than letting it discover the limit through a synthesized pause reply.
10360
- budget: { maxTotalTokens: metadata2.maxTokensPerInteraction }
10346
+ signal: active.abort.signal
10361
10347
  });
10362
- const body = result.finalText.trim() || (result.status === "completed" ? "The agent finished without a closing message." : result.error ?? "The agent failed.");
10348
+ const closing = result.finalText.trim();
10349
+ const completed = result.status === "completed" && Boolean(closing);
10350
+ const detail = result.error?.trim() || (closing ? "the Code agent failed" : "the Code agent did not produce a closing answer");
10351
+ const body = closing || detail;
10363
10352
  await this.#event(command, {
10364
10353
  type: "message",
10365
- actor: result.status === "completed" ? "agent" : "system",
10354
+ actor: completed ? "agent" : "system",
10366
10355
  body
10367
10356
  }, active.conversationRefs).catch(() => void 0);
10368
10357
  await this.#event(command, {
10369
10358
  type: "status",
10370
- status: result.status === "completed" ? "idle" : "failed",
10359
+ status: completed ? "idle" : "failed",
10371
10360
  durationMs: Date.now() - startedAt
10372
10361
  }, active.conversationRefs).catch(() => void 0);
10373
- if (result.status === "failed") {
10374
- const detail = (result.error ?? "").trim() || "the Code agent failed";
10362
+ if (!completed) {
10375
10363
  await this.#diagnostic(command, active, detail);
10376
10364
  await this.#failure(command, active, detail);
10377
10365
  }
10378
10366
  return {
10379
10367
  ...result,
10368
+ status: completed ? "completed" : "failed",
10369
+ ...!completed ? { error: detail } : {},
10380
10370
  tokens: interaction.tokens,
10381
10371
  ...interaction.costKnown ? { costUsd: interaction.costUsd } : {}
10382
10372
  };
@@ -10438,7 +10428,7 @@ var init_node = __esm({
10438
10428
  "../harness/dist/node.js"() {
10439
10429
  "use strict";
10440
10430
  init_cjs_shims();
10441
- init_chunk_L2T3LPEF();
10431
+ init_chunk_5HX5LWTG();
10442
10432
  init_chunk_K76I2TCQ();
10443
10433
  MEASURED_PREMIUM = Object.freeze({
10444
10434
  /** 3 racers vs pure depth at equal budget: 21,044 / 7,936. */
@@ -11519,12 +11509,12 @@ Usage:
11519
11509
  odla-ai pm <goal|task|decision|bug> rm <id>
11520
11510
  odla-ai pm handoff --app <id> [--project <id>] [--json]
11521
11511
  odla-ai discuss groups [--json]
11522
- odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--json]
11512
+ odla-ai discuss list [--app <id>] [--q <text>] [--state open|resolved|all] [--mentions] [--json]
11523
11513
  odla-ai discuss read <topic> [--limit <n> --offset <n>] [--json]
11524
11514
  odla-ai discuss post --app <id> --subject "..." --body "..." [--markup "... @[Label](kind/id)"] [--mutation-id <id>]
11525
11515
  odla-ai discuss reply <topic> --body "..." [--markup "..."] [--mutation-id <id>]
11526
11516
  odla-ai discuss resolve <topic> [--reopen] [--mutation-id <id>]
11527
- odla-ai discuss who --q <text> [--app <id>] [--kinds user,pm:task] [--json]
11517
+ odla-ai discuss who --q <text> [--app <id>] [--topic <id>] [--kinds user,pm:task] [--json]
11528
11518
  odla-ai discuss watch [<topic>] [--cursor <cursor>] [--by <authorId>] [--self <authorId>] [--interval <s>] [--timeout <s>] [--json|--jsonl]
11529
11519
  odla-ai agent jobs [--env dev] [--state pending|running|succeeded|dead_letter] [--limit 50] [--token <ODLA_API_KEY>] [--json]
11530
11520
  odla-ai agent retry <job-id> [--env dev] [--token <ODLA_API_KEY>] [--json]
@@ -11992,6 +11982,7 @@ async function discussList(ctx, parsed) {
11992
11982
  const value2 = stringOpt(parsed.options[flag]);
11993
11983
  if (value2) query.set(param, value2);
11994
11984
  }
11985
+ if (parsed.options.mentions === true) query.set("mentions", "1");
11995
11986
  const qs = query.toString();
11996
11987
  const page2 = await request(ctx, "GET", `/topics${qs ? `?${qs}` : ""}`);
11997
11988
  emit(ctx, page2, () => {
@@ -12098,11 +12089,16 @@ async function discussWho(ctx, parsed) {
12098
12089
  if (app) query.set("app", app);
12099
12090
  const kinds = stringOpt(parsed.options.kinds);
12100
12091
  if (kinds) query.set("kinds", kinds);
12092
+ const topic = stringOpt(parsed.options.topic);
12093
+ if (topic) query.set("topic", topic);
12101
12094
  const found = await request(ctx, "GET", `/mentionables?${query.toString()}`);
12102
12095
  emit(ctx, found, () => {
12103
- ctx.out.log("mention kind hint");
12096
+ ctx.out.log("mention kind use hint");
12104
12097
  for (const item of found.items) {
12105
- ctx.out.log(`@[${item.label}](${item.kind}/${item.id}) ${item.kind} ${item.hint ?? ""}`);
12098
+ const use = item.kind === "agent" ? item.meta?.executionAvailability === "runnable" ? "runnable" : "reference" : "";
12099
+ ctx.out.log(
12100
+ `@[${item.label}](${item.kind}/${item.id}) ${item.kind} ${use} ${item.hint ?? ""}`
12101
+ );
12106
12102
  }
12107
12103
  if (found.failed.length > 0) ctx.out.error(`(no results from: ${found.failed.join(", ")})`);
12108
12104
  });
@@ -12457,6 +12453,8 @@ var init_discuss_command = __esm({
12457
12453
  "reopen",
12458
12454
  "by",
12459
12455
  "self",
12456
+ "topic",
12457
+ "mentions",
12460
12458
  "interval",
12461
12459
  "timeout",
12462
12460
  "cursor",
@@ -14752,7 +14750,7 @@ async function provision(options) {
14752
14750
  const key = import_node_process17.default.env[cfg.ai.keyEnv];
14753
14751
  if (key) {
14754
14752
  const secretName = cfg.ai.secretName ?? defaultSecretName(cfg.ai.provider);
14755
- await (0, import_ai5.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
14753
+ await (0, import_ai6.putSecret)({ endpoint: cfg.dbEndpoint, token, fetch: doFetch }, tenantId, secretName, key);
14756
14754
  out.log(`${env}: ${cfg.ai.provider} key stored in vault (${secretName})`);
14757
14755
  } else {
14758
14756
  out.log(`${env}: ${cfg.ai.keyEnv} not set; skipped provider key storage`);
@@ -14788,13 +14786,13 @@ async function provision(options) {
14788
14786
  }
14789
14787
  }
14790
14788
  }
14791
- var import_apps13, import_ai5, import_node_process17;
14789
+ var import_apps13, import_ai6, import_node_process17;
14792
14790
  var init_provision = __esm({
14793
14791
  "src/provision.ts"() {
14794
14792
  "use strict";
14795
14793
  init_cjs_shims();
14796
14794
  import_apps13 = require("@odla-ai/apps");
14797
- import_ai5 = require("@odla-ai/ai");
14795
+ import_ai6 = require("@odla-ai/ai");
14798
14796
  import_node_process17 = __toESM(require("process"), 1);
14799
14797
  init_config();
14800
14798
  init_calendar();