pi-forge 1.2.3 → 1.2.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 (83) hide show
  1. package/README.md +1 -1
  2. package/dist/client/assets/{CodeMirrorEditor-1gu-DS9k.js → CodeMirrorEditor-DXmxwE2Z.js} +2 -2
  3. package/dist/client/assets/{CodeMirrorEditor-1gu-DS9k.js.map → CodeMirrorEditor-DXmxwE2Z.js.map} +1 -1
  4. package/dist/client/assets/index-CMSjnWtF.js +365 -0
  5. package/dist/client/assets/index-CMSjnWtF.js.map +1 -0
  6. package/dist/client/assets/index-Cp8qEy7Q.css +1 -0
  7. package/dist/client/index.html +2 -2
  8. package/dist/client/sw.js +1 -1
  9. package/dist/client/sw.js.map +1 -1
  10. package/dist/server/agent-extensions/compaction-continuation.js +65 -0
  11. package/dist/server/agent-extensions/compaction-continuation.js.map +1 -0
  12. package/dist/server/agent-resource-loader.js +10 -0
  13. package/dist/server/agent-resource-loader.js.map +1 -1
  14. package/dist/server/ask-user-question/envelope.js +56 -0
  15. package/dist/server/ask-user-question/envelope.js.map +1 -0
  16. package/dist/server/ask-user-question/prompt-strings.js +44 -0
  17. package/dist/server/ask-user-question/prompt-strings.js.map +1 -0
  18. package/dist/server/ask-user-question/registry.js +157 -0
  19. package/dist/server/ask-user-question/registry.js.map +1 -0
  20. package/dist/server/ask-user-question/tool.js +115 -0
  21. package/dist/server/ask-user-question/tool.js.map +1 -0
  22. package/dist/server/ask-user-question/types.js +27 -0
  23. package/dist/server/ask-user-question/types.js.map +1 -0
  24. package/dist/server/ask-user-question/validate.js +135 -0
  25. package/dist/server/ask-user-question/validate.js.map +1 -0
  26. package/dist/server/config.js +10 -0
  27. package/dist/server/config.js.map +1 -1
  28. package/dist/server/index.js +16 -0
  29. package/dist/server/index.js.map +1 -1
  30. package/dist/server/mcp/tool-bridge.js +14 -8
  31. package/dist/server/mcp/tool-bridge.js.map +1 -1
  32. package/dist/server/processes/envelope.js +60 -0
  33. package/dist/server/processes/envelope.js.map +1 -0
  34. package/dist/server/processes/log-store.js +132 -0
  35. package/dist/server/processes/log-store.js.map +1 -0
  36. package/dist/server/processes/manager.js +348 -0
  37. package/dist/server/processes/manager.js.map +1 -0
  38. package/dist/server/processes/prompt-strings.js +43 -0
  39. package/dist/server/processes/prompt-strings.js.map +1 -0
  40. package/dist/server/processes/tool.js +273 -0
  41. package/dist/server/processes/tool.js.map +1 -0
  42. package/dist/server/processes/types.js +21 -0
  43. package/dist/server/processes/types.js.map +1 -0
  44. package/dist/server/processes/watches.js +59 -0
  45. package/dist/server/processes/watches.js.map +1 -0
  46. package/dist/server/quick-actions.js +141 -0
  47. package/dist/server/quick-actions.js.map +1 -0
  48. package/dist/server/routes/ask-user-question.js +129 -0
  49. package/dist/server/routes/ask-user-question.js.map +1 -0
  50. package/dist/server/routes/config.js +12 -0
  51. package/dist/server/routes/config.js.map +1 -1
  52. package/dist/server/routes/processes.js +228 -0
  53. package/dist/server/routes/processes.js.map +1 -0
  54. package/dist/server/routes/quick-actions.js +384 -0
  55. package/dist/server/routes/quick-actions.js.map +1 -0
  56. package/dist/server/routes/todos.js +67 -0
  57. package/dist/server/routes/todos.js.map +1 -0
  58. package/dist/server/session-registry.js +72 -4
  59. package/dist/server/session-registry.js.map +1 -1
  60. package/dist/server/sse-bridge.js +225 -4
  61. package/dist/server/sse-bridge.js.map +1 -1
  62. package/dist/server/todo/envelope.js +87 -0
  63. package/dist/server/todo/envelope.js.map +1 -0
  64. package/dist/server/todo/invariants.js +21 -0
  65. package/dist/server/todo/invariants.js.map +1 -0
  66. package/dist/server/todo/prompt-strings.js +29 -0
  67. package/dist/server/todo/prompt-strings.js.map +1 -0
  68. package/dist/server/todo/reducer.js +189 -0
  69. package/dist/server/todo/reducer.js.map +1 -0
  70. package/dist/server/todo/replay.js +45 -0
  71. package/dist/server/todo/replay.js.map +1 -0
  72. package/dist/server/todo/store.js +92 -0
  73. package/dist/server/todo/store.js.map +1 -0
  74. package/dist/server/todo/task-graph.js +60 -0
  75. package/dist/server/todo/task-graph.js.map +1 -0
  76. package/dist/server/todo/tool.js +95 -0
  77. package/dist/server/todo/tool.js.map +1 -0
  78. package/dist/server/todo/types.js +23 -0
  79. package/dist/server/todo/types.js.map +1 -0
  80. package/package.json +1 -1
  81. package/dist/client/assets/index-BxZV6ddv.js +0 -359
  82. package/dist/client/assets/index-BxZV6ddv.js.map +0 -1
  83. package/dist/client/assets/index-KUhxvBxw.css +0 -1
