@launch11/srgical 0.0.1
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/LICENSE +21 -0
- package/README.md +164 -0
- package/dist/commands/doctor.js +68 -0
- package/dist/commands/init.js +26 -0
- package/dist/commands/run-next.js +54 -0
- package/dist/commands/studio.js +7 -0
- package/dist/core/agent.js +164 -0
- package/dist/core/claude.js +271 -0
- package/dist/core/codex.js +257 -0
- package/dist/core/execution-controls.js +87 -0
- package/dist/core/execution-state.js +87 -0
- package/dist/core/local-pack.js +125 -0
- package/dist/core/planning-epochs.js +116 -0
- package/dist/core/planning-pack-state.js +121 -0
- package/dist/core/prompts.js +230 -0
- package/dist/core/studio-session.js +99 -0
- package/dist/core/templates.js +189 -0
- package/dist/core/workspace.js +67 -0
- package/dist/index.js +60 -0
- package/dist/ui/studio.js +704 -0
- package/docs/adr/0001-tech-stack.md +27 -0
- package/docs/distribution.md +129 -0
- package/docs/product-foundation.md +50 -0
- package/docs/testing-strategy.md +96 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LaunchEleven
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# srgical
|
|
2
|
+
|
|
3
|
+
`srgical` is a local-first orchestration CLI for the workflow you have already been using manually:
|
|
4
|
+
|
|
5
|
+
1. talk to an AI until the plan is sharp,
|
|
6
|
+
2. write a four-file planning pack into the repo,
|
|
7
|
+
3. repeatedly execute the next eligible step,
|
|
8
|
+
4. force validation and handoff updates every time.
|
|
9
|
+
|
|
10
|
+
The current launch slice supports both local `codex` and local `claude` CLI installs through the same `.srgical/`
|
|
11
|
+
workflow. `srgical` detects which supported tools are actually installed, keeps the planning pack agent-neutral, and
|
|
12
|
+
lets you choose the active agent for the current workspace session.
|
|
13
|
+
|
|
14
|
+
## Why This Exists
|
|
15
|
+
|
|
16
|
+
The reference system in `G:\code\Launch11Projects\Writr\migrations-part-5` is strong because it does not just create a
|
|
17
|
+
plan. It creates momentum:
|
|
18
|
+
|
|
19
|
+
- a stable architecture file,
|
|
20
|
+
- a current-context handoff log,
|
|
21
|
+
- a step-by-step tracker,
|
|
22
|
+
- and a repeatable next-agent prompt that keeps execution disciplined.
|
|
23
|
+
|
|
24
|
+
`srgical` turns that from a repeated copy-paste ritual into a product.
|
|
25
|
+
|
|
26
|
+
## Current Slice
|
|
27
|
+
|
|
28
|
+
This repo currently ships the foundation for:
|
|
29
|
+
|
|
30
|
+
- `srgical doctor`
|
|
31
|
+
Reports whether the current workspace has a planning pack, which supported agent is active, and which supported
|
|
32
|
+
agents are available locally.
|
|
33
|
+
- `srgical init`
|
|
34
|
+
Creates a local `.srgical/` planning pack from built-in templates.
|
|
35
|
+
- `srgical studio`
|
|
36
|
+
Opens a full-screen planning studio where you can plan against the repo, inspect supported tools with `/agents`,
|
|
37
|
+
switch the session agent with `/agent <id>`, and explicitly trigger pack writes or execution.
|
|
38
|
+
- `srgical run-next`
|
|
39
|
+
Replays the generated next-agent prompt through the active agent, with `--dry-run` for safe preview and
|
|
40
|
+
`--agent <id>` for a one-run override that does not change the stored workspace choice.
|
|
41
|
+
|
|
42
|
+
## Supported Agents
|
|
43
|
+
|
|
44
|
+
- `codex`
|
|
45
|
+
Supported in the current launch slice for planning, pack writing, and `run-next` execution.
|
|
46
|
+
- `claude`
|
|
47
|
+
Supported through the same adapter seam for planning, pack writing, and execution when the local Claude Code CLI is
|
|
48
|
+
installed and available on `PATH`.
|
|
49
|
+
|
|
50
|
+
If only one supported agent is installed, `srgical` can auto-select it for the workspace session. If both are
|
|
51
|
+
installed, you can keep the stored choice in the studio and still override a single execution with
|
|
52
|
+
`srgical run-next --agent <id>`.
|
|
53
|
+
|
|
54
|
+
## Design Direction
|
|
55
|
+
|
|
56
|
+
The product should feel closer to a creative control room than a grey enterprise shell:
|
|
57
|
+
|
|
58
|
+
- dark graphite base
|
|
59
|
+
- hot coral and amber accents
|
|
60
|
+
- crisp cyan status treatment
|
|
61
|
+
- large, cinematic panel framing
|
|
62
|
+
- transcript-first layout instead of command soup
|
|
63
|
+
|
|
64
|
+
The first TUI pass already leans in that direction, and we can keep pushing it.
|
|
65
|
+
|
|
66
|
+
## Distribution
|
|
67
|
+
|
|
68
|
+
The first production channels are GitHub Packages, the public npm registry, and GitHub Releases for downloadable
|
|
69
|
+
release assets. Version intent stays in git, and GitHub Actions bumps `package.json`, writes `CHANGELOG.md`, pushes
|
|
70
|
+
that release commit back to `main`, publishes the GitHub-scoped package, publishes the npm org package, and creates a
|
|
71
|
+
GitHub Release with the built tarballs attached.
|
|
72
|
+
|
|
73
|
+
For a local production-style packaging check:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
npm run release:pack
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The release bundle lands under `.artifacts/release/`. The broader distribution path, including standalone binaries and
|
|
80
|
+
wrapper package-manager installs, is documented in `docs/distribution.md`.
|
|
81
|
+
|
|
82
|
+
For release work, add a changeset in your feature branch:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
npm run changeset
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
When that branch reaches `main`, the release workflow versions the package, publishes `@launcheleven/srgical` to
|
|
89
|
+
GitHub Packages, publishes `@launch11/srgical` to npm, and opens a matching GitHub Release entry with the packaged
|
|
90
|
+
artifacts.
|
|
91
|
+
|
|
92
|
+
## Install Prerequisites
|
|
93
|
+
|
|
94
|
+
Install `srgical`, then install at least one supported local agent CLI separately.
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm install
|
|
98
|
+
npm run build
|
|
99
|
+
node dist/index.js doctor
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
`doctor` is the source of truth for local availability. If an agent CLI is missing, `srgical` reports it as missing
|
|
103
|
+
instead of pretending it can run that path anyway.
|
|
104
|
+
|
|
105
|
+
The package publishes in two install channels:
|
|
106
|
+
|
|
107
|
+
- GitHub Packages: `@launcheleven/srgical`
|
|
108
|
+
- npm public registry: `@launch11/srgical`
|
|
109
|
+
|
|
110
|
+
For GitHub Packages installs, consumers need an `.npmrc` entry for `@launcheleven` plus a token before running:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
npm install -g @launcheleven/srgical
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
For npm installs, consumers can use:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
npm install -g @launch11/srgical
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## Getting Started
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
npm install
|
|
126
|
+
npm run build
|
|
127
|
+
node dist/index.js doctor
|
|
128
|
+
node dist/index.js studio
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
During development:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
npm run dev -- studio
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Typical flow once a workspace has a pack:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
node dist/index.js doctor
|
|
141
|
+
node dist/index.js run-next --dry-run
|
|
142
|
+
node dist/index.js run-next
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
To override the active workspace agent for one execution only:
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
node dist/index.js run-next --agent codex
|
|
149
|
+
node dist/index.js run-next --agent claude
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Current Claude Caveat
|
|
153
|
+
|
|
154
|
+
Claude support is real, but it is not treated as interchangeable with Codex. The current non-interactive Claude path
|
|
155
|
+
uses `plan` mode for planner replies and `acceptEdits` with allowlisted local tools for pack-writing and execution.
|
|
156
|
+
|
|
157
|
+
If the Claude CLI is not installed locally, `doctor`, the studio, and `run-next --agent claude` all report that
|
|
158
|
+
honestly instead of falling back to a fake Claude path.
|
|
159
|
+
|
|
160
|
+
## Planned Next Steps
|
|
161
|
+
|
|
162
|
+
- deepen the studio experience without weakening the terminal-first workflow
|
|
163
|
+
- keep dual-agent docs and validation honest as Claude runtime behavior gets more live coverage
|
|
164
|
+
- expand release outputs from npm tarballs into standalone binaries and wrapper package-manager installers
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runDoctorCommand = runDoctorCommand;
|
|
7
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
8
|
+
const agent_1 = require("../core/agent");
|
|
9
|
+
const planning_pack_state_1 = require("../core/planning-pack-state");
|
|
10
|
+
const workspace_1 = require("../core/workspace");
|
|
11
|
+
async function runDoctorCommand(workspaceArg) {
|
|
12
|
+
const workspace = (0, workspace_1.resolveWorkspace)(workspaceArg);
|
|
13
|
+
const [resolvedAgent, packState, gitRepo] = await Promise.all([
|
|
14
|
+
(0, agent_1.resolvePrimaryAgent)(workspace),
|
|
15
|
+
(0, planning_pack_state_1.readPlanningPackState)(workspace),
|
|
16
|
+
(0, workspace_1.isGitRepo)(workspace)
|
|
17
|
+
]);
|
|
18
|
+
const { status: activeAgent, statuses } = resolvedAgent;
|
|
19
|
+
const lines = [
|
|
20
|
+
`Workspace: ${workspace}`,
|
|
21
|
+
`Git repo: ${gitRepo ? "yes" : "no"}`,
|
|
22
|
+
`Planning pack: ${packState.packPresent ? "present" : "missing"}`,
|
|
23
|
+
`Active agent: ${activeAgent.label} (${activeAgent.id}) - ${formatAgentAvailability(activeAgent)}`,
|
|
24
|
+
"",
|
|
25
|
+
...renderSupportedAgentLines(statuses, activeAgent.id),
|
|
26
|
+
];
|
|
27
|
+
if (packState.packPresent) {
|
|
28
|
+
lines.push("", ...renderNextStepLines(packState.nextStepSummary, packState.currentPosition.nextRecommended));
|
|
29
|
+
}
|
|
30
|
+
lines.push("", packState.packPresent
|
|
31
|
+
? packState.nextStepSummary || packState.currentPosition.nextRecommended
|
|
32
|
+
? "Next move: run `srgical studio` to refine the plan or `srgical run-next` to execute the next step."
|
|
33
|
+
: "Next move: run `srgical studio` to queue more work or update the tracker with a new recommended step."
|
|
34
|
+
: "Next move: run `srgical init` for a local scaffold or `srgical studio` to plan with the primary agent first.");
|
|
35
|
+
node_process_1.default.stdout.write(`${lines.join("\n")}\n`);
|
|
36
|
+
}
|
|
37
|
+
function renderSupportedAgentLines(statuses, activeAgentId) {
|
|
38
|
+
const lines = ["Supported agents:"];
|
|
39
|
+
for (const status of statuses) {
|
|
40
|
+
lines.push(`- ${status.label} (${status.id})${status.id === activeAgentId ? " [active]" : ""}: ${formatAgentAvailability(status)} via ${status.command}`);
|
|
41
|
+
}
|
|
42
|
+
return lines;
|
|
43
|
+
}
|
|
44
|
+
function formatAgentAvailability(status) {
|
|
45
|
+
return status.available
|
|
46
|
+
? `available (${status.version ?? "version unknown"})`
|
|
47
|
+
: `missing (${status.error ?? "unknown error"})`;
|
|
48
|
+
}
|
|
49
|
+
function renderNextStepLines(nextStepSummary, nextRecommended) {
|
|
50
|
+
if (!nextStepSummary) {
|
|
51
|
+
return [
|
|
52
|
+
"Next Step: unavailable",
|
|
53
|
+
nextRecommended
|
|
54
|
+
? `Tracker points to \`${nextRecommended}\`, but its table row could not be summarized.`
|
|
55
|
+
: "Tracker does not currently expose a next recommended step."
|
|
56
|
+
];
|
|
57
|
+
}
|
|
58
|
+
const lines = [
|
|
59
|
+
`Next Step: ${nextStepSummary.id}${nextStepSummary.phase ? ` (${nextStepSummary.phase})` : ""}`,
|
|
60
|
+
` Scope: ${nextStepSummary.scope || "unknown"}`,
|
|
61
|
+
` Acceptance: ${nextStepSummary.acceptance || "unknown"}`,
|
|
62
|
+
` Status: ${nextStepSummary.status || "unknown"}`
|
|
63
|
+
];
|
|
64
|
+
if (nextStepSummary.notes) {
|
|
65
|
+
lines.push(` Notes: ${nextStepSummary.notes}`);
|
|
66
|
+
}
|
|
67
|
+
return lines;
|
|
68
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runInitCommand = runInitCommand;
|
|
7
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
8
|
+
const templates_1 = require("../core/templates");
|
|
9
|
+
const workspace_1 = require("../core/workspace");
|
|
10
|
+
async function runInitCommand(workspaceArg, force = false) {
|
|
11
|
+
const workspace = (0, workspace_1.resolveWorkspace)(workspaceArg);
|
|
12
|
+
const exists = await (0, workspace_1.planningPackExists)(workspace);
|
|
13
|
+
if (exists && !force) {
|
|
14
|
+
throw new Error("A .srgical planning pack already exists. Re-run with --force to overwrite it.");
|
|
15
|
+
}
|
|
16
|
+
const paths = await (0, workspace_1.ensurePlanningDir)(workspace);
|
|
17
|
+
const templates = (0, templates_1.getInitialTemplates)(paths);
|
|
18
|
+
await Promise.all(Object.entries(templates).map(([filePath, content]) => (0, workspace_1.writeText)(filePath, content)));
|
|
19
|
+
node_process_1.default.stdout.write([
|
|
20
|
+
`Created planning pack in ${paths.dir}`,
|
|
21
|
+
`- ${paths.plan}`,
|
|
22
|
+
`- ${paths.context}`,
|
|
23
|
+
`- ${paths.tracker}`,
|
|
24
|
+
`- ${paths.nextPrompt}`
|
|
25
|
+
].join("\n") + "\n");
|
|
26
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.runRunNextCommand = runRunNextCommand;
|
|
7
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
8
|
+
const agent_1 = require("../core/agent");
|
|
9
|
+
const execution_state_1 = require("../core/execution-state");
|
|
10
|
+
const execution_controls_1 = require("../core/execution-controls");
|
|
11
|
+
const planning_pack_state_1 = require("../core/planning-pack-state");
|
|
12
|
+
const workspace_1 = require("../core/workspace");
|
|
13
|
+
async function runRunNextCommand(workspaceArg, options = {}) {
|
|
14
|
+
const workspace = (0, workspace_1.resolveWorkspace)(workspaceArg);
|
|
15
|
+
const packState = await (0, planning_pack_state_1.readPlanningPackState)(workspace);
|
|
16
|
+
if (!packState.packPresent) {
|
|
17
|
+
throw new Error("No .srgical planning pack found. Run `srgical init` or `srgical studio` first.");
|
|
18
|
+
}
|
|
19
|
+
if (!options.dryRun && !(0, execution_controls_1.hasQueuedNextStep)(packState.currentPosition.nextRecommended)) {
|
|
20
|
+
throw new Error((0, execution_controls_1.formatNoQueuedNextStepMessage)("run-next"));
|
|
21
|
+
}
|
|
22
|
+
await (0, agent_1.resolveExecutionAgent)(workspace, options.agent);
|
|
23
|
+
const paths = (0, workspace_1.getPlanningPackPaths)(workspace);
|
|
24
|
+
const prompt = await (0, workspace_1.readText)(paths.nextPrompt);
|
|
25
|
+
const previewLines = options.dryRun
|
|
26
|
+
? (0, execution_controls_1.renderDryRunPreview)(prompt, packState.nextStepSummary, packState.currentPosition.nextRecommended)
|
|
27
|
+
: (0, execution_controls_1.renderExecutionStepLines)(packState.nextStepSummary, packState.currentPosition.nextRecommended);
|
|
28
|
+
for (const line of previewLines) {
|
|
29
|
+
node_process_1.default.stdout.write(`${line}\n`);
|
|
30
|
+
}
|
|
31
|
+
if (options.dryRun) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
node_process_1.default.stdout.write("\n");
|
|
35
|
+
node_process_1.default.stdout.write(`Running the current next-agent prompt through ${(0, agent_1.getPrimaryAgentAdapter)().label}...\n`);
|
|
36
|
+
try {
|
|
37
|
+
const result = await (0, agent_1.runNextPrompt)(workspace, prompt, {
|
|
38
|
+
agentId: options.agent
|
|
39
|
+
});
|
|
40
|
+
await (0, execution_state_1.saveExecutionState)(workspace, "success", "run-next", result);
|
|
41
|
+
await (0, execution_state_1.appendExecutionLog)(workspace, "success", "run-next", result, {
|
|
42
|
+
stepLabel: (0, execution_controls_1.formatStepLabel)(packState.nextStepSummary, packState.currentPosition.nextRecommended)
|
|
43
|
+
});
|
|
44
|
+
node_process_1.default.stdout.write(`${result}\n`);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
48
|
+
await (0, execution_state_1.saveExecutionState)(workspace, "failure", "run-next", message);
|
|
49
|
+
await (0, execution_state_1.appendExecutionLog)(workspace, "failure", "run-next", message, {
|
|
50
|
+
stepLabel: (0, execution_controls_1.formatStepLabel)(packState.nextStepSummary, packState.currentPosition.nextRecommended)
|
|
51
|
+
});
|
|
52
|
+
throw new Error((0, execution_controls_1.formatExecutionFailureMessage)(message, packState.nextStepSummary, packState.currentPosition.nextRecommended, "run-next"));
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runStudioCommand = runStudioCommand;
|
|
4
|
+
const studio_1 = require("../ui/studio");
|
|
5
|
+
async function runStudioCommand(workspaceArg) {
|
|
6
|
+
await (0, studio_1.launchStudio)({ workspace: workspaceArg });
|
|
7
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getSupportedAgentAdapters = getSupportedAgentAdapters;
|
|
4
|
+
exports.getPrimaryAgentAdapter = getPrimaryAgentAdapter;
|
|
5
|
+
exports.detectSupportedAgents = detectSupportedAgents;
|
|
6
|
+
exports.resolvePrimaryAgent = resolvePrimaryAgent;
|
|
7
|
+
exports.detectPrimaryAgent = detectPrimaryAgent;
|
|
8
|
+
exports.resolveExecutionAgent = resolveExecutionAgent;
|
|
9
|
+
exports.requestPlannerReply = requestPlannerReply;
|
|
10
|
+
exports.writePlanningPack = writePlanningPack;
|
|
11
|
+
exports.runNextPrompt = runNextPrompt;
|
|
12
|
+
exports.selectPrimaryAgent = selectPrimaryAgent;
|
|
13
|
+
exports.resetAgentAdaptersForTesting = resetAgentAdaptersForTesting;
|
|
14
|
+
exports.setAgentAdaptersForTesting = setAgentAdaptersForTesting;
|
|
15
|
+
const claude_1 = require("./claude");
|
|
16
|
+
const codex_1 = require("./codex");
|
|
17
|
+
const studio_session_1 = require("./studio-session");
|
|
18
|
+
const codexAdapter = {
|
|
19
|
+
id: "codex",
|
|
20
|
+
label: "Codex",
|
|
21
|
+
async detectStatus() {
|
|
22
|
+
const status = await (0, codex_1.detectCodex)();
|
|
23
|
+
return {
|
|
24
|
+
id: "codex",
|
|
25
|
+
label: "Codex",
|
|
26
|
+
available: status.available,
|
|
27
|
+
command: status.command,
|
|
28
|
+
version: status.version,
|
|
29
|
+
error: status.error
|
|
30
|
+
};
|
|
31
|
+
},
|
|
32
|
+
requestPlannerReply: codex_1.requestPlannerReply,
|
|
33
|
+
writePlanningPack: codex_1.writePlanningPack,
|
|
34
|
+
runNextPrompt: codex_1.runNextPrompt
|
|
35
|
+
};
|
|
36
|
+
const claudeAdapter = {
|
|
37
|
+
id: "claude",
|
|
38
|
+
label: "Claude Code",
|
|
39
|
+
async detectStatus() {
|
|
40
|
+
const status = await (0, claude_1.detectClaude)();
|
|
41
|
+
return {
|
|
42
|
+
id: "claude",
|
|
43
|
+
label: "Claude Code",
|
|
44
|
+
available: status.available,
|
|
45
|
+
command: status.command,
|
|
46
|
+
version: status.version,
|
|
47
|
+
error: status.error
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
requestPlannerReply: claude_1.requestPlannerReply,
|
|
51
|
+
writePlanningPack: claude_1.writePlanningPack,
|
|
52
|
+
runNextPrompt: claude_1.runNextPrompt
|
|
53
|
+
};
|
|
54
|
+
const defaultAgentAdapters = [codexAdapter, claudeAdapter];
|
|
55
|
+
let registeredAgentAdapters = [...defaultAgentAdapters];
|
|
56
|
+
let primaryAgentId = registeredAgentAdapters[0].id;
|
|
57
|
+
function getSupportedAgentAdapters() {
|
|
58
|
+
return [...registeredAgentAdapters];
|
|
59
|
+
}
|
|
60
|
+
function getPrimaryAgentAdapter() {
|
|
61
|
+
return getAgentAdapterById(primaryAgentId) ?? getSupportedAgentAdapters()[0];
|
|
62
|
+
}
|
|
63
|
+
async function detectSupportedAgents(workspaceRoot) {
|
|
64
|
+
const statuses = await collectAgentStatuses();
|
|
65
|
+
syncPrimaryAgent((await resolvePrimaryAgentStatus(statuses, workspaceRoot)).id);
|
|
66
|
+
return statuses;
|
|
67
|
+
}
|
|
68
|
+
async function resolvePrimaryAgent(workspaceRoot) {
|
|
69
|
+
const statuses = await collectAgentStatuses();
|
|
70
|
+
const status = await resolvePrimaryAgentStatus(statuses, workspaceRoot);
|
|
71
|
+
const adapter = getAgentAdapterById(status.id) ?? getSupportedAgentAdapters()[0];
|
|
72
|
+
syncPrimaryAgent(adapter.id);
|
|
73
|
+
return {
|
|
74
|
+
adapter,
|
|
75
|
+
status,
|
|
76
|
+
statuses
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
async function detectPrimaryAgent(workspaceRoot) {
|
|
80
|
+
return (await resolvePrimaryAgent(workspaceRoot)).status;
|
|
81
|
+
}
|
|
82
|
+
async function resolveExecutionAgent(workspaceRoot, overrideId) {
|
|
83
|
+
const normalizedOverrideId = overrideId?.trim().toLowerCase();
|
|
84
|
+
if (!normalizedOverrideId) {
|
|
85
|
+
return resolvePrimaryAgent(workspaceRoot);
|
|
86
|
+
}
|
|
87
|
+
const statuses = await collectAgentStatuses();
|
|
88
|
+
const status = statuses.find((candidate) => candidate.id === normalizedOverrideId);
|
|
89
|
+
if (!status) {
|
|
90
|
+
throw new Error(`Unknown agent \`${overrideId}\`. Supported agents: ${getSupportedAgentAdapters().map((adapter) => adapter.id).join(", ")}.`);
|
|
91
|
+
}
|
|
92
|
+
if (!status.available) {
|
|
93
|
+
throw new Error(`Cannot use ${status.label} for this run: ${status.error ?? `${status.command} is not available`}.`);
|
|
94
|
+
}
|
|
95
|
+
const adapter = getAgentAdapterById(status.id) ?? getSupportedAgentAdapters()[0];
|
|
96
|
+
syncPrimaryAgent(adapter.id);
|
|
97
|
+
return {
|
|
98
|
+
adapter,
|
|
99
|
+
status,
|
|
100
|
+
statuses
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
async function requestPlannerReply(workspaceRoot, messages) {
|
|
104
|
+
const { adapter } = await resolvePrimaryAgent(workspaceRoot);
|
|
105
|
+
return adapter.requestPlannerReply(workspaceRoot, messages);
|
|
106
|
+
}
|
|
107
|
+
async function writePlanningPack(workspaceRoot, messages) {
|
|
108
|
+
const { adapter } = await resolvePrimaryAgent(workspaceRoot);
|
|
109
|
+
return adapter.writePlanningPack(workspaceRoot, messages);
|
|
110
|
+
}
|
|
111
|
+
async function runNextPrompt(workspaceRoot, prompt, options = {}) {
|
|
112
|
+
const { adapter } = await resolveExecutionAgent(workspaceRoot, options.agentId);
|
|
113
|
+
return adapter.runNextPrompt(workspaceRoot, prompt);
|
|
114
|
+
}
|
|
115
|
+
async function selectPrimaryAgent(workspaceRoot, id) {
|
|
116
|
+
const normalizedId = id.trim().toLowerCase();
|
|
117
|
+
const statuses = await collectAgentStatuses();
|
|
118
|
+
const status = statuses.find((candidate) => candidate.id === normalizedId);
|
|
119
|
+
if (!status) {
|
|
120
|
+
throw new Error(`Unknown agent \`${id}\`. Supported agents: ${getSupportedAgentAdapters().map((adapter) => adapter.id).join(", ")}.`);
|
|
121
|
+
}
|
|
122
|
+
if (!status.available) {
|
|
123
|
+
throw new Error(`Cannot activate ${status.label}: ${status.error ?? `${status.command} is not available`}.`);
|
|
124
|
+
}
|
|
125
|
+
await (0, studio_session_1.saveStoredActiveAgentId)(workspaceRoot, status.id);
|
|
126
|
+
const adapter = getAgentAdapterById(status.id) ?? getSupportedAgentAdapters()[0];
|
|
127
|
+
syncPrimaryAgent(adapter.id);
|
|
128
|
+
return {
|
|
129
|
+
adapter,
|
|
130
|
+
status,
|
|
131
|
+
statuses
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function resetAgentAdaptersForTesting() {
|
|
135
|
+
registeredAgentAdapters = [...defaultAgentAdapters];
|
|
136
|
+
primaryAgentId = registeredAgentAdapters[0].id;
|
|
137
|
+
}
|
|
138
|
+
function setAgentAdaptersForTesting(adapters) {
|
|
139
|
+
registeredAgentAdapters = adapters.length > 0 ? [...adapters] : [...defaultAgentAdapters];
|
|
140
|
+
primaryAgentId = registeredAgentAdapters[0].id;
|
|
141
|
+
}
|
|
142
|
+
async function collectAgentStatuses() {
|
|
143
|
+
return Promise.all(getSupportedAgentAdapters().map((adapter) => adapter.detectStatus()));
|
|
144
|
+
}
|
|
145
|
+
async function resolvePrimaryAgentStatus(statuses, workspaceRoot) {
|
|
146
|
+
const storedId = workspaceRoot ? await (0, studio_session_1.loadStoredActiveAgentId)(workspaceRoot) : null;
|
|
147
|
+
if (storedId) {
|
|
148
|
+
const storedStatus = statuses.find((status) => status.id === storedId);
|
|
149
|
+
if (storedStatus) {
|
|
150
|
+
return storedStatus;
|
|
151
|
+
}
|
|
152
|
+
if (workspaceRoot) {
|
|
153
|
+
await (0, studio_session_1.saveStoredActiveAgentId)(workspaceRoot, null);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return statuses.find((status) => status.available) ?? statuses[0];
|
|
157
|
+
}
|
|
158
|
+
function getAgentAdapterById(id) {
|
|
159
|
+
return registeredAgentAdapters.find((adapter) => adapter.id === id);
|
|
160
|
+
}
|
|
161
|
+
function syncPrimaryAgent(id) {
|
|
162
|
+
const adapter = getAgentAdapterById(id) ?? getSupportedAgentAdapters()[0];
|
|
163
|
+
primaryAgentId = adapter.id;
|
|
164
|
+
}
|