@tt-a1i/openpi 0.3.1 → 0.5.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 (129) hide show
  1. package/README.md +184 -59
  2. package/SETUP.md +23 -7
  3. package/assets/openpi-launch-card-v1.webp +0 -0
  4. package/bin/openpi.js +145 -0
  5. package/extensions/ask-user/index.ts +30 -14
  6. package/extensions/background-terminals/index.ts +30 -2
  7. package/extensions/background-terminals/src/domain.ts +2 -0
  8. package/extensions/background-terminals/src/manager.ts +486 -106
  9. package/extensions/background-terminals/src/output.ts +33 -0
  10. package/extensions/background-terminals/src/prompt.ts +14 -6
  11. package/extensions/background-terminals/src/result-delivery.ts +4 -1
  12. package/extensions/background-terminals/src/ui/ps.ts +132 -129
  13. package/extensions/capabilities/index.ts +30 -42
  14. package/extensions/capabilities/src/ui.ts +93 -0
  15. package/extensions/clear-context/index.ts +83 -0
  16. package/extensions/context-pivot/index.ts +16 -6
  17. package/extensions/cron/schedule.ts +7 -1
  18. package/extensions/file-mutation-display/index.ts +34 -76
  19. package/extensions/file-mutation-display/render.ts +146 -87
  20. package/extensions/file-search/index.ts +8 -7
  21. package/extensions/file-search/src/binaries.ts +75 -59
  22. package/extensions/git-info/src/changed-files-view.ts +47 -14
  23. package/extensions/git-read/index.ts +328 -0
  24. package/extensions/git-read/src/args.ts +171 -0
  25. package/extensions/git-read/src/process.ts +81 -0
  26. package/extensions/git-read/src/prompt.ts +56 -0
  27. package/extensions/model-info/index.ts +21 -33
  28. package/extensions/model-info/session-metrics.ts +96 -0
  29. package/extensions/plan-mode/bash-policy.ts +54 -9
  30. package/extensions/plan-mode/index.ts +7 -2
  31. package/extensions/post-edit/index.ts +16 -6
  32. package/extensions/sessions/git-stats.ts +258 -72
  33. package/extensions/sessions/index.ts +222 -140
  34. package/extensions/sessions/preview-cache.ts +104 -0
  35. package/extensions/sessions/preview-loader.ts +856 -0
  36. package/extensions/sessions/sessions.ts +43 -4
  37. package/extensions/setup/index.ts +127 -131
  38. package/extensions/shared/activity-status.ts +36 -5
  39. package/extensions/shared/agent-session-page.ts +319 -0
  40. package/extensions/shared/agent-tool-renderer.ts +218 -0
  41. package/extensions/shared/agent-transcript.ts +524 -0
  42. package/extensions/shared/below-editor-navigation.ts +26 -0
  43. package/extensions/shared/capability-intent.ts +53 -0
  44. package/extensions/shared/child-session.ts +444 -22
  45. package/extensions/shared/result-budget.ts +134 -0
  46. package/extensions/shared/result-delivery.ts +34 -0
  47. package/extensions/shared/screen-chrome.ts +133 -0
  48. package/extensions/shared/setup-config.ts +97 -38
  49. package/extensions/shared/setup-episode-state.ts +1 -1
  50. package/extensions/shared/spinner.ts +28 -0
  51. package/extensions/shared/terminal-text.ts +110 -23
  52. package/extensions/shared/text-projection.ts +113 -0
  53. package/extensions/shared/tool-activity.ts +382 -0
  54. package/extensions/shared/tool-surface.ts +42 -8
  55. package/extensions/shared/transcript-viewport.ts +46 -0
  56. package/extensions/shared/web-observer-registry.ts +390 -0
  57. package/extensions/shared/worktree.ts +11 -0
  58. package/extensions/subagents/index.ts +461 -186
  59. package/extensions/subagents/navigation.ts +86 -28
  60. package/extensions/subagents/src/agent-types.ts +37 -15
  61. package/extensions/subagents/src/backend.ts +12 -1
  62. package/extensions/subagents/src/backends/pi.ts +375 -66
  63. package/extensions/subagents/src/domain.ts +5 -0
  64. package/extensions/subagents/src/id-sequence.ts +84 -0
  65. package/extensions/subagents/src/manager.ts +651 -536
  66. package/extensions/subagents/src/prompt.ts +185 -42
  67. package/extensions/subagents/src/result-artifact.ts +146 -0
  68. package/extensions/subagents/src/result-delivery.ts +7 -1
  69. package/extensions/subagents/src/runtime.ts +23 -6
  70. package/extensions/subagents/src/ui/takeover.ts +128 -337
  71. package/extensions/subagents/src/ui/transcript.ts +38 -501
  72. package/extensions/subagents/src/ui/wait-result.ts +103 -15
  73. package/extensions/suggestions/src/ui.ts +10 -4
  74. package/extensions/tasks/index.ts +0 -3
  75. package/extensions/tasks/ui.ts +79 -62
  76. package/extensions/ui-customization/footer.ts +7 -44
  77. package/extensions/ui-customization/index.ts +0 -4
  78. package/extensions/user-input-fold/index.ts +185 -0
  79. package/extensions/web/index.ts +234 -0
  80. package/extensions/workflows/artifacts.ts +147 -22
  81. package/extensions/workflows/completion-projection.ts +457 -0
  82. package/extensions/workflows/controller.ts +14 -2
  83. package/extensions/workflows/coordinator.ts +62 -0
  84. package/extensions/workflows/dashboard.ts +458 -339
  85. package/extensions/workflows/handoff.ts +121 -25
  86. package/extensions/workflows/index.ts +1042 -492
  87. package/extensions/workflows/journal.ts +148 -13
  88. package/extensions/workflows/model.ts +131 -19
  89. package/extensions/workflows/navigation.ts +61 -18
  90. package/extensions/workflows/progress-projection.ts +306 -0
  91. package/extensions/workflows/prompt.ts +166 -10
  92. package/extensions/workflows/replay-safety.ts +58 -27
  93. package/extensions/workflows/result-delivery.ts +253 -0
  94. package/extensions/workflows/retention.ts +593 -0
  95. package/extensions/workflows/runner.ts +388 -279
  96. package/extensions/workflows/sandbox-child.cjs +36 -3
  97. package/extensions/workflows/sandbox.ts +62 -8
  98. package/extensions/workflows/serialization.ts +325 -17
  99. package/extensions/workflows/tool-renderer.ts +22 -0
  100. package/extensions/workflows/transcript.ts +149 -0
  101. package/extensions/workspace-cleanup-guard/index.ts +54 -0
  102. package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
  103. package/package.json +28 -8
  104. package/skills/subagents/REFERENCE.md +189 -0
  105. package/skills/subagents/SKILL.md +2 -2
  106. package/skills/workflows/REFERENCE.md +10 -5
  107. package/skills/workflows/SKILL.md +53 -10
  108. package/web/adapter/pi-adapter.ts +661 -0
  109. package/web/host/browser-launcher.ts +20 -0
  110. package/web/host/static-assets.ts +4 -0
  111. package/web/host/terminal-status.ts +38 -0
  112. package/web/host/web-host.ts +789 -0
  113. package/web/http-dispatcher.ts +125 -0
  114. package/web/protocol/types.ts +462 -0
  115. package/web/runtime/pi-runtime.ts +991 -0
  116. package/web/runtime/types.ts +71 -0
  117. package/web/runtime/web-host-lease.ts +497 -0
  118. package/web/trace.ts +18 -0
  119. package/web/ui/app.js +1398 -0
  120. package/web/ui/index.html +139 -0
  121. package/web/ui/styles.css +598 -0
  122. package/web/vite.config.mjs +34 -0
  123. package/extensions/execution-convergence/active-evidence.ts +0 -129
  124. package/extensions/execution-convergence/index.ts +0 -442
  125. package/extensions/execution-convergence/workspace-provenance.ts +0 -338
  126. package/extensions/setup/intercom-fs-helper.cjs +0 -130
  127. package/extensions/setup/intercom.ts +0 -603
  128. package/extensions/subagents/src/backends/stub.ts +0 -296
  129. package/extensions/subagents/src/format.ts +0 -48
