@tt-a1i/openpi 0.4.0 → 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 (109) hide show
  1. package/README.md +102 -40
  2. package/SETUP.md +22 -6
  3. package/assets/openpi-launch-card-v1.webp +0 -0
  4. package/bin/openpi.js +145 -0
  5. package/extensions/background-terminals/index.ts +30 -2
  6. package/extensions/background-terminals/src/domain.ts +2 -0
  7. package/extensions/background-terminals/src/manager.ts +486 -106
  8. package/extensions/background-terminals/src/output.ts +33 -0
  9. package/extensions/background-terminals/src/prompt.ts +13 -5
  10. package/extensions/background-terminals/src/result-delivery.ts +4 -1
  11. package/extensions/clear-context/index.ts +83 -0
  12. package/extensions/context-pivot/index.ts +16 -6
  13. package/extensions/cron/schedule.ts +7 -1
  14. package/extensions/file-mutation-display/render.ts +17 -257
  15. package/extensions/file-search/src/binaries.ts +57 -41
  16. package/extensions/git-read/index.ts +1 -3
  17. package/extensions/model-info/index.ts +21 -33
  18. package/extensions/model-info/session-metrics.ts +96 -0
  19. package/extensions/plan-mode/bash-policy.ts +54 -9
  20. package/extensions/plan-mode/index.ts +7 -2
  21. package/extensions/post-edit/index.ts +16 -6
  22. package/extensions/sessions/git-stats.ts +258 -72
  23. package/extensions/sessions/index.ts +153 -86
  24. package/extensions/sessions/preview-cache.ts +104 -0
  25. package/extensions/sessions/preview-loader.ts +856 -0
  26. package/extensions/sessions/sessions.ts +43 -4
  27. package/extensions/setup/index.ts +123 -127
  28. package/extensions/shared/activity-status.ts +30 -0
  29. package/extensions/shared/agent-session-page.ts +319 -0
  30. package/extensions/shared/agent-tool-renderer.ts +218 -0
  31. package/extensions/shared/agent-transcript.ts +524 -0
  32. package/extensions/shared/capability-intent.ts +1 -1
  33. package/extensions/shared/child-session.ts +437 -21
  34. package/extensions/shared/result-delivery.ts +34 -0
  35. package/extensions/shared/setup-config.ts +73 -33
  36. package/extensions/shared/setup-episode-state.ts +1 -1
  37. package/extensions/shared/terminal-text.ts +110 -23
  38. package/extensions/shared/text-projection.ts +72 -15
  39. package/extensions/shared/tool-activity.ts +382 -0
  40. package/extensions/shared/tool-surface.ts +29 -2
  41. package/extensions/shared/transcript-viewport.ts +46 -0
  42. package/extensions/shared/web-observer-registry.ts +390 -0
  43. package/extensions/shared/worktree.ts +11 -0
  44. package/extensions/subagents/index.ts +270 -59
  45. package/extensions/subagents/navigation.ts +34 -5
  46. package/extensions/subagents/src/backend.ts +12 -1
  47. package/extensions/subagents/src/backends/pi.ts +375 -66
  48. package/extensions/subagents/src/domain.ts +5 -0
  49. package/extensions/subagents/src/manager.ts +34 -2
  50. package/extensions/subagents/src/prompt.ts +32 -4
  51. package/extensions/subagents/src/result-artifact.ts +4 -0
  52. package/extensions/subagents/src/result-delivery.ts +7 -1
  53. package/extensions/subagents/src/runtime.ts +15 -1
  54. package/extensions/subagents/src/ui/takeover.ts +73 -257
  55. package/extensions/subagents/src/ui/transcript.ts +38 -535
  56. package/extensions/subagents/src/ui/wait-result.ts +103 -15
  57. package/extensions/suggestions/src/ui.ts +10 -4
  58. package/extensions/tasks/index.ts +0 -3
  59. package/extensions/ui-customization/footer.ts +0 -40
  60. package/extensions/ui-customization/index.ts +0 -4
  61. package/extensions/user-input-fold/index.ts +1 -1
  62. package/extensions/web/index.ts +234 -0
  63. package/extensions/workflows/artifacts.ts +137 -47
  64. package/extensions/workflows/completion-projection.ts +457 -0
  65. package/extensions/workflows/coordinator.ts +8 -10
  66. package/extensions/workflows/dashboard.ts +167 -228
  67. package/extensions/workflows/handoff.ts +70 -16
  68. package/extensions/workflows/index.ts +488 -198
  69. package/extensions/workflows/journal.ts +148 -13
  70. package/extensions/workflows/model.ts +74 -4
  71. package/extensions/workflows/navigation.ts +32 -8
  72. package/extensions/workflows/progress-projection.ts +306 -0
  73. package/extensions/workflows/prompt.ts +66 -6
  74. package/extensions/workflows/replay-safety.ts +42 -21
  75. package/extensions/workflows/result-delivery.ts +128 -64
  76. package/extensions/workflows/retention.ts +593 -0
  77. package/extensions/workflows/runner.ts +388 -279
  78. package/extensions/workflows/sandbox-child.cjs +25 -3
  79. package/extensions/workflows/sandbox.ts +62 -8
  80. package/extensions/workflows/serialization.ts +325 -17
  81. package/extensions/workflows/tool-renderer.ts +22 -0
  82. package/extensions/workflows/transcript.ts +149 -0
  83. package/extensions/workspace-cleanup-guard/index.ts +54 -0
  84. package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
  85. package/package.json +28 -8
  86. package/skills/subagents/REFERENCE.md +189 -0
  87. package/skills/subagents/SKILL.md +1 -1
  88. package/skills/workflows/REFERENCE.md +4 -2
  89. package/web/adapter/pi-adapter.ts +661 -0
  90. package/web/host/browser-launcher.ts +20 -0
  91. package/web/host/static-assets.ts +4 -0
  92. package/web/host/terminal-status.ts +38 -0
  93. package/web/host/web-host.ts +789 -0
  94. package/web/http-dispatcher.ts +125 -0
  95. package/web/protocol/types.ts +462 -0
  96. package/web/runtime/pi-runtime.ts +991 -0
  97. package/web/runtime/types.ts +71 -0
  98. package/web/runtime/web-host-lease.ts +497 -0
  99. package/web/trace.ts +18 -0
  100. package/web/ui/app.js +1398 -0
  101. package/web/ui/index.html +139 -0
  102. package/web/ui/styles.css +598 -0
  103. package/web/vite.config.mjs +34 -0
  104. package/extensions/execution-convergence/active-evidence.ts +0 -129
  105. package/extensions/execution-convergence/index.ts +0 -442
  106. package/extensions/execution-convergence/workspace-provenance.ts +0 -338
  107. package/extensions/setup/intercom-fs-helper.cjs +0 -130
  108. package/extensions/setup/intercom.ts +0 -603
  109. package/extensions/subagents/src/backends/stub.ts +0 -303
