paseo-room 0.1.0 → 0.2.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/README.md +23 -15
- package/dist/index.js +238 -38
- package/dist/index.js.map +1 -1
- package/dist/plugin-assets/index.server.ts +50 -0
- package/dist/plugin-assets/package.json +5 -0
- package/dist/plugin-assets/paseo-plugin.json +5 -0
- package/dist/plugin-assets/server/carrier.ts +41 -0
- package/dist/plugin-assets/server/contract.ts +3 -0
- package/dist/plugin-assets/tsconfig.json +10 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -64,9 +64,12 @@ starts, its exit code is preserved; a signal is returned using the conventional
|
|
|
64
64
|
## Requirements
|
|
65
65
|
|
|
66
66
|
- Node 22 or newer, macOS or Linux.
|
|
67
|
-
- A running Paseo daemon with CLI and daemon on the same version. Codex
|
|
68
|
-
**0.8.0-beta.1 or newer**; any selection containing Pi requires **0.8.0 or newer**.
|
|
69
|
-
|
|
67
|
+
- A running Paseo daemon with CLI and daemon on the same version. Codex requires
|
|
68
|
+
**0.8.0-beta.1 or newer**; any selection containing Claude or Pi requires **0.8.0 or newer**.
|
|
69
|
+
Claude also requires Paseo plugins to be enabled explicitly: the generated trusted server plugin
|
|
70
|
+
is the strong contract carrier, and `paseo-room` never enables plugins for you. The plugin is
|
|
71
|
+
version-bounded to `>=0.8.0 <0.9.0`. `paseo-room` checks compatibility before touching anything,
|
|
72
|
+
and never installs or upgrades Paseo.
|
|
70
73
|
- An initialised Codex home (`~/.codex/config.toml`) and/or Claude Code home (`~/.claude`).
|
|
71
74
|
Codex must be new enough for `codex debug models` to print its JSON model catalog: the room
|
|
72
75
|
seats Codex only if it can generate the scrubbed catalog copy.
|
|
@@ -86,6 +89,9 @@ starts, its exit code is preserved; a signal is returned using the conventional
|
|
|
86
89
|
room.json # what this CLI created; verify and remove read it
|
|
87
90
|
AUTHENTICATION.md # exact per-role login commands; contains no secrets
|
|
88
91
|
room/WORKSPACE_PROTOCOL.md # the default protocol every seat carries, as one readable file
|
|
92
|
+
plugin/ # Claude only: trusted creation-time system-prompt append carrier
|
|
93
|
+
paseo-plugin.json # accepts Paseo >=0.8.0 <0.9.0
|
|
94
|
+
index.server.ts, server/ # exact provider map + generated role contracts
|
|
89
95
|
roles/codex/<role>/
|
|
90
96
|
config.toml # your config.toml + the room's overrides
|
|
91
97
|
role-instructions.md # readable copy of what this seat was told
|
|
@@ -237,18 +243,20 @@ holding older contract text.
|
|
|
237
243
|
|
|
238
244
|
**The room never replaces an agent's base prompt.** Codex's `model_instructions_file`,
|
|
239
245
|
Claude's `--system-prompt` and Pi's `SYSTEM.md` / `--system-prompt` all *replace* the vendor
|
|
240
|
-
system prompt; using them would mean
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
Claude
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
246
|
+
system prompt; using them would mean vendoring a full copy of that prompt and re-vendoring it
|
|
247
|
+
on every agent release. The role contract is additive instead: `developer_instructions` for
|
|
248
|
+
Codex, a room-owned Paseo creation hook that writes `config.systemPrompt` for Claude, and the
|
|
249
|
+
generated Pi `APPEND_SYSTEM.md` passed with `--append-system-prompt` for Pi. Pi keeps normal
|
|
250
|
+
project `AGENTS.md` / `CLAUDE.md` context loading.
|
|
251
|
+
|
|
252
|
+
For Claude, Paseo maps `config.systemPrompt` to the Claude Code preset's SDK `append` field,
|
|
253
|
+
so the native prompt remains intact while the generated role contract enters the instruction
|
|
254
|
+
layer. The trusted plugin targets only exact `claude-supervisor`, `claude-lead`, and
|
|
255
|
+
`claude-peer` room providers and skips internal agents; existing caller prompt text is
|
|
256
|
+
preserved. `CLAUDE.md` remains unchanged as a degraded and resume fallback. `verify` fails if
|
|
257
|
+
the plugin is absent, disabled, failed, registered from another path, or drifted, because a
|
|
258
|
+
silently missing carrier is not a guarantee. The hook runs for newly created sessions;
|
|
259
|
+
recreate an existing Claude session after setup or an update.
|
|
252
260
|
|
|
253
261
|
Pi providers use a strict command tail:
|
|
254
262
|
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "paseo-room",
|
|
18
|
-
version: "0.
|
|
18
|
+
version: "0.2.0",
|
|
19
19
|
description: "One CLI that configures Codex, Claude Code, and Pi role homes in $HOME and registers them with your local Paseo daemon.",
|
|
20
20
|
keywords: [
|
|
21
21
|
"paseo",
|
|
@@ -49,7 +49,7 @@ var init_package = __esm({
|
|
|
49
49
|
lint: "eslint . --max-warnings 0",
|
|
50
50
|
test: "vitest run",
|
|
51
51
|
"test:package": "vitest run --config vitest.package.config.ts",
|
|
52
|
-
build: `tsup && node --input-type=module -e "import { cpSync, rmSync } from 'node:fs';
|
|
52
|
+
build: `tsup && node --input-type=module -e "import { cpSync, rmSync } from 'node:fs'; for (const [source, target] of [['src/room/prompts', 'dist/prompts'], ['src/plugin-assets', 'dist/plugin-assets']]) { rmSync(target, { recursive: true, force: true }); cpSync(source, target, { recursive: true }); }"`,
|
|
53
53
|
prepack: "npm run build",
|
|
54
54
|
verify: "npm run typecheck && npm run lint && npm test && npm run build && npm run test:package"
|
|
55
55
|
},
|
|
@@ -1868,7 +1868,7 @@ var init_pi = __esm({
|
|
|
1868
1868
|
import { gte, valid } from "semver";
|
|
1869
1869
|
import { z as z2 } from "zod";
|
|
1870
1870
|
function minimumPaseoVersion(agents) {
|
|
1871
|
-
return agents.includes("pi") ? PI_MINIMUM_VERSION : MINIMUM_VERSION;
|
|
1871
|
+
return agents.includes("pi") || agents.includes("claude") ? PI_MINIMUM_VERSION : MINIMUM_VERSION;
|
|
1872
1872
|
}
|
|
1873
1873
|
function normalizeUrl(listen) {
|
|
1874
1874
|
const match = /^(?:ws:\/\/)?([^:/]+|\[[^\]]+\]):(\d+)$/.exec(listen.trim());
|
|
@@ -1929,15 +1929,38 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
1929
1929
|
}, warn() {
|
|
1930
1930
|
}, error() {
|
|
1931
1931
|
} };
|
|
1932
|
-
const
|
|
1933
|
-
const client = factory({
|
|
1932
|
+
const clientConfig = {
|
|
1934
1933
|
url: `${daemon.url}/ws`,
|
|
1935
1934
|
...options.password === void 0 ? {} : { password: options.password },
|
|
1936
1935
|
appVersion: MINIMUM_VERSION,
|
|
1937
1936
|
connectTimeoutMs: 1e4,
|
|
1938
1937
|
reconnect: { enabled: false },
|
|
1939
1938
|
logger: quiet
|
|
1940
|
-
}
|
|
1939
|
+
};
|
|
1940
|
+
let client;
|
|
1941
|
+
if (options.factory !== void 0) {
|
|
1942
|
+
client = options.factory(clientConfig);
|
|
1943
|
+
} else {
|
|
1944
|
+
const [{ createPaseoApi }, { DaemonClient }] = await Promise.all([
|
|
1945
|
+
import("@getpaseo/client"),
|
|
1946
|
+
import("@getpaseo/client/internal/daemon-client")
|
|
1947
|
+
]);
|
|
1948
|
+
const raw = new DaemonClient({ ...clientConfig, clientId: `paseo-room-${String(process.pid)}`, clientType: "cli" });
|
|
1949
|
+
client = {
|
|
1950
|
+
...createPaseoApi(raw),
|
|
1951
|
+
connect: () => raw.connect(),
|
|
1952
|
+
close: () => raw.close(),
|
|
1953
|
+
ensureConnected: () => {
|
|
1954
|
+
raw.ensureConnected();
|
|
1955
|
+
},
|
|
1956
|
+
getConnectionState: () => raw.getConnectionState(),
|
|
1957
|
+
listPlugins: () => raw.listPlugins(),
|
|
1958
|
+
installDirectoryPlugin: (path, id) => raw.installDirectoryPlugin(path, id),
|
|
1959
|
+
reloadPlugin: (id) => raw.reloadPlugin(id),
|
|
1960
|
+
enablePlugin: (id) => raw.enablePlugin(id),
|
|
1961
|
+
removePlugin: (id) => raw.removePlugin(id)
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1941
1964
|
try {
|
|
1942
1965
|
await client.connect();
|
|
1943
1966
|
return await run2({
|
|
@@ -1958,6 +1981,25 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
1958
1981
|
async writeProfiles(profiles) {
|
|
1959
1982
|
await client.config.patch({ agentProfiles: [...profiles] });
|
|
1960
1983
|
},
|
|
1984
|
+
async pluginsEnabled() {
|
|
1985
|
+
const response = configSchema.parse(await client.config.get());
|
|
1986
|
+
return response.config.pluginsEnabled === true;
|
|
1987
|
+
},
|
|
1988
|
+
async listPlugins() {
|
|
1989
|
+
return await client.listPlugins();
|
|
1990
|
+
},
|
|
1991
|
+
async installPlugin(path, id) {
|
|
1992
|
+
return await client.installDirectoryPlugin(path, id);
|
|
1993
|
+
},
|
|
1994
|
+
async reloadPlugin(id) {
|
|
1995
|
+
return await client.reloadPlugin(id);
|
|
1996
|
+
},
|
|
1997
|
+
async enablePlugin(id) {
|
|
1998
|
+
return await client.enablePlugin(id);
|
|
1999
|
+
},
|
|
2000
|
+
async removePlugin(id) {
|
|
2001
|
+
await client.removePlugin(id);
|
|
2002
|
+
},
|
|
1961
2003
|
async refresh(ids) {
|
|
1962
2004
|
await client.providers.refresh({ providers: [...ids] });
|
|
1963
2005
|
}
|
|
@@ -2024,14 +2066,59 @@ var init_paseo = __esm({
|
|
|
2024
2066
|
config: z2.object({
|
|
2025
2067
|
providers: z2.record(z2.string(), z2.unknown()).optional(),
|
|
2026
2068
|
// Read loosely: entries carry operator fields the room neither knows nor touches.
|
|
2027
|
-
agentProfiles: z2.array(z2.record(z2.string(), z2.unknown())).optional()
|
|
2069
|
+
agentProfiles: z2.array(z2.record(z2.string(), z2.unknown())).optional(),
|
|
2070
|
+
pluginsEnabled: z2.boolean().optional()
|
|
2028
2071
|
})
|
|
2029
2072
|
});
|
|
2030
2073
|
}
|
|
2031
2074
|
});
|
|
2032
2075
|
|
|
2076
|
+
// src/plugin.ts
|
|
2077
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2078
|
+
import { join as join10 } from "path";
|
|
2079
|
+
function loadAsset(path) {
|
|
2080
|
+
return readFileSync2(new URL(`./plugin-assets/${path}`, import.meta.url), "utf8");
|
|
2081
|
+
}
|
|
2082
|
+
function claudeCarrierPluginDir(layout) {
|
|
2083
|
+
return join10(layout.roomHome, "plugin");
|
|
2084
|
+
}
|
|
2085
|
+
function renderClaudeCarrierContract(roles) {
|
|
2086
|
+
const contracts = Object.fromEntries(roles.map((role) => [providerId("claude", role), renderInstructions(role)]));
|
|
2087
|
+
return [
|
|
2088
|
+
"/** Generated by paseo-room. The Markdown prompt assets remain authoritative. */",
|
|
2089
|
+
`export const GENERATION = ${JSON.stringify(contractDigest())};`,
|
|
2090
|
+
`export const CONTRACT: Readonly<Record<string, string>> = ${JSON.stringify(contracts, null, 2)};`,
|
|
2091
|
+
""
|
|
2092
|
+
].join("\n");
|
|
2093
|
+
}
|
|
2094
|
+
function claudeCarrierEntries(layout, roles) {
|
|
2095
|
+
const root = claudeCarrierPluginDir(layout);
|
|
2096
|
+
return [
|
|
2097
|
+
{ kind: "dir", path: root },
|
|
2098
|
+
{ kind: "dir", path: join10(root, "server") },
|
|
2099
|
+
...STATIC_ASSETS.map((path) => ({ kind: "file", path: join10(root, path), content: loadAsset(path) })),
|
|
2100
|
+
{ kind: "file", path: join10(root, "server", "contract.ts"), content: renderClaudeCarrierContract(roles) }
|
|
2101
|
+
];
|
|
2102
|
+
}
|
|
2103
|
+
var CLAUDE_CARRIER_PLUGIN_ID, STATIC_ASSETS;
|
|
2104
|
+
var init_plugin = __esm({
|
|
2105
|
+
"src/plugin.ts"() {
|
|
2106
|
+
"use strict";
|
|
2107
|
+
init_roles();
|
|
2108
|
+
init_instructions();
|
|
2109
|
+
CLAUDE_CARRIER_PLUGIN_ID = "paseo-room-claude-carrier";
|
|
2110
|
+
STATIC_ASSETS = [
|
|
2111
|
+
"paseo-plugin.json",
|
|
2112
|
+
"package.json",
|
|
2113
|
+
"tsconfig.json",
|
|
2114
|
+
"index.server.ts",
|
|
2115
|
+
"server/carrier.ts"
|
|
2116
|
+
];
|
|
2117
|
+
}
|
|
2118
|
+
});
|
|
2119
|
+
|
|
2033
2120
|
// src/commands.ts
|
|
2034
|
-
import { join as
|
|
2121
|
+
import { join as join11, sep as sep2 } from "path";
|
|
2035
2122
|
import { lstat as lstat4, rm as rm3 } from "fs/promises";
|
|
2036
2123
|
function sessionOptions(options) {
|
|
2037
2124
|
const password = (options.env ?? process.env).PASEO_PASSWORD;
|
|
@@ -2063,7 +2150,7 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2063
2150
|
const entries = [
|
|
2064
2151
|
{ kind: "dir", path: layout.roomHome },
|
|
2065
2152
|
{ kind: "dir", path: sharedRoom(layout) },
|
|
2066
|
-
{ kind: "file", path:
|
|
2153
|
+
{ kind: "file", path: join11(sharedRoom(layout), "WORKSPACE_PROTOCOL.md"), content: renderInstructions("workspace") }
|
|
2067
2154
|
];
|
|
2068
2155
|
const providers = {};
|
|
2069
2156
|
const profiles = [];
|
|
@@ -2097,17 +2184,19 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2097
2184
|
if (agents.every((id) => binaries[id] !== void 0)) {
|
|
2098
2185
|
entries.push({
|
|
2099
2186
|
kind: "file",
|
|
2100
|
-
path:
|
|
2187
|
+
path: join11(layout.roomHome, AUTHENTICATION_GUIDE),
|
|
2101
2188
|
content: renderAuthenticationGuide(layout.roomHome, binaries, agents, roles)
|
|
2102
2189
|
});
|
|
2103
2190
|
checks.push(warn(
|
|
2104
2191
|
"room.authentication-not-validated",
|
|
2105
|
-
`Role authentication was not validated. Login instructions are managed at ${
|
|
2192
|
+
`Role authentication was not validated. Login instructions are managed at ${join11(layout.roomHome, AUTHENTICATION_GUIDE)}.`,
|
|
2106
2193
|
"After setup --apply, follow that guide or run: paseo-room auth login <agent> <role>"
|
|
2107
2194
|
));
|
|
2108
2195
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2196
|
+
const pluginPath = agents.includes("claude") ? claudeCarrierPluginDir(layout) : void 0;
|
|
2197
|
+
if (pluginPath !== void 0) entries.push(...claudeCarrierEntries(layout, roles));
|
|
2198
|
+
entries.push({ kind: "file", path: join11(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest()) });
|
|
2199
|
+
return { entries, providers, profiles, checks, ...pluginPath === void 0 ? {} : { pluginPath } };
|
|
2111
2200
|
}
|
|
2112
2201
|
function contractProvenance(previous) {
|
|
2113
2202
|
const digest = contractDigest();
|
|
@@ -2162,10 +2251,10 @@ async function roomPathSafety(layout, agents, roles) {
|
|
|
2162
2251
|
const checks = [];
|
|
2163
2252
|
if (!await safeDirectory(layout.roomHome, checks)) return checks;
|
|
2164
2253
|
await safeDirectory(sharedRoom(layout), checks);
|
|
2165
|
-
const rolesHome =
|
|
2254
|
+
const rolesHome = join11(layout.roomHome, "roles");
|
|
2166
2255
|
if (!await safeDirectory(rolesHome, checks)) return checks;
|
|
2167
2256
|
for (const agent of agents) {
|
|
2168
|
-
const agentHome =
|
|
2257
|
+
const agentHome = join11(rolesHome, agent);
|
|
2169
2258
|
if (!await safeDirectory(agentHome, checks)) continue;
|
|
2170
2259
|
for (const role of roles) await safeDirectory(roleHome(layout, agent, role), checks);
|
|
2171
2260
|
}
|
|
@@ -2186,14 +2275,54 @@ async function staleFrom(layout, previous, agents, roles) {
|
|
|
2186
2275
|
if (await exists(path)) retainedDirectories.push(path);
|
|
2187
2276
|
}
|
|
2188
2277
|
}
|
|
2189
|
-
return {
|
|
2278
|
+
return {
|
|
2279
|
+
providerIds,
|
|
2280
|
+
profileIds,
|
|
2281
|
+
removePlugin: previous.agents.includes("claude") && !agents.includes("claude"),
|
|
2282
|
+
retainedDirectories
|
|
2283
|
+
};
|
|
2190
2284
|
}
|
|
2191
|
-
|
|
2192
|
-
const
|
|
2193
|
-
const
|
|
2285
|
+
function pluginPlan(desired, stale, plugins, expectedPath) {
|
|
2286
|
+
const livePlugin = plugins.find((plugin) => plugin.id === CLAUDE_CARRIER_PLUGIN_ID);
|
|
2287
|
+
const shouldManage = desired.pluginPath !== void 0 || stale.removePlugin;
|
|
2288
|
+
if (!shouldManage) return { checks: [] };
|
|
2289
|
+
if (livePlugin !== void 0 && livePlugin.path !== expectedPath) {
|
|
2290
|
+
return {
|
|
2291
|
+
livePlugin,
|
|
2292
|
+
checks: [fail(
|
|
2293
|
+
"claude.plugin.path",
|
|
2294
|
+
`Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is registered from ${livePlugin.path}, not the room-owned path ${expectedPath}.`,
|
|
2295
|
+
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2296
|
+
)]
|
|
2297
|
+
};
|
|
2298
|
+
}
|
|
2299
|
+
if (desired.pluginPath !== void 0) {
|
|
2300
|
+
const healthy = livePlugin?.enabled === true && livePlugin.status === "running" && livePlugin.error === void 0;
|
|
2301
|
+
return {
|
|
2302
|
+
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2303
|
+
operation: { action: livePlugin === void 0 ? "create" : healthy ? "noop" : "update", kind: "plugin", target: CLAUDE_CARRIER_PLUGIN_ID },
|
|
2304
|
+
checks: []
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
return {
|
|
2308
|
+
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2309
|
+
operation: { action: livePlugin === void 0 ? "noop" : "remove", kind: "plugin", target: CLAUDE_CARRIER_PLUGIN_ID },
|
|
2310
|
+
checks: []
|
|
2311
|
+
};
|
|
2312
|
+
}
|
|
2313
|
+
async function planRoom(session, desired, stale, expectedPluginPath) {
|
|
2314
|
+
const inspectPlugins = desired.pluginPath !== void 0 || stale.removePlugin;
|
|
2315
|
+
const [live, liveProfiles, plugins] = await Promise.all([
|
|
2316
|
+
session.readProviders(),
|
|
2317
|
+
session.readProfiles(),
|
|
2318
|
+
inspectPlugins ? session.listPlugins() : Promise.resolve([])
|
|
2319
|
+
]);
|
|
2194
2320
|
const byId = new Map(liveProfiles.map((entry) => [String(entry.id), entry]));
|
|
2321
|
+
const plugin = pluginPlan(desired, stale, plugins, expectedPluginPath);
|
|
2195
2322
|
return {
|
|
2196
2323
|
liveProfiles,
|
|
2324
|
+
...plugin.livePlugin === void 0 ? {} : { livePlugin: plugin.livePlugin },
|
|
2325
|
+
pluginChecks: plugin.checks,
|
|
2197
2326
|
operations: [
|
|
2198
2327
|
...await planEntries(desired.entries),
|
|
2199
2328
|
...Object.entries(desired.providers).map(([id, provider]) => ({
|
|
@@ -2207,10 +2336,53 @@ async function planRoom(session, desired, stale) {
|
|
|
2207
2336
|
target: profile.id
|
|
2208
2337
|
})),
|
|
2209
2338
|
...stale.providerIds.filter((id) => id in live).map((id) => ({ action: "remove", kind: "provider", target: id })),
|
|
2210
|
-
...stale.profileIds.filter((id) => byId.has(id)).map((id) => ({ action: "remove", kind: "profile", target: id }))
|
|
2339
|
+
...stale.profileIds.filter((id) => byId.has(id)).map((id) => ({ action: "remove", kind: "profile", target: id })),
|
|
2340
|
+
...plugin.operation === void 0 ? [] : [plugin.operation]
|
|
2211
2341
|
]
|
|
2212
2342
|
};
|
|
2213
2343
|
}
|
|
2344
|
+
function pluginsEnabledCheck(enabled) {
|
|
2345
|
+
return enabled ? pass("claude.plugin.enabled", "Paseo plugins are enabled for the required Claude contract carrier.") : fail(
|
|
2346
|
+
"claude.plugin.enabled",
|
|
2347
|
+
"Claude rooms require the paseo-room trusted server plugin, but Paseo plugins are disabled. Plugins are trusted, unsandboxed code with access to the daemon machine.",
|
|
2348
|
+
"Review that trust boundary, enable plugins in Paseo Settings, reload Paseo, then run setup again."
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
function pluginRuntimeCheck(plugin, expectedPath) {
|
|
2352
|
+
if (plugin === void 0) {
|
|
2353
|
+
return fail("claude.plugin.runtime", `Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is not registered.`, "Run: paseo-room setup --apply");
|
|
2354
|
+
}
|
|
2355
|
+
if (plugin.path !== expectedPath) {
|
|
2356
|
+
return fail(
|
|
2357
|
+
"claude.plugin.runtime",
|
|
2358
|
+
`Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is registered from ${plugin.path}, not ${expectedPath}.`,
|
|
2359
|
+
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
if (!plugin.enabled || plugin.status !== "running" || plugin.error !== void 0) {
|
|
2363
|
+
const detail = plugin.error === void 0 ? `status ${plugin.status}` : `${plugin.status}: ${plugin.error}`;
|
|
2364
|
+
return fail("claude.plugin.runtime", `Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is not running (${detail}).`, "Run: paseo-room setup --apply");
|
|
2365
|
+
}
|
|
2366
|
+
return pass("claude.plugin.runtime", `Claude contract carrier plugin is running from ${expectedPath}.`);
|
|
2367
|
+
}
|
|
2368
|
+
async function reconcilePlugin(session, desired, plan, pending, expectedPath) {
|
|
2369
|
+
if (desired.pluginPath === void 0) {
|
|
2370
|
+
if (plan.livePlugin !== void 0) await session.removePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2371
|
+
return [];
|
|
2372
|
+
}
|
|
2373
|
+
let plugin = plan.livePlugin;
|
|
2374
|
+
if (plugin === void 0) {
|
|
2375
|
+
plugin = await session.installPlugin(expectedPath, CLAUDE_CARRIER_PLUGIN_ID);
|
|
2376
|
+
} else {
|
|
2377
|
+
if (!plugin.enabled) plugin = await session.enablePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2378
|
+
const filesChanged = pending.some(
|
|
2379
|
+
(operation) => (operation.kind === "file" || operation.kind === "dir") && operation.target.startsWith(`${expectedPath}${sep2}`)
|
|
2380
|
+
);
|
|
2381
|
+
const needsReload = filesChanged || plugin.status !== "running" || plugin.error !== void 0;
|
|
2382
|
+
if (needsReload) plugin = await session.reloadPlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2383
|
+
}
|
|
2384
|
+
return [pluginRuntimeCheck(plugin, expectedPath)];
|
|
2385
|
+
}
|
|
2214
2386
|
async function setup(options = {}) {
|
|
2215
2387
|
const layout = resolveLayout(options, options.env);
|
|
2216
2388
|
const agents = options.agents ?? ["codex"];
|
|
@@ -2222,7 +2394,8 @@ async function setup(options = {}) {
|
|
|
2222
2394
|
const safetyAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2223
2395
|
const pathSafety = await roomPathSafety(layout, safetyAgents, ROLES);
|
|
2224
2396
|
if (hasFailure(pathSafety)) return failed("setup", pathSafety);
|
|
2225
|
-
const
|
|
2397
|
+
const compatibilityAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2398
|
+
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(compatibilityAgents));
|
|
2226
2399
|
if (!daemon.daemon) return failed("setup", daemon.checks);
|
|
2227
2400
|
const desired = await buildDesired(layout, agents, ROLES);
|
|
2228
2401
|
const stale = await staleFrom(layout, previous, agents, ROLES);
|
|
@@ -2234,19 +2407,24 @@ async function setup(options = {}) {
|
|
|
2234
2407
|
const checks = [...daemon.checks, ...desired.checks, ...retained];
|
|
2235
2408
|
if (hasFailure(checks)) return failed("setup", checks);
|
|
2236
2409
|
return withSession(daemon.daemon, async (session) => {
|
|
2237
|
-
const
|
|
2410
|
+
const expectedPluginPath = claudeCarrierPluginDir(layout);
|
|
2411
|
+
const plan = await planRoom(session, desired, stale, expectedPluginPath);
|
|
2412
|
+
const { operations, liveProfiles } = plan;
|
|
2238
2413
|
const pending = operations.filter((operation) => operation.action !== "noop");
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2414
|
+
const pluginPreconditions = desired.pluginPath === void 0 ? plan.pluginChecks : [pluginsEnabledCheck(await session.pluginsEnabled()), ...plan.pluginChecks];
|
|
2415
|
+
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2416
|
+
if (hasFailure(preApplyDiagnostics) || !options.apply) {
|
|
2241
2417
|
return {
|
|
2242
2418
|
command: "setup",
|
|
2243
|
-
outcome: pending.length > 0 ? "changes-planned" : "ok",
|
|
2419
|
+
outcome: hasFailure(preApplyDiagnostics) ? "failed" : pending.length > 0 ? "changes-planned" : "ok",
|
|
2244
2420
|
changed: false,
|
|
2245
2421
|
checks: preApplyDiagnostics,
|
|
2246
2422
|
operations
|
|
2247
2423
|
};
|
|
2248
2424
|
}
|
|
2249
|
-
|
|
2425
|
+
const markerPath = join11(layout.roomHome, MARKER);
|
|
2426
|
+
const markerEntries = desired.entries.filter((entry) => entry.path === markerPath);
|
|
2427
|
+
await applyEntries(desired.entries.filter((entry) => entry.path !== markerPath));
|
|
2250
2428
|
const ids = Object.keys(desired.providers);
|
|
2251
2429
|
await session.writeProviders(desired.providers);
|
|
2252
2430
|
if (stale.providerIds.length > 0) await session.removeProviders(stale.providerIds);
|
|
@@ -2256,24 +2434,27 @@ async function setup(options = {}) {
|
|
|
2256
2434
|
await session.writeProfiles(appliedProfiles);
|
|
2257
2435
|
}
|
|
2258
2436
|
await session.refresh(ids);
|
|
2437
|
+
const pluginChecks = await reconcilePlugin(session, desired, plan, pending, expectedPluginPath);
|
|
2438
|
+
await applyEntries(markerEntries);
|
|
2259
2439
|
const appliedMarker = {
|
|
2260
2440
|
version: package_default.version,
|
|
2261
2441
|
agents: [...agents],
|
|
2262
2442
|
roles: [...ROLES],
|
|
2263
2443
|
contract: contractDigest()
|
|
2264
2444
|
};
|
|
2445
|
+
const appliedChecks = [
|
|
2446
|
+
...checks,
|
|
2447
|
+
...pluginPreconditions,
|
|
2448
|
+
...pluginChecks,
|
|
2449
|
+
pass("room.applied", `Room ready at ${layout.roomHome} with ${String(ids.length)} Paseo providers.`),
|
|
2450
|
+
...contractProvenance(appliedMarker),
|
|
2451
|
+
...thinkingDiagnostics(appliedProfiles, desired.profiles)
|
|
2452
|
+
];
|
|
2265
2453
|
return {
|
|
2266
2454
|
command: "setup",
|
|
2267
|
-
outcome: "ok",
|
|
2455
|
+
outcome: hasFailure(appliedChecks) ? "failed" : "ok",
|
|
2268
2456
|
changed: pending.length > 0,
|
|
2269
|
-
|
|
2270
|
-
// cleared a warning. `appliedProfiles` is the exact array written above when one changed.
|
|
2271
|
-
checks: [
|
|
2272
|
-
...checks,
|
|
2273
|
-
pass("room.applied", `Room ready at ${layout.roomHome} with ${String(ids.length)} Paseo providers.`),
|
|
2274
|
-
...contractProvenance(appliedMarker),
|
|
2275
|
-
...thinkingDiagnostics(appliedProfiles, desired.profiles)
|
|
2276
|
-
],
|
|
2457
|
+
checks: appliedChecks,
|
|
2277
2458
|
operations
|
|
2278
2459
|
};
|
|
2279
2460
|
}, sessionOptions(options));
|
|
@@ -2296,18 +2477,26 @@ async function verify(options = {}) {
|
|
|
2296
2477
|
const checks = [...daemon.checks, ...desired.checks];
|
|
2297
2478
|
if (hasFailure(checks)) return failed("verify", checks);
|
|
2298
2479
|
return withSession(daemon.daemon, async (session) => {
|
|
2299
|
-
const
|
|
2480
|
+
const expectedPluginPath = claudeCarrierPluginDir(layout);
|
|
2481
|
+
const plan = await planRoom(session, desired, NOTHING_STALE, expectedPluginPath);
|
|
2482
|
+
const { operations, liveProfiles } = plan;
|
|
2300
2483
|
const drifted = operations.filter((operation) => operation.action !== "noop");
|
|
2301
2484
|
const count = (kind) => drifted.filter((operation) => operation.kind === kind).length;
|
|
2302
2485
|
const providers = count("provider");
|
|
2303
2486
|
const profiles = count("profile");
|
|
2304
|
-
const driftedFiles = drifted.filter((operation) =>
|
|
2487
|
+
const driftedFiles = drifted.filter((operation) => !["provider", "profile", "plugin"].includes(operation.kind));
|
|
2305
2488
|
const files = driftedFiles.length;
|
|
2489
|
+
const pluginChecks = desired.pluginPath === void 0 ? [] : [
|
|
2490
|
+
pluginsEnabledCheck(await session.pluginsEnabled()),
|
|
2491
|
+
...plan.pluginChecks,
|
|
2492
|
+
pluginRuntimeCheck(plan.livePlugin, expectedPluginPath)
|
|
2493
|
+
];
|
|
2306
2494
|
const all = [
|
|
2307
2495
|
...checks,
|
|
2308
2496
|
files === 0 ? pass("room.files", "Every managed role file matches the current definition.") : fail("room.files", `${String(files)} managed role files are missing or outdated.${fileDriftDetail(driftedFiles.map((operation) => operation.target))}`, "Run: paseo-room setup --apply"),
|
|
2309
2497
|
providers === 0 ? pass("room.providers", `All ${String(Object.keys(desired.providers).length)} Paseo providers are registered as expected.`) : fail("room.providers", `${String(providers)} Paseo providers are missing or differ.`, "Run: paseo-room setup --apply"),
|
|
2310
2498
|
profiles === 0 ? pass("room.profiles", `All ${String(desired.profiles.length)} Paseo agent profiles are registered as expected.`) : fail("room.profiles", `${String(profiles)} Paseo agent profiles are missing or differ.`, "Run: paseo-room setup --apply"),
|
|
2499
|
+
...pluginChecks,
|
|
2311
2500
|
// Diagnostics last: a warning never precedes the failure it might be mistaken for.
|
|
2312
2501
|
...contractProvenance(marker),
|
|
2313
2502
|
...thinkingDiagnostics(liveProfiles, desired.profiles)
|
|
@@ -2333,9 +2522,11 @@ async function remove(options = {}) {
|
|
|
2333
2522
|
if (invalid.length > 0) return failed("remove", invalid);
|
|
2334
2523
|
const ids = marker.agents.flatMap((agent) => marker.roles.map((role) => providerId(agent, role)));
|
|
2335
2524
|
const profiles = new Set(marker.agents.flatMap((agent) => marker.roles.map((role) => profileId(agent, role))));
|
|
2525
|
+
const hasClaude = marker.agents.includes("claude");
|
|
2336
2526
|
const operations = [
|
|
2337
2527
|
...ids.map((id) => ({ action: "remove", kind: "provider", target: id })),
|
|
2338
2528
|
...[...profiles].map((id) => ({ action: "remove", kind: "profile", target: id })),
|
|
2529
|
+
...hasClaude ? [{ action: "remove", kind: "plugin", target: CLAUDE_CARRIER_PLUGIN_ID }] : [],
|
|
2339
2530
|
{ action: "remove", kind: "dir", target: layout.roomHome }
|
|
2340
2531
|
];
|
|
2341
2532
|
const credentialWarning = warn(
|
|
@@ -2360,6 +2551,14 @@ async function remove(options = {}) {
|
|
|
2360
2551
|
}
|
|
2361
2552
|
try {
|
|
2362
2553
|
await withSession(daemon.daemon, async (session) => {
|
|
2554
|
+
if (hasClaude) {
|
|
2555
|
+
const plugin = (await session.listPlugins()).find((entry) => entry.id === CLAUDE_CARRIER_PLUGIN_ID);
|
|
2556
|
+
const expectedPath = claudeCarrierPluginDir(layout);
|
|
2557
|
+
if (plugin !== void 0 && plugin.path !== expectedPath) {
|
|
2558
|
+
throw new Error(`refusing to remove foreign plugin path ${plugin.path}`);
|
|
2559
|
+
}
|
|
2560
|
+
if (plugin !== void 0) await session.removePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2561
|
+
}
|
|
2363
2562
|
await session.removeProviders(ids);
|
|
2364
2563
|
const live = await session.readProfiles();
|
|
2365
2564
|
const kept = live.filter((entry) => !profiles.has(String(entry.id)));
|
|
@@ -2403,12 +2602,13 @@ var init_commands = __esm({
|
|
|
2403
2602
|
init_fsops();
|
|
2404
2603
|
init_layout();
|
|
2405
2604
|
init_paseo();
|
|
2605
|
+
init_plugin();
|
|
2406
2606
|
init_result();
|
|
2407
2607
|
init_instructions();
|
|
2408
2608
|
init_room();
|
|
2409
2609
|
init_roles();
|
|
2410
2610
|
AGENTS = { codex: codexAgent, claude: claudeAgent, pi: piAgent };
|
|
2411
|
-
NOTHING_STALE = { providerIds: [], profileIds: [], retainedDirectories: [] };
|
|
2611
|
+
NOTHING_STALE = { providerIds: [], profileIds: [], removePlugin: false, retainedDirectories: [] };
|
|
2412
2612
|
}
|
|
2413
2613
|
});
|
|
2414
2614
|
|