@@ -2,21 +2,28 @@ import type { Theme } from "@earendil-works/pi-coding-agent";
2
2
  import type { TUI } from "@earendil-works/pi-tui";
3
3
  import {
4
4
  fitNavigationSides,
5
+ renderNavigationMetrics,
5
6
  type BelowEditorStripState,
6
7
  } from "../shared/below-editor-navigation.ts";
7
8
  import {
8
9
  unreadActivityCounts,
9
10
  type ActivityCounts,
10
11
  } from "../shared/activity-status.ts";
12
+ import { spinnerFrame } from "../shared/spinner.ts";
11
13
  import { sanitizeTerminalText } from "../shared/terminal-text.ts";
12
14
  import { formatElapsed, type SubagentSnapshot } from "./src/domain.ts";
13
- import { formatContextUtilization } from "./src/format.ts";
15
+ import { contextPercent } from "../shared/context-utilization.ts";
14
16
 
15
17
  export interface SubagentStripEntry {
16
18
  snapshot: SubagentSnapshot;
17
19
  counts: ActivityCounts;
18
20
  }
19
21
 
22
+ /** Changes only when the strip needs an immediate lifecycle repaint. */
23
+ export function subagentStripEntryKey(entry: SubagentStripEntry | undefined) {
24
+ return entry ? `${entry.snapshot.id}:${entry.snapshot.status}` : undefined;
25
+ }
26
+
20
27
  function cleanLine(value: string) {
21
28
  return sanitizeTerminalText(value).replace(/\s+/g, " ").trim();
22
29
  }
