@bastani/atomic 0.9.3-alpha.1 → 0.9.3-alpha.3
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 +15 -0
- package/dist/builtin/cursor/CHANGELOG.md +21 -0
- package/dist/builtin/cursor/README.md +2 -1
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/cursor/src/cursor-models-raw.json +2 -9
- package/dist/builtin/cursor/src/model-mapper.ts +14 -3
- package/dist/builtin/cursor/src/proto/protobuf-codec-base64.ts +22 -0
- package/dist/builtin/cursor/src/proto/protobuf-codec-request.ts +53 -13
- package/dist/builtin/cursor/src/proto/protobuf-codec-wire.ts +24 -7
- package/dist/builtin/cursor/src/proto/protobuf-codec.ts +3 -2
- package/dist/builtin/cursor/src/stream.ts +5 -11
- package/dist/builtin/cursor/src/transport-types.ts +3 -0
- package/dist/builtin/cursor/src/transport.ts +1 -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 +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +15 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/extension/fanout-child.ts +1 -0
- package/dist/builtin/subagents/src/extension/index.ts +6 -3
- package/dist/builtin/subagents/src/extension/schemas.ts +0 -5
- package/dist/builtin/subagents/src/runs/background/async-job-tracker.ts +1 -4
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +15 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +35 -1
- package/dist/builtin/subagents/src/runs/shared/subagent-prompt-runtime.ts +4 -2
- package/dist/builtin/subagents/src/shared/types-async.ts +1 -0
- package/dist/builtin/subagents/src/slash/prompt-template-bridge.ts +27 -5
- package/dist/builtin/subagents/src/tui/render-layout.ts +27 -4
- package/dist/builtin/subagents/src/tui/render-result-animation.ts +22 -31
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +6 -6
- package/dist/builtin/subagents/src/tui/render-result.ts +20 -19
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +3 -3
- package/dist/builtin/subagents/src/tui/render-widget.ts +46 -7
- package/dist/builtin/subagents/src/tui/render.ts +2 -2
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +49 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/authoring.d.ts +1 -1
- package/dist/builtin/workflows/src/durable/backend.ts +343 -0
- package/dist/builtin/workflows/src/durable/child-primitive.ts +79 -0
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +421 -0
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +171 -0
- package/dist/builtin/workflows/src/durable/factory.ts +96 -0
- package/dist/builtin/workflows/src/durable/file-backend.ts +433 -0
- package/dist/builtin/workflows/src/durable/index.ts +73 -0
- package/dist/builtin/workflows/src/durable/resume-catalog.ts +217 -0
- package/dist/builtin/workflows/src/durable/resume-runtime.ts +299 -0
- package/dist/builtin/workflows/src/durable/scoped-backend.ts +171 -0
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +284 -0
- package/dist/builtin/workflows/src/durable/tool-primitive.ts +180 -0
- package/dist/builtin/workflows/src/durable/types.ts +168 -0
- package/dist/builtin/workflows/src/durable/ui-primitive.ts +96 -0
- package/dist/builtin/workflows/src/engine/options.ts +3 -0
- package/dist/builtin/workflows/src/engine/primitives/parallel.ts +2 -2
- package/dist/builtin/workflows/src/engine/primitives/task.ts +4 -4
- package/dist/builtin/workflows/src/engine/primitives/ui.ts +22 -8
- package/dist/builtin/workflows/src/engine/primitives/workflow.ts +8 -0
- package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +69 -0
- package/dist/builtin/workflows/src/engine/run-durable-stage-session.ts +31 -0
- package/dist/builtin/workflows/src/engine/run.ts +148 -6
- package/dist/builtin/workflows/src/engine/runtime.ts +8 -2
- package/dist/builtin/workflows/src/extension/extension-factory.ts +6 -12
- package/dist/builtin/workflows/src/extension/extension-lifecycle.ts +5 -1
- package/dist/builtin/workflows/src/extension/extension-runtime-state.ts +3 -0
- package/dist/builtin/workflows/src/extension/runtime.ts +48 -9
- package/dist/builtin/workflows/src/extension/workflow-run-control-command.ts +143 -4
- package/dist/builtin/workflows/src/runs/background/quit.ts +61 -0
- package/dist/builtin/workflows/src/runs/background/status.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-direct-helpers.ts +5 -5
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-call.ts +74 -33
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-context.ts +20 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +8 -7
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-replay.ts +1 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-types.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/executor-types.ts +19 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-context.ts +4 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +10 -10
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-options.ts +5 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-send-user-message.ts +25 -0
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-types.ts +3 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +16 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.ts +20 -0
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.d.ts +23 -1
- package/dist/builtin/workflows/src/shared/authoring-contract-ui.ts +30 -1
- package/dist/builtin/workflows/src/shared/store-public-types.ts +6 -2
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +12 -6
- package/dist/builtin/workflows/src/shared/types.ts +55 -0
- package/dist/builtin/workflows/src/tui/graph-view-constants.ts +1 -1
- package/dist/builtin/workflows/src/tui/graph-view-graph-render.ts +41 -0
- package/dist/builtin/workflows/src/tui/graph-view-input.ts +82 -24
- package/dist/builtin/workflows/src/tui/graph-view-render.ts +7 -0
- package/dist/builtin/workflows/src/tui/graph-view-state.ts +22 -2
- package/dist/builtin/workflows/src/tui/graph-view-types.ts +4 -5
- package/dist/builtin/workflows/src/tui/overlay-adapter.ts +9 -11
- package/dist/builtin/workflows/src/tui/stage-chat-view-footer-status.ts +9 -3
- package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +11 -2
- package/dist/builtin/workflows/src/tui/stage-chat-view-live-events.ts +35 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +51 -17
- package/dist/builtin/workflows/src/tui/stage-chat-view-status.ts +36 -0
- package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +5 -1
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +3 -1
- package/dist/builtin/workflows/src/tui/status-list.ts +14 -2
- package/dist/builtin/workflows/src/tui/widget.ts +23 -8
- package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +5 -4
- package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -8
- package/dist/builtin/workflows/src/tui/workflow-resume-selector.ts +151 -0
- package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
- package/dist/core/extensions/loader-virtual-modules.js +47 -30
- package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
- package/dist/core/messages.d.ts +1 -0
- package/dist/core/messages.d.ts.map +1 -1
- package/dist/core/messages.js +46 -1
- package/dist/core/messages.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +12 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/session-manager-core.d.ts +15 -7
- package/dist/core/session-manager-core.d.ts.map +1 -1
- package/dist/core/session-manager-core.js +20 -9
- package/dist/core/session-manager-core.js.map +1 -1
- package/dist/core/session-manager-entries.d.ts +2 -2
- package/dist/core/session-manager-entries.d.ts.map +1 -1
- package/dist/core/session-manager-entries.js +9 -3
- package/dist/core/session-manager-entries.js.map +1 -1
- package/dist/core/session-manager-history.d.ts.map +1 -1
- package/dist/core/session-manager-history.js +2 -1
- package/dist/core/session-manager-history.js.map +1 -1
- package/dist/core/session-manager-list.d.ts +3 -3
- package/dist/core/session-manager-list.d.ts.map +1 -1
- package/dist/core/session-manager-list.js +27 -8
- package/dist/core/session-manager-list.js.map +1 -1
- package/dist/core/session-manager-storage.d.ts +3 -1
- package/dist/core/session-manager-storage.d.ts.map +1 -1
- package/dist/core/session-manager-storage.js +55 -12
- package/dist/core/session-manager-storage.js.map +1 -1
- package/dist/core/session-manager-tool-dependencies.d.ts +10 -0
- package/dist/core/session-manager-tool-dependencies.d.ts.map +1 -0
- package/dist/core/session-manager-tool-dependencies.js +133 -0
- package/dist/core/session-manager-tool-dependencies.js.map +1 -0
- package/dist/core/session-manager-types.d.ts +22 -0
- package/dist/core/session-manager-types.d.ts.map +1 -1
- package/dist/core/session-manager-types.js.map +1 -1
- package/dist/core/session-manager.d.ts +2 -2
- package/dist/core/session-manager.d.ts.map +1 -1
- package/dist/core/session-manager.js +1 -1
- package/dist/core/session-manager.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts +1 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-runtime.js +12 -0
- package/dist/modes/interactive/components/chat-session-host-runtime.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts +4 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.d.ts.map +1 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js +131 -0
- package/dist/modes/interactive/components/chat-session-host-terminal-cleanup.js.map +1 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts +2 -0
- package/dist/modes/interactive/components/chat-session-host.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host.js +7 -1
- package/dist/modes/interactive/components/chat-session-host.js.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-transcript.js +15 -4
- package/dist/modes/interactive/components/chat-transcript.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +3 -0
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +26 -0
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/docs/compaction.md +2 -0
- package/docs/models.md +1 -1
- package/docs/providers.md +2 -1
- package/docs/session-format.md +6 -0
- package/docs/sessions.md +6 -0
- package/docs/workflows.md +105 -3
- package/package.json +4 -3
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
MountedStageCustomUi,
|
|
12
12
|
StageUiBroker,
|
|
13
13
|
} from "../shared/stage-ui-broker.js";
|
|
14
|
-
import type { StageNotice, StageStatus } from "../shared/store-types.js";
|
|
14
|
+
import type { RunStatus, StageNotice, StageStatus } from "../shared/store-types.js";
|
|
15
15
|
import type { GraphTheme } from "./graph-theme.js";
|
|
16
16
|
import type { PromptCardState } from "./prompt-card.js";
|
|
17
17
|
|
|
@@ -19,6 +19,7 @@ export const VIEW_LINE_COUNT = 32;
|
|
|
19
19
|
export const PROMPT_SCROLL_STEP_ROWS = 4;
|
|
20
20
|
export const HEADER_ROWS = 1;
|
|
21
21
|
export const SEP_ROWS = 1;
|
|
22
|
+
export const STAGE_CHAT_MOUSE_SCROLL_TOGGLE_LABEL = "ctrl+t";
|
|
22
23
|
|
|
23
24
|
export function isReadOnlyArchiveStatus(status: StageStatus): boolean {
|
|
24
25
|
return status === "completed" || status === "failed" || status === "skipped";
|
|
@@ -144,6 +145,9 @@ export interface StageChatViewContext {
|
|
|
144
145
|
promptScrollOffset: number;
|
|
145
146
|
promptMaxScroll: number;
|
|
146
147
|
localPaused: boolean;
|
|
148
|
+
mouseScrollCaptureEnabled: boolean;
|
|
149
|
+
lastObservedStageStatus: StageStatus | undefined;
|
|
150
|
+
lastObservedRunStatus: RunStatus | undefined;
|
|
147
151
|
seenNoticeIds: Set<string>;
|
|
148
152
|
_unsubscribeStore: (() => void) | null;
|
|
149
153
|
_unsubscribeHandle: (() => void) | null;
|
|
@@ -95,6 +95,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
95
95
|
private promptScrollOffset!: StageChatViewContext["promptScrollOffset"];
|
|
96
96
|
private promptMaxScroll!: StageChatViewContext["promptMaxScroll"];
|
|
97
97
|
private localPaused!: StageChatViewContext["localPaused"];
|
|
98
|
+
private mouseScrollCaptureEnabled!: StageChatViewContext["mouseScrollCaptureEnabled"];
|
|
98
99
|
private seenNoticeIds!: StageChatViewContext["seenNoticeIds"];
|
|
99
100
|
private _unsubscribeStore!: StageChatViewContext["_unsubscribeStore"];
|
|
100
101
|
private _unsubscribeHandle!: StageChatViewContext["_unsubscribeHandle"];
|
|
@@ -177,7 +178,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
177
178
|
}
|
|
178
179
|
|
|
179
180
|
wantsMouseScrollTracking(): boolean {
|
|
180
|
-
return
|
|
181
|
+
return this.mouseScrollCaptureEnabled;
|
|
181
182
|
}
|
|
182
183
|
|
|
183
184
|
handleInput(data: string): boolean {
|
|
@@ -220,6 +221,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
220
221
|
void this.promptEditorSubmitFromEnter;
|
|
221
222
|
void this.promptScrollOffset;
|
|
222
223
|
void this.promptMaxScroll;
|
|
224
|
+
void this.mouseScrollCaptureEnabled;
|
|
223
225
|
void this.seenNoticeIds;
|
|
224
226
|
void this._unsubscribeStore;
|
|
225
227
|
void this._unsubscribeHandle;
|
|
@@ -56,6 +56,10 @@ export interface RenderStatusListOpts {
|
|
|
56
56
|
width?: number;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
function isQuitRun(run: RunSnapshot): boolean {
|
|
60
|
+
return run.endedAt === undefined && run.status === "paused" && run.exitReason === "quit";
|
|
61
|
+
}
|
|
62
|
+
|
|
59
63
|
/**
|
|
60
64
|
* Render a list of run snapshots as the canonical rounded `BACKGROUND`
|
|
61
65
|
* surface: one panel plus one card per run.
|
|
@@ -157,6 +161,7 @@ function renderRunEntry(
|
|
|
157
161
|
|
|
158
162
|
function runAccent(run: RunSnapshot, theme?: GraphTheme): string {
|
|
159
163
|
if (!theme) return "#000000";
|
|
164
|
+
if (isQuitRun(run)) return theme.warning;
|
|
160
165
|
switch (run.status) {
|
|
161
166
|
case "completed": return theme.success;
|
|
162
167
|
case "running": return theme.warning;
|
|
@@ -172,6 +177,7 @@ function runAccent(run: RunSnapshot, theme?: GraphTheme): string {
|
|
|
172
177
|
}
|
|
173
178
|
|
|
174
179
|
function runTrailing(run: RunSnapshot, theme?: GraphTheme): { text: string; fg?: string } | undefined {
|
|
180
|
+
if (isQuitRun(run)) return { text: "○ quit", fg: theme?.warning };
|
|
175
181
|
switch (run.status) {
|
|
176
182
|
case "completed": return { text: "✓ completed", fg: theme?.success };
|
|
177
183
|
case "running": return { text: "● running", fg: theme?.warning };
|
|
@@ -205,6 +211,7 @@ function runCardMeta(run: RunSnapshot, now: number): string {
|
|
|
205
211
|
? fmtDuration(elapsedRunMs(run, now))
|
|
206
212
|
: undefined;
|
|
207
213
|
|
|
214
|
+
if (isQuitRun(run)) return "resumable via /workflow resume";
|
|
208
215
|
if (run.status === "running") {
|
|
209
216
|
if (isChain) parts.push(`${done}/${total}`);
|
|
210
217
|
const labels = runningStageLabels(run);
|
|
@@ -315,15 +322,17 @@ function effectiveWidth(width?: number): number {
|
|
|
315
322
|
interface Counts {
|
|
316
323
|
active: number;
|
|
317
324
|
paused: number;
|
|
325
|
+
quit: number;
|
|
318
326
|
completed: number;
|
|
319
327
|
failed: number;
|
|
320
328
|
pending: number;
|
|
321
329
|
}
|
|
322
330
|
|
|
323
331
|
function countBuckets(runs: readonly RunSnapshot[]): Counts {
|
|
324
|
-
const c: Counts = { active: 0, paused: 0, completed: 0, failed: 0, pending: 0 };
|
|
332
|
+
const c: Counts = { active: 0, paused: 0, quit: 0, completed: 0, failed: 0, pending: 0 };
|
|
325
333
|
for (const r of runs) {
|
|
326
|
-
if (r
|
|
334
|
+
if (isQuitRun(r)) c.quit++;
|
|
335
|
+
else if (r.endedAt === undefined) {
|
|
327
336
|
if (r.status === "pending") c.pending++;
|
|
328
337
|
else if (r.status === "paused") c.paused++;
|
|
329
338
|
else if (r.status === "running") c.active++;
|
|
@@ -343,6 +352,7 @@ function themedBadges(c: Counts, theme: GraphTheme): FlatBandBadge[] {
|
|
|
343
352
|
// Keep the word label: the pause glyph is less familiar than the other
|
|
344
353
|
// status glyphs, so this intentional asymmetry improves scanability.
|
|
345
354
|
if (c.paused > 0) out.push({ text: `❚❚ ${c.paused} paused`, fg: theme.warning });
|
|
355
|
+
if (c.quit > 0) out.push({ text: `${c.quit} quit`, fg: theme.warning });
|
|
346
356
|
if (c.pending > 0) out.push({ text: `○ ${c.pending}`, fg: theme.dim });
|
|
347
357
|
if (c.failed > 0) out.push({ text: `⊘ ${c.failed}`, fg: theme.error });
|
|
348
358
|
return out;
|
|
@@ -355,6 +365,7 @@ function plainBadges(c: Counts): FlatBandBadge[] {
|
|
|
355
365
|
// Keep the word label: the pause glyph is less familiar than the other
|
|
356
366
|
// status glyphs, so this intentional asymmetry improves scanability.
|
|
357
367
|
if (c.paused > 0) out.push({ text: `❚❚ ${c.paused} paused` });
|
|
368
|
+
if (c.quit > 0) out.push({ text: `${c.quit} quit` });
|
|
358
369
|
if (c.pending > 0) out.push({ text: `○ ${c.pending}` });
|
|
359
370
|
if (c.failed > 0) out.push({ text: `⊘ ${c.failed}` });
|
|
360
371
|
return out;
|
|
@@ -381,6 +392,7 @@ function emptyStateLine(theme?: GraphTheme): string {
|
|
|
381
392
|
}
|
|
382
393
|
|
|
383
394
|
function statusIconForRun(run: RunSnapshot): string {
|
|
395
|
+
if (isQuitRun(run)) return "○";
|
|
384
396
|
switch (run.status) {
|
|
385
397
|
case "completed": return "✓";
|
|
386
398
|
case "skipped": return "⊘";
|
|
@@ -73,9 +73,14 @@ function recentlyEnded(run: RunSnapshot, now: number): boolean {
|
|
|
73
73
|
return run.endedAt !== undefined && now - run.endedAt <= RECENT_ENDED_WINDOW_MS;
|
|
74
74
|
}
|
|
75
75
|
|
|
76
|
+
function isQuitRun(run: RunSnapshot): boolean {
|
|
77
|
+
return run.endedAt === undefined && run.status === "paused" && run.exitReason === "quit";
|
|
78
|
+
}
|
|
79
|
+
|
|
76
80
|
interface RunCounts {
|
|
77
81
|
active: number;
|
|
78
82
|
paused: number;
|
|
83
|
+
quit: number;
|
|
79
84
|
done: number;
|
|
80
85
|
failed: number;
|
|
81
86
|
/** Runs with a pending HIL prompt — surfaced as a separate badge so the
|
|
@@ -106,9 +111,10 @@ function countRuns(
|
|
|
106
111
|
runs: readonly RunSnapshot[],
|
|
107
112
|
allRuns: readonly RunSnapshot[] = runs,
|
|
108
113
|
): RunCounts {
|
|
109
|
-
const counts: RunCounts = { active: 0, paused: 0, done: 0, failed: 0, awaiting: 0 };
|
|
114
|
+
const counts: RunCounts = { active: 0, paused: 0, quit: 0, done: 0, failed: 0, awaiting: 0 };
|
|
110
115
|
for (const r of runs) {
|
|
111
|
-
if (r
|
|
116
|
+
if (isQuitRun(r)) counts.quit++;
|
|
117
|
+
else if (r.endedAt === undefined && r.status === "paused") counts.paused++;
|
|
112
118
|
else if (r.endedAt === undefined) counts.active++;
|
|
113
119
|
else if (r.status === "completed" || r.status === "skipped" || r.status === "cancelled" || r.status === "blocked") counts.done++;
|
|
114
120
|
else if (r.status === "failed" || r.status === "killed") counts.failed++;
|
|
@@ -165,6 +171,7 @@ function shortId(run: RunSnapshot): string {
|
|
|
165
171
|
}
|
|
166
172
|
|
|
167
173
|
function statusGlyph(run: RunSnapshot): string {
|
|
174
|
+
if (isQuitRun(run)) return "○";
|
|
168
175
|
switch (run.status) {
|
|
169
176
|
case "running":
|
|
170
177
|
return "●";
|
|
@@ -188,6 +195,7 @@ function statusGlyph(run: RunSnapshot): string {
|
|
|
188
195
|
}
|
|
189
196
|
|
|
190
197
|
function statusFg(run: RunSnapshot, theme: GraphTheme): string {
|
|
198
|
+
if (isQuitRun(run)) return theme.warning;
|
|
191
199
|
switch (run.status) {
|
|
192
200
|
case "running":
|
|
193
201
|
case "paused":
|
|
@@ -237,6 +245,7 @@ function metaLine(run: RunSnapshot, now: number): string {
|
|
|
237
245
|
if (run.endedAt !== undefined) {
|
|
238
246
|
return elapsedLabel(run, now);
|
|
239
247
|
}
|
|
248
|
+
if (isQuitRun(run)) return "quit · resumable via /workflow resume";
|
|
240
249
|
const parts: string[] = [modeLabel(run)];
|
|
241
250
|
const prog = progressLabel(run);
|
|
242
251
|
if (prog) parts.push(prog);
|
|
@@ -257,6 +266,9 @@ function countBadges(counts: RunCounts, theme: GraphTheme): FlatBandBadge[] {
|
|
|
257
266
|
if (counts.paused > 0) {
|
|
258
267
|
badges.push({ text: `❚❚ ${counts.paused} paused`, fg: theme.warning });
|
|
259
268
|
}
|
|
269
|
+
if (counts.quit > 0) {
|
|
270
|
+
badges.push({ text: `${counts.quit} quit`, fg: theme.warning });
|
|
271
|
+
}
|
|
260
272
|
// Awaiting input is shown in Sky per DESIGN.md status semantics: a live
|
|
261
273
|
// human-in-the-loop request that requires attention. Mirror the graph node's
|
|
262
274
|
// question-mark status glyph, then keep ↵ as the attach/respond action hint.
|
|
@@ -332,16 +344,18 @@ function themedCollapsed(
|
|
|
332
344
|
const dim = hexToAnsi(theme.dim);
|
|
333
345
|
const muted = hexToAnsi(theme.textMuted);
|
|
334
346
|
const warning = hexToAnsi(theme.warning);
|
|
335
|
-
const total = counts.active + counts.paused + counts.done + counts.failed;
|
|
347
|
+
const total = counts.active + counts.paused + counts.quit + counts.done + counts.failed;
|
|
336
348
|
const active = counts.active;
|
|
337
349
|
const paused = counts.paused > 0 ? `${dim} · ${RESET}${warning}${counts.paused} ❚❚${RESET}` : "";
|
|
338
|
-
|
|
350
|
+
const quit = counts.quit > 0 ? `${dim} · ${RESET}${warning}${counts.quit} quit${RESET}` : "";
|
|
351
|
+
return ` ${mauve}▾${RESET} ${muted}${total} background${RESET}${dim} · ${RESET}${warning}${active} ●${RESET}${paused}${quit}`;
|
|
339
352
|
}
|
|
340
353
|
|
|
341
354
|
function plainCollapsed(counts: RunCounts): string {
|
|
342
|
-
const total = counts.active + counts.paused + counts.done + counts.failed;
|
|
355
|
+
const total = counts.active + counts.paused + counts.quit + counts.done + counts.failed;
|
|
343
356
|
const paused = counts.paused > 0 ? ` · ${counts.paused} ❚❚` : "";
|
|
344
|
-
|
|
357
|
+
const quit = counts.quit > 0 ? ` · ${counts.quit} quit` : "";
|
|
358
|
+
return ` ▾ ${total} background · ${counts.active} ●${paused}${quit}`;
|
|
345
359
|
}
|
|
346
360
|
|
|
347
361
|
// ---------------------------------------------------------------------------
|
|
@@ -372,7 +386,8 @@ export function buildThemedWidgetLines(
|
|
|
372
386
|
// visually persists for a beat before dropping off.
|
|
373
387
|
const visibleCounts: RunCounts = {
|
|
374
388
|
active: display.filter((r) => r.endedAt === undefined && r.status !== "paused").length,
|
|
375
|
-
paused: display.filter((r) => r.endedAt === undefined && r.status === "paused").length,
|
|
389
|
+
paused: display.filter((r) => r.endedAt === undefined && r.status === "paused" && !isQuitRun(r)).length,
|
|
390
|
+
quit: display.filter(isQuitRun).length,
|
|
376
391
|
done: display.filter((r) => r.endedAt !== undefined && (r.status === "completed" || r.status === "skipped" || r.status === "cancelled" || r.status === "blocked")).length,
|
|
377
392
|
failed: display.filter((r) => r.endedAt !== undefined && (r.status === "failed" || r.status === "killed")).length,
|
|
378
393
|
awaiting: counts.awaiting,
|
|
@@ -386,7 +401,7 @@ export function buildThemedWidgetLines(
|
|
|
386
401
|
return [themed ? themedCollapsed(visibleCounts, graphTheme) : plainCollapsed(visibleCounts)];
|
|
387
402
|
}
|
|
388
403
|
|
|
389
|
-
const total = counts.active + counts.paused + counts.done + counts.failed;
|
|
404
|
+
const total = counts.active + counts.paused + counts.quit + counts.done + counts.failed;
|
|
390
405
|
const subtitle = `${total} run${total === 1 ? "" : "s"}`;
|
|
391
406
|
|
|
392
407
|
const badgeList = countBadges(visibleCounts, graphTheme);
|
|
@@ -29,8 +29,8 @@ export interface WorkflowAttachPaneOpts {
|
|
|
29
29
|
onClose: () => void;
|
|
30
30
|
/** Called when the user requests the host to hide the popup. */
|
|
31
31
|
onHide?: () => void;
|
|
32
|
-
/** Called when the user
|
|
33
|
-
|
|
32
|
+
/** Called when the user quits/detaches the active run (q in graph mode). */
|
|
33
|
+
onQuit?: (runId: string) => void;
|
|
34
34
|
/** Called when the user resolves a HIL prompt via the graph view. */
|
|
35
35
|
onPromptResolve?: (runId: string, promptId: string, response: unknown) => void;
|
|
36
36
|
/** Live pi-tui host objects used by attached stage chat to reuse coding-agent editor UI. */
|
|
@@ -73,8 +73,9 @@ export interface WorkflowAttachPaneOpts {
|
|
|
73
73
|
requestFocus?: () => void;
|
|
74
74
|
/**
|
|
75
75
|
* Host hook for terminal mouse reporting. Graph mode uses wheel input
|
|
76
|
-
* for canvas scrolling
|
|
77
|
-
* scrolling
|
|
76
|
+
* for canvas scrolling. Stage-chat mode captures wheel input by default so
|
|
77
|
+
* transcript/prompt scrolling stays inside the active workflow chat; ctrl+t
|
|
78
|
+
* toggles copy mode, which disables capture for terminal text selection.
|
|
78
79
|
*/
|
|
79
80
|
setMouseScrollTracking?: (enabled: boolean) => void;
|
|
80
81
|
/** Optional clock injection for deterministic transition-quarantine tests. */
|
|
@@ -19,7 +19,6 @@
|
|
|
19
19
|
* - src/tui/stage-chat-view.ts (chat mode Component)
|
|
20
20
|
* - src/runs/foreground/stage-control-registry.ts (live handles)
|
|
21
21
|
*/
|
|
22
|
-
|
|
23
22
|
import type { Component, EditorComponent, EditorTheme, TUI } from "@earendil-works/pi-tui";
|
|
24
23
|
import type { ChatMessageRenderOptions, ReadonlyFooterDataProvider } from "@bastani/atomic";
|
|
25
24
|
import type { Store } from "../shared/store.js";
|
|
@@ -39,7 +38,6 @@ import type { StageUiBroker } from "../shared/stage-ui-broker.js";
|
|
|
39
38
|
import type { StageSnapshot, StoreSnapshot } from "../shared/store-types.js";
|
|
40
39
|
import { expandWorkflowGraph } from "../shared/expanded-workflow-graph.js";
|
|
41
40
|
import { WORKFLOW_STATUS_KEY } from "./workflow-status.js";
|
|
42
|
-
|
|
43
41
|
/**
|
|
44
42
|
* Surface used to write Pi's footer/status tag while the attach pane is
|
|
45
43
|
* mounted. Passing `undefined` clears the slot — required on dispose so
|
|
@@ -50,7 +48,6 @@ import { WORKFLOW_STATUS_KEY } from "./workflow-status.js";
|
|
|
50
48
|
*/
|
|
51
49
|
import type { AttachUiStatusSurface, WorkflowAttachPaneMode, WorkflowAttachPaneOpts } from "./workflow-attach-pane-types.js";
|
|
52
50
|
const ENTER_TRANSITION_QUARANTINE_MS = 200;
|
|
53
|
-
|
|
54
51
|
export class WorkflowAttachPane implements Component {
|
|
55
52
|
private store: Store;
|
|
56
53
|
private theme: GraphTheme;
|
|
@@ -60,7 +57,7 @@ export class WorkflowAttachPane implements Component {
|
|
|
60
57
|
private uiStatus: AttachUiStatusSurface | undefined;
|
|
61
58
|
private onClose: () => void;
|
|
62
59
|
private onHide?: () => void;
|
|
63
|
-
private
|
|
60
|
+
private onQuit?: (runId: string) => void;
|
|
64
61
|
private onPromptResolve?: (runId: string, promptId: string, response: unknown) => void;
|
|
65
62
|
private getViewportRows?: () => number | undefined;
|
|
66
63
|
private hostRequestRender?: () => void;
|
|
@@ -73,7 +70,6 @@ export class WorkflowAttachPane implements Component {
|
|
|
73
70
|
private getChatRenderSettings?: () => Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">> | undefined;
|
|
74
71
|
private footerData?: ReadonlyFooterDataProvider;
|
|
75
72
|
private now: () => number;
|
|
76
|
-
|
|
77
73
|
private mode: WorkflowAttachPaneMode = "graph";
|
|
78
74
|
private visible = true;
|
|
79
75
|
private graphView: GraphView;
|
|
@@ -97,7 +93,7 @@ export class WorkflowAttachPane implements Component {
|
|
|
97
93
|
this.uiStatus = opts.uiStatus;
|
|
98
94
|
this.onClose = opts.onClose;
|
|
99
95
|
this.onHide = opts.onHide;
|
|
100
|
-
this.
|
|
96
|
+
this.onQuit = opts.onQuit;
|
|
101
97
|
this.onPromptResolve = opts.onPromptResolve;
|
|
102
98
|
this.getViewportRows = opts.getViewportRows;
|
|
103
99
|
this.hostRequestRender = opts.requestRender;
|
|
@@ -130,7 +126,7 @@ export class WorkflowAttachPane implements Component {
|
|
|
130
126
|
graphTheme: this.theme,
|
|
131
127
|
onClose: this.onClose,
|
|
132
128
|
onHide: this.onHide,
|
|
133
|
-
|
|
129
|
+
onQuit: this.onQuit,
|
|
134
130
|
onPromptResolve: this.onPromptResolve,
|
|
135
131
|
onStageAttach: (runId, stageId) => this._attachToStage(runId, stageId, {
|
|
136
132
|
suppressInitialPromptSubmit: true,
|
|
@@ -366,7 +362,11 @@ export class WorkflowAttachPane implements Component {
|
|
|
366
362
|
if (!this.visible) return false;
|
|
367
363
|
if (this.mode === "stage-chat" && this.chatView) {
|
|
368
364
|
if (this._shouldQuarantineStagePromptEnter(data)) return true;
|
|
369
|
-
|
|
365
|
+
const beforeMouseTracking = this.chatView.wantsMouseScrollTracking();
|
|
366
|
+
const handled = this.chatView.handleInput(data);
|
|
367
|
+
const afterMouseTracking = this.chatView?.wantsMouseScrollTracking();
|
|
368
|
+
if (afterMouseTracking !== undefined && afterMouseTracking !== beforeMouseTracking) this._syncMouseScrollTracking();
|
|
369
|
+
return handled;
|
|
370
370
|
}
|
|
371
371
|
if (this._shouldQuarantineGraphEnter(data)) return true;
|
|
372
372
|
return this.graphView.handleInput(data);
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { SessionSelectorComponent, type SessionInfo } from "@bastani/atomic";
|
|
2
|
+
import type { ResumableWorkflowEntry } from "../durable/types.js";
|
|
3
|
+
import type {
|
|
4
|
+
PiCustomComponent,
|
|
5
|
+
PiCustomOverlayFactoryTui,
|
|
6
|
+
PiCustomOverlayFunction,
|
|
7
|
+
} from "../extension/wiring.js";
|
|
8
|
+
import type { RunSnapshot, StageSnapshot } from "../shared/store-types.js";
|
|
9
|
+
|
|
10
|
+
export type WorkflowResumeSelectorResult =
|
|
11
|
+
| { kind: "live"; runId: string }
|
|
12
|
+
| { kind: "durable"; workflowId: string }
|
|
13
|
+
| { kind: "close" };
|
|
14
|
+
|
|
15
|
+
export interface WorkflowResumeSelectorUiSurface {
|
|
16
|
+
custom?: PiCustomOverlayFunction;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface WorkflowResumeSelectorItem {
|
|
20
|
+
readonly result: Exclude<WorkflowResumeSelectorResult, { kind: "close" }>;
|
|
21
|
+
readonly session: SessionInfo;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function latestStageTimestamp(stage: StageSnapshot): number {
|
|
25
|
+
return stage.endedAt ?? stage.startedAt ?? 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function latestRunTimestamp(run: RunSnapshot): number {
|
|
29
|
+
const stageTimes = run.stages.map(latestStageTimestamp);
|
|
30
|
+
return Math.max(run.endedAt ?? 0, run.resumedAt ?? 0, run.pausedAt ?? 0, run.startedAt, ...stageTimes);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function completedStageCount(run: RunSnapshot): number {
|
|
34
|
+
return run.stages.filter((stage) => stage.status === "completed" || stage.status === "failed").length;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function liveRunSession(run: RunSnapshot): WorkflowResumeSelectorItem {
|
|
38
|
+
const completed = completedStageCount(run);
|
|
39
|
+
const total = run.stages.length;
|
|
40
|
+
const modified = new Date(latestRunTimestamp(run));
|
|
41
|
+
const firstMessage = `${run.name} ${run.status} ${completed}/${total} stages`;
|
|
42
|
+
return {
|
|
43
|
+
result: { kind: "live", runId: run.id },
|
|
44
|
+
session: {
|
|
45
|
+
path: `workflow-live:${run.id}`,
|
|
46
|
+
id: run.id,
|
|
47
|
+
cwd: "Live workflow runs",
|
|
48
|
+
created: new Date(run.startedAt),
|
|
49
|
+
modified,
|
|
50
|
+
messageCount: total,
|
|
51
|
+
firstMessage,
|
|
52
|
+
allMessagesText: `${run.id} ${run.name} ${run.status} ${completed}/${total} stages`,
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function durableWorkflowSession(entry: ResumableWorkflowEntry): WorkflowResumeSelectorItem {
|
|
58
|
+
const checkpointText = `${entry.completedCheckpoints} checkpoints`;
|
|
59
|
+
const promptText = `${entry.pendingPrompts} prompts`;
|
|
60
|
+
const firstMessage = `${entry.name} ${entry.status} ${checkpointText} ${promptText}`;
|
|
61
|
+
return {
|
|
62
|
+
result: { kind: "durable", workflowId: entry.workflowId },
|
|
63
|
+
session: {
|
|
64
|
+
path: `workflow-durable:${entry.workflowId}`,
|
|
65
|
+
id: entry.workflowId,
|
|
66
|
+
cwd: "Durable workflow runs",
|
|
67
|
+
created: new Date(entry.createdAt),
|
|
68
|
+
modified: new Date(entry.updatedAt),
|
|
69
|
+
messageCount: entry.completedCheckpoints,
|
|
70
|
+
firstMessage,
|
|
71
|
+
allMessagesText: `${entry.workflowId} ${entry.name} ${entry.status} ${checkpointText} ${promptText}`,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function workflowResumeSelectorItems(
|
|
77
|
+
liveRuns: readonly RunSnapshot[],
|
|
78
|
+
durableEntries: readonly ResumableWorkflowEntry[],
|
|
79
|
+
): WorkflowResumeSelectorItem[] {
|
|
80
|
+
const liveIds = new Set(liveRuns.map((run) => run.id));
|
|
81
|
+
return [
|
|
82
|
+
...liveRuns.map(liveRunSession),
|
|
83
|
+
...durableEntries
|
|
84
|
+
.filter((entry) => !liveIds.has(entry.workflowId))
|
|
85
|
+
.map(durableWorkflowSession),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function openWorkflowResumeSelector(
|
|
90
|
+
ui: WorkflowResumeSelectorUiSurface,
|
|
91
|
+
liveRuns: readonly RunSnapshot[],
|
|
92
|
+
durableEntries: readonly ResumableWorkflowEntry[],
|
|
93
|
+
): Promise<WorkflowResumeSelectorResult> {
|
|
94
|
+
const custom = ui.custom;
|
|
95
|
+
if (typeof custom !== "function") return Promise.resolve({ kind: "close" });
|
|
96
|
+
|
|
97
|
+
const items = workflowResumeSelectorItems(liveRuns, durableEntries);
|
|
98
|
+
|
|
99
|
+
const resultByPath = new Map(items.map((item) => [item.session.path, item.result]));
|
|
100
|
+
const sessions = items.map((item) => item.session);
|
|
101
|
+
const loadSessions = async (onProgress?: (loaded: number, total: number) => void): Promise<SessionInfo[]> => {
|
|
102
|
+
onProgress?.(sessions.length, sessions.length);
|
|
103
|
+
return [...sessions];
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return new Promise<WorkflowResumeSelectorResult>((resolve) => {
|
|
107
|
+
let settled = false;
|
|
108
|
+
const settle = (result: WorkflowResumeSelectorResult, done?: (result: undefined) => void): void => {
|
|
109
|
+
if (settled) return;
|
|
110
|
+
settled = true;
|
|
111
|
+
resolve(result);
|
|
112
|
+
done?.(undefined);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const factory = (
|
|
116
|
+
tui: PiCustomOverlayFactoryTui,
|
|
117
|
+
_theme: unknown,
|
|
118
|
+
_keys: unknown,
|
|
119
|
+
done: (result: undefined) => void,
|
|
120
|
+
): PiCustomComponent => {
|
|
121
|
+
const selector = new SessionSelectorComponent(
|
|
122
|
+
loadSessions,
|
|
123
|
+
loadSessions,
|
|
124
|
+
(path) => settle(resultByPath.get(path) ?? { kind: "close" }, done),
|
|
125
|
+
() => settle({ kind: "close" }, done),
|
|
126
|
+
() => settle({ kind: "close" }, done),
|
|
127
|
+
() => tui.requestRender?.(),
|
|
128
|
+
{ showRenameHint: false },
|
|
129
|
+
);
|
|
130
|
+
// Workflow rows are synthetic SessionInfo records. Reuse the /resume
|
|
131
|
+
// selector chrome, but never let its session-file delete action touch a
|
|
132
|
+
// path derived from a workflow id.
|
|
133
|
+
selector.getSessionList().onDeleteSession = async () => {
|
|
134
|
+
tui.requestRender?.();
|
|
135
|
+
};
|
|
136
|
+
selector.focused = true;
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
render: (width) => selector.render(width),
|
|
140
|
+
handleInput: (data) => selector.handleInput(data),
|
|
141
|
+
invalidate: () => {
|
|
142
|
+
selector.invalidate?.();
|
|
143
|
+
tui.requestRender?.();
|
|
144
|
+
},
|
|
145
|
+
dispose: () => settle({ kind: "close" }),
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
void custom(factory, { overlay: false });
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader-virtual-modules.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader-virtual-modules.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"loader-virtual-modules.d.ts","sourceRoot":"","sources":["../../../src/core/extensions/loader-virtual-modules.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AA4DnD,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,mBAAmB,IAAI,IAAI,CAI1C;AAED,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,mBAAmB,CAOrE;AAoED,wBAAsB,mBAAmB,CACvC,aAAa,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,mBAAmB,GAC/B,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAmBvC","sourcesContent":["import * as fs from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport * as path from \"node:path\";\nimport { fileURLToPath, pathToFileURL } from \"node:url\";\nimport { createJiti } from \"jiti/static\";\nimport { isBunBinary } from \"../../config.ts\";\nimport { resolvePath } from \"../../utils/paths.ts\";\nimport type { ExtensionFactory } from \"./types.ts\";\n\nconst require = createRequire(import.meta.url);\nlet _virtualModules: Record<string, object> | null = null;\nlet _virtualModulesPromise: Promise<Record<string, object>> | null = null;\n\nasync function loadVirtualModules(): Promise<Record<string, object>> {\n const [typebox, typeboxCompile, typeboxValue, piAgentCore, piTui, piAi, piAiOauth, piCodingAgent] = await Promise.all([\n import(\"typebox\"),\n import(\"typebox/compile\"),\n import(\"typebox/value\"),\n import(\"@earendil-works/pi-agent-core\"),\n import(\"@earendil-works/pi-tui\"),\n import(\"@earendil-works/pi-ai\"),\n import(\"@earendil-works/pi-ai/oauth\"),\n // NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,\n // avoiding a circular dependency while preserving the package-name extension import path.\n import(\"../../index.ts\"),\n ]);\n\n return {\n typebox,\n \"typebox/compile\": typeboxCompile,\n \"typebox/value\": typeboxValue,\n \"@sinclair/typebox\": typebox,\n \"@sinclair/typebox/compile\": typeboxCompile,\n \"@sinclair/typebox/value\": typeboxValue,\n \"@earendil-works/pi-agent-core\": piAgentCore,\n \"@earendil-works/pi-tui\": piTui,\n \"@earendil-works/pi-ai\": piAi,\n \"@earendil-works/pi-ai/oauth\": piAiOauth,\n \"@bastani/atomic\": piCodingAgent,\n \"@mariozechner/pi-agent-core\": piAgentCore,\n \"@mariozechner/pi-tui\": piTui,\n \"@mariozechner/pi-ai\": piAi,\n \"@mariozechner/pi-ai/oauth\": piAiOauth,\n };\n}\n\n/** Modules available to extensions via virtualModules (for compiled Bun binary). */\nasync function getVirtualModules(): Promise<Record<string, object>> {\n if (_virtualModules) return _virtualModules;\n _virtualModulesPromise ??= loadVirtualModules().then(\n (virtualModules) => {\n _virtualModules = virtualModules;\n return virtualModules;\n },\n (error: Error) => {\n _virtualModulesPromise = null;\n throw error;\n },\n );\n return _virtualModulesPromise;\n}\nlet _aliases: Record<string, string> | null = null;\n\nlet extensionCacheCwd: string | undefined;\nlet extensionCacheGeneration = 0;\nconst extensionCache = new Map<string, ExtensionFactory>();\n\nexport interface ExtensionCacheToken {\n cwd: string;\n generation: number;\n}\n\nexport function clearExtensionCache(): void {\n extensionCache.clear();\n extensionCacheCwd = undefined;\n extensionCacheGeneration++;\n}\n\nexport function useExtensionCacheCwd(cwd: string): ExtensionCacheToken {\n const resolvedCwd = resolvePath(cwd);\n if (extensionCacheCwd !== undefined && extensionCacheCwd !== resolvedCwd) {\n clearExtensionCache();\n }\n extensionCacheCwd = resolvedCwd;\n return { cwd: resolvedCwd, generation: extensionCacheGeneration };\n}\n\nfunction isCurrentCacheToken(cacheToken: ExtensionCacheToken | undefined): cacheToken is ExtensionCacheToken {\n return (\n cacheToken !== undefined &&\n extensionCacheCwd === cacheToken.cwd &&\n extensionCacheGeneration === cacheToken.generation\n );\n}\n\nfunction extensionImportSpecifier(extensionPath: string, cacheToken: ExtensionCacheToken | undefined): string {\n const url = pathToFileURL(extensionPath);\n const cacheKey = cacheToken ? `${cacheToken.generation}:${cacheToken.cwd}` : `${Date.now()}:${Math.random()}`;\n url.searchParams.set(\"atomicExtensionCache\", cacheKey);\n return url.href;\n}\n\n/**\n * Get aliases for jiti (used in Node.js/development mode).\n * In Bun binary mode, virtualModules is used instead.\n */\nfunction getAliases(): Record<string, string> {\n if (_aliases) return _aliases;\n\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n const packageIndex = path.resolve(__dirname, \"../..\", \"index.js\");\n\n const typeboxEntry = require.resolve(\"typebox\");\n const typeboxCompileEntry = require.resolve(\"typebox/compile\");\n const typeboxValueEntry = require.resolve(\"typebox/value\");\n\n const packagesRoot = path.resolve(__dirname, \"../../../../\");\n const resolveWorkspaceOrImport = (workspaceRelativePath: string, specifier: string): string => {\n const workspacePath = path.join(packagesRoot, workspaceRelativePath);\n if (fs.existsSync(workspacePath)) {\n return workspacePath;\n }\n return fileURLToPath(import.meta.resolve(specifier));\n };\n\n const piCodingAgentEntry = packageIndex;\n const piAgentCoreEntry = resolveWorkspaceOrImport(\"agent/dist/index.js\", \"@earendil-works/pi-agent-core\");\n const piTuiEntry = resolveWorkspaceOrImport(\"tui/dist/index.js\", \"@earendil-works/pi-tui\");\n const piAiEntry = resolveWorkspaceOrImport(\"ai/dist/index.js\", \"@earendil-works/pi-ai\");\n const piAiOauthEntry = resolveWorkspaceOrImport(\"ai/dist/oauth.js\", \"@earendil-works/pi-ai/oauth\");\n\n _aliases = {\n \"@bastani/atomic\": piCodingAgentEntry,\n \"@earendil-works/pi-coding-agent\": piCodingAgentEntry,\n \"@earendil-works/pi-agent-core\": piAgentCoreEntry,\n \"@earendil-works/pi-tui\": piTuiEntry,\n \"@earendil-works/pi-ai\": piAiEntry,\n \"@earendil-works/pi-ai/oauth\": piAiOauthEntry,\n \"@mariozechner/pi-agent-core\": piAgentCoreEntry,\n \"@mariozechner/pi-tui\": piTuiEntry,\n \"@mariozechner/pi-ai\": piAiEntry,\n \"@mariozechner/pi-ai/oauth\": piAiOauthEntry,\n typebox: typeboxEntry,\n \"typebox/compile\": typeboxCompileEntry,\n \"typebox/value\": typeboxValueEntry,\n \"@sinclair/typebox\": typeboxEntry,\n \"@sinclair/typebox/compile\": typeboxCompileEntry,\n \"@sinclair/typebox/value\": typeboxValueEntry,\n };\n\n return _aliases;\n}\n\nexport async function loadExtensionModule(\n extensionPath: string,\n cacheToken?: ExtensionCacheToken,\n): Promise<ExtensionFactory | undefined> {\n if (isCurrentCacheToken(cacheToken)) {\n const cachedFactory = extensionCache.get(extensionPath);\n if (cachedFactory) return cachedFactory;\n }\n\n const forceTransformedImports = isBunBinary || process.platform === \"win32\";\n const jiti = createJiti(import.meta.url, {\n moduleCache: false,\n ...(forceTransformedImports ? { fsCache: false, tryNative: false } : {}),\n ...(isBunBinary ? { virtualModules: await getVirtualModules() } : { alias: getAliases() }),\n });\n const module = await jiti.import(extensionImportSpecifier(extensionPath, cacheToken), { default: true });\n const factory = module as ExtensionFactory;\n if (typeof factory !== \"function\") return undefined;\n if (isCurrentCacheToken(cacheToken)) {\n extensionCache.set(extensionPath, factory);\n }\n return factory;\n}\n"]}
|
|
@@ -2,39 +2,56 @@ import * as fs from "node:fs";
|
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
|
-
import * as _bundledPiAgentCore from "@earendil-works/pi-agent-core";
|
|
6
|
-
import * as _bundledPiAi from "@earendil-works/pi-ai";
|
|
7
|
-
import * as _bundledPiAiOauth from "@earendil-works/pi-ai/oauth";
|
|
8
|
-
import * as _bundledPiTui from "@earendil-works/pi-tui";
|
|
9
5
|
import { createJiti } from "jiti/static";
|
|
10
|
-
import * as _bundledTypebox from "typebox";
|
|
11
|
-
import * as _bundledTypeboxCompile from "typebox/compile";
|
|
12
|
-
import * as _bundledTypeboxValue from "typebox/value";
|
|
13
6
|
import { isBunBinary } from "../../config.js";
|
|
14
7
|
import { resolvePath } from "../../utils/paths.js";
|
|
15
|
-
// NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
|
|
16
|
-
// avoiding a circular dependency. Extensions can import from the Atomic package
|
|
17
|
-
// name (or upstream-compatible pi package names).
|
|
18
|
-
import * as _bundledPiCodingAgent from "../../index.js";
|
|
19
|
-
/** Modules available to extensions via virtualModules (for compiled Bun binary) */
|
|
20
|
-
const VIRTUAL_MODULES = {
|
|
21
|
-
typebox: _bundledTypebox,
|
|
22
|
-
"typebox/compile": _bundledTypeboxCompile,
|
|
23
|
-
"typebox/value": _bundledTypeboxValue,
|
|
24
|
-
"@sinclair/typebox": _bundledTypebox,
|
|
25
|
-
"@sinclair/typebox/compile": _bundledTypeboxCompile,
|
|
26
|
-
"@sinclair/typebox/value": _bundledTypeboxValue,
|
|
27
|
-
"@earendil-works/pi-agent-core": _bundledPiAgentCore,
|
|
28
|
-
"@earendil-works/pi-tui": _bundledPiTui,
|
|
29
|
-
"@earendil-works/pi-ai": _bundledPiAi,
|
|
30
|
-
"@earendil-works/pi-ai/oauth": _bundledPiAiOauth,
|
|
31
|
-
"@bastani/atomic": _bundledPiCodingAgent,
|
|
32
|
-
"@mariozechner/pi-agent-core": _bundledPiAgentCore,
|
|
33
|
-
"@mariozechner/pi-tui": _bundledPiTui,
|
|
34
|
-
"@mariozechner/pi-ai": _bundledPiAi,
|
|
35
|
-
"@mariozechner/pi-ai/oauth": _bundledPiAiOauth,
|
|
36
|
-
};
|
|
37
8
|
const require = createRequire(import.meta.url);
|
|
9
|
+
let _virtualModules = null;
|
|
10
|
+
let _virtualModulesPromise = null;
|
|
11
|
+
async function loadVirtualModules() {
|
|
12
|
+
const [typebox, typeboxCompile, typeboxValue, piAgentCore, piTui, piAi, piAiOauth, piCodingAgent] = await Promise.all([
|
|
13
|
+
import("typebox"),
|
|
14
|
+
import("typebox/compile"),
|
|
15
|
+
import("typebox/value"),
|
|
16
|
+
import("@earendil-works/pi-agent-core"),
|
|
17
|
+
import("@earendil-works/pi-tui"),
|
|
18
|
+
import("@earendil-works/pi-ai"),
|
|
19
|
+
import("@earendil-works/pi-ai/oauth"),
|
|
20
|
+
// NOTE: This import works because loader.ts exports are NOT re-exported from index.ts,
|
|
21
|
+
// avoiding a circular dependency while preserving the package-name extension import path.
|
|
22
|
+
import("../../index.js"),
|
|
23
|
+
]);
|
|
24
|
+
return {
|
|
25
|
+
typebox,
|
|
26
|
+
"typebox/compile": typeboxCompile,
|
|
27
|
+
"typebox/value": typeboxValue,
|
|
28
|
+
"@sinclair/typebox": typebox,
|
|
29
|
+
"@sinclair/typebox/compile": typeboxCompile,
|
|
30
|
+
"@sinclair/typebox/value": typeboxValue,
|
|
31
|
+
"@earendil-works/pi-agent-core": piAgentCore,
|
|
32
|
+
"@earendil-works/pi-tui": piTui,
|
|
33
|
+
"@earendil-works/pi-ai": piAi,
|
|
34
|
+
"@earendil-works/pi-ai/oauth": piAiOauth,
|
|
35
|
+
"@bastani/atomic": piCodingAgent,
|
|
36
|
+
"@mariozechner/pi-agent-core": piAgentCore,
|
|
37
|
+
"@mariozechner/pi-tui": piTui,
|
|
38
|
+
"@mariozechner/pi-ai": piAi,
|
|
39
|
+
"@mariozechner/pi-ai/oauth": piAiOauth,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/** Modules available to extensions via virtualModules (for compiled Bun binary). */
|
|
43
|
+
async function getVirtualModules() {
|
|
44
|
+
if (_virtualModules)
|
|
45
|
+
return _virtualModules;
|
|
46
|
+
_virtualModulesPromise ??= loadVirtualModules().then((virtualModules) => {
|
|
47
|
+
_virtualModules = virtualModules;
|
|
48
|
+
return virtualModules;
|
|
49
|
+
}, (error) => {
|
|
50
|
+
_virtualModulesPromise = null;
|
|
51
|
+
throw error;
|
|
52
|
+
});
|
|
53
|
+
return _virtualModulesPromise;
|
|
54
|
+
}
|
|
38
55
|
let _aliases = null;
|
|
39
56
|
let extensionCacheCwd;
|
|
40
57
|
let extensionCacheGeneration = 0;
|
|
@@ -118,7 +135,7 @@ export async function loadExtensionModule(extensionPath, cacheToken) {
|
|
|
118
135
|
const jiti = createJiti(import.meta.url, {
|
|
119
136
|
moduleCache: false,
|
|
120
137
|
...(forceTransformedImports ? { fsCache: false, tryNative: false } : {}),
|
|
121
|
-
...(isBunBinary ? { virtualModules:
|
|
138
|
+
...(isBunBinary ? { virtualModules: await getVirtualModules() } : { alias: getAliases() }),
|
|
122
139
|
});
|
|
123
140
|
const module = await jiti.import(extensionImportSpecifier(extensionPath, cacheToken), { default: true });
|
|
124
141
|
const factory = module;
|