@tt-a1i/openpi 0.6.1 → 0.7.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.
- package/THIRD_PARTY_NOTICES.md +242 -0
- package/extensions/ai-providers/cursor/connect-frame-reader.ts +76 -0
- package/extensions/ai-providers/cursor/provider.ts +6 -19
- package/extensions/file-mutation-display/index.ts +13 -9
- package/extensions/shared/agent-transcript.ts +3 -2
- package/extensions/web/index.ts +69 -5
- package/extensions/workflows/artifacts.ts +362 -23
- package/extensions/workflows/dashboard.ts +2 -0
- package/package.json +28 -4
- package/web/dist/app.js +87 -0
- package/web/dist/favicon.svg +9 -0
- package/web/dist/index.html +15 -0
- package/web/dist/styles.css +3 -0
- package/web/host/web-host.ts +6 -9
- package/web/ui/index.html +3 -131
- package/web/ui/public/favicon.svg +9 -0
- package/web/ui/src/app/App.tsx +134 -0
- package/web/ui/src/app/providers.tsx +38 -0
- package/web/ui/src/components/Markdown.tsx +58 -0
- package/web/ui/src/components/OpenPiLogo.tsx +41 -0
- package/web/ui/src/features/activity/ActivityBar.tsx +120 -0
- package/web/ui/src/features/composer/Composer.tsx +237 -0
- package/web/ui/src/features/sessions/SessionSidebar.tsx +418 -0
- package/web/ui/src/features/transcript/Transcript.tsx +860 -0
- package/web/ui/src/i18n.ts +159 -0
- package/web/ui/src/lib/format.ts +57 -0
- package/web/ui/src/main.tsx +16 -0
- package/web/ui/src/protocol/client.ts +199 -0
- package/web/ui/src/protocol/event-stream.ts +88 -0
- package/web/ui/src/store/web-store.ts +926 -0
- package/web/ui/src/styles.css +420 -0
- package/web/ui/tsconfig.json +12 -0
- package/web/ui/vite-env.d.ts +1 -0
- package/web/vite.config.mjs +21 -1
- package/web/host/static-assets.ts +0 -4
- package/web/ui/app.js +0 -1700
- package/web/ui/styles.css +0 -680
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
1
2
|
import * as fs from "node:fs";
|
|
2
3
|
import * as path from "node:path";
|
|
3
4
|
import {
|
|
@@ -22,10 +23,14 @@ import {
|
|
|
22
23
|
} from "./serialization.ts";
|
|
23
24
|
|
|
24
25
|
export const JOURNAL_FILE = "journal.json";
|
|
26
|
+
export const WORKFLOW_COMMIT_FILE = ".workflow-commit.json";
|
|
25
27
|
|
|
26
28
|
const ARTIFACT_TRANSCRIPT_MAX_BYTES = 32 * 1024;
|
|
27
29
|
const ARTIFACT_TRANSCRIPT_ENTRY_MAX_BYTES = 8 * 1024;
|
|
28
30
|
const AGENT_RESULT_ARTIFACT_MAX_BYTES = 2 * 1024 * 1024;
|
|
31
|
+
const WORKFLOW_MANIFEST_MAX_BYTES = 1024 * 1024;
|
|
32
|
+
const WORKFLOW_TRANSCRIPTS_MAX_BYTES = 2 * 1024 * 1024;
|
|
33
|
+
const WORKFLOW_COMMIT_MAX_BYTES = 3 * 1024 * 1024;
|
|
29
34
|
export const WORKFLOW_CHECKPOINT_INTERVAL_MS = 500;
|
|
30
35
|
const ENTRY_TRUNCATION_MARKER = "\n[entry truncated]";
|
|
31
36
|
const TRANSCRIPT_TRUNCATION_MARKER =
|
|
@@ -35,10 +40,317 @@ type WorkflowJournalSource =
|
|
|
35
40
|
| readonly JournalEntry[]
|
|
36
41
|
| WorkflowJournalAccumulator;
|
|
37
42
|
|
|
43
|
+
interface WorkflowArtifactWrite {
|
|
44
|
+
name: typeof JOURNAL_FILE | "result.json" | "transcripts.json";
|
|
45
|
+
content: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface WorkflowCommitArtifact {
|
|
49
|
+
name: WorkflowArtifactWrite["name"];
|
|
50
|
+
bytes: number;
|
|
51
|
+
sha256: string;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
interface WorkflowCommitMarker {
|
|
55
|
+
version: 1;
|
|
56
|
+
runId: string;
|
|
57
|
+
manifest: string;
|
|
58
|
+
predecessorSha256: string;
|
|
59
|
+
artifacts: WorkflowCommitArtifact[];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type WorkflowCommitRecovery =
|
|
63
|
+
| "none"
|
|
64
|
+
| "recovered"
|
|
65
|
+
| "already-committed"
|
|
66
|
+
| "incomplete"
|
|
67
|
+
| "invalid"
|
|
68
|
+
| "failed";
|
|
69
|
+
|
|
70
|
+
const artifactLimits = new Map<WorkflowArtifactWrite["name"], number>([
|
|
71
|
+
["transcripts.json", WORKFLOW_TRANSCRIPTS_MAX_BYTES],
|
|
72
|
+
["result.json", WORKFLOW_MANIFEST_MAX_BYTES],
|
|
73
|
+
[JOURNAL_FILE, JOURNAL_MAX_BYTES],
|
|
74
|
+
]);
|
|
75
|
+
|
|
38
76
|
function textBytes(text: string) {
|
|
39
77
|
return Buffer.byteLength(text, "utf8");
|
|
40
78
|
}
|
|
41
79
|
|
|
80
|
+
function sha256(content: string | Buffer) {
|
|
81
|
+
return createHash("sha256").update(content).digest("hex");
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function removeWorkflowCommit(runDir: string, strict = false) {
|
|
85
|
+
try {
|
|
86
|
+
fs.unlinkSync(path.join(runDir, WORKFLOW_COMMIT_FILE));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return;
|
|
89
|
+
if (strict) throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function workflowCommitMarker(
|
|
94
|
+
details: WorkflowDetails,
|
|
95
|
+
manifest: string,
|
|
96
|
+
artifacts: WorkflowArtifactWrite[],
|
|
97
|
+
predecessorSha256: string,
|
|
98
|
+
): WorkflowCommitMarker {
|
|
99
|
+
for (const { name, content } of artifacts) {
|
|
100
|
+
const bytes = textBytes(content);
|
|
101
|
+
const limit = artifactLimits.get(name);
|
|
102
|
+
if (limit === undefined || bytes > limit) {
|
|
103
|
+
throw new Error(`Workflow artifact ${name} exceeded its commit budget`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
version: 1,
|
|
108
|
+
runId: details.runId,
|
|
109
|
+
manifest,
|
|
110
|
+
predecessorSha256,
|
|
111
|
+
artifacts: artifacts.map(({ name, content }) => ({
|
|
112
|
+
name,
|
|
113
|
+
bytes: textBytes(content),
|
|
114
|
+
sha256: sha256(content),
|
|
115
|
+
})),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function serializeWorkflowCommitMarker(
|
|
120
|
+
details: WorkflowDetails,
|
|
121
|
+
manifest: string,
|
|
122
|
+
artifacts: WorkflowArtifactWrite[],
|
|
123
|
+
predecessorSha256: string,
|
|
124
|
+
) {
|
|
125
|
+
const content = JSON.stringify(
|
|
126
|
+
workflowCommitMarker(details, manifest, artifacts, predecessorSha256),
|
|
127
|
+
);
|
|
128
|
+
if (textBytes(content) > WORKFLOW_COMMIT_MAX_BYTES) {
|
|
129
|
+
throw new Error(
|
|
130
|
+
"Workflow artifact commit receipt exceeded its byte budget",
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return content;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function parseWorkflowCommitMarker(
|
|
137
|
+
runDir: string,
|
|
138
|
+
): WorkflowCommitMarker | "none" | "invalid" {
|
|
139
|
+
const markerPath = path.join(runDir, WORKFLOW_COMMIT_FILE);
|
|
140
|
+
let stat: fs.Stats;
|
|
141
|
+
try {
|
|
142
|
+
stat = fs.lstatSync(markerPath);
|
|
143
|
+
} catch (error) {
|
|
144
|
+
return (error as NodeJS.ErrnoException).code === "ENOENT"
|
|
145
|
+
? "none"
|
|
146
|
+
: "invalid";
|
|
147
|
+
}
|
|
148
|
+
if (
|
|
149
|
+
!stat.isFile() ||
|
|
150
|
+
stat.isSymbolicLink() ||
|
|
151
|
+
stat.size <= 0 ||
|
|
152
|
+
stat.size > WORKFLOW_COMMIT_MAX_BYTES
|
|
153
|
+
) {
|
|
154
|
+
return "invalid";
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let raw: unknown;
|
|
158
|
+
try {
|
|
159
|
+
raw = JSON.parse(fs.readFileSync(markerPath, "utf8"));
|
|
160
|
+
} catch {
|
|
161
|
+
return "invalid";
|
|
162
|
+
}
|
|
163
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return "invalid";
|
|
164
|
+
const record = raw as Record<string, unknown>;
|
|
165
|
+
if (
|
|
166
|
+
record.version !== 1 ||
|
|
167
|
+
typeof record.runId !== "string" ||
|
|
168
|
+
record.runId !== path.basename(runDir) ||
|
|
169
|
+
typeof record.predecessorSha256 !== "string" ||
|
|
170
|
+
!/^[0-9a-f]{64}$/u.test(record.predecessorSha256) ||
|
|
171
|
+
typeof record.manifest !== "string" ||
|
|
172
|
+
textBytes(record.manifest) > WORKFLOW_MANIFEST_MAX_BYTES ||
|
|
173
|
+
!Array.isArray(record.artifacts) ||
|
|
174
|
+
record.artifacts.length < 1 ||
|
|
175
|
+
record.artifacts.length > artifactLimits.size
|
|
176
|
+
) {
|
|
177
|
+
return "invalid";
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
let manifest: Record<string, unknown>;
|
|
181
|
+
try {
|
|
182
|
+
const parsed: unknown = JSON.parse(record.manifest);
|
|
183
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
184
|
+
return "invalid";
|
|
185
|
+
}
|
|
186
|
+
manifest = parsed as Record<string, unknown>;
|
|
187
|
+
} catch {
|
|
188
|
+
return "invalid";
|
|
189
|
+
}
|
|
190
|
+
if (
|
|
191
|
+
manifest.runId !== record.runId ||
|
|
192
|
+
!["completed", "failed", "aborted", "uncertain"].includes(
|
|
193
|
+
String(manifest.status),
|
|
194
|
+
) ||
|
|
195
|
+
manifest.transcriptArtifact !== "transcripts.json" ||
|
|
196
|
+
(manifest.resultArtifact !== undefined &&
|
|
197
|
+
manifest.resultArtifact !== "result.json")
|
|
198
|
+
) {
|
|
199
|
+
return "invalid";
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const artifacts: WorkflowCommitArtifact[] = [];
|
|
203
|
+
const names = new Set<string>();
|
|
204
|
+
for (const value of record.artifacts) {
|
|
205
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
206
|
+
return "invalid";
|
|
207
|
+
}
|
|
208
|
+
const artifact = value as Record<string, unknown>;
|
|
209
|
+
if (
|
|
210
|
+
typeof artifact.name !== "string" ||
|
|
211
|
+
!artifactLimits.has(artifact.name as WorkflowArtifactWrite["name"]) ||
|
|
212
|
+
names.has(artifact.name) ||
|
|
213
|
+
typeof artifact.bytes !== "number" ||
|
|
214
|
+
!Number.isInteger(artifact.bytes) ||
|
|
215
|
+
artifact.bytes < 0 ||
|
|
216
|
+
artifact.bytes >
|
|
217
|
+
(artifactLimits.get(artifact.name as WorkflowArtifactWrite["name"]) ??
|
|
218
|
+
-1) ||
|
|
219
|
+
typeof artifact.sha256 !== "string" ||
|
|
220
|
+
!/^[0-9a-f]{64}$/u.test(artifact.sha256)
|
|
221
|
+
) {
|
|
222
|
+
return "invalid";
|
|
223
|
+
}
|
|
224
|
+
names.add(artifact.name);
|
|
225
|
+
artifacts.push({
|
|
226
|
+
name: artifact.name as WorkflowArtifactWrite["name"],
|
|
227
|
+
bytes: artifact.bytes,
|
|
228
|
+
sha256: artifact.sha256,
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
if (
|
|
232
|
+
!names.has("transcripts.json") ||
|
|
233
|
+
(manifest.resultArtifact === "result.json") !== names.has("result.json")
|
|
234
|
+
) {
|
|
235
|
+
return "invalid";
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
version: 1,
|
|
239
|
+
runId: record.runId,
|
|
240
|
+
manifest: record.manifest,
|
|
241
|
+
predecessorSha256: record.predecessorSha256,
|
|
242
|
+
artifacts,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function hasCommittedManifest(
|
|
247
|
+
manifestPath: string,
|
|
248
|
+
marker: WorkflowCommitMarker,
|
|
249
|
+
) {
|
|
250
|
+
try {
|
|
251
|
+
const stat = fs.lstatSync(manifestPath);
|
|
252
|
+
if (
|
|
253
|
+
!stat.isFile() ||
|
|
254
|
+
stat.isSymbolicLink() ||
|
|
255
|
+
stat.size > WORKFLOW_MANIFEST_MAX_BYTES
|
|
256
|
+
)
|
|
257
|
+
return false;
|
|
258
|
+
const content = fs.readFileSync(manifestPath);
|
|
259
|
+
if (content.byteLength > WORKFLOW_MANIFEST_MAX_BYTES) return false;
|
|
260
|
+
const parsed: unknown = JSON.parse(content.toString("utf8"));
|
|
261
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const manifest = parsed as Record<string, unknown>;
|
|
265
|
+
const markerManifest = JSON.parse(marker.manifest) as Record<
|
|
266
|
+
string,
|
|
267
|
+
unknown
|
|
268
|
+
>;
|
|
269
|
+
return (
|
|
270
|
+
manifest.runId === marker.runId &&
|
|
271
|
+
manifest.status === markerManifest.status &&
|
|
272
|
+
manifest.transcriptArtifact === markerManifest.transcriptArtifact &&
|
|
273
|
+
manifest.resultArtifact === markerManifest.resultArtifact
|
|
274
|
+
);
|
|
275
|
+
} catch {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
/**
|
|
281
|
+
* Complete a terminal artifact commit only when every prepared file matches
|
|
282
|
+
* the exact bounded receipt written before the side-artifact sequence began.
|
|
283
|
+
*/
|
|
284
|
+
export function recoverPendingWorkflowCommit(
|
|
285
|
+
runDir: string,
|
|
286
|
+
): WorkflowCommitRecovery {
|
|
287
|
+
const marker = parseWorkflowCommitMarker(runDir);
|
|
288
|
+
if (marker === "none" || marker === "invalid") return marker;
|
|
289
|
+
|
|
290
|
+
for (const artifact of marker.artifacts) {
|
|
291
|
+
const artifactPath = path.join(runDir, artifact.name);
|
|
292
|
+
let stat: fs.Stats;
|
|
293
|
+
let content: Buffer;
|
|
294
|
+
try {
|
|
295
|
+
stat = fs.lstatSync(artifactPath);
|
|
296
|
+
if (
|
|
297
|
+
!stat.isFile() ||
|
|
298
|
+
stat.isSymbolicLink() ||
|
|
299
|
+
stat.size !== artifact.bytes
|
|
300
|
+
) {
|
|
301
|
+
return "incomplete";
|
|
302
|
+
}
|
|
303
|
+
content = fs.readFileSync(artifactPath);
|
|
304
|
+
} catch {
|
|
305
|
+
return "incomplete";
|
|
306
|
+
}
|
|
307
|
+
if (
|
|
308
|
+
content.byteLength !== artifact.bytes ||
|
|
309
|
+
sha256(content) !== artifact.sha256
|
|
310
|
+
) {
|
|
311
|
+
return "incomplete";
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const manifestPath = path.join(runDir, "workflow.json");
|
|
316
|
+
try {
|
|
317
|
+
if (
|
|
318
|
+
fs.existsSync(manifestPath) &&
|
|
319
|
+
hasCommittedManifest(manifestPath, marker)
|
|
320
|
+
) {
|
|
321
|
+
removeWorkflowCommit(runDir);
|
|
322
|
+
return "already-committed";
|
|
323
|
+
}
|
|
324
|
+
// A later terminal/cleanup/delivery publication supersedes this receipt.
|
|
325
|
+
// Artifact validity alone cannot authorize replacing canonical facts.
|
|
326
|
+
let predecessor: Buffer;
|
|
327
|
+
try {
|
|
328
|
+
const stat = fs.lstatSync(manifestPath);
|
|
329
|
+
if (
|
|
330
|
+
!stat.isFile() ||
|
|
331
|
+
stat.isSymbolicLink() ||
|
|
332
|
+
stat.size > WORKFLOW_MANIFEST_MAX_BYTES
|
|
333
|
+
) {
|
|
334
|
+
return "invalid";
|
|
335
|
+
}
|
|
336
|
+
predecessor = fs.readFileSync(manifestPath);
|
|
337
|
+
} catch {
|
|
338
|
+
return "invalid";
|
|
339
|
+
}
|
|
340
|
+
if (
|
|
341
|
+
predecessor.byteLength > WORKFLOW_MANIFEST_MAX_BYTES ||
|
|
342
|
+
sha256(predecessor) !== marker.predecessorSha256
|
|
343
|
+
) {
|
|
344
|
+
return "invalid";
|
|
345
|
+
}
|
|
346
|
+
writeFileAtomic(manifestPath, marker.manifest);
|
|
347
|
+
removeWorkflowCommit(runDir);
|
|
348
|
+
return "recovered";
|
|
349
|
+
} catch {
|
|
350
|
+
return "failed";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
42
354
|
function boundEntry(entry: TranscriptEntry, maxBytes: number) {
|
|
43
355
|
if (textBytes(entry.text) <= maxBytes) return { ...entry };
|
|
44
356
|
const markerBytes = textBytes(ENTRY_TRUNCATION_MARKER);
|
|
@@ -120,11 +432,11 @@ export function persistWorkflowTerminalState(
|
|
|
120
432
|
delete terminalManifest.result;
|
|
121
433
|
delete terminalManifest.resultArtifact;
|
|
122
434
|
delete terminalManifest.transcriptArtifact;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
);
|
|
435
|
+
const content = safeStringify(terminalManifest, {
|
|
436
|
+
maxBytes: WORKFLOW_MANIFEST_MAX_BYTES,
|
|
437
|
+
});
|
|
438
|
+
writeRunFile(runDir, "workflow.json", content);
|
|
439
|
+
return sha256(content);
|
|
128
440
|
}
|
|
129
441
|
|
|
130
442
|
/** Persist one successful child result before any handoff/context projection. */
|
|
@@ -177,15 +489,24 @@ export function persistWorkflowJson(
|
|
|
177
489
|
// later artifact write fails, readers still see an explained terminal run
|
|
178
490
|
// instead of the previous `running` manifest. The final manifest below adds
|
|
179
491
|
// the artifact references once every dependent file has committed.
|
|
180
|
-
|
|
181
|
-
|
|
492
|
+
const terminal = details.status !== "running";
|
|
493
|
+
let predecessorSha256: string | undefined;
|
|
494
|
+
if (terminal) {
|
|
495
|
+
// A retry supersedes an older unfinished receipt before it publishes a new
|
|
496
|
+
// terminal fact. Failing to remove it must stop the new commit rather than
|
|
497
|
+
// let a concurrent reader promote stale artifact identities.
|
|
498
|
+
removeWorkflowCommit(runDir, true);
|
|
499
|
+
predecessorSha256 = persistWorkflowTerminalState(runDir, details);
|
|
182
500
|
}
|
|
183
501
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
502
|
+
const artifactWrites: WorkflowArtifactWrite[] = [
|
|
503
|
+
{
|
|
504
|
+
name: "transcripts.json",
|
|
505
|
+
content: safeStringify(transcripts, {
|
|
506
|
+
maxBytes: WORKFLOW_TRANSCRIPTS_MAX_BYTES,
|
|
507
|
+
}),
|
|
508
|
+
},
|
|
509
|
+
];
|
|
189
510
|
// Written alongside the rest so it inherits atomic write, 500ms coalescing,
|
|
190
511
|
// and the final flush. Only present once a call has actually succeeded.
|
|
191
512
|
// Accumulators already enforce the cap incrementally and can assemble the
|
|
@@ -201,14 +522,15 @@ export function persistWorkflowJson(
|
|
|
201
522
|
"toJson" in journal
|
|
202
523
|
? journal.toJson()
|
|
203
524
|
: JSON.stringify(boundedJournal(journal).journal, null, 2);
|
|
204
|
-
|
|
525
|
+
artifactWrites.push({ name: JOURNAL_FILE, content });
|
|
205
526
|
}
|
|
206
527
|
if (details.result !== undefined) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
528
|
+
artifactWrites.push({
|
|
529
|
+
name: "result.json",
|
|
530
|
+
content: safeStringify(details.result, {
|
|
531
|
+
maxBytes: WORKFLOW_MANIFEST_MAX_BYTES,
|
|
532
|
+
}),
|
|
533
|
+
});
|
|
212
534
|
}
|
|
213
535
|
const compact: WorkflowDetails = {
|
|
214
536
|
...details,
|
|
@@ -218,11 +540,27 @@ export function persistWorkflowJson(
|
|
|
218
540
|
transcriptArtifact: "transcripts.json",
|
|
219
541
|
agents: details.agents.map((agent) => ({ ...agent, transcript: [] })),
|
|
220
542
|
};
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
)
|
|
543
|
+
const manifest = safeStringify(compact, {
|
|
544
|
+
maxBytes: WORKFLOW_MANIFEST_MAX_BYTES,
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
if (predecessorSha256 !== undefined) {
|
|
548
|
+
writeRunFile(
|
|
549
|
+
runDir,
|
|
550
|
+
WORKFLOW_COMMIT_FILE,
|
|
551
|
+
serializeWorkflowCommitMarker(
|
|
552
|
+
details,
|
|
553
|
+
manifest,
|
|
554
|
+
artifactWrites,
|
|
555
|
+
predecessorSha256,
|
|
556
|
+
),
|
|
557
|
+
);
|
|
558
|
+
}
|
|
559
|
+
for (const artifact of artifactWrites) {
|
|
560
|
+
writeRunFile(runDir, artifact.name, artifact.content);
|
|
561
|
+
}
|
|
562
|
+
writeRunFile(runDir, "workflow.json", manifest);
|
|
563
|
+
if (terminal) removeWorkflowCommit(runDir);
|
|
226
564
|
}
|
|
227
565
|
|
|
228
566
|
/**
|
|
@@ -235,6 +573,7 @@ export function persistWorkflowDeliveryState(
|
|
|
235
573
|
runDir: string,
|
|
236
574
|
delivery: WorkflowDelivery,
|
|
237
575
|
) {
|
|
576
|
+
recoverPendingWorkflowCommit(runDir);
|
|
238
577
|
const file = path.join(runDir, "workflow.json");
|
|
239
578
|
const raw: unknown = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
240
579
|
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
import { SPINNER_INTERVAL_MS, spinnerFrame } from "../shared/spinner.ts";
|
|
32
32
|
import { sanitizeTerminalText } from "../shared/terminal-text.ts";
|
|
33
33
|
import { isAcceptanceLedger } from "./acceptance.ts";
|
|
34
|
+
import { recoverPendingWorkflowCommit } from "./artifacts.ts";
|
|
34
35
|
import { projectWorkflowGraph } from "./graph-projection.ts";
|
|
35
36
|
import {
|
|
36
37
|
classifyInterruptedInvocation,
|
|
@@ -161,6 +162,7 @@ function normalizeReadRecord(runId: string, raw: unknown) {
|
|
|
161
162
|
}
|
|
162
163
|
|
|
163
164
|
function readPersistedWorkflowRecord(runId: string) {
|
|
165
|
+
recoverPendingWorkflowCommit(path.join(runsDir(), runId));
|
|
164
166
|
try {
|
|
165
167
|
const raw: unknown = JSON.parse(
|
|
166
168
|
fs.readFileSync(path.join(runsDir(), runId, "workflow.json"), "utf8"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tt-a1i/openpi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "OpenPI — a Pi-native multi-agent workbench with background execution, isolated subagents, replay-safe workflows, goals, tasks, and observable TUI",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "tt-a1i",
|
|
@@ -58,21 +58,43 @@
|
|
|
58
58
|
"acorn": "^8.17.0",
|
|
59
59
|
"effect": "^4.0.0-beta.99",
|
|
60
60
|
"jiti": "2.7.0",
|
|
61
|
-
"marked": "^18.0.5",
|
|
62
61
|
"undici": "8.9.0"
|
|
63
62
|
},
|
|
64
63
|
"devDependencies": {
|
|
64
|
+
"@astryxdesign/core": "0.5.2",
|
|
65
|
+
"@astryxdesign/theme-neutral": "0.5.2",
|
|
66
|
+
"@axe-core/playwright": "4.13.0",
|
|
65
67
|
"@biomejs/biome": "2.5.8",
|
|
66
68
|
"@earendil-works/pi-ai": "^0.85.1",
|
|
67
69
|
"@earendil-works/pi-coding-agent": "^0.85.1",
|
|
68
70
|
"@earendil-works/pi-tui": "^0.85.1",
|
|
69
71
|
"@effect/tsgo": "^0.24.2",
|
|
70
72
|
"@effect/vitest": "^4.0.0-beta.99",
|
|
73
|
+
"@playwright/test": "1.62.1",
|
|
74
|
+
"@stylexjs/stylex": "0.19.0",
|
|
75
|
+
"@tailwindcss/vite": "4.3.3",
|
|
76
|
+
"@testing-library/react": "16.3.3",
|
|
71
77
|
"@types/node": "^26.1.1",
|
|
78
|
+
"@types/react": "19.2.18",
|
|
79
|
+
"@types/react-dom": "19.2.5",
|
|
80
|
+
"@vitejs/plugin-react": "6.1.1",
|
|
81
|
+
"eventsource-parser": "4.1.0",
|
|
82
|
+
"i18next": "26.4.1",
|
|
83
|
+
"jsdom": "30.0.1",
|
|
84
|
+
"lucide-react": "1.39.0",
|
|
85
|
+
"react": "19.2.8",
|
|
86
|
+
"react-dom": "19.2.8",
|
|
87
|
+
"react-i18next": "17.0.13",
|
|
88
|
+
"react-markdown": "10.1.0",
|
|
89
|
+
"rehype-sanitize": "6.0.0",
|
|
90
|
+
"remark-breaks": "4.0.0",
|
|
91
|
+
"remark-gfm": "4.0.1",
|
|
92
|
+
"tailwindcss": "4.3.3",
|
|
72
93
|
"typebox": "^1.3.6",
|
|
73
94
|
"typescript": "^7.0.2",
|
|
74
95
|
"vite": "^8.2.0",
|
|
75
|
-
"vitest": "4.1.10"
|
|
96
|
+
"vitest": "4.1.10",
|
|
97
|
+
"zustand": "5.0.15"
|
|
76
98
|
},
|
|
77
99
|
"peerDependencies": {
|
|
78
100
|
"@earendil-works/pi-ai": ">=0.85.1",
|
|
@@ -98,7 +120,9 @@
|
|
|
98
120
|
"dev:web": "node --experimental-strip-types scripts/dev-web.mjs",
|
|
99
121
|
"dev:web:ui": "vite --config web/vite.config.mjs --host 127.0.0.1",
|
|
100
122
|
"dev:web:backend": "node scripts/dev-web-backend.mjs",
|
|
101
|
-
"
|
|
123
|
+
"build:web": "vite build --config web/vite.config.mjs",
|
|
124
|
+
"check:web": "node --check scripts/dev-web.mjs && node --check web/vite.config.mjs && tsc --noEmit --project web/ui/tsconfig.json && bun run build:web",
|
|
125
|
+
"test:web:e2e": "bun run build:web && playwright test --config tests/web/playwright.config.ts",
|
|
102
126
|
"benchmark:workflow-child-startup": "node --experimental-strip-types scripts/benchmark-workflow-child-startup.mjs",
|
|
103
127
|
"test": "node scripts/run-tests.mjs",
|
|
104
128
|
"provenance": "node scripts/provenance.mjs"
|