@sigrun/ext-codemode 0.1.3 → 0.2.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/CHANGELOG.md CHANGED
@@ -5,12 +5,22 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [0.1.4] - 2026-11-08
9
+
10
+ - CI: grant actions: read to the release workflow (the release-test skip lookup). No code change.
11
+
8
12
  ## [0.1.3] - 2026-11-07
9
13
 
10
- ## [Unreleased]
14
+ ## [0.2.1] - 2026-11-09
15
+
16
+ - CI: needs-core true (the settings rework added the `@sigrun/sigrun` file: dev dependency; the v0.2.0 tag's test job failed before publishing, so the version was never live).
17
+
18
+ ## [0.2.0] - 2026-11-09
11
19
 
12
20
  ### Changed
13
21
 
22
+ - MCP namespaces now come from the `mcp.servers` published hook and refresh
23
+ after settings writes; legacy `mcp.json` reading is removed.
14
24
  - peer pi-durable/pi-ai/chord ~1.5.0 (requires sigrun 0.11.0).
15
25
 
16
26
  ## [0.1.1] - 2026-10-27
package/README.md CHANGED
@@ -7,33 +7,28 @@ a restart — installed via upstream's
7
7
  sigrun's config and prompt additions.
8
8
 
9
9
  The sigrun extension-package contract: the default export is a factory
10
- taking no arguments, returning `{ extension }`.
10
+ taking the sigrun host, returning `{ extension }` and lifecycle handlers.
11
11
 
12
12
  ## What the factory does
13
13
 
14
- 1. Reads the MCP namespace snapshot ONCE from the same config
15
- sigrun-ext-mcp reads: `SIGRUN_MCP_CONFIG` env override →
16
- `~/.config/sigrun/mcp.json`. One `CodemodeNamespace` per enabled
17
- server, `prefix: "<server>/"`. Absent file → empty namespaces (the
18
- codemode tool + section still ship). A bad file warns at factory time
19
- (best-effort) and installs un-namespaced rather than throwing — a bad
20
- config must not cost the model its codemode tool.
14
+ 1. Creates one live MCP namespace array from sigrun-ext-mcp's
15
+ `mcp.servers` published hook. One `CodemodeNamespace` per enabled,
16
+ visible server, `prefix: "<server>/"`. The first call starts without
17
+ blocking the factory; committed settings writes and run settlement
18
+ refresh the same array in place. A failed hook call warns once per
19
+ failure status and preserves the last known namespaces.
21
20
  2. Builds the QuickJS sandbox (`CodemodeSandbox` from
22
21
  `@earendil-works/pi-codemode`) with pinned constructor options:
23
22
  `timeoutMs: 300_000`, `memoryLimitBytes: 256 MiB`.
24
23
  3. Wraps upstream's `createCodemodeExtension({ sandbox, namespaces })`
25
24
  with a companion `codemode_notice` section (below).
26
25
 
27
- Config fields read per server entry (the sigrun-ext-mcp shapes,
28
- duplicated here because that package is not npm-published): `enabled` /
29
- `disabled` (either false means off), `exposure`
30
- (`"codemode"` default | `"deferred"` | `"direct"` | `"hidden"`, plus the
31
- pi alias `"codemode-deferred"`), and `toolExposure` overrides. A server
32
- resolving fully `hidden` (server default `hidden` and every override
33
- `hidden`) is excluded from the snapshot — hidden tools never register,
26
+ The hook supplies each server's resolved `enabled`, `exposure`, and
27
+ `toolExposure` values. A fully `hidden` server (server default `hidden`
28
+ and every override `hidden`) is excluded: hidden tools never register,
34
29
  so nothing scripts can call remains. Per-tool `hidden` resolution is
35
- sigrun-ext-mcp's job at registration time; this snapshot only groups by
36
- prefix, and hidden tools never reach `agent.callable` to be grouped.
30
+ sigrun-ext-mcp's job at registration time; these namespaces only group
31
+ by prefix, and hidden tools never reach `agent.callable` to be grouped.
37
32
 
38
33
  ### The deadline (verified against pi-codemode 1.2.0)
39
34
 
@@ -83,7 +78,7 @@ sigrun-ext-mcp's README; the two must stay consistent.
83
78
 
84
79
  Replace this package by not installing it and installing yours: the
85
80
  surface is one tool named exactly `codemode` (plus the companion
86
- `codemode_notice` section and the factory-time namespace snapshot), so a
81
+ `codemode_notice` section and live MCP namespaces), so a
87
82
  per-name replacement is trivial — uninstall ours, install the replacement
