@modootoday/pmcp 0.15.0 → 0.16.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.
@@ -546,7 +546,6 @@ var Connection = class {
546
546
  constructor(records3) {
547
547
  this.records = records3;
548
548
  }
549
- records;
550
549
  tmux(args, options = {}) {
551
550
  return command(
552
551
  "tmux",
@@ -693,8 +692,6 @@ var LaunchJournal = class {
693
692
  this.id = id2;
694
693
  runtimeUnit(id2);
695
694
  }
696
- records;
697
- id;
698
695
  path(kind) {
699
696
  if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind))
700
697
  throw new Error("invalid_launch_artifact");
@@ -453,7 +453,6 @@ var Connection = class {
453
453
  constructor(records2) {
454
454
  this.records = records2;
455
455
  }
456
- records;
457
456
  tmux(args, options = {}) {
458
457
  return command(
459
458
  "tmux",
@@ -706,8 +705,6 @@ var LaunchJournal = class {
706
705
  this.id = id2;
707
706
  runtimeUnit(id2);
708
707
  }
709
- records;
710
- id;
711
708
  path(kind) {
712
709
  if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind))
713
710
  throw new Error("invalid_launch_artifact");
@@ -1794,7 +1791,6 @@ var Backend = class {
1794
1791
  this.runDirectory = recordStore.directory;
1795
1792
  this.metadataPath = recordStore.metadataPath;
1796
1793
  }
1797
- recordStore;
1798
1794
  connection;
1799
1795
  owner;
1800
1796
  socket;
package/dist/http.js CHANGED
@@ -2294,7 +2294,7 @@ function registerMailboxTools(server, service) {
2294
2294
 
2295
2295
  // src/mcp.ts
2296
2296
  var SERVER_NAME = "pmcp";
2297
- var SERVER_VERSION = "0.15.0";
2297
+ var SERVER_VERSION = "0.16.0";
2298
2298
  function registerSkillTools(server, tools) {
2299
2299
  registerCatalogTool(server, tools);
2300
2300
  registerFindTool(server, tools);
package/dist/index.d.ts CHANGED
@@ -275,7 +275,7 @@ declare function resolveSkillFileUri(uri: string, entries: readonly SkillEntry[]
275
275
 
276
276
  /** Reported to the host on connect. */
277
277
  declare const SERVER_NAME = "pmcp";
278
- declare const SERVER_VERSION = "0.15.0";
278
+ declare const SERVER_VERSION = "0.16.0";
279
279
  /**
280
280
  * Registers the skill_* tools on a server.
281
281
  *
package/dist/index.js CHANGED
@@ -4679,7 +4679,7 @@ function registerMailboxTools(server, service) {
4679
4679
 
4680
4680
  // src/mcp.ts
4681
4681
  var SERVER_NAME = "pmcp";
4682
- var SERVER_VERSION = "0.15.0";
4682
+ var SERVER_VERSION = "0.16.0";
4683
4683
  function registerSkillTools(server, tools) {
4684
4684
  registerCatalogTool(server, tools);
4685
4685
  registerFindTool(server, tools);
@@ -144,8 +144,6 @@ var MailboxError = class extends Error {
144
144
  this.retryable = retryable;
145
145
  this.name = "MailboxError";
146
146
  }
147
- code;
148
- retryable;
149
147
  };
150
148
  function failure(error) {
151
149
  if (error instanceof MailboxError) {
@@ -1091,7 +1089,6 @@ var NotificationOutbox = class {
1091
1089
  constructor(store) {
1092
1090
  this.store = store;
1093
1091
  }
1094
- store;
1095
1092
  discover(binding, nativeTarget) {
1096
1093
  return this.store.run(() => {
1097
1094
  this.store.assertBinding(binding);
package/dist/stdio.js CHANGED
@@ -1941,7 +1941,7 @@ function registerMailboxTools(server, service) {
1941
1941
 
1942
1942
  // src/mcp.ts
1943
1943
  var SERVER_NAME = "pmcp";
1944
- var SERVER_VERSION = "0.15.0";
1944
+ var SERVER_VERSION = "0.16.0";
1945
1945
  function registerSkillTools(server, tools) {
1946
1946
  registerCatalogTool(server, tools);
1947
1947
  registerFindTool(server, tools);
@@ -2003,8 +2003,6 @@ var MailboxError = class extends Error {
2003
2003
  this.retryable = retryable;
2004
2004
  this.name = "MailboxError";
2005
2005
  }
2006
- code;
2007
- retryable;
2008
2006
  };
2009
2007
  function failure(error) {
2010
2008
  if (error instanceof MailboxError) {
@@ -64,6 +64,11 @@ var tuiOptions = [
64
64
  },
65
65
  { name: "rows", describe: "Terminal rows (12\u20131000)", placeholder: "<count>" },
66
66
  { name: "index", describe: "Worker dock index", placeholder: "<count>" },
67
+ {
68
+ name: "memory-mb",
69
+ describe: "Worker memory reservation",
70
+ placeholder: "<count>"
71
+ },
67
72
  {
68
73
  name: "confirm-session",
69
74
  describe: "Exact target ID for stop",
@@ -76,7 +81,7 @@ var tuiOptions = [
76
81
  },
77
82
  {
78
83
  name: "runtime",
79
- describe: "Main native runtime ID for launch",
84
+ describe: "Native runtime ID for main launch or worker start",
80
85
  placeholder: "<runtime>"
81
86
  },
82
87
  {
@@ -98,6 +103,8 @@ var tuiOptions = [
98
103
  var allowed = {
99
104
  launch: ["config", "runtime", "yes", "new"],
100
105
  plan: ["config", "runtime"],
106
+ recovery: ["config"],
107
+ "start-worker": ["config", "view", "runtime", "memory-mb"],
101
108
  doctor: [],
102
109
  create: ["group-file", "columns", "rows", "config", "actor-file"],
103
110
  inspect: ["view"],
@@ -115,6 +122,7 @@ var allowed = {
115
122
  close: ["view"],
116
123
  dismiss: ["view"],
117
124
  management: ["view"],
125
+ "worker-menu": ["view"],
118
126
  check: ["view"]
119
127
  };
120
128
  function parseTui(args) {
@@ -139,7 +147,23 @@ function parseTui(args) {
139
147
  return Number(value);
140
148
  };
141
149
  if (action === "doctor") return { action, input: {} };
142
- if (action === "launch" || action === "plan")
150
+ if (action === "start-worker") {
151
+ const config = one(args, "config");
152
+ const viewFile = one(args, "view");
153
+ if (config && viewFile)
154
+ throw new ArgumentError("Choose --config or --view for worker start");
155
+ return {
156
+ action,
157
+ viewFile,
158
+ input: {},
159
+ startup: {
160
+ config,
161
+ runtime: required("runtime"),
162
+ memoryMb: number("memory-mb")
163
+ }
164
+ };
165
+ }
166
+ if (action === "launch" || action === "plan" || action === "recovery")
143
167
  return {
144
168
  action,
145
169
  input: {},
@@ -887,7 +911,6 @@ var Connection = class {
887
911
  constructor(records2) {
888
912
  this.records = records2;
889
913
  }
890
- records;
891
914
  tmux(args, options = {}) {
892
915
  return command(
893
916
  "tmux",
@@ -1124,8 +1147,6 @@ var LaunchJournal = class {
1124
1147
  this.id = id2;
1125
1148
  runtimeUnit(id2);
1126
1149
  }
1127
- records;
1128
- id;
1129
1150
  path(kind) {
1130
1151
  if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind))
1131
1152
  throw new Error("invalid_launch_artifact");
@@ -2254,7 +2275,6 @@ var Backend = class {
2254
2275
  this.runDirectory = recordStore.directory;
2255
2276
  this.metadataPath = recordStore.metadataPath;
2256
2277
  }
2257
- recordStore;
2258
2278
  connection;
2259
2279
  owner;
2260
2280
  socket;
@@ -3393,7 +3413,6 @@ var CommonHarness = class {
3393
3413
  constructor(view) {
3394
3414
  this.view = view;
3395
3415
  }
3396
- view;
3397
3416
  async call(family, action, input = {}) {
3398
3417
  const group = new GroupStore(this.view.groupFile).state.group;
3399
3418
  if (group.owner !== this.view.groupOwner) throw new Error("foreign_group");
@@ -3427,13 +3446,19 @@ var CommonHarness = class {
3427
3446
  // src/tui/application/feedback.ts
3428
3447
  import { join as join20 } from "path";
3429
3448
  var hints = {
3430
- startup_requires_recovery: "Inspect the saved group with pmcp tui plan and pmcp harness recovery inspect. No runtime was restarted.",
3449
+ startup_requires_recovery: "Run pmcp tui recovery for saved ownership and exact inspection commands. No runtime was restarted.",
3431
3450
  startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
3432
3451
  startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
3433
3452
  startup_busy: "Another workspace launch is running. Wait and retry.",
3434
3453
  startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
3435
3454
  workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
3436
3455
  runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
3456
+ view_client_unavailable: "Open the view before using the worker menu, or run pmcp tui start-worker with --view and --runtime.",
3457
+ runtime_not_granted: "Choose a runtime granted by this owned group. Inspect the group before creating another workspace.",
3458
+ runtime_capacity_exceeded: "Stop an exact owned worker before adding another, or use the main session alone.",
3459
+ aggregate_memory_budget_exceeded: "Use fewer sessions or start a worker with an explicit smaller --memory-mb reservation.",
3460
+ group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
3461
+ startup_group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
3437
3462
  control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
3438
3463
  control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
3439
3464
  native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
@@ -3489,6 +3514,14 @@ function phaseLabel(phase2) {
3489
3514
  return labels[phase2];
3490
3515
  }
3491
3516
 
3517
+ // src/tui/presentation/target.ts
3518
+ function targetStatus(view) {
3519
+ const role2 = view.target === view.mainId ? "MAIN" : "WORKER";
3520
+ const phase2 = view.phase === "read-only" ? "OBSERVE" : phaseLabel(view.phase);
3521
+ const input = view.phase === "controlled" ? "INPUT ON" : "INPUT OFF";
3522
+ return `${role2} ${view.target.slice(0, 8)} | ${phase2} | ${input}`;
3523
+ }
3524
+
3492
3525
  // src/tui/adapters/tmux/connection.ts
3493
3526
  import { spawn as spawn2 } from "child_process";
3494
3527
  import { join as join21 } from "path";
@@ -3537,9 +3570,6 @@ var ViewConnection = class {
3537
3570
  this.identity = identity;
3538
3571
  this.socket = join21(directory, "s");
3539
3572
  }
3540
- directory;
3541
- owner;
3542
- identity;
3543
3573
  socket;
3544
3574
  raw(args) {
3545
3575
  const env = { ...process.env };
@@ -3642,7 +3672,8 @@ function bindKeys(connection, view, file) {
3642
3672
  ["b", "mailbox"],
3643
3673
  ["d", "detach"],
3644
3674
  ["!", "dismiss"],
3645
- ["g", "management"]
3675
+ ["g", "management"],
3676
+ ["n", "worker-menu"]
3646
3677
  ])
3647
3678
  connection.command([
3648
3679
  "bind-key",
@@ -3663,7 +3694,7 @@ function bindKeys(connection, view, file) {
3663
3694
  "bind-key",
3664
3695
  "?",
3665
3696
  "display-message",
3666
- "m main; w/1-4 worker; c control; r release; h history; b mailbox; d detach; ! dismiss error; double Ctrl-g forwards native Ctrl-g"
3697
+ "m main; n add worker; w/1-4 worker; c control; r release; h history; b mailbox; d detach; ! dismiss error; double Ctrl-g forwards native Ctrl-g"
3667
3698
  ]);
3668
3699
  connection.command([
3669
3700
  "set-hook",
@@ -3690,8 +3721,6 @@ var TmuxView = class {
3690
3721
  view.serverIdentity
3691
3722
  );
3692
3723
  }
3693
- view;
3694
- stateFile;
3695
3724
  connection;
3696
3725
  shell(argv2) {
3697
3726
  return `exec ${argv2.map(quote).join(" ")}`;
@@ -3863,7 +3892,7 @@ var TmuxView = class {
3863
3892
  "set-option",
3864
3893
  "-g",
3865
3894
  "status-right",
3866
- `${view.target === view.mainId ? "main" : "worker"} | ${phaseLabel(view.phase)}${failure2} | Ctrl-g ?`
3895
+ `${targetStatus(view)}${failure2} | Ctrl-g ?`
3867
3896
  ]);
3868
3897
  }
3869
3898
  input(pane, enabled) {
@@ -3886,6 +3915,32 @@ var TmuxView = class {
3886
3915
  clients() {
3887
3916
  return this.connection.command(["list-clients", "-F", "#{client_pid}"]);
3888
3917
  }
3918
+ workerMenu(view) {
3919
+ const pane = view.workerPane ?? view.mainPane;
3920
+ this.connection.assertOwned(pane);
3921
+ const popup = [
3922
+ "tmux",
3923
+ "-S",
3924
+ this.connection.socket,
3925
+ "display-popup",
3926
+ "-E",
3927
+ "-t",
3928
+ pane,
3929
+ "-w",
3930
+ "90%",
3931
+ "-h",
3932
+ "80%",
3933
+ this.shell([
3934
+ view.node,
3935
+ view.worker,
3936
+ "worker-menu",
3937
+ "--view",
3938
+ this.stateFile,
3939
+ "--callback"
3940
+ ])
3941
+ ];
3942
+ this.connection.command(["run-shell", "-b", popup.map(quote).join(" ")]);
3943
+ }
3889
3944
  copy(pane) {
3890
3945
  this.connection.assertOwned(pane);
3891
3946
  this.connection.command(["copy-mode", "-t", pane]);
@@ -3952,6 +4007,7 @@ function dockLines(snapshot) {
3952
4007
  ),
3953
4008
  "",
3954
4009
  "Ctrl-g w: observe worker",
4010
+ "Ctrl-g n: add worker",
3955
4011
  "Ctrl-g m: return to main",
3956
4012
  "Ctrl-g c: acquire control",
3957
4013
  "Ctrl-g r: release control",
@@ -3963,7 +4019,7 @@ function dockLines(snapshot) {
3963
4019
  ];
3964
4020
  }
3965
4021
  function coordinationLines(snapshot, view) {
3966
- const target = `TARGET ${view.target === view.mainId ? "main" : "worker"} ${safe(view.target.slice(0, 8))} / ${phaseLabel(view.phase)}`;
4022
+ const target = `TARGET ${safe(targetStatus(view))}`;
3967
4023
  if (view.failure)
3968
4024
  return [
3969
4025
  target,
@@ -4352,8 +4408,6 @@ var MailboxError = class extends Error {
4352
4408
  this.retryable = retryable;
4353
4409
  this.name = "MailboxError";
4354
4410
  }
4355
- code;
4356
- retryable;
4357
4411
  };
4358
4412
  function failure(error) {
4359
4413
  if (error instanceof MailboxError) {
@@ -5717,6 +5771,11 @@ var handlers = {
5717
5771
  close: closeView,
5718
5772
  check: checkControl,
5719
5773
  dismiss: ({ view, file }) => dismissFailure(file, view),
5774
+ "worker-menu": async (context) => {
5775
+ if (!context.renderer.clients()) throw new Error("view_client_unavailable");
5776
+ await releaseControl(context);
5777
+ context.renderer.workerMenu(context.view);
5778
+ },
5720
5779
  management: async (context) => {
5721
5780
  await releaseControl(context);
5722
5781
  if (context.view.managementWindow) {
@@ -5850,6 +5909,70 @@ function runtime() {
5850
5909
  return { node: realpathSync10(available.node), worker: bundled };
5851
5910
  }
5852
5911
 
5912
+ // src/tui/startup/diagnostics.ts
5913
+ function remediation(tools, userManagerAvailable) {
5914
+ const steps = [];
5915
+ if (tools.platform !== "linux")
5916
+ steps.push(
5917
+ "Use a qualified Linux host for the terminal workspace. Other PMCP CLI features do not require this host profile."
5918
+ );
5919
+ const missing = ["tmux", "flock", "systemctl", "systemdRun"].filter(
5920
+ (name) => !tools[name]
5921
+ );
5922
+ if (missing.length)
5923
+ steps.push(
5924
+ `Install the missing tmux, util-linux or systemd tools for your distribution and add them to PATH: ${missing.join(", ")}.`
5925
+ );
5926
+ if (!tools.nodeSupported)
5927
+ steps.push(
5928
+ "Select Node.js 22 or later through your existing version manager and check node --version in this terminal."
5929
+ );
5930
+ if (tools.supported && !userManagerAvailable)
5931
+ steps.push(
5932
+ "Check systemctl --user status in your login session and restore access to its user manager before launching. Doctor does not start or repair services."
5933
+ );
5934
+ return steps;
5935
+ }
5936
+ function inspectTerminalHost() {
5937
+ const tools = dependencies();
5938
+ let userManagerAvailable = false;
5939
+ let reason;
5940
+ try {
5941
+ if (!tools.supported) throw new Error("tui_dependencies_unavailable");
5942
+ assertNativeHost();
5943
+ userManagerAvailable = true;
5944
+ } catch (error) {
5945
+ reason = error instanceof Error ? error.message : "native_host_unavailable";
5946
+ }
5947
+ const runtimes = Object.entries(runtimeExecutables).map(
5948
+ ([id2, executable]) => {
5949
+ const path = locateExecutable(executable);
5950
+ return {
5951
+ id: id2,
5952
+ executable,
5953
+ installed: Boolean(path),
5954
+ ...path ? { path } : {},
5955
+ nativeAcceptance: "not-assessed",
5956
+ authentication: "not-inspected"
5957
+ };
5958
+ }
5959
+ );
5960
+ return {
5961
+ schemaVersion: 1,
5962
+ ok: true,
5963
+ ...tools,
5964
+ supported: tools.supported && userManagerAvailable,
5965
+ userManagerAvailable,
5966
+ ...reason ? { reason } : {},
5967
+ runtimes,
5968
+ qualification: "Prerequisite availability only; native behavior and cgroup delegation require separate qualification.",
5969
+ remediation: remediation(tools, userManagerAvailable),
5970
+ nextCommands: ["pmcp tui plan", "pmcp tui recovery"],
5971
+ providerInvoked: false,
5972
+ authenticationInspected: false
5973
+ };
5974
+ }
5975
+
5853
5976
  // src/tui/application/create.ts
5854
5977
  import { join as join29 } from "path";
5855
5978
  import { realpathSync as realpathSync11 } from "fs";
@@ -5928,10 +6051,9 @@ async function createView(input) {
5928
6051
 
5929
6052
  // src/tui/application/invoke.ts
5930
6053
  async function invokeTui(invocation) {
5931
- if (invocation.action === "launch" || invocation.action === "plan")
6054
+ if (["launch", "plan", "recovery", "start-worker"].includes(invocation.action))
5932
6055
  throw new Error("startup_requires_cli_context");
5933
- if (invocation.action === "doctor")
5934
- return { schemaVersion: 1, ok: true, ...dependencies() };
6056
+ if (invocation.action === "doctor") return inspectTerminalHost();
5935
6057
  if (invocation.action === "create") return createView(invocation.create);
5936
6058
  if (invocation.action === "inspect")
5937
6059
  return operate("inspect", invocation.viewFile);
@@ -6206,6 +6328,115 @@ function assertGroup(group, projectRoot, generation) {
6206
6328
  throw new Error("stale_group_generation");
6207
6329
  }
6208
6330
 
6331
+ // src/tui/application/worker-start.ts
6332
+ async function startViewWorker(file, runtime2, memoryMb) {
6333
+ const { view, common } = openView(file);
6334
+ if (view.state !== "open") throw new Error("view_closed");
6335
+ const observed = await common.snapshot();
6336
+ if (observed.group.state !== "ready") throw new Error("group_not_ready");
6337
+ const main = observed.sessions.find(
6338
+ (session) => session.id === view.mainId && session.role === "main"
6339
+ );
6340
+ if (!main?.alive) throw new Error("startup_main_unavailable");
6341
+ return common.call("session", "start", {
6342
+ input: { role: "worker", runtime: runtime2, memoryMb: memoryMb ?? main.memoryMb }
6343
+ });
6344
+ }
6345
+
6346
+ // src/cli/entry.ts
6347
+ function interactiveEntry(terminal) {
6348
+ if (!terminal.stdin || !terminal.stdout) return false;
6349
+ if (!terminal.term || terminal.term === "dumb") return false;
6350
+ return !terminal.ci || terminal.ci === "false" || terminal.ci === "0";
6351
+ }
6352
+
6353
+ // src/tui/startup/host.ts
6354
+ function terminalSupport() {
6355
+ try {
6356
+ if (!dependencies().supported)
6357
+ throw new Error("tui_dependencies_unavailable");
6358
+ assertNativeHost();
6359
+ planLayout(process.stdout.columns ?? 0, process.stdout.rows ?? 0);
6360
+ return { supported: true };
6361
+ } catch (error) {
6362
+ return {
6363
+ supported: false,
6364
+ reason: error instanceof Error ? error.message : "terminal_unavailable"
6365
+ };
6366
+ }
6367
+ }
6368
+ function requireTerminal() {
6369
+ if (!interactiveEntry({
6370
+ stdin: process.stdin.isTTY === true,
6371
+ stdout: process.stdout.isTTY === true,
6372
+ term: process.env.TERM,
6373
+ ci: process.env.CI
6374
+ }))
6375
+ throw new Error("terminal_required");
6376
+ const support = terminalSupport();
6377
+ if (!support.supported) throw new Error(support.reason);
6378
+ }
6379
+
6380
+ // src/tui/presentation/runtime-choice.ts
6381
+ import { createInterface } from "readline/promises";
6382
+ async function chooseRuntime(prompt, choices, preferred) {
6383
+ if (!choices.length) throw new Error("runtime_executable_unavailable");
6384
+ const fallback = Math.max(choices.indexOf(preferred), 0);
6385
+ process.stderr.write(
6386
+ `${choices.map((runtime2, index) => `${index + 1}. ${runtime2}`).join("\n")}
6387
+ `
6388
+ );
6389
+ while (true) {
6390
+ const answer = (await prompt.question(
6391
+ `Runtime [1-${choices.length}, Enter=${fallback + 1}, q=cancel] `
6392
+ )).trim();
6393
+ if (answer === "q") return void 0;
6394
+ if (answer === "") return choices[fallback];
6395
+ if (!/^\d+$/.test(answer)) continue;
6396
+ const index = Number(answer) - 1;
6397
+ if (Number.isSafeInteger(index) && index >= 0 && index < choices.length)
6398
+ return choices[index];
6399
+ }
6400
+ }
6401
+ async function confirmRuntimeStart(prompt, runtime2) {
6402
+ const answer = await prompt.question(
6403
+ `Start ${runtime2}? [Enter=start, q=cancel] `
6404
+ );
6405
+ return answer.trim() === "";
6406
+ }
6407
+ function terminalPrompt() {
6408
+ return createInterface({ input: process.stdin, output: process.stderr });
6409
+ }
6410
+
6411
+ // src/tui/presentation/worker-menu.ts
6412
+ async function runWorkerMenu(file) {
6413
+ requireTerminal();
6414
+ const { view, common } = openView(file);
6415
+ if (view.state !== "open") throw new Error("view_closed");
6416
+ const observed = await common.snapshot();
6417
+ const group = observed.group;
6418
+ if (group.state !== "ready") throw new Error("startup_group_not_ready");
6419
+ const choices = group.allowedRuntimes.filter((runtime2) => {
6420
+ const executable = runtimeExecutables[runtime2];
6421
+ if (!executable) return false;
6422
+ if (group.profile === "docker")
6423
+ return Boolean(group.dockerPolicy?.commands[runtime2]);
6424
+ return Boolean(locateExecutable(executable, group.projectRoot));
6425
+ });
6426
+ const main = observed.sessions.find((session) => session.id === view.mainId);
6427
+ if (!main?.alive) throw new Error("startup_main_unavailable");
6428
+ process.stderr.write(`PMCP WORKER | ${group.projectRoot}
6429
+ `);
6430
+ const prompt = terminalPrompt();
6431
+ try {
6432
+ const runtime2 = await chooseRuntime(prompt, choices, main.runtime);
6433
+ if (!runtime2 || !await confirmRuntimeStart(prompt, runtime2)) return;
6434
+ await startViewWorker(file, runtime2);
6435
+ } finally {
6436
+ prompt.close();
6437
+ }
6438
+ }
6439
+
6209
6440
  // src/tui/application/worker.ts
6210
6441
  var [role, ...argv] = process.argv.slice(2);
6211
6442
  var callback2 = argv.includes("--callback");
@@ -6222,6 +6453,14 @@ try {
6222
6453
  const result = await prepareWorkspace(request2);
6223
6454
  process.stdout.write(`${JSON.stringify(result)}
6224
6455
  `);
6456
+ } else if (role === "worker-menu") {
6457
+ const invocation = parseTui(
6458
+ parseArgs(
6459
+ ["worker-menu", ...argv.filter((arg) => arg !== "--callback")],
6460
+ tuiOptions
6461
+ )
6462
+ );
6463
+ await runWorkerMenu(invocation.viewFile);
6225
6464
  } else if (role === "attach") {
6226
6465
  const [file, sessionId, leaseFile] = argv;
6227
6466
  if (!file || !sessionId) throw new Error("invalid_attachment_arguments");
@@ -6254,7 +6493,14 @@ try {
6254
6493
  tuiOptions
6255
6494
  )
6256
6495
  );
6257
- if (["create", "doctor", "launch", "plan"].includes(invocation.action))
6496
+ if ([
6497
+ "create",
6498
+ "doctor",
6499
+ "launch",
6500
+ "plan",
6501
+ "recovery",
6502
+ "start-worker"
6503
+ ].includes(invocation.action))
6258
6504
  throw new Error("unsupported_worker_operation");
6259
6505
  const result = role === "locked" ? await operate(
6260
6506
  invocation.action,
@@ -6273,7 +6519,8 @@ try {
6273
6519
  const file = argv[index + 1];
6274
6520
  if (index >= 0 && file) {
6275
6521
  try {
6276
- recordCallbackFailure(file, argv[0] ?? "callback", error);
6522
+ const action = role === "worker-menu" ? role : argv[0] ?? "callback";
6523
+ recordCallbackFailure(file, action, error);
6277
6524
  const { view } = readView(file);
6278
6525
  if (view.state === "open") new TmuxView(view, file).status(view);
6279
6526
  } catch {