paseo-room 0.2.0 → 0.4.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 +37 -19
- package/dist/index.js +117 -28
- package/dist/index.js.map +1 -1
- package/dist/plugin-assets/paseo-plugin.json +0 -1
- package/dist/prompts/contract/lead/complete-peer-brief.md +24 -2
- package/dist/prompts/contract/lead/peer-seat-lifecycle.md +20 -11
- package/dist/prompts/contract/peer/assignment-scope.md +10 -0
- package/dist/prompts/contract/peer/bounded-outcome.md +8 -0
- package/dist/prompts/contract/peer/no-orchestration.md +2 -2
- package/dist/prompts/contract/peer/reproducible-handoff.md +15 -3
- package/dist/prompts/contract/shared-lead-peer/challenge-signals.md +1 -1
- package/dist/prompts/contract/{shared → shared-supervisor-lead}/workspace-protocol-precedence.md +7 -4
- package/dist/prompts/documents/peer.md +2 -1
- package/dist/prompts/documents/workspace.md +3 -2
- package/dist/prompts/pi/runtime.md +6 -2
- package/dist/prompts/workspace/review.md +2 -2
- package/dist/prompts/workspace/verification.md +5 -3
- package/package.json +1 -1
- package/dist/prompts/contract/peer/writing-and-review-scope.md +0 -5
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ interactive stdin/stdout/stderr, and targets exactly one selected agent and role
|
|
|
45
45
|
| `--agent <codex\|claude\|pi>` | `codex` | Which coding agent to seat. Repeat the flag to combine agents. |
|
|
46
46
|
| `--apply` | off | Actually write setup/remove changes; `auth login` does not use it. |
|
|
47
47
|
| `--json` | off | One machine-readable document instead of text. |
|
|
48
|
+
| `--no-claude-memory-contract` | off | Omit the role contract from Claude role `CLAUDE.md` files, leaving the room plugin as the only Claude carrier. Your global memory is still carried. `setup` only. |
|
|
48
49
|
| `--room-home <path>` | `~/.paseo-room` | Where role homes are written. |
|
|
49
50
|
| `--codex-home <path>` | `~/.codex` | Source Codex configuration. |
|
|
50
51
|
| `--claude-home <path>` | `~/.claude` | Source Claude Code configuration. |
|
|
@@ -88,7 +89,7 @@ starts, its exit code is preserved; a signal is returned using the conventional
|
|
|
88
89
|
~/.paseo-room/
|
|
89
90
|
room.json # what this CLI created; verify and remove read it
|
|
90
91
|
AUTHENTICATION.md # exact per-role login commands; contains no secrets
|
|
91
|
-
room/WORKSPACE_PROTOCOL.md # the default protocol
|
|
92
|
+
room/WORKSPACE_PROTOCOL.md # the default protocol Lead and Supervisor carry, as one readable file
|
|
92
93
|
plugin/ # Claude only: trusted creation-time system-prompt append carrier
|
|
93
94
|
paseo-plugin.json # accepts Paseo >=0.8.0 <0.9.0
|
|
94
95
|
index.server.ts, server/ # exact provider map + generated role contracts
|
|
@@ -99,7 +100,7 @@ starts, its exit code is preserved; a signal is returned using the conventional
|
|
|
99
100
|
auth.json # created and owned by Codex after role login, if file-backed
|
|
100
101
|
AGENTS.md, skills, plugins, hooks.json → symlinks into ~/.codex when present (Peer: see below)
|
|
101
102
|
roles/claude/<role>/
|
|
102
|
-
CLAUDE.md # your global memory + role instructions
|
|
103
|
+
CLAUDE.md # your global memory + role instructions (contract omitted with --no-claude-memory-contract)
|
|
103
104
|
settings.json # your settings.json + PASEO_ROOM_ROLE
|
|
104
105
|
.claude.json # seeded once from yours, then owned by Claude
|
|
105
106
|
.credentials.json # created and owned by Claude after role login, if file-backed
|
|
@@ -258,6 +259,14 @@ the plugin is absent, disabled, failed, registered from another path, or drifted
|
|
|
258
259
|
silently missing carrier is not a guarantee. The hook runs for newly created sessions;
|
|
259
260
|
recreate an existing Claude session after setup or an update.
|
|
260
261
|
|
|
262
|
+
`setup --no-claude-memory-contract` leaves the plugin as the only Claude carrier: each role
|
|
263
|
+
`CLAUDE.md` then holds your global memory alone, and the file is not written at all when you
|
|
264
|
+
have none. Only the contract is dropped, never your own memory, and an earlier generation of it
|
|
265
|
+
is removed rather than left behind. The choice is recorded in `room.json`, so `verify` compares
|
|
266
|
+
against it and rejects the flag itself; `setup` without the flag restores the fallback. Keep the
|
|
267
|
+
fallback unless you have a reason not to: the plugin hook is verified, but whether a *resumed*
|
|
268
|
+
session re-enters it is unproven, and `CLAUDE.md` is what covers that case.
|
|
269
|
+
|
|
261
270
|
Pi providers use a strict command tail:
|
|
262
271
|
|
|
263
272
|
```text
|
|
@@ -348,7 +357,8 @@ Supervisor reads the exact current `room-<agent>-lead` profile from `list_profil
|
|
|
348
357
|
materializes every field it defines (agent creation takes no profile id), uses `list_agents(cwd)`
|
|
349
358
|
only to find candidates, then inspects each one's full status for the profile's provider, the
|
|
350
359
|
intended workspace and its mode. For `room-<agent>-peer`, Lead copies provider, mode and
|
|
351
|
-
features exactly,
|
|
360
|
+
features exactly, keeps the profile's model unless the root workspace protocol routes models,
|
|
361
|
+
chooses the thinking effort for the brief, and additionally
|
|
352
362
|
requires the live seat's daemon-added `paseo.parent-agent-id` to name itself. Ownership must be
|
|
353
363
|
corroborated by parentage or known Human-opened history; an ambiguous candidate goes to
|
|
354
364
|
duplicate recovery and Human escalation instead of being adopted.
|
|
@@ -449,10 +459,12 @@ The exact model-facing wording every seat reads lives in the canonical Markdown
|
|
|
449
459
|
that settles it rather than pre-solving the work; any plan or file list in it is provisional.
|
|
450
460
|
One moving write scope has exactly one owner, and at most one Peer is writable at a time.
|
|
451
461
|
Room tools: on.
|
|
452
|
-
- **Peer** owns one bounded
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
and
|
|
462
|
+
- **Peer** owns one bounded assignment — writable inside an assigned scope, or read-only
|
|
463
|
+
against a named candidate, question or area — under exactly one disposition Lead names in the
|
|
464
|
+
brief (Engineer, Architect, Reviewer or Scout), forms its own technical position from the code
|
|
465
|
+
and its own verification, may challenge a failed premise with
|
|
466
|
+
`REOPEN_REQUEST` / `DEPENDENCY_REQUEST` / `BLOCKED`, hands back reproducible evidence either
|
|
467
|
+
way, and never accepts its own difficult change. Room tools: off.
|
|
456
468
|
|
|
457
469
|
Human keeps product goals, priority, material cost, external effects and irreversible risk.
|
|
458
470
|
|
|
@@ -473,18 +485,24 @@ Two further limits are deliberately conservative. **One writable Peer per projec
|
|
|
473
485
|
per moving scope: the room gives you no writer isolation, so separate scopes are not evidence
|
|
474
486
|
of separate working trees, and no workspace protocol relaxes the limit. Concurrent writable
|
|
475
487
|
Peers in isolated worktrees are a deferred decision, not an oversight. And a seat's **model and
|
|
476
|
-
reasoning effort are
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
+
reasoning effort are not one knob**: the model stays the profile's default unless the root
|
|
489
|
+
`WORKSPACE_PROTOCOL.md` explicitly supplies model routing, while the thinking effort is Lead's
|
|
490
|
+
per-brief choice on task risk, uncertainty, context size and verification burden — lowest that
|
|
491
|
+
reliably answers the task, higher for architecture-sensitive or weakly observable work, only an
|
|
492
|
+
option the live Paseo and provider context establishes as supported, and never up to a tier
|
|
493
|
+
advertising automatic delegation. Provider, mode, workspace, parent and feature values are
|
|
494
|
+
eligibility evidence and copied exactly.
|
|
495
|
+
|
|
496
|
+
Every seat that owns workflow also carries a **default workspace protocol** — topology by
|
|
497
|
+
difficulty, verification, review, repository conventions — so a project has that layer without
|
|
498
|
+
doing anything. It goes to Lead and Supervisor, not to Peer: Lead reads the protocol and quotes
|
|
499
|
+
what bears on an assignment into the brief, including the exact verification command, so a Peer
|
|
500
|
+
spends its attention on one brief rather than on deciding which repository rules apply. A
|
|
501
|
+
repository that needs different rules writes `WORKSPACE_PROTOCOL.md` at its root, which wins
|
|
502
|
+
wherever it speaks while the default holds wherever it is silent — the root, because it is
|
|
503
|
+
agent guidance rather than project documentation. `~/.paseo-room/room/WORKSPACE_PROTOCOL.md` is
|
|
504
|
+
the whole default as one file, so you can read what is in force and start from it. The CLI
|
|
505
|
+
never writes into a repository.
|
|
488
506
|
|
|
489
507
|
## Working the room
|
|
490
508
|
|
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.4.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",
|
|
@@ -282,6 +282,7 @@ async function staleChildren(entry) {
|
|
|
282
282
|
}
|
|
283
283
|
async function current(entry) {
|
|
284
284
|
const stat2 = await lstatOrAbsent(entry.path);
|
|
285
|
+
if (entry.kind === "absent") return stat2 ? "different" : "same";
|
|
285
286
|
if (!stat2) return "absent";
|
|
286
287
|
if (entry.kind === "dir") return stat2.isDirectory() ? "same" : "different";
|
|
287
288
|
if (entry.kind === "managed-dir") {
|
|
@@ -301,9 +302,9 @@ async function planEntries(entries) {
|
|
|
301
302
|
for (const entry of entries) {
|
|
302
303
|
const state = await current(entry);
|
|
303
304
|
operations.push({
|
|
304
|
-
action: state === "same" ? "noop" : state === "absent" ? "create" : "update",
|
|
305
|
+
action: state === "same" ? "noop" : entry.kind === "absent" ? "remove" : state === "absent" ? "create" : "update",
|
|
305
306
|
// A managed directory is still a directory to everything that reports operations.
|
|
306
|
-
kind: entry.kind === "managed-dir" ? "dir" : entry.kind,
|
|
307
|
+
kind: entry.kind === "managed-dir" ? "dir" : entry.kind === "absent" ? "file" : entry.kind,
|
|
307
308
|
target: entry.path
|
|
308
309
|
});
|
|
309
310
|
}
|
|
@@ -372,6 +373,12 @@ async function applyManagedDirectory(entry) {
|
|
|
372
373
|
await rm(child, { force: true });
|
|
373
374
|
}
|
|
374
375
|
}
|
|
376
|
+
async function removeManagedFile(path) {
|
|
377
|
+
const stat2 = await lstatOrAbsent(path);
|
|
378
|
+
if (!stat2) return;
|
|
379
|
+
if (!stat2.isFile()) refuse(path, "a regular file this room generated", stat2);
|
|
380
|
+
await rm(path, { force: true });
|
|
381
|
+
}
|
|
375
382
|
async function applyEntries(entries) {
|
|
376
383
|
for (const entry of entries) {
|
|
377
384
|
if (await current(entry) === "same") continue;
|
|
@@ -383,6 +390,10 @@ async function applyEntries(entries) {
|
|
|
383
390
|
await applyManagedDirectory(entry);
|
|
384
391
|
continue;
|
|
385
392
|
}
|
|
393
|
+
if (entry.kind === "absent") {
|
|
394
|
+
await removeManagedFile(entry.path);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
386
397
|
await mkdir(dirname(entry.path), { recursive: true, mode: DIR_MODE });
|
|
387
398
|
await assertReplaceable(entry);
|
|
388
399
|
await replaceAtomically(entry);
|
|
@@ -461,12 +472,14 @@ var init_roles = __esm({
|
|
|
461
472
|
// src/room.ts
|
|
462
473
|
import { join as join3 } from "path";
|
|
463
474
|
import { z } from "zod";
|
|
464
|
-
function renderMarker(version, agents, roles, contract) {
|
|
475
|
+
function renderMarker(version, agents, roles, contract, claudeMemoryContract) {
|
|
465
476
|
return JSON.stringify({
|
|
466
477
|
version,
|
|
467
478
|
agents: [...agents],
|
|
468
479
|
roles: [...roles],
|
|
469
|
-
...contract === void 0 ? {} : { contract }
|
|
480
|
+
...contract === void 0 ? {} : { contract },
|
|
481
|
+
// Written only when it differs from the default, so an unchanged room's marker is unchanged.
|
|
482
|
+
...claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
470
483
|
}, null, 2) + "\n";
|
|
471
484
|
}
|
|
472
485
|
async function readMarker(layout) {
|
|
@@ -494,7 +507,13 @@ var init_room = __esm({
|
|
|
494
507
|
* written before contract provenance existed has no digest, and such a marker must still
|
|
495
508
|
* parse rather than look like a foreign file.
|
|
496
509
|
*/
|
|
497
|
-
contract: z.string().min(1).optional()
|
|
510
|
+
contract: z.string().min(1).optional(),
|
|
511
|
+
/**
|
|
512
|
+
* False when this room deliberately suppressed the role contract in Claude's `CLAUDE.md`,
|
|
513
|
+
* leaving the plugin as its only Claude carrier. Optional and defaulted to true so a room
|
|
514
|
+
* written before the option existed keeps parsing and keeps its current behaviour.
|
|
515
|
+
*/
|
|
516
|
+
claudeMemoryContract: z.boolean().optional()
|
|
498
517
|
});
|
|
499
518
|
}
|
|
500
519
|
});
|
|
@@ -799,7 +818,7 @@ var init_prompts = __esm({
|
|
|
799
818
|
},
|
|
800
819
|
contract: {
|
|
801
820
|
humanAuthority: { path: "contract/shared/human-authority.md", kind: "section" },
|
|
802
|
-
workspaceProtocolPrecedence: { path: "contract/shared/workspace-protocol-precedence.md", kind: "section" },
|
|
821
|
+
workspaceProtocolPrecedence: { path: "contract/shared-supervisor-lead/workspace-protocol-precedence.md", kind: "section" },
|
|
803
822
|
evidenceAndEventWaiting: { path: "contract/shared/evidence-and-event-waiting.md", kind: "section" },
|
|
804
823
|
scopeAndUnrelatedWork: { path: "contract/shared/scope-and-unrelated-work.md", kind: "section" },
|
|
805
824
|
directiveIntegrity: { path: "contract/supervisor/directive-integrity.md", kind: "section" },
|
|
@@ -816,7 +835,7 @@ var init_prompts = __esm({
|
|
|
816
835
|
peerSeatLifecycle: { path: "contract/lead/peer-seat-lifecycle.md", kind: "section" },
|
|
817
836
|
boundedOutcome: { path: "contract/peer/bounded-outcome.md", kind: "section" },
|
|
818
837
|
independentJudgment: { path: "contract/peer/independent-judgment.md", kind: "section" },
|
|
819
|
-
|
|
838
|
+
assignmentScope: { path: "contract/peer/assignment-scope.md", kind: "section" },
|
|
820
839
|
noOrchestration: { path: "contract/peer/no-orchestration.md", kind: "section" },
|
|
821
840
|
reproducibleHandoff: { path: "contract/peer/reproducible-handoff.md", kind: "section" },
|
|
822
841
|
noSelfAcceptance: { path: "contract/peer/no-self-acceptance.md", kind: "section" }
|
|
@@ -849,12 +868,13 @@ var init_prompts = __esm({
|
|
|
849
868
|
// src/room/instructions.ts
|
|
850
869
|
import { createHash } from "crypto";
|
|
851
870
|
function instructionKeys(role) {
|
|
852
|
-
return [...SHARED_KEYS, ...ROLE_KEYS[role]];
|
|
871
|
+
return [...SHARED_KEYS[role], ...ROLE_KEYS[role]];
|
|
853
872
|
}
|
|
854
873
|
function protocolKeys(role) {
|
|
855
874
|
return PROTOCOL_KEYS[role];
|
|
856
875
|
}
|
|
857
876
|
function protocol(keys) {
|
|
877
|
+
if (keys.length === 0) return [];
|
|
858
878
|
return [
|
|
859
879
|
loadPromptAsset("documents", "workspace"),
|
|
860
880
|
...keys.map((key) => loadPromptAsset("workspace", key))
|
|
@@ -873,18 +893,28 @@ function contractDigest() {
|
|
|
873
893
|
for (const kind of INSTRUCTION_KINDS) hash.update(`${kind}\0${renderInstructions(kind)}\0`);
|
|
874
894
|
return `sha256:${hash.digest("hex").slice(0, 16)}`;
|
|
875
895
|
}
|
|
876
|
-
var SHARED_KEYS, ROLE_KEYS, ALL_PROTOCOL_KEYS, PROTOCOL_KEYS, INSTRUCTION_KINDS;
|
|
896
|
+
var AUTHORITY_KEYS, PROTOCOL_READER_KEYS, SHARED_KEYS, ROLE_KEYS, ALL_PROTOCOL_KEYS, PROTOCOL_KEYS, INSTRUCTION_KINDS;
|
|
877
897
|
var init_instructions = __esm({
|
|
878
898
|
"src/room/instructions.ts"() {
|
|
879
899
|
"use strict";
|
|
880
900
|
init_roles();
|
|
881
901
|
init_prompts();
|
|
882
|
-
|
|
902
|
+
AUTHORITY_KEYS = [
|
|
903
|
+
"humanAuthority",
|
|
904
|
+
"evidenceAndEventWaiting",
|
|
905
|
+
"scopeAndUnrelatedWork"
|
|
906
|
+
];
|
|
907
|
+
PROTOCOL_READER_KEYS = [
|
|
883
908
|
"humanAuthority",
|
|
884
909
|
"workspaceProtocolPrecedence",
|
|
885
910
|
"evidenceAndEventWaiting",
|
|
886
911
|
"scopeAndUnrelatedWork"
|
|
887
912
|
];
|
|
913
|
+
SHARED_KEYS = {
|
|
914
|
+
supervisor: PROTOCOL_READER_KEYS,
|
|
915
|
+
lead: PROTOCOL_READER_KEYS,
|
|
916
|
+
peer: AUTHORITY_KEYS
|
|
917
|
+
};
|
|
888
918
|
ROLE_KEYS = {
|
|
889
919
|
supervisor: [
|
|
890
920
|
"directiveIntegrity",
|
|
@@ -907,7 +937,7 @@ var init_instructions = __esm({
|
|
|
907
937
|
"challengeSignals",
|
|
908
938
|
"boundedOutcome",
|
|
909
939
|
"independentJudgment",
|
|
910
|
-
"
|
|
940
|
+
"assignmentScope",
|
|
911
941
|
"noOrchestration",
|
|
912
942
|
"reproducibleHandoff",
|
|
913
943
|
"noSelfAcceptance"
|
|
@@ -922,7 +952,7 @@ var init_instructions = __esm({
|
|
|
922
952
|
PROTOCOL_KEYS = {
|
|
923
953
|
supervisor: ALL_PROTOCOL_KEYS,
|
|
924
954
|
lead: ALL_PROTOCOL_KEYS,
|
|
925
|
-
peer: [
|
|
955
|
+
peer: []
|
|
926
956
|
};
|
|
927
957
|
INSTRUCTION_KINDS = [...ROLES, "workspace"];
|
|
928
958
|
}
|
|
@@ -1087,8 +1117,10 @@ function renderRoleState(source) {
|
|
|
1087
1117
|
for (const key of SEEDED_KEYS) if (key in root) state[key] = root[key];
|
|
1088
1118
|
return JSON.stringify(state, null, 2) + "\n";
|
|
1089
1119
|
}
|
|
1090
|
-
function renderRoleMemory(source, role) {
|
|
1120
|
+
function renderRoleMemory(source, role, contract = true) {
|
|
1091
1121
|
const operator = source?.trim();
|
|
1122
|
+
if (!contract) return operator ? `${operator}
|
|
1123
|
+
` : void 0;
|
|
1092
1124
|
return operator ? `${operator}
|
|
1093
1125
|
|
|
1094
1126
|
${renderInstructions(role)}` : renderInstructions(role);
|
|
@@ -1208,7 +1240,7 @@ var init_claude = __esm({
|
|
|
1208
1240
|
"CronList"
|
|
1209
1241
|
]
|
|
1210
1242
|
},
|
|
1211
|
-
async build(layout, roles) {
|
|
1243
|
+
async build(layout, roles, options = {}) {
|
|
1212
1244
|
const home = layout.agentHome.claude;
|
|
1213
1245
|
const binary = await which(layout.bin.claude, layout.searchPath);
|
|
1214
1246
|
if (!binary) {
|
|
@@ -1261,7 +1293,9 @@ var init_claude = __esm({
|
|
|
1261
1293
|
if (divergence) drift.push(mcpDriftCheck(role, target, statePath, divergence));
|
|
1262
1294
|
}
|
|
1263
1295
|
entries.push({ kind: "dir", path: target });
|
|
1264
|
-
|
|
1296
|
+
const memoryPath = join7(target, "CLAUDE.md");
|
|
1297
|
+
const memory = renderRoleMemory(memorySource, role, options.memoryContract ?? true);
|
|
1298
|
+
entries.push(memory === void 0 ? { kind: "absent", path: memoryPath } : { kind: "file", path: memoryPath, content: memory });
|
|
1265
1299
|
entries.push({ kind: "file", path: join7(target, "settings.json"), content: renderRoleSettings(settingsSource, role, target) });
|
|
1266
1300
|
entries.push({ kind: "file", path: statePath, content: renderRoleState(stateSource), once: true });
|
|
1267
1301
|
entries.push(...await roleResourceEntries({ role, target, home, names: SHARED, shared, executable: EXECUTABLE }));
|
|
@@ -2146,7 +2180,7 @@ function roleProfiles(agent, roles) {
|
|
|
2146
2180
|
notes: ROLE_NOTES[role]
|
|
2147
2181
|
}));
|
|
2148
2182
|
}
|
|
2149
|
-
async function buildDesired(layout, agents, roles) {
|
|
2183
|
+
async function buildDesired(layout, agents, roles, build = {}) {
|
|
2150
2184
|
const entries = [
|
|
2151
2185
|
{ kind: "dir", path: layout.roomHome },
|
|
2152
2186
|
{ kind: "dir", path: sharedRoom(layout) },
|
|
@@ -2158,7 +2192,7 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2158
2192
|
const binaries = {};
|
|
2159
2193
|
for (const id of agents) {
|
|
2160
2194
|
const agent = AGENTS[id];
|
|
2161
|
-
const plan = await agent.build(layout, roles);
|
|
2195
|
+
const plan = await agent.build(layout, roles, build);
|
|
2162
2196
|
entries.push(...plan.entries);
|
|
2163
2197
|
checks.push(...plan.checks);
|
|
2164
2198
|
const credentials = plan.credentials ?? [];
|
|
@@ -2195,7 +2229,7 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2195
2229
|
}
|
|
2196
2230
|
const pluginPath = agents.includes("claude") ? claudeCarrierPluginDir(layout) : void 0;
|
|
2197
2231
|
if (pluginPath !== void 0) entries.push(...claudeCarrierEntries(layout, roles));
|
|
2198
|
-
entries.push({ kind: "file", path: join11(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest()) });
|
|
2232
|
+
entries.push({ kind: "file", path: join11(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest(), markerMemoryContract(agents, build.memoryContract ?? true)) });
|
|
2199
2233
|
return { entries, providers, profiles, checks, ...pluginPath === void 0 ? {} : { pluginPath } };
|
|
2200
2234
|
}
|
|
2201
2235
|
function contractProvenance(previous) {
|
|
@@ -2221,6 +2255,14 @@ function thinkingDiagnostics(live, desired) {
|
|
|
2221
2255
|
`Choose a lower reasoning option for those seats in Paseo if you want the room's own defaults (${ROLE_THINKING.supervisor} for Supervisor, ${ROLE_THINKING.lead} for Lead and Peer).`
|
|
2222
2256
|
)];
|
|
2223
2257
|
}
|
|
2258
|
+
function fileDriftSummary(drifted) {
|
|
2259
|
+
const removals = drifted.filter((operation) => operation.action === "remove").length;
|
|
2260
|
+
const count = String(drifted.length);
|
|
2261
|
+
const subject = drifted.length === 1 ? "managed role file is" : "managed role files are";
|
|
2262
|
+
if (removals === 0) return `${count} ${subject} missing or outdated.`;
|
|
2263
|
+
if (removals === drifted.length) return `${count} ${subject} present but suppressed by this room.`;
|
|
2264
|
+
return `${count} ${subject} missing or outdated, and ${String(removals)} of them are present but suppressed by this room.`;
|
|
2265
|
+
}
|
|
2224
2266
|
function fileDriftDetail(targets) {
|
|
2225
2267
|
const catalogs = targets.filter((target) => target.endsWith(`${sep2}model-catalog.json`));
|
|
2226
2268
|
if (catalogs.length === 0) return "";
|
|
@@ -2383,9 +2425,21 @@ async function reconcilePlugin(session, desired, plan, pending, expectedPath) {
|
|
|
2383
2425
|
}
|
|
2384
2426
|
return [pluginRuntimeCheck(plugin, expectedPath)];
|
|
2385
2427
|
}
|
|
2428
|
+
function markerMemoryContract(agents, memoryContract) {
|
|
2429
|
+
return agents.includes("claude") ? memoryContract : void 0;
|
|
2430
|
+
}
|
|
2431
|
+
function memoryContractDiagnostic(agents, memoryContract) {
|
|
2432
|
+
if (memoryContract || !agents.includes("claude")) return [];
|
|
2433
|
+
return [warn(
|
|
2434
|
+
"claude.memory-contract",
|
|
2435
|
+
"Claude role CLAUDE.md files carry your global memory only: the role contract was not written to them, so the trusted server plugin is this room's only Claude carrier. Only a newly created agent passes through its creation hook, and whether a resumed session re-enters that hook is unproven.",
|
|
2436
|
+
"Drop --no-claude-memory-contract and run setup --apply to restore the file fallback."
|
|
2437
|
+
)];
|
|
2438
|
+
}
|
|
2386
2439
|
async function setup(options = {}) {
|
|
2387
2440
|
const layout = resolveLayout(options, options.env);
|
|
2388
2441
|
const agents = options.agents ?? ["codex"];
|
|
2442
|
+
const memoryContract = options.claudeMemoryContract ?? true;
|
|
2389
2443
|
const invalid = layoutChecks(layout, agents);
|
|
2390
2444
|
if (invalid.length > 0) return failed("setup", invalid);
|
|
2391
2445
|
const rootSafety = await roomPathSafety(layout, [], []);
|
|
@@ -2397,7 +2451,7 @@ async function setup(options = {}) {
|
|
|
2397
2451
|
const compatibilityAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2398
2452
|
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(compatibilityAgents));
|
|
2399
2453
|
if (!daemon.daemon) return failed("setup", daemon.checks);
|
|
2400
|
-
const desired = await buildDesired(layout, agents, ROLES);
|
|
2454
|
+
const desired = await buildDesired(layout, agents, ROLES, { memoryContract });
|
|
2401
2455
|
const stale = await staleFrom(layout, previous, agents, ROLES);
|
|
2402
2456
|
const retained = stale.retainedDirectories.length === 0 ? [] : [warn(
|
|
2403
2457
|
"room.stale-role-homes-preserved",
|
|
@@ -2412,7 +2466,7 @@ async function setup(options = {}) {
|
|
|
2412
2466
|
const { operations, liveProfiles } = plan;
|
|
2413
2467
|
const pending = operations.filter((operation) => operation.action !== "noop");
|
|
2414
2468
|
const pluginPreconditions = desired.pluginPath === void 0 ? plan.pluginChecks : [pluginsEnabledCheck(await session.pluginsEnabled()), ...plan.pluginChecks];
|
|
2415
|
-
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2469
|
+
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...memoryContractDiagnostic(agents, memoryContract), ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2416
2470
|
if (hasFailure(preApplyDiagnostics) || !options.apply) {
|
|
2417
2471
|
return {
|
|
2418
2472
|
command: "setup",
|
|
@@ -2440,13 +2494,15 @@ async function setup(options = {}) {
|
|
|
2440
2494
|
version: package_default.version,
|
|
2441
2495
|
agents: [...agents],
|
|
2442
2496
|
roles: [...ROLES],
|
|
2443
|
-
contract: contractDigest()
|
|
2497
|
+
contract: contractDigest(),
|
|
2498
|
+
...markerMemoryContract(agents, memoryContract) === false ? { claudeMemoryContract: false } : {}
|
|
2444
2499
|
};
|
|
2445
2500
|
const appliedChecks = [
|
|
2446
2501
|
...checks,
|
|
2447
2502
|
...pluginPreconditions,
|
|
2448
2503
|
...pluginChecks,
|
|
2449
2504
|
pass("room.applied", `Room ready at ${layout.roomHome} with ${String(ids.length)} Paseo providers.`),
|
|
2505
|
+
...memoryContractDiagnostic(agents, memoryContract),
|
|
2450
2506
|
...contractProvenance(appliedMarker),
|
|
2451
2507
|
...thinkingDiagnostics(appliedProfiles, desired.profiles)
|
|
2452
2508
|
];
|
|
@@ -2473,7 +2529,10 @@ async function verify(options = {}) {
|
|
|
2473
2529
|
if (hasFailure(pathSafety)) return failed("verify", pathSafety);
|
|
2474
2530
|
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(marker.agents));
|
|
2475
2531
|
if (!daemon.daemon) return failed("verify", daemon.checks);
|
|
2476
|
-
const desired = await buildDesired(layout, marker.agents, marker.roles
|
|
2532
|
+
const desired = await buildDesired(layout, marker.agents, marker.roles, {
|
|
2533
|
+
// The room's own recorded choice, so verify compares against what setup wrote.
|
|
2534
|
+
memoryContract: marker.claudeMemoryContract ?? true
|
|
2535
|
+
});
|
|
2477
2536
|
const checks = [...daemon.checks, ...desired.checks];
|
|
2478
2537
|
if (hasFailure(checks)) return failed("verify", checks);
|
|
2479
2538
|
return withSession(daemon.daemon, async (session) => {
|
|
@@ -2493,11 +2552,12 @@ async function verify(options = {}) {
|
|
|
2493
2552
|
];
|
|
2494
2553
|
const all = [
|
|
2495
2554
|
...checks,
|
|
2496
|
-
files === 0 ? pass("room.files", "Every managed role file matches the current definition.") : fail("room.files", `${
|
|
2555
|
+
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"),
|
|
2497
2556
|
providers === 0 ? pass("room.providers", `All ${String(Object.keys(desired.providers).length)} Paseo providers are registered as expected.`) : fail("room.providers", `${String(providers)} Paseo providers are missing or differ.`, "Run: paseo-room setup --apply"),
|
|
2498
2557
|
profiles === 0 ? pass("room.profiles", `All ${String(desired.profiles.length)} Paseo agent profiles are registered as expected.`) : fail("room.profiles", `${String(profiles)} Paseo agent profiles are missing or differ.`, "Run: paseo-room setup --apply"),
|
|
2499
2558
|
...pluginChecks,
|
|
2500
2559
|
// Diagnostics last: a warning never precedes the failure it might be mistaken for.
|
|
2560
|
+
...memoryContractDiagnostic(marker.agents, marker.claudeMemoryContract ?? true),
|
|
2501
2561
|
...contractProvenance(marker),
|
|
2502
2562
|
...thinkingDiagnostics(liveProfiles, desired.profiles)
|
|
2503
2563
|
];
|
|
@@ -2647,12 +2707,25 @@ async function runWizard(prompts, emit, write, options = {}, onAction) {
|
|
|
2647
2707
|
options: AGENT_IDS.map((value) => ({ value, label: AGENTS[value].label }))
|
|
2648
2708
|
});
|
|
2649
2709
|
if (typeof agents === "symbol" || agents.length === 0) return cancelled();
|
|
2650
|
-
|
|
2710
|
+
let claudeMemoryContract = true;
|
|
2711
|
+
if (agents.includes("claude")) {
|
|
2712
|
+
const carrier = await prompts.select({
|
|
2713
|
+
message: "Claude role contract carrier: the room plugin is always used. Also write the contract into each role CLAUDE.md?",
|
|
2714
|
+
options: [
|
|
2715
|
+
{ value: "both", label: "Yes \u2014 keep it as a fallback (recommended; resume behaviour is unproven)" },
|
|
2716
|
+
{ value: "plugin", label: "No \u2014 plugin only; CLAUDE.md keeps your global memory alone" }
|
|
2717
|
+
]
|
|
2718
|
+
});
|
|
2719
|
+
if (typeof carrier === "symbol") return cancelled();
|
|
2720
|
+
claudeMemoryContract = carrier === "both";
|
|
2721
|
+
}
|
|
2722
|
+
const setupOptions = { ...options, agents, claudeMemoryContract };
|
|
2723
|
+
const preview = await setup(setupOptions);
|
|
2651
2724
|
const status = emit(preview);
|
|
2652
2725
|
if (preview.outcome !== "changes-planned") return status;
|
|
2653
2726
|
const approved = await prompts.confirm({ message: "Apply these changes? Authentication will not be validated or started." });
|
|
2654
2727
|
if (approved !== true) return cancelled();
|
|
2655
|
-
return emit(await setup({ ...
|
|
2728
|
+
return emit(await setup({ ...setupOptions, apply: true }));
|
|
2656
2729
|
}
|
|
2657
2730
|
var terminalPrompts;
|
|
2658
2731
|
var init_wizard = __esm({
|
|
@@ -2718,7 +2791,14 @@ function optionsFrom(raw, base) {
|
|
|
2718
2791
|
if (typeof value === "string" && value.trim()) paths[flag] = value.trim();
|
|
2719
2792
|
}
|
|
2720
2793
|
const agents = Array.isArray(raw.agent) && raw.agent.length > 0 ? raw.agent : void 0;
|
|
2721
|
-
return {
|
|
2794
|
+
return {
|
|
2795
|
+
...base,
|
|
2796
|
+
...paths,
|
|
2797
|
+
...agents ? { agents } : {},
|
|
2798
|
+
...raw.apply === true ? { apply: true } : {},
|
|
2799
|
+
// Commander defaults a --no- flag to true, so only an explicit opt-out is carried.
|
|
2800
|
+
...raw.claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
2801
|
+
};
|
|
2722
2802
|
}
|
|
2723
2803
|
function failedFromThrown(command, error) {
|
|
2724
2804
|
if (error instanceof PromptAssetError) {
|
|
@@ -2768,7 +2848,7 @@ async function runCli(argv, output, context = {}) {
|
|
|
2768
2848
|
return emit(failedFromThrown(wizardCommand, error));
|
|
2769
2849
|
}
|
|
2770
2850
|
}
|
|
2771
|
-
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("--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: (text) => {
|
|
2851
|
+
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: (text) => {
|
|
2772
2852
|
output.stdout(text);
|
|
2773
2853
|
}, writeErr: (text) => {
|
|
2774
2854
|
output.stderr(text);
|
|
@@ -2789,7 +2869,16 @@ async function runCli(argv, output, context = {}) {
|
|
|
2789
2869
|
return 2;
|
|
2790
2870
|
}
|
|
2791
2871
|
try {
|
|
2872
|
+
if (raw.claudeMemoryContract === false && (command === "verify" || command === "remove")) {
|
|
2873
|
+
output.stderr(`paseo-room ${command}: --no-claude-memory-contract is a setup choice recorded in the room marker; ${command} reads it from there.
|
|
2874
|
+
`);
|
|
2875
|
+
return 2;
|
|
2876
|
+
}
|
|
2792
2877
|
if (command === "auth") {
|
|
2878
|
+
if (raw.claudeMemoryContract === false) {
|
|
2879
|
+
output.stderr("paseo-room auth login only authenticates a role and does not use --no-claude-memory-contract.\n");
|
|
2880
|
+
return 2;
|
|
2881
|
+
}
|
|
2793
2882
|
if (raw.apply === true) {
|
|
2794
2883
|
output.stderr("paseo-room auth login is already explicit and does not use --apply.\n");
|
|
2795
2884
|
return 2;
|