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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smithers-orchestrator",
3
- "version": "0.8.3",
3
+ "version": "0.8.4",
4
4
  "description": "AI workflow orchestration with JSX",
5
5
  "author": "William Cory",
6
6
  "license": "MIT",
@@ -7,6 +7,7 @@ export type TaskDescriptor = {
7
7
  ralphId?: string;
8
8
  worktreeId?: string;
9
9
  worktreePath?: string;
10
+ worktreeBranch?: string;
10
11
  outputTable: any | null;
11
12
  outputTableName: string;
12
13
  outputSchema?: import("zod").ZodObject<any>;
@@ -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
  }
@@ -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
- nextWorktreeStack = [...worktreeStack, { id, path: normPath }];
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,
@@ -113,12 +113,15 @@ function findVcsRoot(startDir: string): { type: "git" | "jj"; root: string } | n
113
113
  }
114
114
 
115
115
  /**
116
- * Ensure a git worktree exists at `worktreePath`, creating it from `rootDir`
117
- * if necessary. Safe to call multiple times for the same path.
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 ensureGitWorktree(
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
- let created = false;
153
- const failures: string[] = [];
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 ensureGitWorktree(toolConfig.rootDir, desc.worktreePath);
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 = desc.agent?.id ?? "agent";
751
- const toolsSig = desc.agent?.tools
752
- ? Object.keys(desc.agent.tools).sort().join(",")
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: desc.agent?.id ?? "agent",
1241
- toolsSig: desc.agent?.tools
1242
- ? Object.keys(desc.agent.tools).sort().join(",")
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),