@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 +11 -1
- package/README.md +13 -18
- package/dist/extension.d.ts +6 -7
- package/dist/extension.js +5 -6
- package/dist/index.d.ts +20 -48
- package/dist/index.js +52 -43
- package/package.json +6 -3
- package/dist/config.d.ts +0 -68
- package/dist/config.js +0 -166
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
|
-
## [
|
|
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
|
|
10
|
+
taking the sigrun host, returning `{ extension }` and lifecycle handlers.
|
|
11
11
|
|
|
12
12
|
## What the factory does
|
|
13
13
|
|
|
14
|
-
1.
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
`
|
|
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;
|
|
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
|
|
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
|
package/dist/extension.d.ts
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
|
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
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
10
|
-
*
|
|
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
|
|
27
|
+
/** The live namespace list, refreshed after loading and between runs. */
|
|
50
28
|
readonly namespaces: readonly CodemodeNamespace[];
|
|
51
|
-
/**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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
|
-
*
|
|
10
|
-
*
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
4
|
-
"description": "Codemode extension for sigrun (pi-durable): the `codemode` tool
|
|
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
|
-
}
|