pi-jar 0.3.2 → 0.3.3

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
@@ -302,6 +302,7 @@ Providers cache a prompt as an ordered prefix: tools, then the system prompt, th
302
302
  { "action": "append", "todos": [{ "content": "Handle newly requested edge case", "status": "pending" }] }
303
303
  ```
304
304
  Use `parent` with `append` to add a genuinely new subtask to active work; `start`, `done`, `open`, `edit`, `list`, `add` and `delete` remain available.
305
+ - **Todo gate** — a turn cannot end while `jar_todo` has pending or in-progress leaf tasks: pi-jar injects a hidden reminder and continues (at most 2 reminders per user message; skipped in plan/goal mode, aborted turns and subagents). Disable with `PI_JAR_TODO_GATE=off`.
305
306
  - **`jar_ask`** — structured questions: numbered options with descriptions, single or multi-select, *Type your own answer* (multi-line, paste-friendly) and *Chat about this* to discuss before choosing.
306
307
  - **`/diff`** — review what the agent (and its editing subagents) changed since the first edit to each file: files with `+/−` counts on the left, a full-path header and OMP-inspired split-first **Before/After** diff on the right (unified below 70 pane columns). Changed lines get a colored rail and soft tinted background, replaced lines highlight changed words with a stronger tint, code is syntax-highlighted for known languages, and collapsed regions are labeled `┄ N unchanged lines ┄`. `/` filters files; `t` switches unified and aligned side-by-side views (unified below 70 columns), `←`/`→` (or `[`/`]`) change the context from 0 lines to the whole file, `w` wraps long lines, and `n`/`p`, `Home`/`End` navigate. Large changes offer summary or explicit windowed review (`v`), with clearly labeled coarse fallback and existing safety limits—not arbitrary-file streaming. See [diff review](docs/DIFF-REVIEW.md). `a` accept (keep, stop tracking), `r` then `y` revert (restore the original, or remove a file it created), `A`/`R` for all. The footer shows `± N files · /diff` while anything is unreviewed; each file counts once no matter who edited it. Only `edit`/`write` changes inside the project are tracked; shell-made changes are not.
307
308
  - **Context ownership** — pi-jar does not prune prior reasoning/tool results, impose a soft context limit, or abort/compact/resume work at task boundaries. The context meter and cache diagnostics remain passive. Pi's native compaction and explicit `/compact` (including the user-selected plan approval action) remain available. Retired `contextDiet` and `contextBudget` settings are ignored and dropped on the next settings/profile save; loading does not rewrite user files.
@@ -16,6 +16,7 @@ import { renderFooterLayout, type ActivityState, type FooterActivity, type Foote
16
16
  import { openJarHistory } from "../src/history-ui.ts";
17
17
  import { GOAL_ENTRY, GoalStore } from "../src/goals.ts";
18
18
  import { GoalLoop } from "../src/goal-loop.ts";
19
+ import { registerTodoGate } from "../src/todo-gate.ts";
19
20
  import { FOOTER_FIELDS } from "../src/footer-settings.ts";
20
21
  import { defaultVisualSettings, loadVisualSettings, saveVisualSettings, type JarVisualSettings } from "../src/settings.ts";
21
22
  import { ProfileStore, PROFILE_FILE, PROFILE_ENTRY, conversationStarted, pinnedProfileId } from "../src/profiles.ts";
@@ -358,6 +359,10 @@ export default function piJar(pi: ExtensionAPI): void {
358
359
  completed: () => composer.flash("complete", 4000)
359
360
  });
360
361
  if (!delegatedChild) goalLoop.register();
362
+ if (!delegatedChild) registerTodoGate(pi, {
363
+ todos: () => todos, suspended: () => planMode.isEnabled() || !!goals?.isActive(),
364
+ enabled: () => process.env.PI_JAR_TODO_GATE !== "off"
365
+ });
361
366
  planMode.setOnEnter((ctx) => goalLoop.pause(ctx, "plan mode is on"));
362
367
  const suggestions = new SuggestionState();
363
368
  composer.attachSuggestions(suggestions);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-jar",
3
- "version": "0.3.2",
3
+ "version": "0.3.3",
4
4
  "description": "A warm, role-aware Pi terminal workspace with switchable profiles, animated UI, plan and goal workflows, tasks, change review, model roles, background shells and subagents.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,45 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ import type { Todo, TodoStore } from "./tasks.ts";
3
+
4
+ export const TODO_GATE_TYPE = "pi-jar.todo-gate";
5
+ /** Automatic reminders per user message; keeps a stuck model from looping forever. */
6
+ export const TODO_GATE_MAX_NUDGES = 2;
7
+
8
+ export interface TodoGateOptions {
9
+ todos: () => TodoStore | undefined;
10
+ /** True while plan mode or goal mode owns turn continuation. */
11
+ suspended: () => boolean;
12
+ enabled?: () => boolean;
13
+ }
14
+
15
+ /** Leaf tasks that are still open (parents are derived from their subtasks). */
16
+ export function openLeaves(items: readonly Todo[]): Todo[] {
17
+ const parents = new Set(items.map((item) => item.parentId).filter(Boolean));
18
+ return items.filter((item) => !item.done && !parents.has(item.id));
19
+ }
20
+
21
+ export function todoGateMessage(open: readonly Todo[]): string {
22
+ const lines = open.slice(0, 10).map((item) => `${item.status === "in_progress" ? "[~]" : "[ ]"} ${item.title} (${item.id})`);
23
+ return [
24
+ "[PI-JAR TODO CHECK] jar_todo still has unfinished tasks:",
25
+ ...lines,
26
+ open.length > lines.length ? `… and ${open.length - lines.length} more` : "",
27
+ "Before ending the turn, sync jar_todo: mark verified work completed, or continue with the open task. If a task is obsolete or blocked, remove it or say why."
28
+ ].filter(Boolean).join("\n");
29
+ }
30
+
31
+ /** Refuses to end a turn while jar_todo has pending or in-progress tasks (bounded reminders). */
32
+ export function registerTodoGate(pi: ExtensionAPI, options: TodoGateOptions): void {
33
+ let nudges = 0;
34
+ pi.on("input", (event) => { if (event.source === "interactive") nudges = 0; });
35
+ pi.on("session_start", () => { nudges = 0; });
36
+ pi.on("agent_before_settle", (event) => {
37
+ if (event.outcome !== "completed" || event.continue || event.context?.canContinue === false) return;
38
+ if (options.enabled && !options.enabled()) return;
39
+ if (options.suspended() || nudges >= TODO_GATE_MAX_NUDGES) return;
40
+ const open = openLeaves(options.todos()?.all() ?? []);
41
+ if (!open.length) return;
42
+ nudges++;
43
+ return { entries: [{ type: "custom_message" as const, customType: TODO_GATE_TYPE, display: false, content: todoGateMessage(open) }], continue: true };
44
+ });
45
+ }