@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
@@ -0,0 +1,457 @@
1
+ import { allocateResultBudgets } from "../shared/result-budget.ts";
2
+ import { sanitizeTerminalText } from "../shared/terminal-text.ts";
3
+ import { projectText } from "../shared/text-projection.ts";
4
+ import {
5
+ countStates,
6
+ formatElapsed,
7
+ resultJson,
8
+ sanitizeWorkflowDisplayLine,
9
+ shortenHome,
10
+ statusWord,
11
+ type WorkflowDetails,
12
+ type WorkflowLogEntry,
13
+ type WorkflowStatus,
14
+ } from "./model.ts";
15
+ import { safeStringify } from "./serialization.ts";
16
+
17
+ const MAX_DISPLAY_ENTRIES = 64;
18
+ const MAX_DISPLAY_BYTES = 64 * 1024;
19
+ const MAX_EXPANDED_ENTRY_BYTES = 48 * 1024;
20
+ const MAX_ALERTS = 16;
21
+ const MAX_FIELD_BYTES = 2 * 1024;
22
+
23
+ export interface WorkflowCompletionSourceEntry {
24
+ deliveryId: string;
25
+ details: WorkflowDetails;
26
+ runDir: string;
27
+ }
28
+
29
+ /** Bounded operator-facing facts. Runtime state remains in artifacts/model context. */
30
+ export interface WorkflowCompletionDisplayEntry {
31
+ deliveryId: string;
32
+ runId: string;
33
+ status: WorkflowStatus;
34
+ summary: string;
35
+ alerts: string[];
36
+ resultPreview?: string;
37
+ expanded: string;
38
+ }
39
+
40
+ export interface WorkflowCompletionDisplay {
41
+ version: 1;
42
+ /** Backward-compatible single-run identity for delivery observers. */
43
+ runId?: string;
44
+ entries: WorkflowCompletionDisplayEntry[];
45
+ omittedEntries?: number;
46
+ }
47
+
48
+ function boundedLine(value: string, maxBytes = MAX_FIELD_BYTES) {
49
+ return projectText(sanitizeWorkflowDisplayLine(value), {
50
+ maxBytes,
51
+ maxLines: 1,
52
+ recovery: "",
53
+ });
54
+ }
55
+
56
+ function labels(details: WorkflowDetails, state: "error" | "uncertain") {
57
+ return details.agents
58
+ .filter((agent) => agent.state === state)
59
+ .map((agent) => sanitizeWorkflowDisplayLine(agent.label));
60
+ }
61
+
62
+ function completionSummary(details: WorkflowDetails) {
63
+ const { done, failed } = countStates(details);
64
+ const elapsed = formatElapsed(details.startedAt, details.finishedAt);
65
+ return boundedLine(
66
+ `workflow ${details.name ?? details.runId} · ${done + failed}/${details.agents.length} agents · ${elapsed} · ${statusWord(details.status)}`,
67
+ );
68
+ }
69
+
70
+ function isDroppedWorkLog(entry: WorkflowLogEntry) {
71
+ if (entry.kind === "pipeline-drop") return true;
72
+ const text = entry.text;
73
+ const negated =
74
+ /\b(?:no|none|nothing|not|zero|0)\b.{0,48}\b(?:dropped|discarded|omitted)\b/iu.test(
75
+ text,
76
+ ) ||
77
+ /(?:没有|并未|未曾|未|无|零(?:个|项)?).{0,24}(?:丢弃|丢失|遗漏)/u.test(
78
+ text,
79
+ );
80
+ if (negated) return false;
81
+ return (
82
+ /\b(?:dropped|discarded|omitted)\b/iu.test(text) ||
83
+ /(?:被)?(?:丢弃|丢失|遗漏)/u.test(text)
84
+ );
85
+ }
86
+
87
+ /** Exceptional evidence that must remain visible while the report is collapsed. */
88
+ function completionAlerts(details: WorkflowDetails) {
89
+ const alerts: string[] = [];
90
+ if (details.error) alerts.push(`Error: ${details.error}`);
91
+
92
+ const failed = labels(details, "error");
93
+ if (failed.length > 0) alerts.push(`Failed agents: ${failed.join(", ")}`);
94
+ const uncertain = labels(details, "uncertain");
95
+ if (uncertain.length > 0) {
96
+ alerts.push(`Uncertain agents: ${uncertain.join(", ")}`);
97
+ }
98
+ if (details.logsDropped) {
99
+ alerts.push(`${details.logsDropped} earlier log line(s) dropped`);
100
+ }
101
+
102
+ for (const entry of details.logs ?? []) {
103
+ if (!isDroppedWorkLog(entry)) continue;
104
+ alerts.push(`Dropped work: ${sanitizeWorkflowDisplayLine(entry.text)}`);
105
+ }
106
+
107
+ for (const agent of details.agents) {
108
+ if (agent.worktreePath) {
109
+ const reason = agent.worktreeCleanup?.reason ?? "cleanup was unsafe";
110
+ alerts.push(
111
+ `Retained worktree [${sanitizeWorkflowDisplayLine(agent.label)}]: ${shortenHome(agent.worktreePath)} (${sanitizeWorkflowDisplayLine(reason)})${
112
+ agent.worktreeHandoffArtifact
113
+ ? `; handoff ${sanitizeWorkflowDisplayLine(agent.worktreeHandoffArtifact)}`
114
+ : ""
115
+ }`,
116
+ );
117
+ continue;
118
+ }
119
+ if (!agent.worktreeHandoffArtifact) continue;
120
+ const cleanup = agent.worktreeCleanup;
121
+ const work = cleanup?.commits
122
+ ? `${cleanup.commits} commit${cleanup.commits === 1 ? "" : "s"} on ${cleanup.branch}`
123
+ : agent.worktreeBranch
124
+ ? `branch ${agent.worktreeBranch}`
125
+ : "isolated work";
126
+ alerts.push(
127
+ `Worktree handoff [${sanitizeWorkflowDisplayLine(agent.label)}]: ${sanitizeWorkflowDisplayLine(work)}; ${sanitizeWorkflowDisplayLine(agent.worktreeHandoffArtifact)}`,
128
+ );
129
+ }
130
+
131
+ const unique = [...new Set(alerts)].map((alert) =>
132
+ sanitizeTerminalText(boundedLine(alert, 512)),
133
+ );
134
+ if (unique.length <= MAX_ALERTS) return unique;
135
+ return [
136
+ ...unique.slice(0, MAX_ALERTS - 1),
137
+ `${unique.length - MAX_ALERTS + 1} more exceptional item(s); expand for evidence`,
138
+ ];
139
+ }
140
+
141
+ /** Short return-value preview for collapsed success cards. */
142
+ function completionResultPreview(details: WorkflowDetails) {
143
+ if (details.result === undefined) return undefined;
144
+ if (typeof details.result === "string") return boundedLine(details.result);
145
+ const serialized = safeStringify(details.result, { maxBytes: 2 * 1024 });
146
+ try {
147
+ return boundedLine(JSON.stringify(JSON.parse(serialized)));
148
+ } catch {
149
+ return boundedLine(resultJson(details.result));
150
+ }
151
+ }
152
+
153
+ /** Operator evidence, intentionally independent from the model transport report. */
154
+ function buildOperatorReport(
155
+ details: WorkflowDetails,
156
+ runDir: string,
157
+ deliveryId: string,
158
+ ) {
159
+ const { done, failed, uncertain } = countStates(details);
160
+ const elapsed = formatElapsed(details.startedAt, details.finishedAt);
161
+ const lines = [
162
+ `Workflow ${details.name ? `"${details.name}"` : details.runId} ${details.status} — ${done}/${details.agents.length} agents ok${failed ? `, ${failed} failed` : ""}${uncertain ? `, ${uncertain} uncertain` : ""} across ${details.phases.length} phase(s) in ${elapsed}.`,
163
+ `Run dir: ${shortenHome(runDir)}`,
164
+ `Delivery id: ${deliveryId}`,
165
+ ];
166
+
167
+ const artifacts = [
168
+ details.resultArtifact ? `Result: ${details.resultArtifact}` : undefined,
169
+ details.transcriptArtifact
170
+ ? `Transcripts: ${details.transcriptArtifact}`
171
+ : undefined,
172
+ ].filter((entry): entry is string => entry !== undefined);
173
+ if (artifacts.length > 0) lines.push("", "Artifacts:", ...artifacts);
174
+
175
+ const replayed = details.agents.filter((agent) => agent.replayed).length;
176
+ if (details.resumedFrom) {
177
+ lines.push(
178
+ `Resumed from ${details.resumedFrom}: replayed ${replayed}/${details.agents.length} agent call(s), ran ${details.agents.length - replayed} for real.`,
179
+ );
180
+ }
181
+ if (details.resumeNote) lines.push(`Resume: ${details.resumeNote}`);
182
+ if (details.error) lines.push(`Error: ${details.error}`);
183
+
184
+ if (details.logs?.length) {
185
+ lines.push("", "Log:");
186
+ if (details.logsDropped) {
187
+ lines.push(` (${details.logsDropped} earlier line(s) dropped)`);
188
+ }
189
+ for (const entry of details.logs) lines.push(` ${entry.text}`);
190
+ }
191
+
192
+ const isolated = details.agents.filter(
193
+ (agent) => agent.worktreeBranch || agent.worktreePath,
194
+ );
195
+ if (isolated.length > 0) {
196
+ lines.push("", "Isolated worktrees:");
197
+ for (const agent of isolated) {
198
+ const cleanup = agent.worktreeCleanup;
199
+ const work = cleanup?.commits
200
+ ? `${cleanup.commits} commit${cleanup.commits === 1 ? "" : "s"} on ${cleanup.branch}`
201
+ : agent.worktreeBranch
202
+ ? `committed to branch ${agent.worktreeBranch}`
203
+ : "no commits";
204
+ lines.push(
205
+ `- [${agent.label}] ${work}${
206
+ agent.worktreePath
207
+ ? `; kept at ${shortenHome(agent.worktreePath)} (${cleanup?.reason ?? "uncommitted changes"})`
208
+ : cleanup?.branchDeleted
209
+ ? "; empty branch deleted"
210
+ : cleanup?.reason
211
+ ? `; cleanup warning: ${cleanup.reason}`
212
+ : ""
213
+ }${agent.worktreeHandoffArtifact ? `; handoff ${agent.worktreeHandoffArtifact}` : ""}`,
214
+ );
215
+ }
216
+ }
217
+
218
+ if (details.agents.length > 0) {
219
+ lines.push("", "Agents:");
220
+ for (const agent of details.agents) {
221
+ const state =
222
+ agent.state === "done"
223
+ ? agent.replayed
224
+ ? "ok (replayed)"
225
+ : "ok"
226
+ : agent.state === "error"
227
+ ? "FAILED"
228
+ : agent.state === "uncertain"
229
+ ? "UNCERTAIN"
230
+ : "running";
231
+ lines.push(
232
+ `- [${agent.label}]${agent.phase ? ` (${agent.phase})` : ""} ${state}` +
233
+ (agent.acceptance ? ` · acceptance ${agent.acceptance.status}` : "") +
234
+ (agent.error ? ` — ${agent.error}` : ""),
235
+ );
236
+ }
237
+ }
238
+ if (details.result !== undefined) {
239
+ lines.push("", "Result:", resultJson(details.result));
240
+ }
241
+ return sanitizeTerminalText(lines.join("\n"));
242
+ }
243
+
244
+ /** Build a byte-bounded display projection without retaining runtime objects. */
245
+ export function buildWorkflowCompletionDisplay(
246
+ sourceEntries: readonly WorkflowCompletionSourceEntry[],
247
+ ): WorkflowCompletionDisplay {
248
+ const selected = sourceEntries.slice(0, MAX_DISPLAY_ENTRIES);
249
+ const fixedEntry = ({
250
+ deliveryId,
251
+ details,
252
+ }: WorkflowCompletionSourceEntry) => ({
253
+ deliveryId: boundedLine(deliveryId, 512),
254
+ runId: boundedLine(details.runId, 512),
255
+ status: details.status,
256
+ summary: completionSummary(details),
257
+ alerts: completionAlerts(details),
258
+ resultPreview: completionResultPreview(details),
259
+ expanded: "",
260
+ });
261
+ let fixedEntries = selected.map(fixedEntry);
262
+ const fixedSize = () =>
263
+ Buffer.byteLength(
264
+ JSON.stringify({
265
+ version: 1,
266
+ ...(sourceEntries.length === 1
267
+ ? { runId: fixedEntries[0]?.runId ?? "" }
268
+ : {}),
269
+ entries: fixedEntries,
270
+ ...(sourceEntries.length > selected.length
271
+ ? { omittedEntries: sourceEntries.length - selected.length }
272
+ : {}),
273
+ }),
274
+ "utf8",
275
+ );
276
+ while (selected.length > 1 && fixedSize() > MAX_DISPLAY_BYTES / 2) {
277
+ selected.pop();
278
+ fixedEntries.pop();
279
+ }
280
+ const fixedBytes = fixedSize();
281
+ const reports = selected.map(({ deliveryId, details, runDir }) =>
282
+ buildOperatorReport(details, runDir, deliveryId),
283
+ );
284
+ const allocation = allocateResultBudgets(
285
+ reports.map((report) => Buffer.byteLength(report, "utf8")),
286
+ undefined,
287
+ {
288
+ maxBatchBytes: Math.max(0, MAX_DISPLAY_BYTES - fixedBytes),
289
+ maxResultBytes: MAX_EXPANDED_ENTRY_BYTES,
290
+ minResultBytes: 512,
291
+ headroomShare: 0,
292
+ estimatedBytesPerToken: 4,
293
+ },
294
+ );
295
+ let budgets = [...allocation.budgets];
296
+ const projectedEntries = () =>
297
+ fixedEntries.map((entry, index) => ({
298
+ ...entry,
299
+ expanded: projectText(reports[index]!, {
300
+ maxBytes: budgets[index] ?? 0,
301
+ maxLines: 600,
302
+ recovery: `Full workflow evidence is available in ${shortenHome(selected[index]!.runDir)}.`,
303
+ }),
304
+ }));
305
+ let entries = projectedEntries();
306
+ const projectedSize = () =>
307
+ Buffer.byteLength(
308
+ JSON.stringify({
309
+ version: 1,
310
+ ...(sourceEntries.length === 1
311
+ ? { runId: entries[0]?.runId ?? "" }
312
+ : {}),
313
+ entries,
314
+ ...(sourceEntries.length > selected.length
315
+ ? { omittedEntries: sourceEntries.length - selected.length }
316
+ : {}),
317
+ }),
318
+ "utf8",
319
+ );
320
+ for (let attempt = 0; projectedSize() > MAX_DISPLAY_BYTES; attempt++) {
321
+ if (attempt >= 8) {
322
+ budgets = budgets.map(() => 0);
323
+ } else {
324
+ const expandedBytes = budgets.reduce((sum, budget) => sum + budget, 0);
325
+ const overflow = projectedSize() - MAX_DISPLAY_BYTES;
326
+ const target = Math.max(
327
+ 0,
328
+ expandedBytes - overflow - entries.length * 16,
329
+ );
330
+ const scale = expandedBytes > 0 ? target / expandedBytes : 0;
331
+ budgets = budgets.map((budget) => Math.floor(budget * scale));
332
+ }
333
+ entries = projectedEntries();
334
+ }
335
+ const display: WorkflowCompletionDisplay = {
336
+ version: 1,
337
+ ...(sourceEntries.length === 1
338
+ ? { runId: entries[0]?.runId ?? sourceEntries[0]!.details.runId }
339
+ : {}),
340
+ entries,
341
+ ...(sourceEntries.length > selected.length
342
+ ? { omittedEntries: sourceEntries.length - selected.length }
343
+ : {}),
344
+ };
345
+ return display;
346
+ }
347
+
348
+ function isString(value: unknown, maxBytes = MAX_FIELD_BYTES) {
349
+ return (
350
+ typeof value === "string" && Buffer.byteLength(value, "utf8") <= maxBytes
351
+ );
352
+ }
353
+
354
+ function isStatus(value: unknown): value is WorkflowStatus {
355
+ return (
356
+ value === "running" ||
357
+ value === "completed" ||
358
+ value === "failed" ||
359
+ value === "aborted" ||
360
+ value === "uncertain"
361
+ );
362
+ }
363
+
364
+ function hasOnlyKeys(value: object, allowed: readonly string[]) {
365
+ const keys = new Set(allowed);
366
+ return Object.keys(value).every((key) => keys.has(key));
367
+ }
368
+
369
+ export function isWorkflowCompletionDisplay(
370
+ value: unknown,
371
+ ): value is WorkflowCompletionDisplay {
372
+ if (!value || typeof value !== "object") return false;
373
+ let bytes: number;
374
+ try {
375
+ bytes = Buffer.byteLength(JSON.stringify(value), "utf8");
376
+ } catch {
377
+ return false;
378
+ }
379
+ if (bytes > MAX_DISPLAY_BYTES) return false;
380
+ try {
381
+ const candidate = value as Partial<WorkflowCompletionDisplay>;
382
+ if (
383
+ !hasOnlyKeys(candidate, [
384
+ "version",
385
+ "runId",
386
+ "entries",
387
+ "omittedEntries",
388
+ ]) ||
389
+ candidate.version !== 1 ||
390
+ !Array.isArray(candidate.entries) ||
391
+ candidate.entries.length > MAX_DISPLAY_ENTRIES ||
392
+ (candidate.runId !== undefined && !isString(candidate.runId, 512)) ||
393
+ (candidate.omittedEntries !== undefined &&
394
+ (!Number.isSafeInteger(candidate.omittedEntries) ||
395
+ candidate.omittedEntries <= 0))
396
+ ) {
397
+ return false;
398
+ }
399
+ return candidate.entries.every((entry) => {
400
+ if (!entry || typeof entry !== "object") return false;
401
+ const record = entry as Partial<WorkflowCompletionDisplayEntry>;
402
+ return (
403
+ hasOnlyKeys(record, [
404
+ "deliveryId",
405
+ "runId",
406
+ "status",
407
+ "summary",
408
+ "alerts",
409
+ "resultPreview",
410
+ "expanded",
411
+ ]) &&
412
+ isString(record.deliveryId, 512) &&
413
+ isString(record.runId, 512) &&
414
+ isStatus(record.status) &&
415
+ isString(record.summary) &&
416
+ Array.isArray(record.alerts) &&
417
+ record.alerts.length <= MAX_ALERTS &&
418
+ record.alerts.every((alert) => isString(alert, 512)) &&
419
+ (record.resultPreview === undefined ||
420
+ isString(record.resultPreview)) &&
421
+ isString(record.expanded, MAX_EXPANDED_ENTRY_BYTES)
422
+ );
423
+ });
424
+ } catch {
425
+ return false;
426
+ }
427
+ }
428
+
429
+ export function workflowCompletionSummary(
430
+ entry: WorkflowCompletionDisplayEntry,
431
+ ) {
432
+ return entry.summary;
433
+ }
434
+
435
+ export function workflowCompletionAlerts(
436
+ entry: WorkflowCompletionDisplayEntry,
437
+ ) {
438
+ return entry.alerts;
439
+ }
440
+
441
+ export function workflowCompletionResultPreview(
442
+ entry: WorkflowCompletionDisplayEntry,
443
+ ) {
444
+ return entry.resultPreview;
445
+ }
446
+
447
+ export function buildExpandedWorkflowCompletion(
448
+ display: WorkflowCompletionDisplay,
449
+ ) {
450
+ const reports = display.entries.map((entry) => entry.expanded);
451
+ if (display.omittedEntries) {
452
+ reports.push(
453
+ `${display.omittedEntries} additional workflow completion(s) omitted from this display projection; their full evidence remains in workflow artifacts and model-visible delivery context.`,
454
+ );
455
+ }
456
+ return reports.join("\n\n");
457
+ }
@@ -3,23 +3,21 @@ export interface WorkflowLaunchPolicyInput {
3
3
  background?: boolean;
4
4
  }
