memorix 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/CLAUDE.md +9 -9
  3. package/README.md +7 -2
  4. package/README.zh-CN.md +6 -2
  5. package/dist/cli/index.js +1216 -485
  6. package/dist/index.js +984 -337
  7. package/dist/maintenance-runner.js +564 -180
  8. package/dist/media-video-runner.js +564 -180
  9. package/dist/memcode/index.js +53 -21
  10. package/dist/memcode-runtime/CHANGELOG.md +31 -0
  11. package/dist/sdk.js +983 -336
  12. package/dist/vector-backfill-runner.js +564 -180
  13. package/docs/1.4.2-EVIDENCE-GOVERNED-MEMORY-SPEC.md +434 -0
  14. package/docs/API_REFERENCE.md +1 -1
  15. package/docs/KNOWN_ISSUES_AND_ROADMAP.md +52 -128
  16. package/package.json +5 -2
  17. package/plugins/claude/memorix/.claude-plugin/plugin.json +1 -1
  18. package/plugins/codex/memorix/.codex-plugin/plugin.json +1 -1
  19. package/plugins/copilot/memorix/plugin.json +1 -1
  20. package/plugins/gemini/memorix/gemini-extension.json +1 -1
  21. package/plugins/omp/memorix/package.json +1 -1
  22. package/plugins/openclaw/memorix/.codex-plugin/plugin.json +1 -1
  23. package/plugins/pi/memorix/package.json +1 -1
  24. package/server.json +2 -2
  25. package/src/cli/commands/codegraph.ts +83 -2
  26. package/src/codegraph/auto-context.ts +20 -0
  27. package/src/codegraph/current-facts.ts +1 -0
  28. package/src/codegraph/external-provider.ts +1 -0
  29. package/src/codegraph/store.ts +216 -5
  30. package/src/codegraph/types.ts +42 -0
  31. package/src/knowledge/context-assembly.ts +25 -0
  32. package/src/knowledge/governor.ts +156 -0
  33. package/src/knowledge/outcome-store.ts +102 -0
  34. package/src/knowledge/outcome-types.ts +49 -0
  35. package/src/knowledge/workflows.ts +32 -1
  36. package/src/knowledge/workset.ts +203 -13
  37. package/src/store/sqlite-db.ts +42 -0
  38. package/docs/dev-log/progress.txt +0 -388
@@ -307,7 +307,7 @@ function closeAllDatabases() {
307
307
  _dbCache.delete(key);
308
308
  }
309
309
  }
