@sublang/playbook 0.5.0 → 0.7.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.
@@ -1,203 +1,29 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
3
 
4
- // tmux-play host adapter for the CODE playbook — DR-004 §11. This is
5
- // the only file in IR-004 that imports cligent; removing the import
6
- // shall not affect code.playbook.ts or its tests.
7
-
8
- import type {
9
- BossTurn,
10
- Captain,
11
- CaptainContext,
12
- CaptainSession,
13
- } from '@sublang/cligent/tmux-play';
14
- import createPlaybookRuntime, {
15
- type CodePlaybookOptions,
16
- type PlaybookPorts,
17
- type PlaybookRuntime,
18
- } from './code.playbook.js';
19
-
20
- // PBRT-29/30: CODE runtime options are carried under
21
- // `captain.options.code`, a namespaced object the host forwards
22
- // verbatim through `captain.options`. cligent neither reads nor
23
- // validates `options.code` (PBRT-30); this adapter is the sole
24
- // validator. The CODE options schema defines one key, `committer`: an
25
- // optional Committer-alias player id, one of the baked player ids
26
- // `coder` / `reviewer`. A valid `options.code` is absent, `{}`, or
27
- // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown
28
- // and rejected with a path-named error, and an out-of-range
29
- // `committer` value is rejected naming `captain.options.code.committer`.
30
- // A further CODE option shall be introduced as its own higher-numbered
31
- // item that widens `CODE_OPTION_KEYS`; the validator still fails closed
32
- // on stray keys.
33
- const CODE_OPTION_KEYS = new Set<string>(['committer']);
34
- const COMMITTER_PLAYER_IDS = new Set<string>(['coder', 'reviewer']);
35
-
36
- // The validated CODE options set. `committer`, when present, is the
37
- // resolved Committer-alias player id (PBRT-8 / PBRT-30); a future CODE
38
- // option widens both this type and `CODE_OPTION_KEYS`.
39
- export interface CodeOptions {
40
- committer?: 'coder' | 'reviewer';
41
- }
42
-
43
- export function validateCodeOptions(captainOptions: unknown): CodeOptions {
44
- const code = readCodeNamespace(captainOptions);
45
- if (code === undefined) return {};
46
- if (typeof code !== 'object' || code === null || Array.isArray(code)) {
47
- throw new Error('captain.options.code must be an object');
48
- }
49
- for (const key of Object.keys(code)) {
50
- if (!CODE_OPTION_KEYS.has(key)) {
51
- throw new Error(`Unknown config field captain.options.code.${key}`);
52
- }
53
- }
54
- const options: CodeOptions = {};
55
- const committer = (code as Record<string, unknown>).committer;
56
- if (committer !== undefined) {
57
- if (typeof committer !== 'string' || !COMMITTER_PLAYER_IDS.has(committer)) {
58
- throw new Error(
59
- "captain.options.code.committer must be 'coder' or 'reviewer'",
60
- );
61
- }
62
- options.committer = committer as 'coder' | 'reviewer';
63
- }
64
- return options;
65
- }
66
-
67
- function readCodeNamespace(captainOptions: unknown): unknown {
68
- if (
69
- typeof captainOptions !== 'object' ||
70
- captainOptions === null ||
71
- Array.isArray(captainOptions)
72
- ) {
73
- return undefined;
74
- }
75
- return (captainOptions as Record<string, unknown>).code;
76
- }
77
-
78
- // Captain factory per TMUX-014: `(options: unknown) => Captain`.
79
- // `options` is whatever `captain.options` carries in the YAML config;
80
- // CODE reads only the namespaced `options.code` (PBRT-30). The per-run
81
- // player identity strings (`coderPlayer`, `reviewerPlayer`) are
82
- // derived from `session.players` at init time per PBRT-4 — preferring
83
- // each entry's `model` and falling back to `adapter` when no model is
84
- // pinned — so player prompts and commit-message trailers carry the
85
- // concrete model identity (e.g. `claude-opus-4-7`) rather than the
86
- // adapter family name (e.g. `claude`). They come from `session.players`
87
- // independent of `captain.options.code` and override any same-named
88
- // keys.
4
+ import type { Captain } from '@sublang/cligent/tmux-play';
5
+ import createPlaybookCaptainShell from './playbook-captain.js';
6
+
7
+ export {
8
+ codeCopyPasteGuardNames,
9
+ codeStateCountLabels,
10
+ codePlaybookRegistryEntry,
11
+ createCodeRuntimeOptions,
12
+ validateCodeOptions,
13
+ } from './code.registry.js';
14
+ export type {
15
+ CodeOptions,
16
+ CodePlaybookRegistryEntry,
17
+ CreateCodeRuntimeOptions,
18
+ RegistryPlayer,
19
+ } from './code.registry.js';
20
+
21
+ // Compatibility shim for the historic `./code/tmux-play` package
22
+ // export. Public launch paths now target the Playbook Captain shell
23
+ // directly; explicit configs that still import this module get the
24
+ // same shell with CODE registered.
89
25
  export default function createCodeTmuxPlayCaptain(
90
26
  options: unknown,
91
27
  ): Captain {
92
- // CaptainSession is bound at init time and persists across turns;
93
- // CaptainContext is rebuilt per turn and carries the call
94
- // primitives. The runtime is constructed in `init` so identity
95
- // strings derived from `session.players` can flow into its options;
96
- // PlaybookPorts is built once at init, so the per-turn context
97
- // lives in a closure-scoped slot the port callbacks query lazily.
98
- let runtime: PlaybookRuntime | undefined;
99
- let activeContext: CaptainContext | undefined;
100
-
101
- return {
102
- async init(session: CaptainSession): Promise<void> {
103
- // PBRT-30: validate `captain.options.code` before constructing
104
- // the runtime so a stray key fails `init` closed with a
105
- // path-named error; the empty schema yields an empty options set.
106
- const codeOptions = validateCodeOptions(options);
107
- const playerIdentity = (id: string): string | undefined => {
108
- const entry = session.players.find((p) => p.id === id);
109
- return entry?.model ?? entry?.adapter;
110
- };
111
- const coderPlayer = playerIdentity('coder');
112
- const reviewerPlayer = playerIdentity('reviewer');
113
- // Identity strings from `session.players` override any same-named
114
- // keys and are independent of `captain.options.code` (PBRT-30).
115
- // The validated `committer` alias threads in as the runtime's
116
- // Committer player id (`committerPlayer`, PBRT-8).
117
- const runtimeOptions: CodePlaybookOptions = {
118
- coderPlayer,
119
- reviewerPlayer,
120
- ...(codeOptions.committer !== undefined
121
- ? { committerPlayer: codeOptions.committer }
122
- : {}),
123
- };
124
- runtime = createPlaybookRuntime(runtimeOptions);
125
- const ports: PlaybookPorts = {
126
- callPlayer: async (playerId, prompt, _signal) => {
127
- if (!activeContext) {
128
- throw new Error('callPlayer invoked outside a Boss turn');
129
- }
130
- // PlayerRunResult per TMUX-033 already matches PlayerResult
131
- // (`status: 'ok' | 'aborted' | 'error'`, `finalText?`,
132
- // `error?`). cligent honors context.signal internally;
133
- // the runtime's signal is the same source forwarded
134
- // through handleBossInput, so dropping `_signal` is
135
- // safe.
136
- const r = await activeContext.callPlayer(playerId, prompt);
137
- return {
138
- status: r.status,
139
- finalText: r.finalText,
140
- error: r.error,
141
- };
142
- },
143
- callJudge: async (prompt, _signal) => {
144
- if (!activeContext) {
145
- throw new Error('callJudge invoked outside a Boss turn');
146
- }
147
- // PBRT-15 / DR-007: run the judge call hidden so its JSON
148
- // reply never reaches the Boss pane; the runtime composes the
149
- // human-readable pane lines (PBRT-3) from the parsed result.
150
- const r = await activeContext.callCaptain(prompt, {
151
- visibility: 'hidden',
152
- });
153
- if (r.status !== 'ok') {
154
- throw new Error(
155
- r.error ?? `callCaptain status "${r.status}"`,
156
- );
157
- }
158
- if (r.finalText === undefined) {
159
- throw new Error(
160
- 'callCaptain returned status=ok with no finalText',
161
- );
162
- }
163
- return r.finalText;
164
- },
165
- emitStatus: async (message, data) => {
166
- await session.emitStatus(
167
- message,
168
- data as Record<string, unknown> | undefined,
169
- );
170
- },
171
- emitTelemetry: async (event) => {
172
- await session.emitTelemetry(event);
173
- },
174
- };
175
- await runtime.init(ports);
176
- },
177
-
178
- async handleBossTurn(
179
- turn: BossTurn,
180
- context: CaptainContext,
181
- ): Promise<void> {
182
- if (!runtime) {
183
- throw new Error('init must be called first');
184
- }
185
- activeContext = context;
186
- try {
187
- // Forward the Boss prompt + cligent's per-turn signal into
188
- // the runtime; the runtime stashes the signal so the
189
- // captain bridge passes it down to callPlayer / callJudge.
190
- await runtime.handleBossInput({
191
- text: turn.prompt,
192
- signal: context.signal,
193
- });
194
- } finally {
195
- activeContext = undefined;
196
- }
197
- },
198
-
199
- async dispose(): Promise<void> {
200
- await runtime?.dispose();
201
- },
202
- };
28
+ return createPlaybookCaptainShell(options);
203
29
  }
@@ -0,0 +1,21 @@
1
+ import type { Captain } from '@sublang/cligent/tmux-play';
2
+ import type { PlaybookRuntime } from './code.playbook.js';
3
+ import { type RegistryPlayer } from './code.registry.js';
4
+ export interface CreatePlaybookRuntimeOptions {
5
+ captainOptions: unknown;
6
+ players: readonly RegistryPlayer[];
7
+ }
8
+ export interface PlaybookCaptainRegistryEntry {
9
+ id: string;
10
+ command: string;
11
+ intent: string;
12
+ idleStateId: string;
13
+ finalStateId: string;
14
+ copyPasteGuardNames: readonly string[];
15
+ stateCountLabels?: Readonly<Record<string, string>>;
16
+ validateOptions(captainOptions: unknown): unknown;
17
+ createRuntime(options: CreatePlaybookRuntimeOptions): PlaybookRuntime;
18
+ }
19
+ export declare const playbookCaptainRegistry: readonly PlaybookCaptainRegistryEntry[];
20
+ export declare function createPlaybookCaptainShell(options: unknown, registry?: readonly PlaybookCaptainRegistryEntry[]): Captain;
21
+ export default createPlaybookCaptainShell;