smithers-orchestrator 0.8.3 → 0.8.4
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/package.json +1 -1
- package/src/TaskDescriptor.ts +1 -0
- package/src/agents/CodexAgent.ts +5 -0
- package/src/components/Worktree.ts +2 -1
- package/src/dom/extract.ts +4 -2
- package/src/engine/index.ts +78 -33
package/package.json
CHANGED
package/src/TaskDescriptor.ts
CHANGED
package/src/agents/CodexAgent.ts
CHANGED
|
@@ -106,6 +106,11 @@ export class CodexAgent extends BaseCliAgent {
|
|
|
106
106
|
args,
|
|
107
107
|
stdin: fullPrompt,
|
|
108
108
|
outputFile,
|
|
109
|
+
stdoutBannerPatterns: [
|
|
110
|
+
// Codex CLI prints a startup banner like:
|
|
111
|
+
// "OpenAI Codex v0.99.0-alpha.13 (research preview)"
|
|
112
|
+
/^OpenAI Codex v[^\n]*$/gm,
|
|
113
|
+
],
|
|
109
114
|
cleanup: async () => {
|
|
110
115
|
if (!this.opts.outputLastMessage) {
|
|
111
116
|
await fs.rm(outputFile, { force: true }).catch(() => undefined);
|
|
@@ -4,6 +4,7 @@ import { WORKTREE_EMPTY_PATH_ERROR } from "../constants";
|
|
|
4
4
|
export type WorktreeProps = {
|
|
5
5
|
id?: string;
|
|
6
6
|
path: string;
|
|
7
|
+
branch?: string;
|
|
7
8
|
skipIf?: boolean;
|
|
8
9
|
children?: React.ReactNode;
|
|
9
10
|
};
|
|
@@ -13,6 +14,6 @@ export function Worktree(props: WorktreeProps) {
|
|
|
13
14
|
throw new Error(WORKTREE_EMPTY_PATH_ERROR);
|
|
14
15
|
}
|
|
15
16
|
if (props.skipIf) return null;
|
|
16
|
-
const next: { id?: string; path: string } = { id: props.id, path: props.path };
|
|
17
|
+
const next: { id?: string; path: string; branch?: string } = { id: props.id, path: props.path, branch: props.branch };
|
|
17
18
|
return React.createElement("smithers:worktree", next, props.children);
|
|
18
19
|
}
|
package/src/dom/extract.ts
CHANGED
|
@@ -118,7 +118,7 @@ export function extractFromHost(
|
|
|
118
118
|
* Stack of active <Worktree> contexts (outermost -> innermost).
|
|
119
119
|
* The top of the stack controls the effective root override for tasks.
|
|
120
120
|
*/
|
|
121
|
-
worktreeStack: { id: string; path: string }[];
|
|
121
|
+
worktreeStack: { id: string; path: string; branch?: string }[];
|
|
122
122
|
},
|
|
123
123
|
) {
|
|
124
124
|
if (node.kind === "text") return;
|
|
@@ -179,7 +179,8 @@ export function extractFromHost(
|
|
|
179
179
|
const normPath = isAbsolute(pathVal)
|
|
180
180
|
? resolvePath(pathVal)
|
|
181
181
|
: resolvePath(base, pathVal);
|
|
182
|
-
|
|
182
|
+
const branch = node.rawProps?.branch ? String(node.rawProps.branch) : undefined;
|
|
183
|
+
nextWorktreeStack = [...worktreeStack, { id, path: normPath, branch }];
|
|
183
184
|
}
|
|
184
185
|
if (node.tag === "smithers:task") {
|
|
185
186
|
const raw = node.rawProps || {};
|
|
@@ -233,6 +234,7 @@ export function extractFromHost(
|
|
|
233
234
|
ralphId,
|
|
234
235
|
worktreeId: topWorktree?.id,
|
|
235
236
|
worktreePath: topWorktree?.path,
|
|
237
|
+
worktreeBranch: topWorktree?.branch,
|
|
236
238
|
outputTable,
|
|
237
239
|
outputTableName,
|
|
238
240
|
outputSchema: outputRaw,
|
package/src/engine/index.ts
CHANGED
|
@@ -113,12 +113,15 @@ function findVcsRoot(startDir: string): { type: "git" | "jj"; root: string } | n
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
/**
|
|
116
|
-
* Ensure a
|
|
117
|
-
* if necessary.
|
|
116
|
+
* Ensure a worktree exists at `worktreePath`, creating it from `rootDir`
|
|
117
|
+
* if necessary. When `branch` is provided, a jj bookmark or git branch is
|
|
118
|
+
* created/updated in the new worktree. Safe to call multiple times for the
|
|
119
|
+
* same path.
|
|
118
120
|
*/
|
|
119
|
-
async function
|
|
121
|
+
async function ensureWorktree(
|
|
120
122
|
rootDir: string,
|
|
121
123
|
worktreePath: string,
|
|
124
|
+
branch?: string,
|
|
122
125
|
): Promise<void> {
|
|
123
126
|
if (createdWorktrees.has(worktreePath)) {
|
|
124
127
|
if (existsSync(worktreePath)) return;
|
|
@@ -138,8 +141,9 @@ async function ensureGitWorktree(
|
|
|
138
141
|
);
|
|
139
142
|
}
|
|
140
143
|
|
|
144
|
+
// Best effort: refresh remote refs for git so origin/main can be used as a
|
|
145
|
+
// base when local main is absent.
|
|
141
146
|
if (vcs.type === "git") {
|
|
142
|
-
// Try to fetch origin first so origin/main can resolve when available.
|
|
143
147
|
await new Promise<void>((res) => {
|
|
144
148
|
const child = nodeSpawn("git", ["fetch", "origin"], {
|
|
145
149
|
cwd: vcs.root,
|
|
@@ -148,29 +152,10 @@ async function ensureGitWorktree(
|
|
|
148
152
|
child.on("close", () => res());
|
|
149
153
|
child.on("error", () => res());
|
|
150
154
|
});
|
|
155
|
+
}
|
|
151
156
|
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
for (const ref of ["origin/main", "HEAD"]) {
|
|
155
|
-
const result = await runGitCommand(vcs.root, [
|
|
156
|
-
"worktree",
|
|
157
|
-
"add",
|
|
158
|
-
worktreePath,
|
|
159
|
-
ref,
|
|
160
|
-
]);
|
|
161
|
-
if (result.code === 0) {
|
|
162
|
-
created = true;
|
|
163
|
-
break;
|
|
164
|
-
}
|
|
165
|
-
failures.push(`${ref}: ${result.stderr || `exit ${result.code}`}`);
|
|
166
|
-
}
|
|
167
|
-
if (!created) {
|
|
168
|
-
throw new Error(
|
|
169
|
-
`Failed to create git worktree at ${worktreePath}. Tried origin/main and HEAD. ${failures.join(" | ")}`,
|
|
170
|
-
);
|
|
171
|
-
}
|
|
172
|
-
} else {
|
|
173
|
-
const { workspaceAdd } = await import("../vcs/jj");
|
|
157
|
+
if (vcs.type === "jj") {
|
|
158
|
+
const { workspaceAdd, runJj } = await import("../vcs/jj");
|
|
174
159
|
const name = worktreePath.split("/").pop() ?? "worktree";
|
|
175
160
|
const wsResult = await workspaceAdd(name, worktreePath, { cwd: vcs.root });
|
|
176
161
|
if (!wsResult.success) {
|
|
@@ -178,6 +163,65 @@ async function ensureGitWorktree(
|
|
|
178
163
|
`Failed to create jj workspace at ${worktreePath}: ${wsResult.error}`,
|
|
179
164
|
);
|
|
180
165
|
}
|
|
166
|
+
// Create a bookmark pointing at the new workspace's working copy
|
|
167
|
+
if (branch) {
|
|
168
|
+
const setRes = await runJj(["bookmark", "set", branch, "-r", "@"], {
|
|
169
|
+
cwd: worktreePath,
|
|
170
|
+
});
|
|
171
|
+
if (setRes.code !== 0) {
|
|
172
|
+
throw new Error(
|
|
173
|
+
`Failed to set jj bookmark ${branch} in ${worktreePath}: ${setRes.stderr || `exit ${setRes.code}`}`,
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} else {
|
|
178
|
+
const baseRefs = ["main", "origin/main", "HEAD"] as const;
|
|
179
|
+
if (branch) {
|
|
180
|
+
// -B force-creates the branch (handles restarts gracefully)
|
|
181
|
+
let created = false;
|
|
182
|
+
const failures: string[] = [];
|
|
183
|
+
for (const ref of baseRefs) {
|
|
184
|
+
const result = await runGitCommand(vcs.root, [
|
|
185
|
+
"worktree",
|
|
186
|
+
"add",
|
|
187
|
+
"-B",
|
|
188
|
+
branch,
|
|
189
|
+
worktreePath,
|
|
190
|
+
ref,
|
|
191
|
+
]);
|
|
192
|
+
if (result.code === 0) {
|
|
193
|
+
created = true;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
failures.push(`${ref}: ${result.stderr || `exit ${result.code}`}`);
|
|
197
|
+
}
|
|
198
|
+
if (!created) {
|
|
199
|
+
throw new Error(
|
|
200
|
+
`Failed to create git worktree at ${worktreePath} on branch ${branch}. Tried main, origin/main, and HEAD. ${failures.join(" | ")}`,
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
let created = false;
|
|
205
|
+
const failures: string[] = [];
|
|
206
|
+
for (const ref of baseRefs) {
|
|
207
|
+
const result = await runGitCommand(vcs.root, [
|
|
208
|
+
"worktree",
|
|
209
|
+
"add",
|
|
210
|
+
worktreePath,
|
|
211
|
+
ref,
|
|
212
|
+
]);
|
|
213
|
+
if (result.code === 0) {
|
|
214
|
+
created = true;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
failures.push(`${ref}: ${result.stderr || `exit ${result.code}`}`);
|
|
218
|
+
}
|
|
219
|
+
if (!created) {
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Failed to create git worktree at ${worktreePath}. Tried main, origin/main, and HEAD. ${failures.join(" | ")}`,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
181
225
|
}
|
|
182
226
|
|
|
183
227
|
createdWorktrees.add(worktreePath);
|
|
@@ -738,8 +782,9 @@ async function executeTask(
|
|
|
738
782
|
|
|
739
783
|
// Ensure the worktree directory exists on disk before running the task.
|
|
740
784
|
if (desc.worktreePath) {
|
|
741
|
-
await
|
|
785
|
+
await ensureWorktree(toolConfig.rootDir, desc.worktreePath, desc.worktreeBranch);
|
|
742
786
|
}
|
|
787
|
+
const cacheAgent = Array.isArray(desc.agent) ? desc.agent[0] : desc.agent;
|
|
743
788
|
|
|
744
789
|
try {
|
|
745
790
|
if (signal?.aborted) {
|
|
@@ -747,9 +792,9 @@ async function executeTask(
|
|
|
747
792
|
}
|
|
748
793
|
if (cacheEnabled) {
|
|
749
794
|
const schemaSig = schemaSignature(desc.outputTable as any);
|
|
750
|
-
const agentSig =
|
|
751
|
-
const toolsSig =
|
|
752
|
-
? Object.keys(
|
|
795
|
+
const agentSig = cacheAgent?.id ?? "agent";
|
|
796
|
+
const toolsSig = cacheAgent?.tools
|
|
797
|
+
? Object.keys(cacheAgent.tools).sort().join(",")
|
|
753
798
|
: "";
|
|
754
799
|
// Incorporate JJ state so workspace changes invalidate cache as documented.
|
|
755
800
|
const jjBase = await getJjPointer(taskRoot);
|
|
@@ -1237,9 +1282,9 @@ async function executeTask(
|
|
|
1237
1282
|
nodeId: desc.nodeId,
|
|
1238
1283
|
outputTable: desc.outputTableName,
|
|
1239
1284
|
schemaSig: schemaSignature(desc.outputTable as any),
|
|
1240
|
-
agentSig:
|
|
1241
|
-
toolsSig:
|
|
1242
|
-
? Object.keys(
|
|
1285
|
+
agentSig: cacheAgent?.id ?? "agent",
|
|
1286
|
+
toolsSig: cacheAgent?.tools
|
|
1287
|
+
? Object.keys(cacheAgent.tools).sort().join(",")
|
|
1243
1288
|
: null,
|
|
1244
1289
|
jjPointer: cacheJjBase,
|
|
1245
1290
|
payloadJson: JSON.stringify(payload),
|