@@ -1,32 +1,40 @@
1
- import {
2
- refreshWorkflowGraph,
3
- type TranscriptEntry,
4
- type WorkflowDetails,
5
- } from "./model.ts";
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
6
3
  import {
7
4
  boundedJournal,
8
- parseJournal,
5
+ JOURNAL_MAX_BYTES,
9
6
  type JournalEntry,
7
+ parseJournal,
10
8
  type WorkflowJournal,
9
+ type WorkflowJournalAccumulator,
11
10
  } from "./journal.ts";
12
11
  import {
12
+ refreshWorkflowGraph,
13
+ type TranscriptEntry,
14
+ type WorkflowDelivery,
15
+ type WorkflowDetails,
16
+ } from "./model.ts";
17
+ import {
18
+ encodeCompleteJson,
13
19
  safeStringify,
14
- toSerializable,
15
20
  truncateUtf8,
16
21
  writeFileAtomic,
17
22
  } from "./serialization.ts";
18
- import * as fs from "node:fs";
19
- import * as path from "node:path";
20
23
 
21
24
  export const JOURNAL_FILE = "journal.json";
22
25
 
23
26
  const ARTIFACT_TRANSCRIPT_MAX_BYTES = 32 * 1024;
24
27
  const ARTIFACT_TRANSCRIPT_ENTRY_MAX_BYTES = 8 * 1024;
28
+ const AGENT_RESULT_ARTIFACT_MAX_BYTES = 2 * 1024 * 1024;
25
29
  export const WORKFLOW_CHECKPOINT_INTERVAL_MS = 500;
26
30
  const ENTRY_TRUNCATION_MARKER = "\n[entry truncated]";
27
31
  const TRANSCRIPT_TRUNCATION_MARKER =
28
32
  "[artifact transcript truncated: older entries omitted]";
29
33
 
34
+ type WorkflowJournalSource =
35
+ | readonly JournalEntry[]
36
+ | WorkflowJournalAccumulator;
37
+
30
38
  function textBytes(text: string) {
31
39
  return Buffer.byteLength(text, "utf8");
32
40
  }
