zam-core 0.11.0 → 0.12.0

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.
@@ -6178,9 +6178,18 @@ function getMachineCompanionConfig(path = defaultConfigPath()) {
6178
6178
  if (typeof raw.selectedEvaluatorId === "string") {
6179
6179
  result.selectedEvaluatorId = raw.selectedEvaluatorId;
6180
6180
  }
6181
+ if (typeof raw.selectedVscodeEvaluatorId === "string") {
6182
+ result.selectedVscodeEvaluatorId = raw.selectedVscodeEvaluatorId;
6183
+ }
6184
+ if (typeof raw.selectedAntigravityEvaluatorId === "string") {
6185
+ result.selectedAntigravityEvaluatorId = raw.selectedAntigravityEvaluatorId;
6186
+ }
6181
6187
  if (typeof raw.selectedVscodeModelId === "string") {
6182
6188
  result.selectedVscodeModelId = raw.selectedVscodeModelId;
6183
6189
  }
6190
+ if (typeof raw.selectedAntigravityModelId === "string") {
6191
+ result.selectedAntigravityModelId = raw.selectedAntigravityModelId;
6192
+ }
6184
6193
  if (raw.collapsed && typeof raw.collapsed === "object" && !Array.isArray(raw.collapsed)) {
6185
6194
  const collapsed = {};
6186
6195
  for (const [surface, value] of Object.entries(raw.collapsed)) {
@@ -6211,6 +6220,34 @@ function updateMachineCompanionConfig(update, path = defaultConfigPath()) {
6211
6220
  delete companion.selectedEvaluatorId;
6212
6221
  }
6213
6222
  }
6223
+ if ("selectedVscodeEvaluatorId" in update) {
6224
+ if (update.selectedVscodeEvaluatorId) {
6225
+ companion.selectedVscodeEvaluatorId = update.selectedVscodeEvaluatorId;
6226
+ } else {
6227
+ delete companion.selectedVscodeEvaluatorId;
6228
+ }
6229
+ }
6230
+ if ("selectedAntigravityEvaluatorId" in update) {
6231
+ if (update.selectedAntigravityEvaluatorId) {
6232
+ companion.selectedAntigravityEvaluatorId = update.selectedAntigravityEvaluatorId;
6233
+ } else {
6234
+ delete companion.selectedAntigravityEvaluatorId;
6235
+ }
6236
+ }
6237
+ if ("selectedVscodeModelId" in update) {
6238
+ if (update.selectedVscodeModelId) {
6239
+ companion.selectedVscodeModelId = update.selectedVscodeModelId;
6240
+ } else {
6241
+ delete companion.selectedVscodeModelId;
6242
+ }
6243
+ }
6244
+ if ("selectedAntigravityModelId" in update) {
6245
+ if (update.selectedAntigravityModelId) {
6246
+ companion.selectedAntigravityModelId = update.selectedAntigravityModelId;
6247
+ } else {
6248
+ delete companion.selectedAntigravityModelId;
6249
+ }
6250
+ }
6214
6251
  if (update.collapsed) {
6215
6252
  companion.collapsed = {
6216
6253
  ...companion.collapsed ?? {},
@@ -6244,6 +6281,30 @@ function setCompanionSelectedEvaluatorId(evaluatorId, path = defaultConfigPath()
6244
6281
  }
6245
6282
  saveMachineCompanionConfig(companion, path);
6246
6283
  }
6284
+ function getCompanionSelectedVscodeEvaluatorId(path = defaultConfigPath()) {
6285
+ return getMachineCompanionConfig(path).selectedVscodeEvaluatorId;
6286
+ }
6287
+ function setCompanionSelectedVscodeEvaluatorId(evaluatorId, path = defaultConfigPath()) {
6288
+ const companion = getMachineCompanionConfig(path);
6289
+ if (evaluatorId) {
6290
+ companion.selectedVscodeEvaluatorId = evaluatorId;
6291
+ } else {
6292
+ delete companion.selectedVscodeEvaluatorId;
6293
+ }
6294
+ saveMachineCompanionConfig(companion, path);
6295
+ }
6296
+ function getCompanionSelectedAntigravityEvaluatorId(path = defaultConfigPath()) {
6297
+ return getMachineCompanionConfig(path).selectedAntigravityEvaluatorId;
6298
+ }
6299
+ function setCompanionSelectedAntigravityEvaluatorId(evaluatorId, path = defaultConfigPath()) {
6300
+ const companion = getMachineCompanionConfig(path);
6301
+ if (evaluatorId) {
6302
+ companion.selectedAntigravityEvaluatorId = evaluatorId;
6303
+ } else {
6304
+ delete companion.selectedAntigravityEvaluatorId;
6305
+ }
6306
+ saveMachineCompanionConfig(companion, path);
6307
+ }
6247
6308
  function getCompanionSelectedVscodeModelId(path = defaultConfigPath()) {
6248
6309
  return getMachineCompanionConfig(path).selectedVscodeModelId;
6249
6310
  }
@@ -6256,6 +6317,18 @@ function setCompanionSelectedVscodeModelId(modelId, path = defaultConfigPath())
6256
6317
  }
6257
6318
  saveMachineCompanionConfig(companion, path);
6258
6319
  }
6320
+ function getCompanionSelectedAntigravityModelId(path = defaultConfigPath()) {
6321
+ return getMachineCompanionConfig(path).selectedAntigravityModelId;
6322
+ }
6323
+ function setCompanionSelectedAntigravityModelId(modelId, path = defaultConfigPath()) {
6324
+ const companion = getMachineCompanionConfig(path);
6325
+ if (modelId) {
6326
+ companion.selectedAntigravityModelId = modelId;
6327
+ } else {
6328
+ delete companion.selectedAntigravityModelId;
6329
+ }
6330
+ saveMachineCompanionConfig(companion, path);
6331
+ }
6259
6332
  function getCompanionCollapsed(path = defaultConfigPath()) {
6260
6333
  return getMachineCompanionConfig(path).collapsed ?? {};
6261
6334
  }
@@ -6984,8 +7057,11 @@ __export(kernel_exports, {
6984
7057
  getCardById: () => getCardById,
6985
7058
  getCardDeletionImpact: () => getCardDeletionImpact,
6986
7059
  getCompanionCollapsed: () => getCompanionCollapsed,
7060
+ getCompanionSelectedAntigravityEvaluatorId: () => getCompanionSelectedAntigravityEvaluatorId,
7061
+ getCompanionSelectedAntigravityModelId: () => getCompanionSelectedAntigravityModelId,
6987
7062
  getCompanionSelectedEvaluatorId: () => getCompanionSelectedEvaluatorId,
6988
7063
  getCompanionSelectedUserId: () => getCompanionSelectedUserId,
7064
+ getCompanionSelectedVscodeEvaluatorId: () => getCompanionSelectedVscodeEvaluatorId,
6989
7065
  getCompanionSelectedVscodeModelId: () => getCompanionSelectedVscodeModelId,
6990
7066
  getConfiguredWorkspaces: () => getConfiguredWorkspaces,
6991
7067
  getDatabaseTargetInfo: () => getDatabaseTargetInfo,
@@ -7098,8 +7174,11 @@ __export(kernel_exports, {
7098
7174
  setActiveWorkspaceId: () => setActiveWorkspaceId,
7099
7175
  setAgentConnectAutoDone: () => setAgentConnectAutoDone,
7100
7176
  setCompanionCollapsed: () => setCompanionCollapsed,
7177
+ setCompanionSelectedAntigravityEvaluatorId: () => setCompanionSelectedAntigravityEvaluatorId,
7178
+ setCompanionSelectedAntigravityModelId: () => setCompanionSelectedAntigravityModelId,
7101
7179
  setCompanionSelectedEvaluatorId: () => setCompanionSelectedEvaluatorId,
7102
7180
  setCompanionSelectedUserId: () => setCompanionSelectedUserId,
7181
+ setCompanionSelectedVscodeEvaluatorId: () => setCompanionSelectedVscodeEvaluatorId,
7103
7182
  setCompanionSelectedVscodeModelId: () => setCompanionSelectedVscodeModelId,
7104
7183
  setInstallChannel: () => setInstallChannel,
7105
7184
  setInstallMode: () => setInstallMode,
@@ -7180,330 +7259,119 @@ var init_kernel = __esm({
7180
7259
  }
7181
7260
  });
7182
7261
 
7183
- // src/cli/commands/mcp.ts
7184
- init_kernel();
7185
- import { existsSync as existsSync21, readFileSync as readFileSync18 } from "fs";
7186
- import { dirname as dirname11, join as join24 } from "path";
7187
- import { fileURLToPath as fileURLToPath6 } from "url";
7188
- import {
7189
- RESOURCE_MIME_TYPE,
7190
- registerAppResource,
7191
- registerAppTool
7192
- } from "@modelcontextprotocol/ext-apps/server";
7193
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7194
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7195
- import { z } from "zod";
7196
-
7197
- // src/vscode-extension/companion-evaluator.ts
7198
- var ROUTABLE_EVALUATOR_IDS = [
7199
- "native-mcp-host",
7200
- "vscode-lm",
7201
- "quick-mode"
7202
- ];
7203
- var DETACHED_HARNESS_EVALUATOR_IDS = [
7204
- "claude-code",
7205
- "codex",
7206
- "opencode",
7207
- "goose"
7208
- ];
7209
- function isEvaluatorId(value) {
7210
- return typeof value === "string" && (ROUTABLE_EVALUATOR_IDS.includes(value) || DETACHED_HARNESS_EVALUATOR_IDS.includes(value));
7262
+ // src/cli/llm/client.ts
7263
+ var client_exports = {};
7264
+ __export(client_exports, {
7265
+ DEFAULT_LLM_API_KEY: () => DEFAULT_LLM_API_KEY,
7266
+ DEFAULT_LLM_MAX_TOKENS: () => DEFAULT_LLM_MAX_TOKENS,
7267
+ DEFAULT_LLM_MODEL: () => DEFAULT_LLM_MODEL,
7268
+ DEFAULT_LLM_URL: () => DEFAULT_LLM_URL,
7269
+ LlmResponseTruncatedError: () => LlmResponseTruncatedError,
7270
+ RECALL_DISCUSSION_MAX_OUTPUT_TOKENS: () => RECALL_DISCUSSION_MAX_OUTPUT_TOKENS,
7271
+ RECALL_EVALUATION_MAX_OUTPUT_TOKENS: () => RECALL_EVALUATION_MAX_OUTPUT_TOKENS,
7272
+ RECALL_QUESTION_MAX_OUTPUT_TOKENS: () => RECALL_QUESTION_MAX_OUTPUT_TOKENS,
7273
+ checkVisionReadiness: () => checkVisionReadiness,
7274
+ clearRecallEndpointCache: () => clearRecallEndpointCache,
7275
+ discussReviewViaLLM: () => discussReviewViaLLM,
7276
+ ensureHighQualityQuestion: () => ensureHighQualityQuestion,
7277
+ ensureLlmReadyHeadless: () => ensureLlmReadyHeadless,
7278
+ ensureLocalLlmRunning: () => ensureLocalLlmRunning,
7279
+ evaluateAnswerViaLLM: () => evaluateAnswerViaLLM,
7280
+ extractTextFromScanViaLLM: () => extractTextFromScanViaLLM,
7281
+ fetchWithInteractiveTimeout: () => fetchWithInteractiveTimeout,
7282
+ generateFoundationsProposalsViaLLM: () => generateFoundationsProposalsViaLLM,
7283
+ generateQuestionViaLLM: () => generateQuestionViaLLM,
7284
+ generateSplitProposalsViaLLM: () => generateSplitProposalsViaLLM,
7285
+ generateTitleViaLLM: () => generateTitleViaLLM,
7286
+ getAvailableModels: () => getAvailableModels,
7287
+ getCloudModelRecommendation: () => getCloudModelRecommendation,
7288
+ getLlmConfig: () => getLlmConfig,
7289
+ getProviderForRole: () => getProviderForRole,
7290
+ getProviderRoleStatus: () => getProviderRoleStatus,
7291
+ getVisionConfig: () => getVisionConfig,
7292
+ importCurriculumViaLLM: () => importCurriculumViaLLM,
7293
+ inferApiFlavor: () => inferApiFlavor,
7294
+ isLlmOnline: () => isLlmOnline,
7295
+ repairUmlautsViaLLM: () => repairUmlautsViaLLM,
7296
+ resolveCapability: () => resolveCapability,
7297
+ resolveUsableRecallEndpoint: () => resolveUsableRecallEndpoint,
7298
+ sampleViaLocalLLM: () => sampleViaLocalLLM,
7299
+ translateQuestionViaLLM: () => translateQuestionViaLLM
7300
+ });
7301
+ import { spawn } from "child_process";
7302
+ import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
7303
+ function clearRecallEndpointCache() {
7304
+ cachedRecallEndpoint = null;
7211
7305
  }
7212
- function buildEvaluatorRoutes(inputs, selectedId) {
7213
- return inputs.map((input) => {
7214
- if (!input.routable && !input.reason) {
7215
- throw new Error(
7216
- `Unroutable evaluator "${input.id}" must carry an availability reason`
7217
- );
7218
- }
7219
- return { ...input, selected: input.id === selectedId, active: false };
7220
- });
7306
+ function recallEndpointSignature(cfg) {
7307
+ return [
7308
+ cfg.enabled ? "on" : "off",
7309
+ cfg.providerName ?? "",
7310
+ cfg.url,
7311
+ cfg.model,
7312
+ cfg.apiFlavor,
7313
+ cfg.fallback?.url ?? "",
7314
+ cfg.fallback?.model ?? ""
7315
+ ].join("|");
7221
7316
  }
7222
- var EvaluatorUnavailableError = class extends Error {
7223
- evaluatorId;
7224
- constructor(evaluatorId, reason) {
7225
- super(`Evaluator "${evaluatorId}" is unavailable: ${reason}`);
7226
- this.name = "EvaluatorUnavailableError";
7227
- this.evaluatorId = evaluatorId;
7317
+ async function getLlmConfig(db) {
7318
+ return {
7319
+ enabled: await getSetting(db, "llm.enabled") === "true",
7320
+ url: await getSetting(db, "llm.url") || DEFAULT_LLM_URL,
7321
+ model: await getSetting(db, "llm.model") || DEFAULT_LLM_MODEL,
7322
+ apiKey: await getSetting(db, "llm.api_key") || DEFAULT_LLM_API_KEY,
7323
+ locale: await getSetting(db, "system.locale") || "en"
7324
+ };
7325
+ }
7326
+ function getCloudModelRecommendation(url) {
7327
+ const lowercase = url.toLowerCase();
7328
+ if (lowercase.includes("openrouter.ai")) {
7329
+ return { model: "openrouter/free", flavor: "chat-completions" };
7228
7330
  }
7229
- };
7230
- function activateSelectedEvaluator(routes, selectedId) {
7231
- const route = routes.find((candidate) => candidate.id === selectedId);
7232
- if (!route) {
7233
- throw new EvaluatorUnavailableError(selectedId, "not configured");
7331
+ if (lowercase.includes("openai.com") || lowercase.includes("api.openai")) {
7332
+ return { model: "gpt-5-mini", flavor: "chat-completions" };
7234
7333
  }
7235
- if (!route.routable) {
7236
- throw new EvaluatorUnavailableError(
7237
- selectedId,
7238
- route.reason ?? "not routable on this surface"
7239
- );
7334
+ if (lowercase.includes("googleapis.com") || lowercase.includes("google")) {
7335
+ return { model: "gemini-3.5-flash", flavor: "chat-completions" };
7240
7336
  }
7241
- return { ...route, active: true };
7242
- }
7243
-
7244
- // src/vscode-extension/companion-selection.ts
7245
- function resolveSelection(candidates) {
7246
- if (candidates.invocation !== void 0) {
7247
- return {
7248
- value: candidates.invocation,
7249
- source: "invocation",
7250
- sessionScoped: true
7251
- };
7337
+ if (lowercase.includes("deepseek.com")) {
7338
+ return { model: "deepseek-v4-flash", flavor: "chat-completions" };
7252
7339
  }
7253
- if (candidates.manual !== void 0) {
7254
- return { value: candidates.manual, source: "manual", sessionScoped: false };
7340
+ if (lowercase.includes("mimo")) {
7341
+ return { model: "mimo-v2.5", flavor: "chat-completions" };
7255
7342
  }
7256
- if (candidates.persisted !== void 0) {
7343
+ if (lowercase.includes("anthropic.com")) {
7257
7344
  return {
7258
- value: candidates.persisted,
7259
- source: "persisted",
7260
- sessionScoped: false
7345
+ model: "claude-haiku-4-5-20251001",
7346
+ flavor: "anthropic-messages"
7261
7347
  };
7262
7348
  }
7263
- return {
7264
- value: candidates.fallback,
7265
- source: "default",
7266
- sessionScoped: false
7267
- };
7268
- }
7269
- function isPersistableSelection(result) {
7270
- return result.source === "manual";
7271
- }
7272
-
7273
- // src/vscode-extension/companion-context.ts
7274
- var COMPANION_SURFACES = [
7275
- "recall",
7276
- "graph",
7277
- "settings",
7278
- "studio"
7279
- ];
7280
- function isCompanionSurface(value) {
7281
- return typeof value === "string" && COMPANION_SURFACES.includes(value);
7282
- }
7283
- function isRecord2(value) {
7284
- return typeof value === "object" && value !== null && !Array.isArray(value);
7349
+ return null;
7285
7350
  }
7286
- var KNOWN_NATIVE_HOSTS = {
7287
- "vscode-zam-companion": {
7288
- normalizedId: "vscode-companion",
7289
- label: "VS Code Companion"
7290
- }
7291
- };
7292
- function normalizeNativeHostIdentity(clientInfo, harnessOverride) {
7293
- if (harnessOverride) {
7294
- const known2 = KNOWN_NATIVE_HOSTS[harnessOverride];
7295
- return {
7296
- rawName: clientInfo?.name ?? harnessOverride,
7297
- version: clientInfo?.version,
7298
- normalizedId: known2?.normalizedId,
7299
- label: known2?.label ?? harnessOverride
7300
- };
7351
+ function inferApiFlavor(url) {
7352
+ try {
7353
+ return new URL(url).hostname.toLowerCase().endsWith("anthropic.com") ? "anthropic-messages" : "chat-completions";
7354
+ } catch {
7355
+ return "chat-completions";
7301
7356
  }
7302
- if (!clientInfo) return void 0;
7303
- const known = KNOWN_NATIVE_HOSTS[clientInfo.name];
7304
- return {
7305
- rawName: clientInfo.name,
7306
- version: clientInfo.version,
7307
- normalizedId: known?.normalizedId,
7308
- label: known?.label ?? clientInfo.name
7309
- };
7310
- }
7311
- function resolveCollapsedForSurface(state, surface) {
7312
- return state?.[surface] ?? false;
7313
7357
  }
7314
- function parseCompanionContextReadRequest(value) {
7315
- if (!isRecord2(value) || !isCompanionSurface(value.surface)) {
7316
- throw new Error("Invalid companion context read request");
7358
+ async function readJsonSetting(db, key) {
7359
+ const raw = await getSetting(db, key);
7360
+ if (!raw) return null;
7361
+ try {
7362
+ return JSON.parse(raw);
7363
+ } catch {
7364
+ return null;
7317
7365
  }
7318
- const rawClientInfo = value.clientInfo;
7319
- const clientInfo = isRecord2(rawClientInfo) && typeof rawClientInfo.name === "string" ? {
7320
- name: rawClientInfo.name,
7321
- version: typeof rawClientInfo.version === "string" ? rawClientInfo.version : void 0
7322
- } : void 0;
7323
- const harnessOverride = typeof value.harnessOverride === "string" ? value.harnessOverride : void 0;
7324
- return { surface: value.surface, clientInfo, harnessOverride };
7325
7366
  }
7326
- function parseCompanionContextWriteRequest(value) {
7327
- if (!isRecord2(value) || !isCompanionSurface(value.surface)) {
7328
- throw new Error("Invalid companion context write request");
7329
- }
7330
- const userId = typeof value.userId === "string" ? value.userId : void 0;
7331
- let evaluatorId;
7332
- if (value.evaluatorId !== void 0) {
7333
- if (!isEvaluatorId(value.evaluatorId)) {
7334
- throw new Error(
7335
- `Unknown evaluator id in companion context write request: ${String(value.evaluatorId)}`
7336
- );
7337
- }
7338
- evaluatorId = value.evaluatorId;
7339
- }
7340
- const collapsed = typeof value.collapsed === "boolean" ? value.collapsed : void 0;
7341
- if (userId === void 0 && evaluatorId === void 0 && collapsed === void 0) {
7342
- throw new Error(
7343
- "Companion context write request must set userId, evaluatorId, and/or collapsed"
7344
- );
7367
+ function resolveProviderApiKey(rec) {
7368
+ if (rec.apiKey) return rec.apiKey;
7369
+ if (rec.apiKeyRef) {
7370
+ const key = getProviderApiKey(rec.apiKeyRef);
7371
+ if (key) return key;
7345
7372
  }
7346
- return { surface: value.surface, userId, evaluatorId, collapsed };
7373
+ return DEFAULT_LLM_API_KEY;
7347
7374
  }
7348
- function buildCompanionContext(input) {
7349
- const userSelection = resolveSelection(input.userSelection);
7350
- const evaluatorSelection = resolveSelection(input.evaluatorSelection);
7351
- const routes = buildEvaluatorRoutes(
7352
- input.evaluatorRouteInputs,
7353
- evaluatorSelection.value
7354
- );
7355
- let activeEvaluatorId;
7356
- let activeEvaluatorError;
7357
- try {
7358
- activateSelectedEvaluator(routes, evaluatorSelection.value);
7359
- activeEvaluatorId = evaluatorSelection.value;
7360
- } catch (error) {
7361
- if (error instanceof EvaluatorUnavailableError) {
7362
- activeEvaluatorError = error;
7363
- } else {
7364
- throw error;
7365
- }
7366
- }
7367
- const evaluators = activeEvaluatorId ? routes.map(
7368
- (route) => route.id === activeEvaluatorId ? { ...route, active: true } : route
7369
- ) : routes;
7370
- return {
7371
- read: {
7372
- surface: input.surface,
7373
- nativeHost: input.nativeHost,
7374
- user: {
7375
- currentId: userSelection.value,
7376
- persistedId: input.userSelection.persisted,
7377
- source: userSelection.source
7378
- },
7379
- profiles: input.profiles ?? [],
7380
- harnesses: input.harnesses,
7381
- evaluators,
7382
- selectedEvaluatorId: evaluatorSelection.value,
7383
- activeEvaluatorId,
7384
- collapsed: resolveCollapsedForSurface(input.collapsed, input.surface)
7385
- },
7386
- userSelection,
7387
- evaluatorSelection,
7388
- activeEvaluatorError
7389
- };
7390
- }
7391
-
7392
- // src/cli/bridge-handlers.ts
7393
- init_kernel();
7394
- import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
7395
- import { join as join13 } from "path";
7396
-
7397
- // src/cli/knowledge-contexts.ts
7398
- init_kernel();
7399
- async function resolveOperationKnowledgeContexts(db, requestedNames) {
7400
- const explicitNames = [
7401
- ...new Set(requestedNames.map((name) => name.trim()).filter(Boolean))
7402
- ];
7403
- const activeDefault = getActiveWorkspaceContext();
7404
- const selectedNames = explicitNames.length > 0 ? explicitNames : activeDefault ? [activeDefault] : [];
7405
- const contexts = [];
7406
- for (const name of selectedNames) {
7407
- const context = await getKnowledgeContextByName(db, name);
7408
- if (!context) {
7409
- const subject = explicitNames.length > 0 ? "Knowledge" : "Active knowledge";
7410
- throw new Error(`${subject} context not found: ${name}`);
7411
- }
7412
- contexts.push(context);
7413
- }
7414
- return contexts;
7415
- }
7416
-
7417
- // src/cli/llm/client.ts
7418
- init_kernel();
7419
- import { spawn } from "child_process";
7420
- import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
7421
- var DEFAULT_LLM_URL = "http://localhost:8000/v1";
7422
- var DEFAULT_LLM_MAX_TOKENS = 1e4;
7423
- var LOCAL_CURRICULUM_IMPORT_HARD_TIMEOUT_MS = 6e5;
7424
- var CLOUD_CURRICULUM_IMPORT_HARD_TIMEOUT_MS = 18e4;
7425
- var RECALL_QUESTION_MAX_OUTPUT_TOKENS = 400;
7426
- var RECALL_EVALUATION_MAX_OUTPUT_TOKENS = 1200;
7427
- var RECALL_DISCUSSION_MAX_OUTPUT_TOKENS = 1200;
7428
- var RECALL_ENDPOINT_CACHE_MS = 6e4;
7429
- var cachedRecallEndpoint = null;
7430
- function recallEndpointSignature(cfg) {
7431
- return [
7432
- cfg.enabled ? "on" : "off",
7433
- cfg.providerName ?? "",
7434
- cfg.url,
7435
- cfg.model,
7436
- cfg.apiFlavor,
7437
- cfg.fallback?.url ?? "",
7438
- cfg.fallback?.model ?? ""
7439
- ].join("|");
7440
- }
7441
- var DEFAULT_LLM_MODEL = "qwen3.5:4b";
7442
- var DEFAULT_LLM_API_KEY = "sk-none";
7443
- async function getLlmConfig(db) {
7444
- return {
7445
- enabled: await getSetting(db, "llm.enabled") === "true",
7446
- url: await getSetting(db, "llm.url") || DEFAULT_LLM_URL,
7447
- model: await getSetting(db, "llm.model") || DEFAULT_LLM_MODEL,
7448
- apiKey: await getSetting(db, "llm.api_key") || DEFAULT_LLM_API_KEY,
7449
- locale: await getSetting(db, "system.locale") || "en"
7450
- };
7451
- }
7452
- function getCloudModelRecommendation(url) {
7453
- const lowercase = url.toLowerCase();
7454
- if (lowercase.includes("openrouter.ai")) {
7455
- return { model: "openrouter/free", flavor: "chat-completions" };
7456
- }
7457
- if (lowercase.includes("openai.com") || lowercase.includes("api.openai")) {
7458
- return { model: "gpt-5-mini", flavor: "chat-completions" };
7459
- }
7460
- if (lowercase.includes("googleapis.com") || lowercase.includes("google")) {
7461
- return { model: "gemini-3.5-flash", flavor: "chat-completions" };
7462
- }
7463
- if (lowercase.includes("deepseek.com")) {
7464
- return { model: "deepseek-v4-flash", flavor: "chat-completions" };
7465
- }
7466
- if (lowercase.includes("mimo")) {
7467
- return { model: "mimo-v2.5", flavor: "chat-completions" };
7468
- }
7469
- if (lowercase.includes("anthropic.com")) {
7470
- return {
7471
- model: "claude-haiku-4-5-20251001",
7472
- flavor: "anthropic-messages"
7473
- };
7474
- }
7475
- return null;
7476
- }
7477
- function inferApiFlavor(url) {
7478
- try {
7479
- return new URL(url).hostname.toLowerCase().endsWith("anthropic.com") ? "anthropic-messages" : "chat-completions";
7480
- } catch {
7481
- return "chat-completions";
7482
- }
7483
- }
7484
- async function readJsonSetting(db, key) {
7485
- const raw = await getSetting(db, key);
7486
- if (!raw) return null;
7487
- try {
7488
- return JSON.parse(raw);
7489
- } catch {
7490
- return null;
7491
- }
7492
- }
7493
- function resolveProviderApiKey(rec) {
7494
- if (rec.apiKey) return rec.apiKey;
7495
- if (rec.apiKeyRef) {
7496
- const key = getProviderApiKey(rec.apiKeyRef);
7497
- if (key) return key;
7498
- }
7499
- return DEFAULT_LLM_API_KEY;
7500
- }
7501
- var ROLE_TO_CAPABILITY2 = {
7502
- recall: "text",
7503
- text: "text",
7504
- vision: "image",
7505
- embedding: "embedding"
7506
- };
7507
7375
  function materializeModelEntry(entry, base, enabled, maxFrames) {
7508
7376
  const url = entry.url || base.url;
7509
7377
  const cfg = {
@@ -7677,39 +7545,6 @@ async function getVisionConfig(db) {
7677
7545
  maxFrames: p.maxFrames
7678
7546
  };
7679
7547
  }
7680
- var LANGUAGE_NAMES = {
7681
- en: "English",
7682
- de: "German",
7683
- es: "Spanish",
7684
- fr: "French",
7685
- pt: "Portuguese",
7686
- zh: "Chinese",
7687
- ja: "Japanese"
7688
- };
7689
- var LOCALIZED_RATING_PREFIX = {
7690
- en: "Suggested rating",
7691
- de: "Empfohlene Bewertung",
7692
- es: "Calificaci\xF3n sugerida",
7693
- fr: "Note sugg\xE9r\xE9e",
7694
- pt: "Avalia\xE7\xE3o sugerida",
7695
- zh: "\u5EFA\u8BAE\u8BC4\u5206",
7696
- ja: "\u63A8\u5968\u8A55\u4FA1"
7697
- };
7698
- var BLOOM_VERBS2 = {
7699
- 1: "Remember",
7700
- 2: "Understand",
7701
- 3: "Apply",
7702
- 4: "Analyze",
7703
- 5: "Synthesize"
7704
- };
7705
- var LlmResponseTruncatedError = class extends Error {
7706
- constructor(label) {
7707
- super(
7708
- `${label}: the model's response was cut off before producing usable output (finish_reason=length). The prompt likely left no room in the model's context window for a reply.`
7709
- );
7710
- this.name = "LlmResponseTruncatedError";
7711
- }
7712
- };
7713
7548
  async function readChatContent(res, label) {
7714
7549
  if (!res.ok) {
7715
7550
  const errorText = await res.text().catch(() => "");
@@ -7899,8 +7734,6 @@ ${input.sourceLinkContent}` : ""}`;
7899
7734
  providerName: endpoint.providerName
7900
7735
  };
7901
7736
  }
7902
- var MAX_IMPORT_TEXT_CHARS = 2e5;
7903
- var VALID_GENERATED_MODES = /* @__PURE__ */ new Set(["shadowing", "copilot", "autonomy"]);
7904
7737
  function parseGeneratedCardArray(responseText, label, limits) {
7905
7738
  const startIdx = responseText.indexOf("[");
7906
7739
  const endIdx = responseText.lastIndexOf("]");
@@ -7962,7 +7795,6 @@ function parseGeneratedCardArray(responseText, label, limits) {
7962
7795
  };
7963
7796
  });
7964
7797
  }
7965
- var CURRICULUM_CHUNK_TEXT_CHARS = 3e3;
7966
7798
  function splitCurriculumText(text, maxChars) {
7967
7799
  if (text.length <= maxChars) return [text];
7968
7800
  const chunks = [];
@@ -8438,6 +8270,33 @@ async function resolveUsableRecallEndpoint(db) {
8438
8270
  };
8439
8271
  return selected.endpoint;
8440
8272
  }
8273
+ async function sampleViaLocalLLM(db, messages) {
8274
+ const cfg = await getProviderForRole(db, "recall");
8275
+ const endpoint = await resolveUsableRecallEndpoint(db);
8276
+ const res = await fetchWithInteractiveTimeout(
8277
+ `${endpoint.url}/chat/completions`,
8278
+ {
8279
+ method: "POST",
8280
+ headers: {
8281
+ "Content-Type": "application/json",
8282
+ Authorization: `Bearer ${endpoint.apiKey}`
8283
+ },
8284
+ body: JSON.stringify({
8285
+ model: endpoint.model,
8286
+ messages,
8287
+ temperature: 0.3,
8288
+ max_tokens: RECALL_DISCUSSION_MAX_OUTPUT_TOKENS
8289
+ }),
8290
+ locale: cfg.locale
8291
+ }
8292
+ );
8293
+ const text = await readChatContent(res, "LLM sampling");
8294
+ return {
8295
+ text,
8296
+ model: endpoint.model,
8297
+ providerName: endpoint.providerName
8298
+ };
8299
+ }
8441
8300
  async function resolveUsableTextEndpoint(db) {
8442
8301
  const cfg = await getProviderForRole(db, "text");
8443
8302
  if (!cfg.enabled) {
@@ -8493,7 +8352,7 @@ async function prepareRecallChain(db, opts) {
8493
8352
  } else {
8494
8353
  spawnLocalRunner(endpoint.url, endpoint.model, endpoint.runner);
8495
8354
  while (Date.now() < deadline) {
8496
- await new Promise((resolve7) => setTimeout(resolve7, 1e3));
8355
+ await new Promise((resolve8) => setTimeout(resolve8, 1e3));
8497
8356
  if (await isLlmOnline(endpoint.url)) {
8498
8357
  online = true;
8499
8358
  break;
@@ -8786,7 +8645,7 @@ async function startLocalRunner(url, model, locale, hint) {
8786
8645
  let attempts = 0;
8787
8646
  const dotsPerLine = 30;
8788
8647
  while (true) {
8789
- await new Promise((resolve7) => setTimeout(resolve7, 500));
8648
+ await new Promise((resolve8) => setTimeout(resolve8, 500));
8790
8649
  if (await isLlmOnline(url)) {
8791
8650
  if (attempts > 0) process.stdout.write("\n");
8792
8651
  return true;
@@ -8810,123 +8669,833 @@ async function startLocalRunner(url, model, locale, hint) {
8810
8669
  }
8811
8670
  }
8812
8671
  }
8672
+ async function ensureLocalLlmRunning(db) {
8673
+ const result = await prepareRecallChain(db, {
8674
+ timeoutMs: 25e3,
8675
+ interactive: true
8676
+ });
8677
+ if (result.usable) {
8678
+ const location = result.local ? "local" : "cloud";
8679
+ console.log(
8680
+ `\x1B[32m\u2713 Recall LLM ready (${location}: ${result.model}).\x1B[0m`
8681
+ );
8682
+ return { usable: true };
8683
+ }
8684
+ if (result.reason === "unsupported-provider") {
8685
+ console.warn(
8686
+ `\x1B[31m\u2717 Recall provider is not supported for active recall.\x1B[0m`
8687
+ );
8688
+ } else if (result.reason === "model-not-found") {
8689
+ console.warn(
8690
+ `\x1B[31m\u2717 Configured model "${result.model}" is not available on the server.\x1B[0m`
8691
+ );
8692
+ console.warn(` Available models: ${result.availableModels.join(", ")}`);
8693
+ } else if (result.reason === "offline") {
8694
+ console.warn(
8695
+ `\x1B[33m\u26A0 No recall LLM endpoint is reachable. Continuing without AI coaching.\x1B[0m
8696
+ `
8697
+ );
8698
+ }
8699
+ return { usable: false, reason: result.reason };
8700
+ }
8813
8701
  async function ensureLlmReadyHeadless(db, opts = {}) {
8814
8702
  return prepareRecallChain(db, {
8815
8703
  timeoutMs: opts.timeoutMs ?? 25e3,
8816
8704
  interactive: false
8817
8705
  });
8818
8706
  }
8819
- async function fetchWithInteractiveTimeout(url, options = {}) {
8820
- const {
8821
- timeoutMs = 2e4,
8822
- hardTimeoutMs = 12e4,
8823
- locale = "en",
8824
- ...fetchOptions
8825
- } = options;
8826
- const controller = new AbortController();
8827
- const fetchPromise = fetch(url, {
8828
- ...fetchOptions,
8829
- signal: controller.signal
8830
- });
8831
- if (!process.stdout.isTTY || process.env.ZAM_BRIDGE === "true") {
8832
- let timeoutId;
8833
- const hardTimeout = new Promise((_resolve, reject) => {
8834
- timeoutId = setTimeout(() => {
8835
- reject(new Error(`LLM request timed out after ${hardTimeoutMs}ms`));
8836
- controller.abort();
8837
- }, hardTimeoutMs);
8838
- });
8839
- try {
8840
- return await Promise.race([fetchPromise, hardTimeout]);
8841
- } finally {
8842
- clearTimeout(timeoutId);
8843
- }
8707
+ async function fetchWithInteractiveTimeout(url, options = {}) {
8708
+ const {
8709
+ timeoutMs = 2e4,
8710
+ hardTimeoutMs = 12e4,
8711
+ locale = "en",
8712
+ ...fetchOptions
8713
+ } = options;
8714
+ const controller = new AbortController();
8715
+ const fetchPromise = fetch(url, {
8716
+ ...fetchOptions,
8717
+ signal: controller.signal
8718
+ });
8719
+ if (!process.stdout.isTTY || process.env.ZAM_BRIDGE === "true") {
8720
+ let timeoutId;
8721
+ const hardTimeout = new Promise((_resolve, reject) => {
8722
+ timeoutId = setTimeout(() => {
8723
+ reject(new Error(`LLM request timed out after ${hardTimeoutMs}ms`));
8724
+ controller.abort();
8725
+ }, hardTimeoutMs);
8726
+ });
8727
+ try {
8728
+ return await Promise.race([fetchPromise, hardTimeout]);
8729
+ } finally {
8730
+ clearTimeout(timeoutId);
8731
+ }
8732
+ }
8733
+ let attempts = 0;
8734
+ const dotsPerLine = 30;
8735
+ while (true) {
8736
+ let timeoutId;
8737
+ const timeoutPromise = new Promise((resolve8) => {
8738
+ timeoutId = setTimeout(() => resolve8("timeout"), timeoutMs);
8739
+ });
8740
+ const dotsInterval = setInterval(() => {
8741
+ process.stdout.write(".");
8742
+ attempts++;
8743
+ if (attempts % dotsPerLine === 0) process.stdout.write("\n");
8744
+ }, 500);
8745
+ try {
8746
+ const result = await Promise.race([fetchPromise, timeoutPromise]);
8747
+ clearInterval(dotsInterval);
8748
+ clearTimeout(timeoutId);
8749
+ if (result !== "timeout") {
8750
+ if (attempts > 0) process.stdout.write("\n");
8751
+ return result;
8752
+ }
8753
+ console.log(`
8754
+ \x1B[33m${t(locale, "local_ai_working")}\x1B[0m`);
8755
+ const { confirm } = await import("@inquirer/prompts");
8756
+ const keepWaiting = await confirm({
8757
+ message: t(locale, "keep_waiting"),
8758
+ default: true
8759
+ }).catch(() => false);
8760
+ if (!keepWaiting) {
8761
+ controller.abort();
8762
+ console.log(`\x1B[33m${t(locale, "proceeding_offline")}\x1B[0m
8763
+ `);
8764
+ throw new Error("User cancelled waiting for slow LLM response");
8765
+ }
8766
+ attempts = 0;
8767
+ } catch (err) {
8768
+ clearInterval(dotsInterval);
8769
+ clearTimeout(timeoutId);
8770
+ if (attempts > 0) process.stdout.write("\n");
8771
+ throw err;
8772
+ }
8773
+ }
8774
+ }
8775
+ async function ensureHighQualityQuestion(db, token) {
8776
+ const { enabled } = await getLlmConfig(db);
8777
+ const variationEnabled = await getSetting(db, "llm.dynamic_questions") !== "false";
8778
+ if (enabled && variationEnabled) {
8779
+ try {
8780
+ let sourceLinkContent = null;
8781
+ if (token.sourceLink) {
8782
+ const resolved = await resolveReviewContext(token.sourceLink).catch(
8783
+ () => null
8784
+ );
8785
+ if (resolved) {
8786
+ sourceLinkContent = resolved.content;
8787
+ }
8788
+ }
8789
+ const generated = await generateQuestionViaLLM(db, {
8790
+ slug: token.slug,
8791
+ concept: token.concept,
8792
+ domain: token.domain,
8793
+ bloomLevel: token.bloomLevel,
8794
+ sourceLinkContent,
8795
+ existingQuestion: token.question
8796
+ });
8797
+ if (generated.text.trim().length > 0) {
8798
+ return {
8799
+ question: generated.text,
8800
+ source: "llm",
8801
+ model: generated.model
8802
+ };
8803
+ }
8804
+ } catch {
8805
+ }
8806
+ }
8807
+ if (token.question && token.question.trim().length > 0) {
8808
+ return {
8809
+ question: token.question.trim(),
8810
+ source: "original"
8811
+ };
8812
+ }
8813
+ return null;
8814
+ }
8815
+ async function generateTitleViaLLM(db, input, opts = {}) {
8816
+ const cfg = await getProviderForRole(db, "text");
8817
+ let locale = cfg.locale;
8818
+ const contextName = opts.knowledgeContext || getActiveWorkspaceContext();
8819
+ if (contextName) {
8820
+ const context = await getKnowledgeContextByName(db, contextName);
8821
+ if (context?.language) {
8822
+ locale = normalizeLocale(context.language);
8823
+ }
8824
+ }
8825
+ const endpoint = await resolveUsableTextEndpoint(db);
8826
+ const systemPrompt = `You are an expert at naming knowledge items for a personal knowledge graph.
8827
+ Your task: given a knowledge token's concept (the full reference answer), question, domain, and optional context, produce a short, descriptive, human-friendly TITLE.
8828
+
8829
+ Strict rules from the project ADR:
8830
+ - Concise name for the concept (\u2264 80 chars ideal).
8831
+ - Thoughtful, memorable name \u2014 NOT a definition or the first sentence of the concept.
8832
+ - NEVER echo the domain name (e.g. do not say "Workflow Engine Node Drain" if domain is "workflow-engine"; just "Node Drain Protection").
8833
+ - Prefer a name over spoiling the full concept.
8834
+ - Support Unicode (umlauts, etc.).
8835
+ - Output the title in the same language as the concept/content (e.g., German if the concept is German, English if the concept is English).
8836
+ - Output ONLY the raw title text. No preamble, quotes, explanations, or markdown.
8837
+
8838
+ Example good titles:
8839
+ - "RAG Retrieval Quality Evaluation" (for RAG failure modes focused on retrieval)
8840
+ - "Pythagorean Theorem Converse Proof"
8841
+ - "macOS Applications Directory Layout"
8842
+ - "Chronotype Sleep Preference"`;
8843
+ const userPrompt = `Domain: ${input.domain}
8844
+ Slug: ${input.slug}
8845
+ Question: ${input.question || "(none)"}
8846
+ Concept: ${input.concept}
8847
+ Context: ${input.context || "(none)"}
8848
+
8849
+ Title:`;
8850
+ const url = `${endpoint.url}/chat/completions`;
8851
+ const apiKey = endpoint.apiKey;
8852
+ const model = endpoint.model;
8853
+ try {
8854
+ const res = await fetchWithInteractiveTimeout(url, {
8855
+ method: "POST",
8856
+ headers: {
8857
+ "Content-Type": "application/json",
8858
+ Authorization: `Bearer ${apiKey}`
8859
+ },
8860
+ body: JSON.stringify({
8861
+ model,
8862
+ messages: [
8863
+ { role: "system", content: systemPrompt },
8864
+ { role: "user", content: userPrompt }
8865
+ ],
8866
+ temperature: 0.2,
8867
+ max_tokens: 100
8868
+ }),
8869
+ locale,
8870
+ timeoutMs: opts.timeoutMs,
8871
+ hardTimeoutMs: opts.timeoutMs
8872
+ });
8873
+ const text = await readChatContent(res, "title generation");
8874
+ return {
8875
+ text: text.trim(),
8876
+ model,
8877
+ providerName: endpoint.providerName
8878
+ };
8879
+ } catch (_e) {
8880
+ return {
8881
+ text: input.concept.split(/[.;]/)[0].trim().substring(0, 80),
8882
+ model: "fallback",
8883
+ providerName: "heuristic"
8884
+ };
8885
+ }
8886
+ }
8887
+ async function repairUmlautsViaLLM(db, input, opts = {}) {
8888
+ const cfg = await getProviderForRole(db, "text");
8889
+ const endpoint = await resolveUsableTextEndpoint(db);
8890
+ const systemPrompt = `You are an expert editor specializing in German language orthography.
8891
+ Your task: given a text that may contain legacy ASCII-folded German umlauts (like 'ue' instead of '\xFC', 'ae' instead of '\xE4', 'oe' instead of '\xF6', 'Ue' instead of '\xDC', etc.), repair them back to proper German umlauts (\xE4, \xF6, \xFC, \xC4, \xD6, \xDC, \xDF) where appropriate.
8892
+
8893
+ Strict rules:
8894
+ - ONLY change character sequences that are clearly meant to be German umlauts (e.g. change "Ueber" to "\xDCber", "fuer" to "f\xFCr", "moeglich" to "m\xF6glich").
8895
+ - DO NOT change words that are correct in their current form (e.g. do NOT change "nahe" to "n\xE4he", do NOT change English words or technical terms, do NOT change names like "Ueda").
8896
+ - Output ONLY the repaired text. No preamble, no explanation, no markdown formatting.`;
8897
+ const userPrompt = `Text to repair:
8898
+ """
8899
+ ${input.text}
8900
+ """
8901
+
8902
+ Repaired text:`;
8903
+ const url = `${endpoint.url}/chat/completions`;
8904
+ const apiKey = endpoint.apiKey;
8905
+ const model = endpoint.model;
8906
+ try {
8907
+ const res = await fetchWithInteractiveTimeout(url, {
8908
+ method: "POST",
8909
+ headers: {
8910
+ "Content-Type": "application/json",
8911
+ Authorization: `Bearer ${apiKey}`
8912
+ },
8913
+ body: JSON.stringify({
8914
+ model,
8915
+ messages: [
8916
+ { role: "system", content: systemPrompt },
8917
+ { role: "user", content: userPrompt }
8918
+ ],
8919
+ temperature: 0.1
8920
+ }),
8921
+ locale: cfg.locale,
8922
+ timeoutMs: opts.timeoutMs,
8923
+ hardTimeoutMs: opts.timeoutMs
8924
+ });
8925
+ const text = await readChatContent(res, "umlaut repair");
8926
+ return text.trim();
8927
+ } catch (_e) {
8928
+ return input.text;
8929
+ }
8930
+ }
8931
+ var DEFAULT_LLM_URL, DEFAULT_LLM_MAX_TOKENS, LOCAL_CURRICULUM_IMPORT_HARD_TIMEOUT_MS, CLOUD_CURRICULUM_IMPORT_HARD_TIMEOUT_MS, RECALL_QUESTION_MAX_OUTPUT_TOKENS, RECALL_EVALUATION_MAX_OUTPUT_TOKENS, RECALL_DISCUSSION_MAX_OUTPUT_TOKENS, RECALL_ENDPOINT_CACHE_MS, cachedRecallEndpoint, DEFAULT_LLM_MODEL, DEFAULT_LLM_API_KEY, ROLE_TO_CAPABILITY2, LANGUAGE_NAMES, LOCALIZED_RATING_PREFIX, BLOOM_VERBS2, LlmResponseTruncatedError, MAX_IMPORT_TEXT_CHARS, VALID_GENERATED_MODES, CURRICULUM_CHUNK_TEXT_CHARS;
8932
+ var init_client = __esm({
8933
+ "src/cli/llm/client.ts"() {
8934
+ "use strict";
8935
+ init_kernel();
8936
+ DEFAULT_LLM_URL = "http://localhost:8000/v1";
8937
+ DEFAULT_LLM_MAX_TOKENS = 1e4;
8938
+ LOCAL_CURRICULUM_IMPORT_HARD_TIMEOUT_MS = 6e5;
8939
+ CLOUD_CURRICULUM_IMPORT_HARD_TIMEOUT_MS = 18e4;
8940
+ RECALL_QUESTION_MAX_OUTPUT_TOKENS = 400;
8941
+ RECALL_EVALUATION_MAX_OUTPUT_TOKENS = 1200;
8942
+ RECALL_DISCUSSION_MAX_OUTPUT_TOKENS = 1200;
8943
+ RECALL_ENDPOINT_CACHE_MS = 6e4;
8944
+ cachedRecallEndpoint = null;
8945
+ DEFAULT_LLM_MODEL = "qwen3.5:4b";
8946
+ DEFAULT_LLM_API_KEY = "sk-none";
8947
+ ROLE_TO_CAPABILITY2 = {
8948
+ recall: "text",
8949
+ text: "text",
8950
+ vision: "image",
8951
+ embedding: "embedding"
8952
+ };
8953
+ LANGUAGE_NAMES = {
8954
+ en: "English",
8955
+ de: "German",
8956
+ es: "Spanish",
8957
+ fr: "French",
8958
+ pt: "Portuguese",
8959
+ zh: "Chinese",
8960
+ ja: "Japanese"
8961
+ };
8962
+ LOCALIZED_RATING_PREFIX = {
8963
+ en: "Suggested rating",
8964
+ de: "Empfohlene Bewertung",
8965
+ es: "Calificaci\xF3n sugerida",
8966
+ fr: "Note sugg\xE9r\xE9e",
8967
+ pt: "Avalia\xE7\xE3o sugerida",
8968
+ zh: "\u5EFA\u8BAE\u8BC4\u5206",
8969
+ ja: "\u63A8\u5968\u8A55\u4FA1"
8970
+ };
8971
+ BLOOM_VERBS2 = {
8972
+ 1: "Remember",
8973
+ 2: "Understand",
8974
+ 3: "Apply",
8975
+ 4: "Analyze",
8976
+ 5: "Synthesize"
8977
+ };
8978
+ LlmResponseTruncatedError = class extends Error {
8979
+ constructor(label) {
8980
+ super(
8981
+ `${label}: the model's response was cut off before producing usable output (finish_reason=length). The prompt likely left no room in the model's context window for a reply.`
8982
+ );
8983
+ this.name = "LlmResponseTruncatedError";
8984
+ }
8985
+ };
8986
+ MAX_IMPORT_TEXT_CHARS = 2e5;
8987
+ VALID_GENERATED_MODES = /* @__PURE__ */ new Set(["shadowing", "copilot", "autonomy"]);
8988
+ CURRICULUM_CHUNK_TEXT_CHARS = 3e3;
8989
+ }
8990
+ });
8991
+
8992
+ // src/cli/okf/bundle.ts
8993
+ var bundle_exports = {};
8994
+ __export(bundle_exports, {
8995
+ OKF_VERSION: () => OKF_VERSION,
8996
+ RESERVED_FILES: () => RESERVED_FILES,
8997
+ appendLog: () => appendLog,
8998
+ buildCatalog: () => buildCatalog,
8999
+ isReservedFile: () => isReservedFile,
9000
+ parseFrontmatter: () => parseFrontmatter,
9001
+ renderIndex: () => renderIndex,
9002
+ toCatalogEntry: () => toCatalogEntry,
9003
+ validateArticle: () => validateArticle
9004
+ });
9005
+ function isReservedFile(file) {
9006
+ return RESERVED_FILES.includes(file);
9007
+ }
9008
+ function unquote(raw) {
9009
+ const v = raw.trim();
9010
+ if (v.length >= 2 && (v.startsWith('"') && v.endsWith('"') || v.startsWith("'") && v.endsWith("'"))) {
9011
+ return v.slice(1, -1);
9012
+ }
9013
+ return v;
9014
+ }
9015
+ function parseFrontmatter(markdown) {
9016
+ const lines = markdown.split("\n");
9017
+ if (lines[0]?.trim() !== "---") {
9018
+ throw new Error("frontmatter: file must start with a --- fence");
9019
+ }
9020
+ const fields = {};
9021
+ let listKey = null;
9022
+ let i = 1;
9023
+ for (; i < lines.length; i++) {
9024
+ const line = lines[i];
9025
+ if (line.trim() === "---") break;
9026
+ if (line.trim() === "") continue;
9027
+ const listItem = /^\s+-\s+(.+)$/.exec(line);
9028
+ if (listItem) {
9029
+ if (!listKey) {
9030
+ throw new Error(`frontmatter line ${i + 1}: list item without a key`);
9031
+ }
9032
+ fields[listKey].push(unquote(listItem[1]));
9033
+ continue;
9034
+ }
9035
+ const pair = /^([A-Za-z0-9_-]+):(.*)$/.exec(line);
9036
+ if (!pair) {
9037
+ throw new Error(
9038
+ `frontmatter line ${i + 1}: expected "key: value" or "- item"`
9039
+ );
9040
+ }
9041
+ const key = pair[1];
9042
+ const rest = pair[2].trim();
9043
+ if (rest === "") {
9044
+ fields[key] = [];
9045
+ listKey = key;
9046
+ } else {
9047
+ fields[key] = unquote(rest);
9048
+ listKey = null;
9049
+ }
9050
+ }
9051
+ if (i >= lines.length) {
9052
+ throw new Error("frontmatter: missing closing --- fence");
9053
+ }
9054
+ return { fields, body: lines.slice(i + 1).join("\n") };
9055
+ }
9056
+ function scalar(fields, key) {
9057
+ const v = fields[key];
9058
+ return typeof v === "string" && v.trim() !== "" ? v.trim() : void 0;
9059
+ }
9060
+ function validateArticle(file, markdown) {
9061
+ const problems = [];
9062
+ if (isReservedFile(file)) {
9063
+ return { ok: false, problems: [`${file}: reserved OKF file name`] };
9064
+ }
9065
+ if (!FILE_NAME_RE.test(file)) {
9066
+ problems.push(`${file}: file name must be kebab-case and end in .md`);
9067
+ }
9068
+ let parsed = null;
9069
+ try {
9070
+ parsed = parseFrontmatter(markdown);
9071
+ } catch (err) {
9072
+ problems.push(
9073
+ `${file}: ${err instanceof Error ? err.message : String(err)}`
9074
+ );
9075
+ }
9076
+ if (parsed) {
9077
+ if (!scalar(parsed.fields, "type")) {
9078
+ problems.push(`${file}: frontmatter field "type" is required`);
9079
+ }
9080
+ if (!scalar(parsed.fields, "description")) {
9081
+ problems.push(`${file}: frontmatter field "description" is required`);
9082
+ }
9083
+ if (parsed.body.trim() === "") {
9084
+ problems.push(`${file}: article body is empty`);
9085
+ }
9086
+ }
9087
+ return { ok: problems.length === 0, problems };
9088
+ }
9089
+ function toCatalogEntry(file, markdown) {
9090
+ const { fields } = parseFrontmatter(markdown);
9091
+ const tags = Array.isArray(fields.tags) ? fields.tags : [];
9092
+ return {
9093
+ file,
9094
+ type: scalar(fields, "type") ?? "",
9095
+ title: scalar(fields, "title") ?? file.replace(/\.md$/, ""),
9096
+ description: scalar(fields, "description") ?? "",
9097
+ tags,
9098
+ resource: scalar(fields, "resource"),
9099
+ timestamp: scalar(fields, "timestamp")
9100
+ };
9101
+ }
9102
+ function buildCatalog(articles) {
9103
+ return articles.map(({ file, markdown }) => toCatalogEntry(file, markdown)).sort((a, b) => a.file.localeCompare(b.file));
9104
+ }
9105
+ function renderIndex(catalog, okfVersion = OKF_VERSION) {
9106
+ const types = [...new Set(catalog.map((e) => e.type))].sort();
9107
+ const sections = types.map((type) => {
9108
+ const rows = catalog.filter((e) => e.type === type).map((e) => `- [${e.title}](${e.file}) \u2014 ${e.description}`).join("\n");
9109
+ return `## ${type}
9110
+
9111
+ ${rows}`;
9112
+ });
9113
+ return [
9114
+ "---",
9115
+ `okf_version: "${okfVersion}"`,
9116
+ "---",
9117
+ "",
9118
+ "# ZAM Knowledge Base",
9119
+ "",
9120
+ "Living reference knowledge for this repository in",
9121
+ "[Open Knowledge Format](https://github.com/GoogleCloudPlatform/knowledge-catalog).",
9122
+ "Current truth only \u2014 the *why* behind it lives in [../adr/](../adr/)",
9123
+ "(ADR 2026-07-17). Do not edit by hand: write through the",
9124
+ "`zam_okf_upsert` MCP tool.",
9125
+ "",
9126
+ sections.join("\n\n"),
9127
+ ""
9128
+ ].join("\n");
9129
+ }
9130
+ function appendLog(existing, date, line) {
9131
+ const header = `## ${date}`;
9132
+ const entry = `- ${line}`;
9133
+ const trimmed = existing.trim();
9134
+ if (trimmed === "") {
9135
+ return `# Log
9136
+
9137
+ ${header}
9138
+
9139
+ ${entry}
9140
+ `;
9141
+ }
9142
+ const lines = trimmed.split("\n");
9143
+ const firstHeaderIdx = lines.findIndex((l) => l.startsWith("## "));
9144
+ if (firstHeaderIdx !== -1 && lines[firstHeaderIdx].trim() === header) {
9145
+ lines.splice(firstHeaderIdx + 2, 0, entry);
9146
+ return `${lines.join("\n")}
9147
+ `;
9148
+ }
9149
+ const insertAt = firstHeaderIdx === -1 ? lines.length : firstHeaderIdx;
9150
+ lines.splice(insertAt, 0, header, "", entry, "");
9151
+ return `${lines.join("\n")}
9152
+ `;
9153
+ }
9154
+ var OKF_VERSION, RESERVED_FILES, FILE_NAME_RE;
9155
+ var init_bundle = __esm({
9156
+ "src/cli/okf/bundle.ts"() {
9157
+ "use strict";
9158
+ OKF_VERSION = "0.1";
9159
+ RESERVED_FILES = ["index.md", "log.md"];
9160
+ FILE_NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*\.md$/;
9161
+ }
9162
+ });
9163
+
9164
+ // src/cli/okf/io.ts
9165
+ var io_exports = {};
9166
+ __export(io_exports, {
9167
+ DEFAULT_BUNDLE_DIR: () => DEFAULT_BUNDLE_DIR,
9168
+ loadBundle: () => loadBundle,
9169
+ resolveArticlePath: () => resolveArticlePath,
9170
+ upsertArticle: () => upsertArticle
9171
+ });
9172
+ import { mkdirSync as mkdirSync16, readdirSync as readdirSync3, readFileSync as readFileSync18, writeFileSync as writeFileSync13 } from "fs";
9173
+ import { join as join24, resolve as resolve7 } from "path";
9174
+ function resolveArticlePath(dir, file) {
9175
+ if (file.includes("/") || file.includes("\\") || file.includes("..")) {
9176
+ throw new Error(`invalid article file name: ${file}`);
9177
+ }
9178
+ if (isReservedFile(file)) {
9179
+ throw new Error(`refusing to address reserved file: ${file}`);
9180
+ }
9181
+ return join24(resolve7(dir), file);
9182
+ }
9183
+ function loadBundle(dir) {
9184
+ const root = resolve7(dir);
9185
+ let entries;
9186
+ try {
9187
+ entries = readdirSync3(root).filter(
9188
+ (name) => name.endsWith(".md") && !isReservedFile(name)
9189
+ );
9190
+ } catch {
9191
+ throw new Error(`OKF bundle directory not found: ${root}`);
9192
+ }
9193
+ const articles = entries.sort().map((file) => ({
9194
+ file,
9195
+ markdown: readFileSync18(join24(root, file), "utf8")
9196
+ }));
9197
+ const problems = articles.flatMap(
9198
+ ({ file, markdown }) => validateArticle(file, markdown).problems
9199
+ );
9200
+ const catalog = problems.length === 0 ? buildCatalog(articles) : safeCatalog(articles);
9201
+ return { dir: root, articles, catalog, problems };
9202
+ }
9203
+ function safeCatalog(articles) {
9204
+ const entries = [];
9205
+ for (const { file, markdown } of articles) {
9206
+ try {
9207
+ entries.push(toCatalogEntry(file, markdown));
9208
+ } catch {
9209
+ }
9210
+ }
9211
+ return entries.sort((a, b) => a.file.localeCompare(b.file));
9212
+ }
9213
+ function upsertArticle(dir, file, markdown, today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)) {
9214
+ const target = resolveArticlePath(dir, file);
9215
+ const validation = validateArticle(file, markdown);
9216
+ if (!validation.ok) return { validation };
9217
+ const root = resolve7(dir);
9218
+ mkdirSync16(root, { recursive: true });
9219
+ let created = true;
9220
+ try {
9221
+ readFileSync18(target, "utf8");
9222
+ created = false;
9223
+ } catch {
9224
+ }
9225
+ writeFileSync13(target, markdown, "utf8");
9226
+ const bundle = loadBundle(root);
9227
+ writeFileSync13(join24(root, "index.md"), renderIndex(bundle.catalog), "utf8");
9228
+ let log = "";
9229
+ try {
9230
+ log = readFileSync18(join24(root, "log.md"), "utf8");
9231
+ } catch {
9232
+ }
9233
+ const entry = bundle.catalog.find((e) => e.file === file);
9234
+ writeFileSync13(
9235
+ join24(root, "log.md"),
9236
+ appendLog(
9237
+ log,
9238
+ today,
9239
+ `**${created ? "Creation" : "Update"}** \u2014 [${entry?.title ?? file}](${file})`
9240
+ ),
9241
+ "utf8"
9242
+ );
9243
+ return { validation, entry, created };
9244
+ }
9245
+ var DEFAULT_BUNDLE_DIR;
9246
+ var init_io = __esm({
9247
+ "src/cli/okf/io.ts"() {
9248
+ "use strict";
9249
+ init_bundle();
9250
+ DEFAULT_BUNDLE_DIR = "docs/okf";
9251
+ }
9252
+ });
9253
+
9254
+ // src/cli/commands/mcp.ts
9255
+ init_kernel();
9256
+ import { existsSync as existsSync21, readFileSync as readFileSync19 } from "fs";
9257
+ import { dirname as dirname11, join as join25 } from "path";
9258
+ import { fileURLToPath as fileURLToPath6 } from "url";
9259
+ import {
9260
+ RESOURCE_MIME_TYPE,
9261
+ registerAppResource,
9262
+ registerAppTool
9263
+ } from "@modelcontextprotocol/ext-apps/server";
9264
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9265
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
9266
+ import { z } from "zod";
9267
+
9268
+ // src/vscode-extension/companion-evaluator.ts
9269
+ var ROUTABLE_EVALUATOR_IDS = [
9270
+ "native-mcp-host",
9271
+ "vscode-lm",
9272
+ "zam-text-model",
9273
+ "quick-mode"
9274
+ ];
9275
+ var DETACHED_HARNESS_EVALUATOR_IDS = [
9276
+ "claude-code",
9277
+ "codex",
9278
+ "opencode",
9279
+ "goose"
9280
+ ];
9281
+ function isEvaluatorId(value) {
9282
+ return typeof value === "string" && (ROUTABLE_EVALUATOR_IDS.includes(value) || DETACHED_HARNESS_EVALUATOR_IDS.includes(value));
9283
+ }
9284
+ function buildEvaluatorRoutes(inputs, selectedId) {
9285
+ return inputs.map((input) => {
9286
+ if (!input.routable && !input.reason) {
9287
+ throw new Error(
9288
+ `Unroutable evaluator "${input.id}" must carry an availability reason`
9289
+ );
9290
+ }
9291
+ return { ...input, selected: input.id === selectedId, active: false };
9292
+ });
9293
+ }
9294
+ var EvaluatorUnavailableError = class extends Error {
9295
+ evaluatorId;
9296
+ constructor(evaluatorId, reason) {
9297
+ super(`Evaluator "${evaluatorId}" is unavailable: ${reason}`);
9298
+ this.name = "EvaluatorUnavailableError";
9299
+ this.evaluatorId = evaluatorId;
9300
+ }
9301
+ };
9302
+ function activateSelectedEvaluator(routes, selectedId) {
9303
+ const route = routes.find((candidate) => candidate.id === selectedId);
9304
+ if (!route) {
9305
+ throw new EvaluatorUnavailableError(selectedId, "not configured");
9306
+ }
9307
+ if (!route.routable) {
9308
+ throw new EvaluatorUnavailableError(
9309
+ selectedId,
9310
+ route.reason ?? "not routable on this surface"
9311
+ );
9312
+ }
9313
+ return { ...route, active: true };
9314
+ }
9315
+
9316
+ // src/vscode-extension/companion-selection.ts
9317
+ function resolveSelection(candidates) {
9318
+ if (candidates.invocation !== void 0) {
9319
+ return {
9320
+ value: candidates.invocation,
9321
+ source: "invocation",
9322
+ sessionScoped: true
9323
+ };
9324
+ }
9325
+ if (candidates.manual !== void 0) {
9326
+ return { value: candidates.manual, source: "manual", sessionScoped: false };
9327
+ }
9328
+ if (candidates.persisted !== void 0) {
9329
+ return {
9330
+ value: candidates.persisted,
9331
+ source: "persisted",
9332
+ sessionScoped: false
9333
+ };
9334
+ }
9335
+ return {
9336
+ value: candidates.fallback,
9337
+ source: "default",
9338
+ sessionScoped: false
9339
+ };
9340
+ }
9341
+ function isPersistableSelection(result) {
9342
+ return result.source === "manual";
9343
+ }
9344
+
9345
+ // src/vscode-extension/companion-context.ts
9346
+ var COMPANION_SURFACES = [
9347
+ "recall",
9348
+ "graph",
9349
+ "settings",
9350
+ "studio"
9351
+ ];
9352
+ function isCompanionSurface(value) {
9353
+ return typeof value === "string" && COMPANION_SURFACES.includes(value);
9354
+ }
9355
+ function isRecord2(value) {
9356
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9357
+ }
9358
+ var KNOWN_NATIVE_HOSTS = {
9359
+ "vscode-zam-companion": {
9360
+ normalizedId: "vscode-companion",
9361
+ label: "VS Code Companion"
9362
+ },
9363
+ "antigravity-zam-companion": {
9364
+ normalizedId: "antigravity-companion",
9365
+ label: "Antigravity Companion"
9366
+ }
9367
+ };
9368
+ function normalizeNativeHostIdentity(clientInfo, harnessOverride) {
9369
+ if (harnessOverride) {
9370
+ const known2 = KNOWN_NATIVE_HOSTS[harnessOverride];
9371
+ return {
9372
+ rawName: clientInfo?.name ?? harnessOverride,
9373
+ version: clientInfo?.version,
9374
+ normalizedId: known2?.normalizedId,
9375
+ label: known2?.label ?? harnessOverride
9376
+ };
9377
+ }
9378
+ if (!clientInfo) return void 0;
9379
+ const known = KNOWN_NATIVE_HOSTS[clientInfo.name];
9380
+ return {
9381
+ rawName: clientInfo.name,
9382
+ version: clientInfo.version,
9383
+ normalizedId: known?.normalizedId,
9384
+ label: known?.label ?? clientInfo.name
9385
+ };
9386
+ }
9387
+ function resolveCollapsedForSurface(state, surface) {
9388
+ return state?.[surface] ?? false;
9389
+ }
9390
+ function parseCompanionContextReadRequest(value) {
9391
+ if (!isRecord2(value) || !isCompanionSurface(value.surface)) {
9392
+ throw new Error("Invalid companion context read request");
9393
+ }
9394
+ const rawClientInfo = value.clientInfo;
9395
+ const clientInfo = isRecord2(rawClientInfo) && typeof rawClientInfo.name === "string" ? {
9396
+ name: rawClientInfo.name,
9397
+ version: typeof rawClientInfo.version === "string" ? rawClientInfo.version : void 0
9398
+ } : void 0;
9399
+ const harnessOverride = typeof value.harnessOverride === "string" ? value.harnessOverride : void 0;
9400
+ return { surface: value.surface, clientInfo, harnessOverride };
9401
+ }
9402
+ function parseCompanionContextWriteRequest(value) {
9403
+ if (!isRecord2(value) || !isCompanionSurface(value.surface)) {
9404
+ throw new Error("Invalid companion context write request");
8844
9405
  }
8845
- let attempts = 0;
8846
- const dotsPerLine = 30;
8847
- while (true) {
8848
- let timeoutId;
8849
- const timeoutPromise = new Promise((resolve7) => {
8850
- timeoutId = setTimeout(() => resolve7("timeout"), timeoutMs);
8851
- });
8852
- const dotsInterval = setInterval(() => {
8853
- process.stdout.write(".");
8854
- attempts++;
8855
- if (attempts % dotsPerLine === 0) process.stdout.write("\n");
8856
- }, 500);
8857
- try {
8858
- const result = await Promise.race([fetchPromise, timeoutPromise]);
8859
- clearInterval(dotsInterval);
8860
- clearTimeout(timeoutId);
8861
- if (result !== "timeout") {
8862
- if (attempts > 0) process.stdout.write("\n");
8863
- return result;
8864
- }
8865
- console.log(`
8866
- \x1B[33m${t(locale, "local_ai_working")}\x1B[0m`);
8867
- const { confirm } = await import("@inquirer/prompts");
8868
- const keepWaiting = await confirm({
8869
- message: t(locale, "keep_waiting"),
8870
- default: true
8871
- }).catch(() => false);
8872
- if (!keepWaiting) {
8873
- controller.abort();
8874
- console.log(`\x1B[33m${t(locale, "proceeding_offline")}\x1B[0m
8875
- `);
8876
- throw new Error("User cancelled waiting for slow LLM response");
8877
- }
8878
- attempts = 0;
8879
- } catch (err) {
8880
- clearInterval(dotsInterval);
8881
- clearTimeout(timeoutId);
8882
- if (attempts > 0) process.stdout.write("\n");
8883
- throw err;
9406
+ const userId = typeof value.userId === "string" ? value.userId : void 0;
9407
+ let evaluatorId;
9408
+ if (value.evaluatorId !== void 0) {
9409
+ if (!isEvaluatorId(value.evaluatorId)) {
9410
+ throw new Error(
9411
+ `Unknown evaluator id in companion context write request: ${String(value.evaluatorId)}`
9412
+ );
8884
9413
  }
9414
+ evaluatorId = value.evaluatorId;
9415
+ }
9416
+ const collapsed = typeof value.collapsed === "boolean" ? value.collapsed : void 0;
9417
+ if (userId === void 0 && evaluatorId === void 0 && collapsed === void 0) {
9418
+ throw new Error(
9419
+ "Companion context write request must set userId, evaluatorId, and/or collapsed"
9420
+ );
8885
9421
  }
9422
+ return { surface: value.surface, userId, evaluatorId, collapsed };
8886
9423
  }
8887
- async function ensureHighQualityQuestion(db, token) {
8888
- const { enabled } = await getLlmConfig(db);
8889
- const variationEnabled = await getSetting(db, "llm.dynamic_questions") !== "false";
8890
- if (enabled && variationEnabled) {
8891
- try {
8892
- let sourceLinkContent = null;
8893
- if (token.sourceLink) {
8894
- const resolved = await resolveReviewContext(token.sourceLink).catch(
8895
- () => null
8896
- );
8897
- if (resolved) {
8898
- sourceLinkContent = resolved.content;
8899
- }
8900
- }
8901
- const generated = await generateQuestionViaLLM(db, {
8902
- slug: token.slug,
8903
- concept: token.concept,
8904
- domain: token.domain,
8905
- bloomLevel: token.bloomLevel,
8906
- sourceLinkContent,
8907
- existingQuestion: token.question
8908
- });
8909
- if (generated.text.trim().length > 0) {
8910
- return {
8911
- question: generated.text,
8912
- source: "llm",
8913
- model: generated.model
8914
- };
8915
- }
8916
- } catch {
9424
+ function buildCompanionContext(input) {
9425
+ const userSelection = resolveSelection(input.userSelection);
9426
+ const evaluatorSelection = resolveSelection(input.evaluatorSelection);
9427
+ const routes = buildEvaluatorRoutes(
9428
+ input.evaluatorRouteInputs,
9429
+ evaluatorSelection.value
9430
+ );
9431
+ let activeEvaluatorId;
9432
+ let activeEvaluatorError;
9433
+ try {
9434
+ activateSelectedEvaluator(routes, evaluatorSelection.value);
9435
+ activeEvaluatorId = evaluatorSelection.value;
9436
+ } catch (error) {
9437
+ if (error instanceof EvaluatorUnavailableError) {
9438
+ activeEvaluatorError = error;
9439
+ } else {
9440
+ throw error;
8917
9441
  }
8918
9442
  }
8919
- if (token.question && token.question.trim().length > 0) {
8920
- return {
8921
- question: token.question.trim(),
8922
- source: "original"
8923
- };
9443
+ const evaluators = activeEvaluatorId ? routes.map(
9444
+ (route) => route.id === activeEvaluatorId ? { ...route, active: true } : route
9445
+ ) : routes;
9446
+ return {
9447
+ read: {
9448
+ surface: input.surface,
9449
+ nativeHost: input.nativeHost,
9450
+ user: {
9451
+ currentId: userSelection.value,
9452
+ persistedId: input.userSelection.persisted,
9453
+ source: userSelection.source
9454
+ },
9455
+ profiles: input.profiles ?? [],
9456
+ harnesses: input.harnesses,
9457
+ evaluators,
9458
+ selectedEvaluatorId: evaluatorSelection.value,
9459
+ activeEvaluatorId,
9460
+ collapsed: resolveCollapsedForSurface(input.collapsed, input.surface)
9461
+ },
9462
+ userSelection,
9463
+ evaluatorSelection,
9464
+ activeEvaluatorError
9465
+ };
9466
+ }
9467
+
9468
+ // src/cli/bridge-handlers.ts
9469
+ init_kernel();
9470
+ import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync6 } from "fs";
9471
+ import { join as join13 } from "path";
9472
+
9473
+ // src/cli/knowledge-contexts.ts
9474
+ init_kernel();
9475
+ async function resolveOperationKnowledgeContexts(db, requestedNames) {
9476
+ const explicitNames = [
9477
+ ...new Set(requestedNames.map((name) => name.trim()).filter(Boolean))
9478
+ ];
9479
+ const activeDefault = getActiveWorkspaceContext();
9480
+ const selectedNames = explicitNames.length > 0 ? explicitNames : activeDefault ? [activeDefault] : [];
9481
+ const contexts = [];
9482
+ for (const name of selectedNames) {
9483
+ const context = await getKnowledgeContextByName(db, name);
9484
+ if (!context) {
9485
+ const subject = explicitNames.length > 0 ? "Knowledge" : "Active knowledge";
9486
+ throw new Error(`${subject} context not found: ${name}`);
9487
+ }
9488
+ contexts.push(context);
8924
9489
  }
8925
- return null;
9490
+ return contexts;
8926
9491
  }
8927
9492
 
9493
+ // src/cli/bridge-handlers.ts
9494
+ init_client();
9495
+
8928
9496
  // src/cli/llm/embedder.ts
8929
9497
  init_kernel();
9498
+ init_client();
8930
9499
  var CANONICAL_EMBEDDING_MODEL_ID = "embeddinggemma-300m";
8931
9500
  var EMBEDDINGGEMMA_ALIASES = /* @__PURE__ */ new Set([
8932
9501
  "embeddinggemma",
@@ -11254,6 +11823,7 @@ import { Command } from "commander";
11254
11823
  import { ulid as ulid10 } from "ulid";
11255
11824
 
11256
11825
  // src/cli/adapters/source-reader.ts
11826
+ init_client();
11257
11827
  import dns from "dns";
11258
11828
  import fs from "fs";
11259
11829
  var MAX_SOURCE_BYTES = 2 * 1024 * 1024;
@@ -11328,7 +11898,7 @@ async function readWebLink(url) {
11328
11898
  redirect: "manual",
11329
11899
  signal: controller.signal,
11330
11900
  headers: {
11331
- "User-Agent": "ZAM-Content-Studio/0.10.11"
11901
+ "User-Agent": "ZAM-Content-Studio/0.12.0"
11332
11902
  }
11333
11903
  });
11334
11904
  if (res.status >= 300 && res.status < 400) {
@@ -28491,6 +29061,7 @@ function performInstallRepair(opts = {}, deps = {}) {
28491
29061
 
28492
29062
  // src/cli/llm/capability-probe.ts
28493
29063
  init_kernel();
29064
+ init_client();
28494
29065
  var EMBEDDING_MODEL_HINTS = [
28495
29066
  "embed",
28496
29067
  "text-embedding",
@@ -28610,8 +29181,12 @@ function validateModelSave(entry, probe, now = () => (/* @__PURE__ */ new Date()
28610
29181
  };
28611
29182
  }
28612
29183
 
29184
+ // src/cli/commands/bridge.ts
29185
+ init_client();
29186
+
28613
29187
  // src/cli/llm/vision.ts
28614
29188
  init_kernel();
29189
+ init_client();
28615
29190
  import { randomBytes } from "crypto";
28616
29191
  import { readFileSync as readFileSync16 } from "fs";
28617
29192
  import { tmpdir as tmpdir2 } from "os";
@@ -28651,25 +29226,25 @@ async function observeUiSnapshotViaLLM(db, input) {
28651
29226
  const imageUrls = [];
28652
29227
  const isVideo = /\.(mp4|mov|m4v|avi|mkv|webm)$/i.test(input.imagePath);
28653
29228
  if (isVideo) {
28654
- const { mkdirSync: mkdirSync16, readdirSync: readdirSync3, rmSync: rmSync4 } = await import("fs");
29229
+ const { mkdirSync: mkdirSync17, readdirSync: readdirSync4, rmSync: rmSync4 } = await import("fs");
28655
29230
  const { execSync: execSync5 } = await import("child_process");
28656
29231
  const tempDir = join20(
28657
29232
  tmpdir2(),
28658
29233
  `zam-frames-${randomBytes(4).toString("hex")}`
28659
29234
  );
28660
- mkdirSync16(tempDir, { recursive: true });
29235
+ mkdirSync17(tempDir, { recursive: true });
28661
29236
  try {
28662
29237
  execSync5(
28663
29238
  `ffmpeg -i "${input.imagePath}" -vf "fps=1/3,scale=1280:-1" -vsync vfr "${tempDir}/frame_%03d.png"`,
28664
29239
  { stdio: "ignore" }
28665
29240
  );
28666
- let files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
29241
+ let files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
28667
29242
  if (files.length === 0) {
28668
29243
  execSync5(
28669
29244
  `ffmpeg -i "${input.imagePath}" -vframes 1 "${tempDir}/frame_001.png"`,
28670
29245
  { stdio: "ignore" }
28671
29246
  );
28672
- files = readdirSync3(tempDir).filter((f) => f.endsWith(".png")).sort();
29247
+ files = readdirSync4(tempDir).filter((f) => f.endsWith(".png")).sort();
28673
29248
  }
28674
29249
  const maxFrames = cfg.maxFrames ?? 100;
28675
29250
  let sampledFiles = files;
@@ -29062,6 +29637,7 @@ async function withOptionalDb(fn, onError = defaultErrorHandler) {
29062
29637
  }
29063
29638
 
29064
29639
  // src/cli/providers/config.ts
29640
+ init_client();
29065
29641
  var VALID_API_FLAVORS = [
29066
29642
  "chat-completions",
29067
29643
  "anthropic-messages"
@@ -30487,7 +31063,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
30487
31063
  const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30488
31064
  const defaultExt = platform === "win32" ? ".mkv" : ".mov";
30489
31065
  const outputPath = opts.output ?? join22(tmpdir3(), `zam-recording-${sessionId}${defaultExt}`);
30490
- const { existsSync: existsSync22, writeFileSync: writeFileSync13, openSync, closeSync } = await import("fs");
31066
+ const { existsSync: existsSync22, writeFileSync: writeFileSync14, openSync, closeSync } = await import("fs");
30491
31067
  if (existsSync22(statePath)) {
30492
31068
  jsonOut({
30493
31069
  sessionId,
@@ -30542,7 +31118,7 @@ bridgeCommand.command("start-recording").description("Start screen recording in
30542
31118
  }
30543
31119
  child.unref();
30544
31120
  if (child.pid) {
30545
- writeFileSync13(
31121
+ writeFileSync14(
30546
31122
  statePath,
30547
31123
  JSON.stringify({
30548
31124
  pid: child.pid,
@@ -30579,7 +31155,7 @@ bridgeCommand.command("stop-recording").description(
30579
31155
  }
30580
31156
  const sessionId = opts.session;
30581
31157
  const statePath = join22(tmpdir3(), `zam-recording-${sessionId}.json`);
30582
- const { existsSync: existsSync22, readFileSync: readFileSync19, rmSync: rmSync4 } = await import("fs");
31158
+ const { existsSync: existsSync22, readFileSync: readFileSync20, rmSync: rmSync4 } = await import("fs");
30583
31159
  if (!existsSync22(statePath)) {
30584
31160
  jsonOut({
30585
31161
  sessionId,
@@ -30588,7 +31164,7 @@ bridgeCommand.command("stop-recording").description(
30588
31164
  });
30589
31165
  return;
30590
31166
  }
30591
- const state = JSON.parse(readFileSync19(statePath, "utf8"));
31167
+ const state = JSON.parse(readFileSync20(statePath, "utf8"));
30592
31168
  const { pid, outputPath } = state;
30593
31169
  try {
30594
31170
  process.kill(pid, "SIGINT");
@@ -30604,7 +31180,7 @@ bridgeCommand.command("stop-recording").description(
30604
31180
  };
30605
31181
  let attempts = 0;
30606
31182
  while (isProcessRunning(pid) && attempts < 20) {
30607
- await new Promise((resolve7) => setTimeout(resolve7, 250));
31183
+ await new Promise((resolve8) => setTimeout(resolve8, 250));
30608
31184
  attempts++;
30609
31185
  }
30610
31186
  if (isProcessRunning(pid)) {
@@ -33020,7 +33596,7 @@ function buildEvaluatorRouteInputs(harnessReport, nativeHost, clientSamplingCapa
33020
33596
  const detachedStatusById = new Map(
33021
33597
  harnessReport.harnesses.map((harness) => [harness.harness, harness])
33022
33598
  );
33023
- const isVscodeCompanion = nativeHost?.normalizedId === "vscode-companion";
33599
+ const isVscodeCompanion = nativeHost?.normalizedId === "vscode-companion" || nativeHost?.normalizedId === "antigravity-companion";
33024
33600
  const nativeHostRoutable = !isVscodeCompanion && clientSamplingCapable;
33025
33601
  const inputs = [
33026
33602
  {
@@ -33040,8 +33616,14 @@ function buildEvaluatorRouteInputs(harnessReport, nativeHost, clientSamplingCapa
33040
33616
  id: "vscode-lm",
33041
33617
  displayIdentity: { provider: "VS Code language models" },
33042
33618
  configured: true,
33043
- routable: isVscodeCompanion,
33044
- reason: isVscodeCompanion ? void 0 : "VS Code language-model routing is only available from the VS Code Companion extension."
33619
+ routable: nativeHost?.normalizedId === "vscode-companion",
33620
+ reason: nativeHost?.normalizedId === "vscode-companion" ? void 0 : nativeHost?.normalizedId === "antigravity-companion" ? "Antigravity IDE does not support the VS Code language model API (vscode.lm)." : "VS Code language-model routing is only available from the VS Code Companion extension."
33621
+ },
33622
+ {
33623
+ id: "zam-text-model",
33624
+ displayIdentity: { provider: "ZAM text model" },
33625
+ configured: true,
33626
+ routable: true
33045
33627
  }
33046
33628
  ];
33047
33629
  for (const id of DETACHED_HARNESS_EVALUATOR_IDS) {
@@ -33067,9 +33649,11 @@ function toCompanionCollapsedState(raw) {
33067
33649
  async function assembleCompanionContext(db, input) {
33068
33650
  const companionConfig = getMachineCompanionConfig(input.configPath);
33069
33651
  const persistedUserId = companionConfig.selectedUserId;
33652
+ const isAntigravity = input.nativeHost?.normalizedId === "antigravity-companion";
33653
+ const persistedEvaluatorIdRaw = isAntigravity ? companionConfig.selectedAntigravityEvaluatorId ?? companionConfig.selectedEvaluatorId : companionConfig.selectedVscodeEvaluatorId ?? companionConfig.selectedEvaluatorId;
33070
33654
  const persistedEvaluatorId = isEvaluatorId(
33071
- companionConfig.selectedEvaluatorId
33072
- ) ? companionConfig.selectedEvaluatorId : void 0;
33655
+ persistedEvaluatorIdRaw
33656
+ ) ? persistedEvaluatorIdRaw : void 0;
33073
33657
  const collapsedRaw = companionConfig.collapsed ?? {};
33074
33658
  const harnessReport = getHarnessReportCached();
33075
33659
  const clientSamplingCapable = input.clientSamplingCapable ?? false;
@@ -33081,7 +33665,7 @@ async function assembleCompanionContext(db, input) {
33081
33665
  ]);
33082
33666
  const fallbackUserId = fallbackUserIdRaw ?? void 0;
33083
33667
  const quickModeSettingIsOn = quickModeRaw === "true";
33084
- const defaultEvaluatorId = quickModeSettingIsOn ? "quick-mode" : isVscodeCompanion ? "vscode-lm" : clientSamplingCapable ? "native-mcp-host" : "quick-mode";
33668
+ const defaultEvaluatorId = quickModeSettingIsOn ? "quick-mode" : isAntigravity ? "zam-text-model" : isVscodeCompanion ? "vscode-lm" : clientSamplingCapable ? "native-mcp-host" : "quick-mode";
33085
33669
  const { read } = buildCompanionContext({
33086
33670
  surface: input.surface,
33087
33671
  nativeHost: input.nativeHost,
@@ -33146,7 +33730,13 @@ async function writeCompanionContext(db, request, options = {}) {
33146
33730
  fallback: void 0
33147
33731
  });
33148
33732
  if (isPersistableSelection(selection) && selection.value) {
33149
- update.selectedEvaluatorId = selection.value;
33733
+ const nativeHost = normalizeNativeHostIdentity(options.clientInfo);
33734
+ const isAntigravity = nativeHost?.normalizedId === "antigravity-companion";
33735
+ if (isAntigravity) {
33736
+ update.selectedAntigravityEvaluatorId = selection.value;
33737
+ } else {
33738
+ update.selectedVscodeEvaluatorId = selection.value;
33739
+ }
33150
33740
  reloadRequired = true;
33151
33741
  }
33152
33742
  }
@@ -33228,11 +33818,11 @@ async function publishUiIntent(app, input = {}, opts = {}) {
33228
33818
 
33229
33819
  // src/cli/commands/mcp.ts
33230
33820
  var __dirname = dirname11(fileURLToPath6(import.meta.url));
33231
- var pkgPath = join24(__dirname, "..", "..", "package.json");
33821
+ var pkgPath = join25(__dirname, "..", "..", "package.json");
33232
33822
  if (!existsSync21(pkgPath)) {
33233
- pkgPath = join24(__dirname, "..", "..", "..", "package.json");
33823
+ pkgPath = join25(__dirname, "..", "..", "..", "package.json");
33234
33824
  }
33235
- var pkg = JSON.parse(readFileSync18(pkgPath, "utf-8"));
33825
+ var pkg = JSON.parse(readFileSync19(pkgPath, "utf-8"));
33236
33826
  var STUDIO_RESOURCE_URI = "ui://zam/studio";
33237
33827
  var RECALL_RESOURCE_URI = "ui://zam/recall";
33238
33828
  var GRAPH_RESOURCE_URI = "ui://zam/graph";
@@ -33259,13 +33849,13 @@ var STUDIO_BRIDGE_ALLOWED_COMMANDS = /* @__PURE__ */ new Set([
33259
33849
  function loadPanelHtml(fileName, placeholderTitle) {
33260
33850
  const candidates = [
33261
33851
  // dist/cli/commands/mcp.js → dist/ui/
33262
- join24(__dirname, "..", "..", "ui", fileName),
33852
+ join25(__dirname, "..", "..", "ui", fileName),
33263
33853
  // src/cli/commands/mcp.ts via tsx → <repo>/dist/ui/
33264
- join24(__dirname, "..", "..", "..", "dist", "ui", fileName)
33854
+ join25(__dirname, "..", "..", "..", "dist", "ui", fileName)
33265
33855
  ];
33266
33856
  for (const candidate of candidates) {
33267
33857
  if (existsSync21(candidate)) {
33268
- return readFileSync18(candidate, "utf-8");
33858
+ return readFileSync19(candidate, "utf-8");
33269
33859
  }
33270
33860
  }
33271
33861
  return `<!doctype html>
@@ -34000,6 +34590,117 @@ function createMcpServer(db) {
34000
34590
  });
34001
34591
  })
34002
34592
  );
34593
+ server.registerTool(
34594
+ "zam_companion_sample",
34595
+ {
34596
+ description: "Perform LLM sampling via the server's configured LLM (fallback for companion when vscode-lm is empty)",
34597
+ inputSchema: {
34598
+ messages: z.array(
34599
+ z.object({
34600
+ role: z.enum(["user", "assistant"]),
34601
+ text: z.string()
34602
+ })
34603
+ )
34604
+ },
34605
+ annotations: {
34606
+ ...commonAnnotations
34607
+ },
34608
+ _meta: {
34609
+ ui: { visibility: ["app"] }
34610
+ }
34611
+ },
34612
+ wrapHandler(
34613
+ async (params) => {
34614
+ const { sampleViaLocalLLM: sampleViaLocalLLM2 } = await Promise.resolve().then(() => (init_client(), client_exports));
34615
+ const messages = params.messages.map((m) => ({
34616
+ role: m.role,
34617
+ content: m.text
34618
+ }));
34619
+ return await sampleViaLocalLLM2(db, messages);
34620
+ }
34621
+ )
34622
+ );
34623
+ const okfBundleDirSchema = z.string().optional().describe("Bundle directory (default docs/okf under the server cwd)");
34624
+ server.registerTool(
34625
+ "zam_okf_catalog",
34626
+ {
34627
+ description: "List the OKF knowledge-base articles (type, title, description, tags, resource URL) plus conformance problems, if any",
34628
+ inputSchema: {
34629
+ bundle_dir: okfBundleDirSchema
34630
+ },
34631
+ annotations: {
34632
+ ...commonAnnotations,
34633
+ readOnlyHint: true
34634
+ }
34635
+ },
34636
+ wrapHandler(async (params) => {
34637
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, loadBundle: loadBundle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34638
+ const bundle = loadBundle2(params.bundle_dir ?? DEFAULT_BUNDLE_DIR2);
34639
+ return {
34640
+ dir: bundle.dir,
34641
+ articles: bundle.catalog,
34642
+ problems: bundle.problems
34643
+ };
34644
+ })
34645
+ );
34646
+ server.registerTool(
34647
+ "zam_okf_read",
34648
+ {
34649
+ description: "Read one OKF knowledge-base article: raw markdown plus parsed frontmatter",
34650
+ inputSchema: {
34651
+ bundle_dir: okfBundleDirSchema,
34652
+ file: z.string().describe("Article file name, e.g. fsrs-scheduling.md")
34653
+ },
34654
+ annotations: {
34655
+ ...commonAnnotations,
34656
+ readOnlyHint: true
34657
+ }
34658
+ },
34659
+ wrapHandler(async (params) => {
34660
+ const { readFileSync: readFileSync20 } = await import("fs");
34661
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, resolveArticlePath: resolveArticlePath2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34662
+ const { parseFrontmatter: parseFrontmatter2 } = await Promise.resolve().then(() => (init_bundle(), bundle_exports));
34663
+ const path = resolveArticlePath2(
34664
+ params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
34665
+ params.file
34666
+ );
34667
+ const markdown = readFileSync20(path, "utf8");
34668
+ const { fields } = parseFrontmatter2(markdown);
34669
+ return { file: params.file, frontmatter: fields, markdown };
34670
+ })
34671
+ );
34672
+ server.registerTool(
34673
+ "zam_okf_upsert",
34674
+ {
34675
+ description: "Create or update an OKF knowledge-base article through the validated write path: checks the frontmatter contract, regenerates index.md, and appends the log.md entry. Never edit bundle files directly.",
34676
+ inputSchema: {
34677
+ bundle_dir: okfBundleDirSchema,
34678
+ file: z.string().describe(
34679
+ "Kebab-case article file name ending in .md (permanent ID)"
34680
+ ),
34681
+ markdown: z.string().describe(
34682
+ "Full article: --- frontmatter (type, title, description, tags, resource, timestamp) then the body"
34683
+ )
34684
+ },
34685
+ annotations: {
34686
+ ...commonAnnotations
34687
+ }
34688
+ },
34689
+ wrapHandler(
34690
+ async (params) => {
34691
+ const { DEFAULT_BUNDLE_DIR: DEFAULT_BUNDLE_DIR2, upsertArticle: upsertArticle2 } = await Promise.resolve().then(() => (init_io(), io_exports));
34692
+ const result = upsertArticle2(
34693
+ params.bundle_dir ?? DEFAULT_BUNDLE_DIR2,
34694
+ params.file,
34695
+ params.markdown
34696
+ );
34697
+ if (!result.validation.ok) {
34698
+ return { ok: false, problems: result.validation.problems };
34699
+ }
34700
+ return { ok: true, created: result.created, entry: result.entry };
34701
+ }
34702
+ )
34703
+ );
34003
34704
  return server;
34004
34705
  }
34005
34706
  async function runMcpServer() {