paseo-room 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Room contract carrier for Claude seats, as a Paseo v0.8 server plugin.
3
+ *
4
+ * Paseo launches Claude through the Claude Agent SDK, so no provider-owned argv can carry an
5
+ * additive system prompt (see docs/design.md §6). `AgentSessionConfig.systemPrompt` is the one
6
+ * strong additive channel, and it exists only at creation time — which is exactly what the
7
+ * `agent.create` before-hook can change. Paseo's Claude provider maps that value to the SDK's
8
+ * `{ type: "preset", preset: "claude_code", append }`, so the vendor prompt is kept and the
9
+ * room contract is appended to it.
10
+ *
11
+ * Deliberately dependency-free: the daemon compiles this directory as-is, so it imports no SDK
12
+ * and no package. The types below describe only the fields this hook reads or writes.
13
+ */
14
+ import { composeSystemPrompt, hasExactContract } from './server/carrier.js';
15
+ import { CONTRACT, GENERATION } from './server/contract.js';
16
+
17
+ export interface RoomAgentCreateRequest {
18
+ readonly config: {
19
+ readonly provider: string;
20
+ readonly systemPrompt?: string;
21
+ readonly internal?: boolean;
22
+ };
23
+ }
24
+
25
+ interface RoomServerContext {
26
+ before(
27
+ name: 'agent.create',
28
+ handler: (input: { readonly request: RoomAgentCreateRequest }) => RoomAgentCreateRequest | undefined,
29
+ ): () => void;
30
+ }
31
+
32
+ export function transformAgentCreate(
33
+ request: RoomAgentCreateRequest,
34
+ contracts: Readonly<Record<string, string>> = CONTRACT,
35
+ generation = GENERATION,
36
+ ): RoomAgentCreateRequest | undefined {
37
+ // Internal agents are Paseo's own ephemeral system tasks: they are not room seats.
38
+ if (request.config.internal === true) return undefined;
39
+ // Exact room provider ids only. An operator's own Claude provider is never touched.
40
+ const contract = Object.entries(contracts).find(([id]) => id === request.config.provider)?.[1];
41
+ if (contract === undefined) return undefined;
42
+ if (hasExactContract(request.config.systemPrompt, contract, generation)) return undefined;
43
+ const systemPrompt = composeSystemPrompt(request.config.systemPrompt, contract, generation);
44
+ return { ...request, config: { ...request.config, systemPrompt } };
45
+ }
46
+
47
+ export default function contribute(server: RoomServerContext): () => void {
48
+ const dispose = server.before('agent.create', ({ request }) => transformAgentCreate(request));
49
+ return () => { dispose(); };
50
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "paseo-room-claude-carrier",
3
+ "private": true,
4
+ "type": "module"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "id": "paseo-room-claude-carrier",
3
+ "description": "Adds paseo-room role contracts to Claude's creation-time system prompt.",
4
+ "requirements": { "paseo": ">=0.8.0 <0.9.0" }
5
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Composition rule for the room's Claude creation-time contract carrier.
3
+ *
4
+ * Kept free of every import — including the plugin SDK — so the daemon's plugin compiler
5
+ * needs no installed dependency inside the room plugin directory, and so this repository can
6
+ * unit-test the rule directly.
7
+ */
8
+
9
+ /** Opens the room-owned block, so an already-carried prompt is recognisable without parsing. */
10
+ export const CONTRACT_MARKER_PREFIX = '<!-- paseo-room-contract:';
11
+
12
+ export function contractMarker(generation: string): string {
13
+ return `${CONTRACT_MARKER_PREFIX}${generation} -->`;
14
+ }
15
+
16
+ /** True only when the prompt ends with the complete current room-owned block. */
17
+ export function hasExactContract(
18
+ existing: string | undefined,
19
+ contract: string,
20
+ generation: string,
21
+ ): boolean {
22
+ return existing?.endsWith(`${contractMarker(generation)}\n${contract}`) === true;
23
+ }
24
+
25
+ /**
26
+ * Appends the role contract to whatever prompt the caller already asked for, rather than
27
+ * replacing it. The room block is always last and always marked, so a prompt that already
28
+ * carries one is rewritten in place instead of gaining a second copy.
29
+ */
30
+ export function composeSystemPrompt(
31
+ existing: string | undefined,
32
+ contract: string,
33
+ generation: string,
34
+ ): string {
35
+ const raw = existing ?? '';
36
+ const marker = CONTRACT_MARKER_PREFIX;
37
+ const index = raw.indexOf(marker);
38
+ const base = (index === -1 ? raw : raw.slice(0, index)).trimEnd();
39
+ const block = `${contractMarker(generation)}\n${contract}`;
40
+ return base ? `${base}\n\n${block}` : block;
41
+ }
@@ -0,0 +1,3 @@
1
+ /** Replaced by paseo-room setup with the selected Claude seats' rendered contracts. */
2
+ export const GENERATION = 'unconfigured';
3
+ export const CONTRACT: Readonly<Record<string, string>> = {};
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "strict": true,
7
+ "noEmit": true
8
+ },
9
+ "include": ["index.server.ts", "server/**/*.ts"]
10
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "paseo-room",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "One CLI that configures Codex, Claude Code, and Pi role homes in $HOME and registers them with your local Paseo daemon.",
5
5
  "keywords": [
6
6
  "paseo",
@@ -34,7 +34,7 @@
34
34
  "lint": "eslint . --max-warnings 0",
35
35
  "test": "vitest run",
36
36
  "test:package": "vitest run --config vitest.package.config.ts",
37
- "build": "tsup && node --input-type=module -e \"import { cpSync, rmSync } from 'node:fs'; rmSync('dist/prompts', { recursive: true, force: true }); cpSync('src/room/prompts', 'dist/prompts', { recursive: true });\"",
37
+ "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 }); }\"",
38
38
  "prepack": "npm run build",
39
39
  "verify": "npm run typecheck && npm run lint && npm test && npm run build && npm run test:package"
40
40
  },