@modootoday/pmcp 0.15.0 → 0.16.1

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
@@ -1837,11 +1837,11 @@ function dockerPolicy(value) {
1837
1837
  for (const [runtime2, value2] of entries) {
1838
1838
  if (!Object.hasOwn(runtimeExecutables, runtime2))
1839
1839
  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)))
1840
+ const command3 = strings2(value2, "docker_runtime_command");
1841
+ const executable = command3[0];
1842
+ if (!isAbsolute10(executable) || posix5.normalize(executable) !== executable || command3.some((argument) => /[\r\n\x1b]/.test(argument)))
1843
1843
  throw new Error("invalid_docker_runtime_command");
1844
- parsed[runtime2] = command2;
1844
+ parsed[runtime2] = command3;
1845
1845
  }
1846
1846
  return {
1847
1847
  schemaVersion: 1,
@@ -2962,14 +2962,14 @@ function bridgeName(nonce) {
2962
2962
  function containerEntrypoint(policy, runtime2, nonce, privateProfile) {
2963
2963
  const native2 = policy.commands[runtime2];
2964
2964
  if (!native2) throw new Error("docker_runtime_command_not_granted");
2965
- const command2 = policy.egress?.bridgeExecutable ? [
2965
+ const command3 = policy.egress?.bridgeExecutable ? [
2966
2966
  policy.egress.bridgeExecutable,
2967
2967
  `/state/${bridgeName(nonce)}`,
2968
2968
  "--",
2969
2969
  ...native2
2970
2970
  ] : native2;
2971
2971
  const profile = selectedProfile(policy, privateProfile, runtime2);
2972
- if (!profile) return command2;
2972
+ if (!profile) return command3;
2973
2973
  return [
2974
2974
  profile.lockExecutable,
2975
2975
  "--exclusive",
@@ -2978,7 +2978,7 @@ function containerEntrypoint(policy, runtime2, nonce, privateProfile) {
2978
2978
  "--conflict-exit-code",
2979
2979
  "73",
2980
2980
  `/state/${privateProfileLock}`,
2981
- ...command2
2981
+ ...command3
2982
2982
  ];
2983
2983
  }
2984
2984
  var init_entrypoint = __esm({
@@ -5254,6 +5254,79 @@ var init_runtime2 = __esm({
5254
5254
  }
5255
5255
  });
5256
5256
 
5257
+ // src/tui/startup/diagnostics.ts
5258
+ function remediation(tools, userManagerAvailable) {
5259
+ const steps = [];
5260
+ if (tools.platform !== "linux")
5261
+ steps.push(
5262
+ "Use a qualified Linux host for the terminal workspace. Other PMCP CLI features do not require this host profile."
5263
+ );
5264
+ const missing = ["tmux", "flock", "systemctl", "systemdRun"].filter(
5265
+ (name) => !tools[name]
5266
+ );
5267
+ if (missing.length)
5268
+ steps.push(
5269
+ `Install the missing tmux, util-linux or systemd tools for your distribution and add them to PATH: ${missing.join(", ")}.`
5270
+ );
5271
+ if (!tools.nodeSupported)
5272
+ steps.push(
5273
+ "Select Node.js 22 or later through your existing version manager and check node --version in this terminal."
5274
+ );
5275
+ if (tools.supported && !userManagerAvailable)
5276
+ steps.push(
5277
+ "Check systemctl --user status in your login session and restore access to its user manager before launching. Doctor does not start or repair services."
5278
+ );
5279
+ return steps;
5280
+ }
5281
+ function inspectTerminalHost() {
5282
+ const tools = dependencies();
5283
+ let userManagerAvailable = false;
5284
+ let reason;
5285
+ try {
5286
+ if (!tools.supported) throw new Error("tui_dependencies_unavailable");
5287
+ assertNativeHost();
5288
+ userManagerAvailable = true;
5289
+ } catch (error) {
5290
+ reason = error instanceof Error ? error.message : "native_host_unavailable";
5291
+ }
5292
+ const runtimes = Object.entries(runtimeExecutables).map(
5293
+ ([id2, executable]) => {
5294
+ const path = locateExecutable(executable);
5295
+ return {
5296
+ id: id2,
5297
+ executable,
5298
+ installed: Boolean(path),
5299
+ ...path ? { path } : {},
5300
+ nativeAcceptance: "not-assessed",
5301
+ authentication: "not-inspected"
5302
+ };
5303
+ }
5304
+ );
5305
+ return {
5306
+ schemaVersion: 1,
5307
+ ok: true,
5308
+ ...tools,
5309
+ supported: tools.supported && userManagerAvailable,
5310
+ userManagerAvailable,
5311
+ ...reason ? { reason } : {},
5312
+ runtimes,
5313
+ qualification: "Prerequisite availability only; native behavior and cgroup delegation require separate qualification.",
5314
+ remediation: remediation(tools, userManagerAvailable),
5315
+ nextCommands: ["pmcp tui plan", "pmcp tui recovery"],
5316
+ providerInvoked: false,
5317
+ authenticationInspected: false
5318
+ };
5319
+ }
5320
+ var init_diagnostics = __esm({
5321
+ "src/tui/startup/diagnostics.ts"() {
5322
+ "use strict";
5323
+ init_runtime2();
5324
+ init_admission2();
5325
+ init_registry();
5326
+ init_executable();
5327
+ }
5328
+ });
5329
+
5257
5330
  // src/tui/state/store.ts
5258
5331
  import { randomUUID as randomUUID13 } from "crypto";
5259
5332
  import {
@@ -5592,13 +5665,19 @@ var init_feedback = __esm({
5592
5665
  init_store3();
5593
5666
  init_store2();
5594
5667
  hints = {
5595
- startup_requires_recovery: "Inspect the saved group with pmcp tui plan and pmcp harness recovery inspect. No runtime was restarted.",
5668
+ startup_requires_recovery: "Run pmcp tui recovery for saved ownership and exact inspection commands. No runtime was restarted.",
5596
5669
  startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
5597
5670
  startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
5598
5671
  startup_busy: "Another workspace launch is running. Wait and retry.",
5599
5672
  startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
5600
5673
  workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
5601
5674
  runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
5675
+ view_client_unavailable: "Open the view before using the worker menu, or run pmcp tui start-worker with --view and --runtime.",
5676
+ runtime_not_granted: "Choose a runtime granted by this owned group. Inspect the group before creating another workspace.",
5677
+ runtime_capacity_exceeded: "Stop an exact owned worker before adding another, or use the main session alone.",
5678
+ aggregate_memory_budget_exceeded: "Use fewer sessions or start a worker with an explicit smaller --memory-mb reservation.",
5679
+ group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
5680
+ startup_group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
5602
5681
  control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
5603
5682
  control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
5604
5683
  native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
@@ -5619,6 +5698,20 @@ var init_feedback = __esm({
5619
5698
  }
5620
5699
  });
5621
5700
 
5701
+ // src/tui/presentation/target.ts
5702
+ function targetStatus(view) {
5703
+ const role = view.target === view.mainId ? "MAIN" : "WORKER";
5704
+ const phase2 = view.phase === "read-only" ? "OBSERVE" : phaseLabel(view.phase);
5705
+ const input = view.phase === "controlled" ? "INPUT ON" : "INPUT OFF";
5706
+ return `${role} ${view.target.slice(0, 8)} | ${phase2} | ${input}`;
5707
+ }
5708
+ var init_target = __esm({
5709
+ "src/tui/presentation/target.ts"() {
5710
+ "use strict";
5711
+ init_feedback();
5712
+ }
5713
+ });
5714
+
5622
5715
  // src/tui/adapters/tmux/bindings.ts
5623
5716
  function callback(view, file2, action, args = []) {
5624
5717
  return [
@@ -5645,7 +5738,8 @@ function bindKeys(connection, view, file2) {
5645
5738
  ["b", "mailbox"],
5646
5739
  ["d", "detach"],
5647
5740
  ["!", "dismiss"],
5648
- ["g", "management"]
5741
+ ["g", "management"],
5742
+ ["n", "worker-menu"]
5649
5743
  ])
5650
5744
  connection.command([
5651
5745
  "bind-key",
@@ -5666,7 +5760,7 @@ function bindKeys(connection, view, file2) {
5666
5760
  "bind-key",
5667
5761
  "?",
5668
5762
  "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"
5763
+ "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
5764
  ]);
5671
5765
  connection.command([
5672
5766
  "set-hook",
@@ -5694,7 +5788,7 @@ var init_view = __esm({
5694
5788
  "src/tui/adapters/tmux/view.ts"() {
5695
5789
  "use strict";
5696
5790
  init_command();
5697
- init_feedback();
5791
+ init_target();
5698
5792
  init_connection2();
5699
5793
  init_bindings();
5700
5794
  TmuxView = class {
@@ -5880,7 +5974,7 @@ var init_view = __esm({
5880
5974
  "set-option",
5881
5975
  "-g",
5882
5976
  "status-right",
5883
- `${view.target === view.mainId ? "main" : "worker"} | ${phaseLabel(view.phase)}${failure2} | Ctrl-g ?`
5977
+ `${targetStatus(view)}${failure2} | Ctrl-g ?`
5884
5978
  ]);
5885
5979
  }
5886
5980
  input(pane, enabled) {
@@ -5903,6 +5997,32 @@ var init_view = __esm({
5903
5997
  clients() {
5904
5998
  return this.connection.command(["list-clients", "-F", "#{client_pid}"]);
5905
5999
  }
6000
+ workerMenu(view) {
6001
+ const pane = view.workerPane ?? view.mainPane;
6002
+ this.connection.assertOwned(pane);
6003
+ const popup = [
6004
+ "tmux",
6005
+ "-S",
6006
+ this.connection.socket,
6007
+ "display-popup",
6008
+ "-E",
6009
+ "-t",
6010
+ pane,
6011
+ "-w",
6012
+ "90%",
6013
+ "-h",
6014
+ "80%",
6015
+ this.shell([
6016
+ view.node,
6017
+ view.worker,
6018
+ "worker-menu",
6019
+ "--view",
6020
+ this.stateFile,
6021
+ "--callback"
6022
+ ])
6023
+ ];
6024
+ this.connection.command(["run-shell", "-b", popup.map(quote2).join(" ")]);
6025
+ }
5906
6026
  copy(pane) {
5907
6027
  this.connection.assertOwned(pane);
5908
6028
  this.connection.command(["copy-mode", "-t", pane]);
@@ -6465,6 +6585,11 @@ var init_operations = __esm({
6465
6585
  close: closeView,
6466
6586
  check: checkControl,
6467
6587
  dismiss: ({ view, file: file2 }) => dismissFailure(file2, view),
6588
+ "worker-menu": async (context) => {
6589
+ if (!context.renderer.clients()) throw new Error("view_client_unavailable");
6590
+ await releaseControl(context);
6591
+ context.renderer.workerMenu(context.view);
6592
+ },
6468
6593
  management: async (context) => {
6469
6594
  await releaseControl(context);
6470
6595
  if (context.view.managementWindow) {
@@ -6488,10 +6613,9 @@ __export(invoke_exports2, {
6488
6613
  invokeTui: () => invokeTui
6489
6614
  });
6490
6615
  async function invokeTui(invocation2) {
6491
- if (invocation2.action === "launch" || invocation2.action === "plan")
6616
+ if (["launch", "plan", "recovery", "start-worker"].includes(invocation2.action))
6492
6617
  throw new Error("startup_requires_cli_context");
6493
- if (invocation2.action === "doctor")
6494
- return { schemaVersion: 1, ok: true, ...dependencies() };
6618
+ if (invocation2.action === "doctor") return inspectTerminalHost();
6495
6619
  if (invocation2.action === "create") return createView(invocation2.create);
6496
6620
  if (invocation2.action === "inspect")
6497
6621
  return operate("inspect", invocation2.viewFile);
@@ -6529,7 +6653,7 @@ function failedInvocation(action, error) {
6529
6653
  var init_invoke2 = __esm({
6530
6654
  "src/tui/application/invoke.ts"() {
6531
6655
  "use strict";
6532
- init_runtime2();
6656
+ init_diagnostics();
6533
6657
  init_lock();
6534
6658
  init_create();
6535
6659
  init_operations();
@@ -6538,6 +6662,31 @@ var init_invoke2 = __esm({
6538
6662
  }
6539
6663
  });
6540
6664
 
6665
+ // src/tui/application/worker-start.ts
6666
+ var worker_start_exports = {};
6667
+ __export(worker_start_exports, {
6668
+ startViewWorker: () => startViewWorker
6669
+ });
6670
+ async function startViewWorker(file2, runtime2, memoryMb) {
6671
+ const { view, common: common2 } = openView(file2);
6672
+ if (view.state !== "open") throw new Error("view_closed");
6673
+ const observed = await common2.snapshot();
6674
+ if (observed.group.state !== "ready") throw new Error("group_not_ready");
6675
+ const main2 = observed.sessions.find(
6676
+ (session) => session.id === view.mainId && session.role === "main"
6677
+ );
6678
+ if (!main2?.alive) throw new Error("startup_main_unavailable");
6679
+ return common2.call("session", "start", {
6680
+ input: { role: "worker", runtime: runtime2, memoryMb: memoryMb ?? main2.memoryMb }
6681
+ });
6682
+ }
6683
+ var init_worker_start = __esm({
6684
+ "src/tui/application/worker-start.ts"() {
6685
+ "use strict";
6686
+ init_context2();
6687
+ }
6688
+ });
6689
+
6541
6690
  // src/tui/startup/config.ts
6542
6691
  import { realpathSync as realpathSync17 } from "fs";
6543
6692
  import { resolve as resolve19 } from "path";
@@ -6664,6 +6813,160 @@ var init_state = __esm({
6664
6813
  }
6665
6814
  });
6666
6815
 
6816
+ // src/tui/startup/worker.ts
6817
+ var worker_exports = {};
6818
+ __export(worker_exports, {
6819
+ startWorkspaceWorker: () => startWorkspaceWorker
6820
+ });
6821
+ async function startWorkspaceWorker(request3, runtime2, memoryMb) {
6822
+ if (!Object.hasOwn(runtimeExecutables, runtime2))
6823
+ throw new Error("unsupported_runtime");
6824
+ const settings2 = startupSettings({
6825
+ cwd: request3.cwd,
6826
+ config: request3.config
6827
+ });
6828
+ const state = readStartup(
6829
+ startupFile(settings2.projectRoot),
6830
+ settings2.projectRoot
6831
+ );
6832
+ if (!state || state.phase !== "ready" || !state.groupFile || !state.generation)
6833
+ throw new Error("startup_requires_recovery");
6834
+ const store = new GroupStore(state.groupFile);
6835
+ const group = store.state.group;
6836
+ if (group.projectRoot !== settings2.projectRoot)
6837
+ throw new Error("startup_project_mismatch");
6838
+ if (group.generation !== state.generation)
6839
+ throw new Error("stale_group_generation");
6840
+ if (group.state === "paused") throw new Error("startup_group_paused");
6841
+ if (group.state !== "ready") throw new Error("startup_group_not_ready");
6842
+ if (group.profile === "native" && !locateExecutable(runtimeExecutables[runtime2], request3.cwd))
6843
+ throw new Error("runtime_executable_unavailable");
6844
+ const observed = await invoke({
6845
+ family: "session",
6846
+ action: "list",
6847
+ groupFile: store.path
6848
+ });
6849
+ if (observed.ok !== true) return observed;
6850
+ const sessions = observed.sessions;
6851
+ if (!sessions.some((session) => session.role === "main" && session.alive))
6852
+ throw new Error("startup_main_unavailable");
6853
+ return invoke({
6854
+ family: "session",
6855
+ action: "start",
6856
+ groupFile: store.path,
6857
+ generation: group.generation,
6858
+ input: {
6859
+ runtime: runtime2,
6860
+ role: "worker",
6861
+ memoryMb: memoryMb ?? settings2.sessionMemoryMb
6862
+ }
6863
+ });
6864
+ }
6865
+ var init_worker = __esm({
6866
+ "src/tui/startup/worker.ts"() {
6867
+ "use strict";
6868
+ init_invoke();
6869
+ init_store2();
6870
+ init_registry();
6871
+ init_executable();
6872
+ init_config3();
6873
+ init_state();
6874
+ }
6875
+ });
6876
+
6877
+ // src/tui/startup/recovery.ts
6878
+ var recovery_exports = {};
6879
+ __export(recovery_exports, {
6880
+ inspectStartupRecovery: () => inspectStartupRecovery
6881
+ });
6882
+ function command2(args) {
6883
+ return ["pmcp", ...args].map(quote2).join(" ");
6884
+ }
6885
+ function inspectStartupRecovery(request3) {
6886
+ const settings2 = startupSettings(request3);
6887
+ const checkpoint = startupFile(settings2.projectRoot);
6888
+ const state = readStartup(checkpoint, settings2.projectRoot);
6889
+ const config = settings2.configFile ? ["--config", settings2.configFile] : [];
6890
+ const common2 = {
6891
+ schemaVersion: 1,
6892
+ ok: true,
6893
+ checkpoint,
6894
+ projectRoot: settings2.projectRoot,
6895
+ providerInvoked: false,
6896
+ runtimeRestarted: false,
6897
+ inputReplayed: false,
6898
+ observedNativeProcesses: false
6899
+ };
6900
+ if (!state)
6901
+ return {
6902
+ ...common2,
6903
+ status: "not-started",
6904
+ hint: "No saved workspace exists. Start explicitly from an interactive terminal.",
6905
+ commands: [command2(["tui", ...config])]
6906
+ };
6907
+ if (!state.groupFile || !state.generation)
6908
+ return {
6909
+ ...common2,
6910
+ status: "manual-inspection",
6911
+ phase: state.phase,
6912
+ 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.",
6913
+ commands: []
6914
+ };
6915
+ const store = new GroupStore(state.groupFile);
6916
+ const group = store.state.group;
6917
+ if (group.projectRoot !== settings2.projectRoot)
6918
+ throw new Error("startup_project_mismatch");
6919
+ if (group.generation !== state.generation)
6920
+ throw new Error("stale_group_generation");
6921
+ const target2 = ["--group-file", store.path];
6922
+ const generation = ["--generation", group.generation];
6923
+ const inspect = command2([
6924
+ "harness",
6925
+ "recovery",
6926
+ "inspect",
6927
+ ...target2,
6928
+ "--include-starts"
6929
+ ]);
6930
+ const status = group.state === "ready" ? state.phase : group.state;
6931
+ const commands = [inspect];
6932
+ let hint = "Inspect actual processes and outstanding starts before any recovery action.";
6933
+ if (group.state === "paused") {
6934
+ hint = "The group is paused. Keep it paused until you explicitly decide to resume it.";
6935
+ commands.push(
6936
+ command2(["harness", "group", "resume", ...target2, ...generation])
6937
+ );
6938
+ }
6939
+ if (group.state === "stopped") {
6940
+ hint = "The saved group is stopped. An explicitly confirmed fresh launch can create a replacement.";
6941
+ commands.push(command2(["tui", "--new", ...config]));
6942
+ }
6943
+ if (group.state === "ready" && !["ready", "view-pending"].includes(state.phase)) {
6944
+ hint = "Startup is incomplete. Inspect pending starts; stop the exact owned group before an explicit fresh launch.";
6945
+ commands.push(
6946
+ command2(["harness", "group", "stop", ...target2, ...generation])
6947
+ );
6948
+ }
6949
+ return {
6950
+ ...common2,
6951
+ status,
6952
+ phase: state.phase,
6953
+ groupFile: store.path,
6954
+ generation: group.generation,
6955
+ hint,
6956
+ commands,
6957
+ commandsRequireExplicitAction: true
6958
+ };
6959
+ }
6960
+ var init_recovery2 = __esm({
6961
+ "src/tui/startup/recovery.ts"() {
6962
+ "use strict";
6963
+ init_store2();
6964
+ init_command();
6965
+ init_config3();
6966
+ init_state();
6967
+ }
6968
+ });
6969
+
6667
6970
  // src/tui/startup/host.ts
6668
6971
  var host_exports = {};
6669
6972
  __export(host_exports, {
@@ -6767,13 +7070,48 @@ var init_lock2 = __esm({
6767
7070
  }
6768
7071
  });
6769
7072
 
7073
+ // src/tui/presentation/runtime-choice.ts
7074
+ import { createInterface as createInterface2 } from "readline/promises";
7075
+ async function chooseRuntime(prompt, choices, preferred) {
7076
+ if (!choices.length) throw new Error("runtime_executable_unavailable");
7077
+ const fallback = Math.max(choices.indexOf(preferred), 0);
7078
+ process.stderr.write(
7079
+ `${choices.map((runtime2, index) => `${index + 1}. ${runtime2}`).join("\n")}
7080
+ `
7081
+ );
7082
+ while (true) {
7083
+ const answer = (await prompt.question(
7084
+ `Runtime [1-${choices.length}, Enter=${fallback + 1}, q=cancel] `
7085
+ )).trim();
7086
+ if (answer === "q") return void 0;
7087
+ if (answer === "") return choices[fallback];
7088
+ if (!/^\d+$/.test(answer)) continue;
7089
+ const index = Number(answer) - 1;
7090
+ if (Number.isSafeInteger(index) && index >= 0 && index < choices.length)
7091
+ return choices[index];
7092
+ }
7093
+ }
7094
+ async function confirmRuntimeStart(prompt, runtime2) {
7095
+ const answer = await prompt.question(
7096
+ `Start ${runtime2}? [Enter=start, q=cancel] `
7097
+ );
7098
+ return answer.trim() === "";
7099
+ }
7100
+ function terminalPrompt() {
7101
+ return createInterface2({ input: process.stdin, output: process.stderr });
7102
+ }
7103
+ var init_runtime_choice = __esm({
7104
+ "src/tui/presentation/runtime-choice.ts"() {
7105
+ "use strict";
7106
+ }
7107
+ });
7108
+
6770
7109
  // src/tui/startup/entry.ts
6771
7110
  var entry_exports = {};
6772
7111
  __export(entry_exports, {
6773
7112
  launchStartup: () => launchStartup,
6774
7113
  planStartup: () => planStartup
6775
7114
  });
6776
- import { createInterface as createInterface2 } from "readline/promises";
6777
7115
  function planStartup(request3) {
6778
7116
  const settings2 = startupSettings(request3);
6779
7117
  const state = readStartup(
@@ -6795,26 +7133,41 @@ function planStartup(request3) {
6795
7133
  }
6796
7134
  async function launchStartup(request3, confirmed) {
6797
7135
  requireTerminal();
6798
- const settings2 = startupSettings(request3);
7136
+ let settings2 = startupSettings(request3);
6799
7137
  const state = readStartup(
6800
7138
  startupFile(settings2.projectRoot),
6801
7139
  settings2.projectRoot
6802
7140
  );
6803
7141
  const starting = (!state || request3.fresh) && !settings2.groupFile;
6804
7142
  if (starting) {
7143
+ if (!confirmed) {
7144
+ const prompt = terminalPrompt();
7145
+ try {
7146
+ let runtime2 = settings2.runtime;
7147
+ process.stderr.write(`PMCP MAIN | ${settings2.projectRoot}
7148
+ `);
7149
+ if (!request3.runtime) {
7150
+ const installed = settings2.allowedRuntimes.filter(
7151
+ (id2) => locateExecutable(runtimeExecutables[id2], request3.cwd)
7152
+ );
7153
+ const selected = await chooseRuntime(prompt, installed, runtime2);
7154
+ if (!selected) return cancelledStart();
7155
+ runtime2 = selected;
7156
+ }
7157
+ if (!await confirmRuntimeStart(prompt, runtime2))
7158
+ return cancelledStart();
7159
+ request3 = { ...request3, runtime: runtime2 };
7160
+ settings2 = startupSettings(request3);
7161
+ } finally {
7162
+ prompt.close();
7163
+ }
7164
+ }
6805
7165
  if (!locateExecutable(runtimeExecutables[settings2.runtime], request3.cwd))
6806
7166
  throw new Error("runtime_executable_unavailable");
6807
7167
  process.stderr.write(
6808
7168
  `PMCP main: ${settings2.runtime} | ${settings2.projectRoot}
6809
7169
  `
6810
7170
  );
6811
- if (!confirmed && !await confirmStart(settings2.runtime))
6812
- return {
6813
- schemaVersion: 1,
6814
- ok: true,
6815
- cancelled: true,
6816
- providerInvoked: false
6817
- };
6818
7171
  }
6819
7172
  const prepared = serializedStartup(request3);
6820
7173
  if (prepared.ok !== true) return prepared;
@@ -6824,19 +7177,13 @@ async function launchStartup(request3, confirmed) {
6824
7177
  input: {}
6825
7178
  });
6826
7179
  }
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
- }
7180
+ function cancelledStart() {
7181
+ return {
7182
+ schemaVersion: 1,
7183
+ ok: true,
7184
+ cancelled: true,
7185
+ providerInvoked: false
7186
+ };
6840
7187
  }
6841
7188
  var init_entry2 = __esm({
6842
7189
  "src/tui/startup/entry.ts"() {
@@ -6848,6 +7195,7 @@ var init_entry2 = __esm({
6848
7195
  init_state();
6849
7196
  init_host();
6850
7197
  init_lock2();
7198
+ init_runtime_choice();
6851
7199
  }
6852
7200
  });
6853
7201
 
@@ -6904,15 +7252,15 @@ function one(args, name) {
6904
7252
  function many(args, name) {
6905
7253
  return args.options.get(name) ?? [];
6906
7254
  }
6907
- function printCommandHelp(ui, command2) {
6908
- ui.heading(`pmcp ${command2.name}`);
6909
- ui.line(` ${command2.describe}`);
7255
+ function printCommandHelp(ui, command3) {
7256
+ ui.heading(`pmcp ${command3.name}`);
7257
+ ui.line(` ${command3.describe}`);
6910
7258
  ui.line();
6911
- ui.line(` ${ui.paint(command2.usage, "dim")}`);
6912
- if (command2.options && command2.options.length > 0) {
7259
+ ui.line(` ${ui.paint(command3.usage, "dim")}`);
7260
+ if (command3.options && command3.options.length > 0) {
6913
7261
  ui.line();
6914
7262
  ui.table(
6915
- command2.options.map((option) => [
7263
+ command3.options.map((option) => [
6916
7264
  `--${option.name}${option.placeholder ? ` ${option.placeholder}` : ""}`,
6917
7265
  option.describe
6918
7266
  ])
@@ -6923,7 +7271,7 @@ function printCommandHelp(ui, command2) {
6923
7271
  function printHelp(ui, commands, notImplemented) {
6924
7272
  ui.heading("pmcp \u2014 the skills your installed packages ship");
6925
7273
  ui.line();
6926
- ui.table(commands.map((command2) => [command2.name, command2.describe]));
7274
+ ui.table(commands.map((command3) => [command3.name, command3.describe]));
6927
7275
  ui.line();
6928
7276
  ui.line(
6929
7277
  " With no command, pmcp opens a terminal workspace on a supported TTY."
@@ -7038,7 +7386,7 @@ init_entry();
7038
7386
 
7039
7387
  // src/intent.ts
7040
7388
  function normaliseIntent(intent) {
7041
- return intent.toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
7389
+ return intent.normalize("NFC").toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
7042
7390
  }
7043
7391
 
7044
7392
  // src/cache.ts
@@ -9511,7 +9859,7 @@ function registerMailboxTools(server, service) {
9511
9859
 
9512
9860
  // src/mcp.ts
9513
9861
  var SERVER_NAME = "pmcp";
9514
- var SERVER_VERSION = "0.15.0";
9862
+ var SERVER_VERSION = "0.16.1";
9515
9863
  function registerSkillTools(server, tools) {
9516
9864
  registerCatalogTool(server, tools);
9517
9865
  registerFindTool(server, tools);
@@ -12131,8 +12479,8 @@ var denied = () => ({
12131
12479
  decision: "deny",
12132
12480
  reason: "Shell guard failed validation or execution"
12133
12481
  });
12134
- function guardPath(root, command2) {
12135
- const script = command2[0]?.startsWith(".agents/guards/") ? command2[0] : command2[1];
12482
+ function guardPath(root, command3) {
12483
+ const script = command3[0]?.startsWith(".agents/guards/") ? command3[0] : command3[1];
12136
12484
  if (!script?.startsWith(".agents/guards/")) {
12137
12485
  throw new Error("Hook command must execute a script under .agents/guards/");
12138
12486
  }
@@ -12190,10 +12538,10 @@ function evaluateShellHook(root, policy, tool, payload) {
12190
12538
  if (!shellNames[tool].includes(name)) {
12191
12539
  return { decision: "allow" };
12192
12540
  }
12193
- const command2 = scalar(
12541
+ const command3 = scalar(
12194
12542
  input[tool === "antigravity" ? "CommandLine" : "command"]
12195
12543
  );
12196
- if (command2 === void 0 || command2.length > 65536) {
12544
+ if (command3 === void 0 || command3.length > 65536) {
12197
12545
  return denied();
12198
12546
  }
12199
12547
  const cwd = scalar(input["Cwd"]) ?? scalar(value["cwd"]) ?? root;
@@ -12214,7 +12562,7 @@ function evaluateShellHook(root, policy, tool, payload) {
12214
12562
  input: JSON.stringify({
12215
12563
  runtime: tool,
12216
12564
  event: "before_shell",
12217
- command: command2,
12565
+ command: command3,
12218
12566
  cwd
12219
12567
  }),
12220
12568
  encoding: "utf8",
@@ -12402,11 +12750,11 @@ function hookOutputs(spec) {
12402
12750
  continue;
12403
12751
  }
12404
12752
  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}`;
12753
+ 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
12754
  const timeout = tool === "gemini" ? hook.timeoutMs + 2e3 : Math.ceil(hook.timeoutMs / 1e3) + 2;
12407
12755
  const value = {
12408
12756
  matcher: target2.matcher,
12409
- hooks: [{ type: "command", command: command2, timeout }]
12757
+ hooks: [{ type: "command", command: command3, timeout }]
12410
12758
  };
12411
12759
  const path = `${target2.file}#${target2.key.join(".")}`;
12412
12760
  const entry2 = desired.get(path) ?? {
@@ -13120,13 +13468,13 @@ function readHooks(path, root, raw) {
13120
13468
  return fail2("unknown shell entry field");
13121
13469
  }
13122
13470
  const name = entry2["name"];
13123
- const command2 = entry2["command"];
13471
+ const command3 = entry2["command"];
13124
13472
  const targets = entry2["targets"];
13125
13473
  const timeoutMs = entry2["timeout_ms"] ?? 15e3;
13126
13474
  if (typeof name !== "string" || !/^[a-z0-9][a-z0-9-]*$/u.test(name) || names.has(name)) {
13127
13475
  return fail2("shell names must be unique lowercase names");
13128
13476
  }
13129
- if (!Array.isArray(command2) || command2.length === 0 || !command2.every(
13477
+ if (!Array.isArray(command3) || command3.length === 0 || !command3.every(
13130
13478
  (part) => typeof part === "string" && part.length > 0 && !part.includes("\0")
13131
13479
  )) {
13132
13480
  return fail2("command must be a nonempty argument list");
@@ -13140,7 +13488,7 @@ function readHooks(path, root, raw) {
13140
13488
  return fail2("timeout_ms must be an integer from 100 to 60000");
13141
13489
  }
13142
13490
  try {
13143
- guardPath(root, command2);
13491
+ guardPath(root, command3);
13144
13492
  } catch (error) {
13145
13493
  return fail2(
13146
13494
  error instanceof Error ? error.message : "invalid guard path"
@@ -13149,7 +13497,7 @@ function readHooks(path, root, raw) {
13149
13497
  names.add(name);
13150
13498
  return {
13151
13499
  name,
13152
- command: command2,
13500
+ command: command3,
13153
13501
  targets,
13154
13502
  timeoutMs
13155
13503
  };
@@ -13372,13 +13720,13 @@ function readMcp(path, value) {
13372
13720
  fail(path, where, `unknown field ${key}`);
13373
13721
  }
13374
13722
  }
13375
- const command2 = entry2["command"];
13723
+ const command3 = entry2["command"];
13376
13724
  const url = entry2["url"];
13377
- if (command2 === void 0 === (url === void 0)) {
13725
+ if (command3 === void 0 === (url === void 0)) {
13378
13726
  fail(path, where, "provide exactly one of command or url");
13379
13727
  }
13380
13728
  for (const [key, item] of [
13381
- ["command", command2],
13729
+ ["command", command3],
13382
13730
  ["url", url],
13383
13731
  ["cwd", entry2["cwd"]]
13384
13732
  ]) {
@@ -13476,7 +13824,7 @@ function readMcp(path, value) {
13476
13824
  fail(path, where, "mailbox must be a boolean");
13477
13825
  }
13478
13826
  servers[alias] = {
13479
- ...typeof command2 === "string" ? { command: command2 } : { url },
13827
+ ...typeof command3 === "string" ? { command: command3 } : { url },
13480
13828
  args: list(path, `${where} args`, entry2["args"], false),
13481
13829
  env,
13482
13830
  ...envVars.length > 0 ? { envVars } : {},
@@ -13879,8 +14227,8 @@ import { spawnSync as spawnSync3 } from "child_process";
13879
14227
  import { existsSync as existsSync21, readFileSync as readFileSync28 } from "fs";
13880
14228
  import { join as join25 } from "path";
13881
14229
  import { parse as parse8 } from "smol-toml";
13882
- var spawnRunner = (command2, args, cwd, env) => {
13883
- const result = spawnSync3(command2, [...args], {
14230
+ var spawnRunner = (command3, args, cwd, env) => {
14231
+ const result = spawnSync3(command3, [...args], {
13884
14232
  cwd,
13885
14233
  ...env ? { env: { ...process.env, ...env } } : {},
13886
14234
  encoding: "utf8",
@@ -15137,6 +15485,11 @@ var tuiOptions = [
15137
15485
  },
15138
15486
  { name: "rows", describe: "Terminal rows (12\u20131000)", placeholder: "<count>" },
15139
15487
  { name: "index", describe: "Worker dock index", placeholder: "<count>" },
15488
+ {
15489
+ name: "memory-mb",
15490
+ describe: "Worker memory reservation",
15491
+ placeholder: "<count>"
15492
+ },
15140
15493
  {
15141
15494
  name: "confirm-session",
15142
15495
  describe: "Exact target ID for stop",
@@ -15149,7 +15502,7 @@ var tuiOptions = [
15149
15502
  },
15150
15503
  {
15151
15504
  name: "runtime",
15152
- describe: "Main native runtime ID for launch",
15505
+ describe: "Native runtime ID for main launch or worker start",
15153
15506
  placeholder: "<runtime>"
15154
15507
  },
15155
15508
  {
@@ -15171,6 +15524,8 @@ var tuiOptions = [
15171
15524
  var allowed2 = {
15172
15525
  launch: ["config", "runtime", "yes", "new"],
15173
15526
  plan: ["config", "runtime"],
15527
+ recovery: ["config"],
15528
+ "start-worker": ["config", "view", "runtime", "memory-mb"],
15174
15529
  doctor: [],
15175
15530
  create: ["group-file", "columns", "rows", "config", "actor-file"],
15176
15531
  inspect: ["view"],
@@ -15188,6 +15543,7 @@ var allowed2 = {
15188
15543
  close: ["view"],
15189
15544
  dismiss: ["view"],
15190
15545
  management: ["view"],
15546
+ "worker-menu": ["view"],
15191
15547
  check: ["view"]
15192
15548
  };
15193
15549
  function parseTui(args) {
@@ -15212,7 +15568,23 @@ function parseTui(args) {
15212
15568
  return Number(value);
15213
15569
  };
15214
15570
  if (action === "doctor") return { action, input: {} };
15215
- if (action === "launch" || action === "plan")
15571
+ if (action === "start-worker") {
15572
+ const config = one(args, "config");
15573
+ const viewFile = one(args, "view");
15574
+ if (config && viewFile)
15575
+ throw new ArgumentError("Choose --config or --view for worker start");
15576
+ return {
15577
+ action,
15578
+ viewFile,
15579
+ input: {},
15580
+ startup: {
15581
+ config,
15582
+ runtime: required2("runtime"),
15583
+ memoryMb: number("memory-mb")
15584
+ }
15585
+ };
15586
+ }
15587
+ if (action === "launch" || action === "plan" || action === "recovery")
15216
15588
  return {
15217
15589
  action,
15218
15590
  input: {},
@@ -15263,14 +15635,36 @@ function parseTui(args) {
15263
15635
  var tuiCommand = {
15264
15636
  name: "tui",
15265
15637
  describe: "Open a native terminal workspace with optional configuration",
15266
- usage: "pmcp tui [--config <file>] [--runtime <id>] | plan | doctor | operation --view <file>",
15638
+ usage: "pmcp tui [--config <file>] [--runtime <id>] | plan | recovery | doctor | start-worker --runtime <id> | operation --view <file>",
15267
15639
  options: tuiOptions,
15268
15640
  async run(context) {
15269
15641
  const invocation2 = parseTui(context.args);
15270
15642
  const { invokeTui: invokeTui2, failedInvocation: failedInvocation2 } = await Promise.resolve().then(() => (init_invoke2(), invoke_exports2));
15271
15643
  try {
15272
15644
  let receipt;
15273
- if (invocation2.action === "launch" || invocation2.action === "plan") {
15645
+ if (invocation2.action === "start-worker") {
15646
+ if (invocation2.viewFile) {
15647
+ const { startViewWorker: startViewWorker2 } = await Promise.resolve().then(() => (init_worker_start(), worker_start_exports));
15648
+ receipt = await startViewWorker2(
15649
+ invocation2.viewFile,
15650
+ invocation2.startup.runtime,
15651
+ invocation2.startup?.memoryMb
15652
+ );
15653
+ } else {
15654
+ const { startWorkspaceWorker: startWorkspaceWorker2 } = await Promise.resolve().then(() => (init_worker(), worker_exports));
15655
+ receipt = await startWorkspaceWorker2(
15656
+ { config: invocation2.startup?.config, cwd: context.cwd },
15657
+ invocation2.startup.runtime,
15658
+ invocation2.startup?.memoryMb
15659
+ );
15660
+ }
15661
+ } else if (invocation2.action === "recovery") {
15662
+ const { inspectStartupRecovery: inspectStartupRecovery2 } = await Promise.resolve().then(() => (init_recovery2(), recovery_exports));
15663
+ receipt = inspectStartupRecovery2({
15664
+ ...invocation2.startup,
15665
+ cwd: context.cwd
15666
+ });
15667
+ } else if (invocation2.action === "launch" || invocation2.action === "plan") {
15274
15668
  const { launchStartup: launchStartup2, planStartup: planStartup2 } = await Promise.resolve().then(() => (init_entry2(), entry_exports));
15275
15669
  const request3 = { ...invocation2.startup, cwd: context.cwd };
15276
15670
  receipt = invocation2.action === "plan" ? planStartup2(request3) : await launchStartup2(
@@ -15525,7 +15919,7 @@ function dispatch(argv, options2 = {}) {
15525
15919
  return defaultEntry(options2, { ui, env, cwd });
15526
15920
  }
15527
15921
  if (verb === "--help" || verb === "-h" || verb === "help") {
15528
- const named2 = COMMANDS.find((command3) => command3.name === rest[0]);
15922
+ const named2 = COMMANDS.find((command4) => command4.name === rest[0]);
15529
15923
  if (named2) printCommandHelp(ui, named2);
15530
15924
  else printHelp(ui, COMMANDS, PLANNED);
15531
15925
  return 0;
@@ -15538,8 +15932,8 @@ function dispatch(argv, options2 = {}) {
15538
15932
  if (verb.startsWith("-")) {
15539
15933
  return run(serveCommand, rest0, { ui, env, cwd });
15540
15934
  }
15541
- const command2 = COMMANDS.find((candidate2) => candidate2.name === verb);
15542
- if (command2 === void 0) {
15935
+ const command3 = COMMANDS.find((candidate2) => candidate2.name === verb);
15936
+ if (command3 === void 0) {
15543
15937
  if (PLANNED.includes(verb))
15544
15938
  ui.error(`"${verb}" is designed but not implemented yet`);
15545
15939
  else ui.error(`unknown command "${verb}"`);
@@ -15547,12 +15941,12 @@ function dispatch(argv, options2 = {}) {
15547
15941
  return 2;
15548
15942
  }
15549
15943
  if (rest.includes("--help") || rest.includes("-h")) {
15550
- printCommandHelp(ui, command2);
15944
+ printCommandHelp(ui, command3);
15551
15945
  return 0;
15552
15946
  }
15553
- return run(command2, rest, { ui, env, cwd });
15947
+ return run(command3, rest, { ui, env, cwd });
15554
15948
  }
15555
- var VERSION = "0.15.0";
15949
+ var VERSION = "0.16.1";
15556
15950
  async function defaultEntry(options2, context) {
15557
15951
  const terminal = options2.terminal ?? {
15558
15952
  stdin: process.stdin.isTTY === true,
@@ -15576,16 +15970,16 @@ async function defaultEntry(options2, context) {
15576
15970
  printHelp(context.ui, COMMANDS, PLANNED);
15577
15971
  return 0;
15578
15972
  }
15579
- async function run(command2, argv, context) {
15973
+ async function run(command3, argv, context) {
15580
15974
  try {
15581
- return await command2.run({
15975
+ return await command3.run({
15582
15976
  ...context,
15583
- args: parseArgs(argv, command2.options)
15977
+ args: parseArgs(argv, command3.options)
15584
15978
  });
15585
15979
  } catch (error) {
15586
15980
  if (error instanceof ArgumentError) {
15587
15981
  context.ui.error(error.message);
15588
- printCommandHelp(context.ui, command2);
15982
+ printCommandHelp(context.ui, command3);
15589
15983
  return 2;
15590
15984
  }
15591
15985
  context.ui.error(error instanceof Error ? error.message : "command failed");