@oisin-ee/oisinpi 0.12.1 → 0.29.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.
Files changed (40) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +1 -2
  3. package/node_modules/@oisin-ee/pi-guardrails/package.json +2 -2
  4. package/node_modules/@oisin-ee/pi-prewalk/package.json +2 -2
  5. package/node_modules/@oisin-ee/pi-prewalk/src/generated/prewalk_core_bg.wasm +0 -0
  6. package/node_modules/@oisin-ee/pi-tiers/package.json +2 -2
  7. package/package.json +6 -9
  8. package/node_modules/@oisin-ee/pi-todo/README.md +0 -134
  9. package/node_modules/@oisin-ee/pi-todo/package.json +0 -39
  10. package/node_modules/@oisin-ee/pi-todo/src/contract-fixture.ts +0 -103
  11. package/node_modules/@oisin-ee/pi-todo/src/display.ts +0 -71
  12. package/node_modules/@oisin-ee/pi-todo/src/embedded.ts +0 -105
  13. package/node_modules/@oisin-ee/pi-todo/src/gate.ts +0 -89
  14. package/node_modules/@oisin-ee/pi-todo/src/generated/todo_core.d.ts +0 -573
  15. package/node_modules/@oisin-ee/pi-todo/src/generated/todo_core.js +0 -970
  16. package/node_modules/@oisin-ee/pi-todo/src/generated/todo_core_bg.wasm +0 -0
  17. package/node_modules/@oisin-ee/pi-todo/src/generated/todo_core_bg.wasm.d.ts +0 -22
  18. package/node_modules/@oisin-ee/pi-todo/src/guidance.ts +0 -16
  19. package/node_modules/@oisin-ee/pi-todo/src/handoff.ts +0 -84
  20. package/node_modules/@oisin-ee/pi-todo/src/import-fixture.ts +0 -63
  21. package/node_modules/@oisin-ee/pi-todo/src/index.ts +0 -74
  22. package/node_modules/@oisin-ee/pi-todo/src/lease-fixture.ts +0 -95
  23. package/node_modules/@oisin-ee/pi-todo/src/lifecycle.ts +0 -105
  24. package/node_modules/@oisin-ee/pi-todo/src/plan-approved.ts +0 -106
  25. package/node_modules/@oisin-ee/pi-todo/src/presentation.ts +0 -70
  26. package/node_modules/@oisin-ee/pi-todo/src/provider-fixture.ts +0 -50
  27. package/node_modules/@oisin-ee/pi-todo/src/provider.ts +0 -289
  28. package/node_modules/@oisin-ee/pi-todo/src/registration-fixture.ts +0 -126
  29. package/node_modules/@oisin-ee/pi-todo/src/reminder.ts +0 -30
  30. package/node_modules/@oisin-ee/pi-todo/src/repository.ts +0 -31
  31. package/node_modules/@oisin-ee/pi-todo/src/runtime-fixture.ts +0 -260
  32. package/node_modules/@oisin-ee/pi-todo/src/schema.ts +0 -99
  33. package/node_modules/@oisin-ee/pi-todo/src/seed-fixture.ts +0 -35
  34. package/node_modules/@oisin-ee/pi-todo/src/seed-transport.ts +0 -52
  35. package/node_modules/@oisin-ee/pi-todo/src/source.ts +0 -48
  36. package/node_modules/@oisin-ee/pi-todo/src/state.ts +0 -105
  37. package/node_modules/@oisin-ee/pi-todo/src/tool.ts +0 -60
  38. package/node_modules/@oisin-ee/pi-todo/src/wasm.ts +0 -64
  39. package/node_modules/@oisin-ee/pi-todo/src/widget.ts +0 -77
  40. package/node_modules/@oisin-ee/pi-todo/src/work.ts +0 -237
package/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 Momokaya
3
+ Copyright (c) 2026 Oisín
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -5,7 +5,6 @@ Oisin's [pi](https://pi.dev) packages in one install, at versions tested togethe
5
5
  | Package | Contents |
6
6
  | ---------------------------------------------------------------------------------- | -------------------------------------------------------------------------- |
