@mblarsen/pi-task-ui 0.1.0 → 0.2.0
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 +13 -0
- package/index.ts +57 -1
- package/package.json +1 -1
- package/skill/SKILL.md +15 -0
package/README.md
CHANGED
|
@@ -63,6 +63,19 @@ Parents are independently executable. Their status and progress are not derived
|
|
|
63
63
|
|
|
64
64
|
The bundled `task-ui` Agent Skill teaches the agent when to create task sets, mirror backend transitions, maintain execution telemetry, and avoid fabricating state. Invoke it explicitly with `/skill:task-ui` or let Pi load it when the request matches its description.
|
|
65
65
|
|
|
66
|
+
## Checkpoint reminders
|
|
67
|
+
|
|
68
|
+
The extension sends a hidden context reminder after these successful tool calls:
|
|
69
|
+
|
|
70
|
+
- a Bash command that invokes `git commit`
|
|
71
|
+
- `link_send`
|
|
72
|
+
|
|
73
|
+
The reminder runs only when the projection contains an active or pending task. It asks the agent to reconcile task status, progress, and execution state before work continues. It also asks the agent to report task-ui changes in the next natural status update without interrupting the current work.
|
|
74
|
+
|
|
75
|
+
The extension sends at most one reminder per agent turn. A compound Bash command must succeed as a whole. For example, `git commit && git push` does not trigger a reminder when the push fails.
|
|
76
|
+
|
|
77
|
+
The reminder is hidden from the transcript, but it remains part of the agent context. Task state remains in TUI-only session entries and does not enter the agent context.
|
|
78
|
+
|
|
66
79
|
### Execution telemetry
|
|
67
80
|
|
|
68
81
|
Create and update operations accept:
|
package/index.ts
CHANGED
|
@@ -53,6 +53,31 @@ type SnapshotEvent = { tasks: ExternalTaskInput[]; focusedTaskId?: string };
|
|
|
53
53
|
type RemoveEvent = { taskId: string };
|
|
54
54
|
type OutputEvent = { taskId: string; text: string };
|
|
55
55
|
type FocusEvent = { taskId?: string };
|
|
56
|
+
type TaskUiCheckpoint = "git commit" | "link_send";
|
|
57
|
+
type ToolResultCheckpointInput = { toolName: string; input: unknown; isError: boolean };
|
|
58
|
+
|
|
59
|
+
const GIT_COMMIT_COMMAND = /(?:^|(?:&&|\|\||[;()\n])\s*)git(?:\s+-C\s+(?:"[^"]*"|'[^']*'|[^\s;&|()]+))?\s+commit(?=$|[\s;&|()])/;
|
|
60
|
+
|
|
61
|
+
export function checkpointForToolResult(event: ToolResultCheckpointInput): TaskUiCheckpoint | undefined {
|
|
62
|
+
if (event.isError !== false) return undefined;
|
|
63
|
+
if (event.toolName === "link_send") return "link_send";
|
|
64
|
+
if (event.toolName !== "bash" || typeof event.input !== "object" || event.input === null) return undefined;
|
|
65
|
+
const command = (event.input as { command?: unknown }).command;
|
|
66
|
+
return typeof command === "string" && GIT_COMMIT_COMMAND.test(command) ? "git commit" : undefined;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function checkpointReminder(trigger: TaskUiCheckpoint): string {
|
|
70
|
+
return `Task UI checkpoint: A successful ${trigger} just occurred.
|
|
71
|
+
|
|
72
|
+
Before continuing, reconcile task-ui with the actual work state:
|
|
73
|
+
- update affected task statuses, progress, and execution state;
|
|
74
|
+
- add newly discovered work only when it is meaningful;
|
|
75
|
+
- do not mark a task complete merely because this checkpoint succeeded.
|
|
76
|
+
|
|
77
|
+
If task-ui is already accurate, make no changes.
|
|
78
|
+
|
|
79
|
+
If you change task-ui, mention the update in your next natural user-facing status message. Do not interrupt, pause, or redirect the current work solely to report it; resume the ongoing work immediately.`;
|
|
80
|
+
}
|
|
56
81
|
|
|
57
82
|
type AgentTaskInput = {
|
|
58
83
|
id?: string;
|
|
@@ -272,6 +297,7 @@ function taskLine(
|
|
|
272
297
|
const content = `${indent}${glyph} ${taskLabel}`;
|
|
273
298
|
if (task.status === "failed") return theme.fg("error", content);
|
|
274
299
|
if (task.status === "pending") return theme.fg("muted", content);
|
|
300
|
+
if (task.status === "stopped") return theme.fg("dim", content);
|
|
275
301
|
return focused ? theme.bold(content) : content;
|
|
276
302
|
}
|
|
277
303
|
|
|
@@ -333,7 +359,7 @@ export class TaskBarComponent {
|
|
|
333
359
|
task.label,
|
|
334
360
|
width,
|
|
335
361
|
this.theme,
|
|
336
|
-
task.status === "completed",
|
|
362
|
+
task.status === "completed" || task.status === "stopped",
|
|
337
363
|
));
|
|
338
364
|
}
|
|
339
365
|
}
|
|
@@ -371,6 +397,7 @@ export default function taskUiExtension(pi: ExtensionAPI): void {
|
|
|
371
397
|
let sessionActive = false;
|
|
372
398
|
let spinnerFrame = 0;
|
|
373
399
|
let animationTimer: ReturnType<typeof setInterval> | undefined;
|
|
400
|
+
let checkpointReminderQueued = false;
|
|
374
401
|
|
|
375
402
|
const stopAnimation = () => {
|
|
376
403
|
if (animationTimer) clearInterval(animationTimer);
|
|
@@ -688,9 +715,36 @@ export default function taskUiExtension(pi: ExtensionAPI): void {
|
|
|
688
715
|
handler: async (_args, ctx) => toggleOverlay(ctx),
|
|
689
716
|
});
|
|
690
717
|
|
|
718
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
719
|
+
const checkpoint = checkpointForToolResult(event);
|
|
720
|
+
const hasUnfinishedTasks = state.tasks.some((task) => task.status === "pending" || task.status === "in_progress");
|
|
721
|
+
if (!checkpoint || !hasUnfinishedTasks || checkpointReminderQueued) return;
|
|
722
|
+
|
|
723
|
+
checkpointReminderQueued = true;
|
|
724
|
+
try {
|
|
725
|
+
pi.sendMessage({
|
|
726
|
+
customType: "task-ui-checkpoint-reminder",
|
|
727
|
+
content: checkpointReminder(checkpoint),
|
|
728
|
+
display: false,
|
|
729
|
+
details: { checkpoint, timestamp: Date.now() },
|
|
730
|
+
}, { deliverAs: "steer" });
|
|
731
|
+
} catch (error) {
|
|
732
|
+
checkpointReminderQueued = false;
|
|
733
|
+
if (ctx.hasUI) {
|
|
734
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
735
|
+
ctx.ui.notify(`Task UI checkpoint reminder failed: ${message}`, "warning");
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
|
|
740
|
+
pi.on("turn_start", async () => {
|
|
741
|
+
checkpointReminderQueued = false;
|
|
742
|
+
});
|
|
743
|
+
|
|
691
744
|
pi.on("session_start", async (_event, ctx) => {
|
|
692
745
|
currentCtx = ctx;
|
|
693
746
|
sessionActive = true;
|
|
747
|
+
checkpointReminderQueued = false;
|
|
694
748
|
state = createInitialTaskUiState();
|
|
695
749
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
696
750
|
if (entry.type !== "custom" || entry.customType !== STATE_ENTRY_TYPE) continue;
|
|
@@ -705,6 +759,7 @@ export default function taskUiExtension(pi: ExtensionAPI): void {
|
|
|
705
759
|
});
|
|
706
760
|
|
|
707
761
|
pi.on("session_tree", async (_event, ctx) => {
|
|
762
|
+
checkpointReminderQueued = false;
|
|
708
763
|
state = createInitialTaskUiState();
|
|
709
764
|
for (const entry of ctx.sessionManager.getBranch()) {
|
|
710
765
|
if (entry.type !== "custom" || entry.customType !== STATE_ENTRY_TYPE) continue;
|
|
@@ -717,6 +772,7 @@ export default function taskUiExtension(pi: ExtensionAPI): void {
|
|
|
717
772
|
|
|
718
773
|
pi.on("session_shutdown", async () => {
|
|
719
774
|
sessionActive = false;
|
|
775
|
+
checkpointReminderQueued = false;
|
|
720
776
|
currentCtx = undefined;
|
|
721
777
|
stopAnimation();
|
|
722
778
|
overlayHandle?.hide();
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -87,6 +87,21 @@ When the worker stops running but the task remains unfinished, set `executing: f
|
|
|
87
87
|
|
|
88
88
|
Spawning a sub-agent does not automatically update task-ui. The coordinating agent must call the backend tool and the matching `task_ui_update` separately.
|
|
89
89
|
|
|
90
|
+
## Checkpoint reminders
|
|
91
|
+
|
|
92
|
+
The extension can send a hidden task-ui checkpoint reminder after a successful `git commit` or `link_send` tool call.
|
|
93
|
+
|
|
94
|
+
When you receive this reminder:
|
|
95
|
+
|
|
96
|
+
1. Compare the projection with the actual work state.
|
|
97
|
+
2. Update affected task status, progress, and execution state.
|
|
98
|
+
3. Add newly discovered work only when it is meaningful.
|
|
99
|
+
4. Do not mark work complete only because the checkpoint succeeded.
|
|
100
|
+
5. If you change task-ui, mention the change in your next natural status update.
|
|
101
|
+
6. Resume the current work without waiting for confirmation.
|
|
102
|
+
|
|
103
|
+
If the projection is accurate, do not change it.
|
|
104
|
+
|
|
90
105
|
## Finish or interrupt work
|
|
91
106
|
|
|
92
107
|
After successful completion, call `task_ui_update` with `status: "completed"`, `executing: false`, and `progress: 100`.
|