@justjoshn/pi-pstack 0.15.36-pi.12 → 0.15.36-pi.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +8 -8
- package/automations/benny/FOR_AGENTS.md +1 -1
- package/automations/benny/skills/reproduce-and-fix-issues/SKILL.md +3 -3
- package/automations/benny/skills/setup-benny/SKILL.md +3 -3
- package/automations/benny/skills/triage-issue-reports/SKILL.md +2 -2
- package/docs/parity-status.md +31 -2
- package/package.json +3 -2
- package/skills/pr-review-canvas/SKILL.md +1 -1
- package/skills/statechart/scripts/test/cli.test.ts +10 -1
- package/src/child-env.ts +4 -0
- package/src/index.ts +6 -2
- package/src/loop.ts +92 -47
- package/src/plan.ts +142 -20
- package/src/process-lock.ts +76 -0
- package/src/readonly-shell.ts +6 -8
- package/src/subscriptions/durable-store.ts +2 -75
- package/src/subscriptions/index.ts +2 -17
- package/src/subscriptions/supervisor.ts +1 -1
- package/src/task/records.ts +37 -32
- package/src/task/tool.ts +10 -2
- package/src/todo.ts +29 -6
package/src/todo.ts
CHANGED
|
@@ -8,18 +8,21 @@ export interface TodoItem {
|
|
|
8
8
|
id: string;
|
|
9
9
|
content: string;
|
|
10
10
|
status: TodoStatus;
|
|
11
|
+
dependencies?: string[];
|
|
11
12
|
}
|
|
12
13
|
|
|
13
14
|
export interface TodoInput {
|
|
14
15
|
id: string;
|
|
15
16
|
content?: string;
|
|
16
17
|
status?: TodoStatus;
|
|
18
|
+
dependencies?: string[];
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
interface TodoDetails {
|
|
20
22
|
todos: TodoItem[];
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
const TODO_ENTRY = 'pstack/todos';
|
|
23
26
|
const WIDGET_KEY = 'pstack-todos';
|
|
24
27
|
const MARKS: Record<TodoStatus, string> = { pending: '[ ]', in_progress: '[>]', completed: '[x]', cancelled: '[-]' };
|
|
25
28
|
const GLYPHS: Record<TodoStatus, string> = { pending: '○', in_progress: '▶', completed: '✓', cancelled: '✗' };
|
|
@@ -43,7 +46,8 @@ function complete(item: TodoInput, existing: TodoItem | undefined): TodoItem {
|
|
|
43
46
|
const content = item.content ?? existing?.content;
|
|
44
47
|
const status = item.status ?? existing?.status;
|
|
45
48
|
if (content === undefined || status === undefined) throw new Error(`Todo "${item.id}" is new, so it needs both content and status.`);
|
|
46
|
-
|
|
49
|
+
const dependencies = item.dependencies ?? existing?.dependencies;
|
|
50
|
+
return { id: item.id, content, status, ...(dependencies === undefined ? {} : { dependencies }) };
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
/** TodoWrite: merge updates by id and appends new ids; otherwise the call replaces the list. */
|
|
@@ -68,18 +72,25 @@ export function formatTodos(todos: readonly TodoItem[]): string {
|
|
|
68
72
|
const done = todos.filter((item) => item.status === 'completed').length;
|
|
69
73
|
const active = todos.filter((item) => item.status === 'in_progress').length;
|
|
70
74
|
const header = `Todos (${done}/${todos.length} completed${active > 0 ? `, ${active} in progress` : ''}):`;
|
|
71
|
-
return [header, ...todos.map((item) => `${MARKS[item.status]} ${item.id}: ${item.content}`)].join('\n');
|
|
75
|
+
return [header, ...todos.map((item) => `${MARKS[item.status]} ${item.id}: ${item.content}${item.dependencies === undefined || item.dependencies.length === 0 ? '' : ` (depends on ${item.dependencies.join(', ')})`}`)].join('\n');
|
|
72
76
|
}
|
|
73
77
|
|
|
74
78
|
interface BranchEntry {
|
|
75
79
|
type: string;
|
|
80
|
+
customType?: string;
|
|
81
|
+
data?: unknown;
|
|
76
82
|
message?: { role?: string; toolName?: string; isError?: boolean; details?: unknown };
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
/** The list as of the active branch
|
|
85
|
+
/** The list as of the active branch, from a plan import or the last successful TodoWrite. */
|
|
80
86
|
export function todosFromBranch(entries: readonly BranchEntry[]): TodoItem[] {
|
|
81
87
|
for (let index = entries.length - 1; index >= 0; index -= 1) {
|
|
82
|
-
const
|
|
88
|
+
const entry = entries[index];
|
|
89
|
+
if (entry?.type === 'custom' && entry.customType === TODO_ENTRY) {
|
|
90
|
+
const todos = (entry.data as Partial<TodoDetails> | undefined)?.todos;
|
|
91
|
+
if (Array.isArray(todos)) return todos;
|
|
92
|
+
}
|
|
93
|
+
const message = entry?.message;
|
|
83
94
|
const todos = (message?.details as Partial<TodoDetails> | undefined)?.todos;
|
|
84
95
|
if (entries[index]?.type === 'message' && message?.role === 'toolResult' && message.toolName === 'TodoWrite' && message.isError !== true && Array.isArray(todos)) return todos;
|
|
85
96
|
}
|
|
@@ -116,7 +127,7 @@ const TODO_DESCRIPTION = [
|
|
|
116
127
|
'Prefer creating the first todo as in_progress, and start on it with tool calls in the same batch as the write. Batch todo updates with your other tool calls.',
|
|
117
128
|
].join('\n');
|
|
118
129
|
|
|
119
|
-
export function registerTodoWrite(pi: ExtensionAPI
|
|
130
|
+
export function registerTodoWrite(pi: ExtensionAPI, options: { onWrite?: (todos: readonly TodoItem[]) => void } = {}) {
|
|
120
131
|
let todos: TodoItem[] = [];
|
|
121
132
|
const show = (ctx: ExtensionContext) => {
|
|
122
133
|
if (ctx.hasUI) ctx.ui.setWidget(WIDGET_KEY, widgetLines(todos));
|
|
@@ -139,9 +150,21 @@ export function registerTodoWrite(pi: ExtensionAPI): { section: () => string | u
|
|
|
139
150
|
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
140
151
|
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
141
152
|
todos = applyTodoWrite(todos, params.todos, params.merge);
|
|
153
|
+
try {
|
|
154
|
+
options.onWrite?.(todos);
|
|
155
|
+
} catch (error) {
|
|
156
|
+
ctx.ui.notify(`The todo list was updated, but its plan file could not be synchronized: ${error instanceof Error ? error.message : String(error)}`, 'warning');
|
|
157
|
+
}
|
|
142
158
|
show(ctx);
|
|
143
159
|
return { content: [{ type: 'text', text: formatTodos(todos) }], details: { todos } };
|
|
144
160
|
},
|
|
145
161
|
});
|
|
146
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
section: () => todoSection(todos),
|
|
164
|
+
replace: (items: readonly TodoItem[], ctx: ExtensionContext) => {
|
|
165
|
+
todos = applyTodoWrite([], items, false);
|
|
166
|
+
pi.appendEntry(TODO_ENTRY, { todos });
|
|
167
|
+
show(ctx);
|
|
168
|
+
},
|
|
169
|
+
};
|
|
147
170
|
}
|