@bastani/atomic 0.8.26-alpha.1 → 0.8.26-alpha.2
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 +7 -0
- package/dist/builtin/intercom/CHANGELOG.md +6 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +6 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +7 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/src/runs/background/subagent-runner.ts +8 -3
- package/dist/builtin/subagents/src/runs/foreground/execution.ts +42 -4
- package/dist/builtin/subagents/src/runs/shared/acceptance.ts +2 -1
- package/dist/builtin/subagents/src/runs/shared/worktree.ts +2 -2
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +12 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/research-codebase/SKILL.md +28 -9
- package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +6 -1
- package/dist/builtin/workflows/src/runs/shared/worktree.ts +2 -2
- package/dist/builtin/workflows/src/shared/store.ts +61 -7
- package/dist/builtin/workflows/src/tui/stage-chat-view.ts +37 -2
- package/dist/core/footer-data-provider.d.ts.map +1 -1
- package/dist/core/footer-data-provider.js +3 -0
- package/dist/core/footer-data-provider.js.map +1 -1
- package/dist/core/package-manager.d.ts.map +1 -1
- package/dist/core/package-manager.js +14 -7
- package/dist/core/package-manager.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/modes/interactive/components/footer.d.ts.map +1 -1
- package/dist/modes/interactive/components/footer.js +4 -1
- package/dist/modes/interactive/components/footer.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +3 -2
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/utils/git-env.d.ts +10 -0
- package/dist/utils/git-env.d.ts.map +1 -0
- package/dist/utils/git-env.js +33 -0
- package/dist/utils/git-env.js.map +1 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.8.26-alpha.2] - 2026-06-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Clarified overflow auto-compaction warnings in the TUI footer so automatic transcript compaction is reported distinctly from user-triggered compaction ([#1250](https://github.com/bastani-inc/atomic/issues/1250)).
|
|
10
|
+
- Fixed internal Git subprocesses to strip ambient repository-local Git environment variables before package-manager and footer branch lookups inspect a targeted working tree.
|
|
11
|
+
|
|
5
12
|
## [0.8.26-alpha.1] - 2026-06-05
|
|
6
13
|
|
|
7
14
|
### Fixed
|
|
@@ -4,6 +4,12 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.8.26-alpha.2] - 2026-06-05
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Bumped package version for the Atomic 0.8.26-alpha.2 prerelease.
|
|
12
|
+
|
|
7
13
|
## [0.8.26-alpha.1] - 2026-06-05
|
|
8
14
|
|
|
9
15
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.8.26-alpha.
|
|
3
|
+
"version": "0.8.26-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.8.26-alpha.2] - 2026-06-05
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Bumped package version for the Atomic 0.8.26-alpha.2 prerelease.
|
|
15
|
+
|
|
10
16
|
## [0.8.26-alpha.1] - 2026-06-05
|
|
11
17
|
|
|
12
18
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.8.26-alpha.
|
|
3
|
+
"version": "0.8.26-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
|
|
6
6
|
"contributors": [
|
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.8.26-alpha.2] - 2026-06-05
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed the `no-staged-files` acceptance runtime check and subagent worktree Git commands to ignore ambient Git repository environment variables, so subagents inspect the intended working tree instead of a parent hook or unrelated worktree.
|
|
10
|
+
- Suppressed intermediate model fallback failure notes and live foreground failure updates from successful subagent runs while preserving final failures and raw per-attempt diagnostics ([#1226](https://github.com/bastani-inc/atomic/issues/1226)).
|
|
11
|
+
|
|
5
12
|
## [0.8.26-alpha.1] - 2026-06-05
|
|
6
13
|
|
|
7
14
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.8.26-alpha.
|
|
3
|
+
"version": "0.8.26-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -650,6 +650,7 @@ async function runSingleStep(
|
|
|
650
650
|
const attemptedModels: string[] = [];
|
|
651
651
|
const modelAttempts: ModelAttempt[] = [];
|
|
652
652
|
const attemptNotes: string[] = [];
|
|
653
|
+
const pendingAttemptNotes: string[] = [];
|
|
653
654
|
const eventsPath = path.join(path.dirname(ctx.outputFile), "events.jsonl");
|
|
654
655
|
let finalResult: RunPiStreamingResult | undefined;
|
|
655
656
|
let finalFastMode: boolean | undefined;
|
|
@@ -768,9 +769,13 @@ async function runSingleStep(
|
|
|
768
769
|
finalFastMode = attemptFastMode;
|
|
769
770
|
finalOutputSnapshot = outputSnapshot;
|
|
770
771
|
finalResult = { ...run, exitCode: effectiveExitCode, model: candidate ?? run.model, error, structuredOutput } as RunPiStreamingResult & { structuredOutput?: unknown };
|
|
771
|
-
if (attempt.success
|
|
772
|
-
if (!isRetryableModelFailure(error)
|
|
773
|
-
|
|
772
|
+
if (attempt.success) break;
|
|
773
|
+
if (!completionGuardTriggered && isRetryableModelFailure(error) && index < candidates.length - 1) {
|
|
774
|
+
pendingAttemptNotes.push(formatModelAttemptNote(attempt, candidates[index + 1]));
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
attemptNotes.push(...pendingAttemptNotes);
|
|
778
|
+
break;
|
|
774
779
|
}
|
|
775
780
|
|
|
776
781
|
const rawOutput = finalResult?.finalOutput ?? "";
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
type AgentProgress,
|
|
18
18
|
type ArtifactPaths,
|
|
19
19
|
type ControlEvent,
|
|
20
|
+
type Details,
|
|
20
21
|
type ModelAttempt,
|
|
21
22
|
type RunSyncOptions,
|
|
22
23
|
type SingleResult,
|
|
@@ -139,6 +140,29 @@ function snapshotResult(result: SingleResult, progress: AgentProgress): SingleRe
|
|
|
139
140
|
};
|
|
140
141
|
}
|
|
141
142
|
|
|
143
|
+
type RunSyncUpdate = import("@earendil-works/pi-agent-core").AgentToolResult<Details>;
|
|
144
|
+
|
|
145
|
+
function extractUpdateText(update: RunSyncUpdate): string | undefined {
|
|
146
|
+
const text = update.content
|
|
147
|
+
.map((item) => item.type === "text" ? item.text : undefined)
|
|
148
|
+
.filter((item): item is string => Boolean(item?.trim()))
|
|
149
|
+
.join("\n");
|
|
150
|
+
return text || undefined;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export function shouldSuppressIntermediateRetryableFailureUpdate(update: RunSyncUpdate): boolean {
|
|
154
|
+
const result = update.details?.results?.[0];
|
|
155
|
+
if (!result) return false;
|
|
156
|
+
const progress = update.details?.progress?.[0];
|
|
157
|
+
const status = result.progress?.status ?? progress?.status;
|
|
158
|
+
if (status !== "failed") return false;
|
|
159
|
+
const failureText = result.error
|
|
160
|
+
?? result.progress?.error
|
|
161
|
+
?? progress?.error
|
|
162
|
+
?? extractUpdateText(update);
|
|
163
|
+
return isRetryableModelFailure(failureText);
|
|
164
|
+
}
|
|
165
|
+
|
|
142
166
|
async function runSingleAttempt(
|
|
143
167
|
runtimeCwd: string,
|
|
144
168
|
agent: AgentConfig,
|
|
@@ -875,6 +899,7 @@ export async function runSync(
|
|
|
875
899
|
const modelAttempts: ModelAttempt[] = [];
|
|
876
900
|
const aggregateUsage = emptyUsage();
|
|
877
901
|
const attemptNotes: string[] = [];
|
|
902
|
+
const pendingAttemptNotes: string[] = [];
|
|
878
903
|
let totalToolCount = 0;
|
|
879
904
|
let totalDurationMs = 0;
|
|
880
905
|
|
|
@@ -897,7 +922,18 @@ export async function runSync(
|
|
|
897
922
|
const candidate = modelsToTry[i];
|
|
898
923
|
if (candidate) attemptedModels.push(candidate);
|
|
899
924
|
const outputSnapshot = captureSingleOutputSnapshot(options.outputPath);
|
|
900
|
-
|
|
925
|
+
let attemptOptions = options;
|
|
926
|
+
if (i < modelsToTry.length - 1 && options.onUpdate) {
|
|
927
|
+
const forwardUpdate = options.onUpdate;
|
|
928
|
+
attemptOptions = {
|
|
929
|
+
...options,
|
|
930
|
+
onUpdate: (update) => {
|
|
931
|
+
if (shouldSuppressIntermediateRetryableFailureUpdate(update)) return;
|
|
932
|
+
forwardUpdate(update);
|
|
933
|
+
},
|
|
934
|
+
};
|
|
935
|
+
}
|
|
936
|
+
const result = await runSingleAttempt(runtimeCwd, agent, taskWithAcceptance, candidate, attemptOptions, {
|
|
901
937
|
sessionEnabled,
|
|
902
938
|
systemPrompt,
|
|
903
939
|
resolvedSkillNames: resolvedSkills.length > 0 ? resolvedSkills.map((skill) => skill.name) : undefined,
|
|
@@ -928,10 +964,12 @@ export async function runSync(
|
|
|
928
964
|
if (attemptSucceeded) {
|
|
929
965
|
break;
|
|
930
966
|
}
|
|
931
|
-
if (
|
|
932
|
-
|
|
967
|
+
if (isRetryableModelFailure(result.error) && i < modelsToTry.length - 1) {
|
|
968
|
+
pendingAttemptNotes.push(formatModelAttemptNote(attempt, modelsToTry[i + 1]));
|
|
969
|
+
continue;
|
|
933
970
|
}
|
|
934
|
-
attemptNotes.push(
|
|
971
|
+
attemptNotes.push(...pendingAttemptNotes);
|
|
972
|
+
break;
|
|
935
973
|
}
|
|
936
974
|
|
|
937
975
|
const result = lastResult ?? {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
+
import { createGitEnvironment } from "@bastani/atomic";
|
|
4
5
|
import type {
|
|
5
6
|
AcceptanceConfig,
|
|
6
7
|
AcceptanceEvidenceKind,
|
|
@@ -399,7 +400,7 @@ function reportEvidencePresent(report: AcceptanceReport, kind: AcceptanceEvidenc
|
|
|
399
400
|
}
|
|
400
401
|
|
|
401
402
|
function checkNoStagedFiles(cwd: string): AcceptanceRuntimeCheck {
|
|
402
|
-
const result = spawnSync("git", ["status", "--short"], { cwd, encoding: "utf-8" });
|
|
403
|
+
const result = spawnSync("git", ["status", "--short"], { cwd, env: createGitEnvironment(), encoding: "utf-8" });
|
|
403
404
|
if (result.status !== 0) {
|
|
404
405
|
return { id: "no-staged-files", status: "not-applicable", message: "git status unavailable; no staged-files check skipped" };
|
|
405
406
|
}
|
|
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { APP_NAME } from "@bastani/atomic";
|
|
5
|
+
import { APP_NAME, createGitEnvironment } from "@bastani/atomic";
|
|
6
6
|
|
|
7
7
|
export interface WorktreeSetup {
|
|
8
8
|
cwd: string;
|
|
@@ -82,7 +82,7 @@ interface RepoState {
|
|
|
82
82
|
const DEFAULT_WORKTREE_SETUP_HOOK_TIMEOUT_MS = 30000;
|
|
83
83
|
|
|
84
84
|
function runGit(cwd: string, args: string[]): GitResult {
|
|
85
|
-
const result = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf-8" });
|
|
85
|
+
const result = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf-8", env: createGitEnvironment() });
|
|
86
86
|
return {
|
|
87
87
|
stdout: result.stdout ?? "",
|
|
88
88
|
stderr: result.stderr ?? "",
|
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.8.26-alpha.2] - 2026-06-05
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Bumped package version for the Atomic 0.8.26-alpha.2 prerelease.
|
|
12
|
+
|
|
7
13
|
## [0.8.26-alpha.1] - 2026-06-05
|
|
8
14
|
|
|
9
15
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.8.26-alpha.
|
|
3
|
+
"version": "0.8.26-alpha.2",
|
|
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,18 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.8.26-alpha.2] - 2026-06-05
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Updated the `research-codebase` skill to capture a `breaking_changes_allowed` compatibility posture before research fanout, carry it through sub-agent prompts, and record it in research documents so downstream specs and workflows do not preserve legacy APIs by default when breaking changes are allowed ([#1225](https://github.com/bastani-inc/atomic/issues/1225)).
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- Fixed stage-local workflow HIL `input` and `editor` prompts losing draft text across Ctrl+D detach/reattach; drafts are kept live-only in memory and cleared when the prompt or run/stage exits ([#1179](https://github.com/bastani-inc/atomic/issues/1179)).
|
|
18
|
+
- Fixed workflow worktree Git commands to strip ambient repository-local Git environment variables before inspecting or creating targeted worktrees.
|
|
19
|
+
- Suppressed intermediate model fallback failure warnings from successful workflow stages while preserving final failures and raw per-attempt diagnostics ([#1226](https://github.com/bastani-inc/atomic/issues/1226)).
|
|
20
|
+
|
|
9
21
|
## [0.8.26-alpha.1] - 2026-06-05
|
|
10
22
|
|
|
11
23
|
### Fixed
|
|
@@ -24,14 +24,24 @@ The user's research question/request is: **$ARGUMENTS**
|
|
|
24
24
|
- **CRITICAL**: Read these files yourself in the main context before spawning any sub-tasks
|
|
25
25
|
- This ensures you have full context before decomposing the research
|
|
26
26
|
|
|
27
|
-
2. **
|
|
27
|
+
2. **Determine the compatibility posture:**
|
|
28
|
+
- Before decomposing the research request, identify whether this project must preserve backward compatibility for real downstream users.
|
|
29
|
+
- If the user explicitly allows breaking changes, public API changes, cleanup, or says there are no real users/downstream dependencies, set `breaking_changes_allowed: true`.
|
|
30
|
+
- If the user mentions production users, published APIs, downstream consumers, migration safety, or compatibility requirements, set `breaking_changes_allowed: false`.
|
|
31
|
+
- If the posture is not inferable from the request, ask the user once before continuing, using the available structured question tool when possible.
|
|
32
|
+
- Carry this posture into the research plan, every sub-agent prompt, the final research document frontmatter, and the `## Compatibility Context` section.
|
|
33
|
+
- When `breaking_changes_allowed: true`, document existing legacy behavior, compatibility shims, optional flags, and public APIs as current state, not as constraints future specs must preserve unless the user explicitly asks for preservation.
|
|
34
|
+
- When `breaking_changes_allowed: false`, document public APIs, compatibility-sensitive surfaces, downstream callers, migration constraints, and behavior that future work must preserve.
|
|
35
|
+
|
|
36
|
+
3. **Analyze and decompose the research question:**
|
|
28
37
|
- Break the research question down into composable research areas
|
|
29
38
|
- Take time to ultrathink about the underlying patterns, connections, and architectural implications the user might be seeking
|
|
30
39
|
- Identify specific components, patterns, or concepts to investigate
|
|
31
40
|
- Create a research plan using TodoWrite to track all subtasks
|
|
41
|
+
- Include the compatibility posture in the plan so later synthesis and spec creation inherit the same constraint.
|
|
32
42
|
- Consider which directories, files, or architectural patterns are relevant
|
|
33
43
|
|
|
34
|
-
|
|
44
|
+
4. **Spawn parallel sub-agent tasks:**
|
|
35
45
|
- Create multiple Task agents to research different aspects concurrently
|
|
36
46
|
- We now have specialized agents that know how to do specific research tasks:
|
|
37
47
|
|
|
@@ -67,8 +77,9 @@ The user's research question/request is: **$ARGUMENTS**
|
|
|
67
77
|
- Each agent knows its job - just tell it what you're looking for
|
|
68
78
|
- Don't write detailed prompts about HOW to search - the agents already know
|
|
69
79
|
- Remind agents they are documenting, not evaluating or improving
|
|
80
|
+
- Include `breaking_changes_allowed: true` or `breaking_changes_allowed: false` in each sub-agent prompt so compatibility-sensitive findings are documented with the right posture.
|
|
70
81
|
|
|
71
|
-
|
|
82
|
+
5. **Wait for all sub-agents to complete and synthesize:**
|
|
72
83
|
- IMPORTANT: Wait for ALL sub-agent tasks to complete before proceeding
|
|
73
84
|
- Compile all sub-agent results (both codebase and research findings)
|
|
74
85
|
- Prioritize live codebase findings as primary source of truth
|
|
@@ -79,7 +90,7 @@ The user's research question/request is: **$ARGUMENTS**
|
|
|
79
90
|
- Answer the user's research question with concrete evidence
|
|
80
91
|
- **If findings reveal the original question was misframed** (e.g., the system works differently than assumed, or the components don't exist where expected), flag this to the user before finalizing the document. This is valuable signal — don't bury it.
|
|
81
92
|
|
|
82
|
-
|
|
93
|
+
6. **Generate research document:**
|
|
83
94
|
- Follow the directory structure for research documents:
|
|
84
95
|
|
|
85
96
|
```
|
|
@@ -117,6 +128,8 @@ research/
|
|
|
117
128
|
status: complete
|
|
118
129
|
last_updated: !`date '+%Y-%m-%d'`
|
|
119
130
|
last_updated_by: [Researcher name]
|
|
131
|
+
breaking_changes_allowed: [true or false]
|
|
132
|
+
compatibility_context: "[Short explanation of downstream-user/API compatibility posture]"
|
|
120
133
|
---
|
|
121
134
|
|
|
122
135
|
# Research
|
|
@@ -125,6 +138,10 @@ research/
|
|
|
125
138
|
|
|
126
139
|
[Original user query]
|
|
127
140
|
|
|
141
|
+
## Compatibility Context
|
|
142
|
+
|
|
143
|
+
[State whether breaking changes are allowed. If true, note that existing compatibility shims, optional flags, legacy APIs, and public APIs are documented as current state rather than preservation constraints. If false, summarize compatibility-sensitive surfaces, downstream users/callers, migration constraints, and behavior future work must preserve.]
|
|
144
|
+
|
|
128
145
|
## Summary
|
|
129
146
|
|
|
130
147
|
[High-level documentation of what was found, answering the user's question by describing what exists]
|
|
@@ -167,19 +184,19 @@ research/
|
|
|
167
184
|
[Any areas that need further investigation]
|
|
168
185
|
```
|
|
169
186
|
|
|
170
|
-
|
|
187
|
+
7. **Add GitHub permalinks (if applicable):**
|
|
171
188
|
- Check if on main branch or if commit is pushed: `git branch --show-current` and `git status`
|
|
172
189
|
- If on main/master or pushed, generate GitHub permalinks:
|
|
173
190
|
- Get repo info: `gh repo view --json owner,name`
|
|
174
191
|
- Create permalinks: `https://github.com/{owner}/{repo}/blob/{commit}/{file}#L{line}`
|
|
175
192
|
- Replace local file references with permalinks in the document
|
|
176
193
|
|
|
177
|
-
|
|
194
|
+
8. **Present findings:**
|
|
178
195
|
- Present a concise summary of findings to the user
|
|
179
196
|
- Include key file references for easy navigation
|
|
180
197
|
- Ask if they have follow-up questions or need clarification
|
|
181
198
|
|
|
182
|
-
|
|
199
|
+
9. **Handle follow-up questions:**
|
|
183
200
|
|
|
184
201
|
- If the user has follow-up questions, append to the same research document
|
|
185
202
|
- Update the frontmatter fields `last_updated` and `last_updated_by` to reflect the update
|
|
@@ -207,10 +224,12 @@ research/
|
|
|
207
224
|
- **REMEMBER**: Document what IS, not what SHOULD BE
|
|
208
225
|
- **NO RECOMMENDATIONS**: Only describe the current state of the codebase
|
|
209
226
|
- **File reading**: Always read mentioned files FULLY (no limit/offset) before spawning sub-tasks
|
|
227
|
+
- **Compatibility posture**: Always determine `breaking_changes_allowed` before decomposing the question. This is a single project/research posture, not a request to add compatibility flags. Use it to document whether old APIs and shims are constraints for future work.
|
|
210
228
|
- **Critical ordering**: Follow the numbered steps exactly
|
|
211
229
|
- ALWAYS read mentioned files first before spawning sub-tasks (step 1)
|
|
212
|
-
- ALWAYS
|
|
213
|
-
- ALWAYS
|
|
230
|
+
- ALWAYS determine compatibility posture before decomposing the question (step 2)
|
|
231
|
+
- ALWAYS wait for all sub-agents to complete before synthesizing (step 5)
|
|
232
|
+
- ALWAYS gather metadata before writing the document (as part of step 6)
|
|
214
233
|
- NEVER write the research document with placeholder values
|
|
215
234
|
|
|
216
235
|
- **Frontmatter consistency**:
|
|
@@ -586,6 +586,7 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
|
|
|
586
586
|
let selectedModel: string | undefined;
|
|
587
587
|
const modelAttempts: WorkflowModelAttempt[] = [];
|
|
588
588
|
const modelWarnings: string[] = [];
|
|
589
|
+
const pendingFallbackWarnings: string[] = [];
|
|
589
590
|
const modelCatalog = opts.models === undefined
|
|
590
591
|
? undefined
|
|
591
592
|
: {
|
|
@@ -796,15 +797,19 @@ export function createStageContext(opts: StageRunnerOpts): InternalStageContext
|
|
|
796
797
|
try {
|
|
797
798
|
await promptWithPauseResume(activeSession, text, sdkOptions);
|
|
798
799
|
modelAttempts.push({ model: candidate.id, success: true, ...modelAttemptReasoning(candidate) });
|
|
800
|
+
pendingFallbackWarnings.length = 0;
|
|
799
801
|
return;
|
|
800
802
|
} catch (err) {
|
|
801
803
|
const message = errorMessage(err);
|
|
802
804
|
modelAttempts.push({ model: candidate.id, success: false, ...modelAttemptReasoning(candidate), error: message });
|
|
803
805
|
if (signal?.aborted || !isRetryableModelFailure(message) || index === candidates.length - 1) {
|
|
806
|
+
modelWarnings.push(...pendingFallbackWarnings);
|
|
807
|
+
pendingFallbackWarnings.length = 0;
|
|
808
|
+
notifyModelFallbackMetaChange();
|
|
804
809
|
throw err;
|
|
805
810
|
}
|
|
806
811
|
const nextCandidate = candidates[index + 1]!;
|
|
807
|
-
|
|
812
|
+
pendingFallbackWarnings.push(`[fallback] ${candidateLabel(candidate)} failed: ${message}. Retrying with ${candidateLabel(nextCandidate)}.`);
|
|
808
813
|
await disposeCurrentSession();
|
|
809
814
|
index += 1;
|
|
810
815
|
}
|
|
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import * as fs from "node:fs";
|
|
3
3
|
import * as os from "node:os";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { APP_NAME } from "@bastani/atomic";
|
|
5
|
+
import { APP_NAME, createGitEnvironment } from "@bastani/atomic";
|
|
6
6
|
|
|
7
7
|
export interface WorktreeSetup {
|
|
8
8
|
cwd: string;
|
|
@@ -110,7 +110,7 @@ function runGit(cwd: string, args: string[]): GitResult {
|
|
|
110
110
|
], {
|
|
111
111
|
cwd,
|
|
112
112
|
encoding: "utf-8",
|
|
113
|
-
env: {
|
|
113
|
+
env: createGitEnvironment({ GIT_OPTIONAL_LOCKS: "0" }),
|
|
114
114
|
timeout: 5000,
|
|
115
115
|
});
|
|
116
116
|
return {
|
|
@@ -151,6 +151,16 @@ export interface Store {
|
|
|
151
151
|
): boolean;
|
|
152
152
|
/** Wait for a stage/node-scoped HIL prompt to resolve. */
|
|
153
153
|
awaitStagePendingPrompt(runId: string, stageId: string, promptId: string): Promise<unknown>;
|
|
154
|
+
/**
|
|
155
|
+
* Record a live-only draft for an active stage-local input/editor prompt.
|
|
156
|
+
* Draft text may contain secrets and must never be copied into snapshots,
|
|
157
|
+
* status output, logs, notifications, or persisted metadata.
|
|
158
|
+
*/
|
|
159
|
+
recordStagePromptDraft(runId: string, stageId: string, promptId: string, text: string): boolean;
|
|
160
|
+
/** Return a live-only draft for an active stage-local input/editor prompt, if present. */
|
|
161
|
+
getStagePromptDraft(runId: string, stageId: string, promptId: string): string | undefined;
|
|
162
|
+
/** Clear a live-only draft for a stage-local prompt. */
|
|
163
|
+
clearStagePromptDraft(runId: string, stageId: string, promptId: string): boolean;
|
|
154
164
|
/**
|
|
155
165
|
* Return the live-only prompt answer record for a completed prompt stage, if
|
|
156
166
|
* still available. The returned value may contain secrets and must never be
|
|
@@ -239,6 +249,7 @@ export function createStore(): Store {
|
|
|
239
249
|
const _notices: WorkflowNotice[] = [];
|
|
240
250
|
const _listeners: Set<(snap: StoreSnapshot) => void> = new Set();
|
|
241
251
|
const _stagePromptAnswers = new Map<string, PromptAnswerRecord>();
|
|
252
|
+
const _stagePromptDrafts = new Map<string, string>();
|
|
242
253
|
let _version = 0;
|
|
243
254
|
|
|
244
255
|
/**
|
|
@@ -285,16 +296,36 @@ export function createStore(): Store {
|
|
|
285
296
|
return JSON.stringify([runId, stageId]);
|
|
286
297
|
}
|
|
287
298
|
|
|
288
|
-
function
|
|
299
|
+
function stagePromptDraftKey(runId: string, stageId: string, promptId: string): string {
|
|
300
|
+
return JSON.stringify([runId, stageId, promptId]);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function stageHasActiveTextPrompt(
|
|
304
|
+
runId: string,
|
|
305
|
+
stageId: string,
|
|
306
|
+
promptId: string,
|
|
307
|
+
): { prompt: PendingPrompt } | undefined {
|
|
308
|
+
const run = findRun(runId);
|
|
309
|
+
if (!run || TERMINAL_STATUSES.has(run.status)) return undefined;
|
|
310
|
+
const stage = findStage(run, stageId);
|
|
311
|
+
if (!stage || isTerminalStageStatus(stage.status)) return undefined;
|
|
312
|
+
const prompt = stage.pendingPrompt;
|
|
313
|
+
if (!prompt || prompt.id !== promptId) return undefined;
|
|
314
|
+
if (prompt.kind !== "input" && prompt.kind !== "editor") return undefined;
|
|
315
|
+
return { prompt };
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function rejectStagePrompt(runId: string, stage: StageSnapshot, reason: string): void {
|
|
289
319
|
const prompt = stage.pendingPrompt;
|
|
290
320
|
if (!prompt) return;
|
|
291
321
|
stage.pendingPrompt = undefined;
|
|
322
|
+
_stagePromptDrafts.delete(stagePromptDraftKey(runId, stage.id, prompt.id));
|
|
292
323
|
rejectPrompt(prompt.id, reason);
|
|
293
324
|
}
|
|
294
325
|
|
|
295
|
-
function rejectAllStagePrompts(run: RunSnapshot, reason: string): void {
|
|
326
|
+
function rejectAllStagePrompts(runId: string, run: RunSnapshot, reason: string): void {
|
|
296
327
|
for (const stage of run.stages) {
|
|
297
|
-
rejectStagePrompt(stage, reason);
|
|
328
|
+
rejectStagePrompt(runId, stage, reason);
|
|
298
329
|
}
|
|
299
330
|
}
|
|
300
331
|
|
|
@@ -427,7 +458,7 @@ export function createStore(): Store {
|
|
|
427
458
|
if (stage.workflowChild !== undefined) existing.workflowChild = structuredClone(stage.workflowChild);
|
|
428
459
|
delete existing.awaitingInputSince;
|
|
429
460
|
delete existing.inputRequest;
|
|
430
|
-
rejectStagePrompt(existing, `atomic-workflows: stage ${stage.id} ended before prompt resolved`);
|
|
461
|
+
rejectStagePrompt(runId, existing, `atomic-workflows: stage ${stage.id} ended before prompt resolved`);
|
|
431
462
|
_version++;
|
|
432
463
|
notify();
|
|
433
464
|
},
|
|
@@ -474,7 +505,7 @@ export function createStore(): Store {
|
|
|
474
505
|
run.pendingPrompt = undefined;
|
|
475
506
|
rejectPrompt(pending.id, `atomic-workflows: run ${runId} ended before prompt resolved`);
|
|
476
507
|
}
|
|
477
|
-
rejectAllStagePrompts(run, `atomic-workflows: run ${runId} ended before prompt resolved`);
|
|
508
|
+
rejectAllStagePrompts(runId, run, `atomic-workflows: run ${runId} ended before prompt resolved`);
|
|
478
509
|
_version++;
|
|
479
510
|
notify();
|
|
480
511
|
return true;
|
|
@@ -488,7 +519,7 @@ export function createStore(): Store {
|
|
|
488
519
|
if (pending) {
|
|
489
520
|
rejectPrompt(pending.id, `atomic-workflows: run ${runId} was removed before prompt resolved`);
|
|
490
521
|
}
|
|
491
|
-
rejectAllStagePrompts(run, `atomic-workflows: run ${runId} was removed before prompt resolved`);
|
|
522
|
+
rejectAllStagePrompts(runId, run, `atomic-workflows: run ${runId} was removed before prompt resolved`);
|
|
492
523
|
for (const stage of run.stages) {
|
|
493
524
|
_stagePromptAnswers.delete(stagePromptAnswerKey(runId, stage.id));
|
|
494
525
|
}
|
|
@@ -599,6 +630,7 @@ export function createStore(): Store {
|
|
|
599
630
|
if (!stage) return false;
|
|
600
631
|
const pending = stage.pendingPrompt;
|
|
601
632
|
if (!pending || pending.id !== promptId) return false;
|
|
633
|
+
_stagePromptDrafts.delete(stagePromptDraftKey(runId, stageId, promptId));
|
|
602
634
|
if (options.recordAnswer !== false) {
|
|
603
635
|
_stagePromptAnswers.set(stagePromptAnswerKey(runId, stageId), {
|
|
604
636
|
runId,
|
|
@@ -654,6 +686,21 @@ export function createStore(): Store {
|
|
|
654
686
|
});
|
|
655
687
|
},
|
|
656
688
|
|
|
689
|
+
recordStagePromptDraft(runId: string, stageId: string, promptId: string, text: string): boolean {
|
|
690
|
+
if (stageHasActiveTextPrompt(runId, stageId, promptId) === undefined) return false;
|
|
691
|
+
_stagePromptDrafts.set(stagePromptDraftKey(runId, stageId, promptId), text);
|
|
692
|
+
return true;
|
|
693
|
+
},
|
|
694
|
+
|
|
695
|
+
getStagePromptDraft(runId: string, stageId: string, promptId: string): string | undefined {
|
|
696
|
+
if (stageHasActiveTextPrompt(runId, stageId, promptId) === undefined) return undefined;
|
|
697
|
+
return _stagePromptDrafts.get(stagePromptDraftKey(runId, stageId, promptId));
|
|
698
|
+
},
|
|
699
|
+
|
|
700
|
+
clearStagePromptDraft(runId: string, stageId: string, promptId: string): boolean {
|
|
701
|
+
return _stagePromptDrafts.delete(stagePromptDraftKey(runId, stageId, promptId));
|
|
702
|
+
},
|
|
703
|
+
|
|
657
704
|
getStagePromptAnswer(runId: string, stageId: string): PromptAnswerRecord | undefined {
|
|
658
705
|
return _stagePromptAnswers.get(stagePromptAnswerKey(runId, stageId));
|
|
659
706
|
},
|
|
@@ -896,7 +943,13 @@ export function createStore(): Store {
|
|
|
896
943
|
},
|
|
897
944
|
|
|
898
945
|
clear(): void {
|
|
899
|
-
if (
|
|
946
|
+
if (
|
|
947
|
+
_runs.length === 0 &&
|
|
948
|
+
_notices.length === 0 &&
|
|
949
|
+
_resolvers.size === 0 &&
|
|
950
|
+
_stagePromptAnswers.size === 0 &&
|
|
951
|
+
_stagePromptDrafts.size === 0
|
|
952
|
+
) return;
|
|
900
953
|
_runs.length = 0;
|
|
901
954
|
_notices.length = 0;
|
|
902
955
|
// Reject any outstanding HIL waiters so background promises terminate
|
|
@@ -907,6 +960,7 @@ export function createStore(): Store {
|
|
|
907
960
|
}
|
|
908
961
|
_resolvers.clear();
|
|
909
962
|
_stagePromptAnswers.clear();
|
|
963
|
+
_stagePromptDrafts.clear();
|
|
910
964
|
_version++;
|
|
911
965
|
notify();
|
|
912
966
|
},
|
|
@@ -521,6 +521,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
521
521
|
}
|
|
522
522
|
if (!this.promptState || this.promptState.prompt.id !== prompt.id) {
|
|
523
523
|
this.promptState = createPromptCardState(prompt);
|
|
524
|
+
this._seedPromptTextState(prompt);
|
|
524
525
|
this._resetPromptEditor(prompt);
|
|
525
526
|
this._resetPromptScroll();
|
|
526
527
|
return true;
|
|
@@ -533,19 +534,49 @@ export class StageChatView implements Component, Focusable {
|
|
|
533
534
|
this.promptMaxScroll = 0;
|
|
534
535
|
}
|
|
535
536
|
|
|
537
|
+
private _promptSeedText(prompt: PendingPrompt): string {
|
|
538
|
+
const draft = this.store.getStagePromptDraft(this.runId, this.stageId, prompt.id);
|
|
539
|
+
if (draft !== undefined) return draft;
|
|
540
|
+
return typeof prompt.initial === "string" ? prompt.initial : "";
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
private _seedPromptTextState(prompt: PendingPrompt): void {
|
|
544
|
+
if (prompt.kind !== "input" && prompt.kind !== "editor") return;
|
|
545
|
+
if (!this.promptState || this.promptState.prompt.id !== prompt.id) return;
|
|
546
|
+
const seed = this._promptSeedText(prompt);
|
|
547
|
+
this.promptState.rawText = seed;
|
|
548
|
+
this.promptState.caret = seed.length;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
private _recordPromptDraft(promptId: string, text: string): void {
|
|
552
|
+
this.store.recordStagePromptDraft(this.runId, this.stageId, promptId, text);
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
private _recordCurrentPromptDraft(): void {
|
|
556
|
+
const state = this.promptState;
|
|
557
|
+
if (!state) return;
|
|
558
|
+
const prompt = state.prompt;
|
|
559
|
+
if (prompt.kind !== "input" && prompt.kind !== "editor") return;
|
|
560
|
+
const text = this.promptEditor && this.promptEditorPromptId === prompt.id
|
|
561
|
+
? this.promptEditor.getText()
|
|
562
|
+
: state.rawText;
|
|
563
|
+
this._recordPromptDraft(prompt.id, text);
|
|
564
|
+
}
|
|
565
|
+
|
|
536
566
|
private _resetPromptEditor(prompt: PendingPrompt): void {
|
|
537
567
|
this._disposePromptEditor();
|
|
538
568
|
if ((prompt.kind !== "input" && prompt.kind !== "editor") || !this.piTui) return;
|
|
539
569
|
const editor = this.piEditorFactory
|
|
540
570
|
? this.piEditorFactory(this.piTui, editorThemeFromGraphTheme(this.theme), this.piKeybindings)
|
|
541
571
|
: new Editor(this.piTui, editorThemeFromGraphTheme(this.theme), { paddingX: 0 });
|
|
542
|
-
editor.setText(
|
|
572
|
+
editor.setText(this.promptState?.prompt.id === prompt.id ? this.promptState.rawText : this._promptSeedText(prompt));
|
|
543
573
|
setEditorPlaceholder(editor, "Type your response…");
|
|
544
574
|
setEditorBorderColor(editor, (text) => hexToAnsi(this.theme.accent) + text + RESET);
|
|
545
575
|
editor.onChange = (text: string) => {
|
|
546
576
|
if (this.promptState?.prompt.id !== prompt.id) return;
|
|
547
577
|
this.promptState.rawText = text;
|
|
548
578
|
this.promptState.caret = text.length;
|
|
579
|
+
this._recordPromptDraft(prompt.id, text);
|
|
549
580
|
this.requestRender?.();
|
|
550
581
|
};
|
|
551
582
|
editor.onSubmit = (text: string) => {
|
|
@@ -1248,11 +1279,14 @@ export class StageChatView implements Component, Focusable {
|
|
|
1248
1279
|
return;
|
|
1249
1280
|
}
|
|
1250
1281
|
const action = handlePromptCardInput(data, state, this._promptKeybindings());
|
|
1282
|
+
const prompt = state.prompt;
|
|
1283
|
+
if (prompt.kind === "input" || prompt.kind === "editor") {
|
|
1284
|
+
this._recordPromptDraft(prompt.id, state.rawText);
|
|
1285
|
+
}
|
|
1251
1286
|
if (action.kind === "noop") {
|
|
1252
1287
|
this.requestRender?.();
|
|
1253
1288
|
return;
|
|
1254
1289
|
}
|
|
1255
|
-
const prompt = state.prompt;
|
|
1256
1290
|
const response = action.kind === "submit"
|
|
1257
1291
|
? action.response
|
|
1258
1292
|
: defaultResponseFor(prompt);
|
|
@@ -1306,6 +1340,7 @@ export class StageChatView implements Component, Focusable {
|
|
|
1306
1340
|
const readOnlyPromptArchive = readOnlyArchive && stage?.promptFootprint !== undefined;
|
|
1307
1341
|
if (matchesKey(data, Key.ctrl("d"))) {
|
|
1308
1342
|
if (!this.promptState && this.chatHost.hasInputText()) return this.chatHost.handleInput(data);
|
|
1343
|
+
this._recordCurrentPromptDraft();
|
|
1309
1344
|
this.onDetach();
|
|
1310
1345
|
return true;
|
|
1311
1346
|
}
|