7
7
  | [`@oisin-ee/pi-prewalk`](https://www.npmjs.com/package/@oisin-ee/pi-prewalk) | A frontier model plans and makes the first edit; a cheaper model finishes. |
8
- | [`@oisin-ee/pi-todo`](https://www.npmjs.com/package/@oisin-ee/pi-todo) | The `todo` tool, which also opens the prewalk gate. |
9
8
  | [`@oisin-ee/pi-guardrails`](https://www.npmjs.com/package/@oisin-ee/pi-guardrails) | Git safety, the wait gate, and the agent-typing gate on every tool call. |
10
9
 
11
10
  ```bash
@@ -22,4 +21,4 @@ pi-dogsled is not in the collection. Install it on its own, at the same version
22
21
  pi install npm:@oisin-ee/pi-dogsled@<dogsled version>
23
22
  ```
24
23
 
25
- When pi-dogsled is active, its work widget hosts pi-todo's view. Without it, pi-todo keeps its own widget.
24
+ The `todo` tool now ships in `@oisin-ee/pi-dogsled`. Its work widget hosts the todo view.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oisin-ee/pi-guardrails",
3
- "version": "0.12.1",
3
+ "version": "0.29.0",
4
4
  "description": "Agent guardrails for pi, Claude Code, and Codex: Git safety, no waiting, no typing into other agents, GitOps-only clusters.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -12,7 +12,7 @@
12
12
  "license": "MIT",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/oisin-ee/oisinpi.git",
15
+ "url": "git+https://github.com/oisin-ee/dogsled.git",
16
16
  "directory": "packages/pi-guardrails"
17
17
  },
18
18
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oisin-ee/pi-prewalk",
3
- "version": "0.12.1",
3
+ "version": "0.29.0",
4
4
  "description": "A one-shot planner-to-executor handoff for pi, with branch-backed Rust rules.",
5
5
  "keywords": [
6
6
  "pi-extension",
@@ -10,7 +10,7 @@
10
10
  "license": "MIT",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/oisin-ee/oisinpi.git",
13
+ "url": "git+https://github.com/oisin-ee/dogsled.git",
14
14
  "directory": "packages/pi-prewalk"
15
15
  },
16
16
  "files": [
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oisin-ee/pi-tiers",
3
- "version": "0.12.1",
3
+ "version": "0.29.0",
4
4
  "description": "Sticky virtual model tiers with quota-aware recovery for pi.",
5
5
  "keywords": [
6
6
  "pi-extension",
@@ -10,7 +10,7 @@
10
10
  "license": "MIT",
11
11
  "repository": {
12
12
  "type": "git",
13
- "url": "git+https://github.com/oisin-ee/oisinpi.git",
13
+ "url": "git+https://github.com/oisin-ee/dogsled.git",
14
14
  "directory": "packages/pi-tiers"
15
15
  },
16
16
  "files": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oisin-ee/oisinpi",
3
- "version": "0.12.1",
3
+ "version": "0.29.0",
4
4
  "description": "Oisin's pi packages in one install: prewalk, todo, tiers, and guardrails.",
5
5
  "keywords": [
6
6
  "guardrails",
@@ -12,25 +12,22 @@
12
12
  "license": "MIT",
13
13
  "repository": {
14
14
  "type": "git",
15
- "url": "git+https://github.com/oisin-ee/oisinpi.git",
15
+ "url": "git+https://github.com/oisin-ee/dogsled.git",
16
16
  "directory": "packages/oisinpi"
17
17
  },
18
18
  "type": "module",
19
19
  "dependencies": {
20
- "@oisin-ee/pi-guardrails": "0.12.1",
21
- "@oisin-ee/pi-prewalk": "0.12.1",
22
- "@oisin-ee/pi-tiers": "0.12.1",
23
- "@oisin-ee/pi-todo": "0.12.1"
20
+ "@oisin-ee/pi-guardrails": "0.29.0",
21
+ "@oisin-ee/pi-prewalk": "0.29.0",
22
+ "@oisin-ee/pi-tiers": "0.29.0"
24
23
  },
25
24
  "bundleDependencies": [
26
25
  "@oisin-ee/pi-guardrails",
27
26
  "@oisin-ee/pi-prewalk",
28
- "@oisin-ee/pi-tiers",
29
- "@oisin-ee/pi-todo"
27
+ "@oisin-ee/pi-tiers"
30
28
  ],
31
29
  "pi": {
32
30
  "extensions": [
33
- "./node_modules/@oisin-ee/pi-todo/src/index.ts",
34
31
  "./node_modules/@oisin-ee/pi-prewalk/src/index.ts",
35
32
  "./node_modules/@oisin-ee/pi-tiers/src/index.ts",
36
33
  "./node_modules/@oisin-ee/pi-guardrails/src/pi/index.ts"
@@ -1,134 +0,0 @@
1
- # @oisin-ee/pi-todo
2
-
3
- A `todo` tool for [pi](https://pi.dev). The model keeps one ordered list of outcomes and bounded subtasks for the session. Multiple units can be active together.
4
- The list rules run in Rust, compiled to WebAssembly.
5
-
6
- ```bash
7
- pi install npm:@oisin-ee/pi-todo
8
- ```
9
-
10
- To install it with the other packages, use [`@oisin-ee/oisinpi`](https://www.npmjs.com/package/@oisin-ee/oisinpi).
11
-
12
- ## The tool
13
-
14
- The model calls `todo` with one `op`. Tasks are addressed by their exact content.
15
-
16
- | `op` | Effect |
17
- | -------- | --------------------------------------------------------------- |
18
- | `init` | Replaces the list with `items`. |
19
- | `append` | Adds `items` to the end. |
20
- | `start` | Explicitly activates `task` without demoting other active work. |
21
- | `done` | Completes `task`. Without `task`, completes every open task. |
22
- | `drop` | Abandons `task`. Without `task`, abandons every open task. |
23
- | `view` | Returns the list. It does not change it. |
24
-
25
- Creation and append leave tasks pending. Nothing starts automatically. This replaces the previous single-active, automatic-start behavior.
26
- Use `start` to activate or reopen work explicitly. Starting a child also opens its parent. Starting a parent does not start its children.
27
-
28
- Keep `content` a short headline: aim under 60 characters, imperative, no trailing detail. Put context, approach, or acceptance detail in the optional `description` (one or two sentences). Outcomes and subtask drafts accept `description`; a plain string item has none. A blank description counts as none. `update` with `description` sets it on an open task or subtask, and a blank value clears it. It never changes `content` or how a task is selected. `view` prints a description on the line under its headline; the Pi widget shows headlines only.
29
-
30
- `items` accepts strings or outcomes with string subtasks:
31
-
32
- ```json
33
- {
34
- "op": "init",
35
- "items": [
36
- {
37
- "content": "Password reset",
38
- "subtasks": ["Implement tokens and behavior tests", "Implement form and interaction tests"]
39
- },
40
- "Update documentation"
41
- ]
42
- }
43
- ```
44
-
45
- To target a child, supply its exact `task` content and its exact root `parent` content:
46
-
47
- ```json
48
- { "op": "start", "parent": "Password reset", "task": "Implement tokens and behavior tests" }
49
- ```
50
-
51
- To add children, use `append` with `parent` and string `items`. Only two levels are permitted.
52
- Root names are unique. Child names are unique within their parent. Different parents can use the same child name.
53
- Blank names, duplicate siblings, unknown targets, grandchildren, and malformed requests fail without saving a change.
54
-
55
- Each executable unit fits one agent session, with one deliverable, clear scope, and an acceptance condition.
56
- Split independent outcomes or work that needs another session. Combine trivial steps with their deliverable and tests.
57
- Delegate independent units separately. The lead can execute one unit. Keep dependent units ordered.
58
- Do not delegate an oversized parent as one assignment.
59
-
60
- A parent stays open after its children close and shows **Ready for review**.
61
- Complete the parent explicitly after integration review. Completing a parent with open children fails, including bulk completion.
62
- Dropping a parent abandons open children but preserves completed and abandoned children.
63
- Untargeted `done` and `drop` affect open work only. A parent without `task` is not a bulk-child selector.
64
- Appending children to a closed parent reopens it as pending. An active parent stays active.
65
- Outcome and subtask progress remain separate. Abandoned work never counts as completed.
66
-
67
- ## Activation gate
68
-
69
- Shell, code, and mutation tools need one active local unit. Structured reads, workflow control, and `todo` calls stay available without one.
70
- The gate checks the selected model at each call. The planner selections `tier/plan-claude` and `tier/plan-gpt` skip the activation check, so a planning session can read and inspect without a unit.
71
- Every other selection needs an active unit, including a tier with another name and a physical model that only looks like a planner.
72
- The exemption is a workflow prerequisite, not a permission. It does not grant write access, and other policies such as guardrails still apply.
73
- An active unit stays required for the sessions that implement code.
74
-
75
- ## State
76
-
77
- Each task change is saved as a `todo-state` entry on the session branch. A resumed session, a `/tree` move,
78
- and calls made from inside codemode all see the list of the current branch.
79
- Old flat snapshots retain their recorded statuses and read with empty subtasks.
80
-
81
- ## Widget
82
-
83
- The widget shows only in-progress tasks by default. Active tasks use the theme accent and bold text.
84
- Long task names wrap to the terminal width. An empty list has no widget.
85
-
86
- Press **Alt+T** to expand Upcoming, Done, and Dropped sections beneath the active task. Press **Alt+T** again to collapse them.
87
- `/todos` also toggles these sections. `/todos list` prints the full list.
88
-
89
- Expansion stays open through task updates. Session changes and `/tree` navigation collapse it. Expansion does not change saved tasks.
90
- When no task is active, a short status and the shortcut hint remain visible.
91
- Outside the terminal UI, `/todos` prints the list instead.
92
-
93
- ## Shared work display
94
-
95
- When a compatible pi-dogsled extension is active, its work widget replaces the standalone todo widget.
96
- Tasks remain on the current Pi session branch. Dogsled receives a read-only presentation capability through `pi.events`.
97
- Pi-todo owns the renderer in both standalone and shared views. Dogsled hosts it above the editor without opening a popup.
98
- Alt+J/K scrolls the hosted rows. Alt+I toggles their expansion. Ordinary editor keys remain unchanged.
99
- `/todos` and Alt+T request the shared Todos view. `/todos list` still prints the full list.
100
- When shared presentation ends, the standalone widget returns without changing task state.
101
-
102
- The [version 3 display contract](https://github.com/oisin-ee/dogsled/blob/main/docs/pi-work.md)
103
- defines the snapshot request, acknowledgement, release, and read-only view events.
104
- Version 3 adds `display.sections` to the snapshot: four typed status groups (`in_progress`, `pending`, `completed`, `abandoned`), always present and in that order, each with a count and its items. Each item carries its text, optional description, review flag, contract summary, and all subtasks (each with its own optional description). The core owns this grouping; a presenter only draws it.
105
- Pi-todo does not answer version 2 requests.
106
- Incompatible versions leave the standalone widget available. Rendering failures restore it without changing saved tasks.
107
-
108
- ## Dogsled plans
109
-
110
- Dogsled plan mode (`/plan` and `submit_plan`) emits `dogsled:plan-approved` when the user approves a plan.
111
- Pi-todo continues the approved plan in the same session. No setting is needed.
112
-
113
- After approval, the agent reads the current todo list and adds the remaining approved work before implementation.
114
- It preserves unrelated tasks and completed progress. Free-form plans do not need a special checklist format.
115
- The todo tool owns progress, persistence, reminders, and the shared work display.
116
- Malformed events, a different working directory, or another owner of the `todo` tool stop the handoff with an error.
117
-
118
- ## Reminder
119
-
120
- When the agent stops with open tasks, pi-todo adds a visible reminder to the conversation and continues the turn.
121
- The reminder names open children and parents that await integration review.
122
- It sends at most 3 reminders per user message. After a reminder, it waits for the list to change before it sends
123
- another. A new user message resets the count.
124
-
125
- ## Other todo tools
126
-
127
- If another package already registers `todo`, pi-todo stays inactive and shows a warning that names that package.
128
-
129
- ## Source
130
-
131
- The original flat operations and the reminder derive from
132
- [oh-my-pi](https://github.com/can1357/oh-my-pi/tree/e7fe318/packages/coding-agent/src) (`tools/todo`,
133
- `todo-tracker.ts`), MIT. Saving a custom entry on every change follows
134
- [rpiv-todo](https://github.com/juicesharp/rpiv-mono/tree/main/packages/rpiv-todo).
@@ -1,39 +0,0 @@
1
- {
2
- "name": "@oisin-ee/pi-todo",
3
- "version": "0.12.1",
4
- "description": "A todo tool for pi with a Rust core: one active task, state on the session branch.",
5
- "keywords": [
6
- "pi-extension",
7
- "pi-package",
8
- "todo"
9
- ],
10
- "license": "MIT",
11
- "repository": {
12
- "type": "git",
13
- "url": "git+https://github.com/oisin-ee/oisinpi.git",
14
- "directory": "packages/pi-todo"
15
- },
16
- "files": [
17
- "src/",
18
- "!src/**/*.test.ts"
19
- ],
20
- "type": "module",
21
- "devDependencies": {
22
- "@earendil-works/pi-ai": "catalog:",
23
- "@earendil-works/pi-coding-agent": "catalog:",
24
- "@earendil-works/pi-tui": "catalog:",
25
- "typebox": "catalog:"
26
- },
27
- "peerDependencies": {
28
- "@earendil-works/pi-ai": "*",
29
- "@earendil-works/pi-coding-agent": "*",
30
- "@earendil-works/pi-tui": "*",
31
- "typebox": "*"
32
- },
33
- "pi": {
34
- "extensions": [
35
- "./src/index.ts"
36
- ],
37
- "image": "https://raw.githubusercontent.com/oisin-ee/oisinpi/main/assets/prewalk.png"
38
- }
39
- }
@@ -1,103 +0,0 @@
1
- import type { TodoParams, TodoSession } from "./tool.ts";
2
- import { STATE_ENTRY } from "./state.ts";
3
- import type { SessionEntry } from "@earendil-works/pi-coding-agent";
4
- import type { TodoList } from "./wasm.ts";
5
- import { runTodo } from "./tool.ts";
6
-
7
- interface Fixture {
8
- readonly entries: SessionEntry[];
9
- readonly session: TodoSession;
10
- readonly call: (params: TodoParams) => TodoList;
11
- }
12
-
13
- /**
14
- * Creates a branch-backed caller using the real tool and WASM core.
15
- * @param {readonly unknown[]} snapshots Existing stored snapshots.
16
- * @returns {Fixture} The tool caller and Pi append seam.
17
- */
18
- const first = 0,
19
- second = 1,
20
- third = 2,
21
- last = -1,
22
- fixture = (snapshots: readonly unknown[] = []): Fixture => {
23
- const entries = snapshots.map((data): SessionEntry => ({
24
- customType: STATE_ENTRY,
25
- data,
26
- id: crypto.randomUUID(),
27
- parentId: null,
28
- timestamp: "2026-10-20T00:00:00Z",
29
- type: "custom",
30
- })),
31
- session: TodoSession = {
32
- append: (customType, data): void => {
33
- entries.push({
34
- customType,
35
- data,
36
- id: crypto.randomUUID(),
37
- parentId: entries.at(last)?.id ?? null,
38
- timestamp: "2026-10-20T00:00:00Z",
39
- type: "custom",
40
- });
41
- },
42
- branch: () => entries,
43
- sessionId: "test-session",
44
- };
45
- return { call: (params) => runTodo(params, session).details, entries, session };
46
- },
47
- /**
48
- * A complete self-assigned contract for deterministic behavior tests.
49
- * @returns {NonNullable<TodoParams["contract"]>} Authored contract fields.
50
- */
51
- contract = (): NonNullable<TodoParams["contract"]> => ({
52
- acceptance: {
53
- argv: ["moon", "run", "todo-core:test"],
54
- cwd: ".",
55
- expected: "passes",
56
- kind: "command",
57
- },
58
- assignment: { kind: "self" },
59
- deliverable: "Executable task contracts",
60
- dependsOn: [],
61
- scope: { generates: [], owns: ["src/"], reads: ["README.md"], repositoryId: "oisinpi" },
62
- }),
63
- /**
64
- * Returns the runtime ID from the first root in a tool result.
65
- * @param {TodoList} list Real tool result.
66
- * @returns {string} Runtime ID.
67
- */
68
- firstId = (list: TodoList): string => {
69
- const id = list.tasks.at(first)?.unit?.reference.unitId;
70
- if (id === undefined) {
71
- throw new Error("Expected a runtime unit ID");
72
- }
73
- return id;
74
- },
75
- /**
76
- * Requires a root identity at a known fixture position.
77
- * @param {TodoList} list Tool result.
78
- * @param {number} index Root position.
79
- * @returns {string} Runtime ID.
80
- */
81
- rootId = (list: TodoList, index: number): string => {
82
- const id = list.tasks.at(index)?.unit?.reference.unitId;
83
- if (id === undefined) {
84
- throw new Error("Expected root identity");
85
- }
86
- return id;
87
- },
88
- /**
89
- * Requires a child identity from the first outcome.
90
- * @param {TodoList} list Tool result.
91
- * @param {number} index Child position.
92
- * @returns {string} Runtime ID.
93
- */
94
- childId = (list: TodoList, index: number): string => {
95
- const id = list.tasks.at(first)?.subtasks?.at(index)?.unit?.reference.unitId;
96
- if (id === undefined) {
97
- throw new Error("Expected child identity");
98
- }
99
- return id;
100
- };
101
-
102
- export { childId, contract, first, firstId, fixture, last, rootId, second, third };
103
- export type { Fixture };
@@ -1,71 +0,0 @@
1
- import type { Subtask, TodoList, TodoSection } from "./wasm.ts";
2
- import { sections, view } from "./wasm.ts";
3
- import type { Theme } from "@earendil-works/pi-coding-agent";
4
- import { TodoView } from "./embedded.ts";
5
-
6
- /** Host-owned layout and rendering services for an inline todo view. */
7
- interface ViewHost {
8
- readonly theme: Readonly<Pick<Theme, "fg" | "bold">>;
9
- readonly height: () => number;
10
- readonly requestRender: () => void;
11
- }
12
-
13
- /** A provider-owned view. The host forwards updates without interpreting state. */
14
- interface HostedView {
15
- readonly render: (width: number) => string[];
16
- readonly handleInput: (input: string) => void;
17
- readonly invalidate: () => void;
18
- readonly update: (state: unknown) => void;
19
- readonly dispose: () => void;
20
- }
21
-
22
- /** Transient presentation data, never persisted in a session entry. */
23
- interface TodoDisplay {
24
- readonly empty: boolean;
25
- readonly summary: string;
26
- readonly current: string;
27
- readonly text: string;
28
- /** Status groups owned by the core: in_progress, pending, completed, abandoned. */
29
- readonly sections: readonly TodoSection[];
30
- readonly state: unknown;
31
- readonly createView: (host: ViewHost) => HostedView;
32
- }
33
-
34
- const emptyCount = 0,
35
- headerIndex = 0,
36
- /**
37
- * Projects the core's summary and supplies its reusable renderer.
38
- * @param {TodoList} list The validated current branch snapshot.
39
- * @returns {TodoDisplay} An in-process presentation capability.
40
- */
41
- display = (list: TodoList): TodoDisplay => {
42
- const text = view(list),
43
- active = list.tasks.flatMap((task) => [
44
- ...(task.status === "in_progress" ? [task.content] : []),
45
- ...(task.subtasks ?? [])
46
- .filter(
47
- (child: Readonly<Pick<Subtask, "content" | "status">>) =>
48
- child.status === "in_progress",
49
- )
50
- .map(
51
- (child: Readonly<Pick<Subtask, "content" | "status">>) =>
52
- `${task.content} → ${child.content}`,
53
- ),
54
- ]),
55
- current =
56
- active.length > emptyCount
57
- ? active.join("; ")
58
- : (list.tasks.find((task) => task.status === "pending")?.content ?? "");
59
- return {
60
- createView: (host) => new TodoView(list, host),
61
- current,
62
- empty: list.tasks.length === emptyCount,
63
- sections: sections(list),
64
- state: list,
65
- summary: text.split("\n").at(headerIndex) ?? "",
66
- text,
67
- };
68
- };
69
-
70
- export { display };
71
- export type { HostedView, TodoDisplay, ViewHost };
@@ -1,105 +0,0 @@
1
- import { Key, ScrollView, matchesKey } from "@earendil-works/pi-tui";
2
- import { parse, widget } from "./wasm.ts";
3
- import type { TodoList } from "./wasm.ts";
4
- import { TodoWidget } from "./widget.ts";
5
- import type { ViewHost } from "./display.ts";
6
-
7
- const minimumHeight = 1,
8
- previousLine = -1,
9
- nextLine = 1;
10
-
11
- /** Inline viewport over the same renderer used by the standalone widget. */
12
- class TodoView {
13
- readonly #host: ViewHost;
14
- #list: TodoList;
15
- #expanded = true;
16
- #disposed = false;
17
- readonly #body: TodoWidget;
18
- readonly #scroll: ScrollView;
19
-
20
- /**
21
- * Creates an expansion-local view without event subscriptions or task writes.
22
- * @param {TodoList} list Validated branch tasks.
23
- * @param {ViewHost} host Host layout and rendering services.
24
- */
25
- public constructor(list: TodoList, host: ViewHost) {
26
- this.#host = host;
27
- this.#list = list;
28
- this.#body = this.#content();
29
- this.#scroll = new ScrollView(this.#body, { overscroll: "contain", scrollbar: "hidden" });
30
- }
31
-
32
- /**
33
- * Creates the shared widget content.
34
- * @returns {TodoWidget} Theme-aware rows.
35
- */
36
- #content(): TodoWidget {
37
- const body = new TodoWidget(
38
- widget(this.#list, this.#expanded) ?? [],
39
- this.#expanded,
40
- this.#host.theme,
41
- );
42
- body.setShortcut("Alt+I");
43
- return body;
44
- }
45
-
46
- /**
47
- * Applies a provider snapshot while preserving the viewport.
48
- * @param {unknown} state The opaque state from the display capability.
49
- */
50
- public update(state: unknown): void {
51
- if (!this.#disposed) {
52
- this.#list = parse(state);
53
- this.#body.setRows(widget(this.#list, this.#expanded) ?? [], this.#expanded);
54
- this.invalidate();
55
- }
56
- }
57
-
58
- /**
59
- * Renders a bounded, scrollable view with the active host theme.
60
- * @param {number} width Available terminal columns.
61
- * @returns {string[]} Visible terminal lines.
62
- */
63
- public render(width: number): string[] {
64
- if (this.#disposed) {
65
- return [];
66
- }
67
- const height = Math.max(minimumHeight, this.#host.height());
68
- this.#scroll.updateLayout(this.#body.render(width).length, height, this.#host.requestRender);
69
- return this.#scroll
70
- .render(width)
71
- .slice(this.#scroll.scrollTop, this.#scroll.scrollTop + height);
72
- }
73
-
74
- /**
75
- * Handles only navigation forwarded by the inline host.
76
- * @param {string} input A translated host shortcut.
77
- */
78
- public handleInput(input: string): void {
79
- if (this.#disposed) {
80
- return;
81
- }
82
- if (matchesKey(input, Key.enter)) {
83
- this.#expanded = !this.#expanded;
84
- this.#body.setRows(widget(this.#list, this.#expanded) ?? [], this.#expanded);
85
- } else if (matchesKey(input, Key.up)) {
86
- this.#scroll.scrollBy(previousLine);
87
- } else if (matchesKey(input, Key.down)) {
88
- this.#scroll.scrollBy(nextLine);
89
- }
90
- this.#host.requestRender();
91
- }
92
-
93
- /** Invalidates both native components after a theme or data change. */
94
- public invalidate(): void {
95
- this.#body.invalidate();
96
- this.#scroll.invalidate();
97
- }
98
-
99
- /** Ends this view's lifetime without modifying task state. */
100
- public dispose(): void {
101
- this.#disposed = true;
102
- }
103
- }
104
-
105
- export { TodoView };
@@ -1,89 +0,0 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import type { TaskProvider } from "./provider.ts";
3
- import { ownsTodo } from "./lifecycle.ts";
4
-
5
- /** Planning selections that skip the activation check, exact provider and model IDs. */
6
- const planningSelections = new Set(["plan-claude", "plan-gpt"]),
7
- /**
8
- * Whether the selected model is a planning selection.
9
- * @param {Readonly<object>} model Selected model, when the session has one.
10
- * @returns {boolean} Whether the activation check does not apply.
11
- */
12
- isPlanning = (model: { readonly provider?: string; readonly id?: string } | undefined): boolean =>
13
- model?.provider === "tier" && planningSelections.has(model.id ?? ""),
14
- /** Exact structured reads and workflow controls, never command-text inference. */
15
- controls = new Set([
16
- "ask",
17
- "codemode",
18
- "interview",
19
- "todo",
20
- "tool_search",
21
- "read",
22
- "ls",
23
- "find",
24
- "grep",
25
- "hypa_read",
26
- "hypa_ls",
27
- "hypa_find",
28
- "hypa_grep",
29
- "web_search",
30
- "web_fetch",
31
- "lsp_diagnostics",
32
- "ctx_search",
33
- "ctx_stats",
34
- "mcp__codebase_memory__search_graph",
35
- "mcp__codebase_memory__get_code_snippet",
36
- "mcp__codebase_memory__trace_path",
37
- "mcp__codebase_memory__detect_changes",
38
- "hindsight_search_knowledge_pages",
39
- "hindsight_list_knowledge_pages",
40
- "hindsight_read_knowledge_page",
41
- "hindsight_reflect",
42
- "hindsight_sync_status",
43
- "hindsight_diagnose",
44
- "hindsight_capture_initiative",
45
- "mcp__dogsled__spawn",
46
- "mcp__dogsled__send",
47
- "mcp__dogsled__cancel",
48
- "mcp__dogsled__close",
49
- "mcp__dogsled__done",
50
- "mcp__dogsled__stop",
51
- "mcp__dogsled__read",
52
- "mcp__dogsled__status",
53
- "mcp__dogsled__inbox",
54
- ]),
55
- /**
56
- * Checks actual calls and leases execution-bearing calls.
57
- * @param {Readonly<ExtensionAPI>} pi Runtime hooks and metadata.
58
- * @param {Readonly<TaskProvider>} provider Task state and leases.
59
- */
60
- registerGate = (
61
- pi: Readonly<Pick<ExtensionAPI, "on" | "getAllTools">>,
62
- provider: Readonly<TaskProvider>,
63
- ): void => {
64
- pi.on("tool_call", (event: Readonly<{ toolName: string; toolCallId: string }>, ctx) => {
65
- if (!ownsTodo(pi)) {
66
- return { block: false };
67
- }
68
- provider.context(ctx);
69
- if (controls.has(event.toolName) || isPlanning(ctx.model)) {
70
- return { block: false };
71
- }
72
- const active = provider.capability().active();
73
- if (!active.ok || active.value === null) {
74
- return {
75
- block: true,
76
- reason: active.ok
77
- ? "Start a complete self-assigned leaf with todo before shell, code, mutation, or unknown tools."
78
- : active.error.message,
79
- };
80
- }
81
- provider.lease(event.toolCallId, active.value.ref);
82
- return { block: false };
83
- });
84
- pi.on("tool_execution_end", (event: Readonly<{ toolCallId: string }>) => {
85
- provider.release(event.toolCallId);
86
- });
87
- };
88
-
89
- export { registerGate };