@@ -0,0 +1,189 @@
1
+ import { isTransitionValid } from "./invariants.js";
2
+ import { detectCycle } from "./task-graph.js";
3
+ function errorResult(state, message) {
4
+ return { state, op: { kind: "error", message } };
5
+ }
6
+ /**
7
+ * Pure reducer: `(state, action, params) → (state, op)`. Validation
8
+ * is in-line: structural guards (`subject required`, `id required`,
9
+ * `at least one mutable field`) plus state-aware checks (transition
10
+ * legality, dangling/deleted blockedBy, self-block, cycles).
11
+ *
12
+ * Error messages are intentionally kept in the plugin's voice so
13
+ * existing agent prompts that grep on the error text continue to
14
+ * match. The wire shape (`details.error` string) is the structured
15
+ * channel; the text is the human/LLM-readable channel.
16
+ */
17
+ export function applyTaskMutation(state, action, params) {
18
+ switch (action) {
19
+ case "create":
20
+ return reduceCreate(state, params);
21
+ case "update":
22
+ return reduceUpdate(state, params);
23
+ case "list":
24
+ return {
25
+ state,
26
+ op: {
27
+ kind: "list",
28
+ includeDeleted: params.includeDeleted === true,
29
+ ...(params.status !== undefined ? { statusFilter: params.status } : {}),
30
+ },
31
+ };
32
+ case "get":
33
+ return reduceGet(state, params);
34
+ case "delete":
35
+ return reduceDelete(state, params);
36
+ case "clear":
37
+ return {
38
+ state: { tasks: [], nextId: 1 },
39
+ op: { kind: "clear", count: state.tasks.length },
40
+ };
41
+ }
42
+ }
43
+ function reduceCreate(state, params) {
44
+ if (params.subject === undefined || params.subject.trim().length === 0) {
45
+ return errorResult(state, "subject required for create");
46
+ }
47
+ if (params.blockedBy !== undefined && params.blockedBy.length > 0) {
48
+ for (const dep of params.blockedBy) {
49
+ const depTask = state.tasks.find((t) => t.id === dep);
50
+ if (depTask === undefined)
51
+ return errorResult(state, `blockedBy: #${dep} not found`);
52
+ if (depTask.status === "deleted")
53
+ return errorResult(state, `blockedBy: #${dep} is deleted`);
54
+ }
55
+ }
56
+ const newTask = {
57
+ id: state.nextId,
58
+ subject: params.subject,
59
+ status: "pending",
60
+ };
61
+ if (params.description !== undefined)
62
+ newTask.description = params.description;
63
+ if (params.activeForm !== undefined)
64
+ newTask.activeForm = params.activeForm;
65
+ if (params.blockedBy !== undefined && params.blockedBy.length > 0) {
66
+ newTask.blockedBy = [...params.blockedBy];
67
+ }
68
+ if (params.owner !== undefined)
69
+ newTask.owner = params.owner;
70
+ if (params.metadata !== undefined)
71
+ newTask.metadata = { ...params.metadata };
72
+ return {
73
+ state: { tasks: [...state.tasks, newTask], nextId: state.nextId + 1 },
74
+ op: { kind: "create", taskId: newTask.id },
75
+ };
76
+ }
77
+ function reduceUpdate(state, params) {
78
+ if (params.id === undefined)
79
+ return errorResult(state, "id required for update");
80
+ const idx = state.tasks.findIndex((t) => t.id === params.id);
81
+ if (idx === -1)
82
+ return errorResult(state, `#${params.id} not found`);
83
+ const current = state.tasks[idx];
84
+ const hasMutation = params.subject !== undefined ||
85
+ params.description !== undefined ||
86
+ params.activeForm !== undefined ||
87
+ params.status !== undefined ||
88
+ params.owner !== undefined ||
89
+ params.metadata !== undefined ||
90
+ (params.addBlockedBy !== undefined && params.addBlockedBy.length > 0) ||
91
+ (params.removeBlockedBy !== undefined && params.removeBlockedBy.length > 0);
92
+ if (!hasMutation) {
93
+ return errorResult(state, "update requires at least one mutable field");
94
+ }
95
+ let newStatus = current.status;
96
+ if (params.status !== undefined) {
97
+ if (!isTransitionValid(current.status, params.status)) {
98
+ return errorResult(state, `illegal transition ${current.status} → ${params.status}`);
99
+ }
100
+ newStatus = params.status;
101
+ }
102
+ let newBlockedBy = current.blockedBy ? [...current.blockedBy] : [];
103
+ if (params.removeBlockedBy !== undefined && params.removeBlockedBy.length > 0) {
104
+ const toRemove = new Set(params.removeBlockedBy);
105
+ newBlockedBy = newBlockedBy.filter((dep) => !toRemove.has(dep));
106
+ }
107
+ if (params.addBlockedBy !== undefined && params.addBlockedBy.length > 0) {
108
+ for (const dep of params.addBlockedBy) {
109
+ if (dep === current.id) {
110
+ return errorResult(state, `cannot block #${current.id} on itself`);
111
+ }
112
+ const depTask = state.tasks.find((t) => t.id === dep);
113
+ if (depTask === undefined)
114
+ return errorResult(state, `addBlockedBy: #${dep} not found`);
115
+ if (depTask.status === "deleted") {
116
+ return errorResult(state, `addBlockedBy: #${dep} is deleted`);
117
+ }
118
+ if (!newBlockedBy.includes(dep))
119
+ newBlockedBy.push(dep);
120
+ }
121
+ if (detectCycle(state.tasks, current.id, newBlockedBy)) {
122
+ return errorResult(state, "addBlockedBy would create a cycle in the blockedBy graph");
123
+ }
124
+ }
125
+ // Metadata merge: pass `null` for a key to delete it; otherwise
126
+ // shallow merge atop existing metadata. Result with zero keys
127
+ // collapses to undefined so the persisted shape stays clean.
128
+ let newMetadata = current.metadata;
129
+ if (params.metadata !== undefined) {
130
+ const merged = { ...(current.metadata ?? {}) };
131
+ for (const [k, v] of Object.entries(params.metadata)) {
132
+ if (v === null)
133
+ delete merged[k];
134
+ else
135
+ merged[k] = v;
136
+ }
137
+ newMetadata = Object.keys(merged).length > 0 ? merged : undefined;
138
+ }
139
+ const updated = { ...current, status: newStatus };
140
+ if (params.subject !== undefined)
141
+ updated.subject = params.subject;
142
+ if (params.description !== undefined)
143
+ updated.description = params.description;
144
+ if (params.activeForm !== undefined)
145
+ updated.activeForm = params.activeForm;
146
+ if (params.owner !== undefined)
147
+ updated.owner = params.owner;
148
+ if (newBlockedBy.length > 0)
149
+ updated.blockedBy = newBlockedBy;
150
+ else
151
+ delete updated.blockedBy;
152
+ if (newMetadata === undefined)
153
+ delete updated.metadata;
154
+ else
155
+ updated.metadata = newMetadata;
156
+ const newTasks = [...state.tasks];
157
+ newTasks[idx] = updated;
158
+ return {
159
+ state: { tasks: newTasks, nextId: state.nextId },
160
+ op: { kind: "update", id: updated.id, fromStatus: current.status, toStatus: newStatus },
161
+ };
162
+ }
163
+ function reduceGet(state, params) {
164
+ if (params.id === undefined)
165
+ return errorResult(state, "id required for get");
166
+ const task = state.tasks.find((t) => t.id === params.id);
167
+ if (task === undefined)
168
+ return errorResult(state, `#${params.id} not found`);
169
+ return { state, op: { kind: "get", task } };
170
+ }
171
+ function reduceDelete(state, params) {
172
+ if (params.id === undefined)
173
+ return errorResult(state, "id required for delete");
174
+ const idx = state.tasks.findIndex((t) => t.id === params.id);
175
+ if (idx === -1)
176
+ return errorResult(state, `#${params.id} not found`);
177
+ const current = state.tasks[idx];
178
+ if (current.status === "deleted") {
179
+ return errorResult(state, `#${current.id} is already deleted`);
180
+ }
181
+ const updated = { ...current, status: "deleted" };
182
+ const newTasks = [...state.tasks];
183
+ newTasks[idx] = updated;
184
+ return {
185
+ state: { tasks: newTasks, nextId: state.nextId },
186
+ op: { kind: "delete", id: updated.id, subject: updated.subject },
187
+ };
188
+ }
189
+ //# sourceMappingURL=reducer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reducer.js","sourceRoot":"","sources":["../../src/todo/reducer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import { EMPTY_STATE, TOOL_NAME } from "./types.js";
2
+ /**
3
+ * Discriminator for `details` envelopes that match the persisted
4
+ * `TaskDetails` shape. Defensive — branch entries from older or
5
+ * corrupt sessions are skipped silently rather than crashing the
6
+ * replay walk.
7
+ */
8
+ export function isTaskDetails(value) {
9
+ if (typeof value !== "object" || value === null)
10
+ return false;
11
+ const v = value;
12
+ return Array.isArray(v.tasks) && typeof v.nextId === "number";
13
+ }
14
+ /**
15
+ * Walk the session's current branch in chronological order; the
16
+ * LAST `toolResult` whose `toolName === TOOL_NAME` and whose
17
+ * `details` matches `TaskDetails` wins (last-write-wins). When no
18
+ * matching entry exists, returns EMPTY_STATE.
19
+ *
20
+ * This is the SOURCE OF TRUTH for state across server restarts,
21
+ * forks, and compaction. The in-memory store cache is just a
22
+ * fast-path; resume always re-derives from the branch so a stale
23
+ * cache cannot lie to the user.
24
+ *
25
+ * Pure — does not touch the store cell. Callers commit the
26
+ * returned snapshot themselves.
27
+ */
28
+ export function replayFromBranch(sessionManager) {
29
+ let result = { tasks: [...EMPTY_STATE.tasks], nextId: EMPTY_STATE.nextId };
30
+ for (const entry of sessionManager.getBranch()) {
31
+ if (entry.type !== "message")
32
+ continue;
33
+ const msg = entry.message;
34
+ if (msg.role !== "toolResult" || msg.toolName !== TOOL_NAME)
35
+ continue;
36
+ if (!isTaskDetails(msg.details))
37
+ continue;
38
+ result = {
39
+ tasks: msg.details.tasks.map((t) => ({ ...t })),
40
+ nextId: msg.details.nextId,
41
+ };
42
+ }
43
+ return result;
44
+ }
45
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../../src/todo/replay.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAoC,MAAM,YAAY,CAAC;AAEtF;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC;IAC9D,MAAM,CAAC,GAAG,KAAgC,CAAC;IAC3C,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC;AAChE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAAC,cAA8B;IAC7D,IAAI,MAAM,GAAc,EAAE,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,CAAC;IACtF,KAAK,MAAM,KAAK,IAAI,cAAc,CAAC,SAAS,EAAE,EAAE,CAAC;QAC/C,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS;YAAE,SAAS;QACvC,MAAM,GAAG,GAAG,KAAK,CAAC,OAAkE,CAAC;QACrF,IAAI,GAAG,CAAC,IAAI,KAAK,YAAY,IAAI,GAAG,CAAC,QAAQ,KAAK,SAAS;YAAE,SAAS;QACtE,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,CAAC;YAAE,SAAS;QAC1C,MAAM,GAAG;YACP,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC;YAC/C,MAAM,EAAE,GAAG,CAAC,OAAO,CAAC,MAAM;SAC3B,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { replayFromBranch } from "./replay.js";
2
+ import { EMPTY_STATE } from "./types.js";
3
+ /**
4
+ * Per-session in-memory cache of the latest committed TaskState.
5
+ * The branch IS the source of truth (see replay.ts) — this cache
6
+ * is a fast-path for read-heavy consumers (SSE snapshots, the UI
7
+ * panel's initial fetch). On any cache miss we recompute from the
8
+ * branch, so a stale or evicted cache cannot lie.
9
+ *
10
+ * Keyed by sessionId. Cleared on dispose by `clearForSession`
11
+ * (called from session-registry's dispose path).
12
+ */
13
+ const cacheBySession = new Map();
14
+ const listeners = new Set();
15
+ export function subscribe(fn) {
16
+ listeners.add(fn);
17
+ return () => {
18
+ listeners.delete(fn);
19
+ };
20
+ }
21
+ function notify(sessionId, state) {
22
+ for (const fn of listeners) {
23
+ try {
24
+ fn({ sessionId, state });
25
+ }
26
+ catch {
27
+ // Listener errors must not break the registry — best-effort fanout.
28
+ }
29
+ }
30
+ }
31
+ /**
32
+ * Get the current state for a session. Cache-first; on miss we
33
+ * replay the branch and populate the cache. Always returns a
34
+ * defensive copy so callers can't mutate the cached tasks array.
35
+ */
36
+ export function getState(sessionId, sessionManager) {
37
+ const cached = cacheBySession.get(sessionId);
38
+ if (cached !== undefined) {
39
+ return { tasks: cached.tasks.map((t) => ({ ...t })), nextId: cached.nextId };
40
+ }
41
+ const replayed = replayFromBranch(sessionManager);
42
+ cacheBySession.set(sessionId, replayed);
43
+ return { tasks: replayed.tasks.map((t) => ({ ...t })), nextId: replayed.nextId };
44
+ }
45
+ /**
46
+ * Commit a new state for a session and fan out the change. The
47
+ * reducer is pure; persistence happens via two channels:
48
+ * 1. The agent's tool-result envelope (carries `details.tasks`)
49
+ * becomes part of the session JSONL, which is what `replay.ts`
50
+ * reads on restart / fork / compaction.
51
+ * 2. This cache, for fast SSE / route reads between tool calls.
52
+ */
53
+ export function commitState(sessionId, state) {
54
+ cacheBySession.set(sessionId, state);
55
+ notify(sessionId, state);
56
+ }
57
+ /**
58
+ * Force a re-read from the branch and update the cache. Called by
59
+ * lifecycle hooks (resume, fork, compact) so the cache reflects the
60
+ * authoritative source after the message tree changes. Also called
61
+ * by GET /todos on cache miss.
62
+ */
63
+ export function refreshFromBranch(sessionId, sessionManager) {
64
+ const fresh = replayFromBranch(sessionManager);
65
+ cacheBySession.set(sessionId, fresh);
66
+ notify(sessionId, fresh);
67
+ return fresh;
68
+ }
69
+ export function clearForSession(sessionId) {
70
+ cacheBySession.delete(sessionId);
71
+ }
72
+ /**
73
+ * Read-only peek for the SSE bridge's snapshot path. Returns the
74
+ * cached state if present, or EMPTY_STATE if not — the snapshot
75
+ * caller doesn't have a sessionManager handy and a cache miss just
76
+ * means "no todos to re-deliver." Routes that NEED accuracy go
77
+ * through `getState` (which replays from the branch).
78
+ */
79
+ export function peekCached(sessionId) {
80
+ const cached = cacheBySession.get(sessionId);
81
+ if (cached === undefined)
82
+ return EMPTY_STATE;
83
+ return { tasks: cached.tasks.map((t) => ({ ...t })), nextId: cached.nextId };
84
+ }
85
+ /**
86
+ * Test-only: drop everything. Listeners are NOT cleared (the SSE
87
+ * bridge's subscription is process-lifetime).
88
+ */
89
+ export function _resetForTests() {
90
+ cacheBySession.clear();
91
+ }
92
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../../src/todo/store.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAkB,MAAM,YAAY,CAAC;AAEzD;;;;;;;;;GASG;AACH,MAAM,cAAc,GAAG,IAAI,GAAG,EAAqB,CAAC;AAQpD,MAAM,SAAS,GAAG,IAAI,GAAG,EAAY,CAAC;AAEtC,MAAM,UAAU,SAAS,CAAC,EAAY;IACpC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,GAAG,EAAE;QACV,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;IACvB,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,MAAM,CAAC,SAAiB,EAAE,KAAgB;IACjD,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,EAAE,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,oEAAoE;QACtE,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,SAAiB,EAAE,cAA8B;IACxE,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IAC/E,CAAC;IACD,MAAM,QAAQ,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAClD,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IACxC,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AACnF,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,SAAiB,EAAE,KAAgB;IAC7D,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;AAC3B,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,SAAiB,EAAE,cAA8B;IACjF,MAAM,KAAK,GAAG,gBAAgB,CAAC,cAAc,CAAC,CAAC;IAC/C,cAAc,CAAC,GAAG,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;IACzB,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,SAAiB;IAC/C,cAAc,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AACnC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,UAAU,CAAC,SAAiB;IAC1C,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;IAC7C,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAC7C,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAC/E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc;IAC5B,cAAc,CAAC,KAAK,EAAE,CAAC;AACzB,CAAC"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Detect whether merging `newBlockedBy` into `taskId`'s blockedBy
3
+ * set would introduce a cycle in the dependency graph. Pure of any
4
+ * module state; takes the existing task list and the proposed
5
+ * additions explicitly so the reducer can ask "would this update
6
+ * cycle?" without mutating state first.
7
+ *
8
+ * DFS with a recursion-stack ("visiting") set — classic three-color
9
+ * algorithm. O(V+E). Self-loops are also cycles.
10
+ */
11
+ export function detectCycle(taskList, taskId, newBlockedBy) {
12
+ const edges = new Map();
13
+ for (const t of taskList) {
14
+ if (t.id === taskId) {
15
+ const merged = new Set([...(t.blockedBy ?? []), ...newBlockedBy]);
16
+ edges.set(t.id, [...merged]);
17
+ }
18
+ else {
19
+ edges.set(t.id, t.blockedBy ? [...t.blockedBy] : []);
20
+ }
21
+ }
22
+ const visiting = new Set();
23
+ const visited = new Set();
24
+ const hasCycleFrom = (node) => {
25
+ if (visiting.has(node))
26
+ return true;
27
+ if (visited.has(node))
28
+ return false;
29
+ visiting.add(node);
30
+ for (const nb of edges.get(node) ?? []) {
31
+ if (hasCycleFrom(nb))
32
+ return true;
33
+ }
34
+ visiting.delete(node);
35
+ visited.add(node);
36
+ return false;
37
+ };
38
+ for (const node of edges.keys()) {
39
+ if (hasCycleFrom(node))
40
+ return true;
41
+ }
42
+ return false;
43
+ }
44
+ /**
45
+ * Inverse adjacency: for each task T, which other tasks list T in
46
+ * their blockedBy. Consumed by the `get` action's "blocks:" line
47
+ * and by the UI panel's "blocked → blocker" rendering.
48
+ */
49
+ export function deriveBlocks(taskList) {
50
+ const blocks = new Map();
51
+ for (const t of taskList) {
52
+ for (const dep of t.blockedBy ?? []) {
53
+ const arr = blocks.get(dep) ?? [];
54
+ arr.push(t.id);
55
+ blocks.set(dep, arr);
56
+ }
57
+ }
58
+ return blocks;
59
+ }
60
+ //# sourceMappingURL=task-graph.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task-graph.js","sourceRoot":"","sources":["../../src/todo/task-graph.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AACH,MAAM,UAAU,WAAW,CACzB,QAAyB,EACzB,MAAc,EACd,YAA+B;IAE/B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC1C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;YACpB,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC,EAAE,GAAG,YAAY,CAAC,CAAC,CAAC;YAClE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC;QAC/B,CAAC;aAAM,CAAC;YACN,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvD,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAC;IACnC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;IAClC,MAAM,YAAY,GAAG,CAAC,IAAY,EAAW,EAAE;QAC7C,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;QACpC,IAAI,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACpC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACnB,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;YACvC,IAAI,YAAY,CAAC,EAAE,CAAC;gBAAE,OAAO,IAAI,CAAC;QACpC,CAAC;QACD,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QAClB,OAAO,KAAK,CAAC;IACf,CAAC,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAChC,IAAI,YAAY,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,QAAyB;IACpD,MAAM,MAAM,GAAG,IAAI,GAAG,EAAoB,CAAC;IAC3C,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,KAAK,MAAM,GAAG,IAAI,CAAC,CAAC,SAAS,IAAI,EAAE,EAAE,CAAC;YACpC,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YAClC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;YACf,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;QACvB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,95 @@
1
+ import { Type } from "typebox";
2
+ import { buildToolResult } from "./envelope.js";
3
+ import { DEFAULT_PROMPT_GUIDELINES, DEFAULT_PROMPT_SNIPPET, TOOL_DESCRIPTION, } from "./prompt-strings.js";
4
+ import { applyTaskMutation } from "./reducer.js";
5
+ import { commitState, getState } from "./store.js";
6
+ import { TOOL_LABEL, TOOL_NAME } from "./types.js";
7
+ /**
8
+ * JSON Schema for `todo` tool params. Hand-written (not via the
9
+ * TypeBox DSL) so the shape matches the upstream plugin field-for-
10
+ * field. Structural caps act as a fast pre-filter; the reducer
11
+ * runs after for semantic checks (transition legality, dangling
12
+ * deps, cycles) the schema can't express.
13
+ *
14
+ * Type.Unsafe wraps the raw JSON Schema as a TypeBox schema so it
15
+ * satisfies ToolDefinition.parameters without bringing the rest of
16
+ * the TypeBox DSL into our code.
17
+ */
18
+ const inputSchema = {
19
+ type: "object",
20
+ required: ["action"],
21
+ properties: {
22
+ action: {
23
+ type: "string",
24
+ enum: ["create", "update", "list", "get", "delete", "clear"],
25
+ },
26
+ subject: { type: "string", description: "Task subject line (required for create)" },
27
+ description: { type: "string", description: "Long-form task description" },
28
+ activeForm: {
29
+ type: "string",
30
+ description: "Present-continuous spinner label shown while status is in_progress (e.g. 'writing tests')",
31
+ },
32
+ status: {
33
+ type: "string",
34
+ enum: ["pending", "in_progress", "completed", "deleted"],
35
+ description: "Target status (update) or list filter (list)",
36
+ },
37
+ blockedBy: {
38
+ type: "array",
39
+ items: { type: "number" },
40
+ description: "Initial blockedBy ids (create only)",
41
+ },
42
+ addBlockedBy: {
43
+ type: "array",
44
+ items: { type: "number" },
45
+ description: "Task ids to add to blockedBy (update only, additive merge)",
46
+ },
47
+ removeBlockedBy: {
48
+ type: "array",
49
+ items: { type: "number" },
50
+ description: "Task ids to remove from blockedBy (update only, additive merge)",
51
+ },
52
+ owner: { type: "string", description: "Agent/owner assigned to this task" },
53
+ metadata: {
54
+ type: "object",
55
+ additionalProperties: true,
56
+ description: "Arbitrary metadata; pass null value for a key to delete that key on update",
57
+ },
58
+ id: { type: "number", description: "Task id (required for update, get, delete)" },
59
+ includeDeleted: {
60
+ type: "boolean",
61
+ description: "If true, list action returns deleted (tombstoned) tasks as well. Default: false.",
62
+ },
63
+ },
64
+ };
65
+ /**
66
+ * Build the per-session `todo` tool. Contract-compatible with
67
+ * `@juicesharp/rpiv-todo` — same tool name, input schema, response
68
+ * envelope (`{content:[{type:"text",text}], details:{action,
69
+ * params, tasks, nextId, error?}}`), 4-state machine, and
70
+ * blockedBy semantics. An agent prompt authored against the plugin
71
+ * works against this implementation unchanged.
72
+ *
73
+ * Bound to one session so `execute()` can resolve the right cache
74
+ * key and the right sessionManager (for branch replay).
75
+ */
76
+ export function createTodoTool(sessionId, sessionManager) {
77
+ return {
78
+ name: TOOL_NAME,
79
+ label: TOOL_LABEL,
80
+ description: TOOL_DESCRIPTION,
81
+ promptSnippet: DEFAULT_PROMPT_SNIPPET,
82
+ promptGuidelines: DEFAULT_PROMPT_GUIDELINES,
83
+ parameters: Type.Unsafe(inputSchema),
84
+ async execute(_toolCallId, params) {
85
+ const typed = params;
86
+ // Cache-first read; getState replays from the branch on miss
87
+ // so a server restart doesn't lose state mid-session.
88
+ const current = getState(sessionId, sessionManager);
89
+ const result = applyTaskMutation(current, typed.action, typed);
90
+ commitState(sessionId, result.state);
91
+ return buildToolResult(typed.action, typed, result.state, result.op);
92
+ },
93
+ };
94
+ }
95
+ //# sourceMappingURL=tool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool.js","sourceRoot":"","sources":["../../src/todo/tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE/B,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EACL,yBAAyB,EACzB,sBAAsB,EACtB,gBAAgB,GACjB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,SAAS,EAA4C,MAAM,YAAY,CAAC;AAE7F;;;;;;;;;;GAUG;AACH,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,QAAQ;IACd,QAAQ,EAAE,CAAC,QAAQ,CAAC;IACpB,UAAU,EAAE;QACV,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,CAAC;SAC7D;QACD,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAE;QACnF,WAAW,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,4BAA4B,EAAE;QAC1E,UAAU,EAAE;YACV,IAAI,EAAE,QAAQ;YACd,WAAW,EACT,2FAA2F;SAC9F;QACD,MAAM,EAAE;YACN,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,SAAS,EAAE,aAAa,EAAE,WAAW,EAAE,SAAS,CAAC;YACxD,WAAW,EAAE,8CAA8C;SAC5D;QACD,SAAS,EAAE;YACT,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACzB,WAAW,EAAE,qCAAqC;SACnD;QACD,YAAY,EAAE;YACZ,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACzB,WAAW,EAAE,4DAA4D;SAC1E;QACD,eAAe,EAAE;YACf,IAAI,EAAE,OAAO;YACb,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;YACzB,WAAW,EAAE,iEAAiE;SAC/E;QACD,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,mCAAmC,EAAE;QAC3E,QAAQ,EAAE;YACR,IAAI,EAAE,QAAQ;YACd,oBAAoB,EAAE,IAAI;YAC1B,WAAW,EAAE,4EAA4E;SAC1F;QACD,EAAE,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,4CAA4C,EAAE;QACjF,cAAc,EAAE;YACd,IAAI,EAAE,SAAS;YACf,WAAW,EACT,kFAAkF;SACrF;KACF;CACO,CAAC;AAEX;;;;;;;;;;GAUG;AACH,MAAM,UAAU,cAAc,CAAC,SAAiB,EAAE,cAA8B;IAC9E,OAAO;QACL,IAAI,EAAE,SAAS;QACf,KAAK,EAAE,UAAU;QACjB,WAAW,EAAE,gBAAgB;QAC7B,aAAa,EAAE,sBAAsB;QACrC,gBAAgB,EAAE,yBAAyB;QAC3C,UAAU,EAAE,IAAI,CAAC,MAAM,CAA0B,WAAW,CAAC;QAC7D,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,MAAM;YAC/B,MAAM,KAAK,GAAG,MAAqD,CAAC;YACpE,6DAA6D;YAC7D,sDAAsD;YACtD,MAAM,OAAO,GAAG,QAAQ,CAAC,SAAS,EAAE,cAAc,CAAC,CAAC;YACpD,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;YAC/D,WAAW,CAAC,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC;YACrC,OAAO,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;KACuB,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Shape definitions for the `todo` tool. The wire contract — tool
3
+ * name (`"todo"`), input enum (`create | update | list | get |
4
+ * delete | clear`), 4-state machine (`pending | in_progress |
5
+ * completed | deleted`), dependency model (blockedBy + add/remove
6
+ * variants on update), and response envelope (`{content:[{type:
7
+ * "text", text}], details:{action, params, tasks, nextId, error?}}`)
8
+ * — is contract-compatible with `@juicesharp/rpiv-todo`. An agent
9
+ * prompt authored against the plugin works against this
10
+ * implementation unchanged.
11
+ *
12
+ * Implementation is independent; constants and validation rules were
13
+ * derived from the plugin's published schema descriptions and tests
14
+ * rather than copied. See `docs/todo.md` for the cross-reference.
15
+ *
16
+ * The tool name "todo" is the persistence key for branch replay
17
+ * (we filter `toolResult.toolName === "todo"` to reconstruct state)
18
+ * — DO NOT rename without a migration story for existing sessions.
19
+ */
20
+ export const TOOL_NAME = "todo";
21
+ export const TOOL_LABEL = "Todo";
22
+ export const EMPTY_STATE = { tasks: [], nextId: 1 };
23
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/todo/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,MAAM,CAAC,MAAM,SAAS,GAAG,MAAM,CAAC;AAChC,MAAM,CAAC,MAAM,UAAU,GAAG,MAAM,CAAC;AAqCjC,MAAM,CAAC,MAAM,WAAW,GAAc,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-forge",
3
- "version": "1.2.3",
3
+ "version": "1.2.5",
4
4
  "description": "Browser UI for the pi coding agent — embedded HTTP server with a React workbench (chat, file browser, terminal, git, MCP).",
5
5
  "keywords": [
6
6
  "pi",