88
83
  under the same tool name. Do NOT co-install: pi-durable resolves
89
84
  cross-extension duplicate tool names last-selected-wins, so a co-installed
@@ -4,11 +4,10 @@
4
4
  * `callers: ["model"]`, plus the upstream `codemode` prompt section) with
5
5
  * our two sigrun additions:
6
6
  *
7
- * 1. The MCP namespace snapshot (src/config.ts): one `CodemodeNamespace`
8
- * per configured MCP server, prefix `"<server>/"`, read once at factory
9
- * time. The upstream section groups `agent.callable` tools by the
10
- * longest matching prefix, so sigrun's `<server>/<tool>` MCP tools
11
- * render grouped per server with no renames (the namespace ruler).
7
+ * 1. The live MCP namespaces: one `CodemodeNamespace` per configured MCP
8
+ * server, prefix `"<server>/"`. The upstream section groups
9
+ * `agent.callable` tools by the longest matching prefix, so sigrun's
10
+ * `<server>/<tool>` MCP tools render grouped per server with no renames.
12
11
  *
13
12
  * 2. The warn-if-not-offered channel (the spec's corrected mechanism):
14
13
  * when the extension is installed but the `codemode` TOOL is not
@@ -52,11 +51,11 @@ export declare const CODEMODE_NOT_OFFERED_NOTICE: string;
52
51
  export declare function renderCodemodeNotice(input: PromptInput): string | undefined;
53
52
  /**
54
53
  * The sigrun codemode extension: upstream's `codemode` extension (tool +
55
- * section, with our namespace snapshot) plus the companion notice section.
54
+ * section, with our live namespace array) plus the companion notice section.
56
55
  */
57
56
  export declare function createSigrunCodemodeExtension(options: {
58
57
  /** Runs the scripts: `CodemodeSandbox` on Node. */
59
58
  readonly sandbox: CodemodeSandboxBase;
60
- /** The factory-time MCP namespace snapshot (src/config.ts). */
59
+ /** The live MCP namespace array; upstream reads it at use time. */
61
60
  readonly namespaces?: readonly CodemodeNamespace[];
62
61
  }): Extension;
package/dist/extension.js CHANGED
@@ -4,11 +4,10 @@
4
4
  * `callers: ["model"]`, plus the upstream `codemode` prompt section) with
5
5
  * our two sigrun additions:
6
6
  *
7
- * 1. The MCP namespace snapshot (src/config.ts): one `CodemodeNamespace`
8
- * per configured MCP server, prefix `"<server>/"`, read once at factory
9
- * time. The upstream section groups `agent.callable` tools by the
10
- * longest matching prefix, so sigrun's `<server>/<tool>` MCP tools
11
- * render grouped per server with no renames (the namespace ruler).
7
+ * 1. The live MCP namespaces: one `CodemodeNamespace` per configured MCP
8
+ * server, prefix `"<server>/"`. The upstream section groups
9
+ * `agent.callable` tools by the longest matching prefix, so sigrun's
10
+ * `<server>/<tool>` MCP tools render grouped per server with no renames.
12
11
  *
