@astralyn/pi 0.84.3 → 0.84.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (92) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/cli/project-trust.d.ts.map +1 -1
  3. package/dist/cli/project-trust.js +1 -4
  4. package/dist/cli/project-trust.js.map +1 -1
  5. package/dist/core/extensions/index.d.ts +1 -1
  6. package/dist/core/extensions/index.d.ts.map +1 -1
  7. package/dist/core/extensions/index.js.map +1 -1
  8. package/dist/core/extensions/types.d.ts +2 -13
  9. package/dist/core/extensions/types.d.ts.map +1 -1
  10. package/dist/core/extensions/types.js.map +1 -1
  11. package/dist/extensions/subagent/agents-command.d.ts +4 -0
  12. package/dist/extensions/subagent/agents-command.d.ts.map +1 -0
  13. package/dist/extensions/subagent/agents-command.js +230 -0
  14. package/dist/extensions/subagent/agents-command.js.map +1 -0
  15. package/dist/extensions/subagent/agents.d.ts.map +1 -1
  16. package/dist/extensions/subagent/agents.js +2 -0
  17. package/dist/extensions/subagent/agents.js.map +1 -1
  18. package/dist/extensions/subagent/display-name.d.ts +6 -0
  19. package/dist/extensions/subagent/display-name.d.ts.map +1 -0
  20. package/dist/extensions/subagent/display-name.js +13 -0
  21. package/dist/extensions/subagent/display-name.js.map +1 -0
  22. package/dist/extensions/subagent/index.d.ts.map +1 -1
  23. package/dist/extensions/subagent/index.js +2 -127
  24. package/dist/extensions/subagent/index.js.map +1 -1
  25. package/dist/extensions/subagent/render.d.ts.map +1 -1
  26. package/dist/extensions/subagent/render.js +1 -10
  27. package/dist/extensions/subagent/render.js.map +1 -1
  28. package/dist/extensions/subagent/settings.d.ts +0 -1
  29. package/dist/extensions/subagent/settings.d.ts.map +1 -1
  30. package/dist/extensions/subagent/settings.js +3 -7
  31. package/dist/extensions/subagent/settings.js.map +1 -1
  32. package/dist/extensions/subagent/types.d.ts +2 -0
  33. package/dist/extensions/subagent/types.d.ts.map +1 -1
  34. package/dist/extensions/subagent/types.js.map +1 -1
  35. package/dist/extensions/subagent/ui/model-list.d.ts +67 -0
  36. package/dist/extensions/subagent/ui/model-list.d.ts.map +1 -0
  37. package/dist/extensions/subagent/ui/model-list.js +242 -0
  38. package/dist/extensions/subagent/ui/model-list.js.map +1 -0
  39. package/dist/extensions/subagent/ui/profile-editor.d.ts +54 -0
  40. package/dist/extensions/subagent/ui/profile-editor.d.ts.map +1 -0
  41. package/dist/extensions/subagent/ui/profile-editor.js +154 -0
  42. package/dist/extensions/subagent/ui/profile-editor.js.map +1 -0
  43. package/dist/extensions/subagent/ui/profile-picker.d.ts +32 -0
  44. package/dist/extensions/subagent/ui/profile-picker.d.ts.map +1 -0
  45. package/dist/extensions/subagent/ui/profile-picker.js +149 -0
  46. package/dist/extensions/subagent/ui/profile-picker.js.map +1 -0
  47. package/dist/extensions/todo/constants.d.ts +6 -17
  48. package/dist/extensions/todo/constants.d.ts.map +1 -1
  49. package/dist/extensions/todo/constants.js +20 -31
  50. package/dist/extensions/todo/constants.js.map +1 -1
  51. package/dist/extensions/todo/index.d.ts.map +1 -1
  52. package/dist/extensions/todo/index.js +36 -57
  53. package/dist/extensions/todo/index.js.map +1 -1
  54. package/dist/extensions/todo/schema.d.ts +20 -59
  55. package/dist/extensions/todo/schema.d.ts.map +1 -1
  56. package/dist/extensions/todo/schema.js +23 -69
  57. package/dist/extensions/todo/schema.js.map +1 -1
  58. package/dist/extensions/todo/state.d.ts +23 -65
  59. package/dist/extensions/todo/state.d.ts.map +1 -1
  60. package/dist/extensions/todo/state.js +238 -1262
  61. package/dist/extensions/todo/state.js.map +1 -1
  62. package/dist/extensions/todo/view.d.ts +19 -10
  63. package/dist/extensions/todo/view.d.ts.map +1 -1
  64. package/dist/extensions/todo/view.js +411 -463
  65. package/dist/extensions/todo/view.js.map +1 -1
  66. package/dist/extensions/todo/widget.d.ts +15 -0
  67. package/dist/extensions/todo/widget.d.ts.map +1 -0
  68. package/dist/extensions/todo/widget.js +50 -0
  69. package/dist/extensions/todo/widget.js.map +1 -0
  70. package/dist/modes/interactive/components/extension-selector.d.ts +1 -2
  71. package/dist/modes/interactive/components/extension-selector.d.ts.map +1 -1
  72. package/dist/modes/interactive/components/extension-selector.js +5 -11
  73. package/dist/modes/interactive/components/extension-selector.js.map +1 -1
  74. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  76. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  77. package/dist/modes/rpc/rpc-mode.js +1 -8
  78. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  79. package/docs/bundled/README.md +1 -1
  80. package/docs/bundled/extensions/subagent.md +19 -4
  81. package/docs/bundled/extensions/todo.md +84 -24
  82. package/docs/bundled/tool-presentation.md +1 -1
  83. package/npm-shrinkwrap.json +2 -2
  84. package/package.json +1 -1
  85. package/dist/extensions/subagent/picker.d.ts +0 -31
  86. package/dist/extensions/subagent/picker.d.ts.map +0 -1
  87. package/dist/extensions/subagent/picker.js +0 -109
  88. package/dist/extensions/subagent/picker.js.map +0 -1
  89. package/dist/extensions/todo/overlay.d.ts +0 -24
  90. package/dist/extensions/todo/overlay.d.ts.map +0 -1
  91. package/dist/extensions/todo/overlay.js +0 -135
  92. package/dist/extensions/todo/overlay.js.map +0 -1
