@astralyn/pi 0.87.3 → 0.87.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/bundle/chunks/{chunk-MJ45I4SM.js → chunk-APQ4ACY3.js} +1 -1
  3. package/dist/bundle/chunks/{chunk-TYOQD6VQ.js → chunk-EWLAVDVN.js} +13 -13
  4. package/dist/bundle/chunks/{virtual-modules-NDGCHNG7.js → virtual-modules-W3OGMIX7.js} +1 -1
  5. package/dist/bundle/cli.js +1 -1
  6. package/dist/bundle/index.js +1 -1
  7. package/dist/bundle/rpc-entry.js +1 -1
  8. package/dist/core/background/service.d.ts.map +1 -1
  9. package/dist/core/background/service.js +4 -3
  10. package/dist/core/background/service.js.map +1 -1
  11. package/dist/core/background/types.d.ts +11 -0
  12. package/dist/core/background/types.d.ts.map +1 -1
  13. package/dist/core/background/types.js +14 -0
  14. package/dist/core/background/types.js.map +1 -1
  15. package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
  16. package/dist/core/tools/tool-definition-wrapper.js +11 -1
  17. package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
  18. package/dist/extensions/background/actions.d.ts.map +1 -1
  19. package/dist/extensions/background/actions.js +38 -31
  20. package/dist/extensions/background/actions.js.map +1 -1
  21. package/dist/extensions/todo/constants.d.ts +1 -1
  22. package/dist/extensions/todo/constants.d.ts.map +1 -1
  23. package/dist/extensions/todo/constants.js +2 -2
  24. package/dist/extensions/todo/constants.js.map +1 -1
  25. package/dist/extensions/todo/index.js +1 -1
  26. package/dist/extensions/todo/index.js.map +1 -1
  27. package/dist/extensions/todo/schema.d.ts +1 -0
  28. package/dist/extensions/todo/schema.d.ts.map +1 -1
  29. package/dist/extensions/todo/schema.js +1 -1
  30. package/dist/extensions/todo/schema.js.map +1 -1
  31. package/dist/extensions/todo/state.d.ts.map +1 -1
  32. package/dist/extensions/todo/state.js +20 -8
  33. package/dist/extensions/todo/state.js.map +1 -1
  34. package/dist/extensions/todo/view.d.ts +1 -1
  35. package/dist/extensions/todo/view.d.ts.map +1 -1
  36. package/dist/extensions/todo/view.js +4 -24
  37. package/dist/extensions/todo/view.js.map +1 -1
  38. package/docs/bundled/extensions/todo.md +5 -5
  39. package/npm-shrinkwrap.json +2 -2
  40. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  /** Management only: execution and delivery belong to the session Background service. */
2
2
  import { boundText } from "../../core/background/output.js";
3
- import { isBackgroundTerminal, } from "../../core/background/types.js";
3
+ import { BackgroundLookupError, isBackgroundTerminal, } from "../../core/background/types.js";
4
4
  import { truncateHead } from "../../core/tools/truncate.js";
5
5
  import { sanitizeBinaryOutput } from "../../utils/shell.js";
6
6
  import { BG_LIST_FINISHED_SHOWN, BG_WAIT_DELTA_BYTES } from "./constants.js";
