@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.
@@ -1,4 +1,4 @@
1
- import { E as Embedder } from './server-DAcT89iv.js';
1
+ import { E as Embedder } from './server-VWSY3Shw.js';
2
2
  import { E as EncoderOptions } from './encoder-DGRy9skC.js';
3
3
 
4
4
  /**
package/dist/http.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { S as SkillServerOptions, E as Embedder, a as SkillEntry } from './server-DAcT89iv.js';
2
- import { L as LoadEmbedderOptions } from './embed-CVCEz2FH.js';
3
- export { l as loadEmbedder } from './embed-CVCEz2FH.js';
1
+ import { S as SkillServerOptions, E as Embedder, a as SkillEntry } from './server-VWSY3Shw.js';
2
+ export { C as CatalogOptions, R as Runtime, r as readCatalog, v as verifiedRuntimesOf } from './server-VWSY3Shw.js';
3
+ import { L as LoadEmbedderOptions } from './embed-DRROSD3F.js';
4
+ export { l as loadEmbedder } from './embed-DRROSD3F.js';
4
5
  import { M as MailboxService } from './service-qpCsOXkQ.js';
5
6
  export { D as DEFAULT_MODEL_ID } from './encoder-DGRy9skC.js';
6
7
  import 'zod';
@@ -80,4 +81,4 @@ interface SkillHttpHandler {
80
81
  }
81
82
  declare function createSkillHttpHandler(options: SkillHttpOptions): SkillHttpHandler;
82
83
 
83
- export { DEFAULT_MAX_REQUEST_BYTES, DEFAULT_TIERS, Embedder, type RateLimitOptions, type SkillCaller, type SkillHttpHandler, type SkillHttpOptions, type SkillHttpSemanticOptions, createSkillHttpHandler, visibleTo };
84
+ export { DEFAULT_MAX_REQUEST_BYTES, DEFAULT_TIERS, Embedder, type RateLimitOptions, type SkillCaller, SkillEntry, type SkillHttpHandler, type SkillHttpOptions, type SkillHttpSemanticOptions, createSkillHttpHandler, visibleTo };
package/dist/http.js CHANGED
@@ -694,7 +694,7 @@ function withoutBody({
694
694
 
695
695
  // src/intent.ts
696
696
  function normaliseIntent(intent) {
697
- return intent.toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
697
+ return intent.normalize("NFC").toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
698
698
  }
699
699
 
700
700
  // src/embed.ts
@@ -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.1";
2298
2298
  function registerSkillTools(server, tools) {
2299
2299
  registerCatalogTool(server, tools);
2300
2300
  registerFindTool(server, tools);
@@ -2426,6 +2426,8 @@ export {
2426
2426
  DEFAULT_TIERS,
2427
2427
  createSkillHttpHandler,
2428
2428
  loadEmbedder,
2429
+ readCatalog,
2430
+ verifiedRuntimesOf,
2429
2431
  visibleTo
2430
2432
  };
2431
2433
  //# sourceMappingURL=http.js.map
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { I as IntentCache, R as Ranking, S as SkillServerOptions, E as Embedder, C as CatalogOptions, a as SkillEntry, M as MetadataValue, b as SkillTools } from './server-DAcT89iv.js';
2
- export { A as ASSET_KINDS, c as AssetKind, B as BundleFile, d as CATALOG_PAGE_MAX, e as CATALOG_PAGE_SIZE, f as COMPATIBILITY_MAX, g as CallResponse, h as CatalogItem, i as CatalogResponse, j as Classified, D as DEFAULT_FIND_LIMIT, k as DESCRIBE_FILE_LIMIT, l as DESCRIPTION_MAX, F as FileKind, m as FindOptions, n as FindResult, o as IntentCacheStats, K as KindFilter, p as MAX_SKILL_DEPTH, q as Match, r as MetadataLeaf, N as NAME_MAX, s as READ_BUDGET_BYTES, t as RUNTIMES, u as ReadResult, v as Runtime, w as SKIPPED_DIRS, x as SkillBundle, y as SkillFile, z as SkillFileContent, G as SkillFileListing, H as SkillFilters, J as bundleResponseSchema, L as bundleSkill, O as catalogResponseSchema, P as classify, Q as cosine, T as createSkillTools, U as describeResponseSchema, V as detectRuntime, W as displayPath, X as find, Y as findResultSchema, Z as isUtf8Text, _ as kindOf, $ as lexicalScore, a0 as listSkillFiles, a1 as mimeOf, a2 as normaliseIntent, a3 as parseSkillFrontmatter, a4 as readBody, a5 as readCatalog, a6 as readFrontmatter, a7 as readSkillFile, a8 as runtimeOfClient, a9 as specProblems, aa as verifiedRuntimesOf, ab as withoutBody } from './server-DAcT89iv.js';
3
- import { L as LoadEmbedderOptions } from './embed-CVCEz2FH.js';
4
- export { E as EntryVectorStore, e as encodeEntries, l as loadEmbedder, o as openEntryVectors } from './embed-CVCEz2FH.js';
1
+ import { I as IntentCache, b as Ranking, S as SkillServerOptions, E as Embedder, C as CatalogOptions, a as SkillEntry, M as MetadataValue, c as SkillTools } from './server-VWSY3Shw.js';
2
+ export { A as ASSET_KINDS, d as AssetKind, B as BundleFile, e as CATALOG_PAGE_MAX, f as CATALOG_PAGE_SIZE, g as COMPATIBILITY_MAX, h as CallResponse, i as CatalogItem, j as CatalogResponse, k as Classified, D as DEFAULT_FIND_LIMIT, l as DESCRIBE_FILE_LIMIT, m as DESCRIPTION_MAX, F as FileKind, n as FindOptions, o as FindResult, p as IntentCacheStats, K as KindFilter, q as MAX_SKILL_DEPTH, s as Match, t as MetadataLeaf, N as NAME_MAX, u as READ_BUDGET_BYTES, w as RUNTIMES, x as ReadResult, R as Runtime, y as SKIPPED_DIRS, z as SkillBundle, G as SkillFile, H as SkillFileContent, J as SkillFileListing, L as SkillFilters, O as bundleResponseSchema, P as bundleSkill, Q as catalogResponseSchema, T as classify, U as cosine, V as createSkillTools, W as describeResponseSchema, X as detectRuntime, Y as displayPath, Z as find, _ as findResultSchema, $ as isUtf8Text, a0 as kindOf, a1 as lexicalScore, a2 as listSkillFiles, a3 as mimeOf, a4 as normaliseIntent, a5 as parseSkillFrontmatter, a6 as readBody, r as readCatalog, a7 as readFrontmatter, a8 as readSkillFile, a9 as runtimeOfClient, aa as specProblems, v as verifiedRuntimesOf, ab as withoutBody } from './server-VWSY3Shw.js';
3
+ import { L as LoadEmbedderOptions } from './embed-DRROSD3F.js';
4
+ export { E as EntryVectorStore, e as encodeEntries, l as loadEmbedder, o as openEntryVectors } from './embed-DRROSD3F.js';
5
5
  export { D as DEFAULT_MODEL_ID, a as EmbeddingModel, b as EncoderDtype, E as EncoderOptions, c as EncoderSpec, R as ResolvedEncoder, e as embeddingModels, d as encoderSpec, r as resolveEncoder } from './encoder-DGRy9skC.js';
6
6
  import { P as ProjectConfig } from './config-BkSpjUG6.js';
7
7
  export { C as CONFIG_FILE, a as CatalogSection, b as ConfigError, E as EmbeddingSection, f as findConfig, r as readConfig } from './config-BkSpjUG6.js';
@@ -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.1";
279
279
  /**
280
280
  * Registers the skill_* tools on a server.
281
281
  *
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/intent.ts
2
2
  function normaliseIntent(intent) {
3
- return intent.toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
3
+ return intent.normalize("NFC").toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
4
4
  }
5
5
 
6
6
  // src/storage/sqlite.ts
@@ -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.1";
4683
4683
  function registerSkillTools(server, tools) {
4684
4684
  registerCatalogTool(server, tools);
4685
4685
  registerFindTool(server, tools);
@@ -144,8 +144,7 @@ interface IntentCache {
144
144
  close(): void;
145
145
  }
146
146
  /**
147
- * Same question, differently typed. Case, punctuation and spacing only — the
148
- * key has to survive a caller writing "How do I wire X?" and "how do i wire x".
147
+ * Canonically equivalent Unicode must share the same query and cache key.
149
148
  */