@@ -0,0 +1,149 @@
1
+ import { Container, Input, Spacer, Text, TruncatedText, } from "@earendil-works/pi-tui";
2
+ import { DynamicBorder } from "../../../modes/interactive/components/dynamic-border.js";
3
+ import { keyLabel } from "../../../modes/interactive/components/keybinding-hints.js";
4
+ import { displayAgentDescription, displayAgentName } from "../display-name.js";
5
+ const MAX_VISIBLE_PROFILES = 10;
6
+ function compareProfileNames(left, right) {
7
+ if (left.name < right.name)
8
+ return -1;
9
+ if (left.name > right.name)
10
+ return 1;
11
+ return 0;
12
+ }
13
+ function hint(theme, keybindings, binding, description) {
14
+ const key = keyLabel(binding, { keybindings });
15
+ return key ? `${theme.fg("dim", key)}${theme.fg("muted", ` ${description}`)}` : "";
16
+ }
17
+ /** Searchable, alphabetized profile selector used by /agents. */
18
+ export class ProfilePickerComponent extends Container {
19
+ get focused() {
20
+ return this._focused;
21
+ }
22
+ set focused(value) {
23
+ this._focused = value;
24
+ this.searchInput.focused = value;
25
+ }
26
+ constructor(theme, keybindings, profiles, diagnostics, onDone, initialProfileName) {
27
+ super();
28
+ this.filteredItems = [];
29
+ this.selectedIndex = 0;
30
+ this._focused = false;
31
+ this.theme = theme;
32
+ this.keybindings = keybindings;
33
+ this.profiles = [...profiles].sort(compareProfileNames);
34
+ this.diagnostics = diagnostics;
35
+ this.onDone = onDone;
36
+ this.addChild(new DynamicBorder());
37
+ this.addChild(new Spacer(1));
38
+ this.addChild(new Text(theme.fg("accent", theme.bold("Agents")), 1, 0));
39
+ this.addChild(new Spacer(1));
40
+ this.searchInput = new Input();
41
+ this.addChild(this.searchInput);
42
+ this.addChild(new Spacer(1));
43
+ this.listContainer = new Container();
44
+ this.addChild(this.listContainer);
45
+ this.detailContainer = new Container();
46
+ this.addChild(this.detailContainer);
47
+ this.addChild(new Spacer(1));
48
+ const navigationKeys = [keyLabel("tui.select.up", { keybindings }), keyLabel("tui.select.down", { keybindings })]
49
+ .filter(Boolean)
50
+ .join("/");
51
+ const hints = [
52
+ navigationKeys ? `${theme.fg("dim", navigationKeys)}${theme.fg("muted", " navigate")}` : "",
53
+ hint(theme, keybindings, "tui.select.confirm", "configure"),
54
+ hint(theme, keybindings, "tui.select.cancel", "close"),
55
+ ].filter(Boolean);
56
+ this.addChild(new Text(hints.join(theme.fg("muted", " • ")), 1, 0));
57
+ this.addChild(new Spacer(1));
58
+ this.addChild(new DynamicBorder());
59
+ this.applyFilter("");
60
+ if (initialProfileName) {
61
+ const initialIndex = this.filteredItems.findIndex((item) => item.kind === "profile" && item.agent.name === initialProfileName);
62
+ if (initialIndex >= 0)
63
+ this.selectedIndex = initialIndex;
64
+ }
65
+ this.updateView();
66
+ }
67
+ applyFilter(query) {
68
+ const normalizedQuery = query.trim().toLowerCase();
69
+ const profiles = normalizedQuery
70
+ ? this.profiles.filter((agent) => `${agent.name} ${displayAgentName(agent.name)} ${displayAgentDescription(agent)}`
71
+ .toLowerCase()
72
+ .includes(normalizedQuery))
73
+ : this.profiles;
74
+ this.filteredItems = profiles.map((agent) => ({ kind: "profile", agent }));
75
+ if (!normalizedQuery && this.diagnostics.length > 0) {
76
+ this.filteredItems.push({ kind: "diagnostics", count: this.diagnostics.length });
77
+ }
78
+ this.selectedIndex = normalizedQuery
79
+ ? 0
80
+ : Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));
81
+ this.updateView();
82
+ }
83
+ updateView() {
84
+ this.listContainer.clear();
85
+ this.detailContainer.clear();
86
+ const startIndex = Math.max(0, Math.min(this.selectedIndex - Math.floor(MAX_VISIBLE_PROFILES / 2), this.filteredItems.length - MAX_VISIBLE_PROFILES));
87
+ const endIndex = Math.min(startIndex + MAX_VISIBLE_PROFILES, this.filteredItems.length);
88
+ for (let index = startIndex; index < endIndex; index++) {
89
+ const item = this.filteredItems[index];
90
+ if (!item)
91
+ continue;
92
+ const selected = index === this.selectedIndex;
93
+ const label = item.kind === "profile"
94
+ ? displayAgentName(item.agent.name)
95
+ : `View ${item.count} agent file issue${item.count === 1 ? "" : "s"}`;
96
+ const prefix = selected ? this.theme.fg("accent", "→ ") : " ";
97
+ const text = selected ? this.theme.fg("accent", label) : this.theme.fg("text", label);
98
+ this.listContainer.addChild(new TruncatedText(`${prefix}${text}`, 1, 0));
99
+ }
100
+ if (startIndex > 0 || endIndex < this.filteredItems.length) {
101
+ this.listContainer.addChild(new TruncatedText(this.theme.fg("muted", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`), 1, 0));
102
+ }
103
+ if (this.filteredItems.length === 0) {
104
+ this.listContainer.addChild(new Text(this.theme.fg("muted", " No matching profiles"), 1, 0));
105
+ return;
106
+ }
107
+ const selected = this.filteredItems[this.selectedIndex];
108
+ if (!selected)
109
+ return;
110
+ this.detailContainer.addChild(new Spacer(1));
111
+ if (selected.kind === "profile") {
112
+ this.detailContainer.addChild(new Text(this.theme.fg("muted", displayAgentDescription(selected.agent)), 1, 0));
113
+ }
114
+ else {
115
+ this.detailContainer.addChild(new Text(this.theme.fg("warning", `${selected.count} profile file${selected.count === 1 ? "" : "s"} could not be loaded.`), 1, 0));
116
+ }
117
+ }
118
+ moveSelection(offset) {
119
+ if (this.filteredItems.length === 0)
120
+ return;
121
+ this.selectedIndex = (this.selectedIndex + offset + this.filteredItems.length) % this.filteredItems.length;
122
+ this.updateView();
123
+ }
124
+ confirm() {
125
+ const selected = this.filteredItems[this.selectedIndex];
126
+ if (!selected)
127
+ return;
128
+ this.onDone(selected.kind === "profile" ? { kind: "profile", name: selected.agent.name } : { kind: "diagnostics" });
129
+ }
130
+ handleInput(data) {
131
+ if (this.keybindings.matches(data, "tui.select.up")) {
132
+ this.moveSelection(-1);
133
+ }
134
+ else if (this.keybindings.matches(data, "tui.select.down")) {
135
+ this.moveSelection(1);
136
+ }
137
+ else if (this.keybindings.matches(data, "tui.select.confirm")) {
138
+ this.confirm();
139
+ }
140
+ else if (this.keybindings.matches(data, "tui.select.cancel")) {
141
+ this.onDone(undefined);
142
+ }
143
+ else {
144
+ this.searchInput.handleInput(data);
145
+ this.applyFilter(this.searchInput.getValue());
146
+ }
147
+ }
148
+ }
149
+ //# sourceMappingURL=profile-picker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profile-picker.js","sourceRoot":"","sources":["../../../../src/extensions/subagent/ui/profile-picker.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n\tContainer,\n\ttype Focusable,\n\tInput,\n\ttype KeybindingsManager,\n\tSpacer,\n\tText,\n\tTruncatedText,\n} from \"@earendil-works/pi-tui\";\nimport { DynamicBorder } from \"../../../modes/interactive/components/dynamic-border.ts\";\nimport { keyLabel } from \"../../../modes/interactive/components/keybinding-hints.ts\";\nimport type { Theme } from \"../../../modes/interactive/theme/theme.ts\";\nimport { displayAgentDescription, displayAgentName } from \"../display-name.ts\";\nimport type { AgentDefinition, AgentDiagnostic } from \"../types.ts\";\n\nconst MAX_VISIBLE_PROFILES = 10;\n\ninterface ProfileItem {\n\tkind: \"profile\";\n\tagent: AgentDefinition;\n}\n\ninterface DiagnosticItem {\n\tkind: \"diagnostics\";\n\tcount: number;\n}\n\ntype PickerItem = ProfileItem | DiagnosticItem;\n\nexport type ProfilePickerResult = { kind: \"profile\"; name: string } | { kind: \"diagnostics\" } | undefined;\n\nfunction compareProfileNames(left: AgentDefinition, right: AgentDefinition): number {\n\tif (left.name < right.name) return -1;\n\tif (left.name > right.name) return 1;\n\treturn 0;\n}\n\nfunction hint(\n\ttheme: Theme,\n\tkeybindings: KeybindingsManager,\n\tbinding: \"tui.select.up\" | \"tui.select.down\" | \"tui.select.confirm\" | \"tui.select.cancel\",\n\tdescription: string,\n): string {\n\tconst key = keyLabel(binding, { keybindings });\n\treturn key ? `${theme.fg(\"dim\", key)}${theme.fg(\"muted\", ` ${description}`)}` : \"\";\n}\n\n/** Searchable, alphabetized profile selector used by /agents. */\nexport class ProfilePickerComponent extends Container implements Focusable {\n\tprivate readonly theme: Theme;\n\tprivate readonly keybindings: KeybindingsManager;\n\tprivate readonly profiles: AgentDefinition[];\n\tprivate readonly diagnostics: readonly AgentDiagnostic[];\n\tprivate readonly onDone: (result: ProfilePickerResult) => void;\n\tprivate readonly searchInput: Input;\n\tprivate readonly listContainer: Container;\n\tprivate readonly detailContainer: Container;\n\tprivate filteredItems: PickerItem[] = [];\n\tprivate selectedIndex = 0;\n\tprivate _focused = false;\n\n\tget focused(): boolean {\n\t\treturn this._focused;\n\t}\n\n\tset focused(value: boolean) {\n\t\tthis._focused = value;\n\t\tthis.searchInput.focused = value;\n\t}\n\n\tconstructor(\n\t\ttheme: Theme,\n\t\tkeybindings: KeybindingsManager,\n\t\tprofiles: readonly AgentDefinition[],\n\t\tdiagnostics: readonly AgentDiagnostic[],\n\t\tonDone: (result: ProfilePickerResult) => void,\n\t\tinitialProfileName?: string,\n\t) {\n\t\tsuper();\n\t\tthis.theme = theme;\n\t\tthis.keybindings = keybindings;\n\t\tthis.profiles = [...profiles].sort(compareProfileNames);\n\t\tthis.diagnostics = diagnostics;\n\t\tthis.onDone = onDone;\n\n\t\tthis.addChild(new DynamicBorder());\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"accent\", theme.bold(\"Agents\")), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.searchInput = new Input();\n\t\tthis.addChild(this.searchInput);\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.listContainer = new Container();\n\t\tthis.addChild(this.listContainer);\n\t\tthis.detailContainer = new Container();\n\t\tthis.addChild(this.detailContainer);\n\t\tthis.addChild(new Spacer(1));\n\n\t\tconst navigationKeys = [keyLabel(\"tui.select.up\", { keybindings }), keyLabel(\"tui.select.down\", { keybindings })]\n\t\t\t.filter(Boolean)\n\t\t\t.join(\"/\");\n\t\tconst hints = [\n\t\t\tnavigationKeys ? `${theme.fg(\"dim\", navigationKeys)}${theme.fg(\"muted\", \" navigate\")}` : \"\",\n\t\t\thint(theme, keybindings, \"tui.select.confirm\", \"configure\"),\n\t\t\thint(theme, keybindings, \"tui.select.cancel\", \"close\"),\n\t\t].filter(Boolean);\n\t\tthis.addChild(new Text(hints.join(theme.fg(\"muted\", \" • \")), 1, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new DynamicBorder());\n\n\t\tthis.applyFilter(\"\");\n\t\tif (initialProfileName) {\n\t\t\tconst initialIndex = this.filteredItems.findIndex(\n\t\t\t\t(item) => item.kind === \"profile\" && item.agent.name === initialProfileName,\n\t\t\t);\n\t\t\tif (initialIndex >= 0) this.selectedIndex = initialIndex;\n\t\t}\n\t\tthis.updateView();\n\t}\n\n\tprivate applyFilter(query: string): void {\n\t\tconst normalizedQuery = query.trim().toLowerCase();\n\t\tconst profiles = normalizedQuery\n\t\t\t? this.profiles.filter((agent) =>\n\t\t\t\t\t`${agent.name} ${displayAgentName(agent.name)} ${displayAgentDescription(agent)}`\n\t\t\t\t\t\t.toLowerCase()\n\t\t\t\t\t\t.includes(normalizedQuery),\n\t\t\t\t)\n\t\t\t: this.profiles;\n\t\tthis.filteredItems = profiles.map((agent): ProfileItem => ({ kind: \"profile\", agent }));\n\t\tif (!normalizedQuery && this.diagnostics.length > 0) {\n\t\t\tthis.filteredItems.push({ kind: \"diagnostics\", count: this.diagnostics.length });\n\t\t}\n\t\tthis.selectedIndex = normalizedQuery\n\t\t\t? 0\n\t\t\t: Math.min(this.selectedIndex, Math.max(0, this.filteredItems.length - 1));\n\t\tthis.updateView();\n\t}\n\n\tprivate updateView(): void {\n\t\tthis.listContainer.clear();\n\t\tthis.detailContainer.clear();\n\n\t\tconst startIndex = Math.max(\n\t\t\t0,\n\t\t\tMath.min(\n\t\t\t\tthis.selectedIndex - Math.floor(MAX_VISIBLE_PROFILES / 2),\n\t\t\t\tthis.filteredItems.length - MAX_VISIBLE_PROFILES,\n\t\t\t),\n\t\t);\n\t\tconst endIndex = Math.min(startIndex + MAX_VISIBLE_PROFILES, this.filteredItems.length);\n\t\tfor (let index = startIndex; index < endIndex; index++) {\n\t\t\tconst item = this.filteredItems[index];\n\t\t\tif (!item) continue;\n\t\t\tconst selected = index === this.selectedIndex;\n\t\t\tconst label =\n\t\t\t\titem.kind === \"profile\"\n\t\t\t\t\t? displayAgentName(item.agent.name)\n\t\t\t\t\t: `View ${item.count} agent file issue${item.count === 1 ? \"\" : \"s\"}`;\n\t\t\tconst prefix = selected ? this.theme.fg(\"accent\", \"→ \") : \" \";\n\t\t\tconst text = selected ? this.theme.fg(\"accent\", label) : this.theme.fg(\"text\", label);\n\t\t\tthis.listContainer.addChild(new TruncatedText(`${prefix}${text}`, 1, 0));\n\t\t}\n\n\t\tif (startIndex > 0 || endIndex < this.filteredItems.length) {\n\t\t\tthis.listContainer.addChild(\n\t\t\t\tnew TruncatedText(\n\t\t\t\t\tthis.theme.fg(\"muted\", ` (${this.selectedIndex + 1}/${this.filteredItems.length})`),\n\t\t\t\t\t1,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (this.filteredItems.length === 0) {\n\t\t\tthis.listContainer.addChild(new Text(this.theme.fg(\"muted\", \" No matching profiles\"), 1, 0));\n\t\t\treturn;\n\t\t}\n\n\t\tconst selected = this.filteredItems[this.selectedIndex];\n\t\tif (!selected) return;\n\t\tthis.detailContainer.addChild(new Spacer(1));\n\t\tif (selected.kind === \"profile\") {\n\t\t\tthis.detailContainer.addChild(new Text(this.theme.fg(\"muted\", displayAgentDescription(selected.agent)), 1, 0));\n\t\t} else {\n\t\t\tthis.detailContainer.addChild(\n\t\t\t\tnew Text(\n\t\t\t\t\tthis.theme.fg(\n\t\t\t\t\t\t\"warning\",\n\t\t\t\t\t\t`${selected.count} profile file${selected.count === 1 ? \"\" : \"s\"} could not be loaded.`,\n\t\t\t\t\t),\n\t\t\t\t\t1,\n\t\t\t\t\t0,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate moveSelection(offset: -1 | 1): void {\n\t\tif (this.filteredItems.length === 0) return;\n\t\tthis.selectedIndex = (this.selectedIndex + offset + this.filteredItems.length) % this.filteredItems.length;\n\t\tthis.updateView();\n\t}\n\n\tprivate confirm(): void {\n\t\tconst selected = this.filteredItems[this.selectedIndex];\n\t\tif (!selected) return;\n\t\tthis.onDone(\n\t\t\tselected.kind === \"profile\" ? { kind: \"profile\", name: selected.agent.name } : { kind: \"diagnostics\" },\n\t\t);\n\t}\n\n\thandleInput(data: string): void {\n\t\tif (this.keybindings.matches(data, \"tui.select.up\")) {\n\t\t\tthis.moveSelection(-1);\n\t\t} else if (this.keybindings.matches(data, \"tui.select.down\")) {\n\t\t\tthis.moveSelection(1);\n\t\t} else if (this.keybindings.matches(data, \"tui.select.confirm\")) {\n\t\t\tthis.confirm();\n\t\t} else if (this.keybindings.matches(data, \"tui.select.cancel\")) {\n\t\t\tthis.onDone(undefined);\n\t\t} else {\n\t\t\tthis.searchInput.handleInput(data);\n\t\t\tthis.applyFilter(this.searchInput.getValue());\n\t\t}\n\t}\n}\n"]}
@@ -4,24 +4,13 @@
4
4
  export declare const TODO_TOOL_NAME = "todo";
5
5
  export declare const TODO_TOOL_LABEL = "Todo";
6
6
  export declare const TODOS_COMMAND_NAME = "todos";
7
- /** Max tasks shown in a single `list` tool result body (model-facing). */
8
- export declare const LIST_DISPLAY_MAX_ITEMS = 50;
9
- export declare const TODO_MAX_LIST_LIMIT = 50;
10
- export declare const TODO_MAX_LIST_QUERY_LENGTH = 200;
11
- /** Input limits keep task snapshots and model-facing output bounded. */
7
+ /** Hard capacity of the task list and of a single create/delete batch. */
8
+ export declare const TODO_MAX_ITEMS = 20;
12
9
  export declare const TODO_MAX_BATCH_ITEMS = 20;
13
- export declare const TODO_MAX_SUBJECT_LENGTH = 240;
14
- export declare const TODO_MAX_DESCRIPTION_LENGTH = 4000;
15
- export declare const TODO_MAX_ACTIVE_FORM_LENGTH = 240;
16
- export declare const TODO_MAX_OWNER_LENGTH = 160;
17
- export declare const TODO_MAX_BATCH_KEY_LENGTH = 120;
18
- export declare const TODO_MAX_BLOCKED_BY = 50;
19
- /** Metadata is JSON-like, deeply copied, and bounded independently of task text. */
20
- export declare const TODO_MAX_METADATA_KEY_LENGTH = 160;
21
- export declare const TODO_MAX_METADATA_DEPTH = 8;
22
- export declare const TODO_MAX_METADATA_ENTRIES = 100;
23
- export declare const TODO_MAX_METADATA_BYTES = 16384;
24
- export declare const TODO_TOOL_DESCRIPTION = "Manage the conversation's task list for multi-step coding work.\n\n## When to Use\n- Work with three or more meaningful steps, user-provided task lists, or long sessions where progress can drift\n- Capture new user requirements as tasks right away; after completing a task, add follow-up tasks discovered during implementation\n\n## When NOT to Use\n- Trivial single-step tasks and simple Q&A \u2014 just do the work\n\n## Actions\n- create: add one pending task; subject (short, imperative, reviewable scope) and description (what done means: acceptance criteria or verification detail) are required\n- create_many: atomically add up to 20 pending tasks in input order; each item needs subject and description, and optional unique keys can be linked within the batch with blockedByKeys\n- update: change status or details, and manage dependencies via addBlockedBy/removeBlockedBy (what this task waits on) and addBlocks/removeBlocks (what waits on this task); dependencies are validated and cycle-checked\n- list: one-line summaries with unresolved blockers, optionally filtered by status, paged with limit/afterId, searched with query, or narrowed with unblockedOnly; get: one task in full; delete: tombstone an obsolete task and release its dependents; clear: requires confirm=true and the exact expectedCount, without reusing ids\n- Parameters that do not apply to the chosen action are rejected with guidance\n\n## Status Workflow\npending -> in_progress -> completed; deleted is the only terminal state. Keep exactly one task in_progress (starting one demotes any other active task): mark it in_progress BEFORE starting the work, and completed only when implementation and verification are genuinely done \u2014 never with failing tests or partial work; reopen instead of duplicating. When blocked, do not fake completion: create a task for the blocker and link it with addBlockedBy.\n\n## Examples\n{\"action\": \"create\", \"subject\": \"Fix login redirect\", \"description\": \"Redirect lands on the dashboard; auth tests pass\"}\n{\"action\": \"create_many\", \"items\": [{\"key\": \"parser\", \"subject\": \"Wire parser\", \"description\": \"Parser handles config\"}, {\"subject\": \"Test parser\", \"description\": \"Parser tests pass\", \"blockedByKeys\": [\"parser\"]}]}\n{\"action\": \"update\", \"id\": 2, \"status\": \"in_progress\", \"activeForm\": \"fixing login redirect\"}\n{\"action\": \"update\", \"id\": 1, \"addBlocks\": [2, 3]}\n\n## Tips\n- list before create to avoid duplicates; prefer working in id order \u2014 earlier tasks usually set up context for later ones\n- Use dependencies only for real ordering constraints, and delete obsolete tasks promptly so their dependents are released";
10
+ /** Input limits keep task snapshots and model-facing output bounded. */
11
+ export declare const TODO_MAX_SUBJECT_LENGTH = 160;
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.\n\n## When to Use\n- Work with three or more meaningful steps, a user-provided task list, or a long session where progress can drift\n- Capture new user requirements as tasks right away; after completing a task, add follow-up tasks discovered during implementation\n\n## When NOT to Use\n- Trivial single-step tasks and simple Q&A \u2014 just do the work\n\n## Actions\n- create: atomically add one or many pending tasks from the ordered items array (1 to 20 items); each item needs a short imperative subject and a description of what done means\n- update: edit one task by id \u2014 subject, description, or status; all transitions and reopens are allowed\n- list: show every remaining task with its description, active first\n- delete: remove obsolete tasks by ids (1 to 20, duplicates ignored)\n- The current list holds at most 20 tasks; parameters that do not apply to the chosen action are rejected\n\n## Status Workflow\npending -> in_progress -> completed. Keep exactly one task in_progress: marking one active demotes any other active task to pending. Mark a task in_progress BEFORE starting the work, and completed only when its description is satisfied and relevant checks pass; never with failing tests or partial work. Reopen instead of duplicating. When blocked, do not fake completion: return the task to pending, create a task for the blocker, and finish the blocker first.\n\n## Examples\n{\"action\": \"create\", \"items\": [{\"subject\": \"Wire parser\", \"description\": \"Parser handles the config format\"}, {\"subject\": \"Test parser\", \"description\": \"Parser tests pass\"}]}\n{\"action\": \"update\", \"id\": 2, \"status\": \"in_progress\"}\n{\"action\": \"update\", \"id\": 2, \"status\": \"completed\", \"description\": \"Parser verified against config fixtures\"}\n{\"action\": \"delete\", \"ids\": [3]}\n\n## Tips\n- list before create to avoid duplicates; prefer working in id order \u2014 earlier tasks usually set up context for later ones\n- delete obsolete tasks promptly so the list stays short and accurate";
25
14
  export declare const TODO_PROMPT_SNIPPET = "Track multi-step coding work with a small outcome-oriented task list";
26
15
  export declare const TODO_PROMPT_GUIDELINES: string[];
27
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,sBAAsB,KAAK,CAAC;AACzC,eAAO,MAAM,mBAAmB,KAAyB,CAAC;AAC1D,eAAO,MAAM,0BAA0B,MAAM,CAAC;AAE9C,wEAAwE;AACxE,eAAO,MAAM,oBAAoB,KAAK,CAAC;AACvC,eAAO,MAAM,uBAAuB,MAAM,CAAC;AAC3C,eAAO,MAAM,2BAA2B,OAAQ,CAAC;AACjD,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,mBAAmB,KAAK,CAAC;AAEtC,oFAAoF;AACpF,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,eAAO,MAAM,uBAAuB,IAAI,CAAC;AACzC,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,eAAO,MAAM,uBAAuB,QAAS,CAAC;AAE9C,eAAO,MAAM,qBAAqB,kqFA2ByF,CAAC;AAE5H,eAAO,MAAM,mBAAmB,yEAAyE,CAAC;AAE1G,eAAO,MAAM,sBAAsB,UAIlC,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;AAE/C,eAAO,MAAM,qBAAqB,kkEA2BoC,CAAC;AAEvE,eAAO,MAAM,mBAAmB,yEAAyE,CAAC;AAE1G,eAAO,MAAM,sBAAsB,UAIlC,CAAC"}
@@ -4,55 +4,44 @@
4
4
  export const TODO_TOOL_NAME = "todo";
5
5
  export const TODO_TOOL_LABEL = "Todo";
6
6
  export const TODOS_COMMAND_NAME = "todos";
7
- /** Max tasks shown in a single `list` tool result body (model-facing). */
8
- export const LIST_DISPLAY_MAX_ITEMS = 50;
9
- export const TODO_MAX_LIST_LIMIT = LIST_DISPLAY_MAX_ITEMS;
10
- export const TODO_MAX_LIST_QUERY_LENGTH = 200;
11
- /** Input limits keep task snapshots and model-facing output bounded. */
7
+ /** Hard capacity of the task list and of a single create/delete batch. */
8
+ export const TODO_MAX_ITEMS = 20;
12
9
  export const TODO_MAX_BATCH_ITEMS = 20;
13
- export const TODO_MAX_SUBJECT_LENGTH = 240;
14
- export const TODO_MAX_DESCRIPTION_LENGTH = 4_000;
15
- export const TODO_MAX_ACTIVE_FORM_LENGTH = 240;
16
- export const TODO_MAX_OWNER_LENGTH = 160;
17
- export const TODO_MAX_BATCH_KEY_LENGTH = 120;
18
- export const TODO_MAX_BLOCKED_BY = 50;
19
- /** Metadata is JSON-like, deeply copied, and bounded independently of task text. */
20
- export const TODO_MAX_METADATA_KEY_LENGTH = 160;
21
- export const TODO_MAX_METADATA_DEPTH = 8;
22
- export const TODO_MAX_METADATA_ENTRIES = 100;
23
- export const TODO_MAX_METADATA_BYTES = 16_384;
10
+ /** Input limits keep task snapshots and model-facing output bounded. */
11
+ export const TODO_MAX_SUBJECT_LENGTH = 160;
12
+ export const TODO_MAX_DESCRIPTION_LENGTH = 500;
24
13
  export const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for multi-step coding work.
25
14
 
26
15
  ## When to Use
27
- - Work with three or more meaningful steps, user-provided task lists, or long sessions where progress can drift
16
+ - Work with three or more meaningful steps, a user-provided task list, or a long session where progress can drift
28
17
  - Capture new user requirements as tasks right away; after completing a task, add follow-up tasks discovered during implementation
29
18
 
30
19
  ## When NOT to Use
31
20
  - Trivial single-step tasks and simple Q&A — just do the work
32
21
 
33
22
  ## Actions
34
- - create: add one pending task; subject (short, imperative, reviewable scope) and description (what done means: acceptance criteria or verification detail) are required
35
- - create_many: atomically add up to 20 pending tasks in input order; each item needs subject and description, and optional unique keys can be linked within the batch with blockedByKeys
36
- - update: change status or details, and manage dependencies via addBlockedBy/removeBlockedBy (what this task waits on) and addBlocks/removeBlocks (what waits on this task); dependencies are validated and cycle-checked
37
- - list: one-line summaries with unresolved blockers, optionally filtered by status, paged with limit/afterId, searched with query, or narrowed with unblockedOnly; get: one task in full; delete: tombstone an obsolete task and release its dependents; clear: requires confirm=true and the exact expectedCount, without reusing ids
38
- - Parameters that do not apply to the chosen action are rejected with guidance
23
+ - create: atomically add one or many pending tasks from the ordered items array (1 to 20 items); each item needs a short imperative subject and a description of what done means
24
+ - update: edit one task by id — subject, description, or status; all transitions and reopens are allowed
25
+ - list: show every remaining task with its description, active first
26
+ - delete: remove obsolete tasks by ids (1 to 20, duplicates ignored)
27
+ - The current list holds at most 20 tasks; parameters that do not apply to the chosen action are rejected
39
28
 
40
29
  ## Status Workflow
41
- pending -> in_progress -> completed; deleted is the only terminal state. Keep exactly one task in_progress (starting one demotes any other active task): mark it in_progress BEFORE starting the work, and completed only when implementation and verification are genuinely done — never with failing tests or partial work; reopen instead of duplicating. When blocked, do not fake completion: create a task for the blocker and link it with addBlockedBy.
30
+ pending -> in_progress -> completed. Keep exactly one task in_progress: marking one active demotes any other active task to pending. Mark a task in_progress BEFORE starting the work, and completed only when its description is satisfied and relevant checks pass; never with failing tests or partial work. Reopen instead of duplicating. When blocked, do not fake completion: return the task to pending, create a task for the blocker, and finish the blocker first.
42
31
 
43
32
  ## Examples
44
- {"action": "create", "subject": "Fix login redirect", "description": "Redirect lands on the dashboard; auth tests pass"}
45
- {"action": "create_many", "items": [{"key": "parser", "subject": "Wire parser", "description": "Parser handles config"}, {"subject": "Test parser", "description": "Parser tests pass", "blockedByKeys": ["parser"]}]}
46
- {"action": "update", "id": 2, "status": "in_progress", "activeForm": "fixing login redirect"}
47
- {"action": "update", "id": 1, "addBlocks": [2, 3]}
33
+ {"action": "create", "items": [{"subject": "Wire parser", "description": "Parser handles the config format"}, {"subject": "Test parser", "description": "Parser tests pass"}]}
34
+ {"action": "update", "id": 2, "status": "in_progress"}
35
+ {"action": "update", "id": 2, "status": "completed", "description": "Parser verified against config fixtures"}
36
+ {"action": "delete", "ids": [3]}
48
37
 
49
38
  ## Tips
50
39
  - list before create to avoid duplicates; prefer working in id order — earlier tasks usually set up context for later ones
51
- - Use dependencies only for real ordering constraints, and delete obsolete tasks promptly so their dependents are released`;
40
+ - delete obsolete tasks promptly so the list stays short and accurate`;
52
41
  export const TODO_PROMPT_SNIPPET = "Track multi-step coding work with a small outcome-oriented task list";
53
42
  export const TODO_PROMPT_GUIDELINES = [
54
- "Use `todo` for work with three or more meaningful steps, user-provided task lists, or long sessions where progress can drift; list before creating to avoid duplicates, use create_many for a known batch of up to 20 tasks, and capture follow-up work discovered during implementation as new tasks.",
55
- "Mark the active `todo` task in_progress before starting work (exactly one at a time; the tool demotes any other active item) and completed immediately after verification — never while tests fail or work is partial; reopen instead of duplicating, and when blocked create a task for the blocker and link it with addBlockedBy.",
56
- "Keep the `todo` list short and outcome-oriented; prefer working in id order, use dependencies only for real prerequisites, and delete obsolete items promptly so their dependents are released. Clear only with confirm=true and the current expectedCount.",
43
+ "Use `todo` for work with three or more meaningful steps, user-provided task lists, or long sessions where progress can drift; list first when tasks may already exist, and create one or many tasks with the ordered items array.",
44
+ "Keep exactly one `todo` task in_progress: mark it in_progress before starting work and completed only after verification — never while tests fail or work is partial; reopen instead of duplicating, and when blocked leave the `todo` task pending and create a task for the blocker.",
45
+ "Keep the `todo` list short and outcome-oriented; prefer working in id order and delete obsolete `todo` items promptly.",
57
46
  ];
58
47
  //# sourceMappingURL=constants.js.map
@@ -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,sBAAsB,GAAG,EAAE,CAAC;AACzC,MAAM,CAAC,MAAM,mBAAmB,GAAG,sBAAsB,CAAC;AAC1D,MAAM,CAAC,MAAM,0BAA0B,GAAG,GAAG,CAAC;AAE9C,wEAAwE;AACxE,MAAM,CAAC,MAAM,oBAAoB,GAAG,EAAE,CAAC;AACvC,MAAM,CAAC,MAAM,uBAAuB,GAAG,GAAG,CAAC;AAC3C,MAAM,CAAC,MAAM,2BAA2B,GAAG,KAAK,CAAC;AACjD,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAC/C,MAAM,CAAC,MAAM,qBAAqB,GAAG,GAAG,CAAC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAC7C,MAAM,CAAC,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAEtC,oFAAoF;AACpF,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAChD,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC;AACzC,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAC7C,MAAM,CAAC,MAAM,uBAAuB,GAAG,MAAM,CAAC;AAE9C,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;2HA2BsF,CAAC;AAE5H,MAAM,CAAC,MAAM,mBAAmB,GAAG,sEAAsE,CAAC;AAE1G,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,wSAAwS;IACxS,qUAAqU;IACrU,6PAA6P;CAC7P,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/** Max tasks shown in a single `list` tool result body (model-facing). */\nexport const LIST_DISPLAY_MAX_ITEMS = 50;\nexport const TODO_MAX_LIST_LIMIT = LIST_DISPLAY_MAX_ITEMS;\nexport const TODO_MAX_LIST_QUERY_LENGTH = 200;\n\n/** Input limits keep task snapshots and model-facing output bounded. */\nexport const TODO_MAX_BATCH_ITEMS = 20;\nexport const TODO_MAX_SUBJECT_LENGTH = 240;\nexport const TODO_MAX_DESCRIPTION_LENGTH = 4_000;\nexport const TODO_MAX_ACTIVE_FORM_LENGTH = 240;\nexport const TODO_MAX_OWNER_LENGTH = 160;\nexport const TODO_MAX_BATCH_KEY_LENGTH = 120;\nexport const TODO_MAX_BLOCKED_BY = 50;\n\n/** Metadata is JSON-like, deeply copied, and bounded independently of task text. */\nexport const TODO_MAX_METADATA_KEY_LENGTH = 160;\nexport const TODO_MAX_METADATA_DEPTH = 8;\nexport const TODO_MAX_METADATA_ENTRIES = 100;\nexport const TODO_MAX_METADATA_BYTES = 16_384;\n\nexport const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for multi-step coding work.\n\n## When to Use\n- Work with three or more meaningful steps, user-provided task lists, or long sessions where progress can drift\n- Capture new user requirements as tasks right away; after completing a task, add follow-up tasks discovered during implementation\n\n## When NOT to Use\n- Trivial single-step tasks and simple Q&A — just do the work\n\n## Actions\n- create: add one pending task; subject (short, imperative, reviewable scope) and description (what done means: acceptance criteria or verification detail) are required\n- create_many: atomically add up to 20 pending tasks in input order; each item needs subject and description, and optional unique keys can be linked within the batch with blockedByKeys\n- update: change status or details, and manage dependencies via addBlockedBy/removeBlockedBy (what this task waits on) and addBlocks/removeBlocks (what waits on this task); dependencies are validated and cycle-checked\n- list: one-line summaries with unresolved blockers, optionally filtered by status, paged with limit/afterId, searched with query, or narrowed with unblockedOnly; get: one task in full; delete: tombstone an obsolete task and release its dependents; clear: requires confirm=true and the exact expectedCount, without reusing ids\n- Parameters that do not apply to the chosen action are rejected with guidance\n\n## Status Workflow\npending -> in_progress -> completed; deleted is the only terminal state. Keep exactly one task in_progress (starting one demotes any other active task): mark it in_progress BEFORE starting the work, and completed only when implementation and verification are genuinely done — never with failing tests or partial work; reopen instead of duplicating. When blocked, do not fake completion: create a task for the blocker and link it with addBlockedBy.\n\n## Examples\n{\"action\": \"create\", \"subject\": \"Fix login redirect\", \"description\": \"Redirect lands on the dashboard; auth tests pass\"}\n{\"action\": \"create_many\", \"items\": [{\"key\": \"parser\", \"subject\": \"Wire parser\", \"description\": \"Parser handles config\"}, {\"subject\": \"Test parser\", \"description\": \"Parser tests pass\", \"blockedByKeys\": [\"parser\"]}]}\n{\"action\": \"update\", \"id\": 2, \"status\": \"in_progress\", \"activeForm\": \"fixing login redirect\"}\n{\"action\": \"update\", \"id\": 1, \"addBlocks\": [2, 3]}\n\n## Tips\n- list before create to avoid duplicates; prefer working in id order — earlier tasks usually set up context for later ones\n- Use dependencies only for real ordering constraints, and delete obsolete tasks promptly so their dependents are released`;\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, user-provided task lists, or long sessions where progress can drift; list before creating to avoid duplicates, use create_many for a known batch of up to 20 tasks, and capture follow-up work discovered during implementation as new tasks.\",\n\t\"Mark the active `todo` task in_progress before starting work (exactly one at a time; the tool demotes any other active item) and completed immediately after verification — never while tests fail or work is partial; reopen instead of duplicating, and when blocked create a task for the blocker and link it with addBlockedBy.\",\n\t\"Keep the `todo` list short and outcome-oriented; prefer working in id order, use dependencies only for real prerequisites, and delete obsolete items promptly so their dependents are released. Clear only with confirm=true and the current expectedCount.\",\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,MAAM,CAAC,MAAM,qBAAqB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;sEA2BiC,CAAC;AAEvE,MAAM,CAAC,MAAM,mBAAmB,GAAG,sEAAsE,CAAC;AAE1G,MAAM,CAAC,MAAM,sBAAsB,GAAG;IACrC,mOAAmO;IACnO,wRAAwR;IACxR,wHAAwH;CACxH,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\nexport const TODO_TOOL_DESCRIPTION = `Manage the conversation's task list for multi-step coding work.\n\n## When to Use\n- Work with three or more meaningful steps, a user-provided task list, or a long session where progress can drift\n- Capture new user requirements as tasks right away; after completing a task, add follow-up tasks discovered during implementation\n\n## When NOT to Use\n- Trivial single-step tasks and simple Q&A — just do the work\n\n## Actions\n- create: atomically add one or many pending tasks from the ordered items array (1 to 20 items); each item needs a short imperative subject and a description of what done means\n- update: edit one task by id — subject, description, or status; all transitions and reopens are allowed\n- list: show every remaining task with its description, active first\n- delete: remove obsolete tasks by ids (1 to 20, duplicates ignored)\n- The current list holds at most 20 tasks; parameters that do not apply to the chosen action are rejected\n\n## Status Workflow\npending -> in_progress -> completed. Keep exactly one task in_progress: marking one active demotes any other active task to pending. Mark a task in_progress BEFORE starting the work, and completed only when its description is satisfied and relevant checks pass; never with failing tests or partial work. Reopen instead of duplicating. When blocked, do not fake completion: return the task to pending, create a task for the blocker, and finish the blocker first.\n\n## Examples\n{\"action\": \"create\", \"items\": [{\"subject\": \"Wire parser\", \"description\": \"Parser handles the config format\"}, {\"subject\": \"Test parser\", \"description\": \"Parser tests pass\"}]}\n{\"action\": \"update\", \"id\": 2, \"status\": \"in_progress\"}\n{\"action\": \"update\", \"id\": 2, \"status\": \"completed\", \"description\": \"Parser verified against config fixtures\"}\n{\"action\": \"delete\", \"ids\": [3]}\n\n## Tips\n- list before create to avoid duplicates; prefer working in id order — earlier tasks usually set up context for later ones\n- delete obsolete tasks promptly so the list stays short and accurate`;\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, user-provided task lists, or long sessions where progress can drift; list first when tasks may already exist, and create one or many tasks with the ordered items array.\",\n\t\"Keep exactly one `todo` task in_progress: mark it in_progress before starting work and completed only after verification — never while tests fail or work is partial; reopen instead of duplicating, and when blocked leave the `todo` task pending and create a task for the blocker.\",\n\t\"Keep the `todo` list short and outcome-oriented; prefer working in id order and delete obsolete `todo` items promptly.\",\n];\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/index.ts"],"names":[],"mappings":"AAeA,OAAO,EAEN,KAAK,YAAY,EAGjB,MAAM,gCAAgC,CAAC;AAyCxC,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAuFnD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/extensions/todo/index.ts"],"names":[],"mappings":"AAUA,OAAO,EAEN,KAAK,YAAY,EAGjB,MAAM,gCAAgC,CAAC;AA6BxC,MAAM,CAAC,OAAO,UAAU,IAAI,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAwEnD"}
@@ -1,32 +1,25 @@
1
1
  /**
2
2
  * todo — Pi-native task tracking for multi-step work.
3
3
  *
4
- * The state is intentionally conversation-backed: every tool result carries a
5
- * full snapshot in `details`, and lifecycle handlers replay the current branch.
6
- * This keeps /reload, compaction, and session-tree navigation aligned with the
7
- * conversation without adding a separate disk database. (Compaction-safe by
8
- * design: sessionManager.getBranch() returns the FULL branch history including
9
- * pre-compaction toolResult entries — only buildSessionContext summarizes.)
10
- *
11
- * State is keyed per session id (see state.ts): resume and /tree switches can
12
- * change the session within one process, and execute + lifecycle handlers
13
- * re-point the active bucket before touching state.
4
+ * State is conversation-backed: every tool result carries a full v2 snapshot
5
+ * in `details`, and lifecycle handlers replay the current branch into the
6
+ * closure store created by createTodoStore(). Resume, /reload, and /tree
7
+ * navigation stay aligned with the conversation without a disk database or
8
+ * any per-session global state.
14
9
  */
15
10
  import { Text } from "@earendil-works/pi-tui";
16
11
  import { isStaleExtensionContextError, } from "../../core/extensions/types.js";
17
12
  import { TODO_PROMPT_GUIDELINES, TODO_PROMPT_SNIPPET, TODO_TOOL_DESCRIPTION, TODO_TOOL_LABEL, TODO_TOOL_NAME, TODOS_COMMAND_NAME, } from "./constants.js";
18
- import { TodoOverlay } from "./overlay.js";
19
13
  import { TodoParamsSchema } from "./schema.js";
20
- import { applyTodoMutation, buildTodoDetails, commitTodoState, disposeTodoSession, formatTodoContent, getTodoState, replaceTodoState, replayTodosFromBranch, setActiveTodoSession, } from "./state.js";
21
- import { formatCommandList, formatTodoCall, formatTodoGroupCall } from "./view.js";
22
- function safeReplay(ctx) {
23
- // Every ctx.sessionManager access goes through a stale guard, so the whole
24
- // body stays inside the try: a lifecycle event can race session replacement
25
- // (resume, /tree, /reload), and a stale ctx just means another session took
26
- // over — nothing left to replay for it.
14
+ import { createTodoStore, replayTodosFromBranch } from "./state.js";
15
+ import { formatCommandList, formatTodoCall, formatTodoContent, formatTodoGroupCall } from "./view.js";
16
+ import { TodoWidget } from "./widget.js";
17
+ function safeReplay(ctx, store) {
18
+ // Every ctx.sessionManager access stays inside the guard: a lifecycle event
19
+ // can race session replacement (resume, /tree, /reload), and a stale ctx
20
+ // just means another session took over — nothing left to replay for it.
27
21
  try {
28
- setActiveTodoSession(ctx.sessionManager.getSessionId());
29
- replaceTodoState(replayTodosFromBranch(ctx));
22
+ store.replaceState(replayTodosFromBranch(ctx));
30
23
  }
31
24
  catch (error) {
32
25
  if (!isStaleExtensionContextError(error))
@@ -34,7 +27,8 @@ function safeReplay(ctx) {
34
27
  }
35
28
  }
36
29
  export default function todo(pi) {
37
- let overlay;
30
+ const store = createTodoStore();
31
+ let widget;
38
32
  pi.registerTool({
39
33
  name: TODO_TOOL_NAME,
40
34
  label: TODO_TOOL_LABEL,
@@ -44,23 +38,15 @@ export default function todo(pi) {
44
38
  parameters: TodoParamsSchema,
45
39
  executionMode: "sequential",
46
40
  // Consecutive todo calls collapse into one run like read/find's `explore`
47
- // group; the overlay already carries the live list, so the transcript only
41
+ // group; the widget already carries the live list, so the transcript only
48
42
  // needs the sequence of operations.
49
43
  toolGroup: TODO_TOOL_NAME,
50
- async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
51
- // Re-point the active bucket: resume//tree can switch sessions between
52
- // lifecycle events and this call.
53
- setActiveTodoSession(ctx.sessionManager.getSessionId());
54
- const result = applyTodoMutation(getTodoState(), params);
55
- if (result.operation.kind === "error") {
56
- throw new Error(result.operation.message);
57
- }
58
- commitTodoState(result.state);
59
- const text = formatTodoContent(result.operation, result.state);
60
- return {
61
- content: [{ type: "text", text }],
62
- details: buildTodoDetails(params, result.state),
63
- };
44
+ async execute(_toolCallId, params, _signal, _onUpdate) {
45
+ // Validation errors throw before any state mutation, so the store is
46
+ // untouched on failure.
47
+ const details = store.execute(params);
48
+ const text = formatTodoContent(details.change, details.state);
49
+ return { content: [{ type: "text", text }], details };
64
50
  },
65
51
  renderCall(args, theme, context) {
66
52
  const text = context.lastComponent ?? new Text("", 0, 0);
@@ -71,44 +57,37 @@ export default function todo(pi) {
71
57
  },
72
58
  });
73
59
  pi.registerCommand(TODOS_COMMAND_NAME, {
74
- description: "Show todos for the current conversation branch",
60
+ description: "Show the complete todo list for the current conversation branch",
75
61
  handler: async (_args, ctx) => {
76
62
  if (!ctx.hasUI) {
77
63
  ctx.ui.notify("/todos requires an interactive UI.", "warning");
78
64
  return;
79
65
  }
80
- setActiveTodoSession(ctx.sessionManager.getSessionId());
81
- ctx.ui.notify(formatCommandList(getTodoState()), "info");
66
+ ctx.ui.notify(formatCommandList(store.getState()), "info");
82
67
  },
83
68
  });
84
69
  pi.on("session_start", async (_event, ctx) => {
85
- safeReplay(ctx);
70
+ safeReplay(ctx, store);
86
71
  if (ctx.hasUI) {
87
- overlay ??= new TodoOverlay();
88
- overlay.setUI(ctx.ui);
89
- overlay.resetVisibility();
90
- overlay.update();
72
+ widget ??= new TodoWidget(() => store.getState());
73
+ widget.setUI(ctx.ui);
74
+ widget.update();
91
75
  }
92
76
  });
93
- pi.on("session_compact", async (_event, ctx) => {
94
- safeReplay(ctx);
95
- overlay?.resetVisibility();
96
- overlay?.update();
97
- });
98
77
  pi.on("session_tree", async (_event, ctx) => {
99
- safeReplay(ctx);
100
- overlay?.resetVisibility();
101
- overlay?.update();
78
+ safeReplay(ctx, store);
79
+ widget?.update();
102
80
  });
103
- pi.on("session_shutdown", async (_event, ctx) => {
104
- disposeTodoSession(ctx.sessionManager.getSessionId());
105
- overlay?.dispose();
106
- overlay = undefined;
81
+ pi.on("session_shutdown", async (_event, _ctx) => {
82
+ // The store belongs to this extension runtime and is collected with it;
83
+ // the next runtime restores its own store during session_start.
84
+ widget?.dispose();
85
+ widget = undefined;
107
86
  });
108
87
  pi.on("tool_execution_end", async (event) => {
109
88
  if (event.toolName !== TODO_TOOL_NAME || event.isError)
110
89
  return;
111
- overlay?.update();
90
+ widget?.update();
112
91
  });
113
92
  }
114
93
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
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 * The state is intentionally conversation-backed: every tool result carries a\n * full snapshot in `details`, and lifecycle handlers replay the current branch.\n * This keeps /reload, compaction, and session-tree navigation aligned with the\n * conversation without adding a separate disk database. (Compaction-safe by\n * design: sessionManager.getBranch() returns the FULL branch history including\n * pre-compaction toolResult entries — only buildSessionContext summarizes.)\n *\n * State is keyed per session id (see state.ts): resume and /tree switches can\n * change the session within one process, and execute + lifecycle handlers\n * re-point the active bucket before touching 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 { TodoOverlay } from \"./overlay.ts\";\nimport { type TodoDetails, TodoParamsSchema } from \"./schema.ts\";\nimport {\n\tapplyTodoMutation,\n\tbuildTodoDetails,\n\tcommitTodoState,\n\tdisposeTodoSession,\n\tformatTodoContent,\n\tgetTodoState,\n\treplaceTodoState,\n\treplayTodosFromBranch,\n\tsetActiveTodoSession,\n} from \"./state.ts\";\nimport { formatCommandList, formatTodoCall, formatTodoGroupCall } from \"./view.ts\";\n\ninterface TodoSessionCtx {\n\tsessionManager: { getBranch(): Iterable<unknown>; getSessionId(): string };\n}\n\nfunction safeReplay(ctx: TodoSessionCtx): void {\n\t// Every ctx.sessionManager access goes through a stale guard, so the whole\n\t// body stays inside the try: a lifecycle event can race session replacement\n\t// (resume, /tree, /reload), and a stale ctx just means another session took\n\t// over — nothing left to replay for it.\n\ttry {\n\t\tsetActiveTodoSession(ctx.sessionManager.getSessionId());\n\t\treplaceTodoState(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\tlet overlay: TodoOverlay | 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\texecutionMode: \"sequential\" as ToolExecutionMode,\n\t\t// Consecutive todo calls collapse into one run like read/find's `explore`\n\t\t// group; the overlay 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, ctx): Promise<AgentToolResult<TodoDetails>> {\n\t\t\t// Re-point the active bucket: resume//tree can switch sessions between\n\t\t\t// lifecycle events and this call.\n\t\t\tsetActiveTodoSession(ctx.sessionManager.getSessionId());\n\t\t\tconst result = applyTodoMutation(getTodoState(), params);\n\t\t\tif (result.operation.kind === \"error\") {\n\t\t\t\tthrow new Error(result.operation.message);\n\t\t\t}\n\t\t\tcommitTodoState(result.state);\n\t\t\tconst text = formatTodoContent(result.operation, result.state);\n\t\t\treturn {\n\t\t\t\tcontent: [{ type: \"text\", text }],\n\t\t\t\tdetails: buildTodoDetails(params, result.state),\n\t\t\t};\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 todos 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\tsetActiveTodoSession(ctx.sessionManager.getSessionId());\n\t\t\tctx.ui.notify(formatCommandList(getTodoState()), \"info\");\n\t\t},\n\t});\n\n\tpi.on(\"session_start\", async (_event, ctx) => {\n\t\tsafeReplay(ctx);\n\t\tif (ctx.hasUI) {\n\t\t\toverlay ??= new TodoOverlay();\n\t\t\toverlay.setUI(ctx.ui);\n\t\t\toverlay.resetVisibility();\n\t\t\toverlay.update();\n\t\t}\n\t});\n\n\tpi.on(\"session_compact\", async (_event, ctx) => {\n\t\tsafeReplay(ctx);\n\t\toverlay?.resetVisibility();\n\t\toverlay?.update();\n\t});\n\n\tpi.on(\"session_tree\", async (_event, ctx) => {\n\t\tsafeReplay(ctx);\n\t\toverlay?.resetVisibility();\n\t\toverlay?.update();\n\t});\n\n\tpi.on(\"session_shutdown\", async (_event, ctx) => {\n\t\tdisposeTodoSession(ctx.sessionManager.getSessionId());\n\t\toverlay?.dispose();\n\t\toverlay = 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\toverlay?.update();\n\t});\n}\n"]}
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,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\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"]}