@staff0rd/assist 0.486.0 → 0.487.0

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/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.486.0",
9
+ version: "0.487.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -6829,7 +6829,6 @@ function consumePause(itemId2) {
6829
6829
  }
6830
6830
 
6831
6831
  // src/commands/backlog/executePhase.ts
6832
- import { randomUUID } from "crypto";
6833
6832
  import chalk44 from "chalk";
6834
6833
 
6835
6834
  // src/shared/awaitClaude.ts
@@ -7000,6 +6999,13 @@ function logHookFired(status3, sessionId, source) {
7000
6999
  );
7001
7000
  }
7002
7001
 
7002
+ // src/commands/backlog/assertCodexResumeSupported.ts
7003
+ function assertCodexResumeSupported(options2) {
7004
+ if (options2?.resumeSessionId && options2.harness === "codex") {
7005
+ throw new Error("Codex backlog sessions cannot be resumed yet");
7006
+ }
7007
+ }
7008
+
7003
7009
  // src/commands/backlog/buildCommentLines.ts
7004
7010
  function buildCommentLines(comments3) {
7005
7011
  if (!comments3?.length) return [];
@@ -7142,6 +7148,101 @@ function buildPhasePrompt(item, phaseNumber, phase) {
7142
7148
  });
7143
7149
  }
7144
7150
 
7151
+ // src/shared/harnesses.ts
7152
+ import * as os from "os";
7153
+ import * as path22 from "path";
7154
+
7155
+ // src/shared/checkCliAvailable.ts
7156
+ import { execSync as execSync30 } from "child_process";
7157
+ function checkCliAvailable(cli) {
7158
+ const binary = cli.split(/\s+/)[0];
7159
+ const opts = {
7160
+ encoding: "utf8",
7161
+ stdio: ["ignore", "pipe", "pipe"]
7162
+ };
7163
+ try {
7164
+ execSync30(`command -v ${binary}`, opts);
7165
+ return true;
7166
+ } catch {
7167
+ try {
7168
+ execSync30(`where ${binary}`, opts);
7169
+ return true;
7170
+ } catch {
7171
+ return false;
7172
+ }
7173
+ }
7174
+ }
7175
+
7176
+ // src/shared/harnesses.ts
7177
+ var harnesses = {
7178
+ claude: {
7179
+ kind: "claude",
7180
+ command: "claude",
7181
+ homeDir: path22.join(os.homedir(), ".claude"),
7182
+ sync: {
7183
+ agentsFile: "CLAUDE.md",
7184
+ commandDest: (name) => path22.join("commands", `${name}.md`)
7185
+ }
7186
+ },
7187
+ codex: {
7188
+ kind: "codex",
7189
+ command: "codex",
7190
+ homeDir: path22.join(os.homedir(), ".codex"),
7191
+ sync: {
7192
+ agentsFile: "AGENTS.md",
7193
+ commandDest: (name) => path22.join("skills", name, "SKILL.md")
7194
+ }
7195
+ },
7196
+ pi: {
7197
+ kind: "pi",
7198
+ command: "pi",
7199
+ homeDir: path22.join(os.homedir(), ".pi", "agent"),
7200
+ sync: {
7201
+ agentsFile: "AGENTS.md",
7202
+ commandDest: (name) => path22.join("prompts", `${name}.md`)
7203
+ }
7204
+ }
7205
+ };
7206
+ function isHarnessAvailable(kind) {
7207
+ return checkCliAvailable(harnesses[kind].command);
7208
+ }
7209
+
7210
+ // src/shared/spawnCodex.ts
7211
+ function spawnCodex(prompt, options2 = {}) {
7212
+ const cwd = options2.cwd ?? process.cwd();
7213
+ return spawnInherit(harnesses.codex.command, [
7214
+ "-C",
7215
+ cwd,
7216
+ "--sandbox",
7217
+ options2.sandbox ?? "workspace-write",
7218
+ prompt
7219
+ ]);
7220
+ }
7221
+
7222
+ // src/shared/spawnPi.ts
7223
+ function spawnPi(prompt, options2 = {}) {
7224
+ const cwd = options2.cwd ?? process.cwd();
7225
+ return spawnInherit(harnesses.pi.command, [prompt], { cwd });
7226
+ }
7227
+
7228
+ // src/shared/spawnHarness.ts
7229
+ function spawnHarness(harness, prompt, options2 = {}) {
7230
+ if (harness === "codex") {
7231
+ return spawnCodex(prompt, {
7232
+ cwd: options2.cwd,
7233
+ sandbox: options2.allowEdits === false ? "read-only" : "workspace-write"
7234
+ });
7235
+ }
7236
+ if (harness === "pi") {
7237
+ return spawnPi(prompt, { cwd: options2.cwd });
7238
+ }
7239
+ return spawnClaude(prompt, {
7240
+ allowEdits: options2.allowEdits ?? true,
7241
+ sessionId: options2.sessionId,
7242
+ resumeSessionId: options2.resumeSessionId
7243
+ });
7244
+ }
7245
+
7145
7246
  // src/commands/backlog/watchForMarker.ts
