@astralyn/pi 0.87.2 → 0.87.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.
- package/CHANGELOG.md +19 -0
- package/dist/bundle/chunks/{chunk-NBIUJDV6.js → chunk-ANT4TSUE.js} +1 -1
- package/dist/bundle/chunks/{chunk-RFLBIA5Z.js → chunk-CUDKRZ2V.js} +37 -24
- package/dist/bundle/chunks/{virtual-modules-GOHLE5JL.js → virtual-modules-Y2YZRMU3.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/core/background/service.d.ts.map +1 -1
- package/dist/core/background/service.js +4 -3
- package/dist/core/background/service.js.map +1 -1
- package/dist/core/background/types.d.ts +11 -0
- package/dist/core/background/types.d.ts.map +1 -1
- package/dist/core/background/types.js +14 -0
- package/dist/core/background/types.js.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.d.ts.map +1 -1
- package/dist/core/tools/tool-definition-wrapper.js +121 -1
- package/dist/core/tools/tool-definition-wrapper.js.map +1 -1
- package/dist/extensions/background/actions.d.ts.map +1 -1
- package/dist/extensions/background/actions.js +126 -67
- package/dist/extensions/background/actions.js.map +1 -1
- package/dist/extensions/background/index.js +3 -3
- package/dist/extensions/background/index.js.map +1 -1
- package/dist/extensions/question/index.d.ts.map +1 -1
- package/dist/extensions/question/index.js +3 -1
- package/dist/extensions/question/index.js.map +1 -1
- package/dist/extensions/question/schema.d.ts +18 -0
- package/dist/extensions/question/schema.d.ts.map +1 -1
- package/dist/extensions/question/schema.js +52 -0
- package/dist/extensions/question/schema.js.map +1 -1
- package/dist/extensions/subagent/index.d.ts.map +1 -1
- package/dist/extensions/subagent/index.js +3 -1
- package/dist/extensions/subagent/index.js.map +1 -1
- package/dist/extensions/subagent/resolve.d.ts +14 -1
- package/dist/extensions/subagent/resolve.d.ts.map +1 -1
- package/dist/extensions/subagent/resolve.js +32 -18
- package/dist/extensions/subagent/resolve.js.map +1 -1
- package/dist/extensions/subagent/runner.d.ts.map +1 -1
- package/dist/extensions/subagent/runner.js +4 -1
- package/dist/extensions/subagent/runner.js.map +1 -1
- package/dist/extensions/subagent/schema.d.ts +9 -0
- package/dist/extensions/subagent/schema.d.ts.map +1 -1
- package/dist/extensions/subagent/schema.js +30 -1
- package/dist/extensions/subagent/schema.js.map +1 -1
- package/dist/extensions/subagent/types.d.ts +2 -0
- package/dist/extensions/subagent/types.d.ts.map +1 -1
- package/dist/extensions/subagent/types.js.map +1 -1
- package/dist/extensions/todo/constants.d.ts +1 -1
- package/dist/extensions/todo/constants.d.ts.map +1 -1
- package/dist/extensions/todo/constants.js +8 -8
- package/dist/extensions/todo/constants.js.map +1 -1
- package/dist/extensions/todo/index.d.ts.map +1 -1
- package/dist/extensions/todo/index.js +2 -1
- package/dist/extensions/todo/index.js.map +1 -1
- package/dist/extensions/todo/schema.d.ts +30 -25
- package/dist/extensions/todo/schema.d.ts.map +1 -1
- package/dist/extensions/todo/schema.js +26 -20
- package/dist/extensions/todo/schema.js.map +1 -1
- package/dist/extensions/todo/state.d.ts +12 -8
- package/dist/extensions/todo/state.d.ts.map +1 -1
- package/dist/extensions/todo/state.js +257 -171
- package/dist/extensions/todo/state.js.map +1 -1
- package/dist/extensions/todo/view.d.ts +6 -2
- package/dist/extensions/todo/view.d.ts.map +1 -1
- package/dist/extensions/todo/view.js +234 -144
- package/dist/extensions/todo/view.js.map +1 -1
- package/docs/bundled/extensions/background.md +1 -1
- package/docs/bundled/extensions/question.md +1 -0
- package/docs/bundled/extensions/subagent.md +3 -1
- package/docs/bundled/extensions/todo.md +35 -23
- package/docs/extensions.md +2 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* todo/view.ts —
|
|
2
|
+
* todo/view.ts — v3 presentation: the one-line above-editor widget renderer,
|
|
3
3
|
* tool call/group renderers, the /todos list formatter, and model-facing
|
|
4
4
|
* result text. Wire input (args, details, snapshots) is read defensively and
|
|
5
5
|
* bounded so partial or hostile input cannot grow output without limit.
|
|
@@ -144,39 +144,58 @@ function formatTodoList(state) {
|
|
|
144
144
|
}
|
|
145
145
|
/** /todos command output: full list, subject line plus indented description per task. */
|
|
146
146
|
export const formatCommandList = formatTodoList;
|
|
147
|
-
|
|
147
|
+
// --- Model-facing result text ------------------------------------------------
|
|
148
|
+
/** `#1–#3` for a consecutive run, `#1, #2` otherwise. */
|
|
149
|
+
function formatIdRange(ids) {
|
|
150
|
+
const consecutive = ids.length > 1 && ids.every((id, index) => id === ids[0] + index);
|
|
151
|
+
return consecutive ? `#${ids[0]}–#${ids[ids.length - 1]}` : ids.map((id) => `#${id}`).join(", ");
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* One-line change summary for a completed call, e.g.
|
|
155
|
+
* `Created 1 task: #7: Wire parser (in_progress); Updated #1 (pending -> completed): S; demoted #5 to pending; auto-removed completed #1–#3 to stay within 20`.
|
|
156
|
+
* A call that changed nothing (including `{}`) returns the full list.
|
|
157
|
+
*/
|
|
148
158
|
export function formatTodoContent(change, state) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const
|
|
156
|
-
return
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
159
|
+
const parts = [];
|
|
160
|
+
if (change.created.length > 0) {
|
|
161
|
+
const labels = change.created.map((id) => {
|
|
162
|
+
const item = state.items.find((entry) => entry.id === id);
|
|
163
|
+
if (!item)
|
|
164
|
+
return `#${id}`;
|
|
165
|
+
const suffix = item.status === "pending" ? "" : ` (${item.status})`;
|
|
166
|
+
return `#${id}: ${item.subject}${suffix}`;
|
|
167
|
+
});
|
|
168
|
+
const noun = change.created.length === 1 ? "task" : "tasks";
|
|
169
|
+
parts.push(`Created ${change.created.length} ${noun}: ${labels.join("; ")}`);
|
|
170
|
+
}
|
|
171
|
+
for (const entry of change.updated) {
|
|
172
|
+
const item = state.items.find((candidate) => candidate.id === entry.id);
|
|
173
|
+
const subject = item ? `: ${item.subject}` : "";
|
|
174
|
+
const transition = entry.from === entry.to ? "" : ` (${entry.from} -> ${entry.to})`;
|
|
175
|
+
parts.push(`Updated #${entry.id}${transition}${subject}`);
|
|
176
|
+
}
|
|
177
|
+
if (change.demotedId !== undefined)
|
|
178
|
+
parts.push(`demoted #${change.demotedId} to pending`);
|
|
179
|
+
if (change.deleted.length > 0) {
|
|
180
|
+
const noun = change.deleted.length === 1 ? "task" : "tasks";
|
|
181
|
+
const labels = change.deleted.map((entry) => `#${entry.id}: ${entry.subject}`);
|
|
182
|
+
parts.push(`Deleted ${change.deleted.length} ${noun}: ${labels.join("; ")}`);
|
|
183
|
+
}
|
|
184
|
+
for (const id of change.absent)
|
|
185
|
+
parts.push(`#${id} already absent`);
|
|
186
|
+
if (change.evicted.length > 0) {
|
|
187
|
+
const ids = change.evicted.map((entry) => entry.id);
|
|
188
|
+
parts.push(`auto-removed completed ${formatIdRange(ids)} to stay within ${TODO_MAX_ITEMS}`);
|
|
172
189
|
}
|
|
190
|
+
if (parts.length === 0)
|
|
191
|
+
return formatTodoList(state);
|
|
192
|
+
return parts.join("; ");
|
|
173
193
|
}
|
|
174
194
|
const CALL_ITEMS_MAX = TODO_MAX_BATCH_ITEMS;
|
|
175
195
|
const CALL_SUBJECT_PREVIEW_COUNT = 2;
|
|
176
196
|
const CALL_SUBJECT_PREVIEW_WIDTH = 72;
|
|
177
197
|
const CALL_DESCRIPTION_PREVIEW_LENGTH = 120;
|
|
178
198
|
const GROUP_FAILURE_MAX_LENGTH = 120;
|
|
179
|
-
const TODO_ACTIONS = new Set(["create", "update", "list", "delete"]);
|
|
180
199
|
/** Record-like read; proxies that throw on inspection are treated as absent. */
|
|
181
200
|
function safeRecord(value) {
|
|
182
201
|
try {
|
|
@@ -235,36 +254,55 @@ function readArray(value, limit = CALL_ITEMS_MAX) {
|
|
|
235
254
|
return undefined;
|
|
236
255
|
}
|
|
237
256
|
}
|
|
238
|
-
/** Id lists:
|
|
239
|
-
function readIds(value
|
|
257
|
+
/** Id lists: invalid entries drop (rendering never rejects the whole call). */
|
|
258
|
+
function readIds(value) {
|
|
240
259
|
const array = readArray(value);
|
|
241
|
-
if (strict && (!array || array.length === 0 || array.length > CALL_ITEMS_MAX))
|
|
242
|
-
return undefined;
|
|
243
260
|
if (!array)
|
|
244
|
-
return
|
|
261
|
+
return undefined;
|
|
245
262
|
const ids = [];
|
|
246
263
|
for (const entry of array.values) {
|
|
247
264
|
const id = safeId(entry);
|
|
248
|
-
if (id
|
|
249
|
-
|
|
250
|
-
return undefined;
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
253
|
-
ids.push(id);
|
|
265
|
+
if (id !== undefined)
|
|
266
|
+
ids.push(id);
|
|
254
267
|
}
|
|
255
268
|
return ids;
|
|
256
269
|
}
|
|
257
270
|
/** Create args: sparse or invalid entries drop; the batch caps at the maximum. */
|
|
258
|
-
function
|
|
271
|
+
function batchCreateItems(value) {
|
|
259
272
|
const result = [];
|
|
260
273
|
for (const raw of readArray(value)?.values ?? []) {
|
|
261
274
|
const item = safeRecord(raw);
|
|
262
275
|
if (!item)
|
|
263
276
|
continue;
|
|
264
|
-
|
|
277
|
+
const entry = {
|
|
265
278
|
subject: safeSubject(safeValue(item, "subject")),
|
|
266
279
|
description: safeDescription(safeValue(item, "description")),
|
|
267
|
-
}
|
|
280
|
+
};
|
|
281
|
+
const status = safeStatus(safeValue(item, "status"));
|
|
282
|
+
if (status)
|
|
283
|
+
entry.status = status;
|
|
284
|
+
result.push(entry);
|
|
285
|
+
}
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
/** Update args: sparse or invalid entries drop; the batch caps at the maximum. */
|
|
289
|
+
function batchUpdateItems(value) {
|
|
290
|
+
const result = [];
|
|
291
|
+
for (const raw of readArray(value)?.values ?? []) {
|
|
292
|
+
const item = safeRecord(raw);
|
|
293
|
+
if (!item)
|
|
294
|
+
continue;
|
|
295
|
+
const entry = {
|
|
296
|
+
subject: safeSubject(safeValue(item, "subject")),
|
|
297
|
+
description: safeDescription(safeValue(item, "description")),
|
|
298
|
+
};
|
|
299
|
+
const id = safeId(safeValue(item, "id"));
|
|
300
|
+
if (id !== undefined)
|
|
301
|
+
entry.id = id;
|
|
302
|
+
const status = safeStatus(safeValue(item, "status"));
|
|
303
|
+
if (status)
|
|
304
|
+
entry.status = status;
|
|
305
|
+
result.push(entry);
|
|
268
306
|
}
|
|
269
307
|
return result;
|
|
270
308
|
}
|
|
@@ -287,7 +325,7 @@ function formatSubjectPreview(subjects, total) {
|
|
|
287
325
|
const hidden = Math.max(0, total - shown.length);
|
|
288
326
|
return `${shown.join(", ")}${hidden ? `, +${hidden} more` : ""}`;
|
|
289
327
|
}
|
|
290
|
-
/**
|
|
328
|
+
/** v3 details guard shared by the call and group renderers. */
|
|
291
329
|
function todoDetails(value) {
|
|
292
330
|
const details = safeRecord(value);
|
|
293
331
|
if (!details || safeValue(details, "schemaVersion") !== TODO_DETAILS_SCHEMA_VERSION)
|
|
@@ -296,83 +334,118 @@ function todoDetails(value) {
|
|
|
296
334
|
const state = safeRecord(safeValue(details, "state"));
|
|
297
335
|
if (!change || !state)
|
|
298
336
|
return undefined;
|
|
299
|
-
const kind = safeString(safeValue(change, "kind"));
|
|
300
|
-
if (!kind || !TODO_ACTIONS.has(kind))
|
|
301
|
-
return undefined;
|
|
302
337
|
return { change, state };
|
|
303
338
|
}
|
|
304
339
|
/** Result ids for an expanded create, only when they match the shown items. */
|
|
305
340
|
function createResultIds(result, count) {
|
|
306
341
|
const details = todoDetails(result?.details);
|
|
307
|
-
if (!details
|
|
342
|
+
if (!details)
|
|
308
343
|
return undefined;
|
|
309
|
-
const ids = readIds(safeValue(details.change, "
|
|
310
|
-
return ids
|
|
344
|
+
const ids = readIds(safeValue(details.change, "created"));
|
|
345
|
+
return ids && ids.length === count && count > 0 ? ids : undefined;
|
|
311
346
|
}
|
|
312
|
-
/** Removed entries
|
|
347
|
+
/** Removed entries from a settled result, for the expanded delete detail lines. */
|
|
313
348
|
function deleteResultRemoved(result) {
|
|
314
349
|
const details = todoDetails(result?.details);
|
|
315
|
-
if (!details
|
|
316
|
-
return
|
|
317
|
-
return
|
|
350
|
+
if (!details)
|
|
351
|
+
return [];
|
|
352
|
+
return idSubjectEntries(safeValue(details.change, "deleted"));
|
|
353
|
+
}
|
|
354
|
+
/** Absent ids from a settled delete, for the expanded delete detail lines. */
|
|
355
|
+
function deleteResultAbsent(result) {
|
|
356
|
+
const details = todoDetails(result?.details);
|
|
357
|
+
if (!details)
|
|
358
|
+
return [];
|
|
359
|
+
return readIds(safeValue(details.change, "absent")) ?? [];
|
|
318
360
|
}
|
|
319
361
|
/** Headline plus the parameters the result never echoes as detail lines. */
|
|
320
362
|
function formatCallParts(args, theme, result) {
|
|
321
|
-
const action = clipText(args?.action, TODO_MAX_SUBJECT_LENGTH);
|
|
322
|
-
const verb = TODO_ACTIONS.has(action) ? action : undefined;
|
|
323
|
-
const headline = [theme.fg("toolTitle", theme.bold(verb ? `todo ${verb}` : "todo"))];
|
|
324
363
|
const details = [];
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
364
|
+
const segments = [];
|
|
365
|
+
const pushSegment = (verb, body) => {
|
|
366
|
+
const title = segments.length === 0 ? `todo ${verb}` : verb;
|
|
367
|
+
segments.push(body ? `${theme.fg("toolTitle", theme.bold(title))} ${body}` : theme.fg("toolTitle", theme.bold(title)));
|
|
368
|
+
};
|
|
369
|
+
const hasCreate = args?.create !== undefined;
|
|
370
|
+
const hasUpdate = args?.update !== undefined;
|
|
371
|
+
const hasDelete = args?.delete !== undefined;
|
|
372
|
+
if (hasCreate) {
|
|
373
|
+
const items = batchCreateItems(args?.create);
|
|
374
|
+
const rawCount = readArray(args?.create)?.length;
|
|
375
|
+
// An empty array is strict-mode filler: the call lists the tasks, so the
|
|
376
|
+
// group earns no segment of its own.
|
|
377
|
+
if (rawCount !== 0) {
|
|
378
|
+
const body = [];
|
|
379
|
+
if (rawCount !== undefined) {
|
|
380
|
+
const count = Math.min(rawCount, CALL_ITEMS_MAX);
|
|
381
|
+
body.push(theme.fg("dim", `${count} ${count === 1 ? "task" : "tasks"}`));
|
|
382
|
+
const preview = formatSubjectPreview(items.map((item) => item.subject), count);
|
|
383
|
+
if (preview)
|
|
384
|
+
body.push(theme.fg("dim", "·"), theme.fg("text", preview));
|
|
385
|
+
}
|
|
386
|
+
pushSegment("create", body.join(" "));
|
|
335
387
|
}
|
|
336
388
|
const ids = createResultIds(result, items.length);
|
|
337
389
|
for (const [index, item] of items.entries()) {
|
|
338
390
|
const marker = ids ? theme.fg("accent", `#${ids[index]}`) : theme.fg("accent", `${index + 1}.`);
|
|
339
|
-
|
|
391
|
+
const status = item.status && item.status !== "pending"
|
|
392
|
+
? ` ${theme.fg(STATUS_COLOR[item.status], `(${item.status})`)}`
|
|
393
|
+
: "";
|
|
394
|
+
details.push(item.subject ? `${marker} ${theme.fg("text", item.subject)}${status}` : marker);
|
|
340
395
|
if (item.description)
|
|
341
396
|
details.push(` ${theme.fg("dim", descriptionPreview(item.description))}`);
|
|
342
397
|
}
|
|
343
398
|
}
|
|
344
|
-
|
|
345
|
-
const
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
399
|
+
if (hasUpdate) {
|
|
400
|
+
const items = batchUpdateItems(args?.update);
|
|
401
|
+
const rawCount = readArray(args?.update)?.length;
|
|
402
|
+
if (rawCount !== 0) {
|
|
403
|
+
const entries = [];
|
|
404
|
+
for (const item of items) {
|
|
405
|
+
const parts = [];
|
|
406
|
+
if (item.id !== undefined)
|
|
407
|
+
parts.push(theme.fg("accent", `#${item.id}`));
|
|
408
|
+
if (item.status)
|
|
409
|
+
parts.push(theme.fg(STATUS_COLOR[item.status], item.status));
|
|
410
|
+
if (item.subject)
|
|
411
|
+
parts.push(theme.fg("text", truncateToWidth(item.subject, CALL_SUBJECT_PREVIEW_WIDTH, "…")));
|
|
412
|
+
if (parts.length > 0)
|
|
413
|
+
entries.push(parts.join(" "));
|
|
414
|
+
}
|
|
415
|
+
pushSegment("update", entries.join(theme.fg("dim", ", ")));
|
|
416
|
+
}
|
|
417
|
+
for (const item of items) {
|
|
418
|
+
if (item.description)
|
|
419
|
+
details.push(` ${theme.fg("dim", descriptionPreview(item.description))}`);
|
|
420
|
+
}
|
|
357
421
|
}
|
|
358
|
-
|
|
359
|
-
const
|
|
360
|
-
if (
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
422
|
+
if (hasDelete) {
|
|
423
|
+
const rawCount = readArray(args?.delete)?.length;
|
|
424
|
+
if (rawCount !== 0) {
|
|
425
|
+
const ids = readIds(args?.delete) ?? [];
|
|
426
|
+
pushSegment("delete", ids.length > 0 ? theme.fg("accent", ids.map((id) => `#${id}`).join(", ")) : "");
|
|
427
|
+
}
|
|
428
|
+
// The headline already carries the ids, so details only earn their lines
|
|
429
|
+
// once the result names what was actually removed or already absent.
|
|
430
|
+
for (const entry of deleteResultRemoved(result)) {
|
|
365
431
|
details.push(`${theme.fg("accent", `#${entry.id}`)} ${theme.fg("text", entry.subject)}`);
|
|
366
432
|
}
|
|
433
|
+
for (const id of deleteResultAbsent(result)) {
|
|
434
|
+
details.push(theme.fg("dim", `#${id} already absent`));
|
|
435
|
+
}
|
|
367
436
|
}
|
|
368
|
-
|
|
437
|
+
if (segments.length === 0) {
|
|
438
|
+
// No group fields at all: the call lists the tasks.
|
|
439
|
+
segments.push(theme.fg("toolTitle", theme.bold("todo list")));
|
|
440
|
+
}
|
|
441
|
+
return { headline: segments.join(theme.fg("dim", " ; ")), details };
|
|
369
442
|
}
|
|
370
443
|
/** One-line call summary when collapsed; headline plus details when expanded. */
|
|
371
444
|
export function formatTodoCall(args, theme, expanded, result) {
|
|
372
445
|
const { headline, details } = formatCallParts(args, theme, result);
|
|
373
446
|
return !expanded || details.length === 0 ? headline : [headline, ...details].join("\n");
|
|
374
447
|
}
|
|
375
|
-
/** Id -> item index plus status counts over the
|
|
448
|
+
/** Id -> item index plus status counts over the v3 snapshot (one defensive walk). */
|
|
376
449
|
function stateIndex(state) {
|
|
377
450
|
const array = readArray(safeValue(state, "items"), TODO_MAX_ITEMS);
|
|
378
451
|
if (!array || array.length > TODO_MAX_ITEMS)
|
|
@@ -400,81 +473,98 @@ function stateSubject(map, id) {
|
|
|
400
473
|
const item = map.get(id);
|
|
401
474
|
return item ? safeSubject(safeValue(item, "subject")) : "";
|
|
402
475
|
}
|
|
403
|
-
/**
|
|
404
|
-
function
|
|
405
|
-
const
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
const id = safeId(safeValue(safeRecord(raw), "id"));
|
|
411
|
-
const subject = safeSubject(safeValue(safeRecord(raw), "subject"));
|
|
476
|
+
/** Id+subject entries (deleted, evicted): invalid entries drop. */
|
|
477
|
+
function idSubjectEntries(value) {
|
|
478
|
+
const entries = [];
|
|
479
|
+
for (const raw of readArray(value)?.values ?? []) {
|
|
480
|
+
const item = safeRecord(raw);
|
|
481
|
+
const id = safeId(safeValue(item, "id"));
|
|
482
|
+
const subject = safeSubject(safeValue(item, "subject"));
|
|
412
483
|
if (id === undefined || !subject)
|
|
413
|
-
|
|
414
|
-
|
|
484
|
+
continue;
|
|
485
|
+
entries.push({ id, subject });
|
|
486
|
+
}
|
|
487
|
+
return entries;
|
|
488
|
+
}
|
|
489
|
+
/** Updated entries: {id, from, to} records, invalid entries drop. */
|
|
490
|
+
function updatedEntries(value) {
|
|
491
|
+
const entries = [];
|
|
492
|
+
for (const raw of readArray(value)?.values ?? []) {
|
|
493
|
+
const item = safeRecord(raw);
|
|
494
|
+
const id = safeId(safeValue(item, "id"));
|
|
495
|
+
if (id === undefined)
|
|
496
|
+
continue;
|
|
497
|
+
const entry = { id };
|
|
498
|
+
const from = safeStatus(safeValue(item, "from"));
|
|
499
|
+
if (from)
|
|
500
|
+
entry.from = from;
|
|
501
|
+
const to = safeStatus(safeValue(item, "to"));
|
|
502
|
+
if (to)
|
|
503
|
+
entry.to = to;
|
|
504
|
+
entries.push(entry);
|
|
415
505
|
}
|
|
416
|
-
return
|
|
506
|
+
return entries;
|
|
417
507
|
}
|
|
418
508
|
function formatTodoGroupSuccess(args, theme, result) {
|
|
419
509
|
const parsed = todoDetails(result.details);
|
|
420
510
|
if (!parsed)
|
|
421
511
|
return undefined;
|
|
422
512
|
const { change, state } = parsed;
|
|
423
|
-
const
|
|
513
|
+
const index = stateIndex(state);
|
|
514
|
+
const segments = [];
|
|
515
|
+
const pushVerb = (word, body) => {
|
|
516
|
+
const title = segments.length === 0 ? `todo ${word}` : word;
|
|
517
|
+
segments.push(body ? `${theme.fg("toolTitle", title)} ${body}` : theme.fg("toolTitle", title));
|
|
518
|
+
};
|
|
424
519
|
const withPreview = (word, ids, subjects) => {
|
|
425
520
|
const text = formatSubjectPreview(subjects, subjects.length);
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
const base = `${verb(word)} ${theme.fg("accent", rendered)}`;
|
|
429
|
-
return text ? `${base}${theme.fg("dim", " · ")}${theme.fg("text", text)}` : base;
|
|
521
|
+
const base = theme.fg("accent", formatIdRange(ids));
|
|
522
|
+
pushVerb(word, text ? `${base}${theme.fg("dim", " · ")}${theme.fg("text", text)}` : base);
|
|
430
523
|
};
|
|
431
|
-
const
|
|
432
|
-
if (
|
|
433
|
-
|
|
434
|
-
const index = stateIndex(state);
|
|
435
|
-
if (!ids || !index)
|
|
436
|
-
return undefined;
|
|
437
|
-
const subjects = ids.map((id) => stateSubject(index.byId, id));
|
|
438
|
-
return withPreview("created", ids, subjects);
|
|
524
|
+
const created = readIds(safeValue(change, "created")) ?? [];
|
|
525
|
+
if (created.length > 0 && index) {
|
|
526
|
+
withPreview("created", created, created.map((id) => stateSubject(index.byId, id)));
|
|
439
527
|
}
|
|
440
|
-
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
const to = safeStatus(safeValue(change, "to"));
|
|
446
|
-
const demotedId = safeId(safeValue(change, "demotedId"));
|
|
447
|
-
const parts = [verb("updated"), theme.fg("accent", `#${id}`)];
|
|
448
|
-
if (to)
|
|
449
|
-
parts.push(theme.fg(STATUS_COLOR[to], to));
|
|
450
|
-
const subject = stateSubject(index.byId, id) || safeSubject(args?.subject) || "";
|
|
528
|
+
for (const entry of updatedEntries(safeValue(change, "updated"))) {
|
|
529
|
+
const parts = [theme.fg("accent", `#${entry.id}`)];
|
|
530
|
+
if (entry.to)
|
|
531
|
+
parts.push(theme.fg(STATUS_COLOR[entry.to], entry.to));
|
|
532
|
+
const subject = index ? stateSubject(index.byId, entry.id) : "";
|
|
451
533
|
if (subject)
|
|
452
534
|
parts.push(theme.fg("text", truncateToWidth(subject, CALL_SUBJECT_PREVIEW_WIDTH, "…")));
|
|
453
|
-
|
|
454
|
-
parts.push(theme.fg("dim", `· demoted #${demotedId}`));
|
|
455
|
-
return parts.join(" ");
|
|
535
|
+
pushVerb("updated", parts.join(" "));
|
|
456
536
|
}
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
parts.push(`${counts.inProgress} in progress`);
|
|
464
|
-
if (counts.pending > 0)
|
|
465
|
-
parts.push(`${counts.pending} pending`);
|
|
466
|
-
if (counts.completed > 0)
|
|
467
|
-
parts.push(`${counts.completed} completed`);
|
|
468
|
-
return `${verb("list")}: ${theme.fg("dim", parts.length ? parts.join(", ") : "no tasks")}`;
|
|
537
|
+
const demotedId = safeId(safeValue(change, "demotedId"));
|
|
538
|
+
if (demotedId !== undefined)
|
|
539
|
+
segments.push(theme.fg("dim", `demoted #${demotedId}`));
|
|
540
|
+
const deleted = idSubjectEntries(safeValue(change, "deleted"));
|
|
541
|
+
if (deleted.length > 0) {
|
|
542
|
+
withPreview("deleted", deleted.map((entry) => entry.id), deleted.map((entry) => entry.subject));
|
|
469
543
|
}
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
544
|
+
for (const id of readIds(safeValue(change, "absent")) ?? []) {
|
|
545
|
+
segments.push(theme.fg("dim", `#${id} already absent`));
|
|
546
|
+
}
|
|
547
|
+
const evicted = idSubjectEntries(safeValue(change, "evicted"));
|
|
548
|
+
if (evicted.length > 0) {
|
|
549
|
+
segments.push(theme.fg("dim", `auto-removed ${evicted.map((entry) => `#${entry.id}`).join(", ")}`));
|
|
550
|
+
}
|
|
551
|
+
if (segments.length === 0) {
|
|
552
|
+
// Empty change: the call listed the tasks.
|
|
553
|
+
if (!index)
|
|
473
554
|
return undefined;
|
|
474
|
-
const
|
|
475
|
-
|
|
555
|
+
const parts = [];
|
|
556
|
+
if (index.counts.inProgress > 0)
|
|
557
|
+
parts.push(`${index.counts.inProgress} in progress`);
|
|
558
|
+
if (index.counts.pending > 0)
|
|
559
|
+
parts.push(`${index.counts.pending} pending`);
|
|
560
|
+
if (index.counts.completed > 0)
|
|
561
|
+
parts.push(`${index.counts.completed} completed`);
|
|
562
|
+
return `${theme.fg("toolTitle", "todo list")}: ${theme.fg("dim", parts.length ? parts.join(", ") : "no tasks")}`;
|
|
476
563
|
}
|
|
477
|
-
|
|
564
|
+
// The update segment can still pick up the requested subject when the state
|
|
565
|
+
// no longer holds it (e.g. the task was deleted by a hostile snapshot).
|
|
566
|
+
void args;
|
|
567
|
+
return segments.join(theme.fg("dim", " ; "));
|
|
478
568
|
}
|
|
479
569
|
/** Compact result-aware summary used only by collapsed todo tool groups. */
|
|
480
570
|
export function formatTodoGroupCall(args, theme, context) {
|