@cortexkit/pi-magic-context 0.31.2 → 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.
- package/dist/index.js +2068 -554
- package/dist/subagent-entry.js +1123 -191
- package/package.json +1 -1
package/dist/subagent-entry.js
CHANGED
|
@@ -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
|
|
8635
|
-
|
|
8636
|
-
|
|
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
|
-
|
|
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
|
-
|
|
8666
|
+
warnInvalidShareCategories("unknown category", raw);
|
|
8667
|
+
return [];
|
|
8649
8668
|
}
|
|
8650
8669
|
if (!categories.includes(value))
|
|
8651
8670
|
categories.push(value);
|
|
@@ -8654,16 +8673,33 @@ 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")
|
|
8676
|
+
if (candidates.length === 0 || !tableExists(db, "workspace_members")) {
|
|
8658
8677
|
return null;
|
|
8659
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)
|
|
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();
|
|
8691
|
+
}
|
|
8660
8692
|
const row = db.prepare(`SELECT workspace.share_categories AS shareCategories
|
|
8661
8693
|
FROM workspace_members AS member
|
|
8662
8694
|
JOIN workspaces AS workspace ON workspace.id = member.workspace_id
|
|
8663
8695
|
WHERE member.project_path IN (${placeholders(candidates)})
|
|
8664
8696
|
ORDER BY workspace.id ASC
|
|
8665
8697
|
LIMIT 1`).get(...candidates);
|
|
8666
|
-
|
|
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 ? "
|
|
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: () =>
|
|
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
|
|
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 (
|
|
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 (!
|
|
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 (!
|
|
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 (
|
|
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 (!
|
|
148683
|
+
if (!isPlainObject2(input)) {
|
|
148730
148684
|
payload.issues.push({
|
|
148731
148685
|
expected: "record",
|
|
148732
148686
|
code: "invalid_type",
|
|
@@ -159897,6 +159851,10 @@ var MagicContextConfigSchema = exports_external.object({
|
|
|
159897
159851
|
}).describe("Embedding provider configuration"),
|
|
159898
159852
|
temporal_awareness: exports_external.boolean().default(true).describe('Inject wall-clock gap markers (<!-- +Xm -->) between user messages where > 5 min elapsed since the previous message, and add start/end date attributes on compartments. Gives the agent a sense of session pacing and "how long ago" across multi-day sessions. Graduated from experimental.temporal_awareness; default: true (set false to opt out).'),
|
|
159899
159853
|
keep_subagents: exports_external.boolean().default(false).describe("Debug: keep the child sessions Magic Context spawns for its own subagents (historian, dreamer, sidekick, memory-migration) instead of deleting them on success. Useful for short-term inspection/data collection — their full transcript (prompt, tool calls, token usage, output) stays in the host session store. Kept sessions accumulate until manually cleared; leave false for normal use. Requires a restart to take effect."),
|
|
159854
|
+
todowrite: exports_external.object({
|
|
159855
|
+
enabled: exports_external.boolean().default(true).describe("Pi only: register Magic Context's todowrite task-list tool. Disable if you use your own todo extension. OpenCode ships its own built-in todowrite; this setting has no effect there."),
|
|
159856
|
+
overlay: exports_external.boolean().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
|
|
159857
|
+
}).default({ enabled: true, overlay: true }).describe("Pi-only todowrite tool and overlay controls. Pi registers tools and widgets at extension boot, so changing this after /cd requires /reload or restart."),
|
|
159900
159858
|
smart_drops: exports_external.boolean().default(false).describe("Content-aware reclaim of provably-superseded tool output, layered on the existing execute-pass auto-drop. When on: superseded todowrite (keep newest 1), spent ctx_reduce (keep newest 5), and zero-value meta (bash_status, bash_kill, ctx_note read/dismiss) outputs are dropped; older edits to a file are compressed to a filePath-preserving marker while the newest edit per file stays full. Only acts on passes already busting the cache, so it never originates a cache bust. Honors the protected-tag reserve. Experimental: opt-in, default off until cache stability is proven; when off the wire is byte-identical to the positional-only reclaim. Requires a restart."),
|
|
159901
159859
|
caveman_text_compression: exports_external.object({
|
|
159902
159860
|
enabled: exports_external.boolean().default(false).describe("Apply deterministic caveman-style text compression to old conversation text. Active for primary sessions when enabled; never for subagents. Compresses user/assistant text in oldest-first tiers: ultra (oldest 20%), full, lite, untouched (newest 40%)."),
|
|
@@ -159933,6 +159891,295 @@ var MagicContextConfigSchema = exports_external.object({
|
|
|
159933
159891
|
};
|
|
159934
159892
|
});
|
|
159935
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
|
+
|
|
159936
160183
|
// ../plugin/src/config/variable.ts
|
|
159937
160184
|
import { existsSync as existsSync6, readFileSync as readFileSync3 } from "node:fs";
|
|
159938
160185
|
import { homedir as homedir3 } from "node:os";
|
|
@@ -160265,6 +160512,7 @@ function loadPiConfig(opts = {}) {
|
|
|
160265
160512
|
return a.scope === "user" ? -1 : 1;
|
|
160266
160513
|
});
|
|
160267
160514
|
const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
|
|
160515
|
+
const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
|
|
160268
160516
|
for (const loaded of mergeFiles) {
|
|
160269
160517
|
const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
|
|
160270
160518
|
warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
|
|
@@ -160277,6 +160525,13 @@ function loadPiConfig(opts = {}) {
|
|
|
160277
160525
|
for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
|
|
160278
160526
|
warnings.push(`${prefix} ${warning}`);
|
|
160279
160527
|
}
|
|
160528
|
+
for (const warning of constrainProjectThresholdOverrides({
|
|
160529
|
+
mergedRaw: rawConfig,
|
|
160530
|
+
projectRaw,
|
|
160531
|
+
trustedBaseConfig
|
|
160532
|
+
})) {
|
|
160533
|
+
warnings.push(`${prefix} ${warning}`);
|
|
160534
|
+
}
|
|
160280
160535
|
} else {
|
|
160281
160536
|
rawConfig = mergeRawConfigs(rawConfig, loaded.config);
|
|
160282
160537
|
}
|
|
@@ -160375,6 +160630,7 @@ function loadPiConfigDetailed(opts = {}) {
|
|
|
160375
160630
|
return a.scope === "user" ? -1 : 1;
|
|
160376
160631
|
});
|
|
160377
160632
|
const userRaw = mergeFiles.find((f) => f.scope === "user")?.config;
|
|
160633
|
+
const trustedBaseConfig = parsePiConfig(userRaw ?? {}).config;
|
|
160378
160634
|
for (const loaded of mergeFiles) {
|
|
160379
160635
|
const prefix = loaded.scope === "user" ? "[user config]" : "[project config]";
|
|
160380
160636
|
warnings.push(...loaded.warnings.map((warning) => `${prefix} ${warning}`));
|
|
@@ -160387,6 +160643,13 @@ function loadPiConfigDetailed(opts = {}) {
|
|
|
160387
160643
|
for (const warning of dropInheritedEmbeddingKeyOnRedirect(projectRaw, rawConfig, userRaw)) {
|
|
160388
160644
|
warnings.push(`${prefix} ${warning}`);
|
|
160389
160645
|
}
|
|
160646
|
+
for (const warning of constrainProjectThresholdOverrides({
|
|
160647
|
+
mergedRaw: rawConfig,
|
|
160648
|
+
projectRaw,
|
|
160649
|
+
trustedBaseConfig
|
|
160650
|
+
})) {
|
|
160651
|
+
warnings.push(`${prefix} ${warning}`);
|
|
160652
|
+
}
|
|
160390
160653
|
} else {
|
|
160391
160654
|
rawConfig = mergeRawConfigs(rawConfig, loaded.config);
|
|
160392
160655
|
}
|
|
@@ -161771,6 +162034,7 @@ class OpenAICompatibleEmbeddingProvider {
|
|
|
161771
162034
|
|
|
161772
162035
|
// ../plugin/src/features/magic-context/memory/storage-memory-embeddings.ts
|
|
161773
162036
|
var saveEmbeddingStatements = new WeakMap;
|
|
162037
|
+
var saveEmbeddingIfHashMatchesStatements = new WeakMap;
|
|
161774
162038
|
var loadAllEmbeddingsStatements = new WeakMap;
|
|
161775
162039
|
var deleteEmbeddingStatements = new WeakMap;
|
|
161776
162040
|
var getStoredModelIdStatements = new WeakMap;
|
|
@@ -161801,6 +162065,14 @@ function getSaveEmbeddingStatement(db) {
|
|
|
161801
162065
|
}
|
|
161802
162066
|
return stmt;
|
|
161803
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
|
+
}
|
|
161804
162076
|
function getLoadAllEmbeddingsStatement(db) {
|
|
161805
162077
|
let stmt = loadAllEmbeddingsStatements.get(db);
|
|
161806
162078
|
if (!stmt) {
|
|
@@ -161829,6 +162101,10 @@ function saveEmbedding(db, memoryId, embedding, modelId) {
|
|
|
161829
162101
|
const blob = Buffer.from(embedding.buffer, embedding.byteOffset, embedding.byteLength);
|
|
161830
162102
|
getSaveEmbeddingStatement(db).run(memoryId, blob, modelId);
|
|
161831
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
|
+
}
|
|
161832
162108
|
function loadAllEmbeddings(db, projectPath, modelId) {
|
|
161833
162109
|
const rows = getLoadAllEmbeddingsStatement(db).all(projectPath, modelId).filter(isEmbeddingRow);
|
|
161834
162110
|
const embeddings = new Map;
|
|
@@ -162760,12 +163036,12 @@ function isUnembeddedMemoryRow(row) {
|
|
|
162760
163036
|
if (row === null || typeof row !== "object")
|
|
162761
163037
|
return false;
|
|
162762
163038
|
const candidate = row;
|
|
162763
|
-
return typeof candidate.id === "number" && typeof candidate.content === "string";
|
|
163039
|
+
return typeof candidate.id === "number" && typeof candidate.content === "string" && typeof candidate.normalized_hash === "string";
|
|
162764
163040
|
}
|
|
162765
163041
|
function getLoadUnembeddedMemoriesStatement(db) {
|
|
162766
163042
|
let stmt = loadUnembeddedMemoriesStatements.get(db);
|
|
162767
163043
|
if (!stmt) {
|
|
162768
|
-
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 ?");
|
|
162769
163045
|
loadUnembeddedMemoriesStatements.set(db, stmt);
|
|
162770
163046
|
}
|
|
162771
163047
|
return stmt;
|
|
@@ -162788,8 +163064,9 @@ async function embedUnembeddedMemoriesForProject(db, projectIdentity, batchSize
|
|
|
162788
163064
|
const embedding = result.vectors[index];
|
|
162789
163065
|
if (!embedding)
|
|
162790
163066
|
continue;
|
|
162791
|
-
|
|
162792
|
-
|
|
163067
|
+
if (saveEmbeddingIfHashMatches(db, memory.id, embedding, result.modelId, memory.normalized_hash)) {
|
|
163068
|
+
embeddedCount += 1;
|
|
163069
|
+
}
|
|
162793
163070
|
}
|
|
162794
163071
|
})();
|
|
162795
163072
|
return embeddedCount;
|
|
@@ -164295,12 +164572,19 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
|
|
|
164295
164572
|
let totalTokens = 0;
|
|
164296
164573
|
let messagesProcessed = 0;
|
|
164297
164574
|
let lastOrdinal = startOrdinal - 1;
|
|
164575
|
+
let highestScannedOrdinal = startOrdinal - 1;
|
|
164298
164576
|
let lastMessageId = "";
|
|
164299
164577
|
let firstMessageId = "";
|
|
164300
164578
|
let currentBlock = null;
|
|
164301
164579
|
let pendingNoiseMeta = [];
|
|
164302
164580
|
let commitClusters = 0;
|
|
164303
164581
|
let lastFlushedRole = "";
|
|
164582
|
+
function recordFilteredNoise(meta3) {
|
|
164583
|
+
pendingNoiseMeta.push(meta3);
|
|
164584
|
+
if (!currentBlock) {
|
|
164585
|
+
highestScannedOrdinal = Math.max(highestScannedOrdinal, meta3.ordinal);
|
|
164586
|
+
}
|
|
164587
|
+
}
|
|
164304
164588
|
function flushCurrentBlock() {
|
|
164305
164589
|
if (!currentBlock)
|
|
164306
164590
|
return true;
|
|
@@ -164316,6 +164600,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
|
|
|
164316
164600
|
if (!firstMessageId)
|
|
164317
164601
|
firstMessageId = currentBlock.meta[0]?.messageId ?? "";
|
|
164318
164602
|
lastOrdinal = currentBlock.meta[currentBlock.meta.length - 1]?.ordinal ?? currentBlock.endOrdinal;
|
|
164603
|
+
highestScannedOrdinal = Math.max(highestScannedOrdinal, lastOrdinal);
|
|
164319
164604
|
lastMessageId = currentBlock.meta[currentBlock.meta.length - 1]?.messageId ?? "";
|
|
164320
164605
|
messagesProcessed += currentBlock.meta.length;
|
|
164321
164606
|
lines.push(blockText);
|
|
@@ -164339,7 +164624,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
|
|
|
164339
164624
|
if (msg.role === "user" && !hasMeaningfulUserText(msg.parts)) {
|
|
164340
164625
|
const tcSummaries = extractToolCallSummaries(msg.parts);
|
|
164341
164626
|
if (tcSummaries.length === 0) {
|
|
164342
|
-
|
|
164627
|
+
recordFilteredNoise(meta3);
|
|
164343
164628
|
continue;
|
|
164344
164629
|
}
|
|
164345
164630
|
const tcText = tcSummaries.join(" / ");
|
|
@@ -164371,7 +164656,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
|
|
|
164371
164656
|
const compacted = compactTextForSummary(allParts.join(" / "), msg.role);
|
|
164372
164657
|
const text = compacted.text;
|
|
164373
164658
|
if (!text) {
|
|
164374
|
-
|
|
164659
|
+
recordFilteredNoise(meta3);
|
|
164375
164660
|
continue;
|
|
164376
164661
|
}
|
|
164377
164662
|
const msgHasNarrative = textParts.length > 0;
|
|
@@ -164398,7 +164683,9 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
|
|
|
164398
164683
|
};
|
|
164399
164684
|
pendingNoiseMeta = [];
|
|
164400
164685
|
}
|
|
164401
|
-
flushCurrentBlock()
|
|
164686
|
+
if (flushCurrentBlock() && pendingNoiseMeta.length > 0) {
|
|
164687
|
+
highestScannedOrdinal = Math.max(highestScannedOrdinal, pendingNoiseMeta[pendingNoiseMeta.length - 1]?.ordinal ?? highestScannedOrdinal);
|
|
164688
|
+
}
|
|
164402
164689
|
const toolOnlyRanges = [];
|
|
164403
164690
|
for (const range of flushedToolOnlyBlocks) {
|
|
164404
164691
|
const last = toolOnlyRanges[toolOnlyRanges.length - 1];
|
|
@@ -164415,7 +164702,7 @@ function readSessionChunk(sessionId, tokenBudget, offset = 1, eligibleEndOrdinal
|
|
|
164415
164702
|
endMessageId: lastMessageId,
|
|
164416
164703
|
messageCount: messagesProcessed,
|
|
164417
164704
|
tokenEstimate: totalTokens,
|
|
164418
|
-
hasMore: lastOrdinal < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
|
|
164705
|
+
hasMore: Math.max(lastOrdinal, highestScannedOrdinal) < (eligibleEndOrdinal !== undefined ? Math.min(eligibleEndOrdinal - 1, totalMessageCount) : totalMessageCount),
|
|
164419
164706
|
text: lines.join(`
|
|
164420
164707
|
`),
|
|
164421
164708
|
lines: lineMeta,
|
|
@@ -168860,6 +169147,8 @@ function isMidTurnPi(event, _sessionId) {
|
|
|
168860
169147
|
}
|
|
168861
169148
|
if (latestAssistant === null)
|
|
168862
169149
|
return false;
|
|
169150
|
+
if (hasRealUserAfter(messages, latestAssistantIndex))
|
|
169151
|
+
return false;
|
|
168863
169152
|
if (latestAssistant.stopReason === "toolUse")
|
|
168864
169153
|
return true;
|
|
168865
169154
|
const toolCallIds = getToolCallIds(latestAssistant.content);
|
|
@@ -168882,6 +169171,15 @@ function isMidTurnPi(event, _sessionId) {
|
|
|
168882
169171
|
}
|
|
168883
169172
|
return false;
|
|
168884
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
|
+
}
|
|
168885
169183
|
function getToolCallIds(content) {
|
|
168886
169184
|
const ids = new Set;
|
|
168887
169185
|
if (!Array.isArray(content))
|
|
@@ -169120,16 +169418,16 @@ function synthesizeToolResultParts(msg) {
|
|
|
169120
169418
|
// src/tools/ctx-expand.ts
|
|
169121
169419
|
var ParamsSchema = exports_typebox.Object({
|
|
169122
169420
|
start: exports_typebox.Optional(exports_typebox.Number({
|
|
169123
|
-
description:
|
|
169421
|
+
description: `First message ordinal to expand — a compartment's start="N" attribute, or an ordinal from a ctx_search message hit`
|
|
169124
169422
|
})),
|
|
169125
169423
|
end: exports_typebox.Optional(exports_typebox.Number({
|
|
169126
|
-
description:
|
|
169424
|
+
description: `Last message ordinal to expand (inclusive) — a compartment's end="M" attribute`
|
|
169127
169425
|
})),
|
|
169128
169426
|
verbose: exports_typebox.Optional(exports_typebox.Boolean({
|
|
169129
169427
|
description: "With start/end: list each message separately with its ordinal [N] and per-part preview, so you can recover one in full by ordinal."
|
|
169130
169428
|
})),
|
|
169131
169429
|
message: exports_typebox.Optional(exports_typebox.Number({
|
|
169132
|
-
description: "Full untruncated recovery of ONE message by its ordinal (text + every tool call's
|
|
169430
|
+
description: "Full untruncated recovery of ONE message by its ordinal (every text part + every tool call's complete input/output). Use an ordinal from a compartment, ctx_search hit, or verbose range. Recovers a tool output you dropped with ctx_reduce."
|
|
169133
169431
|
}))
|
|
169134
169432
|
});
|
|
169135
169433
|
function ok(text) {
|
|
@@ -169606,25 +169904,33 @@ function getMemoryCountsByStatusStatement(db) {
|
|
|
169606
169904
|
}
|
|
169607
169905
|
return stmt;
|
|
169608
169906
|
}
|
|
169609
|
-
function
|
|
169610
|
-
const now = Date.now();
|
|
169611
|
-
const normalizedHash = computeNormalizedHash(input.content);
|
|
169907
|
+
function buildInsertMemoryValues(input, normalizedHash, now, includeImportance) {
|
|
169612
169908
|
const insertValues = [
|
|
169613
169909
|
input.projectPath,
|
|
169614
169910
|
input.category,
|
|
169615
169911
|
input.content,
|
|
169616
169912
|
normalizedHash
|
|
169617
169913
|
];
|
|
169618
|
-
if (
|
|
169914
|
+
if (includeImportance) {
|
|
169619
169915
|
insertValues.push(input.importance ?? 50);
|
|
169620
169916
|
}
|
|
169621
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);
|
|
169622
|
-
|
|
169623
|
-
|
|
169624
|
-
|
|
169918
|
+
return insertValues;
|
|
169919
|
+
}
|
|
169920
|
+
function loadInsertedMemory(db, rowid) {
|
|
169921
|
+
const inserted = getMemoryById(db, Number(rowid));
|
|
169625
169922
|
if (!inserted) {
|
|
169626
169923
|
throw new Error("Failed to load inserted memory row");
|
|
169627
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);
|
|
169628
169934
|
invalidateProject(input.projectPath);
|
|
169629
169935
|
return inserted;
|
|
169630
169936
|
}
|
|
@@ -170105,9 +170411,11 @@ import path4 from "node:path";
|
|
|
170105
170411
|
import { promisify } from "node:util";
|
|
170106
170412
|
var execFileAsync = promisify(execFile);
|
|
170107
170413
|
var GIT_TIMEOUT_MS2 = 1e4;
|
|
170414
|
+
var defaultExecFileForVerificationPaths = async (file2, args, options) => await execFileAsync(file2, [...args], options);
|
|
170415
|
+
var execFileForVerificationPaths = defaultExecFileForVerificationPaths;
|
|
170108
170416
|
async function runGit(cwd, args) {
|
|
170109
170417
|
try {
|
|
170110
|
-
const result = await
|
|
170418
|
+
const result = await execFileForVerificationPaths("git", args, {
|
|
170111
170419
|
cwd,
|
|
170112
170420
|
timeout: GIT_TIMEOUT_MS2,
|
|
170113
170421
|
maxBuffer: 16 * 1024 * 1024,
|
|
@@ -170291,7 +170599,10 @@ async function normalizeVerificationFiles(args) {
|
|
|
170291
170599
|
var lastIndexedStatements = new WeakMap;
|
|
170292
170600
|
var insertMessageStatements = new WeakMap;
|
|
170293
170601
|
var upsertIndexStatements = new WeakMap;
|
|
170602
|
+
var upsertCleanIndexStatements = new WeakMap;
|
|
170603
|
+
var upsertDirtyFloorStatements = new WeakMap;
|
|
170294
170604
|
var deleteFtsStatements = new WeakMap;
|
|
170605
|
+
var deleteFtsFromOrdinalStatements = new WeakMap;
|
|
170295
170606
|
var deleteIndexStatements = new WeakMap;
|
|
170296
170607
|
var countIndexedMessageStatements = new WeakMap;
|
|
170297
170608
|
function normalizeIndexText(text) {
|
|
@@ -170300,7 +170611,7 @@ function normalizeIndexText(text) {
|
|
|
170300
170611
|
function getLastIndexedStatement(db) {
|
|
170301
170612
|
let stmt = lastIndexedStatements.get(db);
|
|
170302
170613
|
if (!stmt) {
|
|
170303
|
-
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 = ?");
|
|
170304
170615
|
lastIndexedStatements.set(db, stmt);
|
|
170305
170616
|
}
|
|
170306
170617
|
return stmt;
|
|
@@ -170321,6 +170632,22 @@ function getUpsertIndexStatement(db) {
|
|
|
170321
170632
|
}
|
|
170322
170633
|
return stmt;
|
|
170323
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
|
+
}
|
|
170324
170651
|
function getDeleteFtsStatement(db) {
|
|
170325
170652
|
let stmt = deleteFtsStatements.get(db);
|
|
170326
170653
|
if (!stmt) {
|
|
@@ -170329,6 +170656,14 @@ function getDeleteFtsStatement(db) {
|
|
|
170329
170656
|
}
|
|
170330
170657
|
return stmt;
|
|
170331
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
|
+
}
|
|
170332
170667
|
function getDeleteIndexStatement(db) {
|
|
170333
170668
|
let stmt = deleteIndexStatements.get(db);
|
|
170334
170669
|
if (!stmt) {
|
|
@@ -170349,6 +170684,14 @@ function getLastIndexedOrdinal(db, sessionId) {
|
|
|
170349
170684
|
const row = getLastIndexedStatement(db).get(sessionId);
|
|
170350
170685
|
return typeof row?.last_indexed_ordinal === "number" ? row.last_indexed_ordinal : 0;
|
|
170351
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
|
+
}
|
|
170352
170695
|
function isMessageAlreadyIndexed(db, sessionId, messageId) {
|
|
170353
170696
|
const row = getCountIndexedMessageStatement(db).get(sessionId, messageId);
|
|
170354
170697
|
return (typeof row?.count === "number" ? row.count : 0) > 0;
|
|
@@ -170416,7 +170759,15 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
|
|
|
170416
170759
|
db.exec("BEGIN IMMEDIATE");
|
|
170417
170760
|
let committed = false;
|
|
170418
170761
|
try {
|
|
170419
|
-
|
|
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
|
+
}
|
|
170420
170771
|
const insertMessage = getInsertMessageStatement(db);
|
|
170421
170772
|
for (const message of messages) {
|
|
170422
170773
|
if (message.ordinal <= effectiveWatermark) {
|
|
@@ -170433,7 +170784,7 @@ function indexMessagesAfterOrdinal(db, sessionId, messages, lastIndexedOrdinal,
|
|
|
170433
170784
|
inserted++;
|
|
170434
170785
|
}
|
|
170435
170786
|
const newWatermark = Math.max(effectiveWatermark, finalWatermark);
|
|
170436
|
-
|
|
170787
|
+
getUpsertCleanIndexStatement(db).run(sessionId, newWatermark, now, getHarness());
|
|
170437
170788
|
db.exec("COMMIT");
|
|
170438
170789
|
committed = true;
|
|
170439
170790
|
} finally {
|
|
@@ -171204,7 +171555,7 @@ function setChannel2NudgeState(db, sessionId, state) {
|
|
|
171204
171555
|
db.transaction(() => {
|
|
171205
171556
|
ensureSessionMetaRow(db, sessionId);
|
|
171206
171557
|
const claimedAt = state === "claimed" ? Date.now() : 0;
|
|
171207
|
-
db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at =
|
|
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);
|
|
171208
171559
|
})();
|
|
171209
171560
|
}
|
|
171210
171561
|
function casChannel2NudgeState(db, sessionId, from, to) {
|
|
@@ -171212,7 +171563,7 @@ function casChannel2NudgeState(db, sessionId, from, to) {
|
|
|
171212
171563
|
db.transaction(() => {
|
|
171213
171564
|
ensureSessionMetaRow(db, sessionId);
|
|
171214
171565
|
const claimedAt = to === "claimed" ? Date.now() : 0;
|
|
171215
|
-
const result = db.prepare("UPDATE session_meta SET channel2_nudge_state = ?, channel2_nudge_claimed_at =
|
|
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);
|
|
171216
171567
|
changed = (result.changes ?? 0) > 0;
|
|
171217
171568
|
})();
|
|
171218
171569
|
return changed;
|
|
@@ -171620,6 +171971,13 @@ function setSessionWorkMetrics(db, sessionId, newWorkTokens, totalInputTokens) {
|
|
|
171620
171971
|
SET new_work_tokens = ?, total_input_tokens = ?
|
|
171621
171972
|
WHERE session_id = ?`).run(Math.max(0, Math.floor(newWorkTokens)), Math.max(0, Math.floor(totalInputTokens)), sessionId);
|
|
171622
171973
|
}
|
|
171974
|
+
function getSessionWorkMetrics(db, sessionId) {
|
|
171975
|
+
const row = db.prepare("SELECT new_work_tokens, total_input_tokens FROM session_meta WHERE session_id = ?").get(sessionId);
|
|
171976
|
+
return {
|
|
171977
|
+
newWorkTokens: typeof row?.new_work_tokens === "number" ? row.new_work_tokens : 0,
|
|
171978
|
+
totalInputTokens: typeof row?.total_input_tokens === "number" ? row.total_input_tokens : 0
|
|
171979
|
+
};
|
|
171980
|
+
}
|
|
171623
171981
|
// ../plugin/src/features/magic-context/storage-meta-session.ts
|
|
171624
171982
|
import { Buffer as Buffer3 } from "node:buffer";
|
|
171625
171983
|
|
|
@@ -172941,25 +173299,45 @@ function formatMemoryList(memories) {
|
|
|
172941
173299
|
id: String(m.id),
|
|
172942
173300
|
category: m.category,
|
|
172943
173301
|
status: m.status,
|
|
173302
|
+
verification: m.verificationStatus,
|
|
172944
173303
|
updated: new Date(m.updatedAt).toISOString(),
|
|
172945
173304
|
content: m.content.replace(/\s+/g, " ").trim()
|
|
172946
173305
|
}));
|
|
173306
|
+
const headers = {
|
|
173307
|
+
id: "ID",
|
|
173308
|
+
category: "CATEGORY",
|
|
173309
|
+
status: "STATUS",
|
|
173310
|
+
verification: "VERIFY",
|
|
173311
|
+
updated: "UPDATED",
|
|
173312
|
+
content: "CONTENT"
|
|
173313
|
+
};
|
|
172947
173314
|
const widths = {
|
|
172948
|
-
id: Math.max(
|
|
172949
|
-
category: Math.max(
|
|
172950
|
-
status: Math.max(
|
|
172951
|
-
|
|
173315
|
+
id: Math.max(headers.id.length, ...rows.map((r) => r.id.length)),
|
|
173316
|
+
category: Math.max(headers.category.length, ...rows.map((r) => r.category.length)),
|
|
173317
|
+
status: Math.max(headers.status.length, ...rows.map((r) => r.status.length)),
|
|
173318
|
+
verification: Math.max(headers.verification.length, ...rows.map((r) => r.verification.length)),
|
|
173319
|
+
updated: Math.max(headers.updated.length, ...rows.map((r) => r.updated.length))
|
|
172952
173320
|
};
|
|
172953
173321
|
const fmt = (r) => [
|
|
172954
173322
|
r.id.padEnd(widths.id),
|
|
172955
173323
|
r.category.padEnd(widths.category),
|
|
172956
173324
|
r.status.padEnd(widths.status),
|
|
173325
|
+
r.verification.padEnd(widths.verification),
|
|
172957
173326
|
r.updated.padEnd(widths.updated),
|
|
172958
173327
|
r.content
|
|
172959
173328
|
].join(" | ");
|
|
172960
173329
|
return [
|
|
172961
173330
|
`Found ${rows.length} active ${rows.length === 1 ? "memory" : "memories"}:`,
|
|
172962
173331
|
"",
|
|
173332
|
+
fmt(headers),
|
|
173333
|
+
[
|
|
173334
|
+
"-".repeat(widths.id),
|
|
173335
|
+
"-".repeat(widths.category),
|
|
173336
|
+
"-".repeat(widths.status),
|
|
173337
|
+
"-".repeat(widths.verification),
|
|
173338
|
+
"-".repeat(widths.updated),
|
|
173339
|
+
"-------"
|
|
173340
|
+
].join("-+-"),
|
|
172963
173341
|
...rows.map(fmt)
|
|
172964
173342
|
].join(`
|
|
172965
173343
|
`);
|
|
@@ -173029,8 +173407,9 @@ function createCtxMemoryTool(deps) {
|
|
|
173029
173407
|
const isOwn = targetIdentityForStoredPath(memory2.projectPath) === projectIdentity;
|
|
173030
173408
|
if (isOwn)
|
|
173031
173409
|
return true;
|
|
173032
|
-
return toolShareCategories
|
|
173410
|
+
return toolShareCategories?.includes(memory2.category) ?? false;
|
|
173033
173411
|
};
|
|
173412
|
+
const memoryOwnedByTool = (memory2) => workspaceIdentitySet.identities.length > 1 ? targetIdentityForStoredPath(memory2.projectPath) === projectIdentity : storedPathBelongsToIdentity(memory2.projectPath, projectIdentity);
|
|
173034
173413
|
const snapshot = getProjectEmbeddingSnapshot(projectIdentity);
|
|
173035
173414
|
if (snapshot ? !snapshot.features.memoryEnabled : deps.memoryEnabled === false) {
|
|
173036
173415
|
return err2("Cross-session memory is disabled for this project.");
|
|
@@ -173077,7 +173456,8 @@ function createCtxMemoryTool(deps) {
|
|
|
173077
173456
|
return err2("Error: 'content' is required when action is 'update'.");
|
|
173078
173457
|
}
|
|
173079
173458
|
const memory2 = getMemoryById(deps.db, updateId);
|
|
173080
|
-
|
|
173459
|
+
const updateAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
|
|
173460
|
+
if (!memory2 || !updateAllowed) {
|
|
173081
173461
|
return err2(`Error: Memory with ID ${updateId} was not found.`);
|
|
173082
173462
|
}
|
|
173083
173463
|
if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
|
|
@@ -173124,7 +173504,7 @@ function createCtxMemoryTool(deps) {
|
|
|
173124
173504
|
return err2("Error: One or more source memories were not found.");
|
|
173125
173505
|
}
|
|
173126
173506
|
if (!dreamerAllowed) {
|
|
173127
|
-
const foreign = sourceMemories.find((memory2) => !
|
|
173507
|
+
const foreign = sourceMemories.find((memory2) => !memoryOwnedByTool(memory2));
|
|
173128
173508
|
if (foreign) {
|
|
173129
173509
|
return err2(`Error: Memory with ID ${foreign.id} was not found.`);
|
|
173130
173510
|
}
|
|
@@ -173226,7 +173606,8 @@ function createCtxMemoryTool(deps) {
|
|
|
173226
173606
|
const archiveIds = [...new Set(rawArchiveIds)];
|
|
173227
173607
|
for (const memoryId of archiveIds) {
|
|
173228
173608
|
const memory2 = getMemoryById(deps.db, memoryId);
|
|
173229
|
-
|
|
173609
|
+
const archiveAllowed = memory2 ? dreamerAllowed ? memoryVisibleToTool(memory2) : memoryOwnedByTool(memory2) : false;
|
|
173610
|
+
if (!memory2 || !archiveAllowed) {
|
|
173230
173611
|
return err2(`Error: Memory with ID ${memoryId} was not found.`);
|
|
173231
173612
|
}
|
|
173232
173613
|
if (!dreamerAllowed && !isPrimaryMutableMemory(memory2)) {
|
|
@@ -173365,6 +173746,7 @@ function createCtxNoteTool(deps) {
|
|
|
173365
173746
|
parameters: ParamsSchema3,
|
|
173366
173747
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
173367
173748
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
173749
|
+
const dreamerEnabled = deps.resolveDreamerEnabled?.(ctx) ?? deps.dreamerEnabled;
|
|
173368
173750
|
const action2 = params.action ?? (params.content?.trim() ? "write" : "read");
|
|
173369
173751
|
if (action2 === "write") {
|
|
173370
173752
|
const content = params.content?.trim();
|
|
@@ -173373,7 +173755,7 @@ function createCtxNoteTool(deps) {
|
|
|
173373
173755
|
const anchorOrdinal = captureAnchorOrdinal(deps.db, sessionId);
|
|
173374
173756
|
const surfaceCondition = params.surface_condition?.trim();
|
|
173375
173757
|
if (surfaceCondition) {
|
|
173376
|
-
if (
|
|
173758
|
+
if (dreamerEnabled !== true) {
|
|
173377
173759
|
return err3("Error: Smart notes require dreamer to be enabled. Enable dreamer in magic-context.jsonc to use surface_condition.");
|
|
173378
173760
|
}
|
|
173379
173761
|
const projectIdentity = resolveProjectIdentity(ctx.cwd);
|
|
@@ -173382,6 +173764,7 @@ function createCtxNoteTool(deps) {
|
|
|
173382
173764
|
}
|
|
173383
173765
|
const note2 = addNote(deps.db, "smart", {
|
|
173384
173766
|
content,
|
|
173767
|
+
sessionId,
|
|
173385
173768
|
projectPath: projectIdentity,
|
|
173386
173769
|
surfaceCondition,
|
|
173387
173770
|
anchorOrdinal
|
|
@@ -173633,6 +174016,7 @@ function createCtxReduceTool(deps) {
|
|
|
173633
174016
|
parameters: ParamsSchema4,
|
|
173634
174017
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
173635
174018
|
const sessionId = ctx.sessionManager.getSessionId();
|
|
174019
|
+
const protectedTags = Math.max(0, Math.floor(deps.resolveProtectedTags?.(ctx) ?? deps.protectedTags));
|
|
173636
174020
|
if (!params.drop) {
|
|
173637
174021
|
return err4("Error: 'drop' must be provided.");
|
|
173638
174022
|
}
|
|
@@ -173650,7 +174034,7 @@ function createCtxReduceTool(deps) {
|
|
|
173650
174034
|
return err4(`Error: Unknown tag(s) ${formatIds(unknownIds)}. Check available tags in conversation.`);
|
|
173651
174035
|
}
|
|
173652
174036
|
const activeTags = allTags.filter((tag) => tag.status === "active");
|
|
173653
|
-
const protectedTagIds = activeTags.map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0,
|
|
174037
|
+
const protectedTagIds = activeTags.map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, protectedTags);
|
|
173654
174038
|
const protectedSet = new Set(protectedTagIds);
|
|
173655
174039
|
const tagStatusMap = new Map(allTags.map((tag) => [tag.tagNumber, tag.status]));
|
|
173656
174040
|
const pendingOps = getPendingOps(deps.db, sessionId);
|
|
@@ -174176,24 +174560,46 @@ function getMessageSearchStatementWithCutoff(db) {
|
|
|
174176
174560
|
return stmt;
|
|
174177
174561
|
}
|
|
174178
174562
|
var ftsRowCountStatements = new WeakMap;
|
|
174563
|
+
var ftsRowCountStatementsWithCutoff = new WeakMap;
|
|
174179
174564
|
var ftsMatchCountStatements = new WeakMap;
|
|
174180
|
-
|
|
174181
|
-
|
|
174182
|
-
|
|
174183
|
-
|
|
174184
|
-
|
|
174185
|
-
|
|
174186
|
-
|
|
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);
|
|
174187
174583
|
return typeof row?.n === "number" ? row.n : 0;
|
|
174188
174584
|
}
|
|
174189
|
-
function countSessionFtsMatches(db, sessionId, ftsQuery) {
|
|
174190
|
-
|
|
174191
|
-
|
|
174192
|
-
|
|
174193
|
-
|
|
174194
|
-
|
|
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
|
+
})();
|
|
174195
174601
|
try {
|
|
174196
|
-
const row = stmt.get(sessionId, ftsQuery);
|
|
174602
|
+
const row = cutoff === null ? stmt.get(sessionId, ftsQuery) : stmt.get(sessionId, ftsQuery, cutoff);
|
|
174197
174603
|
return typeof row?.n === "number" ? row.n : 0;
|
|
174198
174604
|
} catch {
|
|
174199
174605
|
return 0;
|
|
@@ -174443,7 +174849,7 @@ function searchMessages(args) {
|
|
|
174443
174849
|
role: row.role
|
|
174444
174850
|
}));
|
|
174445
174851
|
}
|
|
174446
|
-
const corpusSize = getSessionFtsRowCount(args.db, args.sessionId);
|
|
174852
|
+
const corpusSize = getSessionFtsRowCount(args.db, args.sessionId, cutoff);
|
|
174447
174853
|
const queryLists = [];
|
|
174448
174854
|
if (baseQuery.length > 0) {
|
|
174449
174855
|
queryLists.push({
|
|
@@ -174456,7 +174862,7 @@ function searchMessages(args) {
|
|
|
174456
174862
|
const probeQuery = sanitizeFtsQuery(probe);
|
|
174457
174863
|
if (probeQuery.length === 0)
|
|
174458
174864
|
continue;
|
|
174459
|
-
const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery);
|
|
174865
|
+
const df = countSessionFtsMatches(args.db, args.sessionId, probeQuery, cutoff);
|
|
174460
174866
|
const weight = probeDiscriminationWeight(df, corpusSize);
|
|
174461
174867
|
probeWeights.set(probe, weight);
|
|
174462
174868
|
queryLists.push({
|
|
@@ -175627,18 +176033,539 @@ function createCtxSearchTool(deps) {
|
|
|
175627
176033
|
};
|
|
175628
176034
|
}
|
|
175629
176035
|
|
|
175630
|
-
// src/tools/
|
|
175631
|
-
|
|
175632
|
-
|
|
175633
|
-
|
|
175634
|
-
|
|
175635
|
-
|
|
176036
|
+
// src/tools/todo-view-pi.ts
|
|
176037
|
+
import {
|
|
176038
|
+
truncateToWidth
|
|
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
|
|
175636
176060
|
];
|
|
175637
|
-
var
|
|
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
|
|
176149
|
+
var TODO_TOOL_NAME = "todowrite";
|
|
176150
|
+
var TODOS_COMMAND_NAME = "todos";
|
|
176151
|
+
var WIDGET_KEY = "magic-context-todos";
|
|
176152
|
+
var MAX_TODO_CONTENT_ROWS = 12;
|
|
176153
|
+
var STATUS_GLYPH = {
|
|
176154
|
+
pending: "○",
|
|
176155
|
+
in_progress: "◐",
|
|
176156
|
+
completed: "✓",
|
|
176157
|
+
cancelled: "✗"
|
|
176158
|
+
};
|
|
176159
|
+
var STATUS_COLOR = {
|
|
176160
|
+
pending: "dim",
|
|
176161
|
+
in_progress: "warning",
|
|
176162
|
+
completed: "success",
|
|
176163
|
+
cancelled: "error"
|
|
176164
|
+
};
|
|
176165
|
+
var snapshotsBySession = new Map;
|
|
176166
|
+
function isTodoStatus2(value) {
|
|
176167
|
+
return TODO_STATUSES.includes(value);
|
|
176168
|
+
}
|
|
176169
|
+
function isTodoPriority2(value) {
|
|
176170
|
+
return TODO_PRIORITIES.includes(value);
|
|
176171
|
+
}
|
|
176172
|
+
function cloneTodos(todos) {
|
|
176173
|
+
return todos.map((todo) => ({ ...todo }));
|
|
176174
|
+
}
|
|
176175
|
+
function parseTodos(input) {
|
|
176176
|
+
if (!Array.isArray(input))
|
|
176177
|
+
return null;
|
|
176178
|
+
const todos = [];
|
|
176179
|
+
for (const item of input) {
|
|
176180
|
+
if (item === null || typeof item !== "object")
|
|
176181
|
+
return null;
|
|
176182
|
+
const raw = item;
|
|
176183
|
+
if (typeof raw.content !== "string" || !isTodoStatus2(raw.status)) {
|
|
176184
|
+
return null;
|
|
176185
|
+
}
|
|
176186
|
+
const todo = {
|
|
176187
|
+
content: raw.content,
|
|
176188
|
+
status: raw.status
|
|
176189
|
+
};
|
|
176190
|
+
if (isTodoPriority2(raw.priority))
|
|
176191
|
+
todo.priority = raw.priority;
|
|
176192
|
+
if (typeof raw.id === "string" && raw.id.length > 0)
|
|
176193
|
+
todo.id = raw.id;
|
|
176194
|
+
todos.push(todo);
|
|
176195
|
+
}
|
|
176196
|
+
return todos;
|
|
176197
|
+
}
|
|
176198
|
+
function parseTodoStateJson(stateJson) {
|
|
176199
|
+
if (!stateJson)
|
|
176200
|
+
return null;
|
|
176201
|
+
try {
|
|
176202
|
+
return parseTodos(JSON.parse(stateJson));
|
|
176203
|
+
} catch {
|
|
176204
|
+
return null;
|
|
176205
|
+
}
|
|
176206
|
+
}
|
|
176207
|
+
function setTodoSnapshot(sessionId, todos) {
|
|
176208
|
+
const parsed = parseTodos(todos);
|
|
176209
|
+
if (parsed === null)
|
|
176210
|
+
return false;
|
|
176211
|
+
snapshotsBySession.set(sessionId, { todos: parsed });
|
|
176212
|
+
return true;
|
|
176213
|
+
}
|
|
176214
|
+
function seedTodoSnapshotFromStateJson(sessionId, stateJson) {
|
|
176215
|
+
const parsed = parseTodoStateJson(stateJson);
|
|
176216
|
+
if (parsed === null) {
|
|
176217
|
+
snapshotsBySession.delete(sessionId);
|
|
176218
|
+
return false;
|
|
176219
|
+
}
|
|
176220
|
+
snapshotsBySession.set(sessionId, { todos: parsed });
|
|
176221
|
+
return true;
|
|
176222
|
+
}
|
|
176223
|
+
function getTodoSnapshot(sessionId) {
|
|
176224
|
+
if (!sessionId)
|
|
176225
|
+
return { todos: [] };
|
|
176226
|
+
const snapshot = snapshotsBySession.get(sessionId);
|
|
176227
|
+
return { todos: snapshot ? cloneTodos(snapshot.todos) : [] };
|
|
176228
|
+
}
|
|
176229
|
+
function clearTodoSnapshot(sessionId) {
|
|
176230
|
+
snapshotsBySession.delete(sessionId);
|
|
176231
|
+
}
|
|
176232
|
+
function getSessionId(ctx) {
|
|
176233
|
+
try {
|
|
176234
|
+
const id = ctx.sessionManager?.getSessionId?.();
|
|
176235
|
+
return typeof id === "string" && id.length > 0 ? id : undefined;
|
|
176236
|
+
} catch {
|
|
176237
|
+
return;
|
|
176238
|
+
}
|
|
176239
|
+
}
|
|
176240
|
+
function countTodos(todos) {
|
|
176241
|
+
const counts = {
|
|
176242
|
+
total: todos.length,
|
|
176243
|
+
pending: 0,
|
|
176244
|
+
inProgress: 0,
|
|
176245
|
+
completed: 0,
|
|
176246
|
+
cancelled: 0
|
|
176247
|
+
};
|
|
176248
|
+
for (const todo of todos) {
|
|
176249
|
+
switch (todo.status) {
|
|
176250
|
+
case "pending":
|
|
176251
|
+
counts.pending++;
|
|
176252
|
+
break;
|
|
176253
|
+
case "in_progress":
|
|
176254
|
+
counts.inProgress++;
|
|
176255
|
+
break;
|
|
176256
|
+
case "completed":
|
|
176257
|
+
counts.completed++;
|
|
176258
|
+
break;
|
|
176259
|
+
case "cancelled":
|
|
176260
|
+
counts.cancelled++;
|
|
176261
|
+
break;
|
|
176262
|
+
}
|
|
176263
|
+
}
|
|
176264
|
+
return counts;
|
|
176265
|
+
}
|
|
176266
|
+
function activeTitleCount(todos) {
|
|
176267
|
+
return todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
|
|
176268
|
+
}
|
|
176269
|
+
function formatCounts(counts) {
|
|
176270
|
+
if (counts.total === 0)
|
|
176271
|
+
return "No todos";
|
|
176272
|
+
const parts = [`${counts.completed}/${counts.total} completed`];
|
|
176273
|
+
if (counts.inProgress > 0)
|
|
176274
|
+
parts.push(`${counts.inProgress} in progress`);
|
|
176275
|
+
if (counts.pending > 0)
|
|
176276
|
+
parts.push(`${counts.pending} pending`);
|
|
176277
|
+
if (counts.cancelled > 0)
|
|
176278
|
+
parts.push(`${counts.cancelled} cancelled`);
|
|
176279
|
+
return parts.join(" · ");
|
|
176280
|
+
}
|
|
176281
|
+
function formatTodoLine(todo, theme, options = {}) {
|
|
176282
|
+
const glyph = theme.fg(STATUS_COLOR[todo.status], STATUS_GLYPH[todo.status]);
|
|
176283
|
+
const id = options.showId && todo.id ? `${theme.fg("accent", `#${todo.id}`)} ` : "";
|
|
176284
|
+
const color = todo.status === "pending" || todo.status === "in_progress" ? "text" : "dim";
|
|
176285
|
+
let content = theme.fg(color, todo.content);
|
|
176286
|
+
if (todo.status === "completed" || todo.status === "cancelled") {
|
|
176287
|
+
content = theme.strikethrough(content);
|
|
176288
|
+
}
|
|
176289
|
+
return `${glyph} ${id}${content}`;
|
|
176290
|
+
}
|
|
176291
|
+
function formatCommandLine(todo) {
|
|
176292
|
+
const id = todo.id ? `#${todo.id} ` : "";
|
|
176293
|
+
return ` ${STATUS_GLYPH[todo.status]} ${id}${todo.content}`;
|
|
176294
|
+
}
|
|
176295
|
+
function lineComponent(renderLines) {
|
|
176296
|
+
return {
|
|
176297
|
+
render: renderLines,
|
|
176298
|
+
invalidate() {}
|
|
176299
|
+
};
|
|
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
|
+
}
|
|
176311
|
+
function renderTodowriteCall(args, theme) {
|
|
176312
|
+
const todos = parseTodos(args.todos) ?? [];
|
|
176313
|
+
const active = activeTitleCount(todos);
|
|
176314
|
+
const activeColor = active > 0 ? "warning" : "success";
|
|
176315
|
+
const line = `${theme.fg("toolTitle", theme.bold("Todos"))} ${theme.fg("muted", "—")} ${theme.fg(activeColor, `${active} active`)}`;
|
|
176316
|
+
return lineComponent((width) => [truncateToWidth(line, width, "…")]);
|
|
176317
|
+
}
|
|
176318
|
+
function renderTodowriteResult(result, theme) {
|
|
176319
|
+
const detailsTodos = result.details?.todos;
|
|
176320
|
+
let todos = parseTodos(detailsTodos);
|
|
176321
|
+
if (todos === null && Array.isArray(result.content)) {
|
|
176322
|
+
const text = result.content.find((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string")?.text;
|
|
176323
|
+
if (text) {
|
|
176324
|
+
try {
|
|
176325
|
+
todos = parseTodos(JSON.parse(text));
|
|
176326
|
+
} catch {}
|
|
176327
|
+
}
|
|
176328
|
+
}
|
|
176329
|
+
const renderedTodos = todos ?? [];
|
|
176330
|
+
return lineComponent((width) => {
|
|
176331
|
+
if (renderedTodos.length === 0)
|
|
176332
|
+
return [theme.fg("dim", "No todos")];
|
|
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;
|
|
176339
|
+
});
|
|
176340
|
+
}
|
|
176341
|
+
function registerTodosCommand(pi) {
|
|
176342
|
+
pi.registerCommand(TODOS_COMMAND_NAME, {
|
|
176343
|
+
description: "Show the current Magic Context todo list",
|
|
176344
|
+
handler: async (_args, ctx) => {
|
|
176345
|
+
if (!ctx.hasUI) {
|
|
176346
|
+
ctx.ui.notify("The /todos command requires interactive UI.", "error");
|
|
176347
|
+
return;
|
|
176348
|
+
}
|
|
176349
|
+
const sessionId = getSessionId(ctx);
|
|
176350
|
+
const todos = getTodoSnapshot(sessionId).todos;
|
|
176351
|
+
if (todos.length === 0) {
|
|
176352
|
+
ctx.ui.notify("No todos yet.", "info");
|
|
176353
|
+
return;
|
|
176354
|
+
}
|
|
176355
|
+
const counts = countTodos(todos);
|
|
176356
|
+
const lines = [formatCounts(counts)];
|
|
176357
|
+
const groups = [
|
|
176358
|
+
["pending", "── Pending ──"],
|
|
176359
|
+
["in_progress", "── In Progress ──"],
|
|
176360
|
+
["completed", "── Completed ──"],
|
|
176361
|
+
["cancelled", "── Cancelled ──"]
|
|
176362
|
+
];
|
|
176363
|
+
for (const [status, heading] of groups) {
|
|
176364
|
+
const group = todos.filter((todo) => todo.status === status);
|
|
176365
|
+
if (group.length === 0)
|
|
176366
|
+
continue;
|
|
176367
|
+
lines.push(heading);
|
|
176368
|
+
const { visible, hiddenCount } = capTodoRows(group);
|
|
176369
|
+
for (const todo of visible)
|
|
176370
|
+
lines.push(formatCommandLine(todo));
|
|
176371
|
+
if (hiddenCount > 0)
|
|
176372
|
+
lines.push(` +${hiddenCount} more`);
|
|
176373
|
+
}
|
|
176374
|
+
ctx.ui.notify(lines.join(`
|
|
176375
|
+
`), "info");
|
|
176376
|
+
}
|
|
176377
|
+
});
|
|
176378
|
+
}
|
|
176379
|
+
function todoKey(todo, index) {
|
|
176380
|
+
return todo.id ? `id:${todo.id}` : `pos:${index}:${todo.content}`;
|
|
176381
|
+
}
|
|
176382
|
+
function isOverlayLive(todo) {
|
|
176383
|
+
return todo.status === "pending" || todo.status === "in_progress";
|
|
176384
|
+
}
|
|
176385
|
+
|
|
176386
|
+
class TodoOverlay {
|
|
176387
|
+
uiCtx;
|
|
176388
|
+
sessionId;
|
|
176389
|
+
widgetRegistered = false;
|
|
176390
|
+
tui;
|
|
176391
|
+
completedTaskIdsPendingHide = new Set;
|
|
176392
|
+
hiddenCompletedTaskIds = new Set;
|
|
176393
|
+
lastTodoKeys = new Set;
|
|
176394
|
+
setUICtx(sessionId, ctx) {
|
|
176395
|
+
if (ctx !== this.uiCtx || sessionId !== this.sessionId) {
|
|
176396
|
+
this.uiCtx = ctx;
|
|
176397
|
+
this.sessionId = sessionId;
|
|
176398
|
+
this.widgetRegistered = false;
|
|
176399
|
+
this.tui = undefined;
|
|
176400
|
+
this.resetCompletedDisplayState();
|
|
176401
|
+
}
|
|
176402
|
+
}
|
|
176403
|
+
update(sessionId = this.sessionId) {
|
|
176404
|
+
if (!this.uiCtx || !this.sessionId || sessionId !== this.sessionId)
|
|
176405
|
+
return;
|
|
176406
|
+
const todos = getTodoSnapshot(this.sessionId).todos;
|
|
176407
|
+
this.pruneCompletedDisplayState(todos);
|
|
176408
|
+
const visible = this.selectOverlayTodos(todos);
|
|
176409
|
+
if (visible.length === 0) {
|
|
176410
|
+
if (this.widgetRegistered) {
|
|
176411
|
+
this.uiCtx.setWidget(WIDGET_KEY, undefined);
|
|
176412
|
+
this.widgetRegistered = false;
|
|
176413
|
+
this.tui = undefined;
|
|
176414
|
+
}
|
|
176415
|
+
return;
|
|
176416
|
+
}
|
|
176417
|
+
if (!this.widgetRegistered) {
|
|
176418
|
+
this.uiCtx.setWidget(WIDGET_KEY, (tui, theme) => {
|
|
176419
|
+
this.tui = tui;
|
|
176420
|
+
return {
|
|
176421
|
+
render: (width) => this.renderWidget(theme, width),
|
|
176422
|
+
invalidate: () => {
|
|
176423
|
+
this.widgetRegistered = false;
|
|
176424
|
+
this.tui = undefined;
|
|
176425
|
+
}
|
|
176426
|
+
};
|
|
176427
|
+
}, { placement: "aboveEditor" });
|
|
176428
|
+
this.widgetRegistered = true;
|
|
176429
|
+
} else {
|
|
176430
|
+
this.tui?.requestRender();
|
|
176431
|
+
}
|
|
176432
|
+
}
|
|
176433
|
+
resetCompletedDisplayState() {
|
|
176434
|
+
this.completedTaskIdsPendingHide.clear();
|
|
176435
|
+
this.hiddenCompletedTaskIds.clear();
|
|
176436
|
+
this.lastTodoKeys.clear();
|
|
176437
|
+
}
|
|
176438
|
+
hideCompletedTasksFromPreviousTurn() {
|
|
176439
|
+
if (this.completedTaskIdsPendingHide.size === 0)
|
|
176440
|
+
return;
|
|
176441
|
+
for (const taskId of this.completedTaskIdsPendingHide) {
|
|
176442
|
+
this.hiddenCompletedTaskIds.add(taskId);
|
|
176443
|
+
}
|
|
176444
|
+
this.completedTaskIdsPendingHide.clear();
|
|
176445
|
+
this.update();
|
|
176446
|
+
}
|
|
176447
|
+
disposeSession(sessionId) {
|
|
176448
|
+
if (this.sessionId !== sessionId)
|
|
176449
|
+
return;
|
|
176450
|
+
this.dispose();
|
|
176451
|
+
}
|
|
176452
|
+
dispose() {
|
|
176453
|
+
if (this.uiCtx)
|
|
176454
|
+
this.uiCtx.setWidget(WIDGET_KEY, undefined);
|
|
176455
|
+
this.widgetRegistered = false;
|
|
176456
|
+
this.tui = undefined;
|
|
176457
|
+
this.uiCtx = undefined;
|
|
176458
|
+
this.sessionId = undefined;
|
|
176459
|
+
this.resetCompletedDisplayState();
|
|
176460
|
+
}
|
|
176461
|
+
pruneCompletedDisplayState(todos) {
|
|
176462
|
+
const currentKeys = new Set(todos.map((todo, index) => todoKey(todo, index)));
|
|
176463
|
+
const hasSharedKeys = [...currentKeys].some((key) => this.lastTodoKeys.has(key));
|
|
176464
|
+
if (this.lastTodoKeys.size > 0 && currentKeys.size > 0 && !hasSharedKeys) {
|
|
176465
|
+
this.resetCompletedDisplayState();
|
|
176466
|
+
}
|
|
176467
|
+
this.lastTodoKeys = currentKeys;
|
|
176468
|
+
const completedKeys = new Set(todos.map((todo, index) => ({ todo, key: todoKey(todo, index) })).filter(({ todo }) => todo.status === "completed").map(({ key }) => key));
|
|
176469
|
+
for (const taskId of this.completedTaskIdsPendingHide) {
|
|
176470
|
+
if (!completedKeys.has(taskId))
|
|
176471
|
+
this.completedTaskIdsPendingHide.delete(taskId);
|
|
176472
|
+
}
|
|
176473
|
+
for (const taskId of this.hiddenCompletedTaskIds) {
|
|
176474
|
+
if (!completedKeys.has(taskId))
|
|
176475
|
+
this.hiddenCompletedTaskIds.delete(taskId);
|
|
176476
|
+
}
|
|
176477
|
+
}
|
|
176478
|
+
selectOverlayTodos(todos) {
|
|
176479
|
+
return todos.map((todo, index) => ({ todo, key: todoKey(todo, index) })).filter(({ todo, key }) => {
|
|
176480
|
+
if (isOverlayLive(todo))
|
|
176481
|
+
return true;
|
|
176482
|
+
return todo.status === "completed" && !this.hiddenCompletedTaskIds.has(key);
|
|
176483
|
+
});
|
|
176484
|
+
}
|
|
176485
|
+
renderWidget(theme, width) {
|
|
176486
|
+
if (!this.sessionId)
|
|
176487
|
+
return [];
|
|
176488
|
+
const todos = getTodoSnapshot(this.sessionId).todos;
|
|
176489
|
+
this.pruneCompletedDisplayState(todos);
|
|
176490
|
+
const overlayTodos = this.selectOverlayTodos(todos);
|
|
176491
|
+
if (overlayTodos.length === 0)
|
|
176492
|
+
return [];
|
|
176493
|
+
const counts = countTodos(todos);
|
|
176494
|
+
const hasInProgress = counts.inProgress > 0;
|
|
176495
|
+
const headingColor = hasInProgress ? "accent" : "dim";
|
|
176496
|
+
const headingIcon = hasInProgress ? "●" : "○";
|
|
176497
|
+
const heading = `${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, "Todos")} ${theme.fg("muted", "—")} ${theme.fg("dim", formatCounts(counts))}`;
|
|
176498
|
+
const truncate2 = (line) => truncateToWidth(line, width, "…");
|
|
176499
|
+
const lines = [truncate2(heading)];
|
|
176500
|
+
const { visible, hiddenCount } = capTodoRows(overlayTodos);
|
|
176501
|
+
for (const [index, { todo, key }] of visible.entries()) {
|
|
176502
|
+
const isLast = index === visible.length - 1 && hiddenCount === 0;
|
|
176503
|
+
const branch = theme.fg("dim", isLast ? "└─" : "├─");
|
|
176504
|
+
lines.push(truncate2(`${branch} ${formatTodoLine(todo, theme, { showId: true })}`));
|
|
176505
|
+
if (todo.status === "completed")
|
|
176506
|
+
this.completedTaskIdsPendingHide.add(key);
|
|
176507
|
+
}
|
|
176508
|
+
if (hiddenCount > 0) {
|
|
176509
|
+
lines.push(truncate2(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${hiddenCount} more`)}`));
|
|
176510
|
+
}
|
|
176511
|
+
return lines;
|
|
176512
|
+
}
|
|
176513
|
+
}
|
|
176514
|
+
function registerTodoOverlay(pi, deps) {
|
|
176515
|
+
const overlay = new TodoOverlay;
|
|
176516
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
176517
|
+
const sessionId = getSessionId(ctx);
|
|
176518
|
+
if (!sessionId)
|
|
176519
|
+
return;
|
|
176520
|
+
seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
|
|
176521
|
+
if (!ctx.hasUI)
|
|
176522
|
+
return;
|
|
176523
|
+
overlay.setUICtx(sessionId, ctx.ui);
|
|
176524
|
+
overlay.update(sessionId);
|
|
176525
|
+
});
|
|
176526
|
+
pi.on("agent_start", async () => {
|
|
176527
|
+
overlay.hideCompletedTasksFromPreviousTurn();
|
|
176528
|
+
});
|
|
176529
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
176530
|
+
const sessionId = getSessionId(ctx);
|
|
176531
|
+
if (!sessionId)
|
|
176532
|
+
return;
|
|
176533
|
+
clearTodoSnapshot(sessionId);
|
|
176534
|
+
overlay.disposeSession(sessionId);
|
|
176535
|
+
});
|
|
176536
|
+
pi.on("session_before_switch", async (_event, ctx) => {
|
|
176537
|
+
const sessionId = getSessionId(ctx);
|
|
176538
|
+
if (!sessionId)
|
|
176539
|
+
return;
|
|
176540
|
+
clearTodoSnapshot(sessionId);
|
|
176541
|
+
overlay.disposeSession(sessionId);
|
|
176542
|
+
});
|
|
176543
|
+
return overlay;
|
|
176544
|
+
}
|
|
176545
|
+
function registerTodoStateLifecycle(pi, deps) {
|
|
176546
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
176547
|
+
const sessionId = getSessionId(ctx);
|
|
176548
|
+
if (!sessionId)
|
|
176549
|
+
return;
|
|
176550
|
+
seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
|
|
176551
|
+
});
|
|
176552
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
176553
|
+
const sessionId = getSessionId(ctx);
|
|
176554
|
+
if (sessionId)
|
|
176555
|
+
clearTodoSnapshot(sessionId);
|
|
176556
|
+
});
|
|
176557
|
+
pi.on("session_before_switch", async (_event, ctx) => {
|
|
176558
|
+
const sessionId = getSessionId(ctx);
|
|
176559
|
+
if (sessionId)
|
|
176560
|
+
clearTodoSnapshot(sessionId);
|
|
176561
|
+
});
|
|
176562
|
+
}
|
|
176563
|
+
|
|
176564
|
+
// src/tools/todowrite.ts
|
|
175638
176565
|
var TodoItem = exports_typebox.Object({
|
|
175639
176566
|
content: exports_typebox.String({ description: "Brief description of the task" }),
|
|
175640
|
-
status: exports_typebox.Union(
|
|
175641
|
-
priority: exports_typebox.Optional(exports_typebox.Union(
|
|
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)))),
|
|
175642
176569
|
id: exports_typebox.Optional(exports_typebox.String({ description: "Optional stable id for the todo" }))
|
|
175643
176570
|
});
|
|
175644
176571
|
var TodowriteParams = exports_typebox.Object({
|
|
@@ -175646,34 +176573,24 @@ var TodowriteParams = exports_typebox.Object({
|
|
|
175646
176573
|
description: "Replace the current task list with this complete set of todos. Include every task you intend to track this turn — pending, in_progress, completed, or cancelled — because the list overwrites previous state."
|
|
175647
176574
|
})
|
|
175648
176575
|
});
|
|
175649
|
-
var
|
|
175650
|
-
|
|
175651
|
-
"",
|
|
175652
|
-
"
|
|
175653
|
-
"mark
|
|
175654
|
-
"
|
|
175655
|
-
|
|
175656
|
-
"",
|
|
175657
|
-
"Task states:",
|
|
175658
|
-
" - pending: not started yet",
|
|
175659
|
-
" - in_progress: currently working on (limit to ONE task at a time)",
|
|
175660
|
-
" - completed: finished successfully",
|
|
175661
|
-
" - cancelled: no longer needed",
|
|
175662
|
-
"",
|
|
175663
|
-
"Use this tool proactively for non-trivial work spanning 3+ steps.",
|
|
175664
|
-
"Skip it for single-shot answers or trivial 1-2 step tasks."
|
|
175665
|
-
].join(`
|
|
175666
|
-
`);
|
|
176576
|
+
var PROMPT_SNIPPET = "Manage a task list to track multi-step progress";
|
|
176577
|
+
var PROMPT_GUIDELINES = [
|
|
176578
|
+
"Use `todowrite` for non-trivial work spanning 3+ steps, when the user gives you multiple tasks, or when you need to track progress across a verify/fix loop. Skip it for single-shot answers or trivial one-step work.",
|
|
176579
|
+
"Pass the COMPLETE updated todo list every time. This tool replaces the prior list rather than appending to it, so include pending, in_progress, completed, and cancelled tasks that should remain visible.",
|
|
176580
|
+
"When starting a task, mark exactly one todo `in_progress` before doing the work. Mark items `completed` immediately when done; use `cancelled` only for work that is no longer needed.",
|
|
176581
|
+
"Never mark a todo completed if verification is failing, implementation is partial, or an unresolved blocker remains. Keep it `in_progress` and add or update a todo for the blocker instead."
|
|
176582
|
+
];
|
|
175667
176583
|
function createTodowriteTool() {
|
|
175668
176584
|
return {
|
|
175669
|
-
name:
|
|
176585
|
+
name: TODO_TOOL_NAME,
|
|
175670
176586
|
label: "Todos",
|
|
175671
|
-
description:
|
|
176587
|
+
description: "Manage the session task list.",
|
|
176588
|
+
promptSnippet: PROMPT_SNIPPET,
|
|
176589
|
+
promptGuidelines: PROMPT_GUIDELINES,
|
|
175672
176590
|
parameters: TodowriteParams,
|
|
175673
176591
|
async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
|
|
175674
176592
|
const todos = params.todos ?? [];
|
|
175675
|
-
const
|
|
175676
|
-
const active = todos.length - completed;
|
|
176593
|
+
const active = todos.filter((todo) => !TITLE_DONE_STATUSES.has(todo.status)).length;
|
|
175677
176594
|
return {
|
|
175678
176595
|
content: [
|
|
175679
176596
|
{
|
|
@@ -175687,6 +176604,12 @@ function createTodowriteTool() {
|
|
|
175687
176604
|
truncated: false
|
|
175688
176605
|
}
|
|
175689
176606
|
};
|
|
176607
|
+
},
|
|
176608
|
+
renderCall(args, theme) {
|
|
176609
|
+
return renderTodowriteCall(args, theme);
|
|
176610
|
+
},
|
|
176611
|
+
renderResult(result, _opts, theme) {
|
|
176612
|
+
return renderTodowriteResult(result, theme);
|
|
175690
176613
|
}
|
|
175691
176614
|
};
|
|
175692
176615
|
}
|
|
@@ -175712,15 +176635,22 @@ function registerMagicContextTools(pi, opts) {
|
|
|
175712
176635
|
if (!opts.sessionScopedToolsDisabled) {
|
|
175713
176636
|
pi.registerTool(createCtxNoteTool({
|
|
175714
176637
|
db: opts.db,
|
|
175715
|
-
dreamerEnabled: opts.dreamerEnabled ?? false
|
|
176638
|
+
dreamerEnabled: opts.dreamerEnabled ?? false,
|
|
176639
|
+
resolveDreamerEnabled: opts.resolveDreamerEnabled
|
|
175716
176640
|
}));
|
|
175717
176641
|
pi.registerTool(createCtxExpandTool({ db: opts.db }));
|
|
175718
176642
|
}
|
|
175719
|
-
|
|
176643
|
+
if (opts.todowriteEnabled !== false) {
|
|
176644
|
+
pi.registerTool(createTodowriteTool());
|
|
176645
|
+
if (opts.todowriteCommandEnabled !== false) {
|
|
176646
|
+
registerTodosCommand(pi);
|
|
176647
|
+
}
|
|
176648
|
+
}
|
|
175720
176649
|
if (!opts.sessionScopedToolsDisabled) {
|
|
175721
176650
|
pi.registerTool(createCtxReduceTool({
|
|
175722
176651
|
db: opts.db,
|
|
175723
|
-
protectedTags: opts.protectedTags ?? 20
|
|
176652
|
+
protectedTags: opts.protectedTags ?? 20,
|
|
176653
|
+
resolveProtectedTags: opts.resolveProtectedTags
|
|
175724
176654
|
}));
|
|
175725
176655
|
}
|
|
175726
176656
|
}
|
|
@@ -175751,9 +176681,11 @@ function magicContextSubagentExtension(pi) {
|
|
|
175751
176681
|
ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
|
|
175752
176682
|
memoryToolEnabled: dreamerActionsEnabled,
|
|
175753
176683
|
allowDreamerActions: dreamerActionsEnabled,
|
|
175754
|
-
sessionScopedToolsDisabled: true
|
|
176684
|
+
sessionScopedToolsDisabled: true,
|
|
176685
|
+
todowriteEnabled: cfg.todowrite.enabled !== false,
|
|
176686
|
+
todowriteCommandEnabled: false
|
|
175755
176687
|
});
|
|
175756
|
-
log(`[pi-subagent] registered tools: ctx_search${dreamerActionsEnabled ? ", ctx_memory" : ""}` + ` (ctx_note/ctx_expand omitted: --no-session child;` + ` memory=${cfg.memory.enabled}, embedding=${cfg.embedding.provider !== "off"},` + ` git_commits=${cfg.memory.git_commit_indexing.enabled}, dreamer_actions=${dreamerActionsEnabled})`);
|
|
176688
|
+
log(`[pi-subagent] registered tools: ctx_search${dreamerActionsEnabled ? ", ctx_memory" : ""}${cfg.todowrite.enabled !== false ? ", todowrite" : ""}` + ` (ctx_note/ctx_expand omitted: --no-session child;` + ` memory=${cfg.memory.enabled}, embedding=${cfg.embedding.provider !== "off"},` + ` git_commits=${cfg.memory.git_commit_indexing.enabled}, dreamer_actions=${dreamerActionsEnabled})`);
|
|
175757
176689
|
} catch (err5) {
|
|
175758
176690
|
const message = err5 instanceof Error ? err5.message : String(err5);
|
|
175759
176691
|
log(`[pi-subagent] startup failed: ${message}`);
|