@@ -15,40 +15,54 @@ function result(text, details) {
15
15
  export function describeTaskLine(task, now = Date.now()) {
16
16
  return `${task.id} ${task.kind} ${task.status} (${task.mode}) ${runtimeLabel(task, now)} ${task.title.slice(0, 200)}`;
17
17
  }
18
- const UNKNOWN_ID_MESSAGE = "Unknown background task ID";
19
- const AMBIGUOUS_ID_MESSAGE = "Ambiguous background task ID";
20
18
  const LOOKUP_LIST_LIMIT = 10;
21
- /** Task ids a lookup prefix can match: the full id or its kind-stripped suffix. */
22
- function matchesLookupPrefix(task, id) {
23
- return task.id.startsWith(id) || task.id.slice(task.kind.length + 1).startsWith(id);
19
+ /**
20
+ * Finished rows sort most-recent first. list() is creation-ordered, so an
21
+ * unsorted slice would surface the oldest completions rather than the recent
22
+ * finishes a caller is most likely looking for.
23
+ */
24
+ function recentFinishedFirst(tasks) {
25
+ return tasks.sort((left, right) => (right.endedAt ?? 0) - (left.endedAt ?? 0));
26
+ }
27
+ /**
28
+ * Listings cover backgrounded work only; foreground executions deliver inline
29
+ * in the transcript, so they are counted rather than shown. Every listing
30
+ * (bg list output and lookup-failure messages) shares this scope.
31
+ */
32
+ function listedTasks(background) {
33
+ const all = background.list();
34
+ const tasks = all.filter((task) => task.mode === "background");
35
+ return { tasks, foregroundOmitted: all.length - tasks.length };
36
+ }
37
+ function foregroundOmissionNote(omitted) {
38
+ return omitted > 0
39
+ ? `\n${omitted} foreground ${omitted === 1 ? "execution" : "executions"} omitted — foreground work is delivered inline in the transcript.`
40
+ : "";
24
41
  }
25
42
  /**
26
43
  * Rewrite the service's bare lookup failure into an actionable one: an
27
44
  * unknown id gets the session's current tasks (active first, then recent
28
45
  * finishes) so the next call can use a real id without a separate list call,
29
- * and an ambiguous prefix gets exactly the tasks it matched. A missing id
30
- * stays an error — it is likely a mistyped id of a task still running.
46
+ * and an ambiguous prefix gets exactly the tasks it matched — including
47
+ * records outside the current branch, which listings hide but the prefix
48
+ * still collides with.
31
49
  */
32
50
  function lookupFailure(background, id, error) {
33
- const message = error instanceof Error ? error.message : String(error);
34
- const ambiguous = message.includes(AMBIGUOUS_ID_MESSAGE);
35
- if (!ambiguous && !message.includes(UNKNOWN_ID_MESSAGE)) {
36
- return error instanceof Error ? error : new Error(message);
37
- }
38
- if (ambiguous) {
39
- const matches = background.list().filter((task) => matchesLookupPrefix(task, id));
40
- const lines = matches.map((task) => describeTaskLine(task)).join("\n");
41
- return new Error(`Ambiguous background task ID "${id}" matches ${matches.length} tasks:\n${lines}`);
51
+ if (!(error instanceof BackgroundLookupError))
52
+ return error instanceof Error ? error : new Error(String(error));
53
+ if (error.kind === "ambiguous") {
54
+ const lines = error.matches.map((task) => describeTaskLine(task)).join("\n");
55
+ return new Error(`Ambiguous background task ID "${id}" matches ${error.matches.length} tasks:\n${lines}`);
42
56
  }
43
- const tasks = background.list();
57
+ const { tasks, foregroundOmitted } = listedTasks(background);
44
58
  const active = tasks.filter((task) => !isBackgroundTerminal(task.status));
45
- const finished = tasks.filter((task) => isBackgroundTerminal(task.status));
59
+ const finished = recentFinishedFirst(tasks.filter((task) => isBackgroundTerminal(task.status)));
46
60
  const shown = [...active, ...finished].slice(0, LOOKUP_LIST_LIMIT);
47
61
  if (shown.length === 0) {
48
62
  return new Error(`No background task "${id}" in this session. No background tasks in this session.`);
49
63
  }
50
64
  const lines = shown.map((task) => describeTaskLine(task)).join("\n");
51
- return new Error(`No background task "${id}" in this session. IDs from other sessions are not valid here.\nCurrent tasks:\n${lines}`);
65
+ return new Error(`No background task "${id}" in this session. IDs from other sessions are not valid here.\nCurrent tasks:\n${lines}${foregroundOmissionNote(foregroundOmitted)}`);
52
66
  }
53
67
  /** Reserve space for each independent diagnostic before allowing raw output to fill the budget. */
54
68
  function readText(header, slice) {
@@ -146,21 +160,14 @@ export function runKill(background, input) {
146
160
  }
147
161
  }
148
162
  export function runList(background) {
149
- // Listings cover backgrounded work only; foreground executions deliver
150
- // inline in the transcript, so they are counted here rather than shown.
151
- const all = background.list();
152
- const tasks = all.filter((task) => task.mode === "background");
153
- const foregroundOmitted = all.length - tasks.length;
163
+ const { tasks, foregroundOmitted } = listedTasks(background);
154
164
  const active = tasks.filter((task) => !isBackgroundTerminal(task.status));
155
- const finished = tasks.filter((task) => isBackgroundTerminal(task.status));
165
+ const finished = recentFinishedFirst(tasks.filter((task) => isBackgroundTerminal(task.status)));
156
166
  const shown = [...active, ...finished.slice(0, BG_LIST_FINISHED_SHOWN)].slice(0, 100);
157
167
  const hidden = tasks.length - shown.length;
158
- const foregroundNote = foregroundOmitted > 0
159
- ? `\n${foregroundOmitted} foreground ${foregroundOmitted === 1 ? "execution" : "executions"} omitted — foreground work is delivered inline in the transcript.`
160
- : "";
161
168
  return result(shown.length
162
- ? `${shown.map((task) => describeTaskLine(task)).join("\n")}${hidden > 0 ? `\n${hidden} more records not shown.` : ""}${foregroundNote}`
163
- : `No background tasks. Start work through bash or subagent with background: true.${foregroundNote}`, {
169
+ ? `${shown.map((task) => describeTaskLine(task)).join("\n")}${hidden > 0 ? `\n${hidden} more records not shown.` : ""}${foregroundOmissionNote(foregroundOmitted)}`
170
+ : `No background tasks. Start work through bash or subagent with background: true.${foregroundOmissionNote(foregroundOmitted)}`, {
164
171
  action: "list",
165
172
  running: active.length,
166
173
  finished: finished.length,
@@ -1 +1 @@
1
- {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../../src/extensions/background/actions.ts"],"names":[],"mappings":"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","sourcesContent":["/** Management only: execution and delivery belong to the session Background service. */\nimport { boundText } from \"../../core/background/output.ts\";\nimport {\n\ttype BackgroundContext,\n\ttype BackgroundRead,\n\ttype BackgroundTask,\n\tisBackgroundTerminal,\n} from \"../../core/background/types.ts\";\nimport type { AgentToolResult } from \"../../core/extensions/types.ts\";\nimport { truncateHead } from \"../../core/tools/truncate.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\nimport { BG_LIST_FINISHED_SHOWN, BG_WAIT_DELTA_BYTES } from \"./constants.ts\";\nimport { type BgInput, clampReadBytes, clampSinceBytes, clampWaitMs, requireTaskId } from \"./schema.ts\";\nimport { runtimeLabel } from \"./task-view.ts\";\nimport type { BgDetails, BgKillDetails, BgListDetails, BgReadDetails, BgWaitDetails } from \"./types.ts\";\n\nexport function boundedText(text: string): string {\n\treturn truncateHead(sanitizeBinaryOutput(text), { maxBytes: 50 * 1024, maxLines: 2000 }).content;\n}\nfunction result<T extends BgDetails>(text: string, details: T): AgentToolResult<T> {\n\treturn { content: [{ type: \"text\", text: boundedText(text) }], details };\n}\nexport function describeTaskLine(task: BackgroundTask, now = Date.now()): string {\n\treturn `${task.id} ${task.kind} ${task.status} (${task.mode}) ${runtimeLabel(task, now)} ${task.title.slice(0, 200)}`;\n}\n\nconst UNKNOWN_ID_MESSAGE = \"Unknown background task ID\";\nconst AMBIGUOUS_ID_MESSAGE = \"Ambiguous background task ID\";\nconst LOOKUP_LIST_LIMIT = 10;\n\n/** Task ids a lookup prefix can match: the full id or its kind-stripped suffix. */\nfunction matchesLookupPrefix(task: BackgroundTask, id: string): boolean {\n\treturn task.id.startsWith(id) || task.id.slice(task.kind.length + 1).startsWith(id);\n}\n\n/**\n * Rewrite the service's bare lookup failure into an actionable one: an\n * unknown id gets the session's current tasks (active first, then recent\n * finishes) so the next call can use a real id without a separate list call,\n * and an ambiguous prefix gets exactly the tasks it matched. A missing id\n * stays an error — it is likely a mistyped id of a task still running.\n */\nfunction lookupFailure(background: BackgroundContext, id: string, error: unknown): Error {\n\tconst message = error instanceof Error ? error.message : String(error);\n\tconst ambiguous = message.includes(AMBIGUOUS_ID_MESSAGE);\n\tif (!ambiguous && !message.includes(UNKNOWN_ID_MESSAGE)) {\n\t\treturn error instanceof Error ? error : new Error(message);\n\t}\n\tif (ambiguous) {\n\t\tconst matches = background.list().filter((task) => matchesLookupPrefix(task, id));\n\t\tconst lines = matches.map((task) => describeTaskLine(task)).join(\"\\n\");\n\t\treturn new Error(`Ambiguous background task ID \"${id}\" matches ${matches.length} tasks:\\n${lines}`);\n\t}\n\tconst tasks = background.list();\n\tconst active = tasks.filter((task) => !isBackgroundTerminal(task.status));\n\tconst finished = tasks.filter((task) => isBackgroundTerminal(task.status));\n\tconst shown = [...active, ...finished].slice(0, LOOKUP_LIST_LIMIT);\n\tif (shown.length === 0) {\n\t\treturn new Error(`No background task \"${id}\" in this session. No background tasks in this session.`);\n\t}\n\tconst lines = shown.map((task) => describeTaskLine(task)).join(\"\\n\");\n\treturn new Error(\n\t\t`No background task \"${id}\" in this session. IDs from other sessions are not valid here.\\nCurrent tasks:\\n${lines}`,\n\t);\n}\n/** Reserve space for each independent diagnostic before allowing raw output to fill the budget. */\nfunction readText(header: string, slice: BackgroundRead): string {\n\tconst boundedField = (text: string, maxBytes: number) =>\n\t\ttruncateHead(boundText(sanitizeBinaryOutput(text), maxBytes), { maxBytes, maxLines: 100 }).content;\n\treturn [\n\t\tboundedField(header, 4096),\n\t\tslice.task.error ? `Task error: ${boundedField(slice.task.error, 4096)}` : \"\",\n\t\tslice.readError ? `Output read error: ${boundedField(slice.readError, 4096)}` : \"\",\n\t\tboundedField(slice.task.outputPath ?? \"\", 8192),\n\t\tslice.text || \"(no output yet)\",\n\t]\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\nexport async function runRead(background: BackgroundContext, input: BgInput): Promise<AgentToolResult<BgReadDetails>> {\n\tconst id = requireTaskId(input);\n\ttry {\n\t\tconst mode = input.mode ?? \"tail\";\n\t\tconst slice = await background.read(id, { mode, bytes: clampReadBytes(input.bytes) });\n\t\treturn result(\n\t\t\treadText(\n\t\t\t\t`[${describeTaskLine(slice.task)} · ${slice.totalBytes} bytes${slice.truncated ? \" · truncated\" : \"\"}]`,\n\t\t\t\tslice,\n\t\t\t),\n\t\t\t{\n\t\t\t\taction: \"read\",\n\t\t\t\ttaskId: slice.task.id,\n\t\t\t\tmode,\n\t\t\t\tsliceBytes: Buffer.byteLength(slice.text),\n\t\t\t\ttotalBytes: slice.totalBytes,\n\t\t\t\toutputPath: slice.task.outputPath ?? \"\",\n\t\t\t\tkind: slice.task.kind,\n\t\t\t\tstatus: slice.task.status,\n\t\t\t},\n\t\t);\n\t} catch (error) {\n\t\tthrow lookupFailure(background, id, error);\n\t}\n}\nexport async function runWait(\n\tbackground: BackgroundContext,\n\tinput: BgInput,\n\tsignal?: AbortSignal,\n): Promise<AgentToolResult<BgWaitDetails>> {\n\tconst id = requireTaskId(input);\n\ttry {\n\t\tconst release = background.pin(id);\n\t\ttry {\n\t\t\tconst start = Date.now();\n\t\t\tconst task = await background.wait(id, clampWaitMs(input.waitMs), signal);\n\t\t\tconst timedOut = !isBackgroundTerminal(task.status);\n\t\t\t// A closed host resolves waits early without settling anything; do not claim\n\t\t\t// the execution merely outlived the wait window.\n\t\t\tconst windowNote = background.closed\n\t\t\t\t? \" · host closed; execution state unconfirmed\"\n\t\t\t\t: timedOut\n\t\t\t\t\t? \" · wait window expired; execution continues\"\n\t\t\t\t\t: \"\";\n\t\t\tconst slice = await background.read(id, {\n\t\t\t\tbytes: BG_WAIT_DELTA_BYTES,\n\t\t\t\tsinceBytes: clampSinceBytes(input.sinceBytes),\n\t\t\t});\n\t\t\tsignal?.throwIfAborted();\n\t\t\treturn result(readText(`[${describeTaskLine(task)}${windowNote}]`, slice), {\n\t\t\t\taction: \"wait\",\n\t\t\t\ttaskId: task.id,\n\t\t\t\t...(!timedOut ? { backgroundTaskId: task.id } : {}),\n\t\t\t\tstatus: task.status,\n\t\t\t\tkind: task.kind,\n\t\t\t\ttimedOut,\n\t\t\t\texitCode: undefined,\n\t\t\t\twaitedMs: Date.now() - start,\n\t\t\t\tdeltaBytes: Buffer.byteLength(slice.text),\n\t\t\t\ttotalBytes: slice.totalBytes,\n\t\t\t\tdeltaTruncated: slice.truncated,\n\t\t\t\toutputPath: task.outputPath ?? \"\",\n\t\t\t});\n\t\t} finally {\n\t\t\trelease();\n\t\t}\n\t} catch (error) {\n\t\tthrow lookupFailure(background, id, error);\n\t}\n}\nexport function runKill(background: BackgroundContext, input: BgInput): AgentToolResult<BgKillDetails> {\n\tconst id = requireTaskId(input);\n\ttry {\n\t\tconst task = background.get(id);\n\t\tconst requested = background.kill(task.id);\n\t\treturn result(\n\t\t\trequested\n\t\t\t\t? `Cancellation requested for ${task.id}. The task or whole group is stopping; cleanup may still be in progress.`\n\t\t\t\t: `No new cancellation requested for ${task.id} (${background.get(task.id).status}).`,\n\t\t\t{\n\t\t\t\taction: \"kill\",\n\t\t\t\ttaskId: task.id,\n\t\t\t\tcommand: task.command ?? task.title,\n\t\t\t\trequested,\n\t\t\t\tstatus: background.get(task.id).status,\n\t\t\t},\n\t\t);\n\t} catch (error) {\n\t\tthrow lookupFailure(background, id, error);\n\t}\n}\nexport function runList(background: BackgroundContext): AgentToolResult<BgListDetails> {\n\t// Listings cover backgrounded work only; foreground executions deliver\n\t// inline in the transcript, so they are counted here rather than shown.\n\tconst all = background.list();\n\tconst tasks = all.filter((task) => task.mode === \"background\");\n\tconst foregroundOmitted = all.length - tasks.length;\n\tconst active = tasks.filter((task) => !isBackgroundTerminal(task.status));\n\tconst finished = tasks.filter((task) => isBackgroundTerminal(task.status));\n\tconst shown = [...active, ...finished.slice(0, BG_LIST_FINISHED_SHOWN)].slice(0, 100);\n\tconst hidden = tasks.length - shown.length;\n\tconst foregroundNote =\n\t\tforegroundOmitted > 0\n\t\t\t? `\\n${foregroundOmitted} foreground ${foregroundOmitted === 1 ? \"execution\" : \"executions\"} omitted — foreground work is delivered inline in the transcript.`\n\t\t\t: \"\";\n\treturn result(\n\t\tshown.length\n\t\t\t? `${shown.map((task) => describeTaskLine(task)).join(\"\\n\")}${hidden > 0 ? `\\n${hidden} more records not shown.` : \"\"}${foregroundNote}`\n\t\t\t: `No background tasks. Start work through bash or subagent with background: true.${foregroundNote}`,\n\t\t{\n\t\t\taction: \"list\",\n\t\t\trunning: active.length,\n\t\t\tfinished: finished.length,\n\t\t\tshown: shown.length,\n\t\t\thidden,\n\t\t\t...(foregroundOmitted > 0 ? { foregroundOmitted } : {}),\n\t\t},\n\t);\n}\n"]}
1
+ {"version":3,"file":"actions.js","sourceRoot":"","sources":["../../../src/extensions/background/actions.ts"],"names":[],"mappings":"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","sourcesContent":["/** Management only: execution and delivery belong to the session Background service. */\nimport { boundText } from \"../../core/background/output.ts\";\nimport {\n\ttype BackgroundContext,\n\tBackgroundLookupError,\n\ttype BackgroundRead,\n\ttype BackgroundTask,\n\tisBackgroundTerminal,\n} from \"../../core/background/types.ts\";\nimport type { AgentToolResult } from \"../../core/extensions/types.ts\";\nimport { truncateHead } from \"../../core/tools/truncate.ts\";\nimport { sanitizeBinaryOutput } from \"../../utils/shell.ts\";\nimport { BG_LIST_FINISHED_SHOWN, BG_WAIT_DELTA_BYTES } from \"./constants.ts\";\nimport { type BgInput, clampReadBytes, clampSinceBytes, clampWaitMs, requireTaskId } from \"./schema.ts\";\nimport { runtimeLabel } from \"./task-view.ts\";\nimport type { BgDetails, BgKillDetails, BgListDetails, BgReadDetails, BgWaitDetails } from \"./types.ts\";\n\nexport function boundedText(text: string): string {\n\treturn truncateHead(sanitizeBinaryOutput(text), { maxBytes: 50 * 1024, maxLines: 2000 }).content;\n}\nfunction result<T extends BgDetails>(text: string, details: T): AgentToolResult<T> {\n\treturn { content: [{ type: \"text\", text: boundedText(text) }], details };\n}\nexport function describeTaskLine(task: BackgroundTask, now = Date.now()): string {\n\treturn `${task.id} ${task.kind} ${task.status} (${task.mode}) ${runtimeLabel(task, now)} ${task.title.slice(0, 200)}`;\n}\n\nconst LOOKUP_LIST_LIMIT = 10;\n\n/**\n * Finished rows sort most-recent first. list() is creation-ordered, so an\n * unsorted slice would surface the oldest completions rather than the recent\n * finishes a caller is most likely looking for.\n */\nfunction recentFinishedFirst(tasks: BackgroundTask[]): BackgroundTask[] {\n\treturn tasks.sort((left, right) => (right.endedAt ?? 0) - (left.endedAt ?? 0));\n}\n\n/**\n * Listings cover backgrounded work only; foreground executions deliver inline\n * in the transcript, so they are counted rather than shown. Every listing\n * (bg list output and lookup-failure messages) shares this scope.\n */\nfunction listedTasks(background: BackgroundContext): { tasks: BackgroundTask[]; foregroundOmitted: number } {\n\tconst all = background.list();\n\tconst tasks = all.filter((task) => task.mode === \"background\");\n\treturn { tasks, foregroundOmitted: all.length - tasks.length };\n}\n\nfunction foregroundOmissionNote(omitted: number): string {\n\treturn omitted > 0\n\t\t? `\\n${omitted} foreground ${omitted === 1 ? \"execution\" : \"executions\"} omitted — foreground work is delivered inline in the transcript.`\n\t\t: \"\";\n}\n\n/**\n * Rewrite the service's bare lookup failure into an actionable one: an\n * unknown id gets the session's current tasks (active first, then recent\n * finishes) so the next call can use a real id without a separate list call,\n * and an ambiguous prefix gets exactly the tasks it matched — including\n * records outside the current branch, which listings hide but the prefix\n * still collides with.\n */\nfunction lookupFailure(background: BackgroundContext, id: string, error: unknown): Error {\n\tif (!(error instanceof BackgroundLookupError)) return error instanceof Error ? error : new Error(String(error));\n\tif (error.kind === \"ambiguous\") {\n\t\tconst lines = error.matches.map((task) => describeTaskLine(task)).join(\"\\n\");\n\t\treturn new Error(`Ambiguous background task ID \"${id}\" matches ${error.matches.length} tasks:\\n${lines}`);\n\t}\n\tconst { tasks, foregroundOmitted } = listedTasks(background);\n\tconst active = tasks.filter((task) => !isBackgroundTerminal(task.status));\n\tconst finished = recentFinishedFirst(tasks.filter((task) => isBackgroundTerminal(task.status)));\n\tconst shown = [...active, ...finished].slice(0, LOOKUP_LIST_LIMIT);\n\tif (shown.length === 0) {\n\t\treturn new Error(`No background task \"${id}\" in this session. No background tasks in this session.`);\n\t}\n\tconst lines = shown.map((task) => describeTaskLine(task)).join(\"\\n\");\n\treturn new Error(\n\t\t`No background task \"${id}\" in this session. IDs from other sessions are not valid here.\\nCurrent tasks:\\n${lines}${foregroundOmissionNote(foregroundOmitted)}`,\n\t);\n}\n/** Reserve space for each independent diagnostic before allowing raw output to fill the budget. */\nfunction readText(header: string, slice: BackgroundRead): string {\n\tconst boundedField = (text: string, maxBytes: number) =>\n\t\ttruncateHead(boundText(sanitizeBinaryOutput(text), maxBytes), { maxBytes, maxLines: 100 }).content;\n\treturn [\n\t\tboundedField(header, 4096),\n\t\tslice.task.error ? `Task error: ${boundedField(slice.task.error, 4096)}` : \"\",\n\t\tslice.readError ? `Output read error: ${boundedField(slice.readError, 4096)}` : \"\",\n\t\tboundedField(slice.task.outputPath ?? \"\", 8192),\n\t\tslice.text || \"(no output yet)\",\n\t]\n\t\t.filter(Boolean)\n\t\t.join(\"\\n\");\n}\nexport async function runRead(background: BackgroundContext, input: BgInput): Promise<AgentToolResult<BgReadDetails>> {\n\tconst id = requireTaskId(input);\n\ttry {\n\t\tconst mode = input.mode ?? \"tail\";\n\t\tconst slice = await background.read(id, { mode, bytes: clampReadBytes(input.bytes) });\n\t\treturn result(\n\t\t\treadText(\n\t\t\t\t`[${describeTaskLine(slice.task)} · ${slice.totalBytes} bytes${slice.truncated ? \" · truncated\" : \"\"}]`,\n\t\t\t\tslice,\n\t\t\t),\n\t\t\t{\n\t\t\t\taction: \"read\",\n\t\t\t\ttaskId: slice.task.id,\n\t\t\t\tmode,\n\t\t\t\tsliceBytes: Buffer.byteLength(slice.text),\n\t\t\t\ttotalBytes: slice.totalBytes,\n\t\t\t\toutputPath: slice.task.outputPath ?? \"\",\n\t\t\t\tkind: slice.task.kind,\n\t\t\t\tstatus: slice.task.status,\n\t\t\t},\n\t\t);\n\t} catch (error) {\n\t\tthrow lookupFailure(background, id, error);\n\t}\n}\nexport async function runWait(\n\tbackground: BackgroundContext,\n\tinput: BgInput,\n\tsignal?: AbortSignal,\n): Promise<AgentToolResult<BgWaitDetails>> {\n\tconst id = requireTaskId(input);\n\ttry {\n\t\tconst release = background.pin(id);\n\t\ttry {\n\t\t\tconst start = Date.now();\n\t\t\tconst task = await background.wait(id, clampWaitMs(input.waitMs), signal);\n\t\t\tconst timedOut = !isBackgroundTerminal(task.status);\n\t\t\t// A closed host resolves waits early without settling anything; do not claim\n\t\t\t// the execution merely outlived the wait window.\n\t\t\tconst windowNote = background.closed\n\t\t\t\t? \" · host closed; execution state unconfirmed\"\n\t\t\t\t: timedOut\n\t\t\t\t\t? \" · wait window expired; execution continues\"\n\t\t\t\t\t: \"\";\n\t\t\tconst slice = await background.read(id, {\n\t\t\t\tbytes: BG_WAIT_DELTA_BYTES,\n\t\t\t\tsinceBytes: clampSinceBytes(input.sinceBytes),\n\t\t\t});\n\t\t\tsignal?.throwIfAborted();\n\t\t\treturn result(readText(`[${describeTaskLine(task)}${windowNote}]`, slice), {\n\t\t\t\taction: \"wait\",\n\t\t\t\ttaskId: task.id,\n\t\t\t\t...(!timedOut ? { backgroundTaskId: task.id } : {}),\n\t\t\t\tstatus: task.status,\n\t\t\t\tkind: task.kind,\n\t\t\t\ttimedOut,\n\t\t\t\texitCode: undefined,\n\t\t\t\twaitedMs: Date.now() - start,\n\t\t\t\tdeltaBytes: Buffer.byteLength(slice.text),\n\t\t\t\ttotalBytes: slice.totalBytes,\n\t\t\t\tdeltaTruncated: slice.truncated,\n\t\t\t\toutputPath: task.outputPath ?? \"\",\n\t\t\t});\n\t\t} finally {\n\t\t\trelease();\n\t\t}\n\t} catch (error) {\n\t\tthrow lookupFailure(background, id, error);\n\t}\n}\nexport function runKill(background: BackgroundContext, input: BgInput): AgentToolResult<BgKillDetails> {\n\tconst id = requireTaskId(input);\n\ttry {\n\t\tconst task = background.get(id);\n\t\tconst requested = background.kill(task.id);\n\t\treturn result(\n\t\t\trequested\n\t\t\t\t? `Cancellation requested for ${task.id}. The task or whole group is stopping; cleanup may still be in progress.`\n\t\t\t\t: `No new cancellation requested for ${task.id} (${background.get(task.id).status}).`,\n\t\t\t{\n\t\t\t\taction: \"kill\",\n\t\t\t\ttaskId: task.id,\n\t\t\t\tcommand: task.command ?? task.title,\n\t\t\t\trequested,\n\t\t\t\tstatus: background.get(task.id).status,\n\t\t\t},\n\t\t);\n\t} catch (error) {\n\t\tthrow lookupFailure(background, id, error);\n\t}\n}\nexport function runList(background: BackgroundContext): AgentToolResult<BgListDetails> {\n\tconst { tasks, foregroundOmitted } = listedTasks(background);\n\tconst active = tasks.filter((task) => !isBackgroundTerminal(task.status));\n\tconst finished = recentFinishedFirst(tasks.filter((task) => isBackgroundTerminal(task.status)));\n\tconst shown = [...active, ...finished.slice(0, BG_LIST_FINISHED_SHOWN)].slice(0, 100);\n\tconst hidden = tasks.length - shown.length;\n\treturn result(\n\t\tshown.length\n\t\t\t? `${shown.map((task) => describeTaskLine(task)).join(\"\\n\")}${hidden > 0 ? `\\n${hidden} more records not shown.` : \"\"}${foregroundOmissionNote(foregroundOmitted)}`\n\t\t\t: `No background tasks. Start work through bash or subagent with background: true.${foregroundOmissionNote(foregroundOmitted)}`,\n\t\t{\n\t\t\taction: \"list\",\n\t\t\trunning: active.length,\n\t\t\tfinished: finished.length,\n\t\t\tshown: shown.length,\n\t\t\thidden,\n\t\t\t...(foregroundOmitted > 0 ? { foregroundOmitted } : {}),\n\t\t},\n\t);\n}\n"]}
@@ -10,7 +10,7 @@ export declare const TODO_MAX_BATCH_ITEMS = 20;
10
10
  /** Input limits keep task snapshots and model-facing output bounded. */
11
11
  export declare const TODO_MAX_SUBJECT_LENGTH = 160;
12
12
  export declare const TODO_MAX_DESCRIPTION_LENGTH = 500;
13
- export declare const TODO_TOOL_DESCRIPTION = "Manage the conversation's task list for multi-step coding work. One call applies a patch: create adds tasks (status defaults to pending), update edits tasks by id (blank or omitted fields are kept), delete removes tasks by id. Omit or leave empty any group you do not need; call with {} to list every task with its description. Exactly one task may be in_progress: setting one demotes the others. The list holds 20 tasks; when full, the oldest completed tasks are removed automatically. A call that fails validation leaves the list unchanged.\n\n{\"create\": [{\"subject\": \"Wire parser\", \"description\": \"Parser handles the config format\", \"status\": \"in_progress\"}]}\n{\"update\": [{\"id\": 1, \"status\": \"completed\"}, {\"id\": 2, \"status\": \"in_progress\"}]}\n{\"delete\": [3]}";
13
+ export declare const TODO_TOOL_DESCRIPTION = "Manage the conversation's task list for multi-step coding work. One call applies a patch: create adds tasks (status defaults to pending), update edits tasks by id (blank or omitted fields are kept), delete removes tasks by id. Omit or leave empty any group you do not need; call with {} to list every task with its description. At most one task may be in_progress: setting one demotes the others. The list holds 20 tasks; when full, the oldest completed tasks are removed automatically, except tasks created or updated in this call. Delete existing tasks in the same patch to free space; an id cannot be both updated and deleted. A call that fails validation leaves the list unchanged.\n\n{\"create\": [{\"subject\": \"Wire parser\", \"description\": \"Parser handles the config format\", \"status\": \"in_progress\"}]}\n{\"update\": [{\"id\": 1, \"status\": \"completed\"}, {\"id\": 2, \"status\": \"in_progress\"}]}\n{\"delete\": [3]}";
14
14
  export declare const TODO_PROMPT_SNIPPET = "Track multi-step coding work with a small outcome-oriented task list";
15
15
  export declare const TODO_PROMPT_GUIDELINES: string[];
16
16
  //# sourceMappingURL=constants.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,eAAe,SAAS,CAAC;AACtC,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C,0EAA0E;AAC1E,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAO/C,eAAO,MAAM,qBAAqB,8xBAIlB,CAAC;AAEjB,eAAO,MAAM,mBAAmB,yEAAyE,CAAC;AAE1G,eAAO,MAAM,sBAAsB,UAKlC,CAAC"}
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,cAAc,SAAS,CAAC;AACrC,eAAO,MAAM,eAAe,SAAS,CAAC;AACtC,eAAO,MAAM,kBAAkB,UAAU,CAAC;AAE1C,0EAA0E;AAC1E,eAAO,MAAM,cAAc,KAAK,CAAC;AACjC,eAAO,MAAM,oBAAoB,KAAK,CAAC;AAEvC,wEAAwE;AACxE,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAO/C,eAAO,MAAM,qBAAqB,66BAIlB,CAAC;AAEjB,eAAO,MAAM,mBAAmB,yEAAyE,CAAC;AAE1G,eAAO,MAAM,sBAAsB,UAKlC,CAAC"}
@@ -15,7 +15,7 @@ export const TODO_MAX_DESCRIPTION_LENGTH = 500;
15
15
  // Per-parameter bounds and wording live in the schema. The single-in_progress
16
16
  // rule is stated in both places on purpose: it is the invariant models break
17
17
  // most often.
18
- export const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for multi-step coding work. One call applies a patch: create adds tasks (status defaults to pending), update edits tasks by id (blank or omitted fields are kept), delete removes tasks by id. Omit or leave empty any group you do not need; call with {} to list every task with its description. Exactly one task may be in_progress: setting one demotes the others. The list holds 20 tasks; when full, the oldest completed tasks are removed automatically. A call that fails validation leaves the list unchanged.
18
+ export const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for multi-step coding work. One call applies a patch: create adds tasks (status defaults to pending), update edits tasks by id (blank or omitted fields are kept), delete removes tasks by id. Omit or leave empty any group you do not need; call with {} to list every task with its description. At most one task may be in_progress: setting one demotes the others. The list holds 20 tasks; when full, the oldest completed tasks are removed automatically, except tasks created or updated in this call. Delete existing tasks in the same patch to free space; an id cannot be both updated and deleted. A call that fails validation leaves the list unchanged.
19
19
 
20
20
  {"create": [{"subject": "Wire parser", "description": "Parser handles the config format", "status": "in_progress"}]}
21
21
  {"update": [{"id": 1, "status": "completed"}, {"id": 2, "status": "in_progress"}]}
@@ -23,7 +23,7 @@ export const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for mu
23
23
  export const TODO_PROMPT_SNIPPET = "Track multi-step coding work with a small outcome-oriented task list";
24
24
  export const TODO_PROMPT_GUIDELINES = [
25
25
  "Use `todo` for work with three or more meaningful steps, for user-provided task lists, and in long sessions where progress can drift; skip it for trivial single-step tasks and simple Q&A.",
26
- "Keep exactly one `todo` task in_progress: mark it before starting the work, and completed only after its description is satisfied — never while tests fail or work is partial.",
26
+ "Mark the `todo` task you are working on in_progress before starting, and completed only after its description is satisfied. Leave no task in_progress when waiting or when all work is done.",
27
27
  "When blocked, leave the `todo` task pending and create a task for the blocker instead of faking completion.",
28
28
  "Keep the `todo` list short: list before create to avoid duplicates, work in id order, and delete obsolete tasks promptly.",
29
29
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/extensions/todo/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AACtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC,wEAAwE;AACxE,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAE/C,4EAA4E;AAC5E,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,cAAc;AACd,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;gBAIrB,CAAC;AAEjB,MAAM,CAAC,MAAM,mBAAmB,GAAG,sEAAsE,CAAC;AAE1G,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,6LAA6L;IAC7L,gLAAgL;IAChL,6GAA6G;IAC7G,2HAA2H;CAC3H,CAAC","sourcesContent":["/**\n * todo/constants.ts — tool identity, command name, input bounds, and prompt copy.\n */\n\nexport const TODO_TOOL_NAME = \"todo\";\nexport const TODO_TOOL_LABEL = \"Todo\";\nexport const TODOS_COMMAND_NAME = \"todos\";\n\n/** Hard capacity of the task list and of a single create/delete batch. */\nexport const TODO_MAX_ITEMS = 20;\nexport const TODO_MAX_BATCH_ITEMS = 20;\n\n/** Input limits keep task snapshots and model-facing output bounded. */\nexport const TODO_MAX_SUBJECT_LENGTH = 160;\nexport const TODO_MAX_DESCRIPTION_LENGTH = 500;\n\n// Mechanism plus the cross-field rules TodoParamsSchema cannot state (total\n// capacity with automatic reclaim, the single-in_progress rule, atomicity).\n// Per-parameter bounds and wording live in the schema. The single-in_progress\n// rule is stated in both places on purpose: it is the invariant models break\n// most often.\nexport const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for multi-step coding work. One call applies a patch: create adds tasks (status defaults to pending), update edits tasks by id (blank or omitted fields are kept), delete removes tasks by id. Omit or leave empty any group you do not need; call with {} to list every task with its description. Exactly one task may be in_progress: setting one demotes the others. The list holds 20 tasks; when full, the oldest completed tasks are removed automatically. A call that fails validation leaves the list unchanged.\n\n{\"create\": [{\"subject\": \"Wire parser\", \"description\": \"Parser handles the config format\", \"status\": \"in_progress\"}]}\n{\"update\": [{\"id\": 1, \"status\": \"completed\"}, {\"id\": 2, \"status\": \"in_progress\"}]}\n{\"delete\": [3]}`;\n\nexport const TODO_PROMPT_SNIPPET = \"Track multi-step coding work with a small outcome-oriented task list\";\n\nexport const TODO_PROMPT_GUIDELINES = [\n\t\"Use `todo` for work with three or more meaningful steps, for user-provided task lists, and in long sessions where progress can drift; skip it for trivial single-step tasks and simple Q&A.\",\n\t\"Keep exactly one `todo` task in_progress: mark it before starting the work, and completed only after its description is satisfied — never while tests fail or work is partial.\",\n\t\"When blocked, leave the `todo` task pending and create a task for the blocker instead of faking completion.\",\n\t\"Keep the `todo` list short: list before create to avoid duplicates, work in id order, and delete obsolete tasks promptly.\",\n];\n"]}
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../../src/extensions/todo/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC;AACrC,MAAM,CAAC,MAAM,eAAe,GAAG,MAAM,CAAC;AACtC,MAAM,CAAC,MAAM,kBAAkB,GAAG,OAAO,CAAC;AAE1C,0EAA0E;AAC1E,MAAM,CAAC,MAAM,cAAc,GAAG,EAAE,CAAC;AACjC,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AAEvC,wEAAwE;AACxE,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAE/C,4EAA4E;AAC5E,4EAA4E;AAC5E,8EAA8E;AAC9E,6EAA6E;AAC7E,cAAc;AACd,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;gBAIrB,CAAC;AAEjB,MAAM,CAAC,MAAM,mBAAmB,GAAG,sEAAsE,CAAC;AAE1G,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,6LAA6L;IAC7L,8LAA8L;IAC9L,6GAA6G;IAC7G,2HAA2H;CAC3H,CAAC","sourcesContent":["/**\n * todo/constants.ts — tool identity, command name, input bounds, and prompt copy.\n */\n\nexport const TODO_TOOL_NAME = \"todo\";\nexport const TODO_TOOL_LABEL = \"Todo\";\nexport const TODOS_COMMAND_NAME = \"todos\";\n\n/** Hard capacity of the task list and of a single create/delete batch. */\nexport const TODO_MAX_ITEMS = 20;\nexport const TODO_MAX_BATCH_ITEMS = 20;\n\n/** Input limits keep task snapshots and model-facing output bounded. */\nexport const TODO_MAX_SUBJECT_LENGTH = 160;\nexport const TODO_MAX_DESCRIPTION_LENGTH = 500;\n\n// Mechanism plus the cross-field rules TodoParamsSchema cannot state (total\n// capacity with automatic reclaim, the single-in_progress rule, atomicity).\n// Per-parameter bounds and wording live in the schema. The single-in_progress\n// rule is stated in both places on purpose: it is the invariant models break\n// most often.\nexport const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for multi-step coding work. One call applies a patch: create adds tasks (status defaults to pending), update edits tasks by id (blank or omitted fields are kept), delete removes tasks by id. Omit or leave empty any group you do not need; call with {} to list every task with its description. At most one task may be in_progress: setting one demotes the others. The list holds 20 tasks; when full, the oldest completed tasks are removed automatically, except tasks created or updated in this call. Delete existing tasks in the same patch to free space; an id cannot be both updated and deleted. A call that fails validation leaves the list unchanged.\n\n{\"create\": [{\"subject\": \"Wire parser\", \"description\": \"Parser handles the config format\", \"status\": \"in_progress\"}]}\n{\"update\": [{\"id\": 1, \"status\": \"completed\"}, {\"id\": 2, \"status\": \"in_progress\"}]}\n{\"delete\": [3]}`;\n\nexport const TODO_PROMPT_SNIPPET = \"Track multi-step coding work with a small outcome-oriented task list\";\n\nexport const TODO_PROMPT_GUIDELINES = [\n\t\"Use `todo` for work with three or more meaningful steps, for user-provided task lists, and in long sessions where progress can drift; skip it for trivial single-step tasks and simple Q&A.\",\n\t\"Mark the `todo` task you are working on in_progress before starting, and completed only after its description is satisfied. Leave no task in_progress when waiting or when all work is done.\",\n\t\"When blocked, leave the `todo` task pending and create a task for the blocker instead of faking completion.\",\n\t\"Keep the `todo` list short: list before create to avoid duplicates, work in id order, and delete obsolete tasks promptly.\",\n];\n"]}
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * todo — Pi-native task tracking for multi-step work.
3
3
  *
4
- * State is conversation-backed: every tool result carries a full v2 snapshot
4
+ * State is conversation-backed: every tool result carries a full v3 snapshot
5
5
  * in `details`, and lifecycle handlers replay the current branch into the
6
6
  * closure store created by createTodoStore(). Resume, /reload, and /tree
7
7
  * navigation stay aligned with the conversation without a disk database or
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/todo/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAC9C,OAAO,EAGN,4BAA4B,GAE5B,MAAM,gCAAgC,CAAC;AACxC,OAAO,EACN,sBAAsB,EACtB,mBAAmB,EACnB,qBAAqB,EACrB,eAAe,EACf,cAAc,EACd,kBAAkB,GAClB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAoB,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAkB,MAAM,YAAY,CAAC;AACpF,OAAO,EAAE,iBAAiB,EAAE,cAAc,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AACtG,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzC,SAAS,UAAU,CAAC,GAAmB,EAAE,KAAgB;IACxD,4EAA4E;IAC5E,yEAAyE;IACzE,wEAAwE;IACxE,IAAI,CAAC;QACJ,KAAK,CAAC,YAAY,CAAC,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,IAAI,CAAC,4BAA4B,CAAC,KAAK,CAAC;YAAE,MAAM,KAAK,CAAC;IACvD,CAAC;AACF,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAgB;IAC5C,MAAM,KAAK,GAAG,eAAe,EAAE,CAAC;IAChC,IAAI,MAA8B,CAAC;IAEnC,EAAE,CAAC,YAAY,CAAC;QACf,IAAI,EAAE,cAAc;QACpB,KAAK,EAAE,eAAe;QACtB,WAAW,EAAE,qBAAqB;QAClC,aAAa,EAAE,mBAAmB;QAClC,gBAAgB,EAAE,sBAAsB;QACxC,UAAU,EAAE,gBAAgB;QAC5B,mBAAmB,EAAE,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE;QAC9D,aAAa,EAAE,YAAiC;QAChD,0EAA0E;QAC1E,0EAA0E;QAC1E,oCAAoC;QACpC,SAAS,EAAE,cAAc;QAEzB,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS;YACpD,qEAAqE;YACrE,wBAAwB;YACxB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACtC,MAAM,IAAI,GAAG,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9D,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,OAAO,EAAE,CAAC;QACvD,CAAC;QAED,UAAU,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO;YAC9B,MAAM,IAAI,GAAI,OAAO,CAAC,aAAkC,IAAI,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;YAC/E,IAAI,CAAC,OAAO,CACX,OAAO,CAAC,gBAAgB;gBACvB,CAAC,CAAC,mBAAmB,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC;gBAC3C,CAAC,CAAC,cAAc,CAAC,IAAI,EAAE,KAAK,EAAE,OAAO,CAAC,QAAQ,EAAE,OAAO,CAAC,MAAM,CAAC,CAChE,CAAC;YACF,OAAO,IAAI,CAAC;QACb,CAAC;KACD,CAAC,CAAC;IAEH,EAAE,CAAC,eAAe,CAAC,kBAAkB,EAAE;QACtC,WAAW,EAAE,iEAAiE;QAC9E,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7B,IAAI,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC;gBAChB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,oCAAoC,EAAE,SAAS,CAAC,CAAC;gBAC/D,OAAO;YACR,CAAC;YACD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAC5D,CAAC;KACD,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;QAC5C,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACvB,IAAI,GAAG,CAAC,KAAK,EAAE,CAAC;YACf,MAAM,KAAK,IAAI,UAAU,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC;YAClD,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACrB,MAAM,CAAC,MAAM,EAAE,CAAC;QACjB,CAAC;IACF,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,EAAE;QAC3C,UAAU,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QACvB,MAAM,EAAE,MAAM,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,kBAAkB,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;QAChD,wEAAwE;QACxE,gEAAgE;QAChE,MAAM,EAAE,OAAO,EAAE,CAAC;QAClB,MAAM,GAAG,SAAS,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,EAAE,CAAC,oBAAoB,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE;QAC3C,IAAI,KAAK,CAAC,QAAQ,KAAK,cAAc,IAAI,KAAK,CAAC,OAAO;YAAE,OAAO;QAC/D,MAAM,EAAE,MAAM,EAAE,CAAC;IAClB,CAAC,CAAC,CAAC;AACJ,CAAC","sourcesContent":["/**\n * todo — Pi-native task tracking for multi-step work.\n *\n * State is conversation-backed: every tool result carries a full v2 snapshot\n * in `details`, and lifecycle handlers replay the current branch into the\n * closure store created by createTodoStore(). Resume, /reload, and /tree\n * navigation stay aligned with the conversation without a disk database or\n * any per-session global state.\n */\nimport { Text } from \"@earendil-works/pi-tui\";\nimport {\n\ttype AgentToolResult,\n\ttype ExtensionAPI,\n\tisStaleExtensionContextError,\n\ttype ToolExecutionMode,\n} from \"../../core/extensions/types.ts\";\nimport {\n\tTODO_PROMPT_GUIDELINES,\n\tTODO_PROMPT_SNIPPET,\n\tTODO_TOOL_DESCRIPTION,\n\tTODO_TOOL_LABEL,\n\tTODO_TOOL_NAME,\n\tTODOS_COMMAND_NAME,\n} from \"./constants.ts\";\nimport { type TodoDetails, TodoParamsSchema } from \"./schema.ts\";\nimport { createTodoStore, replayTodosFromBranch, type TodoStore } from \"./state.ts\";\nimport { formatCommandList, formatTodoCall, formatTodoContent, formatTodoGroupCall } from \"./view.ts\";\nimport { TodoWidget } from \"./widget.ts\";\n\ninterface TodoSessionCtx {\n\tsessionManager: { getBranch(): Iterable<unknown> };\n}\n\nfunction safeReplay(ctx: TodoSessionCtx, store: TodoStore): void {\n\t// Every ctx.sessionManager access stays inside the guard: a lifecycle event\n\t// can race session replacement (resume, /tree, /reload), and a stale ctx\n\t// just means another session took over — nothing left to replay for it.\n\ttry {\n\t\tstore.replaceState(replayTodosFromBranch(ctx));\n\t} catch (error) {\n\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t}\n}\n\nexport default function todo(pi: ExtensionAPI): void {\n\tconst store = createTodoStore();\n\tlet widget: TodoWidget | undefined;\n\n\tpi.registerTool({\n\t\tname: TODO_TOOL_NAME,\n\t\tlabel: TODO_TOOL_LABEL,\n\t\tdescription: TODO_TOOL_DESCRIPTION,\n\t\tpromptSnippet: TODO_PROMPT_SNIPPET,\n\t\tpromptGuidelines: TODO_PROMPT_GUIDELINES,\n\t\tparameters: TodoParamsSchema,\n\t\tconstrainedSampling: { type: \"json_schema\", strict: \"prefer\" },\n\t\texecutionMode: \"sequential\" as ToolExecutionMode,\n\t\t// Consecutive todo calls collapse into one run like read/find's `explore`\n\t\t// group; the widget already carries the live list, so the transcript only\n\t\t// needs the sequence of operations.\n\t\ttoolGroup: TODO_TOOL_NAME,\n\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate): Promise<AgentToolResult<TodoDetails>> {\n\t\t\t// Validation errors throw before any state mutation, so the store is\n\t\t\t// untouched on failure.\n\t\t\tconst details = store.execute(params);\n\t\t\tconst text = formatTodoContent(details.change, details.state);\n\t\t\treturn { content: [{ type: \"text\", text }], details };\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(\n\t\t\t\tcontext.toolGroupSummary\n\t\t\t\t\t? formatTodoGroupCall(args, theme, context)\n\t\t\t\t\t: formatTodoCall(args, theme, context.expanded, context.result),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t});\n\n\tpi.registerCommand(TODOS_COMMAND_NAME, {\n\t\tdescription: \"Show the complete todo list for the current conversation branch\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tctx.ui.notify(\"/todos requires an interactive UI.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tctx.ui.notify(formatCommandList(store.getState()), \"info\");\n\t\t},\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsafeReplay(ctx, store);\n\t\tif (ctx.hasUI) {\n\t\t\twidget ??= new TodoWidget(() => store.getState());\n\t\t\twidget.setUI(ctx.ui);\n\t\t\twidget.update();\n\t\t}\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsafeReplay(ctx, store);\n\t\twidget?.update();\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, _ctx) => {\n\t\t// The store belongs to this extension runtime and is collected with it;\n\t\t// the next runtime restores its own store during session_start.\n\t\twidget?.dispose();\n\t\twidget = undefined;\n\t});\n\n\tpi.on(\"tool_execution_end\", async (event) => {\n\t\tif (event.toolName !== TODO_TOOL_NAME || event.isError) return;\n\t\twidget?.update();\n\t});\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/extensions/todo/index.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * todo — Pi-native task tracking for multi-step work.\n *\n * State is conversation-backed: every tool result carries a full v3 snapshot\n * in `details`, and lifecycle handlers replay the current branch into the\n * closure store created by createTodoStore(). Resume, /reload, and /tree\n * navigation stay aligned with the conversation without a disk database or\n * any per-session global state.\n */\nimport { Text } from \"@earendil-works/pi-tui\";\nimport {\n\ttype AgentToolResult,\n\ttype ExtensionAPI,\n\tisStaleExtensionContextError,\n\ttype ToolExecutionMode,\n} from \"../../core/extensions/types.ts\";\nimport {\n\tTODO_PROMPT_GUIDELINES,\n\tTODO_PROMPT_SNIPPET,\n\tTODO_TOOL_DESCRIPTION,\n\tTODO_TOOL_LABEL,\n\tTODO_TOOL_NAME,\n\tTODOS_COMMAND_NAME,\n} from \"./constants.ts\";\nimport { type TodoDetails, TodoParamsSchema } from \"./schema.ts\";\nimport { createTodoStore, replayTodosFromBranch, type TodoStore } from \"./state.ts\";\nimport { formatCommandList, formatTodoCall, formatTodoContent, formatTodoGroupCall } from \"./view.ts\";\nimport { TodoWidget } from \"./widget.ts\";\n\ninterface TodoSessionCtx {\n\tsessionManager: { getBranch(): Iterable<unknown> };\n}\n\nfunction safeReplay(ctx: TodoSessionCtx, store: TodoStore): void {\n\t// Every ctx.sessionManager access stays inside the guard: a lifecycle event\n\t// can race session replacement (resume, /tree, /reload), and a stale ctx\n\t// just means another session took over — nothing left to replay for it.\n\ttry {\n\t\tstore.replaceState(replayTodosFromBranch(ctx));\n\t} catch (error) {\n\t\tif (!isStaleExtensionContextError(error)) throw error;\n\t}\n}\n\nexport default function todo(pi: ExtensionAPI): void {\n\tconst store = createTodoStore();\n\tlet widget: TodoWidget | undefined;\n\n\tpi.registerTool({\n\t\tname: TODO_TOOL_NAME,\n\t\tlabel: TODO_TOOL_LABEL,\n\t\tdescription: TODO_TOOL_DESCRIPTION,\n\t\tpromptSnippet: TODO_PROMPT_SNIPPET,\n\t\tpromptGuidelines: TODO_PROMPT_GUIDELINES,\n\t\tparameters: TodoParamsSchema,\n\t\tconstrainedSampling: { type: \"json_schema\", strict: \"prefer\" },\n\t\texecutionMode: \"sequential\" as ToolExecutionMode,\n\t\t// Consecutive todo calls collapse into one run like read/find's `explore`\n\t\t// group; the widget already carries the live list, so the transcript only\n\t\t// needs the sequence of operations.\n\t\ttoolGroup: TODO_TOOL_NAME,\n\n\t\tasync execute(_toolCallId, params, _signal, _onUpdate): Promise<AgentToolResult<TodoDetails>> {\n\t\t\t// Validation errors throw before any state mutation, so the store is\n\t\t\t// untouched on failure.\n\t\t\tconst details = store.execute(params);\n\t\t\tconst text = formatTodoContent(details.change, details.state);\n\t\t\treturn { content: [{ type: \"text\", text }], details };\n\t\t},\n\n\t\trenderCall(args, theme, context) {\n\t\t\tconst text = (context.lastComponent as Text | undefined) ?? new Text(\"\", 0, 0);\n\t\t\ttext.setText(\n\t\t\t\tcontext.toolGroupSummary\n\t\t\t\t\t? formatTodoGroupCall(args, theme, context)\n\t\t\t\t\t: formatTodoCall(args, theme, context.expanded, context.result),\n\t\t\t);\n\t\t\treturn text;\n\t\t},\n\t});\n\n\tpi.registerCommand(TODOS_COMMAND_NAME, {\n\t\tdescription: \"Show the complete todo list for the current conversation branch\",\n\t\thandler: async (_args, ctx) => {\n\t\t\tif (!ctx.hasUI) {\n\t\t\t\tctx.ui.notify(\"/todos requires an interactive UI.\", \"warning\");\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tctx.ui.notify(formatCommandList(store.getState()), \"info\");\n\t\t},\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsafeReplay(ctx, store);\n\t\tif (ctx.hasUI) {\n\t\t\twidget ??= new TodoWidget(() => store.getState());\n\t\t\twidget.setUI(ctx.ui);\n\t\t\twidget.update();\n\t\t}\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsafeReplay(ctx, store);\n\t\twidget?.update();\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, _ctx) => {\n\t\t// The store belongs to this extension runtime and is collected with it;\n\t\t// the next runtime restores its own store during session_start.\n\t\twidget?.dispose();\n\t\twidget = undefined;\n\t});\n\n\tpi.on(\"tool_execution_end\", async (event) => {\n\t\tif (event.toolName !== TODO_TOOL_NAME || event.isError) return;\n\t\twidget?.update();\n\t});\n}\n"]}
@@ -19,6 +19,7 @@ export declare const TODO_DETAILS_SCHEMA_VERSION = 3;
19
19
  */
20
20
  export interface TodoChange {
21
21
  created: number[];
22
+ /** Explicitly updated tasks, from their prior status to the final snapshot status. */
22
23
  updated: Array<{
23
24
  id: number;
24
25
  from: TodoStatus;
@@ -1 +1 @@
1
- {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/schema.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,+DAA+D;AAC/D,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IACjE,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,aAAa,EAAE,OAAO,2BAA2B,CAAC;IAClD,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;CACjB;AA4CD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;EAsB5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,EAAE,SAAoC,CAAC"}
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/schema.ts"],"names":[],"mappings":"AASA,OAAO,EAAE,KAAK,MAAM,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAG5C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAEjE,MAAM,WAAW,QAAQ;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CACf;AAED,+DAA+D;AAC/D,eAAO,MAAM,2BAA2B,IAAI,CAAC;AAE7C;;;;GAIG;AACH,MAAM,WAAW,UAAU;IAC1B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,sFAAsF;IACtF,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,UAAU,CAAC;QAAC,EAAE,EAAE,UAAU,CAAA;KAAE,CAAC,CAAC;IACjE,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,WAAW;IAC3B,aAAa,EAAE,OAAO,2BAA2B,CAAC;IAClD,MAAM,EAAE,UAAU,CAAC;IACnB,KAAK,EAAE,SAAS,CAAC;CACjB;AA4CD,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;EAsB5B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAEzD,eAAO,MAAM,gBAAgB,EAAE,SAAoC,CAAC"}
@@ -12,7 +12,7 @@ import { TODO_MAX_BATCH_ITEMS, TODO_MAX_DESCRIPTION_LENGTH, TODO_MAX_SUBJECT_LEN
12
12
  /** Current shape of snapshots written to todo tool results. */
13
13
  export const TODO_DETAILS_SCHEMA_VERSION = 3;
14
14
  const StatusSchema = StringEnum(["pending", "in_progress", "completed"], {
15
- description: "Task status: pending for future work, in_progress for the single active task, completed for verified done work. Exactly one task may be in_progress; setting one demotes any other active task to pending.",
15
+ description: "Task status: pending for future work, in_progress for the single active task, completed for verified done work. At most one task may be in_progress; setting one demotes any other active task to pending.",
16
16
  });
17
17
  const TaskIdSchema = Type.Integer({ minimum: 1, description: "Positive task id." });
18
18
  const CreateItemSchema = Type.Object({
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/todo/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAgB5G,+DAA+D;AAC/D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAsB7C,MAAM,YAAY,GAAG,UAAU,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAU,EAAE;IACjF,WAAW,EACV,4MAA4M;CAC7M,CAAC,CAAC;AAEH,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC,CAAC;AAEpF,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CACnC;IACC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC;QACpB,SAAS,EAAE,uBAAuB;QAClC,WAAW,EAAE,2DAA2D;KACxE,CAAC;IACF,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC;QACxB,SAAS,EAAE,2BAA2B;QACtC,WAAW,EAAE,4EAA4E;KACzF,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;CACnC,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CACnC;IACC,EAAE,EAAE,YAAY;IAChB,OAAO,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC;QACX,SAAS,EAAE,uBAAuB;QAClC,WAAW,EAAE,8DAA8D;KAC3E,CAAC,CACF;IACD,WAAW,EAAE,IAAI,CAAC,QAAQ,CACzB,IAAI,CAAC,MAAM,CAAC;QACX,SAAS,EAAE,2BAA2B;QACtC,WAAW,EAAE,oFAAoF;KACjG,CAAC,CACF;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;CACnC,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAC1C;IACC,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE;QAC5B,QAAQ,EAAE,oBAAoB;QAC9B,WAAW,EAAE,8EAA8E;KAC3F,CAAC,CACF;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE;QAC5B,QAAQ,EAAE,oBAAoB;QAC9B,WAAW,EAAE,sEAAsE;KACnF,CAAC,CACF;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE;QACxB,QAAQ,EAAE,oBAAoB;QAC9B,WAAW,EAAE,wFAAwF;KACrG,CAAC,CACF;CACD,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAIF,MAAM,CAAC,MAAM,gBAAgB,GAAc,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC","sourcesContent":["/**\n * todo/schema.ts — v3 tool parameters, state shape, and operation model.\n *\n * One call is one patch: the three group fields (create, update, delete) are\n * applied together, there is no action discriminator. The shape is built for\n * strict constrained sampling: optional groups can be null or empty and every\n * filler value a strict sampler may emit ([], \"\", null) is a harmless no-op.\n */\nimport { StringEnum } from \"@earendil-works/pi-ai\";\nimport { type Static, Type } from \"typebox\";\nimport { TODO_MAX_BATCH_ITEMS, TODO_MAX_DESCRIPTION_LENGTH, TODO_MAX_SUBJECT_LENGTH } from \"./constants.ts\";\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: number;\n\tsubject: string;\n\tdescription: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoState {\n\titems: TodoItem[];\n\tnextId: number;\n}\n\n/** Current shape of snapshots written to todo tool results. */\nexport const TODO_DETAILS_SCHEMA_VERSION = 3;\n\n/**\n * What one call changed, as a flat patch record. Every list is empty for a\n * pure list call; `demotedId` is present only when an activation pushed the\n * previously active task back to pending.\n */\nexport interface TodoChange {\n\tcreated: number[];\n\tupdated: Array<{ id: number; from: TodoStatus; to: TodoStatus }>;\n\tdeleted: Array<{ id: number; subject: string }>;\n\tabsent: number[];\n\tevicted: Array<{ id: number; subject: string }>;\n\tdemotedId?: number;\n}\n\nexport interface TodoDetails {\n\tschemaVersion: typeof TODO_DETAILS_SCHEMA_VERSION;\n\tchange: TodoChange;\n\tstate: TodoState;\n}\n\nconst StatusSchema = StringEnum([\"pending\", \"in_progress\", \"completed\"] as const, {\n\tdescription:\n\t\t\"Task status: pending for future work, in_progress for the single active task, completed for verified done work. Exactly one task may be in_progress; setting one demotes any other active task to pending.\",\n});\n\nconst TaskIdSchema = Type.Integer({ minimum: 1, description: \"Positive task id.\" });\n\nconst CreateItemSchema = Type.Object(\n\t{\n\t\tsubject: Type.String({\n\t\t\tmaxLength: TODO_MAX_SUBJECT_LENGTH,\n\t\t\tdescription: \"Short imperative task subject; a reviewable unit of work.\",\n\t\t}),\n\t\tdescription: Type.String({\n\t\t\tmaxLength: TODO_MAX_DESCRIPTION_LENGTH,\n\t\t\tdescription: \"What done means for this task: acceptance criteria or verification detail.\",\n\t\t}),\n\t\tstatus: Type.Optional(StatusSchema),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst UpdateItemSchema = Type.Object(\n\t{\n\t\tid: TaskIdSchema,\n\t\tsubject: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tmaxLength: TODO_MAX_SUBJECT_LENGTH,\n\t\t\t\tdescription: \"Replacement subject; blank or omitted keeps the current one.\",\n\t\t\t}),\n\t\t),\n\t\tdescription: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tmaxLength: TODO_MAX_DESCRIPTION_LENGTH,\n\t\t\t\tdescription: \"Replacement description (what done means); blank or omitted keeps the current one.\",\n\t\t\t}),\n\t\t),\n\t\tstatus: Type.Optional(StatusSchema),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport const TodoParamsSchema = Type.Object(\n\t{\n\t\tcreate: Type.Optional(\n\t\t\tType.Array(CreateItemSchema, {\n\t\t\t\tmaxItems: TODO_MAX_BATCH_ITEMS,\n\t\t\t\tdescription: \"Tasks to add, created atomically in input order; status defaults to pending.\",\n\t\t\t}),\n\t\t),\n\t\tupdate: Type.Optional(\n\t\t\tType.Array(UpdateItemSchema, {\n\t\t\t\tmaxItems: TODO_MAX_BATCH_ITEMS,\n\t\t\t\tdescription: \"Task edits by id; blank or omitted fields keep their current values.\",\n\t\t\t}),\n\t\t),\n\t\tdelete: Type.Optional(\n\t\t\tType.Array(TaskIdSchema, {\n\t\t\t\tmaxItems: TODO_MAX_BATCH_ITEMS,\n\t\t\t\tdescription: \"Ids to remove from the current list; deleting an id that is already absent is a no-op.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport type TodoParams = Static<typeof TodoParamsSchema>;\n\nexport const EMPTY_TODO_STATE: TodoState = { items: [], nextId: 1 };\n"]}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../../src/extensions/todo/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAe,IAAI,EAAE,MAAM,SAAS,CAAC;AAC5C,OAAO,EAAE,oBAAoB,EAAE,2BAA2B,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAgB5G,+DAA+D;AAC/D,MAAM,CAAC,MAAM,2BAA2B,GAAG,CAAC,CAAC;AAuB7C,MAAM,YAAY,GAAG,UAAU,CAAC,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,CAAU,EAAE;IACjF,WAAW,EACV,4MAA4M;CAC7M,CAAC,CAAC;AAEH,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,WAAW,EAAE,mBAAmB,EAAE,CAAC,CAAC;AAEpF,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CACnC;IACC,OAAO,EAAE,IAAI,CAAC,MAAM,CAAC;QACpB,SAAS,EAAE,uBAAuB;QAClC,WAAW,EAAE,2DAA2D;KACxE,CAAC;IACF,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC;QACxB,SAAS,EAAE,2BAA2B;QACtC,WAAW,EAAE,4EAA4E;KACzF,CAAC;IACF,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;CACnC,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAEF,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CACnC;IACC,EAAE,EAAE,YAAY;IAChB,OAAO,EAAE,IAAI,CAAC,QAAQ,CACrB,IAAI,CAAC,MAAM,CAAC;QACX,SAAS,EAAE,uBAAuB;QAClC,WAAW,EAAE,8DAA8D;KAC3E,CAAC,CACF;IACD,WAAW,EAAE,IAAI,CAAC,QAAQ,CACzB,IAAI,CAAC,MAAM,CAAC;QACX,SAAS,EAAE,2BAA2B;QACtC,WAAW,EAAE,oFAAoF;KACjG,CAAC,CACF;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CAAC,YAAY,CAAC;CACnC,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC,MAAM,CAC1C;IACC,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE;QAC5B,QAAQ,EAAE,oBAAoB;QAC9B,WAAW,EAAE,8EAA8E;KAC3F,CAAC,CACF;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,KAAK,CAAC,gBAAgB,EAAE;QAC5B,QAAQ,EAAE,oBAAoB;QAC9B,WAAW,EAAE,sEAAsE;KACnF,CAAC,CACF;IACD,MAAM,EAAE,IAAI,CAAC,QAAQ,CACpB,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE;QACxB,QAAQ,EAAE,oBAAoB;QAC9B,WAAW,EAAE,wFAAwF;KACrG,CAAC,CACF;CACD,EACD,EAAE,oBAAoB,EAAE,KAAK,EAAE,CAC/B,CAAC;AAIF,MAAM,CAAC,MAAM,gBAAgB,GAAc,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC","sourcesContent":["/**\n * todo/schema.ts — v3 tool parameters, state shape, and operation model.\n *\n * One call is one patch: the three group fields (create, update, delete) are\n * applied together, there is no action discriminator. The shape is built for\n * strict constrained sampling: optional groups can be null or empty and every\n * filler value a strict sampler may emit ([], \"\", null) is a harmless no-op.\n */\nimport { StringEnum } from \"@earendil-works/pi-ai\";\nimport { type Static, Type } from \"typebox\";\nimport { TODO_MAX_BATCH_ITEMS, TODO_MAX_DESCRIPTION_LENGTH, TODO_MAX_SUBJECT_LENGTH } from \"./constants.ts\";\n\nexport type TodoStatus = \"pending\" | \"in_progress\" | \"completed\";\n\nexport interface TodoItem {\n\tid: number;\n\tsubject: string;\n\tdescription: string;\n\tstatus: TodoStatus;\n}\n\nexport interface TodoState {\n\titems: TodoItem[];\n\tnextId: number;\n}\n\n/** Current shape of snapshots written to todo tool results. */\nexport const TODO_DETAILS_SCHEMA_VERSION = 3;\n\n/**\n * What one call changed, as a flat patch record. Every list is empty for a\n * pure list call; `demotedId` is present only when an activation pushed the\n * previously active task back to pending.\n */\nexport interface TodoChange {\n\tcreated: number[];\n\t/** Explicitly updated tasks, from their prior status to the final snapshot status. */\n\tupdated: Array<{ id: number; from: TodoStatus; to: TodoStatus }>;\n\tdeleted: Array<{ id: number; subject: string }>;\n\tabsent: number[];\n\tevicted: Array<{ id: number; subject: string }>;\n\tdemotedId?: number;\n}\n\nexport interface TodoDetails {\n\tschemaVersion: typeof TODO_DETAILS_SCHEMA_VERSION;\n\tchange: TodoChange;\n\tstate: TodoState;\n}\n\nconst StatusSchema = StringEnum([\"pending\", \"in_progress\", \"completed\"] as const, {\n\tdescription:\n\t\t\"Task status: pending for future work, in_progress for the single active task, completed for verified done work. At most one task may be in_progress; setting one demotes any other active task to pending.\",\n});\n\nconst TaskIdSchema = Type.Integer({ minimum: 1, description: \"Positive task id.\" });\n\nconst CreateItemSchema = Type.Object(\n\t{\n\t\tsubject: Type.String({\n\t\t\tmaxLength: TODO_MAX_SUBJECT_LENGTH,\n\t\t\tdescription: \"Short imperative task subject; a reviewable unit of work.\",\n\t\t}),\n\t\tdescription: Type.String({\n\t\t\tmaxLength: TODO_MAX_DESCRIPTION_LENGTH,\n\t\t\tdescription: \"What done means for this task: acceptance criteria or verification detail.\",\n\t\t}),\n\t\tstatus: Type.Optional(StatusSchema),\n\t},\n\t{ additionalProperties: false },\n);\n\nconst UpdateItemSchema = Type.Object(\n\t{\n\t\tid: TaskIdSchema,\n\t\tsubject: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tmaxLength: TODO_MAX_SUBJECT_LENGTH,\n\t\t\t\tdescription: \"Replacement subject; blank or omitted keeps the current one.\",\n\t\t\t}),\n\t\t),\n\t\tdescription: Type.Optional(\n\t\t\tType.String({\n\t\t\t\tmaxLength: TODO_MAX_DESCRIPTION_LENGTH,\n\t\t\t\tdescription: \"Replacement description (what done means); blank or omitted keeps the current one.\",\n\t\t\t}),\n\t\t),\n\t\tstatus: Type.Optional(StatusSchema),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport const TodoParamsSchema = Type.Object(\n\t{\n\t\tcreate: Type.Optional(\n\t\t\tType.Array(CreateItemSchema, {\n\t\t\t\tmaxItems: TODO_MAX_BATCH_ITEMS,\n\t\t\t\tdescription: \"Tasks to add, created atomically in input order; status defaults to pending.\",\n\t\t\t}),\n\t\t),\n\t\tupdate: Type.Optional(\n\t\t\tType.Array(UpdateItemSchema, {\n\t\t\t\tmaxItems: TODO_MAX_BATCH_ITEMS,\n\t\t\t\tdescription: \"Task edits by id; blank or omitted fields keep their current values.\",\n\t\t\t}),\n\t\t),\n\t\tdelete: Type.Optional(\n\t\t\tType.Array(TaskIdSchema, {\n\t\t\t\tmaxItems: TODO_MAX_BATCH_ITEMS,\n\t\t\t\tdescription: \"Ids to remove from the current list; deleting an id that is already absent is a no-op.\",\n\t\t\t}),\n\t\t),\n\t},\n\t{ additionalProperties: false },\n);\n\nexport type TodoParams = Static<typeof TodoParamsSchema>;\n\nexport const EMPTY_TODO_STATE: TodoState = { items: [], nextId: 1 };\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/state.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGN,KAAK,UAAU,EACf,KAAK,WAAW,EAEhB,KAAK,UAAU,EACf,KAAK,SAAS,EAEd,MAAM,aAAa,CAAC;AAuJrB,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAE1D;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAqI/E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,GAAG,WAAW,CAElF;AAED,MAAM,WAAW,SAAS;IACzB,QAAQ,IAAI,SAAS,CAAC;IACtB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACrC,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;CACzC;AAQD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAgB9D;AAwCD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE;IAAE,cAAc,EAAE;QAAE,SAAS,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,CAoB5G"}
1
+ {"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/state.ts"],"names":[],"mappings":"AAkBA,OAAO,EAGN,KAAK,UAAU,EACf,KAAK,WAAW,EAEhB,KAAK,UAAU,EACf,KAAK,SAAS,EAEd,MAAM,aAAa,CAAC;AAuJrB,wFAAwF;AACxF,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,SAAS,CAE1D;AAED,MAAM,WAAW,SAAS;IACzB,KAAK,EAAE,SAAS,CAAC;IACjB,MAAM,EAAE,UAAU,CAAC;CACnB;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,GAAG,SAAS,CAoJ/E;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,GAAG,WAAW,CAElF;AAED,MAAM,WAAW,SAAS;IACzB,QAAQ,IAAI,SAAS,CAAC;IACtB,YAAY,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAC;IACrC,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,WAAW,CAAC;CACzC;AAQD,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAgB9D;AAwCD;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE;IAAE,cAAc,EAAE;QAAE,SAAS,IAAI,QAAQ,CAAC,OAAO,CAAC,CAAA;KAAE,CAAA;CAAE,GAAG,SAAS,CAoB5G"}
@@ -185,7 +185,7 @@ export function applyTodoPatch(before, params) {
185
185
  if (deleteSet.has(entry.id))
186
186
  throw new Error(`#${entry.id} cannot be in both update and delete`);
187
187
  }
188
- // Exactly one activation per call: more than one is a real contradiction,
188
+ // At most one activation per call: more than one is a real contradiction,
189
189
  // since a single task can be in_progress at any time.
190
190
  const activations = [];
191
191
  createEntries.forEach((entry, index) => {
@@ -222,7 +222,6 @@ export function applyTodoPatch(before, params) {
222
222
  }
223
223
  let items = before.items.filter((item) => !deleteSet.has(item.id));
224
224
  // --- Apply: update -------------------------------------------------------
225
- const updated = [];
226
225
  if (updateEntries.length > 0) {
227
226
  const entriesById = new Map(updateEntries.map((entry) => [entry.id, entry]));
228
227
  items = items.map((item) => {
@@ -235,7 +234,6 @@ export function applyTodoPatch(before, params) {
235
234
  description: entry.description ?? item.description,
236
235
  status: entry.status ?? item.status,
237
236
  };
238
- updated.push({ id: item.id, from: item.status, to: next.status });
239
237
  return next;
240
238
  });
241
239
  }
@@ -276,14 +274,16 @@ export function applyTodoPatch(before, params) {
276
274
  }
277
275
  }
278
276
  // --- Capacity: completed tasks are reclaimed oldest-first before a call
279
- // may fail for space; only a list with more than the cap in open tasks is
280
- // rejected.
277
+ // may fail for space; only a list that stays over the cap is rejected.
278
+ // Tasks this call created or updated are never reclaimed: silently dropping
279
+ // a task would be confusing when the same result reports it as touched.
281
280
  const evicted = [];
282
281
  if (items.length > TODO_MAX_ITEMS) {
283
282
  let overflow = items.length - TODO_MAX_ITEMS;
284
283
  const evictIds = new Set();
284
+ const touched = new Set([...created, ...updatedIds]);
285
285
  const completedOldestFirst = items
286
- .filter((item) => item.status === "completed")
286
+ .filter((item) => item.status === "completed" && !touched.has(item.id))
287
287
  .sort((first, second) => first.id - second.id);
288
288
  for (const item of completedOldestFirst) {
289
289
  if (overflow <= 0)
@@ -292,10 +292,22 @@ export function applyTodoPatch(before, params) {
292
292
  evicted.push({ id: item.id, subject: item.subject });
293
293
  overflow--;
294
294
  }
295
- if (overflow > 0)
296
- throw new Error(`at most ${TODO_MAX_ITEMS} open tasks; complete or delete some first`);
295
+ if (overflow > 0) {
296
+ const completedIds = before.items.filter((item) => item.status === "completed").map((item) => `#${item.id}`);
297
+ throw new Error(`at most ${TODO_MAX_ITEMS} tasks; complete or delete some first. Patch not applied. Tasks created or updated in this call are not reclaimed. ` +
298
+ `Delete existing tasks in the same patch to free space (remove those ids from update), or complete tasks in a separate call before creating more. ` +
299
+ `Completed ids before this call: ${completedIds.join(", ") || "none"}.`);
300
+ }
297
301
  items = items.filter((item) => !evictIds.has(item.id));
298
302
  }
303
+ // Report transitions only after activation and capacity have settled the snapshot.
304
+ const updated = items
305
+ .filter((item) => updatedIds.has(item.id))
306
+ .map((item) => ({
307
+ id: item.id,
308
+ from: byId.get(item.id).status,
309
+ to: item.status,
310
+ }));
299
311
  const change = { created, updated, deleted, absent, evicted };
300
312
  if (demotedId !== undefined)
301
313
  change.demotedId = demotedId;