13
12
  * 2. The warn-if-not-offered channel (the spec's corrected mechanism):
14
13
  * when the extension is installed but the `codemode` TOOL is not
@@ -58,7 +57,7 @@ export function renderCodemodeNotice(input) {
58
57
  }
59
58
  /**
60
59
  * The sigrun codemode extension: upstream's `codemode` extension (tool +
61
- * section, with our namespace snapshot) plus the companion notice section.
60
+ * section, with our live namespace array) plus the companion notice section.
62
61
  */
63
62
  export function createSigrunCodemodeExtension(options) {
64
63
  const upstream = createCodemodeExtension({
package/dist/index.d.ts CHANGED
@@ -1,41 +1,19 @@
1
1
  /**
2
2
  * sigrun-ext-codemode: the codemode factory (the loader contract).
3
3
  *
4
- * The sigrun extension-package contract (sigrun's src/harness/loader.ts):
5
- * the default export is a FACTORY TAKING NO ARGUMENTS, returning
6
- * `{ extension }`. The loader calls it synchronously at boot, before
7
- * `Harness.open` reads the registry.
4
+ * MCP namespaces come from ext-mcp's `mcp.servers` published hook. Codemode
5
+ * loads before ext-mcp, so the factory starts (but never awaits) the first
6
+ * refresh; committed settings writes and the per-run lifecycle refresh it
7
+ * again. Upstream reads the namespaces array at use time, so the
8
+ * owned array is updated in place rather than replaced.
8
9
  *
9
- * What the factory does, in order:
10
- * 1. Reads the MCP namespace snapshot ONCE (src/config.ts):
11
- * `SIGRUN_MCP_CONFIG` → `~/.config/sigrun/mcp.json`; absent file →
12
- * empty namespaces (the extension still ships its codemode tool +
13
- * section); a bad file warns and installs un-namespaced.
14
- * 2. Builds the QuickJS sandbox with the pinned constructor options
15
- * (timeoutMs 300_000 — pi-codemode's own default, restated so the
16
- * contract does not drift; memoryLimitBytes 256 MiB — a real cap
17
- * where the upstream default is unbounded wasm32).
18
- * 3. Wraps upstream's `createCodemodeExtension({ sandbox, namespaces })`
19
- * with the companion notice section (src/extension.ts).
20
- *
21
- * Note on `timeoutMs` (verified against pi-codemode 1.2.0): the
22
- * CONSTRUCTOR timeout governs `sandbox.execute()`; the durable codemode
23
- * tool path (`sandbox.start()`) takes its deadline from the script's own
24
- * `// @options: {"timeout_ms": N}` line (no options line → no deadline:
25
- * the sandbox default does not apply there). The constructor cap is
26
- * still the right pin: it bounds every execution of the plain sandbox
27
- * surface and documents the intended ceiling.
28
- *
29
- * `quickjs-wasi` is a REAL dependency of this package, not a transitive
30
- * courtesy: pi-codemode's worker resolves `quickjs-wasi/quickjs.wasm`
31
- * from disk at the first script. A re-pinned or deduped-away transitive
32
- * copy strands the wasm file and the first codemode call fails at runtime
33
- * (500s in prod) — declaring it here pins the resolution to this
34
- * package's own tree.
10
+ * `quickjs-wasi` is a direct dependency: pi-codemode's worker resolves its
11
+ * wasm from this package's tree at the first script.
35
12
  */
36
13
  import type { CodemodeSandboxBase } from "@earendil-works/pi-codemode/portable";
37
14
  import type { CodemodeNamespace } from "@earendil-works/pi-durable/codemode";
38
15
  import type { Extension } from "@earendil-works/pi-durable";
16
+ import type { SigrunExtensionResult, SigrunHost } from "@sigrun/sigrun/extension";
39
17
  /** Pinned per-execution wall-clock deadline (pi-codemode's default). */
40
18
  export declare const CODEMODE_TIMEOUT_MS = 300000;
41
19
  /** Pinned QuickJS heap cap (256 MiB; upstream default is unbounded). */
@@ -46,27 +24,21 @@ export interface CodemodeWiring {
46
24
  readonly extension: Extension;
47
25
  /** The sandbox the extension runs scripts in (call `close()` on shutdown). */
48
26
  readonly sandbox: CodemodeSandboxBase;
49
- /** The factory-time namespace snapshot (empty without an mcp.json). */
27
+ /** The live namespace list, refreshed after loading and between runs. */
50
28
  readonly namespaces: readonly CodemodeNamespace[];
51
- /**
52
- * Close the sandbox: aborts in-flight executions and stops the worker
53
- * machinery. Called by hosts on harness shutdown; a fresh factory builds
54
- * a fresh sandbox, so a closed wiring is never reused.
55
- */
29
+ /** The initial hook call; the factory itself never waits on it. */
30
+ readonly ready: Promise<void>;
31
+ /** Refresh before a newly created conversation's first run. */
32
+ readonly conversationCreated: NonNullable<SigrunExtensionResult["conversationCreated"]>;
33
+ /** Refresh after every settled run. */
34
+ readonly runSettled: NonNullable<SigrunExtensionResult["runSettled"]>;
35
+ /** Refresh immediately after any committed settings write. */
36
+ readonly onSettingsChanged: NonNullable<SigrunExtensionResult["onSettingsChanged"]>;
37
+ /** Abort in-flight executions and stop the worker machinery. */
56
38
  close(): Promise<void>;
57
39
  }
58
- /**
59
- * The codemode factory (the loader contract: no arguments; config resolves
60
- * from the package context). Each call builds a FRESH sandbox — one
61
- * wiring, one sandbox, no shared worker state across installs.
62
- */
63
- export declare function createCodemodeWiring(): CodemodeWiring;
64
- /**
65
- * The sigrun extension-package contract: the default export is a factory
66
- * taking no arguments, returning `{ extension }`.
67
- */
40
+ /** Each call builds a fresh sandbox and one live array shared with upstream. */
41
+ export declare function createCodemodeWiring(host?: SigrunHost): CodemodeWiring;
68
42
  declare const _default: typeof createCodemodeWiring;
69
43
  export default _default;
70
44
  export { CODEMODE_NOT_OFFERED_NOTICE, CODEMODE_NOTICE_SECTION_KEY, renderCodemodeNotice, } from "./extension.js";
71
- export { loadNamespaces, MCP_CONFIG_ENV, resolveMcpConfigPath, serverEnabled, } from "./config.js";
72
- export type { McpServerEntry } from "./config.js";
package/dist/index.js CHANGED
@@ -1,68 +1,77 @@
1
1
  /**
2
2
  * sigrun-ext-codemode: the codemode factory (the loader contract).
3
3
  *
4
- * The sigrun extension-package contract (sigrun's src/harness/loader.ts):
5
- * the default export is a FACTORY TAKING NO ARGUMENTS, returning
6
- * `{ extension }`. The loader calls it synchronously at boot, before
7
- * `Harness.open` reads the registry.
4
+ * MCP namespaces come from ext-mcp's `mcp.servers` published hook. Codemode
5
+ * loads before ext-mcp, so the factory starts (but never awaits) the first
6
+ * refresh; committed settings writes and the per-run lifecycle refresh it
7
+ * again. Upstream reads the namespaces array at use time, so the
8
+ * owned array is updated in place rather than replaced.
8
9
  *
9
- * What the factory does, in order:
10
- * 1. Reads the MCP namespace snapshot ONCE (src/config.ts):
11
- * `SIGRUN_MCP_CONFIG` → `~/.config/sigrun/mcp.json`; absent file →
12
- * empty namespaces (the extension still ships its codemode tool +
13
- * section); a bad file warns and installs un-namespaced.
14
- * 2. Builds the QuickJS sandbox with the pinned constructor options
15
- * (timeoutMs 300_000 — pi-codemode's own default, restated so the
16
- * contract does not drift; memoryLimitBytes 256 MiB — a real cap
17
- * where the upstream default is unbounded wasm32).
18
- * 3. Wraps upstream's `createCodemodeExtension({ sandbox, namespaces })`
19
- * with the companion notice section (src/extension.ts).
20
- *
21
- * Note on `timeoutMs` (verified against pi-codemode 1.2.0): the
22
- * CONSTRUCTOR timeout governs `sandbox.execute()`; the durable codemode
23
- * tool path (`sandbox.start()`) takes its deadline from the script's own
24
- * `// @options: {"timeout_ms": N}` line (no options line → no deadline:
25
- * the sandbox default does not apply there). The constructor cap is
26
- * still the right pin: it bounds every execution of the plain sandbox
27
- * surface and documents the intended ceiling.
28
- *
29
- * `quickjs-wasi` is a REAL dependency of this package, not a transitive
30
- * courtesy: pi-codemode's worker resolves `quickjs-wasi/quickjs.wasm`
31
- * from disk at the first script. A re-pinned or deduped-away transitive
32
- * copy strands the wasm file and the first codemode call fails at runtime
33
- * (500s in prod) — declaring it here pins the resolution to this
34
- * package's own tree.
10
+ * `quickjs-wasi` is a direct dependency: pi-codemode's worker resolves its
11
+ * wasm from this package's tree at the first script.
35
12
  */
36
13
  import { CodemodeSandbox } from "@earendil-works/pi-codemode";
37
- import { loadNamespaces } from "./config.js";
38
14
  import { createSigrunCodemodeExtension } from "./extension.js";
39
15
  /** Pinned per-execution wall-clock deadline (pi-codemode's default). */
40
16
  export const CODEMODE_TIMEOUT_MS = 300_000;
41
17
  /** Pinned QuickJS heap cap (256 MiB; upstream default is unbounded). */
42
18
  export const CODEMODE_MEMORY_LIMIT_BYTES = 256 * 1024 * 1024;
43
- /**
44
- * The codemode factory (the loader contract: no arguments; config resolves
45
- * from the package context). Each call builds a FRESH sandbox — one
46
- * wiring, one sandbox, no shared worker state across installs.
47
- */
48
- export function createCodemodeWiring() {
49
- const namespaces = loadNamespaces();
19
+ /** Hidden-only servers have no tools scripts can call. */
20
+ function fullyHidden(server) {
21
+ return server.exposure === "hidden" &&
22
+ Object.values(server.toolExposure).every((exposure) => exposure === "hidden");
23
+ }
24
+ /** Each call builds a fresh sandbox and one live array shared with upstream. */
25
+ export function createCodemodeWiring(host) {
26
+ const namespaces = [];
50
27
  const sandbox = new CodemodeSandbox({
51
28
  timeoutMs: CODEMODE_TIMEOUT_MS,
52
29
  memoryLimitBytes: CODEMODE_MEMORY_LIMIT_BYTES,
53
30
  });
54
31
  const extension = createSigrunCodemodeExtension({ sandbox, namespaces });
32
+ let failure;
33
+ const refreshNamespaces = async () => {
34
+ if (host === undefined)
35
+ return;
36
+ let problem;
37
+ try {
38
+ const result = await host.publishedHooks.call("mcp.servers", {});
39
+ if (result.status === "absent") {
40
+ // MCP is optional: an intentionally disabled provider is not a failure.
41
+ }
42
+ else if (result.status !== "ok") {
43
+ problem = `status ${result.status}`;
44
+ }
45
+ else {
46
+ const { servers } = result.value;
47
+ const next = servers
48
+ .filter((server) => server.enabled && !fullyHidden(server))
49
+ .map((server) => ({
50
+ name: server.name,
51
+ description: `MCP server ${server.name} (tools ${server.name}/<tool>, registered by sigrun-ext-mcp)`,
52
+ prefix: `${server.name}/`,
53
+ }));
54
+ namespaces.splice(0, namespaces.length, ...next);
55
+ }
56
+ }
57
+ catch (cause) {
58
+ problem = cause instanceof Error ? cause.message : String(cause);
59
+ }
60
+ if (problem !== undefined && problem !== failure) {
61
+ console.warn(`sigrun-ext-codemode: mcp.servers refresh failed: ${problem}`);
62
+ }
63
+ failure = problem;
64
+ };
55
65
  return {
56
66
  extension,
57
67
  sandbox,
58
68
  namespaces,
69
+ ready: refreshNamespaces(),
70
+ conversationCreated: refreshNamespaces,
71
+ runSettled: refreshNamespaces,
72
+ onSettingsChanged: refreshNamespaces,
59
73
  close: () => sandbox.close(),
60
74
  };
61
75
  }
62
- /**
63
- * The sigrun extension-package contract: the default export is a factory
64
- * taking no arguments, returning `{ extension }`.
65
- */
66
76
  export default createCodemodeWiring;
67
77
  export { CODEMODE_NOT_OFFERED_NOTICE, CODEMODE_NOTICE_SECTION_KEY, renderCodemodeNotice, } from "./extension.js";
68
- export { loadNamespaces, MCP_CONFIG_ENV, resolveMcpConfigPath, serverEnabled, } from "./config.js";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sigrun/ext-codemode",
3
- "version": "0.1.3",
4
- "description": "Codemode extension for sigrun (pi-durable): the `codemode` tool — model-written JavaScript in a QuickJS sandbox, with MCP-server tools namespaced as script-callable tools",
3
+ "version": "0.2.1",
4
+ "description": "Codemode extension for sigrun (pi-durable): the `codemode` tool \u2014 model-written JavaScript in a QuickJS sandbox, with MCP-server tools namespaced as script-callable tools",
5
5
  "type": "module",
6
6
  "engines": {
7
7
  "node": ">=22.19.0"
@@ -31,15 +31,18 @@
31
31
  "@earendil-works/chord": "~1.5.0",
32
32
  "@earendil-works/pi-ai": "~1.5.0",
33
33
  "@earendil-works/pi-codemode": "~1.5.0",
34
- "@earendil-works/pi-durable": "~1.5.0"
34
+ "@earendil-works/pi-durable": "~1.5.0",
35
+ "typebox": "1.3.27"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@earendil-works/chord": "1.5.0",
38
39
  "@earendil-works/pi-ai": "1.5.0",
39
40
  "@earendil-works/pi-codemode": "1.5.0",
40
41
  "@earendil-works/pi-durable": "1.5.0",
42
+ "@sigrun/sigrun": "file:../sigrun",
41
43
  "@types/node": "^26.6.4",
42
44
  "tsx": "^4.23.15",
45
+ "typebox": "1.3.27",
43
46
  "typescript": "^7.0.2",
44
47
  "vitest": "^5.0.3"
45
48
  },
package/dist/config.d.ts DELETED
@@ -1,68 +0,0 @@
1
- /**
2
- * The codemode config snapshot: the mcp.json-derived namespace list.
3
- *
4
- * Reuses the MCP config RESOLUTION and the namespace-relevant parse SHAPES
5
- * from sigrun-ext-mcp (rev-3 ruling accepts the duplicated parse of the
6
- * config file across repos; sigrun-ext-mcp is not npm-published, so its
7
- * `parseMcpConfig` cannot be imported here — the shapes are duplicated
8
- * with the derivations quoted below for review parity).
9
- *
10
- * Resolution order (identical to sigrun-ext-mcp src/config.ts):
11
- * 1. `SIGRUN_MCP_CONFIG` env override — an explicit path to an mcp.json
12
- * 2. `~/.config/sigrun/mcp.json` (the sigrun config dir)
13
- *
14
- * What we read from each server entry:
15
- * - `serverEnabled` (mcp repo's derivation): `enabled: false` OR
16
- * `disabled: true` skips the server entirely.
17
- * - Exposure (mcp repo's shape): a server-default `exposure` word, plus
18
- * `toolExposure` overrides mapping exact tool names or `*`-patterns to
19
- * exposure words; the pi alias `codemode-deferred` normalizes to
20
- * `codemode`. A server whose tools all resolve `hidden` contributes
21
- * nothing scripts can call (hidden tools never register), so it is
22
- * excluded from the snapshot: that happens when the server default is
23
- * `hidden` and every override is `hidden` too (an exact name wins over
24
- * patterns; among patterns the first match in config order wins — so
25
- * only a fully-hidden override table leaves every tool hidden).
26
- *
27
- * Snapshot semantics (the spec ruling): the config is read ONCE at factory
28
- * time; late or flapping servers render un-namespaced in the section until
29
- * the next boot. The section BODY re-renders live from
30
- * `PromptInput.agent.callable` each turn (upstream's render), so tool
31
- * lists are never stale — only the namespace GROUPING is a snapshot.
32
- */
33
- import type { CodemodeNamespace } from "@earendil-works/pi-durable/codemode";
34
- /** The env override the config store reads (sigrun-ext-mcp's name). */
35
- export declare const MCP_CONFIG_ENV = "SIGRUN_MCP_CONFIG";
36
- /** The exposure words, after normalization (mcp repo's McpExposure). */
37
- declare const EXPOSURE_WORDS: readonly ["codemode", "deferred", "direct", "hidden"];
38
- type ExposureWord = (typeof EXPOSURE_WORDS)[number];
39
- /** One server entry as this package needs it (a subset of the mcp repo's
40
- * McpServerConfig: only the namespace-relevant fields). */
41
- export interface McpServerEntry {
42
- /** Skip this server entirely (either false means off). */
43
- readonly enabled?: boolean;
44
- readonly disabled?: boolean;
45
- /** Server-default exposure (default "codemode"). */
46
- readonly exposure?: ExposureWord;
47
- /** Per-tool overrides: exact names beat `*`-patterns; first pattern wins. */
48
- readonly toolExposure?: Readonly<Record<string, ExposureWord>>;
49
- }
50
- /** Resolve the config path without reading it (for diagnostics). */
51
- export declare function resolveMcpConfigPath(env?: NodeJS.ProcessEnv): string;
52
- /** Whether a configured server participates at all (mcp repo's
53
- * `serverEnabled`): `enabled: false` and `disabled: true` both skip it. */
54
- export declare function serverEnabled(server: McpServerEntry): boolean;
55
- /**
56
- * Read the mcp.json from the resolved path and build the namespace
57
- * snapshot: one `CodemodeNamespace` per participating server with prefix
58
- * `"<server>/"` (the namespace ruler: our tool-name shape is already
59
- * `<server>/<tool>`, so upstream's longest-prefix grouping needs no renames).
60
- *
61
- * A missing file is an EMPTY namespace list (the extension still ships its
62
- * codemode tool + section). An existing-but-bad file or entry warns at
63
- * factory time (the spec's best-effort boot log; NOT the contract — a bad
64
- * config must not cost the model its codemode tool) and the extension
65
- * installs un-namespaced rather than throwing.
66
- */
67
- export declare function loadNamespaces(env?: NodeJS.ProcessEnv): readonly CodemodeNamespace[];
68
- export {};
package/dist/config.js DELETED
@@ -1,166 +0,0 @@
1
- /**
2
- * The codemode config snapshot: the mcp.json-derived namespace list.
3
- *
4
- * Reuses the MCP config RESOLUTION and the namespace-relevant parse SHAPES
5
- * from sigrun-ext-mcp (rev-3 ruling accepts the duplicated parse of the
6
- * config file across repos; sigrun-ext-mcp is not npm-published, so its
7
- * `parseMcpConfig` cannot be imported here — the shapes are duplicated
8
- * with the derivations quoted below for review parity).
9
- *
10
- * Resolution order (identical to sigrun-ext-mcp src/config.ts):
11
- * 1. `SIGRUN_MCP_CONFIG` env override — an explicit path to an mcp.json
12
- * 2. `~/.config/sigrun/mcp.json` (the sigrun config dir)
13
- *
14
- * What we read from each server entry:
15
- * - `serverEnabled` (mcp repo's derivation): `enabled: false` OR
16
- * `disabled: true` skips the server entirely.
17
- * - Exposure (mcp repo's shape): a server-default `exposure` word, plus
18
- * `toolExposure` overrides mapping exact tool names or `*`-patterns to
19
- * exposure words; the pi alias `codemode-deferred` normalizes to
20
- * `codemode`. A server whose tools all resolve `hidden` contributes
21
- * nothing scripts can call (hidden tools never register), so it is
22
- * excluded from the snapshot: that happens when the server default is
23
- * `hidden` and every override is `hidden` too (an exact name wins over
24
- * patterns; among patterns the first match in config order wins — so
25
- * only a fully-hidden override table leaves every tool hidden).
26
- *
27
- * Snapshot semantics (the spec ruling): the config is read ONCE at factory
28
- * time; late or flapping servers render un-namespaced in the section until
29
- * the next boot. The section BODY re-renders live from
30
- * `PromptInput.agent.callable` each turn (upstream's render), so tool
31
- * lists are never stale — only the namespace GROUPING is a snapshot.
32
- */
33
- import { existsSync, readFileSync } from "node:fs";
34
- import { homedir } from "node:os";
35
- import { isAbsolute, resolve } from "node:path";
36
- /** The env override the config store reads (sigrun-ext-mcp's name). */
37
- export const MCP_CONFIG_ENV = "SIGRUN_MCP_CONFIG";
38
- /** The exposure words, after normalization (mcp repo's McpExposure). */
39
- const EXPOSURE_WORDS = ["codemode", "deferred", "direct", "hidden"];
40
- /** Parse one exposure word; the pi alias normalizes to "codemode". */
41
- const EXPOSURE_WORD_SET = new Set(EXPOSURE_WORDS);
42
- function parseExposure(value, where) {
43
- if (value === "codemode-deferred")
44
- return "codemode";
45
- if (typeof value !== "string" || !EXPOSURE_WORD_SET.has(value)) {
46
- throw new Error(`${where} exposure must be one of ${EXPOSURE_WORDS.map((word) => JSON.stringify(word)).join(", ")} (or "codemode-deferred", the pi alias for "codemode")`);
47
- }
48
- return value;
49
- }
50
- /** Resolve the config path without reading it (for diagnostics). */
51
- export function resolveMcpConfigPath(env = process.env) {
52
- const override = env[MCP_CONFIG_ENV];
53
- if (override !== undefined && override !== "") {
54
- // A relative override resolves against cwd, like sigrun's other paths.
55
- return isAbsolute(override) ? override : resolve(override);
56
- }
57
- return resolve(homedir(), ".config", "sigrun", "mcp.json");
58
- }
59
- function isRecord(value) {
60
- return typeof value === "object" && value !== null && !Array.isArray(value);
61
- }
62
- /** Whether a configured server participates at all (mcp repo's
63
- * `serverEnabled`): `enabled: false` and `disabled: true` both skip it. */
64
- export function serverEnabled(server) {
65
- return server.enabled !== false && server.disabled !== true;
66
- }
67
- /** Whether every tool of this server resolves `hidden`: the server default
68
- * is `hidden` and no override can un-hide a tool (every override value is
69
- * `hidden`; an exact name or pattern pointing elsewhere un-hides whatever
70
- * it matches). Hidden tools never register, so such a server contributes
71
- * nothing scripts can call and gets no namespace. */
72
- function serverFullyHidden(server) {
73
- if ((server.exposure ?? "codemode") !== "hidden")
74
- return false;
75
- return Object.values(server.toolExposure ?? {}).every((word) => word === "hidden");
76
- }
77
- /**
78
- * Read the mcp.json from the resolved path and build the namespace
79
- * snapshot: one `CodemodeNamespace` per participating server with prefix
80
- * `"<server>/"` (the namespace ruler: our tool-name shape is already
81
- * `<server>/<tool>`, so upstream's longest-prefix grouping needs no renames).
82
- *
83
- * A missing file is an EMPTY namespace list (the extension still ships its
84
- * codemode tool + section). An existing-but-bad file or entry warns at
85
- * factory time (the spec's best-effort boot log; NOT the contract — a bad
86
- * config must not cost the model its codemode tool) and the extension
87
- * installs un-namespaced rather than throwing.
88
- */
89
- export function loadNamespaces(env = process.env) {
90
- const path = resolveMcpConfigPath(env);
91
- if (!existsSync(path))
92
- return [];
93
- try {
94
- return parseNamespaces(readFileSync(path, "utf8"), path);
95
- }
96
- catch (cause) {
97
- console.warn(`sigrun-ext-codemode: mcp config ${path} is invalid (codemode ` +
98
- `tools install un-namespaced): ${cause instanceof Error ? cause.message : String(cause)}`);
99
- return [];
100
- }
101
- }
102
- /** Parse and validate the config body into the namespace snapshot. */
103
- function parseNamespaces(text, path) {
104
- const parsed = JSON.parse(text);
105
- if (!isRecord(parsed) || !isRecord(parsed.servers)) {
106
- throw new Error(`must be a JSON object with a "servers" object mapping names to server entries`);
107
- }
108
- const namespaces = [];
109
- for (const [name, entry] of Object.entries(parsed.servers)) {
110
- if (!isRecord(entry)) {
111
- throw new Error(`server "${name}" must be an object`);
112
- }
113
- const server = parseServerEntry(entry, path, name);
114
- if (!serverEnabled(server))
115
- continue;
116
- if (serverFullyHidden(server)) {
117
- // The spec's best-effort boot log: hidden-only rows.
118
- console.warn(`sigrun-ext-codemode: mcp server "${name}" resolves fully ` +
119
- `hidden (excluded from the codemode namespaces)`);
120
- continue;
121
- }
122
- namespaces.push({
123
- name,
124
- description: `MCP server ${name} (tools ${name}/<tool>, registered by sigrun-ext-mcp)`,
125
- prefix: `${name}/`,
126
- });
127
- }
128
- return namespaces;
129
- }
130
- /** Validate one server entry's namespace-relevant fields. */
131
- function parseServerEntry(entry, path, name) {
132
- for (const field of ["enabled", "disabled"]) {
133
- const value = entry[field];
134
- if (value !== undefined && typeof value !== "boolean") {
135
- throw new Error(`mcp config ${path}: server "${name}" ${field} must be a boolean`);
136
- }
137
- }
138
- return {
139
- ...(entry.enabled === undefined
140
- ? {}
141
- : { enabled: entry.enabled }),
142
- ...(entry.disabled === undefined
143
- ? {}
144
- : { disabled: entry.disabled }),
145
- ...(entry.exposure === undefined
146
- ? {}
147
- : {
148
- exposure: parseExposure(entry.exposure, `mcp config ${path}: server "${name}"`),
149
- }),
150
- ...(entry.toolExposure === undefined
151
- ? {}
152
- : {
153
- toolExposure: parseToolExposure(entry.toolExposure, path, name),
154
- }),
155
- };
156
- }
157
- function parseToolExposure(value, path, name) {
158
- if (!isRecord(value)) {
159
- throw new Error(`mcp config ${path}: server "${name}" toolExposure must be an object`);
160
- }
161
- const overrides = {};
162
- for (const [key, exposure] of Object.entries(value)) {
163
- overrides[key] = parseExposure(exposure, `mcp config ${path}: server "${name}" toolExposure "${key}"`);
164
- }
165
- return overrides;
166
- }