prloom 0.1.2 → 0.1.3

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.
Files changed (2) hide show
  1. package/dist/cli/index.js +124 -15
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -7238,14 +7238,46 @@ var init_execa = __esm(() => {
7238
7238
  } = getIpcExport());
7239
7239
  });
7240
7240
 
7241
+ // src/lib/adapters/tmux.ts
7242
+ async function waitForTmuxSession(sessionName) {
7243
+ while (true) {
7244
+ const { exitCode } = await execa("tmux", ["has-session", "-t", sessionName], {
7245
+ reject: false
7246
+ });
7247
+ if (exitCode !== 0) {
7248
+ return;
7249
+ }
7250
+ await new Promise((resolve5) => setTimeout(resolve5, 1000));
7251
+ }
7252
+ }
7253
+ var init_tmux = __esm(() => {
7254
+ init_execa();
7255
+ });
7256
+
7241
7257
  // src/lib/adapters/codex.ts
7242
7258
  var codexAdapter;
7243
7259
  var init_codex = __esm(() => {
7244
7260
  init_execa();
7261
+ init_tmux();
7245
7262
  codexAdapter = {
7246
7263
  name: "codex",
7247
- async execute({ cwd, prompt }) {
7248
- const result = await execa("codex", ["exec", prompt, "--full-auto"], {
7264
+ async execute({ cwd, prompt, tmux }) {
7265
+ const args = ["exec", prompt, "--full-auto"];
7266
+ if (tmux) {
7267
+ await execa("tmux", [
7268
+ "new-session",
7269
+ "-d",
7270
+ "-s",
7271
+ tmux.sessionName,
7272
+ "-c",
7273
+ cwd,
7274
+ "codex",
7275
+ ...args
7276
+ ], { reject: false });
7277
+ await waitForTmuxSession(tmux.sessionName);
7278
+ return { exitCode: 0 };
7279
+ }
7280
+ const result = await execa("codex", args, {
7249
7281
  cwd,
7250
7282
  timeout: 0,
7251
7283
  reject: false
@@ -7266,9 +7298,25 @@ var init_codex = __esm(() => {
7266
7298
  var opencodeAdapter;
7267
7299
  var init_opencode = __esm(() => {
7268
7300
  init_execa();
7301
+ init_tmux();
7269
7302
  opencodeAdapter = {
7270
7303
  name: "opencode",
7271
- async execute({ cwd, prompt }) {
7304
+ async execute({ cwd, prompt, tmux }) {
7305
+ if (tmux) {
7306
+ await execa("tmux", [
7307
+ "new-session",
7308
+ "-d",
7309
+ "-s",
7310
+ tmux.sessionName,
7311
+ "-c",
7312
+ cwd,
7313
+ "opencode",
7314
+ "run",
7315
+ prompt
7316
+ ], { reject: false });
7317
+ await waitForTmuxSession(tmux.sessionName);
7318
+ return { exitCode: 0 };
7319
+ }
7272
7320
  const result = await execa("opencode", ["run", prompt], {
7273
7321
  cwd,
7274
7322
  timeout: 0,
@@ -7290,10 +7338,26 @@ var init_opencode = __esm(() => {
7290
7338
  var claudeAdapter;
7291
7339
  var init_claude = __esm(() => {
7292
7340
  init_execa();
7341
+ init_tmux();
7293
7342
  claudeAdapter = {
7294
7343
  name: "claude",
7295
- async execute({ cwd, prompt }) {
7296
- const result = await execa("claude", ["-p", prompt, "--dangerously-skip-permissions"], {
7344
+ async execute({ cwd, prompt, tmux }) {
7345
+ const args = ["-p", prompt, "--dangerously-skip-permissions"];
7346
+ if (tmux) {
7347
+ await execa("tmux", [
7348
+ "new-session",
7349
+ "-d",
7350
+ "-s",
7351
+ tmux.sessionName,
7352
+ "-c",
7353
+ cwd,
7354
+ "claude",
7355
+ ...args
7356
+ ], { reject: false });
7357
+ await waitForTmuxSession(tmux.sessionName);
7358
+ return { exitCode: 0 };
7359
+ }
7360
+ const result = await execa("claude", args, {
7297
7361
  cwd,
7298
7362
  timeout: 0,
7299
7363
  reject: false
@@ -7314,7 +7378,7 @@ var manualAdapter;
7314
7378
  var init_manual = __esm(() => {
7315
7379
  manualAdapter = {
7316
7380
  name: "manual",
7317
- async execute({ cwd, prompt }) {
7381
+ async execute({ cwd, prompt, tmux }) {
7318
7382
  console.log("⚠️ Manual agent: execute() called but should be skipped by dispatcher.");
7319
7383
  console.log(" This plan is intended for IDE-driven execution.");
7320
7384
  return { exitCode: 0 };
@@ -17582,7 +17646,7 @@ __export(exports_dispatcher, {
17582
17646
  });
17583
17647
  import { join as join9 } from "path";
17584
17648
  import { existsSync as existsSync9, statSync as statSync5 } from "fs";
17585
- async function runDispatcher(repoRoot) {
17649
+ async function runDispatcher(repoRoot, options2 = {}) {
17586
17650
  const config = loadConfig(repoRoot);
17587
17651
  const worktreesDir = resolveWorktreesDir(repoRoot, config);
17588
17652
  acquireLock(repoRoot);
@@ -17610,7 +17674,7 @@ async function runDispatcher(repoRoot) {
17610
17674
  await handleCommand2(state, cmd);
17611
17675
  }
17612
17676
  await ingestInboxPlans(repoRoot, worktreesDir, config, state);
17613
- await processActivePlans(repoRoot, config, state, botLogin);
17677
+ await processActivePlans(repoRoot, config, state, botLogin, options2);
17614
17678
  saveState(repoRoot, state);
17615
17679
  await sleepUntilIpcOrTimeout(repoRoot, state.control_cursor, config.poll_interval_ms);
17616
17680
  } catch (error) {
@@ -17667,7 +17731,7 @@ function getFeedbackPollDecision(opts) {
17667
17731
  shouldUpdateLastPolledAt: !pollOnce && shouldPoll
17668
17732
  };
17669
17733
  }
17670
- async function processActivePlans(repoRoot, config, state, botLogin) {
17734
+ async function processActivePlans(repoRoot, config, state, botLogin, options2 = {}) {
17671
17735
  for (const [planId, ps] of Object.entries(state.plans)) {
17672
17736
  try {
17673
17737
  const planPath = join9(ps.worktree, ps.planRelpath);
@@ -17702,7 +17766,7 @@ async function processActivePlans(repoRoot, config, state, botLogin) {
17702
17766
  if (newFeedback.length > 0) {
17703
17767
  console.log(`\uD83D\uDCAC ${newFeedback.length} new feedback for ${planId}`);
17704
17768
  if (!isManualAgent) {
17705
- await runTriage(repoRoot, config, ps, plan, newFeedback);
17769
+ await runTriage(repoRoot, config, ps, plan, newFeedback, options2);
17706
17770
  }
17707
17771
  plan = parsePlan(planPath);
17708
17772
  const maxIds = getMaxFeedbackIds(newFeedback);
@@ -17725,7 +17789,15 @@ async function processActivePlans(repoRoot, config, state, botLogin) {
17725
17789
  const prompt = renderWorkerPrompt(repoRoot, plan, todo);
17726
17790
  const agentName = plan.frontmatter.agent ?? config.agents.default;
17727
17791
  const adapter = getAdapter(agentName);
17728
- await adapter.execute({ cwd: ps.worktree, prompt });
17792
+ const tmuxConfig = options2.tmux ? { sessionName: `prloom-${planId}` } : undefined;
17793
+ if (tmuxConfig) {
17794
+ ps.tmuxSession = tmuxConfig.sessionName;
17795
+ console.log(` [spawned in tmux session: ${tmuxConfig.sessionName}]`);
17796
+ }
17797
+ await adapter.execute({ cwd: ps.worktree, prompt, tmux: tmuxConfig });
17798
+ if (tmuxConfig) {
17799
+ ps.tmuxSession = undefined;
17800
+ }
17729
17801
  const committed = await commitAll(ps.worktree, `[prloom] ${planId}: TODO #${todo.index}`);
17730
17802
  if (committed) {
17731
17803
  await push(ps.worktree, ps.branch);
@@ -17773,13 +17845,14 @@ async function pollNewFeedback(repoRoot, ps, botLogin) {
17773
17845
  };
17774
17846
  return filterNewFeedback(allFeedback, cursors, botLogin);
17775
17847
  }
17776
- async function runTriage(repoRoot, config, ps, plan, feedback) {
17848
+ async function runTriage(repoRoot, config, ps, plan, feedback, options2 = {}) {
17777
17849
  ensureWorktreePrloomDir(ps.worktree);
17778
17850
  const triageAgent = config.agents.designer ?? config.agents.default;
17779
17851
  const adapter = getAdapter(triageAgent);
17780
17852
  const prompt = renderTriagePrompt(repoRoot, plan, feedback);
17781
17853
  console.log(`\uD83D\uDD0D Running triage for ${plan.frontmatter.id}...`);
17782
- await adapter.execute({ cwd: ps.worktree, prompt });
17854
+ const tmuxConfig = options2.tmux ? { sessionName: `prloom-triage-${plan.frontmatter.id}` } : undefined;
17855
+ await adapter.execute({ cwd: ps.worktree, prompt, tmux: tmuxConfig });
17783
17856
  try {
17784
17857
  const result = readTriageResultFile(ps.worktree);
17785
17858
  if (result.rebase_requested) {
@@ -17993,6 +18066,35 @@ var init_open = __esm(() => {
17993
18066
  init_adapters();
17994
18067
  });
17995
18068
 
18069
+ // src/cli/watch.ts
18070
+ var exports_watch = {};
18071
+ __export(exports_watch, {
18072
+ runWatch: () => runWatch
18073
+ });
18074
+ async function runWatch(repoRoot, planId) {
18075
+ const state = loadState(repoRoot);
18076
+ const ps = state.plans[planId];
18077
+ if (!ps) {
18078
+ console.error(`Plan not found in state: ${planId}`);
18079
+ console.error("Make sure the plan has been dispatched at least once.");
18080
+ process.exit(1);
18081
+ }
18082
+ if (!ps.tmuxSession) {
18083
+ console.error(`No active tmux session for ${planId}`);
18084
+ console.error("This plan may not be running, or dispatcher wasn't started with --tmux");
18085
+ process.exit(1);
18086
+ }
18087
+ console.log(`Attaching to ${ps.tmuxSession} (read-only)...`);
18088
+ console.log("Press Ctrl+B D to detach without interrupting the worker.");
18089
+ await execa("tmux", ["attach", "-t", ps.tmuxSession, "-r"], {
18090
+ stdio: "inherit"
18091
+ });
18092
+ }
18093
+ var init_watch = __esm(() => {
18094
+ init_execa();
18095
+ init_state();
18096
+ });
18097
+
17996
18098
  // src/cli/logs.ts
17997
18099
  var exports_logs = {};
17998
18100
  __export(exports_logs, {
@@ -23508,9 +23610,13 @@ yargs_default(hideBin(process.argv)).scriptName("prloom").usage("$0 <command> [o
23508
23610
  }), async (argv) => {
23509
23611
  const { runEdit: runEdit2 } = await Promise.resolve().then(() => (init_edit(), exports_edit));
23510
23612
  await runEdit2(await getRepoRoot(), argv["plan-id"], argv.agent, argv["no-designer"]);
23511
- }).command("start", "Start the dispatcher", () => {}, async () => {
23613
+ }).command("start", "Start the dispatcher", (yargs) => yargs.option("tmux", {
23614
+ type: "boolean",
23615
+ describe: "Run workers in tmux sessions for observation",
23616
+ default: false
23617
+ }), async (argv) => {
23512
23618
  const { runDispatcher: runDispatcher2 } = await Promise.resolve().then(() => (init_dispatcher(), exports_dispatcher));
23513
- await runDispatcher2(await getRepoRoot());
23619
+ await runDispatcher2(await getRepoRoot(), { tmux: argv.tmux });
23514
23620
  }).command("status", "Show plan states", () => {}, async () => {
23515
23621
  const { runStatus: runStatus2 } = await Promise.resolve().then(() => (init_status(), exports_status));
23516
23622
  await runStatus2(await getRepoRoot());
@@ -23523,6 +23629,9 @@ yargs_default(hideBin(process.argv)).scriptName("prloom").usage("$0 <command> [o
23523
23629
  }).command("open <plan-id>", "Open TUI for manual work (requires paused)", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
23524
23630
  const { runOpen: runOpen2 } = await Promise.resolve().then(() => (init_open(), exports_open));
23525
23631
  await runOpen2(await getRepoRoot(), argv["plan-id"]);
23632
+ }).command("watch <plan-id>", "Observe a running worker (requires --tmux mode)", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
23633
+ const { runWatch: runWatch2 } = await Promise.resolve().then(() => (init_watch(), exports_watch));
23634
+ await runWatch2(await getRepoRoot(), argv["plan-id"]);
23526
23635
  }).command("logs <plan-id>", "Show session ID for a plan", (yargs) => yargs.positional("plan-id", { type: "string", demandOption: true }), async (argv) => {
23527
23636
  const { runLogs: runLogs2 } = await Promise.resolve().then(() => (init_logs(), exports_logs));
23528
23637
  await runLogs2(await getRepoRoot(), argv["plan-id"]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prloom",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "prloom": "./dist/cli/index.js"