310
- var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
310
+ var BetterSqlite3, CREATE_OBSERVATIONS_TABLE, CREATE_MINI_SKILLS_TABLE, CREATE_SESSIONS_TABLE, CREATE_META_TABLE, CREATE_TEAM_AGENTS_TABLE, CREATE_TEAM_MESSAGES_TABLE, CREATE_TEAM_TASKS_TABLE, CREATE_TEAM_TASK_DEPS_TABLE, CREATE_TEAM_LOCKS_TABLE, CREATE_TEAM_ROLES_TABLE, CREATE_CHAT_TRANSCRIPT_TABLE, CREATE_GRAPH_ENTITIES_TABLE, CREATE_GRAPH_RELATIONS_TABLE, CREATE_CODE_FILES_TABLE, CREATE_CODE_SYMBOLS_TABLE, CREATE_CODE_EDGES_TABLE, CREATE_OBSERVATION_CODE_REFS_TABLE, CREATE_SCHEMA_MIGRATIONS_TABLE, CREATE_CODE_STATE_SNAPSHOTS_TABLE, CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE, CREATE_KNOWLEDGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_CLAIM_EVIDENCE_TABLE, CREATE_KNOWLEDGE_CLAIM_EVENTS_TABLE, CREATE_KNOWLEDGE_WORKSPACES_TABLE, CREATE_KNOWLEDGE_PAGES_TABLE, CREATE_KNOWLEDGE_PAGE_CLAIMS_TABLE, CREATE_KNOWLEDGE_PAGE_LINKS_TABLE, CREATE_KNOWLEDGE_PROPOSALS_TABLE, CREATE_KNOWLEDGE_WORKFLOWS_TABLE, CREATE_KNOWLEDGE_WORKFLOW_RUNS_TABLE, CREATE_LONG_TERM_MEMORIES_TABLE, CREATE_LONG_TERM_MEMORY_EVIDENCE_TABLE, CREATE_LONG_TERM_MEMORY_EVENTS_TABLE, CREATE_MEMORY_OUTCOME_SIGNALS_TABLE, CREATE_MEDIA_ASSETS_TABLE, CREATE_MEDIA_ASSET_LINKS_TABLE, CREATE_MEDIA_DERIVATIONS_TABLE, CREATE_MEDIA_EMBEDDING_PROFILES_TABLE, CREATE_MEDIA_EMBEDDINGS_TABLE, CREATE_MEDIA_JOBS_TABLE, CREATE_MAINTENANCE_JOBS_TABLE, CREATE_MAINTENANCE_TARGETS_TABLE, CREATE_COMPACTION_CHECKPOINTS_TABLE, CREATE_INDEXES, SCHEMA_MIGRATIONS, _dbCache;
311
311
  var init_sqlite_db = __esm({
312
312
  "src/store/sqlite-db.ts"() {
313
313
  "use strict";
@@ -573,6 +573,17 @@ CREATE TABLE IF NOT EXISTS observation_code_refs (
573
573
  " UNIQUE(projectId, sourceEpoch)",
574
574
  ");"
575
575
  ].join("\n");
576
+ CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE = [
577
+ "CREATE TABLE IF NOT EXISTS code_state_snapshot_files (",
578
+ " snapshotId TEXT NOT NULL,",
579
+ " projectId TEXT NOT NULL,",
580
+ " fileId TEXT NOT NULL,",
581
+ " path TEXT NOT NULL,",
582
+ " contentHash TEXT NOT NULL,",
583
+ " PRIMARY KEY (snapshotId, path),",
584
+ " FOREIGN KEY (snapshotId) REFERENCES code_state_snapshots(id) ON DELETE CASCADE",
585
+ ");"
586
+ ].join("\n");
576
587
  CREATE_KNOWLEDGE_CLAIMS_TABLE = `
577
588
  CREATE TABLE IF NOT EXISTS knowledge_claims (
578
589
  id TEXT PRIMARY KEY,
@@ -783,6 +794,19 @@ CREATE TABLE IF NOT EXISTS long_term_memory_events (
783
794
  createdAt TEXT NOT NULL,
784
795
  FOREIGN KEY (memoryId) REFERENCES long_term_memories(id) ON DELETE CASCADE
785
796
  );
797
+ `;
798
+ CREATE_MEMORY_OUTCOME_SIGNALS_TABLE = `
799
+ CREATE TABLE IF NOT EXISTS memory_outcome_signals (
800
+ id TEXT PRIMARY KEY,
801
+ projectId TEXT NOT NULL,
802
+ candidateKind TEXT NOT NULL,
803
+ candidateId TEXT NOT NULL,
804
+ kind TEXT NOT NULL,
805
+ sourceRef TEXT NOT NULL,
806
+ snapshotId TEXT,
807
+ detail TEXT,
808
+ observedAt TEXT NOT NULL
809
+ );
786
810
  `;
787
811
  CREATE_MEDIA_ASSETS_TABLE = `
788
812
  CREATE TABLE IF NOT EXISTS media_assets (
@@ -1025,6 +1049,20 @@ CREATE UNIQUE INDEX IF NOT EXISTS idx_maintenance_jobs_active_dedupe
1025
1049
  db2.exec("CREATE INDEX IF NOT EXISTS idx_code_edges_snapshot ON code_edges(projectId, snapshotId)");
1026
1050
  }
1027
1051
  },
1052
+ {
1053
+ id: "1.4-codegraph-snapshot-manifest",
1054
+ apply: (db2) => {
1055
+ db2.exec(CREATE_CODE_STATE_SNAPSHOT_FILES_TABLE);
1056
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_code_snapshot_files_project_path ON code_state_snapshot_files(projectId, path)");
1057
+ }
1058
+ },
1059
+ {
1060
+ id: "1.4-memory-outcome-signals",
1061
+ apply: (db2) => {
1062
+ db2.exec(CREATE_MEMORY_OUTCOME_SIGNALS_TABLE);
1063
+ db2.exec("CREATE INDEX IF NOT EXISTS idx_memory_outcomes_candidate ON memory_outcome_signals(projectId, candidateKind, candidateId, observedAt DESC)");
1064
+ }
1065
+ },
1028
1066
  {
1029
1067
  id: "1.2-knowledge-claim-ledger",
1030
1068
  apply: (db2) => {
@@ -1985,8 +2023,8 @@ async function readResponseText(response, signal, maxBytes = MAX_LLM_RESPONSE_BY
1985
2023
  }
1986
2024
  }
1987
2025
  async function readResponseJson(response, signal, maxBytes = MAX_LLM_RESPONSE_BYTES) {
1988
- const text = await readResponseText(response, signal, maxBytes);
1989
- return JSON.parse(text);
2026
+ const text2 = await readResponseText(response, signal, maxBytes);
2027
+ return JSON.parse(text2);
1990
2028
  }
1991
2029
  async function* callLLMWithToolsStream(messages, tools) {
1992
2030
  if (!currentConfig) {
@@ -3158,8 +3196,8 @@ var init_mini_skill_store = __esm({
3158
3196
 
3159
3197
  // src/compact/token-budget.ts
3160
3198
  import { countTokens, isWithinTokenLimit } from "gpt-tokenizer";
3161
- function countTextTokens(text) {
3162
- return countTokens(text);
3199
+ function countTextTokens(text2) {
3200
+ return countTokens(text2);
3163
3201
  }
3164
3202
  var init_token_budget = __esm({
3165
3203
  "src/compact/token-budget.ts"() {
@@ -3265,8 +3303,8 @@ import { createHash } from "crypto";
3265
3303
  import { readFile, writeFile, mkdir } from "fs/promises";
3266
3304
  import { join as join2 } from "path";
3267
3305
  import { homedir as homedir6 } from "os";
3268
- function textHash(text) {
3269
- return createHash("sha256").update(text).digest("hex").slice(0, 16);
3306
+ function textHash(text2) {
3307
+ return createHash("sha256").update(text2).digest("hex").slice(0, 16);
3270
3308
  }
3271
3309
  async function loadDiskCache() {
3272
3310
  try {
@@ -3329,11 +3367,11 @@ var init_fastembed_provider = __esm({
3329
3367
  async embedInputs(inputs, options = {}) {
3330
3368
  return Promise.all(inputs.map((input) => this.embedInput(input, options)));
3331
3369
  }
3332
- async embed(text) {
3333
- const hash4 = textHash(text);
3370
+ async embed(text2) {
3371
+ const hash4 = textHash(text2);
3334
3372
  const cached = cache.get(hash4);
3335
3373
  if (cached) return cached;
3336
- const raw = await this.model.queryEmbed(text);
3374
+ const raw = await this.model.queryEmbed(text2);
3337
3375
  const result = Array.from(raw);
3338
3376
  if (result.length !== this.dimensions) {
3339
3377
  throw new Error(`Expected ${this.dimensions}d embedding, got ${result.length}d`);
@@ -3429,10 +3467,10 @@ var init_transformers_provider = __esm({
3429
3467
  async embedInputs(inputs, options = {}) {
3430
3468
  return Promise.all(inputs.map((input) => this.embedInput(input, options)));
3431
3469
  }
3432
- async embed(text) {
3433
- const cached = cache2.get(text);
3470
+ async embed(text2) {
3471
+ const cached = cache2.get(text2);
3434
3472
  if (cached) return cached;
3435
- const output = await this.extractor(text, {
3473
+ const output = await this.extractor(text2, {
3436
3474
  pooling: "mean",
3437
3475
  normalize: true
3438
3476
  });
@@ -3440,7 +3478,7 @@ var init_transformers_provider = __esm({
3440
3478
  if (result.length !== this.dimensions) {
3441
3479
  throw new Error(`Expected ${this.dimensions}d embedding, got ${result.length}d`);
3442
3480
  }
3443
- this.cacheSet(text, result);
3481
+ this.cacheSet(text2, result);
3444
3482
  return result;
3445
3483
  }
3446
3484
  async embedBatch(texts) {
@@ -3503,8 +3541,8 @@ function dimsCacheFile() {
3503
3541
  function cacheMetaFile() {
3504
3542
  return join3(cacheDir(), ".embedding-api-cache-meta.json");
3505
3543
  }
3506
- function normalizeText(text) {
3507
- return text.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3544
+ function normalizeText(text2) {
3545
+ return text2.replace(/\s+/g, " ").trim().slice(0, MAX_INPUT_CHARS);
3508
3546
  }
3509
3547
  function cacheNamespace(config) {
3510
3548
  return [
@@ -3514,8 +3552,8 @@ function cacheNamespace(config) {
3514
3552
  config.requestedDimensions ?? "native"
3515
3553
  ].join("|");
3516
3554
  }
3517
- function textHash2(text, namespace) {
3518
- return createHash2("sha256").update(`${namespace}\0${text}`).digest("hex").slice(0, 16);
3555
+ function textHash2(text2, namespace) {
3556
+ return createHash2("sha256").update(`${namespace}\0${text2}`).digest("hex").slice(0, 16);
3519
3557
  }
3520
3558
  function inputIdentity(input, options = {}) {
3521
3559
  return JSON.stringify({
@@ -3927,8 +3965,8 @@ var init_api_provider = __esm({
3927
3965
  if (!embedding) throw new Error("API probe returned no embeddings; check model name and API key");
3928
3966
  return embedding.length;
3929
3967
  }
3930
- async embed(text, options) {
3931
- const normalized = normalizeText(text);
3968
+ async embed(text2, options) {
3969
+ const normalized = normalizeText(text2);
3932
3970
  if (isNativeGeminiEndpoint(this.config.baseUrl)) {
3933
3971
  return this.embedInput({ modality: "text", text: normalized }, { intent: "document", ...options });
3934
3972
  }
@@ -3985,7 +4023,7 @@ var init_api_provider = __esm({
3985
4023
  }
3986
4024
  async embedBatch(texts, options) {
3987
4025
  if (isNativeGeminiEndpoint(this.config.baseUrl)) {
3988
- return Promise.all(texts.map((text) => this.embedInput({ modality: "text", text }, { intent: "document", ...options })));
4026
+ return Promise.all(texts.map((text2) => this.embedInput({ modality: "text", text: text2 }, { intent: "document", ...options })));
3989
4027
  }
3990
4028
  await ensureDiskCacheLoaded();
3991
4029
  const normalizedTexts = texts.map(normalizeText);
@@ -4064,8 +4102,8 @@ var init_api_provider = __esm({
4064
4102
  }
4065
4103
  async getCachedEmbeddings(texts) {
4066
4104
  await ensureDiskCacheLoaded();
4067
- return texts.map((text) => {
4068
- const hash4 = textHash2(normalizeText(text), this.cacheKeyNamespace);
4105
+ return texts.map((text2) => {
4106
+ const hash4 = textHash2(normalizeText(text2), this.cacheKeyNamespace);
4069
4107
  return cache3.get(hash4) ?? null;
4070
4108
  });
4071
4109
  }
@@ -4295,9 +4333,9 @@ function wrapProvider(candidate) {
4295
4333
  return {
4296
4334
  name: candidate.name,
4297
4335
  dimensions: candidate.dimensions,
4298
- async embed(text, options) {
4336
+ async embed(text2, options) {
4299
4337
  try {
4300
- return await candidate.embed(text, options);
4338
+ return await candidate.embed(text2, options);
4301
4339
  } catch (error) {
4302
4340
  if (kind === "api" && getEmbeddingMode2() === "auto" && isTemporaryEmbeddingFailure(error)) {
4303
4341
  warnOnce("[memorix] API embedding temporarily unavailable \u2014 switching to local fallback provider");
@@ -4305,7 +4343,7 @@ function wrapProvider(candidate) {
4305
4343
  if (fallback) {
4306
4344
  provider = wrapProvider(fallback);
4307
4345
  warnOnce(`[memorix] Embedding fallback activated: ${provider.name} (${provider.dimensions}d)`);
4308
- return provider.embed(text, options);
4346
+ return provider.embed(text2, options);
4309
4347
  }
4310
4348
  }
4311
4349
  if (isTemporaryEmbeddingFailure(error)) {
@@ -4804,8 +4842,8 @@ function isCjkHeavy(query) {
4804
4842
  const cjkCount = (query.match(CJK_PATTERN) || []).length;
4805
4843
  return query.length > 0 && cjkCount / query.length > 0.3;
4806
4844
  }
4807
- function normalizeExpansion(text) {
4808
- return text.trim().replace(/^[-*]\s*/, "").replace(/^["'`]+|["'`]+$/g, "").split(/\r?\n/)[0].trim();
4845
+ function normalizeExpansion(text2) {
4846
+ return text2.trim().replace(/^[-*]\s*/, "").replace(/^["'`]+|["'`]+$/g, "").split(/\r?\n/)[0].trim();
4809
4847
  }
4810
4848
  async function maybeExpandSearchQuery(query) {
4811
4849
  if (!query || !isCjkHeavy(query) || COMMAND_LIKE_QUERY.test(query)) {
@@ -5168,9 +5206,9 @@ function shouldSkipCompression(narrative, type) {
5168
5206
  if (specialChars / narrative.length > 0.35) return true;
5169
5207
  return false;
5170
5208
  }
5171
- function estimateTokens(text) {
5172
- const cjkChars = (text.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length;
5173
- const otherChars = text.length - cjkChars;
5209
+ function estimateTokens(text2) {
5210
+ const cjkChars = (text2.match(/[\u4e00-\u9fff\u3040-\u309f\u30a0-\u30ff\uac00-\ud7af]/g) || []).length;
5211
+ const otherChars = text2.length - cjkChars;
5174
5212
  return Math.ceil(cjkChars / 1.5 + otherChars / 4);
5175
5213
  }
5176
5214
  var COMPRESS_PROMPT, COMPRESS_PROMPT_GENTLE, RERANK_PROMPT, SKIP_PATTERNS, LOW_COMPRESSION_TYPES;
@@ -5473,10 +5511,10 @@ function isEmbeddingEnabled() {
5473
5511
  function getVectorDimensions() {
5474
5512
  return embeddingEnabled ? embeddingDimensions : null;
5475
5513
  }
5476
- async function generateEmbedding(text) {
5514
+ async function generateEmbedding(text2) {
5477
5515
  const provider2 = await getEmbeddingProvider();
5478
5516
  if (!provider2) return null;
5479
- return provider2.embedInput ? provider2.embedInput({ modality: "text", text }, { intent: "document" }) : provider2.embed(text);
5517
+ return provider2.embedInput ? provider2.embedInput({ modality: "text", text: text2 }, { intent: "document" }) : provider2.embed(text2);
5480
5518
  }
5481
5519
  async function generateEmbeddingInput(input, options = {}) {
5482
5520
  const provider2 = await getEmbeddingProvider();
@@ -6322,9 +6360,9 @@ var init_entity_extractor = __esm({
6322
6360
  });
6323
6361
 
6324
6362
  // src/memory/secret-filter.ts
6325
- function applyRedaction(text) {
6326
- if (!text) return text;
6327
- let out = text;
6363
+ function applyRedaction(text2) {
6364
+ if (!text2) return text2;
6365
+ let out = text2;
6328
6366
  out = out.replace(
6329
6367
  /((?:password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?key|access[_-]?key|private[_-]?key|client[_-]?secret)\s*[:=]\s*["']?)([^\s"',;\n]{6,})/gi,
6330
6368
  "$1[REDACTED]"
@@ -6338,8 +6376,8 @@ function applyRedaction(text) {
6338
6376
  out = out.replace(/\beyJ[A-Za-z0-9._-]{40,}\b/g, "[REDACTED]");
6339
6377
  return out;
6340
6378
  }
6341
- function sanitizeCredentials(text) {
6342
- return applyRedaction(text);
6379
+ function sanitizeCredentials(text2) {
6380
+ return applyRedaction(text2);
6343
6381
  }
6344
6382
  var init_secret_filter = __esm({
6345
6383
  "src/memory/secret-filter.ts"() {
@@ -6390,8 +6428,8 @@ function clampPositiveInteger(value, fallback) {
6390
6428
  return Math.max(1, Math.floor(value));
6391
6429
  }
6392
6430
  function errorText(error) {
6393
- const text = error instanceof Error ? error.message : String(error);
6394
- return sanitizeCredentials(text).slice(0, MAX_ERROR_LENGTH);
6431
+ const text2 = error instanceof Error ? error.message : String(error);
6432
+ return sanitizeCredentials(text2).slice(0, MAX_ERROR_LENGTH);
6395
6433
  }
6396
6434
  function normalizedKinds(kinds) {
6397
6435
  if (!kinds) return void 0;
@@ -7340,9 +7378,9 @@ __export(consolidation_exports, {
7340
7378
  executeConsolidation: () => executeConsolidation,
7341
7379
  findConsolidationCandidates: () => findConsolidationCandidates
7342
7380
  });
7343
- function tokenize(text) {
7381
+ function tokenize(text2) {
7344
7382
  return new Set(
7345
- text.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, " ").split(/\s+/).filter((t2) => t2.length > 1)
7383
+ text2.toLowerCase().replace(/[^a-z0-9\u4e00-\u9fff\s-]/g, " ").split(/\s+/).filter((t2) => t2.length > 1)
7346
7384
  );
7347
7385
  }
7348
7386
  function jaccardSimilarity(a, b) {
@@ -16300,8 +16338,8 @@ var init_headers = __esm({
16300
16338
  });
16301
16339
 
16302
16340
  // packages/ai/dist/utils/sanitize-unicode.js
16303
- function sanitizeSurrogates(text) {
16304
- return text.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
16341
+ function sanitizeSurrogates(text2) {
16342
+ return text2.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "");
16305
16343
  }
16306
16344
  var init_sanitize_unicode = __esm({
16307
16345
  "packages/ai/dist/utils/sanitize-unicode.js"() {
@@ -16783,9 +16821,9 @@ var init_values = __esm({
16783
16821
  }
16784
16822
  return n;
16785
16823
  };
16786
- safeJSON = (text) => {
16824
+ safeJSON = (text2) => {
16787
16825
  try {
16788
- return JSON.parse(text);
16826
+ return JSON.parse(text2);
16789
16827
  } catch (err) {
16790
16828
  return void 0;
16791
16829
  }
@@ -17948,8 +17986,8 @@ async function defaultParseResponse(client, props) {
17948
17986
  const json2 = await response.json();
17949
17987
  return addRequestID(json2, response);
17950
17988
  }
17951
- const text = await response.text();
17952
- return text;
17989
+ const text2 = await response.text();
17990
+ return text2;
17953
17991
  })();
17954
17992
  loggerFor(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails({
17955
17993
  retryOfRequestLogID,
@@ -31308,7 +31346,7 @@ var init_models_generated = __esm({
31308
31346
  provider: "minimax",
31309
31347
  baseUrl: "https://api.minimax.io/anthropic",
31310
31348
  reasoning: true,
31311
- input: ["text", "image"],
31349
+ input: ["text", "image", "video"],
31312
31350
  cost: {
31313
31351
  input: 0.3,
31314
31352
  output: 1.2,
@@ -31361,7 +31399,7 @@ var init_models_generated = __esm({
31361
31399
  provider: "minimax-cn",
31362
31400
  baseUrl: "https://api.minimaxi.com/anthropic",
31363
31401
  reasoning: true,
31364
- input: ["text", "image"],
31402
+ input: ["text", "image", "video"],
31365
31403
  cost: {
31366
31404
  input: 0.3,
31367
31405
  output: 1.2,
@@ -43556,9 +43594,9 @@ var init_values2 = __esm({
43556
43594
  }
43557
43595
  return n;
43558
43596
  };
43559
- safeJSON2 = (text) => {
43597
+ safeJSON2 = (text2) => {
43560
43598
  try {
43561
- return JSON.parse(text);
43599
+ return JSON.parse(text2);
43562
43600
  } catch (err) {
43563
43601
  return void 0;
43564
43602
  }
@@ -44332,8 +44370,8 @@ async function defaultParseResponse2(client, props) {
44332
44370
  const json2 = await response.json();
44333
44371
  return addRequestID2(json2, response);
44334
44372
  }
44335
- const text = await response.text();
44336
- return text;
44373
+ const text2 = await response.text();
44374
+ return text2;
44337
44375
  })();
44338
44376
  loggerFor2(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails2({
44339
44377
  retryOfRequestLogID,
@@ -50636,11 +50674,11 @@ var init_simple_options = __esm({
50636
50674
  });
50637
50675
 
50638
50676
  // packages/ai/dist/providers/transform-messages.js
50639
- function replaceImagesWithPlaceholder(content, placeholder) {
50677
+ function replaceMediaWithPlaceholder(content, dropType, placeholder) {
50640
50678
  const result = [];
50641
50679
  let previousWasPlaceholder = false;
50642
50680
  for (const block of content) {
50643
- if (block.type === "image") {
50681
+ if (block.type === dropType) {
50644
50682
  if (!previousWasPlaceholder) {
50645
50683
  result.push({ type: "text", text: placeholder });
50646
50684
  }
@@ -50648,33 +50686,39 @@ function replaceImagesWithPlaceholder(content, placeholder) {
50648
50686
  continue;
50649
50687
  }
50650
50688
  result.push(block);
50651
- previousWasPlaceholder = block.text === placeholder;
50689
+ previousWasPlaceholder = block.type === "text" && block.text === placeholder;
50652
50690
  }
50653
50691
  return result;
50654
50692
  }
50655
- function downgradeUnsupportedImages(messages, model) {
50656
- if (model.input.includes("image")) {
50693
+ function downgradeUnsupportedMedia(messages, model) {
50694
+ const supportsImage = model.input.includes("image");
50695
+ const supportsVideo = model.input.includes("video");
50696
+ if (supportsImage && supportsVideo) {
50657
50697
  return messages;
50658
50698
  }
50659
50699
  return messages.map((msg) => {
50660
50700
  if (msg.role === "user" && Array.isArray(msg.content)) {
50661
- return {
50662
- ...msg,
50663
- content: replaceImagesWithPlaceholder(msg.content, NON_VISION_USER_IMAGE_PLACEHOLDER)
50664
- };
50701
+ let content = msg.content;
50702
+ if (!supportsImage)
50703
+ content = replaceMediaWithPlaceholder(content, "image", NON_VISION_USER_IMAGE_PLACEHOLDER);
50704
+ if (!supportsVideo)
50705
+ content = replaceMediaWithPlaceholder(content, "video", NON_VISION_USER_VIDEO_PLACEHOLDER);
50706
+ return { ...msg, content };
50665
50707
  }
50666
50708
  if (msg.role === "toolResult") {
50667
- return {
50668
- ...msg,
50669
- content: replaceImagesWithPlaceholder(msg.content, NON_VISION_TOOL_IMAGE_PLACEHOLDER)
50670
- };
50709
+ let content = msg.content;
50710
+ if (!supportsImage)
50711
+ content = replaceMediaWithPlaceholder(content, "image", NON_VISION_TOOL_IMAGE_PLACEHOLDER);
50712
+ if (!supportsVideo)
50713
+ content = replaceMediaWithPlaceholder(content, "video", NON_VISION_TOOL_VIDEO_PLACEHOLDER);
50714
+ return { ...msg, content };
50671
50715
  }
50672
50716
  return msg;
50673
50717
  });
50674
50718
  }
50675
50719
  function transformMessages(messages, model, normalizeToolCallId2) {
50676
50720
  const toolCallIdMap = /* @__PURE__ */ new Map();
50677
- const imageAwareMessages = downgradeUnsupportedImages(messages, model);
50721
+ const imageAwareMessages = downgradeUnsupportedMedia(messages, model);
50678
50722
  const transformed = imageAwareMessages.map((msg) => {
50679
50723
  if (msg.role === "user") {
50680
50724
  return msg;
@@ -50786,13 +50830,15 @@ function transformMessages(messages, model, normalizeToolCallId2) {
50786
50830
  insertSyntheticToolResults();
50787
50831
  return result;
50788
50832
  }
50789
- var NON_VISION_USER_IMAGE_PLACEHOLDER, NON_VISION_TOOL_IMAGE_PLACEHOLDER;
50833
+ var NON_VISION_USER_IMAGE_PLACEHOLDER, NON_VISION_TOOL_IMAGE_PLACEHOLDER, NON_VISION_USER_VIDEO_PLACEHOLDER, NON_VISION_TOOL_VIDEO_PLACEHOLDER;
50790
50834
  var init_transform_messages = __esm({
50791
50835
  "packages/ai/dist/providers/transform-messages.js"() {
50792
50836
  "use strict";
50793
50837
  init_esm_shims();
50794
50838
  NON_VISION_USER_IMAGE_PLACEHOLDER = "(image omitted: model does not support images)";
50795
50839
  NON_VISION_TOOL_IMAGE_PLACEHOLDER = "(tool image omitted: model does not support images)";
50840
+ NON_VISION_USER_VIDEO_PLACEHOLDER = "(video omitted: model does not support video)";
50841
+ NON_VISION_TOOL_VIDEO_PLACEHOLDER = "(tool video omitted: model does not support video)";
50796
50842
  }
50797
50843
  });
50798
50844
 
@@ -50823,8 +50869,8 @@ function getCacheControl(model, cacheRetention) {
50823
50869
  };
50824
50870
  }
50825
50871
  function convertContentBlocks(content) {
50826
- const hasImages = content.some((c) => c.type === "image");
50827
- if (!hasImages) {
50872
+ const hasMedia = content.some((c) => c.type === "image" || c.type === "video");
50873
+ if (!hasMedia) {
50828
50874
  return sanitizeSurrogates(content.map((c) => c.text).join("\n"));
50829
50875
  }
50830
50876
  const blocks = content.map((block) => {
@@ -50834,6 +50880,16 @@ function convertContentBlocks(content) {
50834
50880
  text: sanitizeSurrogates(block.text)
50835
50881
  };
50836
50882
  }
50883
+ if (block.type === "video") {
50884
+ return {
50885
+ type: "video",
50886
+ source: {
50887
+ type: "base64",
50888
+ media_type: block.mimeType,
50889
+ data: block.data
50890
+ }
50891
+ };
50892
+ }
50837
50893
  return {
50838
50894
  type: "image",
50839
50895
  source: {
@@ -50847,7 +50903,7 @@ function convertContentBlocks(content) {
50847
50903
  if (!hasText) {
50848
50904
  blocks.unshift({
50849
50905
  type: "text",
50850
- text: "(see attached image)"
50906
+ text: "(see attached media)"
50851
50907
  });
50852
50908
  }
50853
50909
  return blocks;
@@ -50908,9 +50964,9 @@ function decodeSseLine(line, state) {
50908
50964
  }
50909
50965
  return null;
50910
50966
  }
50911
- function nextLineBreakIndex(text) {
50912
- const carriageReturnIndex = text.indexOf("\r");
50913
- const newlineIndex = text.indexOf("\n");
50967
+ function nextLineBreakIndex(text2) {
50968
+ const carriageReturnIndex = text2.indexOf("\r");
50969
+ const newlineIndex = text2.indexOf("\n");
50914
50970
  if (carriageReturnIndex === -1) {
50915
50971
  return newlineIndex;
50916
50972
  }
@@ -50919,18 +50975,18 @@ function nextLineBreakIndex(text) {
50919
50975
  }
50920
50976
  return Math.min(carriageReturnIndex, newlineIndex);
50921
50977
  }
50922
- function consumeLine(text) {
50923
- const lineBreakIndex = nextLineBreakIndex(text);
50978
+ function consumeLine(text2) {
50979
+ const lineBreakIndex = nextLineBreakIndex(text2);
50924
50980
  if (lineBreakIndex === -1) {
50925
50981
  return null;
50926
50982
  }
50927
50983
  let nextIndex = lineBreakIndex + 1;
50928
- if (text[lineBreakIndex] === "\r" && text[nextIndex] === "\n") {
50984
+ if (text2[lineBreakIndex] === "\r" && text2[nextIndex] === "\n") {
50929
50985
  nextIndex += 1;
50930
50986
  }
50931
50987
  return {
50932
- line: text.slice(0, lineBreakIndex),
50933
- rest: text.slice(nextIndex)
50988
+ line: text2.slice(0, lineBreakIndex),
50989
+ rest: text2.slice(nextIndex)
50934
50990
  };
50935
50991
  }
50936
50992
  async function* iterateSseMessages(body, signal) {
@@ -51197,6 +51253,15 @@ function convertMessages(messages, model, isOAuthToken2, cacheControl, allowEmpt
51197
51253
  type: "text",
51198
51254
  text: sanitizeSurrogates(item.text)
51199
51255
  };
51256
+ } else if (item.type === "video") {
51257
+ return {
51258
+ type: "video",
51259
+ source: {
51260
+ type: "base64",
51261
+ media_type: item.mimeType,
51262
+ data: item.data
51263
+ }
51264
+ };
51200
51265
  } else {
51201
51266
  return {
51202
51267
  type: "image",
@@ -51277,6 +51342,8 @@ function convertMessages(messages, model, isOAuthToken2, cacheControl, allowEmpt
51277
51342
  toolResults.push({
51278
51343
  type: "tool_result",
51279
51344
  tool_use_id: msg.toolCallId,
51345
+ // MiniMax's Anthropic-compatible API accepts video in tool results;
51346
+ // the SDK's ToolResultBlockParam.content type predates video support.
51280
51347
  content: convertContentBlocks(msg.content),
51281
51348
  is_error: msg.isError
51282
51349
  });
@@ -51303,7 +51370,7 @@ function convertMessages(messages, model, isOAuthToken2, cacheControl, allowEmpt
51303
51370
  if (lastMessage.role === "user") {
51304
51371
  if (Array.isArray(lastMessage.content)) {
51305
51372
  const lastBlock = lastMessage.content[lastMessage.content.length - 1];
51306
- if (lastBlock && (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "tool_result")) {
51373
+ if (lastBlock && (lastBlock.type === "text" || lastBlock.type === "image" || lastBlock.type === "video" || lastBlock.type === "tool_result")) {
51307
51374
  lastBlock.cache_control = cacheControl;
51308
51375
  }
51309
51376
  } else if (typeof lastMessage.content === "string") {
@@ -53086,8 +53153,8 @@ var require_common = __commonJS({
53086
53153
  }
53087
53154
  function redactString(obj, key) {
53088
53155
  if (typeof obj === "object" && obj !== null && typeof obj[key] === "string") {
53089
- const text = obj[key];
53090
- if (/grant_type=/i.test(text) || /assertion=/i.test(text) || /secret/i.test(text)) {
53156
+ const text2 = obj[key];
53157
+ if (/grant_type=/i.test(text2) || /assertion=/i.test(text2) || /secret/i.test(text2)) {
53091
53158
  obj[key] = REDACT;
53092
53159
  }
53093
53160
  }
@@ -59915,8 +59982,8 @@ var init_body = __esm({
59915
59982
  * @return Promise
59916
59983
  */
59917
59984
  async json() {
59918
- const text = await this.text();
59919
- return JSON.parse(text);
59985
+ const text2 = await this.text();
59986
+ return JSON.parse(text2);
59920
59987
  }
59921
59988
  /**
59922
59989
  * Decode response as text
@@ -61068,7 +61135,7 @@ var require_gaxios = __commonJS({
61068
61135
  var retry_js_1 = require_retry3();
61069
61136
  var stream_1 = __require("stream");
61070
61137
  var interceptor_js_1 = require_interceptor();
61071
- var randomUUID10 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
61138
+ var randomUUID11 = async () => globalThis.crypto?.randomUUID() || (await import("crypto")).randomUUID();
61072
61139
  var HTTP_STATUS_NO_CONTENT = 204;
61073
61140
  var Gaxios = class {
61074
61141
  agentCache = /* @__PURE__ */ new Map();
@@ -61341,7 +61408,7 @@ var require_gaxios = __commonJS({
61341
61408
  */
61342
61409
  ["Blob", "File", "FormData"].includes(opts.data?.constructor?.name || "");
61343
61410
  if (opts.multipart?.length) {
61344
- const boundary = await randomUUID10();
61411
+ const boundary = await randomUUID11();
61345
61412
  preparedHeaders.set("content-type", `multipart/related; boundary=${boundary}`);
61346
61413
  opts.body = stream_1.Readable.from(this.getMultipartRequest(opts.multipart, boundary));
61347
61414
  } else if (shouldDirectlyPassData) {
@@ -63092,18 +63159,18 @@ var require_parse = __commonJS({
63092
63159
  n: "\n",
63093
63160
  r: "\r",
63094
63161
  t: " "
63095
- }, text, error = function(m2) {
63162
+ }, text2, error = function(m2) {
63096
63163
  throw {
63097
63164
  name: "SyntaxError",
63098
63165
  message: m2,
63099
63166
  at,
63100
- text
63167
+ text: text2
63101
63168
  };
63102
63169
  }, next = function(c) {
63103
63170
  if (c && c !== ch) {
63104
63171
  error("Expected '" + c + "' instead of '" + ch + "'");
63105
63172
  }
63106
- ch = text.charAt(at);
63173
+ ch = text2.charAt(at);
63107
63174
  at += 1;
63108
63175
  return ch;
63109
63176
  }, number44 = function() {
@@ -63150,12 +63217,12 @@ var require_parse = __commonJS({
63150
63217
  var startAt = at;
63151
63218
  while (next()) {
63152
63219
  if (ch === '"') {
63153
- if (at - 1 > startAt) string474 += text.substring(startAt, at - 1);
63220
+ if (at - 1 > startAt) string474 += text2.substring(startAt, at - 1);
63154
63221
  next();
63155
63222
  return string474;
63156
63223
  }
63157
63224
  if (ch === "\\") {
63158
- if (at - 1 > startAt) string474 += text.substring(startAt, at - 1);
63225
+ if (at - 1 > startAt) string474 += text2.substring(startAt, at - 1);
63159
63226
  next();
63160
63227
  if (ch === "u") {
63161
63228
  uffff = 0;
@@ -63288,7 +63355,7 @@ var require_parse = __commonJS({
63288
63355
  };
63289
63356
  return function(source, reviver) {
63290
63357
  var result;
63291
- text = source + "";
63358
+ text2 = source + "";
63292
63359
  at = 0;
63293
63360
  ch = " ";
63294
63361
  result = value();
@@ -64200,8 +64267,8 @@ var require_crypto = __commonJS({
64200
64267
  const result = new TextDecoder().decode(uint8array);
64201
64268
  return result;
64202
64269
  }
64203
- encodeBase64StringUtf8(text) {
64204
- const uint8array = new TextEncoder().encode(text);
64270
+ encodeBase64StringUtf8(text2) {
64271
+ const uint8array = new TextEncoder().encode(text2);
64205
64272
  const result = base64js.fromByteArray(uint8array);
64206
64273
  return result;
64207
64274
  }
@@ -64270,8 +64337,8 @@ var require_crypto2 = __commonJS({
64270
64337
  decodeBase64StringUtf8(base64) {
64271
64338
  return Buffer.from(base64, "base64").toString("utf-8");
64272
64339
  }
64273
- encodeBase64StringUtf8(text) {
64274
- return Buffer.from(text, "utf-8").toString("base64");
64340
+ encodeBase64StringUtf8(text2) {
64341
+ return Buffer.from(text2, "utf-8").toString("base64");
64275
64342
  }
64276
64343
  /**
64277
64344
  * Computes the SHA-256 hash of the provided string.
@@ -84716,8 +84783,8 @@ async function defaultParseResponse3(client, props) {
84716
84783
  const json2 = await response.json();
84717
84784
  return json2;
84718
84785
  }
84719
- const text = await response.text();
84720
- return text;
84786
+ const text2 = await response.text();
84787
+ return text2;
84721
84788
  })();
84722
84789
  loggerFor3(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails3({
84723
84790
  retryOfRequestLogID,
@@ -86294,7 +86361,7 @@ var init_node = __esm({
86294
86361
  if (this.candidates && this.candidates.length > 1) {
86295
86362
  console.warn("there are multiple candidates in the response, returning text from the first one.");
86296
86363
  }
86297
- let text = "";
86364
+ let text2 = "";
86298
86365
  let anyTextPartText = false;
86299
86366
  const nonTextParts = [];
86300
86367
  for (const part of (_h = (_g = (_f = (_e = this.candidates) === null || _e === void 0 ? void 0 : _e[0]) === null || _f === void 0 ? void 0 : _f.content) === null || _g === void 0 ? void 0 : _g.parts) !== null && _h !== void 0 ? _h : []) {
@@ -86308,13 +86375,13 @@ var init_node = __esm({
86308
86375
  continue;
86309
86376
  }
86310
86377
  anyTextPartText = true;
86311
- text += part.text;
86378
+ text2 += part.text;
86312
86379
  }
86313
86380
  }
86314
86381
  if (nonTextParts.length > 0) {
86315
86382
  console.warn(`there are non-text parts ${nonTextParts} in the response, returning concatenation of all text parts. Please refer to the non text parts for a full response from model.`);
86316
86383
  }
86317
- return anyTextPartText ? text : void 0;
86384
+ return anyTextPartText ? text2 : void 0;
86318
86385
  }
86319
86386
  /**
86320
86387
  * Returns the concatenation of all inline data parts from the first candidate
@@ -86567,7 +86634,7 @@ var init_node = __esm({
86567
86634
  */
86568
86635
  get text() {
86569
86636
  var _a5, _b, _c;
86570
- let text = "";
86637
+ let text2 = "";
86571
86638
  let anyTextPartFound = false;
86572
86639
  const nonTextParts = [];
86573
86640
  for (const part of (_c = (_b = (_a5 = this.serverContent) === null || _a5 === void 0 ? void 0 : _a5.modelTurn) === null || _b === void 0 ? void 0 : _b.parts) !== null && _c !== void 0 ? _c : []) {
@@ -86581,13 +86648,13 @@ var init_node = __esm({
86581
86648
  continue;
86582
86649
  }
86583
86650
  anyTextPartFound = true;
86584
- text += part.text;
86651
+ text2 += part.text;
86585
86652
  }
86586
86653
  }
86587
86654
  if (nonTextParts.length > 0) {
86588
86655
  console.warn(`there are non-text parts ${nonTextParts} in the response, returning concatenation of all text parts. Please refer to the non text parts for a full response from model.`);
86589
86656
  }
86590
- return anyTextPartFound ? text : void 0;
86657
+ return anyTextPartFound ? text2 : void 0;
86591
86658
  }
86592
86659
  /**
86593
86660
  * Returns the concatenation of all inline data parts from the server content if present.
@@ -91259,9 +91326,9 @@ var init_node = __esm({
91259
91326
  }
91260
91327
  return n;
91261
91328
  };
91262
- safeJSON3 = (text) => {
91329
+ safeJSON3 = (text2) => {
91263
91330
  try {
91264
- return JSON.parse(text);
91331
+ return JSON.parse(text2);
91265
91332
  } catch (err) {
91266
91333
  return void 0;
91267
91334
  }
@@ -109124,8 +109191,8 @@ function findBoundary(buf) {
109124
109191
  return null;
109125
109192
  }
109126
109193
  function parseMessage2(chunk, parse2, state, dataRequired) {
109127
- const text = new TextDecoder().decode(chunk);
109128
- const lines = text.split(/\r\n|\r|\n/);
109194
+ const text2 = new TextDecoder().decode(chunk);
109195
+ const lines = text2.split(/\r\n|\r|\n/);
109129
109196
  const dataLines = [];
109130
109197
  const ret = {};
109131
109198
  let ignore = true;
@@ -131999,10 +132066,10 @@ function formatMistralError(error) {
131999
132066
  }
132000
132067
  return safeJsonStringify(error);
132001
132068
  }
132002
- function truncateErrorText(text, maxChars) {
132003
- if (text.length <= maxChars)
132004
- return text;
132005
- return `${text.slice(0, maxChars)}... [truncated ${text.length - maxChars} chars]`;
132069
+ function truncateErrorText(text2, maxChars) {
132070
+ if (text2.length <= maxChars)
132071
+ return text2;
132072
+ return `${text2.slice(0, maxChars)}... [truncated ${text2.length - maxChars} chars]`;
132006
132073
  }
132007
132074
  function safeJsonStringify(value) {
132008
132075
  try {
@@ -132122,7 +132189,7 @@ async function consumeChatStream(model, output, stream2, mistralStream) {
132122
132189
  continue;
132123
132190
  }
132124
132191
  if (item.type === "thinking") {
132125
- const deltaText = item.thinking.map((part) => "text" in part ? part.text : "").filter((text) => text.length > 0).join("");
132192
+ const deltaText = item.thinking.map((part) => "text" in part ? part.text : "").filter((text2) => text2.length > 0).join("");
132126
132193
  const thinkingDelta = sanitizeSurrogates(deltaText);
132127
132194
  if (!thinkingDelta)
132128
132195
  continue;
@@ -132319,8 +132386,8 @@ function toChatMessages(messages, supportsImages) {
132319
132386
  }
132320
132387
  return result;
132321
132388
  }
132322
- function buildToolResultText(text, hasImages, supportsImages, isError) {
132323
- const trimmed = text.trim();
132389
+ function buildToolResultText(text2, hasImages, supportsImages, isError) {
132390
+ const trimmed = text2.trim();
132324
132391
  const errorPrefix = isError ? "[tool error] " : "";
132325
132392
  if (trimmed.length > 0) {
132326
132393
  const imageSuffix = hasImages && !supportsImages ? "\n[tool image omitted: model does not support images]" : "";
@@ -133121,14 +133188,14 @@ async function* parseWebSocket(socket, signal, idleTimeoutMs) {
133121
133188
  };
133122
133189
  const onMessage = (event) => {
133123
133190
  void (async () => {
133124
- let text = null;
133191
+ let text2 = null;
133125
133192
  try {
133126
133193
  if (!event || typeof event !== "object" || !("data" in event))
133127
133194
  return;
133128
- text = await decodeWebSocketData(event.data);
133129
- if (!text)
133195
+ text2 = await decodeWebSocketData(event.data);
133196
+ if (!text2)
133130
133197
  return;
133131
- const parsed = JSON.parse(text);
133198
+ const parsed = JSON.parse(text2);
133132
133199
  const type = typeof parsed.type === "string" ? parsed.type : "";
133133
133200
  if (type === "response.completed" || type === "response.done" || type === "response.incomplete") {
133134
133201
  sawCompletion = true;
@@ -133139,7 +133206,7 @@ async function* parseWebSocket(socket, signal, idleTimeoutMs) {
133139
133206
  } catch (cause) {
133140
133207
  failed = new CodexProtocolError(`Invalid Codex WebSocket JSON: ${formatThrownValue(cause)}`, {
133141
133208
  cause,
133142
- payload: text
133209
+ payload: text2
133143
133210
  });
133144
133211
  done = true;
133145
133212
  wake();
@@ -141303,11 +141370,11 @@ function videoRequestConfig(input) {
141303
141370
  };
141304
141371
  }
141305
141372
  async function readMiniMaxJson(response) {
141306
- const text = await response.text();
141373
+ const text2 = await response.text();
141307
141374
  let body = {};
141308
- if (text) {
141375
+ if (text2) {
141309
141376
  try {
141310
- const parsed = JSON.parse(text);
141377
+ const parsed = JSON.parse(text2);
141311
141378
  if (isRecord(parsed)) body = parsed;
141312
141379
  } catch {
141313
141380
  throw new Error("MiniMax video API returned invalid JSON");
@@ -142128,6 +142195,7 @@ __export(store_exports, {
142128
142195
  CodeGraphStore: () => CodeGraphStore
142129
142196
  });
142130
142197
  import { randomUUID as randomUUID6 } from "crypto";
142198
+ import { posix as pathPosix } from "path";
142131
142199
  function rowToFile(row) {
142132
142200
  return {
142133
142201
  id: row.id,
@@ -142178,6 +142246,26 @@ function rowToEdge(row) {
142178
142246
  ...row.sourceEpoch != null ? { sourceEpoch: Number(row.sourceEpoch) } : {}
142179
142247
  };
142180
142248
  }
142249
+ function resolveRelativeImportTarget(sourcePath, evidence, fileByPath) {
142250
+ const target = evidence?.trim().replace(/\\/g, "/");
142251
+ if (!target?.startsWith(".")) return void 0;
142252
+ const base = normalizeCodePath(pathPosix.normalize(pathPosix.join(pathPosix.dirname(sourcePath), target)));
142253
+ if (!base || base === ".." || base.startsWith("../")) return void 0;
142254
+ const candidates = [base];
142255
+ const extensionMatch = base.match(/(\.[A-Za-z0-9]+)$/);
142256
+ const hasExtension = Boolean(extensionMatch);
142257
+ if (!hasExtension) {
142258
+ const extensions = [...new Set([...fileByPath.keys()].map((path19) => path19.match(/(\.[A-Za-z0-9]+)$/)?.[1]).filter((extension3) => Boolean(extension3)))];
142259
+ for (const extension3 of extensions) {
142260
+ candidates.push(`${base}${extension3}`, `${base}/index${extension3}`);
142261
+ }
142262
+ } else {
142263
+ const sourceStem = base.slice(0, -extensionMatch[1].length);
142264
+ const sourceExtensions = extensionMatch[1] === ".js" ? [".ts", ".tsx", ".js", ".jsx"] : extensionMatch[1] === ".mjs" ? [".mts", ".mjs"] : extensionMatch[1] === ".cjs" ? [".cts", ".cjs"] : [];
142265
+ for (const extension3 of sourceExtensions) candidates.push(`${sourceStem}${extension3}`);
142266
+ }
142267
+ return candidates.map((candidate) => fileByPath.get(candidate)).find((file) => Boolean(file));
142268
+ }
142181
142269
  function rowToRef(row) {
142182
142270
  return {
142183
142271
  id: row.id,
@@ -142238,6 +142326,18 @@ function rowToSnapshot(row) {
142238
142326
  ...row.previousSnapshotId ? { previousSnapshotId: row.previousSnapshotId } : {}
142239
142327
  };
142240
142328
  }
142329
+ function rowToSnapshotFile(row) {
142330
+ return {
142331
+ snapshotId: row.snapshotId,
142332
+ projectId: row.projectId,
142333
+ fileId: row.fileId,
142334
+ path: row.path,
142335
+ contentHash: row.contentHash
142336
+ };
142337
+ }
142338
+ function snapshotIsIncomplete(snapshot) {
142339
+ return snapshot.completeness.skippedOversizedFiles > 0 || (snapshot.completeness.unreadableFiles ?? 0) > 0 || snapshot.completeness.removalScanDeferred;
142340
+ }
142241
142341
  var CodeGraphStore;
142242
142342
  var init_store = __esm({
142243
142343
  "src/codegraph/store.ts"() {
@@ -142409,6 +142509,7 @@ var init_store = __esm({
142409
142509
  }
142410
142510
  });
142411
142511
  tx();
142512
+ this.reconcileLocalImportTargets(projectId);
142412
142513
  }
142413
142514
  /**
142414
142515
  * Reconcile only files whose source changed plus files that disappeared.
@@ -142418,7 +142519,10 @@ var init_store = __esm({
142418
142519
  const changed = input.changed.filter((delta) => delta.file.projectId === projectId);
142419
142520
  const metadataOnly = (input.metadataOnly ?? []).filter((file) => file.projectId === projectId);
142420
142521
  const removedFileIds = input.removedFileIds ?? [];
142421
- if (changed.length === 0 && metadataOnly.length === 0 && removedFileIds.length === 0) return;
142522
+ if (changed.length === 0 && metadataOnly.length === 0 && removedFileIds.length === 0) {
142523
+ this.reconcileLocalImportTargets(projectId);
142524
+ return;
142525
+ }
142422
142526
  const staleRefsForFile = this.db.prepare(`
142423
142527
  UPDATE observation_code_refs
142424
142528
  SET status = 'stale', updatedAt = ?
@@ -142428,9 +142532,18 @@ var init_store = __esm({
142428
142532
  )
142429
142533
  )
142430
142534
  `);
142431
- const deleteEdgesForFile = this.db.prepare(`
142535
+ const deleteEdgesFromFile = this.db.prepare(`
142432
142536
  DELETE FROM code_edges
142433
- WHERE projectId = ? AND (fromFileId = ? OR toFileId = ?)
142537
+ WHERE projectId = ? AND fromFileId = ?
142538
+ `);
142539
+ const clearImportTargetsForFile = this.db.prepare(`
142540
+ UPDATE code_edges
142541
+ SET toFileId = NULL
142542
+ WHERE projectId = ? AND toFileId = ? AND type = 'imports'
142543
+ `);
142544
+ const deleteOtherIncomingEdgesForFile = this.db.prepare(`
142545
+ DELETE FROM code_edges
142546
+ WHERE projectId = ? AND toFileId = ? AND type != 'imports'
142434
142547
  `);
142435
142548
  const deleteSymbolsForFile = this.db.prepare(`DELETE FROM code_symbols WHERE projectId = ? AND fileId = ?`);
142436
142549
  const deleteFile = this.db.prepare(`DELETE FROM code_files WHERE projectId = ? AND id = ?`);
@@ -142467,14 +142580,18 @@ var init_store = __esm({
142467
142580
  const staleAt = (/* @__PURE__ */ new Date()).toISOString();
142468
142581
  for (const fileId of removedFileIds) {
142469
142582
  staleRefsForFile.run(staleAt, projectId, fileId, projectId, fileId);
142470
- deleteEdgesForFile.run(projectId, fileId, fileId);
142583
+ deleteEdgesFromFile.run(projectId, fileId);
142584
+ clearImportTargetsForFile.run(projectId, fileId);
142585
+ deleteOtherIncomingEdgesForFile.run(projectId, fileId);
142471
142586
  deleteSymbolsForFile.run(projectId, fileId);
142472
142587
  deleteFile.run(projectId, fileId);
142473
142588
  }
142474
142589
  for (const delta of changed) {
142475
142590
  const fileId = delta.file.id;
142476
142591
  staleRefsForFile.run(staleAt, projectId, fileId, projectId, fileId);
142477
- deleteEdgesForFile.run(projectId, fileId, fileId);
142592
+ deleteEdgesFromFile.run(projectId, fileId);
142593
+ clearImportTargetsForFile.run(projectId, fileId);
142594
+ deleteOtherIncomingEdgesForFile.run(projectId, fileId);
142478
142595
  deleteSymbolsForFile.run(projectId, fileId);
142479
142596
  writeFile5(delta.file);
142480
142597
  for (const symbol of delta.symbols) {
@@ -142512,6 +142629,7 @@ var init_store = __esm({
142512
142629
  for (const file of metadataOnly) writeFile5(file);
142513
142630
  });
142514
142631
  tx();
142632
+ this.reconcileLocalImportTargets(projectId);
142515
142633
  }
142516
142634
  upsertObservationRefs(refs) {
142517
142635
  if (refs.length === 0) return;
@@ -142573,6 +142691,25 @@ var init_store = __esm({
142573
142691
  });
142574
142692
  tx();
142575
142693
  }
142694
+ /**
142695
+ * Lite import extraction records the original import text first. Resolve only
142696
+ * local relative imports against the current index after each refresh, so an
142697
+ * added or removed target cannot leave a fabricated file-to-file relation.
142698
+ */
142699
+ reconcileLocalImportTargets(projectId) {
142700
+ const fileById = new Map(this.listFiles(projectId).map((file) => [file.id, file]));
142701
+ const fileByPath = new Map([...fileById.values()].map((file) => [file.path, file]));
142702
+ const update2 = this.db.prepare("UPDATE code_edges SET toFileId = ? WHERE id = ?");
142703
+ const tx = this.db.transaction(() => {
142704
+ for (const edge of this.listEdges(projectId)) {
142705
+ if (edge.type !== "imports" || !edge.fromFileId) continue;
142706
+ const source = fileById.get(edge.fromFileId);
142707
+ const target = source ? resolveRelativeImportTarget(source.path, edge.evidence, fileByPath) : void 0;
142708
+ update2.run(target?.id ?? null, edge.id);
142709
+ }
142710
+ });
142711
+ tx();
142712
+ }
142576
142713
  getFile(projectId, path19) {
142577
142714
  const row = this.db.prepare(`SELECT * FROM code_files WHERE projectId = ? AND path = ?`).get(projectId, normalizeCodePath(path19));
142578
142715
  return row ? rowToFile(row) : null;
@@ -142647,6 +142784,18 @@ var init_store = __esm({
142647
142784
  ORDER BY observationId, status, id
142648
142785
  `).all(projectId).map(rowToRef);
142649
142786
  }
142787
+ /** Count distinct observations whose binding to these exact files is stale. */
142788
+ countStaleObservationRefsForFiles(projectId, fileIds) {
142789
+ const ids = [...new Set(fileIds.filter(Boolean))];
142790
+ if (ids.length === 0) return 0;
142791
+ const placeholders = ids.map(() => "?").join(", ");
142792
+ const row = this.db.prepare(`
142793
+ SELECT COUNT(DISTINCT observationId) AS count
142794
+ FROM observation_code_refs
142795
+ WHERE projectId = ? AND status = 'stale' AND fileId IN (${placeholders})
142796
+ `).get(projectId, ...ids);
142797
+ return Number(row?.count ?? 0);
142798
+ }
142650
142799
  listReferencedSymbols(projectId) {
142651
142800
  return this.db.prepare(`
142652
142801
  SELECT DISTINCT symbols.*
@@ -142668,6 +142817,94 @@ var init_store = __esm({
142668
142817
  "SELECT * FROM code_state_snapshots WHERE projectId = ? ORDER BY sourceEpoch DESC LIMIT ?"
142669
142818
  ).all(projectId, safeLimit).map(rowToSnapshot);
142670
142819
  }
142820
+ listSnapshotFiles(snapshotId) {
142821
+ return this.db.prepare(`
142822
+ SELECT snapshotId, projectId, fileId, path, contentHash
142823
+ FROM code_state_snapshot_files
142824
+ WHERE snapshotId = ?
142825
+ ORDER BY path
142826
+ `).all(snapshotId).map(rowToSnapshotFile);
142827
+ }
142828
+ diffSnapshots(projectId, fromSnapshotId, toSnapshotId) {
142829
+ const snapshots = this.db.prepare(`
142830
+ SELECT * FROM code_state_snapshots WHERE id IN (?, ?)
142831
+ `).all(fromSnapshotId, toSnapshotId).map(rowToSnapshot);
142832
+ if (snapshots.length !== 2) {
142833
+ return { projectId, fromSnapshotId, toSnapshotId, available: false, reason: "missing-snapshot", changes: [] };
142834
+ }
142835
+ if (snapshots.some((snapshot) => snapshot.projectId !== projectId)) {
142836
+ return { projectId, fromSnapshotId, toSnapshotId, available: false, reason: "project-mismatch", changes: [] };
142837
+ }
142838
+ if (snapshots.some(snapshotIsIncomplete)) {
142839
+ return { projectId, fromSnapshotId, toSnapshotId, available: false, reason: "incomplete-snapshot", changes: [] };
142840
+ }
142841
+ const before = this.listSnapshotFiles(fromSnapshotId);
142842
+ const after = this.listSnapshotFiles(toSnapshotId);
142843
+ if (before.length === 0 || after.length === 0) {
142844
+ return {
142845
+ projectId,
142846
+ fromSnapshotId,
142847
+ toSnapshotId,
142848
+ available: false,
142849
+ reason: "legacy-snapshot-without-manifest",
142850
+ changes: []
142851
+ };
142852
+ }
142853
+ const beforeByPath = new Map(before.map((file) => [file.path, file]));
142854
+ const afterByPath = new Map(after.map((file) => [file.path, file]));
142855
+ const paths = [.../* @__PURE__ */ new Set([...beforeByPath.keys(), ...afterByPath.keys()])].sort();
142856
+ const changes = [];
142857
+ for (const path19 of paths) {
142858
+ const oldFile = beforeByPath.get(path19);
142859
+ const newFile = afterByPath.get(path19);
142860
+ if (!oldFile && newFile) {
142861
+ changes.push({ path: path19, kind: "added", afterHash: newFile.contentHash });
142862
+ } else if (oldFile && !newFile) {
142863
+ changes.push({ path: path19, kind: "removed", beforeHash: oldFile.contentHash });
142864
+ } else if (oldFile && newFile && oldFile.contentHash !== newFile.contentHash) {
142865
+ changes.push({ path: path19, kind: "modified", beforeHash: oldFile.contentHash, afterHash: newFile.contentHash });
142866
+ }
142867
+ }
142868
+ return { projectId, fromSnapshotId, toSnapshotId, available: true, changes };
142869
+ }
142870
+ impactSlice(projectId, changedPaths, maxRelations = 24) {
142871
+ const safeLimit = Number.isFinite(maxRelations) ? Math.max(1, Math.min(100, Math.floor(maxRelations))) : 24;
142872
+ const normalizedPaths = [...new Set(changedPaths.map(normalizeCodePath).filter(Boolean))].sort();
142873
+ const files = this.listFiles(projectId);
142874
+ const fileById = new Map(files.map((file) => [file.id, file.path]));
142875
+ const fileIdBySymbolId = new Map(this.listSymbols(projectId).map((symbol) => [symbol.id, symbol.fileId]));
142876
+ const changedIds = new Set(files.filter((file) => normalizedPaths.includes(file.path)).map((file) => file.id));
142877
+ const direct = /* @__PURE__ */ new Set();
142878
+ let relationCount = 0;
142879
+ let truncated = false;
142880
+ for (const edge of this.listEdges(projectId)) {
142881
+ const endpoints = [
142882
+ edge.fromFileId,
142883
+ edge.toFileId,
142884
+ edge.fromSymbolId ? fileIdBySymbolId.get(edge.fromSymbolId) : void 0,
142885
+ edge.toSymbolId ? fileIdBySymbolId.get(edge.toSymbolId) : void 0
142886
+ ].filter((id) => Boolean(id));
142887
+ if (!endpoints.some((id) => changedIds.has(id))) continue;
142888
+ const relatedPaths = endpoints.filter((id) => !changedIds.has(id)).map((id) => fileById.get(id)).filter((path19) => Boolean(path19));
142889
+ if (relatedPaths.length === 0) continue;
142890
+ if (relationCount >= safeLimit) {
142891
+ truncated = true;
142892
+ break;
142893
+ }
142894
+ relationCount += 1;
142895
+ for (const path19 of relatedPaths) direct.add(path19);
142896
+ }
142897
+ const snapshot = this.latestSnapshot(projectId);
142898
+ return {
142899
+ projectId,
142900
+ ...snapshot ? { snapshotId: snapshot.id } : {},
142901
+ changedPaths: normalizedPaths,
142902
+ directlyConnectedPaths: [...direct].sort(),
142903
+ relationCount,
142904
+ truncated,
142905
+ provider: snapshot?.provider ?? "lite"
142906
+ };
142907
+ }
142671
142908
  /**
142672
142909
  * Record a completed scan and mark all current structural facts with its
142673
142910
  * epoch. The snapshot is written only after refresh reconciliation succeeds,
@@ -142709,6 +142946,16 @@ var init_store = __esm({
142709
142946
  this.db.prepare(
142710
142947
  "UPDATE observation_code_refs SET snapshotId = ? WHERE projectId = ? AND status = 'current'"
142711
142948
  ).run(id, input.projectId);
142949
+ const snapshotFileRows = this.db.prepare(`
142950
+ SELECT id, projectId, path, contentHash FROM code_files WHERE projectId = ? ORDER BY path
142951
+ `).all(input.projectId);
142952
+ const insertSnapshotFile = this.db.prepare(`
142953
+ INSERT INTO code_state_snapshot_files (snapshotId, projectId, fileId, path, contentHash)
142954
+ VALUES (?, ?, ?, ?, ?)
142955
+ `);
142956
+ for (const file of snapshotFileRows) {
142957
+ insertSnapshotFile.run(id, file.projectId, file.id, file.path, file.contentHash);
142958
+ }
142712
142959
  snapshot = {
142713
142960
  ...input,
142714
142961
  id,
@@ -142874,8 +143121,8 @@ __export(lite_provider_exports, {
142874
143121
  import { createHash as createHash7 } from "crypto";
142875
143122
  import { readdirSync as readdirSync2, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
142876
143123
  import { join as join4, relative } from "path";
142877
- function hashText(text) {
142878
- return createHash7("sha256").update(text).digest("hex");
143124
+ function hashText(text2) {
143125
+ return createHash7("sha256").update(text2).digest("hex");
142879
143126
  }
142880
143127
  function extension2(path19) {
142881
143128
  const index = path19.lastIndexOf(".");
@@ -142919,19 +143166,19 @@ function walk(root, exclude, maxFiles) {
142919
143166
  visit(root);
142920
143167
  return out;
142921
143168
  }
142922
- function lineOf(text, index) {
142923
- return text.slice(0, index).split(/\r?\n/).length;
143169
+ function lineOf(text2, index) {
143170
+ return text2.slice(0, index).split(/\r?\n/).length;
142924
143171
  }
142925
- function extractSymbols(projectId, file, text, indexedAt) {
143172
+ function extractSymbols(projectId, file, text2, indexedAt) {
142926
143173
  const symbols = [];
142927
143174
  const profile = file.language ? languageProfiles[file.language] : void 0;
142928
143175
  const patterns = profile?.symbols ?? [];
142929
143176
  for (const { kind, re } of patterns) {
142930
143177
  re.lastIndex = 0;
142931
- for (const match2 of text.matchAll(re)) {
143178
+ for (const match2 of text2.matchAll(re)) {
142932
143179
  const name = match2[1];
142933
143180
  if (!name || name.includes("\n")) continue;
142934
- const startLine = lineOf(text, match2.index ?? 0);
143181
+ const startLine = lineOf(text2, match2.index ?? 0);
142935
143182
  const id = makeCodeSymbolId({ projectId, path: file.path, qualifiedName: name, kind });
142936
143183
  symbols.push({
142937
143184
  id,
@@ -142951,13 +143198,13 @@ function extractSymbols(projectId, file, text, indexedAt) {
142951
143198
  }
142952
143199
  return symbols;
142953
143200
  }
142954
- function extractImportEdges(projectId, file, text, indexedAt) {
143201
+ function extractImportEdges(projectId, file, text2, indexedAt) {
142955
143202
  const edges = [];
142956
143203
  const profile = file.language ? languageProfiles[file.language] : void 0;
142957
143204
  const imports = profile?.imports ?? [];
142958
143205
  for (const re of imports) {
142959
143206
  re.lastIndex = 0;
142960
- for (const match2 of text.matchAll(re)) {
143207
+ for (const match2 of text2.matchAll(re)) {
142961
143208
  const target = (match2[1] ?? "").trim();
142962
143209
  if (!target) continue;
142963
143210
  if (target.includes("\n")) {
@@ -142992,14 +143239,14 @@ function extractImportEdges(projectId, file, text, indexedAt) {
142992
143239
  function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
142993
143240
  const rel = normalizeCodePath(relative(projectRoot, abs));
142994
143241
  try {
142995
- const text = readFileSync6(abs, "utf-8");
143242
+ const text2 = readFileSync6(abs, "utf-8");
142996
143243
  const stat4 = fileStat ?? statSync3(abs);
142997
143244
  const file = {
142998
143245
  id: makeCodeFileId(projectId, rel),
142999
143246
  projectId,
143000
143247
  path: rel,
143001
143248
  language: languageForPath(rel),
143002
- contentHash: hashText(text),
143249
+ contentHash: hashText(text2),
143003
143250
  // Preserve filesystem precision. Rounding here made same-size edits in
143004
143251
  // a short Windows timestamp window indistinguishable to refresh.
143005
143252
  mtimeMs: Number(stat4.mtimeMs),
@@ -143009,8 +143256,8 @@ function indexFileLite(projectId, projectRoot, abs, indexedAt, fileStat) {
143009
143256
  return {
143010
143257
  delta: {
143011
143258
  file,
143012
- symbols: extractSymbols(projectId, file, text, indexedAt),
143013
- edges: extractImportEdges(projectId, file, text, indexedAt)
143259
+ symbols: extractSymbols(projectId, file, text2, indexedAt),
143260
+ edges: extractImportEdges(projectId, file, text2, indexedAt)
143014
143261
  },
143015
143262
  unreadable: false
143016
143263
  };
@@ -143289,18 +143536,18 @@ __export(binder_exports, {
143289
143536
  function observationText(obs) {
143290
143537
  return [obs.title, obs.narrative, ...obs.facts ?? []].join("\n");
143291
143538
  }
143292
- function mentionsSymbol(text, symbol) {
143539
+ function mentionsSymbol(text2, symbol) {
143293
143540
  const name = symbol.name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
143294
- return new RegExp(`(^|[^\\w$])${name}([^\\w$]|$)`).test(text);
143541
+ return new RegExp(`(^|[^\\w$])${name}([^\\w$]|$)`).test(text2);
143295
143542
  }
143296
- function identifierCandidates(text) {
143297
- return [...new Set(text.match(/[A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?/g) ?? [])];
143543
+ function identifierCandidates(text2) {
143544
+ return [...new Set(text2.match(/[A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?/g) ?? [])];
143298
143545
  }
143299
- function codeIdentifierCandidates(text) {
143546
+ function codeIdentifierCandidates(text2) {
143300
143547
  const explicit = /* @__PURE__ */ new Set();
143301
- for (const match2 of text.matchAll(/`([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)`/g)) explicit.add(match2[1]);
143302
- for (const match2 of text.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match2[1]);
143303
- return identifierCandidates(text).filter((candidate) => explicit.has(candidate) || candidate.includes("::") || /[!?=]$/.test(candidate) || /^[A-Z]/.test(candidate) || /[_$]/.test(candidate) || /[a-z0-9][A-Z]/.test(candidate) || /[A-Z]{2}/.test(candidate));
143548
+ for (const match2 of text2.matchAll(/`([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)`/g)) explicit.add(match2[1]);
143549
+ for (const match2 of text2.matchAll(/\b([A-Za-z_$][\w$]*(?:::[A-Za-z_$][\w$]*)*[!?=]?)\s*\(/g)) explicit.add(match2[1]);
143550
+ return identifierCandidates(text2).filter((candidate) => explicit.has(candidate) || candidate.includes("::") || /[!?=]$/.test(candidate) || /^[A-Z]/.test(candidate) || /[_$]/.test(candidate) || /[a-z0-9][A-Z]/.test(candidate) || /[A-Z]{2}/.test(candidate));
143304
143551
  }
143305
143552
  function fileRef(projectId, obs, file) {
143306
143553
  return {
@@ -143330,7 +143577,7 @@ function symbolRef(projectId, obs, file, symbol) {
143330
143577
  }
143331
143578
  function resolveObservationCodeRefs(obs, lookup2) {
143332
143579
  const refs = /* @__PURE__ */ new Map();
143333
- const text = observationText(obs);
143580
+ const text2 = observationText(obs);
143334
143581
  const candidateFiles = /* @__PURE__ */ new Map();
143335
143582
  for (const rawPath of obs.filesModified ?? []) {
143336
143583
  const file = lookup2.findFile(normalizeCodePath(rawPath));
@@ -143340,7 +143587,7 @@ function resolveObservationCodeRefs(obs, lookup2) {
143340
143587
  refs.set(ref.id, ref);
143341
143588
  }
143342
143589
  const hintedFileIds = new Set(candidateFiles.keys());
143343
- const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text) : codeIdentifierCandidates(text);
143590
+ const symbolNames = hintedFileIds.size > 0 ? identifierCandidates(text2) : codeIdentifierCandidates(text2);
143344
143591
  const symbols = lookup2.findSymbols(symbolNames, [...hintedFileIds]);
143345
143592
  const symbolsByName = /* @__PURE__ */ new Map();
143346
143593
  for (const symbol of symbols) {
@@ -143351,7 +143598,7 @@ function resolveObservationCodeRefs(obs, lookup2) {
143351
143598
  for (const group of symbolsByName.values()) {
143352
143599
  const candidates = group.length === 1 ? group : [];
143353
143600
  for (const symbol of candidates) {
143354
- if (!mentionsSymbol(text, symbol)) continue;
143601
+ if (!mentionsSymbol(text2, symbol)) continue;
143355
143602
  const file = candidateFiles.get(symbol.fileId) ?? lookup2.findFile(symbol.path);
143356
143603
  if (!file) continue;
143357
143604
  candidateFiles.set(file.id, file);
@@ -144079,8 +144326,8 @@ function taskTokens(task) {
144079
144326
  return normalizedTokens(task);
144080
144327
  }
144081
144328
  function scoreClaim(claim, tokens) {
144082
- const text = [claim.subject, claim.predicate, claim.objectValue].join(" ").toLocaleLowerCase("en-US");
144083
- const words = normalizedTokens(text);
144329
+ const text2 = [claim.subject, claim.predicate, claim.objectValue].join(" ").toLocaleLowerCase("en-US");
144330
+ const words = normalizedTokens(text2);
144084
144331
  let matches = 0;
144085
144332
  for (const token of tokens) {
144086
144333
  if (words.has(token)) matches += 1;
@@ -144976,29 +145223,141 @@ var init_workflow_store = __esm({
144976
145223
  }
144977
145224
  });
144978
145225
 
145226
+ // src/knowledge/outcome-types.ts
145227
+ var MEMORY_OUTCOME_CANDIDATE_KINDS, MEMORY_OUTCOME_SIGNAL_KINDS;
145228
+ var init_outcome_types = __esm({
145229
+ "src/knowledge/outcome-types.ts"() {
145230
+ "use strict";
145231
+ init_esm_shims();
145232
+ MEMORY_OUTCOME_CANDIDATE_KINDS = ["claim", "durable-memory", "workflow"];
145233
+ MEMORY_OUTCOME_SIGNAL_KINDS = [
145234
+ "verification-passed",
145235
+ "verification-failed",
145236
+ "verified-reuse",
145237
+ "user-pin",
145238
+ "user-correction",
145239
+ "source-changed",
145240
+ "conflict-confirmed",
145241
+ "manual-review"
145242
+ ];
145243
+ }
145244
+ });
145245
+
145246
+ // src/knowledge/outcome-store.ts
145247
+ import { randomUUID as randomUUID10 } from "crypto";
145248
+ function optionalText4(value) {
145249
+ return typeof value === "string" && value.trim() ? value : void 0;
145250
+ }
145251
+ function text(value, field, max = 1e3) {
145252
+ const safe = sanitizeCredentials(value).replace(/\s+/g, " ").trim();
145253
+ if (!safe) throw new Error("Outcome signal " + field + " is required.");
145254
+ if (safe.length > max) throw new Error("Outcome signal " + field + " is too long.");
145255
+ return safe;
145256
+ }
145257
+ function oneOf(value, values, field) {
145258
+ if (typeof value !== "string" || !values.includes(value)) {
145259
+ throw new Error("Outcome signal " + field + " is invalid.");
145260
+ }
145261
+ return value;
145262
+ }
145263
+ function rowToSignal(row) {
145264
+ return {
145265
+ id: row.id,
145266
+ projectId: row.projectId,
145267
+ candidateKind: row.candidateKind,
145268
+ candidateId: row.candidateId,
145269
+ kind: row.kind,
145270
+ sourceRef: row.sourceRef,
145271
+ ...optionalText4(row.snapshotId) ? { snapshotId: row.snapshotId } : {},
145272
+ ...optionalText4(row.detail) ? { detail: row.detail } : {},
145273
+ observedAt: row.observedAt
145274
+ };
145275
+ }
145276
+ var OutcomeStore;
145277
+ var init_outcome_store = __esm({
145278
+ "src/knowledge/outcome-store.ts"() {
145279
+ "use strict";
145280
+ init_esm_shims();
145281
+ init_secret_filter();
145282
+ init_sqlite_db();
145283
+ init_outcome_types();
145284
+ OutcomeStore = class {
145285
+ db = null;
145286
+ async init(dataDir) {
145287
+ this.db = getDatabase(dataDir);
145288
+ }
145289
+ record(input) {
145290
+ const signal = {
145291
+ id: randomUUID10(),
145292
+ projectId: text(input.projectId, "project id"),
145293
+ candidateKind: oneOf(input.candidateKind, MEMORY_OUTCOME_CANDIDATE_KINDS, "candidate kind"),
145294
+ candidateId: text(input.candidateId, "candidate id"),
145295
+ kind: oneOf(input.kind, MEMORY_OUTCOME_SIGNAL_KINDS, "kind"),
145296
+ sourceRef: text(input.sourceRef, "source reference"),
145297
+ ...input.snapshotId ? { snapshotId: text(input.snapshotId, "snapshot id") } : {},
145298
+ ...input.detail ? { detail: text(input.detail, "detail") } : {},
145299
+ observedAt: input.observedAt ?? (/* @__PURE__ */ new Date()).toISOString()
145300
+ };
145301
+ this.db.prepare(`
145302
+ INSERT INTO memory_outcome_signals (
145303
+ id, projectId, candidateKind, candidateId, kind, sourceRef, snapshotId, detail, observedAt
145304
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
145305
+ `).run(
145306
+ signal.id,
145307
+ signal.projectId,
145308
+ signal.candidateKind,
145309
+ signal.candidateId,
145310
+ signal.kind,
145311
+ signal.sourceRef,
145312
+ signal.snapshotId ?? null,
145313
+ signal.detail ?? null,
145314
+ signal.observedAt
145315
+ );
145316
+ return signal;
145317
+ }
145318
+ latestForCandidates(projectId, candidateKind, candidateIds) {
145319
+ const ids = [...new Set(candidateIds.filter(Boolean))];
145320
+ if (ids.length === 0) return /* @__PURE__ */ new Map();
145321
+ const placeholders = ids.map(() => "?").join(", ");
145322
+ const rows = this.db.prepare(`
145323
+ SELECT * FROM memory_outcome_signals
145324
+ WHERE projectId = ? AND candidateKind = ? AND candidateId IN (${placeholders})
145325
+ ORDER BY observedAt DESC, id DESC
145326
+ `).all(projectId, candidateKind, ...ids);
145327
+ const latest = /* @__PURE__ */ new Map();
145328
+ for (const row of rows) {
145329
+ const signal = rowToSignal(row);
145330
+ if (!latest.has(signal.candidateId)) latest.set(signal.candidateId, signal);
145331
+ }
145332
+ return latest;
145333
+ }
145334
+ };
145335
+ }
145336
+ });
145337
+
144979
145338
  // src/codegraph/task-lens.ts
144980
- function containsTaskKeyword(text, keyword) {
145339
+ function containsTaskKeyword(text2, keyword) {
144981
145340
  const matcher = /^[a-z0-9_-]+$/i.test(keyword) ? new RegExp(`(^|[^a-z0-9_-])${keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(?=[^a-z0-9_-]|$)`, "gi") : new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi");
144982
145341
  let match2;
144983
- while ((match2 = matcher.exec(text)) !== null) {
145342
+ while ((match2 = matcher.exec(text2)) !== null) {
144984
145343
  const keywordIndex = match2.index + (match2[1]?.length ?? 0);
144985
- if (!isNegatedTaskKeyword(text, keywordIndex)) return true;
145344
+ if (!isNegatedTaskKeyword(text2, keywordIndex)) return true;
144986
145345
  }
144987
145346
  return false;
144988
145347
  }
144989
- function isNegatedTaskKeyword(text, keywordIndex) {
145348
+ function isNegatedTaskKeyword(text2, keywordIndex) {
144990
145349
  const boundary = Math.max(
144991
- text.lastIndexOf(".", keywordIndex - 1),
144992
- text.lastIndexOf("!", keywordIndex - 1),
144993
- text.lastIndexOf("?", keywordIndex - 1),
144994
- text.lastIndexOf(";", keywordIndex - 1),
144995
- text.lastIndexOf("\n", keywordIndex - 1),
144996
- text.lastIndexOf("\u3002", keywordIndex - 1),
144997
- text.lastIndexOf("\uFF01", keywordIndex - 1),
144998
- text.lastIndexOf("\uFF1F", keywordIndex - 1),
144999
- text.lastIndexOf("\uFF1B", keywordIndex - 1)
145350
+ text2.lastIndexOf(".", keywordIndex - 1),
145351
+ text2.lastIndexOf("!", keywordIndex - 1),
145352
+ text2.lastIndexOf("?", keywordIndex - 1),
145353
+ text2.lastIndexOf(";", keywordIndex - 1),
145354
+ text2.lastIndexOf("\n", keywordIndex - 1),
145355
+ text2.lastIndexOf("\u3002", keywordIndex - 1),
145356
+ text2.lastIndexOf("\uFF01", keywordIndex - 1),
145357
+ text2.lastIndexOf("\uFF1F", keywordIndex - 1),
145358
+ text2.lastIndexOf("\uFF1B", keywordIndex - 1)
145000
145359
  );
145001
- let prefix = text.slice(boundary + 1, keywordIndex).toLowerCase();
145360
+ let prefix = text2.slice(boundary + 1, keywordIndex).toLowerCase();
145002
145361
  const contrast = /(?:,|\uff0c)\s*(?:but|however|instead|yet|\u4f46\u662f|\u4f46|\u800c\u662f)\s*/gi;
145003
145362
  let contrastMatch;
145004
145363
  let contrastEnd = -1;
@@ -145057,7 +145416,7 @@ function requiredText(data, key) {
145057
145416
  }
145058
145417
  return value.trim();
145059
145418
  }
145060
- function optionalText4(data, key) {
145419
+ function optionalText5(data, key) {
145061
145420
  const value = data[key];
145062
145421
  if (value === void 0 || value === null || value === "") return void 0;
145063
145422
  if (typeof value !== "string") throw new Error("workflow frontmatter field " + key + " must be text");
@@ -145100,11 +145459,11 @@ function phaseFromValue(value, index) {
145100
145459
  }
145101
145460
  const data = value;
145102
145461
  const title = requiredText(data, "title");
145103
- const phaseId = optionalText4(data, "id") ?? slug(title) + "-" + (index + 1);
145462
+ const phaseId = optionalText5(data, "id") ?? slug(title) + "-" + (index + 1);
145104
145463
  return {
145105
145464
  id: phaseId,
145106
145465
  title,
145107
- instructions: optionalText4(data, "instructions") ?? "",
145466
+ instructions: optionalText5(data, "instructions") ?? "",
145108
145467
  branches: optionalStringArray(data, "branches"),
145109
145468
  expectedOutputs: optionalStringArray(data, "expectedOutputs"),
145110
145469
  verificationGates: optionalStringArray(data, "verificationGates")
@@ -145213,7 +145572,7 @@ function parsedWorkflow(data, body, input) {
145213
145572
  id: requiredText(data, "id"),
145214
145573
  workspaceId: input.workspaceId,
145215
145574
  title: requiredText(data, "title"),
145216
- description: optionalText4(data, "description") ?? "",
145575
+ description: optionalText5(data, "description") ?? "",
145217
145576
  status: optionalStatus(data),
145218
145577
  version: optionalNumber(data, "version", 1),
145219
145578
  taskLenses: optionalStringArray(data, "taskLenses"),
@@ -145232,9 +145591,9 @@ function parsedWorkflow(data, body, input) {
145232
145591
  sourcePath: normalizeSourcePath(input.sourcePath),
145233
145592
  sourceHash: "",
145234
145593
  contentHash: input.contentHash,
145235
- createdAt: optionalText4(data, "createdAt") ?? now2(),
145236
- updatedAt: optionalText4(data, "updatedAt") ?? now2(),
145237
- ...optionalText4(data, "importedFrom") ? { importedFrom: optionalText4(data, "importedFrom") } : {}
145594
+ createdAt: optionalText5(data, "createdAt") ?? now2(),
145595
+ updatedAt: optionalText5(data, "updatedAt") ?? now2(),
145596
+ ...optionalText5(data, "importedFrom") ? { importedFrom: optionalText5(data, "importedFrom") } : {}
145238
145597
  };
145239
145598
  if (candidate.phases.length === 0) {
145240
145599
  throw new Error("workflow must contain at least one phase or a non-empty body");
@@ -145333,11 +145692,11 @@ async function syncCanonicalWorkflows(workspace) {
145333
145692
  return { workflows, errors };
145334
145693
  }
145335
145694
  function inferTaskLenses(value) {
145336
- const text = value.toLowerCase();
145337
- return Object.entries(LENS_TERMS).filter(([, terms]) => terms.some((term) => text.includes(term))).map(([lens]) => lens);
145695
+ const text2 = value.toLowerCase();
145696
+ return Object.entries(LENS_TERMS).filter(([, terms]) => terms.some((term) => text2.includes(term))).map(([lens]) => lens);
145338
145697
  }
145339
145698
  function taskScore(workflow, task) {
145340
- const text = task.toLowerCase();
145699
+ const text2 = task.toLowerCase();
145341
145700
  let score = 0;
145342
145701
  const reasons = [];
145343
145702
  const matchedLenses = /* @__PURE__ */ new Set();
@@ -145604,7 +145963,31 @@ async function recordWorkflowRun(input) {
145604
145963
  throw new Error("Workflow was not found for this workspace");
145605
145964
  }
145606
145965
  if (!input.run.task.trim()) throw new Error("Workflow run task is required");
145607
- return store.recordRun(input.run);
145966
+ const run = store.recordRun(input.run);
145967
+ const kind = run.outcome === "passed" && run.verificationVerdict === "passed" ? "verification-passed" : run.outcome === "failed" || run.verificationVerdict === "failed" ? "verification-failed" : void 0;
145968
+ if (!kind) return run;
145969
+ const evidence = /* @__PURE__ */ new Map();
145970
+ evidence.set(workflow.id, "workflow");
145971
+ for (const reference of run.selectedEvidence) {
145972
+ const match2 = /^(claim|durable):(.+)$/.exec(reference);
145973
+ if (!match2) continue;
145974
+ evidence.set(match2[2], match2[1] === "claim" ? "claim" : "durable-memory");
145975
+ }
145976
+ const outcomes = new OutcomeStore();
145977
+ await outcomes.init(workflowStoreDataDir(input.workspace));
145978
+ for (const [candidateId, candidateKind] of evidence) {
145979
+ outcomes.record({
145980
+ projectId: run.projectId,
145981
+ candidateKind,
145982
+ candidateId,
145983
+ kind,
145984
+ sourceRef: "workflow-run:" + run.id,
145985
+ ...run.startingSnapshotId ? { snapshotId: run.startingSnapshotId } : {},
145986
+ ...run.failureReason ? { detail: run.failureReason } : {},
145987
+ observedAt: run.completedAt ?? run.startedAt
145988
+ });
145989
+ }
145990
+ return run;
145608
145991
  }
145609
145992
  async function selectWorkspaceWorkflows(input) {
145610
145993
  const synced = await syncCanonicalWorkflows(input.workspace);
@@ -145629,6 +146012,7 @@ var init_workflows2 = __esm({
145629
146012
  init_workflow_sync();
145630
146013
  init_workspace();
145631
146014
  init_workflow_store();
146015
+ init_outcome_store();
145632
146016
  init_task_lens();
145633
146017
  KNOWN_AGENTS = [
145634
146018
  "windsurf",