@bastani/atomic 0.8.27-alpha.1 → 0.8.28-alpha.1
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/CHANGELOG.md +24 -0
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +2 -2
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +20 -0
- package/dist/builtin/workflows/README.md +11 -9
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/authoring.d.ts +5 -2
- package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +3 -1
- package/dist/builtin/workflows/src/extension/hil-answer-notifications.ts +17 -25
- package/dist/builtin/workflows/src/extension/index.ts +133 -18
- package/dist/builtin/workflows/src/extension/render-result.ts +22 -2
- package/dist/builtin/workflows/src/extension/workflow-schema.ts +3 -3
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +210 -16
- package/dist/builtin/workflows/src/sdk-surface.ts +1 -1
- package/dist/builtin/workflows/src/shared/authoring-contract.d.ts +42 -5
- package/dist/builtin/workflows/src/shared/store-types.ts +8 -2
- package/dist/builtin/workflows/src/shared/store.ts +51 -0
- package/dist/builtin/workflows/src/shared/types.ts +14 -4
- package/dist/builtin/workflows/src/tui/graph-view.ts +4 -1
- package/dist/builtin/workflows/src/tui/prompt-card.ts +6 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +11 -1
- package/dist/core/agent-session.d.ts +4 -4
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +147 -31
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/auth-guidance.d.ts +10 -1
- package/dist/core/auth-guidance.d.ts.map +1 -1
- package/dist/core/auth-guidance.js +26 -1
- package/dist/core/auth-guidance.js.map +1 -1
- package/dist/core/compaction/branch-summarization.d.ts +2 -2
- package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
- package/dist/core/compaction/branch-summarization.js +7 -7
- package/dist/core/compaction/branch-summarization.js.map +1 -1
- package/dist/core/compaction/compaction.d.ts +4 -84
- package/dist/core/compaction/compaction.d.ts.map +1 -1
- package/dist/core/compaction/compaction.js +3 -479
- package/dist/core/compaction/compaction.js.map +1 -1
- package/dist/core/compaction/context-compaction.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction.js +39 -82
- package/dist/core/compaction/context-compaction.js.map +1 -1
- package/dist/core/compaction/index.d.ts +1 -1
- package/dist/core/compaction/index.d.ts.map +1 -1
- package/dist/core/compaction/index.js +1 -1
- package/dist/core/compaction/index.js.map +1 -1
- package/dist/core/extensions/types.d.ts +10 -8
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/core/messages.d.ts +1 -11
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +10 -25
- package/dist/core/messages.js.map +1 -1
- package/dist/core/session-manager.d.ts +5 -8
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +12 -76
- package/dist/core/session-manager.js.map +1 -1
- package/dist/core/settings-manager.d.ts +0 -3
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +0 -4
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.d.ts +1 -5
- package/dist/modes/interactive/components/chat-message-renderer.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-message-renderer.js +5 -9
- package/dist/modes/interactive/components/chat-message-renderer.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +0 -3
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/index.d.ts +0 -1
- package/dist/modes/interactive/components/index.d.ts.map +1 -1
- package/dist/modes/interactive/components/index.js +0 -1
- package/dist/modes/interactive/components/index.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +4 -27
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-client.js +2 -2
- package/dist/modes/rpc/rpc-client.js.map +1 -1
- package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-mode.js +1 -1
- package/dist/modes/rpc/rpc-mode.js.map +1 -1
- package/dist/modes/rpc/rpc-types.d.ts +0 -1
- package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
- package/dist/modes/rpc/rpc-types.js.map +1 -1
- package/docs/compaction.md +210 -181
- package/docs/extensions.md +31 -20
- package/docs/json.md +3 -4
- package/docs/session-format.md +12 -21
- package/docs/sessions.md +3 -1
- package/docs/settings.md +2 -5
- package/docs/workflows.md +11 -9
- package/examples/extensions/README.md +1 -1
- package/examples/extensions/custom-compaction.ts +43 -106
- package/examples/extensions/handoff.ts +6 -44
- package/examples/extensions/trigger-compact.ts +5 -4
- package/package.json +5 -5
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +0 -16
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +0 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +0 -43
- package/dist/modes/interactive/components/compaction-summary-message.js.map +0 -1
|
@@ -110,11 +110,13 @@ type StageListItem = {
|
|
|
110
110
|
awaitingInputSince?: number;
|
|
111
111
|
pendingPrompt?: PendingPrompt;
|
|
112
112
|
inputRequest?: StageInputRequest;
|
|
113
|
+
promptFootprint?: PendingPrompt;
|
|
113
114
|
};
|
|
114
115
|
type StageListResult = { action: "stages"; runId: string; filter: string; stages: StageListItem[]; error?: string };
|
|
115
116
|
type StageDetailItem = StageSnapshot & { transcriptPath?: string };
|
|
116
117
|
type StageDetailResult = { action: "stage"; runId: string; stage?: StageDetailItem; error?: string };
|
|
117
118
|
type TranscriptEntry = { role: string; text?: string; toolName?: string; output?: string; timestamp?: number };
|
|
119
|
+
type TranscriptInlineMode = "path_only" | "preview" | "fallback_preview";
|
|
118
120
|
type TranscriptResult = {
|
|
119
121
|
action: "transcript";
|
|
120
122
|
runId: string;
|
|
@@ -127,6 +129,9 @@ type TranscriptResult = {
|
|
|
127
129
|
sessionId?: string;
|
|
128
130
|
sessionFile?: string;
|
|
129
131
|
transcriptPath?: string;
|
|
132
|
+
lazyReadPrompt?: string;
|
|
133
|
+
fallbackNote?: string;
|
|
134
|
+
inlineMode?: TranscriptInlineMode;
|
|
130
135
|
};
|
|
131
136
|
type SendResult = { action: "send"; runId: string; stageId: string; delivery: string; status: "ok" | "noop"; message: string };
|
|
132
137
|
type PauseResult = { action: "pause"; runId: string; status: string; message: string };
|
|
@@ -213,7 +218,7 @@ function renderNotice(
|
|
|
213
218
|
const TRANSCRIPT_NOTICE_ENTRY_LIMIT = 5;
|
|
214
219
|
const TRANSCRIPT_NOTICE_CHAR_LIMIT = 240;
|
|
215
220
|
|
|
216
|
-
function
|
|
221
|
+
function transcriptEntriesNoticeText(entries: readonly TranscriptEntry[]): string {
|
|
217
222
|
if (entries.length === 0) return "no transcript entries";
|
|
218
223
|
const shown = entries.slice(0, TRANSCRIPT_NOTICE_ENTRY_LIMIT);
|
|
219
224
|
const text = shown
|
|
@@ -225,6 +230,21 @@ function transcriptNoticeText(entries: readonly TranscriptEntry[]): string {
|
|
|
225
230
|
return fitLine(`${text}${entrySuffix}`, TRANSCRIPT_NOTICE_CHAR_LIMIT);
|
|
226
231
|
}
|
|
227
232
|
|
|
233
|
+
function transcriptNoticeText(result: TranscriptResult): string {
|
|
234
|
+
if ((result.inlineMode === "path_only" || result.lazyReadPrompt !== undefined) && result.entries.length === 0) {
|
|
235
|
+
const path = result.transcriptPath ?? result.sessionFile ?? "transcript file";
|
|
236
|
+
const count = result.entryCount === undefined
|
|
237
|
+
? ""
|
|
238
|
+
: ` (${result.entryCount} ${result.entryCount === 1 ? "entry" : "entries"})`;
|
|
239
|
+
return fitLine(`not inlined; read ${path}${count}`, TRANSCRIPT_NOTICE_CHAR_LIMIT);
|
|
240
|
+
}
|
|
241
|
+
const entriesText = transcriptEntriesNoticeText(result.entries);
|
|
242
|
+
if (result.inlineMode === "fallback_preview" || result.fallbackNote !== undefined) {
|
|
243
|
+
return fitLine(`no session file; preview: ${entriesText}`, TRANSCRIPT_NOTICE_CHAR_LIMIT);
|
|
244
|
+
}
|
|
245
|
+
return entriesText;
|
|
246
|
+
}
|
|
247
|
+
|
|
228
248
|
export function renderResult(result: WorkflowToolResult, opts?: RenderResultOpts): string {
|
|
229
249
|
const partial = opts?.isPartial === true;
|
|
230
250
|
const themed = opts?.plain !== true;
|
|
@@ -350,7 +370,7 @@ export function renderResult(result: WorkflowToolResult, opts?: RenderResultOpts
|
|
|
350
370
|
|
|
351
371
|
case "transcript": {
|
|
352
372
|
const r = result as TranscriptResult;
|
|
353
|
-
const text = transcriptNoticeText(r
|
|
373
|
+
const text = transcriptNoticeText(r);
|
|
354
374
|
const suffix = r.truncated ? " (truncated)" : "";
|
|
355
375
|
return renderNotice("WORKFLOW TRANSCRIPT", `${r.runId}/${r.stageId.slice(0, 12)} ${r.source}: ${text}${suffix}`, opts, themed);
|
|
356
376
|
}
|
|
@@ -114,7 +114,7 @@ export const WorkflowParametersSchema = Type.Object({
|
|
|
114
114
|
Type.Literal("resume"),
|
|
115
115
|
Type.Literal("reload"),
|
|
116
116
|
], {
|
|
117
|
-
description: "Workflow action: run/list/get/inputs/status, inspect stage metadata, send messages or prompt answers, pause/resume/interrupt/kill runs, or reload workflow resources. For transcript inspection, prefer status/stages/stage first to get sessionFile/transcriptPath, quote the exact path without rewriting separators (Windows backslashes are valid), then search it with rg/grep and read small ranges; transcript
|
|
117
|
+
description: "Workflow action: run/list/get/inputs/status, inspect stage metadata, send messages or prompt answers, pause/resume/interrupt/kill runs, or reload workflow resources. For transcript inspection, prefer status/stages/stage first to get sessionFile/transcriptPath, quote the exact path without rewriting separators (Windows backslashes are valid), then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview.",
|
|
118
118
|
})),
|
|
119
119
|
runId: Type.Optional(Type.String({
|
|
120
120
|
description: "Run identifier or unique prefix for status/stages/stage/transcript/send/pause/resume/interrupt/kill. Use '--all' or all:true for supported bulk run-control actions.",
|
|
@@ -146,14 +146,14 @@ export const WorkflowParametersSchema = Type.Object({
|
|
|
146
146
|
})),
|
|
147
147
|
limit: Type.Optional(Type.Integer({
|
|
148
148
|
minimum: 0,
|
|
149
|
-
description: "Transcript-only: explicitly inline at most this many recent entries. Omit both limit and tail to use the
|
|
149
|
+
description: "Transcript-only: explicitly inline at most this many recent entries. Omit both limit and tail to use the path-only default when sessionFile/transcriptPath exists; prefer rg/grep on the exact quoted sessionFile/transcriptPath for targeted lookup without rewriting platform path separators.",
|
|
150
150
|
})),
|
|
151
151
|
tail: Type.Optional(Type.Integer({
|
|
152
152
|
minimum: 0,
|
|
153
153
|
description: "Transcript-only: explicitly inline the last N entries; overrides limit. Use for quick recent-context checks after status/stages/stage expose the transcript path.",
|
|
154
154
|
})),
|
|
155
155
|
includeToolOutput: Type.Optional(Type.Boolean({
|
|
156
|
-
description: "Transcript-only: include captured tool output entries when building
|
|
156
|
+
description: "Transcript-only: include captured tool output entries when building inlined snapshot previews; this does not bypass the path-only default. Prefer rg/grep on the exact quoted sessionFile/transcriptPath for large outputs. Live session transcripts may not expose tool output.",
|
|
157
157
|
})),
|
|
158
158
|
text: Type.Optional(Type.String({
|
|
159
159
|
description: "Text to send to a stage for prompt answers, steering, follow-ups, or resume messages.",
|
|
@@ -14,6 +14,8 @@ import type {
|
|
|
14
14
|
WorkflowRunContext,
|
|
15
15
|
WorkflowUIContext,
|
|
16
16
|
WorkflowUIAdapter,
|
|
17
|
+
WorkflowCustomUiFactory,
|
|
18
|
+
WorkflowCustomUiOptions,
|
|
17
19
|
WorkflowInputSchema,
|
|
18
20
|
StageContext,
|
|
19
21
|
StageOptions,
|
|
@@ -56,7 +58,7 @@ import type {
|
|
|
56
58
|
WorkflowFailureRecoverability,
|
|
57
59
|
WorkflowFailureDisposition,
|
|
58
60
|
PendingPrompt,
|
|
59
|
-
|
|
61
|
+
CustomPromptIdentitySource,
|
|
60
62
|
WorkflowChildReplaySnapshot,
|
|
61
63
|
WorkflowChildRunRef,
|
|
62
64
|
} from "../../shared/store-types.js";
|
|
@@ -108,7 +110,7 @@ export interface RunContinuationOpts {
|
|
|
108
110
|
readonly resumeFromStageId: string;
|
|
109
111
|
}
|
|
110
112
|
|
|
111
|
-
export interface RunOpts extends Omit<AuthoringContract.RunOpts, "adapters" | "store" | "cancellation" | "overlay" | "registry" | "stageControlRegistry" | "continuation" | "onRunStart" | "onStageStart" | "onStageEnd" | "onRunEnd"> {
|
|
113
|
+
export interface RunOpts extends Omit<AuthoringContract.RunOpts, "adapters" | "store" | "cancellation" | "overlay" | "registry" | "stageControlRegistry" | "continuation" | "onRunStart" | "onStageStart" | "onStageEnd" | "onRunEnd" | "ui"> {
|
|
112
114
|
adapters?: StageAdapters;
|
|
113
115
|
/** Invocation working directory exposed to workflow definitions as ctx.cwd. */
|
|
114
116
|
cwd?: string;
|
|
@@ -271,14 +273,28 @@ function resolveInputRuntimeDefaults(
|
|
|
271
273
|
// HIL unavailable fallback — rejects with precise per-primitive error
|
|
272
274
|
// ---------------------------------------------------------------------------
|
|
273
275
|
|
|
274
|
-
|
|
275
|
-
readonly kind:
|
|
276
|
+
type PrimitivePromptDescriptor =
|
|
277
|
+
| { readonly kind: "input"; readonly message: string; readonly initial?: string }
|
|
278
|
+
| { readonly kind: "confirm"; readonly message: string }
|
|
279
|
+
| { readonly kind: "select"; readonly message: string; readonly choices: readonly string[] }
|
|
280
|
+
| { readonly kind: "editor"; readonly message: string; readonly initial?: string };
|
|
281
|
+
|
|
282
|
+
interface CustomPromptDescriptor<T> {
|
|
283
|
+
readonly kind: "custom";
|
|
276
284
|
readonly message: string;
|
|
277
|
-
readonly
|
|
278
|
-
readonly
|
|
285
|
+
readonly factory: WorkflowCustomUiFactory<T>;
|
|
286
|
+
readonly options?: WorkflowCustomUiOptions;
|
|
287
|
+
readonly customIdentityHash: string;
|
|
288
|
+
readonly customIdentitySource: CustomPromptIdentitySource;
|
|
279
289
|
}
|
|
280
290
|
|
|
281
|
-
|
|
291
|
+
type PromptDescriptor<T = unknown> = PrimitivePromptDescriptor | CustomPromptDescriptor<T>;
|
|
292
|
+
|
|
293
|
+
function isCustomPromptDescriptor<T>(descriptor: PromptDescriptor<T>): descriptor is CustomPromptDescriptor<T> {
|
|
294
|
+
return descriptor.kind === "custom";
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function fallbackForPromptDescriptor(descriptor: PrimitivePromptDescriptor): unknown {
|
|
282
298
|
switch (descriptor.kind) {
|
|
283
299
|
case "input":
|
|
284
300
|
case "editor":
|
|
@@ -286,7 +302,7 @@ function fallbackForPromptDescriptor(descriptor: PromptDescriptor): unknown {
|
|
|
286
302
|
case "confirm":
|
|
287
303
|
return false;
|
|
288
304
|
case "select":
|
|
289
|
-
return descriptor.choices
|
|
305
|
+
return descriptor.choices[0] ?? "";
|
|
290
306
|
}
|
|
291
307
|
}
|
|
292
308
|
|
|
@@ -295,8 +311,12 @@ function makePrompt(descriptor: PromptDescriptor): PendingPrompt {
|
|
|
295
311
|
id: `hil-${crypto.randomUUID()}`,
|
|
296
312
|
kind: descriptor.kind,
|
|
297
313
|
message: descriptor.message,
|
|
298
|
-
...(descriptor.
|
|
299
|
-
...(descriptor.initial !== undefined ? { initial: descriptor.initial } : {}),
|
|
314
|
+
...(!isCustomPromptDescriptor(descriptor) && descriptor.kind === "select" ? { choices: descriptor.choices } : {}),
|
|
315
|
+
...(!isCustomPromptDescriptor(descriptor) && (descriptor.kind === "input" || descriptor.kind === "editor") && descriptor.initial !== undefined ? { initial: descriptor.initial } : {}),
|
|
316
|
+
...(isCustomPromptDescriptor(descriptor) ? {
|
|
317
|
+
customIdentityHash: descriptor.customIdentityHash,
|
|
318
|
+
customIdentitySource: descriptor.customIdentitySource,
|
|
319
|
+
} : {}),
|
|
300
320
|
createdAt: Date.now(),
|
|
301
321
|
};
|
|
302
322
|
}
|
|
@@ -307,13 +327,19 @@ function stableHash(value: unknown): string {
|
|
|
307
327
|
}
|
|
308
328
|
|
|
309
329
|
function promptDescriptorHash(descriptor: PromptDescriptor): string {
|
|
330
|
+
if (isCustomPromptDescriptor(descriptor)) {
|
|
331
|
+
return stableHash({
|
|
332
|
+
kind: "custom",
|
|
333
|
+
customIdentityHash: descriptor.customIdentityHash,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
310
336
|
return stableHash({
|
|
311
337
|
kind: descriptor.kind,
|
|
312
338
|
message: descriptor.message,
|
|
313
|
-
choices: descriptor.choices
|
|
339
|
+
choices: descriptor.kind === "select" ? descriptor.choices : [],
|
|
314
340
|
// Include input/editor initial text because it is visible prompt context;
|
|
315
341
|
// changing it should not replay a stale answer from the same callsite.
|
|
316
|
-
initial: descriptor.initial ?? null,
|
|
342
|
+
initial: descriptor.kind === "input" || descriptor.kind === "editor" ? descriptor.initial ?? null : null,
|
|
317
343
|
});
|
|
318
344
|
}
|
|
319
345
|
|
|
@@ -334,6 +360,80 @@ function hilAbortError(signal: AbortSignal): Error {
|
|
|
334
360
|
: new Error("atomic-workflows: HIL aborted");
|
|
335
361
|
}
|
|
336
362
|
|
|
363
|
+
function resolveCustomPromptIdentity<T>(
|
|
364
|
+
factory: WorkflowCustomUiFactory<T>,
|
|
365
|
+
options: WorkflowCustomUiOptions | undefined,
|
|
366
|
+
): Pick<CustomPromptDescriptor<T>, "customIdentityHash" | "customIdentitySource"> {
|
|
367
|
+
const replayIdentity = options?.replayIdentity?.trim();
|
|
368
|
+
if (replayIdentity !== undefined && replayIdentity.length > 0) {
|
|
369
|
+
return {
|
|
370
|
+
customIdentityHash: stableHash({ source: "caller", value: replayIdentity }),
|
|
371
|
+
customIdentitySource: "caller",
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
if (factory.name.trim().length > 0) {
|
|
375
|
+
return {
|
|
376
|
+
customIdentityHash: stableHash({ source: "factory", value: factory.name }),
|
|
377
|
+
customIdentitySource: "factory",
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
try {
|
|
381
|
+
const source = Function.prototype.toString.call(factory);
|
|
382
|
+
if (source.trim().length > 0) {
|
|
383
|
+
return {
|
|
384
|
+
customIdentityHash: stableHash({ source: "factory", value: source }),
|
|
385
|
+
customIdentitySource: "factory",
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
} catch {
|
|
389
|
+
// Fall through to callsite-only identity below.
|
|
390
|
+
}
|
|
391
|
+
return {
|
|
392
|
+
customIdentityHash: stableHash({ source: "callsite" }),
|
|
393
|
+
customIdentitySource: "callsite",
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function customPromptDescriptor<T>(
|
|
398
|
+
factory: WorkflowCustomUiFactory<T>,
|
|
399
|
+
options: WorkflowCustomUiOptions | undefined,
|
|
400
|
+
): CustomPromptDescriptor<T> {
|
|
401
|
+
const label = options?.label?.trim();
|
|
402
|
+
return {
|
|
403
|
+
kind: "custom",
|
|
404
|
+
message: label && label.length > 0 ? label : "Custom TUI prompt",
|
|
405
|
+
factory,
|
|
406
|
+
...(options !== undefined ? { options } : {}),
|
|
407
|
+
...resolveCustomPromptIdentity(factory, options),
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
interface MergedHilSignal {
|
|
412
|
+
readonly signal: AbortSignal;
|
|
413
|
+
readonly dispose: () => void;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function mergeHilSignals(primary: AbortSignal, secondary: AbortSignal | undefined): MergedHilSignal {
|
|
417
|
+
if (secondary === undefined) return { signal: primary, dispose: () => undefined };
|
|
418
|
+
const controller = new AbortController();
|
|
419
|
+
const abortFrom = (source: AbortSignal): void => {
|
|
420
|
+
if (!controller.signal.aborted) controller.abort(source.reason);
|
|
421
|
+
};
|
|
422
|
+
const onPrimaryAbort = (): void => abortFrom(primary);
|
|
423
|
+
const onSecondaryAbort = (): void => abortFrom(secondary);
|
|
424
|
+
primary.addEventListener("abort", onPrimaryAbort, { once: true });
|
|
425
|
+
secondary.addEventListener("abort", onSecondaryAbort, { once: true });
|
|
426
|
+
if (primary.aborted) abortFrom(primary);
|
|
427
|
+
else if (secondary.aborted) abortFrom(secondary);
|
|
428
|
+
return {
|
|
429
|
+
signal: controller.signal,
|
|
430
|
+
dispose: () => {
|
|
431
|
+
primary.removeEventListener("abort", onPrimaryAbort);
|
|
432
|
+
secondary.removeEventListener("abort", onSecondaryAbort);
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
337
437
|
function makeUnavailableUIContext(): WorkflowUIContext {
|
|
338
438
|
const msg = (primitive: string): string =>
|
|
339
439
|
`atomic-workflows: HIL ctx.ui.${primitive} is unavailable because Atomic runtime did not provide a UI adapter`;
|
|
@@ -342,6 +442,31 @@ function makeUnavailableUIContext(): WorkflowUIContext {
|
|
|
342
442
|
confirm: () => Promise.reject(new Error(msg("confirm"))),
|
|
343
443
|
select: () => Promise.reject(new Error(msg("select"))),
|
|
344
444
|
editor: () => Promise.reject(new Error(msg("editor"))),
|
|
445
|
+
custom: () => Promise.reject(new Error(msg("custom"))),
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
function normalizeUIContext(adapter: WorkflowUIAdapter | undefined): WorkflowUIContext {
|
|
450
|
+
const unavailable = makeUnavailableUIContext();
|
|
451
|
+
if (adapter === undefined) return unavailable;
|
|
452
|
+
return {
|
|
453
|
+
input(prompt) {
|
|
454
|
+
return adapter.input.call(adapter, prompt);
|
|
455
|
+
},
|
|
456
|
+
confirm(message) {
|
|
457
|
+
return adapter.confirm.call(adapter, message);
|
|
458
|
+
},
|
|
459
|
+
select<T extends string>(message: string, options: readonly T[]): Promise<T> {
|
|
460
|
+
return adapter.select.call(adapter, message, options) as Promise<T>;
|
|
461
|
+
},
|
|
462
|
+
editor(initial) {
|
|
463
|
+
return adapter.editor.call(adapter, initial);
|
|
464
|
+
},
|
|
465
|
+
custom<T>(factory: WorkflowCustomUiFactory<T>, options?: WorkflowCustomUiOptions): Promise<T> {
|
|
466
|
+
return adapter.custom !== undefined
|
|
467
|
+
? adapter.custom.call(adapter, factory, options) as Promise<T>
|
|
468
|
+
: unavailable.custom(factory, options);
|
|
469
|
+
},
|
|
345
470
|
};
|
|
346
471
|
}
|
|
347
472
|
|
|
@@ -1400,8 +1525,19 @@ export async function runChain(
|
|
|
1400
1525
|
return workflowDetailsFromRun("chain", runResult, results, options, validationWarnings);
|
|
1401
1526
|
}
|
|
1402
1527
|
|
|
1403
|
-
function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
1528
|
+
export function raceAbort<T>(promise: Promise<T>, signal: AbortSignal): Promise<T> {
|
|
1404
1529
|
if (signal.aborted) {
|
|
1530
|
+
// Callers invoke `raceAbort(call(), signal)`, so `call()` is evaluated —
|
|
1531
|
+
// and the underlying work (e.g. a stage prompt) is already in flight —
|
|
1532
|
+
// before this function observes an already-aborted signal. Attach a no-op
|
|
1533
|
+
// rejection handler so that in-flight promise can never surface as an
|
|
1534
|
+
// unhandled rejection. Without this, killing a workflow mid-prompt orphans
|
|
1535
|
+
// the prompt promise; its eventual rejection (commonly
|
|
1536
|
+
// "No API key found for ...") escapes every workflow error boundary and is
|
|
1537
|
+
// raised as a process-level uncaught exception that crashes the whole CLI.
|
|
1538
|
+
// The run is being aborted, so the orphaned settlement is intentionally
|
|
1539
|
+
// discarded here.
|
|
1540
|
+
void promise.catch(() => {});
|
|
1405
1541
|
return Promise.reject(signal.reason ?? new DOMException("workflow killed", "AbortError"));
|
|
1406
1542
|
}
|
|
1407
1543
|
return new Promise<T>((resolve, reject) => {
|
|
@@ -2640,11 +2776,16 @@ export async function run<TInputs extends WorkflowInputValues>(
|
|
|
2640
2776
|
};
|
|
2641
2777
|
};
|
|
2642
2778
|
|
|
2643
|
-
const buildPromptNodeUiAdapter = ():
|
|
2644
|
-
const ask = async (descriptor: PromptDescriptor): Promise<unknown> => {
|
|
2779
|
+
const buildPromptNodeUiAdapter = (): WorkflowUIContext => {
|
|
2780
|
+
const ask = async <T>(descriptor: PromptDescriptor<T>): Promise<unknown> => {
|
|
2781
|
+
const isCustom = isCustomPromptDescriptor(descriptor);
|
|
2645
2782
|
if (ownController.signal.aborted) {
|
|
2783
|
+
if (isCustom) throw hilAbortError(ownController.signal);
|
|
2646
2784
|
return fallbackForPromptDescriptor(descriptor);
|
|
2647
2785
|
}
|
|
2786
|
+
if (isCustom && descriptor.options?.signal?.aborted) {
|
|
2787
|
+
throw hilAbortError(descriptor.options.signal);
|
|
2788
|
+
}
|
|
2648
2789
|
|
|
2649
2790
|
const prompt = makePrompt(descriptor);
|
|
2650
2791
|
const stageId = crypto.randomUUID();
|
|
@@ -2741,6 +2882,55 @@ export async function run<TInputs extends WorkflowInputValues>(
|
|
|
2741
2882
|
finalizePromptStage("completed");
|
|
2742
2883
|
return replayAnswer.value;
|
|
2743
2884
|
}
|
|
2885
|
+
|
|
2886
|
+
if (isCustom) {
|
|
2887
|
+
if (descriptor.options?.overlay === true) {
|
|
2888
|
+
const error = new Error("atomic-workflows: ctx.ui.custom overlay mode is unavailable in the workflow graph viewer");
|
|
2889
|
+
applyFailureToStage(stageSnapshot, classifyExecutorFailure(error));
|
|
2890
|
+
finalizePromptStage("failed");
|
|
2891
|
+
throw error;
|
|
2892
|
+
}
|
|
2893
|
+
|
|
2894
|
+
const mergedSignal = mergeHilSignals(ownController.signal, descriptor.options?.signal);
|
|
2895
|
+
try {
|
|
2896
|
+
if (mergedSignal.signal.aborted) throw hilAbortError(mergedSignal.signal);
|
|
2897
|
+
const accepted = activeStore.recordStageAwaitingInput(runId, stageId, true, prompt.createdAt);
|
|
2898
|
+
if (!accepted) {
|
|
2899
|
+
const error = new Error("atomic-workflows: ctx.ui.custom prompt node is unavailable");
|
|
2900
|
+
stageSnapshot.skippedReason = "prompt-unavailable";
|
|
2901
|
+
finalizePromptStage("skipped");
|
|
2902
|
+
throw error;
|
|
2903
|
+
}
|
|
2904
|
+
const response = await stageUiBroker.requestCustomUi(
|
|
2905
|
+
runId,
|
|
2906
|
+
stageId,
|
|
2907
|
+
descriptor.factory as unknown as Parameters<typeof stageUiBroker.requestCustomUi>[2],
|
|
2908
|
+
descriptor.options as Parameters<typeof stageUiBroker.requestCustomUi>[3],
|
|
2909
|
+
mergedSignal.signal,
|
|
2910
|
+
);
|
|
2911
|
+
activeStore.recordStagePromptAnswer(runId, stageId, prompt, response, {
|
|
2912
|
+
answerSource: "workflow_ui",
|
|
2913
|
+
});
|
|
2914
|
+
finalizePromptStage("completed");
|
|
2915
|
+
return response;
|
|
2916
|
+
} catch (err) {
|
|
2917
|
+
activeStore.recordStageAwaitingInput(runId, stageId, false);
|
|
2918
|
+
stageUiBroker.cancelStagePrompt(runId, stageId, err);
|
|
2919
|
+
if (mergedSignal.signal.aborted) {
|
|
2920
|
+
stageSnapshot.skippedReason = ownController.signal.aborted ? "run-aborted" : "prompt-aborted";
|
|
2921
|
+
finalizePromptStage("skipped");
|
|
2922
|
+
throw hilAbortError(mergedSignal.signal);
|
|
2923
|
+
}
|
|
2924
|
+
if (!finalized) {
|
|
2925
|
+
applyFailureToStage(stageSnapshot, classifyExecutorFailure(err));
|
|
2926
|
+
finalizePromptStage("failed");
|
|
2927
|
+
}
|
|
2928
|
+
throw err;
|
|
2929
|
+
} finally {
|
|
2930
|
+
mergedSignal.dispose();
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2744
2934
|
const accepted = activeStore.recordStagePendingPrompt(runId, stageId, prompt);
|
|
2745
2935
|
if (!accepted) {
|
|
2746
2936
|
stageSnapshot.skippedReason = "prompt-unavailable";
|
|
@@ -2818,6 +3008,10 @@ export async function run<TInputs extends WorkflowInputValues>(
|
|
|
2818
3008
|
});
|
|
2819
3009
|
return typeof response === "string" ? response : initial ?? "";
|
|
2820
3010
|
},
|
|
3011
|
+
async custom<T>(factory: WorkflowCustomUiFactory<T>, options?: WorkflowCustomUiOptions): Promise<T> {
|
|
3012
|
+
const response = await ask(customPromptDescriptor(factory, options));
|
|
3013
|
+
return response as T;
|
|
3014
|
+
},
|
|
2821
3015
|
};
|
|
2822
3016
|
};
|
|
2823
3017
|
|
|
@@ -2827,7 +3021,7 @@ export async function run<TInputs extends WorkflowInputValues>(
|
|
|
2827
3021
|
get cwd() { return resolveWorkflowCwd(); },
|
|
2828
3022
|
// Prompt nodes and caller-provided UI adapters are mutually exclusive;
|
|
2829
3023
|
// executor-owned prompt nodes intentionally take precedence when enabled.
|
|
2830
|
-
ui: opts.usePromptNodesForUi === true ? buildPromptNodeUiAdapter() : opts.ui
|
|
3024
|
+
ui: opts.usePromptNodesForUi === true ? buildPromptNodeUiAdapter() : normalizeUIContext(opts.ui),
|
|
2831
3025
|
|
|
2832
3026
|
stage(name: string, options?: StageOptions, stageFailFastScope?: ParallelFailFastScope) {
|
|
2833
3027
|
options = stageOptionsWithGitWorktree(stageOptionsWithInputDefaults(options, inputRuntimeDefaults), workflowInvocationCwd);
|
|
@@ -37,7 +37,7 @@ export type { StageNode } from "./runs/shared/graph-inference.js";
|
|
|
37
37
|
export { setupGitWorktree } from "./runs/shared/worktree.js";
|
|
38
38
|
export type { GitWorktreeSetupOptions, GitWorktreeSetupResult } from "./runs/shared/worktree.js";
|
|
39
39
|
export { createStore, store } from "./shared/store.js";
|
|
40
|
-
export type { RunStatus, StageStatus, ToolEvent, StageSnapshot, RunSnapshot, StoreSnapshot, WorkflowNotice, NoticeLevel, WorkflowOverlayAdapter, PromptKind, PendingPrompt } from "./shared/store-types.js";
|
|
40
|
+
export type { RunStatus, StageStatus, ToolEvent, StageSnapshot, RunSnapshot, StoreSnapshot, WorkflowNotice, NoticeLevel, WorkflowOverlayAdapter, PromptKind, CustomPromptIdentitySource, PendingPrompt } from "./shared/store-types.js";
|
|
41
41
|
|
|
42
42
|
// Phase D — cancellation registry
|
|
43
43
|
export { createCancellationRegistry, cancellationRegistry } from "./runs/background/cancellation-registry.js";
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* Workflow authoring contract shared by the runtime type graph and the
|
|
3
|
+
* standalone package typing surface.
|
|
4
4
|
*
|
|
5
|
-
* This module intentionally
|
|
6
|
-
*
|
|
5
|
+
* This module intentionally avoids executor internals, stores, or runtime graph
|
|
6
|
+
* modules here. Public custom-TUI types are type-only imports from the same
|
|
7
|
+
* extension-compatible surfaces used by Atomic extension UI.
|
|
7
8
|
*/
|
|
9
|
+
import type { KeybindingsManager, Theme } from "@bastani/atomic";
|
|
10
|
+
import type { Component, OverlayHandle, OverlayOptions, TUI } from "@earendil-works/pi-tui";
|
|
8
11
|
import type { Static, TOptional, TSchema } from "typebox";
|
|
9
12
|
export type { Static, TSchema };
|
|
10
13
|
export type WorkflowSerializablePrimitive = string | number | boolean | null;
|
|
@@ -335,13 +338,47 @@ export interface WorkflowChildResult<TOutputs extends WorkflowOutputValues = Wor
|
|
|
335
338
|
readonly status: "completed";
|
|
336
339
|
readonly outputs: TOutputs;
|
|
337
340
|
}
|
|
341
|
+
export type WorkflowCustomUiComponent = Component & {
|
|
342
|
+
dispose?(): void;
|
|
343
|
+
};
|
|
344
|
+
export type WorkflowCustomUiTui = TUI;
|
|
345
|
+
export type WorkflowCustomUiTheme = Theme;
|
|
346
|
+
export type WorkflowCustomUiKeybindings = KeybindingsManager;
|
|
347
|
+
export type WorkflowCustomUiOverlayOptions = OverlayOptions;
|
|
348
|
+
export type WorkflowCustomUiOverlayHandle = OverlayHandle;
|
|
349
|
+
export type WorkflowCustomUiFactory<T> = (tui: TUI, theme: Theme, keybindings: KeybindingsManager, done: (value: T) => void) => WorkflowCustomUiComponent | Promise<WorkflowCustomUiComponent>;
|
|
350
|
+
export interface WorkflowCustomUiOptions {
|
|
351
|
+
/** Render as a nested overlay. Workflow graph hosts may reject this when unsupported. */
|
|
352
|
+
readonly overlay?: boolean;
|
|
353
|
+
/** AbortSignal to programmatically dismiss the custom UI. */
|
|
354
|
+
readonly signal?: AbortSignal;
|
|
355
|
+
/** Overlay positioning/sizing options. Can be static or a function for dynamic updates. */
|
|
356
|
+
readonly overlayOptions?: OverlayOptions | (() => OverlayOptions);
|
|
357
|
+
/** Called with the real overlay handle after an overlay is shown. */
|
|
358
|
+
readonly onHandle?: (handle: OverlayHandle) => void;
|
|
359
|
+
/**
|
|
360
|
+
* Workflow-only replay identity. Recommended whenever widget state or
|
|
361
|
+
* semantics can change without the callsite changing. Do not include secrets;
|
|
362
|
+
* the runtime stores only a hash.
|
|
363
|
+
*/
|
|
364
|
+
readonly replayIdentity?: string;
|
|
365
|
+
/** Safe display-only label for graph/status surfaces. Defaults to "Custom TUI prompt". Not part of replay identity. */
|
|
366
|
+
readonly label?: string;
|
|
367
|
+
}
|
|
338
368
|
export interface WorkflowUIContext {
|
|
339
369
|
input(prompt: string): Promise<string>;
|
|
340
370
|
confirm(message: string): Promise<boolean>;
|
|
341
371
|
select<T extends string>(message: string, options: readonly T[]): Promise<T>;
|
|
342
372
|
editor(initial?: string): Promise<string>;
|
|
373
|
+
custom<T>(factory: WorkflowCustomUiFactory<T>, options?: WorkflowCustomUiOptions): Promise<T>;
|
|
374
|
+
}
|
|
375
|
+
export interface WorkflowUIAdapter {
|
|
376
|
+
input(prompt: string): Promise<string>;
|
|
377
|
+
confirm(message: string): Promise<boolean>;
|
|
378
|
+
select<T extends string>(message: string, options: readonly T[]): Promise<T>;
|
|
379
|
+
editor(initial?: string): Promise<string>;
|
|
380
|
+
custom?<T>(factory: WorkflowCustomUiFactory<T>, options?: WorkflowCustomUiOptions): Promise<T>;
|
|
343
381
|
}
|
|
344
|
-
export type WorkflowUIAdapter = WorkflowUIContext;
|
|
345
382
|
export interface WorkflowRunContext<TInputs extends WorkflowInputValues = WorkflowInputValues, TDefinitionBrand extends object = {}> {
|
|
346
383
|
readonly inputs: Readonly<TInputs>;
|
|
347
384
|
readonly cwd?: string;
|
|
@@ -32,10 +32,12 @@ export type WorkflowFailureCode =
|
|
|
32
32
|
| "unknown";
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Human-in-the-loop prompt kind. Mirrors the
|
|
35
|
+
* Human-in-the-loop prompt kind. Mirrors the `WorkflowUIContext` methods.
|
|
36
36
|
* cross-ref: src/shared/types.ts WorkflowUIContext
|
|
37
37
|
*/
|
|
38
|
-
export type PromptKind = "input" | "confirm" | "select" | "editor";
|
|
38
|
+
export type PromptKind = "input" | "confirm" | "select" | "editor" | "custom";
|
|
39
|
+
|
|
40
|
+
export type CustomPromptIdentitySource = "caller" | "factory" | "callsite";
|
|
39
41
|
|
|
40
42
|
/**
|
|
41
43
|
* A pending HIL prompt awaiting user response. Surfaced through the graph
|
|
@@ -53,6 +55,10 @@ export interface PendingPrompt {
|
|
|
53
55
|
readonly choices?: readonly string[];
|
|
54
56
|
/** Initial value for `kind: "input"` and `kind: "editor"`. */
|
|
55
57
|
readonly initial?: string;
|
|
58
|
+
/** Hash of caller-supplied or derived replay identity for `kind: "custom"`. */
|
|
59
|
+
readonly customIdentityHash?: string;
|
|
60
|
+
/** Explains how a custom prompt replay identity was derived without storing the raw identity. */
|
|
61
|
+
readonly customIdentitySource?: CustomPromptIdentitySource;
|
|
56
62
|
/** Issue timestamp (ms since epoch). */
|
|
57
63
|
readonly createdAt: number;
|
|
58
64
|
}
|
|
@@ -119,6 +119,11 @@ export interface ResolveStagePendingPromptOptions {
|
|
|
119
119
|
readonly answerSource?: StagePromptAnswerSource;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
|
+
export interface RecordStagePromptAnswerOptions {
|
|
123
|
+
/** Identifies who answered the prompt so notification code can avoid echoing workflow-tool answers. */
|
|
124
|
+
readonly answerSource?: StagePromptAnswerSource;
|
|
125
|
+
}
|
|
126
|
+
|
|
122
127
|
export interface Store {
|
|
123
128
|
runs(): readonly RunSnapshot[];
|
|
124
129
|
notices(): readonly WorkflowNotice[];
|
|
@@ -206,6 +211,19 @@ export interface Store {
|
|
|
206
211
|
): boolean;
|
|
207
212
|
/** Wait for a stage/node-scoped HIL prompt to resolve. */
|
|
208
213
|
awaitStagePendingPrompt(runId: string, stageId: string, promptId: string): Promise<unknown>;
|
|
214
|
+
/**
|
|
215
|
+
* Record a live-only prompt answer for prompt-node UIs that do not use
|
|
216
|
+
* `stage.pendingPrompt` (notably arbitrary `ctx.ui.custom<T>` widgets).
|
|
217
|
+
* The raw value stays in the private answer ledger and is never serialized
|
|
218
|
+
* into snapshots or persistence.
|
|
219
|
+
*/
|
|
220
|
+
recordStagePromptAnswer(
|
|
221
|
+
runId: string,
|
|
222
|
+
stageId: string,
|
|
223
|
+
prompt: PendingPrompt,
|
|
224
|
+
response: unknown,
|
|
225
|
+
options?: RecordStagePromptAnswerOptions,
|
|
226
|
+
): boolean;
|
|
209
227
|
/**
|
|
210
228
|
* Record a live-only draft for an active stage-local input/editor prompt.
|
|
211
229
|
* Draft text may contain secrets and must never be copied into snapshots,
|
|
@@ -774,6 +792,39 @@ export function createStore(): Store {
|
|
|
774
792
|
});
|
|
775
793
|
},
|
|
776
794
|
|
|
795
|
+
recordStagePromptAnswer(
|
|
796
|
+
runId: string,
|
|
797
|
+
stageId: string,
|
|
798
|
+
prompt: PendingPrompt,
|
|
799
|
+
response: unknown,
|
|
800
|
+
options: RecordStagePromptAnswerOptions = {},
|
|
801
|
+
): boolean {
|
|
802
|
+
const run = findRun(runId);
|
|
803
|
+
if (!run) return false;
|
|
804
|
+
if (TERMINAL_STATUSES.has(run.status)) return false;
|
|
805
|
+
const stage = findStage(run, stageId);
|
|
806
|
+
if (!stage) return false;
|
|
807
|
+
if (isTerminalStageStatus(stage.status)) return false;
|
|
808
|
+
_stagePromptAnswers.set(stagePromptAnswerKey(runId, stageId), {
|
|
809
|
+
runId,
|
|
810
|
+
stageId,
|
|
811
|
+
promptId: prompt.id,
|
|
812
|
+
kind: prompt.kind,
|
|
813
|
+
value: response,
|
|
814
|
+
answeredAt: Date.now(),
|
|
815
|
+
...(options.answerSource !== undefined ? { answerSource: options.answerSource } : {}),
|
|
816
|
+
});
|
|
817
|
+
if (stage.promptFootprint === undefined) stage.promptFootprint = { ...prompt };
|
|
818
|
+
stage.promptAnswerState = "available";
|
|
819
|
+
if (stage.status === "awaiting_input") {
|
|
820
|
+
stage.status = "running";
|
|
821
|
+
delete stage.awaitingInputSince;
|
|
822
|
+
}
|
|
823
|
+
_version++;
|
|
824
|
+
notify();
|
|
825
|
+
return true;
|
|
826
|
+
},
|
|
827
|
+
|
|
777
828
|
recordStagePromptDraft(runId: string, stageId: string, promptId: string, text: string): boolean {
|
|
778
829
|
if (stageHasActiveTextPrompt(runId, stageId, promptId) === undefined) return false;
|
|
779
830
|
_stagePromptDrafts.set(stagePromptDraftKey(runId, stageId, promptId), text);
|
|
@@ -116,14 +116,24 @@ export interface WorkflowChildResult<TOutputs extends WorkflowOutputValues = Wor
|
|
|
116
116
|
* Each primitive suspends the current stage until the user responds.
|
|
117
117
|
* Mirrors pi ctx.ui.input / confirm / select / editor methods.
|
|
118
118
|
*/
|
|
119
|
-
export type
|
|
119
|
+
export type WorkflowCustomUiComponent = AuthoringContract.WorkflowCustomUiComponent;
|
|
120
|
+
export type WorkflowCustomUiTui = AuthoringContract.WorkflowCustomUiTui;
|
|
121
|
+
export type WorkflowCustomUiTheme = AuthoringContract.WorkflowCustomUiTheme;
|
|
122
|
+
export type WorkflowCustomUiKeybindings = AuthoringContract.WorkflowCustomUiKeybindings;
|
|
123
|
+
export type WorkflowCustomUiOverlayOptions = AuthoringContract.WorkflowCustomUiOverlayOptions;
|
|
124
|
+
export type WorkflowCustomUiOverlayHandle = AuthoringContract.WorkflowCustomUiOverlayHandle;
|
|
125
|
+
export type WorkflowCustomUiFactory<T> = AuthoringContract.WorkflowCustomUiFactory<T>;
|
|
126
|
+
export type WorkflowCustomUiOptions = AuthoringContract.WorkflowCustomUiOptions;
|
|
127
|
+
|
|
128
|
+
export interface WorkflowUIContext extends AuthoringContract.WorkflowUIContext {}
|
|
120
129
|
|
|
121
130
|
/**
|
|
122
131
|
* Adapter supplied by the pi runtime (or test harness) to back the HIL
|
|
123
|
-
* primitives.
|
|
124
|
-
* the executor
|
|
132
|
+
* primitives. The custom-widget method is optional for compatibility with
|
|
133
|
+
* existing primitive-only adapters; the executor normalizes a missing custom
|
|
134
|
+
* method to the same unavailable-UI rejection used in headless mode.
|
|
125
135
|
*/
|
|
126
|
-
export
|
|
136
|
+
export interface WorkflowUIAdapter extends AuthoringContract.WorkflowUIAdapter {}
|
|
127
137
|
|
|
128
138
|
// ---------------------------------------------------------------------------
|
|
129
139
|
// StageOptions — per-stage configuration + pi SDK session options
|
|
@@ -357,7 +357,10 @@ export class GraphView implements Component {
|
|
|
357
357
|
? expandWorkflowGraph(this.currentSnapshot, run.id)
|
|
358
358
|
: { stages: [], targets: new Map() };
|
|
359
359
|
const stages = [...this.expandedGraph.stages];
|
|
360
|
-
const hasStagePrompt = stages.some((stage) =>
|
|
360
|
+
const hasStagePrompt = stages.some((stage) =>
|
|
361
|
+
stage.pendingPrompt !== undefined ||
|
|
362
|
+
(stage.status === "awaiting_input" && stage.promptFootprint?.kind === "custom")
|
|
363
|
+
);
|
|
361
364
|
if (!hasStagePrompt) return stages;
|
|
362
365
|
return stages.filter((stage) => {
|
|
363
366
|
// Prompt-node injection can leave unstarted author stages in the store
|