@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
@@ -0,0 +1,390 @@
1
+ export type WebCapabilityKind =
2
+ | "subagents"
3
+ | "workflows"
4
+ | "background-terminals";
5
+
6
+ export const WEB_MAX_CAPABILITY_ITEMS = 32;
7
+ const WEB_MAX_ACTIVITY_TEXT = 160;
8
+ const WEB_MAX_WORKFLOW_AGENTS_SCANNED = 1_024;
9
+
10
+ export interface WebSubagentActivity {
11
+ readonly id: string;
12
+ readonly title: string;
13
+ readonly status: "running" | "done" | "error";
14
+ readonly outcome?: "completed" | "failed" | "interrupted";
15
+ readonly createdAt: number;
16
+ readonly settledAt?: number;
17
+ }
18
+
19
+ export interface WebWorkflowActivity {
20
+ readonly runId: string;
21
+ readonly name?: string;
22
+ readonly status: "running" | "completed" | "failed" | "aborted" | "uncertain";
23
+ readonly currentPhase?: string;
24
+ readonly startedAt: number;
25
+ readonly finishedAt?: number;
26
+ readonly agents: {
27
+ readonly total: number;
28
+ readonly running: number;
29
+ readonly done: number;
30
+ readonly error: number;
31
+ readonly uncertain: number;
32
+ readonly omitted?: number;
33
+ };
34
+ }
35
+
36
+ export interface WebBackgroundTerminalActivity {
37
+ readonly id: string;
38
+ readonly title: string;
39
+ readonly status: "running" | "done" | "failed" | "killed" | "timed_out";
40
+ readonly createdAt: number;
41
+ readonly settledAt?: number;
42
+ readonly exitCode?: number;
43
+ readonly signal?: string;
44
+ }
45
+
46
+ export interface WebCapabilityProjection<
47
+ Item =
48
+ | WebSubagentActivity
49
+ | WebWorkflowActivity
50
+ | WebBackgroundTerminalActivity,
51
+ > {
52
+ readonly items: readonly Item[];
53
+ readonly omitted: number;
54
+ /** Records were omitted or a user-visible string was shortened. */
55
+ readonly truncated: boolean;
56
+ }
57
+
58
+ export interface WebCapabilitySnapshot {
59
+ readonly subagents?: WebCapabilityProjection<WebSubagentActivity>;
60
+ readonly workflows?: WebCapabilityProjection<WebWorkflowActivity>;
61
+ readonly "background-terminals"?: WebCapabilityProjection<WebBackgroundTerminalActivity>;
62
+ }
63
+
64
+ export interface WebCapabilityProvider {
65
+ readonly kind: WebCapabilityKind;
66
+ readonly snapshot: () => WebCapabilityProjection;
67
+ readonly subscribe?: (listener: () => void) => () => void;
68
+ }
69
+
70
+ interface BoundedActivityText {
71
+ readonly value: string;
72
+ readonly truncated: boolean;
73
+ }
74
+
75
+ function boundedActivityText(value: string): BoundedActivityText {
76
+ if (value.length <= WEB_MAX_ACTIVITY_TEXT) {
77
+ return { value, truncated: false };
78
+ }
79
+ return {
80
+ value: `${value.slice(0, WEB_MAX_ACTIVITY_TEXT - 1)}…`,
81
+ truncated: true,
82
+ };
83
+ }
84
+
85
+ function newestActivityAt(value: {
86
+ readonly createdAt?: number;
87
+ readonly startedAt?: number;
88
+ readonly settledAt?: number;
89
+ readonly finishedAt?: number;
90
+ }) {
91
+ return (
92
+ value.settledAt ??
93
+ value.finishedAt ??
94
+ value.createdAt ??
95
+ value.startedAt ??
96
+ 0
97
+ );
98
+ }
99
+
100
+ function boundedActivityProjection<
101
+ Source extends {
102
+ readonly status: string;
103
+ readonly createdAt?: number;
104
+ readonly startedAt?: number;
105
+ readonly settledAt?: number;
106
+ readonly finishedAt?: number;
107
+ },
108
+ Item,
109
+ >(
110
+ source: readonly Source[],
111
+ project: (value: Source) => {
112
+ readonly item: Item;
113
+ readonly truncated: boolean;
114
+ },
115
+ ) {
116
+ const selected: Source[] = [];
117
+ const compare = (left: Source, right: Source) => {
118
+ const running =
119
+ Number(right.status === "running") - Number(left.status === "running");
120
+ return running || newestActivityAt(right) - newestActivityAt(left);
121
+ };
122
+ for (const value of source) {
123
+ const insertAt = selected.findIndex(
124
+ (current) => compare(value, current) < 0,
125
+ );
126
+ if (insertAt < 0) selected.push(value);
127
+ else selected.splice(insertAt, 0, value);
128
+ if (selected.length > WEB_MAX_CAPABILITY_ITEMS) selected.pop();
129
+ }
130
+ const projected = selected.map(project);
131
+ const omitted = Math.max(0, source.length - projected.length);
132
+ return {
133
+ items: projected.map(({ item }) => item),
134
+ omitted,
135
+ truncated: omitted > 0 || projected.some((value) => value.truncated),
136
+ };
137
+ }
138
+
139
+ export function projectSubagentCapability(
140
+ source: readonly {
141
+ readonly id: string;
142
+ readonly title: string;
143
+ readonly status: WebSubagentActivity["status"];
144
+ readonly outcome?: WebSubagentActivity["outcome"];
145
+ readonly createdAt: number;
146
+ readonly settledAt?: number;
147
+ }[],
148
+ ): WebCapabilityProjection<WebSubagentActivity> {
149
+ return boundedActivityProjection(source, (value) => {
150
+ const id = boundedActivityText(value.id);
151
+ const title = boundedActivityText(value.title);
152
+ return {
153
+ item: {
154
+ id: id.value,
155
+ title: title.value,
156
+ status: value.status,
157
+ ...(value.outcome ? { outcome: value.outcome } : {}),
158
+ createdAt: value.createdAt,
159
+ ...(value.settledAt !== undefined
160
+ ? { settledAt: value.settledAt }
161
+ : {}),
162
+ },
163
+ truncated: id.truncated || title.truncated,
164
+ };
165
+ });
166
+ }
167
+
168
+ export function projectWorkflowCapability(
169
+ source: readonly {
170
+ readonly runId: string;
171
+ readonly name?: string;
172
+ readonly status: WebWorkflowActivity["status"];
173
+ readonly currentPhase?: string;
174
+ readonly startedAt: number;
175
+ readonly finishedAt?: number;
176
+ readonly agents: readonly {
177
+ readonly state: keyof WebWorkflowActivity["agents"] | string;
178
+ }[];
179
+ }[],
180
+ ): WebCapabilityProjection<WebWorkflowActivity> {
181
+ return boundedActivityProjection(source, (value) => {
182
+ const runId = boundedActivityText(value.runId);
183
+ const name = value.name ? boundedActivityText(value.name) : undefined;
184
+ const phase = value.currentPhase
185
+ ? boundedActivityText(value.currentPhase)
186
+ : undefined;
187
+ const counts = {
188
+ total: value.agents.length,
189
+ running: 0,
190
+ done: 0,
191
+ error: 0,
192
+ uncertain: 0,
193
+ };
194
+ const scannedAgents = value.agents.slice(
195
+ 0,
196
+ WEB_MAX_WORKFLOW_AGENTS_SCANNED,
197
+ );
198
+ for (const agent of scannedAgents) {
199
+ if (agent.state === "running") counts.running++;
200
+ else if (agent.state === "done") counts.done++;
201
+ else if (agent.state === "error") counts.error++;
202
+ else counts.uncertain++;
203
+ }
204
+ const agentsOmitted = value.agents.length - scannedAgents.length;
205
+ return {
206
+ item: {
207
+ runId: runId.value,
208
+ ...(name ? { name: name.value } : {}),
209
+ status: value.status,
210
+ ...(phase ? { currentPhase: phase.value } : {}),
211
+ startedAt: value.startedAt,
212
+ ...(value.finishedAt !== undefined
213
+ ? { finishedAt: value.finishedAt }
214
+ : {}),
215
+ agents: {
216
+ ...counts,
217
+ ...(agentsOmitted > 0 ? { omitted: agentsOmitted } : {}),
218
+ },
219
+ },
220
+ truncated:
221
+ runId.truncated ||
222
+ name?.truncated === true ||
223
+ phase?.truncated === true ||
224
+ agentsOmitted > 0,
225
+ };
226
+ });
227
+ }
228
+
229
+ export function projectBackgroundTerminalCapability(
230
+ source: readonly {
231
+ readonly id: string;
232
+ readonly title: string;
233
+ readonly status: WebBackgroundTerminalActivity["status"];
234
+ readonly createdAt: number;
235
+ readonly settledAt?: number;
236
+ readonly exitCode?: number;
237
+ readonly signal?: string;
238
+ }[],
239
+ ): WebCapabilityProjection<WebBackgroundTerminalActivity> {
240
+ return boundedActivityProjection(source, (value) => {
241
+ const id = boundedActivityText(value.id);
242
+ const title = boundedActivityText(value.title);
243
+ const signal = value.signal ? boundedActivityText(value.signal) : undefined;
244
+ return {
245
+ item: {
246
+ id: id.value,
247
+ title: title.value,
248
+ status: value.status,
249
+ createdAt: value.createdAt,
250
+ ...(value.settledAt !== undefined
251
+ ? { settledAt: value.settledAt }
252
+ : {}),
253
+ ...(value.exitCode !== undefined ? { exitCode: value.exitCode } : {}),
254
+ ...(signal ? { signal: signal.value } : {}),
255
+ },
256
+ truncated: id.truncated || title.truncated || signal?.truncated === true,
257
+ };
258
+ });
259
+ }
260
+
261
+ /** The Pi SessionManager object itself is the capability-lifetime identity. */
262
+ export type WebCapabilityScope = object;
263
+
264
+ type CapabilityListener = (scope: WebCapabilityScope) => void;
265
+ type ProviderSubscriptions = Map<
266
+ WebCapabilityScope,
267
+ Map<WebCapabilityKind, () => void>
268
+ >;
269
+
270
+ interface WebObserverRegistry {
271
+ readonly version: 1;
272
+ readonly providers: Map<
273
+ WebCapabilityScope,
274
+ Map<WebCapabilityKind, WebCapabilityProvider>
275
+ >;
276
+ readonly listeners: Map<CapabilityListener, ProviderSubscriptions>;
277
+ }
278
+
279
+ const WEB_OBSERVER_REGISTRY_KEY = Symbol.for(
280
+ "@tt-a1i/openpi/web-observer-registry/v1",
281
+ );
282
+
283
+ function sharedWebObserverRegistry(): WebObserverRegistry {
284
+ const existing: unknown = Reflect.get(globalThis, WEB_OBSERVER_REGISTRY_KEY);
285
+ if (existing !== undefined) {
286
+ if (
287
+ !existing ||
288
+ typeof existing !== "object" ||
289
+ (existing as { version?: unknown }).version !== 1 ||
290
+ !((existing as { providers?: unknown }).providers instanceof Map) ||
291
+ !((existing as { listeners?: unknown }).listeners instanceof Map)
292
+ ) {
293
+ throw new Error("Incompatible OpenPI Web observer registry");
294
+ }
295
+ return existing as WebObserverRegistry;
296
+ }
297
+ const registry: WebObserverRegistry = {
298
+ version: 1,
299
+ providers: new Map(),
300
+ listeners: new Map(),
301
+ };
302
+ Object.defineProperty(globalThis, WEB_OBSERVER_REGISTRY_KEY, {
303
+ value: registry,
304
+ configurable: false,
305
+ enumerable: false,
306
+ writable: false,
307
+ });
308
+ return registry;
309
+ }
310
+
311
+ // Pi may load package extensions from a managed install while the standalone
312
+ // CLI runs from a global npm install. Symbol.for keeps those same-process module
313
+ // copies on one versioned registry without adding a second persistence layer.
314
+ const { providers, listeners } = sharedWebObserverRegistry();
315
+
316
+ function connect(
317
+ scope: WebCapabilityScope,
318
+ provider: WebCapabilityProvider,
319
+ listener: CapabilityListener,
320
+ ) {
321
+ const subscriptions = listeners.get(listener);
322
+ let scopedSubscriptions = subscriptions?.get(scope);
323
+ if (!scopedSubscriptions && subscriptions) {
324
+ scopedSubscriptions = new Map();
325
+ subscriptions.set(scope, scopedSubscriptions);
326
+ }
327
+ scopedSubscriptions?.get(provider.kind)?.();
328
+ const unsubscribe = provider.subscribe?.(() => listener(scope));
329
+ if (unsubscribe) scopedSubscriptions?.set(provider.kind, unsubscribe);
330
+ else scopedSubscriptions?.delete(provider.kind);
331
+ }
332
+
333
+ export function registerWebCapability(
334
+ scope: WebCapabilityScope,
335
+ provider: WebCapabilityProvider,
336
+ ) {
337
+ let scopedProviders = providers.get(scope);
338
+ if (!scopedProviders) {
339
+ scopedProviders = new Map();
340
+ providers.set(scope, scopedProviders);
341
+ }
342
+ scopedProviders.set(provider.kind, provider);
343
+ for (const listener of listeners.keys()) {
344
+ connect(scope, provider, listener);
345
+ listener(scope);
346
+ }
347
+ return () => {
348
+ const currentProviders = providers.get(scope);
349
+ if (currentProviders?.get(provider.kind) !== provider) return;
350
+ currentProviders.delete(provider.kind);
351
+ if (currentProviders.size === 0) providers.delete(scope);
352
+ for (const [listener, subscriptions] of listeners) {
353
+ const scopedSubscriptions = subscriptions.get(scope);
354
+ scopedSubscriptions?.get(provider.kind)?.();
355
+ scopedSubscriptions?.delete(provider.kind);
356
+ if (scopedSubscriptions?.size === 0) subscriptions.delete(scope);
357
+ listener(scope);
358
+ }
359
+ };
360
+ }
361
+
362
+ export function subscribeWebCapabilities(listener: CapabilityListener) {
363
+ const subscriptions: ProviderSubscriptions = new Map();
364
+ listeners.set(listener, subscriptions);
365
+ for (const [scope, scopedProviders] of providers) {
366
+ for (const provider of scopedProviders.values()) {
367
+ connect(scope, provider, listener);
368
+ }
369
+ }
370
+ return () => {
371
+ listeners.delete(listener);
372
+ for (const scopedSubscriptions of subscriptions.values()) {
373
+ for (const unsubscribe of scopedSubscriptions.values()) unsubscribe();
374
+ }
375
+ };
376
+ }
377
+
378
+ export function webCapabilitySnapshot(
379
+ scope: WebCapabilityScope,
380
+ ): WebCapabilitySnapshot {
381
+ const scopedProviders = providers.get(scope);
382
+ if (!scopedProviders) return {};
383
+ return Object.fromEntries(
384
+ [...scopedProviders].map(([kind, provider]) => [kind, provider.snapshot()]),
385
+ );
386
+ }
387
+
388
+ export function notifyWebCapabilities(scope: WebCapabilityScope) {
389
+ for (const listener of listeners.keys()) listener(scope);
390
+ }
@@ -292,6 +292,17 @@ export interface WorktreeCleanup {
292
292
  readonly ignored?: boolean;
293
293
  }
294
294
 
295
+ export function formatWorktreeCleanupWarning(
296
+ cleanup: WorktreeCleanup,
297
+ path: string,
298
+ ): string | undefined {
299
+ if (cleanup.removed && cleanup.branchDeleted) return undefined;
300
+ const location = cleanup.removed
301
+ ? `branch ${cleanup.branch} remains`
302
+ : `checkout was not confirmed removed; inspect ${path}`;
303
+ return cleanup.reason ? `${cleanup.reason}; ${location}` : location;
304
+ }
305
+
295
306
  export interface WorktreeCommitCount {
296
307
  readonly ok: boolean;
297
308
  readonly count?: number;