paseo-room 0.5.0 → 0.6.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 +56 -3
- package/dist/index.js +1745 -197
- package/dist/index.js.map +1 -1
- package/dist/plugin-assets/paseo-plugin.json +1 -1
- package/dist/prompts/contract/peer.md +12 -0
- package/dist/runtime-plugin/client/data.ts +67 -0
- package/dist/runtime-plugin/client/views.tsx +176 -0
- package/dist/runtime-plugin/index.client.tsx +21 -0
- package/dist/runtime-plugin/index.server.ts +53 -0
- package/dist/runtime-plugin/package.json +5 -0
- package/dist/runtime-plugin/paseo-plugin.json +4 -0
- package/dist/runtime-plugin/server/bridge/bridge.mjs +146 -0
- package/dist/runtime-plugin/server/brief.ts +39 -0
- package/dist/runtime-plugin/server/capabilities.ts +45 -0
- package/dist/runtime-plugin/server/context.ts +80 -0
- package/dist/runtime-plugin/server/contracts/actions.ts +39 -0
- package/dist/runtime-plugin/server/contracts/assignment.ts +51 -0
- package/dist/runtime-plugin/server/contracts/envelope.ts +26 -0
- package/dist/runtime-plugin/server/contracts/peer.ts +112 -0
- package/dist/runtime-plugin/server/controller.ts +558 -0
- package/dist/runtime-plugin/server/correlations.ts +157 -0
- package/dist/runtime-plugin/server/domain/acceptance.ts +69 -0
- package/dist/runtime-plugin/server/domain/authorize.ts +37 -0
- package/dist/runtime-plugin/server/domain/receipts.ts +73 -0
- package/dist/runtime-plugin/server/domain/state.ts +413 -0
- package/dist/runtime-plugin/server/domain/validate.ts +69 -0
- package/dist/runtime-plugin/server/domain/views.ts +233 -0
- package/dist/runtime-plugin/server/events/schema.ts +214 -0
- package/dist/runtime-plugin/server/gate.ts +233 -0
- package/dist/runtime-plugin/server/generated/location.ts +11 -0
- package/dist/runtime-plugin/server/git.ts +158 -0
- package/dist/runtime-plugin/server/handlers/actions.ts +130 -0
- package/dist/runtime-plugin/server/handlers/peer.ts +183 -0
- package/dist/runtime-plugin/server/handlers/turns.ts +113 -0
- package/dist/runtime-plugin/server/hooks.ts +73 -0
- package/dist/runtime-plugin/server/lifecycle.ts +60 -0
- package/dist/runtime-plugin/server/notices.ts +103 -0
- package/dist/runtime-plugin/server/ownership.ts +63 -0
- package/dist/runtime-plugin/server/paseo-port.ts +194 -0
- package/dist/runtime-plugin/server/recognition.ts +80 -0
- package/dist/runtime-plugin/server/recovery.ts +166 -0
- package/dist/runtime-plugin/server/rpc.ts +169 -0
- package/dist/runtime-plugin/server/spool.ts +143 -0
- package/dist/runtime-plugin/server/store/project.ts +228 -0
- package/dist/runtime-plugin/server/store/publish.ts +142 -0
- package/dist/runtime-plugin/server/tools.ts +91 -0
- package/dist/runtime-plugin/shared/identity.ts +16 -0
- package/dist/runtime-plugin/shared/limits.ts +40 -0
- package/dist/runtime-plugin/shared/manifest.ts +51 -0
- package/dist/runtime-plugin/shared/policy.ts +61 -0
- package/dist/runtime-plugin/shared/rpc-contracts.ts +66 -0
- package/dist/runtime-plugin/shared/rpc.ts +34 -0
- package/dist/runtime-plugin/tsconfig.json +22 -0
- package/package.json +9 -4
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.6.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",
|
|
@@ -45,17 +45,17 @@ var init_package = __esm({
|
|
|
45
45
|
"dist/"
|
|
46
46
|
],
|
|
47
47
|
scripts: {
|
|
48
|
-
typecheck: "tsc --noEmit",
|
|
48
|
+
typecheck: "tsc --noEmit && tsc -p src/runtime-plugin",
|
|
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'; for (const [source, target] of [['src/room/prompts', 'dist/prompts'], ['src/room/skills', 'dist/skills'], ['src/plugin-assets', 'dist/plugin-assets']]) { rmSync(target, { recursive: true, force: true }); cpSync(source, target, { recursive: true }); }"`,
|
|
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/room/skills', 'dist/skills'], ['src/plugin-assets', 'dist/plugin-assets'], ['src/runtime-plugin', 'dist/runtime-plugin']]) { 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
|
},
|
|
56
56
|
dependencies: {
|
|
57
57
|
"@clack/prompts": "^1.8.0",
|
|
58
|
-
"@getpaseo/client": "0.
|
|
58
|
+
"@getpaseo/client": "0.9.1",
|
|
59
59
|
commander: "^14.0.3",
|
|
60
60
|
semver: "^7.8.5",
|
|
61
61
|
"smol-toml": "^1.8.0",
|
|
@@ -63,9 +63,14 @@ var init_package = __esm({
|
|
|
63
63
|
},
|
|
64
64
|
devDependencies: {
|
|
65
65
|
"@eslint/js": "^10.0.1",
|
|
66
|
+
"@getpaseo/plugin": "0.9.1",
|
|
67
|
+
"@getpaseo/protocol": "0.9.1",
|
|
66
68
|
"@types/node": "^22.0.0",
|
|
69
|
+
"@types/react": "19.2.2",
|
|
67
70
|
"@types/semver": "^7.7.0",
|
|
68
71
|
eslint: "^10.10.0",
|
|
72
|
+
react: "19.1.0",
|
|
73
|
+
"react-native": "0.81.5",
|
|
69
74
|
tsup: "^8.5.1",
|
|
70
75
|
typescript: "~5.9.3",
|
|
71
76
|
"typescript-eslint": "^8.70.0",
|
|
@@ -76,14 +81,14 @@ var init_package = __esm({
|
|
|
76
81
|
});
|
|
77
82
|
|
|
78
83
|
// src/result.ts
|
|
79
|
-
function pass(
|
|
80
|
-
return { id, status: "pass", message };
|
|
84
|
+
function pass(id2, message) {
|
|
85
|
+
return { id: id2, status: "pass", message };
|
|
81
86
|
}
|
|
82
|
-
function warn(
|
|
83
|
-
return { id, status: "warn", message, ...fix === void 0 ? {} : { fix } };
|
|
87
|
+
function warn(id2, message, fix) {
|
|
88
|
+
return { id: id2, status: "warn", message, ...fix === void 0 ? {} : { fix } };
|
|
84
89
|
}
|
|
85
|
-
function fail(
|
|
86
|
-
return { id, status: "fail", message, fix };
|
|
90
|
+
function fail(id2, message, fix) {
|
|
91
|
+
return { id: id2, status: "fail", message, fix };
|
|
87
92
|
}
|
|
88
93
|
function failed(command, checks) {
|
|
89
94
|
return { command, outcome: "failed", changed: false, checks, operations: [] };
|
|
@@ -158,9 +163,9 @@ function preservedCredentialCheck(input) {
|
|
|
158
163
|
function presentNames(names, supported) {
|
|
159
164
|
return supported.filter((name) => names.has(name));
|
|
160
165
|
}
|
|
161
|
-
function ambientNamesCheck(
|
|
166
|
+
function ambientNamesCheck(id2, agent, role, names) {
|
|
162
167
|
return warn(
|
|
163
|
-
`${
|
|
168
|
+
`${id2}.ambient-auth-unverifiable`,
|
|
164
169
|
`${agent} ${role} auth: ambient auth unverifiable; ${names.join(", ")} ${names.length === 1 ? "is" : "are"} present by name in the setup process. paseo-room does not copy credential values into providers, so availability to the Paseo-launched role, token validity, and freshness were not checked.`
|
|
165
170
|
);
|
|
166
171
|
}
|
|
@@ -214,13 +219,13 @@ function layoutChecks(layout, agents) {
|
|
|
214
219
|
"Point --room-home at a dedicated directory, by default ~/.paseo-room."
|
|
215
220
|
));
|
|
216
221
|
}
|
|
217
|
-
for (const
|
|
218
|
-
const home = layout.agentHome[
|
|
222
|
+
for (const id2 of agents) {
|
|
223
|
+
const home = layout.agentHome[id2];
|
|
219
224
|
if (contains(layout.roomHome, home) || contains(home, layout.roomHome)) {
|
|
220
225
|
checks.push(fail(
|
|
221
|
-
`${
|
|
222
|
-
`The ${
|
|
223
|
-
`Pass --${
|
|
226
|
+
`${id2}.home`,
|
|
227
|
+
`The ${id2} home ${home} overlaps the room home ${layout.roomHome}.`,
|
|
228
|
+
`Pass --${id2}-home pointing at your own ${id2} configuration (this happens when setup runs inside a room seat).`
|
|
224
229
|
));
|
|
225
230
|
}
|
|
226
231
|
}
|
|
@@ -444,13 +449,14 @@ function providerLabel(agent, role) {
|
|
|
444
449
|
const title2 = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
445
450
|
return `${title2(agent)} ${title2(role)}`;
|
|
446
451
|
}
|
|
447
|
-
var ROLES, AGENT_IDS, ROLE_PASEO_TOOLS, ROLE_THINKING, DELEGATING_THINKING, ROLE_ICON, ROLE_COLOR, ROLE_NOTES;
|
|
452
|
+
var ROLES, AGENT_IDS, ROLE_PASEO_TOOLS, ROLE_PEER_REPORTING, ROLE_THINKING, DELEGATING_THINKING, ROLE_ICON, ROLE_COLOR, ROLE_NOTES;
|
|
448
453
|
var init_roles = __esm({
|
|
449
454
|
"src/roles.ts"() {
|
|
450
455
|
"use strict";
|
|
451
456
|
ROLES = ["supervisor", "lead", "peer"];
|
|
452
457
|
AGENT_IDS = ["codex", "claude", "pi"];
|
|
453
458
|
ROLE_PASEO_TOOLS = { supervisor: true, lead: true, peer: false };
|
|
459
|
+
ROLE_PEER_REPORTING = { supervisor: false, lead: false, peer: true };
|
|
454
460
|
ROLE_THINKING = { supervisor: "low", lead: "high", peer: "high" };
|
|
455
461
|
DELEGATING_THINKING = ["ultra", "ultracode"];
|
|
456
462
|
ROLE_ICON = { supervisor: "eye", lead: "compass", peer: "code" };
|
|
@@ -466,14 +472,15 @@ var init_roles = __esm({
|
|
|
466
472
|
// src/room.ts
|
|
467
473
|
import { join as join3 } from "path";
|
|
468
474
|
import { z } from "zod";
|
|
469
|
-
function renderMarker(version, agents, roles, contract, claudeMemoryContract) {
|
|
475
|
+
function renderMarker(version, agents, roles, contract, claudeMemoryContract, runtime) {
|
|
470
476
|
return JSON.stringify({
|
|
471
477
|
version,
|
|
472
478
|
agents: [...agents],
|
|
473
479
|
roles: [...roles],
|
|
474
480
|
...contract === void 0 ? {} : { contract },
|
|
475
481
|
// Written only when it differs from the default, so an unchanged room's marker is unchanged.
|
|
476
|
-
...claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
482
|
+
...claudeMemoryContract === false ? { claudeMemoryContract: false } : {},
|
|
483
|
+
...runtime === void 0 ? {} : { runtime }
|
|
477
484
|
}, null, 2) + "\n";
|
|
478
485
|
}
|
|
479
486
|
async function readMarker(layout) {
|
|
@@ -507,7 +514,12 @@ var init_room = __esm({
|
|
|
507
514
|
* leaving the plugin as its only Claude carrier. Optional and defaulted to true so a room
|
|
508
515
|
* written before the option existed keeps parsing and keeps its current behaviour.
|
|
509
516
|
*/
|
|
510
|
-
claudeMemoryContract: z.boolean().optional()
|
|
517
|
+
claudeMemoryContract: z.boolean().optional(),
|
|
518
|
+
/**
|
|
519
|
+
* Present only when this room opted in to runtime coordination. Missing means disabled, so a
|
|
520
|
+
* marker written before runtime existed keeps parsing and keeps its behaviour.
|
|
521
|
+
*/
|
|
522
|
+
runtime: z.strictObject({ enabled: z.literal(true), generation: z.string().min(1), schema: z.literal(1) }).optional()
|
|
511
523
|
});
|
|
512
524
|
}
|
|
513
525
|
});
|
|
@@ -677,12 +689,12 @@ async function loginRole(agentValue, roleValue, options, output) {
|
|
|
677
689
|
}
|
|
678
690
|
const childEnv = { ...env, ...invocation.env };
|
|
679
691
|
const launch = options.spawn ?? spawnLogin;
|
|
680
|
-
return new Promise((
|
|
692
|
+
return new Promise((resolve5) => {
|
|
681
693
|
let settled = false;
|
|
682
|
-
const finish = (
|
|
694
|
+
const finish = (code2) => {
|
|
683
695
|
if (settled) return;
|
|
684
696
|
settled = true;
|
|
685
|
-
|
|
697
|
+
resolve5(code2);
|
|
686
698
|
};
|
|
687
699
|
let child;
|
|
688
700
|
try {
|
|
@@ -704,9 +716,9 @@ async function loginRole(agentValue, roleValue, options, output) {
|
|
|
704
716
|
`);
|
|
705
717
|
finish(1);
|
|
706
718
|
});
|
|
707
|
-
child.once("close", (
|
|
708
|
-
if (
|
|
709
|
-
finish(
|
|
719
|
+
child.once("close", (code2, signal) => {
|
|
720
|
+
if (code2 !== null) {
|
|
721
|
+
finish(code2);
|
|
710
722
|
return;
|
|
711
723
|
}
|
|
712
724
|
if (signal) {
|
|
@@ -962,11 +974,11 @@ function detectPaseoServers(servers) {
|
|
|
962
974
|
function evidence(match) {
|
|
963
975
|
return match.field === "name" ? `${match.server} (server name)` : `${match.server} (${match.field}: ${match.value})`;
|
|
964
976
|
}
|
|
965
|
-
function paseoMcpCheck(
|
|
977
|
+
function paseoMcpCheck(id2, path, servers) {
|
|
966
978
|
const matches = detectPaseoServers(servers);
|
|
967
979
|
if (matches.length === 0) return void 0;
|
|
968
980
|
return fail(
|
|
969
|
-
|
|
981
|
+
id2,
|
|
970
982
|
`${path} declares MCP servers that are recognizably Paseo-related: ${matches.map(evidence).join("; ")}.`,
|
|
971
983
|
`Remove or rename those servers in ${path} so no room seat reaches Paseo outside its own seat, then run setup again. paseo-room never edits your MCP configuration, and this name check is a bounded heuristic rather than a sandbox.`
|
|
972
984
|
);
|
|
@@ -1202,22 +1214,22 @@ async function claudeCredentialDiagnostic(layout, role, platform = process.platf
|
|
|
1202
1214
|
const login = roleCommand(roleEnvironment, binary, ["auth", "login"]);
|
|
1203
1215
|
const status = roleCommand(roleEnvironment, binary, ["auth", "status"]);
|
|
1204
1216
|
const state = await inspectCredentialPath(path);
|
|
1205
|
-
const
|
|
1217
|
+
const id2 = `claude.auth.${role}`;
|
|
1206
1218
|
if (state.kind !== "missing") {
|
|
1207
|
-
return { path, checks: [preservedCredentialCheck({ id, agent: "Claude", role, path, state, login, status })] };
|
|
1219
|
+
return { path, checks: [preservedCredentialCheck({ id: id2, agent: "Claude", role, path, state, login, status })] };
|
|
1208
1220
|
}
|
|
1209
1221
|
const ambient = presentNames(layout.envNames, AUTH_ENV);
|
|
1210
|
-
if (ambient.length > 0) return { path, checks: [ambientNamesCheck(
|
|
1222
|
+
if (ambient.length > 0) return { path, checks: [ambientNamesCheck(id2, "Claude", role, ambient)] };
|
|
1211
1223
|
if (platform === "darwin") {
|
|
1212
1224
|
return { path, checks: [{
|
|
1213
|
-
id: `${
|
|
1225
|
+
id: `${id2}.native-keyring-unverifiable`,
|
|
1214
1226
|
status: "warn",
|
|
1215
1227
|
message: `Claude ${role} auth: native-keyring unverifiable; the current runtime's secure-storage location is pinned to this role home, but no Keychain entry was queried and older runtime behavior was not assumed. Token validity and freshness were not checked.`,
|
|
1216
1228
|
fix: `Authenticate this role with: ${login}. If that subcommand is unavailable, launch ${roleCommand(roleEnvironment, binary)} and run /login. Check status with: ${status}.`
|
|
1217
1229
|
}] };
|
|
1218
1230
|
}
|
|
1219
1231
|
return { path, checks: [{
|
|
1220
|
-
id: `${
|
|
1232
|
+
id: `${id2}.login-required`,
|
|
1221
1233
|
status: "warn",
|
|
1222
1234
|
message: `Claude ${role} auth: login-required; no role-owned .credentials.json or supported ambient auth environment name was detected. Authentication was not attempted.`,
|
|
1223
1235
|
fix: `Authenticate this role with: ${login}. If that subcommand is unavailable, launch ${roleCommand(roleEnvironment, binary)} and run /login. Check status with: ${status}.`
|
|
@@ -1441,14 +1453,14 @@ async function credentialDiagnostic(layout, role, store, binary) {
|
|
|
1441
1453
|
const login = roleCommand({ CODEX_HOME: home }, binary, ["login"]);
|
|
1442
1454
|
const status = roleCommand({ CODEX_HOME: home }, binary, ["login", "status"]);
|
|
1443
1455
|
const state = await inspectCredentialPath(path);
|
|
1444
|
-
const
|
|
1456
|
+
const id2 = `codex.auth.${role}`;
|
|
1445
1457
|
const checks = [];
|
|
1446
1458
|
if (state.kind !== "missing") {
|
|
1447
|
-
checks.push(preservedCredentialCheck({ id, agent: "Codex", role, path, state, login, status }));
|
|
1459
|
+
checks.push(preservedCredentialCheck({ id: id2, agent: "Codex", role, path, state, login, status }));
|
|
1448
1460
|
} else {
|
|
1449
1461
|
if (store === "keyring" || store === "auto") {
|
|
1450
1462
|
checks.push({
|
|
1451
|
-
id: `${
|
|
1463
|
+
id: `${id2}.native-keyring-unverifiable`,
|
|
1452
1464
|
status: "warn",
|
|
1453
1465
|
message: `Codex ${role} auth: native-keyring unverifiable; cli_auth_credentials_store is ${store}, and no keyring was queried. Token validity and freshness were not checked.`,
|
|
1454
1466
|
fix: `Authenticate this role with: ${login}. Check status with: ${status}.`
|
|
@@ -1457,7 +1469,7 @@ async function credentialDiagnostic(layout, role, store, binary) {
|
|
|
1457
1469
|
const apiKey = layout.envNames.has("OPENAI_API_KEY") ? " OPENAI_API_KEY is present only in the setup process; Codex has not stored it for this role and paseo-room does not copy it into the provider." : "";
|
|
1458
1470
|
const detail = store === "ephemeral" ? "cli_auth_credentials_store is ephemeral, so no persistent credential file is expected" : `no role-owned auth.json exists${store === "file" ? " for the configured file store" : ""}`;
|
|
1459
1471
|
checks.push({
|
|
1460
|
-
id: `${
|
|
1472
|
+
id: `${id2}.login-required`,
|
|
1461
1473
|
status: "warn",
|
|
1462
1474
|
message: `Codex ${role} auth: login-required; ${detail}.${apiKey} Authentication was not attempted.`,
|
|
1463
1475
|
fix: `Authenticate this role with: ${login}. Check status with: ${status}.`
|
|
@@ -1466,7 +1478,7 @@ async function credentialDiagnostic(layout, role, store, binary) {
|
|
|
1466
1478
|
}
|
|
1467
1479
|
if (state.kind !== "missing" && (store === "keyring" || store === "auto")) {
|
|
1468
1480
|
checks.push({
|
|
1469
|
-
id: `${
|
|
1481
|
+
id: `${id2}.native-keyring-unverifiable`,
|
|
1470
1482
|
status: "warn",
|
|
1471
1483
|
message: `Codex ${role} auth: native-keyring unverifiable; cli_auth_credentials_store is ${store}, and the preserved auth.json does not prove what the runtime will use. No keyring was queried.`,
|
|
1472
1484
|
fix: `Check status without exposing credentials: ${status}.`
|
|
@@ -1682,10 +1694,10 @@ function runProbeProcess(executable, adapterEntry, env, options, cwd) {
|
|
|
1682
1694
|
clearTimeout(timer);
|
|
1683
1695
|
resolveResult(result);
|
|
1684
1696
|
};
|
|
1685
|
-
const abort = (
|
|
1697
|
+
const abort = (reason3) => {
|
|
1686
1698
|
if (settled) return;
|
|
1687
1699
|
terminateProbe(child);
|
|
1688
|
-
finish({ ok: false, stdout: Buffer.concat(stdout).toString("utf8"), reason });
|
|
1700
|
+
finish({ ok: false, stdout: Buffer.concat(stdout).toString("utf8"), reason: reason3 });
|
|
1689
1701
|
};
|
|
1690
1702
|
const capture = (chunk, keep) => {
|
|
1691
1703
|
if (settled) return;
|
|
@@ -1714,9 +1726,9 @@ function runProbeProcess(executable, adapterEntry, env, options, cwd) {
|
|
|
1714
1726
|
child.on("error", () => {
|
|
1715
1727
|
abort("failure");
|
|
1716
1728
|
});
|
|
1717
|
-
child.on("close", (
|
|
1729
|
+
child.on("close", (code2) => {
|
|
1718
1730
|
const output = Buffer.concat(stdout).toString("utf8");
|
|
1719
|
-
finish(
|
|
1731
|
+
finish(code2 === 0 ? { ok: true, stdout: output } : { ok: false, stdout: output, reason: "failure" });
|
|
1720
1732
|
});
|
|
1721
1733
|
child.stdin.on("error", () => {
|
|
1722
1734
|
});
|
|
@@ -1805,12 +1817,12 @@ async function piCredentialDiagnostic(layout, role, binary = "pi") {
|
|
|
1805
1817
|
const path = join10(home, "auth.json");
|
|
1806
1818
|
const launch = piLoginCommand(home, binary);
|
|
1807
1819
|
const state = await inspectCredentialPath(path);
|
|
1808
|
-
const
|
|
1820
|
+
const id2 = `pi.auth.${role}`;
|
|
1809
1821
|
if (state.kind !== "missing") {
|
|
1810
1822
|
return {
|
|
1811
1823
|
path,
|
|
1812
1824
|
checks: [preservedCredentialCheck({
|
|
1813
|
-
id,
|
|
1825
|
+
id: id2,
|
|
1814
1826
|
agent: "Pi",
|
|
1815
1827
|
role,
|
|
1816
1828
|
path,
|
|
@@ -1820,9 +1832,9 @@ async function piCredentialDiagnostic(layout, role, binary = "pi") {
|
|
|
1820
1832
|
};
|
|
1821
1833
|
}
|
|
1822
1834
|
const configured = presentNames(layout.envNames, AUTH_ENV2);
|
|
1823
|
-
if (configured.length > 0) return { path, checks: [ambientNamesCheck(
|
|
1835
|
+
if (configured.length > 0) return { path, checks: [ambientNamesCheck(id2, "Pi", role, configured)] };
|
|
1824
1836
|
return { path, checks: [{
|
|
1825
|
-
id: `${
|
|
1837
|
+
id: `${id2}.login-required`,
|
|
1826
1838
|
status: "warn",
|
|
1827
1839
|
message: `Pi ${role} auth: login-required; no role-owned auth.json or known provider environment name was detected. Ambient provider auth may exist, but it was not inspected or validated.`,
|
|
1828
1840
|
fix: `Start a minimal, non-room-equivalent interactive login session with: ${launch}. Run /login, then exit Pi.`
|
|
@@ -1994,8 +2006,8 @@ var init_pi = __esm({
|
|
|
1994
2006
|
// src/paseo.ts
|
|
1995
2007
|
import { gte, valid } from "semver";
|
|
1996
2008
|
import { z as z2 } from "zod";
|
|
1997
|
-
function minimumPaseoVersion(agents) {
|
|
1998
|
-
return agents.includes("pi") || agents.includes("claude") ? PI_MINIMUM_VERSION : MINIMUM_VERSION;
|
|
2009
|
+
function minimumPaseoVersion(agents, runtime = false) {
|
|
2010
|
+
return runtime || agents.includes("pi") || agents.includes("claude") ? PI_MINIMUM_VERSION : MINIMUM_VERSION;
|
|
1999
2011
|
}
|
|
2000
2012
|
function normalizeUrl(listen) {
|
|
2001
2013
|
const match = /^(?:ws:\/\/)?([^:/]+|\[[^\]]+\]):(\d+)$/.exec(listen.trim());
|
|
@@ -2004,7 +2016,7 @@ function normalizeUrl(listen) {
|
|
|
2004
2016
|
const local = ["localhost", "127.0.0.1", "0.0.0.0", "[::1]", "[::]"].includes(host) ? "127.0.0.1" : host;
|
|
2005
2017
|
return `ws://${local}:${port}`;
|
|
2006
2018
|
}
|
|
2007
|
-
function assessStatus(raw, minimumVersion = MINIMUM_VERSION) {
|
|
2019
|
+
function assessStatus(raw, minimumVersion = MINIMUM_VERSION, cliVersion) {
|
|
2008
2020
|
const parsed = statusSchema.safeParse(raw);
|
|
2009
2021
|
if (!parsed.success) {
|
|
2010
2022
|
return { checks: [fail("paseo.status", "Paseo status output was not understood.", "Upgrade Paseo, then run: paseo daemon status --json")] };
|
|
@@ -2013,8 +2025,8 @@ function assessStatus(raw, minimumVersion = MINIMUM_VERSION) {
|
|
|
2013
2025
|
if (status.localDaemon !== "running") {
|
|
2014
2026
|
return { checks: [fail("paseo.daemon", `Paseo daemon is ${status.localDaemon}.`, "Start it with: paseo daemon start")] };
|
|
2015
2027
|
}
|
|
2016
|
-
const cli = valid(status.cliVersion);
|
|
2017
|
-
const daemon = status.daemonVersion
|
|
2028
|
+
const cli = valid(status.cliVersion ?? cliVersion);
|
|
2029
|
+
const daemon = status.daemonVersion == null ? null : valid(status.daemonVersion);
|
|
2018
2030
|
if (!cli || !daemon) {
|
|
2019
2031
|
return { checks: [fail("paseo.version", "Paseo did not report a usable version.", "Upgrade Paseo to a release that reports semver versions.")] };
|
|
2020
2032
|
}
|
|
@@ -2044,11 +2056,16 @@ async function checkDaemon(layout, env = process.env, minimumVersion = MINIMUM_V
|
|
|
2044
2056
|
if (!output.ok) {
|
|
2045
2057
|
return { checks: [fail("paseo.status", "Could not read Paseo daemon status.", "Run: paseo daemon status --json")] };
|
|
2046
2058
|
}
|
|
2059
|
+
let status;
|
|
2047
2060
|
try {
|
|
2048
|
-
|
|
2061
|
+
status = JSON.parse(output.stdout);
|
|
2049
2062
|
} catch {
|
|
2050
2063
|
return assessStatus(void 0, minimumVersion);
|
|
2051
2064
|
}
|
|
2065
|
+
const reported = status?.cliVersion;
|
|
2066
|
+
if (typeof reported === "string") return assessStatus(status, minimumVersion);
|
|
2067
|
+
const version = await probe(binary, ["--version"], { HOME: layout.home, PATH: layout.searchPath });
|
|
2068
|
+
return assessStatus(status, minimumVersion, version.ok ? version.stdout.trim() : void 0);
|
|
2052
2069
|
}
|
|
2053
2070
|
async function withSession(daemon, run2, options = {}) {
|
|
2054
2071
|
const quiet = { debug() {
|
|
@@ -2072,7 +2089,13 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
2072
2089
|
import("@getpaseo/client"),
|
|
2073
2090
|
import("@getpaseo/client/internal/daemon-client")
|
|
2074
2091
|
]);
|
|
2075
|
-
const
|
|
2092
|
+
const { capabilities, ...config } = clientConfig;
|
|
2093
|
+
const raw = new DaemonClient({
|
|
2094
|
+
...config,
|
|
2095
|
+
...capabilities === void 0 ? {} : { capabilities },
|
|
2096
|
+
clientId: `paseo-room-${String(process.pid)}`,
|
|
2097
|
+
clientType: "cli"
|
|
2098
|
+
});
|
|
2076
2099
|
client = {
|
|
2077
2100
|
...createPaseoApi(raw),
|
|
2078
2101
|
connect: () => raw.connect(),
|
|
@@ -2082,10 +2105,10 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
2082
2105
|
},
|
|
2083
2106
|
getConnectionState: () => raw.getConnectionState(),
|
|
2084
2107
|
listPlugins: () => raw.listPlugins(),
|
|
2085
|
-
installDirectoryPlugin: (path,
|
|
2086
|
-
reloadPlugin: (
|
|
2087
|
-
enablePlugin: (
|
|
2088
|
-
removePlugin: (
|
|
2108
|
+
installDirectoryPlugin: (path, id2) => raw.installDirectoryPlugin(path, id2),
|
|
2109
|
+
reloadPlugin: (id2) => raw.reloadPlugin(id2),
|
|
2110
|
+
enablePlugin: (id2) => raw.enablePlugin(id2),
|
|
2111
|
+
removePlugin: (id2) => raw.removePlugin(id2)
|
|
2089
2112
|
};
|
|
2090
2113
|
}
|
|
2091
2114
|
try {
|
|
@@ -2115,17 +2138,17 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
2115
2138
|
async listPlugins() {
|
|
2116
2139
|
return await client.listPlugins();
|
|
2117
2140
|
},
|
|
2118
|
-
async installPlugin(path,
|
|
2119
|
-
return await client.installDirectoryPlugin(path,
|
|
2141
|
+
async installPlugin(path, id2) {
|
|
2142
|
+
return await client.installDirectoryPlugin(path, id2);
|
|
2120
2143
|
},
|
|
2121
|
-
async reloadPlugin(
|
|
2122
|
-
return await client.reloadPlugin(
|
|
2144
|
+
async reloadPlugin(id2) {
|
|
2145
|
+
return await client.reloadPlugin(id2);
|
|
2123
2146
|
},
|
|
2124
|
-
async enablePlugin(
|
|
2125
|
-
return await client.enablePlugin(
|
|
2147
|
+
async enablePlugin(id2) {
|
|
2148
|
+
return await client.enablePlugin(id2);
|
|
2126
2149
|
},
|
|
2127
|
-
async removePlugin(
|
|
2128
|
-
await client.removePlugin(
|
|
2150
|
+
async removePlugin(id2) {
|
|
2151
|
+
await client.removePlugin(id2);
|
|
2129
2152
|
},
|
|
2130
2153
|
async refresh(ids) {
|
|
2131
2154
|
await client.providers.refresh({ providers: [...ids] });
|
|
@@ -2185,9 +2208,9 @@ var init_paseo = __esm({
|
|
|
2185
2208
|
PI_MINIMUM_VERSION = "0.8.0";
|
|
2186
2209
|
statusSchema = z2.object({
|
|
2187
2210
|
listen: z2.string().min(1),
|
|
2188
|
-
localDaemon: z2.
|
|
2189
|
-
cliVersion: z2.string(),
|
|
2190
|
-
daemonVersion: z2.string().
|
|
2211
|
+
localDaemon: z2.string().min(1),
|
|
2212
|
+
cliVersion: z2.string().optional(),
|
|
2213
|
+
daemonVersion: z2.string().nullish()
|
|
2191
2214
|
});
|
|
2192
2215
|
configSchema = z2.object({
|
|
2193
2216
|
config: z2.object({
|
|
@@ -2215,8 +2238,8 @@ function typescriptTemplateLiteral(value) {
|
|
|
2215
2238
|
}
|
|
2216
2239
|
function renderClaudeCarrierContract(roles) {
|
|
2217
2240
|
const entries = roles.map((role) => {
|
|
2218
|
-
const
|
|
2219
|
-
return ` ${
|
|
2241
|
+
const id2 = JSON.stringify(providerId("claude", role));
|
|
2242
|
+
return ` ${id2}: ${typescriptTemplateLiteral(renderInstructions(role))},`;
|
|
2220
2243
|
});
|
|
2221
2244
|
return [
|
|
2222
2245
|
"/** Generated by paseo-room. The Markdown prompt assets remain authoritative. */",
|
|
@@ -2236,13 +2259,14 @@ function claudeCarrierEntries(layout, roles) {
|
|
|
2236
2259
|
{ kind: "file", path: join11(root, "server", "contract.ts"), content: renderClaudeCarrierContract(roles) }
|
|
2237
2260
|
];
|
|
2238
2261
|
}
|
|
2239
|
-
var CLAUDE_CARRIER_PLUGIN_ID, STATIC_ASSETS;
|
|
2262
|
+
var CLAUDE_CARRIER_PLUGIN_ID, CLAUDE_CARRIER_PASEO_RANGE, STATIC_ASSETS;
|
|
2240
2263
|
var init_plugin = __esm({
|
|
2241
2264
|
"src/plugin.ts"() {
|
|
2242
2265
|
"use strict";
|
|
2243
2266
|
init_roles();
|
|
2244
2267
|
init_instructions();
|
|
2245
2268
|
CLAUDE_CARRIER_PLUGIN_ID = "paseo-room-claude-carrier";
|
|
2269
|
+
CLAUDE_CARRIER_PASEO_RANGE = ">=0.8.0 <0.10.0";
|
|
2246
2270
|
STATIC_ASSETS = [
|
|
2247
2271
|
"paseo-plugin.json",
|
|
2248
2272
|
"package.json",
|
|
@@ -2253,9 +2277,1312 @@ var init_plugin = __esm({
|
|
|
2253
2277
|
}
|
|
2254
2278
|
});
|
|
2255
2279
|
|
|
2280
|
+
// src/runtime-plugin/shared/policy.ts
|
|
2281
|
+
function runtimeRolePolicy(role, peerReportingEligible) {
|
|
2282
|
+
if (role === "supervisor") return { capabilities: [...SUPERVISOR_OPERATIONS] };
|
|
2283
|
+
if (role === "lead") return { capabilities: [...LEAD_OPERATIONS] };
|
|
2284
|
+
return peerReportingEligible ? { capabilities: [...PEER_REPORTING_TOOLS], peerReporting: PEER_REPORTING_DECLARATION } : { capabilities: [] };
|
|
2285
|
+
}
|
|
2286
|
+
var RUNTIME_ROLES, RUNTIME_AGENTS, SUPERVISOR_OPERATIONS, LEAD_OPERATIONS, PEER_REPORTING_TOOLS, RUNTIME_CAPABILITIES, PEER_REPORTING_DECLARATION;
|
|
2287
|
+
var init_policy = __esm({
|
|
2288
|
+
"src/runtime-plugin/shared/policy.ts"() {
|
|
2289
|
+
"use strict";
|
|
2290
|
+
RUNTIME_ROLES = ["supervisor", "lead", "peer"];
|
|
2291
|
+
RUNTIME_AGENTS = ["codex", "claude", "pi"];
|
|
2292
|
+
SUPERVISOR_OPERATIONS = ["room_status", "runtime_findings", "message_lead"];
|
|
2293
|
+
LEAD_OPERATIONS = [
|
|
2294
|
+
"assignment_create",
|
|
2295
|
+
"assignment_dispatch",
|
|
2296
|
+
"assignment_answer",
|
|
2297
|
+
"assignment_rework",
|
|
2298
|
+
"assignment_accept",
|
|
2299
|
+
"assignment_reject",
|
|
2300
|
+
"assignment_abandon",
|
|
2301
|
+
"assignment_close",
|
|
2302
|
+
"assignment_status",
|
|
2303
|
+
"gate_run"
|
|
2304
|
+
];
|
|
2305
|
+
PEER_REPORTING_TOOLS = ["ask", "handoff"];
|
|
2306
|
+
RUNTIME_CAPABILITIES = [
|
|
2307
|
+
...SUPERVISOR_OPERATIONS,
|
|
2308
|
+
...LEAD_OPERATIONS,
|
|
2309
|
+
...PEER_REPORTING_TOOLS
|
|
2310
|
+
];
|
|
2311
|
+
PEER_REPORTING_DECLARATION = {
|
|
2312
|
+
protocol: 1,
|
|
2313
|
+
tools: ["ask", "handoff"],
|
|
2314
|
+
qualifiedVia: "exact-room-provider"
|
|
2315
|
+
};
|
|
2316
|
+
}
|
|
2317
|
+
});
|
|
2318
|
+
|
|
2319
|
+
// src/runtime-plugin/shared/manifest.ts
|
|
2320
|
+
import { z as z3 } from "zod";
|
|
2321
|
+
var MANIFEST_FILE, generation, peerReportingSchema, providerSchema, providerId2, runtimeRoomManifestSchema;
|
|
2322
|
+
var init_manifest = __esm({
|
|
2323
|
+
"src/runtime-plugin/shared/manifest.ts"() {
|
|
2324
|
+
"use strict";
|
|
2325
|
+
init_policy();
|
|
2326
|
+
MANIFEST_FILE = "generated/room-manifest.json";
|
|
2327
|
+
generation = z3.string().min(1).max(256);
|
|
2328
|
+
peerReportingSchema = z3.strictObject({
|
|
2329
|
+
protocol: z3.literal(1),
|
|
2330
|
+
tools: z3.tuple([z3.literal(PEER_REPORTING_TOOLS[0]), z3.literal(PEER_REPORTING_TOOLS[1])]),
|
|
2331
|
+
qualifiedVia: z3.literal("exact-room-provider")
|
|
2332
|
+
});
|
|
2333
|
+
providerSchema = z3.strictObject({
|
|
2334
|
+
agent: z3.enum(RUNTIME_AGENTS),
|
|
2335
|
+
role: z3.enum(RUNTIME_ROLES),
|
|
2336
|
+
capabilities: z3.array(z3.enum(RUNTIME_CAPABILITIES)),
|
|
2337
|
+
peerReporting: peerReportingSchema.optional()
|
|
2338
|
+
}).superRefine((entry, context) => {
|
|
2339
|
+
const eligible = entry.role === "peer" && entry.peerReporting !== void 0;
|
|
2340
|
+
const expected = runtimeRolePolicy(entry.role, eligible);
|
|
2341
|
+
if (JSON.stringify(entry.capabilities) !== JSON.stringify(expected.capabilities)) {
|
|
2342
|
+
context.addIssue({ code: "custom", message: `capabilities do not match the ${entry.role} runtime policy` });
|
|
2343
|
+
}
|
|
2344
|
+
if (entry.peerReporting !== void 0 && entry.role !== "peer") {
|
|
2345
|
+
context.addIssue({ code: "custom", message: "peerReporting is only valid on a Peer entry" });
|
|
2346
|
+
}
|
|
2347
|
+
});
|
|
2348
|
+
providerId2 = z3.string().regex(/^[a-z0-9][a-z0-9-]*$/).max(128);
|
|
2349
|
+
runtimeRoomManifestSchema = z3.strictObject({
|
|
2350
|
+
schema: z3.literal(1),
|
|
2351
|
+
roomGeneration: generation,
|
|
2352
|
+
contractGeneration: generation,
|
|
2353
|
+
reportingPolicyGeneration: generation,
|
|
2354
|
+
providers: z3.record(providerId2, providerSchema)
|
|
2355
|
+
});
|
|
2356
|
+
}
|
|
2357
|
+
});
|
|
2358
|
+
|
|
2359
|
+
// src/runtime-plugin/shared/identity.ts
|
|
2360
|
+
var RUNTIME_PLUGIN_ID, RUNTIME_PASEO_RANGE;
|
|
2361
|
+
var init_identity = __esm({
|
|
2362
|
+
"src/runtime-plugin/shared/identity.ts"() {
|
|
2363
|
+
"use strict";
|
|
2364
|
+
RUNTIME_PLUGIN_ID = "paseo-room-runtime";
|
|
2365
|
+
RUNTIME_PASEO_RANGE = ">=0.8.0 <0.10.0";
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
|
|
2369
|
+
// src/runtime.ts
|
|
2370
|
+
import { createHash as createHash2 } from "crypto";
|
|
2371
|
+
import { readdirSync, readFileSync as readFileSync4 } from "fs";
|
|
2372
|
+
import { join as join12, relative, sep as sep2 } from "path";
|
|
2373
|
+
import { fileURLToPath } from "url";
|
|
2374
|
+
function runtimePluginDir(layout) {
|
|
2375
|
+
return join12(layout.roomHome, "runtime-plugin");
|
|
2376
|
+
}
|
|
2377
|
+
function digest(value) {
|
|
2378
|
+
return `sha256:${createHash2("sha256").update(JSON.stringify(value)).digest("hex").slice(0, 16)}`;
|
|
2379
|
+
}
|
|
2380
|
+
function reportingPolicy() {
|
|
2381
|
+
return {
|
|
2382
|
+
supervisor: runtimeRolePolicy("supervisor", ROLE_PEER_REPORTING.supervisor),
|
|
2383
|
+
lead: runtimeRolePolicy("lead", ROLE_PEER_REPORTING.lead),
|
|
2384
|
+
peer: runtimeRolePolicy("peer", ROLE_PEER_REPORTING.peer)
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
function reportingPolicyGeneration() {
|
|
2388
|
+
return digest(reportingPolicy());
|
|
2389
|
+
}
|
|
2390
|
+
function renderRuntimeManifest(agents, roles) {
|
|
2391
|
+
const policy = reportingPolicy();
|
|
2392
|
+
const providers = {};
|
|
2393
|
+
for (const agent of agents) {
|
|
2394
|
+
for (const role of roles) {
|
|
2395
|
+
const { capabilities, peerReporting } = policy[role];
|
|
2396
|
+
providers[providerId(agent, role)] = {
|
|
2397
|
+
agent,
|
|
2398
|
+
role,
|
|
2399
|
+
capabilities: [...capabilities],
|
|
2400
|
+
...peerReporting === void 0 ? {} : { peerReporting: { ...peerReporting, tools: ["ask", "handoff"] } }
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
const contractGeneration = contractDigest();
|
|
2405
|
+
return runtimeRoomManifestSchema.parse({
|
|
2406
|
+
schema: 1,
|
|
2407
|
+
roomGeneration: digest({ agents: [...agents], roles: [...roles], contractGeneration }),
|
|
2408
|
+
contractGeneration,
|
|
2409
|
+
reportingPolicyGeneration: reportingPolicyGeneration(),
|
|
2410
|
+
providers
|
|
2411
|
+
});
|
|
2412
|
+
}
|
|
2413
|
+
function renderRuntimeManifestFile(agents, roles) {
|
|
2414
|
+
return `${JSON.stringify(renderRuntimeManifest(agents, roles), null, 2)}
|
|
2415
|
+
`;
|
|
2416
|
+
}
|
|
2417
|
+
function renderRuntimeLocation(layout) {
|
|
2418
|
+
const location = { pluginDirectory: runtimePluginDir(layout), runtimeRoot: join12(layout.roomHome, "runtime", "v1") };
|
|
2419
|
+
return [
|
|
2420
|
+
"/** Generated by paseo-room setup for this room. */",
|
|
2421
|
+
"export interface RoomLocation {",
|
|
2422
|
+
" readonly pluginDirectory: string;",
|
|
2423
|
+
" readonly runtimeRoot: string;",
|
|
2424
|
+
"}",
|
|
2425
|
+
"",
|
|
2426
|
+
`export const ROOM_LOCATION: RoomLocation | undefined = ${JSON.stringify(location)};`,
|
|
2427
|
+
""
|
|
2428
|
+
].join("\n");
|
|
2429
|
+
}
|
|
2430
|
+
function bundleInventory() {
|
|
2431
|
+
const entries = readdirSync(BUNDLE, { recursive: true, withFileTypes: true });
|
|
2432
|
+
const toRelative = (entry) => relative(BUNDLE, join12(entry.parentPath, entry.name)).split(sep2).join("/");
|
|
2433
|
+
return {
|
|
2434
|
+
dirs: entries.filter((entry) => entry.isDirectory()).map(toRelative).sort(),
|
|
2435
|
+
files: entries.filter((entry) => entry.isFile()).map(toRelative).sort()
|
|
2436
|
+
};
|
|
2437
|
+
}
|
|
2438
|
+
function runtimePluginEntries(layout, agents, roles) {
|
|
2439
|
+
const root = runtimePluginDir(layout);
|
|
2440
|
+
const { dirs, files } = bundleInventory();
|
|
2441
|
+
const generatedDir = MANIFEST_FILE.split("/").slice(0, -1).join("/");
|
|
2442
|
+
return [
|
|
2443
|
+
{ kind: "dir", path: root },
|
|
2444
|
+
...[.../* @__PURE__ */ new Set([...dirs, generatedDir])].sort().map((path) => ({ kind: "dir", path: join12(root, path) })),
|
|
2445
|
+
...files.map((path) => ({
|
|
2446
|
+
kind: "file",
|
|
2447
|
+
path: join12(root, path),
|
|
2448
|
+
content: path === LOCATION_MODULE ? renderRuntimeLocation(layout) : readFileSync4(join12(BUNDLE, path), "utf8")
|
|
2449
|
+
})),
|
|
2450
|
+
{ kind: "file", path: join12(root, MANIFEST_FILE), content: renderRuntimeManifestFile(agents, roles) }
|
|
2451
|
+
];
|
|
2452
|
+
}
|
|
2453
|
+
var BUNDLE, LOCATION_MODULE;
|
|
2454
|
+
var init_runtime = __esm({
|
|
2455
|
+
"src/runtime.ts"() {
|
|
2456
|
+
"use strict";
|
|
2457
|
+
init_roles();
|
|
2458
|
+
init_instructions();
|
|
2459
|
+
init_manifest();
|
|
2460
|
+
init_identity();
|
|
2461
|
+
init_policy();
|
|
2462
|
+
BUNDLE = fileURLToPath(new URL("./runtime-plugin/", import.meta.url));
|
|
2463
|
+
LOCATION_MODULE = "server/generated/location.ts";
|
|
2464
|
+
}
|
|
2465
|
+
});
|
|
2466
|
+
|
|
2467
|
+
// src/runtime-plugin/server/domain/state.ts
|
|
2468
|
+
function emptyProjectState(projectId) {
|
|
2469
|
+
return { projectId, assignments: /* @__PURE__ */ new Map(), ownership: /* @__PURE__ */ new Map(), notices: /* @__PURE__ */ new Map(), lastSequence: 0 };
|
|
2470
|
+
}
|
|
2471
|
+
function activeWriter(state) {
|
|
2472
|
+
for (const owner of state.ownership.values()) if (owner.state !== "released") return owner;
|
|
2473
|
+
return void 0;
|
|
2474
|
+
}
|
|
2475
|
+
function need(condition, message) {
|
|
2476
|
+
return condition ? void 0 : message;
|
|
2477
|
+
}
|
|
2478
|
+
function assignmentOf(state, event) {
|
|
2479
|
+
return event.assignmentId === void 0 ? void 0 : state.assignments.get(event.assignmentId);
|
|
2480
|
+
}
|
|
2481
|
+
function inState(assignment2, allowed, action) {
|
|
2482
|
+
return need(allowed.includes(assignment2.state), `Cannot ${action} while the assignment is ${assignment2.state}.`);
|
|
2483
|
+
}
|
|
2484
|
+
function checkEvent(state, event) {
|
|
2485
|
+
if (event.projectId !== state.projectId) return "Event belongs to another project.";
|
|
2486
|
+
switch (event.type) {
|
|
2487
|
+
case "project.bound":
|
|
2488
|
+
return need(state.binding === void 0, "The project is already bound.");
|
|
2489
|
+
case "project.rebound":
|
|
2490
|
+
return need(state.binding !== void 0, "Only a bound project can be rebound.");
|
|
2491
|
+
case "project.ownership-conflict":
|
|
2492
|
+
return void 0;
|
|
2493
|
+
case "project.ownership-resolved":
|
|
2494
|
+
return need(state.ownershipConflict !== void 0, "There is no ownership conflict to resolve.");
|
|
2495
|
+
case "notice.pending":
|
|
2496
|
+
return need(!state.notices.has(event.data.noticeId), `Notice ${event.data.noticeId} already exists.`);
|
|
2497
|
+
case "notice.sent":
|
|
2498
|
+
case "notice.failed":
|
|
2499
|
+
case "notice.uncertain":
|
|
2500
|
+
return need(state.notices.has(event.data.noticeId), `Unknown notice ${event.data.noticeId}.`);
|
|
2501
|
+
default:
|
|
2502
|
+
break;
|
|
2503
|
+
}
|
|
2504
|
+
if (event.type === "assignment.created") {
|
|
2505
|
+
if (event.assignmentId === void 0) return "An assignment event needs an assignment id.";
|
|
2506
|
+
return need(!state.assignments.has(event.assignmentId), `Assignment ${event.assignmentId} already exists.`);
|
|
2507
|
+
}
|
|
2508
|
+
const assignment2 = assignmentOf(state, event);
|
|
2509
|
+
if (assignment2 === void 0) return `Event ${event.type} names no known assignment.`;
|
|
2510
|
+
const owner = state.ownership.get(assignment2.id);
|
|
2511
|
+
const writable = assignment2.input.mode === "writable";
|
|
2512
|
+
switch (event.type) {
|
|
2513
|
+
case "assignment.dispatch-requested":
|
|
2514
|
+
return inState(assignment2, ["draft"], "dispatch") ?? need(state.ownershipConflict === void 0, "Dispatch is paused by a Lead ownership conflict.") ?? need(!writable || activeWriter(state) === void 0, "Another writer still owns this project.");
|
|
2515
|
+
case "ownership.reserved":
|
|
2516
|
+
return need(writable, "A read-only assignment reserves no writer ownership.") ?? inState(assignment2, ["dispatching"], "reserve ownership") ?? need(activeWriter(state) === void 0, "Another writer still owns this project.");
|
|
2517
|
+
case "agent.create-requested":
|
|
2518
|
+
return inState(assignment2, ["dispatching"], "create the Peer") ?? need(!writable || owner?.state === "reserved", "A writable Peer is created only after ownership is reserved.") ?? need(assignment2.peerAgentId === void 0, "This assignment already has a Peer.");
|
|
2519
|
+
case "agent.create-succeeded":
|
|
2520
|
+
case "agent.create-failed":
|
|
2521
|
+
case "agent.create-uncertain":
|
|
2522
|
+
return need(assignment2.openIntents[event.data.intentId] === "agent.create-requested" || assignment2.state === "uncertain" && event.type !== "agent.create-uncertain", "No unresolved create intent matches.");
|
|
2523
|
+
case "binding.refused":
|
|
2524
|
+
return inState(assignment2, ["dispatching"], "refuse the Peer binding") ?? need(assignment2.peerAgentId === event.data.agentId, "The refusal names a different agent than the one created.");
|
|
2525
|
+
case "binding.published":
|
|
2526
|
+
return inState(assignment2, ["dispatching"], "bind the Peer") ?? need(assignment2.peerAgentId === event.data.agentId, "The binding names a different agent than the one created.");
|
|
2527
|
+
case "ownership.held":
|
|
2528
|
+
return need(owner?.state === "reserved", "Only reserved ownership becomes held.") ?? need(assignment2.peerAgentId === event.data.agentId && assignment2.observedProviderId !== void 0, "Ownership is held only by the bound Peer.");
|
|
2529
|
+
case "reporting.generation-opened": {
|
|
2530
|
+
const from = {
|
|
2531
|
+
initial: ["dispatching"],
|
|
2532
|
+
answer: ["questioned"],
|
|
2533
|
+
"follow-up": ["blocked"],
|
|
2534
|
+
rework: ["rework"]
|
|
2535
|
+
};
|
|
2536
|
+
return inState(assignment2, from[event.data.turn], `open a ${event.data.turn} reporting generation`) ?? need(event.data.generation === assignment2.reportingGeneration + 1, "Reporting generations increase by exactly one.") ?? need(assignment2.reportingState === "closed" || assignment2.reportingState === "consumed", "The previous reporting generation is not terminal.") ?? need(assignment2.observedProviderId !== void 0, "No proven Peer binding exists.") ?? need(!writable || owner?.state === "held", "A writable Peer needs held ownership before any turn.");
|
|
2537
|
+
}
|
|
2538
|
+
case "run.requested":
|
|
2539
|
+
return need(event.data.generation === assignment2.reportingGeneration && assignment2.reportingState === "open", "A run must use the open reporting generation.") ?? need(assignment2.runGeneration < event.data.generation, "This reporting generation already has its run.") ?? need(!Object.values(assignment2.openIntents).includes("run.requested"), "Another run is still unresolved.");
|
|
2540
|
+
case "run.succeeded":
|
|
2541
|
+
case "run.failed":
|
|
2542
|
+
case "run.uncertain":
|
|
2543
|
+
return need(assignment2.openIntents[event.data.intentId] === "run.requested" || assignment2.state === "uncertain" && event.type !== "run.uncertain", "No unresolved run intent matches.");
|
|
2544
|
+
case "report.accepted":
|
|
2545
|
+
return need(event.data.generation === assignment2.reportingGeneration, "The report names a stale generation.") ?? need(assignment2.reportingState === "open" || assignment2.reportingState === "uncertain", "The reporting generation is not open.") ?? inState(assignment2, ["active", "awaiting-permission", "uncertain"], "accept a report");
|
|
2546
|
+
case "report.refused":
|
|
2547
|
+
return void 0;
|
|
2548
|
+
case "report.missing":
|
|
2549
|
+
return need(event.data.generation === assignment2.reportingGeneration, "Missing report names a stale generation.") ?? need(assignment2.reportingState === "open" || assignment2.reportingState === "uncertain", "The reporting generation is not open.") ?? inState(assignment2, ["active", "awaiting-permission", "uncertain"], "record a missing report");
|
|
2550
|
+
case "report.uncertain":
|
|
2551
|
+
return need(event.data.generation === assignment2.reportingGeneration, "Uncertain report names a stale generation.");
|
|
2552
|
+
case "permission.awaiting":
|
|
2553
|
+
return inState(assignment2, ["active"], "await a reporting permission") ?? need(event.data.generation === assignment2.reportingGeneration && assignment2.reportingState === "open", "Permission applies only to the open generation.");
|
|
2554
|
+
case "permission.resolved":
|
|
2555
|
+
return inState(assignment2, ["awaiting-permission"], "resolve a permission") ?? need(assignment2.awaitingPermissionId === event.data.permissionRequestId, "A different permission request is outstanding.");
|
|
2556
|
+
case "assignment.answered":
|
|
2557
|
+
return inState(assignment2, ["questioned", "blocked"], "answer");
|
|
2558
|
+
case "assignment.rework-requested":
|
|
2559
|
+
return inState(assignment2, ["handed-back"], "request rework");
|
|
2560
|
+
case "assignment.accepted":
|
|
2561
|
+
return inState(assignment2, ["handed-back"], "accept");
|
|
2562
|
+
case "assignment.rejected":
|
|
2563
|
+
return inState(assignment2, ["handed-back"], "reject");
|
|
2564
|
+
case "assignment.abandoned":
|
|
2565
|
+
if (assignment2.state === "uncertain") {
|
|
2566
|
+
return need(assignment2.reportingState !== "open" && assignment2.reportingState !== "uncertain", "Abandon an uncertain assignment only after its generation is terminal.") ?? need(assignment2.closure === "closing" || assignment2.closure === "uncertain", "Abandon an uncertain assignment only after archive is requested.");
|
|
2567
|
+
}
|
|
2568
|
+
return inState(assignment2, ["draft", "questioned", "blocked", "handed-back", "rework"], "abandon");
|
|
2569
|
+
case "assignment.close-requested":
|
|
2570
|
+
return need(TERMINAL_STATES.includes(assignment2.state) || assignment2.state === "uncertain", "Close an assignment only after a technical decision or while it is uncertain.") ?? need(assignment2.closure === "open" || assignment2.closure === "uncertain", "The assignment is already closing or closed.");
|
|
2571
|
+
case "archive.requested":
|
|
2572
|
+
return need(assignment2.closure === "closing" || assignment2.closure === "uncertain", "Archive follows a close request.") ?? need(assignment2.peerAgentId === event.data.agentId, "Archive names a different agent.") ?? need(!Object.values(assignment2.openIntents).includes("archive.requested"), "Another archive is still unresolved.");
|
|
2573
|
+
case "archive.succeeded":
|
|
2574
|
+
case "archive.failed":
|
|
2575
|
+
case "archive.uncertain":
|
|
2576
|
+
return need(assignment2.openIntents[event.data.intentId] === "archive.requested" || assignment2.closure === "uncertain" && event.type !== "archive.uncertain", "No unresolved archive intent matches.");
|
|
2577
|
+
case "ownership.releasing":
|
|
2578
|
+
return need(owner?.state === "held", "Only held ownership starts releasing.") ?? need(assignment2.closure === "closing", "Release starts only after a close request.");
|
|
2579
|
+
case "ownership.released":
|
|
2580
|
+
return need(owner?.state === "releasing" || owner?.state === "uncertain", "Only releasing or uncertain ownership is released.") ?? need(assignment2.closure === "closed", "Ownership is released only after archive is proven.");
|
|
2581
|
+
case "ownership.uncertain":
|
|
2582
|
+
return need(owner !== void 0 && owner.state !== "released", "No unreleased ownership to mark uncertain.");
|
|
2583
|
+
case "gate.requested":
|
|
2584
|
+
return inState(assignment2, ["handed-back"], "run a gate") ?? need(assignment2.candidate !== void 0 && assignment2.candidate.commit === event.data.candidate.commit, "A gate runs only against the projected candidate.") ?? need(!assignment2.gates.some((gate) => gate.status === "running"), "Another gate is still running.");
|
|
2585
|
+
case "gate.finished":
|
|
2586
|
+
return need(assignment2.gates.some((gate) => gate.gateRunId === event.data.result.id && gate.status !== "finished"), "No unsettled gate matches.");
|
|
2587
|
+
case "gate.uncertain":
|
|
2588
|
+
return need(assignment2.gates.some((gate) => gate.gateRunId === event.data.gateRunId && gate.status === "running"), "No running gate matches.");
|
|
2589
|
+
default: {
|
|
2590
|
+
const exhaustive = event;
|
|
2591
|
+
return `Unhandled event ${exhaustive.type}.`;
|
|
2592
|
+
}
|
|
2593
|
+
}
|
|
2594
|
+
}
|
|
2595
|
+
function update(state, id2, change, event) {
|
|
2596
|
+
const current2 = state.assignments.get(id2);
|
|
2597
|
+
if (current2 === void 0) return;
|
|
2598
|
+
state.assignments.set(id2, { ...current2, ...change, eventIds: [...current2.eventIds, event.id] });
|
|
2599
|
+
}
|
|
2600
|
+
function withIntent(view, intentId, type) {
|
|
2601
|
+
const next = { ...view.openIntents };
|
|
2602
|
+
if (type === void 0) Reflect.deleteProperty(next, intentId);
|
|
2603
|
+
else next[intentId] = type;
|
|
2604
|
+
return next;
|
|
2605
|
+
}
|
|
2606
|
+
function withoutPermission(view) {
|
|
2607
|
+
const copy = { ...view };
|
|
2608
|
+
delete copy.awaitingPermissionId;
|
|
2609
|
+
return copy;
|
|
2610
|
+
}
|
|
2611
|
+
function setOwner(state, id2, change) {
|
|
2612
|
+
const current2 = state.ownership.get(id2);
|
|
2613
|
+
if (current2 !== void 0) state.ownership.set(id2, { ...current2, ...change });
|
|
2614
|
+
}
|
|
2615
|
+
function applyReport(state, view, event) {
|
|
2616
|
+
const report = {
|
|
2617
|
+
generation: event.data.generation,
|
|
2618
|
+
tool: event.data.tool,
|
|
2619
|
+
requestId: event.data.requestId,
|
|
2620
|
+
fingerprint: event.data.fingerprint,
|
|
2621
|
+
receipt: event.data.receipt,
|
|
2622
|
+
report: event.data.report,
|
|
2623
|
+
eventId: event.id
|
|
2624
|
+
};
|
|
2625
|
+
update(state, view.id, {
|
|
2626
|
+
state: event.data.receipt.assignmentState,
|
|
2627
|
+
reportingState: "consumed",
|
|
2628
|
+
reports: [...view.reports, report],
|
|
2629
|
+
...event.data.candidate === void 0 ? {} : { candidate: event.data.candidate },
|
|
2630
|
+
...event.data.inspectedCommit === void 0 ? {} : { inspectedCommit: event.data.inspectedCommit }
|
|
2631
|
+
}, event);
|
|
2632
|
+
}
|
|
2633
|
+
function applyEvent(state, event) {
|
|
2634
|
+
state.lastSequence = Math.max(state.lastSequence, event.sequence);
|
|
2635
|
+
switch (event.type) {
|
|
2636
|
+
case "project.bound":
|
|
2637
|
+
case "project.rebound":
|
|
2638
|
+
state.binding = { canonicalRoot: event.data.canonicalRoot, gitCommonDir: event.data.gitCommonDir };
|
|
2639
|
+
return;
|
|
2640
|
+
case "project.ownership-conflict":
|
|
2641
|
+
state.ownershipConflict = { leadAgentIds: event.data.leadAgentIds, eventId: event.id };
|
|
2642
|
+
return;
|
|
2643
|
+
case "project.ownership-resolved":
|
|
2644
|
+
delete state.ownershipConflict;
|
|
2645
|
+
return;
|
|
2646
|
+
case "notice.pending":
|
|
2647
|
+
state.notices.set(event.data.noticeId, {
|
|
2648
|
+
noticeId: event.data.noticeId,
|
|
2649
|
+
kind: event.data.kind,
|
|
2650
|
+
class: event.data.class,
|
|
2651
|
+
state: "pending",
|
|
2652
|
+
eventId: event.id,
|
|
2653
|
+
...event.assignmentId === void 0 ? {} : { assignmentId: event.assignmentId }
|
|
2654
|
+
});
|
|
2655
|
+
return;
|
|
2656
|
+
case "notice.sent":
|
|
2657
|
+
case "notice.failed":
|
|
2658
|
+
case "notice.uncertain": {
|
|
2659
|
+
const notice = state.notices.get(event.data.noticeId);
|
|
2660
|
+
if (notice !== void 0) state.notices.set(notice.noticeId, { ...notice, state: event.type === "notice.sent" ? "sent" : event.type === "notice.failed" ? "failed" : "uncertain" });
|
|
2661
|
+
return;
|
|
2662
|
+
}
|
|
2663
|
+
case "assignment.created":
|
|
2664
|
+
if (event.assignmentId === void 0) return;
|
|
2665
|
+
state.assignments.set(event.assignmentId, {
|
|
2666
|
+
id: event.assignmentId,
|
|
2667
|
+
input: event.data.input,
|
|
2668
|
+
leadAgentId: event.data.leadAgentId,
|
|
2669
|
+
leadProviderId: event.data.leadProviderId,
|
|
2670
|
+
state: "draft",
|
|
2671
|
+
closure: "open",
|
|
2672
|
+
reportingGeneration: 0,
|
|
2673
|
+
reportingState: "closed",
|
|
2674
|
+
runGeneration: 0,
|
|
2675
|
+
reports: [],
|
|
2676
|
+
gates: [],
|
|
2677
|
+
openIntents: {},
|
|
2678
|
+
eventIds: [event.id]
|
|
2679
|
+
});
|
|
2680
|
+
return;
|
|
2681
|
+
default:
|
|
2682
|
+
break;
|
|
2683
|
+
}
|
|
2684
|
+
const view = assignmentOf(state, event);
|
|
2685
|
+
if (view === void 0) return;
|
|
2686
|
+
const id2 = view.id;
|
|
2687
|
+
switch (event.type) {
|
|
2688
|
+
case "assignment.dispatch-requested":
|
|
2689
|
+
update(state, id2, { state: "dispatching", peerProviderId: event.data.peerProviderId, workspaceId: event.data.workspaceId }, event);
|
|
2690
|
+
return;
|
|
2691
|
+
case "ownership.reserved":
|
|
2692
|
+
state.ownership.set(id2, { assignmentId: id2, workspaceId: event.data.workspaceId, baseCommit: event.data.baseCommit, state: "reserved" });
|
|
2693
|
+
update(state, id2, {}, event);
|
|
2694
|
+
return;
|
|
2695
|
+
case "agent.create-requested":
|
|
2696
|
+
update(state, id2, { openIntents: withIntent(view, event.data.intentId, event.type) }, event);
|
|
2697
|
+
return;
|
|
2698
|
+
case "agent.create-succeeded":
|
|
2699
|
+
update(state, id2, { peerAgentId: event.data.agentId, openIntents: withIntent(view, event.data.intentId, void 0), ...view.state === "uncertain" ? { state: "dispatching" } : {} }, event);
|
|
2700
|
+
return;
|
|
2701
|
+
case "agent.create-failed": {
|
|
2702
|
+
const owner = state.ownership.get(id2);
|
|
2703
|
+
if (owner?.state === "reserved" || owner?.state === "uncertain" && owner.agentId === void 0) setOwner(state, id2, { state: "released" });
|
|
2704
|
+
update(state, id2, { state: "blocked", openIntents: withIntent(view, event.data.intentId, void 0) }, event);
|
|
2705
|
+
return;
|
|
2706
|
+
}
|
|
2707
|
+
case "agent.create-uncertain":
|
|
2708
|
+
setOwner(state, id2, { state: "uncertain" });
|
|
2709
|
+
update(state, id2, { state: "uncertain" }, event);
|
|
2710
|
+
return;
|
|
2711
|
+
case "binding.refused":
|
|
2712
|
+
setOwner(state, id2, { state: "uncertain", agentId: event.data.agentId });
|
|
2713
|
+
update(state, id2, { state: "uncertain" }, event);
|
|
2714
|
+
return;
|
|
2715
|
+
case "binding.published":
|
|
2716
|
+
update(state, id2, { observedProviderId: event.data.providerId, observedModel: event.data.model, workspaceId: event.data.workspaceId }, event);
|
|
2717
|
+
return;
|
|
2718
|
+
case "ownership.held":
|
|
2719
|
+
setOwner(state, id2, { state: "held", agentId: event.data.agentId });
|
|
2720
|
+
update(state, id2, {}, event);
|
|
2721
|
+
return;
|
|
2722
|
+
case "reporting.generation-opened":
|
|
2723
|
+
update(state, id2, { reportingGeneration: event.data.generation, reportingState: "open" }, event);
|
|
2724
|
+
return;
|
|
2725
|
+
case "run.requested":
|
|
2726
|
+
update(state, id2, { state: "active", runGeneration: event.data.generation, openIntents: withIntent(view, event.data.intentId, event.type) }, event);
|
|
2727
|
+
return;
|
|
2728
|
+
case "run.succeeded":
|
|
2729
|
+
update(state, id2, { openIntents: withIntent(view, event.data.intentId, void 0), ...view.state === "uncertain" ? { state: "active", reportingState: "open" } : {} }, event);
|
|
2730
|
+
return;
|
|
2731
|
+
case "run.failed":
|
|
2732
|
+
update(state, id2, { state: "blocked", reportingState: "consumed", openIntents: withIntent(view, event.data.intentId, void 0) }, event);
|
|
2733
|
+
return;
|
|
2734
|
+
case "run.uncertain":
|
|
2735
|
+
update(state, id2, { state: "uncertain", reportingState: "uncertain" }, event);
|
|
2736
|
+
return;
|
|
2737
|
+
case "report.accepted":
|
|
2738
|
+
applyReport(state, view, event);
|
|
2739
|
+
return;
|
|
2740
|
+
case "report.refused":
|
|
2741
|
+
update(state, id2, {}, event);
|
|
2742
|
+
return;
|
|
2743
|
+
case "report.missing":
|
|
2744
|
+
state.assignments.set(id2, { ...withoutPermission(view), state: "blocked", reportingState: "consumed", eventIds: [...view.eventIds, event.id] });
|
|
2745
|
+
return;
|
|
2746
|
+
case "report.uncertain":
|
|
2747
|
+
update(state, id2, { state: "uncertain", reportingState: "uncertain" }, event);
|
|
2748
|
+
return;
|
|
2749
|
+
case "permission.awaiting":
|
|
2750
|
+
update(state, id2, { state: "awaiting-permission", awaitingPermissionId: event.data.permissionRequestId }, event);
|
|
2751
|
+
return;
|
|
2752
|
+
case "permission.resolved":
|
|
2753
|
+
state.assignments.set(id2, { ...withoutPermission(view), state: "active", eventIds: [...view.eventIds, event.id] });
|
|
2754
|
+
return;
|
|
2755
|
+
case "assignment.answered":
|
|
2756
|
+
update(state, id2, {}, event);
|
|
2757
|
+
return;
|
|
2758
|
+
case "assignment.rework-requested":
|
|
2759
|
+
update(state, id2, { state: "rework" }, event);
|
|
2760
|
+
return;
|
|
2761
|
+
case "assignment.accepted":
|
|
2762
|
+
case "assignment.rejected":
|
|
2763
|
+
case "assignment.abandoned": {
|
|
2764
|
+
const type = event.type === "assignment.accepted" ? "accepted" : event.type === "assignment.rejected" ? "rejected" : "abandoned";
|
|
2765
|
+
update(state, id2, { state: type, decision: { type, eventId: event.id } }, event);
|
|
2766
|
+
return;
|
|
2767
|
+
}
|
|
2768
|
+
case "assignment.close-requested":
|
|
2769
|
+
update(state, id2, { closure: "closing" }, event);
|
|
2770
|
+
return;
|
|
2771
|
+
case "archive.requested":
|
|
2772
|
+
update(state, id2, { openIntents: withIntent(view, event.data.intentId, event.type) }, event);
|
|
2773
|
+
return;
|
|
2774
|
+
case "archive.succeeded":
|
|
2775
|
+
update(state, id2, { closure: "closed", openIntents: withIntent(view, event.data.intentId, void 0) }, event);
|
|
2776
|
+
return;
|
|
2777
|
+
case "archive.failed":
|
|
2778
|
+
update(state, id2, { closure: "uncertain", openIntents: withIntent(view, event.data.intentId, void 0) }, event);
|
|
2779
|
+
return;
|
|
2780
|
+
case "archive.uncertain":
|
|
2781
|
+
update(state, id2, { closure: "uncertain" }, event);
|
|
2782
|
+
return;
|
|
2783
|
+
case "ownership.releasing":
|
|
2784
|
+
setOwner(state, id2, { state: "releasing" });
|
|
2785
|
+
update(state, id2, {}, event);
|
|
2786
|
+
return;
|
|
2787
|
+
case "ownership.released":
|
|
2788
|
+
setOwner(state, id2, { state: "released" });
|
|
2789
|
+
update(state, id2, {}, event);
|
|
2790
|
+
return;
|
|
2791
|
+
case "ownership.uncertain":
|
|
2792
|
+
setOwner(state, id2, { state: "uncertain" });
|
|
2793
|
+
update(state, id2, {}, event);
|
|
2794
|
+
return;
|
|
2795
|
+
case "gate.requested":
|
|
2796
|
+
update(state, id2, { gates: [...view.gates, { gateRunId: event.data.gateRunId, candidate: event.data.candidate, command: event.data.command, status: "running" }] }, event);
|
|
2797
|
+
return;
|
|
2798
|
+
case "gate.finished":
|
|
2799
|
+
update(state, id2, { gates: view.gates.map((gate) => gate.gateRunId === event.data.result.id ? { ...gate, status: "finished", result: event.data.result } : gate) }, event);
|
|
2800
|
+
return;
|
|
2801
|
+
case "gate.uncertain":
|
|
2802
|
+
update(state, id2, { gates: view.gates.map((gate) => gate.gateRunId === event.data.gateRunId ? { ...gate, status: "uncertain" } : gate) }, event);
|
|
2803
|
+
return;
|
|
2804
|
+
default:
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
}
|
|
2808
|
+
function project(projectId, events) {
|
|
2809
|
+
const state = emptyProjectState(projectId);
|
|
2810
|
+
for (const event of events) {
|
|
2811
|
+
const problem = checkEvent(state, event);
|
|
2812
|
+
if (problem !== void 0) return { state, violations: [{ eventId: event.id, type: event.type, message: problem }] };
|
|
2813
|
+
applyEvent(state, event);
|
|
2814
|
+
}
|
|
2815
|
+
return { state, violations: [] };
|
|
2816
|
+
}
|
|
2817
|
+
var TERMINAL_STATES;
|
|
2818
|
+
var init_state = __esm({
|
|
2819
|
+
"src/runtime-plugin/server/domain/state.ts"() {
|
|
2820
|
+
"use strict";
|
|
2821
|
+
TERMINAL_STATES = ["accepted", "rejected", "abandoned"];
|
|
2822
|
+
}
|
|
2823
|
+
});
|
|
2824
|
+
|
|
2825
|
+
// src/runtime-plugin/server/domain/receipts.ts
|
|
2826
|
+
import { createHash as createHash3 } from "crypto";
|
|
2827
|
+
var init_receipts = __esm({
|
|
2828
|
+
"src/runtime-plugin/server/domain/receipts.ts"() {
|
|
2829
|
+
"use strict";
|
|
2830
|
+
}
|
|
2831
|
+
});
|
|
2832
|
+
|
|
2833
|
+
// src/runtime-plugin/server/domain/views.ts
|
|
2834
|
+
function quiescence(state) {
|
|
2835
|
+
const blockers = [];
|
|
2836
|
+
for (const view of state.assignments.values()) {
|
|
2837
|
+
if (view.state !== "draft" && !TERMINAL_STATES.includes(view.state)) blockers.push({ kind: "assignment", id: view.id, detail: `is ${view.state}` });
|
|
2838
|
+
if (view.peerAgentId !== void 0 && view.closure !== "closed") blockers.push({ kind: "archive", id: view.id, detail: `managed Peer ${view.peerAgentId} is not archived (${view.closure})` });
|
|
2839
|
+
for (const gate of view.gates) {
|
|
2840
|
+
if (gate.status === "running" || gate.status === "uncertain" && view.closure !== "closed") {
|
|
2841
|
+
blockers.push({ kind: "gate", id: gate.gateRunId, detail: `gate is ${gate.status}` });
|
|
2842
|
+
}
|
|
2843
|
+
}
|
|
2844
|
+
for (const [intent, type] of Object.entries(view.openIntents)) blockers.push({ kind: "intent", id: intent, detail: `${type} has no result` });
|
|
2845
|
+
}
|
|
2846
|
+
for (const owner of state.ownership.values()) {
|
|
2847
|
+
if (owner.state !== "released") blockers.push({ kind: "ownership", id: owner.assignmentId, detail: `writer ownership is ${owner.state}` });
|
|
2848
|
+
}
|
|
2849
|
+
for (const notice of state.notices.values()) {
|
|
2850
|
+
if (notice.state === "pending" || notice.state === "uncertain") blockers.push({ kind: "delivery", id: notice.noticeId, detail: `notice is ${notice.state}` });
|
|
2851
|
+
}
|
|
2852
|
+
return { quiescent: blockers.length === 0, blockers };
|
|
2853
|
+
}
|
|
2854
|
+
var init_views = __esm({
|
|
2855
|
+
"src/runtime-plugin/server/domain/views.ts"() {
|
|
2856
|
+
"use strict";
|
|
2857
|
+
init_receipts();
|
|
2858
|
+
init_state();
|
|
2859
|
+
}
|
|
2860
|
+
});
|
|
2861
|
+
|
|
2862
|
+
// src/runtime-plugin/shared/limits.ts
|
|
2863
|
+
import { z as z4 } from "zod";
|
|
2864
|
+
function utf8Bytes(value) {
|
|
2865
|
+
return encoder.encode(value).length;
|
|
2866
|
+
}
|
|
2867
|
+
function boundedString(max = MAX_STRING_BYTES) {
|
|
2868
|
+
return z4.string().refine((value) => {
|
|
2869
|
+
const bytes = utf8Bytes(value);
|
|
2870
|
+
return bytes >= 1 && bytes <= max;
|
|
2871
|
+
}, { message: `must be 1 byte to ${String(max)} bytes of UTF-8` });
|
|
2872
|
+
}
|
|
2873
|
+
function boundedArray(item) {
|
|
2874
|
+
return z4.array(item).max(MAX_ARRAY_ITEMS);
|
|
2875
|
+
}
|
|
2876
|
+
var MAX_AGGREGATE_BYTES, MAX_ARRAY_ITEMS, MAX_STRING_BYTES, MAX_COMMAND_BYTES, MAX_GATE_TIMEOUT_SECONDS, encoder;
|
|
2877
|
+
var init_limits = __esm({
|
|
2878
|
+
"src/runtime-plugin/shared/limits.ts"() {
|
|
2879
|
+
"use strict";
|
|
2880
|
+
MAX_AGGREGATE_BYTES = 64 * 1024;
|
|
2881
|
+
MAX_ARRAY_ITEMS = 64;
|
|
2882
|
+
MAX_STRING_BYTES = 8 * 1024;
|
|
2883
|
+
MAX_COMMAND_BYTES = 16 * 1024;
|
|
2884
|
+
MAX_GATE_TIMEOUT_SECONDS = 3600;
|
|
2885
|
+
encoder = new TextEncoder();
|
|
2886
|
+
}
|
|
2887
|
+
});
|
|
2888
|
+
|
|
2889
|
+
// src/runtime-plugin/server/contracts/assignment.ts
|
|
2890
|
+
import { z as z5 } from "zod";
|
|
2891
|
+
var ASSIGNMENT_MODES, ASSIGNMENT_KINDS, commitSchema, gateSpecSchema, items, assignmentCreateSchema, candidateRefSchema;
|
|
2892
|
+
var init_assignment = __esm({
|
|
2893
|
+
"src/runtime-plugin/server/contracts/assignment.ts"() {
|
|
2894
|
+
"use strict";
|
|
2895
|
+
init_limits();
|
|
2896
|
+
ASSIGNMENT_MODES = ["writable", "read-only"];
|
|
2897
|
+
ASSIGNMENT_KINDS = ["engineer", "architect", "reviewer", "scout"];
|
|
2898
|
+
commitSchema = z5.string().regex(/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/);
|
|
2899
|
+
gateSpecSchema = z5.strictObject({
|
|
2900
|
+
command: boundedString(MAX_COMMAND_BYTES),
|
|
2901
|
+
timeoutSeconds: z5.number().int().min(1).max(MAX_GATE_TIMEOUT_SECONDS),
|
|
2902
|
+
runtimeRerun: z5.enum(["none", "optional", "required"]),
|
|
2903
|
+
processContractVersion: z5.literal(1)
|
|
2904
|
+
});
|
|
2905
|
+
items = boundedArray(boundedString());
|
|
2906
|
+
assignmentCreateSchema = z5.strictObject({
|
|
2907
|
+
mode: z5.enum(ASSIGNMENT_MODES),
|
|
2908
|
+
kind: z5.enum(ASSIGNMENT_KINDS),
|
|
2909
|
+
outcome: boundedString(),
|
|
2910
|
+
prerequisites: items,
|
|
2911
|
+
writeScope: items,
|
|
2912
|
+
exclusions: items,
|
|
2913
|
+
invariants: items,
|
|
2914
|
+
acceptanceEvidence: items,
|
|
2915
|
+
gate: gateSpecSchema.optional(),
|
|
2916
|
+
expectedHandoff: items,
|
|
2917
|
+
reopenConditions: items,
|
|
2918
|
+
baseCommit: commitSchema
|
|
2919
|
+
});
|
|
2920
|
+
candidateRefSchema = z5.strictObject({
|
|
2921
|
+
kind: z5.literal("git-commit"),
|
|
2922
|
+
commit: commitSchema,
|
|
2923
|
+
baseCommit: commitSchema,
|
|
2924
|
+
changedPaths: z5.array(z5.string().min(1)),
|
|
2925
|
+
workspaceId: z5.string().min(1),
|
|
2926
|
+
branch: z5.string().min(1).optional()
|
|
2927
|
+
});
|
|
2928
|
+
}
|
|
2929
|
+
});
|
|
2930
|
+
|
|
2931
|
+
// src/runtime-plugin/server/contracts/actions.ts
|
|
2932
|
+
import { z as z6 } from "zod";
|
|
2933
|
+
var assignmentIdSchema, reason, assignment, SUPERVISOR_ACTION_SCHEMAS, LEAD_ACTION_SCHEMAS;
|
|
2934
|
+
var init_actions = __esm({
|
|
2935
|
+
"src/runtime-plugin/server/contracts/actions.ts"() {
|
|
2936
|
+
"use strict";
|
|
2937
|
+
init_limits();
|
|
2938
|
+
init_assignment();
|
|
2939
|
+
assignmentIdSchema = z6.string().regex(/^asg_[A-Za-z0-9_-]{8,64}$/);
|
|
2940
|
+
reason = boundedString();
|
|
2941
|
+
assignment = { assignmentId: assignmentIdSchema };
|
|
2942
|
+
SUPERVISOR_ACTION_SCHEMAS = {
|
|
2943
|
+
room_status: z6.strictObject({}),
|
|
2944
|
+
runtime_findings: z6.strictObject({}),
|
|
2945
|
+
message_lead: z6.strictObject({ message: boundedString() })
|
|
2946
|
+
};
|
|
2947
|
+
LEAD_ACTION_SCHEMAS = {
|
|
2948
|
+
assignment_create: assignmentCreateSchema,
|
|
2949
|
+
// An eligible exact Peer provider id from the room manifest; the model is never selectable.
|
|
2950
|
+
assignment_dispatch: z6.strictObject({ ...assignment, peerProvider: z6.string().min(1).max(128) }),
|
|
2951
|
+
assignment_answer: z6.strictObject({ ...assignment, answer: boundedString() }),
|
|
2952
|
+
assignment_rework: z6.strictObject({ ...assignment, instructions: boundedString() }),
|
|
2953
|
+
assignment_accept: z6.strictObject({
|
|
2954
|
+
...assignment,
|
|
2955
|
+
reason,
|
|
2956
|
+
// Required by the domain when the Peer gate or a required rerun is red; never a waiver of
|
|
2957
|
+
// a missing gate.
|
|
2958
|
+
override: z6.strictObject({ reason, residualRiskAcknowledged: z6.literal(true) }).optional()
|
|
2959
|
+
}),
|
|
2960
|
+
assignment_reject: z6.strictObject({ ...assignment, reason }),
|
|
2961
|
+
assignment_abandon: z6.strictObject({ ...assignment, reason }),
|
|
2962
|
+
assignment_close: z6.strictObject(assignment),
|
|
2963
|
+
assignment_status: z6.strictObject({ assignmentId: assignmentIdSchema.optional() }),
|
|
2964
|
+
gate_run: z6.strictObject(assignment)
|
|
2965
|
+
};
|
|
2966
|
+
}
|
|
2967
|
+
});
|
|
2968
|
+
|
|
2969
|
+
// src/runtime-plugin/server/contracts/peer.ts
|
|
2970
|
+
import { z as z7 } from "zod";
|
|
2971
|
+
var text, texts, verificationReportSchema, askInputSchema, HANDOFF_DETAILS, handoffBase, PEER_REPORT_ERROR_CODES, peerReportReceiptSchema, peerReportErrorSchema;
|
|
2972
|
+
var init_peer = __esm({
|
|
2973
|
+
"src/runtime-plugin/server/contracts/peer.ts"() {
|
|
2974
|
+
"use strict";
|
|
2975
|
+
init_limits();
|
|
2976
|
+
text = boundedString();
|
|
2977
|
+
texts = boundedArray(text);
|
|
2978
|
+
verificationReportSchema = z7.strictObject({
|
|
2979
|
+
command: boundedString(MAX_COMMAND_BYTES),
|
|
2980
|
+
outcome: z7.enum(["passed", "failed", "not-run"]),
|
|
2981
|
+
note: text.optional()
|
|
2982
|
+
});
|
|
2983
|
+
askInputSchema = z7.strictObject({
|
|
2984
|
+
question: text,
|
|
2985
|
+
blockingContext: text,
|
|
2986
|
+
evidence: texts
|
|
2987
|
+
});
|
|
2988
|
+
HANDOFF_DETAILS = {
|
|
2989
|
+
engineer: z7.strictObject({ kind: z7.literal("engineer") }),
|
|
2990
|
+
architect: z7.strictObject({
|
|
2991
|
+
kind: z7.literal("architect"),
|
|
2992
|
+
alternatives: texts,
|
|
2993
|
+
strongestCounterargument: text,
|
|
2994
|
+
reversalConditions: texts,
|
|
2995
|
+
evidence: texts
|
|
2996
|
+
}),
|
|
2997
|
+
reviewer: z7.strictObject({
|
|
2998
|
+
kind: z7.literal("reviewer"),
|
|
2999
|
+
findings: boundedArray(z7.strictObject({ claim: text, evidence: texts }))
|
|
3000
|
+
}),
|
|
3001
|
+
scout: z7.strictObject({
|
|
3002
|
+
kind: z7.literal("scout"),
|
|
3003
|
+
evidence: texts,
|
|
3004
|
+
remainingUnknowns: texts,
|
|
3005
|
+
confidence: z7.enum(["low", "medium", "high"])
|
|
3006
|
+
})
|
|
3007
|
+
};
|
|
3008
|
+
handoffBase = {
|
|
3009
|
+
summary: text,
|
|
3010
|
+
deliverables: texts,
|
|
3011
|
+
verification: boundedArray(verificationReportSchema),
|
|
3012
|
+
residualRisks: texts,
|
|
3013
|
+
evidence: texts
|
|
3014
|
+
};
|
|
3015
|
+
PEER_REPORT_ERROR_CODES = [
|
|
3016
|
+
"report_malformed",
|
|
3017
|
+
"report_unauthorized",
|
|
3018
|
+
"report_stale",
|
|
3019
|
+
"report_conflict",
|
|
3020
|
+
"report_state",
|
|
3021
|
+
"report_precondition",
|
|
3022
|
+
"report_uncertain"
|
|
3023
|
+
];
|
|
3024
|
+
peerReportReceiptSchema = z7.strictObject({
|
|
3025
|
+
schema: z7.literal(1),
|
|
3026
|
+
receipt: z7.string().min(1).max(128),
|
|
3027
|
+
tool: z7.enum(["ask", "handoff"]),
|
|
3028
|
+
status: z7.literal("accepted"),
|
|
3029
|
+
assignmentState: z7.enum(["questioned", "blocked", "handed-back"])
|
|
3030
|
+
});
|
|
3031
|
+
peerReportErrorSchema = z7.strictObject({
|
|
3032
|
+
schema: z7.literal(1),
|
|
3033
|
+
error: z7.strictObject({
|
|
3034
|
+
code: z7.enum(PEER_REPORT_ERROR_CODES),
|
|
3035
|
+
message: boundedString(1024),
|
|
3036
|
+
retryable: z7.boolean()
|
|
3037
|
+
})
|
|
3038
|
+
});
|
|
3039
|
+
}
|
|
3040
|
+
});
|
|
3041
|
+
|
|
3042
|
+
// src/runtime-plugin/server/events/schema.ts
|
|
3043
|
+
import { z as z8 } from "zod";
|
|
3044
|
+
function variants(strict) {
|
|
3045
|
+
return EVENT_TYPES.map((type) => z8.strictObject({
|
|
3046
|
+
...envelope,
|
|
3047
|
+
type: z8.literal(type),
|
|
3048
|
+
payloadVersion: z8.literal(1),
|
|
3049
|
+
data: strict ? z8.strictObject(EVENT_PAYLOADS[type]) : z8.object(EVENT_PAYLOADS[type])
|
|
3050
|
+
}));
|
|
3051
|
+
}
|
|
3052
|
+
function validateForWrite(event) {
|
|
3053
|
+
return writerSchema.parse(event);
|
|
3054
|
+
}
|
|
3055
|
+
function readEvent(value) {
|
|
3056
|
+
if (typeof value !== "object" || value === null) return { ok: false, reason: "invalid", detail: "Event is not an object." };
|
|
3057
|
+
const { type, payloadVersion } = value;
|
|
3058
|
+
if (typeof type !== "string" || !Object.hasOwn(EVENT_PAYLOADS, type)) {
|
|
3059
|
+
return { ok: false, reason: "unknown-type", detail: `Unknown event type ${typeof type === "string" ? type : typeof type}.` };
|
|
3060
|
+
}
|
|
3061
|
+
if (typeof payloadVersion !== "number" || !SUPPORTED_PAYLOAD_VERSIONS[type].includes(payloadVersion)) {
|
|
3062
|
+
return { ok: false, reason: "unsupported-version", detail: `Unsupported payload version ${String(payloadVersion)} for ${type}.` };
|
|
3063
|
+
}
|
|
3064
|
+
const parsed = readerSchema.safeParse(value);
|
|
3065
|
+
return parsed.success ? { ok: true, event: parsed.data } : { ok: false, reason: "invalid", detail: z8.prettifyError(parsed.error) };
|
|
3066
|
+
}
|
|
3067
|
+
var EVENT_SCHEMA, id, text2, reason2, generation2, digest2, timestamp, gateResultSchema, NOTICE_DISPOSITIONS, NOTICE_CLASSES, EVENT_PAYLOADS, EVENT_TYPES, SUPPORTED_PAYLOAD_VERSIONS, actorSchema, envelope, firstWriter, restWriters, firstReader, restReaders, writerSchema, readerSchema;
|
|
3068
|
+
var init_schema = __esm({
|
|
3069
|
+
"src/runtime-plugin/server/events/schema.ts"() {
|
|
3070
|
+
"use strict";
|
|
3071
|
+
init_limits();
|
|
3072
|
+
init_policy();
|
|
3073
|
+
init_actions();
|
|
3074
|
+
init_assignment();
|
|
3075
|
+
init_peer();
|
|
3076
|
+
EVENT_SCHEMA = "paseo-room.runtime-event";
|
|
3077
|
+
id = z8.string().min(1).max(128);
|
|
3078
|
+
text2 = boundedString();
|
|
3079
|
+
reason2 = boundedString(1024);
|
|
3080
|
+
generation2 = z8.number().int().min(1);
|
|
3081
|
+
digest2 = z8.string().regex(/^sha256:[0-9a-f]{64}$/);
|
|
3082
|
+
timestamp = z8.iso.datetime({ offset: true });
|
|
3083
|
+
gateResultSchema = z8.strictObject({
|
|
3084
|
+
id,
|
|
3085
|
+
assignmentId: assignmentIdSchema,
|
|
3086
|
+
candidate: candidateRefSchema,
|
|
3087
|
+
command: boundedString(MAX_COMMAND_BYTES),
|
|
3088
|
+
startedAt: timestamp,
|
|
3089
|
+
finishedAt: timestamp.optional(),
|
|
3090
|
+
exitCode: z8.number().int().optional(),
|
|
3091
|
+
signal: z8.string().min(1).max(32).optional(),
|
|
3092
|
+
timedOut: z8.boolean(),
|
|
3093
|
+
termination: z8.enum(["exited", "signaled", "killed", "uncertain"]),
|
|
3094
|
+
processContractVersion: z8.literal(1),
|
|
3095
|
+
environmentPolicyVersion: z8.literal(1),
|
|
3096
|
+
outputDigest: digest2,
|
|
3097
|
+
outputTailAttachment: z8.string().min(1).max(256).optional(),
|
|
3098
|
+
workspaceMoved: z8.boolean()
|
|
3099
|
+
});
|
|
3100
|
+
NOTICE_DISPOSITIONS = ["record", "panel", "lead-now", "supervisor-digest", "supervisor-now", "operator-now", "human-required"];
|
|
3101
|
+
NOTICE_CLASSES = ["record", "owner", "operator", "page"];
|
|
3102
|
+
EVENT_PAYLOADS = {
|
|
3103
|
+
// Project binding and Lead ownership (§4.1, §5.2).
|
|
3104
|
+
"project.bound": { canonicalRoot: z8.string().min(1), gitCommonDir: z8.string().min(1) },
|
|
3105
|
+
"project.rebound": { canonicalRoot: z8.string().min(1), gitCommonDir: z8.string().min(1), previousRoot: z8.string().min(1) },
|
|
3106
|
+
"project.ownership-conflict": { leadAgentIds: z8.array(id).min(2), leadProviderIds: z8.array(id).min(1) },
|
|
3107
|
+
"project.ownership-resolved": { keptLeadAgentId: id, decidedBy: z8.enum(["evidence", "human"]) },
|
|
3108
|
+
// Assignment lifecycle decided by Lead or Human (§5.1).
|
|
3109
|
+
"assignment.created": { input: assignmentCreateSchema, leadAgentId: id, leadProviderId: id },
|
|
3110
|
+
"assignment.dispatch-requested": { peerProviderId: id, workspaceId: id },
|
|
3111
|
+
"assignment.answered": { answer: text2 },
|
|
3112
|
+
"assignment.rework-requested": { instructions: text2 },
|
|
3113
|
+
"assignment.accepted": {
|
|
3114
|
+
candidate: candidateRefSchema.optional(),
|
|
3115
|
+
inspectedCommit: commitSchema.optional(),
|
|
3116
|
+
reason: reason2,
|
|
3117
|
+
override: z8.strictObject({ reason: reason2, residualRiskAcknowledged: z8.literal(true) }).optional(),
|
|
3118
|
+
gateResultId: id.optional()
|
|
3119
|
+
},
|
|
3120
|
+
"assignment.rejected": { reason: reason2 },
|
|
3121
|
+
"assignment.abandoned": { reason: reason2 },
|
|
3122
|
+
"assignment.close-requested": {},
|
|
3123
|
+
// Writer ownership, independent of assignment state (§5.3).
|
|
3124
|
+
"ownership.reserved": { workspaceId: id, baseCommit: commitSchema },
|
|
3125
|
+
"ownership.held": { agentId: id },
|
|
3126
|
+
"ownership.releasing": { agentId: id },
|
|
3127
|
+
"ownership.released": { agentId: id, archivedAt: timestamp },
|
|
3128
|
+
"ownership.uncertain": { reason: reason2 },
|
|
3129
|
+
// External effects: agent creation, turn delivery and archive (§6).
|
|
3130
|
+
"agent.create-requested": { intentId: id, peerProviderId: id, workspaceId: id, parentAgentId: id, label: id },
|
|
3131
|
+
"agent.create-succeeded": { intentId: id, agentId: id },
|
|
3132
|
+
"agent.create-failed": { intentId: id, reason: reason2 },
|
|
3133
|
+
"agent.create-uncertain": { intentId: id, reason: reason2 },
|
|
3134
|
+
// A created Peer whose fresh snapshot did not prove provider, parent, workspace, idleness
|
|
3135
|
+
// and no prior prompt. It is archived, never adopted or prompted (§3.3, §5.3).
|
|
3136
|
+
"binding.refused": { agentId: id, reason: reason2 },
|
|
3137
|
+
"binding.published": {
|
|
3138
|
+
agentId: id,
|
|
3139
|
+
providerId: id,
|
|
3140
|
+
model: z8.string().min(1).max(256),
|
|
3141
|
+
parentAgentId: id,
|
|
3142
|
+
workspaceId: id,
|
|
3143
|
+
roomGeneration: id
|
|
3144
|
+
},
|
|
3145
|
+
"reporting.generation-opened": { generation: generation2, capabilityHash: digest2, turn: z8.enum(["initial", "answer", "rework", "follow-up"]) },
|
|
3146
|
+
"run.requested": { intentId: id, generation: generation2, promptDigest: digest2 },
|
|
3147
|
+
"run.succeeded": { intentId: id, generation: generation2 },
|
|
3148
|
+
"run.failed": { intentId: id, generation: generation2, reason: reason2 },
|
|
3149
|
+
"run.uncertain": { intentId: id, generation: generation2, reason: reason2 },
|
|
3150
|
+
"archive.requested": { intentId: id, agentId: id },
|
|
3151
|
+
"archive.succeeded": { intentId: id, agentId: id, archivedAt: timestamp, liveStatus: z8.literal("closed") },
|
|
3152
|
+
"archive.failed": { intentId: id, agentId: id, reason: reason2 },
|
|
3153
|
+
"archive.uncertain": { intentId: id, agentId: id, reason: reason2 },
|
|
3154
|
+
// Peer reporting (§3.4). Report content is evidence; identity and candidate are derived.
|
|
3155
|
+
"report.accepted": {
|
|
3156
|
+
generation: generation2,
|
|
3157
|
+
tool: z8.enum(PEER_REPORTING_TOOLS),
|
|
3158
|
+
requestId: id,
|
|
3159
|
+
fingerprint: digest2,
|
|
3160
|
+
receipt: peerReportReceiptSchema,
|
|
3161
|
+
report: z8.record(z8.string(), z8.unknown()),
|
|
3162
|
+
candidate: candidateRefSchema.optional(),
|
|
3163
|
+
inspectedCommit: commitSchema.optional()
|
|
3164
|
+
},
|
|
3165
|
+
"report.refused": { generation: generation2.optional(), tool: z8.string().min(1).max(64), requestId: id, code: z8.enum(PEER_REPORT_ERROR_CODES), reason: reason2 },
|
|
3166
|
+
"report.missing": { generation: generation2 },
|
|
3167
|
+
"report.uncertain": { generation: generation2, requestId: id.optional(), reason: reason2 },
|
|
3168
|
+
"permission.awaiting": { generation: generation2, permissionRequestId: id, tool: z8.enum(PEER_REPORTING_TOOLS) },
|
|
3169
|
+
"permission.resolved": { generation: generation2, permissionRequestId: id, outcome: z8.enum(["allowed", "denied", "other"]) },
|
|
3170
|
+
// Independent runtime gate (D6).
|
|
3171
|
+
"gate.requested": {
|
|
3172
|
+
gateRunId: id,
|
|
3173
|
+
candidate: candidateRefSchema,
|
|
3174
|
+
command: boundedString(MAX_COMMAND_BYTES),
|
|
3175
|
+
timeoutSeconds: gateSpecSchema.shape.timeoutSeconds,
|
|
3176
|
+
processContractVersion: z8.literal(1),
|
|
3177
|
+
environmentPolicyVersion: z8.literal(1)
|
|
3178
|
+
},
|
|
3179
|
+
"gate.finished": { result: gateResultSchema },
|
|
3180
|
+
"gate.uncertain": { gateRunId: id, reason: reason2 },
|
|
3181
|
+
// Deterministic notices (D9). Delivery is at least once with a stable id.
|
|
3182
|
+
"notice.pending": {
|
|
3183
|
+
noticeId: id,
|
|
3184
|
+
kind: z8.string().min(1).max(64),
|
|
3185
|
+
class: z8.enum(NOTICE_CLASSES),
|
|
3186
|
+
disposition: z8.enum(NOTICE_DISPOSITIONS),
|
|
3187
|
+
recipientAgentId: id.optional(),
|
|
3188
|
+
recipientRole: z8.enum(RUNTIME_ROLES).exclude(["peer"]).optional(),
|
|
3189
|
+
text: text2
|
|
3190
|
+
},
|
|
3191
|
+
"notice.sent": { noticeId: id },
|
|
3192
|
+
"notice.failed": { noticeId: id, reason: reason2 },
|
|
3193
|
+
"notice.uncertain": { noticeId: id, reason: reason2 }
|
|
3194
|
+
};
|
|
3195
|
+
EVENT_TYPES = Object.keys(EVENT_PAYLOADS);
|
|
3196
|
+
SUPPORTED_PAYLOAD_VERSIONS = Object.fromEntries(
|
|
3197
|
+
EVENT_TYPES.map((type) => [type, [1]])
|
|
3198
|
+
);
|
|
3199
|
+
actorSchema = z8.strictObject({
|
|
3200
|
+
source: z8.enum(["human", "plugin", "seat", "paseo"]),
|
|
3201
|
+
role: z8.enum(RUNTIME_ROLES).optional(),
|
|
3202
|
+
agentId: id.optional(),
|
|
3203
|
+
providerId: id.optional()
|
|
3204
|
+
});
|
|
3205
|
+
envelope = {
|
|
3206
|
+
schema: z8.literal(EVENT_SCHEMA),
|
|
3207
|
+
version: z8.literal(1),
|
|
3208
|
+
id: z8.string().regex(/^evt_[A-Za-z0-9_-]{8,64}$/),
|
|
3209
|
+
sequence: z8.number().int().min(1),
|
|
3210
|
+
projectId: z8.uuid(),
|
|
3211
|
+
assignmentId: assignmentIdSchema.optional(),
|
|
3212
|
+
actor: actorSchema,
|
|
3213
|
+
causationId: id.optional(),
|
|
3214
|
+
idempotencyKey: id.optional(),
|
|
3215
|
+
occurredAt: timestamp
|
|
3216
|
+
};
|
|
3217
|
+
[firstWriter, ...restWriters] = variants(true);
|
|
3218
|
+
[firstReader, ...restReaders] = variants(false);
|
|
3219
|
+
if (firstWriter === void 0 || firstReader === void 0) throw new Error("The runtime event union is empty.");
|
|
3220
|
+
writerSchema = z8.discriminatedUnion("type", [firstWriter, ...restWriters]);
|
|
3221
|
+
readerSchema = z8.discriminatedUnion("type", [firstReader, ...restReaders]);
|
|
3222
|
+
}
|
|
3223
|
+
});
|
|
3224
|
+
|
|
3225
|
+
// src/runtime-plugin/server/store/publish.ts
|
|
3226
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
3227
|
+
import { chmod as chmod2, link, lstat as lstat4, mkdir as mkdir2, open, readdir as readdir3, unlink as unlink2 } from "fs/promises";
|
|
3228
|
+
import { join as join13 } from "path";
|
|
3229
|
+
function code(error) {
|
|
3230
|
+
return typeof error === "object" && error !== null && "code" in error ? String(error.code) : void 0;
|
|
3231
|
+
}
|
|
3232
|
+
async function ensurePrivateDirectory(path) {
|
|
3233
|
+
await mkdir2(path, { recursive: true, mode: PRIVATE_DIR_MODE });
|
|
3234
|
+
const stat2 = await lstat4(path);
|
|
3235
|
+
if (!stat2.isDirectory()) throw new Error(`${path} is not a directory.`);
|
|
3236
|
+
if ((stat2.mode & 511) !== PRIVATE_DIR_MODE) await chmod2(path, PRIVATE_DIR_MODE);
|
|
3237
|
+
}
|
|
3238
|
+
async function writeTemporary(directory, content, hook) {
|
|
3239
|
+
const temporary = join13(directory, `${TEMPORARY_PREFIX}${String(process.pid)}-${randomBytes2(8).toString("hex")}`);
|
|
3240
|
+
const handle = await open(temporary, "wx", PRIVATE_FILE_MODE);
|
|
3241
|
+
try {
|
|
3242
|
+
await handle.writeFile(content);
|
|
3243
|
+
hook?.("temporary-written");
|
|
3244
|
+
await handle.sync();
|
|
3245
|
+
hook?.("temporary-synced");
|
|
3246
|
+
} finally {
|
|
3247
|
+
await handle.close();
|
|
3248
|
+
}
|
|
3249
|
+
return temporary;
|
|
3250
|
+
}
|
|
3251
|
+
async function syncDirectory(directory) {
|
|
3252
|
+
let handle;
|
|
3253
|
+
try {
|
|
3254
|
+
handle = await open(directory, "r");
|
|
3255
|
+
await handle.sync();
|
|
3256
|
+
} catch (error) {
|
|
3257
|
+
if (!["EISDIR", "EINVAL", "EPERM", "EBADF", "ENOTSUP"].includes(code(error) ?? "")) throw error;
|
|
3258
|
+
} finally {
|
|
3259
|
+
await handle?.close();
|
|
3260
|
+
}
|
|
3261
|
+
}
|
|
3262
|
+
async function publishWith(directory, content, allocate, hook) {
|
|
3263
|
+
const temporary = await writeTemporary(directory, content, hook);
|
|
3264
|
+
let published;
|
|
3265
|
+
try {
|
|
3266
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
3267
|
+
const name = allocate(attempt);
|
|
3268
|
+
if (name === void 0) break;
|
|
3269
|
+
const target = join13(directory, name);
|
|
3270
|
+
try {
|
|
3271
|
+
await link(temporary, target);
|
|
3272
|
+
published = target;
|
|
3273
|
+
break;
|
|
3274
|
+
} catch (error) {
|
|
3275
|
+
const reason3 = code(error);
|
|
3276
|
+
if (reason3 === "EEXIST") continue;
|
|
3277
|
+
if (reason3 !== void 0 && UNSUPPORTED.has(reason3)) throw new PublishUnsupportedError(directory, error);
|
|
3278
|
+
throw error;
|
|
3279
|
+
}
|
|
3280
|
+
}
|
|
3281
|
+
if (published !== void 0) hook?.("linked");
|
|
3282
|
+
} finally {
|
|
3283
|
+
await unlink2(temporary).catch(() => void 0);
|
|
3284
|
+
}
|
|
3285
|
+
if (published === void 0) return "";
|
|
3286
|
+
hook?.("temporary-removed");
|
|
3287
|
+
await syncDirectory(directory);
|
|
3288
|
+
return published;
|
|
3289
|
+
}
|
|
3290
|
+
async function publishOnce(directory, name, content, hook) {
|
|
3291
|
+
const path = await publishWith(directory, content, (attempt) => attempt === 0 ? name : void 0, hook);
|
|
3292
|
+
if (path === "") throw new AlreadyPublishedError(join13(directory, name));
|
|
3293
|
+
return path;
|
|
3294
|
+
}
|
|
3295
|
+
async function staleTemporaries(directory) {
|
|
3296
|
+
const names = await readdir3(directory);
|
|
3297
|
+
return names.filter((name) => name.startsWith(TEMPORARY_PREFIX)).sort();
|
|
3298
|
+
}
|
|
3299
|
+
var TEMPORARY_PREFIX, PRIVATE_DIR_MODE, PRIVATE_FILE_MODE, PublishUnsupportedError, AlreadyPublishedError, UNSUPPORTED;
|
|
3300
|
+
var init_publish = __esm({
|
|
3301
|
+
"src/runtime-plugin/server/store/publish.ts"() {
|
|
3302
|
+
"use strict";
|
|
3303
|
+
TEMPORARY_PREFIX = ".tmp-";
|
|
3304
|
+
PRIVATE_DIR_MODE = 448;
|
|
3305
|
+
PRIVATE_FILE_MODE = 384;
|
|
3306
|
+
PublishUnsupportedError = class extends Error {
|
|
3307
|
+
constructor(directory, cause) {
|
|
3308
|
+
super(`The filesystem at ${directory} cannot publish runtime state without the risk of overwriting it (hard links unavailable).`, { cause });
|
|
3309
|
+
this.name = "PublishUnsupportedError";
|
|
3310
|
+
}
|
|
3311
|
+
};
|
|
3312
|
+
AlreadyPublishedError = class extends Error {
|
|
3313
|
+
constructor(path) {
|
|
3314
|
+
super(`${path} is already published and is never replaced.`);
|
|
3315
|
+
this.path = path;
|
|
3316
|
+
this.name = "AlreadyPublishedError";
|
|
3317
|
+
}
|
|
3318
|
+
path;
|
|
3319
|
+
};
|
|
3320
|
+
UNSUPPORTED = /* @__PURE__ */ new Set(["EPERM", "ENOTSUP", "EOPNOTSUPP", "ENOSYS", "EXDEV", "EMLINK"]);
|
|
3321
|
+
}
|
|
3322
|
+
});
|
|
3323
|
+
|
|
3324
|
+
// src/runtime-plugin/server/store/project.ts
|
|
3325
|
+
import { randomBytes as randomBytes3, randomUUID } from "crypto";
|
|
3326
|
+
import { mkdir as mkdir3, readdir as readdir4, readFile as readFile4, rename as rename2, rm as rm3, writeFile as writeFile2 } from "fs/promises";
|
|
3327
|
+
import { basename as basename2, join as join14 } from "path";
|
|
3328
|
+
import { z as z9 } from "zod";
|
|
3329
|
+
function runtimeRoot(roomHome) {
|
|
3330
|
+
return join14(roomHome, "runtime", RUNTIME_LAYOUT_VERSION);
|
|
3331
|
+
}
|
|
3332
|
+
function eventName(sequence) {
|
|
3333
|
+
return `${String(sequence).padStart(12, "0")}.json`;
|
|
3334
|
+
}
|
|
3335
|
+
function slug(root) {
|
|
3336
|
+
const name = basename2(root).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 32);
|
|
3337
|
+
return name === "" ? "project" : name;
|
|
3338
|
+
}
|
|
3339
|
+
var RUNTIME_LAYOUT_VERSION, EVENT_FILE, MAX_SEQUENCE_ATTEMPTS, metaSchema, ProjectStore;
|
|
3340
|
+
var init_project = __esm({
|
|
3341
|
+
"src/runtime-plugin/server/store/project.ts"() {
|
|
3342
|
+
"use strict";
|
|
3343
|
+
init_schema();
|
|
3344
|
+
init_publish();
|
|
3345
|
+
RUNTIME_LAYOUT_VERSION = "v1";
|
|
3346
|
+
EVENT_FILE = /^(\d{12})\.json$/;
|
|
3347
|
+
MAX_SEQUENCE_ATTEMPTS = 64;
|
|
3348
|
+
metaSchema = z9.strictObject({
|
|
3349
|
+
schema: z9.literal(1),
|
|
3350
|
+
projectId: z9.uuid(),
|
|
3351
|
+
createdAt: z9.iso.datetime({ offset: true }),
|
|
3352
|
+
canonicalRoot: z9.string().min(1),
|
|
3353
|
+
gitCommonDir: z9.string().min(1)
|
|
3354
|
+
});
|
|
3355
|
+
ProjectStore = class _ProjectStore {
|
|
3356
|
+
constructor(directory, meta, now) {
|
|
3357
|
+
this.directory = directory;
|
|
3358
|
+
this.meta = meta;
|
|
3359
|
+
this.now = now;
|
|
3360
|
+
}
|
|
3361
|
+
directory;
|
|
3362
|
+
meta;
|
|
3363
|
+
now;
|
|
3364
|
+
lastSequence = 0;
|
|
3365
|
+
get eventsDirectory() {
|
|
3366
|
+
return join14(this.directory, "events");
|
|
3367
|
+
}
|
|
3368
|
+
get cacheDirectory() {
|
|
3369
|
+
return join14(this.directory, "cache");
|
|
3370
|
+
}
|
|
3371
|
+
get gatesDirectory() {
|
|
3372
|
+
return join14(this.directory, "gates");
|
|
3373
|
+
}
|
|
3374
|
+
get quarantineDirectory() {
|
|
3375
|
+
return join14(this.directory, "quarantine");
|
|
3376
|
+
}
|
|
3377
|
+
/** Opens an existing project directory; refuses one whose `meta.json` is not exactly v1. */
|
|
3378
|
+
static async open(directory, now = () => /* @__PURE__ */ new Date()) {
|
|
3379
|
+
const meta = metaSchema.parse(JSON.parse(await readFile4(join14(directory, "meta.json"), "utf8")));
|
|
3380
|
+
return new _ProjectStore(directory, meta, now);
|
|
3381
|
+
}
|
|
3382
|
+
/**
|
|
3383
|
+
* Binds a new project to its canonical Git common directory. Identity is a minted UUID, never
|
|
3384
|
+
* a hash of a movable path; a moved repository is rebound only by an explicit operator action.
|
|
3385
|
+
*/
|
|
3386
|
+
static async create(root, binding, now = () => /* @__PURE__ */ new Date()) {
|
|
3387
|
+
const projects = join14(root, "projects");
|
|
3388
|
+
await ensurePrivateDirectory(projects);
|
|
3389
|
+
const projectId = randomUUID();
|
|
3390
|
+
const directory = join14(projects, `${slug(binding.canonicalRoot)}-${projectId}`);
|
|
3391
|
+
await ensurePrivateDirectory(directory);
|
|
3392
|
+
for (const child of ["events", "cache", "gates", "quarantine"]) await ensurePrivateDirectory(join14(directory, child));
|
|
3393
|
+
const meta = { schema: 1, projectId, createdAt: now().toISOString(), ...binding };
|
|
3394
|
+
await publishOnce(directory, "meta.json", `${JSON.stringify(meta, null, 2)}
|
|
3395
|
+
`);
|
|
3396
|
+
const store = new _ProjectStore(directory, meta, now);
|
|
3397
|
+
await store.append({ type: "project.bound", payloadVersion: 1, actor: { source: "plugin" }, data: { ...binding } });
|
|
3398
|
+
return store;
|
|
3399
|
+
}
|
|
3400
|
+
/** Finds the project bound to this Git common directory. Never matches by name, remote or cwd. */
|
|
3401
|
+
static async find(root, gitCommonDir, now) {
|
|
3402
|
+
for (const store of await _ProjectStore.list(root, now)) {
|
|
3403
|
+
if (store.meta.gitCommonDir === gitCommonDir) return store;
|
|
3404
|
+
}
|
|
3405
|
+
return void 0;
|
|
3406
|
+
}
|
|
3407
|
+
static async list(root, now) {
|
|
3408
|
+
const projects = join14(root, "projects");
|
|
3409
|
+
let names;
|
|
3410
|
+
try {
|
|
3411
|
+
names = await readdir4(projects);
|
|
3412
|
+
} catch {
|
|
3413
|
+
return [];
|
|
3414
|
+
}
|
|
3415
|
+
const stores = [];
|
|
3416
|
+
for (const name of names.sort()) {
|
|
3417
|
+
try {
|
|
3418
|
+
stores.push(await _ProjectStore.open(join14(projects, name), now));
|
|
3419
|
+
} catch {
|
|
3420
|
+
}
|
|
3421
|
+
}
|
|
3422
|
+
return stores;
|
|
3423
|
+
}
|
|
3424
|
+
/** Reads every event strictly. Any unreadable file pauses the project; nothing is moved. */
|
|
3425
|
+
async replay() {
|
|
3426
|
+
const names = (await readdir4(this.eventsDirectory)).sort();
|
|
3427
|
+
const problems = [];
|
|
3428
|
+
const events = [];
|
|
3429
|
+
const ids = /* @__PURE__ */ new Map();
|
|
3430
|
+
for (const name of names) {
|
|
3431
|
+
if (name.startsWith(".tmp-")) continue;
|
|
3432
|
+
const match = EVENT_FILE.exec(name);
|
|
3433
|
+
if (!match) {
|
|
3434
|
+
problems.push({ file: name, reason: "unexpected-file", detail: "Not a runtime event file name." });
|
|
3435
|
+
continue;
|
|
3436
|
+
}
|
|
3437
|
+
let raw;
|
|
3438
|
+
let value;
|
|
3439
|
+
try {
|
|
3440
|
+
raw = await readFile4(join14(this.eventsDirectory, name), "utf8");
|
|
3441
|
+
value = JSON.parse(raw);
|
|
3442
|
+
} catch (error) {
|
|
3443
|
+
problems.push({ file: name, reason: "unreadable", detail: error instanceof Error ? error.message : String(error) });
|
|
3444
|
+
continue;
|
|
3445
|
+
}
|
|
3446
|
+
const read = readEvent(value);
|
|
3447
|
+
if (!read.ok) {
|
|
3448
|
+
problems.push({ file: name, reason: read.reason, detail: read.detail });
|
|
3449
|
+
continue;
|
|
3450
|
+
}
|
|
3451
|
+
const event = read.event;
|
|
3452
|
+
if (event.sequence !== Number(match[1])) {
|
|
3453
|
+
problems.push({ file: name, reason: "sequence-mismatch", detail: `File name does not match sequence ${String(event.sequence)}.` });
|
|
3454
|
+
continue;
|
|
3455
|
+
}
|
|
3456
|
+
if (event.projectId !== this.meta.projectId) {
|
|
3457
|
+
problems.push({ file: name, reason: "foreign-project", detail: `Event belongs to project ${event.projectId}.` });
|
|
3458
|
+
continue;
|
|
3459
|
+
}
|
|
3460
|
+
const previous = ids.get(event.id);
|
|
3461
|
+
if (previous !== void 0) {
|
|
3462
|
+
problems.push({ file: name, reason: "duplicate-id", detail: `Event id ${event.id} is also used by ${previous}.` });
|
|
3463
|
+
continue;
|
|
3464
|
+
}
|
|
3465
|
+
ids.set(event.id, name);
|
|
3466
|
+
events.push(event);
|
|
3467
|
+
}
|
|
3468
|
+
const gaps = [];
|
|
3469
|
+
let expected = 1;
|
|
3470
|
+
for (const event of events) {
|
|
3471
|
+
for (; expected < event.sequence; expected += 1) gaps.push(expected);
|
|
3472
|
+
expected = event.sequence + 1;
|
|
3473
|
+
}
|
|
3474
|
+
const highest = names.map((name) => EVENT_FILE.exec(name)?.[1]).filter((value) => value !== void 0).map(Number);
|
|
3475
|
+
this.lastSequence = Math.max(this.lastSequence, 0, ...highest);
|
|
3476
|
+
return {
|
|
3477
|
+
status: problems.length === 0 ? "ok" : "paused",
|
|
3478
|
+
events,
|
|
3479
|
+
problems,
|
|
3480
|
+
gaps,
|
|
3481
|
+
staleTemporaries: await staleTemporaries(this.eventsDirectory)
|
|
3482
|
+
};
|
|
3483
|
+
}
|
|
3484
|
+
/**
|
|
3485
|
+
* Validates and publishes one event at the next free sequence. A concurrent writer that took
|
|
3486
|
+
* that sequence causes reallocation, never replacement.
|
|
3487
|
+
*/
|
|
3488
|
+
async append(event) {
|
|
3489
|
+
if (this.lastSequence === 0) await this.replay();
|
|
3490
|
+
const base = this.lastSequence;
|
|
3491
|
+
for (let attempt = 0; attempt < MAX_SEQUENCE_ATTEMPTS; attempt += 1) {
|
|
3492
|
+
const sequence = base + 1 + attempt;
|
|
3493
|
+
const full = validateForWrite({
|
|
3494
|
+
...event,
|
|
3495
|
+
schema: EVENT_SCHEMA,
|
|
3496
|
+
version: 1,
|
|
3497
|
+
id: `evt_${randomBytes3(12).toString("hex")}`,
|
|
3498
|
+
sequence,
|
|
3499
|
+
projectId: this.meta.projectId,
|
|
3500
|
+
occurredAt: this.now().toISOString()
|
|
3501
|
+
});
|
|
3502
|
+
try {
|
|
3503
|
+
await publishOnce(this.eventsDirectory, eventName(sequence), `${JSON.stringify(full)}
|
|
3504
|
+
`);
|
|
3505
|
+
this.lastSequence = Math.max(this.lastSequence, sequence);
|
|
3506
|
+
return full;
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
if (!(error instanceof AlreadyPublishedError)) throw error;
|
|
3509
|
+
}
|
|
3510
|
+
}
|
|
3511
|
+
throw new Error(`Could not allocate an event sequence in ${this.eventsDirectory}.`);
|
|
3512
|
+
}
|
|
3513
|
+
/** Writes one disposable derived view. Cache files may be replaced; they are never authority. */
|
|
3514
|
+
async writeCache(name, content) {
|
|
3515
|
+
await mkdir3(this.cacheDirectory, { recursive: true, mode: 448 });
|
|
3516
|
+
const temporary = join14(this.cacheDirectory, `.tmp-${String(process.pid)}-${randomBytes3(6).toString("hex")}`);
|
|
3517
|
+
await writeFile2(temporary, content, { mode: PRIVATE_FILE_MODE });
|
|
3518
|
+
await rename2(temporary, join14(this.cacheDirectory, name));
|
|
3519
|
+
}
|
|
3520
|
+
async readCache(name) {
|
|
3521
|
+
try {
|
|
3522
|
+
return await readFile4(join14(this.cacheDirectory, name), "utf8");
|
|
3523
|
+
} catch {
|
|
3524
|
+
return void 0;
|
|
3525
|
+
}
|
|
3526
|
+
}
|
|
3527
|
+
/** Deletes every cached view; the next replay rebuilds them. */
|
|
3528
|
+
async clearCache() {
|
|
3529
|
+
await rm3(this.cacheDirectory, { recursive: true, force: true });
|
|
3530
|
+
await ensurePrivateDirectory(this.cacheDirectory);
|
|
3531
|
+
}
|
|
3532
|
+
/** Moves one named event file aside. Only an explicit operator action calls this. */
|
|
3533
|
+
async quarantine(file) {
|
|
3534
|
+
if (!EVENT_FILE.test(file) && !file.startsWith(".tmp-")) throw new Error(`${file} is not a runtime event file.`);
|
|
3535
|
+
await ensurePrivateDirectory(this.quarantineDirectory);
|
|
3536
|
+
await rename2(join14(this.eventsDirectory, file), join14(this.quarantineDirectory, file));
|
|
3537
|
+
}
|
|
3538
|
+
};
|
|
3539
|
+
}
|
|
3540
|
+
});
|
|
3541
|
+
|
|
3542
|
+
// src/runtime-state.ts
|
|
3543
|
+
import { readdir as readdir5 } from "fs/promises";
|
|
3544
|
+
import { join as join15 } from "path";
|
|
3545
|
+
async function inspectRuntimeState(roomHome) {
|
|
3546
|
+
const root = runtimeRoot(roomHome);
|
|
3547
|
+
const names = await readdir5(join15(root, "projects")).catch(() => []);
|
|
3548
|
+
const blockers = [];
|
|
3549
|
+
const stores = [];
|
|
3550
|
+
for (const name of names.sort()) {
|
|
3551
|
+
try {
|
|
3552
|
+
stores.push(await ProjectStore.open(join15(root, "projects", name)));
|
|
3553
|
+
} catch {
|
|
3554
|
+
blockers.push({ project: name, kind: "assignment", id: name, detail: "has unreadable project metadata" });
|
|
3555
|
+
}
|
|
3556
|
+
}
|
|
3557
|
+
for (const store of stores) {
|
|
3558
|
+
const replay = await store.replay();
|
|
3559
|
+
const projection = project(store.meta.projectId, replay.events);
|
|
3560
|
+
if (replay.status !== "ok" || projection.violations.length > 0) {
|
|
3561
|
+
blockers.push({ project: store.meta.canonicalRoot, kind: "assignment", id: store.meta.projectId, detail: "the project ledger is paused and cannot be proven quiet" });
|
|
3562
|
+
continue;
|
|
3563
|
+
}
|
|
3564
|
+
for (const blocker of quiescence(projection.state).blockers) blockers.push({ ...blocker, project: store.meta.canonicalRoot });
|
|
3565
|
+
}
|
|
3566
|
+
return { root, projects: names.length, blockers };
|
|
3567
|
+
}
|
|
3568
|
+
function describeBlockers(summary, limit = 5) {
|
|
3569
|
+
const shown = summary.blockers.slice(0, limit).map((blocker) => `${blocker.project}: ${blocker.kind} ${blocker.id} ${blocker.detail}`);
|
|
3570
|
+
const more = summary.blockers.length > limit ? `; and ${String(summary.blockers.length - limit)} more` : "";
|
|
3571
|
+
return `${shown.join("; ")}${more}`;
|
|
3572
|
+
}
|
|
3573
|
+
var init_runtime_state = __esm({
|
|
3574
|
+
"src/runtime-state.ts"() {
|
|
3575
|
+
"use strict";
|
|
3576
|
+
init_state();
|
|
3577
|
+
init_views();
|
|
3578
|
+
init_project();
|
|
3579
|
+
}
|
|
3580
|
+
});
|
|
3581
|
+
|
|
2256
3582
|
// src/commands.ts
|
|
2257
|
-
import { join as
|
|
2258
|
-
import { lstat as
|
|
3583
|
+
import { join as join16, sep as sep3 } from "path";
|
|
3584
|
+
import { lstat as lstat5, rm as rm4 } from "fs/promises";
|
|
3585
|
+
import { satisfies } from "semver";
|
|
2259
3586
|
function sessionOptions(options) {
|
|
2260
3587
|
const password = (options.env ?? process.env).PASEO_PASSWORD;
|
|
2261
3588
|
return { ...password === void 0 ? {} : { password }, ...options.factory ? { factory: options.factory } : {} };
|
|
@@ -2282,21 +3609,47 @@ function roleProfiles(agent, roles) {
|
|
|
2282
3609
|
notes: ROLE_NOTES[role]
|
|
2283
3610
|
}));
|
|
2284
3611
|
}
|
|
3612
|
+
function carrierSpec(layout) {
|
|
3613
|
+
return {
|
|
3614
|
+
id: CLAUDE_CARRIER_PLUGIN_ID,
|
|
3615
|
+
path: claudeCarrierPluginDir(layout),
|
|
3616
|
+
check: "claude.plugin",
|
|
3617
|
+
running: "Claude contract carrier plugin",
|
|
3618
|
+
enabledPass: "Paseo plugins are enabled for the required Claude contract carrier.",
|
|
3619
|
+
enabledFail: "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.",
|
|
3620
|
+
paseoRange: CLAUDE_CARRIER_PASEO_RANGE,
|
|
3621
|
+
rangeCheck: "claude.paseo-range",
|
|
3622
|
+
rangeRemedy: "Use a Paseo release inside that range, or upgrade paseo-room, then run setup again."
|
|
3623
|
+
};
|
|
3624
|
+
}
|
|
3625
|
+
function runtimeSpec(layout) {
|
|
3626
|
+
return {
|
|
3627
|
+
id: RUNTIME_PLUGIN_ID,
|
|
3628
|
+
path: runtimePluginDir(layout),
|
|
3629
|
+
check: "runtime.plugin",
|
|
3630
|
+
running: "Runtime coordination plugin (preview)",
|
|
3631
|
+
enabledPass: "Paseo plugins are enabled for the runtime coordination plugin.",
|
|
3632
|
+
enabledFail: "Runtime coordination is a trusted server plugin, but Paseo plugins are disabled. Plugins are trusted, unsandboxed code with access to the daemon machine.",
|
|
3633
|
+
paseoRange: RUNTIME_PASEO_RANGE,
|
|
3634
|
+
rangeCheck: "runtime.paseo-range",
|
|
3635
|
+
rangeRemedy: "Use a Paseo release inside that range, or run setup without --runtime."
|
|
3636
|
+
};
|
|
3637
|
+
}
|
|
2285
3638
|
async function buildDesired(layout, agents, roles, build = {}) {
|
|
2286
3639
|
const entries = [
|
|
2287
3640
|
{ kind: "dir", path: layout.roomHome },
|
|
2288
3641
|
{ kind: "dir", path: sharedRoom(layout) },
|
|
2289
3642
|
// Earlier versions generated a default workspace protocol template here. The room ships no
|
|
2290
3643
|
// default protocol now, so the old generated regular file is declared absent and removed.
|
|
2291
|
-
{ kind: "absent", path:
|
|
3644
|
+
{ kind: "absent", path: join16(sharedRoom(layout), "WORKSPACE_PROTOCOL.md") },
|
|
2292
3645
|
...roomSkillEntries(layout, agents)
|
|
2293
3646
|
];
|
|
2294
3647
|
const providers = {};
|
|
2295
3648
|
const profiles = [];
|
|
2296
3649
|
const checks = [];
|
|
2297
3650
|
const binaries = {};
|
|
2298
|
-
for (const
|
|
2299
|
-
const agent = AGENTS[
|
|
3651
|
+
for (const id2 of agents) {
|
|
3652
|
+
const agent = AGENTS[id2];
|
|
2300
3653
|
const plan = await agent.build(layout, roles, build);
|
|
2301
3654
|
entries.push(...plan.entries);
|
|
2302
3655
|
checks.push(...plan.checks);
|
|
@@ -2306,13 +3659,13 @@ async function buildDesired(layout, agents, roles, build = {}) {
|
|
|
2306
3659
|
const collisions = plan.entries.filter((entry) => credentialPaths.has(entry.path));
|
|
2307
3660
|
if (collisions.length > 0) {
|
|
2308
3661
|
checks.push(fail(
|
|
2309
|
-
`${
|
|
3662
|
+
`${id2}.credentials`,
|
|
2310
3663
|
`${agent.label} produced managed entries for preserve-only credential paths: ${collisions.map((entry) => entry.path).join(", ")}.`,
|
|
2311
3664
|
"This is an adapter defect; do not apply the plan."
|
|
2312
3665
|
));
|
|
2313
3666
|
}
|
|
2314
3667
|
if (plan.binary === void 0) continue;
|
|
2315
|
-
binaries[
|
|
3668
|
+
binaries[id2] = plan.binary;
|
|
2316
3669
|
Object.assign(providers, roleProviders(layout, agent, {
|
|
2317
3670
|
binary: plan.binary,
|
|
2318
3671
|
...plan.argv ? { argv: plan.argv } : {},
|
|
@@ -2320,33 +3673,42 @@ async function buildDesired(layout, agents, roles, build = {}) {
|
|
|
2320
3673
|
}, roles));
|
|
2321
3674
|
profiles.push(...roleProfiles(agent, roles));
|
|
2322
3675
|
}
|
|
2323
|
-
if (agents.every((
|
|
3676
|
+
if (agents.every((id2) => binaries[id2] !== void 0)) {
|
|
2324
3677
|
entries.push({
|
|
2325
3678
|
kind: "file",
|
|
2326
|
-
path:
|
|
3679
|
+
path: join16(layout.roomHome, AUTHENTICATION_GUIDE),
|
|
2327
3680
|
content: renderAuthenticationGuide(layout.roomHome, binaries, agents, roles)
|
|
2328
3681
|
});
|
|
2329
3682
|
checks.push(warn(
|
|
2330
3683
|
"room.authentication-not-validated",
|
|
2331
|
-
`Role authentication was not validated. Login instructions are managed at ${
|
|
3684
|
+
`Role authentication was not validated. Login instructions are managed at ${join16(layout.roomHome, AUTHENTICATION_GUIDE)}.`,
|
|
2332
3685
|
"After setup --apply, follow that guide or run: paseo-room auth login <agent> <role>"
|
|
2333
3686
|
));
|
|
2334
3687
|
}
|
|
2335
|
-
const
|
|
2336
|
-
if (
|
|
2337
|
-
|
|
2338
|
-
|
|
3688
|
+
const plugins = [];
|
|
3689
|
+
if (agents.includes("claude")) {
|
|
3690
|
+
plugins.push(carrierSpec(layout));
|
|
3691
|
+
entries.push(...claudeCarrierEntries(layout, roles));
|
|
3692
|
+
}
|
|
3693
|
+
let runtime;
|
|
3694
|
+
if (build.runtime === true) {
|
|
3695
|
+
plugins.push(runtimeSpec(layout));
|
|
3696
|
+
entries.push(...runtimePluginEntries(layout, agents, roles));
|
|
3697
|
+
runtime = { enabled: true, generation: renderRuntimeManifest(agents, roles).roomGeneration, schema: 1 };
|
|
3698
|
+
}
|
|
3699
|
+
entries.push({ kind: "file", path: join16(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest(), markerMemoryContract(agents, build.memoryContract ?? true), runtime) });
|
|
3700
|
+
return { entries, providers, profiles, checks, plugins, ...runtime === void 0 ? {} : { runtime } };
|
|
2339
3701
|
}
|
|
2340
3702
|
function contractProvenance(previous) {
|
|
2341
|
-
const
|
|
3703
|
+
const digest3 = contractDigest();
|
|
2342
3704
|
if (!previous) return [];
|
|
2343
|
-
if (previous.contract ===
|
|
2344
|
-
return [pass("room.contract", `Role contract generation ${
|
|
3705
|
+
if (previous.contract === digest3) {
|
|
3706
|
+
return [pass("room.contract", `Role contract generation ${digest3} matches this package.`)];
|
|
2345
3707
|
}
|
|
2346
3708
|
const held = previous.contract === void 0 ? `was installed before the room recorded its contract generation (package ${previous.version})` : `holds contract generation ${previous.contract}`;
|
|
2347
3709
|
return [warn(
|
|
2348
3710
|
"room.contract",
|
|
2349
|
-
`This room ${held}; this package renders ${
|
|
3711
|
+
`This room ${held}; this package renders ${digest3}. Managed role documents are rewritten by setup, and a running seat keeps the text it started with.`,
|
|
2350
3712
|
"Run: paseo-room setup --apply, then restart the affected seats."
|
|
2351
3713
|
)];
|
|
2352
3714
|
}
|
|
@@ -2369,14 +3731,14 @@ function fileDriftSummary(drifted) {
|
|
|
2369
3731
|
return `${count} ${subject} missing or outdated, and ${String(removals)} of them are present but suppressed by this room.`;
|
|
2370
3732
|
}
|
|
2371
3733
|
function fileDriftDetail(targets) {
|
|
2372
|
-
const catalogs = targets.filter((target) => target.endsWith(`${
|
|
3734
|
+
const catalogs = targets.filter((target) => target.endsWith(`${sep3}model-catalog.json`));
|
|
2373
3735
|
if (catalogs.length === 0) return "";
|
|
2374
3736
|
const count = catalogs.length === 1 ? "One of them is a generated Codex model catalog" : `${String(catalogs.length)} of them are generated Codex model catalogs`;
|
|
2375
3737
|
return ` ${count}, so those seats currently lack the scrubbed catalog closure rather than only older contract text.`;
|
|
2376
3738
|
}
|
|
2377
3739
|
async function safeDirectory(path, checks) {
|
|
2378
3740
|
try {
|
|
2379
|
-
const stat2 = await
|
|
3741
|
+
const stat2 = await lstat5(path);
|
|
2380
3742
|
if (stat2.isDirectory()) return true;
|
|
2381
3743
|
checks.push(fail(
|
|
2382
3744
|
"room.path-safety",
|
|
@@ -2400,10 +3762,10 @@ async function roomPathSafety(layout, agents, roles) {
|
|
|
2400
3762
|
const room = sharedRoom(layout);
|
|
2401
3763
|
const roomIsSafe = await safeDirectory(room, checks);
|
|
2402
3764
|
if (roomIsSafe && agents.length > 0) {
|
|
2403
|
-
const skills =
|
|
3765
|
+
const skills = join16(room, "skills");
|
|
2404
3766
|
if (await safeDirectory(skills, checks)) {
|
|
2405
3767
|
const source = roomSkillSource(layout);
|
|
2406
|
-
if (await safeDirectory(source, checks)) await safeDirectory(
|
|
3768
|
+
if (await safeDirectory(source, checks)) await safeDirectory(join16(source, "references"), checks);
|
|
2407
3769
|
}
|
|
2408
3770
|
const projections = skillProjectionRoot(layout);
|
|
2409
3771
|
if (await safeDirectory(projections, checks)) {
|
|
@@ -2413,16 +3775,16 @@ async function roomPathSafety(layout, agents, roles) {
|
|
|
2413
3775
|
}
|
|
2414
3776
|
}
|
|
2415
3777
|
}
|
|
2416
|
-
const rolesHome =
|
|
3778
|
+
const rolesHome = join16(layout.roomHome, "roles");
|
|
2417
3779
|
if (!await safeDirectory(rolesHome, checks)) return checks;
|
|
2418
3780
|
for (const agent of agents) {
|
|
2419
|
-
const agentHome =
|
|
3781
|
+
const agentHome = join16(rolesHome, agent);
|
|
2420
3782
|
if (!await safeDirectory(agentHome, checks)) continue;
|
|
2421
3783
|
for (const role of roles) await safeDirectory(roleHome(layout, agent, role), checks);
|
|
2422
3784
|
}
|
|
2423
3785
|
return checks;
|
|
2424
3786
|
}
|
|
2425
|
-
async function staleFrom(layout, previous, agents, roles) {
|
|
3787
|
+
async function staleFrom(layout, previous, agents, roles, runtime) {
|
|
2426
3788
|
if (!previous) return NOTHING_STALE;
|
|
2427
3789
|
const kept = new Set(agents.flatMap((agent) => roles.map((role) => providerId(agent, role))));
|
|
2428
3790
|
const providerIds = [];
|
|
@@ -2440,110 +3802,148 @@ async function staleFrom(layout, previous, agents, roles) {
|
|
|
2440
3802
|
return {
|
|
2441
3803
|
providerIds,
|
|
2442
3804
|
profileIds,
|
|
2443
|
-
|
|
3805
|
+
removePlugins: [
|
|
3806
|
+
...previous.agents.includes("claude") && !agents.includes("claude") ? [carrierSpec(layout)] : [],
|
|
3807
|
+
...previous.runtime?.enabled === true && !runtime ? [runtimeSpec(layout)] : []
|
|
3808
|
+
],
|
|
2444
3809
|
retainedDirectories
|
|
2445
3810
|
};
|
|
2446
3811
|
}
|
|
2447
|
-
function pluginPlan(
|
|
2448
|
-
const livePlugin = plugins.find((plugin) => plugin.id ===
|
|
2449
|
-
|
|
2450
|
-
if (!shouldManage) return { checks: [] };
|
|
2451
|
-
if (livePlugin !== void 0 && livePlugin.path !== expectedPath) {
|
|
3812
|
+
function pluginPlan(spec, wanted, plugins) {
|
|
3813
|
+
const livePlugin = plugins.find((plugin) => plugin.id === spec.id);
|
|
3814
|
+
if (livePlugin !== void 0 && livePlugin.path !== spec.path) {
|
|
2452
3815
|
return {
|
|
2453
3816
|
livePlugin,
|
|
2454
3817
|
checks: [fail(
|
|
2455
|
-
|
|
2456
|
-
`Plugin ${
|
|
3818
|
+
`${spec.check}.path`,
|
|
3819
|
+
`Plugin ${spec.id} is registered from ${livePlugin.path}, not the room-owned path ${spec.path}.`,
|
|
2457
3820
|
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2458
3821
|
)]
|
|
2459
3822
|
};
|
|
2460
3823
|
}
|
|
2461
|
-
if (
|
|
3824
|
+
if (wanted) {
|
|
2462
3825
|
const healthy = livePlugin?.enabled === true && livePlugin.status === "running" && livePlugin.error === void 0;
|
|
2463
3826
|
return {
|
|
2464
3827
|
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2465
|
-
operation: { action: livePlugin === void 0 ? "create" : healthy ? "noop" : "update", kind: "plugin", target:
|
|
3828
|
+
operation: { action: livePlugin === void 0 ? "create" : healthy ? "noop" : "update", kind: "plugin", target: spec.id },
|
|
2466
3829
|
checks: []
|
|
2467
3830
|
};
|
|
2468
3831
|
}
|
|
2469
3832
|
return {
|
|
2470
3833
|
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2471
|
-
operation: { action: livePlugin === void 0 ? "noop" : "remove", kind: "plugin", target:
|
|
3834
|
+
operation: { action: livePlugin === void 0 ? "noop" : "remove", kind: "plugin", target: spec.id },
|
|
2472
3835
|
checks: []
|
|
2473
3836
|
};
|
|
2474
3837
|
}
|
|
2475
|
-
async function planRoom(session, desired, stale
|
|
2476
|
-
const inspectPlugins = desired.
|
|
3838
|
+
async function planRoom(session, desired, stale) {
|
|
3839
|
+
const inspectPlugins = desired.plugins.length > 0 || stale.removePlugins.length > 0;
|
|
2477
3840
|
const [live, liveProfiles, plugins] = await Promise.all([
|
|
2478
3841
|
session.readProviders(),
|
|
2479
3842
|
session.readProfiles(),
|
|
2480
3843
|
inspectPlugins ? session.listPlugins() : Promise.resolve([])
|
|
2481
3844
|
]);
|
|
2482
3845
|
const byId = new Map(liveProfiles.map((entry) => [String(entry.id), entry]));
|
|
2483
|
-
const
|
|
3846
|
+
const planned = [
|
|
3847
|
+
...desired.plugins.map((spec) => pluginPlan(spec, true, plugins)),
|
|
3848
|
+
...stale.removePlugins.map((spec) => pluginPlan(spec, false, plugins))
|
|
3849
|
+
];
|
|
3850
|
+
const livePlugins = /* @__PURE__ */ new Map();
|
|
3851
|
+
for (const plugin of planned) if (plugin.livePlugin !== void 0) livePlugins.set(plugin.livePlugin.id, plugin.livePlugin);
|
|
2484
3852
|
return {
|
|
2485
3853
|
liveProfiles,
|
|
2486
|
-
|
|
2487
|
-
pluginChecks: plugin.checks,
|
|
3854
|
+
livePlugins,
|
|
3855
|
+
pluginChecks: planned.flatMap((plugin) => plugin.checks),
|
|
2488
3856
|
operations: [
|
|
2489
3857
|
...await planEntries(desired.entries),
|
|
2490
|
-
...Object.entries(desired.providers).map(([
|
|
2491
|
-
action: !(
|
|
3858
|
+
...Object.entries(desired.providers).map(([id2, provider]) => ({
|
|
3859
|
+
action: !(id2 in live) ? "create" : providerMatches(provider, live[id2]) ? "noop" : "update",
|
|
2492
3860
|
kind: "provider",
|
|
2493
|
-
target:
|
|
3861
|
+
target: id2
|
|
2494
3862
|
})),
|
|
2495
3863
|
...desired.profiles.map((profile) => ({
|
|
2496
3864
|
action: !byId.has(profile.id) ? "create" : profileMatches(profile, byId.get(profile.id)) ? "noop" : "update",
|
|
2497
3865
|
kind: "profile",
|
|
2498
3866
|
target: profile.id
|
|
2499
3867
|
})),
|
|
2500
|
-
...stale.providerIds.filter((
|
|
2501
|
-
...stale.profileIds.filter((
|
|
2502
|
-
...plugin.operation === void 0 ? [] : [plugin.operation]
|
|
3868
|
+
...stale.providerIds.filter((id2) => id2 in live).map((id2) => ({ action: "remove", kind: "provider", target: id2 })),
|
|
3869
|
+
...stale.profileIds.filter((id2) => byId.has(id2)).map((id2) => ({ action: "remove", kind: "profile", target: id2 })),
|
|
3870
|
+
...planned.flatMap((plugin) => plugin.operation === void 0 ? [] : [plugin.operation])
|
|
2503
3871
|
]
|
|
2504
3872
|
};
|
|
2505
3873
|
}
|
|
2506
|
-
function pluginsEnabledCheck(enabled) {
|
|
2507
|
-
return enabled ? pass(
|
|
2508
|
-
|
|
2509
|
-
|
|
3874
|
+
function pluginsEnabledCheck(spec, enabled) {
|
|
3875
|
+
return enabled ? pass(`${spec.check}.enabled`, spec.enabledPass) : fail(
|
|
3876
|
+
`${spec.check}.enabled`,
|
|
3877
|
+
spec.enabledFail,
|
|
2510
3878
|
"Review that trust boundary, enable plugins in Paseo Settings, reload Paseo, then run setup again."
|
|
2511
3879
|
);
|
|
2512
3880
|
}
|
|
2513
|
-
function pluginRuntimeCheck(
|
|
3881
|
+
function pluginRuntimeCheck(spec, plugin) {
|
|
2514
3882
|
if (plugin === void 0) {
|
|
2515
|
-
return fail(
|
|
3883
|
+
return fail(`${spec.check}.runtime`, `Plugin ${spec.id} is not registered.`, "Run: paseo-room setup --apply");
|
|
2516
3884
|
}
|
|
2517
|
-
if (plugin.path !==
|
|
3885
|
+
if (plugin.path !== spec.path) {
|
|
2518
3886
|
return fail(
|
|
2519
|
-
|
|
2520
|
-
`Plugin ${
|
|
3887
|
+
`${spec.check}.runtime`,
|
|
3888
|
+
`Plugin ${spec.id} is registered from ${plugin.path}, not ${spec.path}.`,
|
|
2521
3889
|
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2522
3890
|
);
|
|
2523
3891
|
}
|
|
2524
3892
|
if (!plugin.enabled || plugin.status !== "running" || plugin.error !== void 0) {
|
|
2525
3893
|
const detail = plugin.error === void 0 ? `status ${plugin.status}` : `${plugin.status}: ${plugin.error}`;
|
|
2526
|
-
return fail(
|
|
3894
|
+
return fail(`${spec.check}.runtime`, `Plugin ${spec.id} is not running (${detail}).`, "Run: paseo-room setup --apply");
|
|
2527
3895
|
}
|
|
2528
|
-
return pass(
|
|
3896
|
+
return pass(`${spec.check}.runtime`, `${spec.running} is running from ${spec.path}.`);
|
|
2529
3897
|
}
|
|
2530
|
-
async function
|
|
2531
|
-
|
|
2532
|
-
if (plan.
|
|
2533
|
-
return [];
|
|
3898
|
+
async function reconcilePlugins(session, desired, stale, plan, pending) {
|
|
3899
|
+
for (const spec of stale.removePlugins) {
|
|
3900
|
+
if (plan.livePlugins.has(spec.id)) await session.removePlugin(spec.id);
|
|
2534
3901
|
}
|
|
2535
|
-
|
|
2536
|
-
|
|
2537
|
-
plugin =
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
3902
|
+
const checks = [];
|
|
3903
|
+
for (const spec of desired.plugins) {
|
|
3904
|
+
let plugin = plan.livePlugins.get(spec.id);
|
|
3905
|
+
if (plugin === void 0) {
|
|
3906
|
+
plugin = await session.installPlugin(spec.path, spec.id);
|
|
3907
|
+
} else {
|
|
3908
|
+
if (!plugin.enabled) plugin = await session.enablePlugin(spec.id);
|
|
3909
|
+
const filesChanged = pending.some(
|
|
3910
|
+
(operation) => (operation.kind === "file" || operation.kind === "dir") && operation.target.startsWith(`${spec.path}${sep3}`)
|
|
3911
|
+
);
|
|
3912
|
+
const needsReload = filesChanged || plugin.status !== "running" || plugin.error !== void 0;
|
|
3913
|
+
if (needsReload) plugin = await session.reloadPlugin(spec.id);
|
|
3914
|
+
}
|
|
3915
|
+
checks.push(pluginRuntimeCheck(spec, plugin));
|
|
3916
|
+
}
|
|
3917
|
+
return checks;
|
|
3918
|
+
}
|
|
3919
|
+
function pluginRangeCheck(spec, version) {
|
|
3920
|
+
return satisfies(version, spec.paseoRange) ? pass(spec.rangeCheck, `Paseo ${version} is inside the ${spec.running} range ${spec.paseoRange}.`) : fail(spec.rangeCheck, `${spec.running} supports Paseo ${spec.paseoRange}; the running daemon is ${version}.`, spec.rangeRemedy);
|
|
3921
|
+
}
|
|
3922
|
+
async function runtimeDeselectionCheck(layout) {
|
|
3923
|
+
const summary = await inspectRuntimeState(layout.roomHome);
|
|
3924
|
+
if (summary.projects === 0) return [];
|
|
3925
|
+
if (summary.blockers.length > 0) {
|
|
3926
|
+
return [fail(
|
|
3927
|
+
"runtime.deselect",
|
|
3928
|
+
`Runtime coordination still has active or uncertain work: ${describeBlockers(summary)}.`,
|
|
3929
|
+
"Keep --runtime and finish, close or abandon that work first; the runtime state is preserved either way."
|
|
3930
|
+
)];
|
|
2545
3931
|
}
|
|
2546
|
-
return [
|
|
3932
|
+
return [warn(
|
|
3933
|
+
"runtime.state-retained",
|
|
3934
|
+
`Runtime coordination is quiet; its recorded state for ${String(summary.projects)} project(s) stays at ${summary.root}.`,
|
|
3935
|
+
"Export it with: paseo-room export --out <dir>, or keep it for a later --runtime."
|
|
3936
|
+
)];
|
|
3937
|
+
}
|
|
3938
|
+
async function runtimeRemovalWarning(layout) {
|
|
3939
|
+
const summary = await inspectRuntimeState(layout.roomHome).catch(() => void 0);
|
|
3940
|
+
if (summary === void 0 || summary.projects === 0) return [];
|
|
3941
|
+
const active = summary.blockers.length > 0 ? ` It still has active or uncertain work: ${describeBlockers(summary)}.` : "";
|
|
3942
|
+
return [warn(
|
|
3943
|
+
"room.remove.runtime-state",
|
|
3944
|
+
`Deleting ${layout.roomHome} also deletes runtime coordination history for ${String(summary.projects)} project(s).${active}`,
|
|
3945
|
+
"Export it first with: paseo-room export --out <dir>"
|
|
3946
|
+
)];
|
|
2547
3947
|
}
|
|
2548
3948
|
function markerMemoryContract(agents, memoryContract) {
|
|
2549
3949
|
return agents.includes("claude") ? memoryContract : void 0;
|
|
@@ -2569,23 +3969,29 @@ async function setup(options = {}) {
|
|
|
2569
3969
|
const pathSafety = await roomPathSafety(layout, safetyAgents, ROLES);
|
|
2570
3970
|
if (hasFailure(pathSafety)) return failed("setup", pathSafety);
|
|
2571
3971
|
const compatibilityAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2572
|
-
const
|
|
3972
|
+
const runtime = options.runtime === true;
|
|
3973
|
+
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(compatibilityAgents, runtime || previous?.runtime?.enabled === true));
|
|
2573
3974
|
if (!daemon.daemon) return failed("setup", daemon.checks);
|
|
2574
|
-
const
|
|
2575
|
-
const
|
|
3975
|
+
const version = daemon.daemon.version;
|
|
3976
|
+
const desired = await buildDesired(layout, agents, ROLES, { memoryContract, runtime });
|
|
3977
|
+
const stale = await staleFrom(layout, previous, agents, ROLES, runtime);
|
|
3978
|
+
const runtimeChecks = [
|
|
3979
|
+
...desired.plugins.map((spec) => pluginRangeCheck(spec, version)),
|
|
3980
|
+
...stale.removePlugins.some((spec) => spec.id === RUNTIME_PLUGIN_ID) ? await runtimeDeselectionCheck(layout) : []
|
|
3981
|
+
];
|
|
2576
3982
|
const retained = stale.retainedDirectories.length === 0 ? [] : [warn(
|
|
2577
3983
|
"room.stale-role-homes-preserved",
|
|
2578
3984
|
`Preserved ${String(stale.retainedDirectories.length)} deselected role homes because they may contain role-owned credentials or runtime state.`,
|
|
2579
3985
|
`Use paseo-room remove --apply to delete the entire room after reviewing its credential warning, or remove deselected homes manually: ${stale.retainedDirectories.join(", ")}`
|
|
2580
3986
|
)];
|
|
2581
|
-
const checks = [...daemon.checks, ...desired.checks, ...retained];
|
|
3987
|
+
const checks = [...daemon.checks, ...runtimeChecks, ...desired.checks, ...retained];
|
|
2582
3988
|
if (hasFailure(checks)) return failed("setup", checks);
|
|
2583
3989
|
return withSession(daemon.daemon, async (session) => {
|
|
2584
|
-
const
|
|
2585
|
-
const plan = await planRoom(session, desired, stale, expectedPluginPath);
|
|
3990
|
+
const plan = await planRoom(session, desired, stale);
|
|
2586
3991
|
const { operations, liveProfiles } = plan;
|
|
2587
3992
|
const pending = operations.filter((operation) => operation.action !== "noop");
|
|
2588
|
-
const
|
|
3993
|
+
const enabled = desired.plugins.length === 0 ? true : await session.pluginsEnabled();
|
|
3994
|
+
const pluginPreconditions = [...desired.plugins.map((spec) => pluginsEnabledCheck(spec, enabled)), ...plan.pluginChecks];
|
|
2589
3995
|
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...memoryContractDiagnostic(agents, memoryContract), ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2590
3996
|
if (hasFailure(preApplyDiagnostics) || !options.apply) {
|
|
2591
3997
|
return {
|
|
@@ -2596,7 +4002,7 @@ async function setup(options = {}) {
|
|
|
2596
4002
|
operations
|
|
2597
4003
|
};
|
|
2598
4004
|
}
|
|
2599
|
-
const markerPath =
|
|
4005
|
+
const markerPath = join16(layout.roomHome, MARKER);
|
|
2600
4006
|
const markerEntries = desired.entries.filter((entry) => entry.path === markerPath);
|
|
2601
4007
|
await applyEntries(desired.entries.filter((entry) => entry.path !== markerPath));
|
|
2602
4008
|
const ids = Object.keys(desired.providers);
|
|
@@ -2608,14 +4014,15 @@ async function setup(options = {}) {
|
|
|
2608
4014
|
await session.writeProfiles(appliedProfiles);
|
|
2609
4015
|
}
|
|
2610
4016
|
await session.refresh(ids);
|
|
2611
|
-
const pluginChecks = await
|
|
4017
|
+
const pluginChecks = await reconcilePlugins(session, desired, stale, plan, pending);
|
|
2612
4018
|
await applyEntries(markerEntries);
|
|
2613
4019
|
const appliedMarker = {
|
|
2614
4020
|
version: package_default.version,
|
|
2615
4021
|
agents: [...agents],
|
|
2616
4022
|
roles: [...ROLES],
|
|
2617
4023
|
contract: contractDigest(),
|
|
2618
|
-
...markerMemoryContract(agents, memoryContract) === false ? { claudeMemoryContract: false } : {}
|
|
4024
|
+
...markerMemoryContract(agents, memoryContract) === false ? { claudeMemoryContract: false } : {},
|
|
4025
|
+
...desired.runtime === void 0 ? {} : { runtime: desired.runtime }
|
|
2619
4026
|
};
|
|
2620
4027
|
const appliedChecks = [
|
|
2621
4028
|
...checks,
|
|
@@ -2647,17 +4054,23 @@ async function verify(options = {}) {
|
|
|
2647
4054
|
if (invalid.length > 0) return failed("verify", invalid);
|
|
2648
4055
|
const pathSafety = await roomPathSafety(layout, marker.agents, marker.roles);
|
|
2649
4056
|
if (hasFailure(pathSafety)) return failed("verify", pathSafety);
|
|
2650
|
-
const
|
|
4057
|
+
const runtime = marker.runtime?.enabled === true;
|
|
4058
|
+
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(marker.agents, runtime));
|
|
2651
4059
|
if (!daemon.daemon) return failed("verify", daemon.checks);
|
|
4060
|
+
const version = daemon.daemon.version;
|
|
2652
4061
|
const desired = await buildDesired(layout, marker.agents, marker.roles, {
|
|
2653
|
-
// The room's own recorded
|
|
2654
|
-
memoryContract: marker.claudeMemoryContract ?? true
|
|
4062
|
+
// The room's own recorded choices, so verify compares against what setup wrote.
|
|
4063
|
+
memoryContract: marker.claudeMemoryContract ?? true,
|
|
4064
|
+
runtime
|
|
2655
4065
|
});
|
|
2656
|
-
const
|
|
2657
|
-
|
|
4066
|
+
const runtimeChecks = [
|
|
4067
|
+
...desired.plugins.map((spec) => pluginRangeCheck(spec, version)),
|
|
4068
|
+
...runtime ? [marker.runtime?.generation === desired.runtime?.generation ? pass("runtime.generation", `Runtime manifest generation ${String(desired.runtime?.generation)} matches this package.`) : fail("runtime.generation", `This room was set up with runtime generation ${String(marker.runtime?.generation)}; this package generates ${String(desired.runtime?.generation)}.`, "Run: paseo-room setup --runtime --apply")] : []
|
|
4069
|
+
];
|
|
4070
|
+
const checks = [...daemon.checks, ...runtimeChecks, ...desired.checks];
|
|
4071
|
+
if (hasFailure(daemon.checks) || hasFailure(desired.checks)) return failed("verify", checks);
|
|
2658
4072
|
return withSession(daemon.daemon, async (session) => {
|
|
2659
|
-
const
|
|
2660
|
-
const plan = await planRoom(session, desired, NOTHING_STALE, expectedPluginPath);
|
|
4073
|
+
const plan = await planRoom(session, desired, NOTHING_STALE);
|
|
2661
4074
|
const { operations, liveProfiles } = plan;
|
|
2662
4075
|
const drifted = operations.filter((operation) => operation.action !== "noop");
|
|
2663
4076
|
const count = (kind) => drifted.filter((operation) => operation.kind === kind).length;
|
|
@@ -2665,11 +4078,12 @@ async function verify(options = {}) {
|
|
|
2665
4078
|
const profiles = count("profile");
|
|
2666
4079
|
const driftedFiles = drifted.filter((operation) => !["provider", "profile", "plugin"].includes(operation.kind));
|
|
2667
4080
|
const files = driftedFiles.length;
|
|
2668
|
-
const
|
|
2669
|
-
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
4081
|
+
const enabled = desired.plugins.length === 0 ? true : await session.pluginsEnabled();
|
|
4082
|
+
const pluginChecks = desired.plugins.flatMap((spec) => [
|
|
4083
|
+
pluginsEnabledCheck(spec, enabled),
|
|
4084
|
+
...plan.pluginChecks.filter((check) => check.id.startsWith(`${spec.check}.`)),
|
|
4085
|
+
pluginRuntimeCheck(spec, plan.livePlugins.get(spec.id))
|
|
4086
|
+
]);
|
|
2673
4087
|
const all = [
|
|
2674
4088
|
...checks,
|
|
2675
4089
|
files === 0 ? pass("room.files", "Every managed role file matches the current definition.") : fail("room.files", `${fileDriftSummary(driftedFiles)}${fileDriftDetail(driftedFiles.map((operation) => operation.target))}`, "Run: paseo-room setup --apply"),
|
|
@@ -2702,22 +4116,26 @@ async function remove(options = {}) {
|
|
|
2702
4116
|
if (invalid.length > 0) return failed("remove", invalid);
|
|
2703
4117
|
const ids = marker.agents.flatMap((agent) => marker.roles.map((role) => providerId(agent, role)));
|
|
2704
4118
|
const profiles = new Set(marker.agents.flatMap((agent) => marker.roles.map((role) => profileId(agent, role))));
|
|
2705
|
-
const
|
|
4119
|
+
const plugins = [
|
|
4120
|
+
...marker.agents.includes("claude") ? [carrierSpec(layout)] : [],
|
|
4121
|
+
...marker.runtime?.enabled === true ? [runtimeSpec(layout)] : []
|
|
4122
|
+
];
|
|
2706
4123
|
const operations = [
|
|
2707
|
-
...ids.map((
|
|
2708
|
-
...[...profiles].map((
|
|
2709
|
-
...
|
|
4124
|
+
...ids.map((id2) => ({ action: "remove", kind: "provider", target: id2 })),
|
|
4125
|
+
...[...profiles].map((id2) => ({ action: "remove", kind: "profile", target: id2 })),
|
|
4126
|
+
...plugins.map((spec) => ({ action: "remove", kind: "plugin", target: spec.id })),
|
|
2710
4127
|
{ action: "remove", kind: "dir", target: layout.roomHome }
|
|
2711
4128
|
];
|
|
2712
4129
|
const credentialWarning = warn(
|
|
2713
4130
|
"room.remove.credentials",
|
|
2714
4131
|
`Deleting ${layout.roomHome} will delete role-owned credential files stored inside it. Native OS keyring entries are not inspected or deleted and may remain. Operator agent-home credentials are untouched.`
|
|
2715
4132
|
);
|
|
4133
|
+
const runtimeWarnings = await runtimeRemovalWarning(layout);
|
|
2716
4134
|
if (!options.apply) {
|
|
2717
|
-
return { command: "remove", outcome: "changes-planned", changed: false, checks: [credentialWarning], operations };
|
|
4135
|
+
return { command: "remove", outcome: "changes-planned", changed: false, checks: [credentialWarning, ...runtimeWarnings], operations };
|
|
2718
4136
|
}
|
|
2719
4137
|
const daemon = await checkDaemon(layout, options.env);
|
|
2720
|
-
const checks = [credentialWarning];
|
|
4138
|
+
const checks = [credentialWarning, ...runtimeWarnings];
|
|
2721
4139
|
if (!daemon.daemon) {
|
|
2722
4140
|
return failed("remove", [
|
|
2723
4141
|
credentialWarning,
|
|
@@ -2731,13 +4149,15 @@ async function remove(options = {}) {
|
|
|
2731
4149
|
}
|
|
2732
4150
|
try {
|
|
2733
4151
|
await withSession(daemon.daemon, async (session) => {
|
|
2734
|
-
if (
|
|
2735
|
-
const
|
|
2736
|
-
const
|
|
2737
|
-
|
|
2738
|
-
throw new Error(`refusing to remove foreign plugin path ${plugin.path}`);
|
|
4152
|
+
if (plugins.length > 0) {
|
|
4153
|
+
const live2 = await session.listPlugins();
|
|
4154
|
+
for (const spec of plugins) {
|
|
4155
|
+
const plugin = live2.find((entry) => entry.id === spec.id);
|
|
4156
|
+
if (plugin !== void 0 && plugin.path !== spec.path) throw new Error(`refusing to remove foreign plugin path ${plugin.path}`);
|
|
4157
|
+
}
|
|
4158
|
+
for (const spec of plugins) {
|
|
4159
|
+
if (live2.some((entry) => entry.id === spec.id)) await session.removePlugin(spec.id);
|
|
2739
4160
|
}
|
|
2740
|
-
if (plugin !== void 0) await session.removePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2741
4161
|
}
|
|
2742
4162
|
await session.removeProviders(ids);
|
|
2743
4163
|
const live = await session.readProfiles();
|
|
@@ -2756,7 +4176,7 @@ async function remove(options = {}) {
|
|
|
2756
4176
|
]);
|
|
2757
4177
|
}
|
|
2758
4178
|
try {
|
|
2759
|
-
await
|
|
4179
|
+
await rm4(layout.roomHome, { recursive: true, force: true });
|
|
2760
4180
|
} catch (error) {
|
|
2761
4181
|
const detail = error instanceof Error ? error.message : "unknown filesystem error";
|
|
2762
4182
|
checks.push(fail(
|
|
@@ -2787,9 +4207,11 @@ var init_commands = __esm({
|
|
|
2787
4207
|
init_instructions();
|
|
2788
4208
|
init_skills();
|
|
2789
4209
|
init_room();
|
|
4210
|
+
init_runtime();
|
|
4211
|
+
init_runtime_state();
|
|
2790
4212
|
init_roles();
|
|
2791
4213
|
AGENTS = { codex: codexAgent, claude: claudeAgent, pi: piAgent };
|
|
2792
|
-
NOTHING_STALE = { providerIds: [], profileIds: [],
|
|
4214
|
+
NOTHING_STALE = { providerIds: [], profileIds: [], removePlugins: [], retainedDirectories: [] };
|
|
2793
4215
|
}
|
|
2794
4216
|
});
|
|
2795
4217
|
|
|
@@ -2840,7 +4262,15 @@ async function runWizard(prompts, emit, write, options = {}, onAction) {
|
|
|
2840
4262
|
if (typeof carrier === "symbol") return cancelled();
|
|
2841
4263
|
claudeMemoryContract = carrier === "both";
|
|
2842
4264
|
}
|
|
2843
|
-
const
|
|
4265
|
+
const coordination = await prompts.select({
|
|
4266
|
+
message: "Runtime coordination (preview): track assignments, Peer reports and writer ownership through a trusted Paseo plugin?",
|
|
4267
|
+
options: [
|
|
4268
|
+
{ value: "off", label: "No \u2014 keep the room as it is (default)" },
|
|
4269
|
+
{ value: "runtime", label: "Yes \u2014 install the paseo-room-runtime plugin (trusted, unsandboxed code)" }
|
|
4270
|
+
]
|
|
4271
|
+
});
|
|
4272
|
+
if (typeof coordination === "symbol") return cancelled();
|
|
4273
|
+
const setupOptions = { ...options, agents, claudeMemoryContract, ...coordination === "runtime" ? { runtime: true } : {} };
|
|
2844
4274
|
const preview = await setup(setupOptions);
|
|
2845
4275
|
const status = emit(preview);
|
|
2846
4276
|
if (preview.outcome !== "changes-planned") return status;
|
|
@@ -2866,9 +4296,105 @@ var init_wizard = __esm({
|
|
|
2866
4296
|
init_package();
|
|
2867
4297
|
init_auth();
|
|
2868
4298
|
init_commands();
|
|
2869
|
-
init_fsops();
|
|
2870
4299
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
2871
4300
|
|
|
4301
|
+
// src/export.ts
|
|
4302
|
+
init_package();
|
|
4303
|
+
init_layout();
|
|
4304
|
+
init_result();
|
|
4305
|
+
init_schema();
|
|
4306
|
+
init_project();
|
|
4307
|
+
import { copyFile, mkdir as mkdir4, readdir as readdir6, readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
|
|
4308
|
+
import { join as join17, resolve as resolve4 } from "path";
|
|
4309
|
+
var LIMITATION = "Briefs, answers, reports and gate commands are stored as written and cannot be proven secret-free; review the export before sharing it.";
|
|
4310
|
+
async function isEmptyOrAbsent(path) {
|
|
4311
|
+
try {
|
|
4312
|
+
return (await readdir6(path)).length === 0;
|
|
4313
|
+
} catch {
|
|
4314
|
+
return true;
|
|
4315
|
+
}
|
|
4316
|
+
}
|
|
4317
|
+
async function exportRuntime(options = {}) {
|
|
4318
|
+
const layout = resolveLayout(options, options.env);
|
|
4319
|
+
const root = runtimeRoot(layout.roomHome);
|
|
4320
|
+
let projects;
|
|
4321
|
+
try {
|
|
4322
|
+
projects = (await readdir6(join17(root, "projects"))).sort();
|
|
4323
|
+
} catch {
|
|
4324
|
+
projects = [];
|
|
4325
|
+
}
|
|
4326
|
+
if (projects.length === 0) {
|
|
4327
|
+
return failed("export", [fail("export.nothing", `No runtime state was found under ${root}.`, "Runtime coordination records state only after setup --runtime and a first assignment.")]);
|
|
4328
|
+
}
|
|
4329
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
4330
|
+
const destination = resolve4(options.out ?? join17(root, "exports", stamp));
|
|
4331
|
+
if (!await isEmptyOrAbsent(destination)) {
|
|
4332
|
+
return failed("export", [fail("export.destination", `${destination} already exists and is not empty.`, "Choose a new or empty --out directory.")]);
|
|
4333
|
+
}
|
|
4334
|
+
const operations = [{ action: "create", kind: "dir", target: destination }];
|
|
4335
|
+
const summary = [];
|
|
4336
|
+
const copies = [];
|
|
4337
|
+
for (const name of projects) {
|
|
4338
|
+
const source = join17(root, "projects", name);
|
|
4339
|
+
const target = join17(destination, "projects", name);
|
|
4340
|
+
let meta;
|
|
4341
|
+
try {
|
|
4342
|
+
meta = JSON.parse(await readFile5(join17(source, "meta.json"), "utf8"));
|
|
4343
|
+
} catch {
|
|
4344
|
+
meta = {};
|
|
4345
|
+
}
|
|
4346
|
+
const entry = { projectId: meta.projectId ?? name, canonicalRoot: meta.canonicalRoot ?? "unknown", events: 0, omitted: [], gateOutputs: 0 };
|
|
4347
|
+
copies.push([join17(source, "meta.json"), join17(target, "meta.json")]);
|
|
4348
|
+
for (const file of (await readdir6(join17(source, "events")).catch(() => [])).sort()) {
|
|
4349
|
+
if (file.startsWith(".tmp-")) continue;
|
|
4350
|
+
let valid2;
|
|
4351
|
+
try {
|
|
4352
|
+
valid2 = readEvent(JSON.parse(await readFile5(join17(source, "events", file), "utf8"))).ok;
|
|
4353
|
+
} catch {
|
|
4354
|
+
valid2 = false;
|
|
4355
|
+
}
|
|
4356
|
+
if (valid2) {
|
|
4357
|
+
entry.events += 1;
|
|
4358
|
+
copies.push([join17(source, "events", file), join17(target, "events", file)]);
|
|
4359
|
+
} else entry.omitted.push(file);
|
|
4360
|
+
}
|
|
4361
|
+
for (const file of (await readdir6(join17(source, "gates")).catch(() => [])).sort()) {
|
|
4362
|
+
if (file.endsWith(".result.json")) copies.push([join17(source, "gates", file), join17(target, "gates", file)]);
|
|
4363
|
+
else if (file.endsWith(".log") && options.includeGateOutput === true) {
|
|
4364
|
+
entry.gateOutputs += 1;
|
|
4365
|
+
copies.push([join17(source, "gates", file), join17(target, "gates", file)]);
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
summary.push(entry);
|
|
4369
|
+
}
|
|
4370
|
+
operations.push(...copies.map(([, target]) => ({ action: "create", kind: "file", target })));
|
|
4371
|
+
operations.push({ action: "create", kind: "file", target: join17(destination, "summary.json") });
|
|
4372
|
+
const checks = [
|
|
4373
|
+
warn("export.limitation", LIMITATION),
|
|
4374
|
+
...options.includeGateOutput === true ? [warn("export.gate-output", "Gate output tails are included; they are best-effort masked only.")] : [],
|
|
4375
|
+
...summary.filter((entry) => entry.omitted.length > 0).map((entry) => warn("export.omitted", `Project ${entry.projectId}: ${String(entry.omitted.length)} unreadable event file(s) were left out by name: ${entry.omitted.join(", ")}.`))
|
|
4376
|
+
];
|
|
4377
|
+
if (!options.apply) return { command: "export", outcome: "changes-planned", changed: false, checks, operations };
|
|
4378
|
+
await mkdir4(destination, { recursive: true, mode: 448 });
|
|
4379
|
+
for (const [from, to] of copies) {
|
|
4380
|
+
await mkdir4(join17(to, ".."), { recursive: true, mode: 448 });
|
|
4381
|
+
await copyFile(from, to);
|
|
4382
|
+
}
|
|
4383
|
+
await writeFile3(join17(destination, "summary.json"), `${JSON.stringify({
|
|
4384
|
+
schema: 1,
|
|
4385
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4386
|
+
packageVersion: package_default.version,
|
|
4387
|
+
gateOutputIncluded: options.includeGateOutput === true,
|
|
4388
|
+
limitation: LIMITATION,
|
|
4389
|
+
projects: summary
|
|
4390
|
+
}, null, 2)}
|
|
4391
|
+
`, { mode: 384 });
|
|
4392
|
+
return { command: "export", outcome: "ok", changed: true, checks: [...checks, pass("export.written", `Exported ${String(summary.length)} project(s) to ${destination}.`)], operations };
|
|
4393
|
+
}
|
|
4394
|
+
|
|
4395
|
+
// src/cli.ts
|
|
4396
|
+
init_fsops();
|
|
4397
|
+
|
|
2872
4398
|
// src/render.ts
|
|
2873
4399
|
function renderJson(result) {
|
|
2874
4400
|
return `${JSON.stringify({ schemaVersion: 2, ...result }, null, 2)}
|
|
@@ -2901,7 +4427,7 @@ init_roles();
|
|
|
2901
4427
|
init_prompts();
|
|
2902
4428
|
init_skills();
|
|
2903
4429
|
function collectAgent(value, previous) {
|
|
2904
|
-
const agent = AGENT_IDS.find((
|
|
4430
|
+
const agent = AGENT_IDS.find((id2) => id2 === value);
|
|
2905
4431
|
if (!agent) throw new InvalidArgumentError(`Unknown agent: ${value}`);
|
|
2906
4432
|
const seated = previous ?? [];
|
|
2907
4433
|
return seated.includes(agent) ? seated : [...seated, agent];
|
|
@@ -2920,7 +4446,8 @@ function optionsFrom(raw, base) {
|
|
|
2920
4446
|
...agents ? { agents } : {},
|
|
2921
4447
|
...raw.apply === true ? { apply: true } : {},
|
|
2922
4448
|
// Commander defaults a --no- flag to true, so only an explicit opt-out is carried.
|
|
2923
|
-
...raw.claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
4449
|
+
...raw.claudeMemoryContract === false ? { claudeMemoryContract: false } : {},
|
|
4450
|
+
...raw.runtime === true ? { runtime: true } : {}
|
|
2924
4451
|
};
|
|
2925
4452
|
}
|
|
2926
4453
|
function failedFromThrown(command, error) {
|
|
@@ -2950,7 +4477,7 @@ function failedFromThrown(command, error) {
|
|
|
2950
4477
|
}
|
|
2951
4478
|
async function runCli(argv, output, context = {}) {
|
|
2952
4479
|
const password = (context.options?.env ?? process.env).PASEO_PASSWORD;
|
|
2953
|
-
const redact = (
|
|
4480
|
+
const redact = (text3) => password ? text3.split(password).join("[redacted]") : text3;
|
|
2954
4481
|
let json = false;
|
|
2955
4482
|
const emit = (result) => {
|
|
2956
4483
|
output.stdout(redact(json ? renderJson(result) : renderHuman(result)));
|
|
@@ -2969,8 +4496,8 @@ async function runCli(argv, output, context = {}) {
|
|
|
2969
4496
|
return await runWizard2(
|
|
2970
4497
|
context.prompts ?? terminalPrompts2,
|
|
2971
4498
|
emit,
|
|
2972
|
-
(
|
|
2973
|
-
output.stdout(
|
|
4499
|
+
(text3) => {
|
|
4500
|
+
output.stdout(text3);
|
|
2974
4501
|
},
|
|
2975
4502
|
base,
|
|
2976
4503
|
(action) => {
|
|
@@ -2981,10 +4508,10 @@ async function runCli(argv, output, context = {}) {
|
|
|
2981
4508
|
return emit(failedFromThrown(wizardCommand, error));
|
|
2982
4509
|
}
|
|
2983
4510
|
}
|
|
2984
|
-
const program = new Command().name("paseo-room").description("Configure Codex/Claude/Pi role homes in $HOME and register them with your local Paseo daemon.").version(package_default.version).usage("<setup|verify|remove> [options]\n auth login <codex|claude|pi> <supervisor|lead|peer> [options]").argument("<command>", "setup | verify | remove | auth").argument("[command-arguments...]", "auth login <agent> <role>").option("--agent <agent>", "codex, claude, or pi; repeat to combine (default: codex)", collectAgent).option("--apply", "actually make the changes (default: dry run)").option("--json", "machine-readable output").option("--no-claude-memory-contract", "omit the role contract from Claude role CLAUDE.md files, leaving the room plugin as the only Claude carrier").option("--room-home <path>", "where role homes are written (default: ~/.paseo-room)").option("--codex-home <path>", "source Codex config (default: ~/.codex)").option("--claude-home <path>", "source Claude Code config (default: ~/.claude)").option("--pi-home <path>", "source Pi config (default: ~/.pi/agent)").option("--codex-bin <path>", "Codex executable (default: found on PATH)").option("--claude-bin <path>", "Claude Code executable (default: found on PATH)").option("--pi-bin <path>", "Pi executable (default: found on PATH)").option("--paseo-bin <path>", "Paseo executable (default: found on PATH)").configureOutput({ writeOut: (
|
|
2985
|
-
output.stdout(
|
|
2986
|
-
}, writeErr: (
|
|
2987
|
-
output.stderr(
|
|
4511
|
+
const program = new Command().name("paseo-room").description("Configure Codex/Claude/Pi role homes in $HOME and register them with your local Paseo daemon.").version(package_default.version).usage("<setup|verify|remove|export> [options]\n auth login <codex|claude|pi> <supervisor|lead|peer> [options]").argument("<command>", "setup | verify | remove | export | auth").argument("[command-arguments...]", "auth login <agent> <role>").option("--agent <agent>", "codex, claude, or pi; repeat to combine (default: codex)", collectAgent).option("--apply", "actually make the changes (default: dry run)").option("--json", "machine-readable output").option("--no-claude-memory-contract", "omit the role contract from Claude role CLAUDE.md files, leaving the room plugin as the only Claude carrier").option("--runtime", "opt in to runtime coordination (preview): installs the trusted paseo-room-runtime plugin; omit it to deselect").option("--out <dir>", "export: destination directory (default: a new directory under the room's runtime exports)").option("--include-gate-output", "export: also copy bounded gate output tails (best-effort masked)").option("--room-home <path>", "where role homes are written (default: ~/.paseo-room)").option("--codex-home <path>", "source Codex config (default: ~/.codex)").option("--claude-home <path>", "source Claude Code config (default: ~/.claude)").option("--pi-home <path>", "source Pi config (default: ~/.pi/agent)").option("--codex-bin <path>", "Codex executable (default: found on PATH)").option("--claude-bin <path>", "Claude Code executable (default: found on PATH)").option("--pi-bin <path>", "Pi executable (default: found on PATH)").option("--paseo-bin <path>", "Paseo executable (default: found on PATH)").configureOutput({ writeOut: (text3) => {
|
|
4512
|
+
output.stdout(text3);
|
|
4513
|
+
}, writeErr: (text3) => {
|
|
4514
|
+
output.stderr(text3);
|
|
2988
4515
|
} }).exitOverride();
|
|
2989
4516
|
let command;
|
|
2990
4517
|
let commandArguments;
|
|
@@ -3004,10 +4531,24 @@ async function runCli(argv, output, context = {}) {
|
|
|
3004
4531
|
try {
|
|
3005
4532
|
if (raw.claudeMemoryContract === false && (command === "verify" || command === "remove")) {
|
|
3006
4533
|
output.stderr(`paseo-room ${command}: --no-claude-memory-contract is a setup choice recorded in the room marker; ${command} reads it from there.
|
|
4534
|
+
`);
|
|
4535
|
+
return 2;
|
|
4536
|
+
}
|
|
4537
|
+
if ((raw.out !== void 0 || raw.includeGateOutput === true) && command !== "export") {
|
|
4538
|
+
output.stderr(`paseo-room ${command}: --out and --include-gate-output only apply to export.
|
|
4539
|
+
`);
|
|
4540
|
+
return 2;
|
|
4541
|
+
}
|
|
4542
|
+
if (raw.runtime === true && (command === "verify" || command === "remove" || command === "export")) {
|
|
4543
|
+
output.stderr(`paseo-room ${command}: --runtime is a setup choice recorded in the room marker; ${command} reads it from there.
|
|
3007
4544
|
`);
|
|
3008
4545
|
return 2;
|
|
3009
4546
|
}
|
|
3010
4547
|
if (command === "auth") {
|
|
4548
|
+
if (raw.runtime === true) {
|
|
4549
|
+
output.stderr("paseo-room auth login only authenticates a role and does not use --runtime.\n");
|
|
4550
|
+
return 2;
|
|
4551
|
+
}
|
|
3011
4552
|
if (raw.claudeMemoryContract === false) {
|
|
3012
4553
|
output.stderr("paseo-room auth login only authenticates a role and does not use --no-claude-memory-contract.\n");
|
|
3013
4554
|
return 2;
|
|
@@ -3042,21 +4583,28 @@ async function runCli(argv, output, context = {}) {
|
|
|
3042
4583
|
if (command === "setup") return emit(await setup(options));
|
|
3043
4584
|
if (command === "verify") return emit(await verify(options));
|
|
3044
4585
|
if (command === "remove") return emit(await remove(options));
|
|
4586
|
+
if (command === "export") {
|
|
4587
|
+
return emit(await exportRuntime({
|
|
4588
|
+
...options,
|
|
4589
|
+
...typeof raw.out === "string" && raw.out.trim() !== "" ? { out: raw.out.trim() } : {},
|
|
4590
|
+
...raw.includeGateOutput === true ? { includeGateOutput: true } : {}
|
|
4591
|
+
}));
|
|
4592
|
+
}
|
|
3045
4593
|
} catch (error) {
|
|
3046
4594
|
return emit(failedFromThrown(command, error));
|
|
3047
4595
|
}
|
|
3048
|
-
output.stderr(`paseo-room: unknown command "${command}". Try: setup, verify, remove, or auth login.
|
|
4596
|
+
output.stderr(`paseo-room: unknown command "${command}". Try: setup, verify, remove, export, or auth login.
|
|
3049
4597
|
`);
|
|
3050
4598
|
return 2;
|
|
3051
4599
|
}
|
|
3052
4600
|
|
|
3053
4601
|
// src/index.ts
|
|
3054
4602
|
process.exitCode = await runCli(process.argv.slice(2), {
|
|
3055
|
-
stdout: (
|
|
3056
|
-
process.stdout.write(
|
|
4603
|
+
stdout: (text3) => {
|
|
4604
|
+
process.stdout.write(text3);
|
|
3057
4605
|
},
|
|
3058
|
-
stderr: (
|
|
3059
|
-
process.stderr.write(
|
|
4606
|
+
stderr: (text3) => {
|
|
4607
|
+
process.stderr.write(text3);
|
|
3060
4608
|
}
|
|
3061
4609
|
});
|
|
3062
4610
|
//# sourceMappingURL=index.js.map
|