150
149
  declare function normaliseIntent(intent: string): string;
151
150
 
@@ -541,4 +540,4 @@ declare function createSkillTools(options: SkillServerOptions): {
541
540
  };
542
541
  type SkillTools = ReturnType<typeof createSkillTools>;
543
542
 
544
- export { lexicalScore as $, ASSET_KINDS as A, type BundleFile as B, type CatalogOptions as C, DEFAULT_FIND_LIMIT as D, type Embedder as E, type FileKind as F, type SkillFileListing as G, type SkillFilters as H, type IntentCache as I, bundleResponseSchema as J, type KindFilter as K, bundleSkill as L, type MetadataValue as M, NAME_MAX as N, catalogResponseSchema as O, classify as P, cosine as Q, type Ranking as R, type SkillServerOptions as S, createSkillTools as T, describeResponseSchema as U, detectRuntime as V, displayPath as W, find as X, findResultSchema as Y, isUtf8Text as Z, kindOf as _, type SkillEntry as a, listSkillFiles as a0, mimeOf as a1, normaliseIntent as a2, frontmatterObject as a3, readBody as a4, readCatalog as a5, readFrontmatter as a6, readSkillFile as a7, runtimeOfClient as a8, specProblems as a9, verifiedRuntimesOf as aa, withoutBody as ab, type SkillTools as b, type AssetKind as c, CATALOG_PAGE_MAX as d, CATALOG_PAGE_SIZE as e, COMPATIBILITY_MAX as f, type CallResponse as g, type CatalogItem as h, type CatalogResponse as i, type Classified as j, DESCRIBE_FILE_LIMIT as k, DESCRIPTION_MAX as l, type FindOptions as m, type FindResult as n, type IntentCacheStats as o, MAX_SKILL_DEPTH as p, type Match as q, type MetadataLeaf as r, READ_BUDGET_BYTES as s, RUNTIMES as t, type ReadResult as u, type Runtime as v, SKIPPED_DIRS as w, type SkillBundle as x, type SkillFile as y, type SkillFileContent as z };
543
+ export { isUtf8Text as $, ASSET_KINDS as A, type BundleFile as B, type CatalogOptions as C, DEFAULT_FIND_LIMIT as D, type Embedder as E, type FileKind as F, type SkillFile as G, type SkillFileContent as H, type IntentCache as I, type SkillFileListing as J, type KindFilter as K, type SkillFilters as L, type MetadataValue as M, NAME_MAX as N, bundleResponseSchema as O, bundleSkill as P, catalogResponseSchema as Q, type Runtime as R, type SkillServerOptions as S, classify as T, cosine as U, createSkillTools as V, describeResponseSchema as W, detectRuntime as X, displayPath as Y, find as Z, findResultSchema as _, type SkillEntry as a, kindOf as a0, lexicalScore as a1, listSkillFiles as a2, mimeOf as a3, normaliseIntent as a4, frontmatterObject as a5, readBody as a6, readFrontmatter as a7, readSkillFile as a8, runtimeOfClient as a9, specProblems as aa, withoutBody as ab, type Ranking as b, type SkillTools as c, type AssetKind as d, CATALOG_PAGE_MAX as e, CATALOG_PAGE_SIZE as f, COMPATIBILITY_MAX as g, type CallResponse as h, type CatalogItem as i, type CatalogResponse as j, type Classified as k, DESCRIBE_FILE_LIMIT as l, DESCRIPTION_MAX as m, type FindOptions as n, type FindResult as o, type IntentCacheStats as p, MAX_SKILL_DEPTH as q, readCatalog as r, type Match as s, type MetadataLeaf as t, READ_BUDGET_BYTES as u, verifiedRuntimesOf as v, RUNTIMES as w, type ReadResult as x, SKIPPED_DIRS as y, type SkillBundle as z };
package/dist/stdio.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { serveStdio } from '@modelcontextprotocol/server/stdio';
3
- import { S as SkillServerOptions, C as CatalogOptions } from './server-DAcT89iv.js';
3
+ import { S as SkillServerOptions, C as CatalogOptions } from './server-VWSY3Shw.js';
4
4
  import { M as MailboxService } from './service-qpCsOXkQ.js';
