@bastani/atomic 0.8.14 → 0.8.15
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 +29 -0
- package/README.md +0 -8
- package/dist/builtin/intercom/CHANGELOG.md +5 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +5 -0
- package/dist/builtin/mcp/index.ts +4 -8
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +2 -0
- 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/CHANGELOG.md +5 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +12 -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
|
@@ -37,7 +37,17 @@ import {
|
|
|
37
37
|
import type { PendingPrompt } from "../shared/store-types.js";
|
|
38
38
|
import type { GraphTheme } from "./graph-theme.js";
|
|
39
39
|
import { hexToAnsi, hexBg, paint, RESET, BOLD } from "./color-utils.js";
|
|
40
|
-
import { Key, matchesKey } from "./text-helpers.js";
|
|
40
|
+
import { Key, decodePrintableKey, matchesKey } from "./text-helpers.js";
|
|
41
|
+
import {
|
|
42
|
+
type KeybindingsLike,
|
|
43
|
+
TUI_ACTION,
|
|
44
|
+
deleteRange,
|
|
45
|
+
lineEnd,
|
|
46
|
+
lineStart,
|
|
47
|
+
matchesAction,
|
|
48
|
+
wordLeft,
|
|
49
|
+
wordRight,
|
|
50
|
+
} from "./keybindings-adapter.js";
|
|
41
51
|
|
|
42
52
|
// ---------------------------------------------------------------------------
|
|
43
53
|
// State
|
|
@@ -98,6 +108,7 @@ export type PromptCardAction =
|
|
|
98
108
|
export function handlePromptCardInput(
|
|
99
109
|
data: string,
|
|
100
110
|
state: PromptCardState,
|
|
111
|
+
keybindings?: KeybindingsLike,
|
|
101
112
|
): PromptCardAction {
|
|
102
113
|
if (matchesKey(data, Key.ctrl("c")) || matchesKey(data, Key.escape)) {
|
|
103
114
|
return { kind: "cancel" };
|
|
@@ -109,9 +120,9 @@ export function handlePromptCardInput(
|
|
|
109
120
|
case "select":
|
|
110
121
|
return handleSelect(data, state);
|
|
111
122
|
case "input":
|
|
112
|
-
return handleInput(data, state);
|
|
123
|
+
return handleInput(data, state, keybindings);
|
|
113
124
|
case "editor":
|
|
114
|
-
return handleEditor(data, state);
|
|
125
|
+
return handleEditor(data, state, keybindings);
|
|
115
126
|
}
|
|
116
127
|
}
|
|
117
128
|
|
|
@@ -154,56 +165,138 @@ function handleSelect(data: string, state: PromptCardState): PromptCardAction {
|
|
|
154
165
|
return action;
|
|
155
166
|
}
|
|
156
167
|
|
|
157
|
-
function handleInput(
|
|
158
|
-
|
|
168
|
+
function handleInput(
|
|
169
|
+
data: string,
|
|
170
|
+
state: PromptCardState,
|
|
171
|
+
keybindings: KeybindingsLike | undefined,
|
|
172
|
+
): PromptCardAction {
|
|
173
|
+
if (matchesTextAction(keybindings, data, TUI_ACTION.inputSubmit, Key.enter)) {
|
|
159
174
|
return { kind: "submit", response: state.rawText };
|
|
160
175
|
}
|
|
161
|
-
|
|
176
|
+
if (matchesAction(keybindings, data, "tui.input.newLine")) {
|
|
177
|
+
insertText(state, "\n");
|
|
178
|
+
return { kind: "noop" };
|
|
179
|
+
}
|
|
180
|
+
return applyTextEdit(data, state, keybindings, { multiline: true });
|
|
162
181
|
}
|
|
163
182
|
|
|
164
|
-
function handleEditor(
|
|
183
|
+
function handleEditor(
|
|
184
|
+
data: string,
|
|
185
|
+
state: PromptCardState,
|
|
186
|
+
keybindings: KeybindingsLike | undefined,
|
|
187
|
+
): PromptCardAction {
|
|
165
188
|
if (matchesKey(data, Key.tab) || matchesKey(data, Key.shift("tab"))) {
|
|
166
189
|
state.editorSubmitFocused = !state.editorSubmitFocused;
|
|
167
190
|
return { kind: "noop" };
|
|
168
191
|
}
|
|
169
192
|
if (state.editorSubmitFocused) {
|
|
170
|
-
if (
|
|
193
|
+
if (matchesTextAction(keybindings, data, TUI_ACTION.inputSubmit, Key.enter)) {
|
|
171
194
|
return { kind: "submit", response: state.rawText };
|
|
172
195
|
}
|
|
173
196
|
return { kind: "noop" };
|
|
174
197
|
}
|
|
175
|
-
if (
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
if (
|
|
199
|
+
matchesTextAction(keybindings, data, TUI_ACTION.inputSubmit, Key.enter) ||
|
|
200
|
+
matchesAction(keybindings, data, "tui.input.newLine")
|
|
201
|
+
) {
|
|
202
|
+
insertText(state, "\n");
|
|
178
203
|
return { kind: "noop" };
|
|
179
204
|
}
|
|
180
|
-
return applyTextEdit(data, state);
|
|
205
|
+
return applyTextEdit(data, state, keybindings, { multiline: true });
|
|
181
206
|
}
|
|
182
207
|
|
|
183
208
|
function applyTextEdit(
|
|
184
209
|
data: string,
|
|
185
210
|
state: PromptCardState,
|
|
211
|
+
keybindings: KeybindingsLike | undefined,
|
|
212
|
+
opts: { multiline: boolean },
|
|
186
213
|
): PromptCardAction {
|
|
187
|
-
|
|
188
|
-
|
|
214
|
+
const caret = Math.max(0, Math.min(state.caret, state.rawText.length));
|
|
215
|
+
state.caret = caret;
|
|
216
|
+
|
|
217
|
+
if (opts.multiline && matchesAction(keybindings, data, TUI_ACTION.editorCursorUp)) {
|
|
218
|
+
const nextCaret = caretLineUp(state.rawText, caret);
|
|
219
|
+
if (nextCaret !== null) state.caret = nextCaret;
|
|
220
|
+
return { kind: "noop" };
|
|
221
|
+
}
|
|
222
|
+
if (opts.multiline && matchesAction(keybindings, data, TUI_ACTION.editorCursorDown)) {
|
|
223
|
+
const nextCaret = caretLineDown(state.rawText, caret);
|
|
224
|
+
if (nextCaret !== null) state.caret = nextCaret;
|
|
225
|
+
return { kind: "noop" };
|
|
226
|
+
}
|
|
227
|
+
if (matchesAction(keybindings, data, "tui.editor.cursorWordLeft")) {
|
|
228
|
+
state.caret = wordLeft(state.rawText, caret);
|
|
229
|
+
return { kind: "noop" };
|
|
230
|
+
}
|
|
231
|
+
if (matchesAction(keybindings, data, "tui.editor.cursorWordRight")) {
|
|
232
|
+
state.caret = wordRight(state.rawText, caret);
|
|
233
|
+
return { kind: "noop" };
|
|
234
|
+
}
|
|
235
|
+
if (matchesAction(keybindings, data, "tui.editor.cursorLineStart")) {
|
|
236
|
+
state.caret = lineStart(state.rawText, caret);
|
|
237
|
+
return { kind: "noop" };
|
|
238
|
+
}
|
|
239
|
+
if (matchesAction(keybindings, data, "tui.editor.cursorLineEnd")) {
|
|
240
|
+
state.caret = lineEnd(state.rawText, caret);
|
|
241
|
+
return { kind: "noop" };
|
|
242
|
+
}
|
|
243
|
+
if (matchesTextAction(keybindings, data, TUI_ACTION.editorCursorLeft, Key.left)) {
|
|
244
|
+
state.caret = previousGraphemeBoundary(state.rawText, caret);
|
|
245
|
+
return { kind: "noop" };
|
|
246
|
+
}
|
|
247
|
+
if (matchesTextAction(keybindings, data, TUI_ACTION.editorCursorRight, Key.right)) {
|
|
248
|
+
state.caret = nextGraphemeBoundary(state.rawText, caret);
|
|
249
|
+
return { kind: "noop" };
|
|
250
|
+
}
|
|
251
|
+
if (matchesAction(keybindings, data, "tui.editor.deleteWordBackward")) {
|
|
252
|
+
const start = wordLeft(state.rawText, caret);
|
|
253
|
+
const result = deleteRange(state.rawText, start, caret, caret);
|
|
254
|
+
state.rawText = result.text;
|
|
255
|
+
state.caret = result.caret;
|
|
189
256
|
return { kind: "noop" };
|
|
190
257
|
}
|
|
191
|
-
if (
|
|
192
|
-
|
|
258
|
+
if (matchesAction(keybindings, data, "tui.editor.deleteWordForward")) {
|
|
259
|
+
const end = wordRight(state.rawText, caret);
|
|
260
|
+
const result = deleteRange(state.rawText, caret, end, caret);
|
|
261
|
+
state.rawText = result.text;
|
|
262
|
+
state.caret = result.caret;
|
|
193
263
|
return { kind: "noop" };
|
|
194
264
|
}
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
265
|
+
if (matchesAction(keybindings, data, "tui.editor.deleteToLineStart")) {
|
|
266
|
+
const start = lineStart(state.rawText, caret);
|
|
267
|
+
const result = deleteRange(state.rawText, start, caret, caret);
|
|
268
|
+
state.rawText = result.text;
|
|
269
|
+
state.caret = result.caret;
|
|
270
|
+
return { kind: "noop" };
|
|
271
|
+
}
|
|
272
|
+
if (matchesAction(keybindings, data, "tui.editor.deleteToLineEnd")) {
|
|
273
|
+
const end = lineEnd(state.rawText, caret);
|
|
274
|
+
const result = deleteRange(state.rawText, caret, end, caret);
|
|
275
|
+
state.rawText = result.text;
|
|
276
|
+
state.caret = result.caret;
|
|
277
|
+
return { kind: "noop" };
|
|
278
|
+
}
|
|
279
|
+
if (matchesTextAction(keybindings, data, "tui.editor.deleteCharBackward", Key.backspace)) {
|
|
280
|
+
if (caret > 0) {
|
|
281
|
+
const prev = previousGraphemeBoundary(state.rawText, caret);
|
|
282
|
+
const result = deleteRange(state.rawText, prev, caret, caret);
|
|
283
|
+
state.rawText = result.text;
|
|
284
|
+
state.caret = result.caret;
|
|
285
|
+
}
|
|
286
|
+
return { kind: "noop" };
|
|
287
|
+
}
|
|
288
|
+
if (matchesAction(keybindings, data, "tui.editor.deleteCharForward")) {
|
|
289
|
+
if (caret < state.rawText.length) {
|
|
290
|
+
const result = deleteRange(state.rawText, caret, nextGraphemeBoundary(state.rawText, caret), caret);
|
|
291
|
+
state.rawText = result.text;
|
|
292
|
+
state.caret = result.caret;
|
|
200
293
|
}
|
|
201
294
|
return { kind: "noop" };
|
|
202
295
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
state
|
|
296
|
+
|
|
297
|
+
const printable = decodePrintableKey(data) ?? data;
|
|
298
|
+
if (isPrintableText(printable)) {
|
|
299
|
+
insertText(state, printable);
|
|
207
300
|
return { kind: "noop" };
|
|
208
301
|
}
|
|
209
302
|
return { kind: "noop" };
|
|
@@ -250,10 +343,73 @@ function nextGraphemeBoundary(value: string, caret: number): number {
|
|
|
250
343
|
return value.length;
|
|
251
344
|
}
|
|
252
345
|
|
|
346
|
+
function clampGraphemeBoundary(value: string, caret: number): number {
|
|
347
|
+
const safeCaret = Math.max(0, Math.min(caret, value.length));
|
|
348
|
+
if (safeCaret === value.length) return safeCaret;
|
|
349
|
+
for (const part of graphemeParts(value)) {
|
|
350
|
+
if (part.start === safeCaret) return safeCaret;
|
|
351
|
+
if (part.start > safeCaret) break;
|
|
352
|
+
}
|
|
353
|
+
return previousGraphemeBoundary(value, safeCaret);
|
|
354
|
+
}
|
|
355
|
+
|
|
253
356
|
function isPrintableText(data: string): boolean {
|
|
254
357
|
return data.length > 0 && !data.startsWith("\x1b") && !/[\x00-\x1f\x7f]/.test(data);
|
|
255
358
|
}
|
|
256
359
|
|
|
360
|
+
function insertText(state: PromptCardState, text: string): void {
|
|
361
|
+
const caret = Math.max(0, Math.min(state.caret, state.rawText.length));
|
|
362
|
+
state.rawText = state.rawText.slice(0, caret) + text + state.rawText.slice(caret);
|
|
363
|
+
state.caret = caret + text.length;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function matchesTextAction(
|
|
367
|
+
keybindings: KeybindingsLike | undefined,
|
|
368
|
+
data: string,
|
|
369
|
+
action: string,
|
|
370
|
+
fallback?: Parameters<typeof matchesKey>[1],
|
|
371
|
+
): boolean {
|
|
372
|
+
return matchesAction(keybindings, data, action) || (fallback !== undefined && matchesKey(data, fallback));
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function visualColumnAt(text: string, caret: number): number {
|
|
376
|
+
return visibleWidth(text.slice(0, clampGraphemeBoundary(text, caret)));
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function offsetAtVisualColumn(text: string, targetCol: number): number {
|
|
380
|
+
let col = 0;
|
|
381
|
+
for (const part of graphemeParts(text)) {
|
|
382
|
+
const width = part.width;
|
|
383
|
+
if (col + width > targetCol) return part.start;
|
|
384
|
+
col += width;
|
|
385
|
+
}
|
|
386
|
+
return text.length;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function caretLineUp(raw: string, caret: number): number | null {
|
|
390
|
+
const safe = clampGraphemeBoundary(raw, caret);
|
|
391
|
+
const lineStartOffset = raw.lastIndexOf("\n", safe - 1) + 1;
|
|
392
|
+
if (lineStartOffset === 0) return null;
|
|
393
|
+
const prevLineEnd = lineStartOffset - 1;
|
|
394
|
+
const prevLineStart = raw.lastIndexOf("\n", prevLineEnd - 1) + 1;
|
|
395
|
+
const col = visualColumnAt(raw.slice(lineStartOffset, safe), raw.slice(lineStartOffset, safe).length);
|
|
396
|
+
const prevLine = raw.slice(prevLineStart, prevLineEnd);
|
|
397
|
+
return prevLineStart + offsetAtVisualColumn(prevLine, col);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function caretLineDown(raw: string, caret: number): number | null {
|
|
401
|
+
const safe = clampGraphemeBoundary(raw, caret);
|
|
402
|
+
const nextNl = raw.indexOf("\n", safe);
|
|
403
|
+
if (nextNl === -1) return null;
|
|
404
|
+
const lineStartOffset = raw.lastIndexOf("\n", safe - 1) + 1;
|
|
405
|
+
const col = visualColumnAt(raw.slice(lineStartOffset, safe), raw.slice(lineStartOffset, safe).length);
|
|
406
|
+
const nextLineStart = nextNl + 1;
|
|
407
|
+
const nextNlAfter = raw.indexOf("\n", nextLineStart);
|
|
408
|
+
const nextLineEnd = nextNlAfter === -1 ? raw.length : nextNlAfter;
|
|
409
|
+
const nextLine = raw.slice(nextLineStart, nextLineEnd);
|
|
410
|
+
return nextLineStart + offsetAtVisualColumn(nextLine, col);
|
|
411
|
+
}
|
|
412
|
+
|
|
257
413
|
/**
|
|
258
414
|
* Compute the safe default response when the user dismisses the prompt.
|
|
259
415
|
* Used by the overlay to keep the workflow body unblocked even on cancel.
|
|
@@ -358,9 +514,8 @@ export interface PromptCardRenderOpts {
|
|
|
358
514
|
export function renderPromptCard(opts: PromptCardRenderOpts): string[] {
|
|
359
515
|
const { state, theme, width } = opts;
|
|
360
516
|
const innerWidth = Math.max(20, width - 2);
|
|
361
|
-
const
|
|
362
|
-
const
|
|
363
|
-
const bg = hexBg(theme.backgroundPanel);
|
|
517
|
+
const borderColor = theme.border;
|
|
518
|
+
const bg = "";
|
|
364
519
|
|
|
365
520
|
const lines: string[] = [];
|
|
366
521
|
lines.push(makeBorderTop(borderColor, " AWAITING INPUT ", theme, innerWidth, bg));
|
|
@@ -390,9 +545,9 @@ function makeBorderTop(
|
|
|
390
545
|
innerWidth: number,
|
|
391
546
|
bg: string,
|
|
392
547
|
): string {
|
|
393
|
-
const labelText = paint(label, theme.
|
|
548
|
+
const labelText = paint(label, theme.textMuted, { bold: true });
|
|
394
549
|
const labelW = visibleWidth(labelText);
|
|
395
|
-
const fillLen = Math.max(0, innerWidth - labelW
|
|
550
|
+
const fillLen = Math.max(0, innerWidth - labelW);
|
|
396
551
|
return (
|
|
397
552
|
bg +
|
|
398
553
|
paint("╭", color) +
|