pi-session-mcp 0.11.9 → 0.12.0
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/.pi-session-mcp-cli/doctor.js +89 -4
- package/.pi-session-mcp-cli/execution-modes.js +16 -4
- package/.pi-session-mcp-cli/override-lock.js +4 -4
- package/.pi-session-mcp-cli/setup-process.js +5 -4
- package/.pi-session-mcp-cli/writer-boundary.js +10 -4
- package/.pi-session-mcp-cli/writer-fence.js +50 -0
- package/.pi-session-mcp-cli/writer-lease.js +140 -0
- package/.pi-session-mcp-cli/writer-lifecycle.js +55 -23
- package/.pi-session-mcp-cli/writer-state.js +4 -2
- package/CHANGELOG.md +69 -0
- package/README.md +3 -2
- package/dist/capabilities.d.ts +2 -0
- package/dist/capabilities.js +4 -1
- package/dist/child-stdio-transport.d.ts +21 -0
- package/dist/child-stdio-transport.js +88 -0
- package/dist/client-adapters/types.d.ts +1 -0
- package/dist/doctor.d.ts +3 -1
- package/dist/doctor.js +89 -4
- package/dist/execution-modes.d.ts +12 -2
- package/dist/execution-modes.js +16 -4
- package/dist/main.js +9 -2
- package/dist/override-lock.d.ts +2 -0
- package/dist/override-lock.js +4 -4
- package/dist/pi-adapter.d.ts +30 -0
- package/dist/pi-adapter.js +11 -0
- package/dist/refusal-server.d.ts +1 -1
- package/dist/refusal-server.js +24 -2
- package/dist/server.d.ts +2 -2
- package/dist/server.js +18 -53
- package/dist/session-registry.d.ts +9 -2
- package/dist/session-registry.js +17 -12
- package/dist/setup-cli.js +7 -2
- package/dist/setup-hooks.js +0 -2
- package/dist/setup-modes.js +7 -1
- package/dist/setup-process.d.ts +3 -1
- package/dist/setup-process.js +5 -4
- package/dist/setup-writers.d.ts +5 -0
- package/dist/setup-writers.js +200 -92
- package/dist/tool-schemas.d.ts +339 -0
- package/dist/tool-schemas.js +49 -0
- package/dist/wake-hook.d.ts +4 -5
- package/dist/wake-hook.js +3 -6
- package/dist/writer-acceptance.js +10 -1
- package/dist/writer-boundary.d.ts +6 -4
- package/dist/writer-boundary.js +10 -4
- package/dist/writer-fence.d.ts +12 -0
- package/dist/writer-fence.js +50 -0
- package/dist/writer-launch.d.ts +9 -1
- package/dist/writer-launch.js +127 -30
- package/dist/writer-lease.d.ts +28 -0
- package/dist/writer-lease.js +140 -0
- package/dist/writer-lifecycle.d.ts +7 -1
- package/dist/writer-lifecycle.js +55 -23
- package/dist/writer-relay.d.ts +33 -0
- package/dist/writer-relay.js +359 -0
- package/dist/writer-state.d.ts +13 -1
- package/dist/writer-state.js +4 -2
- package/docs/acceptance.md +47 -0
- package/docs/adr/0012-leased-writer-pool.md +357 -0
- package/docs/architecture.md +61 -13
- package/docs/client-setup.md +11 -15
- package/docs/doctor-design.md +10 -4
- package/docs/execution-modes.md +87 -44
- package/docs/installation.md +34 -22
- package/docs/isolation-setup.md +88 -102
- package/docs/roadmap.md +5 -0
- package/docs/threat-model.md +7 -4
- package/docs/tool-contracts.md +29 -12
- package/package.json +1 -1
- package/scripts/isolation-acceptance.mjs +7 -1
- package/skills/pi-session-implementation/SKILL.md +1 -0
- package/skills/pi-session-orchestration/SKILL.md +6 -5
- package/skills/pi-session-orchestration/references/start-errors.md +5 -1
package/dist/doctor.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createProcessRunner } from "./setup-process.js";
|
|
|
6
6
|
import { podmanInfo, isSafeRefName } from "./writer-boundary.js";
|
|
7
7
|
import { containedBuiltinSkills, containedProfileRefused } from "./writer-lifecycle.js";
|
|
8
8
|
import { readWriterState, writerAcceptanceCurrent, writerName, writerPaths } from "./writer-state.js";
|
|
9
|
+
import { LEASE_DIRECTORY } from "./writer-lease.js";
|
|
9
10
|
import { loadConfig } from "./config.js";
|
|
10
11
|
import { containerRuntimeUsable } from "./container-runtime.js";
|
|
11
12
|
import { PiExtensionError } from "./extension-spec.js";
|
|
@@ -31,7 +32,7 @@ async function readPrefix(path, maxBytes) {
|
|
|
31
32
|
}
|
|
32
33
|
}
|
|
33
34
|
const defaultFileSystem = { readFile, stat, access };
|
|
34
|
-
const CHECK_ORDER = ["node_version_unsupported", "package_metadata_unreadable", "build_missing", "build_stale", "version_mismatch", "config_missing", "config_unreadable", "config_invalid", "workspace_missing", "workspace_not_directory", "workspace_unreadable", "workspace_not_writable", "workspace_gitfile", "workspace_mode_unset", "workspace_host_writes_accepted", "container_runtime_unavailable", "container_profile_refused", "writer_not_ready", "writer_dirty", "writer_uncollected_bundle", "extension_missing", "extension_version_mismatch", "extension_unpinned", "skill_missing", "skill_unreadable", "skill_duplicate", "skill_override_outdated", "event_spool_root"];
|
|
35
|
+
const CHECK_ORDER = ["node_version_unsupported", "package_metadata_unreadable", "build_missing", "build_stale", "version_mismatch", "config_missing", "config_unreadable", "config_invalid", "workspace_missing", "workspace_not_directory", "workspace_unreadable", "workspace_not_writable", "workspace_gitfile", "workspace_mode_unset", "workspace_base_unset", "workspace_host_writes_accepted", "container_runtime_unavailable", "container_profile_refused", "writer_lease_directory", "writer_not_ready", "writer_not_migrated", "writer_off_default_base", "writer_dirty", "writer_git_identity_missing", "writer_uncollected_bundle", "extension_missing", "extension_version_mismatch", "extension_unpinned", "skill_missing", "skill_unreadable", "skill_duplicate", "skill_override_outdated", "event_spool_root"];
|
|
35
36
|
function failure(id, subject, remediation) { return Object.freeze({ id, severity: "error", subject, remediation }); }
|
|
36
37
|
function warning(id, subject, remediation) { return Object.freeze({ id, severity: "warning", subject, remediation }); }
|
|
37
38
|
function ok(id, subject) { return Object.freeze({ id, severity: "ok", subject }); }
|
|
@@ -147,7 +148,7 @@ async function hasGitfilePointer(fs, workspace) {
|
|
|
147
148
|
return false;
|
|
148
149
|
}
|
|
149
150
|
}
|
|
150
|
-
async function
|
|
151
|
+
async function defaultRootInfo(path) {
|
|
151
152
|
try {
|
|
152
153
|
const info = await lstat(path);
|
|
153
154
|
return { isDirectory: info.isDirectory(), uid: info.uid, mode: info.mode };
|
|
@@ -158,6 +159,30 @@ async function defaultEventSpoolRoot(path) {
|
|
|
158
159
|
throw error;
|
|
159
160
|
}
|
|
160
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Offline check of the host-only writer lease directory. A missing directory is fine because the
|
|
164
|
+
* first lease creates it. An existing entry must be a real directory of the current user with mode
|
|
165
|
+
* 0700: the rule `acquireWriterLease` applies before it opens a lease file. Nothing is created,
|
|
166
|
+
* repaired or read, and no path or lease file name appears in the report.
|
|
167
|
+
*/
|
|
168
|
+
async function writerLeaseCheck(home, probe, uid) {
|
|
169
|
+
let info;
|
|
170
|
+
try {
|
|
171
|
+
info = await probe(join(home, ...LEASE_DIRECTORY));
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return failure("writer_lease_directory", "writer lease", "grant access to the writer lease directory");
|
|
175
|
+
}
|
|
176
|
+
if (info === undefined)
|
|
177
|
+
return undefined;
|
|
178
|
+
// Lease files must keep their inodes: a moved or recreated directory would let a second holder in.
|
|
179
|
+
if (!info.isDirectory)
|
|
180
|
+
return failure("writer_lease_directory", "writer lease", "close every writer session and setup run, then replace the entry with a private directory owned by the current user");
|
|
181
|
+
if ((uid !== undefined && info.uid !== uid) || (info.mode & 0o7777) !== 0o700) {
|
|
182
|
+
return failure("writer_lease_directory", "writer lease", "repair the lease directory in place (owner and chmod 700); never move or delete it or its lock files");
|
|
183
|
+
}
|
|
184
|
+
return ok("writer_lease_directory", "writer lease");
|
|
185
|
+
}
|
|
161
186
|
/**
|
|
162
187
|
* Offline check of the event spool's runtime directories, run only when event delivery is enabled.
|
|
163
188
|
* Nothing is created or repaired, and no path appears in the report. A missing runtime directory
|
|
@@ -208,6 +233,44 @@ async function defaultWriterRuntime() {
|
|
|
208
233
|
}
|
|
209
234
|
} };
|
|
210
235
|
}
|
|
236
|
+
async function writerGitIdentity(clone) {
|
|
237
|
+
try {
|
|
238
|
+
const handle = await open(join(clone, ".git", "config"), constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
239
|
+
try {
|
|
240
|
+
const info = await handle.stat();
|
|
241
|
+
if (!info.isFile() || info.size > 65_536)
|
|
242
|
+
return false;
|
|
243
|
+
const buffer = Buffer.alloc(65_536);
|
|
244
|
+
const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
|
|
245
|
+
if ((await handle.stat()).size > 65_536)
|
|
246
|
+
return false;
|
|
247
|
+
let user = false, name = false, email = false;
|
|
248
|
+
for (const line of buffer.subarray(0, bytesRead).toString("utf8").split("\n")) {
|
|
249
|
+
const section = /^\s*\[([^\]]+)\]/.exec(line);
|
|
250
|
+
if (section) {
|
|
251
|
+
user = section[1].trim().toLowerCase() === "user";
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (!user)
|
|
255
|
+
continue;
|
|
256
|
+
const key = /^\s*(name|email)\s*=\s*(.*?)\s*$/i.exec(line);
|
|
257
|
+
if (!key || !key[2] || /^(?:""|[#;])/.test(key[2]))
|
|
258
|
+
continue;
|
|
259
|
+
if (key[1].toLowerCase() === "name")
|
|
260
|
+
name = true;
|
|
261
|
+
else
|
|
262
|
+
email = true;
|
|
263
|
+
}
|
|
264
|
+
return name && email;
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
await handle.close();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
211
274
|
async function writerDirty(clone, state) {
|
|
212
275
|
try {
|
|
213
276
|
const git = join(clone, ".git");
|
|
@@ -280,6 +343,7 @@ async function writerDirty(clone, state) {
|
|
|
280
343
|
export async function runDoctor(dependencies = {}) {
|
|
281
344
|
const fs = dependencies.fileSystem ?? defaultFileSystem;
|
|
282
345
|
const root = dependencies.packageRoot ?? process.cwd();
|
|
346
|
+
const home = dependencies.home ?? homedir();
|
|
283
347
|
const checks = [];
|
|
284
348
|
let metadata;
|
|
285
349
|
try {
|
|
@@ -418,9 +482,13 @@ export async function runDoctor(dependencies = {}) {
|
|
|
418
482
|
let runtime;
|
|
419
483
|
let rawConfig, rawConfigLoaded = false, rawConfigReadable = false;
|
|
420
484
|
let availableBuiltinSkills = new Set();
|
|
485
|
+
const offDefaultBase = new Map();
|
|
421
486
|
for (const [alias, entry] of config.workspaceModes) {
|
|
422
487
|
if (entry.mode !== "container" || !entry.container)
|
|
423
488
|
continue;
|
|
489
|
+
checks.push(entry.container.base === undefined
|
|
490
|
+
? warning("workspace_base_unset", alias, `record the workspace default base with pi-session-mcp-setup --repo ${alias} --base <branch> --apply`)
|
|
491
|
+
: ok("workspace_base_unset", alias));
|
|
424
492
|
if (!rawConfigLoaded) {
|
|
425
493
|
rawConfigLoaded = true;
|
|
426
494
|
try {
|
|
@@ -445,7 +513,7 @@ export async function runDoctor(dependencies = {}) {
|
|
|
445
513
|
checks.push(ok("container_profile_refused", alias));
|
|
446
514
|
}
|
|
447
515
|
for (let index = 1; index <= entry.container.writers; index++) {
|
|
448
|
-
const name = writerName(alias, index),
|
|
516
|
+
const name = writerName(alias, index), paths = writerPaths(home, name);
|
|
449
517
|
const remediation = `run pi-session-mcp-setup --repo ${alias} --apply, or --writer ${name} --recreate --apply`;
|
|
450
518
|
let state, ready = false;
|
|
451
519
|
try {
|
|
@@ -459,6 +527,11 @@ export async function runDoctor(dependencies = {}) {
|
|
|
459
527
|
checks.push(ready ? ok("writer_not_ready", name) : failure("writer_not_ready", name, remediation));
|
|
460
528
|
if (!state)
|
|
461
529
|
continue;
|
|
530
|
+
checks.push(state.launch === "relay" ? ok("writer_not_migrated", name)
|
|
531
|
+
: warning("writer_not_migrated", name, `run pi-session-mcp-setup --repo ${alias} --apply to migrate the writer to the relay`));
|
|
532
|
+
if (entry.container.base !== undefined && ready && state.base.ref !== `refs/heads/${entry.container.base}`) {
|
|
533
|
+
offDefaultBase.set(alias, (offDefaultBase.get(alias) ?? 0) + 1);
|
|
534
|
+
}
|
|
462
535
|
checks.push(await writerDirty(paths.clone, state)
|
|
463
536
|
? warning("writer_dirty", name, "collect writer work before resetting or recreating the writer") : ok("writer_dirty", name));
|
|
464
537
|
let bundles = false;
|
|
@@ -471,9 +544,18 @@ export async function runDoctor(dependencies = {}) {
|
|
|
471
544
|
catch (error) {
|
|
472
545
|
bundles = !isMissing(error);
|
|
473
546
|
}
|
|
547
|
+
checks.push(await writerGitIdentity(paths.clone) ? ok("writer_git_identity_missing", name)
|
|
548
|
+
: warning("writer_git_identity_missing", name, "set user.name and user.email in the operator checkout, then reset the writer"));
|
|
474
549
|
checks.push(bundles ? warning("writer_uncollected_bundle", name, "collect or review outstanding writer bundles") : ok("writer_uncollected_bundle", name));
|
|
475
550
|
}
|
|
476
551
|
}
|
|
552
|
+
for (const [alias, entry] of config.workspaceModes) {
|
|
553
|
+
if (entry.mode !== "container" || !entry.container || entry.container.base === undefined)
|
|
554
|
+
continue;
|
|
555
|
+
const count = offDefaultBase.get(alias) ?? 0;
|
|
556
|
+
checks.push(count === 0 ? ok("writer_off_default_base", alias)
|
|
557
|
+
: warning("writer_off_default_base", alias, `${count} ${count === 1 ? "writer is" : "writers are"} off the workspace default base; finish each with pi-session-mcp-setup --writer <name> --finish --apply once its work is collected`));
|
|
558
|
+
}
|
|
477
559
|
}
|
|
478
560
|
if (config) {
|
|
479
561
|
// This advisory check needs positive evidence, like the gitfile check: unreadable or
|
|
@@ -546,7 +628,10 @@ export async function runDoctor(dependencies = {}) {
|
|
|
546
628
|
}
|
|
547
629
|
}
|
|
548
630
|
if (config?.eventDelivery === "spool")
|
|
549
|
-
checks.push(await eventSpoolCheck(dependencies.env ?? process.env, dependencies.eventSpoolRoot ??
|
|
631
|
+
checks.push(await eventSpoolCheck(dependencies.env ?? process.env, dependencies.eventSpoolRoot ?? defaultRootInfo, dependencies.uid ?? process.getuid?.()));
|
|
632
|
+
const lease = await writerLeaseCheck(home, dependencies.writerLeaseRoot ?? defaultRootInfo, dependencies.uid ?? process.getuid?.());
|
|
633
|
+
if (lease)
|
|
634
|
+
checks.push(lease);
|
|
550
635
|
if (config)
|
|
551
636
|
checks.push(...await skillOverrideChecks(config.skillOverrideDirectory, shippedSkillsDirectory(dependencies.packageRoot), dependencies.releasedSkillTrees));
|
|
552
637
|
const rank = new Map(CHECK_ORDER.map((id, index) => [id, index]));
|
|
@@ -6,13 +6,17 @@ export interface ContainerSettings {
|
|
|
6
6
|
readonly profiles: readonly string[];
|
|
7
7
|
readonly image: string;
|
|
8
8
|
readonly network: "pasta" | "none";
|
|
9
|
-
readonly cache: "none" | "go";
|
|
9
|
+
readonly cache: "none" | "go" | "uv";
|
|
10
|
+
readonly tags: "none" | "reachable";
|
|
11
|
+
readonly base?: string;
|
|
10
12
|
}
|
|
11
13
|
export interface WorkspaceModeEntry {
|
|
12
14
|
readonly mode: WorkspaceMode;
|
|
13
15
|
readonly container?: ContainerSettings;
|
|
14
16
|
}
|
|
15
17
|
export declare const EXECUTION_MODES_FILE = "execution-modes.json";
|
|
18
|
+
/** A base is a plain local branch name: safe, not `HEAD`, no ref prefix, no commit ID, at most 200 characters. */
|
|
19
|
+
export declare function isShortBranchName(value: string): boolean;
|
|
16
20
|
export declare function executionModesPath(configPath: string): string;
|
|
17
21
|
export declare const executionModesSchema: z.ZodObject<{
|
|
18
22
|
schemaVersion: z.ZodLiteral<1>;
|
|
@@ -37,7 +41,13 @@ export declare const executionModesSchema: z.ZodObject<{
|
|
|
37
41
|
cache: z.ZodDefault<z.ZodEnum<{
|
|
38
42
|
go: "go";
|
|
39
43
|
none: "none";
|
|
44
|
+
uv: "uv";
|
|
40
45
|
}>>;
|
|
46
|
+
tags: z.ZodDefault<z.ZodEnum<{
|
|
47
|
+
none: "none";
|
|
48
|
+
reachable: "reachable";
|
|
49
|
+
}>>;
|
|
50
|
+
base: z.ZodOptional<z.ZodString>;
|
|
41
51
|
}, z.core.$strict>], "mode">>;
|
|
42
52
|
}, z.core.$strict>;
|
|
43
53
|
type ModeProfile = {
|
|
@@ -46,5 +56,5 @@ type ModeProfile = {
|
|
|
46
56
|
};
|
|
47
57
|
export declare function resolveExecutionModes(raw: unknown, workspaceAliases: ReadonlySet<string>, profiles: ReadonlyMap<string, ModeProfile>): ReadonlyMap<string, WorkspaceModeEntry>;
|
|
48
58
|
export declare function loadExecutionModes(configPath: string, workspaceAliases: ReadonlySet<string>, profiles: ReadonlyMap<string, ModeProfile>): Promise<ReadonlyMap<string, WorkspaceModeEntry>>;
|
|
49
|
-
export declare function codingAllowed(entry: WorkspaceModeEntry): boolean;
|
|
59
|
+
export declare function codingAllowed(entry: WorkspaceModeEntry, relay: boolean): boolean;
|
|
50
60
|
export {};
|
package/dist/execution-modes.js
CHANGED
|
@@ -3,7 +3,16 @@ import { dirname, join } from "node:path";
|
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { ConfigError } from "./config.js";
|
|
5
5
|
import { ImmutableMap } from "./execution-profile.js";
|
|
6
|
+
import { isSafeRefName } from "./writer-boundary.js";
|
|
6
7
|
export const EXECUTION_MODES_FILE = "execution-modes.json";
|
|
8
|
+
/** A base is a plain local branch name: safe, not `HEAD`, no ref prefix, no commit ID, at most 200 characters. */
|
|
9
|
+
export function isShortBranchName(value) {
|
|
10
|
+
return value.length <= 200
|
|
11
|
+
&& isSafeRefName(value)
|
|
12
|
+
&& value !== "HEAD"
|
|
13
|
+
&& !value.startsWith("refs/")
|
|
14
|
+
&& !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i.test(value);
|
|
15
|
+
}
|
|
7
16
|
export function executionModesPath(configPath) {
|
|
8
17
|
return join(dirname(configPath), EXECUTION_MODES_FILE);
|
|
9
18
|
}
|
|
@@ -17,7 +26,9 @@ const entrySchema = z.discriminatedUnion("mode", [
|
|
|
17
26
|
profiles: z.array(z.string()).min(1),
|
|
18
27
|
image: z.union([z.literal("node"), z.string().regex(/^[0-9a-f]{64}$/)]).default("node"),
|
|
19
28
|
network: z.enum(["pasta", "none"]).default("pasta"),
|
|
20
|
-
cache: z.enum(["none", "go"]).default("none"),
|
|
29
|
+
cache: z.enum(["none", "go", "uv"]).default("none"),
|
|
30
|
+
tags: z.enum(["none", "reachable"]).default("none"),
|
|
31
|
+
base: z.string().refine(isShortBranchName).optional(),
|
|
21
32
|
}).strict(),
|
|
22
33
|
]);
|
|
23
34
|
export const executionModesSchema = z.object({
|
|
@@ -39,9 +50,10 @@ export function resolveExecutionModes(raw, workspaceAliases, profiles) {
|
|
|
39
50
|
throw new ConfigError("invalid_execution_modes");
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
|
-
const { mode, ...settings } = entry;
|
|
53
|
+
const { mode, base, ...settings } = entry;
|
|
43
54
|
entries.set(alias, Object.freeze({ mode, container: Object.freeze({
|
|
44
55
|
...settings, profiles: Object.freeze([...settings.profiles]),
|
|
56
|
+
...(base === undefined ? {} : { base }),
|
|
45
57
|
}) }));
|
|
46
58
|
}
|
|
47
59
|
else {
|
|
@@ -66,6 +78,6 @@ export async function loadExecutionModes(configPath, workspaceAliases, profiles)
|
|
|
66
78
|
}
|
|
67
79
|
return resolveExecutionModes(raw, workspaceAliases, profiles);
|
|
68
80
|
}
|
|
69
|
-
export function codingAllowed(entry) {
|
|
70
|
-
return entry.mode === "unset" || entry.mode === "host";
|
|
81
|
+
export function codingAllowed(entry, relay) {
|
|
82
|
+
return entry.mode === "unset" || entry.mode === "host" || (entry.mode === "container" && relay);
|
|
71
83
|
}
|
package/dist/main.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { stdoutGuard } from "./stdout-guard-install.js";
|
|
3
3
|
import { isAbsolute } from "node:path";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { RoutingPiSessionAdapter, WriterRelayAdapter } from "./writer-relay.js";
|
|
4
6
|
import { serveStdio, StdioServerTransport } from "@modelcontextprotocol/server/stdio";
|
|
5
7
|
import { loadConfig } from "./config.js";
|
|
6
8
|
import { EventSpoolError, TurnEventSpool } from "./event-spool.js";
|
|
@@ -45,13 +47,18 @@ async function main() {
|
|
|
45
47
|
spool = TurnEventSpool.open(directory, { diagnostic: writeDiagnostic });
|
|
46
48
|
}
|
|
47
49
|
const events = spool;
|
|
48
|
-
|
|
50
|
+
const hostAdapter = new SdkPiSessionAdapter({ diagnosticSink: writeDiagnostic });
|
|
51
|
+
const relay = guardPath === undefined;
|
|
52
|
+
const adapter = relay ? new RoutingPiSessionAdapter(hostAdapter, new WriterRelayAdapter({
|
|
53
|
+
home: homedir(), workspaceModes: config.workspaceModes, executionProfiles: config.executionProfiles,
|
|
54
|
+
}), config.workspaceModes) : hostAdapter;
|
|
55
|
+
registry = new SessionRegistry(adapter, config.workspaces, {
|
|
49
56
|
...(guardPath === undefined ? {} : { writerGuard: createWriterGuard(guardPath, config.workspaces) }),
|
|
50
57
|
...(events === undefined ? {} : {
|
|
51
58
|
onTurnTerminal: (event) => events.write({ kind: "turn_terminal", session: event.sessionId, turn: event.turnId, state: event.state, via: event.via }),
|
|
52
59
|
}),
|
|
53
60
|
});
|
|
54
|
-
handle = serveStdio(() => createServer(registry, config.workspaces, config.executionProfiles, config.defaultExecutionProfile, config.configuration, config.workspaceModes, delivery), {
|
|
61
|
+
handle = serveStdio(() => createServer(registry, config.workspaces, config.executionProfiles, config.defaultExecutionProfile, config.configuration, config.workspaceModes, delivery, relay), {
|
|
55
62
|
transport: new StdioServerTransport(process.stdin, stdoutGuard.transportStdout),
|
|
56
63
|
onerror: () => writeDiagnostic({ level: "error", event: "mcp_transport_error", stage: "transport", code: "mcp_transport_error" }),
|
|
57
64
|
});
|
package/dist/override-lock.d.ts
CHANGED
|
@@ -32,3 +32,5 @@ export interface OverrideLock {
|
|
|
32
32
|
* `override_lock_unavailable`.
|
|
33
33
|
*/
|
|
34
34
|
export declare function acquireOverrideLock(overrideDirectory: string, options?: OverrideLockOptions): Promise<OverrideLock>;
|
|
35
|
+
export declare function flockBinary(options: OverrideLockOptions): string | undefined;
|
|
36
|
+
export declare function lockWithFlock(flockPath: string, lockFd: number, timeoutMs: number, signal: AbortSignal | undefined, nonBlocking?: boolean): Promise<void>;
|
package/dist/override-lock.js
CHANGED
|
@@ -72,7 +72,7 @@ export async function acquireOverrideLock(overrideDirectory, options = {}) {
|
|
|
72
72
|
},
|
|
73
73
|
};
|
|
74
74
|
}
|
|
75
|
-
function flockBinary(options) {
|
|
75
|
+
export function flockBinary(options) {
|
|
76
76
|
const trusted = options.isTrustedBinary ?? ((path) => isTrustedSystemBinary(path));
|
|
77
77
|
return (options.flockCandidates ?? FLOCK_CANDIDATES).find((candidate) => trusted(candidate));
|
|
78
78
|
}
|
|
@@ -86,7 +86,7 @@ async function openLockFile(lockPath, platform, timeoutMs, signal) {
|
|
|
86
86
|
throw new OverrideLockError(error.message === "ownership_lock_busy" ? "override_lock_busy" : "override_lock_unavailable");
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
|
-
function lockWithFlock(flockPath, lockFd, timeoutMs, signal) {
|
|
89
|
+
export function lockWithFlock(flockPath, lockFd, timeoutMs, signal, nonBlocking = false) {
|
|
90
90
|
return new Promise((resolve, reject) => {
|
|
91
91
|
if (signal?.aborted) {
|
|
92
92
|
reject(new OverrideLockError("override_lock_unavailable"));
|
|
@@ -97,7 +97,7 @@ function lockWithFlock(flockPath, lockFd, timeoutMs, signal) {
|
|
|
97
97
|
// flock parses fractional seconds with the locale's decimal separator, so only whole
|
|
98
98
|
// seconds go on the command line. The exact bound is the timer below, which stops flock.
|
|
99
99
|
const wholeSeconds = String(Math.max(1, Math.ceil(timeoutMs / 1_000)));
|
|
100
|
-
locker = spawn(flockPath, ["-E", String(FLOCK_BUSY_EXIT), "-w", wholeSeconds, "3"], { stdio: ["ignore", "ignore", "ignore", lockFd], detached: true });
|
|
100
|
+
locker = spawn(flockPath, ["-E", String(FLOCK_BUSY_EXIT), ...(nonBlocking ? ["-n"] : ["-w", wholeSeconds]), "3"], { stdio: ["ignore", "ignore", "ignore", lockFd], detached: true });
|
|
101
101
|
}
|
|
102
102
|
catch {
|
|
103
103
|
reject(new OverrideLockError("override_lock_unavailable"));
|
|
@@ -111,7 +111,7 @@ function lockWithFlock(flockPath, lockFd, timeoutMs, signal) {
|
|
|
111
111
|
catch { /* already gone */ }
|
|
112
112
|
} };
|
|
113
113
|
const cleanup = () => { clearTimeout(bound); signal?.removeEventListener("abort", stop); };
|
|
114
|
-
const bound = setTimeout(() => { expired = true; stop(); }, timeoutMs);
|
|
114
|
+
const bound = nonBlocking ? undefined : setTimeout(() => { expired = true; stop(); }, timeoutMs);
|
|
115
115
|
signal?.addEventListener("abort", stop, { once: true });
|
|
116
116
|
locker.once("error", () => { cleanup(); reject(new OverrideLockError("override_lock_unavailable")); });
|
|
117
117
|
locker.once("close", (code) => {
|
package/dist/pi-adapter.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type { PermissionProfile };
|
|
|
3
3
|
export type PiTurnOutcome = {
|
|
4
4
|
status: "completed";
|
|
5
5
|
assistantText: string;
|
|
6
|
+
truncated?: boolean;
|
|
6
7
|
} | {
|
|
7
8
|
status: "failed";
|
|
8
9
|
} | {
|
|
@@ -18,7 +19,34 @@ export declare class PiSessionCreationError extends Error {
|
|
|
18
19
|
readonly code: PiSessionCreationErrorCode;
|
|
19
20
|
constructor(code: PiSessionCreationErrorCode);
|
|
20
21
|
}
|
|
22
|
+
export declare class WriterUnavailableError extends Error {
|
|
23
|
+
readonly reason: "none_ready" | "none_at_base" | "none_admitted";
|
|
24
|
+
readonly counts?: {
|
|
25
|
+
busy: number;
|
|
26
|
+
notAtBaseline: number;
|
|
27
|
+
settingsChanged: number;
|
|
28
|
+
} | undefined;
|
|
29
|
+
readonly code = "writer_unavailable";
|
|
30
|
+
constructor(reason: "none_ready" | "none_at_base" | "none_admitted", counts?: {
|
|
31
|
+
busy: number;
|
|
32
|
+
notAtBaseline: number;
|
|
33
|
+
settingsChanged: number;
|
|
34
|
+
} | undefined);
|
|
35
|
+
}
|
|
21
36
|
export interface PiSessionHandle {
|
|
37
|
+
readonly placement?: {
|
|
38
|
+
writer: string;
|
|
39
|
+
base: {
|
|
40
|
+
ref: string;
|
|
41
|
+
commit: string;
|
|
42
|
+
};
|
|
43
|
+
} | undefined;
|
|
44
|
+
readonly deadlines?: {
|
|
45
|
+
preflightMs?: number;
|
|
46
|
+
abortMs?: number;
|
|
47
|
+
cleanupMs?: number;
|
|
48
|
+
} | undefined;
|
|
49
|
+
isLost?(): boolean;
|
|
22
50
|
readonly sdkSessionId: string;
|
|
23
51
|
/** Only whitelisted, post-verified selection metadata may cross the adapter boundary. */
|
|
24
52
|
readonly appliedSelection: PiAppliedSelection;
|
|
@@ -38,6 +66,8 @@ export interface PiSessionHandle {
|
|
|
38
66
|
export interface PiSessionAdapter {
|
|
39
67
|
create(input: {
|
|
40
68
|
cwd: string;
|
|
69
|
+
workspace: string;
|
|
70
|
+
base?: string;
|
|
41
71
|
executionProfile: ResolvedExecutionProfile;
|
|
42
72
|
signal?: AbortSignal;
|
|
43
73
|
}): Promise<PiSessionHandle>;
|
package/dist/pi-adapter.js
CHANGED
|
@@ -26,3 +26,14 @@ export class PiSessionCreationError extends Error {
|
|
|
26
26
|
this.code = code;
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
+
export class WriterUnavailableError extends Error {
|
|
30
|
+
reason;
|
|
31
|
+
counts;
|
|
32
|
+
code = "writer_unavailable";
|
|
33
|
+
constructor(reason, counts) {
|
|
34
|
+
super("No writer is available");
|
|
35
|
+
this.reason = reason;
|
|
36
|
+
this.counts = counts;
|
|
37
|
+
this.name = "WriterUnavailableError";
|
|
38
|
+
}
|
|
39
|
+
}
|
package/dist/refusal-server.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Readable, Writable } from "node:stream";
|
|
2
|
-
export type RefusalCode = "container_runtime_unavailable" | "writer_not_ready";
|
|
2
|
+
export type RefusalCode = "container_runtime_unavailable" | "writer_not_ready" | "writer_busy" | "writer_fenced";
|
|
3
3
|
export declare function serveRefusal(code: RefusalCode, io?: {
|
|
4
4
|
stdin: Readable;
|
|
5
5
|
stdout: Writable;
|
package/dist/refusal-server.js
CHANGED
|
@@ -5,10 +5,20 @@ import { PACKAGE_VERSION } from "./package-metadata.js";
|
|
|
5
5
|
const messages = {
|
|
6
6
|
container_runtime_unavailable: "Rootless Podman is not usable on this machine; this writer refuses all work.",
|
|
7
7
|
writer_not_ready: "This writer is not ready; run pi-session-mcp-setup for its repository.",
|
|
8
|
+
writer_busy: "This writer is already leased; try another writer.",
|
|
9
|
+
writer_fenced: "Container absence could not be confirmed; this writer refuses all work.",
|
|
8
10
|
};
|
|
9
11
|
export async function serveRefusal(code, io) {
|
|
10
12
|
const stdout = io?.stdout ?? (await import("./stdout-guard-install.js")).stdoutGuard.transportStdout;
|
|
11
|
-
|
|
13
|
+
const stdin = io?.stdin ?? process.stdin;
|
|
14
|
+
let finish;
|
|
15
|
+
const ended = new Promise((resolve) => { finish = resolve; });
|
|
16
|
+
stdin.once("end", finish);
|
|
17
|
+
stdin.once("close", finish);
|
|
18
|
+
const signals = ["SIGINT", "SIGTERM", "SIGHUP"];
|
|
19
|
+
for (const signal of signals)
|
|
20
|
+
process.once(signal, finish);
|
|
21
|
+
const handle = serveStdio(() => {
|
|
12
22
|
const server = new McpServer({ name: "pi-session-mcp", version: PACKAGE_VERSION }, { capabilities: { tools: {} } });
|
|
13
23
|
for (const name of ["pi_capabilities_get", "pi_session_start", "pi_session_list", "pi_session_get", "pi_session_prompt", "pi_session_abort", "pi_session_close", "pi_turn_get"]) {
|
|
14
24
|
server.registerTool(name, { inputSchema: z.object({}).passthrough() }, () => {
|
|
@@ -18,7 +28,19 @@ export async function serveRefusal(code, io) {
|
|
|
18
28
|
}
|
|
19
29
|
return server;
|
|
20
30
|
}, {
|
|
21
|
-
transport: new StdioServerTransport(
|
|
31
|
+
transport: new StdioServerTransport(stdin, stdout),
|
|
22
32
|
onerror: () => { process.stderr.write(JSON.stringify({ level: "error", event: "mcp_transport_error", stage: "transport", code: "mcp_transport_error" }) + "\n"); },
|
|
23
33
|
});
|
|
34
|
+
try {
|
|
35
|
+
if (stdin.readableEnded || stdin.destroyed)
|
|
36
|
+
finish();
|
|
37
|
+
await ended;
|
|
38
|
+
await handle.close();
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
stdin.off("end", finish);
|
|
42
|
+
stdin.off("close", finish);
|
|
43
|
+
for (const signal of signals)
|
|
44
|
+
process.off(signal, finish);
|
|
45
|
+
}
|
|
24
46
|
}
|
package/dist/server.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
-
import {
|
|
2
|
+
import type { ConfigurationMetadata } from "./config.js";
|
|
3
3
|
import { type WorkspaceModeEntry } from "./execution-modes.js";
|
|
4
4
|
import { type ConfiguredExecutionProfile } from "./execution-profile.js";
|
|
5
5
|
import { type SessionRegistry } from "./session-registry.js";
|
|
6
|
-
export declare function createServer(registry: SessionRegistry, workspaces: ReadonlyMap<string, string>, executionProfiles: ReadonlyMap<string, ConfiguredExecutionProfile>, defaultExecutionProfile: string, configuration: ConfigurationMetadata, workspaceModes?: ReadonlyMap<string, WorkspaceModeEntry>, eventDelivery?: "spool" | "off"): McpServer;
|
|
6
|
+
export declare function createServer(registry: SessionRegistry, workspaces: ReadonlyMap<string, string>, executionProfiles: ReadonlyMap<string, ConfiguredExecutionProfile>, defaultExecutionProfile: string, configuration: ConfigurationMetadata, workspaceModes?: ReadonlyMap<string, WorkspaceModeEntry>, eventDelivery?: "spool" | "off", relay?: boolean): McpServer;
|
package/dist/server.js
CHANGED
|
@@ -2,55 +2,11 @@ import { McpServer } from "@modelcontextprotocol/server";
|
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import packageMetadata from "../package.json" with { type: "json" };
|
|
4
4
|
import { projectCapabilities } from "./capabilities.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { ALIAS_PATTERN, toResolvedExecutionProfile } from "./execution-profile.js";
|
|
5
|
+
import { codingAllowed, isShortBranchName } from "./execution-modes.js";
|
|
6
|
+
import { PiSessionCreationError, WriterUnavailableError } from "./pi-adapter.js";
|
|
7
|
+
import { toResolvedExecutionProfile } from "./execution-profile.js";
|
|
9
8
|
import { RegistryError } from "./session-registry.js";
|
|
10
|
-
|
|
11
|
-
const viewSchema = z.object({
|
|
12
|
-
id: z.string(), workspace: z.string(), executionProfile: z.string(), profile: z.enum(["read-only", "coding"]),
|
|
13
|
-
provider: z.string(), model: z.string(), thinkingLevel: thinkingLevelSchema,
|
|
14
|
-
state: z.enum(["idle", "running", "failed", "closing", "closed"]), createdAt: z.string(), updatedAt: z.string(),
|
|
15
|
-
lastError: z.string().optional(),
|
|
16
|
-
});
|
|
17
|
-
const aliasSchema = z.string().regex(ALIAS_PATTERN);
|
|
18
|
-
// Input IDs: tools/list keeps `format: "uuid"` but omits zod's long repeated `pattern`; the
|
|
19
|
-
// server still validates with the unchanged `z.string().uuid()`, so accepted values and issues are identical.
|
|
20
|
-
const idInputSchema = z.string().meta({ format: "uuid" }).pipe(z.string().uuid());
|
|
21
|
-
const successSchema = z.object({ ok: z.literal(true), session: viewSchema });
|
|
22
|
-
const turnBase = { turnId: z.string().uuid(), sessionId: z.string().uuid(), startedAt: z.string(), updatedAt: z.string() };
|
|
23
|
-
const turnSchema = z.discriminatedUnion("state", [
|
|
24
|
-
z.object({ ...turnBase, state: z.literal("running") }),
|
|
25
|
-
z.object({ ...turnBase, state: z.literal("completed"), completedAt: z.string(), assistantText: z.string(), truncated: z.boolean() }),
|
|
26
|
-
z.object({ ...turnBase, state: z.literal("failed"), completedAt: z.string(), error: z.object({ code: z.literal("turn_failed"), message: z.literal("Pi turn failed") }) }),
|
|
27
|
-
z.object({ ...turnBase, state: z.literal("aborted"), completedAt: z.string() }),
|
|
28
|
-
]);
|
|
29
|
-
const promptSuccessSchema = z.object({ ok: z.literal(true), session: viewSchema, turn: turnSchema });
|
|
30
|
-
const turnSuccessSchema = z.object({ ok: z.literal(true), turn: turnSchema });
|
|
31
|
-
const listSchema = z.object({ ok: z.literal(true), sessions: z.array(viewSchema) });
|
|
32
|
-
const errorSchema = z.object({ ok: z.literal(false), error: z.object({ code: z.string(), message: z.string() }) });
|
|
33
|
-
const capabilitiesSchema = z.object({
|
|
34
|
-
ok: z.literal(true),
|
|
35
|
-
server: z.object({ name: z.literal(packageMetadata.name), version: z.literal(packageMetadata.version) }).strict(),
|
|
36
|
-
configuration: z.object({
|
|
37
|
-
fingerprint: z.string().regex(CONFIGURATION_FINGERPRINT_PATTERN),
|
|
38
|
-
reloadPolicy: z.literal(RELOAD_POLICY),
|
|
39
|
-
}).strict(),
|
|
40
|
-
events: z.object({ delivery: z.enum(["spool", "off"]) }).strict(),
|
|
41
|
-
workspaces: z.array(z.object({ alias: aliasSchema, mode: z.enum(["unset", "host", "container", "read-only"]) }).strict()),
|
|
42
|
-
executionProfiles: z.array(z.object({
|
|
43
|
-
alias: aliasSchema,
|
|
44
|
-
default: z.boolean(),
|
|
45
|
-
permissionProfile: z.enum(["read-only", "coding"]),
|
|
46
|
-
provider: z.string(),
|
|
47
|
-
model: z.string(),
|
|
48
|
-
thinkingLevel: thinkingLevelSchema,
|
|
49
|
-
extensions: z.array(z.string()),
|
|
50
|
-
grantedExtensionTools: z.array(z.string()),
|
|
51
|
-
skills: z.array(aliasSchema),
|
|
52
|
-
}).strict()),
|
|
53
|
-
}).strict();
|
|
9
|
+
import { aliasSchema, idInputSchema, successSchema, promptSuccessSchema, turnSuccessSchema, listSchema, errorSchema, capabilitiesSchema } from "./tool-schemas.js";
|
|
54
10
|
function result(value, isError = false) {
|
|
55
11
|
return { content: [{ type: "text", text: JSON.stringify(value) }], structuredContent: value, ...(isError ? { isError: true } : {}) };
|
|
56
12
|
}
|
|
@@ -59,11 +15,14 @@ async function safe(action) {
|
|
|
59
15
|
return result(await action());
|
|
60
16
|
}
|
|
61
17
|
catch (error) {
|
|
18
|
+
if (error instanceof WriterUnavailableError) {
|
|
19
|
+
return result({ ok: false, error: { code: error.code, message: error.message, reason: error.reason, ...(error.counts === undefined ? {} : { counts: error.counts }) } }, true);
|
|
20
|
+
}
|
|
62
21
|
const known = error instanceof RegistryError || error instanceof PiSessionCreationError;
|
|
63
22
|
return result({ ok: false, error: { code: known ? error.code : "internal_error", message: known ? error.message : "Operation failed" } }, true);
|
|
64
23
|
}
|
|
65
24
|
}
|
|
66
|
-
export function createServer(registry, workspaces, executionProfiles, defaultExecutionProfile, configuration, workspaceModes = new Map(), eventDelivery = "off") {
|
|
25
|
+
export function createServer(registry, workspaces, executionProfiles, defaultExecutionProfile, configuration, workspaceModes = new Map(), eventDelivery = "off", relay = false) {
|
|
67
26
|
const server = new McpServer({ name: packageMetadata.name, version: packageMetadata.version }, { capabilities: { tools: {} } });
|
|
68
27
|
server.registerTool("pi_capabilities_get", {
|
|
69
28
|
description: "List configured workspace and execution-profile aliases. This reports configured intent, not provider or runtime availability. Call once at the start of a run.",
|
|
@@ -73,17 +32,23 @@ export function createServer(registry, workspaces, executionProfiles, defaultExe
|
|
|
73
32
|
}, () => result(projectCapabilities(workspaces, executionProfiles, configuration, workspaceModes, eventDelivery)));
|
|
74
33
|
server.registerTool("pi_session_start", {
|
|
75
34
|
description: "Start a managed embedded Pi session for a configured workspace alias.",
|
|
76
|
-
inputSchema: z.object({ workspace: aliasSchema, executionProfile: aliasSchema.optional() }).strict(), outputSchema: z.union([successSchema, errorSchema]),
|
|
35
|
+
inputSchema: z.object({ workspace: aliasSchema, executionProfile: aliasSchema.optional(), base: z.string().refine(isShortBranchName).optional() }).strict(), outputSchema: z.union([successSchema, errorSchema]),
|
|
77
36
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
78
|
-
}, ({ workspace, executionProfile }) => safe(async () => {
|
|
37
|
+
}, ({ workspace, executionProfile, base }) => safe(async () => {
|
|
79
38
|
const alias = executionProfile ?? defaultExecutionProfile;
|
|
80
39
|
const configured = executionProfiles.get(alias);
|
|
81
40
|
if (!configured)
|
|
82
41
|
throw new RegistryError("unknown_execution_profile");
|
|
83
|
-
if (
|
|
42
|
+
if (!workspaces.has(workspace))
|
|
43
|
+
throw new RegistryError("unknown_workspace");
|
|
44
|
+
const mode = workspaceModes.get(workspace) ?? { mode: "unset" };
|
|
45
|
+
if (base !== undefined && (configured.permissionProfile !== "coding" || mode.mode !== "container")) {
|
|
46
|
+
throw new RegistryError("base_not_applicable");
|
|
47
|
+
}
|
|
48
|
+
if (configured.permissionProfile === "coding" && !codingAllowed(mode, relay)) {
|
|
84
49
|
throw new RegistryError("workspace_mode_forbids_coding");
|
|
85
50
|
}
|
|
86
|
-
return { ok: true, session: await registry.start(workspace, toResolvedExecutionProfile(configured)) };
|
|
51
|
+
return { ok: true, session: await registry.start(workspace, toResolvedExecutionProfile(configured), base === undefined ? {} : { base }) };
|
|
87
52
|
}));
|
|
88
53
|
server.registerTool("pi_session_list", {
|
|
89
54
|
description: "List local Pi sessions and their state.", inputSchema: z.object({}).strict(), outputSchema: z.union([listSchema, errorSchema]),
|
|
@@ -11,6 +11,11 @@ export interface SessionView {
|
|
|
11
11
|
provider: string;
|
|
12
12
|
model: string;
|
|
13
13
|
thinkingLevel: ThinkingLevel;
|
|
14
|
+
writer?: string;
|
|
15
|
+
base?: {
|
|
16
|
+
ref: string;
|
|
17
|
+
commit: string;
|
|
18
|
+
};
|
|
14
19
|
state: SessionState;
|
|
15
20
|
createdAt: string;
|
|
16
21
|
updatedAt: string;
|
|
@@ -41,7 +46,7 @@ export interface PromptResult {
|
|
|
41
46
|
session: SessionView;
|
|
42
47
|
turn: TurnView;
|
|
43
48
|
}
|
|
44
|
-
export type RegistryErrorCode = "unknown_workspace" | "unknown_session" | "session_running" | "prompt_rejected" | "prompt_timeout" | "abort_failed" | "abort_timeout" | "unknown_turn" | "unknown_execution_profile" | "workspace_mode_forbids_coding" | "writer_uncollected" | "writer_busy" | "server_stopping";
|
|
49
|
+
export type RegistryErrorCode = "writer_session_lost" | "base_not_applicable" | "unknown_workspace" | "unknown_session" | "session_running" | "prompt_rejected" | "prompt_timeout" | "abort_failed" | "abort_timeout" | "unknown_turn" | "unknown_execution_profile" | "workspace_mode_forbids_coding" | "writer_uncollected" | "writer_busy" | "server_stopping";
|
|
45
50
|
export declare class RegistryError extends Error {
|
|
46
51
|
readonly code: RegistryErrorCode;
|
|
47
52
|
constructor(code: RegistryErrorCode);
|
|
@@ -84,7 +89,9 @@ export declare class SessionRegistry {
|
|
|
84
89
|
private stopping;
|
|
85
90
|
private readonly shutdownController;
|
|
86
91
|
constructor(adapter: PiSessionAdapter, workspaces: ReadonlyMap<string, string>, options?: SessionRegistryOptions);
|
|
87
|
-
start(workspace: string, executionProfile: ResolvedExecutionProfile
|
|
92
|
+
start(workspace: string, executionProfile: ResolvedExecutionProfile, options?: {
|
|
93
|
+
base?: string;
|
|
94
|
+
}): Promise<SessionView>;
|
|
88
95
|
private guardedCreate;
|
|
89
96
|
private createAndRegister;
|
|
90
97
|
list(): SessionView[];
|