@@ -101,44 +109,61 @@ function writeRunFile(runDir: string, name: string, content: string) {
101
109
  writeFileAtomic(path.join(runDir, name), content);
102
110
  }
103
111
 
112
+ export function persistWorkflowTerminalState(
113
+ runDir: string,
114
+ details: WorkflowDetails,
115
+ ) {
116
+ const terminalManifest: WorkflowDetails = {
117
+ ...details,
118
+ agents: details.agents.map((agent) => ({ ...agent, transcript: [] })),
119
+ };
120
+ delete terminalManifest.result;
121
+ delete terminalManifest.resultArtifact;
122
+ delete terminalManifest.transcriptArtifact;
123
+ writeRunFile(
124
+ runDir,
125
+ "workflow.json",
126
+ safeStringify(terminalManifest, { maxBytes: 1024 * 1024 }),
127
+ );
128
+ }
129
+
104
130
  /** Persist one successful child result before any handoff/context projection. */
105
131
  export function persistWorkflowAgentResult(
106
132
  runDir: string,
107
133
  index: number,
108
134
  result: { output: string; structured?: unknown },
109
135
  ) {
110
- const artifact = path.join(
111
- "agent-results",
112
- `agent-${String(index).padStart(4, "0")}.json`,
113
- );
114
- writeRunFile(
115
- runDir,
116
- artifact,
117
- JSON.stringify(
118
- toSerializable(
119
- {
120
- output: result.output,
121
- ...(result.structured !== undefined
122
- ? { structured: result.structured }
123
- : {}),
124
- },
125
- {
126
- maxDepth: 32,
127
- maxNodes: 100_000,
128
- maxStringBytes: 2 * 1024 * 1024,
129
- },
130
- ),
131
- null,
132
- 2,
133
- ),
136
+ // Artifact references are persisted as portable workflow paths. The host
137
+ // filesystem join happens in writeRunFile; handoff validation and replay
138
+ // consumers intentionally use `/` regardless of the platform.
139
+ const artifact = `agent-results/agent-${String(index).padStart(4, "0")}.json`;
140
+ const encoded = encodeCompleteJson(
141
+ {
142
+ output: result.output,
143
+ ...(result.structured !== undefined
144
+ ? { structured: result.structured }
145
+ : {}),
146
+ },
147
+ {
148
+ maxBytes: AGENT_RESULT_ARTIFACT_MAX_BYTES,
149
+ maxDepth: 32,
150
+ maxNodes: 100_000,
151
+ maxStringBytes: AGENT_RESULT_ARTIFACT_MAX_BYTES,
152
+ },
134
153
  );
154
+ if (!encoded.ok) {
155
+ throw new Error(
156
+ `Agent result artifact exceeded the ${AGENT_RESULT_ARTIFACT_MAX_BYTES}-byte budget (${encoded.limit} limit at ${encoded.path})`,
157
+ );
158
+ }
159
+ writeRunFile(runDir, artifact, encoded.json);
135
160
  return artifact;
136
161
  }
137
162
 
138
163
  export function persistWorkflowJson(
139
164
  runDir: string,
140
165
  details: WorkflowDetails,
141
- journal?: readonly JournalEntry[],
166
+ journal?: WorkflowJournalSource,
142
167
  ) {
143
168
  refreshWorkflowGraph(details);
144
169
  const transcripts = Object.fromEntries(
@@ -147,6 +172,15 @@ export function persistWorkflowJson(
147
172
  boundedArtifactTranscript(agent.transcript),
148
173
  ]),
149
174
  );
175
+
176
+ // Publish terminal execution facts before dependent side artifacts. If a
177
+ // later artifact write fails, readers still see an explained terminal run
178
+ // instead of the previous `running` manifest. The final manifest below adds
179
+ // the artifact references once every dependent file has committed.
180
+ if (details.status !== "running") {
181
+ persistWorkflowTerminalState(runDir, details);
182
+ }
183
+
150
184
  writeRunFile(
151
185
  runDir,
152
186
  "transcripts.json",
@@ -154,15 +188,20 @@ export function persistWorkflowJson(
154
188
  );
155
189
  // Written alongside the rest so it inherits atomic write, 500ms coalescing,
156
190
  // and the final flush. Only present once a call has actually succeeded.
157
- // boundedJournal has already brought this under the cap, and plain
158
- // JSON.stringify is deliberate: safeStringify would swap an over-cap value
159
- // for a preview stub, silently turning the journal into something unusable.
160
- if (journal && journal.length > 0) {
161
- writeRunFile(
162
- runDir,
163
- JOURNAL_FILE,
164
- JSON.stringify(boundedJournal(journal).journal, null, 2),
165
- );
191
+ // Accumulators already enforce the cap incrementally and can assemble the
192
+ // canonical JSON from their cached entry fragments. Plain arrays retain the
193
+ // compatibility path through boundedJournal; plain JSON.stringify is
194
+ // deliberate because safeStringify would swap an over-cap value for a
195
+ // preview stub, silently turning the journal into something unusable.
196
+ if (
197
+ journal &&
198
+ (journal.length > 0 || ("toJson" in journal && journal.dropped > 0))
199
+ ) {
200
+ const content =
201
+ "toJson" in journal
202
+ ? journal.toJson()
203
+ : JSON.stringify(boundedJournal(journal).journal, null, 2);
204
+ writeRunFile(runDir, JOURNAL_FILE, content);
166
205
  }
167
206
  if (details.result !== undefined) {
168
207
  writeRunFile(
@@ -186,6 +225,25 @@ export function persistWorkflowJson(
186
225
  );
187
226
  }
188
227
 
228
+ /**
229
+ * Update only the durable delivery receipt. Completion delivery may retain a
230
+ * compact memory projection after the run has been evicted, so rewriting the
231
+ * whole details object here would risk replacing exact result artifacts with
232
+ * that projection.
233
+ */
234
+ export function persistWorkflowDeliveryState(
235
+ runDir: string,
236
+ delivery: WorkflowDelivery,
237
+ ) {
238
+ const file = path.join(runDir, "workflow.json");
239
+ const raw: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
240
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
241
+ throw new Error(`Invalid persisted workflow details: ${file}`);
242
+ }
243
+ const next = { ...(raw as Record<string, unknown>), delivery };
244
+ writeFileAtomic(file, JSON.stringify(next));
245
+ }
246
+
189
247
  /** Coalesce live checkpoints while keeping final persistence synchronous. */
190
248
  export function createWorkflowPersistence(
191
249
  runDir: string,
@@ -195,10 +253,10 @@ export function createWorkflowPersistence(
195
253
  persist?: (
196
254
  runDir: string,
197
255
  details: WorkflowDetails,
198
- journal?: readonly JournalEntry[],
256
+ journal?: WorkflowJournalSource,
199
257
  ) => void;
200
- /** Read at write time so callers only have to append to their own array. */
201
- journal?: () => readonly JournalEntry[];
258
+ /** Read at write time so callers only have to append to their journal. */
259
+ journal?: () => WorkflowJournalSource;
202
260
  } = {},
203
261
  ) {
204
262
  const intervalMs = Math.max(
@@ -256,10 +314,42 @@ export function createWorkflowPersistence(
256
314
  * turn into a new way for a run to fail.
257
315
  */
258
316
  export function loadJournal(runDir: string): WorkflowJournal | undefined {
317
+ let descriptor: number | undefined;
259
318
  try {
260
- const raw = fs.readFileSync(path.join(runDir, JOURNAL_FILE), "utf8");
319
+ descriptor = fs.openSync(path.join(runDir, JOURNAL_FILE), "r");
320
+ const initialSize = fs.fstatSync(descriptor).size;
321
+ if (initialSize > JOURNAL_MAX_BYTES) return undefined;
322
+
323
+ // Read at most one byte beyond the cap. The descriptor pins the inspected
324
+ // file, while the extra byte catches growth after fstat without ever
325
+ // allocating or parsing an unbounded journal.
326
+ const bytes = Buffer.allocUnsafe(initialSize + 1);
327
+ let length = 0;
328
+ while (length < bytes.length) {
329
+ const read = fs.readSync(
330
+ descriptor,
331
+ bytes,
332
+ length,
333
+ bytes.length - length,
334
+ null,
335
+ );
336
+ if (read === 0) break;
337
+ length += read;
338
+ }
339
+ // Filling the extra byte means the file changed after inspection. Reject
340
+ // that race even when the new size would still fit the ordinary cap.
341
+ if (length === bytes.length) return undefined;
342
+ const raw = bytes.toString("utf8", 0, length);
261
343
  return parseJournal(JSON.parse(raw));
262
344
  } catch {
263
345
  return undefined;
346
+ } finally {
347
+ if (descriptor !== undefined) {
348
+ try {
349
+ fs.closeSync(descriptor);
350
+ } catch {
351
+ // Loading a replay cache is optional and always fails open to a miss.
352
+ }
353
+ }
264
354
  }
265
355
  }