@bastani/atomic 0.8.14-0 → 0.8.15-0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/README.md +0 -8
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +3 -0
- package/dist/builtin/mcp/index.ts +4 -8
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/tmux/SKILL.md +220 -0
- package/dist/builtin/subagents/skills/tmux/scripts/find-sessions.sh +112 -0
- package/dist/builtin/subagents/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +10 -1
- package/dist/builtin/workflows/README.md +3 -1
- package/dist/builtin/workflows/builtin/ralph.ts +222 -295
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/background-ui-adapter.ts +20 -11
- package/dist/builtin/workflows/src/extension/index.ts +1 -0
- package/dist/builtin/workflows/src/extension/status-writer.ts +18 -3
- package/dist/builtin/workflows/src/runs/background/runner.ts +8 -10
- package/dist/builtin/workflows/src/runs/foreground/executor.ts +484 -91
- package/dist/builtin/workflows/src/runs/foreground/stage-control-registry.ts +13 -2
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +41 -15
- package/dist/builtin/workflows/src/runs/shared/graph-inference.ts +31 -0
- package/dist/builtin/workflows/src/runs/shared/prompt-callsite.ts +98 -0
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +3 -1
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
- package/dist/builtin/workflows/src/shared/store-types.ts +12 -1
- package/dist/builtin/workflows/src/shared/store.ts +77 -3
- package/dist/builtin/workflows/src/tui/graph-view.ts +17 -1
- package/dist/builtin/workflows/src/tui/prompt-card.ts +185 -30
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +386 -21
- package/docs/changelog.mdx +41 -14
- package/docs/docs.json +1 -0
- package/docs/extensions.md +19 -19
- package/docs/images/workflow-input-picker.png +0 -0
- package/docs/images/workflow-list.png +0 -0
- package/docs/index.md +33 -27
- package/docs/providers.md +2 -2
- package/docs/quickstart.md +15 -15
- package/docs/sdk.md +8 -8
- package/docs/sessions.md +5 -5
- package/docs/settings.md +27 -1
- package/docs/skills.md +2 -2
- package/docs/subagents.md +157 -0
- package/docs/usage.md +7 -7
- package/docs/windows.md +8 -0
- package/docs/workflows.md +62 -9
- package/package.json +2 -1
- package/docs/images/doom-extension.png +0 -0
- package/docs/images/exy.png +0 -3
|
@@ -47,7 +47,7 @@ import {
|
|
|
47
47
|
type ChatMessageRenderOptions,
|
|
48
48
|
type ReadonlyFooterDataProvider,
|
|
49
49
|
} from "@bastani/atomic";
|
|
50
|
-
import { Box, Text } from "@earendil-works/pi-tui";
|
|
50
|
+
import { Box, Editor, Text } from "@earendil-works/pi-tui";
|
|
51
51
|
import type {
|
|
52
52
|
Component,
|
|
53
53
|
EditorComponent,
|
|
@@ -63,9 +63,10 @@ import {
|
|
|
63
63
|
type StageCustomUiRequest,
|
|
64
64
|
type StageUiBroker,
|
|
65
65
|
} from "../shared/stage-ui-broker.js";
|
|
66
|
-
import type { StageNotice, StageSnapshot } from "../shared/store-types.js";
|
|
66
|
+
import type { PendingPrompt, StageNotice, StageSnapshot, StageStatus } from "../shared/store-types.js";
|
|
67
67
|
import type { GraphTheme } from "./graph-theme.js";
|
|
68
68
|
import type { StageControlHandle } from "../runs/foreground/stage-control-registry.js";
|
|
69
|
+
import { isKeybindingsLike } from "./keybindings-adapter.js";
|
|
69
70
|
import { BOLD, RESET, hexBg, hexToAnsi, lerpColor } from "./color-utils.js";
|
|
70
71
|
import { Key, matchesKey, visibleWidth } from "./text-helpers.js";
|
|
71
72
|
import {
|
|
@@ -73,11 +74,23 @@ import {
|
|
|
73
74
|
planStageChatFrame,
|
|
74
75
|
resolveStageChatViewportRows,
|
|
75
76
|
} from "./stage-chat-layout.js";
|
|
77
|
+
import {
|
|
78
|
+
createPromptCardState,
|
|
79
|
+
defaultResponseFor,
|
|
80
|
+
handlePromptCardInput,
|
|
81
|
+
renderPromptCard,
|
|
82
|
+
type PromptCardState,
|
|
83
|
+
} from "./prompt-card.js";
|
|
84
|
+
import { renderRoundedBoxLines } from "./chat-surface.js";
|
|
76
85
|
|
|
77
86
|
// ---------------------------------------------------------------------------
|
|
78
87
|
// Options & types
|
|
79
88
|
// ---------------------------------------------------------------------------
|
|
80
89
|
|
|
90
|
+
function isReadOnlyArchiveStatus(status: StageStatus): boolean {
|
|
91
|
+
return status === "completed" || status === "failed" || status === "skipped";
|
|
92
|
+
}
|
|
93
|
+
|
|
81
94
|
export interface StageChatViewOpts {
|
|
82
95
|
store: Store;
|
|
83
96
|
graphTheme: GraphTheme;
|
|
@@ -151,6 +164,7 @@ type AgentSnapshotMessage = AgentSession["messages"][number];
|
|
|
151
164
|
* overrides this by passing `getViewportRows()`.
|
|
152
165
|
*/
|
|
153
166
|
const VIEW_LINE_COUNT = 32;
|
|
167
|
+
const PROMPT_SCROLL_STEP_ROWS = 4;
|
|
154
168
|
|
|
155
169
|
/** Header strip — ` STAGE wf / stage <meta> ● status` without a leading marker glyph. */
|
|
156
170
|
const HEADER_ROWS = 1;
|
|
@@ -180,9 +194,15 @@ export class StageChatView implements Component, Focusable {
|
|
|
180
194
|
private piTui?: TUI;
|
|
181
195
|
private piTheme?: unknown;
|
|
182
196
|
private piKeybindings?: unknown;
|
|
197
|
+
private piEditorFactory?: StageChatViewOpts["piEditorFactory"];
|
|
183
198
|
private chatHost: ChatSessionHost<NoticeEntry>;
|
|
184
199
|
private stageUiBroker: StageUiBroker;
|
|
185
200
|
private mountedCustomUi: MountedStageCustomUi | null = null;
|
|
201
|
+
private promptState: PromptCardState | null = null;
|
|
202
|
+
private promptEditor: EditorComponent | null = null;
|
|
203
|
+
private promptEditorPromptId: string | null = null;
|
|
204
|
+
private promptScrollOffset = 0;
|
|
205
|
+
private promptMaxScroll = 0;
|
|
186
206
|
private getChatRenderSettings?: () =>
|
|
187
207
|
| Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">>
|
|
188
208
|
| undefined;
|
|
@@ -211,6 +231,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
211
231
|
this.piTui = opts.piTui;
|
|
212
232
|
this.piTheme = opts.piTheme;
|
|
213
233
|
this.piKeybindings = opts.piKeybindings;
|
|
234
|
+
this.piEditorFactory = opts.piEditorFactory;
|
|
214
235
|
this.getChatRenderSettings = opts.getChatRenderSettings;
|
|
215
236
|
this.footerData = opts.footerData;
|
|
216
237
|
this.stageUiBroker = opts.stageUiBroker ?? stageUiBroker;
|
|
@@ -299,6 +320,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
299
320
|
const initialStage = this._currentStage();
|
|
300
321
|
this._snapshotMessagesFromSessionFile(initialStage);
|
|
301
322
|
this._absorbStageNotices(initialStage);
|
|
323
|
+
this._syncPromptState(initialStage?.pendingPrompt);
|
|
302
324
|
|
|
303
325
|
this._unsubscribeStore = this.store.subscribe(() => {
|
|
304
326
|
const stage = this._currentStage();
|
|
@@ -314,6 +336,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
314
336
|
// `stage.setModel`, `stage.compact`, …) so they thread through the
|
|
315
337
|
// transcript without a special render path.
|
|
316
338
|
changed = this._absorbStageNotices(stage) || changed;
|
|
339
|
+
changed = this._syncPromptState(stage?.pendingPrompt) || changed;
|
|
317
340
|
this.chatHost.syncAnimationTick();
|
|
318
341
|
if (changed) this.requestRender?.();
|
|
319
342
|
});
|
|
@@ -416,6 +439,68 @@ export class StageChatView implements Component, Focusable {
|
|
|
416
439
|
return run?.stages.find((s) => s.id === this.stageId);
|
|
417
440
|
}
|
|
418
441
|
|
|
442
|
+
private _syncPromptState(prompt: PendingPrompt | undefined): boolean {
|
|
443
|
+
if (!prompt) {
|
|
444
|
+
const changed = this.promptState !== null;
|
|
445
|
+
this.promptState = null;
|
|
446
|
+
this._disposePromptEditor();
|
|
447
|
+
this.promptScrollOffset = 0;
|
|
448
|
+
this.promptMaxScroll = 0;
|
|
449
|
+
return changed;
|
|
450
|
+
}
|
|
451
|
+
if (!this.promptState || this.promptState.prompt.id !== prompt.id) {
|
|
452
|
+
this.promptState = createPromptCardState(prompt);
|
|
453
|
+
this._resetPromptEditor(prompt);
|
|
454
|
+
this.promptScrollOffset = 0;
|
|
455
|
+
this.promptMaxScroll = 0;
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
return false;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
private _resetPromptEditor(prompt: PendingPrompt): void {
|
|
462
|
+
this._disposePromptEditor();
|
|
463
|
+
if ((prompt.kind !== "input" && prompt.kind !== "editor") || !this.piTui) return;
|
|
464
|
+
const editor = this.piEditorFactory
|
|
465
|
+
? this.piEditorFactory(this.piTui, editorThemeFromGraphTheme(this.theme), this.piKeybindings)
|
|
466
|
+
: new Editor(this.piTui, editorThemeFromGraphTheme(this.theme), { paddingX: 0 });
|
|
467
|
+
editor.setText(typeof prompt.initial === "string" ? prompt.initial : "");
|
|
468
|
+
setEditorPlaceholder(editor, "Type your response…");
|
|
469
|
+
setEditorBorderColor(editor, (text) => hexToAnsi(this.theme.accent) + text + RESET);
|
|
470
|
+
editor.onChange = (text: string) => {
|
|
471
|
+
if (this.promptState?.prompt.id !== prompt.id) return;
|
|
472
|
+
this.promptState.rawText = text;
|
|
473
|
+
this.promptState.caret = text.length;
|
|
474
|
+
this.requestRender?.();
|
|
475
|
+
};
|
|
476
|
+
editor.onSubmit = (text: string) => {
|
|
477
|
+
this._resolvePromptResponse(prompt.id, text);
|
|
478
|
+
};
|
|
479
|
+
this.promptEditor = editor;
|
|
480
|
+
this.promptEditorPromptId = prompt.id;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
private _disposePromptEditor(): void {
|
|
484
|
+
const editor = this.promptEditor;
|
|
485
|
+
this.promptEditor = null;
|
|
486
|
+
this.promptEditorPromptId = null;
|
|
487
|
+
const disposable = editor as (EditorComponent & { dispose?: () => void }) | null;
|
|
488
|
+
disposable?.dispose?.();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
private _resolvePromptResponse(promptId: string, response: unknown): void {
|
|
492
|
+
const prompt = this.promptState?.prompt;
|
|
493
|
+
if (!prompt || prompt.id !== promptId) return;
|
|
494
|
+
this.promptState = null;
|
|
495
|
+
this._disposePromptEditor();
|
|
496
|
+
// A false return means the prompt was already resolved/removed (for
|
|
497
|
+
// example by run abort). The local UI is already stale, so clearing it is
|
|
498
|
+
// the least surprising recovery path.
|
|
499
|
+
this.store.resolveStagePendingPrompt(this.runId, this.stageId, prompt.id, response);
|
|
500
|
+
this.requestRender?.();
|
|
501
|
+
this.onDetach();
|
|
502
|
+
}
|
|
503
|
+
|
|
419
504
|
// -------------------------------------------------------------------------
|
|
420
505
|
// Frame sizing
|
|
421
506
|
// -------------------------------------------------------------------------
|
|
@@ -449,13 +534,13 @@ export class StageChatView implements Component, Focusable {
|
|
|
449
534
|
private _isPaused(
|
|
450
535
|
stage: StageSnapshot | undefined = this._currentStage(),
|
|
451
536
|
): boolean {
|
|
452
|
-
return this.localPaused || stage?.status === "paused";
|
|
537
|
+
return this.localPaused || stage?.status === "paused" || this._liveHandle()?.status === "paused";
|
|
453
538
|
}
|
|
454
539
|
|
|
455
540
|
private _isReadOnlyArchive(stage: StageSnapshot | undefined = this._currentStage()): boolean {
|
|
456
541
|
if (this._liveHandle()) return false;
|
|
457
542
|
if (!stage) return true;
|
|
458
|
-
return stage.status
|
|
543
|
+
return isReadOnlyArchiveStatus(stage.status) || Boolean(stage.sessionFile);
|
|
459
544
|
}
|
|
460
545
|
|
|
461
546
|
private async _handleSlashCommand(text: string): Promise<boolean> {
|
|
@@ -491,12 +576,15 @@ export class StageChatView implements Component, Focusable {
|
|
|
491
576
|
const headerLines = this._renderHeader(w, stage);
|
|
492
577
|
const sepLines = [this._sepRule(w)];
|
|
493
578
|
const customUiActive = this.mountedCustomUi !== null;
|
|
579
|
+
this._syncPromptState(stage?.pendingPrompt);
|
|
580
|
+
const promptActive = !customUiActive && this.promptState !== null;
|
|
494
581
|
const readOnlyArchive = this._isReadOnlyArchive(stage);
|
|
495
|
-
const
|
|
496
|
-
const
|
|
497
|
-
const
|
|
498
|
-
const
|
|
499
|
-
const
|
|
582
|
+
const chatChromeHidden = customUiActive || promptActive || readOnlyArchive;
|
|
583
|
+
const pendingLines = chatChromeHidden ? [] : this.chatHost.renderPendingMessages(w);
|
|
584
|
+
const workingLines = chatChromeHidden ? [] : this.chatHost.renderWorkingStatus(w);
|
|
585
|
+
const usageLines = chatChromeHidden ? [] : this.chatHost.renderUsage(w);
|
|
586
|
+
const editorLines = chatChromeHidden ? [] : this.chatHost.renderEditor(w);
|
|
587
|
+
const footerLines = chatChromeHidden ? [] : this.chatHost.renderFooter(w);
|
|
500
588
|
|
|
501
589
|
const totalRows = this._viewLineCount();
|
|
502
590
|
const plan = planStageChatFrame({
|
|
@@ -516,13 +604,20 @@ export class StageChatView implements Component, Focusable {
|
|
|
516
604
|
const visibleFooterLines = footerLines.slice(0, plan.footerRows);
|
|
517
605
|
const bodyBudget = plan.bodyRows;
|
|
518
606
|
if (blocked) this.chatHost.scrollToBottom();
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
607
|
+
|
|
608
|
+
let bodyLines: string[];
|
|
609
|
+
if (customUiActive) {
|
|
610
|
+
bodyLines = this._renderCustomUiBody(w, bodyBudget);
|
|
611
|
+
} else if (promptActive) {
|
|
612
|
+
bodyLines = this._renderPromptBody(w, bodyBudget);
|
|
613
|
+
} else if (blocked) {
|
|
614
|
+
bodyLines = this._renderBlockedBody(w, bodyBudget, stage);
|
|
615
|
+
} else if (readOnlyArchive) {
|
|
616
|
+
bodyLines = this._renderReadOnlyArchiveBody(w, bodyBudget, stage);
|
|
617
|
+
} else {
|
|
618
|
+
bodyLines = this.chatHost.renderBody(w, bodyBudget);
|
|
619
|
+
}
|
|
620
|
+
|
|
526
621
|
const lines = [
|
|
527
622
|
...headerLines,
|
|
528
623
|
...sepLines,
|
|
@@ -592,6 +687,10 @@ export class StageChatView implements Component, Focusable {
|
|
|
592
687
|
budget: number,
|
|
593
688
|
stage: StageSnapshot | undefined,
|
|
594
689
|
): string[] {
|
|
690
|
+
if (stage?.promptFootprint) {
|
|
691
|
+
return this._renderReadOnlyPromptArchiveBody(width, budget, stage);
|
|
692
|
+
}
|
|
693
|
+
|
|
595
694
|
const t = this.theme;
|
|
596
695
|
const calloutRows = 6;
|
|
597
696
|
const transcriptBudget = Math.max(1, budget - calloutRows);
|
|
@@ -631,6 +730,78 @@ export class StageChatView implements Component, Focusable {
|
|
|
631
730
|
return lines;
|
|
632
731
|
}
|
|
633
732
|
|
|
733
|
+
private _renderReadOnlyPromptArchiveBody(
|
|
734
|
+
width: number,
|
|
735
|
+
budget: number,
|
|
736
|
+
stage: StageSnapshot,
|
|
737
|
+
): string[] {
|
|
738
|
+
const t = this.theme;
|
|
739
|
+
const prompt = stage.promptFootprint;
|
|
740
|
+
if (!prompt) return this._fitBodyLines([], width, budget);
|
|
741
|
+
|
|
742
|
+
const innerWidth = Math.max(2, width - 2);
|
|
743
|
+
const bodyLines: string[] = [];
|
|
744
|
+
const messageBox = new Box(2, 1);
|
|
745
|
+
messageBox.addChild(new Text(paint(prompt.message, t.text), 0, 0));
|
|
746
|
+
bodyLines.push(...messageBox.render(innerWidth));
|
|
747
|
+
bodyLines.push(...new Text(paint("prompt type", t.textMuted, { bold: true }) + paint(` ${prompt.kind}`, t.text), 2, 0).render(innerWidth));
|
|
748
|
+
|
|
749
|
+
if (prompt.kind === "select" && prompt.choices && prompt.choices.length > 0) {
|
|
750
|
+
bodyLines.push(...new Text(paint("choices", t.textMuted, { bold: true }), 2, 0).render(innerWidth));
|
|
751
|
+
for (const choice of prompt.choices) {
|
|
752
|
+
bodyLines.push(...new Text(paint("• ", t.dim) + paint(choice, t.text), 4, 0).render(innerWidth));
|
|
753
|
+
}
|
|
754
|
+
} else if (prompt.kind === "confirm") {
|
|
755
|
+
bodyLines.push(...new Text(paint("choices", t.textMuted, { bold: true }) + paint(" yes / no", t.text), 2, 0).render(innerWidth));
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
if ((prompt.kind === "input" || prompt.kind === "editor") && prompt.initial && prompt.initial.length > 0) {
|
|
759
|
+
bodyLines.push(...new Text(paint("initial value shown", t.textMuted, { bold: true }), 2, 0).render(innerWidth));
|
|
760
|
+
bodyLines.push(...new Text(paint(prompt.initial, t.dim), 4, 0).render(innerWidth));
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
const answer = this._readOnlyPromptAnswer(stage, prompt);
|
|
764
|
+
bodyLines.push("");
|
|
765
|
+
bodyLines.push(...new Text(paint("your response", t.textMuted, { bold: true }), 2, 0).render(innerWidth));
|
|
766
|
+
bodyLines.push(...new Text(paint(answer, answer.startsWith("(") ? t.dim : t.text), 4, 0).render(innerWidth));
|
|
767
|
+
bodyLines.push(...new Text(
|
|
768
|
+
paint("esc", t.accent, { bold: true }) +
|
|
769
|
+
paint(" close", t.textMuted) +
|
|
770
|
+
paint(" · ", t.dim) +
|
|
771
|
+
paint("ctrl+d", t.accent, { bold: true }) +
|
|
772
|
+
paint(" return to graph", t.textMuted),
|
|
773
|
+
2,
|
|
774
|
+
0,
|
|
775
|
+
).render(innerWidth));
|
|
776
|
+
|
|
777
|
+
const title = stage.status === "skipped" ? "QUESTION SKIPPED" : "QUESTION ASKED";
|
|
778
|
+
const cardLines = renderRoundedBoxLines({
|
|
779
|
+
title,
|
|
780
|
+
bodyLines,
|
|
781
|
+
width,
|
|
782
|
+
theme: t,
|
|
783
|
+
accent: t.border,
|
|
784
|
+
});
|
|
785
|
+
return this._fitPromptBodyLines(cardLines, width, budget);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
private _readOnlyPromptAnswer(stage: StageSnapshot, prompt: PendingPrompt): string {
|
|
789
|
+
const answer = this.store.getStagePromptAnswer(this.runId, stage.id);
|
|
790
|
+
if (answer && answer.promptId === prompt.id) {
|
|
791
|
+
return formatReadOnlyPromptAnswer(answer.value, prompt.kind);
|
|
792
|
+
}
|
|
793
|
+
switch (stage.promptAnswerState) {
|
|
794
|
+
case "ambiguous":
|
|
795
|
+
return "(response replay is ambiguous)";
|
|
796
|
+
case "unavailable":
|
|
797
|
+
return "(response unavailable)";
|
|
798
|
+
case "available":
|
|
799
|
+
return "(response no longer in live memory)";
|
|
800
|
+
default:
|
|
801
|
+
return "(no response saved)";
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
634
805
|
private _renderBlockedBody(
|
|
635
806
|
width: number,
|
|
636
807
|
budget: number,
|
|
@@ -677,6 +848,62 @@ export class StageChatView implements Component, Focusable {
|
|
|
677
848
|
const component = this.mountedCustomUi?.component;
|
|
678
849
|
if (component) setComponentFocused(component, this.focused);
|
|
679
850
|
const lines = component ? component.render(width) : [];
|
|
851
|
+
return this._fitBodyLines(lines, width, budget);
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
private _renderPromptBody(width: number, budget: number): string[] {
|
|
855
|
+
const primitiveLines = this._renderPrimitivePromptBody(width);
|
|
856
|
+
if (primitiveLines) return this._fitPromptBodyLines(primitiveLines, width, budget);
|
|
857
|
+
|
|
858
|
+
const state = this.promptState;
|
|
859
|
+
const lines = state
|
|
860
|
+
? renderPromptCard({
|
|
861
|
+
state,
|
|
862
|
+
theme: this.theme,
|
|
863
|
+
width,
|
|
864
|
+
cursorOn: this.focused,
|
|
865
|
+
})
|
|
866
|
+
: [];
|
|
867
|
+
return this._fitPromptBodyLines(lines, width, budget);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
private _renderPrimitivePromptBody(width: number): string[] | null {
|
|
871
|
+
const state = this.promptState;
|
|
872
|
+
const editor = this.promptEditor;
|
|
873
|
+
if (!state || !editor) return null;
|
|
874
|
+
setEditorFocused(editor, this.focused);
|
|
875
|
+
setEditorBorderColor(editor, (text) => hexToAnsi(this.theme.accent) + text + RESET);
|
|
876
|
+
|
|
877
|
+
const innerWidth = Math.max(2, width - 2);
|
|
878
|
+
const bodyLines: string[] = [];
|
|
879
|
+
const messageBox = new Box(2, 1);
|
|
880
|
+
messageBox.addChild(new Text(paint(state.prompt.message, this.theme.text), 0, 0));
|
|
881
|
+
bodyLines.push(...messageBox.render(innerWidth));
|
|
882
|
+
bodyLines.push(...new Text(paint("response", this.theme.textMuted, { bold: true }), 2, 0).render(innerWidth));
|
|
883
|
+
for (const line of editor.render(Math.max(20, innerWidth - 4))) {
|
|
884
|
+
bodyLines.push(" " + line);
|
|
885
|
+
}
|
|
886
|
+
bodyLines.push("");
|
|
887
|
+
bodyLines.push(...new Text(renderHintsForPrompt(state.prompt.kind, this.theme), 2, 0).render(innerWidth));
|
|
888
|
+
|
|
889
|
+
return renderRoundedBoxLines({
|
|
890
|
+
title: "AWAITING INPUT",
|
|
891
|
+
bodyLines,
|
|
892
|
+
width,
|
|
893
|
+
theme: this.theme,
|
|
894
|
+
accent: this.theme.border,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
private _fitPromptBodyLines(lines: readonly string[], width: number, budget: number): string[] {
|
|
899
|
+
this.promptMaxScroll = Math.max(0, lines.length - budget);
|
|
900
|
+
this.promptScrollOffset = Math.max(0, Math.min(this.promptScrollOffset, this.promptMaxScroll));
|
|
901
|
+
const framed = lines.slice(this.promptScrollOffset, this.promptScrollOffset + budget);
|
|
902
|
+
while (framed.length < budget) framed.push(this._blank(width));
|
|
903
|
+
return framed;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
private _fitBodyLines(lines: readonly string[], width: number, budget: number): string[] {
|
|
680
907
|
const framed = lines.slice(0, budget);
|
|
681
908
|
while (framed.length < budget) framed.push(this._blank(width));
|
|
682
909
|
return framed;
|
|
@@ -778,6 +1005,35 @@ export class StageChatView implements Component, Focusable {
|
|
|
778
1005
|
return true;
|
|
779
1006
|
}
|
|
780
1007
|
|
|
1008
|
+
private _handlePromptInput(data: string): void {
|
|
1009
|
+
const state = this.promptState;
|
|
1010
|
+
if (!state) return;
|
|
1011
|
+
if (this.promptEditor && this.promptEditorPromptId === state.prompt.id) {
|
|
1012
|
+
if (matchesKey(data, Key.escape) || matchesKey(data, Key.ctrl("c"))) {
|
|
1013
|
+
this._resolvePromptResponse(state.prompt.id, defaultResponseFor(state.prompt));
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
setEditorFocused(this.promptEditor, this.focused);
|
|
1017
|
+
this.promptEditor.handleInput(data);
|
|
1018
|
+
this.requestRender?.();
|
|
1019
|
+
return;
|
|
1020
|
+
}
|
|
1021
|
+
const action = handlePromptCardInput(
|
|
1022
|
+
data,
|
|
1023
|
+
state,
|
|
1024
|
+
isKeybindingsLike(this.piKeybindings) ? this.piKeybindings : undefined,
|
|
1025
|
+
);
|
|
1026
|
+
if (action.kind === "noop") {
|
|
1027
|
+
this.requestRender?.();
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const prompt = state.prompt;
|
|
1031
|
+
const response = action.kind === "submit"
|
|
1032
|
+
? action.response
|
|
1033
|
+
: defaultResponseFor(prompt);
|
|
1034
|
+
this._resolvePromptResponse(prompt.id, response);
|
|
1035
|
+
}
|
|
1036
|
+
|
|
781
1037
|
// -------------------------------------------------------------------------
|
|
782
1038
|
// Input
|
|
783
1039
|
// -------------------------------------------------------------------------
|
|
@@ -800,15 +1056,21 @@ export class StageChatView implements Component, Focusable {
|
|
|
800
1056
|
this.requestRender?.();
|
|
801
1057
|
return true;
|
|
802
1058
|
}
|
|
803
|
-
|
|
804
|
-
return true;
|
|
805
|
-
}
|
|
1059
|
+
this._syncPromptState(this._currentStage()?.pendingPrompt);
|
|
806
1060
|
if (matchesKey(data, Key.ctrl("d"))) {
|
|
807
|
-
if (this.chatHost.hasInputText()) return this.chatHost.handleInput(data);
|
|
1061
|
+
if (!this.promptState && this.chatHost.hasInputText()) return this.chatHost.handleInput(data);
|
|
808
1062
|
if (this._isPaused()) this.onClose();
|
|
809
1063
|
else this.onDetach();
|
|
810
1064
|
return true;
|
|
811
1065
|
}
|
|
1066
|
+
if (this.promptState) {
|
|
1067
|
+
if (this._handlePromptScrollInput(data, this.promptEditor === null)) return true;
|
|
1068
|
+
this._handlePromptInput(data);
|
|
1069
|
+
return true;
|
|
1070
|
+
}
|
|
1071
|
+
if (this.chatHost.handleScrollInput(data)) {
|
|
1072
|
+
return true;
|
|
1073
|
+
}
|
|
812
1074
|
if (matchesKey(data, Key.escape)) {
|
|
813
1075
|
if (
|
|
814
1076
|
this._isStreaming() ||
|
|
@@ -836,8 +1098,70 @@ export class StageChatView implements Component, Focusable {
|
|
|
836
1098
|
return this.chatHost.handleInput(data);
|
|
837
1099
|
}
|
|
838
1100
|
|
|
1101
|
+
private _handlePromptScrollInput(data: string, includeKeyboard = true): boolean {
|
|
1102
|
+
const wheelDeltaRows = this._mouseWheelDeltaRows(data);
|
|
1103
|
+
if (wheelDeltaRows !== 0) {
|
|
1104
|
+
this._scrollPromptBy(wheelDeltaRows);
|
|
1105
|
+
return true;
|
|
1106
|
+
}
|
|
1107
|
+
if (this._isMouseSequence(data)) return true;
|
|
1108
|
+
if (!includeKeyboard) return false;
|
|
1109
|
+
if (matchesKey(data, "pageUp")) {
|
|
1110
|
+
this._scrollPromptBy(-this._promptPageSize());
|
|
1111
|
+
return true;
|
|
1112
|
+
}
|
|
1113
|
+
if (matchesKey(data, "pageDown")) {
|
|
1114
|
+
this._scrollPromptBy(this._promptPageSize());
|
|
1115
|
+
return true;
|
|
1116
|
+
}
|
|
1117
|
+
if (!this.promptEditor && matchesKey(data, "home")) {
|
|
1118
|
+
this.promptScrollOffset = 0;
|
|
1119
|
+
this.requestRender?.();
|
|
1120
|
+
return true;
|
|
1121
|
+
}
|
|
1122
|
+
if (!this.promptEditor && matchesKey(data, "end")) {
|
|
1123
|
+
this.promptScrollOffset = this.promptMaxScroll;
|
|
1124
|
+
this.requestRender?.();
|
|
1125
|
+
return true;
|
|
1126
|
+
}
|
|
1127
|
+
return false;
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
private _scrollPromptBy(deltaRows: number): void {
|
|
1131
|
+
this.promptScrollOffset = Math.max(
|
|
1132
|
+
0,
|
|
1133
|
+
Math.min(this.promptMaxScroll, this.promptScrollOffset + deltaRows),
|
|
1134
|
+
);
|
|
1135
|
+
this.requestRender?.();
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
private _promptPageSize(): number {
|
|
1139
|
+
return Math.max(4, this._viewLineCount() - HEADER_ROWS - SEP_ROWS - 2);
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
private _mouseWheelDeltaRows(data: string): number {
|
|
1143
|
+
const sgr = data.match(/^\x1b\[<(\d+);\d+;\d+M$/);
|
|
1144
|
+
if (sgr) return this._wheelDeltaForButtonCode(Number.parseInt(sgr[1]!, 10));
|
|
1145
|
+
if (data.startsWith("\x1b[M") && data.length >= 6) {
|
|
1146
|
+
return this._wheelDeltaForButtonCode(data.charCodeAt(3) - 32);
|
|
1147
|
+
}
|
|
1148
|
+
return 0;
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
private _wheelDeltaForButtonCode(code: number): number {
|
|
1152
|
+
if ((code & 64) === 0) return 0;
|
|
1153
|
+
const direction = code & 3;
|
|
1154
|
+
if (direction === 0) return -PROMPT_SCROLL_STEP_ROWS;
|
|
1155
|
+
if (direction === 1) return PROMPT_SCROLL_STEP_ROWS;
|
|
1156
|
+
return 0;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
private _isMouseSequence(data: string): boolean {
|
|
1160
|
+
return /^\x1b\[<\d+;\d+;\d+[mM]$/.test(data) || data.startsWith("\x1b[M");
|
|
1161
|
+
}
|
|
1162
|
+
|
|
839
1163
|
invalidate(): void {
|
|
840
|
-
|
|
1164
|
+
this._syncPromptState(this._currentStage()?.pendingPrompt);
|
|
841
1165
|
}
|
|
842
1166
|
|
|
843
1167
|
dispose(): void {
|
|
@@ -846,6 +1170,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
846
1170
|
this._unsubscribeHandle?.();
|
|
847
1171
|
this._unsubscribeHandle = null;
|
|
848
1172
|
this._rejectMountedCustomUi("stage chat view disposed");
|
|
1173
|
+
this._disposePromptEditor();
|
|
849
1174
|
this._unregisterStageUiHost?.();
|
|
850
1175
|
this._unregisterStageUiHost = null;
|
|
851
1176
|
this.chatHost.dispose();
|
|
@@ -905,6 +1230,18 @@ interface TranscriptDebugEntry {
|
|
|
905
1230
|
readonly output: string;
|
|
906
1231
|
}
|
|
907
1232
|
|
|
1233
|
+
function formatReadOnlyPromptAnswer(value: unknown, kind: PendingPrompt["kind"]): string {
|
|
1234
|
+
if (kind === "confirm") return value === true ? "yes" : "no";
|
|
1235
|
+
if (typeof value === "string") return value.length > 0 ? value : "(empty response)";
|
|
1236
|
+
if (typeof value === "number" || typeof value === "boolean" || value === null) return String(value);
|
|
1237
|
+
try {
|
|
1238
|
+
const encoded = JSON.stringify(value);
|
|
1239
|
+
return encoded ?? String(value);
|
|
1240
|
+
} catch {
|
|
1241
|
+
return String(value);
|
|
1242
|
+
}
|
|
1243
|
+
}
|
|
1244
|
+
|
|
908
1245
|
function transcriptDebugEntries(entry: TranscriptEntry): TranscriptDebugEntry[] {
|
|
909
1246
|
if (isChatMessageEntry(entry) && entry.kind === "assistant") {
|
|
910
1247
|
const entries: TranscriptDebugEntry[] = [];
|
|
@@ -993,6 +1330,27 @@ function setComponentFocused(component: Component, focused: boolean): void {
|
|
|
993
1330
|
if ("focused" in candidate) candidate.focused = focused;
|
|
994
1331
|
}
|
|
995
1332
|
|
|
1333
|
+
function setEditorFocused(editor: EditorComponent, focused: boolean): void {
|
|
1334
|
+
setComponentFocused(editor, focused);
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
function setEditorPlaceholder(editor: EditorComponent, placeholder: string | undefined): void {
|
|
1338
|
+
const candidate = editor as EditorComponent & {
|
|
1339
|
+
setPlaceholder?: (value: string | undefined) => void;
|
|
1340
|
+
};
|
|
1341
|
+
candidate.setPlaceholder?.(placeholder);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
function setEditorBorderColor(
|
|
1345
|
+
editor: EditorComponent,
|
|
1346
|
+
borderColor: (text: string) => string,
|
|
1347
|
+
): void {
|
|
1348
|
+
const candidate = editor as EditorComponent & {
|
|
1349
|
+
borderColor?: (text: string) => string;
|
|
1350
|
+
};
|
|
1351
|
+
if ("borderColor" in candidate) candidate.borderColor = borderColor;
|
|
1352
|
+
}
|
|
1353
|
+
|
|
996
1354
|
function isChatMessageEntry(entry: TranscriptEntry): entry is ChatMessageEntry {
|
|
997
1355
|
return "kind" in entry && entry.role !== "notice";
|
|
998
1356
|
}
|
|
@@ -1083,6 +1441,13 @@ function paint(text: string, fg: string, opts: PaintOpts = {}): string {
|
|
|
1083
1441
|
return out + text + RESET;
|
|
1084
1442
|
}
|
|
1085
1443
|
|
|
1444
|
+
function renderHintsForPrompt(kind: PendingPrompt["kind"], theme: GraphTheme): string {
|
|
1445
|
+
if (kind === "input" || kind === "editor") {
|
|
1446
|
+
return `${paint("enter", theme.textMuted, { bold: true })} Submit · ${paint("esc/ctrl+c", theme.textMuted, { bold: true })} Skip`;
|
|
1447
|
+
}
|
|
1448
|
+
return `${paint("enter", theme.textMuted, { bold: true })} Select · ${paint("esc/ctrl+c", theme.textMuted, { bold: true })} Skip`;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1086
1451
|
/**
|
|
1087
1452
|
* Foreground styling for text that will be wrapped by a `Box` background.
|
|
1088
1453
|
* A normal `RESET` would also clear the parent background, so close only the
|
package/docs/changelog.mdx
CHANGED
|
@@ -3,27 +3,54 @@ title: "Changelog"
|
|
|
3
3
|
description: "What's new in Atomic"
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
<Update label="May
|
|
6
|
+
<Update label="May 25, 2026" tags={["v0.8.14"]}>
|
|
7
|
+
|
|
8
|
+
## Stable release
|
|
9
|
+
|
|
10
|
+
- **0.8.14 is the stable promotion of the 0.8.14 prerelease.** It keeps the upstream Pi sync, bundled workflow documentation refresh, and bundled subagent guidance updates from the prerelease.
|
|
11
|
+
- **Synced with upstream Pi patches.** Atomic's coding-agent fork is now aligned with upstream Pi patches since v0.75.4 and bundles Pi libraries at 0.75.5.
|
|
12
|
+
- **Bundled workflow docs are current.** The docs now describe Ralph's final PR-preparation phase, deep-research report artifacts, and newer workflow inspection/control actions such as `stages`, `stage`, `transcript`, `send`, `pause`, and `reload`. See [Workflows](/workflows).
|
|
13
|
+
- **Bundled subagent guidance is easier to find.** Atomic now documents how to use bundled subagents for delegation, background work, nested fanout boundaries, model fallbacks, and custom agent guardrails. See [Subagents](/subagents).
|
|
14
|
+
|
|
15
|
+
## Fixes
|
|
16
|
+
|
|
17
|
+
- Managed extension installs, git package ref reconciliation, async file tools, export HTML escaping, OpenCode session headers, OAuth device-code login, footer path abbreviation, clipboard native loading, and collapsed read output rendering received upstream fixes.
|
|
18
|
+
- The built-in header model label now refreshes when you change models, matching the footer below the chat box.
|
|
19
|
+
|
|
20
|
+
</Update>
|
|
21
|
+
|
|
22
|
+
<Update label="May 21, 2026" tags={["v0.8.13"]}>
|
|
23
|
+
|
|
24
|
+
## Stable release
|
|
25
|
+
|
|
26
|
+
- **0.8.13 is the stable promotion of the 0.8.13 prerelease.** It keeps the workflow discovery and async subagent widget fixes from the prerelease while updating Atomic agent guidance.
|
|
27
|
+
|
|
28
|
+
## Fixes
|
|
29
|
+
|
|
30
|
+
- **Packaged workflow discovery loads reliably.** Package-authored workflow resources load through `jiti`, so SDK imports such as `@bastani/workflows` resolve consistently from the Bun-packaged binary. See [Workflows](/workflows).
|
|
31
|
+
- **Workflow diagnostics are preserved.** Invalid default exports and supported SDK import diagnostics remain visible when workflows are discovered from packaged resources.
|
|
32
|
+
- **Async subagent widget spacing is stable.** Background subagent widgets preserve spacing before the prompt box. See [Subagents](/subagents).
|
|
33
|
+
|
|
34
|
+
</Update>
|
|
35
|
+
|
|
36
|
+
<Update label="May 20, 2026" tags={["v0.8.12"]}>
|
|
7
37
|
|
|
8
38
|
## New features
|
|
9
39
|
|
|
10
|
-
-
|
|
11
|
-
- **
|
|
12
|
-
- **Configurable HTTP idle timeout.** You can now tune how long Atomic keeps idle HTTP connections open via settings, which helps on slow or unreliable networks. See [Settings](/settings).
|
|
40
|
+
- **Configurable HTTP idle timeout.** Tune how long Atomic keeps idle HTTP connections open, with presets from 30 seconds through 30 minutes and an option to disable the timeout. See [Settings](/settings).
|
|
41
|
+
- **Workflow documentation and navigation.** Atomic added the Workflows docs page so users can discover built-in workflows, authoring patterns, package setup, and run control from the docs site. See [Workflows](/workflows).
|
|
13
42
|
|
|
14
43
|
## Updates
|
|
15
44
|
|
|
16
|
-
- **
|
|
17
|
-
- **
|
|
18
|
-
- **
|
|
19
|
-
- **Workflow tool rendering is width-aware.** Tool output inside workflow stages now respects the terminal width and forwards builtin tools correctly to stage sessions. See [Workflows](/workflows).
|
|
45
|
+
- **Model reasoning level in the system prompt.** Atomic now includes the active model's reasoning level in generated system prompts and project context markup. See [Models](/models).
|
|
46
|
+
- **Runtime and extension compatibility.** Internal runtime, tool, TUI, tests, and extension example imports moved to explicit `.ts` specifiers for better raw-TypeScript extension compatibility.
|
|
47
|
+
- **Atomic docs refresh.** Package usage, customization, workflows, and release guidance were refreshed for Atomic branding.
|
|
20
48
|
|
|
21
|
-
##
|
|
49
|
+
## Fixes
|
|
22
50
|
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **Workflow
|
|
26
|
-
-
|
|
27
|
-
- **Settled workflow stages detach from run control.** Completed stages no longer linger in the active run, keeping the workflow view accurate.
|
|
51
|
+
- **Better Windows self-update behavior.** Package-manager installs now handle native dependency cleanup, quarantine cases, and unavailable updates more clearly. See [Windows](/windows).
|
|
52
|
+
- **Smoother subagent rendering.** Live subagent result rendering, async render updates, transient child-error recovery, and live widget animations are more stable. See [Subagents](/subagents).
|
|
53
|
+
- **Workflow stages settle cleanly.** Completed stages detach from run-control tracking, and the empty workflow graph waiting state is centered. See [Workflows](/workflows).
|
|
54
|
+
- **Consistent duration display.** Elapsed times across bash, subagent, and workflow output are shown as whole seconds and never go negative.
|
|
28
55
|
|
|
29
56
|
</Update>
|