@code-yeongyu/senpi 2026.9.18 → 2026.9.19-2
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 +117 -0
- package/dist/bundle/chunks/{anthropic-messages-ZEJ3DPCP.js → anthropic-messages-WZCFP6BT.js} +1 -1
- package/dist/bundle/chunks/{app-server-command-ELSORC4D.js → app-server-command-QBJ4GPN7.js} +1 -1
- package/dist/bundle/chunks/bun-extension-importer-JQZRPNJH.js +10 -0
- package/dist/bundle/chunks/{chunk-Y6HDGVS5.js → chunk-34GCWLNV.js} +1 -1
- package/dist/bundle/chunks/{chunk-XJUVWITG.js → chunk-3FRTRCR6.js} +1 -1
- package/dist/bundle/chunks/chunk-5E5TLB76.js +8 -0
- package/dist/bundle/chunks/{chunk-AH72FRZ2.js → chunk-EHSDTQ6I.js} +13 -13
- package/dist/bundle/chunks/{chunk-VRAYXSST.js → chunk-FDLVINNY.js} +2 -2
- package/dist/bundle/chunks/{chunk-QNZ24YOQ.js → chunk-HGAX6JZ7.js} +3 -1
- package/dist/bundle/chunks/{chunk-QGBRSL64.js → chunk-HSH674C6.js} +1 -1
- package/dist/bundle/chunks/chunk-IUZUN32Z.js +13 -0
- package/dist/bundle/chunks/chunk-IXAUDPOK.js +2 -0
- package/dist/bundle/chunks/{chunk-NLOZDJQX.js → chunk-LF3XTYYF.js} +3 -3
- package/dist/bundle/chunks/{chunk-ANTGWIGI.js → chunk-MUC3MZTV.js} +1 -1
- package/dist/bundle/chunks/chunk-NQAVM5XL.js +2 -0
- package/dist/bundle/chunks/chunk-P7PWCGLJ.js +5 -0
- package/dist/bundle/chunks/{chunk-4C7LNOHI.js → chunk-PLZK7ABD.js} +1 -1
- package/dist/bundle/chunks/{chunk-CKNRCW35.js → chunk-RLRLQRI2.js} +1 -1
- package/dist/bundle/chunks/{chunk-7FXIHFZU.js → chunk-UAYBYTFN.js} +1 -1
- package/dist/bundle/chunks/{chunk-7A47FVQC.js → chunk-W4SRGDQ4.js} +3 -6
- package/dist/bundle/chunks/{chunk-QROWINIH.js → chunk-XK24AFYU.js} +1 -1
- package/dist/bundle/chunks/cli-main-MLJ2H637.js +3 -0
- package/dist/bundle/chunks/cursor-agent.js +38 -0
- package/dist/bundle/chunks/cursor.js +2 -0
- package/dist/bundle/chunks/devin-agent.js +5 -0
- package/dist/bundle/chunks/devin.js +75 -0
- package/dist/bundle/chunks/{help-fast-path-WQCATMI4.js → help-fast-path-RQDCLDQT.js} +1 -1
- package/dist/bundle/chunks/host-command-3LS772D5.js +2 -0
- package/dist/bundle/chunks/{host-lifecycle-EAXSS3F2.js → host-lifecycle-TA5XGUW5.js} +1 -1
- package/dist/bundle/chunks/host-lifecycle.js +20 -0
- package/dist/bundle/chunks/interactive-host-runtime-AITXUDCT.js +4 -0
- package/dist/bundle/chunks/interactive-mode-7C75IXNF.js +2 -0
- package/dist/bundle/chunks/{multi-session-host-OK6WMHB4.js → multi-session-host-2YCJKQOL.js} +1 -1
- package/dist/bundle/chunks/{openai-completions-2RS3VSPV.js → openai-completions-TOWYKX2U.js} +1 -1
- package/dist/bundle/chunks/{package-manager-cli-C2K4YRGI.js → package-manager-cli-4ANSELYT.js} +1 -1
- package/dist/bundle/chunks/{rotation-stream-536AQTIS.js → rotation-stream-KDLA7K6X.js} +1 -1
- package/dist/bundle/chunks/rpc-mode-OPFQIWFP.js +2 -0
- package/dist/bundle/chunks/{session-picker-BUHK3FCS.js → session-picker-DXCITGLS.js} +1 -1
- package/dist/bundle/chunks/session-worker.js +359 -346
- package/dist/bundle/chunks/{state-store-OYDXEIXU.js → state-store-HBBOD7I5.js} +1 -1
- package/dist/bundle/cli.js +1 -1
- package/dist/bundle/index.js +1 -1
- package/dist/bundle/rpc-entry.js +1 -1
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +2 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/cli/deferred-commands.d.ts +7 -0
- package/dist/cli/deferred-commands.d.ts.map +1 -1
- package/dist/cli/deferred-commands.js +12 -0
- package/dist/cli/deferred-commands.js.map +1 -1
- package/dist/cli/host-command.d.ts +21 -0
- package/dist/cli/host-command.d.ts.map +1 -0
- package/dist/cli/host-command.js +148 -0
- package/dist/cli/host-command.js.map +1 -0
- package/dist/core/agent-session.d.ts +4 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +57 -33
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-7.d.ts +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-7.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-7.js +2 -17
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-7.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-8.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-8.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-8.js +5 -0
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-8.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-code.d.ts +3 -0
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-code.d.ts.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-code.js +28 -0
- package/dist/core/extensions/builtin/prompt-preset/kimi-k2-code.js.map +1 -0
- package/dist/core/extensions/builtin/prompt-preset/presets.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.js +21 -1
- package/dist/core/extensions/builtin/prompt-preset/presets.js.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/settings.d.ts +1 -1
- package/dist/core/extensions/builtin/prompt-preset/settings.d.ts.map +1 -1
- package/dist/core/extensions/builtin/prompt-preset/settings.js +1 -0
- package/dist/core/extensions/builtin/prompt-preset/settings.js.map +1 -1
- package/dist/core/extensions/bun-extension-importer.d.ts.map +1 -1
- package/dist/core/extensions/bun-extension-importer.js +31 -5
- package/dist/core/extensions/bun-extension-importer.js.map +1 -1
- package/dist/core/extensions/bun-extension-registry.d.ts +9 -0
- package/dist/core/extensions/bun-extension-registry.d.ts.map +1 -1
- package/dist/core/extensions/bun-extension-registry.js +72 -16
- package/dist/core/extensions/bun-extension-registry.js.map +1 -1
- package/dist/core/extensions/extension-runtime-module.d.ts +5 -0
- package/dist/core/extensions/extension-runtime-module.d.ts.map +1 -0
- package/dist/core/extensions/extension-runtime-module.js +6 -0
- package/dist/core/extensions/extension-runtime-module.js.map +1 -0
- package/dist/core/model-resolver.d.ts.map +1 -1
- package/dist/core/model-resolver.js +1 -0
- package/dist/core/model-resolver.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +7 -1
- package/dist/main.js.map +1 -1
- package/dist/modes/index.d.ts +3 -0
- package/dist/modes/index.d.ts.map +1 -1
- package/dist/modes/index.js +5 -0
- package/dist/modes/index.js.map +1 -1
- package/dist/modes/rpc/host-daemon-env.d.ts +18 -0
- package/dist/modes/rpc/host-daemon-env.d.ts.map +1 -0
- package/dist/modes/rpc/host-daemon-env.js +95 -0
- package/dist/modes/rpc/host-daemon-env.js.map +1 -0
- package/dist/modes/rpc/host-decision.d.ts +1 -1
- package/dist/modes/rpc/host-decision.d.ts.map +1 -1
- package/dist/modes/rpc/host-decision.js +2 -0
- package/dist/modes/rpc/host-decision.js.map +1 -1
- package/dist/modes/rpc/host-ensure.js +8 -1
- package/dist/modes/rpc/host-ensure.js.map +1 -1
- package/dist/modes/rpc/host-launch-spec.d.ts +43 -0
- package/dist/modes/rpc/host-launch-spec.d.ts.map +1 -0
- package/dist/modes/rpc/host-launch-spec.js +183 -0
- package/dist/modes/rpc/host-launch-spec.js.map +1 -0
- package/dist/modes/rpc/host-launch.d.ts +3 -1
- package/dist/modes/rpc/host-launch.d.ts.map +1 -1
- package/dist/modes/rpc/host-launch.js +24 -5
- package/dist/modes/rpc/host-launch.js.map +1 -1
- package/dist/modes/rpc/host-lifecycle.d.ts.map +1 -1
- package/dist/modes/rpc/host-lifecycle.js +36 -2
- package/dist/modes/rpc/host-lifecycle.js.map +1 -1
- package/dist/modes/rpc/host-probe.d.ts +7 -0
- package/dist/modes/rpc/host-probe.d.ts.map +1 -1
- package/dist/modes/rpc/host-probe.js +15 -4
- package/dist/modes/rpc/host-probe.js.map +1 -1
- package/dist/modes/rpc/host-process-metrics.d.ts +10 -0
- package/dist/modes/rpc/host-process-metrics.d.ts.map +1 -0
- package/dist/modes/rpc/host-process-metrics.js +83 -0
- package/dist/modes/rpc/host-process-metrics.js.map +1 -0
- package/dist/modes/rpc/host-runner.d.ts +38 -0
- package/dist/modes/rpc/host-runner.d.ts.map +1 -0
- package/dist/modes/rpc/host-runner.js +195 -0
- package/dist/modes/rpc/host-runner.js.map +1 -0
- package/dist/modes/rpc/host-status.d.ts +58 -0
- package/dist/modes/rpc/host-status.d.ts.map +1 -0
- package/dist/modes/rpc/host-status.js +117 -0
- package/dist/modes/rpc/host-status.js.map +1 -0
- package/docs/extensions.md +35 -0
- package/docs/providers.md +30 -0
- package/docs/rpc.md +177 -15
- package/docs/settings.md +1 -1
- package/node_modules/@code-yeongyu/senpi-codemode/CHANGELOG.md +84 -0
- package/node_modules/@code-yeongyu/senpi-codemode/package.json +4 -4
- package/node_modules/@earendil-works/pi-agent-core/package.json +3 -3
- package/node_modules/@earendil-works/pi-ai/README.md +9 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/env-api-keys.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js +2 -2
- package/node_modules/@earendil-works/pi-ai/dist/models.generated.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts +150 -7
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js +17 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/all.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai-stream.d.ts +13 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai-stream.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai-stream.js +55 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai-stream.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.d.ts +11 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.js +94 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.models.d.ts +4 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.models.d.ts.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.models.js +6 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/bai.models.js.map +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/.manifest.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/bai.json +1 -0
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/mistral.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opencode.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/opengateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/openrouter.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/venice.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/vercel-ai-gateway.json +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts +142 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js +156 -3
- package/node_modules/@earendil-works/pi-ai/dist/providers/kimi-coding.models.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.d.ts.map +1 -1
- package/node_modules/@earendil-works/pi-ai/dist/types.js.map +1 -1
- package/node_modules/@earendil-works/pi-ai/package.json +2 -2
- package/node_modules/@earendil-works/pi-pty/package.json +1 -1
- package/node_modules/@earendil-works/pi-telemetry/package.json +1 -1
- package/node_modules/@earendil-works/pi-tui/package.json +1 -1
- package/package.json +7 -7
- package/dist/bundle/chunks/bun-extension-importer-SW7XGNSG.js +0 -10
- package/dist/bundle/chunks/chunk-AGXZF7RF.js +0 -2
- package/dist/bundle/chunks/chunk-F5JZNLDE.js +0 -8
- package/dist/bundle/chunks/cli-main-4TWJXJKM.js +0 -3
- package/dist/bundle/chunks/interactive-host-runtime-EDXG6A5L.js +0 -4
- package/dist/bundle/chunks/interactive-mode-PD7S7KFZ.js +0 -2
- package/dist/bundle/chunks/rpc-mode-TIJFUWXS.js +0 -2
- package/node_modules/@earendil-works/pi-ai/dist/providers/data/kimi-coding.json +0 -1
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The file a client hands `senpi host`: what the daemon should be started with, and whether this
|
|
3
|
+
* machine may act on it at all.
|
|
4
|
+
*
|
|
5
|
+
* A launch spec is the one input that decides what code a long-lived, machine-wide daemon LOADS, so
|
|
6
|
+
* it is a trust boundary rather than a convenience format. Four properties are proven before any
|
|
7
|
+
* process is started, and each maps to a refusal a caller can branch on:
|
|
8
|
+
*
|
|
9
|
+
* - `launch_spec_insecure` - the file is not owned by this uid, or it is group/world writable.
|
|
10
|
+
* Anyone who can rewrite the spec can choose the daemon's extensions.
|
|
11
|
+
* - `launch_spec_path_escape` - an extension path leaves the spec directory's tree, lexically or
|
|
12
|
+
* through a symlink. The spec directory is the profile; a path outside it is somebody else's.
|
|
13
|
+
* - `launch_spec_env_denied` - an `env` key outside `^(SENPI|OMO|PI)_[A-Z0-9_]+$`. The spec may
|
|
14
|
+
* add to the brand's own lane and nothing else; it may never set `PATH` for a daemon.
|
|
15
|
+
* - `launch_spec_missing_extension` - a listed extension does not exist. Failing here is the
|
|
16
|
+
* point: a daemon that booted with half a profile would serve every client with half a profile.
|
|
17
|
+
*
|
|
18
|
+
* The spec is deliberately a FILE, never stdin or an argv blob: those cannot be owner-checked, and
|
|
19
|
+
* the directory holding the file is what the extension paths are resolved against.
|
|
20
|
+
*/
|
|
21
|
+
import { readFile, realpath, stat } from "node:fs/promises";
|
|
22
|
+
import { dirname, isAbsolute, relative, resolve } from "node:path";
|
|
23
|
+
import { isSessionRuntimeKind } from "../../cli/args.js";
|
|
24
|
+
/** Keys a spec's `env` may carry: the brand's own lane, and nothing that steers process startup. */
|
|
25
|
+
const SPEC_ENV_NAME = /^(SENPI|OMO|PI)_[A-Z0-9_]+$/u;
|
|
26
|
+
/** A spec that may not be acted on, naming the property that failed and the path that failed it. */
|
|
27
|
+
export class HostLaunchSpecError extends Error {
|
|
28
|
+
constructor(reason, detail) {
|
|
29
|
+
super(`${reason}: ${detail}`);
|
|
30
|
+
this.name = "HostLaunchSpecError";
|
|
31
|
+
this.reason = reason;
|
|
32
|
+
this.detail = detail;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/** What an ensure without a spec launches: the daemon defaults, no extensions, no extra env. */
|
|
36
|
+
export const DEFAULT_HOST_LAUNCH_SPEC = {
|
|
37
|
+
hostArgs: [],
|
|
38
|
+
policy: {},
|
|
39
|
+
env: {},
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Reads, trusts and resolves one launch spec. Every failure is a `HostLaunchSpecError`; nothing is
|
|
43
|
+
* started, and no daemon state is written, until this has returned.
|
|
44
|
+
*/
|
|
45
|
+
export async function loadHostLaunchSpec(specPath, platform = process.platform) {
|
|
46
|
+
const absolute = resolve(specPath);
|
|
47
|
+
await assertTrustedFile(absolute, platform);
|
|
48
|
+
const spec = parseHostLaunchSpec(await readSpecText(absolute));
|
|
49
|
+
const env = trustedEnv(spec.env);
|
|
50
|
+
const extensions = await resolveExtensions(spec.core.extensions, dirname(absolute));
|
|
51
|
+
return {
|
|
52
|
+
hostArgs: [
|
|
53
|
+
"--session-runtime",
|
|
54
|
+
spec.core.session_runtime,
|
|
55
|
+
...extensions.flatMap((path) => ["--extension", path]),
|
|
56
|
+
],
|
|
57
|
+
policy: {
|
|
58
|
+
...(spec.tunables?.idleExitMs !== undefined && { idleExitMs: spec.tunables.idleExitMs }),
|
|
59
|
+
...(spec.tunables?.coldStart !== undefined && { coldStart: spec.tunables.coldStart }),
|
|
60
|
+
},
|
|
61
|
+
env,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Parses the document. A boundary: everything past it is typed and nothing re-validates it. */
|
|
65
|
+
export function parseHostLaunchSpec(text) {
|
|
66
|
+
let parsed;
|
|
67
|
+
try {
|
|
68
|
+
parsed = JSON.parse(text);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
throw new HostLaunchSpecError("launch_spec_invalid", error instanceof Error ? error.message : "not JSON");
|
|
72
|
+
}
|
|
73
|
+
if (!isRecord(parsed))
|
|
74
|
+
throw new HostLaunchSpecError("launch_spec_invalid", "the spec is not a JSON object");
|
|
75
|
+
if (parsed.spec_version !== 1) {
|
|
76
|
+
throw new HostLaunchSpecError("launch_spec_invalid", `unsupported spec_version ${String(parsed.spec_version)}`);
|
|
77
|
+
}
|
|
78
|
+
const core = parsed.core;
|
|
79
|
+
if (!isRecord(core))
|
|
80
|
+
throw new HostLaunchSpecError("launch_spec_invalid", "core must be an object");
|
|
81
|
+
if (typeof core.session_runtime !== "string" || !isSessionRuntimeKind(core.session_runtime)) {
|
|
82
|
+
throw new HostLaunchSpecError("launch_spec_invalid", "core.session_runtime must be in-process or worker");
|
|
83
|
+
}
|
|
84
|
+
// A daemon that is not multi-session cannot serve a second client, so a spec asking for one is a
|
|
85
|
+
// client bug rather than a configuration: it is refused instead of quietly upgraded.
|
|
86
|
+
if (core.multi_session !== true) {
|
|
87
|
+
throw new HostLaunchSpecError("launch_spec_invalid", "core.multi_session must be true");
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(core.extensions) || !core.extensions.every((entry) => typeof entry === "string")) {
|
|
90
|
+
throw new HostLaunchSpecError("launch_spec_invalid", "core.extensions must be an array of strings");
|
|
91
|
+
}
|
|
92
|
+
const tunables = parsedTunables(parsed.tunables);
|
|
93
|
+
return {
|
|
94
|
+
spec_version: 1,
|
|
95
|
+
core: { session_runtime: core.session_runtime, multi_session: true, extensions: core.extensions },
|
|
96
|
+
...(tunables && { tunables }),
|
|
97
|
+
...(isRecord(parsed.env) && { env: parsedEnv(parsed.env) }),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async function readSpecText(absolute) {
|
|
101
|
+
try {
|
|
102
|
+
return await readFile(absolute, "utf8");
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw new HostLaunchSpecError("launch_spec_unreadable", error instanceof Error ? error.message : absolute);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Ownership and permissions of the spec file. win32 has neither a comparable uid nor POSIX mode
|
|
110
|
+
* bits on this path, so the check is POSIX-only and says so rather than pretending to pass.
|
|
111
|
+
*/
|
|
112
|
+
async function assertTrustedFile(absolute, platform) {
|
|
113
|
+
if (platform === "win32")
|
|
114
|
+
return;
|
|
115
|
+
const stats = await stat(absolute).catch((error) => {
|
|
116
|
+
throw new HostLaunchSpecError("launch_spec_unreadable", error instanceof Error ? error.message : absolute);
|
|
117
|
+
});
|
|
118
|
+
const uid = process.getuid?.();
|
|
119
|
+
if (uid !== undefined && stats.uid !== uid) {
|
|
120
|
+
throw new HostLaunchSpecError("launch_spec_insecure", `${absolute} is owned by uid ${stats.uid}, not ${uid}`);
|
|
121
|
+
}
|
|
122
|
+
if ((stats.mode & 0o022) !== 0) {
|
|
123
|
+
throw new HostLaunchSpecError("launch_spec_insecure", `${absolute} is group/world writable (mode ${(stats.mode & 0o777).toString(8)})`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
function trustedEnv(env) {
|
|
127
|
+
for (const name of Object.keys(env ?? {})) {
|
|
128
|
+
if (!SPEC_ENV_NAME.test(name)) {
|
|
129
|
+
throw new HostLaunchSpecError("launch_spec_env_denied", `${name} is outside ${SPEC_ENV_NAME.source}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return env ?? {};
|
|
133
|
+
}
|
|
134
|
+
/** Resolves each listed extension inside the spec's own directory, or refuses to start at all. */
|
|
135
|
+
async function resolveExtensions(extensions, specDir) {
|
|
136
|
+
const rootReal = await realpath(specDir).catch(() => specDir);
|
|
137
|
+
const resolved = [];
|
|
138
|
+
for (const entry of extensions) {
|
|
139
|
+
const target = resolve(specDir, entry);
|
|
140
|
+
if (escapes(specDir, target)) {
|
|
141
|
+
throw new HostLaunchSpecError("launch_spec_path_escape", `${entry} resolves outside ${specDir}`);
|
|
142
|
+
}
|
|
143
|
+
const real = await realpath(target).catch(() => undefined);
|
|
144
|
+
if (real === undefined) {
|
|
145
|
+
throw new HostLaunchSpecError("launch_spec_missing_extension", `${target} does not exist`);
|
|
146
|
+
}
|
|
147
|
+
// A path that is lexically inside the tree can still LEAD outside it through a symlink, and
|
|
148
|
+
// the daemon would load whatever it points at.
|
|
149
|
+
if (escapes(rootReal, real)) {
|
|
150
|
+
throw new HostLaunchSpecError("launch_spec_path_escape", `${entry} links outside ${specDir}`);
|
|
151
|
+
}
|
|
152
|
+
resolved.push(real);
|
|
153
|
+
}
|
|
154
|
+
return resolved;
|
|
155
|
+
}
|
|
156
|
+
function escapes(root, target) {
|
|
157
|
+
const step = relative(root, target);
|
|
158
|
+
return step.startsWith("..") || isAbsolute(step);
|
|
159
|
+
}
|
|
160
|
+
function parsedTunables(value) {
|
|
161
|
+
if (!isRecord(value))
|
|
162
|
+
return undefined;
|
|
163
|
+
const idleExitMs = value.idleExitMs;
|
|
164
|
+
const coldStart = value.coldStart;
|
|
165
|
+
return {
|
|
166
|
+
...(typeof idleExitMs === "number" && Number.isFinite(idleExitMs) && idleExitMs > 0 && { idleExitMs }),
|
|
167
|
+
...((coldStart === "transient" || coldStart === "persistent") && { coldStart }),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
function parsedEnv(value) {
|
|
171
|
+
const env = {};
|
|
172
|
+
for (const [name, entry] of Object.entries(value)) {
|
|
173
|
+
if (typeof entry !== "string") {
|
|
174
|
+
throw new HostLaunchSpecError("launch_spec_invalid", `env.${name} must be a string`);
|
|
175
|
+
}
|
|
176
|
+
env[name] = entry;
|
|
177
|
+
}
|
|
178
|
+
return env;
|
|
179
|
+
}
|
|
180
|
+
function isRecord(value) {
|
|
181
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=host-launch-spec.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host-launch-spec.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-launch-spec.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The file a client hands `senpi host`: what the daemon should be started with, and whether this\n * machine may act on it at all.\n *\n * A launch spec is the one input that decides what code a long-lived, machine-wide daemon LOADS, so\n * it is a trust boundary rather than a convenience format. Four properties are proven before any\n * process is started, and each maps to a refusal a caller can branch on:\n *\n * - `launch_spec_insecure` - the file is not owned by this uid, or it is group/world writable.\n * Anyone who can rewrite the spec can choose the daemon's extensions.\n * - `launch_spec_path_escape` - an extension path leaves the spec directory's tree, lexically or\n * through a symlink. The spec directory is the profile; a path outside it is somebody else's.\n * - `launch_spec_env_denied` - an `env` key outside `^(SENPI|OMO|PI)_[A-Z0-9_]+$`. The spec may\n * add to the brand's own lane and nothing else; it may never set `PATH` for a daemon.\n * - `launch_spec_missing_extension` - a listed extension does not exist. Failing here is the\n * point: a daemon that booted with half a profile would serve every client with half a profile.\n *\n * The spec is deliberately a FILE, never stdin or an argv blob: those cannot be owner-checked, and\n * the directory holding the file is what the extension paths are resolved against.\n */\nimport { readFile, realpath, stat } from \"node:fs/promises\";\nimport { dirname, isAbsolute, relative, resolve } from \"node:path\";\nimport { isSessionRuntimeKind, type SessionRuntimeKind } from \"../../cli/args.ts\";\nimport type { HostColdStart, HostLifecyclePolicyInput } from \"./host-lifecycle.ts\";\n\n/** Keys a spec's `env` may carry: the brand's own lane, and nothing that steers process startup. */\nconst SPEC_ENV_NAME = /^(SENPI|OMO|PI)_[A-Z0-9_]+$/u;\n\nexport interface HostLaunchSpecCore {\n\treadonly session_runtime: SessionRuntimeKind;\n\t/** A daemon serves many clients at once; a spec that says otherwise is refused, not honoured. */\n\treadonly multi_session: true;\n\t/** Extension paths, relative to the directory holding the spec file. */\n\treadonly extensions: readonly string[];\n}\n\n/** The document itself, as a client writes it. */\nexport interface HostLaunchSpec {\n\treadonly spec_version: 1;\n\treadonly core: HostLaunchSpecCore;\n\treadonly tunables?: { readonly idleExitMs?: number; readonly coldStart?: HostColdStart };\n\treadonly env?: Readonly<Record<string, string>>;\n}\n\nexport type HostLaunchSpecRefusal =\n\t| \"launch_spec_unreadable\"\n\t| \"launch_spec_invalid\"\n\t| \"launch_spec_insecure\"\n\t| \"launch_spec_path_escape\"\n\t| \"launch_spec_env_denied\"\n\t| \"launch_spec_missing_extension\";\n\n/** A spec that may not be acted on, naming the property that failed and the path that failed it. */\nexport class HostLaunchSpecError extends Error {\n\treadonly reason: HostLaunchSpecRefusal;\n\treadonly detail: string;\n\n\tconstructor(reason: HostLaunchSpecRefusal, detail: string) {\n\t\tsuper(`${reason}: ${detail}`);\n\t\tthis.name = \"HostLaunchSpecError\";\n\t\tthis.reason = reason;\n\t\tthis.detail = detail;\n\t}\n}\n\n/** A trusted spec, in the shape the ensure/handoff entry points take. */\nexport interface ResolvedHostLaunchSpec {\n\t/** Host CLI arguments: the session runtime and one absolute `--extension` per listed path. */\n\treadonly hostArgs: readonly string[];\n\treadonly policy: HostLifecyclePolicyInput;\n\treadonly env: Readonly<Record<string, string>>;\n}\n\n/** What an ensure without a spec launches: the daemon defaults, no extensions, no extra env. */\nexport const DEFAULT_HOST_LAUNCH_SPEC: ResolvedHostLaunchSpec = {\n\thostArgs: [],\n\tpolicy: {},\n\tenv: {},\n};\n\n/**\n * Reads, trusts and resolves one launch spec. Every failure is a `HostLaunchSpecError`; nothing is\n * started, and no daemon state is written, until this has returned.\n */\nexport async function loadHostLaunchSpec(\n\tspecPath: string,\n\tplatform: NodeJS.Platform = process.platform,\n): Promise<ResolvedHostLaunchSpec> {\n\tconst absolute = resolve(specPath);\n\tawait assertTrustedFile(absolute, platform);\n\tconst spec = parseHostLaunchSpec(await readSpecText(absolute));\n\tconst env = trustedEnv(spec.env);\n\tconst extensions = await resolveExtensions(spec.core.extensions, dirname(absolute));\n\treturn {\n\t\thostArgs: [\n\t\t\t\"--session-runtime\",\n\t\t\tspec.core.session_runtime,\n\t\t\t...extensions.flatMap((path) => [\"--extension\", path]),\n\t\t],\n\t\tpolicy: {\n\t\t\t...(spec.tunables?.idleExitMs !== undefined && { idleExitMs: spec.tunables.idleExitMs }),\n\t\t\t...(spec.tunables?.coldStart !== undefined && { coldStart: spec.tunables.coldStart }),\n\t\t},\n\t\tenv,\n\t};\n}\n\n/** Parses the document. A boundary: everything past it is typed and nothing re-validates it. */\nexport function parseHostLaunchSpec(text: string): HostLaunchSpec {\n\tlet parsed: unknown;\n\ttry {\n\t\tparsed = JSON.parse(text);\n\t} catch (error: unknown) {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_invalid\", error instanceof Error ? error.message : \"not JSON\");\n\t}\n\tif (!isRecord(parsed)) throw new HostLaunchSpecError(\"launch_spec_invalid\", \"the spec is not a JSON object\");\n\tif (parsed.spec_version !== 1) {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_invalid\", `unsupported spec_version ${String(parsed.spec_version)}`);\n\t}\n\tconst core = parsed.core;\n\tif (!isRecord(core)) throw new HostLaunchSpecError(\"launch_spec_invalid\", \"core must be an object\");\n\tif (typeof core.session_runtime !== \"string\" || !isSessionRuntimeKind(core.session_runtime)) {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_invalid\", \"core.session_runtime must be in-process or worker\");\n\t}\n\t// A daemon that is not multi-session cannot serve a second client, so a spec asking for one is a\n\t// client bug rather than a configuration: it is refused instead of quietly upgraded.\n\tif (core.multi_session !== true) {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_invalid\", \"core.multi_session must be true\");\n\t}\n\tif (!Array.isArray(core.extensions) || !core.extensions.every((entry) => typeof entry === \"string\")) {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_invalid\", \"core.extensions must be an array of strings\");\n\t}\n\tconst tunables = parsedTunables(parsed.tunables);\n\treturn {\n\t\tspec_version: 1,\n\t\tcore: { session_runtime: core.session_runtime, multi_session: true, extensions: core.extensions },\n\t\t...(tunables && { tunables }),\n\t\t...(isRecord(parsed.env) && { env: parsedEnv(parsed.env) }),\n\t};\n}\n\nasync function readSpecText(absolute: string): Promise<string> {\n\ttry {\n\t\treturn await readFile(absolute, \"utf8\");\n\t} catch (error: unknown) {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_unreadable\", error instanceof Error ? error.message : absolute);\n\t}\n}\n\n/**\n * Ownership and permissions of the spec file. win32 has neither a comparable uid nor POSIX mode\n * bits on this path, so the check is POSIX-only and says so rather than pretending to pass.\n */\nasync function assertTrustedFile(absolute: string, platform: NodeJS.Platform): Promise<void> {\n\tif (platform === \"win32\") return;\n\tconst stats = await stat(absolute).catch((error: unknown) => {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_unreadable\", error instanceof Error ? error.message : absolute);\n\t});\n\tconst uid = process.getuid?.();\n\tif (uid !== undefined && stats.uid !== uid) {\n\t\tthrow new HostLaunchSpecError(\"launch_spec_insecure\", `${absolute} is owned by uid ${stats.uid}, not ${uid}`);\n\t}\n\tif ((stats.mode & 0o022) !== 0) {\n\t\tthrow new HostLaunchSpecError(\n\t\t\t\"launch_spec_insecure\",\n\t\t\t`${absolute} is group/world writable (mode ${(stats.mode & 0o777).toString(8)})`,\n\t\t);\n\t}\n}\n\nfunction trustedEnv(env: Readonly<Record<string, string>> | undefined): Readonly<Record<string, string>> {\n\tfor (const name of Object.keys(env ?? {})) {\n\t\tif (!SPEC_ENV_NAME.test(name)) {\n\t\t\tthrow new HostLaunchSpecError(\"launch_spec_env_denied\", `${name} is outside ${SPEC_ENV_NAME.source}`);\n\t\t}\n\t}\n\treturn env ?? {};\n}\n\n/** Resolves each listed extension inside the spec's own directory, or refuses to start at all. */\nasync function resolveExtensions(extensions: readonly string[], specDir: string): Promise<string[]> {\n\tconst rootReal = await realpath(specDir).catch(() => specDir);\n\tconst resolved: string[] = [];\n\tfor (const entry of extensions) {\n\t\tconst target = resolve(specDir, entry);\n\t\tif (escapes(specDir, target)) {\n\t\t\tthrow new HostLaunchSpecError(\"launch_spec_path_escape\", `${entry} resolves outside ${specDir}`);\n\t\t}\n\t\tconst real = await realpath(target).catch(() => undefined);\n\t\tif (real === undefined) {\n\t\t\tthrow new HostLaunchSpecError(\"launch_spec_missing_extension\", `${target} does not exist`);\n\t\t}\n\t\t// A path that is lexically inside the tree can still LEAD outside it through a symlink, and\n\t\t// the daemon would load whatever it points at.\n\t\tif (escapes(rootReal, real)) {\n\t\t\tthrow new HostLaunchSpecError(\"launch_spec_path_escape\", `${entry} links outside ${specDir}`);\n\t\t}\n\t\tresolved.push(real);\n\t}\n\treturn resolved;\n}\n\nfunction escapes(root: string, target: string): boolean {\n\tconst step = relative(root, target);\n\treturn step.startsWith(\"..\") || isAbsolute(step);\n}\n\nfunction parsedTunables(value: unknown): HostLaunchSpec[\"tunables\"] | undefined {\n\tif (!isRecord(value)) return undefined;\n\tconst idleExitMs = value.idleExitMs;\n\tconst coldStart = value.coldStart;\n\treturn {\n\t\t...(typeof idleExitMs === \"number\" && Number.isFinite(idleExitMs) && idleExitMs > 0 && { idleExitMs }),\n\t\t...((coldStart === \"transient\" || coldStart === \"persistent\") && { coldStart }),\n\t};\n}\n\nfunction parsedEnv(value: Record<string, unknown>): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, entry] of Object.entries(value)) {\n\t\tif (typeof entry !== \"string\") {\n\t\t\tthrow new HostLaunchSpecError(\"launch_spec_invalid\", `env.${name} must be a string`);\n\t\t}\n\t\tenv[name] = entry;\n\t}\n\treturn env;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n"]}
|
|
@@ -15,7 +15,9 @@ export declare const PINNED_HOST_CLIENT_CAPABILITIES: readonly ["extension_event
|
|
|
15
15
|
* `--internal-rpc-host-supervisor` route that main() dispatches before argument parsing.
|
|
16
16
|
* Exported for tests.
|
|
17
17
|
*/
|
|
18
|
-
export declare function defaultHostLaunch(supervisorArgs: readonly string[], compiled?: boolean
|
|
18
|
+
export declare function defaultHostLaunch(supervisorArgs: readonly string[], compiled?: boolean,
|
|
19
|
+
/** Null stands for a bundled layout, where no standalone sibling program exists. */
|
|
20
|
+
sibling?: string | null): {
|
|
19
21
|
command: string;
|
|
20
22
|
args: string[];
|
|
21
23
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-launch.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-launch.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"host-launch.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-launch.ts"],"names":[],"mappings":"AAeA;;;;GAIG;AACH,eAAO,MAAM,+BAA+B,qDAAwE,CAAC;AAErH;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAChC,cAAc,EAAE,SAAS,MAAM,EAAE,EACjC,QAAQ,GAAE,OAAqB;AAC/B,oFAAoF;AACpF,OAAO,GAAE,MAAM,GAAG,IAA8C,GAC9D;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE,CAarC"}
|
|
@@ -6,11 +6,12 @@
|
|
|
6
6
|
* handoff - build the same command here, so a rebranded binary, a compiled standalone and a
|
|
7
7
|
* source checkout all agree on one re-entry route.
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { basename, dirname, resolve } from "node:path";
|
|
10
11
|
import { fileURLToPath } from "node:url";
|
|
11
12
|
import { isBunBinary } from "../../config.js";
|
|
12
13
|
import { CUSTOM_UNSUPPORTED_CAPABILITY, EXTENSION_EVENTS_CAPABILITY } from "./custom-capability.js";
|
|
13
|
-
import { INTERNAL_SUPERVISOR_FLAG } from "./host-lifecycle.js";
|
|
14
|
+
import { INTERNAL_SUPERVISOR_FLAG, resolveCliMainPath } from "./host-lifecycle.js";
|
|
14
15
|
/**
|
|
15
16
|
* Every ensured host starts with this installation-wide profile, independent of the first
|
|
16
17
|
* caller. In particular, extension_events must remain available when a terminal client starts
|
|
@@ -28,17 +29,35 @@ export const PINNED_HOST_CLIENT_CAPABILITIES = [EXTENSION_EVENTS_CAPABILITY, CUS
|
|
|
28
29
|
* `--internal-rpc-host-supervisor` route that main() dispatches before argument parsing.
|
|
29
30
|
* Exported for tests.
|
|
30
31
|
*/
|
|
31
|
-
export function defaultHostLaunch(supervisorArgs, compiled = isBunBinary
|
|
32
|
+
export function defaultHostLaunch(supervisorArgs, compiled = isBunBinary,
|
|
33
|
+
/** Null stands for a bundled layout, where no standalone sibling program exists. */
|
|
34
|
+
sibling = resolveHostLifecycleEntryPath() ?? null) {
|
|
32
35
|
if (compiled)
|
|
33
36
|
return { command: process.execPath, args: [INTERNAL_SUPERVISOR_FLAG, ...supervisorArgs] };
|
|
37
|
+
if (sibling !== null)
|
|
38
|
+
return { command: process.execPath, args: [...process.execArgv, sibling, ...supervisorArgs] };
|
|
39
|
+
// Bundled, the host-lifecycle entry beside us is a bundler chunk, not the standalone
|
|
40
|
+
// program the unbundled tree ships: run directly it returns immediately without ever
|
|
41
|
+
// listening, so ensure saw "exited with code 0 before answering get_protocol_info".
|
|
42
|
+
// The CLI entry does honour the internal route in every layout, so re-enter it the way
|
|
43
|
+
// compiled binaries do, taking the entry from the package's declared bin rather than
|
|
44
|
+
// counting "..", which lands on the package root once this module is bundled.
|
|
34
45
|
return {
|
|
35
46
|
command: process.execPath,
|
|
36
|
-
args: [...process.execArgv,
|
|
47
|
+
args: [...process.execArgv, resolveCliMainPath(), INTERNAL_SUPERVISOR_FLAG, ...supervisorArgs],
|
|
37
48
|
};
|
|
38
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* The standalone host-lifecycle program beside this module, or undefined when this module
|
|
52
|
+
* has been bundled - there the neighbour of the same name is an emitted chunk that does not
|
|
53
|
+
* start a supervisor on its own.
|
|
54
|
+
*/
|
|
39
55
|
function resolveHostLifecycleEntryPath() {
|
|
40
56
|
const modulePath = fileURLToPath(import.meta.url);
|
|
57
|
+
if (basename(dirname(modulePath)) === "chunks")
|
|
58
|
+
return undefined;
|
|
41
59
|
const extension = modulePath.endsWith(".ts") ? ".ts" : ".js";
|
|
42
|
-
|
|
60
|
+
const sibling = resolve(dirname(modulePath), `host-lifecycle${extension}`);
|
|
61
|
+
return existsSync(sibling) ? sibling : undefined;
|
|
43
62
|
}
|
|
44
63
|
//# sourceMappingURL=host-launch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-launch.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-launch.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"host-launch.js","sourceRoot":"","sources":["../../../src/modes/rpc/host-launch.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,6BAA6B,EAAE,2BAA2B,EAAE,MAAM,wBAAwB,CAAC;AACpG,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAEnF;;;;GAIG;AACH,MAAM,CAAC,MAAM,+BAA+B,GAAG,CAAC,2BAA2B,EAAE,6BAA6B,CAAU,CAAC;AAErH;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAChC,cAAiC,EACjC,QAAQ,GAAY,WAAW;AAC/B,oFAAoF;AACpF,OAAO,GAAkB,6BAA6B,EAAE,IAAI,IAAI;IAEhE,IAAI,QAAQ;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,wBAAwB,EAAE,GAAG,cAAc,CAAC,EAAE,CAAC;IACxG,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,OAAO,EAAE,GAAG,cAAc,CAAC,EAAE,CAAC;IACpH,qFAAqF;IACrF,qFAAqF;IACrF,oFAAoF;IACpF,uFAAuF;IACvF,qFAAqF;IACrF,8EAA8E;IAC9E,OAAO;QACN,OAAO,EAAE,OAAO,CAAC,QAAQ;QACzB,IAAI,EAAE,CAAC,GAAG,OAAO,CAAC,QAAQ,EAAE,kBAAkB,EAAE,EAAE,wBAAwB,EAAE,GAAG,cAAc,CAAC;KAC9F,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,SAAS,6BAA6B;IACrC,MAAM,UAAU,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAClD,IAAI,QAAQ,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC;IACjE,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;IAC7D,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,iBAAiB,SAAS,EAAE,CAAC,CAAC;IAC3E,OAAO,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;AAClD,CAAC","sourcesContent":["/**\n * How a client re-enters this engine as a lifecycle supervisor.\n *\n * The supervisor owns the public socket and the idle-exit policy; it spawns the committed RPC\n * socket host itself. Both callers that start one - the ordinary ensure and a generation\n * handoff - build the same command here, so a rebranded binary, a compiled standalone and a\n * source checkout all agree on one re-entry route.\n */\nimport { existsSync } from \"node:fs\";\nimport { basename, dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { isBunBinary } from \"../../config.ts\";\nimport { CUSTOM_UNSUPPORTED_CAPABILITY, EXTENSION_EVENTS_CAPABILITY } from \"./custom-capability.ts\";\nimport { INTERNAL_SUPERVISOR_FLAG, resolveCliMainPath } from \"./host-lifecycle.ts\";\n\n/**\n * Every ensured host starts with this installation-wide profile, independent of the first\n * caller. In particular, extension_events must remain available when a terminal client starts\n * the shared host before the desktop connects.\n */\nexport const PINNED_HOST_CLIENT_CAPABILITIES = [EXTENSION_EVENTS_CAPABILITY, CUSTOM_UNSUPPORTED_CAPABILITY] as const;\n\n/**\n * Builds the spawnable supervisor command for one supervisor argv\n * (`--socket <public> [--bind <path>] [--replace <dev>:<ino>] [host cli args...]`).\n *\n * A compiled standalone binary cannot re-enter itself through a script path: bun executables\n * always boot their embedded entrypoint and parse the whole argv as CLI arguments, so\n * `host-lifecycle.ts --socket <path>` dies with \"Unknown option: --socket\" before the host ever\n * answers get_protocol_info. Compiled binaries therefore re-enter through the hidden\n * `--internal-rpc-host-supervisor` route that main() dispatches before argument parsing.\n * Exported for tests.\n */\nexport function defaultHostLaunch(\n\tsupervisorArgs: readonly string[],\n\tcompiled: boolean = isBunBinary,\n\t/** Null stands for a bundled layout, where no standalone sibling program exists. */\n\tsibling: string | null = resolveHostLifecycleEntryPath() ?? null,\n): { command: string; args: string[] } {\n\tif (compiled) return { command: process.execPath, args: [INTERNAL_SUPERVISOR_FLAG, ...supervisorArgs] };\n\tif (sibling !== null) return { command: process.execPath, args: [...process.execArgv, sibling, ...supervisorArgs] };\n\t// Bundled, the host-lifecycle entry beside us is a bundler chunk, not the standalone\n\t// program the unbundled tree ships: run directly it returns immediately without ever\n\t// listening, so ensure saw \"exited with code 0 before answering get_protocol_info\".\n\t// The CLI entry does honour the internal route in every layout, so re-enter it the way\n\t// compiled binaries do, taking the entry from the package's declared bin rather than\n\t// counting \"..\", which lands on the package root once this module is bundled.\n\treturn {\n\t\tcommand: process.execPath,\n\t\targs: [...process.execArgv, resolveCliMainPath(), INTERNAL_SUPERVISOR_FLAG, ...supervisorArgs],\n\t};\n}\n\n/**\n * The standalone host-lifecycle program beside this module, or undefined when this module\n * has been bundled - there the neighbour of the same name is an emitted chunk that does not\n * start a supervisor on its own.\n */\nfunction resolveHostLifecycleEntryPath(): string | undefined {\n\tconst modulePath = fileURLToPath(import.meta.url);\n\tif (basename(dirname(modulePath)) === \"chunks\") return undefined;\n\tconst extension = modulePath.endsWith(\".ts\") ? \".ts\" : \".js\";\n\tconst sibling = resolve(dirname(modulePath), `host-lifecycle${extension}`);\n\treturn existsSync(sibling) ? sibling : undefined;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-lifecycle.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"host-lifecycle.d.ts","sourceRoot":"","sources":["../../../src/modes/rpc/host-lifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
import { spawn } from "node:child_process";
|
|
41
41
|
import { randomUUID } from "node:crypto";
|
|
42
|
-
import { realpathSync, writeSync } from "node:fs";
|
|
42
|
+
import { existsSync, readFileSync, realpathSync, writeSync } from "node:fs";
|
|
43
43
|
import { access, chmod, mkdir, readFile, rename, rm, unlink, writeFile } from "node:fs/promises";
|
|
44
44
|
import { createConnection, createServer } from "node:net";
|
|
45
45
|
import { tmpdir } from "node:os";
|
|
@@ -248,7 +248,41 @@ function parseSocketIdentity(value) {
|
|
|
248
248
|
export function resolveCliMainPath() {
|
|
249
249
|
const modulePath = fileURLToPath(import.meta.url);
|
|
250
250
|
const extension = modulePath.endsWith(".ts") ? ".ts" : ".js";
|
|
251
|
-
|
|
251
|
+
const unbundled = resolve(dirname(modulePath), "..", "..", `cli-main${extension}`);
|
|
252
|
+
if (existsSync(unbundled))
|
|
253
|
+
return unbundled;
|
|
254
|
+
// Bundled, ".." twice reaches the PACKAGE ROOT rather than dist/, naming a cli-main that
|
|
255
|
+
// was never emitted. Take the entry from the package's own declared bin instead of
|
|
256
|
+
// counting directories: it is the one statement of where the CLI lives that holds in
|
|
257
|
+
// every layout. Falls back to the old path when nothing is declared, so a caller that
|
|
258
|
+
// was working keeps working.
|
|
259
|
+
return resolveDeclaredCliEntry(modulePath) ?? unbundled;
|
|
260
|
+
}
|
|
261
|
+
/** The CLI entry declared by the nearest enclosing package.json, when it exists on disk. */
|
|
262
|
+
function resolveDeclaredCliEntry(modulePath) {
|
|
263
|
+
let dir = dirname(modulePath);
|
|
264
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
265
|
+
const manifestPath = resolve(dir, "package.json");
|
|
266
|
+
if (existsSync(manifestPath)) {
|
|
267
|
+
try {
|
|
268
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
269
|
+
const declared = manifest.bin;
|
|
270
|
+
const candidates = typeof declared === "string" ? [declared] : Object.values(declared ?? {});
|
|
271
|
+
for (const candidate of candidates) {
|
|
272
|
+
const entry = resolve(dir, candidate);
|
|
273
|
+
if (existsSync(entry))
|
|
274
|
+
return entry;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
catch { }
|
|
278
|
+
return undefined;
|
|
279
|
+
}
|
|
280
|
+
const parent = dirname(dir);
|
|
281
|
+
if (parent === dir)
|
|
282
|
+
return undefined;
|
|
283
|
+
dir = parent;
|
|
284
|
+
}
|
|
285
|
+
return undefined;
|
|
252
286
|
}
|
|
253
287
|
/**
|
|
254
288
|
* Resolves the host child spawn. Explicit child commands (desktop launchers)
|