@bastani/atomic 0.9.5-alpha.7 → 0.9.5-alpha.8
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 +17 -2
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +17 -0
- package/dist/builtin/intercom/README.md +27 -21
- package/dist/builtin/intercom/broker/broker.ts +3 -6
- package/dist/builtin/intercom/broker/paths.ts +42 -5
- package/dist/builtin/intercom/broker/spawn.ts +78 -10
- package/dist/builtin/intercom/config.ts +6 -3
- package/dist/builtin/intercom/contact-supervisor-tool.ts +6 -4
- package/dist/builtin/intercom/index-heavy.ts +1 -1
- package/dist/builtin/intercom/index.ts +162 -130
- package/dist/builtin/intercom/intercom-tool.ts +9 -3
- package/dist/builtin/intercom/package.json +10 -1
- package/dist/builtin/intercom/result-renderers.ts +1 -1
- package/dist/builtin/intercom/ui/compose.ts +2 -2
- package/dist/builtin/intercom/ui/inline-message.ts +2 -2
- package/dist/builtin/intercom/ui/session-list.ts +2 -2
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +6 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/tui/render-event-formatting.ts +15 -48
- package/dist/builtin/subagents/src/tui/render-layout.ts +9 -0
- package/dist/builtin/subagents/src/tui/render-widget-graph.ts +27 -25
- package/dist/builtin/subagents/src/tui/render-widget.ts +46 -69
- package/dist/builtin/subagents/src/tui/render.ts +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +11 -0
- package/dist/builtin/workflows/README.md +3 -1
- package/dist/builtin/workflows/builtin/goal-artifacts.ts +3 -1
- package/dist/builtin/workflows/builtin/goal-prompts.ts +23 -2
- package/dist/builtin/workflows/builtin/goal-reducer.ts +21 -0
- package/dist/builtin/workflows/builtin/goal-review.ts +60 -6
- package/dist/builtin/workflows/builtin/goal-runner.ts +32 -21
- package/dist/builtin/workflows/builtin/goal-types.ts +7 -1
- package/dist/builtin/workflows/builtin/ralph-core.ts +52 -4
- package/dist/builtin/workflows/builtin/ralph-review-gate.ts +10 -3
- package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +23 -2
- package/dist/builtin/workflows/builtin/ralph-runner.ts +38 -16
- package/dist/builtin/workflows/builtin/review-convergence.ts +111 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/create-spec/SKILL.md +2 -0
- package/dist/builtin/workflows/src/durable/child-primitive.ts +12 -8
- package/dist/builtin/workflows/src/durable/dbos-backend.ts +47 -2
- package/dist/builtin/workflows/src/durable/dbos-envelope.ts +28 -0
- package/dist/builtin/workflows/src/durable/stage-primitive.ts +165 -27
- package/dist/builtin/workflows/src/durable/types.ts +14 -1
- package/dist/builtin/workflows/src/engine/run.ts +12 -13
- package/dist/builtin/workflows/src/extension/workflow-prompts.ts +2 -0
- package/dist/builtin/workflows/src/runs/foreground/executor-continuation.ts +13 -3
- package/dist/builtin/workflows/src/runs/foreground/executor-stage-factory.ts +1 -1
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-controller.ts +18 -19
- package/dist/builtin/workflows/src/runs/foreground/stage-runner-unresolved-overflow.ts +38 -0
- package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +18 -1
- package/dist/builtin/workflows/src/shared/persistence-restore.ts +23 -5
- package/dist/builtin/workflows/src/shared/persistence-session-entries.ts +4 -0
- package/dist/bun/internal-intercom-broker.d.ts +6 -0
- package/dist/bun/internal-intercom-broker.d.ts.map +1 -0
- package/dist/bun/internal-intercom-broker.js +39 -0
- package/dist/bun/internal-intercom-broker.js.map +1 -0
- package/dist/bun/split-loader.d.ts.map +1 -1
- package/dist/bun/split-loader.js +21 -8
- package/dist/bun/split-loader.js.map +1 -1
- package/dist/core/agent-session-auto-compaction.d.ts +4 -5
- package/dist/core/agent-session-auto-compaction.d.ts.map +1 -1
- package/dist/core/agent-session-auto-compaction.js +49 -9
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +3 -1
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +9 -3
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-models.d.ts +2 -2
- package/dist/core/agent-session-models.d.ts.map +1 -1
- package/dist/core/agent-session-models.js.map +1 -1
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +1 -0
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts +2 -0
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +152 -4
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-services.d.ts +1 -0
- package/dist/core/agent-session-services.d.ts.map +1 -1
- package/dist/core/agent-session-services.js +1 -0
- package/dist/core/agent-session-services.js.map +1 -1
- package/dist/core/agent-session-types.d.ts +15 -1
- package/dist/core/agent-session-types.d.ts.map +1 -1
- package/dist/core/agent-session-types.js.map +1 -1
- package/dist/core/agent-session.d.ts +4 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +4 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/compaction/context-compaction-runner.d.ts.map +1 -1
- package/dist/core/compaction/context-compaction-runner.js +36 -20
- package/dist/core/compaction/context-compaction-runner.js.map +1 -1
- package/dist/core/copilot-anthropic-sse-repair.d.ts +23 -0
- package/dist/core/copilot-anthropic-sse-repair.d.ts.map +1 -0
- package/dist/core/copilot-anthropic-sse-repair.js +340 -0
- package/dist/core/copilot-anthropic-sse-repair.js.map +1 -0
- package/dist/core/copilot-gemini-reasoning.d.ts +16 -8
- package/dist/core/copilot-gemini-reasoning.d.ts.map +1 -1
- package/dist/core/copilot-gemini-reasoning.js +27 -22
- package/dist/core/copilot-gemini-reasoning.js.map +1 -1
- package/dist/core/copilot-hosts.d.ts +12 -0
- package/dist/core/copilot-hosts.d.ts.map +1 -0
- package/dist/core/copilot-hosts.js +33 -0
- package/dist/core/copilot-hosts.js.map +1 -0
- package/dist/core/extensions/agent-events.d.ts +1 -1
- package/dist/core/extensions/agent-events.d.ts.map +1 -1
- package/dist/core/extensions/agent-events.js.map +1 -1
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +5 -5
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/sdk-types.d.ts +2 -0
- package/dist/core/sdk-types.d.ts.map +1 -1
- package/dist/core/sdk-types.js.map +1 -1
- package/dist/core/sdk.d.ts.map +1 -1
- package/dist/core/sdk.js +1 -0
- package/dist/core/sdk.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts +1 -0
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +6 -0
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/index-extensions.d.ts +1 -1
- package/dist/index-extensions.d.ts.map +1 -1
- package/dist/index-extensions.js.map +1 -1
- package/dist/main-deferred-startup.d.ts +12 -0
- package/dist/main-deferred-startup.d.ts.map +1 -1
- package/dist/main-deferred-startup.js +21 -0
- package/dist/main-deferred-startup.js.map +1 -1
- package/dist/main-early-input.d.ts +41 -0
- package/dist/main-early-input.d.ts.map +1 -0
- package/dist/main-early-input.js +147 -0
- package/dist/main-early-input.js.map +1 -0
- package/dist/main.d.ts +0 -6
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +19 -13
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.d.ts.map +1 -1
- package/dist/modes/interactive/components/chat-session-host-events.js +15 -0
- package/dist/modes/interactive/components/chat-session-host-events.js.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-agent-events.js +24 -0
- package/dist/modes/interactive/interactive-agent-events.js.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-input-handling.js +279 -181
- package/dist/modes/interactive/interactive-input-handling.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.d.ts +9 -0
- package/dist/modes/interactive/interactive-mode-base.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-base.js +36 -0
- package/dist/modes/interactive/interactive-mode-base.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.d.ts +4 -0
- package/dist/modes/interactive/interactive-mode-surface.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-surface.js.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.d.ts +3 -0
- package/dist/modes/interactive/interactive-mode-types.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode-types.js.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-render-chat.js +21 -9
- package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
- package/dist/modes/interactive/interactive-startup.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-startup.js +6 -1
- package/dist/modes/interactive/interactive-startup.js.map +1 -1
- package/docs/compaction.md +4 -0
- package/docs/json.md +3 -1
- package/docs/providers.md +1 -0
- package/docs/rpc.md +3 -1
- package/docs/settings.md +26 -0
- package/docs/terminal-setup.md +4 -0
- package/docs/usage.md +2 -2
- package/docs/workflows.md +13 -3
- package/npm-shrinkwrap.json +513 -23
- package/package.json +3 -2
|
@@ -5,7 +5,7 @@ import { formatDuration, formatModelThinking, shortenPath } from "../shared/form
|
|
|
5
5
|
import { formatNestedAggregate } from "../runs/shared/nested-render.ts";
|
|
6
6
|
import { formatAgentRunningLabel } from "../shared/status-format.ts";
|
|
7
7
|
import { buildLiveStatusLine, formatCurrentToolLine, formatTokenStat, formatToolUseStat, statJoin } from "./render-status-progress.ts";
|
|
8
|
-
import { runningGlyph,
|
|
8
|
+
import { runningGlyph, runningPulseGlyph, truncLine, type Theme } from "./render-layout.ts";
|
|
9
9
|
|
|
10
10
|
export function formatWidgetAgents(agents: string[]): string {
|
|
11
11
|
const distinct = [...new Set(agents)];
|
|
@@ -40,47 +40,9 @@ export function widgetActivity(job: AsyncJobState): string {
|
|
|
40
40
|
return "Done";
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export function widgetStepRunningSeed(step: NonNullable<AsyncJobState["steps"]>[number], fallbackIndex?: number): number | undefined {
|
|
44
|
-
return runningSeed(
|
|
45
|
-
fallbackIndex,
|
|
46
|
-
step.index,
|
|
47
|
-
step.toolCount,
|
|
48
|
-
step.turnCount,
|
|
49
|
-
step.tokens?.total,
|
|
50
|
-
step.lastActivityAt,
|
|
51
|
-
step.currentToolStartedAt,
|
|
52
|
-
step.durationMs,
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export function widgetStepsRunningSeed(steps: Array<NonNullable<AsyncJobState["steps"]>[number]> | undefined): number | undefined {
|
|
57
|
-
let seed: number | undefined;
|
|
58
|
-
for (const [index, step] of (steps ?? []).entries()) seed = runningSeed(seed, widgetStepRunningSeed(step, index));
|
|
59
|
-
return seed;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
export function widgetJobRunningSeed(job: AsyncJobState): number | undefined {
|
|
63
|
-
return runningSeed(
|
|
64
|
-
job.updatedAt,
|
|
65
|
-
job.lastActivityAt,
|
|
66
|
-
job.toolCount,
|
|
67
|
-
job.turnCount,
|
|
68
|
-
job.totalTokens?.total,
|
|
69
|
-
job.currentStep,
|
|
70
|
-
job.runningSteps,
|
|
71
|
-
job.completedSteps,
|
|
72
|
-
widgetStepsRunningSeed(job.steps),
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export function widgetJobsRunningSeed(jobs: AsyncJobState[]): number | undefined {
|
|
77
|
-
let seed: number | undefined;
|
|
78
|
-
for (const job of jobs) seed = runningSeed(seed, widgetJobRunningSeed(job));
|
|
79
|
-
return seed;
|
|
80
|
-
}
|
|
81
43
|
|
|
82
|
-
export function widgetStatusGlyph(job: AsyncJobState, theme: Theme,
|
|
83
|
-
if (job.status === "running") return theme.fg("accent",
|
|
44
|
+
export function widgetStatusGlyph(job: AsyncJobState, theme: Theme, pulseFrame?: number): string {
|
|
45
|
+
if (job.status === "running") return theme.fg("accent", runningPulseGlyph(pulseFrame));
|
|
84
46
|
if (job.status === "queued") return theme.fg("muted", "◦");
|
|
85
47
|
if (job.status === "complete") return theme.fg("success", "✓");
|
|
86
48
|
if (job.status === "paused") return theme.fg("warning", "■");
|
|
@@ -95,6 +57,14 @@ export function widgetStepGlyph(status: AsyncJobStep["status"], theme: Theme, se
|
|
|
95
57
|
return theme.fg("muted", "◦");
|
|
96
58
|
}
|
|
97
59
|
|
|
60
|
+
export function widgetStepPulseGlyph(status: AsyncJobStep["status"], theme: Theme, pulseFrame?: number): string {
|
|
61
|
+
if (status === "running") return theme.fg("accent", runningPulseGlyph(pulseFrame));
|
|
62
|
+
if (status === "complete" || status === "completed") return theme.fg("success", "✓");
|
|
63
|
+
if (status === "failed") return theme.fg("error", "✗");
|
|
64
|
+
if (status === "paused") return theme.fg("warning", "■");
|
|
65
|
+
return theme.fg("muted", "◦");
|
|
66
|
+
}
|
|
67
|
+
|
|
98
68
|
export function widgetStepStatus(status: AsyncJobStep["status"], theme: Theme): string {
|
|
99
69
|
if (status === "running") return theme.fg("accent", "running");
|
|
100
70
|
if (status === "complete" || status === "completed") return theme.fg("success", "complete");
|
|
@@ -186,17 +156,14 @@ export function nestedRunName(run: NestedRunSummary): string {
|
|
|
186
156
|
return run.id;
|
|
187
157
|
}
|
|
188
158
|
|
|
189
|
-
export function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary["status"], theme: Theme,
|
|
190
|
-
if (state === "running") return theme.fg("accent",
|
|
159
|
+
export function nestedStatusGlyph(state: NestedRunSummary["state"] | NestedStepSummary["status"], theme: Theme, pulseFrame?: number): string {
|
|
160
|
+
if (state === "running") return theme.fg("accent", runningPulseGlyph(pulseFrame));
|
|
191
161
|
if (state === "complete" || state === "completed") return theme.fg("success", "✓");
|
|
192
162
|
if (state === "failed") return theme.fg("error", "✗");
|
|
193
163
|
if (state === "paused") return theme.fg("warning", "■");
|
|
194
164
|
return theme.fg("muted", "◦");
|
|
195
165
|
}
|
|
196
166
|
|
|
197
|
-
export function nestedRunSeed(run: NestedRunSummary): number | undefined {
|
|
198
|
-
return runningSeed(run.lastUpdate, run.lastActivityAt, run.currentStep, run.toolCount, run.turnCount, run.totalTokens?.total, run.currentToolStartedAt);
|
|
199
|
-
}
|
|
200
167
|
|
|
201
168
|
export function nestedActivity(input: Pick<NestedRunSummary | NestedStepSummary, "activityState" | "lastActivityAt" | "currentTool" | "currentToolStartedAt" | "currentPath" | "turnCount" | "toolCount">, state: NestedRunSummary["state"] | NestedStepSummary["status"], snapshotNow?: number): string {
|
|
202
169
|
const facts: string[] = [];
|
|
@@ -240,7 +207,7 @@ export function formatNestedWidgetLines(children: NestedRunSummary[] | undefined
|
|
|
240
207
|
}
|
|
241
208
|
const activity = nestedActivity(child, child.state, snapshotNow ?? child.lastUpdate);
|
|
242
209
|
const error = child.error ? ` · ${child.error}` : "";
|
|
243
|
-
lines.push(theme.fg("dim", `${prefix}↳ ${nestedStatusGlyph(child.state, theme,
|
|
210
|
+
lines.push(theme.fg("dim", `${prefix}↳ ${nestedStatusGlyph(child.state, theme, now)} ${nestedRunName(child)} · ${child.state} · ${activity}${error}`));
|
|
244
211
|
if (depth === maxDepth) {
|
|
245
212
|
const aggregate = formatNestedAggregate([...(child.steps?.flatMap((step) => step.children ?? []) ?? []), ...(child.children ?? [])]);
|
|
246
213
|
if (aggregate && lines.length < lineBudget) lines.push(theme.fg("dim", `${prefix} ↳ ${aggregate}`));
|
|
@@ -248,7 +215,7 @@ export function formatNestedWidgetLines(children: NestedRunSummary[] | undefined
|
|
|
248
215
|
}
|
|
249
216
|
for (const step of child.steps ?? []) {
|
|
250
217
|
if (lines.length >= lineBudget) return;
|
|
251
|
-
lines.push(theme.fg("dim", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme,
|
|
218
|
+
lines.push(theme.fg("dim", `${prefix} ↳ ${nestedStatusGlyph(step.status, theme, now)} ${step.agent} · ${step.status} · ${nestedActivity(step, step.status, snapshotNow ?? child.lastUpdate)}`));
|
|
252
219
|
append(step.children, depth + 1, `${prefix} `);
|
|
253
220
|
}
|
|
254
221
|
append(child.children, depth + 1, `${prefix} `);
|
|
@@ -129,6 +129,15 @@ export function pulseGlyph(frame?: number): string {
|
|
|
129
129
|
return PULSE_FRAMES[index % PULSE_FRAMES.length]!;
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Running async/background subagent pulse. It reuses the exact foreground pulse
|
|
134
|
+
* glyph frames while accepting an explicit frame advanced by the widget only on
|
|
135
|
+
* real async progress/status updates, matching foreground result semantics.
|
|
136
|
+
*/
|
|
137
|
+
export function runningPulseGlyph(frame?: number): string {
|
|
138
|
+
return pulseGlyph(frame);
|
|
139
|
+
}
|
|
140
|
+
|
|
132
141
|
export function progressRunningSeed(progress: ProgressSeedSource | undefined): number | undefined {
|
|
133
142
|
if (!progress) return undefined;
|
|
134
143
|
return runningSeed(
|
|
@@ -14,15 +14,13 @@ import {
|
|
|
14
14
|
widgetStatusGlyph,
|
|
15
15
|
widgetStepActivity,
|
|
16
16
|
widgetStepActivityLine,
|
|
17
|
-
|
|
18
|
-
widgetStepRunningSeed,
|
|
17
|
+
widgetStepPulseGlyph,
|
|
19
18
|
widgetStepStats,
|
|
20
19
|
widgetStepStatus,
|
|
21
|
-
widgetStepsRunningSeed,
|
|
22
20
|
widgetJobName,
|
|
23
21
|
} from "./render-event-formatting.ts";
|
|
24
22
|
|
|
25
|
-
export function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth(),
|
|
23
|
+
export function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth(), pulseFrame?: number): string[] {
|
|
26
24
|
if (!job.steps?.length) return [];
|
|
27
25
|
const total = job.chainStepCount ?? job.steps.length;
|
|
28
26
|
const lines: string[] = [];
|
|
@@ -30,7 +28,7 @@ export function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded =
|
|
|
30
28
|
const steps = job.steps.slice(span.start, span.start + span.count);
|
|
31
29
|
if (span.isParallel) {
|
|
32
30
|
const status = aggregateStepStatus(steps);
|
|
33
|
-
lines.push(` ${
|
|
31
|
+
lines.push(` ${widgetStepPulseGlyph(status, theme, pulseFrame)} Step ${span.stepIndex + 1}/${total}: ${themeBold(theme, "parallel group")} ${theme.fg("dim", "·")} ${theme.fg("dim", formatParallelOutcome(steps, span.count))}`);
|
|
34
32
|
continue;
|
|
35
33
|
}
|
|
36
34
|
const step = steps[0];
|
|
@@ -38,15 +36,15 @@ export function widgetChainDetails(job: AsyncJobState, theme: Theme, expanded =
|
|
|
38
36
|
lines.push(` ${theme.fg("dim", `◦ Step ${span.stepIndex + 1}/${total}: pending`)}`);
|
|
39
37
|
continue;
|
|
40
38
|
}
|
|
41
|
-
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, "Step", span.stepIndex + 1, total, expanded, width,
|
|
39
|
+
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, "Step", span.stepIndex + 1, total, expanded, width, pulseFrame));
|
|
42
40
|
}
|
|
43
41
|
return lines;
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
export function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth(),
|
|
44
|
+
export function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, expanded = false, width = getTermWidth(), _now?: number, pulseFrame?: number): string[] {
|
|
47
45
|
if (!job.steps?.length) return [];
|
|
48
46
|
if (job.mode !== "parallel" && job.mode !== "chain") return [];
|
|
49
|
-
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width,
|
|
47
|
+
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, pulseFrame);
|
|
50
48
|
const total = job.stepsTotal ?? job.steps.length;
|
|
51
49
|
const lines: string[] = [];
|
|
52
50
|
for (const [index, step] of job.steps.entries()) {
|
|
@@ -54,8 +52,8 @@ export function widgetParallelAgentDetails(job: AsyncJobState, theme: Theme, exp
|
|
|
54
52
|
const activity = widgetStepActivity(step, job.updatedAt);
|
|
55
53
|
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
56
54
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking, step.fastMode);
|
|
57
|
-
lines.push(` ${theme.fg("dim", `${marker} ${
|
|
58
|
-
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 1,
|
|
55
|
+
lines.push(` ${theme.fg("dim", `${marker} ${widgetStepPulseGlyph(step.status, theme, pulseFrame)} ${itemTitle} ${index + 1}/${total}: ${step.agent} · ${widgetStepStatus(step.status, theme)}${modelDisplay}${activity ? ` · ${activity}` : ""}`)}`);
|
|
56
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, expanded ? 8 : 1, pulseFrame)) lines.push(` ${nestedLine}`);
|
|
59
57
|
}
|
|
60
58
|
return lines;
|
|
61
59
|
}
|
|
@@ -69,15 +67,15 @@ export function foregroundStyleWidgetStepLines(
|
|
|
69
67
|
total: number,
|
|
70
68
|
expanded: boolean,
|
|
71
69
|
width: number,
|
|
72
|
-
|
|
70
|
+
pulseFrame?: number,
|
|
73
71
|
): string[] {
|
|
74
72
|
const status = widgetStepStatus(step.status, theme);
|
|
75
73
|
const stats = widgetStepStats(theme, step);
|
|
76
74
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking, step.fastMode);
|
|
77
|
-
const lines = [` ${
|
|
75
|
+
const lines = [` ${widgetStepPulseGlyph(step.status, theme, pulseFrame)} ${itemTitle} ${index}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`];
|
|
78
76
|
const activity = widgetStepActivityLine(step, width, expanded, job.updatedAt);
|
|
79
77
|
if (activity) lines.push(` ${theme.fg("dim", `⎿ ${activity}`)}`);
|
|
80
|
-
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, undefined,
|
|
78
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, expanded, job.updatedAt, undefined, pulseFrame)) {
|
|
81
79
|
lines.push(` ${nestedLine}`);
|
|
82
80
|
}
|
|
83
81
|
if (step.status === "running") {
|
|
@@ -100,40 +98,40 @@ export function foregroundStyleWidgetStepLines(
|
|
|
100
98
|
return lines;
|
|
101
99
|
}
|
|
102
100
|
|
|
103
|
-
export function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number,
|
|
101
|
+
export function foregroundStyleWidgetDetails(job: AsyncJobState, theme: Theme, expanded: boolean, width: number, pulseFrame?: number): string[] {
|
|
104
102
|
if (!job.steps?.length) return [
|
|
105
103
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
106
|
-
...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, undefined,
|
|
104
|
+
...formatNestedWidgetLines(job.nestedChildren, theme, width, expanded, job.updatedAt, undefined, pulseFrame).map((line) => ` ${line}`),
|
|
107
105
|
];
|
|
108
|
-
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width,
|
|
106
|
+
if (job.mode === "chain" && !job.activeParallelGroup && job.parallelGroups?.length) return widgetChainDetails(job, theme, expanded, width, pulseFrame);
|
|
109
107
|
const total = job.stepsTotal ?? job.steps.length;
|
|
110
108
|
const itemTitle = job.mode === "parallel" || job.activeParallelGroup ? "Agent" : "Step";
|
|
111
109
|
const lines: string[] = [];
|
|
112
110
|
for (const [index, step] of job.steps.entries()) {
|
|
113
|
-
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width,
|
|
111
|
+
lines.push(...foregroundStyleWidgetStepLines(job, theme, step, itemTitle, index + 1, total, expanded, width, pulseFrame));
|
|
114
112
|
}
|
|
115
113
|
const attached = new Set(job.steps.flatMap((step) => step.children?.map((child) => child.id) ?? []));
|
|
116
114
|
const unattached = job.nestedChildren?.filter((child) => !attached.has(child.id)) ?? [];
|
|
117
|
-
for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt, undefined,
|
|
115
|
+
for (const nestedLine of formatNestedWidgetLines(unattached, theme, width, expanded, job.updatedAt, undefined, pulseFrame)) {
|
|
118
116
|
lines.push(` ${nestedLine}`);
|
|
119
117
|
}
|
|
120
118
|
return lines;
|
|
121
119
|
}
|
|
122
120
|
|
|
123
|
-
export function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean,
|
|
121
|
+
export function buildSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, expanded: boolean, _now?: number, pulseFrame?: number): string[] {
|
|
124
122
|
const stats = widgetStats(job, theme);
|
|
125
123
|
const count = job.mode === "chain" ? job.chainStepCount : job.stepsTotal ?? job.agents?.length ?? job.steps?.length;
|
|
126
124
|
const mode = widgetJobName(job);
|
|
127
125
|
const title = `async subagent ${mode}${count && count > 1 ? ` (${count})` : ""}`;
|
|
128
126
|
return [
|
|
129
127
|
`${theme.fg("toolTitle", themeBold(theme, title))} ${theme.fg("dim", "· background")}`,
|
|
130
|
-
`${widgetStatusGlyph(job, theme,
|
|
131
|
-
...foregroundStyleWidgetDetails(job, theme, expanded, width,
|
|
128
|
+
`${widgetStatusGlyph(job, theme, pulseFrame)} ${themeBold(theme, mode)}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
129
|
+
...foregroundStyleWidgetDetails(job, theme, expanded, width, pulseFrame),
|
|
132
130
|
].map((line) => truncLine(line, width));
|
|
133
131
|
}
|
|
134
132
|
|
|
135
|
-
export function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number,
|
|
136
|
-
const fullLines = buildSingleWidgetLines(job, theme, width, false,
|
|
133
|
+
export function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width: number, _now?: number, pulseFrame?: number): string[] {
|
|
134
|
+
const fullLines = buildSingleWidgetLines(job, theme, width, false, _now, pulseFrame);
|
|
137
135
|
if (fullLines.length <= 10 || !job.steps?.length || (job.mode !== "parallel" && !job.activeParallelGroup)) return fullLines;
|
|
138
136
|
|
|
139
137
|
const total = job.stepsTotal ?? job.steps.length;
|
|
@@ -145,8 +143,12 @@ export function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width
|
|
|
145
143
|
const stepStats = widgetStepStats(theme, step);
|
|
146
144
|
const activitySuffix = activity ? ` ${theme.fg("dim", "·")} ${theme.fg("dim", activity)}` : "";
|
|
147
145
|
const modelDisplay = modelThinkingBadge(theme, step.model, step.thinking, step.fastMode);
|
|
148
|
-
lines.push(` ${
|
|
149
|
-
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, undefined,
|
|
146
|
+
lines.push(` ${widgetStepPulseGlyph(step.status, theme, pulseFrame)} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
|
|
147
|
+
for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, undefined, pulseFrame)) lines.push(` ${nestedLine}`);
|
|
148
|
+
if (step.status === "running") {
|
|
149
|
+
const output = widgetOutputPath(job, step);
|
|
150
|
+
if (output) lines.push(` ${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
|
|
151
|
+
}
|
|
150
152
|
}
|
|
151
153
|
if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` Press ${keyHint("app.tools.expand", "for live detail")}`));
|
|
152
154
|
return lines.map((line) => truncLine(line, width));
|
|
@@ -2,8 +2,10 @@ import type { ExtensionContext } from "@bastani/atomic";
|
|
|
2
2
|
import { Container, Text, type Component } from "@earendil-works/pi-tui";
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { MAX_WIDGET_JOBS, WIDGET_KEY, type AsyncJobState } from "../shared/types.ts";
|
|
5
|
-
import { getTermWidth,
|
|
5
|
+
import { getTermWidth, runningPulseGlyph, truncLine, type Theme } from "./render-layout.ts";
|
|
6
6
|
import { themeBold } from "./render-status-progress.ts";
|
|
7
|
+
import { advanceResultPulseFrame } from "./render-result-animation.ts";
|
|
8
|
+
import { widgetRenderKey } from "./render-stable-output.ts";
|
|
7
9
|
import {
|
|
8
10
|
buildSingleWidgetLines,
|
|
9
11
|
compactSingleWidgetLines,
|
|
@@ -13,7 +15,6 @@ import {
|
|
|
13
15
|
import {
|
|
14
16
|
widgetActivity,
|
|
15
17
|
widgetJobName,
|
|
16
|
-
widgetJobsRunningSeed,
|
|
17
18
|
widgetStats,
|
|
18
19
|
widgetStatusGlyph,
|
|
19
20
|
} from "./render-event-formatting.ts";
|
|
@@ -26,22 +27,24 @@ class LiveWidgetComponent implements Component {
|
|
|
26
27
|
private readonly theme: Theme,
|
|
27
28
|
private readonly getExpanded: () => boolean,
|
|
28
29
|
private readonly getNow: () => number,
|
|
30
|
+
private readonly getPulseFrame: () => number | undefined,
|
|
29
31
|
) {}
|
|
30
32
|
|
|
31
33
|
render(width: number): string[] {
|
|
32
34
|
const jobs = this.getJobs();
|
|
33
35
|
const expanded = this.getExpanded();
|
|
34
36
|
const now = this.getNow();
|
|
35
|
-
const
|
|
37
|
+
const pulseFrame = this.getPulseFrame();
|
|
38
|
+
const lines = this.buildLines(jobs, width, expanded, now, pulseFrame);
|
|
36
39
|
this.container.clear();
|
|
37
40
|
for (const line of fitWidgetLineBudget(lines, this.theme, width, expanded)) this.container.addChild(new Text(line, 1, 0));
|
|
38
41
|
return this.container.render(width);
|
|
39
42
|
}
|
|
40
43
|
|
|
41
|
-
private buildLines(jobs: AsyncJobState[], width: number, expanded: boolean, now: number): string[] {
|
|
42
|
-
if (expanded) return buildWidgetLines(jobs, this.theme, width, true, now);
|
|
43
|
-
if (jobs.length === 1) return compactSingleWidgetLines(jobs[0]!, this.theme, width, now);
|
|
44
|
-
return buildWidgetLines(jobs, this.theme, width, false, now);
|
|
44
|
+
private buildLines(jobs: AsyncJobState[], width: number, expanded: boolean, now: number, pulseFrame: number | undefined): string[] {
|
|
45
|
+
if (expanded) return buildWidgetLines(jobs, this.theme, width, true, now, pulseFrame);
|
|
46
|
+
if (jobs.length === 1) return compactSingleWidgetLines(jobs[0]!, this.theme, width, now, pulseFrame);
|
|
47
|
+
return buildWidgetLines(jobs, this.theme, width, false, now, pulseFrame);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
invalidate(): void {
|
|
@@ -49,22 +52,23 @@ class LiveWidgetComponent implements Component {
|
|
|
49
52
|
}
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
function buildWidgetComponent(getJobs: () => AsyncJobState[], getExpanded: () => boolean, getNow: () => number): (_tui: unknown, theme: Theme) => Component {
|
|
53
|
-
return (_tui, theme) => new LiveWidgetComponent(getJobs, theme, getExpanded, getNow);
|
|
55
|
+
function buildWidgetComponent(getJobs: () => AsyncJobState[], getExpanded: () => boolean, getNow: () => number, getPulseFrame: () => number | undefined): (_tui: unknown, theme: Theme) => Component {
|
|
56
|
+
return (_tui, theme) => new LiveWidgetComponent(getJobs, theme, getExpanded, getNow, getPulseFrame);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
interface RenderRequestingContext {
|
|
57
60
|
ui: ExtensionContext["ui"] & { requestRender?: () => void };
|
|
58
61
|
}
|
|
59
62
|
|
|
60
|
-
// There is only ever one async-agents widget per host process, so the
|
|
61
|
-
//
|
|
63
|
+
// There is only ever one async-agents widget per host process, so the mounted
|
|
64
|
+
// component reads its driving context/jobs/pulse frame from module-level
|
|
62
65
|
// singletons instead of remounting the widget for every visible update.
|
|
63
66
|
let latestWidgetCtx: ExtensionContext | undefined;
|
|
64
67
|
let latestWidgetJobs: AsyncJobState[] = [];
|
|
65
68
|
let latestWidgetFrameNow = 0;
|
|
66
69
|
let latestWidgetExpanded = false;
|
|
67
|
-
let
|
|
70
|
+
let latestWidgetSnapshotKey: string | undefined;
|
|
71
|
+
let latestWidgetPulseFrame: number | undefined;
|
|
68
72
|
let mountedWidgetCtx: ExtensionContext | undefined;
|
|
69
73
|
let mountedWidgetOwnerKey: string | undefined;
|
|
70
74
|
let widgetMounted = false;
|
|
@@ -77,6 +81,10 @@ function getLatestWidgetFrameNow(): number {
|
|
|
77
81
|
return latestWidgetFrameNow;
|
|
78
82
|
}
|
|
79
83
|
|
|
84
|
+
function getLatestWidgetPulseFrame(): number | undefined {
|
|
85
|
+
return latestWidgetPulseFrame;
|
|
86
|
+
}
|
|
87
|
+
|
|
80
88
|
function getLatestWidgetExpanded(): boolean {
|
|
81
89
|
// LiveWidgetComponent re-renders outside a specific renderWidget() call, so
|
|
82
90
|
// remember the last expansion state observed from a live host UI. Workflow
|
|
@@ -94,6 +102,8 @@ function clearLatestWidgetState(): void {
|
|
|
94
102
|
latestWidgetJobs = [];
|
|
95
103
|
latestWidgetFrameNow = 0;
|
|
96
104
|
latestWidgetExpanded = false;
|
|
105
|
+
latestWidgetSnapshotKey = undefined;
|
|
106
|
+
latestWidgetPulseFrame = undefined;
|
|
97
107
|
mountedWidgetCtx = undefined;
|
|
98
108
|
mountedWidgetOwnerKey = undefined;
|
|
99
109
|
widgetMounted = false;
|
|
@@ -142,63 +152,35 @@ function unmountWidgetBestEffort(ctx: ExtensionContext | undefined): void {
|
|
|
142
152
|
}
|
|
143
153
|
}
|
|
144
154
|
|
|
145
|
-
function
|
|
146
|
-
|
|
147
|
-
// header/step spinners never freeze before the work actually settles.
|
|
148
|
-
return jobs.some((job) => job.status === "running" || job.steps?.some((step) => step.status === "running"));
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
function refreshAnimatedWidget(): void {
|
|
152
|
-
if (!latestWidgetCtx?.hasUI) return;
|
|
153
|
-
try {
|
|
154
|
-
latestWidgetFrameNow = Date.now();
|
|
155
|
-
requestWidgetRender(latestWidgetCtx);
|
|
156
|
-
} catch {
|
|
157
|
-
// A stale render context means the cosmetic ticker can no longer update the
|
|
158
|
-
// mounted widget safely; tear it down best-effort and let the next status
|
|
159
|
-
// update remount on the active host context.
|
|
160
|
-
stopWidgetAnimation();
|
|
161
|
-
}
|
|
155
|
+
function widgetSnapshotKey(jobs: AsyncJobState[]): string {
|
|
156
|
+
return jobs.map(widgetRenderKey).join("\n");
|
|
162
157
|
}
|
|
163
158
|
|
|
164
|
-
function
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
}
|
|
171
|
-
refreshAnimatedWidget();
|
|
172
|
-
}, RUNNING_ANIMATION_MS);
|
|
173
|
-
widgetTimer.unref?.();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Stop only the ticker, keeping the last-rendered widget context/jobs intact.
|
|
177
|
-
function stopWidgetTicker(): void {
|
|
178
|
-
if (widgetTimer) {
|
|
179
|
-
clearInterval(widgetTimer);
|
|
180
|
-
widgetTimer = undefined;
|
|
181
|
-
}
|
|
159
|
+
function refreshWidgetSnapshot(jobs: AsyncJobState[]): void {
|
|
160
|
+
const snapshotKey = widgetSnapshotKey(jobs);
|
|
161
|
+
if (snapshotKey === latestWidgetSnapshotKey) return;
|
|
162
|
+
latestWidgetSnapshotKey = snapshotKey;
|
|
163
|
+
latestWidgetFrameNow = Date.now();
|
|
164
|
+
latestWidgetPulseFrame = advanceResultPulseFrame(latestWidgetPulseFrame);
|
|
182
165
|
}
|
|
183
166
|
|
|
184
|
-
// Full teardown:
|
|
185
|
-
//
|
|
167
|
+
// Full teardown: clear the mounted widget if possible, and forget the driving
|
|
168
|
+
// context/jobs entirely.
|
|
186
169
|
export function stopWidgetAnimation(): void {
|
|
187
|
-
stopWidgetTicker();
|
|
188
170
|
if (widgetMounted) unmountWidgetBestEffort(mountedWidgetCtx);
|
|
189
171
|
clearLatestWidgetState();
|
|
190
172
|
}
|
|
191
173
|
|
|
192
|
-
export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false, now: number = Date.now()): string[] {
|
|
174
|
+
export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = getTermWidth(), expanded = false, now: number = Date.now(), pulseFrame?: number): string[] {
|
|
193
175
|
if (jobs.length === 0) return [];
|
|
194
|
-
if (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded, now);
|
|
176
|
+
if (jobs.length === 1) return buildSingleWidgetLines(jobs[0]!, theme, width, expanded, now, pulseFrame);
|
|
195
177
|
const running = jobs.filter((job) => job.status === "running");
|
|
196
178
|
const queued = jobs.filter((job) => job.status === "queued");
|
|
197
179
|
const finished = jobs.filter((job) => job.status !== "running" && job.status !== "queued");
|
|
198
180
|
|
|
199
181
|
const lines: string[] = [];
|
|
200
182
|
const hasActive = running.length > 0 || queued.length > 0;
|
|
201
|
-
const headerGlyph = running.length > 0 ?
|
|
183
|
+
const headerGlyph = running.length > 0 ? runningPulseGlyph(pulseFrame) : hasActive ? "●" : "○";
|
|
202
184
|
lines.push(truncLine(`${theme.fg(hasActive ? "accent" : "dim", headerGlyph)} ${theme.fg(hasActive ? "accent" : "dim", "Async agents")} ${theme.fg("dim", "· background")}`, width));
|
|
203
185
|
|
|
204
186
|
const items: string[][] = [];
|
|
@@ -211,9 +193,9 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
211
193
|
if (slots <= 0) { hiddenRunning++; continue; }
|
|
212
194
|
const stats = widgetStats(job, theme);
|
|
213
195
|
items.push([
|
|
214
|
-
`${widgetStatusGlyph(job, theme,
|
|
196
|
+
`${widgetStatusGlyph(job, theme, pulseFrame)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
215
197
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
216
|
-
...widgetParallelAgentDetails(job, theme, expanded, width, now),
|
|
198
|
+
...widgetParallelAgentDetails(job, theme, expanded, width, now, pulseFrame),
|
|
217
199
|
]);
|
|
218
200
|
slots--;
|
|
219
201
|
}
|
|
@@ -228,9 +210,9 @@ export function buildWidgetLines(jobs: AsyncJobState[], theme: Theme, width = ge
|
|
|
228
210
|
if (slots <= 0) { hiddenFinished++; continue; }
|
|
229
211
|
const stats = widgetStats(job, theme);
|
|
230
212
|
items.push([
|
|
231
|
-
`${widgetStatusGlyph(job, theme,
|
|
213
|
+
`${widgetStatusGlyph(job, theme, pulseFrame)} ${themeBold(theme, widgetJobName(job))}${stats ? ` ${theme.fg("dim", "·")} ${stats}` : ""}`,
|
|
232
214
|
` ${theme.fg("dim", `⎿ ${widgetActivity(job)}`)}`,
|
|
233
|
-
...widgetParallelAgentDetails(job, theme, expanded, width, now),
|
|
215
|
+
...widgetParallelAgentDetails(job, theme, expanded, width, now, pulseFrame),
|
|
234
216
|
]);
|
|
235
217
|
slots--;
|
|
236
218
|
}
|
|
@@ -280,7 +262,7 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
280
262
|
}
|
|
281
263
|
latestWidgetCtx = ctx;
|
|
282
264
|
latestWidgetJobs = [...jobs];
|
|
283
|
-
|
|
265
|
+
refreshWidgetSnapshot(jobs);
|
|
284
266
|
if (widgetMounted && mountedWidgetOwnerKey !== ownerKey) {
|
|
285
267
|
// Session rebinding can leave the previous host UI alive briefly; clear the
|
|
286
268
|
// old mount before installing the singleton widget on the new owner/context.
|
|
@@ -290,13 +272,10 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
290
272
|
widgetMounted = false;
|
|
291
273
|
}
|
|
292
274
|
if (!widgetMounted) {
|
|
293
|
-
// belowEditor
|
|
294
|
-
//
|
|
295
|
-
//
|
|
296
|
-
|
|
297
|
-
// editor keeps the live line within the bottom viewport (flicker-free), and
|
|
298
|
-
// matches the workflow companion widget's placement (#1109).
|
|
299
|
-
ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(getLatestWidgetJobs, getLatestWidgetExpanded, getLatestWidgetFrameNow), {
|
|
275
|
+
// belowEditor keeps the live async widget pinned to the bottom viewport,
|
|
276
|
+
// matching the workflow companion widget's placement (#1109). The pulse frame
|
|
277
|
+
// is advanced only by semantic async status updates, not by a cosmetic timer.
|
|
278
|
+
ctx.ui.setWidget(WIDGET_KEY, buildWidgetComponent(getLatestWidgetJobs, getLatestWidgetExpanded, getLatestWidgetFrameNow, getLatestWidgetPulseFrame), {
|
|
300
279
|
placement: "belowEditor",
|
|
301
280
|
});
|
|
302
281
|
mountedWidgetCtx = ctx;
|
|
@@ -311,8 +290,6 @@ export function renderWidget(ctx: ExtensionContext, jobs: AsyncJobState[]): void
|
|
|
311
290
|
mountedWidgetOwnerKey = ownerKey;
|
|
312
291
|
requestWidgetRender(ctx);
|
|
313
292
|
}
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
if (hasAnimatedWidgetJobs(jobs)) ensureWidgetAnimation();
|
|
317
|
-
else stopWidgetTicker();
|
|
293
|
+
// The async pulse is update-driven like foreground subagent results, so no
|
|
294
|
+
// periodic cosmetic ticker is needed.
|
|
318
295
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* rendering responsibility across sibling modules.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
export { PULSE_FRAMES, RUNNING_ANIMATION_MS, RUNNING_FRAMES, currentRunningFrame, pulseGlyph } from "./render-layout.ts";
|
|
8
|
+
export { PULSE_FRAMES, RUNNING_ANIMATION_MS, RUNNING_FRAMES, currentRunningFrame, pulseGlyph, runningPulseGlyph } from "./render-layout.ts";
|
|
9
9
|
export {
|
|
10
10
|
advanceResultPulseFrame,
|
|
11
11
|
clearLegacyResultAnimationTimer,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.5-alpha.
|
|
3
|
+
"version": "0.9.5-alpha.8",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.5-alpha.8] - 2026-07-08
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed builtin `goal` and `ralph` reviewer convergence reporting so review artifacts and reducer/controller decisions now persist explicit parse/convergence summaries (`parsed`, `approved`, `stopReviewLoop`, `nextAction`, `finalActionRemaining`, and diagnostics). Malformed or missing structured reviewer output is surfaced as a parse failure distinct from parsed reviewer rejection/findings; PR/MR/review creation is represented as a post-approval final action when it is the only remaining task; successful reviewer convergence records the final action (`pull-request` or `finish`) without consuming another implementation loop; and the workflow authoring docs, default workflow guidance, and create-spec prompt now tell custom reviewer-gated workflows to model authorized PR, release, deployment, or publication work as final actions rather than implementation blockers while writing local, action-oriented prompts that do not rely on workflow/stage names as hidden context.
|
|
14
|
+
|
|
15
|
+
- Fixed workflow stage model fallback for unrecoverable context-window overflow: when Atomic's same-model auto-compaction exhausts its recovery attempt, the stage now advances to the next configured `fallbackModels` tier and, once all tiers are exhausted, stops with the terminal context-overflow error instead of compacting and retrying the same model indefinitely.
|
|
16
|
+
- Fixed workflow controlled-pause ordering around unresolved context overflow: paused stages now honor the pause/resume handshake before surfacing the terminal overflow, and a resume message is not sent over an already-recorded unresolved-overflow signal.
|
|
17
|
+
- Fixed reattached workflow stage fallback after unresolved context overflow: if the restored/resumed model tier exhausts context recovery, fallback now continues with the next tier after that resumed model (or stops terminally when it was the final tier) instead of replaying the primary model.
|
|
18
|
+
- Fixed durable workflow resume hydration so replayed `ctx.parallel`/`ctx.task` fanout keeps its original branch parentage, completed stages restore persisted summaries, timing, session/model metadata, and DBOS/file checkpoints round-trip the richer stage metadata used by status and graph views.
|
|
19
|
+
|
|
9
20
|
## [0.9.5-alpha.6] - 2026-07-06
|
|
10
21
|
|
|
11
22
|
### Fixed
|
|
@@ -326,7 +326,7 @@ Atomic registers the canonical `structured_output` tool only for schema-enabled
|
|
|
326
326
|
|
|
327
327
|
### Model fallbacks
|
|
328
328
|
|
|
329
|
-
Stages and high-level task helpers can retry transient provider/model failures with an ordered `fallbackModels` list. The primary `model` is tried first, then each fallback, and finally the current Atomic-selected model when available. Fallbacks are only used for retryable model/provider failures such as rate limits, quota/auth/provider outages, unavailable models, network timeouts, and 5xx errors — ordinary tool, shell, validation, cancellation, and workflow-code failures are not retried.
|
|
329
|
+
Stages and high-level task helpers can retry transient provider/model failures with an ordered `fallbackModels` list. The primary `model` is tried first, then each fallback, and finally the current Atomic-selected model when available. Fallbacks are only used for retryable model/provider failures such as rate limits, quota/auth/provider outages, unavailable models, network timeouts, context-window overflows that Atomic's auto-compaction cannot resolve on the current model, and 5xx errors — ordinary tool, shell, validation, cancellation, and workflow-code failures are not retried.
|
|
330
330
|
|
|
331
331
|
```typescript
|
|
332
332
|
import { workflow } from "@bastani/workflows";
|
|
@@ -544,6 +544,8 @@ Input overrides are bare `key=value` tokens (no leading `--`). Values are JSON-p
|
|
|
544
544
|
|
|
545
545
|
Workflows always run as **background tasks** in interactive sessions — the chat editor stays free while a run executes. Press **F2** (or `/workflow connect <run-id>`) to attach to the live graph viewer; HIL prompts (`ctx.ui.input/confirm/select/editor/custom`) appear as awaiting-input graph nodes. Press Enter on a focused node, or click a visible graph node directly, to open that stage and answer locally, never as a modal dialog over the chat. Attached stage chats capture mouse/trackpad wheel events by default so scrolling stays inside the active stage transcript or prompt instead of falling through to terminal/main-chat scrollback. Press `ctrl+t` to toggle **copy mode**: copy mode disables workflow-chat mouse reporting so normal terminal/tmux text selection can work; press `ctrl+t` again to leave copy mode and restore workflow-chat scrolling. While copy mode is on, wheel/trackpad gestures are handled by the terminal/tmux and may scroll terminal scrollback, so leave copy mode before using the wheel again. Human input is detected when those runtime `ctx.ui.*` calls execute; workflows no longer have a declaration-time HIL flag.
|
|
546
546
|
|
|
547
|
+
Durable resume preserves the original completed-stage shape: replayed stage/task/parallel/child checkpoints retain summaries, durations, session/model metadata, and parallel branch parentage in the graph instead of flattening fanout branches into a sequential replay chain.
|
|
548
|
+
|
|
547
549
|
Nested `ctx.workflow(...)` calls are displayed as an expanded graph within the top-level run. `/workflow status` and run pickers list only top-level user-launched workflows, not implementation-owned child runs. The `workflow` tool's `stages`, `stage`, `transcript`, `send`, `pause`, `interrupt`, and `resume` actions can still target visible child stage ids, prefixes, or names from the expanded graph; Atomic routes the control action to the owning nested run internally. (`stages`, `stage`, `transcript`, and `send` are `workflow` tool actions, not `/workflow` slash subcommands; the slash command exposes `connect`, `attach`, `pause`, `list`, `status`, `interrupt`, `kill`, `resume`, `reload`, and `inputs`.)
|
|
548
550
|
|
|
549
551
|
Prompt answer replay is live-memory only. `StageSnapshot.promptAnswerState` reports whether continuation can replay a prompt answer (`available`), must ask again because the private ledger entry is gone (`unavailable`), or must ask again because multiple matching prompt nodes are ambiguous (`ambiguous`). Raw answers stay in a private `PromptAnswerRecord` ledger, are never serialized to snapshots or persistence, and remain resident in memory until the answer is cleared, the run is removed, or the store is cleared. Replay keys include prompt kind, message text, select choices, input/editor initial value, custom prompt identity hash, and hashed author callsite, so changing any of those inputs may intentionally re-ask on continuation. Empty `ctx.ui.select(..., [])` calls throw before creating a prompt node. Arbitrary custom-widget answers cannot be supplied with `workflow send`; focus the `custom` awaiting-input node in the interactive graph instead.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { writeFile } from "node:fs/promises";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import type { ReviewDecision, ReviewRecord } from "./goal-types.js";
|
|
4
|
+
import type { ReviewConvergenceSummary } from "./review-convergence.js";
|
|
4
5
|
|
|
5
6
|
export function artifactSafeName(value: string): string {
|
|
6
7
|
const safe = value
|
|
@@ -21,6 +22,7 @@ export async function writeReviewArtifact(
|
|
|
21
22
|
reviewer: string,
|
|
22
23
|
decision: ReviewDecision,
|
|
23
24
|
rawText: string,
|
|
25
|
+
convergenceDecision: ReviewConvergenceSummary,
|
|
24
26
|
): Promise<string> {
|
|
25
27
|
const artifactPath = join(
|
|
26
28
|
artifactDir,
|
|
@@ -28,7 +30,7 @@ export async function writeReviewArtifact(
|
|
|
28
30
|
);
|
|
29
31
|
await writeFile(
|
|
30
32
|
artifactPath,
|
|
31
|
-
`${JSON.stringify({ reviewer, decision, raw_text: rawText }, null, 2)}\n`,
|
|
33
|
+
`${JSON.stringify({ reviewer, decision, convergence_decision: convergenceDecision, raw_text: rawText }, null, 2)}\n`,
|
|
32
34
|
{ encoding: "utf8" },
|
|
33
35
|
);
|
|
34
36
|
return artifactPath;
|