7146
7247
  import { existsSync as existsSync24, unwatchFile, watchFile } from "fs";
7147
7248
 
@@ -7150,8 +7251,8 @@ import { existsSync as existsSync23, readFileSync as readFileSync17 } from "fs";
7150
7251
 
7151
7252
  // src/commands/backlog/writeSignal.ts
7152
7253
  import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync18 } from "fs";
7153
- import { homedir as homedir8 } from "os";
7154
- import { dirname as dirname16, join as join20 } from "path";
7254
+ import { homedir as homedir9 } from "os";
7255
+ import { dirname as dirname16, join as join21 } from "path";
7155
7256
  import chalk41 from "chalk";
7156
7257
 
7157
7258
  // src/commands/backlog/recordSignalOwner.ts
@@ -7162,10 +7263,10 @@ import {
7162
7263
  rmSync,
7163
7264
  writeFileSync as writeFileSync17
7164
7265
  } from "fs";
7165
- import { homedir as homedir7 } from "os";
7166
- import { dirname as dirname15, join as join19 } from "path";
7266
+ import { homedir as homedir8 } from "os";
7267
+ import { dirname as dirname15, join as join20 } from "path";
7167
7268
  function getOwnerPath(itemId2) {
7168
- return join19(homedir7(), ".assist", "signals", `owner-${itemId2}.json`);
7269
+ return join20(homedir8(), ".assist", "signals", `owner-${itemId2}.json`);
7169
7270
  }
