@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.
package/dist/cli.js CHANGED
@@ -471,8 +471,6 @@ var init_types = __esm({
471
471
  this.retryable = retryable;
472
472
  this.name = "MailboxError";
473
473
  }
474
- code;
475
- retryable;
476
474
  };
477
475
  }
478
476
  });
@@ -1837,11 +1835,11 @@ function dockerPolicy(value) {
1837
1835
  for (const [runtime2, value2] of entries) {
1838
1836
  if (!Object.hasOwn(runtimeExecutables, runtime2))
1839
1837
  throw new Error("unsupported_docker_runtime");
1840
- const command2 = strings2(value2, "docker_runtime_command");
1841
- const executable = command2[0];
1842
- if (!isAbsolute10(executable) || posix5.normalize(executable) !== executable || command2.some((argument) => /[\r\n\x1b]/.test(argument)))
1838
+ const command3 = strings2(value2, "docker_runtime_command");
1839
+ const executable = command3[0];
1840
+ if (!isAbsolute10(executable) || posix5.normalize(executable) !== executable || command3.some((argument) => /[\r\n\x1b]/.test(argument)))
1843
1841
  throw new Error("invalid_docker_runtime_command");
1844
- parsed[runtime2] = command2;
1842
+ parsed[runtime2] = command3;
1845
1843
  }
1846
1844
  return {
1847
1845
  schemaVersion: 1,
@@ -2303,7 +2301,6 @@ var init_connection = __esm({
2303
2301
  constructor(records2) {
2304
2302
  this.records = records2;
2305
2303
  }
2306
- records;
2307
2304
  tmux(args, options2 = {}) {
2308
2305
  return command(
2309
2306
  "tmux",
@@ -2581,8 +2578,6 @@ var init_journal = __esm({
2581
2578
  this.id = id2;
2582
2579
  runtimeUnit(id2);
2583
2580
  }
2584
- records;
2585
- id;
2586
2581
  path(kind2) {
2587
2582
  if (!["intent", "proof", "grant", "committed", "aborted"].includes(kind2))
2588
2583
  throw new Error("invalid_launch_artifact");
@@ -2962,14 +2957,14 @@ function bridgeName(nonce) {
2962
2957
  function containerEntrypoint(policy, runtime2, nonce, privateProfile) {
2963
2958
  const native2 = policy.commands[runtime2];
2964
2959
  if (!native2) throw new Error("docker_runtime_command_not_granted");
2965
- const command2 = policy.egress?.bridgeExecutable ? [
2960
+ const command3 = policy.egress?.bridgeExecutable ? [
2966
2961
  policy.egress.bridgeExecutable,
2967
2962
  `/state/${bridgeName(nonce)}`,
2968
2963
  "--",
2969
2964
  ...native2
2970
2965
  ] : native2;
2971
2966
  const profile = selectedProfile(policy, privateProfile, runtime2);
2972
- if (!profile) return command2;
2967
+ if (!profile) return command3;
2973
2968
  return [
2974
2969
  profile.lockExecutable,
2975
2970
  "--exclusive",
@@ -2978,7 +2973,7 @@ function containerEntrypoint(policy, runtime2, nonce, privateProfile) {
2978
2973
  "--conflict-exit-code",
2979
2974
  "73",
2980
2975
  `/state/${privateProfileLock}`,
2981
- ...command2
2976
+ ...command3
2982
2977
  ];
2983
2978
  }
2984
2979
  var init_entrypoint = __esm({
@@ -3867,7 +3862,6 @@ var init_backend = __esm({
3867
3862
  this.runDirectory = recordStore.directory;
3868
3863
  this.metadataPath = recordStore.metadataPath;
3869
3864
  }
3870
- recordStore;
3871
3865
  connection;
3872
3866
  owner;
3873
3867
  socket;
@@ -5254,6 +5248,79 @@ var init_runtime2 = __esm({
5254
5248
  }
5255
5249
  });
5256
5250
 
5251
+ // src/tui/startup/diagnostics.ts
5252
+ function remediation(tools, userManagerAvailable) {
5253
+ const steps = [];
5254
+ if (tools.platform !== "linux")
5255
+ steps.push(
5256
+ "Use a qualified Linux host for the terminal workspace. Other PMCP CLI features do not require this host profile."
5257
+ );
5258
+ const missing = ["tmux", "flock", "systemctl", "systemdRun"].filter(
5259
+ (name) => !tools[name]
5260
+ );
5261
+ if (missing.length)
5262
+ steps.push(
5263
+ `Install the missing tmux, util-linux or systemd tools for your distribution and add them to PATH: ${missing.join(", ")}.`
5264
+ );
5265
+ if (!tools.nodeSupported)
5266
+ steps.push(
5267
+ "Select Node.js 22 or later through your existing version manager and check node --version in this terminal."
5268
+ );
5269
+ if (tools.supported && !userManagerAvailable)
5270
+ steps.push(
5271
+ "Check systemctl --user status in your login session and restore access to its user manager before launching. Doctor does not start or repair services."
5272
+ );
5273
+ return steps;
5274
+ }
5275
+ function inspectTerminalHost() {
5276
+ const tools = dependencies();
5277
+ let userManagerAvailable = false;
5278
+ let reason;
5279
+ try {
5280
+ if (!tools.supported) throw new Error("tui_dependencies_unavailable");
5281
+ assertNativeHost();
5282
+ userManagerAvailable = true;
5283
+ } catch (error) {
5284
+ reason = error instanceof Error ? error.message : "native_host_unavailable";
5285
+ }
5286
+ const runtimes = Object.entries(runtimeExecutables).map(
5287
+ ([id2, executable]) => {
5288
+ const path = locateExecutable(executable);
5289
+ return {
5290
+ id: id2,
5291
+ executable,
5292
+ installed: Boolean(path),
5293
+ ...path ? { path } : {},
5294
+ nativeAcceptance: "not-assessed",
5295
+ authentication: "not-inspected"
5296
+ };
5297
+ }
5298
+ );
5299
+ return {
5300
+ schemaVersion: 1,
5301
+ ok: true,
5302
+ ...tools,
5303
+ supported: tools.supported && userManagerAvailable,
5304
+ userManagerAvailable,
5305
+ ...reason ? { reason } : {},
5306
+ runtimes,
5307
+ qualification: "Prerequisite availability only; native behavior and cgroup delegation require separate qualification.",
5308
+ remediation: remediation(tools, userManagerAvailable),
5309
+ nextCommands: ["pmcp tui plan", "pmcp tui recovery"],
5310
+ providerInvoked: false,
5311
+ authenticationInspected: false
5312
+ };
5313
+ }
5314
+ var init_diagnostics = __esm({
5315
+ "src/tui/startup/diagnostics.ts"() {
5316
+ "use strict";
5317
+ init_runtime2();
5318
+ init_admission2();
5319
+ init_registry();
5320
+ init_executable();
5321
+ }
5322
+ });
5323
+
5257
5324
  // src/tui/state/store.ts
5258
5325
  import { randomUUID as randomUUID13 } from "crypto";
5259
5326
  import {
@@ -5473,9 +5540,6 @@ var init_connection2 = __esm({
5473
5540
  this.identity = identity;
5474
5541
  this.socket = join49(directory, "s");
5475
5542
  }
5476
- directory;
5477
- owner;
5478
- identity;
5479
5543
  socket;
5480
5544
  raw(args) {
5481
5545
  const env = { ...process.env };
@@ -5592,13 +5656,19 @@ var init_feedback = __esm({
5592
5656
  init_store3();
5593
5657
  init_store2();
5594
5658
  hints = {
5595
- startup_requires_recovery: "Inspect the saved group with pmcp tui plan and pmcp harness recovery inspect. No runtime was restarted.",
5659
+ startup_requires_recovery: "Run pmcp tui recovery for saved ownership and exact inspection commands. No runtime was restarted.",
5596
5660
  startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
5597
5661
  startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
5598
5662
  startup_busy: "Another workspace launch is running. Wait and retry.",
5599
5663
  startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
5600
5664
  workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
5601
5665
  runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
5666
+ view_client_unavailable: "Open the view before using the worker menu, or run pmcp tui start-worker with --view and --runtime.",
5667
+ runtime_not_granted: "Choose a runtime granted by this owned group. Inspect the group before creating another workspace.",
5668
+ runtime_capacity_exceeded: "Stop an exact owned worker before adding another, or use the main session alone.",
5669
+ aggregate_memory_budget_exceeded: "Use fewer sessions or start a worker with an explicit smaller --memory-mb reservation.",
5670
+ group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
5671
+ startup_group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
5602
5672
  control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
5603
5673
  control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
5604
5674
  native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
@@ -5619,6 +5689,20 @@ var init_feedback = __esm({
5619
5689
  }
5620
5690
  });
5621
5691
 
5692
+ // src/tui/presentation/target.ts
5693
+ function targetStatus(view) {
5694
+ const role = view.target === view.mainId ? "MAIN" : "WORKER";
5695
+ const phase2 = view.phase === "read-only" ? "OBSERVE" : phaseLabel(view.phase);
5696
+ const input = view.phase === "controlled" ? "INPUT ON" : "INPUT OFF";
5697
+ return `${role} ${view.target.slice(0, 8)} | ${phase2} | ${input}`;
5698
+ }
5699
+ var init_target = __esm({
5700
+ "src/tui/presentation/target.ts"() {
5701
+ "use strict";
5702
+ init_feedback();
5703
+ }
5704
+ });
5705
+
5622
5706
  // src/tui/adapters/tmux/bindings.ts
5623
5707
  function callback(view, file2, action, args = []) {
5624
5708
  return [
@@ -5645,7 +5729,8 @@ function bindKeys(connection, view, file2) {
5645
5729
  ["b", "mailbox"],
5646
5730
  ["d", "detach"],
5647
5731
  ["!", "dismiss"],
5648
- ["g", "management"]
5732
+ ["g", "management"],
5733
+ ["n", "worker-menu"]
5649
5734
  ])
5650
5735
  connection.command([
5651
5736
  "bind-key",
@@ -5666,7 +5751,7 @@ function bindKeys(connection, view, file2) {
5666
5751
  "bind-key",
5667
5752
  "?",
5668
5753
  "display-message",
5669
- "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"
5754
+ "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"
5670
5755
  ]);
5671
5756
  connection.command([
5672
5757
  "set-hook",
@@ -5694,7 +5779,7 @@ var init_view = __esm({
5694
5779
  "src/tui/adapters/tmux/view.ts"() {
5695
5780
  "use strict";
5696
5781
  init_command();
5697
- init_feedback();
5782
+ init_target();
5698
5783
  init_connection2();
5699
5784
  init_bindings();
5700
5785
  TmuxView = class {
@@ -5707,8 +5792,6 @@ var init_view = __esm({
5707
5792
  view.serverIdentity
5708
5793
  );
5709
5794
  }
5710
- view;
5711
- stateFile;
5712
5795
  connection;
5713
5796
  shell(argv) {
5714
5797
  return `exec ${argv.map(quote2).join(" ")}`;
@@ -5880,7 +5963,7 @@ var init_view = __esm({
5880
5963
  "set-option",
5881
5964
  "-g",
5882
5965
  "status-right",
5883
- `${view.target === view.mainId ? "main" : "worker"} | ${phaseLabel(view.phase)}${failure2} | Ctrl-g ?`
5966
+ `${targetStatus(view)}${failure2} | Ctrl-g ?`
5884
5967
  ]);
5885
5968
  }
5886
5969
  input(pane, enabled) {
@@ -5903,6 +5986,32 @@ var init_view = __esm({
5903
5986
  clients() {
5904
5987
  return this.connection.command(["list-clients", "-F", "#{client_pid}"]);
5905
5988
  }
5989
+ workerMenu(view) {
5990
+ const pane = view.workerPane ?? view.mainPane;
5991
+ this.connection.assertOwned(pane);
5992
+ const popup = [
5993
+ "tmux",
5994
+ "-S",
5995
+ this.connection.socket,
5996
+ "display-popup",
5997
+ "-E",
5998
+ "-t",
5999
+ pane,
6000
+ "-w",
6001
+ "90%",
6002
+ "-h",
6003
+ "80%",
6004
+ this.shell([
6005
+ view.node,
6006
+ view.worker,
6007
+ "worker-menu",
6008
+ "--view",
6009
+ this.stateFile,
6010
+ "--callback"
6011
+ ])
6012
+ ];
6013
+ this.connection.command(["run-shell", "-b", popup.map(quote2).join(" ")]);
6014
+ }
5906
6015
  copy(pane) {
5907
6016
  this.connection.assertOwned(pane);
5908
6017
  this.connection.command(["copy-mode", "-t", pane]);
@@ -5926,7 +6035,6 @@ var init_common = __esm({
5926
6035
  constructor(view) {
5927
6036
  this.view = view;
5928
6037
  }
5929
- view;
5930
6038
  async call(family, action, input = {}) {
5931
6039
  const group = new GroupStore(this.view.groupFile).state.group;
5932
6040
  if (group.owner !== this.view.groupOwner) throw new Error("foreign_group");
@@ -6465,6 +6573,11 @@ var init_operations = __esm({
6465
6573
  close: closeView,
6466
6574
  check: checkControl,
6467
6575
  dismiss: ({ view, file: file2 }) => dismissFailure(file2, view),
6576
+ "worker-menu": async (context) => {
6577
+ if (!context.renderer.clients()) throw new Error("view_client_unavailable");
6578
+ await releaseControl(context);
6579
+ context.renderer.workerMenu(context.view);
6580
+ },
6468
6581
  management: async (context) => {
6469
6582
  await releaseControl(context);
6470
6583
  if (context.view.managementWindow) {
@@ -6488,10 +6601,9 @@ __export(invoke_exports2, {
6488
6601
  invokeTui: () => invokeTui
6489
6602
  });
6490
6603
  async function invokeTui(invocation2) {
6491
- if (invocation2.action === "launch" || invocation2.action === "plan")
6604
+ if (["launch", "plan", "recovery", "start-worker"].includes(invocation2.action))
6492
6605
  throw new Error("startup_requires_cli_context");
6493
- if (invocation2.action === "doctor")
6494
- return { schemaVersion: 1, ok: true, ...dependencies() };
6606
+ if (invocation2.action === "doctor") return inspectTerminalHost();
6495
6607
  if (invocation2.action === "create") return createView(invocation2.create);
6496
6608
  if (invocation2.action === "inspect")
6497
6609
  return operate("inspect", invocation2.viewFile);
@@ -6529,7 +6641,7 @@ function failedInvocation(action, error) {
6529
6641
  var init_invoke2 = __esm({
6530
6642
  "src/tui/application/invoke.ts"() {
6531
6643
  "use strict";
6532
- init_runtime2();
6644
+ init_diagnostics();
6533
6645
  init_lock();
6534
6646
  init_create();
6535
6647
  init_operations();
@@ -6538,6 +6650,31 @@ var init_invoke2 = __esm({
6538
6650
  }
6539
6651
  });
6540
6652
 
6653
+ // src/tui/application/worker-start.ts
6654
+ var worker_start_exports = {};
6655
+ __export(worker_start_exports, {
6656
+ startViewWorker: () => startViewWorker
6657
+ });
6658
+ async function startViewWorker(file2, runtime2, memoryMb) {
6659
+ const { view, common: common2 } = openView(file2);
6660
+ if (view.state !== "open") throw new Error("view_closed");
6661
+ const observed = await common2.snapshot();
6662
+ if (observed.group.state !== "ready") throw new Error("group_not_ready");
6663
+ const main2 = observed.sessions.find(
6664
+ (session) => session.id === view.mainId && session.role === "main"
6665
+ );
6666
+ if (!main2?.alive) throw new Error("startup_main_unavailable");
6667
+ return common2.call("session", "start", {
6668
+ input: { role: "worker", runtime: runtime2, memoryMb: memoryMb ?? main2.memoryMb }
6669
+ });
6670
+ }
6671
+ var init_worker_start = __esm({
6672
+ "src/tui/application/worker-start.ts"() {
6673
+ "use strict";
6674
+ init_context2();
6675
+ }
6676
+ });
6677
+
6541
6678
  // src/tui/startup/config.ts
6542
6679
  import { realpathSync as realpathSync17 } from "fs";
6543
6680
  import { resolve as resolve19 } from "path";
@@ -6664,6 +6801,160 @@ var init_state = __esm({
6664
6801
  }
6665
6802
  });
6666
6803
 
6804
+ // src/tui/startup/worker.ts
6805
+ var worker_exports = {};
6806
+ __export(worker_exports, {
6807
+ startWorkspaceWorker: () => startWorkspaceWorker
6808
+ });
6809
+ async function startWorkspaceWorker(request3, runtime2, memoryMb) {
6810
+ if (!Object.hasOwn(runtimeExecutables, runtime2))
6811
+ throw new Error("unsupported_runtime");
6812
+ const settings2 = startupSettings({
6813
+ cwd: request3.cwd,
6814
+ config: request3.config
6815
+ });
6816
+ const state = readStartup(
6817
+ startupFile(settings2.projectRoot),
6818
+ settings2.projectRoot
6819
+ );
6820
+ if (!state || state.phase !== "ready" || !state.groupFile || !state.generation)
6821
+ throw new Error("startup_requires_recovery");
6822
+ const store = new GroupStore(state.groupFile);
6823
+ const group = store.state.group;
6824
+ if (group.projectRoot !== settings2.projectRoot)
6825
+ throw new Error("startup_project_mismatch");
6826
+ if (group.generation !== state.generation)
6827
+ throw new Error("stale_group_generation");
6828
+ if (group.state === "paused") throw new Error("startup_group_paused");
6829
+ if (group.state !== "ready") throw new Error("startup_group_not_ready");
6830
+ if (group.profile === "native" && !locateExecutable(runtimeExecutables[runtime2], request3.cwd))
6831
+ throw new Error("runtime_executable_unavailable");
6832
+ const observed = await invoke({
6833
+ family: "session",
6834
+ action: "list",
6835
+ groupFile: store.path
6836
+ });
6837
+ if (observed.ok !== true) return observed;
6838
+ const sessions = observed.sessions;
6839
+ if (!sessions.some((session) => session.role === "main" && session.alive))
6840
+ throw new Error("startup_main_unavailable");
6841
+ return invoke({
6842
+ family: "session",
6843
+ action: "start",
6844
+ groupFile: store.path,
6845
+ generation: group.generation,
6846
+ input: {
6847
+ runtime: runtime2,
6848
+ role: "worker",
6849
+ memoryMb: memoryMb ?? settings2.sessionMemoryMb
6850
+ }
6851
+ });
6852
+ }
6853
+ var init_worker = __esm({
6854
+ "src/tui/startup/worker.ts"() {
6855
+ "use strict";
6856
+ init_invoke();
6857
+ init_store2();
6858
+ init_registry();
6859
+ init_executable();
6860
+ init_config3();
6861
+ init_state();
6862
+ }
6863
+ });
6864
+
6865
+ // src/tui/startup/recovery.ts
6866
+ var recovery_exports = {};
6867
+ __export(recovery_exports, {
6868
+ inspectStartupRecovery: () => inspectStartupRecovery
6869
+ });
6870
+ function command2(args) {
6871
+ return ["pmcp", ...args].map(quote2).join(" ");
6872
+ }
6873
+ function inspectStartupRecovery(request3) {
6874
+ const settings2 = startupSettings(request3);
6875
+ const checkpoint = startupFile(settings2.projectRoot);
6876
+ const state = readStartup(checkpoint, settings2.projectRoot);
6877
+ const config = settings2.configFile ? ["--config", settings2.configFile] : [];
6878
+ const common2 = {
6879
+ schemaVersion: 1,
6880
+ ok: true,
6881
+ checkpoint,
6882
+ projectRoot: settings2.projectRoot,
6883
+ providerInvoked: false,
6884
+ runtimeRestarted: false,
6885
+ inputReplayed: false,
6886
+ observedNativeProcesses: false
6887
+ };
6888
+ if (!state)
6889
+ return {
6890
+ ...common2,
6891
+ status: "not-started",
6892
+ hint: "No saved workspace exists. Start explicitly from an interactive terminal.",
6893
+ commands: [command2(["tui", ...config])]
6894
+ };
6895
+ if (!state.groupFile || !state.generation)
6896
+ return {
6897
+ ...common2,
6898
+ status: "manual-inspection",
6899
+ phase: state.phase,
6900
+ hint: "Creation stopped before a group reference was saved. Preserve this checkpoint and inspect owned launch evidence before changing it. No group ownership can be inferred from a matching project path.",
6901
+ commands: []
6902
+ };
6903
+ const store = new GroupStore(state.groupFile);
6904
+ const group = store.state.group;
6905
+ if (group.projectRoot !== settings2.projectRoot)
6906
+ throw new Error("startup_project_mismatch");
6907
+ if (group.generation !== state.generation)
6908
+ throw new Error("stale_group_generation");
6909
+ const target2 = ["--group-file", store.path];
6910
+ const generation = ["--generation", group.generation];
6911
+ const inspect = command2([
6912
+ "harness",
6913
+ "recovery",
6914
+ "inspect",
6915
+ ...target2,
6916
+ "--include-starts"
6917
+ ]);
6918
+ const status = group.state === "ready" ? state.phase : group.state;
6919
+ const commands = [inspect];
6920
+ let hint = "Inspect actual processes and outstanding starts before any recovery action.";
6921
+ if (group.state === "paused") {
6922
+ hint = "The group is paused. Keep it paused until you explicitly decide to resume it.";
6923
+ commands.push(
6924
+ command2(["harness", "group", "resume", ...target2, ...generation])
6925
+ );
6926
+ }
6927
+ if (group.state === "stopped") {
6928
+ hint = "The saved group is stopped. An explicitly confirmed fresh launch can create a replacement.";
6929
+ commands.push(command2(["tui", "--new", ...config]));
6930
+ }
6931
+ if (group.state === "ready" && !["ready", "view-pending"].includes(state.phase)) {
6932
+ hint = "Startup is incomplete. Inspect pending starts; stop the exact owned group before an explicit fresh launch.";
6933
+ commands.push(
6934
+ command2(["harness", "group", "stop", ...target2, ...generation])
6935
+ );
6936
+ }
6937
+ return {
6938
+ ...common2,
6939
+ status,
6940
+ phase: state.phase,
6941
+ groupFile: store.path,
6942
+ generation: group.generation,
6943
+ hint,
6944
+ commands,
6945
+ commandsRequireExplicitAction: true
6946
+ };
6947
+ }
6948
+ var init_recovery2 = __esm({
6949
+ "src/tui/startup/recovery.ts"() {
6950
+ "use strict";
6951
+ init_store2();
6952
+ init_command();
6953
+ init_config3();
6954
+ init_state();
6955
+ }
6956
+ });
6957
+
6667
6958
  // src/tui/startup/host.ts
6668
6959
  var host_exports = {};
6669
6960
  __export(host_exports, {
@@ -6767,13 +7058,48 @@ var init_lock2 = __esm({
6767
7058
  }
6768
7059
  });
6769
7060
 
7061
+ // src/tui/presentation/runtime-choice.ts
7062
+ import { createInterface as createInterface2 } from "readline/promises";
7063
+ async function chooseRuntime(prompt, choices, preferred) {
7064
+ if (!choices.length) throw new Error("runtime_executable_unavailable");
7065
+ const fallback = Math.max(choices.indexOf(preferred), 0);
7066
+ process.stderr.write(
7067
+ `${choices.map((runtime2, index) => `${index + 1}. ${runtime2}`).join("\n")}
7068
+ `
7069
+ );
7070
+ while (true) {
7071
+ const answer = (await prompt.question(
7072
+ `Runtime [1-${choices.length}, Enter=${fallback + 1}, q=cancel] `
7073
+ )).trim();
7074
+ if (answer === "q") return void 0;
7075
+ if (answer === "") return choices[fallback];
7076
+ if (!/^\d+$/.test(answer)) continue;
7077
+ const index = Number(answer) - 1;
7078
+ if (Number.isSafeInteger(index) && index >= 0 && index < choices.length)
7079
+ return choices[index];
7080
+ }
7081
+ }
7082
+ async function confirmRuntimeStart(prompt, runtime2) {
7083
+ const answer = await prompt.question(
7084
+ `Start ${runtime2}? [Enter=start, q=cancel] `
7085
+ );
7086
+ return answer.trim() === "";
7087
+ }
7088
+ function terminalPrompt() {
7089
+ return createInterface2({ input: process.stdin, output: process.stderr });
7090
+ }
7091
+ var init_runtime_choice = __esm({
7092
+ "src/tui/presentation/runtime-choice.ts"() {
7093
+ "use strict";
7094
+ }
7095
+ });
7096
+
6770
7097
  // src/tui/startup/entry.ts
6771
7098
  var entry_exports = {};
6772
7099
  __export(entry_exports, {
6773
7100
  launchStartup: () => launchStartup,
6774
7101
  planStartup: () => planStartup
6775
7102
  });
6776
- import { createInterface as createInterface2 } from "readline/promises";
6777
7103
  function planStartup(request3) {
6778
7104
  const settings2 = startupSettings(request3);
6779
7105
  const state = readStartup(
@@ -6795,26 +7121,41 @@ function planStartup(request3) {
6795
7121
  }
6796
7122
  async function launchStartup(request3, confirmed) {
6797
7123
  requireTerminal();
6798
- const settings2 = startupSettings(request3);
7124
+ let settings2 = startupSettings(request3);
6799
7125
  const state = readStartup(
6800
7126
  startupFile(settings2.projectRoot),
6801
7127
  settings2.projectRoot
6802
7128
  );
6803
7129
  const starting = (!state || request3.fresh) && !settings2.groupFile;
6804
7130
  if (starting) {
7131
+ if (!confirmed) {
7132
+ const prompt = terminalPrompt();
7133
+ try {
7134
+ let runtime2 = settings2.runtime;
7135
+ process.stderr.write(`PMCP MAIN | ${settings2.projectRoot}
7136
+ `);
7137
+ if (!request3.runtime) {
7138
+ const installed = settings2.allowedRuntimes.filter(
7139
+ (id2) => locateExecutable(runtimeExecutables[id2], request3.cwd)
7140
+ );
7141
+ const selected = await chooseRuntime(prompt, installed, runtime2);
7142
+ if (!selected) return cancelledStart();
7143
+ runtime2 = selected;
7144
+ }
7145
+ if (!await confirmRuntimeStart(prompt, runtime2))
7146
+ return cancelledStart();
7147
+ request3 = { ...request3, runtime: runtime2 };
7148
+ settings2 = startupSettings(request3);
7149
+ } finally {
7150
+ prompt.close();
7151
+ }
7152
+ }
6805
7153
  if (!locateExecutable(runtimeExecutables[settings2.runtime], request3.cwd))
6806
7154
  throw new Error("runtime_executable_unavailable");
6807
7155
  process.stderr.write(
6808
7156
  `PMCP main: ${settings2.runtime} | ${settings2.projectRoot}
6809
7157
  `
6810
7158
  );
6811
- if (!confirmed && !await confirmStart(settings2.runtime))
6812
- return {
6813
- schemaVersion: 1,
6814
- ok: true,
6815
- cancelled: true,
6816
- providerInvoked: false
6817
- };
6818
7159
  }
6819
7160
  const prepared = serializedStartup(request3);
6820
7161
  if (prepared.ok !== true) return prepared;
@@ -6824,19 +7165,13 @@ async function launchStartup(request3, confirmed) {
6824
7165
  input: {}
6825
7166
  });
6826
7167
  }
6827
- async function confirmStart(runtime2) {
6828
- const prompt = createInterface2({
6829
- input: process.stdin,
6830
- output: process.stderr
6831
- });
6832
- try {
6833
- const answer = await prompt.question(
6834
- `Start ${runtime2}? [Enter=start, q=cancel] `
6835
- );
6836
- return answer.trim() === "";
6837
- } finally {
6838
- prompt.close();
6839
- }
7168
+ function cancelledStart() {
7169
+ return {
7170
+ schemaVersion: 1,
7171
+ ok: true,
7172
+ cancelled: true,
7173
+ providerInvoked: false
7174
+ };
6840
7175
  }
6841
7176
  var init_entry2 = __esm({
6842
7177
  "src/tui/startup/entry.ts"() {
@@ -6848,6 +7183,7 @@ var init_entry2 = __esm({
6848
7183
  init_state();
6849
7184
  init_host();
6850
7185
  init_lock2();
7186
+ init_runtime_choice();
6851
7187
  }
6852
7188
  });
6853
7189
 
@@ -6904,15 +7240,15 @@ function one(args, name) {
6904
7240
  function many(args, name) {
6905
7241
  return args.options.get(name) ?? [];
6906
7242
  }
6907
- function printCommandHelp(ui, command2) {
6908
- ui.heading(`pmcp ${command2.name}`);
6909
- ui.line(` ${command2.describe}`);
7243
+ function printCommandHelp(ui, command3) {
7244
+ ui.heading(`pmcp ${command3.name}`);
7245
+ ui.line(` ${command3.describe}`);
6910
7246
  ui.line();
6911
- ui.line(` ${ui.paint(command2.usage, "dim")}`);
6912
- if (command2.options && command2.options.length > 0) {
7247
+ ui.line(` ${ui.paint(command3.usage, "dim")}`);
7248
+ if (command3.options && command3.options.length > 0) {
6913
7249
  ui.line();
6914
7250
  ui.table(
6915
- command2.options.map((option) => [
7251
+ command3.options.map((option) => [
6916
7252
  `--${option.name}${option.placeholder ? ` ${option.placeholder}` : ""}`,
6917
7253
  option.describe
6918
7254
  ])
@@ -6923,7 +7259,7 @@ function printCommandHelp(ui, command2) {
6923
7259
  function printHelp(ui, commands, notImplemented) {
6924
7260
  ui.heading("pmcp \u2014 the skills your installed packages ship");
6925
7261
  ui.line();
6926
- ui.table(commands.map((command2) => [command2.name, command2.describe]));
7262
+ ui.table(commands.map((command3) => [command3.name, command3.describe]));
6927
7263
  ui.line();
6928
7264
  ui.line(
6929
7265
  " With no command, pmcp opens a terminal workspace on a supported TTY."
@@ -9511,7 +9847,7 @@ function registerMailboxTools(server, service) {
9511
9847
 
9512
9848
  // src/mcp.ts
9513
9849
  var SERVER_NAME = "pmcp";
9514
- var SERVER_VERSION = "0.15.0";
9850
+ var SERVER_VERSION = "0.16.0";
9515
9851
  function registerSkillTools(server, tools) {
9516
9852
  registerCatalogTool(server, tools);
9517
9853
  registerFindTool(server, tools);
@@ -12131,8 +12467,8 @@ var denied = () => ({
12131
12467
  decision: "deny",
12132
12468
  reason: "Shell guard failed validation or execution"
12133
12469
  });
12134
- function guardPath(root, command2) {
12135
- const script = command2[0]?.startsWith(".agents/guards/") ? command2[0] : command2[1];
12470
+ function guardPath(root, command3) {
12471
+ const script = command3[0]?.startsWith(".agents/guards/") ? command3[0] : command3[1];
12136
12472
  if (!script?.startsWith(".agents/guards/")) {
12137
12473
  throw new Error("Hook command must execute a script under .agents/guards/");
12138
12474
  }
@@ -12190,10 +12526,10 @@ function evaluateShellHook(root, policy, tool, payload) {
12190
12526
  if (!shellNames[tool].includes(name)) {
12191
12527
  return { decision: "allow" };
12192
12528
  }
12193
- const command2 = scalar(
12529
+ const command3 = scalar(
12194
12530
  input[tool === "antigravity" ? "CommandLine" : "command"]
12195
12531
  );
12196
- if (command2 === void 0 || command2.length > 65536) {
12532
+ if (command3 === void 0 || command3.length > 65536) {
12197
12533
  return denied();
12198
12534
  }
12199
12535
  const cwd = scalar(input["Cwd"]) ?? scalar(value["cwd"]) ?? root;
@@ -12214,7 +12550,7 @@ function evaluateShellHook(root, policy, tool, payload) {
12214
12550
  input: JSON.stringify({
12215
12551
  runtime: tool,
12216
12552
  event: "before_shell",
12217
- command: command2,
12553
+ command: command3,
12218
12554
  cwd
12219
12555
  }),
12220
12556
  encoding: "utf8",
@@ -12402,11 +12738,11 @@ function hookOutputs(spec) {
12402
12738
  continue;
12403
12739
  }
12404
12740
  const target2 = native[tool];
12405
- const command2 = `d="$PWD"; while [ ! -f "$d/pmcp.toml" ]; do [ "$d" = / ] && exit 1; d=$(dirname "$d"); done; exec node "$d/${ADAPTER}" --pmcp-shell-hook ${tool} "$d/${policyFile}" ${integrity2}`;
12741
+ const command3 = `d="$PWD"; while [ ! -f "$d/pmcp.toml" ]; do [ "$d" = / ] && exit 1; d=$(dirname "$d"); done; exec node "$d/${ADAPTER}" --pmcp-shell-hook ${tool} "$d/${policyFile}" ${integrity2}`;
12406
12742
  const timeout = tool === "gemini" ? hook.timeoutMs + 2e3 : Math.ceil(hook.timeoutMs / 1e3) + 2;
12407
12743
  const value = {
12408
12744
  matcher: target2.matcher,
12409
- hooks: [{ type: "command", command: command2, timeout }]
12745
+ hooks: [{ type: "command", command: command3, timeout }]
12410
12746
  };
12411
12747
  const path = `${target2.file}#${target2.key.join(".")}`;
12412
12748
  const entry2 = desired.get(path) ?? {
@@ -13120,13 +13456,13 @@ function readHooks(path, root, raw) {
13120
13456
  return fail2("unknown shell entry field");
13121
13457
  }
13122
13458
  const name = entry2["name"];
13123
- const command2 = entry2["command"];
13459
+ const command3 = entry2["command"];
13124
13460
  const targets = entry2["targets"];
13125
13461
  const timeoutMs = entry2["timeout_ms"] ?? 15e3;
13126
13462
  if (typeof name !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(name) || names.has(name)) {
13127
13463
  return fail2("shell names must be unique lowercase names");
13128
13464
  }
13129
- if (!Array.isArray(command2) || command2.length === 0 || !command2.every(
13465
+ if (!Array.isArray(command3) || command3.length === 0 || !command3.every(
13130
13466
  (part) => typeof part === "string" && part.length > 0 && !part.includes("\0")
13131
13467
  )) {
13132
13468
  return fail2("command must be a nonempty argument list");
@@ -13140,7 +13476,7 @@ function readHooks(path, root, raw) {
13140
13476
  return fail2("timeout_ms must be an integer from 100 to 60000");
13141
13477
  }
13142
13478
  try {
13143
- guardPath(root, command2);
13479
+ guardPath(root, command3);
13144
13480
  } catch (error) {
13145
13481
  return fail2(
13146
13482
  error instanceof Error ? error.message : "invalid guard path"
@@ -13149,7 +13485,7 @@ function readHooks(path, root, raw) {
13149
13485
  names.add(name);
13150
13486
  return {
13151
13487
  name,
13152
- command: command2,
13488
+ command: command3,
13153
13489
  targets,
13154
13490
  timeoutMs
13155
13491
  };
@@ -13372,13 +13708,13 @@ function readMcp(path, value) {
13372
13708
  fail(path, where, `unknown field ${key}`);
13373
13709
  }
13374
13710
  }
13375
- const command2 = entry2["command"];
13711
+ const command3 = entry2["command"];
13376
13712
  const url = entry2["url"];
13377
- if (command2 === void 0 === (url === void 0)) {
13713
+ if (command3 === void 0 === (url === void 0)) {
13378
13714
  fail(path, where, "provide exactly one of command or url");
13379
13715
  }
13380
13716
  for (const [key, item] of [
13381
- ["command", command2],
13717
+ ["command", command3],
13382
13718
  ["url", url],
13383
13719
  ["cwd", entry2["cwd"]]
13384
13720
  ]) {
@@ -13476,7 +13812,7 @@ function readMcp(path, value) {
13476
13812
  fail(path, where, "mailbox must be a boolean");
13477
13813
  }
13478
13814
  servers[alias] = {
13479
- ...typeof command2 === "string" ? { command: command2 } : { url },
13815
+ ...typeof command3 === "string" ? { command: command3 } : { url },
13480
13816
  args: list(path, `${where} args`, entry2["args"], false),
13481
13817
  env,
13482
13818
  ...envVars.length > 0 ? { envVars } : {},
@@ -13879,8 +14215,8 @@ import { spawnSync as spawnSync3 } from "child_process";
13879
14215
  import { existsSync as existsSync21, readFileSync as readFileSync28 } from "fs";
13880
14216
  import { join as join25 } from "path";
13881
14217
  import { parse as parse8 } from "smol-toml";
13882
- var spawnRunner = (command2, args, cwd, env) => {
13883
- const result = spawnSync3(command2, [...args], {
14218
+ var spawnRunner = (command3, args, cwd, env) => {
14219
+ const result = spawnSync3(command3, [...args], {
13884
14220
  cwd,
13885
14221
  ...env ? { env: { ...process.env, ...env } } : {},
13886
14222
  encoding: "utf8",
@@ -15137,6 +15473,11 @@ var tuiOptions = [
15137
15473
  },
15138
15474
  { name: "rows", describe: "Terminal rows (12\u20131000)", placeholder: "<count>" },
15139
15475
  { name: "index", describe: "Worker dock index", placeholder: "<count>" },
15476
+ {
15477
+ name: "memory-mb",
15478
+ describe: "Worker memory reservation",
15479
+ placeholder: "<count>"
15480
+ },
15140
15481
  {
15141
15482
  name: "confirm-session",
15142
15483
  describe: "Exact target ID for stop",
@@ -15149,7 +15490,7 @@ var tuiOptions = [
15149
15490
  },
15150
15491
  {
15151
15492
  name: "runtime",
15152
- describe: "Main native runtime ID for launch",
15493
+ describe: "Native runtime ID for main launch or worker start",
15153
15494
  placeholder: "<runtime>"
15154
15495
  },
15155
15496
  {
@@ -15171,6 +15512,8 @@ var tuiOptions = [
15171
15512
  var allowed2 = {
15172
15513
  launch: ["config", "runtime", "yes", "new"],
15173
15514
  plan: ["config", "runtime"],
15515
+ recovery: ["config"],
15516
+ "start-worker": ["config", "view", "runtime", "memory-mb"],
15174
15517
  doctor: [],
15175
15518
  create: ["group-file", "columns", "rows", "config", "actor-file"],
15176
15519
  inspect: ["view"],
@@ -15188,6 +15531,7 @@ var allowed2 = {
15188
15531
  close: ["view"],
15189
15532
  dismiss: ["view"],
15190
15533
  management: ["view"],
15534
+ "worker-menu": ["view"],
15191
15535
  check: ["view"]
15192
15536
  };
15193
15537
  function parseTui(args) {
@@ -15212,7 +15556,23 @@ function parseTui(args) {
15212
15556
  return Number(value);
15213
15557
  };
15214
15558
  if (action === "doctor") return { action, input: {} };
15215
- if (action === "launch" || action === "plan")
15559
+ if (action === "start-worker") {
15560
+ const config = one(args, "config");
15561
+ const viewFile = one(args, "view");
15562
+ if (config && viewFile)
15563
+ throw new ArgumentError("Choose --config or --view for worker start");
15564
+ return {
15565
+ action,
15566
+ viewFile,
15567
+ input: {},
15568
+ startup: {
15569
+ config,
15570
+ runtime: required2("runtime"),
15571
+ memoryMb: number("memory-mb")
15572
+ }
15573
+ };
15574
+ }
15575
+ if (action === "launch" || action === "plan" || action === "recovery")
15216
15576
  return {
15217
15577
  action,
15218
15578
  input: {},
@@ -15263,14 +15623,36 @@ function parseTui(args) {
15263
15623
  var tuiCommand = {
15264
15624
  name: "tui",
15265
15625
  describe: "Open a native terminal workspace with optional configuration",
15266
- usage: "pmcp tui [--config <file>] [--runtime <id>] | plan | doctor | operation --view <file>",
15626
+ usage: "pmcp tui [--config <file>] [--runtime <id>] | plan | recovery | doctor | start-worker --runtime <id> | operation --view <file>",
15267
15627
  options: tuiOptions,
15268
15628
  async run(context) {
15269
15629
  const invocation2 = parseTui(context.args);
15270
15630
  const { invokeTui: invokeTui2, failedInvocation: failedInvocation2 } = await Promise.resolve().then(() => (init_invoke2(), invoke_exports2));
15271
15631
  try {
15272
15632
  let receipt;
15273
- if (invocation2.action === "launch" || invocation2.action === "plan") {
15633
+ if (invocation2.action === "start-worker") {
15634
+ if (invocation2.viewFile) {
15635
+ const { startViewWorker: startViewWorker2 } = await Promise.resolve().then(() => (init_worker_start(), worker_start_exports));
15636
+ receipt = await startViewWorker2(
15637
+ invocation2.viewFile,
15638
+ invocation2.startup.runtime,
15639
+ invocation2.startup?.memoryMb
15640
+ );
15641
+ } else {
15642
+ const { startWorkspaceWorker: startWorkspaceWorker2 } = await Promise.resolve().then(() => (init_worker(), worker_exports));
15643
+ receipt = await startWorkspaceWorker2(
15644
+ { config: invocation2.startup?.config, cwd: context.cwd },
15645
+ invocation2.startup.runtime,
15646
+ invocation2.startup?.memoryMb
15647
+ );
15648
+ }
15649
+ } else if (invocation2.action === "recovery") {
15650
+ const { inspectStartupRecovery: inspectStartupRecovery2 } = await Promise.resolve().then(() => (init_recovery2(), recovery_exports));
15651
+ receipt = inspectStartupRecovery2({
15652
+ ...invocation2.startup,
15653
+ cwd: context.cwd
15654
+ });
15655
+ } else if (invocation2.action === "launch" || invocation2.action === "plan") {
15274
15656
  const { launchStartup: launchStartup2, planStartup: planStartup2 } = await Promise.resolve().then(() => (init_entry2(), entry_exports));
15275
15657
  const request3 = { ...invocation2.startup, cwd: context.cwd };
15276
15658
  receipt = invocation2.action === "plan" ? planStartup2(request3) : await launchStartup2(
@@ -15525,7 +15907,7 @@ function dispatch(argv, options2 = {}) {
15525
15907
  return defaultEntry(options2, { ui, env, cwd });
15526
15908
  }
15527
15909
  if (verb === "--help" || verb === "-h" || verb === "help") {
15528
- const named2 = COMMANDS.find((command3) => command3.name === rest[0]);
15910
+ const named2 = COMMANDS.find((command4) => command4.name === rest[0]);
15529
15911
  if (named2) printCommandHelp(ui, named2);
15530
15912
  else printHelp(ui, COMMANDS, PLANNED);
15531
15913
  return 0;
@@ -15538,8 +15920,8 @@ function dispatch(argv, options2 = {}) {
15538
15920
  if (verb.startsWith("-")) {
15539
15921
  return run(serveCommand, rest0, { ui, env, cwd });
15540
15922
  }
15541
- const command2 = COMMANDS.find((candidate2) => candidate2.name === verb);
15542
- if (command2 === void 0) {
15923
+ const command3 = COMMANDS.find((candidate2) => candidate2.name === verb);
15924
+ if (command3 === void 0) {
15543
15925
  if (PLANNED.includes(verb))
15544
15926
  ui.error(`"${verb}" is designed but not implemented yet`);
15545
15927
  else ui.error(`unknown command "${verb}"`);
@@ -15547,12 +15929,12 @@ function dispatch(argv, options2 = {}) {
15547
15929
  return 2;
15548
15930
  }
15549
15931
  if (rest.includes("--help") || rest.includes("-h")) {
15550
- printCommandHelp(ui, command2);
15932
+ printCommandHelp(ui, command3);
15551
15933
  return 0;
15552
15934
  }
15553
- return run(command2, rest, { ui, env, cwd });
15935
+ return run(command3, rest, { ui, env, cwd });
15554
15936
  }
15555
- var VERSION = "0.15.0";
15937
+ var VERSION = "0.16.0";
15556
15938
  async function defaultEntry(options2, context) {
15557
15939
  const terminal = options2.terminal ?? {
15558
15940
  stdin: process.stdin.isTTY === true,
@@ -15576,16 +15958,16 @@ async function defaultEntry(options2, context) {
15576
15958
  printHelp(context.ui, COMMANDS, PLANNED);
15577
15959
  return 0;
15578
15960
  }
15579
- async function run(command2, argv, context) {
15961
+ async function run(command3, argv, context) {
15580
15962
  try {
15581
- return await command2.run({
15963
+ return await command3.run({
15582
15964
  ...context,
15583
- args: parseArgs(argv, command2.options)
15965
+ args: parseArgs(argv, command3.options)
15584
15966
  });
15585
15967
  } catch (error) {
15586
15968
  if (error instanceof ArgumentError) {
15587
15969
  context.ui.error(error.message);
15588
- printCommandHelp(context.ui, command2);
15970
+ printCommandHelp(context.ui, command3);
15589
15971
  return 2;
15590
15972
  }
15591
15973
  context.ui.error(error instanceof Error ? error.message : "command failed");