@mblarsen/pi-task-ui 0.1.0 → 0.2.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 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;
@@ -260,7 +285,7 @@ function taskLine(
260
285
  case "in_progress": glyph = theme.fg("accent", "◼"); break;
261
286
  case "pending": glyph = theme.fg("dim", "◻"); break;
262
287
  case "failed": glyph = theme.fg("error", "✖"); break;
263
- case "stopped": glyph = theme.fg("dim", "■"); break;
288
+ case "stopped": glyph = "■"; break;
264
289
  }
265
290
  }
266
291
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mblarsen/pi-task-ui",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Backend-neutral task sidebar and agent tools for Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",
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`.