7170
7271
  function recordSignalOwner(itemId2) {
7171
7272
  const sessionId = process.env.ASSIST_SESSION_ID;
@@ -7195,7 +7296,7 @@ function clearSignalOwner(itemId2) {
7195
7296
  // src/commands/backlog/writeSignal.ts
7196
7297
  function getSignalPath(sessionId = process.env.ASSIST_SESSION_ID) {
7197
7298
  if (!sessionId) return void 0;
7198
- return join20(homedir8(), ".assist", "signals", `signal-${sessionId}.json`);
7299
+ return join21(homedir9(), ".assist", "signals", `signal-${sessionId}.json`);
7199
7300
  }
7200
7301
  function resolveSignalTarget(event, data) {
7201
7302
  const caller = process.env.ASSIST_SESSION_ID;
@@ -7264,8 +7365,11 @@ function stopWatching() {
7264
7365
  }
7265
7366
 
7266
7367
  // src/commands/backlog/launchPhaseClaude.ts
7267
- async function launchPhaseClaude(prompt, spawnOptions, context) {
7268
- const { child, done: done2 } = spawnClaude(prompt, spawnOptions);
7368
+ async function launchPhaseClaude(prompt, spawnOptions, context, harness = "claude") {
7369
+ const { child, done: done2 } = spawnHarness(harness, prompt, {
7370
+ ...spawnOptions,
7371
+ cwd: process.cwd()
7372
+ });
7269
7373
  watchForMarker(child);
7270
7374
  const exitCode = await awaitClaude(done2, context);
7271
7375
  stopWatching();
@@ -7284,16 +7388,77 @@ function resumeNudge() {
7284
7388
 
7285
7389
  // src/commands/backlog/launchPhaseSession.ts
7286
7390
  function launchPhaseSession(item, phaseNumber, phase, phaseLabel2, claudeSessionId, spawnOptions) {
7287
- const resumeSessionId = spawnOptions?.resumeSessionId;
7391
+ const harness = spawnOptions?.harness;
7392
+ const { harness: _harness, ...phaseOptions } = spawnOptions ?? {};
7393
+ const resumeSessionId = phaseOptions.resumeSessionId;
7288
7394
  return launchPhaseClaude(
7289
7395
  resumeSessionId ? resumeNudge() : buildPhasePrompt(item, phaseNumber, phase),
7290
- resumeSessionId ? spawnOptions ?? {} : { ...spawnOptions, sessionId: claudeSessionId },
7291
- phaseLabel2
7396
+ resumeSessionId ? phaseOptions : { ...phaseOptions, sessionId: claudeSessionId },
7397
+ phaseLabel2,
7398
+ harness
7292
7399
  );
7293
7400
  }
7294
7401
 
7402
+ // src/commands/backlog/resolvePhaseResult.ts
7403
+ import { existsSync as existsSync25, unlinkSync as unlinkSync4 } from "fs";
7404
+ import chalk42 from "chalk";
7405
+
7406
+ // src/commands/backlog/handleIncompletePhase.ts
7407
+ import enquirer4 from "enquirer";
7408
+ async function handleIncompletePhase() {
7409
+ const { action } = await exitOnCancel(
7410
+ enquirer4.prompt({
7411
+ type: "select",
7412
+ name: "action",
7413
+ message: "Phase was not marked complete. What would you like to do?",
7414
+ choices: ["Retry this phase", "Skip to next phase", "Abort"]
7415
+ })
7416
+ );
7417
+ if (action === "Retry this phase") return "retry";
7418
+ if (action === "Skip to next phase") return "skip";
7419
+ return "abort";
7420
+ }
7421
+
7422
+ // src/commands/backlog/resolvePhaseResult.ts
7423
+ function cleanupSignal() {
7424
+ const statusPath = getSignalPath();
7425
+ if (statusPath && existsSync25(statusPath)) {
7426
+ unlinkSync4(statusPath);
7427
+ }
7428
+ }
7429
+ async function isTerminalStatus(itemId2) {
7430
+ const { orm } = await getReady();
7431
+ const item = await loadItem(orm, itemId2);
7432
+ return item?.status === "done" || item?.status === "wontdo";
7433
+ }
7434
+ async function resolvePhaseResult(phaseIndex, itemId2) {
7435
+ const signalPath = getSignalPath();
7436
+ if (!signalPath || !existsSync25(signalPath)) {
7437
+ if (await isTerminalStatus(itemId2)) return { kind: "abort" };
7438
+ const action = await handleIncompletePhase();
7439
+ if (action === "abort") return { kind: "abort" };
7440
+ return action === "skip" ? { kind: "skip" } : { kind: "retry" };
7441
+ }
7442
+ const signal = readSignal();
7443
+ cleanupSignal();
7444
+ if (signal?.event === "rewind") {
7445
+ const targetPhase = signal.targetPhase;
7446
+ const targetPhaseNumber = targetPhase + 1;
7447
+ console.log(chalk42.yellow(`
7448
+ Rewinding to phase ${targetPhaseNumber}.`));
7449
+ return { kind: "rewind", targetPhase };
7450
+ }
7451
+ const phaseNumber = phaseIndex + 1;
7452
+ console.log(chalk42.green(`
7453
+ Phase ${phaseNumber} completed.`));
7454
+ return { kind: "advance" };
7455
+ }
7456
+
7457
+ // src/commands/backlog/preparePhaseSession.ts
7458
+ import { randomUUID } from "crypto";
7459
+
7295
7460
  // src/commands/backlog/persistPhaseSession.ts
7296
- import os from "os";
7461
+ import os2 from "os";
7297
7462
 
7298
7463
  // src/shared/db/recordPhaseSession.ts
7299
7464
  import { sql as sql12 } from "drizzle-orm";
@@ -7318,8 +7483,8 @@ async function persistPhaseSession(itemId2, phaseIdx, claudeSessionId) {
7318
7483
  itemId2,
7319
7484
  phaseIdx,
7320
7485
  claudeSessionId,
7321
- os.hostname(),
7322
- os.userInfo().username
7486
+ os2.hostname(),
7487
+ os2.userInfo().username
7323
7488
  );
7324
7489
  } catch (error) {
7325
7490
  console.error(
@@ -7348,8 +7513,8 @@ async function persistPhaseSessionId(itemId2, phaseIdx, claudeSessionId) {
7348
7513
 
7349
7514
  // src/shared/emitActivity.ts
7350
7515
  import { mkdirSync as mkdirSync9, readFileSync as readFileSync18, rmSync as rmSync2, writeFileSync as writeFileSync19 } from "fs";
7351
- import { homedir as homedir9 } from "os";
7352
- import { dirname as dirname17, join as join21 } from "path";
7516
+ import { homedir as homedir10 } from "os";
7517
+ import { dirname as dirname17, join as join22 } from "path";
7353
7518
  import { z as z4 } from "zod";
7354
7519
  var activitySchema = z4.object({
7355
7520
  kind: z4.enum(["command", "backlog"]),
@@ -7364,7 +7529,7 @@ var activitySchema = z4.object({
7364
7529
  startedAt: z4.number()
7365
7530
  });
7366
7531
  function activityPath(sessionId) {
7367
- return join21(homedir9(), ".assist", "activity", `activity-${sessionId}.json`);
7532
+ return join22(homedir10(), ".assist", "activity", `activity-${sessionId}.json`);
7368
7533
  }
7369
7534
  function emitActivity(activity2) {
7370
7535
  const sessionId = process.env.ASSIST_ACTIVITY_ID;
@@ -7410,75 +7575,20 @@ function reportPhaseActivity(item, phaseNumber, totalPhases, phase, claudeSessio
7410
7575
  });
7411
7576
  }
7412
7577
 
7413
- // src/commands/backlog/resolvePhaseResult.ts
7414
- import { existsSync as existsSync25, unlinkSync as unlinkSync4 } from "fs";
7415
- import chalk42 from "chalk";
7416
-
7417
- // src/commands/backlog/handleIncompletePhase.ts
7418
- import enquirer4 from "enquirer";
7419
- async function handleIncompletePhase() {
7420
- const { action } = await exitOnCancel(
7421
- enquirer4.prompt({
7422
- type: "select",
7423
- name: "action",
7424
- message: "Phase was not marked complete. What would you like to do?",
7425
- choices: ["Retry this phase", "Skip to next phase", "Abort"]
7426
- })
7427
- );
7428
- if (action === "Retry this phase") return "retry";
7429
- if (action === "Skip to next phase") return "skip";
7430
- return "abort";
7431
- }
7432
-
7433
- // src/commands/backlog/resolvePhaseResult.ts
7434
- function cleanupSignal() {
7435
- const statusPath = getSignalPath();
7436
- if (statusPath && existsSync25(statusPath)) {
7437
- unlinkSync4(statusPath);
7438
- }
7439
- }
7440
- async function isTerminalStatus(itemId2) {
7441
- const { orm } = await getReady();
7442
- const item = await loadItem(orm, itemId2);
7443
- return item?.status === "done" || item?.status === "wontdo";
7444
- }
7445
- async function resolvePhaseResult(phaseIndex, itemId2) {
7446
- const signalPath = getSignalPath();
7447
- if (!signalPath || !existsSync25(signalPath)) {
7448
- if (await isTerminalStatus(itemId2)) return { kind: "abort" };
7449
- const action = await handleIncompletePhase();
7450
- if (action === "abort") return { kind: "abort" };
7451
- return action === "skip" ? { kind: "skip" } : { kind: "retry" };
7452
- }
7453
- const signal = readSignal();
7454
- cleanupSignal();
7455
- if (signal?.event === "rewind") {
7456
- const targetPhase = signal.targetPhase;
7457
- const targetPhaseNumber = targetPhase + 1;
7458
- console.log(chalk42.yellow(`
7459
- Rewinding to phase ${targetPhaseNumber}.`));
7460
- return { kind: "rewind", targetPhase };
7461
- }
7462
- const phaseNumber = phaseIndex + 1;
7463
- console.log(chalk42.green(`
7464
- Phase ${phaseNumber} completed.`));
7465
- return { kind: "advance" };
7466
- }
7467
-
7468
7578
  // src/commands/backlog/verifyResumeConversation.ts
7469
7579
  import chalk43 from "chalk";
7470
7580
 
7471
7581
  // src/commands/sessions/shared/findSessionJsonlPath.ts
7472
- import * as path24 from "path";
7582
+ import * as path25 from "path";
7473
7583
 
7474
7584
  // src/commands/sessions/shared/discoverSessions.ts
7475
7585
  import * as fs18 from "fs";
7476
- import * as os2 from "os";
7477
- import * as path23 from "path";
7586
+ import * as os3 from "os";
7587
+ import * as path24 from "path";
7478
7588
 
7479
7589
  // src/commands/sessions/shared/parseSessionFile.ts
7480
7590
  import * as fs17 from "fs";
7481
- import * as path22 from "path";
7591
+ import * as path23 from "path";
7482
7592
 
7483
7593
  // src/commands/sessions/shared/deriveHistoryFields.ts
7484
7594
  var KNOWN = [
@@ -7602,10 +7712,10 @@ async function readHeadLines(handle) {
7602
7712
  }
7603
7713
  function deriveProject(cwd, filePath, origin) {
7604
7714
  if (!cwd) return dirNameToProject(filePath);
7605
- return origin === "windows" ? path22.win32.basename(cwd) : path22.basename(cwd);
7715
+ return origin === "windows" ? path23.win32.basename(cwd) : path23.basename(cwd);
7606
7716
  }
7607
7717
  function dirNameToProject(filePath) {
7608
- const dirName = path22.basename(path22.dirname(filePath));
7718
+ const dirName = path23.basename(path23.dirname(filePath));
7609
7719
  const parts = dirName.split("--");
7610
7720
  return parts[parts.length - 1].replace(/-/g, "/");
7611
7721
  }
@@ -7613,7 +7723,7 @@ function dirNameToProject(filePath) {
7613
7723
  // src/commands/sessions/shared/discoverSessions.ts
7614
7724
  function sessionRoots() {
7615
7725
  const roots = [
7616
- { dir: path23.join(os2.homedir(), ".claude", "projects"), origin: "wsl" }
7726
+ { dir: path24.join(os3.homedir(), ".claude", "projects"), origin: "wsl" }
7617
7727
  ];
7618
7728
  const windowsRoot = loadConfig().sessions?.windowsProjectsRoot;
7619
7729
  if (windowsRoot) roots.push({ dir: windowsRoot, origin: "windows" });
@@ -7631,7 +7741,7 @@ async function discoverSessionJsonlPaths() {
7631
7741
  }
7632
7742
  await Promise.all(
7633
7743
  projectDirs.map(async (dirName) => {
7634
- const dirPath = path23.join(dir, dirName);
7744
+ const dirPath = path24.join(dir, dirName);
7635
7745
  let entries;
7636
7746
  try {
7637
7747
  entries = await fs18.promises.readdir(dirPath);
@@ -7640,7 +7750,7 @@ async function discoverSessionJsonlPaths() {
7640
7750
  }
7641
7751
  for (const file of entries) {
7642
7752
  if (file.endsWith(".jsonl"))
7643
- results.push({ path: path23.join(dirPath, file), origin });
7753
+ results.push({ path: path24.join(dirPath, file), origin });
7644
7754
  }
7645
7755
  })
7646
7756
  );
@@ -7670,7 +7780,7 @@ async function discoverSessions() {
7670
7780
  async function findSessionJsonlPath(sessionId) {
7671
7781
  const paths = await discoverSessionJsonlPaths();
7672
7782
  const direct = paths.find(
7673
- (p) => path24.basename(p.path, ".jsonl") === sessionId
7783
+ (p) => path25.basename(p.path, ".jsonl") === sessionId
7674
7784
  );
7675
7785
  if (direct) return direct.path;
7676
7786
  for (const p of paths) {
@@ -7703,6 +7813,21 @@ async function verifyPhaseResume(itemId2, resumeSessionId, phaseLabel2) {
7703
7813
  return true;
7704
7814
  }
7705
7815
 
7816
+ // src/commands/backlog/preparePhaseSession.ts
7817
+ async function preparePhaseSession(item, phase, phaseIndex, phaseNumber, totalPhases, phaseLabel2, spawnOptions) {
7818
+ const resumeSessionId = spawnOptions?.resumeSessionId;
7819
+ const claudeSessionId = resumeSessionId ?? randomUUID();
7820
+ if (resumeSessionId && !await verifyPhaseResume(item.id, resumeSessionId, phaseLabel2)) {
7821
+ return void 0;
7822
+ }
7823
+ reportPhaseActivity(item, phaseNumber, totalPhases, phase, claudeSessionId);
7824
+ if (!resumeSessionId) {
7825
+ await persistPhaseSessionId(item.id, phaseIndex, claudeSessionId);
7826
+ }
7827
+ await persistPhaseSession(item.id, phaseIndex, claudeSessionId);
7828
+ return { claudeSessionId };
7829
+ }
7830
+
7706
7831
  // src/commands/backlog/executePhase.ts
7707
7832
  async function executePhase(item, phaseIndex, phases, spawnOptions, totalPhases = phases.length) {
7708
7833
  const phase = phases[phaseIndex];
@@ -7714,27 +7839,27 @@ async function executePhase(item, phaseIndex, phases, spawnOptions, totalPhases
7714
7839
  `
7715
7840
  )
7716
7841
  );
7717
- const resumeSessionId = spawnOptions?.resumeSessionId;
7718
- const claudeSessionId = resumeSessionId ?? randomUUID();
7842
+ assertCodexResumeSupported(spawnOptions);
7719
7843
  process.env.ASSIST_SESSION_ID ??= String(process.pid);
7720
7844
  process.env.ASSIST_BACKLOG_ITEM_ID = String(item.id);
7721
7845
  recordSignalOwner(item.id);
7722
7846
  const phaseLabel2 = `phase ${phaseNumber}/${totalPhases}`;
7723
- if (resumeSessionId) {
7724
- if (!await verifyPhaseResume(item.id, resumeSessionId, phaseLabel2))
7725
- return { kind: "abort" };
7726
- }
7727
- reportPhaseActivity(item, phaseNumber, totalPhases, phase, claudeSessionId);
7728
- if (!resumeSessionId) {
7729
- await persistPhaseSessionId(item.id, phaseIndex, claudeSessionId);
7730
- }
7731
- await persistPhaseSession(item.id, phaseIndex, claudeSessionId);
7847
+ const phaseSession = await preparePhaseSession(
7848
+ item,
7849
+ phase,
7850
+ phaseIndex,
7851
+ phaseNumber,
7852
+ totalPhases,
7853
+ phaseLabel2,
7854
+ spawnOptions
7855
+ );
7856
+ if (!phaseSession) return { kind: "abort" };
7732
7857
  const exitCode = await launchPhaseSession(
7733
7858
  item,
7734
7859
  phaseNumber,
7735
7860
  phase,
7736
7861
  phaseLabel2,
7737
- claudeSessionId,
7862
+ phaseSession.claudeSessionId,
7738
7863
  spawnOptions
7739
7864
  );
7740
7865
  if (exitCode === CLAUDE_SPAWN_FAILED) return { kind: "abort" };
@@ -8300,7 +8425,7 @@ import chalk61 from "chalk";
8300
8425
  import { WebSocketServer } from "ws";
8301
8426
 
8302
8427
  // src/shared/getInstallDir.ts
8303
- import { execSync as execSync30 } from "child_process";
8428
+ import { execSync as execSync31 } from "child_process";
8304
8429
  import { dirname as dirname19, resolve as resolve8 } from "path";
8305
8430
  import { fileURLToPath as fileURLToPath3 } from "url";
8306
8431
  var __filename2 = fileURLToPath3(import.meta.url);
@@ -8310,7 +8435,7 @@ function getInstallDir() {
8310
8435
  }
8311
8436
  function isGitRepo(dir) {
8312
8437
  try {
8313
- const result = execSync30("git rev-parse --show-toplevel", {
8438
+ const result = execSync31("git rev-parse --show-toplevel", {
8314
8439
  cwd: dir,
8315
8440
  stdio: "pipe"
8316
8441
  }).toString().trim();
@@ -8327,11 +8452,11 @@ import {
8327
8452
  import chalk57 from "chalk";
8328
8453
 
8329
8454
  // src/lib/openBrowser.ts
8330
- import { execSync as execSync31 } from "child_process";
8455
+ import { execSync as execSync32 } from "child_process";
8331
8456
  function tryExec(commands) {
8332
8457
  for (const cmd of commands) {
8333
8458
  try {
8334
- execSync31(cmd, { stdio: "ignore" });
8459
+ execSync32(cmd, { stdio: "ignore" });
8335
8460
  return true;
8336
8461
  } catch {
8337
8462
  }
@@ -8632,13 +8757,13 @@ function gitCommonDir(cwd) {
8632
8757
 
8633
8758
  // src/shared/loadJson.ts
8634
8759
  import { existsSync as existsSync26, mkdirSync as mkdirSync11, readFileSync as readFileSync19, writeFileSync as writeFileSync20 } from "fs";
8635
- import { homedir as homedir11 } from "os";
8636
- import { join as join23 } from "path";
8760
+ import { homedir as homedir12 } from "os";
8761
+ import { join as join24 } from "path";
8637
8762
  function getStoreDir() {
8638
- return join23(homedir11(), ".assist");
8763
+ return join24(homedir12(), ".assist");
8639
8764
  }
8640
8765
  function getStorePath(filename) {
8641
- return join23(getStoreDir(), filename);
8766
+ return join24(getStoreDir(), filename);
8642
8767
  }
8643
8768
  function loadJson(filename) {
8644
8769
  const path71 = getStorePath(filename);
@@ -8709,10 +8834,10 @@ function hostedGroup(cwd, origin, clone) {
8709
8834
  // src/shared/createBundleHandler.ts
8710
8835
  import { createHash } from "crypto";
8711
8836
  import { readFileSync as readFileSync20, statSync as statSync3 } from "fs";
8712
- import { dirname as dirname20, join as join24 } from "path";
8837
+ import { dirname as dirname20, join as join25 } from "path";
8713
8838
  import { fileURLToPath as fileURLToPath4 } from "url";
8714
8839
  function createBundleHandler(importMetaUrl, bundlePath, contentType = "application/javascript") {
8715
- const file = join24(dirname20(fileURLToPath4(importMetaUrl)), bundlePath);
8840
+ const file = join25(dirname20(fileURLToPath4(importMetaUrl)), bundlePath);
8716
8841
  let cache4;
8717
8842
  return (req, res) => {
8718
8843
  const mtimeMs = statSync3(file).mtimeMs;
@@ -8849,12 +8974,12 @@ async function loadItemSummaries(orm, origin) {
8849
8974
  import { existsSync as existsSync27 } from "fs";
8850
8975
 
8851
8976
  // src/commands/backlog/cloneTargetDir.ts
8852
- import { join as join25, resolve as resolve9 } from "path";
8977
+ import { join as join26, resolve as resolve9 } from "path";
8853
8978
  function cloneTargetDir(origin, baseDir) {
8854
8979
  if (origin.startsWith("local:")) return null;
8855
8980
  const repoName = origin.split("/").filter(Boolean).pop();
8856
8981
  if (!repoName) return null;
8857
- return resolve9(join25(baseDir, repoName));
8982
+ return resolve9(join26(baseDir, repoName));
8858
8983
  }
8859
8984
 
8860
8985
  // src/commands/backlog/resolveRepoLocation.ts
@@ -9841,19 +9966,19 @@ function handleServerRuns(req, res) {
9841
9966
  // src/commands/sessions/web/getReviewSynthesis.ts
9842
9967
  import { execFile as execFile4 } from "child_process";
9843
9968
  import { readFileSync as readFileSync21 } from "fs";
9844
- import { homedir as homedir12 } from "os";
9845
- import { basename as basename8, join as join27 } from "path";
9969
+ import { homedir as homedir13 } from "os";
9970
+ import { basename as basename8, join as join28 } from "path";
9846
9971
  import { promisify as promisify3 } from "util";
9847
9972
 
9848
9973
  // src/commands/sessions/web/findSynthesisForBranch.ts
9849
9974
  import { existsSync as existsSync28, readdirSync as readdirSync2, statSync as statSync4 } from "fs";
9850
- import { basename as basename7, dirname as dirname21, join as join26 } from "path";
9975
+ import { basename as basename7, dirname as dirname21, join as join27 } from "path";
9851
9976
  function findSynthesisForBranch(repoReviewsDir, branch2) {
9852
- const branchKeyPath = join26(repoReviewsDir, `${branch2}-`);
9977
+ const branchKeyPath = join27(repoReviewsDir, `${branch2}-`);
9853
9978
  const parent = dirname21(branchKeyPath);
9854
9979
  const branchPrefix = basename7(branchKeyPath);
9855
9980
  if (!existsSync28(parent)) return null;
9856
- const synthesisFiles = readdirSync2(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join26(parent, name, "synthesis.md")).filter((path71) => existsSync28(path71)).map((path71) => ({ path: path71, mtime: statSync4(path71).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
9981
+ const synthesisFiles = readdirSync2(parent).filter((name) => name.startsWith(branchPrefix)).map((name) => join27(parent, name, "synthesis.md")).filter((path71) => existsSync28(path71)).map((path71) => ({ path: path71, mtime: statSync4(path71).mtimeMs })).sort((a, b) => b.mtime - a.mtime);
9857
9982
  return synthesisFiles[0]?.path ?? null;
9858
9983
  }
9859
9984
 
@@ -9870,8 +9995,8 @@ async function resolveSynthesisPath(cwd) {
9870
9995
  runGit(cwd, ["rev-parse", "--show-toplevel"]),
9871
9996
  runGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"])
9872
9997
  ]);
9873
- const repoReviewsDir = join27(
9874
- homedir12(),
9998
+ const repoReviewsDir = join28(
9999
+ homedir13(),
9875
10000
  ".assist",
9876
10001
  "reviews",
9877
10002
  basename8(repoRoot)
@@ -10013,65 +10138,6 @@ async function gitStatus(req, res) {
10013
10138
  }
10014
10139
  }
10015
10140
 
10016
- // src/shared/harnesses.ts
10017
- import * as os3 from "os";
10018
- import * as path25 from "path";
10019
-
10020
- // src/shared/checkCliAvailable.ts
10021
- import { execSync as execSync32 } from "child_process";
10022
- function checkCliAvailable(cli) {
10023
- const binary = cli.split(/\s+/)[0];
10024
- const opts = {
10025
- encoding: "utf8",
10026
- stdio: ["ignore", "pipe", "pipe"]
10027
- };
10028
- try {
10029
- execSync32(`command -v ${binary}`, opts);
10030
- return true;
10031
- } catch {
10032
- try {
10033
- execSync32(`where ${binary}`, opts);
10034
- return true;
10035
- } catch {
10036
- return false;
10037
- }
10038
- }
10039
- }
10040
-
10041
- // src/shared/harnesses.ts
10042
- var harnesses = {
10043
- claude: {
10044
- kind: "claude",
10045
- command: "claude",
10046
- homeDir: path25.join(os3.homedir(), ".claude"),
10047
- sync: {
10048
- agentsFile: "CLAUDE.md",
10049
- commandDest: (name) => path25.join("commands", `${name}.md`)
10050
- }
10051
- },
10052
- codex: {
10053
- kind: "codex",
10054
- command: "codex",
10055
- homeDir: path25.join(os3.homedir(), ".codex"),
10056
- sync: {
10057
- agentsFile: "AGENTS.md",
10058
- commandDest: (name) => path25.join("skills", name, "SKILL.md")
10059
- }
10060
- },
10061
- pi: {
10062
- kind: "pi",
10063
- command: "pi",
10064
- homeDir: path25.join(os3.homedir(), ".pi", "agent"),
10065
- sync: {
10066
- agentsFile: "AGENTS.md",
10067
- commandDest: (name) => path25.join("prompts", `${name}.md`)
10068
- }
10069
- }
10070
- };
10071
- function isHarnessAvailable(kind) {
10072
- return checkCliAvailable(harnesses[kind].command);
10073
- }
10074
-
10075
10141
  // src/commands/sessions/web/harnessCapabilities.ts
10076
10142
  function harnessCapabilities(_req, res) {
10077
10143
  const config = loadConfig().harness;
@@ -12989,33 +13055,6 @@ import enquirer7 from "enquirer";
12989
13055
  // src/commands/backlog/launchMode.ts
12990
13056
  import { randomUUID as randomUUID4 } from "crypto";
12991
13057
 
12992
- // src/shared/spawnCodex.ts
12993
- function spawnCodex(prompt, options2 = {}) {
12994
- const cwd = options2.cwd ?? process.cwd();
12995
- return spawnInherit(harnesses.codex.command, ["-C", cwd, prompt]);
12996
- }
12997
-
12998
- // src/shared/spawnPi.ts
12999
- function spawnPi(prompt, options2 = {}) {
13000
- const cwd = options2.cwd ?? process.cwd();
13001
- return spawnInherit(harnesses.pi.command, [prompt], { cwd });
13002
- }
13003
-
13004
- // src/shared/spawnHarness.ts
13005
- function spawnHarness(harness, prompt, options2 = {}) {
13006
- if (harness === "codex") {
13007
- return spawnCodex(prompt, { cwd: options2.cwd });
13008
- }
13009
- if (harness === "pi") {
13010
- return spawnPi(prompt, { cwd: options2.cwd });
13011
- }
13012
- return spawnClaude(prompt, {
13013
- allowEdits: true,
13014
- sessionId: options2.sessionId,
13015
- resumeSessionId: options2.resumeSessionId
13016
- });
13017
- }
13018
-
13019
13058
  // src/commands/backlog/handleLaunchSignal.ts
13020
13059
  import chalk86 from "chalk";
13021
13060
 
@@ -13217,7 +13256,10 @@ function registerRewindCommand(cmd) {
13217
13256
 
13218
13257
  // src/commands/backlog/registerRunCommand.ts
13219
13258
  function registerRunCommand(cmd) {
13220
- cmd.command("run <id>").description("Run a backlog item's plan phase-by-phase with Claude").option("-w, --write", "Run Claude with auto permission mode (default)").option("--no-write", "Run Claude without auto permission mode").option(
13259
+ cmd.command("run <id>").description("Run a backlog item's plan phase-by-phase").option("-w, --write", "Run the harness with write access (default)").option("--no-write", "Run the harness without write access").option(
13260
+ "--harness <harness>",
13261
+ "Coding harness to launch (claude|codex|pi); defaults to the configured harness.engine"
13262
+ ).option(
13221
13263
  "--resume-session <id>",
13222
13264
  "Resume an interrupted Claude session for the current phase (used by the sessions daemon on restart)"
13223
13265
  ).action(
@@ -13225,6 +13267,7 @@ function registerRunCommand(cmd) {
13225
13267
  if (!opts.resumeSession) pullIfConfigured();
13226
13268
  await run2(id, {
13227
13269
  allowEdits: opts.write !== false,
13270
+ harness: resolveHarness(opts.harness),
13228
13271
  resumeSessionId: opts.resumeSession
13229
13272
  });
13230
13273
  }
@@ -15740,16 +15783,14 @@ function parseInput(raw) {
15740
15783
  }
15741
15784
  }
15742
15785
  function preToolUseOutput(decision) {
15743
- if (decision.permissionDecision === "allow") {
15744
- return {
15745
- hookSpecificOutput: {
15746
- hookEventName: "PreToolUse",
15747
- permissionDecision: "allow",
15748
- permissionDecisionReason: decision.permissionDecisionReason
15749
- }
15750
- };
15751
- }
15752
- return { decision: "block", reason: decision.permissionDecisionReason };
15786
+ if (decision.permissionDecision === "allow") return void 0;
15787
+ return {
15788
+ hookSpecificOutput: {
15789
+ hookEventName: "PreToolUse",
15790
+ permissionDecision: "deny",
15791
+ permissionDecisionReason: decision.permissionDecisionReason
15792
+ }
15793
+ };
15753
15794
  }
15754
15795
  function permissionRequestOutput(decision) {
15755
15796
  return {
@@ -15768,7 +15809,7 @@ async function codexHook() {
15768
15809
  const decision = decideCommand(input.toolName, input.command);
15769
15810
  if (!decision) return;
15770
15811
  const output = input.event === "PermissionRequest" ? permissionRequestOutput(decision) : preToolUseOutput(decision);
15771
- console.log(JSON.stringify(output));
15812
+ if (output) console.log(JSON.stringify(output));
15772
15813
  }
15773
15814
 
15774
15815
  // src/commands/registerCodexHook.ts