@rmrdeveloper/sideroom-pi 5.0.0 → 6.0.1
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/README.md +108 -79
- package/{src/assets → assets}/artifacts/GUIDELINES_TEMPLATE.md +40 -11
- package/extensions/ask/execute.ts +56 -0
- package/extensions/ask/index.ts +77 -0
- package/extensions/ask/model.ts +196 -0
- package/extensions/ask/ui.ts +445 -0
- package/extensions/modified-files/index.ts +45 -0
- package/extensions/modified-files/model.ts +74 -0
- package/extensions/modified-files/session.ts +200 -0
- package/extensions/modified-files/ui.ts +207 -0
- package/extensions/todo/execute.ts +69 -0
- package/extensions/todo/guards.ts +107 -0
- package/extensions/todo/index.ts +91 -0
- package/extensions/todo/model.ts +327 -0
- package/extensions/todo/session.ts +166 -0
- package/extensions/todo/ui.ts +41 -0
- package/package.json +26 -27
- package/dist/pi-extension.js +0 -1175
- package/skills/sideroom-critic/SKILL.md +0 -32
- package/skills/sideroom-grilling/SKILL.md +0 -81
- package/skills/sideroom-spec/SKILL.md +0 -38
- package/skills/sideroom-transcribe-audio/SKILL.md +0 -38
- package/skills/sideroom-transcribe-audio/scripts/transcribe.py +0 -40
- package/src/assets/agents/sideroom-code-reviewer.md +0 -30
- package/src/assets/agents/sideroom-fixer.md +0 -37
- package/src/assets/agents/sideroom-implementer.md +0 -39
- package/src/assets/agents/sideroom-planner.md +0 -31
- package/src/assets/agents/sideroom-verifier.md +0 -32
- package/src/assets/artifacts/guidelines/java.md +0 -211
- package/src/assets/artifacts/guidelines/javascript.md +0 -235
- package/src/assets/artifacts/guidelines/php-laravel.md +0 -210
- package/src/assets/artifacts/guidelines/python.md +0 -227
- package/src/assets/artifacts/guidelines/typescript.md +0 -231
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
import { type Static, Type } from 'typebox';
|
|
2
|
+
import { Check } from 'typebox/value';
|
|
3
|
+
|
|
4
|
+
export const TOOL_NAME = 'sideroom_todo';
|
|
5
|
+
export const UI_UNAVAILABLE =
|
|
6
|
+
'Error: UI not available (running in non-interactive mode)';
|
|
7
|
+
export const TODO_ACTION = {
|
|
8
|
+
propose: 'propose',
|
|
9
|
+
update: 'update',
|
|
10
|
+
} as const;
|
|
11
|
+
export const TODO_STATUS = {
|
|
12
|
+
pending: 'pending',
|
|
13
|
+
inProgress: 'in_progress',
|
|
14
|
+
completed: 'completed',
|
|
15
|
+
cancelled: 'cancelled',
|
|
16
|
+
} as const;
|
|
17
|
+
|
|
18
|
+
const MAX_TODO_ITEMS = 20;
|
|
19
|
+
const MAX_TODO_ID_LENGTH = 32;
|
|
20
|
+
const MAX_TODO_CONTENT_LENGTH = 200;
|
|
21
|
+
const STATUS_COLUMN_WIDTH = 12;
|
|
22
|
+
|
|
23
|
+
const TodoStatusSchema = Type.Union([
|
|
24
|
+
Type.Literal(TODO_STATUS.pending),
|
|
25
|
+
Type.Literal(TODO_STATUS.inProgress),
|
|
26
|
+
Type.Literal(TODO_STATUS.completed),
|
|
27
|
+
Type.Literal(TODO_STATUS.cancelled),
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
const TodoItemSchema = Type.Object({
|
|
31
|
+
id: Type.String({ description: 'Stable identifier for this item' }),
|
|
32
|
+
content: Type.String({ description: 'Short description of the work' }),
|
|
33
|
+
status: TodoStatusSchema,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const TodoPatchSchema = Type.Object({
|
|
37
|
+
id: Type.String({ description: 'Identifier of the item to patch' }),
|
|
38
|
+
status: Type.Optional(TodoStatusSchema),
|
|
39
|
+
content: Type.Optional(
|
|
40
|
+
Type.String({ description: 'Replacement work description' }),
|
|
41
|
+
),
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const ProposeParamsSchema = Type.Object({
|
|
45
|
+
action: Type.Literal(TODO_ACTION.propose),
|
|
46
|
+
items: Type.Array(TodoItemSchema, {
|
|
47
|
+
maxItems: MAX_TODO_ITEMS,
|
|
48
|
+
description: 'Replacement board, with at most 20 items',
|
|
49
|
+
}),
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
const UpdateParamsSchema = Type.Object({
|
|
53
|
+
action: Type.Literal(TODO_ACTION.update),
|
|
54
|
+
patches: Type.Array(TodoPatchSchema, {
|
|
55
|
+
minItems: 1,
|
|
56
|
+
description: 'Patches to apply together',
|
|
57
|
+
}),
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
export const TodoParamsSchema = Type.Union([
|
|
61
|
+
ProposeParamsSchema,
|
|
62
|
+
UpdateParamsSchema,
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
export type TodoParams = Static<typeof TodoParamsSchema>;
|
|
66
|
+
export type TodoAction = (typeof TODO_ACTION)[keyof typeof TODO_ACTION];
|
|
67
|
+
export type TodoStatus = (typeof TODO_STATUS)[keyof typeof TODO_STATUS];
|
|
68
|
+
|
|
69
|
+
export interface TodoItem {
|
|
70
|
+
readonly id: string;
|
|
71
|
+
readonly content: string;
|
|
72
|
+
readonly status: TodoStatus;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface TodoPatch {
|
|
76
|
+
readonly id: string;
|
|
77
|
+
readonly status?: TodoStatus;
|
|
78
|
+
readonly content?: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export type ParsedTodoParams =
|
|
82
|
+
| {
|
|
83
|
+
readonly action: typeof TODO_ACTION.propose;
|
|
84
|
+
readonly items: readonly TodoItem[];
|
|
85
|
+
}
|
|
86
|
+
| {
|
|
87
|
+
readonly action: typeof TODO_ACTION.update;
|
|
88
|
+
readonly patches: readonly TodoPatch[];
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export type ParseTodoResult =
|
|
92
|
+
| { readonly ok: true; readonly params: ParsedTodoParams }
|
|
93
|
+
| { readonly ok: false; readonly message: string };
|
|
94
|
+
|
|
95
|
+
export type ApplyTodoResult =
|
|
96
|
+
| { readonly ok: true; readonly items: readonly TodoItem[] }
|
|
97
|
+
| { readonly ok: false; readonly message: string };
|
|
98
|
+
|
|
99
|
+
export interface TodoGuardState {
|
|
100
|
+
readonly items: readonly TodoItem[];
|
|
101
|
+
readonly turnMutated: boolean;
|
|
102
|
+
readonly turnCalledTodo: boolean;
|
|
103
|
+
readonly proposeNudgedThisRun: boolean;
|
|
104
|
+
readonly updateWatchdogThisTurn: boolean;
|
|
105
|
+
readonly steerFromUs: boolean;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const STATUS_MARKS: Readonly<Record<TodoStatus, string>> = {
|
|
109
|
+
[TODO_STATUS.pending]: '-',
|
|
110
|
+
[TODO_STATUS.inProgress]: '>',
|
|
111
|
+
[TODO_STATUS.completed]: '✓',
|
|
112
|
+
[TODO_STATUS.cancelled]: '~',
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const BOARD_INSTRUCTIONS =
|
|
116
|
+
'Call sideroom_todo update before the next item. While any item is pending, exactly one must be in_progress. Complete the current item and start the next in the same update. propose replaces the list; update patches by id.';
|
|
117
|
+
|
|
118
|
+
export function parseTodoParams(value: unknown): ParseTodoResult {
|
|
119
|
+
if (!Check(TodoParamsSchema, value)) {
|
|
120
|
+
return { ok: false, message: 'Error: Invalid sideroom_todo parameters' };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (value.action === TODO_ACTION.propose) {
|
|
124
|
+
const items = normalizeItems(value.items);
|
|
125
|
+
if (!items.ok) {
|
|
126
|
+
return { ok: false, message: items.message };
|
|
127
|
+
}
|
|
128
|
+
const invariant = validateBoard(items.items);
|
|
129
|
+
if (invariant !== undefined) {
|
|
130
|
+
return { ok: false, message: invariant };
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
params: { action: TODO_ACTION.propose, items: items.items },
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const patches = normalizePatches(value.patches);
|
|
139
|
+
if (!patches.ok) {
|
|
140
|
+
return { ok: false, message: patches.message };
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
ok: true,
|
|
144
|
+
params: { action: TODO_ACTION.update, patches: patches.patches },
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function applyTodoParams(
|
|
149
|
+
items: readonly TodoItem[],
|
|
150
|
+
params: ParsedTodoParams,
|
|
151
|
+
): ApplyTodoResult {
|
|
152
|
+
if (params.action === TODO_ACTION.propose) {
|
|
153
|
+
return { ok: true, items: params.items };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const patchesById = new Map(params.patches.map((patch) => [patch.id, patch]));
|
|
157
|
+
for (const id of patchesById.keys()) {
|
|
158
|
+
if (!items.some((item) => item.id === id)) {
|
|
159
|
+
return { ok: false, message: `Error: Unknown todo id: ${id}` };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const updated = items.map((item) => {
|
|
164
|
+
const patch = patchesById.get(item.id);
|
|
165
|
+
if (patch === undefined) {
|
|
166
|
+
return item;
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
id: item.id,
|
|
170
|
+
content: patch.content ?? item.content,
|
|
171
|
+
status: patch.status ?? item.status,
|
|
172
|
+
};
|
|
173
|
+
});
|
|
174
|
+
const invariant = validateBoard(updated);
|
|
175
|
+
if (invariant !== undefined) {
|
|
176
|
+
return { ok: false, message: invariant };
|
|
177
|
+
}
|
|
178
|
+
return { ok: true, items: updated };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function formatBoardBlock(items: readonly TodoItem[]): string {
|
|
182
|
+
const idWidth = Math.max(1, ...items.map((item) => item.id.length));
|
|
183
|
+
const rows = items.map(
|
|
184
|
+
(item) =>
|
|
185
|
+
`${STATUS_MARKS[item.status]} ${item.id.padEnd(idWidth)} ${item.status.padEnd(STATUS_COLUMN_WIDTH)} ${item.content}`,
|
|
186
|
+
);
|
|
187
|
+
return [
|
|
188
|
+
'sideroom_todo (live board; do not recap in prose)',
|
|
189
|
+
...rows,
|
|
190
|
+
'',
|
|
191
|
+
BOARD_INSTRUCTIONS,
|
|
192
|
+
].join('\n');
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function shouldNudgePropose(state: TodoGuardState): boolean {
|
|
196
|
+
return (
|
|
197
|
+
state.turnMutated &&
|
|
198
|
+
state.items.length === 0 &&
|
|
199
|
+
!state.proposeNudgedThisRun &&
|
|
200
|
+
!state.steerFromUs
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export function shouldWatchdogUpdate(state: TodoGuardState): boolean {
|
|
205
|
+
return (
|
|
206
|
+
state.turnMutated &&
|
|
207
|
+
state.items.length > 0 &&
|
|
208
|
+
!state.turnCalledTodo &&
|
|
209
|
+
!state.updateWatchdogThisTurn &&
|
|
210
|
+
!state.steerFromUs
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function normalizeItems(
|
|
215
|
+
items: readonly Static<typeof TodoItemSchema>[],
|
|
216
|
+
):
|
|
217
|
+
| { readonly ok: true; readonly items: readonly TodoItem[] }
|
|
218
|
+
| { readonly ok: false; readonly message: string } {
|
|
219
|
+
const seen = new Set<string>();
|
|
220
|
+
const normalized: TodoItem[] = [];
|
|
221
|
+
for (const item of items) {
|
|
222
|
+
const id = normalizeId(item.id);
|
|
223
|
+
if (!id.ok) {
|
|
224
|
+
return id;
|
|
225
|
+
}
|
|
226
|
+
if (seen.has(id.value)) {
|
|
227
|
+
return { ok: false, message: `Error: Duplicate todo id: ${id.value}` };
|
|
228
|
+
}
|
|
229
|
+
seen.add(id.value);
|
|
230
|
+
|
|
231
|
+
const content = normalizeContent(item.content);
|
|
232
|
+
if (!content.ok) {
|
|
233
|
+
return content;
|
|
234
|
+
}
|
|
235
|
+
normalized.push({
|
|
236
|
+
id: id.value,
|
|
237
|
+
content: content.value,
|
|
238
|
+
status: item.status,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return { ok: true, items: normalized };
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function normalizePatches(
|
|
245
|
+
patches: readonly Static<typeof TodoPatchSchema>[],
|
|
246
|
+
):
|
|
247
|
+
| { readonly ok: true; readonly patches: readonly TodoPatch[] }
|
|
248
|
+
| { readonly ok: false; readonly message: string } {
|
|
249
|
+
const seen = new Set<string>();
|
|
250
|
+
const normalized: TodoPatch[] = [];
|
|
251
|
+
for (const patch of patches) {
|
|
252
|
+
const id = normalizeId(patch.id);
|
|
253
|
+
if (!id.ok) {
|
|
254
|
+
return id;
|
|
255
|
+
}
|
|
256
|
+
if (seen.has(id.value)) {
|
|
257
|
+
return {
|
|
258
|
+
ok: false,
|
|
259
|
+
message: `Error: Duplicate todo patch id: ${id.value}`,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
seen.add(id.value);
|
|
263
|
+
if (patch.status === undefined && patch.content === undefined) {
|
|
264
|
+
return {
|
|
265
|
+
ok: false,
|
|
266
|
+
message: `Error: Todo patch '${id.value}' must change status or content`,
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
let content: string | undefined;
|
|
271
|
+
if (patch.content !== undefined) {
|
|
272
|
+
const normalizedContent = normalizeContent(patch.content);
|
|
273
|
+
if (!normalizedContent.ok) {
|
|
274
|
+
return normalizedContent;
|
|
275
|
+
}
|
|
276
|
+
content = normalizedContent.value;
|
|
277
|
+
}
|
|
278
|
+
normalized.push({ id: id.value, status: patch.status, content });
|
|
279
|
+
}
|
|
280
|
+
return { ok: true, patches: normalized };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function normalizeId(
|
|
284
|
+
value: string,
|
|
285
|
+
):
|
|
286
|
+
| { readonly ok: true; readonly value: string }
|
|
287
|
+
| { readonly ok: false; readonly message: string } {
|
|
288
|
+
const normalized = value.trim();
|
|
289
|
+
if (normalized.length === 0 || normalized.length > MAX_TODO_ID_LENGTH) {
|
|
290
|
+
return {
|
|
291
|
+
ok: false,
|
|
292
|
+
message: `Error: Todo id must contain 1 to ${String(MAX_TODO_ID_LENGTH)} characters`,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
return { ok: true, value: normalized };
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function normalizeContent(
|
|
299
|
+
value: string,
|
|
300
|
+
):
|
|
301
|
+
| { readonly ok: true; readonly value: string }
|
|
302
|
+
| { readonly ok: false; readonly message: string } {
|
|
303
|
+
const normalized = value.trim();
|
|
304
|
+
if (normalized.length === 0 || normalized.length > MAX_TODO_CONTENT_LENGTH) {
|
|
305
|
+
return {
|
|
306
|
+
ok: false,
|
|
307
|
+
message: `Error: Todo content must contain 1 to ${String(MAX_TODO_CONTENT_LENGTH)} characters`,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return { ok: true, value: normalized };
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function validateBoard(items: readonly TodoItem[]): string | undefined {
|
|
314
|
+
const pendingCount = items.filter(
|
|
315
|
+
(item) => item.status === TODO_STATUS.pending,
|
|
316
|
+
).length;
|
|
317
|
+
const inProgressCount = items.filter(
|
|
318
|
+
(item) => item.status === TODO_STATUS.inProgress,
|
|
319
|
+
).length;
|
|
320
|
+
if (inProgressCount > 1) {
|
|
321
|
+
return 'Error: At most one todo item may be in_progress';
|
|
322
|
+
}
|
|
323
|
+
if (pendingCount > 0 && inProgressCount !== 1) {
|
|
324
|
+
return 'Error: A board with pending items must have exactly one in_progress item';
|
|
325
|
+
}
|
|
326
|
+
return undefined;
|
|
327
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
} from '@earendil-works/pi-coding-agent';
|
|
5
|
+
import { Text } from '@earendil-works/pi-tui';
|
|
6
|
+
import {
|
|
7
|
+
formatBoardBlock,
|
|
8
|
+
parseTodoParams,
|
|
9
|
+
TODO_ACTION,
|
|
10
|
+
TOOL_NAME,
|
|
11
|
+
type TodoItem,
|
|
12
|
+
} from './model.ts';
|
|
13
|
+
import { renderTodoWidget } from './ui.ts';
|
|
14
|
+
|
|
15
|
+
export const SNAPSHOT_TYPE = 'sideroom-todo';
|
|
16
|
+
export const WIDGET_KEY = 'sideroom-todo';
|
|
17
|
+
export const TODO_WIDGET_REFRESH_EVENT = 'sideroom:todo-widget-refreshed';
|
|
18
|
+
|
|
19
|
+
export interface TodoStore {
|
|
20
|
+
items: readonly TodoItem[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type BoardLifecycle = (ctx: ExtensionContext) => void;
|
|
24
|
+
|
|
25
|
+
type SessionBranch = ReturnType<
|
|
26
|
+
ExtensionContext['sessionManager']['getBranch']
|
|
27
|
+
>;
|
|
28
|
+
|
|
29
|
+
export function createTodoStore(items: readonly TodoItem[] = []): TodoStore {
|
|
30
|
+
return { items };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function reconstructTodoItems(
|
|
34
|
+
branch: SessionBranch,
|
|
35
|
+
): readonly TodoItem[] {
|
|
36
|
+
const snapshotItems = findSnapshotItems(branch);
|
|
37
|
+
if (snapshotItems !== undefined) {
|
|
38
|
+
return snapshotItems;
|
|
39
|
+
}
|
|
40
|
+
return findToolResultItems(branch) ?? [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function restoreBoard(
|
|
44
|
+
store: TodoStore,
|
|
45
|
+
ctx: Pick<ExtensionContext, 'sessionManager'>,
|
|
46
|
+
): void {
|
|
47
|
+
store.items = reconstructTodoItems(ctx.sessionManager.getBranch());
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function persistSnapshot(
|
|
51
|
+
pi: Pick<ExtensionAPI, 'appendEntry'>,
|
|
52
|
+
items: readonly TodoItem[],
|
|
53
|
+
): void {
|
|
54
|
+
pi.appendEntry(SNAPSHOT_TYPE, { items });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function refreshBoard(
|
|
58
|
+
store: TodoStore,
|
|
59
|
+
ctx: Pick<ExtensionContext, 'ui'>,
|
|
60
|
+
): void {
|
|
61
|
+
if (store.items.length === 0) {
|
|
62
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
ctx.ui.setWidget(WIDGET_KEY, (_tui, theme) => {
|
|
66
|
+
const lines = renderTodoWidget(store.items, theme);
|
|
67
|
+
return new Text(lines?.join('\n') ?? '', 0, 0);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function restoreAndRefreshBoard(
|
|
72
|
+
store: TodoStore,
|
|
73
|
+
ctx: ExtensionContext,
|
|
74
|
+
): void {
|
|
75
|
+
restoreBoard(store, ctx);
|
|
76
|
+
refreshBoard(store, ctx);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function commitBoard(
|
|
80
|
+
store: TodoStore,
|
|
81
|
+
pi: Pick<ExtensionAPI, 'appendEntry'>,
|
|
82
|
+
ctx: Pick<ExtensionContext, 'ui'>,
|
|
83
|
+
items: readonly TodoItem[],
|
|
84
|
+
): void {
|
|
85
|
+
store.items = items;
|
|
86
|
+
persistSnapshot(pi, items);
|
|
87
|
+
refreshBoard(store, ctx);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function registerSessionRefreshEvents(
|
|
91
|
+
pi: ExtensionAPI,
|
|
92
|
+
restoreAndRefresh: BoardLifecycle,
|
|
93
|
+
): void {
|
|
94
|
+
pi.on('session_start', (_event, ctx) => restoreAndRefresh(ctx));
|
|
95
|
+
pi.on('session_tree', (_event, ctx) => restoreAndRefresh(ctx));
|
|
96
|
+
pi.on('session_compact', (_event, ctx) => restoreAndRefresh(ctx));
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function registerBoardContext(
|
|
100
|
+
pi: ExtensionAPI,
|
|
101
|
+
store: TodoStore,
|
|
102
|
+
restoreAndRefresh: BoardLifecycle,
|
|
103
|
+
): void {
|
|
104
|
+
pi.on('before_agent_start', (event, ctx) => {
|
|
105
|
+
restoreAndRefresh(ctx);
|
|
106
|
+
if (store.items.length === 0) {
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
systemPrompt: `${event.systemPrompt}\n\n${formatBoardBlock(store.items)}`,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function findSnapshotItems(
|
|
116
|
+
branch: SessionBranch,
|
|
117
|
+
): readonly TodoItem[] | undefined {
|
|
118
|
+
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
119
|
+
const entry = branch[index];
|
|
120
|
+
if (entry?.type === 'custom' && entry.customType === SNAPSHOT_TYPE) {
|
|
121
|
+
const items = readSnapshot(entry.data);
|
|
122
|
+
if (items !== undefined) {
|
|
123
|
+
return items;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function findToolResultItems(
|
|
131
|
+
branch: SessionBranch,
|
|
132
|
+
): readonly TodoItem[] | undefined {
|
|
133
|
+
for (let index = branch.length - 1; index >= 0; index -= 1) {
|
|
134
|
+
const entry = branch[index];
|
|
135
|
+
if (entry?.type !== 'message') {
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const message = entry.message;
|
|
139
|
+
if (message.role !== 'toolResult' || message.toolName !== TOOL_NAME) {
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
const items = readSnapshot(message.details);
|
|
143
|
+
if (items !== undefined) {
|
|
144
|
+
return items;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return undefined;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function readSnapshot(value: unknown): readonly TodoItem[] | undefined {
|
|
151
|
+
if (!isRecord(value) || !Array.isArray(value.items)) {
|
|
152
|
+
return undefined;
|
|
153
|
+
}
|
|
154
|
+
const parsed = parseTodoParams({
|
|
155
|
+
action: TODO_ACTION.propose,
|
|
156
|
+
items: value.items,
|
|
157
|
+
});
|
|
158
|
+
if (!parsed.ok || parsed.params.action !== TODO_ACTION.propose) {
|
|
159
|
+
return undefined;
|
|
160
|
+
}
|
|
161
|
+
return parsed.params.items;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
165
|
+
return typeof value === 'object' && value !== null;
|
|
166
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Theme } from '@earendil-works/pi-coding-agent';
|
|
2
|
+
import { formatBoardBlock, TODO_STATUS, type TodoItem } from './model.ts';
|
|
3
|
+
|
|
4
|
+
export function renderTodoWidget(
|
|
5
|
+
items: readonly TodoItem[],
|
|
6
|
+
theme: Theme,
|
|
7
|
+
): string[] | undefined {
|
|
8
|
+
if (items.length === 0) {
|
|
9
|
+
return undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const active = items.filter(
|
|
13
|
+
(item) => item.status === TODO_STATUS.inProgress,
|
|
14
|
+
).length;
|
|
15
|
+
const queued = items.filter(
|
|
16
|
+
(item) => item.status === TODO_STATUS.pending,
|
|
17
|
+
).length;
|
|
18
|
+
const blockLines = formatBoardBlock(items).split('\n');
|
|
19
|
+
const rows = blockLines.slice(1, 1 + items.length);
|
|
20
|
+
const header = theme.fg(
|
|
21
|
+
'accent',
|
|
22
|
+
theme.bold(
|
|
23
|
+
`Sideroom board (${String(active)} active, ${String(queued)} queued)`,
|
|
24
|
+
),
|
|
25
|
+
);
|
|
26
|
+
const renderedRows = rows.map((row, index) => {
|
|
27
|
+
const item = items[index];
|
|
28
|
+
if (item === undefined) {
|
|
29
|
+
return row;
|
|
30
|
+
}
|
|
31
|
+
if (item.status === TODO_STATUS.inProgress) {
|
|
32
|
+
return theme.fg('success', row);
|
|
33
|
+
}
|
|
34
|
+
if (item.status === TODO_STATUS.pending) {
|
|
35
|
+
return theme.fg('text', row);
|
|
36
|
+
}
|
|
37
|
+
return theme.fg('muted', row);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return [header, ...renderedRows];
|
|
41
|
+
}
|
package/package.json
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rmrdeveloper/sideroom-pi",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "A global Pi package
|
|
3
|
+
"version": "6.0.1",
|
|
4
|
+
"description": "A global Pi package with a questionnaire, work board, and edited-file view for parent agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
7
|
+
"extensions",
|
|
8
|
+
"assets",
|
|
9
|
+
"!extensions/**/*.test.ts"
|
|
10
10
|
],
|
|
11
11
|
"scripts": {
|
|
12
|
-
"
|
|
13
|
-
"prepublishOnly": "npm run check && npm run build",
|
|
12
|
+
"prepublishOnly": "npm run check",
|
|
14
13
|
"changeset": "changeset",
|
|
15
14
|
"changeset:status": "changeset status",
|
|
16
15
|
"version-packages": "changeset version",
|
|
17
|
-
"release": "npm run check && changeset publish",
|
|
18
16
|
"commitlint": "commitlint",
|
|
19
17
|
"types": "tsc --noEmit",
|
|
20
|
-
"test": "node --test
|
|
21
|
-
"lint": "biome lint
|
|
22
|
-
"lint:fix": "biome lint --write
|
|
23
|
-
"format": "biome format --write
|
|
24
|
-
"format:check": "biome format
|
|
25
|
-
"check": "
|
|
18
|
+
"test": "node --test 'extensions/**/*.test.ts'",
|
|
19
|
+
"lint": "biome lint extensions scripts commitlint.config.mjs",
|
|
20
|
+
"lint:fix": "biome lint --write extensions scripts commitlint.config.mjs",
|
|
21
|
+
"format": "biome format --write extensions scripts package.json biome.json commitlint.config.mjs .changeset/config.json",
|
|
22
|
+
"format:check": "biome format extensions scripts package.json biome.json commitlint.config.mjs .changeset/config.json",
|
|
23
|
+
"check:if-blocks": "node scripts/check-if-blocks.mjs",
|
|
24
|
+
"check": "npm run types && npm run check:if-blocks && biome check extensions scripts package.json biome.json commitlint.config.mjs .changeset/config.json && npm test"
|
|
26
25
|
},
|
|
27
26
|
"engines": {
|
|
28
27
|
"node": ">=22.19.0"
|
|
29
28
|
},
|
|
30
29
|
"keywords": [
|
|
31
|
-
"agents",
|
|
32
|
-
"ai",
|
|
33
|
-
"coding-agents",
|
|
34
|
-
"multi-agent",
|
|
35
|
-
"orchestration",
|
|
36
30
|
"pi",
|
|
37
31
|
"pi-package",
|
|
38
|
-
"pi-
|
|
32
|
+
"pi-extension",
|
|
33
|
+
"questionnaire",
|
|
34
|
+
"todo",
|
|
35
|
+
"edited-files",
|
|
36
|
+
"sideroom_ask",
|
|
37
|
+
"sideroom_todo",
|
|
39
38
|
"typescript"
|
|
40
39
|
],
|
|
41
40
|
"license": "MIT",
|
|
@@ -48,23 +47,23 @@
|
|
|
48
47
|
},
|
|
49
48
|
"pi": {
|
|
50
49
|
"extensions": [
|
|
51
|
-
"./
|
|
52
|
-
],
|
|
53
|
-
"skills": [
|
|
54
|
-
"./skills"
|
|
50
|
+
"./extensions"
|
|
55
51
|
]
|
|
56
52
|
},
|
|
57
53
|
"peerDependencies": {
|
|
58
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
54
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
55
|
+
"@earendil-works/pi-tui": "*",
|
|
56
|
+
"typebox": "*"
|
|
59
57
|
},
|
|
60
58
|
"devDependencies": {
|
|
61
|
-
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
62
59
|
"@biomejs/biome": "2.5.11",
|
|
63
60
|
"@changesets/cli": "^3.0.2",
|
|
64
61
|
"@commitlint/cli": "^21.2.2",
|
|
65
62
|
"@commitlint/config-conventional": "^21.2.2",
|
|
63
|
+
"@earendil-works/pi-coding-agent": "0.84.4",
|
|
64
|
+
"@earendil-works/pi-tui": "0.84.4",
|
|
66
65
|
"@types/node": "^22.20.1",
|
|
67
|
-
"
|
|
66
|
+
"typebox": "1.3.7",
|
|
68
67
|
"typescript": "^5.6.0"
|
|
69
68
|
}
|
|
70
69
|
}
|