@cortexkit/pi-magic-context 0.31.3 → 0.31.4

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.
@@ -8198,6 +8198,7 @@ function healNullTextColumns(db) {
8198
8198
  ["cache_ttl", ""],
8199
8199
  ["last_nudge_band", ""],
8200
8200
  ["last_nudge_level", ""],
8201
+ ["channel2_nudge_claim_token", ""],
8201
8202
  ["last_transform_error", ""],
8202
8203
  ["nudge_anchor_message_id", ""],
8203
8204
  ["nudge_anchor_text", ""],
@@ -8261,6 +8262,7 @@ function healNullIntegerColumns(db) {
8261
8262
  }
8262
8263
 
8263
8264
  // ../plugin/src/features/magic-context/workspaces.ts
8265
+ init_logger();
8264
8266
  import { createHash as createHash2 } from "node:crypto";
8265
8267
 
8266
8268
  // ../plugin/src/features/magic-context/memory/constants.ts
@@ -8615,6 +8617,7 @@ function storedPathBelongsToIdentity(storedProjectPath, projectIdentity) {
8615
8617
  }
8616
8618
  // ../plugin/src/features/magic-context/workspaces.ts
8617
8619
  var VALID_SHARE_CATEGORIES = new Set(V2_MEMORY_CATEGORIES);
8620
+ var DEFAULT_WORKSPACE_SHARE_CATEGORIES = ["CONSTRAINTS"];
8618
8621
  function tableExists(db, tableName) {
8619
8622
  const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name = ? LIMIT 1").get(tableName);
8620
8623
  return Boolean(row);
@@ -8629,23 +8632,39 @@ function uniqueSorted(values) {
8629
8632
  function placeholders(values) {
8630
8633
  return values.map(() => "?").join(", ");
8631
8634
  }
8635
+ function defaultWorkspaceShareCategories() {
8636
+ return [...DEFAULT_WORKSPACE_SHARE_CATEGORIES];
8637
+ }
8638
+ function warnInvalidShareCategories(reason, raw) {
8639
+ log("[magic-context] WARN: invalid workspace share_categories; sharing no foreign memory categories", {
8640
+ reason,
8641
+ raw
8642
+ });
8643
+ }
8632
8644
  function normalizeShareCategories(raw) {
8633
- if (raw === null || raw === undefined)
8634
- return null;
8635
- if (typeof raw !== "string")
8636
- return null;
8645
+ if (raw === null || raw === undefined) {
8646
+ return defaultWorkspaceShareCategories();
8647
+ }
8648
+ if (typeof raw !== "string") {
8649
+ warnInvalidShareCategories("not a string", raw);
8650
+ return [];
8651
+ }
8637
8652
  let parsed;
8638
8653
  try {
8639
8654
  parsed = JSON.parse(raw);
8640
8655
  } catch {
8641
- return null;
8656
+ warnInvalidShareCategories("malformed JSON", raw);
8657
+ return [];
8658
+ }
8659
+ if (!Array.isArray(parsed)) {
8660
+ warnInvalidShareCategories("not a JSON array", raw);
8661
+ return [];
8642
8662
  }
8643
- if (!Array.isArray(parsed))
8644
- return null;
8645
8663
  const categories = [];
8646
8664
  for (const value of parsed) {
8647
8665
  if (typeof value !== "string" || !VALID_SHARE_CATEGORIES.has(value)) {
8648
- return null;
8666
+ warnInvalidShareCategories("unknown category", raw);
8667
+ return [];
8649
8668
  }
8650
8669
  if (!categories.includes(value))
8651
8670
  categories.push(value);
@@ -8654,8 +8673,21 @@ function normalizeShareCategories(raw) {
8654
8673
  }
8655
8674
  function selectWorkspaceShareCategories(db, identities) {
8656
8675
  const candidates = uniqueSorted(identities.filter((identity) => identity.length > 0));
8657
- if (candidates.length === 0 || !tableExists(db, "workspace_members") || !tableExists(db, "workspaces") || !columnExists(db, "workspaces", "share_categories")) {
8676
+ if (candidates.length === 0 || !tableExists(db, "workspace_members")) {
8677
+ return null;
8678
+ }
8679
+ const hasMembership = Boolean(db.prepare(`SELECT 1
8680
+ FROM workspace_members
8681
+ WHERE project_path IN (${placeholders(candidates)})
8682
+ LIMIT 1`).get(...candidates));
8683
+ if (!hasMembership)
8658
8684
  return null;
8685
+ if (!tableExists(db, "workspaces")) {
8686
+ log("[magic-context] WARN: workspace member has no workspaces table; sharing no foreign memory categories");
8687
+ return [];
8688
+ }
8689
+ if (!columnExists(db, "workspaces", "share_categories")) {
8690
+ return defaultWorkspaceShareCategories();
8659
8691
  }
8660
8692
  const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
8661
8693
  FROM workspace_members AS member
@@ -8663,7 +8695,11 @@ function selectWorkspaceShareCategories(db, identities) {
8663
8695
  WHERE member.project_path IN (${placeholders(candidates)})
8664
8696
  ORDER BY workspace.id ASC
8665
8697
  LIMIT 1`).get(...candidates);
8666
- return normalizeShareCategories(row?.shareCategories ?? null);
8698
+ if (!row) {
8699
+ log("[magic-context] WARN: workspace member has no workspace share_categories row; sharing no foreign memory categories");
8700
+ return [];
8701
+ }
8702
+ return normalizeShareCategories(row.shareCategories);
8667
8703
  }
8668
8704
  function resolveWorkspaceShareCategories(db, projectIdentity) {
8669
8705
  return selectWorkspaceShareCategories(db, [projectIdentity]);
@@ -8768,7 +8804,7 @@ function computeWorkspaceEpochFingerprint(db, identities) {
8768
8804
  const hash = createHash2("sha256");
8769
8805
  hash.update("share_categories", "utf8");
8770
8806
  hash.update("\x00");
8771
- hash.update(shareCategories === null ? "ALL" : JSON.stringify(shareCategories), "utf8");
8807
+ hash.update(shareCategories === null ? "NO_WORKSPACE" : JSON.stringify(shareCategories), "utf8");
8772
8808
  hash.update(`
8773
8809
  `);
8774
8810
  for (const identity of canonical) {
@@ -143735,6 +143771,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143735
143771
  CREATE TABLE IF NOT EXISTS message_history_index (
143736
143772
  session_id TEXT PRIMARY KEY,
143737
143773
  last_indexed_ordinal INTEGER NOT NULL DEFAULT 0,
143774
+ dirty_floor_ordinal INTEGER NOT NULL DEFAULT 0,
143738
143775
  updated_at INTEGER NOT NULL,
143739
143776
  harness TEXT NOT NULL DEFAULT 'opencode'
143740
143777
  );
@@ -143764,6 +143801,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
143764
143801
  last_nudge_level TEXT DEFAULT '',
143765
143802
  channel2_nudge_state TEXT DEFAULT '',
143766
143803
  channel2_nudge_claimed_at INTEGER DEFAULT 0,
143804
+ channel2_nudge_claim_token TEXT DEFAULT '',
143767
143805
  last_emergency_input_sample INTEGER DEFAULT 0,
143768
143806
  last_transform_error TEXT DEFAULT '',
143769
143807
  nudge_anchor_message_id TEXT DEFAULT '',
@@ -144010,6 +144048,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
144010
144048
  ensureColumn(db, "session_meta", "last_nudge_level", "TEXT DEFAULT ''");
144011
144049
  ensureColumn(db, "session_meta", "channel2_nudge_state", "TEXT DEFAULT ''");
144012
144050
  ensureColumn(db, "session_meta", "channel2_nudge_claimed_at", "INTEGER DEFAULT 0");
144051
+ ensureColumn(db, "session_meta", "channel2_nudge_claim_token", "TEXT DEFAULT ''");
144013
144052
  ensureColumn(db, "session_meta", "last_emergency_input_sample", "INTEGER DEFAULT 0");
144014
144053
  ensureColumn(db, "session_meta", "last_transform_error", "TEXT DEFAULT ''");
144015
144054
  ensureColumn(db, "session_meta", "nudge_anchor_message_id", "TEXT DEFAULT ''");
@@ -144222,6 +144261,7 @@ CREATE INDEX IF NOT EXISTS idx_dream_queue_pending ON dream_queue(started_at, en
144222
144261
  ON transform_decisions(session_id, harness);
144223
144262
  `);
144224
144263
  ensureColumn(db, "tags", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
144264
+ ensureColumn(db, "message_history_index", "dirty_floor_ordinal", "INTEGER NOT NULL DEFAULT 0");
144225
144265
  ensureColumn(db, "pending_ops", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
144226
144266
  ensureColumn(db, "source_contents", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
144227
144267
  ensureColumn(db, "compartments", "harness", "TEXT NOT NULL DEFAULT 'opencode'");
@@ -144237,7 +144277,7 @@ var CHANNEL2_CLAIM_TTL_MS = 120000;
144237
144277
  function healWedgedChannel2Claims(db) {
144238
144278
  try {
144239
144279
  const staleBefore = Date.now() - CHANNEL2_CLAIM_TTL_MS;
144240
- db.prepare("UPDATE session_meta SET channel2_nudge_state = 'pending', channel2_nudge_claimed_at = 0 WHERE channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)").run(staleBefore);
144280
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = 'pending', channel2_nudge_claimed_at = 0, channel2_nudge_claim_token = '' WHERE channel2_nudge_state = 'claimed' AND (channel2_nudge_claimed_at IS NULL OR channel2_nudge_claimed_at = 0 OR channel2_nudge_claimed_at <= ?)").run(staleBefore);
144241
144281
  } catch {}
144242
144282
  }
144243
144283
  function openDatabase(dbPathOrOptions) {
@@ -145054,92 +145094,6 @@ function migrateLegacyExperimental(rawConfig, warnings) {
145054
145094
  return patched;
145055
145095
  }
145056
145096
 
145057
- // ../plugin/src/config/project-security.ts
145058
- var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
145059
- var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
145060
- var EMBEDDING_DESTINATION_FIELDS = ["endpoint", "provider"];
145061
- function isPlainObject2(value) {
145062
- return typeof value === "object" && value !== null && !Array.isArray(value);
145063
- }
145064
- function stripUnsafeProjectConfigFields(projectRaw) {
145065
- const warnings = [];
145066
- if ("auto_update" in projectRaw) {
145067
- delete projectRaw.auto_update;
145068
- warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
145069
- }
145070
- if ("language" in projectRaw) {
145071
- delete projectRaw.language;
145072
- warnings.push("Ignoring language from project config (security: output language is a user-level setting).");
145073
- }
145074
- if ("sqlite" in projectRaw) {
145075
- delete projectRaw.sqlite;
145076
- warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
145077
- }
145078
- const embedding = projectRaw.embedding;
145079
- if (isPlainObject2(embedding)) {
145080
- const removed = [];
145081
- for (const field of EMBEDDING_DESTINATION_FIELDS) {
145082
- if (field in embedding) {
145083
- delete embedding[field];
145084
- removed.push(field);
145085
- }
145086
- }
145087
- if (removed.length > 0) {
145088
- warnings.push(`Ignoring embedding.${removed.join("/")} from project config ` + "(security: a repository cannot choose where private text is embedded).");
145089
- }
145090
- }
145091
- for (const agentKey of HIDDEN_AGENT_KEYS) {
145092
- const block = projectRaw[agentKey];
145093
- if (!isPlainObject2(block))
145094
- continue;
145095
- const removed = [];
145096
- for (const field of AGENT_ESCALATION_FIELDS) {
145097
- if (field in block) {
145098
- delete block[field];
145099
- removed.push(field);
145100
- }
145101
- }
145102
- if (removed.length > 0) {
145103
- warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
145104
- }
145105
- }
145106
- return warnings;
145107
- }
145108
- function normalizeEndpoint(value) {
145109
- if (typeof value !== "string")
145110
- return;
145111
- const trimmed = value.trim().replace(/\/+$/, "");
145112
- return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
145113
- }
145114
- function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
145115
- const projectEmbedding = projectRaw.embedding;
145116
- if (!isPlainObject2(projectEmbedding))
145117
- return [];
145118
- const redirectsEndpoint = "endpoint" in projectEmbedding;
145119
- if (!redirectsEndpoint)
145120
- return [];
145121
- const userEmbedding = userRaw?.embedding;
145122
- if (isPlainObject2(userEmbedding)) {
145123
- const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
145124
- const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
145125
- if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
145126
- return [];
145127
- }
145128
- }
145129
- const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
145130
- if (providesOwnKey)
145131
- return [];
145132
- const mergedEmbedding = mergedRaw.embedding;
145133
- if (!isPlainObject2(mergedEmbedding))
145134
- return [];
145135
- if (!("api_key" in mergedEmbedding))
145136
- return [];
145137
- delete mergedEmbedding.api_key;
145138
- return [
145139
- "Dropped inherited user embedding api_key because project config redirected " + "embedding.endpoint without supplying its own key (security: prevents key " + "exfiltration to a repository-chosen endpoint)."
145140
- ];
145141
- }
145142
-
145143
145097
  // ../../node_modules/.bun/zod@4.4.3/node_modules/zod/v4/classic/external.js
145144
145098
  var exports_external = {};
145145
145099
  __export(exports_external, {
@@ -145771,7 +145725,7 @@ __export(exports_util, {
145771
145725
  jsonStringifyReplacer: () => jsonStringifyReplacer,
145772
145726
  joinValues: () => joinValues,
145773
145727
  issue: () => issue,
145774
- isPlainObject: () => isPlainObject3,
145728
+ isPlainObject: () => isPlainObject2,
145775
145729
  isObject: () => isObject,
145776
145730
  hexToUint8Array: () => hexToUint8Array,
145777
145731
  getSizableOrigin: () => getSizableOrigin,
@@ -145953,7 +145907,7 @@ var allowsEval = /* @__PURE__ */ cached(() => {
145953
145907
  return false;
145954
145908
  }
145955
145909
  });
145956
- function isPlainObject3(o) {
145910
+ function isPlainObject2(o) {
145957
145911
  if (isObject(o) === false)
145958
145912
  return false;
145959
145913
  const ctor = o.constructor;
@@ -145970,7 +145924,7 @@ function isPlainObject3(o) {
145970
145924
  return true;
145971
145925
  }
145972
145926
  function shallowClone(o) {
145973
- if (isPlainObject3(o))
145927
+ if (isPlainObject2(o))
145974
145928
  return { ...o };
145975
145929
  if (Array.isArray(o))
145976
145930
  return [...o];
@@ -146174,7 +146128,7 @@ function omit(schema, mask) {
146174
146128
  return clone(schema, def);
146175
146129
  }
146176
146130
  function extend(schema, shape) {
146177
- if (!isPlainObject3(shape)) {
146131
+ if (!isPlainObject2(shape)) {
146178
146132
  throw new Error("Invalid input to extend: expected a plain object");
146179
146133
  }
146180
146134
  const checks = schema._zod.def.checks;
@@ -146197,7 +146151,7 @@ function extend(schema, shape) {
146197
146151
  return clone(schema, def);
146198
146152
  }
146199
146153
  function safeExtend(schema, shape) {
146200
- if (!isPlainObject3(shape)) {
146154
+ if (!isPlainObject2(shape)) {
146201
146155
  throw new Error("Invalid input to safeExtend: expected a plain object");
146202
146156
  }
146203
146157
  const def = mergeDefs(schema._zod.def, {
@@ -148540,7 +148494,7 @@ function mergeValues(a, b) {
148540
148494
  if (a instanceof Date && b instanceof Date && +a === +b) {
148541
148495
  return { valid: true, data: a };
148542
148496
  }
148543
- if (isPlainObject3(a) && isPlainObject3(b)) {
148497
+ if (isPlainObject2(a) && isPlainObject2(b)) {
148544
148498
  const bKeys = Object.keys(b);
148545
148499
  const sharedKeys = Object.keys(a).filter((key) => bKeys.indexOf(key) !== -1);
148546
148500
  const newObj = { ...a, ...b };
@@ -148726,7 +148680,7 @@ var $ZodRecord = /* @__PURE__ */ $constructor("$ZodRecord", (inst, def) => {
148726
148680
  $ZodType.init(inst, def);
148727
148681
  inst._zod.parse = (payload, ctx) => {
148728
148682
  const input = payload.value;
148729
- if (!isPlainObject3(input)) {
148683
+ if (!isPlainObject2(input)) {
148730
148684
  payload.issues.push({
148731
148685
  expected: "record",
148732
148686
  code: "invalid_type",
@@ -159937,6 +159891,295 @@ var MagicContextConfigSchema = exports_external.object({
159937
159891
  };
159938
159892
  });
159939
159893
 
159894
+ // ../plugin/src/config/project-security.ts
159895
+ var HIDDEN_AGENT_KEYS = ["historian", "dreamer", "sidekick"];
159896
+ var HISTORIAN_USER_ONLY_FIELDS = ["model", "fallback_models"];
159897
+ var AGENT_ESCALATION_FIELDS = ["prompt", "permission", "tools", "system_prompt"];
159898
+ var EMBEDDING_DESTINATION_FIELDS = ["endpoint", "provider"];
159899
+ var PERCENTAGE_THRESHOLD_REASON = "security: a repository may only raise compaction thresholds above the user's effective value; it cannot force earlier historian work or cloned-repo cost escalation.";
159900
+ var TOKEN_THRESHOLD_REASON = "security: a repository may only raise execute_threshold_tokens above the user's trusted token threshold; it cannot force earlier historian work or cloned-repo cost escalation.";
159901
+ var TOKEN_THRESHOLD_INTRODUCTION_REASON = "security: a repository cannot introduce a new execute_threshold_tokens override when the user has no trusted token threshold for that key; that could force earlier historian work or cloned-repo cost escalation.";
159902
+ function isPlainObject3(value) {
159903
+ return typeof value === "object" && value !== null && !Array.isArray(value);
159904
+ }
159905
+ function isValidPercentageThreshold(value) {
159906
+ return typeof value === "number" && Number.isFinite(value) && value >= 20 && value <= 80;
159907
+ }
159908
+ function isValidTokenThreshold(value) {
159909
+ return typeof value === "number" && Number.isFinite(value) && value >= 5000 && value <= 2000000;
159910
+ }
159911
+ function normalizeTrustedPercentageThresholds(value) {
159912
+ if (typeof value === "number" && Number.isFinite(value)) {
159913
+ return { defaultValue: value, overrides: new Map };
159914
+ }
159915
+ if (isPlainObject3(value) && typeof value.default === "number" && Number.isFinite(value.default)) {
159916
+ const overrides = new Map;
159917
+ for (const [key, child] of Object.entries(value)) {
159918
+ if (key === "default")
159919
+ continue;
159920
+ if (typeof child === "number" && Number.isFinite(child)) {
159921
+ overrides.set(key, child);
159922
+ }
159923
+ }
159924
+ return { defaultValue: value.default, overrides };
159925
+ }
159926
+ return { defaultValue: DEFAULT_EXECUTE_THRESHOLD_PERCENTAGE, overrides: new Map };
159927
+ }
159928
+ function normalizeTrustedTokenThresholds(value) {
159929
+ if (!isPlainObject3(value)) {
159930
+ return { defaultValue: undefined, overrides: new Map };
159931
+ }
159932
+ const overrides = new Map;
159933
+ for (const [key, child] of Object.entries(value)) {
159934
+ if (key === "default")
159935
+ continue;
159936
+ if (typeof child === "number" && Number.isFinite(child)) {
159937
+ overrides.set(key, child);
159938
+ }
159939
+ }
159940
+ return {
159941
+ defaultValue: typeof value.default === "number" && Number.isFinite(value.default) ? value.default : undefined,
159942
+ overrides
159943
+ };
159944
+ }
159945
+ function clonePercentageThresholds(value) {
159946
+ return {
159947
+ defaultValue: value.defaultValue,
159948
+ overrides: new Map(value.overrides)
159949
+ };
159950
+ }
159951
+ function cloneTokenThresholds(value) {
159952
+ return {
159953
+ defaultValue: value.defaultValue,
159954
+ overrides: new Map(value.overrides)
159955
+ };
159956
+ }
159957
+ function percentageThresholdsEqual(left, right) {
159958
+ if (left.defaultValue !== right.defaultValue)
159959
+ return false;
159960
+ if (left.overrides.size !== right.overrides.size)
159961
+ return false;
159962
+ for (const [key, value] of left.overrides) {
159963
+ if (right.overrides.get(key) !== value)
159964
+ return false;
159965
+ }
159966
+ return true;
159967
+ }
159968
+ function setMergedPercentageThreshold(mergedRaw, value) {
159969
+ if (value.overrides.size === 0) {
159970
+ mergedRaw.execute_threshold_percentage = value.defaultValue;
159971
+ return;
159972
+ }
159973
+ const serialized = { default: value.defaultValue };
159974
+ for (const [key, threshold] of value.overrides) {
159975
+ serialized[key] = threshold;
159976
+ }
159977
+ mergedRaw.execute_threshold_percentage = serialized;
159978
+ }
159979
+ function setMergedTokenThreshold(mergedRaw, value) {
159980
+ if (value.defaultValue === undefined && value.overrides.size === 0) {
159981
+ delete mergedRaw.execute_threshold_tokens;
159982
+ return;
159983
+ }
159984
+ const serialized = {};
159985
+ if (value.defaultValue !== undefined) {
159986
+ serialized.default = value.defaultValue;
159987
+ }
159988
+ for (const [key, threshold] of value.overrides) {
159989
+ serialized[key] = threshold;
159990
+ }
159991
+ mergedRaw.execute_threshold_tokens = serialized;
159992
+ }
159993
+ function makeProjectThresholdWarning(field, reason) {
159994
+ return `Ignoring ${field} from project config (${reason})`;
159995
+ }
159996
+ function stripUnsafeProjectConfigFields(projectRaw) {
159997
+ const warnings = [];
159998
+ if ("auto_update" in projectRaw) {
159999
+ delete projectRaw.auto_update;
160000
+ warnings.push("Ignoring auto_update from project config (security: this setting only honors user-level config).");
160001
+ }
160002
+ if ("language" in projectRaw) {
160003
+ delete projectRaw.language;
160004
+ warnings.push("Ignoring language from project config (security: output language is a user-level setting).");
160005
+ }
160006
+ if ("sqlite" in projectRaw) {
160007
+ delete projectRaw.sqlite;
160008
+ warnings.push("Ignoring sqlite.* from project config (security: SQLite cache/mmap PRAGMAs apply to the " + "process-global shared database handle; only user-level config may set them).");
160009
+ }
160010
+ const embedding = projectRaw.embedding;
160011
+ if (isPlainObject3(embedding)) {
160012
+ const removed = [];
160013
+ for (const field of EMBEDDING_DESTINATION_FIELDS) {
160014
+ if (field in embedding) {
160015
+ delete embedding[field];
160016
+ removed.push(field);
160017
+ }
160018
+ }
160019
+ if (removed.length > 0) {
160020
+ warnings.push(`Ignoring embedding.${removed.join("/")} from project config ` + "(security: a repository cannot choose where private text is embedded).");
160021
+ }
160022
+ }
160023
+ const historian = projectRaw.historian;
160024
+ if (isPlainObject3(historian)) {
160025
+ const removed = [];
160026
+ for (const field of HISTORIAN_USER_ONLY_FIELDS) {
160027
+ if (field in historian) {
160028
+ delete historian[field];
160029
+ removed.push(field);
160030
+ }
160031
+ }
160032
+ if (removed.length > 0) {
160033
+ warnings.push(`Ignoring historian.${removed.join("/")} from project config ` + "(security: historian model selection is user-level only; a repository cannot force extra compaction cost).");
160034
+ }
160035
+ }
160036
+ for (const agentKey of HIDDEN_AGENT_KEYS) {
160037
+ const block = projectRaw[agentKey];
160038
+ if (!isPlainObject3(block))
160039
+ continue;
160040
+ const removed = [];
160041
+ for (const field of AGENT_ESCALATION_FIELDS) {
160042
+ if (field in block) {
160043
+ delete block[field];
160044
+ removed.push(field);
160045
+ }
160046
+ }
160047
+ if (removed.length > 0) {
160048
+ warnings.push(`Ignoring ${agentKey}.${removed.join("/")} from project config ` + "(security: a repository cannot reprogram or re-permission hidden agents).");
160049
+ }
160050
+ }
160051
+ return warnings;
160052
+ }
160053
+ function constrainProjectThresholdOverrides(args) {
160054
+ const warnings = [];
160055
+ const basePercentage = normalizeTrustedPercentageThresholds(args.trustedBaseConfig.execute_threshold_percentage);
160056
+ const baseTokens = normalizeTrustedTokenThresholds(args.trustedBaseConfig.execute_threshold_tokens);
160057
+ if ("execute_threshold_percentage" in args.projectRaw) {
160058
+ const projectValue = args.projectRaw.execute_threshold_percentage;
160059
+ if (isValidPercentageThreshold(projectValue)) {
160060
+ const constrained = clonePercentageThresholds(basePercentage);
160061
+ constrained.defaultValue = Math.max(basePercentage.defaultValue, projectValue);
160062
+ for (const [modelKey, threshold] of basePercentage.overrides) {
160063
+ const raisedThreshold = Math.max(threshold, projectValue);
160064
+ if (raisedThreshold === constrained.defaultValue) {
160065
+ constrained.overrides.delete(modelKey);
160066
+ } else {
160067
+ constrained.overrides.set(modelKey, raisedThreshold);
160068
+ }
160069
+ }
160070
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
160071
+ if (percentageThresholdsEqual(constrained, basePercentage)) {
160072
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage", PERCENTAGE_THRESHOLD_REASON));
160073
+ }
160074
+ } else if (isPlainObject3(projectValue)) {
160075
+ const constrained = clonePercentageThresholds(basePercentage);
160076
+ let touchedValidEntry = false;
160077
+ if (isValidPercentageThreshold(projectValue.default)) {
160078
+ touchedValidEntry = true;
160079
+ if (projectValue.default > basePercentage.defaultValue) {
160080
+ constrained.defaultValue = projectValue.default;
160081
+ } else {
160082
+ warnings.push(makeProjectThresholdWarning("execute_threshold_percentage.default", PERCENTAGE_THRESHOLD_REASON));
160083
+ }
160084
+ }
160085
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
160086
+ if (modelKey === "default")
160087
+ continue;
160088
+ if (!isValidPercentageThreshold(rawValue))
160089
+ continue;
160090
+ touchedValidEntry = true;
160091
+ const baseValue = basePercentage.overrides.get(modelKey) ?? basePercentage.defaultValue;
160092
+ if (rawValue > baseValue) {
160093
+ if (rawValue === constrained.defaultValue) {
160094
+ constrained.overrides.delete(modelKey);
160095
+ } else {
160096
+ constrained.overrides.set(modelKey, rawValue);
160097
+ }
160098
+ } else {
160099
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_percentage.${modelKey}`, PERCENTAGE_THRESHOLD_REASON));
160100
+ }
160101
+ }
160102
+ if (touchedValidEntry) {
160103
+ setMergedPercentageThreshold(args.mergedRaw, constrained);
160104
+ }
160105
+ }
160106
+ }
160107
+ if ("execute_threshold_tokens" in args.projectRaw && isPlainObject3(args.projectRaw.execute_threshold_tokens)) {
160108
+ const projectValue = args.projectRaw.execute_threshold_tokens;
160109
+ const constrained = cloneTokenThresholds(baseTokens);
160110
+ let touchedValidEntry = false;
160111
+ if (isValidTokenThreshold(projectValue.default)) {
160112
+ touchedValidEntry = true;
160113
+ if (baseTokens.defaultValue === undefined) {
160114
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_INTRODUCTION_REASON));
160115
+ } else if (projectValue.default > baseTokens.defaultValue) {
160116
+ constrained.defaultValue = projectValue.default;
160117
+ } else {
160118
+ warnings.push(makeProjectThresholdWarning("execute_threshold_tokens.default", TOKEN_THRESHOLD_REASON));
160119
+ }
160120
+ }
160121
+ for (const [modelKey, rawValue] of Object.entries(projectValue)) {
160122
+ if (modelKey === "default")
160123
+ continue;
160124
+ if (!isValidTokenThreshold(rawValue))
160125
+ continue;
160126
+ touchedValidEntry = true;
160127
+ const baseValue = baseTokens.overrides.get(modelKey) ?? baseTokens.defaultValue;
160128
+ if (baseValue === undefined) {
160129
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_INTRODUCTION_REASON));
160130
+ continue;
160131
+ }
160132
+ if (rawValue > baseValue) {
160133
+ if (rawValue === constrained.defaultValue) {
160134
+ constrained.overrides.delete(modelKey);
160135
+ } else {
160136
+ constrained.overrides.set(modelKey, rawValue);
160137
+ }
160138
+ } else {
160139
+ warnings.push(makeProjectThresholdWarning(`execute_threshold_tokens.${modelKey}`, TOKEN_THRESHOLD_REASON));
160140
+ }
160141
+ }
160142
+ if (touchedValidEntry) {
160143
+ setMergedTokenThreshold(args.mergedRaw, constrained);
160144
+ }
160145
+ }
160146
+ return warnings;
160147
+ }
160148
+ function normalizeEndpoint(value) {
160149
+ if (typeof value !== "string")
160150
+ return;
160151
+ const trimmed = value.trim().replace(/\/+$/, "");
160152
+ return trimmed.length > 0 ? trimmed.toLowerCase() : undefined;
160153
+ }
160154
+ function dropInheritedEmbeddingKeyOnRedirect(projectRaw, mergedRaw, userRaw) {
160155
+ const projectEmbedding = projectRaw.embedding;
160156
+ if (!isPlainObject3(projectEmbedding))
160157
+ return [];
160158
+ const redirectsEndpoint = "endpoint" in projectEmbedding;
160159
+ if (!redirectsEndpoint)
160160
+ return [];
160161
+ const userEmbedding = userRaw?.embedding;
160162
+ if (isPlainObject3(userEmbedding)) {
160163
+ const projectEndpoint = normalizeEndpoint(projectEmbedding.endpoint);
160164
+ const userEndpoint = normalizeEndpoint(userEmbedding.endpoint);
160165
+ if (projectEndpoint !== undefined && projectEndpoint === userEndpoint) {
160166
+ return [];
160167
+ }
160168
+ }
160169
+ const providesOwnKey = typeof projectEmbedding.api_key === "string" && projectEmbedding.api_key.length > 0;
160170
+ if (providesOwnKey)
160171
+ return [];
160172
+ const mergedEmbedding = mergedRaw.embedding;
160173
+ if (!isPlainObject3(mergedEmbedding))
160174
+ return [];
160175
+ if (!("api_key" in mergedEmbedding))
160176
+ return [];
160177
+ delete mergedEmbedding.api_key;
160178
+ return [
160179
+ "Dropped inherited user embedding api_key because project config redirected " + "embedding.endpoint without supplying its own key (security: prevents key " + "exfiltration to a repository-chosen endpoint)."
160180
+ ];
160181
+ }
160182
+
159940
160183
  // ../plugin/src/config/variable.ts
159941
160184
  import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
159942
160185
  import { homedir as homedir3 } from "node:os";
@@ -160269,6 +160512,7 @@ function loadPiConfig(opts = {}) {
160269
160512
  return a.scope === "user" ? -1 : 1;
160270
160513
  });
160271
160514
  const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
160515
+ const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
160272
160516
  for (const loaded of mergeFiles) {
160273
160517
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
160274
160518
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
@@ -160281,6 +160525,13 @@ function loadPiConfig(opts = {}) {
160281
160525
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
160282
160526
  warnings.push(`${prefix} ${warning}`);
160283
160527
  }
160528
+ for (const warning of constrainProjectThresholdOverrides({
160529
+ mergedRaw: rawConfig,
160530
+ projectRaw,
160531
+ trustedBaseConfig
160532
+ })) {
160533
+ warnings.push(`${prefix} ${warning}`);
160534
+ }
160284
160535
  } else {
160285
160536
  rawConfig = mergeRawConfigs(rawConfig, loaded.config);
160286
160537
  }
@@ -160379,6 +160630,7 @@ function loadPiConfigDetailed(opts = {}) {
160379
160630
  return a.scope === "user" ? -1 : 1;
160380
160631
  });
160381
160632
  const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
160633
+ const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
160382
160634
  for (const loaded of mergeFiles) {
160383
160635
  const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
160384
160636
  warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
@@ -160391,6 +160643,13 @@ function loadPiConfigDetailed(opts = {}) {
160391
160643
  for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
160392
160644
  warnings.push(`${prefix} ${warning}`);
160393
160645
  }
160646
+ for (const warning of constrainProjectThresholdOverrides({
160647
+ mergedRaw: rawConfig,
160648
+ projectRaw,
160649
+ trustedBaseConfig
160650
+ })) {
160651
+ warnings.push(`${prefix} ${warning}`);
160652
+ }
160394
160653
  } else {
160395
160654
  rawConfig = mergeRawConfigs(rawConfig, loaded.config);
160396
160655
  }
@@ -161775,6 +162034,7 @@ class OpenAICompatibleEmbeddingProvider {
161775
162034
 
161776
162035
  // ../plugin/src/features/magic-context/memory/storage-memory-embeddings.ts
161777
162036
  var saveEmbeddingStatements = new WeakMap;
162037
+ var saveEmbeddingIfHashMatchesStatements = new WeakMap;
161778
162038
  var loadAllEmbeddingsStatements = new WeakMap;
161779
162039
  var deleteEmbeddingStatements = new WeakMap;
161780
162040
  var getStoredModelIdStatements = new WeakMap;
@@ -161805,6 +162065,14 @@ function getSaveEmbeddingStatement(db) {
161805
162065
  }
161806
162066
  return stmt;
161807
162067
  }
162068
+ function getSaveEmbeddingIfHashMatchesStatement(db) {
162069
+ let stmt = saveEmbeddingIfHashMatchesStatements.get(db);
162070
+ if (!stmt) {
162071
+ stmt = db.prepare("INSERT INTO memory_embeddings (memory_id, embedding, model_id) SELECT ?, ?, ? FROM memories WHERE id = ? AND normalized_hash = ? ON CONFLICT(memory_id, model_id) DO UPDATE SET embedding = excluded.embedding");
162072
+ saveEmbeddingIfHashMatchesStatements.set(db, stmt);
162073
+ }
162074
+ return stmt;
162075
+ }
161808
162076
  function getLoadAllEmbeddingsStatement(db) {
161809
162077
  let stmt = loadAllEmbeddingsStatements.get(db);
161810
162078
  if (!stmt) {
@@ -161833,6 +162101,10 @@ function saveEmbedding(db, memoryId, embedding, modelId) {
161833
162101
  const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
161834
162102
  getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
161835
162103
  }
162104
+ function saveEmbeddingIfHashMatches(db, memoryId, embedding, modelId, normalizedHash) {
162105
+ const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
162106
+ return getSaveEmbeddingIfHashMatchesStatement(db).run(memoryId, blob, modelId, memoryId, normalizedHash).changes > 0;
162107
+ }
161836
162108
  function loadAllEmbeddings(db, projectPath, modelId) {
161837
162109
  const rows = getLoadAllEmbeddingsStatement(db).all(projectPath, modelId).filter(isEmbeddingRow);
161838
162110
  const embeddings = new Map;
@@ -162764,12 +163036,12 @@ function isUnembeddedMemoryRow(row) {
162764
163036
  if (row === null || typeof row !== "object")
162765
163037
  return false;
162766
163038
  const candidate = row;
162767
- return typeof candidate.id === "number" && typeof candidate.content === "string";
163039
+ return typeof candidate.id === "number" && typeof candidate.content === "string" && typeof candidate.normalized_hash === "string";
162768
163040
  }
162769
163041
  function getLoadUnembeddedMemoriesStatement(db) {
162770
163042
  let stmt = loadUnembeddedMemoriesStatements.get(db);
162771
163043
  if (!stmt) {
162772
- stmt = db.prepare("SELECT m.id AS id, m.content AS content FROM memories m LEFT JOIN memory_embeddings me ON m.id = me.memory_id AND me.model_id = ? WHERE m.project_path = ? AND m.status = 'active' AND me.memory_id IS NULL LIMIT ?");
163044
+ stmt = db.prepare("SELECT m.id AS id, m.content AS content, m.normalized_hash AS normalized_hash FROM memories m LEFT JOIN memory_embeddings me ON m.id = me.memory_id AND me.model_id = ? WHERE m.project_path = ? AND m.status = 'active' AND me.memory_id IS NULL LIMIT ?");
162773
163045
  loadUnembeddedMemoriesStatements.set(db, stmt);
162774
163046
  }
162775
163047
  return stmt;
@@ -162792,8 +163064,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
162792
163064
  const embedding = result.vectors[index];
162793
163065
  if (!embedding)
162794
163066
  continue;
162795
- saveEmbedding(db, memory.id, embedding, result.modelId);
162796
- embeddedCount += 1;
163067
+ if (saveEmbeddingIfHashMatches(db, memory.id, embedding, result.modelId, memory.normalized_hash)) {
163068
+ embeddedCount += 1;
163069
+ }
162797
163070
  }
162798
163071
  })();
162799
163072
  return embeddedCount;
@@ -164299,12 +164572,19 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
164299
164572
  let totalTokens = 0;
164300
164573
  let messagesProcessed = 0;
164301
164574
  let lastOrdinal = startOrdinal - 1;
164575
+ let highestScannedOrdinal = startOrdinal - 1;
164302
164576
  let lastMessageId = "";
164303
164577
  let firstMessageId = "";
164304
164578
  let currentBlock = null;
164305
164579
  let pendingNoiseMeta = [];
164306
164580
  let commitClusters = 0;
164307
164581
  let lastFlushedRole = "";
164582
+ function recordFilteredNoise(meta3) {
164583
+ pendingNoiseMeta.push(meta3);
164584
+ if (!currentBlock) {
164585
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, meta3.ordinal);
164586
+ }
164587
+ }
164308
164588
  function flushCurrentBlock() {
164309
164589
  if (!currentBlock)
164310
164590
  return true;
@@ -164320,6 +164600,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
164320
164600
  if (!firstMessageId)
164321
164601
  firstMessageId = currentBlock.meta[0]?.messageId ?? "";
164322
164602
  lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
164603
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, lastOrdinal);
164323
164604
  lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
164324
164605
  messagesProcessed += currentBlock.meta.length;
164325
164606
  lines.push(blockText);
@@ -164343,7 +164624,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
164343
164624
  if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
164344
164625
  const tcSummaries = extractToolCallSummaries(msg.parts);
164345
164626
  if (tcSummaries.length === 0) {
164346
- pendingNoiseMeta.push(meta3);
164627
+ recordFilteredNoise(meta3);
164347
164628
  continue;
164348
164629
  }
164349
164630
  const tcText = tcSummaries.join(" / ");
@@ -164375,7 +164656,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
164375
164656
  const compacted = compactTextForSummary(allParts.join(" / "), msg.role);
164376
164657
  const text = compacted.text;
164377
164658
  if (!text) {
164378
- pendingNoiseMeta.push(meta3);
164659
+ recordFilteredNoise(meta3);
164379
164660
  continue;
164380
164661
  }
164381
164662
  const msgHasNarrative = textParts.length > 0;
@@ -164402,7 +164683,9 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
164402
164683
  };
164403
164684
  pendingNoiseMeta = [];
164404
164685
  }
164405
- flushCurrentBlock();
164686
+ if (flushCurrentBlock() && pendingNoiseMeta.length > 0) {
164687
+ highestScannedOrdinal = Math.max(highestScannedOrdinal, pendingNoiseMeta[pendingNoiseMeta.length - 1]?.ordinal ?? highestScannedOrdinal);
164688
+ }
164406
164689
  const toolOnlyRanges = [];
164407
164690
  for (const range of flushedToolOnlyBlocks) {
164408
164691
  const last = toolOnlyRanges[toolOnlyRanges.length - 1];
@@ -164419,7 +164702,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
164419
164702
  endMessageId: lastMessageId,
164420
164703
  messageCount: messagesProcessed,
164421
164704
  tokenEstimate: totalTokens,
164422
- hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
164705
+ hasMore: Math.max(lastOrdinal, highestScannedOrdinal) < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
164423
164706
  text: lines.join(`
164424
164707
  `),
164425
164708
  lines: lineMeta,
@@ -168864,6 +169147,8 @@ function isMidTurnPi(event, _sessionId) {
168864
169147
  }
168865
169148
  if (latestAssistant === null)
168866
169149
  return false;
169150
+ if (hasRealUserAfter(messages, latestAssistantIndex))
169151
+ return false;
168867
169152
  if (latestAssistant.stopReason === "toolUse")
168868
169153
  return true;
168869
169154
  const toolCallIds = getToolCallIds(latestAssistant.content);
@@ -168886,6 +169171,15 @@ function isMidTurnPi(event, _sessionId) {
168886
169171
  }
168887
169172
  return false;
168888
169173
  }
169174
+ function hasRealUserAfter(messages, latestAssistantIndex) {
169175
+ for (const msg of messages.slice(latestAssistantIndex + 1)) {
169176
+ if (msg === null || typeof msg !== "object")
169177
+ continue;
169178
+ if (msg.role === "user")
169179
+ return true;
169180
+ }
169181
+ return false;
169182
+ }
168889
169183
  function getToolCallIds(content) {
168890
169184
  const ids = new Set;
168891
169185
  if (!Array.isArray(content))
@@ -169610,25 +169904,33 @@ function getMemoryCountsByStatusStatement(db) {
169610
169904
  }
169611
169905
  return stmt;
169612
169906
  }
169613
- function insertMemory(db, input) {
169614
- const now = Date.now();
169615
- const normalizedHash = computeNormalizedHash(input.content);
169907
+ function buildInsertMemoryValues(input, normalizedHash, now, includeImportance) {
169616
169908
  const insertValues = [
169617
169909
  input.projectPath,
169618
169910
  input.category,
169619
169911
  input.content,
169620
169912
  normalizedHash
169621
169913
  ];
169622
- if (hasMemoryImportanceColumn(db)) {
169914
+ if (includeImportance) {
169623
169915
  insertValues.push(input.importance ?? 50);
169624
169916
  }
169625
169917
  insertValues.push(input.sourceSessionId ?? null, input.sourceType ?? "historian", 1, 0, now, now, now, now, null, "active", input.expiresAt ?? null, "unverified", null, null, null, input.metadataJson ?? null);
169626
- const result = getInsertMemoryStatement(db).run(...insertValues);
169627
- const insertedResult = result;
169628
- const inserted = getMemoryById(db, Number(insertedResult.lastInsertRowid));
169918
+ return insertValues;
169919
+ }
169920
+ function loadInsertedMemory(db, rowid) {
169921
+ const inserted = getMemoryById(db, Number(rowid));
169629
169922
  if (!inserted) {
169630
169923
  throw new Error("Failed to load inserted memory row");
169631
169924
  }
169925
+ return inserted;
169926
+ }
169927
+ function insertMemory(db, input) {
169928
+ const now = Date.now();
169929
+ const normalizedHash = computeNormalizedHash(input.content);
169930
+ const insertValues = buildInsertMemoryValues(input, normalizedHash, now, hasMemoryImportanceColumn(db));
169931
+ const result = getInsertMemoryStatement(db).run(...insertValues);
169932
+ const insertedResult = result;
169933
+ const inserted = loadInsertedMemory(db, insertedResult.lastInsertRowid);
169632
169934
  invalidateProject(input.projectPath);
169633
169935
  return inserted;
169634
169936
  }
@@ -170297,7 +170599,10 @@ async function normalizeVerificationFiles(args) {
170297
170599
  var lastIndexedStatements = new WeakMap;
170298
170600
  var insertMessageStatements = new WeakMap;
170299
170601
  var upsertIndexStatements = new WeakMap;
170602
+ var upsertCleanIndexStatements = new WeakMap;
170603
+ var upsertDirtyFloorStatements = new WeakMap;
170300
170604
  var deleteFtsStatements = new WeakMap;
170605
+ var deleteFtsFromOrdinalStatements = new WeakMap;
170301
170606
  var deleteIndexStatements = new WeakMap;
170302
170607
  var countIndexedMessageStatements = new WeakMap;
170303
170608
  function normalizeIndexText(text) {
@@ -170306,7 +170611,7 @@ function normalizeIndexText(text) {
170306
170611
  function getLastIndexedStatement(db) {
170307
170612
  let stmt = lastIndexedStatements.get(db);
170308
170613
  if (!stmt) {
170309
- stmt = db.prepare("SELECT last_indexed_ordinal FROM message_history_index WHERE session_id = ?");
170614
+ stmt = db.prepare("SELECT last_indexed_ordinal, dirty_floor_ordinal FROM message_history_index WHERE session_id = ?");
170310
170615
  lastIndexedStatements.set(db, stmt);
170311
170616
  }
170312
170617
  return stmt;
@@ -170327,6 +170632,22 @@ function getUpsertIndexStatement(db) {
170327
170632
  }
170328
170633
  return stmt;
170329
170634
  }
170635
+ function getUpsertCleanIndexStatement(db) {
170636
+ let stmt = upsertCleanIndexStatements.get(db);
170637
+ if (!stmt) {
170638
+ stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, dirty_floor_ordinal, updated_at, harness) VALUES (?, ?, 0, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = excluded.last_indexed_ordinal, dirty_floor_ordinal = 0, updated_at = excluded.updated_at");
170639
+ upsertCleanIndexStatements.set(db, stmt);
170640
+ }
170641
+ return stmt;
170642
+ }
170643
+ function getUpsertDirtyFloorStatement(db) {
170644
+ let stmt = upsertDirtyFloorStatements.get(db);
170645
+ if (!stmt) {
170646
+ stmt = db.prepare("INSERT INTO message_history_index (session_id, last_indexed_ordinal, dirty_floor_ordinal, updated_at, harness) VALUES (?, ?, ?, ?, ?) ON CONFLICT(session_id) DO UPDATE SET last_indexed_ordinal = MAX(message_history_index.last_indexed_ordinal, excluded.last_indexed_ordinal), dirty_floor_ordinal = CASE WHEN message_history_index.dirty_floor_ordinal <= 0 THEN excluded.dirty_floor_ordinal WHEN excluded.dirty_floor_ordinal <= 0 THEN message_history_index.dirty_floor_ordinal ELSE MIN(message_history_index.dirty_floor_ordinal, excluded.dirty_floor_ordinal) END, updated_at = excluded.updated_at");
170647
+ upsertDirtyFloorStatements.set(db, stmt);
170648
+ }
170649
+ return stmt;
170650
+ }
170330
170651
  function getDeleteFtsStatement(db) {
170331
170652
  let stmt = deleteFtsStatements.get(db);
170332
170653
  if (!stmt) {
@@ -170335,6 +170656,14 @@ function getDeleteFtsStatement(db) {
170335
170656
  }
170336
170657
  return stmt;
170337
170658
  }
170659
+ function getDeleteFtsFromOrdinalStatement(db) {
170660
+ let stmt = deleteFtsFromOrdinalStatements.get(db);
170661
+ if (!stmt) {
170662
+ stmt = db.prepare("DELETE FROM message_history_fts WHERE session_id = ? AND CAST(message_ordinal AS INTEGER) >= ?");
170663
+ deleteFtsFromOrdinalStatements.set(db, stmt);
170664
+ }
170665
+ return stmt;
170666
+ }
170338
170667
  function getDeleteIndexStatement(db) {
170339
170668
  let stmt = deleteIndexStatements.get(db);
170340
170669
  if (!stmt) {
@@ -170355,6 +170684,14 @@ function getLastIndexedOrdinal(db, sessionId) {
170355
170684
  const row = getLastIndexedStatement(db).get(sessionId);
170356
170685
  return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
170357
170686
  }
170687
+ function getDirtyIndexFloor(db, sessionId) {
170688
+ const row = getLastIndexedStatement(db).get(sessionId);
170689
+ return typeof row?.dirty_floor_ordinal === "number" && row.dirty_floor_ordinal > 0 ? row.dirty_floor_ordinal : null;
170690
+ }
170691
+ function markMessageIndexDirty(db, sessionId, floorOrdinal) {
170692
+ const dirtyFloor = Math.max(1, Math.floor(floorOrdinal));
170693
+ getUpsertDirtyFloorStatement(db).run(sessionId, getLastIndexedOrdinal(db, sessionId), dirtyFloor, Date.now(), getHarness());
170694
+ }
170358
170695
  function isMessageAlreadyIndexed(db, sessionId, messageId) {
170359
170696
  const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
170360
170697
  return (typeof row?.count === "number" ? row.count : 0) > 0;
@@ -170422,7 +170759,15 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
170422
170759
  db.exec("BEGIN IMMEDIATE");
170423
170760
  let committed = false;
170424
170761
  try {
170425
- const effectiveWatermark = Math.max(lastIndexedOrdinal, getLastIndexedOrdinal(db, sessionId));
170762
+ let effectiveWatermark = Math.max(lastIndexedOrdinal, getLastIndexedOrdinal(db, sessionId));
170763
+ const dirtyFloor = getDirtyIndexFloor(db, sessionId);
170764
+ if (dirtyFloor !== null) {
170765
+ const rewindOrdinal = Math.max(1, Math.min(dirtyFloor, finalWatermark + 1));
170766
+ if (rewindOrdinal <= finalWatermark) {
170767
+ getDeleteFtsFromOrdinalStatement(db).run(sessionId, rewindOrdinal);
170768
+ }
170769
+ effectiveWatermark = Math.min(effectiveWatermark, rewindOrdinal - 1);
170770
+ }
170426
170771
  const insertMessage = getInsertMessageStatement(db);
170427
170772
  for (const message of messages) {
170428
170773
  if (message.ordinal <= effectiveWatermark) {
@@ -170439,7 +170784,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
170439
170784
  inserted++;
170440
170785
  }
170441
170786
  const newWatermark = Math.max(effectiveWatermark, finalWatermark);
170442
- getUpsertIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
170787
+ getUpsertCleanIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
170443
170788
  db.exec("COMMIT");
170444
170789
  committed = true;
170445
170790
  } finally {
@@ -171210,7 +171555,7 @@ function setChannel2NudgeState(db, sessionId, state) {
171210
171555
  db.transaction(() => {
171211
171556
  ensureSessionMetaRow(db, sessionId);
171212
171557
  const claimedAt = state === "claimed" ? Date.now() : 0;
171213
- db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ?").run(state, claimedAt, sessionId);
171558
+ db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ?, channel2_nudge_claim_token = '' WHERE session_id = ?").run(state, claimedAt, sessionId);
171214
171559
  })();
171215
171560
  }
171216
171561
  function casChannel2NudgeState(db, sessionId, from, to) {
@@ -171218,7 +171563,7 @@ function casChannel2NudgeState(db, sessionId, from, to) {
171218
171563
  db.transaction(() => {
171219
171564
  ensureSessionMetaRow(db, sessionId);
171220
171565
  const claimedAt = to === "claimed" ? Date.now() : 0;
171221
- const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ? WHERE session_id = ? AND channel2_nudge_state = ?").run(to, claimedAt, sessionId, from);
171566
+ const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at = ?, channel2_nudge_claim_token = '' WHERE session_id = ? AND channel2_nudge_state = ?").run(to, claimedAt, sessionId, from);
171222
171567
  changed = (result.changes ?? 0) > 0;
171223
171568
  })();
171224
171569
  return changed;
@@ -173062,8 +173407,9 @@ function createCtxMemoryTool(deps) {
173062
173407
  const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
173063
173408
  if (isOwn)
173064
173409
  return true;
173065
- return toolShareCategories === null || toolShareCategories.includes(memory2.category);
173410
+ return toolShareCategories?.includes(memory2.category) ?? false;
173066
173411
  };
173412
+ const memoryOwnedByTool = (memory2) => workspaceIdentitySet.identities.length > 1 ? targetIdentityForStoredPath(memory2.projectPath) === projectIdentity : storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
173067
173413
  const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
173068
173414
  if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
173069
173415
  return err2("Cross-session memory is disabled for this project.");
@@ -173110,7 +173456,8 @@ function createCtxMemoryTool(deps) {
173110
173456
  return err2("Error: 'content' is required when action is 'update'.");
173111
173457
  }
173112
173458
  const memory2 = getMemoryById(deps.db, updateId);
173113
- if (!memory2 || !memoryVisibleToTool(memory2)) {
173459
+ const updateAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
173460
+ if (!memory2 || !updateAllowed) {
173114
173461
  return err2(`Error: Memory with ID ${updateId} was not found.`);
173115
173462
  }
173116
173463
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
@@ -173157,7 +173504,7 @@ function createCtxMemoryTool(deps) {
173157
173504
  return err2("Error: One or more source memories were not found.");
173158
173505
  }
173159
173506
  if (!dreamerAllowed) {
173160
- const foreign = sourceMemories.find((memory2) => !memoryVisibleToTool(memory2));
173507
+ const foreign = sourceMemories.find((memory2) => !memoryOwnedByTool(memory2));
173161
173508
  if (foreign) {
173162
173509
  return err2(`Error: Memory with ID ${foreign.id} was not found.`);
173163
173510
  }
@@ -173259,7 +173606,8 @@ function createCtxMemoryTool(deps) {
173259
173606
  const archiveIds = [...new Set(rawArchiveIds)];
173260
173607
  for (const memoryId of archiveIds) {
173261
173608
  const memory2 = getMemoryById(deps.db, memoryId);
173262
- if (!memory2 || !memoryVisibleToTool(memory2)) {
173609
+ const archiveAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
173610
+ if (!memory2 || !archiveAllowed) {
173263
173611
  return err2(`Error: Memory with ID ${memoryId} was not found.`);
173264
173612
  }
173265
173613
  if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
@@ -174212,24 +174560,46 @@ function getMessageSearchStatementWithCutoff(db) {
174212
174560
  return stmt;
174213
174561
  }
174214
174562
  var ftsRowCountStatements = new WeakMap;
174563
+ var ftsRowCountStatementsWithCutoff = new WeakMap;
174215
174564
  var ftsMatchCountStatements = new WeakMap;
174216
- function getSessionFtsRowCount(db, sessionId) {
174217
- let stmt = ftsRowCountStatements.get(db);
174218
- if (!stmt) {
174219
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
174220
- ftsRowCountStatements.set(db, stmt);
174221
- }
174222
- const row = stmt.get(sessionId);
174565
+ var ftsMatchCountStatementsWithCutoff = new WeakMap;
174566
+ function getSessionFtsRowCount(db, sessionId, cutoff) {
174567
+ const stmt = cutoff === null ? (() => {
174568
+ let cached2 = ftsRowCountStatements.get(db);
174569
+ if (!cached2) {
174570
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ?");
174571
+ ftsRowCountStatements.set(db, cached2);
174572
+ }
174573
+ return cached2;
174574
+ })() : (() => {
174575
+ let cached2 = ftsRowCountStatementsWithCutoff.get(db);
174576
+ if (!cached2) {
174577
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND CAST(message_ordinal AS INTEGER) <= ?");
174578
+ ftsRowCountStatementsWithCutoff.set(db, cached2);
174579
+ }
174580
+ return cached2;
174581
+ })();
174582
+ const row = cutoff === null ? stmt.get(sessionId) : stmt.get(sessionId, cutoff);
174223
174583
  return typeof row?.n === "number" ? row.n : 0;
174224
174584
  }
174225
- function countSessionFtsMatches(db, sessionId, ftsQuery) {
174226
- let stmt = ftsMatchCountStatements.get(db);
174227
- if (!stmt) {
174228
- stmt = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
174229
- ftsMatchCountStatements.set(db, stmt);
174230
- }
174585
+ function countSessionFtsMatches(db, sessionId, ftsQuery, cutoff) {
174586
+ const stmt = cutoff === null ? (() => {
174587
+ let cached2 = ftsMatchCountStatements.get(db);
174588
+ if (!cached2) {
174589
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ?");
174590
+ ftsMatchCountStatements.set(db, cached2);
174591
+ }
174592
+ return cached2;
174593
+ })() : (() => {
174594
+ let cached2 = ftsMatchCountStatementsWithCutoff.get(db);
174595
+ if (!cached2) {
174596
+ cached2 = db.prepare("SELECT COUNT(*) AS n FROM message_history_fts WHERE session_id = ? AND message_history_fts MATCH ? AND CAST(message_ordinal AS INTEGER) <= ?");
174597
+ ftsMatchCountStatementsWithCutoff.set(db, cached2);
174598
+ }
174599
+ return cached2;
174600
+ })();
174231
174601
  try {
174232
- const row = stmt.get(sessionId, ftsQuery);
174602
+ const row = cutoff === null ? stmt.get(sessionId, ftsQuery) : stmt.get(sessionId, ftsQuery, cutoff);
174233
174603
  return typeof row?.n === "number" ? row.n : 0;
174234
174604
  } catch {
174235
174605
  return 0;
@@ -174479,7 +174849,7 @@ function searchMessages(args) {
174479
174849
  role: row.role
174480
174850
  }));
174481
174851
  }
174482
- const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
174852
+ const corpusSize = getSessionFtsRowCount(args.db, args.sessionId, cutoff);
174483
174853
  const queryLists = [];
174484
174854
  if (baseQuery.length > 0) {
174485
174855
  queryLists.push({
@@ -174492,7 +174862,7 @@ function searchMessages(args) {
174492
174862
  const probeQuery = sanitizeFtsQuery(probe);
174493
174863
  if (probeQuery.length === 0)
174494
174864
  continue;
174495
- const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
174865
+ const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery, cutoff);
174496
174866
  const weight = probeDiscriminationWeight(df, corpusSize);
174497
174867
  probeWeights.set(probe, weight);
174498
174868
  queryLists.push({
@@ -175667,17 +176037,119 @@ function createCtxSearchTool(deps) {
175667
176037
  import {
175668
176038
  truncateToWidth
175669
176039
  } from "@earendil-works/pi-tui";
176040
+
176041
+ // ../plugin/src/hooks/magic-context/todo-view.ts
176042
+ import { createHash as createHash8 } from "node:crypto";
176043
+ var TODO_STATUS_PENDING = "pending";
176044
+ var TODO_STATUS_IN_PROGRESS = "in_progress";
176045
+ var TODO_STATUS_COMPLETED = "completed";
176046
+ var TODO_STATUS_CANCELLED = "cancelled";
176047
+ var TODO_PRIORITY_HIGH = "high";
176048
+ var TODO_PRIORITY_MEDIUM = "medium";
176049
+ var TODO_PRIORITY_LOW = "low";
176050
+ var TODO_STATUSES = [
176051
+ TODO_STATUS_PENDING,
176052
+ TODO_STATUS_IN_PROGRESS,
176053
+ TODO_STATUS_COMPLETED,
176054
+ TODO_STATUS_CANCELLED
176055
+ ];
176056
+ var TODO_PRIORITIES = [
176057
+ TODO_PRIORITY_HIGH,
176058
+ TODO_PRIORITY_MEDIUM,
176059
+ TODO_PRIORITY_LOW
176060
+ ];
176061
+ var TODO_STATUS_SET = new Set(TODO_STATUSES);
176062
+ var TODO_PRIORITY_SET = new Set(TODO_PRIORITIES);
176063
+ var TERMINAL_STATUSES = new Set([
176064
+ TODO_STATUS_COMPLETED,
176065
+ TODO_STATUS_CANCELLED
176066
+ ]);
176067
+ var TITLE_DONE_STATUSES = new Set([TODO_STATUS_COMPLETED]);
176068
+ var SYNTHETIC_CALL_ID_PREFIX = "mc_synthetic_todo_";
176069
+ function normalizeTodoStateJson(todos) {
176070
+ if (!Array.isArray(todos))
176071
+ return null;
176072
+ const normalized = [];
176073
+ for (const todo of todos) {
176074
+ if (!isTodoItem(todo))
176075
+ return null;
176076
+ normalized.push({
176077
+ content: todo.content,
176078
+ status: todo.status,
176079
+ priority: todo.priority ?? TODO_PRIORITY_MEDIUM
176080
+ });
176081
+ }
176082
+ return JSON.stringify(normalized);
176083
+ }
176084
+ function buildSyntheticTodoPart(stateJson) {
176085
+ const todos = parseTodoState(stateJson);
176086
+ if (todos === null || todos.length === 0)
176087
+ return null;
176088
+ if (todos.every((t) => TERMINAL_STATUSES.has(t.status)))
176089
+ return null;
176090
+ const callID = computeSyntheticCallId(stateJson);
176091
+ const activeCount = todos.filter((t) => !TITLE_DONE_STATUSES.has(t.status)).length;
176092
+ const output = JSON.stringify(todos, null, 2);
176093
+ const ts = 0;
176094
+ return {
176095
+ type: "tool",
176096
+ callID,
176097
+ tool: "todowrite",
176098
+ state: {
176099
+ status: "completed",
176100
+ input: { todos },
176101
+ output,
176102
+ title: `${activeCount} todos`,
176103
+ metadata: { todos, truncated: false },
176104
+ time: { start: ts, end: ts }
176105
+ },
176106
+ syntheticTodoMarker: true
176107
+ };
176108
+ }
176109
+ function computeSyntheticCallId(stateJson) {
176110
+ const hash2 = createHash8("sha256").update(stateJson).digest("hex").slice(0, 16);
176111
+ return `${SYNTHETIC_CALL_ID_PREFIX}${hash2}`;
176112
+ }
176113
+ function parseTodoState(stateJson) {
176114
+ if (stateJson.length === 0)
176115
+ return null;
176116
+ try {
176117
+ const parsed = JSON.parse(stateJson);
176118
+ if (!Array.isArray(parsed))
176119
+ return null;
176120
+ const result = [];
176121
+ for (const item of parsed) {
176122
+ if (!isTodoItem(item))
176123
+ return null;
176124
+ result.push({
176125
+ content: item.content,
176126
+ status: item.status,
176127
+ priority: item.priority ?? TODO_PRIORITY_MEDIUM
176128
+ });
176129
+ }
176130
+ return result;
176131
+ } catch {
176132
+ return null;
176133
+ }
176134
+ }
176135
+ function isTodoStatus(value) {
176136
+ return typeof value === "string" && TODO_STATUS_SET.has(value);
176137
+ }
176138
+ function isTodoPriority(value) {
176139
+ return typeof value === "string" && TODO_PRIORITY_SET.has(value);
176140
+ }
176141
+ function isTodoItem(value) {
176142
+ if (value === null || typeof value !== "object")
176143
+ return false;
176144
+ const todo = value;
176145
+ return typeof todo.content === "string" && isTodoStatus(todo.status) && (todo.priority === undefined || isTodoPriority(todo.priority));
176146
+ }
176147
+
176148
+ // src/tools/todo-view-pi.ts
175670
176149
  var TODO_TOOL_NAME = "todowrite";
175671
176150
  var TODOS_COMMAND_NAME = "todos";
175672
176151
  var WIDGET_KEY = "magic-context-todos";
175673
- var MAX_OVERLAY_CONTENT_ROWS = 12;
175674
- var TODO_STATUSES = [
175675
- "pending",
175676
- "in_progress",
175677
- "completed",
175678
- "cancelled"
175679
- ];
175680
- var TODO_PRIORITIES = ["high", "medium", "low"];
176152
+ var MAX_TODO_CONTENT_ROWS = 12;
175681
176153
  var STATUS_GLYPH = {
175682
176154
  pending: "○",
175683
176155
  in_progress: "◐",
@@ -175691,10 +176163,10 @@ var STATUS_COLOR = {
175691
176163
  cancelled: "error"
175692
176164
  };
175693
176165
  var snapshotsBySession = new Map;
175694
- function isTodoStatus(value) {
176166
+ function isTodoStatus2(value) {
175695
176167
  return TODO_STATUSES.includes(value);
175696
176168
  }
175697
- function isTodoPriority(value) {
176169
+ function isTodoPriority2(value) {
175698
176170
  return TODO_PRIORITIES.includes(value);
175699
176171
  }
175700
176172
  function cloneTodos(todos) {
@@ -175708,14 +176180,14 @@ function parseTodos(input) {
175708
176180
  if (item === null || typeof item !== "object")
175709
176181
  return null;
175710
176182
  const raw = item;
175711
- if (typeof raw.content !== "string" || !isTodoStatus(raw.status)) {
176183
+ if (typeof raw.content !== "string" || !isTodoStatus2(raw.status)) {
175712
176184
  return null;
175713
176185
  }
175714
176186
  const todo = {
175715
176187
  content: raw.content,
175716
176188
  status: raw.status
175717
176189
  };
175718
- if (isTodoPriority(raw.priority))
176190
+ if (isTodoPriority2(raw.priority))
175719
176191
  todo.priority = raw.priority;
175720
176192
  if (typeof raw.id === "string" && raw.id.length > 0)
175721
176193
  todo.id = raw.id;
@@ -175792,7 +176264,7 @@ function countTodos(todos) {
175792
176264
  return counts;
175793
176265
  }
175794
176266
  function activeTitleCount(todos) {
175795
- return todos.filter((todo) => todo.status !== "completed").length;
176267
+ return todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
175796
176268
  }
175797
176269
  function formatCounts(counts) {
175798
176270
  if (counts.total === 0)
@@ -175826,6 +176298,16 @@ function lineComponent(renderLines) {
175826
176298
  invalidate() {}
175827
176299
  };
175828
176300
  }
176301
+ function capTodoRows(rows) {
176302
+ if (rows.length <= MAX_TODO_CONTENT_ROWS) {
176303
+ return { visible: [...rows], hiddenCount: 0 };
176304
+ }
176305
+ const visibleCount = Math.max(0, MAX_TODO_CONTENT_ROWS - 1);
176306
+ return {
176307
+ visible: rows.slice(0, visibleCount),
176308
+ hiddenCount: rows.length - visibleCount
176309
+ };
176310
+ }
175829
176311
  function renderTodowriteCall(args, theme) {
175830
176312
  const todos = parseTodos(args.todos) ?? [];
175831
176313
  const active = activeTitleCount(todos);
@@ -175848,7 +176330,12 @@ function renderTodowriteResult(result, theme) {
175848
176330
  return lineComponent((width) => {
175849
176331
  if (renderedTodos.length === 0)
175850
176332
  return [theme.fg("dim", "No todos")];
175851
- return renderedTodos.map((todo) => truncateToWidth(formatTodoLine(todo, theme, { showId: true }), width, "…"));
176333
+ const { visible, hiddenCount } = capTodoRows(renderedTodos);
176334
+ const lines = visible.map((todo) => truncateToWidth(formatTodoLine(todo, theme, { showId: true }), width, "…"));
176335
+ if (hiddenCount > 0) {
176336
+ lines.push(truncateToWidth(theme.fg("dim", `+${hiddenCount} more`), width, "…"));
176337
+ }
176338
+ return lines;
175852
176339
  });
175853
176340
  }
175854
176341
  function registerTodosCommand(pi) {
@@ -175878,8 +176365,11 @@ function registerTodosCommand(pi) {
175878
176365
  if (group.length === 0)
175879
176366
  continue;
175880
176367
  lines.push(heading);
175881
- for (const todo of group)
176368
+ const { visible, hiddenCount } = capTodoRows(group);
176369
+ for (const todo of visible)
175882
176370
  lines.push(formatCommandLine(todo));
176371
+ if (hiddenCount > 0)
176372
+ lines.push(` +${hiddenCount} more`);
175883
176373
  }
175884
176374
  ctx.ui.notify(lines.join(`
175885
176375
  `), "info");
@@ -176007,19 +176497,16 @@ class TodoOverlay {
176007
176497
  const heading = `${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, "Todos")} ${theme.fg("muted", "—")} ${theme.fg("dim", formatCounts(counts))}`;
176008
176498
  const truncate2 = (line) => truncateToWidth(line, width, "…");
176009
176499
  const lines = [truncate2(heading)];
176010
- const hasTruncatedTail = overlayTodos.length > MAX_OVERLAY_CONTENT_ROWS;
176011
- const visibleRows = hasTruncatedTail ? MAX_OVERLAY_CONTENT_ROWS - 1 : MAX_OVERLAY_CONTENT_ROWS;
176012
- const truncatedTail = Math.max(0, overlayTodos.length - visibleRows);
176013
- const visible = overlayTodos.slice(0, visibleRows);
176500
+ const { visible, hiddenCount } = capTodoRows(overlayTodos);
176014
176501
  for (const [index, { todo, key }] of visible.entries()) {
176015
- const isLast = index === visible.length - 1 && truncatedTail === 0;
176502
+ const isLast = index === visible.length - 1 && hiddenCount === 0;
176016
176503
  const branch = theme.fg("dim", isLast ? "└─" : "├─");
176017
176504
  lines.push(truncate2(`${branch} ${formatTodoLine(todo, theme, { showId: true })}`));
176018
176505
  if (todo.status === "completed")
176019
176506
  this.completedTaskIdsPendingHide.add(key);
176020
176507
  }
176021
- if (truncatedTail > 0) {
176022
- lines.push(truncate2(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${truncatedTail} more`)}`));
176508
+ if (hiddenCount > 0) {
176509
+ lines.push(truncate2(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${hiddenCount} more`)}`));
176023
176510
  }
176024
176511
  return lines;
176025
176512
  }
@@ -176075,17 +176562,10 @@ function registerTodoStateLifecycle(pi, deps) {
176075
176562
  }
176076
176563
 
176077
176564
  // src/tools/todowrite.ts
176078
- var STATUS_VALUES = [
176079
- "pending",
176080
- "in_progress",
176081
- "completed",
176082
- "cancelled"
176083
- ];
176084
- var PRIORITY_VALUES = ["high", "medium", "low"];
176085
176565
  var TodoItem = exports_typebox.Object({
176086
176566
  content: exports_typebox.String({ description: "Brief description of the task" }),
176087
- status: exports_typebox.Union(STATUS_VALUES.map((v) => exports_typebox.Literal(v))),
176088
- priority: exports_typebox.Optional(exports_typebox.Union(PRIORITY_VALUES.map((v) => exports_typebox.Literal(v)))),
176567
+ status: exports_typebox.Union(TODO_STATUSES.map((v) => exports_typebox.Literal(v))),
176568
+ priority: exports_typebox.Optional(exports_typebox.Union(TODO_PRIORITIES.map((v) => exports_typebox.Literal(v)))),
176089
176569
  id: exports_typebox.Optional(exports_typebox.String({ description: "Optional stable id for the todo" }))
176090
176570
  });
176091
176571
  var TodowriteParams = exports_typebox.Object({
@@ -176110,8 +176590,7 @@ function createTodowriteTool() {
176110
176590
  parameters: TodowriteParams,
176111
176591
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
176112
176592
  const todos = params.todos ?? [];
176113
- const completed = todos.filter((t) => t.status === "completed").length;
176114
- const active = todos.length - completed;
176593
+ const active = todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
176115
176594
  return {
176116
176595
  content: [
176117
176596
  {