5
5
  import { M as MailboxAttachmentOptions } from './attachment-tU5FZrgZ.js';
6
6
  import 'zod';
package/dist/stdio.js CHANGED
@@ -1050,7 +1050,7 @@ import { createHash as createHash2 } from "crypto";
1050
1050
 
1051
1051
  // src/intent.ts
1052
1052
  function normaliseIntent(intent) {
1053
- return intent.toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
1053
+ return intent.normalize("NFC").toLowerCase().replaceAll(/[^\p{L}\p{N}\s]/gu, " ").split(/\s+/u).filter(Boolean).join(" ");
1054
1054
  }
1055
1055
 
1056
1056
  // src/find.ts
@@ -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.1";
1945
1945
  function registerSkillTools(server, tools) {
1946
1946
  registerCatalogTool(server, tools);
1947
1947
  registerFindTool(server, tools);
@@ -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: {},
@@ -3427,13 +3451,19 @@ var CommonHarness = class {
3427
3451
  // src/tui/application/feedback.ts
3428
3452
  import { join as join20 } from "path";
3429
3453
  var hints = {
3430
- startup_requires_recovery: "Inspect the saved group with pmcp tui plan and pmcp harness recovery inspect. No runtime was restarted.",
3454
+ startup_requires_recovery: "Run pmcp tui recovery for saved ownership and exact inspection commands. No runtime was restarted.",
3431
3455
  startup_group_paused: "The saved group is paused. Resume it explicitly through the harness CLI before reopening.",
3432
3456
  startup_main_unavailable: "Inspect the saved main and recovery state. Stop the old group explicitly before using pmcp tui --new.",
3433
3457
  startup_busy: "Another workspace launch is running. Wait and retry.",
3434
3458
  startup_runtime_conflict: "The saved main uses another runtime. Stop the old group before creating a new workspace.",
3435
3459
  workspace_already_running: "Reopen the existing workspace, or stop its exact owned group before using --new.",
3436
3460
  runtime_executable_unavailable: "Install the selected native CLI on PATH, or choose an installed runtime with --runtime.",
3461
+ view_client_unavailable: "Open the view before using the worker menu, or run pmcp tui start-worker with --view and --runtime.",
3462
+ runtime_not_granted: "Choose a runtime granted by this owned group. Inspect the group before creating another workspace.",
3463
+ runtime_capacity_exceeded: "Stop an exact owned worker before adding another, or use the main session alone.",
3464
+ aggregate_memory_budget_exceeded: "Use fewer sessions or start a worker with an explicit smaller --memory-mb reservation.",
3465
+ group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
3466
+ startup_group_not_ready: "Inspect the group and resume a paused group explicitly before adding a worker.",
3437
3467
  control_connection_lost: "The native writer or lease disappeared. Inspect and release control before reconnecting.",
3438
3468
  control_busy: "Another controller holds the lease. Release it explicitly before retrying.",
3439
3469
  native_writer_active: "A native writer is still attached. Detach that writer before retrying.",
@@ -3489,6 +3519,14 @@ function phaseLabel(phase2) {
3489
3519
  return labels[phase2];
3490
3520
  }
3491
3521
 
3522
+ // src/tui/presentation/target.ts
3523
+ function targetStatus(view) {
3524
+ const role2 = view.target === view.mainId ? "MAIN" : "WORKER";
3525
+ const phase2 = view.phase === "read-only" ? "OBSERVE" : phaseLabel(view.phase);
3526
+ const input = view.phase === "controlled" ? "INPUT ON" : "INPUT OFF";
3527
+ return `${role2} ${view.target.slice(0, 8)} | ${phase2} | ${input}`;
3528
+ }
3529
+
3492
3530
  // src/tui/adapters/tmux/connection.ts
3493
3531
  import { spawn as spawn2 } from "child_process";
3494
3532
  import { join as join21 } from "path";
@@ -3642,7 +3680,8 @@ function bindKeys(connection, view, file) {
3642
3680
  ["b", "mailbox"],
3643
3681
  ["d", "detach"],
3644
3682
  ["!", "dismiss"],
3645
- ["g", "management"]
3683
+ ["g", "management"],
3684
+ ["n", "worker-menu"]
3646
3685
  ])
3647
3686
  connection.command([
3648
3687
  "bind-key",
@@ -3663,7 +3702,7 @@ function bindKeys(connection, view, file) {
3663
3702
  "bind-key",
3664
3703
  "?",
3665
3704
  "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"
3705
+ "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
3706
  ]);
3668
3707
  connection.command([
3669
3708
  "set-hook",
@@ -3863,7 +3902,7 @@ var TmuxView = class {
3863
3902
  "set-option",
3864
3903
  "-g",
3865
3904
  "status-right",
3866
- `${view.target === view.mainId ? "main" : "worker"} | ${phaseLabel(view.phase)}${failure2} | Ctrl-g ?`
3905
+ `${targetStatus(view)}${failure2} | Ctrl-g ?`
3867
3906
  ]);
3868
3907
  }
3869
3908
  input(pane, enabled) {
@@ -3886,6 +3925,32 @@ var TmuxView = class {
3886
3925
  clients() {
3887
3926
  return this.connection.command(["list-clients", "-F", "#{client_pid}"]);
3888
3927
  }
3928
+ workerMenu(view) {
3929
+ const pane = view.workerPane ?? view.mainPane;
3930
+ this.connection.assertOwned(pane);
3931
+ const popup = [
3932
+ "tmux",
3933
+ "-S",
3934
+ this.connection.socket,
3935
+ "display-popup",
3936
+ "-E",
3937
+ "-t",
3938
+ pane,
3939
+ "-w",
3940
+ "90%",
3941
+ "-h",
3942
+ "80%",
3943
+ this.shell([
3944
+ view.node,
3945
+ view.worker,
3946
+ "worker-menu",
3947
+ "--view",
3948
+ this.stateFile,
3949
+ "--callback"
3950
+ ])
3951
+ ];
3952
+ this.connection.command(["run-shell", "-b", popup.map(quote).join(" ")]);
3953
+ }
3889
3954
  copy(pane) {
3890
3955
  this.connection.assertOwned(pane);
3891
3956
  this.connection.command(["copy-mode", "-t", pane]);
@@ -3952,6 +4017,7 @@ function dockLines(snapshot) {
3952
4017
  ),
3953
4018
  "",
3954
4019
  "Ctrl-g w: observe worker",
4020
+ "Ctrl-g n: add worker",
3955
4021
  "Ctrl-g m: return to main",
3956
4022
  "Ctrl-g c: acquire control",
3957
4023
  "Ctrl-g r: release control",
@@ -3963,7 +4029,7 @@ function dockLines(snapshot) {
3963
4029
  ];
3964
4030
  }
3965
4031
  function coordinationLines(snapshot, view) {
3966
- const target = `TARGET ${view.target === view.mainId ? "main" : "worker"} ${safe(view.target.slice(0, 8))} / ${phaseLabel(view.phase)}`;
4032
+ const target = `TARGET ${safe(targetStatus(view))}`;
3967
4033
  if (view.failure)
3968
4034
  return [
3969
4035
  target,
@@ -5717,6 +5783,11 @@ var handlers = {
5717
5783
  close: closeView,
5718
5784
  check: checkControl,
5719
5785
  dismiss: ({ view, file }) => dismissFailure(file, view),
5786
+ "worker-menu": async (context) => {
5787
+ if (!context.renderer.clients()) throw new Error("view_client_unavailable");
5788
+ await releaseControl(context);
5789
+ context.renderer.workerMenu(context.view);
5790
+ },
5720
5791
  management: async (context) => {
5721
5792
  await releaseControl(context);
5722
5793
  if (context.view.managementWindow) {
@@ -5850,6 +5921,70 @@ function runtime() {
5850
5921
  return { node: realpathSync10(available.node), worker: bundled };
5851
5922
  }
5852
5923
 
5924
+ // src/tui/startup/diagnostics.ts
5925
+ function remediation(tools, userManagerAvailable) {
5926
+ const steps = [];
5927
+ if (tools.platform !== "linux")
5928
+ steps.push(
5929
+ "Use a qualified Linux host for the terminal workspace. Other PMCP CLI features do not require this host profile."
5930
+ );
5931
+ const missing = ["tmux", "flock", "systemctl", "systemdRun"].filter(
5932
+ (name) => !tools[name]
5933
+ );
5934
+ if (missing.length)
5935
+ steps.push(
5936
+ `Install the missing tmux, util-linux or systemd tools for your distribution and add them to PATH: ${missing.join(", ")}.`
5937
+ );
5938
+ if (!tools.nodeSupported)
5939
+ steps.push(
5940
+ "Select Node.js 22 or later through your existing version manager and check node --version in this terminal."
5941
+ );
5942
+ if (tools.supported && !userManagerAvailable)
5943
+ steps.push(
5944
+ "Check systemctl --user status in your login session and restore access to its user manager before launching. Doctor does not start or repair services."
5945
+ );
5946
+ return steps;
5947
+ }
5948
+ function inspectTerminalHost() {
5949
+ const tools = dependencies();
5950
+ let userManagerAvailable = false;
5951
+ let reason;
5952
+ try {
5953
+ if (!tools.supported) throw new Error("tui_dependencies_unavailable");
5954
+ assertNativeHost();
5955
+ userManagerAvailable = true;
5956
+ } catch (error) {
5957
+ reason = error instanceof Error ? error.message : "native_host_unavailable";
5958
+ }
5959
+ const runtimes = Object.entries(runtimeExecutables).map(
5960
+ ([id2, executable]) => {
5961
+ const path = locateExecutable(executable);
5962
+ return {
5963
+ id: id2,
5964
+ executable,
5965
+ installed: Boolean(path),
5966
+ ...path ? { path } : {},
5967
+ nativeAcceptance: "not-assessed",
5968
+ authentication: "not-inspected"
5969
+ };
5970
+ }
5971
+ );
5972
+ return {
5973
+ schemaVersion: 1,
5974
+ ok: true,
5975
+ ...tools,
5976
+ supported: tools.supported && userManagerAvailable,
5977
+ userManagerAvailable,
5978
+ ...reason ? { reason } : {},
5979
+ runtimes,
5980
+ qualification: "Prerequisite availability only; native behavior and cgroup delegation require separate qualification.",
5981
+ remediation: remediation(tools, userManagerAvailable),
5982
+ nextCommands: ["pmcp tui plan", "pmcp tui recovery"],
5983
+ providerInvoked: false,
5984
+ authenticationInspected: false
5985
+ };
5986
+ }
5987
+
5853
5988
  // src/tui/application/create.ts
5854
5989
  import { join as join29 } from "path";
5855
5990
  import { realpathSync as realpathSync11 } from "fs";
@@ -5928,10 +6063,9 @@ async function createView(input) {
5928
6063
 
5929
6064
  // src/tui/application/invoke.ts
5930
6065
  async function invokeTui(invocation) {
5931
- if (invocation.action === "launch" || invocation.action === "plan")
6066
+ if (["launch", "plan", "recovery", "start-worker"].includes(invocation.action))
5932
6067
  throw new Error("startup_requires_cli_context");
5933
- if (invocation.action === "doctor")
5934
- return { schemaVersion: 1, ok: true, ...dependencies() };
6068
+ if (invocation.action === "doctor") return inspectTerminalHost();
5935
6069
  if (invocation.action === "create") return createView(invocation.create);
5936
6070
  if (invocation.action === "inspect")
5937
6071
  return operate("inspect", invocation.viewFile);
@@ -6206,6 +6340,115 @@ function assertGroup(group, projectRoot, generation) {
6206
6340
  throw new Error("stale_group_generation");
6207
6341
  }
6208
6342
 
6343
+ // src/tui/application/worker-start.ts
6344
+ async function startViewWorker(file, runtime2, memoryMb) {
6345
+ const { view, common } = openView(file);
6346
+ if (view.state !== "open") throw new Error("view_closed");
6347
+ const observed = await common.snapshot();
6348
+ if (observed.group.state !== "ready") throw new Error("group_not_ready");
6349
+ const main = observed.sessions.find(
6350
+ (session) => session.id === view.mainId && session.role === "main"
6351
+ );
6352
+ if (!main?.alive) throw new Error("startup_main_unavailable");
6353
+ return common.call("session", "start", {
6354
+ input: { role: "worker", runtime: runtime2, memoryMb: memoryMb ?? main.memoryMb }
6355
+ });
6356
+ }
6357
+
6358
+ // src/cli/entry.ts
6359
+ function interactiveEntry(terminal) {
6360
+ if (!terminal.stdin || !terminal.stdout) return false;
6361
+ if (!terminal.term || terminal.term === "dumb") return false;
6362
+ return !terminal.ci || terminal.ci === "false" || terminal.ci === "0";
6363
+ }
6364
+
6365
+ // src/tui/startup/host.ts
6366
+ function terminalSupport() {
6367
+ try {
6368
+ if (!dependencies().supported)
6369
+ throw new Error("tui_dependencies_unavailable");
6370
+ assertNativeHost();
6371
+ planLayout(process.stdout.columns ?? 0, process.stdout.rows ?? 0);
6372
+ return { supported: true };
6373
+ } catch (error) {
6374
+ return {
6375
+ supported: false,
6376
+ reason: error instanceof Error ? error.message : "terminal_unavailable"
6377
+ };
6378
+ }
6379
+ }
6380
+ function requireTerminal() {
6381
+ if (!interactiveEntry({
6382
+ stdin: process.stdin.isTTY === true,
6383
+ stdout: process.stdout.isTTY === true,
6384
+ term: process.env.TERM,
6385
+ ci: process.env.CI
6386
+ }))
6387
+ throw new Error("terminal_required");
6388
+ const support = terminalSupport();
6389
+ if (!support.supported) throw new Error(support.reason);
6390
+ }
6391
+
6392
+ // src/tui/presentation/runtime-choice.ts
6393
+ import { createInterface } from "readline/promises";
6394
+ async function chooseRuntime(prompt, choices, preferred) {
6395
+ if (!choices.length) throw new Error("runtime_executable_unavailable");
6396
+ const fallback = Math.max(choices.indexOf(preferred), 0);
6397
+ process.stderr.write(
6398
+ `${choices.map((runtime2, index) => `${index + 1}. ${runtime2}`).join("\n")}
6399
+ `
6400
+ );
6401
+ while (true) {
6402
+ const answer = (await prompt.question(
6403
+ `Runtime [1-${choices.length}, Enter=${fallback + 1}, q=cancel] `
6404
+ )).trim();
6405
+ if (answer === "q") return void 0;
6406
+ if (answer === "") return choices[fallback];
6407
+ if (!/^\d+$/.test(answer)) continue;
6408
+ const index = Number(answer) - 1;
6409
+ if (Number.isSafeInteger(index) && index >= 0 && index < choices.length)
6410
+ return choices[index];
6411
+ }
6412
+ }
6413
+ async function confirmRuntimeStart(prompt, runtime2) {
6414
+ const answer = await prompt.question(
6415
+ `Start ${runtime2}? [Enter=start, q=cancel] `
6416
+ );
6417
+ return answer.trim() === "";
6418
+ }
6419
+ function terminalPrompt() {
6420
+ return createInterface({ input: process.stdin, output: process.stderr });
6421
+ }
6422
+
6423
+ // src/tui/presentation/worker-menu.ts
6424
+ async function runWorkerMenu(file) {
6425
+ requireTerminal();
6426
+ const { view, common } = openView(file);
6427
+ if (view.state !== "open") throw new Error("view_closed");
6428
+ const observed = await common.snapshot();
6429
+ const group = observed.group;
6430
+ if (group.state !== "ready") throw new Error("startup_group_not_ready");
6431
+ const choices = group.allowedRuntimes.filter((runtime2) => {
6432
+ const executable = runtimeExecutables[runtime2];
6433
+ if (!executable) return false;
6434
+ if (group.profile === "docker")
6435
+ return Boolean(group.dockerPolicy?.commands[runtime2]);
6436
+ return Boolean(locateExecutable(executable, group.projectRoot));
6437
+ });
6438
+ const main = observed.sessions.find((session) => session.id === view.mainId);
6439
+ if (!main?.alive) throw new Error("startup_main_unavailable");
6440
+ process.stderr.write(`PMCP WORKER | ${group.projectRoot}
6441
+ `);
6442
+ const prompt = terminalPrompt();
6443
+ try {
6444
+ const runtime2 = await chooseRuntime(prompt, choices, main.runtime);
6445
+ if (!runtime2 || !await confirmRuntimeStart(prompt, runtime2)) return;
6446
+ await startViewWorker(file, runtime2);
6447
+ } finally {
6448
+ prompt.close();
6449
+ }
6450
+ }
6451
+
6209
6452
  // src/tui/application/worker.ts
6210
6453
  var [role, ...argv] = process.argv.slice(2);
6211
6454
  var callback2 = argv.includes("--callback");
@@ -6222,6 +6465,14 @@ try {
6222
6465
  const result = await prepareWorkspace(request2);
6223
6466
  process.stdout.write(`${JSON.stringify(result)}
6224
6467
  `);
6468
+ } else if (role === "worker-menu") {
6469
+ const invocation = parseTui(
6470
+ parseArgs(
6471
+ ["worker-menu", ...argv.filter((arg) => arg !== "--callback")],
6472
+ tuiOptions
6473
+ )
6474
+ );
6475
+ await runWorkerMenu(invocation.viewFile);
6225
6476
  } else if (role === "attach") {
6226
6477
  const [file, sessionId, leaseFile] = argv;
6227
6478
  if (!file || !sessionId) throw new Error("invalid_attachment_arguments");
@@ -6254,7 +6505,14 @@ try {
6254
6505
  tuiOptions
6255
6506
  )
6256
6507
  );
6257
- if (["create", "doctor", "launch", "plan"].includes(invocation.action))
6508
+ if ([
6509
+ "create",
6510
+ "doctor",
6511
+ "launch",
6512
+ "plan",
6513
+ "recovery",
6514
+ "start-worker"
6515
+ ].includes(invocation.action))
6258
6516
  throw new Error("unsupported_worker_operation");
6259
6517
  const result = role === "locked" ? await operate(
6260
6518
  invocation.action,
@@ -6273,7 +6531,8 @@ try {
6273
6531
  const file = argv[index + 1];
6274
6532
  if (index >= 0 && file) {
6275
6533
  try {
6276
- recordCallbackFailure(file, argv[0] ?? "callback", error);
6534
+ const action = role === "worker-menu" ? role : argv[0] ?? "callback";
6535
+ recordCallbackFailure(file, action, error);
6277
6536
  const { view } = readView(file);
6278
6537
  if (view.state === "open") new TmuxView(view, file).status(view);
6279
6538
  } catch {
package/docs/hosting.md CHANGED
@@ -26,6 +26,19 @@ const skills = createSkillHttpHandler({
26
26
  export default { fetch: (request: Request) => skills.fetch(request) };
27
27
  ```
28
28
 
29
+ Import `readCatalog` and `verifiedRuntimesOf` from the same `/http` entry when
30
+ the host also displays the catalog. `verifiedRuntimesOf(metadata)` accepts an
31
+ array or a comma/whitespace-separated string, drops unknown names and removes
32
+ duplicates while preserving declaration order. Hosts that need a fixed display
33
+ order can sort the result themselves.
34
+
35
+ Pass `onReject: ({ path, reason }) => reportRejectedCatalog(path, reason)` to
36
+ observe rejected marketplace entries during startup and `reload()`. This callback
37
+ is synchronous and reports only the path and reason. A custom `loadCatalog` must
38
+ forward its options to `readCatalog` to retain rejection reporting. If the host
39
+ loads a separate site catalog, pass the callback to that load as well. Deduplicate
40
+ repeated warnings in the host and keep local paths out of public responses.
41
+
29
42
  The handler checks requests before they reach MCP:
30
43
 
31
44
  | Option | Default | Effect |