5
5
 
6
- export interface WorkflowLaunchPolicy {
7
- wait: boolean;
8
- detached: boolean;
9
- }
10
-
11
- /** Resolve legacy/background and host capability without silently changing semantics. */
12
- export function resolveWorkflowLaunchPolicy(
6
+ /**
7
+ * Resolve the caller's launch preference to one positive runtime mode.
8
+ * `background` remains only as the published inverse compatibility alias.
9
+ */
10
+ export function resolveWorkflowLaunchMode(
13
11
  input: WorkflowLaunchPolicyInput,
14
12
  canDeliverLater: boolean,
15
- ): WorkflowLaunchPolicy {
13
+ ) {
16
14
  if (
17
15
  input.wait !== undefined &&
18
16
  input.background !== undefined &&
19
17
  input.wait === input.background
20
18
  ) {
21
19
  throw new Error(
22
- "wait and background conflict: background is the deprecated inverse of wait",
20
+ "wait and background conflict: background is the deprecated inverse of wait; remove background and provide only wait",
23
21
  );
24
22
  }
25
23
  const wait =
@@ -30,7 +28,7 @@ export function resolveWorkflowLaunchPolicy(
30
28
  "This host cannot deliver a workflow result later; use wait: true",
31
29
  );
32
30
  }
33
- return { wait, detached: !wait };
31
+ return wait ? "inline" : "detached";
34
32
  }
35
33
 
36
34
  /**