paseo-room 0.4.1 → 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 +178 -73
- package/dist/index.js +2069 -400
- package/dist/index.js.map +1 -1
- package/dist/plugin-assets/paseo-plugin.json +1 -1
- package/dist/prompts/contract/lead.md +152 -0
- package/dist/prompts/contract/peer.md +74 -0
- package/dist/prompts/contract/shared-authority.md +33 -0
- package/dist/prompts/contract/shared-seat-identity.md +18 -0
- package/dist/prompts/contract/supervisor.md +89 -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/dist/skills/paseo-project-onboarding/SKILL.md +122 -0
- package/dist/skills/paseo-project-onboarding/references/workspace-protocol-template.md +56 -0
- package/package.json +9 -4
- package/dist/prompts/contract/lead/complete-peer-brief.md +0 -32
- package/dist/prompts/contract/lead/independent-review.md +0 -9
- package/dist/prompts/contract/lead/moving-write-ownership.md +0 -13
- package/dist/prompts/contract/lead/peer-seat-lifecycle.md +0 -39
- package/dist/prompts/contract/lead/project-technical-ownership.md +0 -12
- package/dist/prompts/contract/lead/technical-acceptance.md +0 -6
- package/dist/prompts/contract/peer/assignment-scope.md +0 -10
- package/dist/prompts/contract/peer/bounded-outcome.md +0 -13
- package/dist/prompts/contract/peer/independent-judgment.md +0 -11
- package/dist/prompts/contract/peer/no-orchestration.md +0 -5
- package/dist/prompts/contract/peer/no-self-acceptance.md +0 -5
- package/dist/prompts/contract/peer/reproducible-handoff.md +0 -17
- package/dist/prompts/contract/shared/evidence-and-event-waiting.md +0 -7
- package/dist/prompts/contract/shared/human-authority.md +0 -8
- package/dist/prompts/contract/shared/scope-and-unrelated-work.md +0 -4
- package/dist/prompts/contract/shared-supervisor-lead/workspace-protocol-precedence.md +0 -18
- package/dist/prompts/contract/supervisor/directive-integrity.md +0 -5
- package/dist/prompts/contract/supervisor/escalation-boundaries.md +0 -5
- package/dist/prompts/contract/supervisor/lead-discovery-and-recovery.md +0 -52
- package/dist/prompts/contract/supervisor/observation-and-advice.md +0 -13
- package/dist/prompts/contract/supervisor/technical-non-interference.md +0 -8
- package/dist/prompts/documents/workspace.md +0 -6
- package/dist/prompts/workspace/repository-conventions.md +0 -7
- package/dist/prompts/workspace/review.md +0 -4
- package/dist/prompts/workspace/topology.md +0 -13
- package/dist/prompts/workspace/verification.md +0 -10
- /package/dist/prompts/contract/{shared-lead-peer/challenge-signals.md → challenge-signals.md} +0 -0
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/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,15 +163,9 @@ function preservedCredentialCheck(input) {
|
|
|
158
163
|
function presentNames(names, supported) {
|
|
159
164
|
return supported.filter((name) => names.has(name));
|
|
160
165
|
}
|
|
161
|
-
function
|
|
166
|
+
function ambientNamesCheck(id2, agent, role, names) {
|
|
162
167
|
return warn(
|
|
163
|
-
`${
|
|
164
|
-
`${agent} ${role} auth: configured structurally; ${names.join(", ")} ${names.length === 1 ? "is" : "are"} present by name. Values, token validity, and freshness were not checked.`
|
|
165
|
-
);
|
|
166
|
-
}
|
|
167
|
-
function ambientNamesCheck(id, agent, role, names) {
|
|
168
|
-
return warn(
|
|
169
|
-
`${id}.ambient-auth-unverifiable`,
|
|
168
|
+
`${id2}.ambient-auth-unverifiable`,
|
|
170
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.`
|
|
171
170
|
);
|
|
172
171
|
}
|
|
@@ -220,13 +219,13 @@ function layoutChecks(layout, agents) {
|
|
|
220
219
|
"Point --room-home at a dedicated directory, by default ~/.paseo-room."
|
|
221
220
|
));
|
|
222
221
|
}
|
|
223
|
-
for (const
|
|
224
|
-
const home = layout.agentHome[
|
|
222
|
+
for (const id2 of agents) {
|
|
223
|
+
const home = layout.agentHome[id2];
|
|
225
224
|
if (contains(layout.roomHome, home) || contains(home, layout.roomHome)) {
|
|
226
225
|
checks.push(fail(
|
|
227
|
-
`${
|
|
228
|
-
`The ${
|
|
229
|
-
`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).`
|
|
230
229
|
));
|
|
231
230
|
}
|
|
232
231
|
}
|
|
@@ -450,13 +449,14 @@ function providerLabel(agent, role) {
|
|
|
450
449
|
const title2 = (value) => value.charAt(0).toUpperCase() + value.slice(1);
|
|
451
450
|
return `${title2(agent)} ${title2(role)}`;
|
|
452
451
|
}
|
|
453
|
-
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;
|
|
454
453
|
var init_roles = __esm({
|
|
455
454
|
"src/roles.ts"() {
|
|
456
455
|
"use strict";
|
|
457
456
|
ROLES = ["supervisor", "lead", "peer"];
|
|
458
457
|
AGENT_IDS = ["codex", "claude", "pi"];
|
|
459
458
|
ROLE_PASEO_TOOLS = { supervisor: true, lead: true, peer: false };
|
|
459
|
+
ROLE_PEER_REPORTING = { supervisor: false, lead: false, peer: true };
|
|
460
460
|
ROLE_THINKING = { supervisor: "low", lead: "high", peer: "high" };
|
|
461
461
|
DELEGATING_THINKING = ["ultra", "ultracode"];
|
|
462
462
|
ROLE_ICON = { supervisor: "eye", lead: "compass", peer: "code" };
|
|
@@ -472,14 +472,15 @@ var init_roles = __esm({
|
|
|
472
472
|
// src/room.ts
|
|
473
473
|
import { join as join3 } from "path";
|
|
474
474
|
import { z } from "zod";
|
|
475
|
-
function renderMarker(version, agents, roles, contract, claudeMemoryContract) {
|
|
475
|
+
function renderMarker(version, agents, roles, contract, claudeMemoryContract, runtime) {
|
|
476
476
|
return JSON.stringify({
|
|
477
477
|
version,
|
|
478
478
|
agents: [...agents],
|
|
479
479
|
roles: [...roles],
|
|
480
480
|
...contract === void 0 ? {} : { contract },
|
|
481
481
|
// Written only when it differs from the default, so an unchanged room's marker is unchanged.
|
|
482
|
-
...claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
482
|
+
...claudeMemoryContract === false ? { claudeMemoryContract: false } : {},
|
|
483
|
+
...runtime === void 0 ? {} : { runtime }
|
|
483
484
|
}, null, 2) + "\n";
|
|
484
485
|
}
|
|
485
486
|
async function readMarker(layout) {
|
|
@@ -513,7 +514,12 @@ var init_room = __esm({
|
|
|
513
514
|
* leaving the plugin as its only Claude carrier. Optional and defaulted to true so a room
|
|
514
515
|
* written before the option existed keeps parsing and keeps its current behaviour.
|
|
515
516
|
*/
|
|
516
|
-
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()
|
|
517
523
|
});
|
|
518
524
|
}
|
|
519
525
|
});
|
|
@@ -683,12 +689,12 @@ async function loginRole(agentValue, roleValue, options, output) {
|
|
|
683
689
|
}
|
|
684
690
|
const childEnv = { ...env, ...invocation.env };
|
|
685
691
|
const launch = options.spawn ?? spawnLogin;
|
|
686
|
-
return new Promise((
|
|
692
|
+
return new Promise((resolve5) => {
|
|
687
693
|
let settled = false;
|
|
688
|
-
const finish = (
|
|
694
|
+
const finish = (code2) => {
|
|
689
695
|
if (settled) return;
|
|
690
696
|
settled = true;
|
|
691
|
-
|
|
697
|
+
resolve5(code2);
|
|
692
698
|
};
|
|
693
699
|
let child;
|
|
694
700
|
try {
|
|
@@ -710,9 +716,9 @@ async function loginRole(agentValue, roleValue, options, output) {
|
|
|
710
716
|
`);
|
|
711
717
|
finish(1);
|
|
712
718
|
});
|
|
713
|
-
child.once("close", (
|
|
714
|
-
if (
|
|
715
|
-
finish(
|
|
719
|
+
child.once("close", (code2, signal) => {
|
|
720
|
+
if (code2 !== null) {
|
|
721
|
+
finish(code2);
|
|
716
722
|
return;
|
|
717
723
|
}
|
|
718
724
|
if (signal) {
|
|
@@ -765,18 +771,11 @@ function validateHeadOrCapsule(source, kind) {
|
|
|
765
771
|
}
|
|
766
772
|
return trimmed;
|
|
767
773
|
}
|
|
768
|
-
function
|
|
769
|
-
|
|
770
|
-
if (!trimmed.trim()) throw new Error("is empty.");
|
|
771
|
-
const lines = trimmed.split("\n");
|
|
772
|
-
const heading = lines.shift() ?? "";
|
|
773
|
-
if (!/^## \S.*$/.test(heading) || headingCount(trimmed, 2) !== 1) {
|
|
774
|
-
throw new Error("section must begin with exactly one H2 heading.");
|
|
775
|
-
}
|
|
776
|
-
if (lines.some((line) => /^#{1,6}\s/.test(line))) {
|
|
774
|
+
function renderSection(heading, bodyLines) {
|
|
775
|
+
if (bodyLines.some((line) => /^#{1,6}\s/.test(line))) {
|
|
777
776
|
throw new Error("section must not contain additional Markdown headings.");
|
|
778
777
|
}
|
|
779
|
-
const body =
|
|
778
|
+
const body = bodyLines.join("\n").trim();
|
|
780
779
|
if (!body) throw new Error("section must contain at least one non-empty statement.");
|
|
781
780
|
const statements = body.split(/\n\s*\n/).map(
|
|
782
781
|
(statement) => statement.split("\n").map((line) => line.trim()).join(" ")
|
|
@@ -787,6 +786,40 @@ function validateSection(source) {
|
|
|
787
786
|
return `${heading}
|
|
788
787
|
${statements.map((statement) => `- ${statement}`).join("\n")}`;
|
|
789
788
|
}
|
|
789
|
+
function validateSection(source) {
|
|
790
|
+
const trimmed = source.trimEnd();
|
|
791
|
+
if (!trimmed.trim()) throw new Error("is empty.");
|
|
792
|
+
const lines = trimmed.split("\n");
|
|
793
|
+
const heading = lines.shift() ?? "";
|
|
794
|
+
if (!/^## \S.*$/.test(heading) || headingCount(trimmed, 2) !== 1) {
|
|
795
|
+
throw new Error("section must begin with exactly one H2 heading.");
|
|
796
|
+
}
|
|
797
|
+
return renderSection(heading, lines);
|
|
798
|
+
}
|
|
799
|
+
function splitSections(lines) {
|
|
800
|
+
const preface = [];
|
|
801
|
+
const sections = [];
|
|
802
|
+
for (const line of lines) {
|
|
803
|
+
if (/^## \S.*$/.test(line)) {
|
|
804
|
+
sections.push({ heading: line, lines: [] });
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
const current2 = sections.at(-1);
|
|
808
|
+
if (current2 === void 0) preface.push(line);
|
|
809
|
+
else current2.lines.push(line);
|
|
810
|
+
}
|
|
811
|
+
return { preface, sections };
|
|
812
|
+
}
|
|
813
|
+
function validateBody(source) {
|
|
814
|
+
const trimmed = source.trimEnd();
|
|
815
|
+
if (!trimmed.trim()) throw new Error("is empty.");
|
|
816
|
+
const { preface, sections } = splitSections(trimmed.split("\n"));
|
|
817
|
+
if (sections.length === 0) throw new Error("role body must contain at least one H2 section.");
|
|
818
|
+
if (preface.some((line) => line.trim())) {
|
|
819
|
+
throw new Error("role body must not contain content above its first H2 heading.");
|
|
820
|
+
}
|
|
821
|
+
return sections.map((section) => renderSection(section.heading, section.lines)).join("\n\n");
|
|
822
|
+
}
|
|
790
823
|
function loadPromptAsset(group, key) {
|
|
791
824
|
const logicalAsset = `${group}.${String(key)}`;
|
|
792
825
|
const cached = cache.get(logicalAsset);
|
|
@@ -796,7 +829,7 @@ function loadPromptAsset(group, key) {
|
|
|
796
829
|
try {
|
|
797
830
|
const source = readFileSync(assetUrl, "utf8");
|
|
798
831
|
if (source.includes("\uFFFD")) throw new Error("is not valid UTF-8 text.");
|
|
799
|
-
const validated =
|
|
832
|
+
const validated = validate(source, asset.kind);
|
|
800
833
|
cache.set(logicalAsset, validated);
|
|
801
834
|
return validated;
|
|
802
835
|
} catch (error) {
|
|
@@ -805,6 +838,16 @@ function loadPromptAsset(group, key) {
|
|
|
805
838
|
throw new PromptAssetError(logicalAsset, assetUrl, detail, { cause: error });
|
|
806
839
|
}
|
|
807
840
|
}
|
|
841
|
+
function validate(source, kind) {
|
|
842
|
+
switch (kind) {
|
|
843
|
+
case "section":
|
|
844
|
+
return validateSection(source);
|
|
845
|
+
case "body":
|
|
846
|
+
return validateBody(source);
|
|
847
|
+
default:
|
|
848
|
+
return validateHeadOrCapsule(source, kind);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
808
851
|
var PROMPT_ASSETS, PromptAssetError, cache;
|
|
809
852
|
var init_prompts = __esm({
|
|
810
853
|
"src/room/prompts.ts"() {
|
|
@@ -813,38 +856,15 @@ var init_prompts = __esm({
|
|
|
813
856
|
documents: {
|
|
814
857
|
supervisor: { path: "documents/supervisor.md", kind: "head" },
|
|
815
858
|
lead: { path: "documents/lead.md", kind: "head" },
|
|
816
|
-
peer: { path: "documents/peer.md", kind: "head" }
|
|
817
|
-
workspace: { path: "documents/workspace.md", kind: "head" }
|
|
859
|
+
peer: { path: "documents/peer.md", kind: "head" }
|
|
818
860
|
},
|
|
819
861
|
contract: {
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
leadDiscoveryAndRecovery: { path: "contract/supervisor/lead-discovery-and-recovery.md", kind: "section" },
|
|
827
|
-
observationAndAdvice: { path: "contract/supervisor/observation-and-advice.md", kind: "section" },
|
|
828
|
-
escalationBoundaries: { path: "contract/supervisor/escalation-boundaries.md", kind: "section" },
|
|
829
|
-
projectTechnicalOwnership: { path: "contract/lead/project-technical-ownership.md", kind: "section" },
|
|
830
|
-
movingWriteOwnership: { path: "contract/lead/moving-write-ownership.md", kind: "section" },
|
|
831
|
-
completePeerBrief: { path: "contract/lead/complete-peer-brief.md", kind: "section" },
|
|
832
|
-
challengeSignals: { path: "contract/shared-lead-peer/challenge-signals.md", kind: "section" },
|
|
833
|
-
technicalAcceptance: { path: "contract/lead/technical-acceptance.md", kind: "section" },
|
|
834
|
-
independentReview: { path: "contract/lead/independent-review.md", kind: "section" },
|
|
835
|
-
peerSeatLifecycle: { path: "contract/lead/peer-seat-lifecycle.md", kind: "section" },
|
|
836
|
-
boundedOutcome: { path: "contract/peer/bounded-outcome.md", kind: "section" },
|
|
837
|
-
independentJudgment: { path: "contract/peer/independent-judgment.md", kind: "section" },
|
|
838
|
-
assignmentScope: { path: "contract/peer/assignment-scope.md", kind: "section" },
|
|
839
|
-
noOrchestration: { path: "contract/peer/no-orchestration.md", kind: "section" },
|
|
840
|
-
reproducibleHandoff: { path: "contract/peer/reproducible-handoff.md", kind: "section" },
|
|
841
|
-
noSelfAcceptance: { path: "contract/peer/no-self-acceptance.md", kind: "section" }
|
|
842
|
-
},
|
|
843
|
-
workspace: {
|
|
844
|
-
topology: { path: "workspace/topology.md", kind: "section" },
|
|
845
|
-
verification: { path: "workspace/verification.md", kind: "section" },
|
|
846
|
-
review: { path: "workspace/review.md", kind: "section" },
|
|
847
|
-
repositoryConventions: { path: "workspace/repository-conventions.md", kind: "section" }
|
|
862
|
+
sharedAuthority: { path: "contract/shared-authority.md", kind: "body" },
|
|
863
|
+
sharedSeatIdentity: { path: "contract/shared-seat-identity.md", kind: "section" },
|
|
864
|
+
challengeSignals: { path: "contract/challenge-signals.md", kind: "section" },
|
|
865
|
+
supervisor: { path: "contract/supervisor.md", kind: "body" },
|
|
866
|
+
lead: { path: "contract/lead.md", kind: "body" },
|
|
867
|
+
peer: { path: "contract/peer.md", kind: "body" }
|
|
848
868
|
},
|
|
849
869
|
pi: {
|
|
850
870
|
communicationStyle: { path: "pi/communication-style.md", kind: "capsule" },
|
|
@@ -868,24 +888,12 @@ var init_prompts = __esm({
|
|
|
868
888
|
// src/room/instructions.ts
|
|
869
889
|
import { createHash } from "crypto";
|
|
870
890
|
function instructionKeys(role) {
|
|
871
|
-
return [...SHARED_KEYS[role],
|
|
872
|
-
}
|
|
873
|
-
function protocolKeys(role) {
|
|
874
|
-
return PROTOCOL_KEYS[role];
|
|
875
|
-
}
|
|
876
|
-
function protocol(keys) {
|
|
877
|
-
if (keys.length === 0) return [];
|
|
878
|
-
return [
|
|
879
|
-
loadPromptAsset("documents", "workspace"),
|
|
880
|
-
...keys.map((key) => loadPromptAsset("workspace", key))
|
|
881
|
-
];
|
|
891
|
+
return [...SHARED_KEYS[role], ROLE_KEYS[role]];
|
|
882
892
|
}
|
|
883
893
|
function renderInstructions(kind) {
|
|
884
|
-
if (kind === "workspace") return protocol(ALL_PROTOCOL_KEYS).join("\n\n") + "\n";
|
|
885
894
|
return [
|
|
886
895
|
loadPromptAsset("documents", kind),
|
|
887
|
-
...instructionKeys(kind).map((key) => loadPromptAsset("contract", key))
|
|
888
|
-
...protocol(protocolKeys(kind))
|
|
896
|
+
...instructionKeys(kind).map((key) => loadPromptAsset("contract", key))
|
|
889
897
|
].join("\n\n") + "\n";
|
|
890
898
|
}
|
|
891
899
|
function contractDigest() {
|
|
@@ -893,68 +901,23 @@ function contractDigest() {
|
|
|
893
901
|
for (const kind of INSTRUCTION_KINDS) hash.update(`${kind}\0${renderInstructions(kind)}\0`);
|
|
894
902
|
return `sha256:${hash.digest("hex").slice(0, 16)}`;
|
|
895
903
|
}
|
|
896
|
-
var
|
|
904
|
+
var SHARED_KEYS, ROLE_KEYS, INSTRUCTION_KINDS;
|
|
897
905
|
var init_instructions = __esm({
|
|
898
906
|
"src/room/instructions.ts"() {
|
|
899
907
|
"use strict";
|
|
900
908
|
init_roles();
|
|
901
909
|
init_prompts();
|
|
902
|
-
AUTHORITY_KEYS = [
|
|
903
|
-
"humanAuthority",
|
|
904
|
-
"evidenceAndEventWaiting",
|
|
905
|
-
"scopeAndUnrelatedWork"
|
|
906
|
-
];
|
|
907
|
-
PROTOCOL_READER_KEYS = [
|
|
908
|
-
"humanAuthority",
|
|
909
|
-
"workspaceProtocolPrecedence",
|
|
910
|
-
"evidenceAndEventWaiting",
|
|
911
|
-
"scopeAndUnrelatedWork"
|
|
912
|
-
];
|
|
913
910
|
SHARED_KEYS = {
|
|
914
|
-
supervisor:
|
|
915
|
-
lead:
|
|
916
|
-
peer:
|
|
911
|
+
supervisor: ["sharedAuthority", "sharedSeatIdentity"],
|
|
912
|
+
lead: ["sharedAuthority", "sharedSeatIdentity", "challengeSignals"],
|
|
913
|
+
peer: ["sharedAuthority", "challengeSignals"]
|
|
917
914
|
};
|
|
918
915
|
ROLE_KEYS = {
|
|
919
|
-
supervisor:
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
"leadDiscoveryAndRecovery",
|
|
923
|
-
"observationAndAdvice",
|
|
924
|
-
"escalationBoundaries"
|
|
925
|
-
],
|
|
926
|
-
lead: [
|
|
927
|
-
"projectTechnicalOwnership",
|
|
928
|
-
"movingWriteOwnership",
|
|
929
|
-
"completePeerBrief",
|
|
930
|
-
"challengeSignals",
|
|
931
|
-
"technicalAcceptance",
|
|
932
|
-
"independentReview",
|
|
933
|
-
"peerSeatLifecycle"
|
|
934
|
-
],
|
|
935
|
-
// Peer needs Challenge Signals as well as its own obligations.
|
|
936
|
-
peer: [
|
|
937
|
-
"challengeSignals",
|
|
938
|
-
"boundedOutcome",
|
|
939
|
-
"independentJudgment",
|
|
940
|
-
"assignmentScope",
|
|
941
|
-
"noOrchestration",
|
|
942
|
-
"reproducibleHandoff",
|
|
943
|
-
"noSelfAcceptance"
|
|
944
|
-
]
|
|
945
|
-
};
|
|
946
|
-
ALL_PROTOCOL_KEYS = [
|
|
947
|
-
"topology",
|
|
948
|
-
"verification",
|
|
949
|
-
"review",
|
|
950
|
-
"repositoryConventions"
|
|
951
|
-
];
|
|
952
|
-
PROTOCOL_KEYS = {
|
|
953
|
-
supervisor: ALL_PROTOCOL_KEYS,
|
|
954
|
-
lead: ALL_PROTOCOL_KEYS,
|
|
955
|
-
peer: []
|
|
916
|
+
supervisor: "supervisor",
|
|
917
|
+
lead: "lead",
|
|
918
|
+
peer: "peer"
|
|
956
919
|
};
|
|
957
|
-
INSTRUCTION_KINDS = [...ROLES
|
|
920
|
+
INSTRUCTION_KINDS = [...ROLES];
|
|
958
921
|
}
|
|
959
922
|
});
|
|
960
923
|
|
|
@@ -1011,11 +974,11 @@ function detectPaseoServers(servers) {
|
|
|
1011
974
|
function evidence(match) {
|
|
1012
975
|
return match.field === "name" ? `${match.server} (server name)` : `${match.server} (${match.field}: ${match.value})`;
|
|
1013
976
|
}
|
|
1014
|
-
function paseoMcpCheck(
|
|
977
|
+
function paseoMcpCheck(id2, path, servers) {
|
|
1015
978
|
const matches = detectPaseoServers(servers);
|
|
1016
979
|
if (matches.length === 0) return void 0;
|
|
1017
980
|
return fail(
|
|
1018
|
-
|
|
981
|
+
id2,
|
|
1019
982
|
`${path} declares MCP servers that are recognizably Paseo-related: ${matches.map(evidence).join("; ")}.`,
|
|
1020
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.`
|
|
1021
984
|
);
|
|
@@ -1040,14 +1003,39 @@ function isPaseoSkill(name) {
|
|
|
1040
1003
|
function receivesExecutableResources(role) {
|
|
1041
1004
|
return role !== "peer";
|
|
1042
1005
|
}
|
|
1043
|
-
|
|
1006
|
+
function receivesRoomSkill(role) {
|
|
1007
|
+
return role === "lead";
|
|
1008
|
+
}
|
|
1009
|
+
async function skillNames(path, reserved, keep) {
|
|
1044
1010
|
try {
|
|
1045
|
-
return (await readdir2(path)).filter((name) =>
|
|
1011
|
+
return (await readdir2(path)).filter((name) => keep(name) && !reserved.has(name.toLowerCase())).sort();
|
|
1046
1012
|
} catch (error) {
|
|
1047
1013
|
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
|
|
1048
1014
|
throw error;
|
|
1049
1015
|
}
|
|
1050
1016
|
}
|
|
1017
|
+
async function skillsProjection(input, path, alias, present, reserved) {
|
|
1018
|
+
const roomSkill = receivesRoomSkill(input.role) ? input.roomSkill : void 0;
|
|
1019
|
+
const projectsChildren = !receivesExecutableResources(input.role) || roomSkill !== void 0;
|
|
1020
|
+
if (!projectsChildren) return void 0;
|
|
1021
|
+
const reservedNames = new Set([...reserved].map((name) => name.toLowerCase()));
|
|
1022
|
+
const keep = roomSkill === void 0 ? (name) => !isPaseoSkill(name) : (name) => name.toLowerCase() !== roomSkill.name.toLowerCase();
|
|
1023
|
+
const operator = present.has(path) ? await skillNames(path, reservedNames, keep) : [];
|
|
1024
|
+
const children = roomSkill === void 0 ? operator : [...operator, roomSkill.name];
|
|
1025
|
+
const projection = input.role === "lead" ? input.leadSkillProjection : alias;
|
|
1026
|
+
return [
|
|
1027
|
+
{
|
|
1028
|
+
kind: "managed-dir",
|
|
1029
|
+
path: projection,
|
|
1030
|
+
children,
|
|
1031
|
+
...input.role === "peer" ? { legacyLink: path } : {},
|
|
1032
|
+
...reserved.size === 0 ? {} : { reserved: [...reserved] }
|
|
1033
|
+
},
|
|
1034
|
+
...operator.map((child) => ({ kind: "link", path: join6(projection, child), target: join6(path, child) })),
|
|
1035
|
+
...roomSkill === void 0 ? [] : [{ kind: "link", path: join6(projection, roomSkill.name), target: roomSkill.source }],
|
|
1036
|
+
...input.role === "lead" ? [{ kind: "link", path: alias, target: projection }] : []
|
|
1037
|
+
];
|
|
1038
|
+
}
|
|
1051
1039
|
async function roleResourceEntries(input) {
|
|
1052
1040
|
const present = new Set(input.shared);
|
|
1053
1041
|
const executable = new Set(input.executable);
|
|
@@ -1056,21 +1044,14 @@ async function roleResourceEntries(input) {
|
|
|
1056
1044
|
for (const name of input.names) {
|
|
1057
1045
|
const path = join6(input.home, name);
|
|
1058
1046
|
const alias = join6(input.target, name);
|
|
1059
|
-
if (
|
|
1060
|
-
|
|
1061
|
-
if (
|
|
1062
|
-
|
|
1063
|
-
entries.push({
|
|
1064
|
-
kind: "managed-dir",
|
|
1065
|
-
path: alias,
|
|
1066
|
-
children,
|
|
1067
|
-
legacyLink: path,
|
|
1068
|
-
...reserved.size === 0 ? {} : { reserved: [...reserved] }
|
|
1069
|
-
});
|
|
1070
|
-
for (const child of children) entries.push({ kind: "link", path: join6(alias, child), target: join6(path, child) });
|
|
1047
|
+
if (name === SKILLS) {
|
|
1048
|
+
const projection = await skillsProjection(input, path, alias, present, reserved);
|
|
1049
|
+
if (projection !== void 0) {
|
|
1050
|
+
entries.push(...projection);
|
|
1071
1051
|
continue;
|
|
1072
1052
|
}
|
|
1073
1053
|
}
|
|
1054
|
+
if (!receivesExecutableResources(input.role) && executable.has(name)) continue;
|
|
1074
1055
|
if (!present.has(path)) continue;
|
|
1075
1056
|
entries.push({ kind: "link", path: alias, target: path });
|
|
1076
1057
|
}
|
|
@@ -1084,9 +1065,99 @@ var init_resources = __esm({
|
|
|
1084
1065
|
}
|
|
1085
1066
|
});
|
|
1086
1067
|
|
|
1068
|
+
// src/room/skills.ts
|
|
1069
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1070
|
+
import { join as join7 } from "path";
|
|
1071
|
+
function validateSkillAsset(path, source) {
|
|
1072
|
+
if (source.includes("\uFFFD")) throw new Error("is not valid UTF-8 text.");
|
|
1073
|
+
if (!source.trim()) throw new Error("is empty.");
|
|
1074
|
+
if (path === "SKILL.md") {
|
|
1075
|
+
const frontmatter = /^---\n([\s\S]*?)\n---\n/.exec(source)?.[1];
|
|
1076
|
+
if (frontmatter === void 0) throw new Error("must begin with YAML frontmatter.");
|
|
1077
|
+
const declaredName = /^name: (.+)$/m.exec(frontmatter)?.[1];
|
|
1078
|
+
if (declaredName !== ROOM_SKILL_NAME) {
|
|
1079
|
+
throw new Error(`frontmatter name must be ${ROOM_SKILL_NAME}.`);
|
|
1080
|
+
}
|
|
1081
|
+
if (!/^description: \S.+$/m.test(frontmatter)) {
|
|
1082
|
+
throw new Error("frontmatter must contain a non-empty single-line description.");
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
if (path === "references/workspace-protocol-template.md" && !source.startsWith("# Workspace protocol template (scaffold)\n")) {
|
|
1086
|
+
throw new Error("must begin with the workspace protocol scaffold heading.");
|
|
1087
|
+
}
|
|
1088
|
+
return source;
|
|
1089
|
+
}
|
|
1090
|
+
function loadSkillAsset(path) {
|
|
1091
|
+
const cached = cache2.get(path);
|
|
1092
|
+
if (cached !== void 0) return cached;
|
|
1093
|
+
const logicalAsset = `${ROOM_SKILL_NAME}/${path}`;
|
|
1094
|
+
const assetUrl = new URL(`./skills/${ROOM_SKILL_NAME}/${path}`, import.meta.url);
|
|
1095
|
+
try {
|
|
1096
|
+
const validated = validateSkillAsset(path, readFileSync2(assetUrl, "utf8"));
|
|
1097
|
+
cache2.set(path, validated);
|
|
1098
|
+
return validated;
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
if (error instanceof SkillAssetError) throw error;
|
|
1101
|
+
const detail = error instanceof Error ? error.message : "could not be read.";
|
|
1102
|
+
throw new SkillAssetError(logicalAsset, assetUrl, detail, { cause: error });
|
|
1103
|
+
}
|
|
1104
|
+
}
|
|
1105
|
+
function roomSkillSource(layout) {
|
|
1106
|
+
return join7(sharedRoom(layout), "skills", ROOM_SKILL_NAME);
|
|
1107
|
+
}
|
|
1108
|
+
function skillProjectionRoot(layout) {
|
|
1109
|
+
return join7(sharedRoom(layout), "skill-projections");
|
|
1110
|
+
}
|
|
1111
|
+
function agentSkillProjectionRoot(layout, agent) {
|
|
1112
|
+
return join7(skillProjectionRoot(layout), agent);
|
|
1113
|
+
}
|
|
1114
|
+
function leadSkillProjection(layout, agent) {
|
|
1115
|
+
return join7(agentSkillProjectionRoot(layout, agent), "lead");
|
|
1116
|
+
}
|
|
1117
|
+
function roomSkillEntries(layout, agents) {
|
|
1118
|
+
const root = roomSkillSource(layout);
|
|
1119
|
+
return [
|
|
1120
|
+
{ kind: "dir", path: join7(sharedRoom(layout), "skills") },
|
|
1121
|
+
{ kind: "dir", path: skillProjectionRoot(layout) },
|
|
1122
|
+
...agents.map((agent) => ({ kind: "dir", path: agentSkillProjectionRoot(layout, agent) })),
|
|
1123
|
+
{ kind: "managed-dir", path: root, children: ["SKILL.md", ...SKILL_DIRECTORIES] },
|
|
1124
|
+
{
|
|
1125
|
+
kind: "managed-dir",
|
|
1126
|
+
path: join7(root, "references"),
|
|
1127
|
+
children: SKILL_FILES.filter((path) => path.startsWith("references/")).map((path) => path.slice("references/".length))
|
|
1128
|
+
},
|
|
1129
|
+
...SKILL_FILES.map((path) => ({
|
|
1130
|
+
kind: "file",
|
|
1131
|
+
path: join7(root, path),
|
|
1132
|
+
content: loadSkillAsset(path)
|
|
1133
|
+
}))
|
|
1134
|
+
];
|
|
1135
|
+
}
|
|
1136
|
+
var ROOM_SKILL_NAME, SKILL_DIRECTORIES, SKILL_FILES, SkillAssetError, cache2;
|
|
1137
|
+
var init_skills = __esm({
|
|
1138
|
+
"src/room/skills.ts"() {
|
|
1139
|
+
"use strict";
|
|
1140
|
+
init_layout();
|
|
1141
|
+
ROOM_SKILL_NAME = "paseo-project-onboarding";
|
|
1142
|
+
SKILL_DIRECTORIES = ["references"];
|
|
1143
|
+
SKILL_FILES = ["SKILL.md", "references/workspace-protocol-template.md"];
|
|
1144
|
+
SkillAssetError = class extends Error {
|
|
1145
|
+
logicalAsset;
|
|
1146
|
+
assetUrl;
|
|
1147
|
+
constructor(logicalAsset, assetUrl, detail, options) {
|
|
1148
|
+
super(`Skill asset ${logicalAsset} at ${assetUrl.pathname} ${detail} Reinstall paseo-room and try again.`, options);
|
|
1149
|
+
this.name = "SkillAssetError";
|
|
1150
|
+
this.logicalAsset = logicalAsset;
|
|
1151
|
+
this.assetUrl = assetUrl;
|
|
1152
|
+
}
|
|
1153
|
+
};
|
|
1154
|
+
cache2 = /* @__PURE__ */ new Map();
|
|
1155
|
+
}
|
|
1156
|
+
});
|
|
1157
|
+
|
|
1087
1158
|
// src/agents/claude.ts
|
|
1088
1159
|
import { readFile as readFile2 } from "fs/promises";
|
|
1089
|
-
import { join as
|
|
1160
|
+
import { join as join8 } from "path";
|
|
1090
1161
|
function asObject(value) {
|
|
1091
1162
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
|
|
1092
1163
|
}
|
|
@@ -1105,17 +1176,21 @@ async function readStateIfPresent(path) {
|
|
|
1105
1176
|
throw error;
|
|
1106
1177
|
}
|
|
1107
1178
|
}
|
|
1108
|
-
function renderRoleSettings(
|
|
1109
|
-
const settings =
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1179
|
+
function renderRoleSettings(role, secureStorageDir) {
|
|
1180
|
+
const settings = {
|
|
1181
|
+
// Claude applies settings.env after the launch environment, so repeat provider closures here.
|
|
1182
|
+
env: {
|
|
1183
|
+
...CONTROL_PLANE_ENV,
|
|
1184
|
+
PASEO_ROOM_ROLE: role,
|
|
1185
|
+
...secureStorageDir === void 0 ? {} : { [SECURE_STORAGE_ENV]: secureStorageDir }
|
|
1186
|
+
},
|
|
1187
|
+
// Provider disallowedTools is not sufficient alone; the isolated home carries the same list.
|
|
1188
|
+
permissions: { deny: DISALLOWED_TOOLS },
|
|
1189
|
+
// These restrictive settings cannot be weakened by another settings scope.
|
|
1190
|
+
disableAgentView: true,
|
|
1191
|
+
disableWorkflows: true,
|
|
1192
|
+
crossSessionInbound: "refuse"
|
|
1115
1193
|
};
|
|
1116
|
-
settings.disableAgentView = true;
|
|
1117
|
-
settings.disableWorkflows = true;
|
|
1118
|
-
settings.crossSessionInbound = "refuse";
|
|
1119
1194
|
return JSON.stringify(settings, null, 2) + "\n";
|
|
1120
1195
|
}
|
|
1121
1196
|
function renderRoleState(source) {
|
|
@@ -1132,41 +1207,31 @@ function renderRoleMemory(source, role, contract = true) {
|
|
|
1132
1207
|
|
|
1133
1208
|
${renderInstructions(role)}` : renderInstructions(role);
|
|
1134
1209
|
}
|
|
1135
|
-
function
|
|
1136
|
-
const settings = readObject(settingsSource);
|
|
1137
|
-
const settingsEnv = asObject(settings.env);
|
|
1138
|
-
const names = new Set(presentNames(envNames, AUTH_ENV));
|
|
1139
|
-
for (const name of AUTH_ENV) if (Object.hasOwn(settingsEnv, name)) names.add(name);
|
|
1140
|
-
if (Object.hasOwn(settings, "apiKeyHelper")) names.add("apiKeyHelper");
|
|
1141
|
-
return [...names];
|
|
1142
|
-
}
|
|
1143
|
-
async function claudeCredentialDiagnostic(layout, role, settingsSource, platform = process.platform, binary = "claude") {
|
|
1210
|
+
async function claudeCredentialDiagnostic(layout, role, platform = process.platform, binary = "claude") {
|
|
1144
1211
|
const home = roleHome(layout, "claude", role);
|
|
1145
|
-
const path =
|
|
1212
|
+
const path = join8(home, ".credentials.json");
|
|
1146
1213
|
const roleEnvironment = { CLAUDE_CONFIG_DIR: home, [SECURE_STORAGE_ENV]: home };
|
|
1147
1214
|
const login = roleCommand(roleEnvironment, binary, ["auth", "login"]);
|
|
1148
1215
|
const status = roleCommand(roleEnvironment, binary, ["auth", "status"]);
|
|
1149
1216
|
const state = await inspectCredentialPath(path);
|
|
1150
|
-
const
|
|
1217
|
+
const id2 = `claude.auth.${role}`;
|
|
1151
1218
|
if (state.kind !== "missing") {
|
|
1152
|
-
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 })] };
|
|
1153
1220
|
}
|
|
1154
|
-
const configured = claudeAuthMethodNames(settingsSource, /* @__PURE__ */ new Set());
|
|
1155
|
-
if (configured.length > 0) return { path, checks: [configuredByNamesCheck(id, "Claude", role, configured)] };
|
|
1156
1221
|
const ambient = presentNames(layout.envNames, AUTH_ENV);
|
|
1157
|
-
if (ambient.length > 0) return { path, checks: [ambientNamesCheck(
|
|
1222
|
+
if (ambient.length > 0) return { path, checks: [ambientNamesCheck(id2, "Claude", role, ambient)] };
|
|
1158
1223
|
if (platform === "darwin") {
|
|
1159
1224
|
return { path, checks: [{
|
|
1160
|
-
id: `${
|
|
1225
|
+
id: `${id2}.native-keyring-unverifiable`,
|
|
1161
1226
|
status: "warn",
|
|
1162
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.`,
|
|
1163
1228
|
fix: `Authenticate this role with: ${login}. If that subcommand is unavailable, launch ${roleCommand(roleEnvironment, binary)} and run /login. Check status with: ${status}.`
|
|
1164
1229
|
}] };
|
|
1165
1230
|
}
|
|
1166
1231
|
return { path, checks: [{
|
|
1167
|
-
id: `${
|
|
1232
|
+
id: `${id2}.login-required`,
|
|
1168
1233
|
status: "warn",
|
|
1169
|
-
message: `Claude ${role} auth: login-required; no role-owned .credentials.json or
|
|
1234
|
+
message: `Claude ${role} auth: login-required; no role-owned .credentials.json or supported ambient auth environment name was detected. Authentication was not attempted.`,
|
|
1170
1235
|
fix: `Authenticate this role with: ${login}. If that subcommand is unavailable, launch ${roleCommand(roleEnvironment, binary)} and run /login. Check status with: ${status}.`
|
|
1171
1236
|
}] };
|
|
1172
1237
|
}
|
|
@@ -1181,7 +1246,7 @@ function mcpDriftCheck(role, home, path, divergence) {
|
|
|
1181
1246
|
`If this role should have the same servers, add them with: CLAUDE_CONFIG_DIR=${home} claude mcp add ... \u2014 or delete ${path} and run setup --apply to reseed it from your current Claude state, which discards that role's other runtime state.`
|
|
1182
1247
|
);
|
|
1183
1248
|
}
|
|
1184
|
-
var SHARED, EXECUTABLE, RUNTIME_SKILLS, MCP_KEY, AUTH_ENV, CONTROL_PLANE_ENV, SECURE_STORAGE_ENV, SEEDED_KEYS, claudeAgent;
|
|
1249
|
+
var SHARED, EXECUTABLE, RUNTIME_SKILLS, MCP_KEY, AUTH_ENV, CONTROL_PLANE_ENV, DISALLOWED_TOOLS, SECURE_STORAGE_ENV, SEEDED_KEYS, claudeAgent;
|
|
1185
1250
|
var init_claude = __esm({
|
|
1186
1251
|
"src/agents/claude.ts"() {
|
|
1187
1252
|
"use strict";
|
|
@@ -1193,6 +1258,7 @@ var init_claude = __esm({
|
|
|
1193
1258
|
init_which();
|
|
1194
1259
|
init_mcp();
|
|
1195
1260
|
init_resources();
|
|
1261
|
+
init_skills();
|
|
1196
1262
|
SHARED = [
|
|
1197
1263
|
"skills",
|
|
1198
1264
|
"plugins",
|
|
@@ -1218,6 +1284,22 @@ var init_claude = __esm({
|
|
|
1218
1284
|
CLAUDE_CODE_DISABLE_AGENT_VIEW: "1",
|
|
1219
1285
|
CLAUDE_CODE_DISABLE_WORKFLOWS: "1"
|
|
1220
1286
|
};
|
|
1287
|
+
DISALLOWED_TOOLS = [
|
|
1288
|
+
"Task",
|
|
1289
|
+
"Agent",
|
|
1290
|
+
"Workflow",
|
|
1291
|
+
"ListAgents",
|
|
1292
|
+
"SendMessage",
|
|
1293
|
+
"TeamCreate",
|
|
1294
|
+
"TeamDelete",
|
|
1295
|
+
"TaskCreate",
|
|
1296
|
+
"TaskGet",
|
|
1297
|
+
"TaskList",
|
|
1298
|
+
"TaskUpdate",
|
|
1299
|
+
"CronCreate",
|
|
1300
|
+
"CronDelete",
|
|
1301
|
+
"CronList"
|
|
1302
|
+
];
|
|
1221
1303
|
SECURE_STORAGE_ENV = "CLAUDE_SECURESTORAGE_CONFIG_DIR";
|
|
1222
1304
|
SEEDED_KEYS = ["hasCompletedOnboarding", "theme", "installMethod", "userID", MCP_KEY];
|
|
1223
1305
|
claudeAgent = {
|
|
@@ -1230,24 +1312,7 @@ var init_claude = __esm({
|
|
|
1230
1312
|
// Keep the legacy Task name and block current native orchestration,
|
|
1231
1313
|
// shared task/cron coordination, and cross-session paths. Paseo alone owns
|
|
1232
1314
|
// agent lifecycle and coordination.
|
|
1233
|
-
pins: {
|
|
1234
|
-
disallowedTools: [
|
|
1235
|
-
"Task",
|
|
1236
|
-
"Agent",
|
|
1237
|
-
"Workflow",
|
|
1238
|
-
"ListAgents",
|
|
1239
|
-
"SendMessage",
|
|
1240
|
-
"TeamCreate",
|
|
1241
|
-
"TeamDelete",
|
|
1242
|
-
"TaskCreate",
|
|
1243
|
-
"TaskGet",
|
|
1244
|
-
"TaskList",
|
|
1245
|
-
"TaskUpdate",
|
|
1246
|
-
"CronCreate",
|
|
1247
|
-
"CronDelete",
|
|
1248
|
-
"CronList"
|
|
1249
|
-
]
|
|
1250
|
-
},
|
|
1315
|
+
pins: { disallowedTools: DISALLOWED_TOOLS },
|
|
1251
1316
|
async build(layout, roles, options = {}) {
|
|
1252
1317
|
const home = layout.agentHome.claude;
|
|
1253
1318
|
const binary = await which(layout.bin.claude, layout.searchPath);
|
|
@@ -1258,8 +1323,7 @@ var init_claude = __esm({
|
|
|
1258
1323
|
return { entries: [], checks: [fail("claude.home", `No Claude config directory at ${home}.`, "Run claude once to initialise it, or pass --claude-home.")] };
|
|
1259
1324
|
}
|
|
1260
1325
|
const checks = [pass("claude.home", `Claude Code found at ${binary} using ${home}.`)];
|
|
1261
|
-
const
|
|
1262
|
-
const memorySource = await readIfPresent(join7(home, "CLAUDE.md"));
|
|
1326
|
+
const memorySource = await readIfPresent(join8(home, "CLAUDE.md"));
|
|
1263
1327
|
let stateSource;
|
|
1264
1328
|
let operatorServers;
|
|
1265
1329
|
try {
|
|
@@ -1279,9 +1343,10 @@ var init_claude = __esm({
|
|
|
1279
1343
|
const credentials = [];
|
|
1280
1344
|
const providerEnv = {};
|
|
1281
1345
|
const drift = [];
|
|
1346
|
+
const roomSkill = { name: ROOM_SKILL_NAME, source: roomSkillSource(layout) };
|
|
1282
1347
|
for (const role of roles) {
|
|
1283
1348
|
const target = roleHome(layout, "claude", role);
|
|
1284
|
-
const statePath =
|
|
1349
|
+
const statePath = join8(target, ".claude.json");
|
|
1285
1350
|
let roleStateSource;
|
|
1286
1351
|
let roleServers;
|
|
1287
1352
|
try {
|
|
@@ -1301,13 +1366,23 @@ var init_claude = __esm({
|
|
|
1301
1366
|
if (divergence) drift.push(mcpDriftCheck(role, target, statePath, divergence));
|
|
1302
1367
|
}
|
|
1303
1368
|
entries.push({ kind: "dir", path: target });
|
|
1304
|
-
const memoryPath =
|
|
1369
|
+
const memoryPath = join8(target, "CLAUDE.md");
|
|
1305
1370
|
const memory = renderRoleMemory(memorySource, role, options.memoryContract ?? true);
|
|
1306
1371
|
entries.push(memory === void 0 ? { kind: "absent", path: memoryPath } : { kind: "file", path: memoryPath, content: memory });
|
|
1307
|
-
entries.push({ kind: "file", path:
|
|
1372
|
+
entries.push({ kind: "file", path: join8(target, "settings.json"), content: renderRoleSettings(role, target) });
|
|
1308
1373
|
entries.push({ kind: "file", path: statePath, content: renderRoleState(stateSource), once: true });
|
|
1309
|
-
entries.push(...await roleResourceEntries({
|
|
1310
|
-
|
|
1374
|
+
entries.push(...await roleResourceEntries({
|
|
1375
|
+
role,
|
|
1376
|
+
target,
|
|
1377
|
+
home,
|
|
1378
|
+
names: SHARED,
|
|
1379
|
+
shared,
|
|
1380
|
+
executable: EXECUTABLE,
|
|
1381
|
+
reservedSkills: RUNTIME_SKILLS,
|
|
1382
|
+
roomSkill,
|
|
1383
|
+
leadSkillProjection: leadSkillProjection(layout, "claude")
|
|
1384
|
+
}));
|
|
1385
|
+
credentials.push(await claudeCredentialDiagnostic(layout, role, process.platform, binary));
|
|
1311
1386
|
providerEnv[role] = { [SECURE_STORAGE_ENV]: target };
|
|
1312
1387
|
}
|
|
1313
1388
|
return { entries, credentials, checks: [...checks, ...drift], binary, providerEnv };
|
|
@@ -1317,7 +1392,7 @@ var init_claude = __esm({
|
|
|
1317
1392
|
});
|
|
1318
1393
|
|
|
1319
1394
|
// src/agents/codex.ts
|
|
1320
|
-
import { join as
|
|
1395
|
+
import { join as join9 } from "path";
|
|
1321
1396
|
import { parse, stringify } from "smol-toml";
|
|
1322
1397
|
function table(parent, key) {
|
|
1323
1398
|
const value = parent[key];
|
|
@@ -1374,18 +1449,18 @@ function codexCredentialStore(config) {
|
|
|
1374
1449
|
}
|
|
1375
1450
|
async function credentialDiagnostic(layout, role, store, binary) {
|
|
1376
1451
|
const home = roleHome(layout, "codex", role);
|
|
1377
|
-
const path =
|
|
1452
|
+
const path = join9(home, "auth.json");
|
|
1378
1453
|
const login = roleCommand({ CODEX_HOME: home }, binary, ["login"]);
|
|
1379
1454
|
const status = roleCommand({ CODEX_HOME: home }, binary, ["login", "status"]);
|
|
1380
1455
|
const state = await inspectCredentialPath(path);
|
|
1381
|
-
const
|
|
1456
|
+
const id2 = `codex.auth.${role}`;
|
|
1382
1457
|
const checks = [];
|
|
1383
1458
|
if (state.kind !== "missing") {
|
|
1384
|
-
checks.push(preservedCredentialCheck({ id, agent: "Codex", role, path, state, login, status }));
|
|
1459
|
+
checks.push(preservedCredentialCheck({ id: id2, agent: "Codex", role, path, state, login, status }));
|
|
1385
1460
|
} else {
|
|
1386
1461
|
if (store === "keyring" || store === "auto") {
|
|
1387
1462
|
checks.push({
|
|
1388
|
-
id: `${
|
|
1463
|
+
id: `${id2}.native-keyring-unverifiable`,
|
|
1389
1464
|
status: "warn",
|
|
1390
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.`,
|
|
1391
1466
|
fix: `Authenticate this role with: ${login}. Check status with: ${status}.`
|
|
@@ -1394,7 +1469,7 @@ async function credentialDiagnostic(layout, role, store, binary) {
|
|
|
1394
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." : "";
|
|
1395
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" : ""}`;
|
|
1396
1471
|
checks.push({
|
|
1397
|
-
id: `${
|
|
1472
|
+
id: `${id2}.login-required`,
|
|
1398
1473
|
status: "warn",
|
|
1399
1474
|
message: `Codex ${role} auth: login-required; ${detail}.${apiKey} Authentication was not attempted.`,
|
|
1400
1475
|
fix: `Authenticate this role with: ${login}. Check status with: ${status}.`
|
|
@@ -1403,7 +1478,7 @@ async function credentialDiagnostic(layout, role, store, binary) {
|
|
|
1403
1478
|
}
|
|
1404
1479
|
if (state.kind !== "missing" && (store === "keyring" || store === "auto")) {
|
|
1405
1480
|
checks.push({
|
|
1406
|
-
id: `${
|
|
1481
|
+
id: `${id2}.native-keyring-unverifiable`,
|
|
1407
1482
|
status: "warn",
|
|
1408
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.`,
|
|
1409
1484
|
fix: `Check status without exposing credentials: ${status}.`
|
|
@@ -1423,6 +1498,7 @@ var init_codex = __esm({
|
|
|
1423
1498
|
init_which();
|
|
1424
1499
|
init_mcp();
|
|
1425
1500
|
init_resources();
|
|
1501
|
+
init_skills();
|
|
1426
1502
|
SHARED2 = ["AGENTS.md", "skills", "plugins", "hooks.json"];
|
|
1427
1503
|
EXECUTABLE2 = ["plugins", "hooks.json"];
|
|
1428
1504
|
CATALOG = "model-catalog.json";
|
|
@@ -1441,7 +1517,7 @@ var init_codex = __esm({
|
|
|
1441
1517
|
if (!binary) {
|
|
1442
1518
|
return { entries: [], checks: [fail("codex.bin", "Codex executable not found.", "Install Codex, or pass --codex-bin /path/to/codex.")] };
|
|
1443
1519
|
}
|
|
1444
|
-
const configPath =
|
|
1520
|
+
const configPath = join9(home, "config.toml");
|
|
1445
1521
|
const raw = await readIfPresent(configPath);
|
|
1446
1522
|
if (raw === void 0) {
|
|
1447
1523
|
return { entries: [], checks: [fail("codex.home", `No config.toml in ${home}.`, "Run codex once to initialise it, or pass --codex-home.")] };
|
|
@@ -1489,19 +1565,29 @@ var init_codex = __esm({
|
|
|
1489
1565
|
}
|
|
1490
1566
|
checks.push(pass("codex.catalog", evidence2.markers === 0 ? `Codex model catalog captured from ${capture}; it declared no multi_agent_version field, and each role home gets that generated copy instead of Codex's built-in catalog.` : `Codex model catalog captured from ${capture}; ${String(evidence2.markers)} multi_agent_version field(s) are nulled in the generated copy in each role home.`));
|
|
1491
1567
|
const shared = await existingPaths(home, SHARED2);
|
|
1568
|
+
const roomSkill = { name: ROOM_SKILL_NAME, source: roomSkillSource(layout) };
|
|
1492
1569
|
for (const role of roles) {
|
|
1493
1570
|
const target = roleHome(layout, "codex", role);
|
|
1494
|
-
const catalogPath =
|
|
1571
|
+
const catalogPath = join9(target, CATALOG);
|
|
1495
1572
|
const roleDocument = renderInstructions(role);
|
|
1496
1573
|
entries.push({ kind: "dir", path: target });
|
|
1497
|
-
entries.push({ kind: "file", path:
|
|
1574
|
+
entries.push({ kind: "file", path: join9(target, "role-instructions.md"), content: roleDocument });
|
|
1498
1575
|
entries.push({
|
|
1499
1576
|
kind: "file",
|
|
1500
|
-
path:
|
|
1577
|
+
path: join9(target, "config.toml"),
|
|
1501
1578
|
content: renderRoleConfig(source, { roleDocument, catalogPath })
|
|
1502
1579
|
});
|
|
1503
1580
|
entries.push({ kind: "file", path: catalogPath, content: catalogSource });
|
|
1504
|
-
entries.push(...await roleResourceEntries({
|
|
1581
|
+
entries.push(...await roleResourceEntries({
|
|
1582
|
+
role,
|
|
1583
|
+
target,
|
|
1584
|
+
home,
|
|
1585
|
+
names: SHARED2,
|
|
1586
|
+
shared,
|
|
1587
|
+
executable: EXECUTABLE2,
|
|
1588
|
+
roomSkill,
|
|
1589
|
+
leadSkillProjection: leadSkillProjection(layout, "codex")
|
|
1590
|
+
}));
|
|
1505
1591
|
credentials.push(await credentialDiagnostic(layout, role, store, binary));
|
|
1506
1592
|
}
|
|
1507
1593
|
return { entries, credentials, checks, binary };
|
|
@@ -1514,7 +1600,7 @@ var init_codex = __esm({
|
|
|
1514
1600
|
import { spawn as spawn2 } from "child_process";
|
|
1515
1601
|
import { mkdtemp, readFile as readFile3, realpath, rm as rm2, stat } from "fs/promises";
|
|
1516
1602
|
import { tmpdir } from "os";
|
|
1517
|
-
import { isAbsolute as isAbsolute2, join as
|
|
1603
|
+
import { isAbsolute as isAbsolute2, join as join10, resolve as resolve3 } from "path";
|
|
1518
1604
|
function asObject2(value) {
|
|
1519
1605
|
return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : void 0;
|
|
1520
1606
|
}
|
|
@@ -1538,12 +1624,12 @@ function adapterFailure(home, detail) {
|
|
|
1538
1624
|
return fail(
|
|
1539
1625
|
"pi.adapter",
|
|
1540
1626
|
detail,
|
|
1541
|
-
`Install ${PACKAGE_NAME} in ${
|
|
1627
|
+
`Install ${PACKAGE_NAME} in ${join10(home, "npm")} with Pi yourself, then run setup again.`
|
|
1542
1628
|
);
|
|
1543
1629
|
}
|
|
1544
1630
|
async function resolvePiAdapter(home) {
|
|
1545
|
-
const packageRoot =
|
|
1546
|
-
const manifestPath =
|
|
1631
|
+
const packageRoot = join10(home, "npm", "node_modules", PACKAGE_NAME);
|
|
1632
|
+
const manifestPath = join10(packageRoot, "package.json");
|
|
1547
1633
|
try {
|
|
1548
1634
|
const [canonicalRoot, manifestStat, raw] = await Promise.all([
|
|
1549
1635
|
realpath(packageRoot),
|
|
@@ -1608,10 +1694,10 @@ function runProbeProcess(executable, adapterEntry, env, options, cwd) {
|
|
|
1608
1694
|
clearTimeout(timer);
|
|
1609
1695
|
resolveResult(result);
|
|
1610
1696
|
};
|
|
1611
|
-
const abort = (
|
|
1697
|
+
const abort = (reason3) => {
|
|
1612
1698
|
if (settled) return;
|
|
1613
1699
|
terminateProbe(child);
|
|
1614
|
-
finish({ ok: false, stdout: Buffer.concat(stdout).toString("utf8"), reason });
|
|
1700
|
+
finish({ ok: false, stdout: Buffer.concat(stdout).toString("utf8"), reason: reason3 });
|
|
1615
1701
|
};
|
|
1616
1702
|
const capture = (chunk, keep) => {
|
|
1617
1703
|
if (settled) return;
|
|
@@ -1640,9 +1726,9 @@ function runProbeProcess(executable, adapterEntry, env, options, cwd) {
|
|
|
1640
1726
|
child.on("error", () => {
|
|
1641
1727
|
abort("failure");
|
|
1642
1728
|
});
|
|
1643
|
-
child.on("close", (
|
|
1729
|
+
child.on("close", (code2) => {
|
|
1644
1730
|
const output = Buffer.concat(stdout).toString("utf8");
|
|
1645
|
-
finish(
|
|
1731
|
+
finish(code2 === 0 ? { ok: true, stdout: output } : { ok: false, stdout: output, reason: "failure" });
|
|
1646
1732
|
});
|
|
1647
1733
|
child.stdin.on("error", () => {
|
|
1648
1734
|
});
|
|
@@ -1651,7 +1737,7 @@ function runProbeProcess(executable, adapterEntry, env, options, cwd) {
|
|
|
1651
1737
|
});
|
|
1652
1738
|
}
|
|
1653
1739
|
async function runProbe(executable, adapterEntry, env, options) {
|
|
1654
|
-
const createdCwd = await mkdtemp(
|
|
1740
|
+
const createdCwd = await mkdtemp(join10(tmpdir(), "paseo-room-pi-probe-"));
|
|
1655
1741
|
const cwd = await realpath(createdCwd);
|
|
1656
1742
|
try {
|
|
1657
1743
|
return await runProbeProcess(executable, adapterEntry, {
|
|
@@ -1728,15 +1814,15 @@ async function readPiOptional(path) {
|
|
|
1728
1814
|
}
|
|
1729
1815
|
async function piCredentialDiagnostic(layout, role, binary = "pi") {
|
|
1730
1816
|
const home = roleHome(layout, "pi", role);
|
|
1731
|
-
const path =
|
|
1817
|
+
const path = join10(home, "auth.json");
|
|
1732
1818
|
const launch = piLoginCommand(home, binary);
|
|
1733
1819
|
const state = await inspectCredentialPath(path);
|
|
1734
|
-
const
|
|
1820
|
+
const id2 = `pi.auth.${role}`;
|
|
1735
1821
|
if (state.kind !== "missing") {
|
|
1736
1822
|
return {
|
|
1737
1823
|
path,
|
|
1738
1824
|
checks: [preservedCredentialCheck({
|
|
1739
|
-
id,
|
|
1825
|
+
id: id2,
|
|
1740
1826
|
agent: "Pi",
|
|
1741
1827
|
role,
|
|
1742
1828
|
path,
|
|
@@ -1746,9 +1832,9 @@ async function piCredentialDiagnostic(layout, role, binary = "pi") {
|
|
|
1746
1832
|
};
|
|
1747
1833
|
}
|
|
1748
1834
|
const configured = presentNames(layout.envNames, AUTH_ENV2);
|
|
1749
|
-
if (configured.length > 0) return { path, checks: [ambientNamesCheck(
|
|
1835
|
+
if (configured.length > 0) return { path, checks: [ambientNamesCheck(id2, "Pi", role, configured)] };
|
|
1750
1836
|
return { path, checks: [{
|
|
1751
|
-
id: `${
|
|
1837
|
+
id: `${id2}.login-required`,
|
|
1752
1838
|
status: "warn",
|
|
1753
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.`,
|
|
1754
1840
|
fix: `Start a minimal, non-room-equivalent interactive login session with: ${launch}. Run /login, then exit Pi.`
|
|
@@ -1768,6 +1854,7 @@ var init_pi = __esm({
|
|
|
1768
1854
|
init_which();
|
|
1769
1855
|
init_mcp();
|
|
1770
1856
|
init_resources();
|
|
1857
|
+
init_skills();
|
|
1771
1858
|
PACKAGE_NAME = "pi-mcp-adapter";
|
|
1772
1859
|
PROBE_ID = "paseo-room-pi-mcp-probe";
|
|
1773
1860
|
PROBE_TIMEOUT_MS = 1e4;
|
|
@@ -1824,7 +1911,7 @@ var init_pi = __esm({
|
|
|
1824
1911
|
PI_OFFLINE: "1"
|
|
1825
1912
|
});
|
|
1826
1913
|
if (capability.status === "fail") return { entries: [], checks: [resolved.check, capability] };
|
|
1827
|
-
const settingsPath =
|
|
1914
|
+
const settingsPath = join10(home, "settings.json");
|
|
1828
1915
|
let settings;
|
|
1829
1916
|
try {
|
|
1830
1917
|
settings = renderPiSettings(await readPiOptional(settingsPath));
|
|
@@ -1842,7 +1929,7 @@ var init_pi = __esm({
|
|
|
1842
1929
|
]
|
|
1843
1930
|
};
|
|
1844
1931
|
}
|
|
1845
|
-
const operatorAppendPath =
|
|
1932
|
+
const operatorAppendPath = join10(home, "APPEND_SYSTEM.md");
|
|
1846
1933
|
let operatorAppend;
|
|
1847
1934
|
try {
|
|
1848
1935
|
operatorAppend = await readPiOptional(operatorAppendPath);
|
|
@@ -1861,7 +1948,7 @@ var init_pi = __esm({
|
|
|
1861
1948
|
};
|
|
1862
1949
|
}
|
|
1863
1950
|
const shared = await existingPaths(home, SHARED3);
|
|
1864
|
-
const mcpPath =
|
|
1951
|
+
const mcpPath = join10(home, MCP_FILE);
|
|
1865
1952
|
let servers;
|
|
1866
1953
|
try {
|
|
1867
1954
|
servers = jsonServerTable(await readPiOptional(mcpPath), MCP_KEYS);
|
|
@@ -1884,13 +1971,23 @@ var init_pi = __esm({
|
|
|
1884
1971
|
const entries = [];
|
|
1885
1972
|
const credentials = [];
|
|
1886
1973
|
const argv = {};
|
|
1974
|
+
const roomSkill = { name: ROOM_SKILL_NAME, source: roomSkillSource(layout) };
|
|
1887
1975
|
for (const role of roles) {
|
|
1888
1976
|
const target = roleHome(layout, "pi", role);
|
|
1889
|
-
const appendPath =
|
|
1977
|
+
const appendPath = join10(target, "APPEND_SYSTEM.md");
|
|
1890
1978
|
entries.push({ kind: "dir", path: target });
|
|
1891
|
-
entries.push({ kind: "file", path:
|
|
1979
|
+
entries.push({ kind: "file", path: join10(target, "settings.json"), content: settings });
|
|
1892
1980
|
entries.push({ kind: "file", path: appendPath, content: renderPiAppend(operatorAppend, role) });
|
|
1893
|
-
entries.push(...await roleResourceEntries({
|
|
1981
|
+
entries.push(...await roleResourceEntries({
|
|
1982
|
+
role,
|
|
1983
|
+
target,
|
|
1984
|
+
home,
|
|
1985
|
+
names: SHARED3,
|
|
1986
|
+
shared,
|
|
1987
|
+
executable: EXECUTABLE3,
|
|
1988
|
+
roomSkill,
|
|
1989
|
+
leadSkillProjection: leadSkillProjection(layout, "pi")
|
|
1990
|
+
}));
|
|
1894
1991
|
credentials.push(await piCredentialDiagnostic(layout, role, binary));
|
|
1895
1992
|
argv[role] = ["--no-extensions", "--extension", resolved.adapter.entry, "--no-approve", "--append-system-prompt", appendPath];
|
|
1896
1993
|
}
|
|
@@ -1909,8 +2006,8 @@ var init_pi = __esm({
|
|
|
1909
2006
|
// src/paseo.ts
|
|
1910
2007
|
import { gte, valid } from "semver";
|
|
1911
2008
|
import { z as z2 } from "zod";
|
|
1912
|
-
function minimumPaseoVersion(agents) {
|
|
1913
|
-
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;
|
|
1914
2011
|
}
|
|
1915
2012
|
function normalizeUrl(listen) {
|
|
1916
2013
|
const match = /^(?:ws:\/\/)?([^:/]+|\[[^\]]+\]):(\d+)$/.exec(listen.trim());
|
|
@@ -1919,7 +2016,7 @@ function normalizeUrl(listen) {
|
|
|
1919
2016
|
const local = ["localhost", "127.0.0.1", "0.0.0.0", "[::1]", "[::]"].includes(host) ? "127.0.0.1" : host;
|
|
1920
2017
|
return `ws://${local}:${port}`;
|
|
1921
2018
|
}
|
|
1922
|
-
function assessStatus(raw, minimumVersion = MINIMUM_VERSION) {
|
|
2019
|
+
function assessStatus(raw, minimumVersion = MINIMUM_VERSION, cliVersion) {
|
|
1923
2020
|
const parsed = statusSchema.safeParse(raw);
|
|
1924
2021
|
if (!parsed.success) {
|
|
1925
2022
|
return { checks: [fail("paseo.status", "Paseo status output was not understood.", "Upgrade Paseo, then run: paseo daemon status --json")] };
|
|
@@ -1928,8 +2025,8 @@ function assessStatus(raw, minimumVersion = MINIMUM_VERSION) {
|
|
|
1928
2025
|
if (status.localDaemon !== "running") {
|
|
1929
2026
|
return { checks: [fail("paseo.daemon", `Paseo daemon is ${status.localDaemon}.`, "Start it with: paseo daemon start")] };
|
|
1930
2027
|
}
|
|
1931
|
-
const cli = valid(status.cliVersion);
|
|
1932
|
-
const daemon = status.daemonVersion
|
|
2028
|
+
const cli = valid(status.cliVersion ?? cliVersion);
|
|
2029
|
+
const daemon = status.daemonVersion == null ? null : valid(status.daemonVersion);
|
|
1933
2030
|
if (!cli || !daemon) {
|
|
1934
2031
|
return { checks: [fail("paseo.version", "Paseo did not report a usable version.", "Upgrade Paseo to a release that reports semver versions.")] };
|
|
1935
2032
|
}
|
|
@@ -1959,11 +2056,16 @@ async function checkDaemon(layout, env = process.env, minimumVersion = MINIMUM_V
|
|
|
1959
2056
|
if (!output.ok) {
|
|
1960
2057
|
return { checks: [fail("paseo.status", "Could not read Paseo daemon status.", "Run: paseo daemon status --json")] };
|
|
1961
2058
|
}
|
|
2059
|
+
let status;
|
|
1962
2060
|
try {
|
|
1963
|
-
|
|
2061
|
+
status = JSON.parse(output.stdout);
|
|
1964
2062
|
} catch {
|
|
1965
2063
|
return assessStatus(void 0, minimumVersion);
|
|
1966
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);
|
|
1967
2069
|
}
|
|
1968
2070
|
async function withSession(daemon, run2, options = {}) {
|
|
1969
2071
|
const quiet = { debug() {
|
|
@@ -1987,7 +2089,13 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
1987
2089
|
import("@getpaseo/client"),
|
|
1988
2090
|
import("@getpaseo/client/internal/daemon-client")
|
|
1989
2091
|
]);
|
|
1990
|
-
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
|
+
});
|
|
1991
2099
|
client = {
|
|
1992
2100
|
...createPaseoApi(raw),
|
|
1993
2101
|
connect: () => raw.connect(),
|
|
@@ -1997,10 +2105,10 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
1997
2105
|
},
|
|
1998
2106
|
getConnectionState: () => raw.getConnectionState(),
|
|
1999
2107
|
listPlugins: () => raw.listPlugins(),
|
|
2000
|
-
installDirectoryPlugin: (path,
|
|
2001
|
-
reloadPlugin: (
|
|
2002
|
-
enablePlugin: (
|
|
2003
|
-
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)
|
|
2004
2112
|
};
|
|
2005
2113
|
}
|
|
2006
2114
|
try {
|
|
@@ -2030,17 +2138,17 @@ async function withSession(daemon, run2, options = {}) {
|
|
|
2030
2138
|
async listPlugins() {
|
|
2031
2139
|
return await client.listPlugins();
|
|
2032
2140
|
},
|
|
2033
|
-
async installPlugin(path,
|
|
2034
|
-
return await client.installDirectoryPlugin(path,
|
|
2141
|
+
async installPlugin(path, id2) {
|
|
2142
|
+
return await client.installDirectoryPlugin(path, id2);
|
|
2035
2143
|
},
|
|
2036
|
-
async reloadPlugin(
|
|
2037
|
-
return await client.reloadPlugin(
|
|
2144
|
+
async reloadPlugin(id2) {
|
|
2145
|
+
return await client.reloadPlugin(id2);
|
|
2038
2146
|
},
|
|
2039
|
-
async enablePlugin(
|
|
2040
|
-
return await client.enablePlugin(
|
|
2147
|
+
async enablePlugin(id2) {
|
|
2148
|
+
return await client.enablePlugin(id2);
|
|
2041
2149
|
},
|
|
2042
|
-
async removePlugin(
|
|
2043
|
-
await client.removePlugin(
|
|
2150
|
+
async removePlugin(id2) {
|
|
2151
|
+
await client.removePlugin(id2);
|
|
2044
2152
|
},
|
|
2045
2153
|
async refresh(ids) {
|
|
2046
2154
|
await client.providers.refresh({ providers: [...ids] });
|
|
@@ -2100,9 +2208,9 @@ var init_paseo = __esm({
|
|
|
2100
2208
|
PI_MINIMUM_VERSION = "0.8.0";
|
|
2101
2209
|
statusSchema = z2.object({
|
|
2102
2210
|
listen: z2.string().min(1),
|
|
2103
|
-
localDaemon: z2.
|
|
2104
|
-
cliVersion: z2.string(),
|
|
2105
|
-
daemonVersion: z2.string().
|
|
2211
|
+
localDaemon: z2.string().min(1),
|
|
2212
|
+
cliVersion: z2.string().optional(),
|
|
2213
|
+
daemonVersion: z2.string().nullish()
|
|
2106
2214
|
});
|
|
2107
2215
|
configSchema = z2.object({
|
|
2108
2216
|
config: z2.object({
|
|
@@ -2116,20 +2224,29 @@ var init_paseo = __esm({
|
|
|
2116
2224
|
});
|
|
2117
2225
|
|
|
2118
2226
|
// src/plugin.ts
|
|
2119
|
-
import { readFileSync as
|
|
2120
|
-
import { join as
|
|
2227
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
2228
|
+
import { join as join11 } from "path";
|
|
2121
2229
|
function loadAsset(path) {
|
|
2122
|
-
return
|
|
2230
|
+
return readFileSync3(new URL(`./plugin-assets/${path}`, import.meta.url), "utf8");
|
|
2123
2231
|
}
|
|
2124
2232
|
function claudeCarrierPluginDir(layout) {
|
|
2125
|
-
return
|
|
2233
|
+
return join11(layout.roomHome, "plugin");
|
|
2234
|
+
}
|
|
2235
|
+
function typescriptTemplateLiteral(value) {
|
|
2236
|
+
const escaped = value.replaceAll("\\", "\\\\").replaceAll("`", "\\`").replaceAll("${", "\\${");
|
|
2237
|
+
return `\`${escaped}\``;
|
|
2126
2238
|
}
|
|
2127
2239
|
function renderClaudeCarrierContract(roles) {
|
|
2128
|
-
const
|
|
2240
|
+
const entries = roles.map((role) => {
|
|
2241
|
+
const id2 = JSON.stringify(providerId("claude", role));
|
|
2242
|
+
return ` ${id2}: ${typescriptTemplateLiteral(renderInstructions(role))},`;
|
|
2243
|
+
});
|
|
2129
2244
|
return [
|
|
2130
2245
|
"/** Generated by paseo-room. The Markdown prompt assets remain authoritative. */",
|
|
2131
2246
|
`export const GENERATION = ${JSON.stringify(contractDigest())};`,
|
|
2132
|
-
|
|
2247
|
+
"export const CONTRACT: Readonly<Record<string, string>> = {",
|
|
2248
|
+
...entries,
|
|
2249
|
+
"};",
|
|
2133
2250
|
""
|
|
2134
2251
|
].join("\n");
|
|
2135
2252
|
}
|
|
@@ -2137,18 +2254,19 @@ function claudeCarrierEntries(layout, roles) {
|
|
|
2137
2254
|
const root = claudeCarrierPluginDir(layout);
|
|
2138
2255
|
return [
|
|
2139
2256
|
{ kind: "dir", path: root },
|
|
2140
|
-
{ kind: "dir", path:
|
|
2141
|
-
...STATIC_ASSETS.map((path) => ({ kind: "file", path:
|
|
2142
|
-
{ kind: "file", path:
|
|
2257
|
+
{ kind: "dir", path: join11(root, "server") },
|
|
2258
|
+
...STATIC_ASSETS.map((path) => ({ kind: "file", path: join11(root, path), content: loadAsset(path) })),
|
|
2259
|
+
{ kind: "file", path: join11(root, "server", "contract.ts"), content: renderClaudeCarrierContract(roles) }
|
|
2143
2260
|
];
|
|
2144
2261
|
}
|
|
2145
|
-
var CLAUDE_CARRIER_PLUGIN_ID, STATIC_ASSETS;
|
|
2262
|
+
var CLAUDE_CARRIER_PLUGIN_ID, CLAUDE_CARRIER_PASEO_RANGE, STATIC_ASSETS;
|
|
2146
2263
|
var init_plugin = __esm({
|
|
2147
2264
|
"src/plugin.ts"() {
|
|
2148
2265
|
"use strict";
|
|
2149
2266
|
init_roles();
|
|
2150
2267
|
init_instructions();
|
|
2151
2268
|
CLAUDE_CARRIER_PLUGIN_ID = "paseo-room-claude-carrier";
|
|
2269
|
+
CLAUDE_CARRIER_PASEO_RANGE = ">=0.8.0 <0.10.0";
|
|
2152
2270
|
STATIC_ASSETS = [
|
|
2153
2271
|
"paseo-plugin.json",
|
|
2154
2272
|
"package.json",
|
|
@@ -2159,9 +2277,1312 @@ var init_plugin = __esm({
|
|
|
2159
2277
|
}
|
|
2160
2278
|
});
|
|
2161
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
|
+
|
|
2162
3582
|
// src/commands.ts
|
|
2163
|
-
import { join as
|
|
2164
|
-
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";
|
|
2165
3586
|
function sessionOptions(options) {
|
|
2166
3587
|
const password = (options.env ?? process.env).PASEO_PASSWORD;
|
|
2167
3588
|
return { ...password === void 0 ? {} : { password }, ...options.factory ? { factory: options.factory } : {} };
|
|
@@ -2188,18 +3609,47 @@ function roleProfiles(agent, roles) {
|
|
|
2188
3609
|
notes: ROLE_NOTES[role]
|
|
2189
3610
|
}));
|
|
2190
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
|
+
}
|
|
2191
3638
|
async function buildDesired(layout, agents, roles, build = {}) {
|
|
2192
3639
|
const entries = [
|
|
2193
3640
|
{ kind: "dir", path: layout.roomHome },
|
|
2194
3641
|
{ kind: "dir", path: sharedRoom(layout) },
|
|
2195
|
-
|
|
3642
|
+
// Earlier versions generated a default workspace protocol template here. The room ships no
|
|
3643
|
+
// default protocol now, so the old generated regular file is declared absent and removed.
|
|
3644
|
+
{ kind: "absent", path: join16(sharedRoom(layout), "WORKSPACE_PROTOCOL.md") },
|
|
3645
|
+
...roomSkillEntries(layout, agents)
|
|
2196
3646
|
];
|
|
2197
3647
|
const providers = {};
|
|
2198
3648
|
const profiles = [];
|
|
2199
3649
|
const checks = [];
|
|
2200
3650
|
const binaries = {};
|
|
2201
|
-
for (const
|
|
2202
|
-
const agent = AGENTS[
|
|
3651
|
+
for (const id2 of agents) {
|
|
3652
|
+
const agent = AGENTS[id2];
|
|
2203
3653
|
const plan = await agent.build(layout, roles, build);
|
|
2204
3654
|
entries.push(...plan.entries);
|
|
2205
3655
|
checks.push(...plan.checks);
|
|
@@ -2209,13 +3659,13 @@ async function buildDesired(layout, agents, roles, build = {}) {
|
|
|
2209
3659
|
const collisions = plan.entries.filter((entry) => credentialPaths.has(entry.path));
|
|
2210
3660
|
if (collisions.length > 0) {
|
|
2211
3661
|
checks.push(fail(
|
|
2212
|
-
`${
|
|
3662
|
+
`${id2}.credentials`,
|
|
2213
3663
|
`${agent.label} produced managed entries for preserve-only credential paths: ${collisions.map((entry) => entry.path).join(", ")}.`,
|
|
2214
3664
|
"This is an adapter defect; do not apply the plan."
|
|
2215
3665
|
));
|
|
2216
3666
|
}
|
|
2217
3667
|
if (plan.binary === void 0) continue;
|
|
2218
|
-
binaries[
|
|
3668
|
+
binaries[id2] = plan.binary;
|
|
2219
3669
|
Object.assign(providers, roleProviders(layout, agent, {
|
|
2220
3670
|
binary: plan.binary,
|
|
2221
3671
|
...plan.argv ? { argv: plan.argv } : {},
|
|
@@ -2223,33 +3673,42 @@ async function buildDesired(layout, agents, roles, build = {}) {
|
|
|
2223
3673
|
}, roles));
|
|
2224
3674
|
profiles.push(...roleProfiles(agent, roles));
|
|
2225
3675
|
}
|
|
2226
|
-
if (agents.every((
|
|
3676
|
+
if (agents.every((id2) => binaries[id2] !== void 0)) {
|
|
2227
3677
|
entries.push({
|
|
2228
3678
|
kind: "file",
|
|
2229
|
-
path:
|
|
3679
|
+
path: join16(layout.roomHome, AUTHENTICATION_GUIDE),
|
|
2230
3680
|
content: renderAuthenticationGuide(layout.roomHome, binaries, agents, roles)
|
|
2231
3681
|
});
|
|
2232
3682
|
checks.push(warn(
|
|
2233
3683
|
"room.authentication-not-validated",
|
|
2234
|
-
`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)}.`,
|
|
2235
3685
|
"After setup --apply, follow that guide or run: paseo-room auth login <agent> <role>"
|
|
2236
3686
|
));
|
|
2237
3687
|
}
|
|
2238
|
-
const
|
|
2239
|
-
if (
|
|
2240
|
-
|
|
2241
|
-
|
|
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 } };
|
|
2242
3701
|
}
|
|
2243
3702
|
function contractProvenance(previous) {
|
|
2244
|
-
const
|
|
3703
|
+
const digest3 = contractDigest();
|
|
2245
3704
|
if (!previous) return [];
|
|
2246
|
-
if (previous.contract ===
|
|
2247
|
-
return [pass("room.contract", `Role contract generation ${
|
|
3705
|
+
if (previous.contract === digest3) {
|
|
3706
|
+
return [pass("room.contract", `Role contract generation ${digest3} matches this package.`)];
|
|
2248
3707
|
}
|
|
2249
3708
|
const held = previous.contract === void 0 ? `was installed before the room recorded its contract generation (package ${previous.version})` : `holds contract generation ${previous.contract}`;
|
|
2250
3709
|
return [warn(
|
|
2251
3710
|
"room.contract",
|
|
2252
|
-
`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.`,
|
|
2253
3712
|
"Run: paseo-room setup --apply, then restart the affected seats."
|
|
2254
3713
|
)];
|
|
2255
3714
|
}
|
|
@@ -2272,14 +3731,14 @@ function fileDriftSummary(drifted) {
|
|
|
2272
3731
|
return `${count} ${subject} missing or outdated, and ${String(removals)} of them are present but suppressed by this room.`;
|
|
2273
3732
|
}
|
|
2274
3733
|
function fileDriftDetail(targets) {
|
|
2275
|
-
const catalogs = targets.filter((target) => target.endsWith(`${
|
|
3734
|
+
const catalogs = targets.filter((target) => target.endsWith(`${sep3}model-catalog.json`));
|
|
2276
3735
|
if (catalogs.length === 0) return "";
|
|
2277
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`;
|
|
2278
3737
|
return ` ${count}, so those seats currently lack the scrubbed catalog closure rather than only older contract text.`;
|
|
2279
3738
|
}
|
|
2280
3739
|
async function safeDirectory(path, checks) {
|
|
2281
3740
|
try {
|
|
2282
|
-
const stat2 = await
|
|
3741
|
+
const stat2 = await lstat5(path);
|
|
2283
3742
|
if (stat2.isDirectory()) return true;
|
|
2284
3743
|
checks.push(fail(
|
|
2285
3744
|
"room.path-safety",
|
|
@@ -2300,17 +3759,32 @@ async function safeDirectory(path, checks) {
|
|
|
2300
3759
|
async function roomPathSafety(layout, agents, roles) {
|
|
2301
3760
|
const checks = [];
|
|
2302
3761
|
if (!await safeDirectory(layout.roomHome, checks)) return checks;
|
|
2303
|
-
|
|
2304
|
-
const
|
|
3762
|
+
const room = sharedRoom(layout);
|
|
3763
|
+
const roomIsSafe = await safeDirectory(room, checks);
|
|
3764
|
+
if (roomIsSafe && agents.length > 0) {
|
|
3765
|
+
const skills = join16(room, "skills");
|
|
3766
|
+
if (await safeDirectory(skills, checks)) {
|
|
3767
|
+
const source = roomSkillSource(layout);
|
|
3768
|
+
if (await safeDirectory(source, checks)) await safeDirectory(join16(source, "references"), checks);
|
|
3769
|
+
}
|
|
3770
|
+
const projections = skillProjectionRoot(layout);
|
|
3771
|
+
if (await safeDirectory(projections, checks)) {
|
|
3772
|
+
for (const agent of agents) {
|
|
3773
|
+
const agentRoot = agentSkillProjectionRoot(layout, agent);
|
|
3774
|
+
if (await safeDirectory(agentRoot, checks)) await safeDirectory(leadSkillProjection(layout, agent), checks);
|
|
3775
|
+
}
|
|
3776
|
+
}
|
|
3777
|
+
}
|
|
3778
|
+
const rolesHome = join16(layout.roomHome, "roles");
|
|
2305
3779
|
if (!await safeDirectory(rolesHome, checks)) return checks;
|
|
2306
3780
|
for (const agent of agents) {
|
|
2307
|
-
const agentHome =
|
|
3781
|
+
const agentHome = join16(rolesHome, agent);
|
|
2308
3782
|
if (!await safeDirectory(agentHome, checks)) continue;
|
|
2309
3783
|
for (const role of roles) await safeDirectory(roleHome(layout, agent, role), checks);
|
|
2310
3784
|
}
|
|
2311
3785
|
return checks;
|
|
2312
3786
|
}
|
|
2313
|
-
async function staleFrom(layout, previous, agents, roles) {
|
|
3787
|
+
async function staleFrom(layout, previous, agents, roles, runtime) {
|
|
2314
3788
|
if (!previous) return NOTHING_STALE;
|
|
2315
3789
|
const kept = new Set(agents.flatMap((agent) => roles.map((role) => providerId(agent, role))));
|
|
2316
3790
|
const providerIds = [];
|
|
@@ -2328,110 +3802,148 @@ async function staleFrom(layout, previous, agents, roles) {
|
|
|
2328
3802
|
return {
|
|
2329
3803
|
providerIds,
|
|
2330
3804
|
profileIds,
|
|
2331
|
-
|
|
3805
|
+
removePlugins: [
|
|
3806
|
+
...previous.agents.includes("claude") && !agents.includes("claude") ? [carrierSpec(layout)] : [],
|
|
3807
|
+
...previous.runtime?.enabled === true && !runtime ? [runtimeSpec(layout)] : []
|
|
3808
|
+
],
|
|
2332
3809
|
retainedDirectories
|
|
2333
3810
|
};
|
|
2334
3811
|
}
|
|
2335
|
-
function pluginPlan(
|
|
2336
|
-
const livePlugin = plugins.find((plugin) => plugin.id ===
|
|
2337
|
-
|
|
2338
|
-
if (!shouldManage) return { checks: [] };
|
|
2339
|
-
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) {
|
|
2340
3815
|
return {
|
|
2341
3816
|
livePlugin,
|
|
2342
3817
|
checks: [fail(
|
|
2343
|
-
|
|
2344
|
-
`Plugin ${
|
|
3818
|
+
`${spec.check}.path`,
|
|
3819
|
+
`Plugin ${spec.id} is registered from ${livePlugin.path}, not the room-owned path ${spec.path}.`,
|
|
2345
3820
|
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2346
3821
|
)]
|
|
2347
3822
|
};
|
|
2348
3823
|
}
|
|
2349
|
-
if (
|
|
3824
|
+
if (wanted) {
|
|
2350
3825
|
const healthy = livePlugin?.enabled === true && livePlugin.status === "running" && livePlugin.error === void 0;
|
|
2351
3826
|
return {
|
|
2352
3827
|
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2353
|
-
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 },
|
|
2354
3829
|
checks: []
|
|
2355
3830
|
};
|
|
2356
3831
|
}
|
|
2357
3832
|
return {
|
|
2358
3833
|
...livePlugin === void 0 ? {} : { livePlugin },
|
|
2359
|
-
operation: { action: livePlugin === void 0 ? "noop" : "remove", kind: "plugin", target:
|
|
3834
|
+
operation: { action: livePlugin === void 0 ? "noop" : "remove", kind: "plugin", target: spec.id },
|
|
2360
3835
|
checks: []
|
|
2361
3836
|
};
|
|
2362
3837
|
}
|
|
2363
|
-
async function planRoom(session, desired, stale
|
|
2364
|
-
const inspectPlugins = desired.
|
|
3838
|
+
async function planRoom(session, desired, stale) {
|
|
3839
|
+
const inspectPlugins = desired.plugins.length > 0 || stale.removePlugins.length > 0;
|
|
2365
3840
|
const [live, liveProfiles, plugins] = await Promise.all([
|
|
2366
3841
|
session.readProviders(),
|
|
2367
3842
|
session.readProfiles(),
|
|
2368
3843
|
inspectPlugins ? session.listPlugins() : Promise.resolve([])
|
|
2369
3844
|
]);
|
|
2370
3845
|
const byId = new Map(liveProfiles.map((entry) => [String(entry.id), entry]));
|
|
2371
|
-
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);
|
|
2372
3852
|
return {
|
|
2373
3853
|
liveProfiles,
|
|
2374
|
-
|
|
2375
|
-
pluginChecks: plugin.checks,
|
|
3854
|
+
livePlugins,
|
|
3855
|
+
pluginChecks: planned.flatMap((plugin) => plugin.checks),
|
|
2376
3856
|
operations: [
|
|
2377
3857
|
...await planEntries(desired.entries),
|
|
2378
|
-
...Object.entries(desired.providers).map(([
|
|
2379
|
-
action: !(
|
|
3858
|
+
...Object.entries(desired.providers).map(([id2, provider]) => ({
|
|
3859
|
+
action: !(id2 in live) ? "create" : providerMatches(provider, live[id2]) ? "noop" : "update",
|
|
2380
3860
|
kind: "provider",
|
|
2381
|
-
target:
|
|
3861
|
+
target: id2
|
|
2382
3862
|
})),
|
|
2383
3863
|
...desired.profiles.map((profile) => ({
|
|
2384
3864
|
action: !byId.has(profile.id) ? "create" : profileMatches(profile, byId.get(profile.id)) ? "noop" : "update",
|
|
2385
3865
|
kind: "profile",
|
|
2386
3866
|
target: profile.id
|
|
2387
3867
|
})),
|
|
2388
|
-
...stale.providerIds.filter((
|
|
2389
|
-
...stale.profileIds.filter((
|
|
2390
|
-
...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])
|
|
2391
3871
|
]
|
|
2392
3872
|
};
|
|
2393
3873
|
}
|
|
2394
|
-
function pluginsEnabledCheck(enabled) {
|
|
2395
|
-
return enabled ? pass(
|
|
2396
|
-
|
|
2397
|
-
|
|
3874
|
+
function pluginsEnabledCheck(spec, enabled) {
|
|
3875
|
+
return enabled ? pass(`${spec.check}.enabled`, spec.enabledPass) : fail(
|
|
3876
|
+
`${spec.check}.enabled`,
|
|
3877
|
+
spec.enabledFail,
|
|
2398
3878
|
"Review that trust boundary, enable plugins in Paseo Settings, reload Paseo, then run setup again."
|
|
2399
3879
|
);
|
|
2400
3880
|
}
|
|
2401
|
-
function pluginRuntimeCheck(
|
|
3881
|
+
function pluginRuntimeCheck(spec, plugin) {
|
|
2402
3882
|
if (plugin === void 0) {
|
|
2403
|
-
return fail(
|
|
3883
|
+
return fail(`${spec.check}.runtime`, `Plugin ${spec.id} is not registered.`, "Run: paseo-room setup --apply");
|
|
2404
3884
|
}
|
|
2405
|
-
if (plugin.path !==
|
|
3885
|
+
if (plugin.path !== spec.path) {
|
|
2406
3886
|
return fail(
|
|
2407
|
-
|
|
2408
|
-
`Plugin ${
|
|
3887
|
+
`${spec.check}.runtime`,
|
|
3888
|
+
`Plugin ${spec.id} is registered from ${plugin.path}, not ${spec.path}.`,
|
|
2409
3889
|
"Move or remove that conflicting plugin registration manually, then run setup again."
|
|
2410
3890
|
);
|
|
2411
3891
|
}
|
|
2412
3892
|
if (!plugin.enabled || plugin.status !== "running" || plugin.error !== void 0) {
|
|
2413
3893
|
const detail = plugin.error === void 0 ? `status ${plugin.status}` : `${plugin.status}: ${plugin.error}`;
|
|
2414
|
-
return fail(
|
|
3894
|
+
return fail(`${spec.check}.runtime`, `Plugin ${spec.id} is not running (${detail}).`, "Run: paseo-room setup --apply");
|
|
2415
3895
|
}
|
|
2416
|
-
return pass(
|
|
3896
|
+
return pass(`${spec.check}.runtime`, `${spec.running} is running from ${spec.path}.`);
|
|
2417
3897
|
}
|
|
2418
|
-
async function
|
|
2419
|
-
|
|
2420
|
-
if (plan.
|
|
2421
|
-
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);
|
|
2422
3901
|
}
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
plugin =
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
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));
|
|
2433
3916
|
}
|
|
2434
|
-
return
|
|
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
|
+
)];
|
|
3931
|
+
}
|
|
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
|
+
)];
|
|
2435
3947
|
}
|
|
2436
3948
|
function markerMemoryContract(agents, memoryContract) {
|
|
2437
3949
|
return agents.includes("claude") ? memoryContract : void 0;
|
|
@@ -2457,23 +3969,29 @@ async function setup(options = {}) {
|
|
|
2457
3969
|
const pathSafety = await roomPathSafety(layout, safetyAgents, ROLES);
|
|
2458
3970
|
if (hasFailure(pathSafety)) return failed("setup", pathSafety);
|
|
2459
3971
|
const compatibilityAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2460
|
-
const
|
|
3972
|
+
const runtime = options.runtime === true;
|
|
3973
|
+
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(compatibilityAgents, runtime || previous?.runtime?.enabled === true));
|
|
2461
3974
|
if (!daemon.daemon) return failed("setup", daemon.checks);
|
|
2462
|
-
const
|
|
2463
|
-
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
|
+
];
|
|
2464
3982
|
const retained = stale.retainedDirectories.length === 0 ? [] : [warn(
|
|
2465
3983
|
"room.stale-role-homes-preserved",
|
|
2466
3984
|
`Preserved ${String(stale.retainedDirectories.length)} deselected role homes because they may contain role-owned credentials or runtime state.`,
|
|
2467
3985
|
`Use paseo-room remove --apply to delete the entire room after reviewing its credential warning, or remove deselected homes manually: ${stale.retainedDirectories.join(", ")}`
|
|
2468
3986
|
)];
|
|
2469
|
-
const checks = [...daemon.checks, ...desired.checks, ...retained];
|
|
3987
|
+
const checks = [...daemon.checks, ...runtimeChecks, ...desired.checks, ...retained];
|
|
2470
3988
|
if (hasFailure(checks)) return failed("setup", checks);
|
|
2471
3989
|
return withSession(daemon.daemon, async (session) => {
|
|
2472
|
-
const
|
|
2473
|
-
const plan = await planRoom(session, desired, stale, expectedPluginPath);
|
|
3990
|
+
const plan = await planRoom(session, desired, stale);
|
|
2474
3991
|
const { operations, liveProfiles } = plan;
|
|
2475
3992
|
const pending = operations.filter((operation) => operation.action !== "noop");
|
|
2476
|
-
const
|
|
3993
|
+
const enabled = desired.plugins.length === 0 ? true : await session.pluginsEnabled();
|
|
3994
|
+
const pluginPreconditions = [...desired.plugins.map((spec) => pluginsEnabledCheck(spec, enabled)), ...plan.pluginChecks];
|
|
2477
3995
|
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...memoryContractDiagnostic(agents, memoryContract), ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2478
3996
|
if (hasFailure(preApplyDiagnostics) || !options.apply) {
|
|
2479
3997
|
return {
|
|
@@ -2484,7 +4002,7 @@ async function setup(options = {}) {
|
|
|
2484
4002
|
operations
|
|
2485
4003
|
};
|
|
2486
4004
|
}
|
|
2487
|
-
const markerPath =
|
|
4005
|
+
const markerPath = join16(layout.roomHome, MARKER);
|
|
2488
4006
|
const markerEntries = desired.entries.filter((entry) => entry.path === markerPath);
|
|
2489
4007
|
await applyEntries(desired.entries.filter((entry) => entry.path !== markerPath));
|
|
2490
4008
|
const ids = Object.keys(desired.providers);
|
|
@@ -2496,14 +4014,15 @@ async function setup(options = {}) {
|
|
|
2496
4014
|
await session.writeProfiles(appliedProfiles);
|
|
2497
4015
|
}
|
|
2498
4016
|
await session.refresh(ids);
|
|
2499
|
-
const pluginChecks = await
|
|
4017
|
+
const pluginChecks = await reconcilePlugins(session, desired, stale, plan, pending);
|
|
2500
4018
|
await applyEntries(markerEntries);
|
|
2501
4019
|
const appliedMarker = {
|
|
2502
4020
|
version: package_default.version,
|
|
2503
4021
|
agents: [...agents],
|
|
2504
4022
|
roles: [...ROLES],
|
|
2505
4023
|
contract: contractDigest(),
|
|
2506
|
-
...markerMemoryContract(agents, memoryContract) === false ? { claudeMemoryContract: false } : {}
|
|
4024
|
+
...markerMemoryContract(agents, memoryContract) === false ? { claudeMemoryContract: false } : {},
|
|
4025
|
+
...desired.runtime === void 0 ? {} : { runtime: desired.runtime }
|
|
2507
4026
|
};
|
|
2508
4027
|
const appliedChecks = [
|
|
2509
4028
|
...checks,
|
|
@@ -2535,17 +4054,23 @@ async function verify(options = {}) {
|
|
|
2535
4054
|
if (invalid.length > 0) return failed("verify", invalid);
|
|
2536
4055
|
const pathSafety = await roomPathSafety(layout, marker.agents, marker.roles);
|
|
2537
4056
|
if (hasFailure(pathSafety)) return failed("verify", pathSafety);
|
|
2538
|
-
const
|
|
4057
|
+
const runtime = marker.runtime?.enabled === true;
|
|
4058
|
+
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(marker.agents, runtime));
|
|
2539
4059
|
if (!daemon.daemon) return failed("verify", daemon.checks);
|
|
4060
|
+
const version = daemon.daemon.version;
|
|
2540
4061
|
const desired = await buildDesired(layout, marker.agents, marker.roles, {
|
|
2541
|
-
// The room's own recorded
|
|
2542
|
-
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
|
|
2543
4065
|
});
|
|
2544
|
-
const
|
|
2545
|
-
|
|
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);
|
|
2546
4072
|
return withSession(daemon.daemon, async (session) => {
|
|
2547
|
-
const
|
|
2548
|
-
const plan = await planRoom(session, desired, NOTHING_STALE, expectedPluginPath);
|
|
4073
|
+
const plan = await planRoom(session, desired, NOTHING_STALE);
|
|
2549
4074
|
const { operations, liveProfiles } = plan;
|
|
2550
4075
|
const drifted = operations.filter((operation) => operation.action !== "noop");
|
|
2551
4076
|
const count = (kind) => drifted.filter((operation) => operation.kind === kind).length;
|
|
@@ -2553,11 +4078,12 @@ async function verify(options = {}) {
|
|
|
2553
4078
|
const profiles = count("profile");
|
|
2554
4079
|
const driftedFiles = drifted.filter((operation) => !["provider", "profile", "plugin"].includes(operation.kind));
|
|
2555
4080
|
const files = driftedFiles.length;
|
|
2556
|
-
const
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
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
|
+
]);
|
|
2561
4087
|
const all = [
|
|
2562
4088
|
...checks,
|
|
2563
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"),
|
|
@@ -2590,22 +4116,26 @@ async function remove(options = {}) {
|
|
|
2590
4116
|
if (invalid.length > 0) return failed("remove", invalid);
|
|
2591
4117
|
const ids = marker.agents.flatMap((agent) => marker.roles.map((role) => providerId(agent, role)));
|
|
2592
4118
|
const profiles = new Set(marker.agents.flatMap((agent) => marker.roles.map((role) => profileId(agent, role))));
|
|
2593
|
-
const
|
|
4119
|
+
const plugins = [
|
|
4120
|
+
...marker.agents.includes("claude") ? [carrierSpec(layout)] : [],
|
|
4121
|
+
...marker.runtime?.enabled === true ? [runtimeSpec(layout)] : []
|
|
4122
|
+
];
|
|
2594
4123
|
const operations = [
|
|
2595
|
-
...ids.map((
|
|
2596
|
-
...[...profiles].map((
|
|
2597
|
-
...
|
|
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 })),
|
|
2598
4127
|
{ action: "remove", kind: "dir", target: layout.roomHome }
|
|
2599
4128
|
];
|
|
2600
4129
|
const credentialWarning = warn(
|
|
2601
4130
|
"room.remove.credentials",
|
|
2602
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.`
|
|
2603
4132
|
);
|
|
4133
|
+
const runtimeWarnings = await runtimeRemovalWarning(layout);
|
|
2604
4134
|
if (!options.apply) {
|
|
2605
|
-
return { command: "remove", outcome: "changes-planned", changed: false, checks: [credentialWarning], operations };
|
|
4135
|
+
return { command: "remove", outcome: "changes-planned", changed: false, checks: [credentialWarning, ...runtimeWarnings], operations };
|
|
2606
4136
|
}
|
|
2607
4137
|
const daemon = await checkDaemon(layout, options.env);
|
|
2608
|
-
const checks = [credentialWarning];
|
|
4138
|
+
const checks = [credentialWarning, ...runtimeWarnings];
|
|
2609
4139
|
if (!daemon.daemon) {
|
|
2610
4140
|
return failed("remove", [
|
|
2611
4141
|
credentialWarning,
|
|
@@ -2619,13 +4149,15 @@ async function remove(options = {}) {
|
|
|
2619
4149
|
}
|
|
2620
4150
|
try {
|
|
2621
4151
|
await withSession(daemon.daemon, async (session) => {
|
|
2622
|
-
if (
|
|
2623
|
-
const
|
|
2624
|
-
const
|
|
2625
|
-
|
|
2626
|
-
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);
|
|
2627
4160
|
}
|
|
2628
|
-
if (plugin !== void 0) await session.removePlugin(CLAUDE_CARRIER_PLUGIN_ID);
|
|
2629
4161
|
}
|
|
2630
4162
|
await session.removeProviders(ids);
|
|
2631
4163
|
const live = await session.readProfiles();
|
|
@@ -2644,7 +4176,7 @@ async function remove(options = {}) {
|
|
|
2644
4176
|
]);
|
|
2645
4177
|
}
|
|
2646
4178
|
try {
|
|
2647
|
-
await
|
|
4179
|
+
await rm4(layout.roomHome, { recursive: true, force: true });
|
|
2648
4180
|
} catch (error) {
|
|
2649
4181
|
const detail = error instanceof Error ? error.message : "unknown filesystem error";
|
|
2650
4182
|
checks.push(fail(
|
|
@@ -2673,10 +4205,13 @@ var init_commands = __esm({
|
|
|
2673
4205
|
init_plugin();
|
|
2674
4206
|
init_result();
|
|
2675
4207
|
init_instructions();
|
|
4208
|
+
init_skills();
|
|
2676
4209
|
init_room();
|
|
4210
|
+
init_runtime();
|
|
4211
|
+
init_runtime_state();
|
|
2677
4212
|
init_roles();
|
|
2678
4213
|
AGENTS = { codex: codexAgent, claude: claudeAgent, pi: piAgent };
|
|
2679
|
-
NOTHING_STALE = { providerIds: [], profileIds: [],
|
|
4214
|
+
NOTHING_STALE = { providerIds: [], profileIds: [], removePlugins: [], retainedDirectories: [] };
|
|
2680
4215
|
}
|
|
2681
4216
|
});
|
|
2682
4217
|
|
|
@@ -2727,7 +4262,15 @@ async function runWizard(prompts, emit, write, options = {}, onAction) {
|
|
|
2727
4262
|
if (typeof carrier === "symbol") return cancelled();
|
|
2728
4263
|
claudeMemoryContract = carrier === "both";
|
|
2729
4264
|
}
|
|
2730
|
-
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 } : {} };
|
|
2731
4274
|
const preview = await setup(setupOptions);
|
|
2732
4275
|
const status = emit(preview);
|
|
2733
4276
|
if (preview.outcome !== "changes-planned") return status;
|
|
@@ -2753,9 +4296,105 @@ var init_wizard = __esm({
|
|
|
2753
4296
|
init_package();
|
|
2754
4297
|
init_auth();
|
|
2755
4298
|
init_commands();
|
|
2756
|
-
init_fsops();
|
|
2757
4299
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
2758
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
|
+
|
|
2759
4398
|
// src/render.ts
|
|
2760
4399
|
function renderJson(result) {
|
|
2761
4400
|
return `${JSON.stringify({ schemaVersion: 2, ...result }, null, 2)}
|
|
@@ -2786,8 +4425,9 @@ function renderHuman(result) {
|
|
|
2786
4425
|
init_result();
|
|
2787
4426
|
init_roles();
|
|
2788
4427
|
init_prompts();
|
|
4428
|
+
init_skills();
|
|
2789
4429
|
function collectAgent(value, previous) {
|
|
2790
|
-
const agent = AGENT_IDS.find((
|
|
4430
|
+
const agent = AGENT_IDS.find((id2) => id2 === value);
|
|
2791
4431
|
if (!agent) throw new InvalidArgumentError(`Unknown agent: ${value}`);
|
|
2792
4432
|
const seated = previous ?? [];
|
|
2793
4433
|
return seated.includes(agent) ? seated : [...seated, agent];
|
|
@@ -2806,7 +4446,8 @@ function optionsFrom(raw, base) {
|
|
|
2806
4446
|
...agents ? { agents } : {},
|
|
2807
4447
|
...raw.apply === true ? { apply: true } : {},
|
|
2808
4448
|
// Commander defaults a --no- flag to true, so only an explicit opt-out is carried.
|
|
2809
|
-
...raw.claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
4449
|
+
...raw.claudeMemoryContract === false ? { claudeMemoryContract: false } : {},
|
|
4450
|
+
...raw.runtime === true ? { runtime: true } : {}
|
|
2810
4451
|
};
|
|
2811
4452
|
}
|
|
2812
4453
|
function failedFromThrown(command, error) {
|
|
@@ -2817,6 +4458,13 @@ function failedFromThrown(command, error) {
|
|
|
2817
4458
|
"Reinstall paseo-room, then try again."
|
|
2818
4459
|
)]);
|
|
2819
4460
|
}
|
|
4461
|
+
if (error instanceof SkillAssetError) {
|
|
4462
|
+
return failed(command, [fail(
|
|
4463
|
+
`${command}.skill-asset`,
|
|
4464
|
+
error.message,
|
|
4465
|
+
"Reinstall paseo-room, then try again."
|
|
4466
|
+
)]);
|
|
4467
|
+
}
|
|
2820
4468
|
if (error instanceof ManagedPathError) {
|
|
2821
4469
|
return failed(command, [fail(`${command}.managed-path`, error.message, error.fix)]);
|
|
2822
4470
|
}
|
|
@@ -2829,7 +4477,7 @@ function failedFromThrown(command, error) {
|
|
|
2829
4477
|
}
|
|
2830
4478
|
async function runCli(argv, output, context = {}) {
|
|
2831
4479
|
const password = (context.options?.env ?? process.env).PASEO_PASSWORD;
|
|
2832
|
-
const redact = (
|
|
4480
|
+
const redact = (text3) => password ? text3.split(password).join("[redacted]") : text3;
|
|
2833
4481
|
let json = false;
|
|
2834
4482
|
const emit = (result) => {
|
|
2835
4483
|
output.stdout(redact(json ? renderJson(result) : renderHuman(result)));
|
|
@@ -2848,8 +4496,8 @@ async function runCli(argv, output, context = {}) {
|
|
|
2848
4496
|
return await runWizard2(
|
|
2849
4497
|
context.prompts ?? terminalPrompts2,
|
|
2850
4498
|
emit,
|
|
2851
|
-
(
|
|
2852
|
-
output.stdout(
|
|
4499
|
+
(text3) => {
|
|
4500
|
+
output.stdout(text3);
|
|
2853
4501
|
},
|
|
2854
4502
|
base,
|
|
2855
4503
|
(action) => {
|
|
@@ -2860,10 +4508,10 @@ async function runCli(argv, output, context = {}) {
|
|
|
2860
4508
|
return emit(failedFromThrown(wizardCommand, error));
|
|
2861
4509
|
}
|
|
2862
4510
|
}
|
|
2863
|
-
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: (
|
|
2864
|
-
output.stdout(
|
|
2865
|
-
}, writeErr: (
|
|
2866
|
-
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);
|
|
2867
4515
|
} }).exitOverride();
|
|
2868
4516
|
let command;
|
|
2869
4517
|
let commandArguments;
|
|
@@ -2883,10 +4531,24 @@ async function runCli(argv, output, context = {}) {
|
|
|
2883
4531
|
try {
|
|
2884
4532
|
if (raw.claudeMemoryContract === false && (command === "verify" || command === "remove")) {
|
|
2885
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.
|
|
2886
4544
|
`);
|
|
2887
4545
|
return 2;
|
|
2888
4546
|
}
|
|
2889
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
|
+
}
|
|
2890
4552
|
if (raw.claudeMemoryContract === false) {
|
|
2891
4553
|
output.stderr("paseo-room auth login only authenticates a role and does not use --no-claude-memory-contract.\n");
|
|
2892
4554
|
return 2;
|
|
@@ -2921,21 +4583,28 @@ async function runCli(argv, output, context = {}) {
|
|
|
2921
4583
|
if (command === "setup") return emit(await setup(options));
|
|
2922
4584
|
if (command === "verify") return emit(await verify(options));
|
|
2923
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
|
+
}
|
|
2924
4593
|
} catch (error) {
|
|
2925
4594
|
return emit(failedFromThrown(command, error));
|
|
2926
4595
|
}
|
|
2927
|
-
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.
|
|
2928
4597
|
`);
|
|
2929
4598
|
return 2;
|
|
2930
4599
|
}
|
|
2931
4600
|
|
|
2932
4601
|
// src/index.ts
|
|
2933
4602
|
process.exitCode = await runCli(process.argv.slice(2), {
|
|
2934
|
-
stdout: (
|
|
2935
|
-
process.stdout.write(
|
|
4603
|
+
stdout: (text3) => {
|
|
4604
|
+
process.stdout.write(text3);
|
|
2936
4605
|
},
|
|
2937
|
-
stderr: (
|
|
2938
|
-
process.stderr.write(
|
|
4606
|
+
stderr: (text3) => {
|
|
4607
|
+
process.stderr.write(text3);
|
|
2939
4608
|
}
|
|
2940
4609
|
});
|
|
2941
4610
|
//# sourceMappingURL=index.js.map
|