@@ -58,13 +65,22 @@ function statusColor(status: SubagentSnapshot["status"]) {
58
65
  return "error" as const;
59
66
  }
60
67
 
61
- function statusSquare(snapshot: SubagentSnapshot, theme: Theme) {
62
- return theme.fg(statusColor(snapshot.status), "■");
68
+ /**
69
+ * One status indicator per run state; doubles as the focus marker when
70
+ * selected. Running spins, in step with the dashboard and takeover headers.
71
+ */
72
+ function statusGlyph(snapshot: SubagentSnapshot, theme: Theme, now: number) {
73
+ if (snapshot.status === "running")
74
+ return theme.fg("warning", spinnerFrame(now));
75
+ if (snapshot.status === "done") return theme.fg("success", "✓");
76
+ return theme.fg("error", "✗");
63
77
  }
64
78
 
79
+ const SUBAGENT_STRIP_INTERVAL_MS = 500;
80
+
65
81
  /** One-line subagent manager entry with the same affordance as Workflow. */
66
82
  export class SubagentStripWidget {
67
- private readonly timer: ReturnType<typeof setInterval>;
83
+ private timer: ReturnType<typeof setInterval> | undefined;
68
84
  private readonly tui: TUI;
69
85
  private readonly theme: Theme;
70
86
  private readonly strip: BelowEditorStripState;
@@ -80,42 +96,84 @@ export class SubagentStripWidget {
80
96
  this.theme = theme;
81
97
  this.strip = strip;
82
98
  this.getEntry = getEntry;
83
- this.timer = setInterval(() => this.tui.requestRender(), 500);
84
- this.timer.unref?.();
99
+ this.syncSpinner();
85
100
  }
86
101
 
87
102
  dispose() {
88
- clearInterval(this.timer);
103
+ if (this.timer) clearInterval(this.timer);
104
+ this.timer = undefined;
89
105
  }
90
106
 
91
- invalidate() {}
107
+ invalidate() {
108
+ this.syncSpinner();
109
+ }
110
+
111
+ private syncSpinner() {
112
+ if (this.getEntry()?.snapshot.status === "running") {
113
+ if (this.timer) return;
114
+ const timer = setInterval(() => {
115
+ if (this.timer !== timer) return;
116
+ if (this.getEntry()?.snapshot.status !== "running") {
117
+ clearInterval(timer);
118
+ this.timer = undefined;
119
+ }
120
+ this.tui.requestRender();
121
+ }, SUBAGENT_STRIP_INTERVAL_MS);
122
+ this.timer = timer;
123
+ timer.unref?.();
124
+ return;
125
+ }
126
+ if (this.timer) clearInterval(this.timer);
127
+ this.timer = undefined;
128
+ }
92
129
 
93
130
  render(width: number) {
131
+ this.syncSpinner();
94
132
  const entry = this.getEntry();
95
133
  if (!entry || width <= 0) return [];
96
134
  const { snapshot, counts } = entry;
97
- const marker = this.strip.focused
135
+ const glyph = this.strip.focused
98
136
  ? this.theme.fg("accent", "❯")
99
- : this.theme.fg("dim", "○");
100
- const titleText = normalizeSubagentTitle(snapshot.title, snapshot.id);
101
- const title = this.strip.focused
102
- ? this.theme.bold(this.theme.fg("accent", titleText))
103
- : this.theme.fg("text", titleText);
104
- const model = snapshot.meta.modelLabel
105
- ? cleanLine(snapshot.meta.modelLabel)
106
- : undefined;
107
- const left = ` ${marker} ${statusSquare(snapshot, this.theme)} ${title}${model ? this.theme.fg("dim", ` · ${model}`) : ""}`;
108
- const settled = counts.done + counts.failed;
109
- const total = counts.running + settled;
110
- const metrics = [
111
- `${settled}/${total} agents`,
112
- formatElapsed(snapshot),
113
- formatContextUtilization(snapshot.usage),
137
+ : statusGlyph(snapshot, this.theme, Date.now());
138
+ // A name only means something when it names the only active subagent; with
139
+ // several, an aggregate label is honest and the counts carry the detail.
140
+ const total = counts.running + counts.done + counts.failed;
141
+ const single = total === 1;
142
+ const labelText = single
143
+ ? normalizeSubagentTitle(snapshot.title, snapshot.id)
144
+ : "subagents";
145
+ const label = this.strip.focused
146
+ ? this.theme.bold(this.theme.fg("accent", labelText))
147
+ : this.theme.fg("text", labelText);
148
+ // The footer already shows the session model; the takeover view keeps the
149
+ // per-subagent model, so the one-line strip stays title-only.
150
+ const left = ` ${glyph} ${label}`;
151
+ // Worded counts read at a glance; the selected run's own state comes
152
+ // first so the emphasis colour always lands on the matching count. A lone
153
+ // subagent needs no count — the glyph and label already say it.
154
+ const donePart = counts.done > 0 ? `${counts.done} done` : undefined;
155
+ const failedPart =
156
+ counts.failed > 0 ? `${counts.failed} failed` : undefined;
157
+ const activity = single
158
+ ? []
159
+ : counts.running > 0
160
+ ? [`${counts.running} running`]
161
+ : snapshot.status === "error"
162
+ ? [failedPart, donePart]
163
+ : [donePart, failedPart];
164
+ const percent = contextPercent(snapshot.usage);
165
+ const right = renderNavigationMetrics(
166
+ this.theme,
167
+ [
168
+ ...activity,
169
+ formatElapsed(snapshot),
170
+ percent === undefined ? undefined : `${percent}% ctx`,
171
+ ],
114
172
  this.strip.focused ? "enter open · ↑ back" : "↓ to manage",
115
- ]
116
- .filter((part): part is string => Boolean(part))
117
- .join(" · ");
118
- const right = this.theme.fg(statusColor(snapshot.status), metrics);
173
+ single || snapshot.status === "running"
174
+ ? undefined
175
+ : statusColor(snapshot.status),
176
+ );
119
177
  return [fitNavigationSides(left, right, width)];
120
178
  }
121
179
  }
@@ -24,18 +24,18 @@ import * as fs from "node:fs";
24
24
  import * as path from "node:path";
25
25
  import type { Model } from "@earendil-works/pi-ai";
26
26
  import {
27
- parseFrontmatter,
28
27
  type ModelRegistry,
28
+ parseFrontmatter,
29
29
  } from "@earendil-works/pi-coding-agent";
30
30
  import {
31
31
  CHILD_EXCLUDED_TOOL_NAMES,
32
32
  CHILD_SAFE_PACKAGE_TOOL_NAMES,
33
33
  } from "../../shared/child-session.ts";
34
- import { sanitizeTerminalText } from "../../shared/terminal-text.ts";
35
34
  import {
36
35
  isSubagentRoleName,
37
36
  type SubagentRoleModel,
38
37
  } from "../../shared/subagent-roles.ts";
38
+ import { sanitizeTerminalText } from "../../shared/terminal-text.ts";
39
39
  import { REASONING_EFFORTS, type ReasoningEffort } from "./domain.ts";
40
40
 
41
41
  /** Directory name scanned under both the agent dir and a project's `.pi`. */
@@ -101,7 +101,17 @@ export interface AgentType {
101
101
  readonly source: string;
102
102
  }
103
103
 
104
- const READ_ONLY_TOOLS = ["read", "grep", "find", "ls", "fd", "rg"];
104
+ export const READ_ONLY_AGENT_TOOLS = [
105
+ "read",
106
+ "grep",
107
+ "find",
108
+ "ls",
109
+ "fd",
110
+ "rg",
111
+ "git_show",
112
+ "git_diff",
113
+ "git_log",
114
+ ];
105
115
 
106
116
  /**
107
117
  * Built-in role definitions are deliberately provider-free: model selection is
@@ -112,33 +122,45 @@ export const BUILT_IN_AGENT_TYPES: readonly AgentType[] = [
112
122
  {
113
123
  name: "explorer",
114
124
  description:
115
- "Read-only codebase exploration. Use high for routine, local, direct tracing; xhigh for interacting state transitions, concurrency or trust boundaries, or subtle multi-path lifecycle/control-flow; max only for exceptionally difficult broad unfamiliar architecture with unresolved competing flows.",
116
- tools: READ_ONLY_TOOLS,
117
- reasoningEffort: "high",
125
+ "Read-only codebase exploration. Usually use moderate reasoning, increasing it for harder tasks.",
126
+ tools: READ_ONLY_AGENT_TOOLS,
118
127
  body: "Explore the codebase read-only. Trace the real flow, inspect related callers, and report concise evidence with file paths and line references.",
119
128
  source: "built-in:explorer",
120
129
  },
121
130
  {
122
131
  name: "implementer",
123
- description: "Focused implementation with repository checks.",
124
- tools: ["read", "bash", "edit", "write", "grep", "find", "ls", "fd", "rg"],
125
- reasoningEffort: "high",
132
+ description:
133
+ "Focused implementation with repository checks. Usually use medium-high reasoning, adjusted for scope, risk, and task difficulty.",
134
+ tools: [
135
+ "read",
136
+ "bash",
137
+ "edit",
138
+ "write",
139
+ "grep",
140
+ "find",
141
+ "ls",
142
+ "fd",
143
+ "rg",
144
+ "git_show",
145
+ "git_diff",
146
+ "git_log",
147
+ ],
126
148
  body: "Implement the requested change carefully. Trace the affected flow first, make the smallest correct edit, and run relevant checks before reporting results.",
127
149
  source: "built-in:implementer",
128
150
  },
129
151
  {
130
152
  name: "reviewer",
131
- description: "Read-only review for correctness, safety, and regressions.",
132
- tools: READ_ONLY_TOOLS,
133
- reasoningEffort: "medium",
153
+ description:
154
+ "Read-only review for correctness, safety, and regressions. Usually use high reasoning, adjusted for task difficulty.",
155
+ tools: READ_ONLY_AGENT_TOOLS,
134
156
  body: "Review the requested code or change read-only. Identify concrete correctness, security, and regression risks with evidence; do not modify files.",
135
157
  source: "built-in:reviewer",
136
158
  },
137
159
  {
138
160
  name: "advisor",
139
- description: "Deep read-only analysis and technical advice.",
140
- tools: READ_ONLY_TOOLS,
141
- reasoningEffort: "xhigh",
161
+ description:
162
+ "Deep read-only analysis and technical advice. Usually use high reasoning, adjusted for task difficulty.",
163
+ tools: READ_ONLY_AGENT_TOOLS,
142
164
  body: "Analyze the problem deeply without modifying files. Explain the relevant tradeoffs, risks, and recommended next step using repository evidence.",
143
165
  source: "built-in:advisor",
144
166
  },
@@ -5,11 +5,11 @@
5
5
  * on it rather than on the pi session directly.
6
6
  *
7
7
  * - pi (./backends/pi.ts): in-process `createAgentSession()` via the pi SDK.
8
- * - stub (./backends/stub.ts): scripted sessions, test-only.
9
8
  */
10
9
 
11
10
  import type { Effect, Scope, Stream } from "effect";
12
11
  import { Context } from "effect";
12
+ import type { AgentToolRenderer } from "../../shared/agent-tool-renderer.ts";
13
13
  import type {
14
14
  BackendName,
15
15
  SendError,
@@ -19,6 +19,13 @@ import type {
19
19
  SubagentMeta,
20
20
  } from "./domain.ts";
21
21
 
22
+ export interface SubagentCleanupReceipt {
23
+ /** True when the session/worktree state could not be proven quiescent. */
24
+ readonly uncertain: boolean;
25
+ /** Bounded operator-facing explanation of the cleanup result. */
26
+ readonly message: string;
27
+ }
28
+
22
29
  /**
23
30
  * A live subagent session. The manager is the single consumer of `events`;
24
31
  * it folds them into the `SubagentSnapshot` everything else reads.
@@ -42,6 +49,10 @@ export interface SubagentSession {
42
49
  * this with a timeout and fall back to closing the session scope.
43
50
  */
44
51
  readonly interrupt: Effect.Effect<void>;
52
+ /** Ephemeral Pi-native tool projection for the operator-facing child page. */
53
+ readonly toolRenderer?: AgentToolRenderer;
54
+ /** Cleanup receipt populated when scope teardown cannot prove safety. */
55
+ readonly cleanupReceipt?: () => SubagentCleanupReceipt | undefined;
45
56
  }
46
57
 
47
58
  export interface SubagentBackend {