@cortexkit/pi-magic-context 0.31.1 → 0.31.3

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.
@@ -159897,6 +159897,10 @@ var MagicContextConfigSchema = exports_external.object({
159897
159897
  }).describe("Embedding provider configuration"),
159898
159898
  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
159899
  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."),
159900
+ todowrite: exports_external.object({
159901
+ 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."),
159902
+ overlay: exports_external.boolean().default(true).describe("Pi only: show the persistent todo overlay above the editor while tasks are active.")
159903
+ }).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
159904
  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
159905
  caveman_text_compression: exports_external.object({
159902
159906
  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%)."),
@@ -169120,16 +169124,16 @@ function synthesizeToolResultParts(msg) {
169120
169124
  // src/tools/ctx-expand.ts
169121
169125
  var ParamsSchema = exports_typebox.Object({
169122
169126
  start: exports_typebox.Optional(exports_typebox.Number({
169123
- description: "Start message ordinal (from compartment start attribute)"
169127
+ description: `First message ordinal to expand — a compartment's start="N" attribute, or an ordinal from a ctx_search message hit`
169124
169128
  })),
169125
169129
  end: exports_typebox.Optional(exports_typebox.Number({
169126
- description: "End message ordinal (from compartment end attribute)"
169130
+ description: `Last message ordinal to expand (inclusive) — a compartment's end="M" attribute`
169127
169131
  })),
169128
169132
  verbose: exports_typebox.Optional(exports_typebox.Boolean({
169129
169133
  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
169134
  })),
169131
169135
  message: exports_typebox.Optional(exports_typebox.Number({
169132
- description: "Full untruncated recovery of ONE message by its ordinal (text + every tool call's full input/output). Recovers a tool output you dropped with ctx_reduce."
169136
+ 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
169137
  }))
169134
169138
  });
169135
169139
  function ok(text) {
@@ -170105,9 +170109,11 @@ import path4 from "node:path";
170105
170109
  import { promisify } from "node:util";
170106
170110
  var execFileAsync = promisify(execFile);
170107
170111
  var GIT_TIMEOUT_MS2 = 1e4;
170112
+ var defaultExecFileForVerificationPaths = async (file2, args, options) => await execFileAsync(file2, [...args], options);
170113
+ var execFileForVerificationPaths = defaultExecFileForVerificationPaths;
170108
170114
  async function runGit(cwd, args) {
170109
170115
  try {
170110
- const result = await execFileAsync("git", [...args], {
170116
+ const result = await execFileForVerificationPaths("git", args, {
170111
170117
  cwd,
170112
170118
  timeout: GIT_TIMEOUT_MS2,
170113
170119
  maxBuffer: 16 * 1024 * 1024,
@@ -171620,6 +171626,13 @@ function setSessionWorkMetrics(db, sessionId, newWorkTokens, totalInputTokens) {
171620
171626
  SET new_work_tokens = ?, total_input_tokens = ?
171621
171627
  WHERE session_id = ?`).run(Math.max(0, Math.floor(newWorkTokens)), Math.max(0, Math.floor(totalInputTokens)), sessionId);
171622
171628
  }
171629
+ function getSessionWorkMetrics(db, sessionId) {
171630
+ const row = db.prepare("SELECT new_work_tokens, total_input_tokens FROM session_meta WHERE session_id = ?").get(sessionId);
171631
+ return {
171632
+ newWorkTokens: typeof row?.new_work_tokens === "number" ? row.new_work_tokens : 0,
171633
+ totalInputTokens: typeof row?.total_input_tokens === "number" ? row.total_input_tokens : 0
171634
+ };
171635
+ }
171623
171636
  // ../plugin/src/features/magic-context/storage-meta-session.ts
171624
171637
  import { Buffer as Buffer3 } from "node:buffer";
171625
171638
 
@@ -172941,25 +172954,45 @@ function formatMemoryList(memories) {
172941
172954
  id: String(m.id),
172942
172955
  category: m.category,
172943
172956
  status: m.status,
172957
+ verification: m.verificationStatus,
172944
172958
  updated: new Date(m.updatedAt).toISOString(),
172945
172959
  content: m.content.replace(/\s+/g, " ").trim()
172946
172960
  }));
172961
+ const headers = {
172962
+ id: "ID",
172963
+ category: "CATEGORY",
172964
+ status: "STATUS",
172965
+ verification: "VERIFY",
172966
+ updated: "UPDATED",
172967
+ content: "CONTENT"
172968
+ };
172947
172969
  const widths = {
172948
- id: Math.max(2, ...rows.map((r) => r.id.length)),
172949
- category: Math.max(8, ...rows.map((r) => r.category.length)),
172950
- status: Math.max(6, ...rows.map((r) => r.status.length)),
172951
- updated: Math.max(7, ...rows.map((r) => r.updated.length))
172970
+ id: Math.max(headers.id.length, ...rows.map((r) => r.id.length)),
172971
+ category: Math.max(headers.category.length, ...rows.map((r) => r.category.length)),
172972
+ status: Math.max(headers.status.length, ...rows.map((r) => r.status.length)),
172973
+ verification: Math.max(headers.verification.length, ...rows.map((r) => r.verification.length)),
172974
+ updated: Math.max(headers.updated.length, ...rows.map((r) => r.updated.length))
172952
172975
  };
172953
172976
  const fmt = (r) => [
172954
172977
  r.id.padEnd(widths.id),
172955
172978
  r.category.padEnd(widths.category),
172956
172979
  r.status.padEnd(widths.status),
172980
+ r.verification.padEnd(widths.verification),
172957
172981
  r.updated.padEnd(widths.updated),
172958
172982
  r.content
172959
172983
  ].join(" | ");
172960
172984
  return [
172961
172985
  `Found ${rows.length} active ${rows.length === 1 ? "memory" : "memories"}:`,
172962
172986
  "",
172987
+ fmt(headers),
172988
+ [
172989
+ "-".repeat(widths.id),
172990
+ "-".repeat(widths.category),
172991
+ "-".repeat(widths.status),
172992
+ "-".repeat(widths.verification),
172993
+ "-".repeat(widths.updated),
172994
+ "-------"
172995
+ ].join("-+-"),
172963
172996
  ...rows.map(fmt)
172964
172997
  ].join(`
172965
172998
  `);
@@ -173365,6 +173398,7 @@ function createCtxNoteTool(deps) {
173365
173398
  parameters: ParamsSchema3,
173366
173399
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
173367
173400
  const sessionId = ctx.sessionManager.getSessionId();
173401
+ const dreamerEnabled = deps.resolveDreamerEnabled?.(ctx) ?? deps.dreamerEnabled;
173368
173402
  const action2 = params.action ?? (params.content?.trim() ? "write" : "read");
173369
173403
  if (action2 === "write") {
173370
173404
  const content = params.content?.trim();
@@ -173373,7 +173407,7 @@ function createCtxNoteTool(deps) {
173373
173407
  const anchorOrdinal = captureAnchorOrdinal(deps.db, sessionId);
173374
173408
  const surfaceCondition = params.surface_condition?.trim();
173375
173409
  if (surfaceCondition) {
173376
- if (deps.dreamerEnabled !== true) {
173410
+ if (dreamerEnabled !== true) {
173377
173411
  return err3("Error: Smart notes require dreamer to be enabled. Enable dreamer in magic-context.jsonc to use surface_condition.");
173378
173412
  }
173379
173413
  const projectIdentity = resolveProjectIdentity(ctx.cwd);
@@ -173382,6 +173416,7 @@ function createCtxNoteTool(deps) {
173382
173416
  }
173383
173417
  const note2 = addNote(deps.db, "smart", {
173384
173418
  content,
173419
+ sessionId,
173385
173420
  projectPath: projectIdentity,
173386
173421
  surfaceCondition,
173387
173422
  anchorOrdinal
@@ -173633,6 +173668,7 @@ function createCtxReduceTool(deps) {
173633
173668
  parameters: ParamsSchema4,
173634
173669
  async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
173635
173670
  const sessionId = ctx.sessionManager.getSessionId();
173671
+ const protectedTags = Math.max(0, Math.floor(deps.resolveProtectedTags?.(ctx) ?? deps.protectedTags));
173636
173672
  if (!params.drop) {
173637
173673
  return err4("Error: 'drop' must be provided.");
173638
173674
  }
@@ -173650,7 +173686,7 @@ function createCtxReduceTool(deps) {
173650
173686
  return err4(`Error: Unknown tag(s) ${formatIds(unknownIds)}. Check available tags in conversation.`);
173651
173687
  }
173652
173688
  const activeTags = allTags.filter((tag) => tag.status === "active");
173653
- const protectedTagIds = activeTags.map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, deps.protectedTags);
173689
+ const protectedTagIds = activeTags.map((tag) => tag.tagNumber).sort((left, right) => right - left).slice(0, protectedTags);
173654
173690
  const protectedSet = new Set(protectedTagIds);
173655
173691
  const tagStatusMap = new Map(allTags.map((tag) => [tag.tagNumber, tag.status]));
173656
173692
  const pendingOps = getPendingOps(deps.db, sessionId);
@@ -175627,6 +175663,417 @@ function createCtxSearchTool(deps) {
175627
175663
  };
175628
175664
  }
175629
175665
 
175666
+ // src/tools/todo-view-pi.ts
175667
+ import {
175668
+ truncateToWidth
175669
+ } from "@earendil-works/pi-tui";
175670
+ var TODO_TOOL_NAME = "todowrite";
175671
+ var TODOS_COMMAND_NAME = "todos";
175672
+ var WIDGET_KEY = "magic-context-todos";
175673
+ var MAX_OVERLAY_CONTENT_ROWS = 12;
175674
+ var TODO_STATUSES = [
175675
+ "pending",
175676
+ "in_progress",
175677
+ "completed",
175678
+ "cancelled"
175679
+ ];
175680
+ var TODO_PRIORITIES = ["high", "medium", "low"];
175681
+ var STATUS_GLYPH = {
175682
+ pending: "○",
175683
+ in_progress: "◐",
175684
+ completed: "✓",
175685
+ cancelled: "✗"
175686
+ };
175687
+ var STATUS_COLOR = {
175688
+ pending: "dim",
175689
+ in_progress: "warning",
175690
+ completed: "success",
175691
+ cancelled: "error"
175692
+ };
175693
+ var snapshotsBySession = new Map;
175694
+ function isTodoStatus(value) {
175695
+ return TODO_STATUSES.includes(value);
175696
+ }
175697
+ function isTodoPriority(value) {
175698
+ return TODO_PRIORITIES.includes(value);
175699
+ }
175700
+ function cloneTodos(todos) {
175701
+ return todos.map((todo) => ({ ...todo }));
175702
+ }
175703
+ function parseTodos(input) {
175704
+ if (!Array.isArray(input))
175705
+ return null;
175706
+ const todos = [];
175707
+ for (const item of input) {
175708
+ if (item === null || typeof item !== "object")
175709
+ return null;
175710
+ const raw = item;
175711
+ if (typeof raw.content !== "string" || !isTodoStatus(raw.status)) {
175712
+ return null;
175713
+ }
175714
+ const todo = {
175715
+ content: raw.content,
175716
+ status: raw.status
175717
+ };
175718
+ if (isTodoPriority(raw.priority))
175719
+ todo.priority = raw.priority;
175720
+ if (typeof raw.id === "string" && raw.id.length > 0)
175721
+ todo.id = raw.id;
175722
+ todos.push(todo);
175723
+ }
175724
+ return todos;
175725
+ }
175726
+ function parseTodoStateJson(stateJson) {
175727
+ if (!stateJson)
175728
+ return null;
175729
+ try {
175730
+ return parseTodos(JSON.parse(stateJson));
175731
+ } catch {
175732
+ return null;
175733
+ }
175734
+ }
175735
+ function setTodoSnapshot(sessionId, todos) {
175736
+ const parsed = parseTodos(todos);
175737
+ if (parsed === null)
175738
+ return false;
175739
+ snapshotsBySession.set(sessionId, { todos: parsed });
175740
+ return true;
175741
+ }
175742
+ function seedTodoSnapshotFromStateJson(sessionId, stateJson) {
175743
+ const parsed = parseTodoStateJson(stateJson);
175744
+ if (parsed === null) {
175745
+ snapshotsBySession.delete(sessionId);
175746
+ return false;
175747
+ }
175748
+ snapshotsBySession.set(sessionId, { todos: parsed });
175749
+ return true;
175750
+ }
175751
+ function getTodoSnapshot(sessionId) {
175752
+ if (!sessionId)
175753
+ return { todos: [] };
175754
+ const snapshot = snapshotsBySession.get(sessionId);
175755
+ return { todos: snapshot ? cloneTodos(snapshot.todos) : [] };
175756
+ }
175757
+ function clearTodoSnapshot(sessionId) {
175758
+ snapshotsBySession.delete(sessionId);
175759
+ }
175760
+ function getSessionId(ctx) {
175761
+ try {
175762
+ const id = ctx.sessionManager?.getSessionId?.();
175763
+ return typeof id === "string" && id.length > 0 ? id : undefined;
175764
+ } catch {
175765
+ return;
175766
+ }
175767
+ }
175768
+ function countTodos(todos) {
175769
+ const counts = {
175770
+ total: todos.length,
175771
+ pending: 0,
175772
+ inProgress: 0,
175773
+ completed: 0,
175774
+ cancelled: 0
175775
+ };
175776
+ for (const todo of todos) {
175777
+ switch (todo.status) {
175778
+ case "pending":
175779
+ counts.pending++;
175780
+ break;
175781
+ case "in_progress":
175782
+ counts.inProgress++;
175783
+ break;
175784
+ case "completed":
175785
+ counts.completed++;
175786
+ break;
175787
+ case "cancelled":
175788
+ counts.cancelled++;
175789
+ break;
175790
+ }
175791
+ }
175792
+ return counts;
175793
+ }
175794
+ function activeTitleCount(todos) {
175795
+ return todos.filter((todo) => todo.status !== "completed").length;
175796
+ }
175797
+ function formatCounts(counts) {
175798
+ if (counts.total === 0)
175799
+ return "No todos";
175800
+ const parts = [`${counts.completed}/${counts.total} completed`];
175801
+ if (counts.inProgress > 0)
175802
+ parts.push(`${counts.inProgress} in progress`);
175803
+ if (counts.pending > 0)
175804
+ parts.push(`${counts.pending} pending`);
175805
+ if (counts.cancelled > 0)
175806
+ parts.push(`${counts.cancelled} cancelled`);
175807
+ return parts.join(" · ");
175808
+ }
175809
+ function formatTodoLine(todo, theme, options = {}) {
175810
+ const glyph = theme.fg(STATUS_COLOR[todo.status], STATUS_GLYPH[todo.status]);
175811
+ const id = options.showId && todo.id ? `${theme.fg("accent", `#${todo.id}`)} ` : "";
175812
+ const color = todo.status === "pending" || todo.status === "in_progress" ? "text" : "dim";
175813
+ let content = theme.fg(color, todo.content);
175814
+ if (todo.status === "completed" || todo.status === "cancelled") {
175815
+ content = theme.strikethrough(content);
175816
+ }
175817
+ return `${glyph} ${id}${content}`;
175818
+ }
175819
+ function formatCommandLine(todo) {
175820
+ const id = todo.id ? `#${todo.id} ` : "";
175821
+ return ` ${STATUS_GLYPH[todo.status]} ${id}${todo.content}`;
175822
+ }
175823
+ function lineComponent(renderLines) {
175824
+ return {
175825
+ render: renderLines,
175826
+ invalidate() {}
175827
+ };
175828
+ }
175829
+ function renderTodowriteCall(args, theme) {
175830
+ const todos = parseTodos(args.todos) ?? [];
175831
+ const active = activeTitleCount(todos);
175832
+ const activeColor = active > 0 ? "warning" : "success";
175833
+ const line = `${theme.fg("toolTitle", theme.bold("Todos"))} ${theme.fg("muted", "—")} ${theme.fg(activeColor, `${active} active`)}`;
175834
+ return lineComponent((width) => [truncateToWidth(line, width, "…")]);
175835
+ }
175836
+ function renderTodowriteResult(result, theme) {
175837
+ const detailsTodos = result.details?.todos;
175838
+ let todos = parseTodos(detailsTodos);
175839
+ if (todos === null && Array.isArray(result.content)) {
175840
+ const text = result.content.find((part) => part !== null && typeof part === "object" && part.type === "text" && typeof part.text === "string")?.text;
175841
+ if (text) {
175842
+ try {
175843
+ todos = parseTodos(JSON.parse(text));
175844
+ } catch {}
175845
+ }
175846
+ }
175847
+ const renderedTodos = todos ?? [];
175848
+ return lineComponent((width) => {
175849
+ if (renderedTodos.length === 0)
175850
+ return [theme.fg("dim", "No todos")];
175851
+ return renderedTodos.map((todo) => truncateToWidth(formatTodoLine(todo, theme, { showId: true }), width, "…"));
175852
+ });
175853
+ }
175854
+ function registerTodosCommand(pi) {
175855
+ pi.registerCommand(TODOS_COMMAND_NAME, {
175856
+ description: "Show the current Magic Context todo list",
175857
+ handler: async (_args, ctx) => {
175858
+ if (!ctx.hasUI) {
175859
+ ctx.ui.notify("The /todos command requires interactive UI.", "error");
175860
+ return;
175861
+ }
175862
+ const sessionId = getSessionId(ctx);
175863
+ const todos = getTodoSnapshot(sessionId).todos;
175864
+ if (todos.length === 0) {
175865
+ ctx.ui.notify("No todos yet.", "info");
175866
+ return;
175867
+ }
175868
+ const counts = countTodos(todos);
175869
+ const lines = [formatCounts(counts)];
175870
+ const groups = [
175871
+ ["pending", "── Pending ──"],
175872
+ ["in_progress", "── In Progress ──"],
175873
+ ["completed", "── Completed ──"],
175874
+ ["cancelled", "── Cancelled ──"]
175875
+ ];
175876
+ for (const [status, heading] of groups) {
175877
+ const group = todos.filter((todo) => todo.status === status);
175878
+ if (group.length === 0)
175879
+ continue;
175880
+ lines.push(heading);
175881
+ for (const todo of group)
175882
+ lines.push(formatCommandLine(todo));
175883
+ }
175884
+ ctx.ui.notify(lines.join(`
175885
+ `), "info");
175886
+ }
175887
+ });
175888
+ }
175889
+ function todoKey(todo, index) {
175890
+ return todo.id ? `id:${todo.id}` : `pos:${index}:${todo.content}`;
175891
+ }
175892
+ function isOverlayLive(todo) {
175893
+ return todo.status === "pending" || todo.status === "in_progress";
175894
+ }
175895
+
175896
+ class TodoOverlay {
175897
+ uiCtx;
175898
+ sessionId;
175899
+ widgetRegistered = false;
175900
+ tui;
175901
+ completedTaskIdsPendingHide = new Set;
175902
+ hiddenCompletedTaskIds = new Set;
175903
+ lastTodoKeys = new Set;
175904
+ setUICtx(sessionId, ctx) {
175905
+ if (ctx !== this.uiCtx || sessionId !== this.sessionId) {
175906
+ this.uiCtx = ctx;
175907
+ this.sessionId = sessionId;
175908
+ this.widgetRegistered = false;
175909
+ this.tui = undefined;
175910
+ this.resetCompletedDisplayState();
175911
+ }
175912
+ }
175913
+ update(sessionId = this.sessionId) {
175914
+ if (!this.uiCtx || !this.sessionId || sessionId !== this.sessionId)
175915
+ return;
175916
+ const todos = getTodoSnapshot(this.sessionId).todos;
175917
+ this.pruneCompletedDisplayState(todos);
175918
+ const visible = this.selectOverlayTodos(todos);
175919
+ if (visible.length === 0) {
175920
+ if (this.widgetRegistered) {
175921
+ this.uiCtx.setWidget(WIDGET_KEY, undefined);
175922
+ this.widgetRegistered = false;
175923
+ this.tui = undefined;
175924
+ }
175925
+ return;
175926
+ }
175927
+ if (!this.widgetRegistered) {
175928
+ this.uiCtx.setWidget(WIDGET_KEY, (tui, theme) => {
175929
+ this.tui = tui;
175930
+ return {
175931
+ render: (width) => this.renderWidget(theme, width),
175932
+ invalidate: () => {
175933
+ this.widgetRegistered = false;
175934
+ this.tui = undefined;
175935
+ }
175936
+ };
175937
+ }, { placement: "aboveEditor" });
175938
+ this.widgetRegistered = true;
175939
+ } else {
175940
+ this.tui?.requestRender();
175941
+ }
175942
+ }
175943
+ resetCompletedDisplayState() {
175944
+ this.completedTaskIdsPendingHide.clear();
175945
+ this.hiddenCompletedTaskIds.clear();
175946
+ this.lastTodoKeys.clear();
175947
+ }
175948
+ hideCompletedTasksFromPreviousTurn() {
175949
+ if (this.completedTaskIdsPendingHide.size === 0)
175950
+ return;
175951
+ for (const taskId of this.completedTaskIdsPendingHide) {
175952
+ this.hiddenCompletedTaskIds.add(taskId);
175953
+ }
175954
+ this.completedTaskIdsPendingHide.clear();
175955
+ this.update();
175956
+ }
175957
+ disposeSession(sessionId) {
175958
+ if (this.sessionId !== sessionId)
175959
+ return;
175960
+ this.dispose();
175961
+ }
175962
+ dispose() {
175963
+ if (this.uiCtx)
175964
+ this.uiCtx.setWidget(WIDGET_KEY, undefined);
175965
+ this.widgetRegistered = false;
175966
+ this.tui = undefined;
175967
+ this.uiCtx = undefined;
175968
+ this.sessionId = undefined;
175969
+ this.resetCompletedDisplayState();
175970
+ }
175971
+ pruneCompletedDisplayState(todos) {
175972
+ const currentKeys = new Set(todos.map((todo, index) => todoKey(todo, index)));
175973
+ const hasSharedKeys = [...currentKeys].some((key) => this.lastTodoKeys.has(key));
175974
+ if (this.lastTodoKeys.size > 0 && currentKeys.size > 0 && !hasSharedKeys) {
175975
+ this.resetCompletedDisplayState();
175976
+ }
175977
+ this.lastTodoKeys = currentKeys;
175978
+ const completedKeys = new Set(todos.map((todo, index) => ({ todo, key: todoKey(todo, index) })).filter(({ todo }) => todo.status === "completed").map(({ key }) => key));
175979
+ for (const taskId of this.completedTaskIdsPendingHide) {
175980
+ if (!completedKeys.has(taskId))
175981
+ this.completedTaskIdsPendingHide.delete(taskId);
175982
+ }
175983
+ for (const taskId of this.hiddenCompletedTaskIds) {
175984
+ if (!completedKeys.has(taskId))
175985
+ this.hiddenCompletedTaskIds.delete(taskId);
175986
+ }
175987
+ }
175988
+ selectOverlayTodos(todos) {
175989
+ return todos.map((todo, index) => ({ todo, key: todoKey(todo, index) })).filter(({ todo, key }) => {
175990
+ if (isOverlayLive(todo))
175991
+ return true;
175992
+ return todo.status === "completed" && !this.hiddenCompletedTaskIds.has(key);
175993
+ });
175994
+ }
175995
+ renderWidget(theme, width) {
175996
+ if (!this.sessionId)
175997
+ return [];
175998
+ const todos = getTodoSnapshot(this.sessionId).todos;
175999
+ this.pruneCompletedDisplayState(todos);
176000
+ const overlayTodos = this.selectOverlayTodos(todos);
176001
+ if (overlayTodos.length === 0)
176002
+ return [];
176003
+ const counts = countTodos(todos);
176004
+ const hasInProgress = counts.inProgress > 0;
176005
+ const headingColor = hasInProgress ? "accent" : "dim";
176006
+ const headingIcon = hasInProgress ? "●" : "○";
176007
+ const heading = `${theme.fg(headingColor, headingIcon)} ${theme.fg(headingColor, "Todos")} ${theme.fg("muted", "—")} ${theme.fg("dim", formatCounts(counts))}`;
176008
+ const truncate2 = (line) => truncateToWidth(line, width, "…");
176009
+ const lines = [truncate2(heading)];
176010
+ const hasTruncatedTail = overlayTodos.length > MAX_OVERLAY_CONTENT_ROWS;
176011
+ const visibleRows = hasTruncatedTail ? MAX_OVERLAY_CONTENT_ROWS - 1 : MAX_OVERLAY_CONTENT_ROWS;
176012
+ const truncatedTail = Math.max(0, overlayTodos.length - visibleRows);
176013
+ const visible = overlayTodos.slice(0, visibleRows);
176014
+ for (const [index, { todo, key }] of visible.entries()) {
176015
+ const isLast = index === visible.length - 1 && truncatedTail === 0;
176016
+ const branch = theme.fg("dim", isLast ? "└─" : "├─");
176017
+ lines.push(truncate2(`${branch} ${formatTodoLine(todo, theme, { showId: true })}`));
176018
+ if (todo.status === "completed")
176019
+ this.completedTaskIdsPendingHide.add(key);
176020
+ }
176021
+ if (truncatedTail > 0) {
176022
+ lines.push(truncate2(`${theme.fg("dim", "└─")} ${theme.fg("dim", `+${truncatedTail} more`)}`));
176023
+ }
176024
+ return lines;
176025
+ }
176026
+ }
176027
+ function registerTodoOverlay(pi, deps) {
176028
+ const overlay = new TodoOverlay;
176029
+ pi.on("session_start", async (_event, ctx) => {
176030
+ const sessionId = getSessionId(ctx);
176031
+ if (!sessionId)
176032
+ return;
176033
+ seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
176034
+ if (!ctx.hasUI)
176035
+ return;
176036
+ overlay.setUICtx(sessionId, ctx.ui);
176037
+ overlay.update(sessionId);
176038
+ });
176039
+ pi.on("agent_start", async () => {
176040
+ overlay.hideCompletedTasksFromPreviousTurn();
176041
+ });
176042
+ pi.on("session_shutdown", async (_event, ctx) => {
176043
+ const sessionId = getSessionId(ctx);
176044
+ if (!sessionId)
176045
+ return;
176046
+ clearTodoSnapshot(sessionId);
176047
+ overlay.disposeSession(sessionId);
176048
+ });
176049
+ pi.on("session_before_switch", async (_event, ctx) => {
176050
+ const sessionId = getSessionId(ctx);
176051
+ if (!sessionId)
176052
+ return;
176053
+ clearTodoSnapshot(sessionId);
176054
+ overlay.disposeSession(sessionId);
176055
+ });
176056
+ return overlay;
176057
+ }
176058
+ function registerTodoStateLifecycle(pi, deps) {
176059
+ pi.on("session_start", async (_event, ctx) => {
176060
+ const sessionId = getSessionId(ctx);
176061
+ if (!sessionId)
176062
+ return;
176063
+ seedTodoSnapshotFromStateJson(sessionId, deps.readLastTodoState(sessionId));
176064
+ });
176065
+ pi.on("session_shutdown", async (_event, ctx) => {
176066
+ const sessionId = getSessionId(ctx);
176067
+ if (sessionId)
176068
+ clearTodoSnapshot(sessionId);
176069
+ });
176070
+ pi.on("session_before_switch", async (_event, ctx) => {
176071
+ const sessionId = getSessionId(ctx);
176072
+ if (sessionId)
176073
+ clearTodoSnapshot(sessionId);
176074
+ });
176075
+ }
176076
+
175630
176077
  // src/tools/todowrite.ts
175631
176078
  var STATUS_VALUES = [
175632
176079
  "pending",
@@ -175646,29 +176093,20 @@ var TodowriteParams = exports_typebox.Object({
175646
176093
  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
176094
  })
175648
176095
  });
175649
- var TOOL_DESCRIPTION = [
175650
- "Manage your task list for this session.",
175651
- "",
175652
- "Use this tool to plan multi-step work, track in-flight tasks, and",
175653
- "mark progress as you complete steps. Pass the COMPLETE updated list",
175654
- "of todos every time — this tool replaces the prior list rather than",
175655
- "appending to it.",
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
- `);
176096
+ var PROMPT_SNIPPET = "Manage a task list to track multi-step progress";
176097
+ var PROMPT_GUIDELINES = [
176098
+ "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.",
176099
+ "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.",
176100
+ "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.",
176101
+ "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."
176102
+ ];
175667
176103
  function createTodowriteTool() {
175668
176104
  return {
175669
- name: "todowrite",
176105
+ name: TODO_TOOL_NAME,
175670
176106
  label: "Todos",
175671
- description: TOOL_DESCRIPTION,
176107
+ description: "Manage the session task list.",
176108
+ promptSnippet: PROMPT_SNIPPET,
176109
+ promptGuidelines: PROMPT_GUIDELINES,
175672
176110
  parameters: TodowriteParams,
175673
176111
  async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
175674
176112
  const todos = params.todos ?? [];
@@ -175687,6 +176125,12 @@ function createTodowriteTool() {
175687
176125
  truncated: false
175688
176126
  }
175689
176127
  };
176128
+ },
176129
+ renderCall(args, theme) {
176130
+ return renderTodowriteCall(args, theme);
176131
+ },
176132
+ renderResult(result, _opts, theme) {
176133
+ return renderTodowriteResult(result, theme);
175690
176134
  }
175691
176135
  };
175692
176136
  }
@@ -175712,15 +176156,22 @@ function registerMagicContextTools(pi, opts) {
175712
176156
  if (!opts.sessionScopedToolsDisabled) {
175713
176157
  pi.registerTool(createCtxNoteTool({
175714
176158
  db: opts.db,
175715
- dreamerEnabled: opts.dreamerEnabled ?? false
176159
+ dreamerEnabled: opts.dreamerEnabled ?? false,
176160
+ resolveDreamerEnabled: opts.resolveDreamerEnabled
175716
176161
  }));
175717
176162
  pi.registerTool(createCtxExpandTool({ db: opts.db }));
175718
176163
  }
175719
- pi.registerTool(createTodowriteTool());
176164
+ if (opts.todowriteEnabled !== false) {
176165
+ pi.registerTool(createTodowriteTool());
176166
+ if (opts.todowriteCommandEnabled !== false) {
176167
+ registerTodosCommand(pi);
176168
+ }
176169
+ }
175720
176170
  if (!opts.sessionScopedToolsDisabled) {
175721
176171
  pi.registerTool(createCtxReduceTool({
175722
176172
  db: opts.db,
175723
- protectedTags: opts.protectedTags ?? 20
176173
+ protectedTags: opts.protectedTags ?? 20,
176174
+ resolveProtectedTags: opts.resolveProtectedTags
175724
176175
  }));
175725
176176
  }
175726
176177
  }
@@ -175751,9 +176202,11 @@ function magicContextSubagentExtension(pi) {
175751
176202
  ensureProjectRegistered: ensureProjectRegisteredFromPiDirectory,
175752
176203
  memoryToolEnabled: dreamerActionsEnabled,
175753
176204
  allowDreamerActions: dreamerActionsEnabled,
175754
- sessionScopedToolsDisabled: true
176205
+ sessionScopedToolsDisabled: true,
176206
+ todowriteEnabled: cfg.todowrite.enabled !== false,
176207
+ todowriteCommandEnabled: false
175755
176208
  });
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})`);
176209
+ 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
176210
  } catch (err5) {
175758
176211
  const message = err5 instanceof Error ? err5.message : String(err5);
175759
176212
  log(`[pi-subagent] startup failed: ${message}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/pi-magic-context",
3
- "version": "0.31.1",
3
+ "version": "0.31.3",
4
4
  "type": "module",
5
5
  "description": "Pi coding agent extension for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",