paseo-room 0.1.0 → 0.3.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 +50 -33
- package/dist/index.js +256 -45
- 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 +4 -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/dist/prompts/contract/lead/complete-peer-brief.md +24 -2
- package/dist/prompts/contract/lead/peer-seat-lifecycle.md +20 -11
- package/dist/prompts/contract/peer/assignment-scope.md +10 -0
- package/dist/prompts/contract/peer/bounded-outcome.md +8 -0
- package/dist/prompts/contract/peer/no-orchestration.md +2 -2
- package/dist/prompts/contract/peer/reproducible-handoff.md +15 -3
- package/dist/prompts/contract/shared-lead-peer/challenge-signals.md +1 -1
- package/dist/prompts/contract/{shared → shared-supervisor-lead}/workspace-protocol-precedence.md +7 -4
- package/dist/prompts/documents/peer.md +2 -1
- package/dist/prompts/documents/workspace.md +3 -2
- package/dist/prompts/pi/runtime.md +6 -2
- package/dist/prompts/workspace/review.md +2 -2
- package/dist/prompts/workspace/verification.md +5 -3
- package/package.json +2 -2
- package/dist/prompts/contract/peer/writing-and-review-scope.md +0 -5
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.3.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
|
},
|
|
@@ -799,7 +799,7 @@ var init_prompts = __esm({
|
|
|
799
799
|
},
|
|
800
800
|
contract: {
|
|
801
801
|
humanAuthority: { path: "contract/shared/human-authority.md", kind: "section" },
|
|
802
|
-
workspaceProtocolPrecedence: { path: "contract/shared/workspace-protocol-precedence.md", kind: "section" },
|
|
802
|
+
workspaceProtocolPrecedence: { path: "contract/shared-supervisor-lead/workspace-protocol-precedence.md", kind: "section" },
|
|
803
803
|
evidenceAndEventWaiting: { path: "contract/shared/evidence-and-event-waiting.md", kind: "section" },
|
|
804
804
|
scopeAndUnrelatedWork: { path: "contract/shared/scope-and-unrelated-work.md", kind: "section" },
|
|
805
805
|
directiveIntegrity: { path: "contract/supervisor/directive-integrity.md", kind: "section" },
|
|
@@ -816,7 +816,7 @@ var init_prompts = __esm({
|
|
|
816
816
|
peerSeatLifecycle: { path: "contract/lead/peer-seat-lifecycle.md", kind: "section" },
|
|
817
817
|
boundedOutcome: { path: "contract/peer/bounded-outcome.md", kind: "section" },
|
|
818
818
|
independentJudgment: { path: "contract/peer/independent-judgment.md", kind: "section" },
|
|
819
|
-
|
|
819
|
+
assignmentScope: { path: "contract/peer/assignment-scope.md", kind: "section" },
|
|
820
820
|
noOrchestration: { path: "contract/peer/no-orchestration.md", kind: "section" },
|
|
821
821
|
reproducibleHandoff: { path: "contract/peer/reproducible-handoff.md", kind: "section" },
|
|
822
822
|
noSelfAcceptance: { path: "contract/peer/no-self-acceptance.md", kind: "section" }
|
|
@@ -849,12 +849,13 @@ var init_prompts = __esm({
|
|
|
849
849
|
// src/room/instructions.ts
|
|
850
850
|
import { createHash } from "crypto";
|
|
851
851
|
function instructionKeys(role) {
|
|
852
|
-
return [...SHARED_KEYS, ...ROLE_KEYS[role]];
|
|
852
|
+
return [...SHARED_KEYS[role], ...ROLE_KEYS[role]];
|
|
853
853
|
}
|
|
854
854
|
function protocolKeys(role) {
|
|
855
855
|
return PROTOCOL_KEYS[role];
|
|
856
856
|
}
|
|
857
857
|
function protocol(keys) {
|
|
858
|
+
if (keys.length === 0) return [];
|
|
858
859
|
return [
|
|
859
860
|
loadPromptAsset("documents", "workspace"),
|
|
860
861
|
...keys.map((key) => loadPromptAsset("workspace", key))
|
|
@@ -873,18 +874,28 @@ function contractDigest() {
|
|
|
873
874
|
for (const kind of INSTRUCTION_KINDS) hash.update(`${kind}\0${renderInstructions(kind)}\0`);
|
|
874
875
|
return `sha256:${hash.digest("hex").slice(0, 16)}`;
|
|
875
876
|
}
|
|
876
|
-
var SHARED_KEYS, ROLE_KEYS, ALL_PROTOCOL_KEYS, PROTOCOL_KEYS, INSTRUCTION_KINDS;
|
|
877
|
+
var AUTHORITY_KEYS, PROTOCOL_READER_KEYS, SHARED_KEYS, ROLE_KEYS, ALL_PROTOCOL_KEYS, PROTOCOL_KEYS, INSTRUCTION_KINDS;
|
|
877
878
|
var init_instructions = __esm({
|
|
878
879
|
"src/room/instructions.ts"() {
|
|
879
880
|
"use strict";
|
|
880
881
|
init_roles();
|
|
881
882
|
init_prompts();
|
|
882
|
-
|
|
883
|
+
AUTHORITY_KEYS = [
|
|
884
|
+
"humanAuthority",
|
|
885
|
+
"evidenceAndEventWaiting",
|
|
886
|
+
"scopeAndUnrelatedWork"
|
|
887
|
+
];
|
|
888
|
+
PROTOCOL_READER_KEYS = [
|
|
883
889
|
"humanAuthority",
|
|
884
890
|
"workspaceProtocolPrecedence",
|
|
885
891
|
"evidenceAndEventWaiting",
|
|
886
892
|
"scopeAndUnrelatedWork"
|
|
887
893
|
];
|
|
894
|
+
SHARED_KEYS = {
|
|
895
|
+
supervisor: PROTOCOL_READER_KEYS,
|
|
896
|
+
lead: PROTOCOL_READER_KEYS,
|
|
897
|
+
peer: AUTHORITY_KEYS
|
|
898
|
+
};
|
|
888
899
|
ROLE_KEYS = {
|
|
889
900
|
supervisor: [
|
|
890
901
|
"directiveIntegrity",
|
|
@@ -907,7 +918,7 @@ var init_instructions = __esm({
|
|
|
907
918
|
"challengeSignals",
|
|
908
919
|
"boundedOutcome",
|
|
909
920
|
"independentJudgment",
|
|
910
|
-
"
|
|
921
|
+
"assignmentScope",
|
|
911
922
|
"noOrchestration",
|
|
912
923
|
"reproducibleHandoff",
|
|
913
924
|
"noSelfAcceptance"
|
|
@@ -922,7 +933,7 @@ var init_instructions = __esm({
|
|
|
922
933
|
PROTOCOL_KEYS = {
|
|
923
934
|
supervisor: ALL_PROTOCOL_KEYS,
|
|
924
935
|
lead: ALL_PROTOCOL_KEYS,
|
|
925
|
-
peer: [
|
|
936
|
+
peer: []
|
|
926
937
|
};
|
|
927
938
|
INSTRUCTION_KINDS = [...ROLES, "workspace"];
|
|
928
939
|
}
|
|
@@ -1868,7 +1879,7 @@ var init_pi = __esm({
|
|
|
1868
1879
|
import { gte, valid } from "semver";
|
|
1869
1880
|
import { z as z2 } from "zod";
|
|
1870
1881
|
function minimumPaseoVersion(agents) {
|
|
1871
|
-
return agents.includes("pi") ? PI_MINIMUM_VERSION : MINIMUM_VERSION;
|
|
1882
|
+
return agents.includes("pi") || agents.includes("claude") ? PI_MINIMUM_VERSION : MINIMUM_VERSION;
|
|
1872
1883
|
}
|
|
1873
1884
|
function normalizeUrl(listen) {
|
|
1874
1885
|
const match = /^(?:ws:\/\/)?([^:/]+|\[[^\]]+\]):(\d+)$/.exec(listen.trim());
|
|
@@ -1929,15 +1940,38 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
1929
1940
|
}, warn() {
|
|
1930
1941
|
}, error() {
|
|
1931
1942
|
} };
|
|
1932
|
-
const
|
|
1933
|
-
const client = factory({
|
|
1943
|
+
const clientConfig = {
|
|
1934
1944
|
url: `${daemon.url}/ws`,
|
|
1935
1945
|
...options.password === void 0 ? {} : { password: options.password },
|
|
1936
1946
|
appVersion: MINIMUM_VERSION,
|
|
1937
1947
|
connectTimeoutMs: 1e4,
|
|
1938
1948
|
reconnect: { enabled: false },
|
|
1939
1949
|
logger: quiet
|
|
1940
|
-
}
|
|
1950
|
+
};
|
|
1951
|
+
let client;
|
|
1952
|
+
if (options.factory !== void 0) {
|
|
1953
|
+
client = options.factory(clientConfig);
|
|
1954
|
+
} else {
|
|
1955
|
+
const [{ createPaseoApi }, { DaemonClient }] = await Promise.all([
|
|
1956
|
+
import("@getpaseo/client"),
|
|
1957
|
+
import("@getpaseo/client/internal/daemon-client")
|
|
1958
|
+
]);
|
|
1959
|
+
const raw = new DaemonClient({ ...clientConfig, clientId: `paseo-room-${String(process.pid)}`, clientType: "cli" });
|
|
1960
|
+
client = {
|
|
1961
|
+
...createPaseoApi(raw),
|
|
1962
|
+
connect: () => raw.connect(),
|
|
1963
|
+
close: () => raw.close(),
|
|
1964
|
+
ensureConnected: () => {
|
|
1965
|
+
raw.ensureConnected();
|
|
1966
|
+
},
|
|
1967
|
+
getConnectionState: () => raw.getConnectionState(),
|
|
1968
|
+
listPlugins: () => raw.listPlugins(),
|
|
1969
|
+
installDirectoryPlugin: (path, id) => raw.installDirectoryPlugin(path, id),
|
|
1970
|
+
reloadPlugin: (id) => raw.reloadPlugin(id),
|
|
1971
|
+
enablePlugin: (id) => raw.enablePlugin(id),
|
|
1972
|
+
removePlugin: (id) => raw.removePlugin(id)
|
|
1973
|
+
};
|
|
1974
|
+
}
|
|
1941
1975
|
try {
|
|
1942
1976
|
await client.connect();
|
|
1943
1977
|
return await run2({
|
|
@@ -1958,6 +1992,25 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
1958
1992
|
async writeProfiles(profiles) {
|
|
1959
1993
|
await client.config.patch({ agentProfiles: [...profiles] });
|
|
1960
1994
|
},
|
|
1995
|
+
async pluginsEnabled() {
|
|
1996
|
+
const response = configSchema.parse(await client.config.get());
|
|
1997
|
+
return response.config.pluginsEnabled === true;
|
|
1998
|
+
},
|
|
1999
|
+
async listPlugins() {
|
|
2000
|
+
return await client.listPlugins();
|
|
2001
|
+
},
|
|
2002
|
+
async installPlugin(path, id) {
|
|
2003
|
+
return await client.installDirectoryPlugin(path, id);
|
|
2004
|
+
},
|
|
2005
|
+
async reloadPlugin(id) {
|
|
2006
|
+
return await client.reloadPlugin(id);
|
|
2007
|
+
},
|
|
2008
|
+
async enablePlugin(id) {
|
|
2009
|
+
return await client.enablePlugin(id);
|
|
2010
|
+
},
|
|
2011
|
+
async removePlugin(id) {
|
|
2012
|
+
await client.removePlugin(id);
|
|
2013
|
+
},
|
|
1961
2014
|
async refresh(ids) {
|
|
1962
2015
|
await client.providers.refresh({ providers: [...ids] });
|
|
1963
2016
|
}
|
|
@@ -2024,14 +2077,59 @@ var init_paseo = __esm({
|
|
|
2024
2077
|
config: z2.object({
|
|
2025
2078
|
providers: z2.record(z2.string(), z2.unknown()).optional(),
|
|
2026
2079
|
// Read loosely: entries carry operator fields the room neither knows nor touches.
|
|
2027
|
-
agentProfiles: z2.array(z2.record(z2.string(), z2.unknown())).optional()
|
|
2080
|
+
agentProfiles: z2.array(z2.record(z2.string(), z2.unknown())).optional(),
|
|
2081
|
+
pluginsEnabled: z2.boolean().optional()
|
|
2028
2082
|
})
|
|
2029
2083
|
});
|
|
2030
2084
|
}
|
|
2031
2085
|
});
|
|
2032
2086
|
|
|
2087
|
+
// src/plugin.ts
|
|
2088
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
2089
|
+
import { join as join10 } from "path";
|
|
2090
|
+
function loadAsset(path) {
|
|
2091
|
+
return readFileSync2(new URL(`./plugin-assets/${path}`, import.meta.url), "utf8");
|
|
2092
|
+
}
|
|
2093
|
+
function claudeCarrierPluginDir(layout) {
|
|
2094
|
+
return join10(layout.roomHome, "plugin");
|
|
2095
|
+
}
|
|
2096
|
+
function renderClaudeCarrierContract(roles) {
|
|
2097
|
+
const contracts = Object.fromEntries(roles.map((role) => [providerId("claude", role), renderInstructions(role)]));
|
|
2098
|
+
return [
|
|
2099
|
+
"/** Generated by paseo-room. The Markdown prompt assets remain authoritative. */",
|
|
2100
|
+
`export const GENERATION = ${JSON.stringify(contractDigest())};`,
|
|
2101
|
+
`export const CONTRACT: Readonly<Record<string, string>> = ${JSON.stringify(contracts, null, 2)};`,
|
|
2102
|
+
""
|
|
2103
|
+
].join("\n");
|
|
2104
|
+
}
|
|
2105
|
+
function claudeCarrierEntries(layout, roles) {
|
|
2106
|
+
const root = claudeCarrierPluginDir(layout);
|
|
2107
|
+
return [
|
|
2108
|
+
{ kind: "dir", path: root },
|
|
2109
|
+
{ kind: "dir", path: join10(root, "server") },
|
|
2110
|
+
...STATIC_ASSETS.map((path) => ({ kind: "file", path: join10(root, path), content: loadAsset(path) })),
|
|
2111
|
+
{ kind: "file", path: join10(root, "server", "contract.ts"), content: renderClaudeCarrierContract(roles) }
|
|
2112
|
+
];
|
|
2113
|
+
}
|
|
2114
|
+
var CLAUDE_CARRIER_PLUGIN_ID, STATIC_ASSETS;
|
|
2115
|
+
var init_plugin = __esm({
|
|
2116
|
+
"src/plugin.ts"() {
|
|
2117
|
+
"use strict";
|
|
2118
|
+
init_roles();
|
|
2119
|
+
init_instructions();
|
|
2120
|
+
CLAUDE_CARRIER_PLUGIN_ID = "paseo-room-claude-carrier";
|
|
2121
|
+
STATIC_ASSETS = [
|
|
2122
|
+
"paseo-plugin.json",
|
|
2123
|
+
"package.json",
|
|
2124
|
+
"tsconfig.json",
|
|
2125
|
+
"index.server.ts",
|
|
2126
|
+
"server/carrier.ts"
|
|
2127
|
+
];
|
|
2128
|
+
}
|
|
2129
|
+
});
|
|
2130
|
+
|
|
2033
2131
|
// src/commands.ts
|
|
2034
|
-
import { join as
|
|
2132
|
+
import { join as join11, sep as sep2 } from "path";
|
|
2035
2133
|
import { lstat as lstat4, rm as rm3 } from "fs/promises";
|
|
2036
2134
|
function sessionOptions(options) {
|
|
2037
2135
|
const password = (options.env ?? process.env).PASEO_PASSWORD;
|
|
@@ -2063,7 +2161,7 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2063
2161
|
const entries = [
|
|
2064
2162
|
{ kind: "dir", path: layout.roomHome },
|
|
2065
2163
|
{ kind: "dir", path: sharedRoom(layout) },
|
|
2066
|
-
{ kind: "file", path:
|
|
2164
|
+
{ kind: "file", path: join11(sharedRoom(layout), "WORKSPACE_PROTOCOL.md"), content: renderInstructions("workspace") }
|
|
2067
2165
|
];
|
|
2068
2166
|
const providers = {};
|
|
2069
2167
|
const profiles = [];
|
|
@@ -2097,17 +2195,19 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2097
2195
|
if (agents.every((id) => binaries[id] !== void 0)) {
|
|
2098
2196
|
entries.push({
|
|
2099
2197
|
kind: "file",
|
|
2100
|
-
path:
|
|
2198
|
+
path: join11(layout.roomHome, AUTHENTICATION_GUIDE),
|
|
2101
2199
|
content: renderAuthenticationGuide(layout.roomHome, binaries, agents, roles)
|
|
2102
2200
|
});
|
|
2103
2201
|
checks.push(warn(
|
|
2104
2202
|
"room.authentication-not-validated",
|
|
2105
|
-
`Role authentication was not validated. Login instructions are managed at ${
|
|
2203
|
+
`Role authentication was not validated. Login instructions are managed at ${join11(layout.roomHome, AUTHENTICATION_GUIDE)}.`,
|
|
2106
2204
|
"After setup --apply, follow that guide or run: paseo-room auth login <agent> <role>"
|
|
2107
2205
|
));
|
|
2108
2206
|
}
|
|
2109
|
-
|
|
2110
|
-
|
|
2207
|
+
const pluginPath = agents.includes("claude") ? claudeCarrierPluginDir(layout) : void 0;
|
|
2208
|
+
if (pluginPath !== void 0) entries.push(...claudeCarrierEntries(layout, roles));
|
|
2209
|
+
entries.push({ kind: "file", path: join11(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest()) });
|
|
2210
|
+
return { entries, providers, profiles, checks, ...pluginPath === void 0 ? {} : { pluginPath } };
|
|
2111
2211
|
}
|
|
2112
2212
|
function contractProvenance(previous) {
|
|
2113
2213
|
const digest = contractDigest();
|
|
@@ -2162,10 +2262,10 @@ async function roomPathSafety(layout, agents, roles) {
|
|
|
2162
2262
|
const checks = [];
|
|
2163
2263
|
if (!await safeDirectory(layout.roomHome, checks)) return checks;
|
|
2164
2264
|
await safeDirectory(sharedRoom(layout), checks);
|
|
2165
|
-
const rolesHome =
|
|
2265
|
+
const rolesHome = join11(layout.roomHome, "roles");
|
|
2166
2266
|
if (!await safeDirectory(rolesHome, checks)) return checks;
|
|
2167
2267
|
for (const agent of agents) {
|
|
2168
|
-
const agentHome =
|
|
2268
|
+
const agentHome = join11(rolesHome, agent);
|
|
2169
2269
|
if (!await safeDirectory(agentHome, checks)) continue;
|
|
2170
2270
|
for (const role of roles) await safeDirectory(roleHome(layout, agent, role), checks);
|
|
2171
2271
|
}
|
|
@@ -2186,14 +2286,54 @@ async function staleFrom(layout, previous, agents, roles) {
|
|
|
2186
2286
|
if (await exists(path)) retainedDirectories.push(path);
|
|
2187
2287
|
}
|
|
2188
2288
|
}
|
|
2189
|
-
return {
|
|
2289
|
+
return {
|
|
2290
|
+
providerIds,
|
|
2291
|
+
profileIds,
|
|
2292
|
+
removePlugin: previous.agents.includes("claude") && !agents.includes("claude"),
|
|
2293
|
+
retainedDirectories
|
|
2294
|
+
};
|
|
2295
|
+
}
|
|
2296
|
+
function pluginPlan(desired, stale, plugins, expectedPath) {
|
|
2297
|
+
const livePlugin = plugins.find((plugin) => plugin.id === CLAUDE_CARRIER_PLUGIN_ID);
|
|
2298
|
+
const shouldManage = desired.pluginPath !== void 0 || stale.removePlugin;
|
|
2299
|
+
if (!shouldManage) return { checks: [] };
|
|
2300
|
+
if (livePlugin !== void 0 && livePlugin.path !== expectedPath) {
|
|
2301
|
+
return {
|
|
2302
|
+
livePlugin,
|
|
2303
|
+
checks: [fail(
|
|
2304
|
+
"claude.plugin.path",
|
|
2305
|
+
`Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is registered from ${livePlugin.path}, not the room-owned path ${expectedPath}.`,
|
|
2306
|
+
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2307
|
+
)]
|
|
2308
|
+
};
|
|
2309
|
+
}
|
|
2310
|
+
if (desired.pluginPath !== void 0) {
|
|
2311
|
+
const healthy = livePlugin?.enabled === true && livePlugin.status === "running" && livePlugin.error === void 0;
|
|
2312
|
+
return {
|
|
2313
|
+
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2314
|
+
operation: { action: livePlugin === void 0 ? "create" : healthy ? "noop" : "update", kind: "plugin", target: CLAUDE_CARRIER_PLUGIN_ID },
|
|
2315
|
+
checks: []
|
|
2316
|
+
};
|
|
2317
|
+
}
|
|
2318
|
+
return {
|
|
2319
|
+
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2320
|
+
operation: { action: livePlugin === void 0 ? "noop" : "remove", kind: "plugin", target: CLAUDE_CARRIER_PLUGIN_ID },
|
|
2321
|
+
checks: []
|
|
2322
|
+
};
|
|
2190
2323
|
}
|
|
2191
|
-
async function planRoom(session, desired, stale) {
|
|
2192
|
-
const
|
|
2193
|
-
const liveProfiles = await
|
|
2324
|
+
async function planRoom(session, desired, stale, expectedPluginPath) {
|
|
2325
|
+
const inspectPlugins = desired.pluginPath !== void 0 || stale.removePlugin;
|
|
2326
|
+
const [live, liveProfiles, plugins] = await Promise.all([
|
|
2327
|
+
session.readProviders(),
|
|
2328
|
+
session.readProfiles(),
|
|
2329
|
+
inspectPlugins ? session.listPlugins() : Promise.resolve([])
|
|
2330
|
+
]);
|
|
2194
2331
|
const byId = new Map(liveProfiles.map((entry) => [String(entry.id), entry]));
|
|
2332
|
+
const plugin = pluginPlan(desired, stale, plugins, expectedPluginPath);
|
|
2195
2333
|
return {
|
|
2196
2334
|
liveProfiles,
|
|
2335
|
+
...plugin.livePlugin === void 0 ? {} : { livePlugin: plugin.livePlugin },
|
|
2336
|
+
pluginChecks: plugin.checks,
|
|
2197
2337
|
operations: [
|
|
2198
2338
|
...await planEntries(desired.entries),
|
|
2199
2339
|
...Object.entries(desired.providers).map(([id, provider]) => ({
|
|
@@ -2207,10 +2347,53 @@ async function planRoom(session, desired, stale) {
|
|
|
2207
2347
|
target: profile.id
|
|
2208
2348
|
})),
|
|
2209
2349
|
...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 }))
|
|
2350
|
+
...stale.profileIds.filter((id) => byId.has(id)).map((id) => ({ action: "remove", kind: "profile", target: id })),
|
|
2351
|
+
...plugin.operation === void 0 ? [] : [plugin.operation]
|
|
2211
2352
|
]
|
|
2212
2353
|
};
|
|
2213
2354
|
}
|
|
2355
|
+
function pluginsEnabledCheck(enabled) {
|
|
2356
|
+
return enabled ? pass("claude.plugin.enabled", "Paseo plugins are enabled for the required Claude contract carrier.") : fail(
|
|
2357
|
+
"claude.plugin.enabled",
|
|
2358
|
+
"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.",
|
|
2359
|
+
"Review that trust boundary, enable plugins in Paseo Settings, reload Paseo, then run setup again."
|
|
2360
|
+
);
|
|
2361
|
+
}
|
|
2362
|
+
function pluginRuntimeCheck(plugin, expectedPath) {
|
|
2363
|
+
if (plugin === void 0) {
|
|
2364
|
+
return fail("claude.plugin.runtime", `Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is not registered.`, "Run: paseo-room setup --apply");
|
|
2365
|
+
}
|
|
2366
|
+
if (plugin.path !== expectedPath) {
|
|
2367
|
+
return fail(
|
|
2368
|
+
"claude.plugin.runtime",
|
|
2369
|
+
`Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is registered from ${plugin.path}, not ${expectedPath}.`,
|
|
2370
|
+
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
if (!plugin.enabled || plugin.status !== "running" || plugin.error !== void 0) {
|
|
2374
|
+
const detail = plugin.error === void 0 ? `status ${plugin.status}` : `${plugin.status}: ${plugin.error}`;
|
|
2375
|
+
return fail("claude.plugin.runtime", `Plugin ${CLAUDE_CARRIER_PLUGIN_ID} is not running (${detail}).`, "Run: paseo-room setup --apply");
|
|
2376
|
+
}
|
|
2377
|
+
return pass("claude.plugin.runtime", `Claude contract carrier plugin is running from ${expectedPath}.`);
|
|
2378
|
+
}
|
|
2379
|
+
async function reconcilePlugin(session, desired, plan, pending, expectedPath) {
|
|
2380
|
+
if (desired.pluginPath === void 0) {
|
|
2381
|
+
if (plan.livePlugin !== void 0) await session.removePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2382
|
+
return [];
|
|
2383
|
+
}
|
|
2384
|
+
let plugin = plan.livePlugin;
|
|
2385
|
+
if (plugin === void 0) {
|
|
2386
|
+
plugin = await session.installPlugin(expectedPath, CLAUDE_CARRIER_PLUGIN_ID);
|
|
2387
|
+
} else {
|
|
2388
|
+
if (!plugin.enabled) plugin = await session.enablePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2389
|
+
const filesChanged = pending.some(
|
|
2390
|
+
(operation) => (operation.kind === "file" || operation.kind === "dir") && operation.target.startsWith(`${expectedPath}${sep2}`)
|
|
2391
|
+
);
|
|
2392
|
+
const needsReload = filesChanged || plugin.status !== "running" || plugin.error !== void 0;
|
|
2393
|
+
if (needsReload) plugin = await session.reloadPlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2394
|
+
}
|
|
2395
|
+
return [pluginRuntimeCheck(plugin, expectedPath)];
|
|
2396
|
+
}
|
|
2214
2397
|
async function setup(options = {}) {
|
|
2215
2398
|
const layout = resolveLayout(options, options.env);
|
|
2216
2399
|
const agents = options.agents ?? ["codex"];
|
|
@@ -2222,7 +2405,8 @@ async function setup(options = {}) {
|
|
|
2222
2405
|
const safetyAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2223
2406
|
const pathSafety = await roomPathSafety(layout, safetyAgents, ROLES);
|
|
2224
2407
|
if (hasFailure(pathSafety)) return failed("setup", pathSafety);
|
|
2225
|
-
const
|
|
2408
|
+
const compatibilityAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2409
|
+
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(compatibilityAgents));
|
|
2226
2410
|
if (!daemon.daemon) return failed("setup", daemon.checks);
|
|
2227
2411
|
const desired = await buildDesired(layout, agents, ROLES);
|
|
2228
2412
|
const stale = await staleFrom(layout, previous, agents, ROLES);
|
|
@@ -2234,19 +2418,24 @@ async function setup(options = {}) {
|
|
|
2234
2418
|
const checks = [...daemon.checks, ...desired.checks, ...retained];
|
|
2235
2419
|
if (hasFailure(checks)) return failed("setup", checks);
|
|
2236
2420
|
return withSession(daemon.daemon, async (session) => {
|
|
2237
|
-
const
|
|
2421
|
+
const expectedPluginPath = claudeCarrierPluginDir(layout);
|
|
2422
|
+
const plan = await planRoom(session, desired, stale, expectedPluginPath);
|
|
2423
|
+
const { operations, liveProfiles } = plan;
|
|
2238
2424
|
const pending = operations.filter((operation) => operation.action !== "noop");
|
|
2239
|
-
const
|
|
2240
|
-
|
|
2425
|
+
const pluginPreconditions = desired.pluginPath === void 0 ? plan.pluginChecks : [pluginsEnabledCheck(await session.pluginsEnabled()), ...plan.pluginChecks];
|
|
2426
|
+
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2427
|
+
if (hasFailure(preApplyDiagnostics) || !options.apply) {
|
|
2241
2428
|
return {
|
|
2242
2429
|
command: "setup",
|
|
2243
|
-
outcome: pending.length > 0 ? "changes-planned" : "ok",
|
|
2430
|
+
outcome: hasFailure(preApplyDiagnostics) ? "failed" : pending.length > 0 ? "changes-planned" : "ok",
|
|
2244
2431
|
changed: false,
|
|
2245
2432
|
checks: preApplyDiagnostics,
|
|
2246
2433
|
operations
|
|
2247
2434
|
};
|
|
2248
2435
|
}
|
|
2249
|
-
|
|
2436
|
+
const markerPath = join11(layout.roomHome, MARKER);
|
|
2437
|
+
const markerEntries = desired.entries.filter((entry) => entry.path === markerPath);
|
|
2438
|
+
await applyEntries(desired.entries.filter((entry) => entry.path !== markerPath));
|
|
2250
2439
|
const ids = Object.keys(desired.providers);
|
|
2251
2440
|
await session.writeProviders(desired.providers);
|
|
2252
2441
|
if (stale.providerIds.length > 0) await session.removeProviders(stale.providerIds);
|
|
@@ -2256,24 +2445,27 @@ async function setup(options = {}) {
|
|
|
2256
2445
|
await session.writeProfiles(appliedProfiles);
|
|
2257
2446
|
}
|
|
2258
2447
|
await session.refresh(ids);
|
|
2448
|
+
const pluginChecks = await reconcilePlugin(session, desired, plan, pending, expectedPluginPath);
|
|
2449
|
+
await applyEntries(markerEntries);
|
|
2259
2450
|
const appliedMarker = {
|
|
2260
2451
|
version: package_default.version,
|
|
2261
2452
|
agents: [...agents],
|
|
2262
2453
|
roles: [...ROLES],
|
|
2263
2454
|
contract: contractDigest()
|
|
2264
2455
|
};
|
|
2456
|
+
const appliedChecks = [
|
|
2457
|
+
...checks,
|
|
2458
|
+
...pluginPreconditions,
|
|
2459
|
+
...pluginChecks,
|
|
2460
|
+
pass("room.applied", `Room ready at ${layout.roomHome} with ${String(ids.length)} Paseo providers.`),
|
|
2461
|
+
...contractProvenance(appliedMarker),
|
|
2462
|
+
...thinkingDiagnostics(appliedProfiles, desired.profiles)
|
|
2463
|
+
];
|
|
2265
2464
|
return {
|
|
2266
2465
|
command: "setup",
|
|
2267
|
-
outcome: "ok",
|
|
2466
|
+
outcome: hasFailure(appliedChecks) ? "failed" : "ok",
|
|
2268
2467
|
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
|
-
],
|
|
2468
|
+
checks: appliedChecks,
|
|
2277
2469
|
operations
|
|
2278
2470
|
};
|
|
2279
2471
|
}, sessionOptions(options));
|
|
@@ -2296,18 +2488,26 @@ async function verify(options = {}) {
|
|
|
2296
2488
|
const checks = [...daemon.checks, ...desired.checks];
|
|
2297
2489
|
if (hasFailure(checks)) return failed("verify", checks);
|
|
2298
2490
|
return withSession(daemon.daemon, async (session) => {
|
|
2299
|
-
const
|
|
2491
|
+
const expectedPluginPath = claudeCarrierPluginDir(layout);
|
|
2492
|
+
const plan = await planRoom(session, desired, NOTHING_STALE, expectedPluginPath);
|
|
2493
|
+
const { operations, liveProfiles } = plan;
|
|
2300
2494
|
const drifted = operations.filter((operation) => operation.action !== "noop");
|
|
2301
2495
|
const count = (kind) => drifted.filter((operation) => operation.kind === kind).length;
|
|
2302
2496
|
const providers = count("provider");
|
|
2303
2497
|
const profiles = count("profile");
|
|
2304
|
-
const driftedFiles = drifted.filter((operation) =>
|
|
2498
|
+
const driftedFiles = drifted.filter((operation) => !["provider", "profile", "plugin"].includes(operation.kind));
|
|
2305
2499
|
const files = driftedFiles.length;
|
|
2500
|
+
const pluginChecks = desired.pluginPath === void 0 ? [] : [
|
|
2501
|
+
pluginsEnabledCheck(await session.pluginsEnabled()),
|
|
2502
|
+
...plan.pluginChecks,
|
|
2503
|
+
pluginRuntimeCheck(plan.livePlugin, expectedPluginPath)
|
|
2504
|
+
];
|
|
2306
2505
|
const all = [
|
|
2307
2506
|
...checks,
|
|
2308
2507
|
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
2508
|
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
2509
|
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"),
|
|
2510
|
+
...pluginChecks,
|
|
2311
2511
|
// Diagnostics last: a warning never precedes the failure it might be mistaken for.
|
|
2312
2512
|
...contractProvenance(marker),
|
|
2313
2513
|
...thinkingDiagnostics(liveProfiles, desired.profiles)
|
|
@@ -2333,9 +2533,11 @@ async function remove(options = {}) {
|
|
|
2333
2533
|
if (invalid.length > 0) return failed("remove", invalid);
|
|
2334
2534
|
const ids = marker.agents.flatMap((agent) => marker.roles.map((role) => providerId(agent, role)));
|
|
2335
2535
|
const profiles = new Set(marker.agents.flatMap((agent) => marker.roles.map((role) => profileId(agent, role))));
|
|
2536
|
+
const hasClaude = marker.agents.includes("claude");
|
|
2336
2537
|
const operations = [
|
|
2337
2538
|
...ids.map((id) => ({ action: "remove", kind: "provider", target: id })),
|
|
2338
2539
|
...[...profiles].map((id) => ({ action: "remove", kind: "profile", target: id })),
|
|
2540
|
+
...hasClaude ? [{ action: "remove", kind: "plugin", target: CLAUDE_CARRIER_PLUGIN_ID }] : [],
|
|
2339
2541
|
{ action: "remove", kind: "dir", target: layout.roomHome }
|
|
2340
2542
|
];
|
|
2341
2543
|
const credentialWarning = warn(
|
|
@@ -2360,6 +2562,14 @@ async function remove(options = {}) {
|
|
|
2360
2562
|
}
|
|
2361
2563
|
try {
|
|
2362
2564
|
await withSession(daemon.daemon, async (session) => {
|
|
2565
|
+
if (hasClaude) {
|
|
2566
|
+
const plugin = (await session.listPlugins()).find((entry) => entry.id === CLAUDE_CARRIER_PLUGIN_ID);
|
|
2567
|
+
const expectedPath = claudeCarrierPluginDir(layout);
|
|
2568
|
+
if (plugin !== void 0 && plugin.path !== expectedPath) {
|
|
2569
|
+
throw new Error(`refusing to remove foreign plugin path ${plugin.path}`);
|
|
2570
|
+
}
|
|
2571
|
+
if (plugin !== void 0) await session.removePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2572
|
+
}
|
|
2363
2573
|
await session.removeProviders(ids);
|
|
2364
2574
|
const live = await session.readProfiles();
|
|
2365
2575
|
const kept = live.filter((entry) => !profiles.has(String(entry.id)));
|
|
@@ -2403,12 +2613,13 @@ var init_commands = __esm({
|
|
|
2403
2613
|
init_fsops();
|
|
2404
2614
|
init_layout();
|
|
2405
2615
|
init_paseo();
|
|
2616
|
+
init_plugin();
|
|
2406
2617
|
init_result();
|
|
2407
2618
|
init_instructions();
|
|
2408
2619
|
init_room();
|
|
2409
2620
|
init_roles();
|
|
2410
2621
|
AGENTS = { codex: codexAgent, claude: claudeAgent, pi: piAgent };
|
|
2411
|
-
NOTHING_STALE = { providerIds: [], profileIds: [], retainedDirectories: [] };
|
|
2622
|
+
NOTHING_STALE = { providerIds: [], profileIds: [], removePlugin: false, retainedDirectories: [] };
|
|
2412
2623
|
}
|
|
2413
2624
|
});
|
|
2414
2625
|
|