@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.
@@ -11,6 +11,9 @@
11
11
  // alias's first alternative)
12
12
  // Boss event: free-text judge classification
13
13
  // Adjudication: LLM-judge per state
14
+ // Contract: PlayerResult / PlaybookPorts / PlaybookRuntime imported
15
+ // and re-exported from @sublang/playbook/runtime
16
+ // (slc/link.md §Output, DR-004 Addendum A4)
14
17
  import { createActor, fromPromise } from 'xstate';
15
18
  import { codingMachine, } from './code.fsm.js';
16
19
  import { enumerateAwaitBossReply, enumerateCaptainStates, enumerateRootEvents, } from './code.fsm.introspect.js';
@@ -11,6 +11,9 @@
11
11
  // alias's first alternative)
12
12
  // Boss event: free-text judge classification
13
13
  // Adjudication: LLM-judge per state
14
+ // Contract: PlayerResult / PlaybookPorts / PlaybookRuntime imported
15
+ // and re-exported from @sublang/playbook/runtime
16
+ // (slc/link.md §Output, DR-004 Addendum A4)
14
17
 
15
18
  import { createActor, fromPromise } from 'xstate';
16
19
  import {
@@ -25,33 +28,20 @@ import {
25
28
  enumerateCaptainStates,
26
29
  enumerateRootEvents,
27
30
  } from './code.fsm.introspect.js';
28
-
29
- // Public contract — `PlayerResult`, `PlaybookPorts`, `PlaybookRuntime`,
30
- // `CodePlaybookOptions`, and the default `createPlaybookRuntime` factory
31
- // per slc/link.md and DR-004 §10.
32
-
33
- export interface PlayerResult {
34
- status: 'ok' | 'aborted' | 'error';
35
- finalText?: string;
36
- error?: string;
37
- }
38
-
39
- export interface PlaybookPorts {
40
- callPlayer(
41
- playerId: string,
42
- prompt: string,
43
- signal: AbortSignal,
44
- ): Promise<PlayerResult>;
45
- callJudge(prompt: string, signal: AbortSignal): Promise<string>;
46
- emitStatus(message: string, data?: unknown): Promise<void>;
47
- emitTelemetry(event: { topic: string; payload: unknown }): Promise<void>;
48
- }
49
-
50
- export interface PlaybookRuntime {
51
- init(ports: PlaybookPorts): Promise<void>;
52
- handleBossInput(turn: { text: string; signal: AbortSignal }): Promise<void>;
53
- dispose(): Promise<void>;
54
- }
31
+ import type {
32
+ PlaybookPorts,
33
+ PlaybookRuntime,
34
+ PlayerResult,
35
+ } from '@sublang/playbook/runtime';
36
+
37
+ // Public contract. `PlayerResult`, `PlaybookPorts`, and `PlaybookRuntime`
38
+ // are re-exported from the shared `@sublang/playbook/runtime` module
39
+ // (slc/link.md §Output, DR-004 Addendum A4) so this playbook and any
40
+ // future one resolve one contract definition rather than redefining it.
41
+ // `CodePlaybookOptions` and the default `createPlaybookRuntime` factory
42
+ // (typed `PlaybookRuntimeFactory<CodePlaybookOptions>`) stay
43
+ // CODE-specific.
44
+ export type { PlayerResult, PlaybookPorts, PlaybookRuntime };
55
45
 
56
46
  export type CodePlaybookOptions = CodingInput;
57
47
 
@@ -0,0 +1,43 @@
1
+ import { type CodePlaybookOptions, type PlaybookRuntime } from './code.playbook.js';
2
+ export declare const codeCopyPasteGuardNames: readonly ["accepted", "approved", "challengeAccepted", "challengeRejected", "challengesRaised", "changesMadeCode", "changesMadeCodeAndChallenged", "changesMadeMixed", "changesMadeMixedAndChallenged", "changesMadeSpecs", "changesMadeSpecsAndChallenged", "hasFindings", "needsRevision", "noFindings", "noOpenItems"];
3
+ export declare const codeStateCountLabels: {
4
+ readonly adjudicateChallenges: "rebuttal";
5
+ readonly reviewBossCommitSpecs: "review round";
6
+ readonly reviewBossCommitCode: "review round";
7
+ readonly reviewBossCommitMixed: "review round";
8
+ readonly reviewIrTaskCommitSpecs: "review round";
9
+ readonly reviewIrTaskCommitCode: "review round";
10
+ readonly reviewIrTaskCommitMixed: "review round";
11
+ readonly reviewChangesSpecs: "review round";
12
+ readonly reviewChangesCode: "review round";
13
+ readonly reviewChangesMixed: "review round";
14
+ readonly reviewChangesAndChallengesSpecs: "review round";
15
+ readonly reviewChangesAndChallengesCode: "review round";
16
+ readonly reviewChangesAndChallengesMixed: "review round";
17
+ };
18
+ export interface CodeOptions {
19
+ committer?: 'coder' | 'reviewer';
20
+ }
21
+ export interface RegistryPlayer {
22
+ id: string;
23
+ adapter?: string;
24
+ model?: string;
25
+ }
26
+ export interface CreateCodeRuntimeOptions {
27
+ captainOptions: unknown;
28
+ players: readonly RegistryPlayer[];
29
+ }
30
+ export interface CodePlaybookRegistryEntry {
31
+ id: 'code';
32
+ command: 'code';
33
+ intent: string;
34
+ idleStateId: 'ready';
35
+ finalStateId: 'done';
36
+ copyPasteGuardNames: readonly string[];
37
+ stateCountLabels: typeof codeStateCountLabels;
38
+ validateOptions(captainOptions: unknown): CodeOptions;
39
+ createRuntime(options: CreateCodeRuntimeOptions): PlaybookRuntime;
40
+ }
41
+ export declare function validateCodeOptions(captainOptions: unknown): CodeOptions;
42
+ export declare function createCodeRuntimeOptions({ captainOptions, players, }: CreateCodeRuntimeOptions): CodePlaybookOptions;
43
+ export declare const codePlaybookRegistryEntry: CodePlaybookRegistryEntry;
@@ -0,0 +1,109 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+ import createPlaybookRuntime from './code.playbook.js';
4
+ // PBRT-29/30: CODE runtime options are carried under
5
+ // `captain.options.code`, a namespaced object the host forwards
6
+ // verbatim through `captain.options`. cligent neither reads nor
7
+ // validates `options.code`; the CODE registry entry is the sole
8
+ // validator. The CODE options schema defines one key, `committer`: an
9
+ // optional Committer-alias player id, one of the baked player ids
10
+ // `coder` / `reviewer`. A valid `options.code` is absent, `{}`, or
11
+ // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown
12
+ // and rejected with a path-named error, and an out-of-range
13
+ // `committer` value is rejected naming `captain.options.code.committer`.
14
+ // A further CODE option shall be introduced as its own higher-numbered
15
+ // item that widens `CODE_OPTION_KEYS`; the validator still fails closed
16
+ // on stray keys.
17
+ const CODE_OPTION_KEYS = new Set(['committer']);
18
+ const COMMITTER_PLAYER_IDS = new Set(['coder', 'reviewer']);
19
+ export const codeCopyPasteGuardNames = [
20
+ 'accepted',
21
+ 'approved',
22
+ 'challengeAccepted',
23
+ 'challengeRejected',
24
+ 'challengesRaised',
25
+ 'changesMadeCode',
26
+ 'changesMadeCodeAndChallenged',
27
+ 'changesMadeMixed',
28
+ 'changesMadeMixedAndChallenged',
29
+ 'changesMadeSpecs',
30
+ 'changesMadeSpecsAndChallenged',
31
+ 'hasFindings',
32
+ 'needsRevision',
33
+ 'noFindings',
34
+ 'noOpenItems',
35
+ ];
36
+ export const codeStateCountLabels = {
37
+ adjudicateChallenges: 'rebuttal',
38
+ reviewBossCommitSpecs: 'review round',
39
+ reviewBossCommitCode: 'review round',
40
+ reviewBossCommitMixed: 'review round',
41
+ reviewIrTaskCommitSpecs: 'review round',
42
+ reviewIrTaskCommitCode: 'review round',
43
+ reviewIrTaskCommitMixed: 'review round',
44
+ reviewChangesSpecs: 'review round',
45
+ reviewChangesCode: 'review round',
46
+ reviewChangesMixed: 'review round',
47
+ reviewChangesAndChallengesSpecs: 'review round',
48
+ reviewChangesAndChallengesCode: 'review round',
49
+ reviewChangesAndChallengesMixed: 'review round',
50
+ };
51
+ export function validateCodeOptions(captainOptions) {
52
+ const code = readCodeNamespace(captainOptions);
53
+ if (code === undefined)
54
+ return {};
55
+ if (typeof code !== 'object' || code === null || Array.isArray(code)) {
56
+ throw new Error('captain.options.code must be an object');
57
+ }
58
+ for (const key of Object.keys(code)) {
59
+ if (!CODE_OPTION_KEYS.has(key)) {
60
+ throw new Error(`Unknown config field captain.options.code.${key}`);
61
+ }
62
+ }
63
+ const options = {};
64
+ const committer = code.committer;
65
+ if (committer !== undefined) {
66
+ if (typeof committer !== 'string' || !COMMITTER_PLAYER_IDS.has(committer)) {
67
+ throw new Error("captain.options.code.committer must be 'coder' or 'reviewer'");
68
+ }
69
+ options.committer = committer;
70
+ }
71
+ return options;
72
+ }
73
+ function readCodeNamespace(captainOptions) {
74
+ if (typeof captainOptions !== 'object' ||
75
+ captainOptions === null ||
76
+ Array.isArray(captainOptions)) {
77
+ return undefined;
78
+ }
79
+ return captainOptions.code;
80
+ }
81
+ function playerIdentity(players, id) {
82
+ const entry = players.find((p) => p.id === id);
83
+ return entry?.model ?? entry?.adapter;
84
+ }
85
+ export function createCodeRuntimeOptions({ captainOptions, players, }) {
86
+ const codeOptions = validateCodeOptions(captainOptions);
87
+ const coderPlayer = playerIdentity(players, 'coder');
88
+ const reviewerPlayer = playerIdentity(players, 'reviewer');
89
+ return {
90
+ coderPlayer,
91
+ reviewerPlayer,
92
+ ...(codeOptions.committer !== undefined
93
+ ? { committerPlayer: codeOptions.committer }
94
+ : {}),
95
+ };
96
+ }
97
+ export const codePlaybookRegistryEntry = {
98
+ id: 'code',
99
+ command: 'code',
100
+ intent: 'software development / SDLC coding workflow',
101
+ idleStateId: 'ready',
102
+ finalStateId: 'done',
103
+ copyPasteGuardNames: codeCopyPasteGuardNames,
104
+ stateCountLabels: codeStateCountLabels,
105
+ validateOptions: validateCodeOptions,
106
+ createRuntime(options) {
107
+ return createPlaybookRuntime(createCodeRuntimeOptions(options));
108
+ },
109
+ };
@@ -0,0 +1,159 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
+
4
+ import createPlaybookRuntime, {
5
+ type CodePlaybookOptions,
6
+ type PlaybookRuntime,
7
+ } from './code.playbook.js';
8
+
9
+ // PBRT-29/30: CODE runtime options are carried under
10
+ // `captain.options.code`, a namespaced object the host forwards
11
+ // verbatim through `captain.options`. cligent neither reads nor
12
+ // validates `options.code`; the CODE registry entry is the sole
13
+ // validator. The CODE options schema defines one key, `committer`: an
14
+ // optional Committer-alias player id, one of the baked player ids
15
+ // `coder` / `reviewer`. A valid `options.code` is absent, `{}`, or
16
+ // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown
17
+ // and rejected with a path-named error, and an out-of-range
18
+ // `committer` value is rejected naming `captain.options.code.committer`.
19
+ // A further CODE option shall be introduced as its own higher-numbered
20
+ // item that widens `CODE_OPTION_KEYS`; the validator still fails closed
21
+ // on stray keys.
22
+ const CODE_OPTION_KEYS = new Set<string>(['committer']);
23
+ const COMMITTER_PLAYER_IDS = new Set<string>(['coder', 'reviewer']);
24
+ export const codeCopyPasteGuardNames = [
25
+ 'accepted',
26
+ 'approved',
27
+ 'challengeAccepted',
28
+ 'challengeRejected',
29
+ 'challengesRaised',
30
+ 'changesMadeCode',
31
+ 'changesMadeCodeAndChallenged',
32
+ 'changesMadeMixed',
33
+ 'changesMadeMixedAndChallenged',
34
+ 'changesMadeSpecs',
35
+ 'changesMadeSpecsAndChallenged',
36
+ 'hasFindings',
37
+ 'needsRevision',
38
+ 'noFindings',
39
+ 'noOpenItems',
40
+ ] as const;
41
+
42
+ export const codeStateCountLabels = {
43
+ adjudicateChallenges: 'rebuttal',
44
+ reviewBossCommitSpecs: 'review round',
45
+ reviewBossCommitCode: 'review round',
46
+ reviewBossCommitMixed: 'review round',
47
+ reviewIrTaskCommitSpecs: 'review round',
48
+ reviewIrTaskCommitCode: 'review round',
49
+ reviewIrTaskCommitMixed: 'review round',
50
+ reviewChangesSpecs: 'review round',
51
+ reviewChangesCode: 'review round',
52
+ reviewChangesMixed: 'review round',
53
+ reviewChangesAndChallengesSpecs: 'review round',
54
+ reviewChangesAndChallengesCode: 'review round',
55
+ reviewChangesAndChallengesMixed: 'review round',
56
+ } as const;
57
+
58
+ // The validated CODE options set. `committer`, when present, is the
59
+ // resolved Committer-alias player id (PBRT-8 / PBRT-30); a future CODE
60
+ // option widens both this type and `CODE_OPTION_KEYS`.
61
+ export interface CodeOptions {
62
+ committer?: 'coder' | 'reviewer';
63
+ }
64
+
65
+ export interface RegistryPlayer {
66
+ id: string;
67
+ adapter?: string;
68
+ model?: string;
69
+ }
70
+
71
+ export interface CreateCodeRuntimeOptions {
72
+ captainOptions: unknown;
73
+ players: readonly RegistryPlayer[];
74
+ }
75
+
76
+ export interface CodePlaybookRegistryEntry {
77
+ id: 'code';
78
+ command: 'code';
79
+ intent: string;
80
+ idleStateId: 'ready';
81
+ finalStateId: 'done';
82
+ copyPasteGuardNames: readonly string[];
83
+ stateCountLabels: typeof codeStateCountLabels;
84
+ validateOptions(captainOptions: unknown): CodeOptions;
85
+ createRuntime(options: CreateCodeRuntimeOptions): PlaybookRuntime;
86
+ }
87
+
88
+ export function validateCodeOptions(captainOptions: unknown): CodeOptions {
89
+ const code = readCodeNamespace(captainOptions);
90
+ if (code === undefined) return {};
91
+ if (typeof code !== 'object' || code === null || Array.isArray(code)) {
92
+ throw new Error('captain.options.code must be an object');
93
+ }
94
+ for (const key of Object.keys(code)) {
95
+ if (!CODE_OPTION_KEYS.has(key)) {
96
+ throw new Error(`Unknown config field captain.options.code.${key}`);
97
+ }
98
+ }
99
+ const options: CodeOptions = {};
100
+ const committer = (code as Record<string, unknown>).committer;
101
+ if (committer !== undefined) {
102
+ if (typeof committer !== 'string' || !COMMITTER_PLAYER_IDS.has(committer)) {
103
+ throw new Error(
104
+ "captain.options.code.committer must be 'coder' or 'reviewer'",
105
+ );
106
+ }
107
+ options.committer = committer as 'coder' | 'reviewer';
108
+ }
109
+ return options;
110
+ }
111
+
112
+ function readCodeNamespace(captainOptions: unknown): unknown {
113
+ if (
114
+ typeof captainOptions !== 'object' ||
115
+ captainOptions === null ||
116
+ Array.isArray(captainOptions)
117
+ ) {
118
+ return undefined;
119
+ }
120
+ return (captainOptions as Record<string, unknown>).code;
121
+ }
122
+
123
+ function playerIdentity(
124
+ players: readonly RegistryPlayer[],
125
+ id: string,
126
+ ): string | undefined {
127
+ const entry = players.find((p) => p.id === id);
128
+ return entry?.model ?? entry?.adapter;
129
+ }
130
+
131
+ export function createCodeRuntimeOptions({
132
+ captainOptions,
133
+ players,
134
+ }: CreateCodeRuntimeOptions): CodePlaybookOptions {
135
+ const codeOptions = validateCodeOptions(captainOptions);
136
+ const coderPlayer = playerIdentity(players, 'coder');
137
+ const reviewerPlayer = playerIdentity(players, 'reviewer');
138
+ return {
139
+ coderPlayer,
140
+ reviewerPlayer,
141
+ ...(codeOptions.committer !== undefined
142
+ ? { committerPlayer: codeOptions.committer }
143
+ : {}),
144
+ };
145
+ }
146
+
147
+ export const codePlaybookRegistryEntry: CodePlaybookRegistryEntry = {
148
+ id: 'code',
149
+ command: 'code',
150
+ intent: 'software development / SDLC coding workflow',
151
+ idleStateId: 'ready',
152
+ finalStateId: 'done',
153
+ copyPasteGuardNames: codeCopyPasteGuardNames,
154
+ stateCountLabels: codeStateCountLabels,
155
+ validateOptions: validateCodeOptions,
156
+ createRuntime(options) {
157
+ return createPlaybookRuntime(createCodeRuntimeOptions(options));
158
+ },
159
+ };
@@ -1,6 +1,4 @@
1
1
  import type { Captain } from '@sublang/cligent/tmux-play';
2
- export interface CodeOptions {
3
- committer?: 'coder' | 'reviewer';
4
- }
5
- export declare function validateCodeOptions(captainOptions: unknown): CodeOptions;
2
+ export { codeCopyPasteGuardNames, codeStateCountLabels, codePlaybookRegistryEntry, createCodeRuntimeOptions, validateCodeOptions, } from './code.registry.js';
3
+ export type { CodeOptions, CodePlaybookRegistryEntry, CreateCodeRuntimeOptions, RegistryPlayer, } from './code.registry.js';
6
4
  export default function createCodeTmuxPlayCaptain(options: unknown): Captain;
@@ -1,160 +1,11 @@
1
1
  // SPDX-License-Identifier: Apache-2.0
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
- import createPlaybookRuntime from './code.playbook.js';
4
- // PBRT-29/30: CODE runtime options are carried under
5
- // `captain.options.code`, a namespaced object the host forwards
6
- // verbatim through `captain.options`. cligent neither reads nor
7
- // validates `options.code` (PBRT-30); this adapter is the sole
8
- // validator. The CODE options schema defines one key, `committer`: an
9
- // optional Committer-alias player id, one of the baked player ids
10
- // `coder` / `reviewer`. A valid `options.code` is absent, `{}`, or
11
- // `{ committer: 'coder' | 'reviewer' }`; every other key is unknown
12
- // and rejected with a path-named error, and an out-of-range
13
- // `committer` value is rejected naming `captain.options.code.committer`.
14
- // A further CODE option shall be introduced as its own higher-numbered
15
- // item that widens `CODE_OPTION_KEYS`; the validator still fails closed
16
- // on stray keys.
17
- const CODE_OPTION_KEYS = new Set(['committer']);
18
- const COMMITTER_PLAYER_IDS = new Set(['coder', 'reviewer']);
19
- export function validateCodeOptions(captainOptions) {
20
- const code = readCodeNamespace(captainOptions);
21
- if (code === undefined)
22
- return {};
23
- if (typeof code !== 'object' || code === null || Array.isArray(code)) {
24
- throw new Error('captain.options.code must be an object');
25
- }
26
- for (const key of Object.keys(code)) {
27
- if (!CODE_OPTION_KEYS.has(key)) {
28
- throw new Error(`Unknown config field captain.options.code.${key}`);
29
- }
30
- }
31
- const options = {};
32
- const committer = code.committer;
33
- if (committer !== undefined) {
34
- if (typeof committer !== 'string' || !COMMITTER_PLAYER_IDS.has(committer)) {
35
- throw new Error("captain.options.code.committer must be 'coder' or 'reviewer'");
36
- }
37
- options.committer = committer;
38
- }
39
- return options;
40
- }
41
- function readCodeNamespace(captainOptions) {
42
- if (typeof captainOptions !== 'object' ||
43
- captainOptions === null ||
44
- Array.isArray(captainOptions)) {
45
- return undefined;
46
- }
47
- return captainOptions.code;
48
- }
49
- // Captain factory per TMUX-014: `(options: unknown) => Captain`.
50
- // `options` is whatever `captain.options` carries in the YAML config;
51
- // CODE reads only the namespaced `options.code` (PBRT-30). The per-run
52
- // player identity strings (`coderPlayer`, `reviewerPlayer`) are
53
- // derived from `session.players` at init time per PBRT-4 — preferring
54
- // each entry's `model` and falling back to `adapter` when no model is
55
- // pinned — so player prompts and commit-message trailers carry the
56
- // concrete model identity (e.g. `claude-opus-4-7`) rather than the
57
- // adapter family name (e.g. `claude`). They come from `session.players`
58
- // independent of `captain.options.code` and override any same-named
59
- // keys.
3
+ import createPlaybookCaptainShell from './playbook-captain.js';
4
+ export { codeCopyPasteGuardNames, codeStateCountLabels, codePlaybookRegistryEntry, createCodeRuntimeOptions, validateCodeOptions, } from './code.registry.js';
5
+ // Compatibility shim for the historic `./code/tmux-play` package
6
+ // export. Public launch paths now target the Playbook Captain shell
7
+ // directly; explicit configs that still import this module get the
8
+ // same shell with CODE registered.
60
9
  export default function createCodeTmuxPlayCaptain(options) {
61
- // CaptainSession is bound at init time and persists across turns;
62
- // CaptainContext is rebuilt per turn and carries the call
63
- // primitives. The runtime is constructed in `init` so identity
64
- // strings derived from `session.players` can flow into its options;
65
- // PlaybookPorts is built once at init, so the per-turn context
66
- // lives in a closure-scoped slot the port callbacks query lazily.
67
- let runtime;
68
- let activeContext;
69
- return {
70
- async init(session) {
71
- // PBRT-30: validate `captain.options.code` before constructing
72
- // the runtime so a stray key fails `init` closed with a
73
- // path-named error; the empty schema yields an empty options set.
74
- const codeOptions = validateCodeOptions(options);
75
- const playerIdentity = (id) => {
76
- const entry = session.players.find((p) => p.id === id);
77
- return entry?.model ?? entry?.adapter;
78
- };
79
- const coderPlayer = playerIdentity('coder');
80
- const reviewerPlayer = playerIdentity('reviewer');
81
- // Identity strings from `session.players` override any same-named
82
- // keys and are independent of `captain.options.code` (PBRT-30).
83
- // The validated `committer` alias threads in as the runtime's
84
- // Committer player id (`committerPlayer`, PBRT-8).
85
- const runtimeOptions = {
86
- coderPlayer,
87
- reviewerPlayer,
88
- ...(codeOptions.committer !== undefined
89
- ? { committerPlayer: codeOptions.committer }
90
- : {}),
91
- };
92
- runtime = createPlaybookRuntime(runtimeOptions);
93
- const ports = {
94
- callPlayer: async (playerId, prompt, _signal) => {
95
- if (!activeContext) {
96
- throw new Error('callPlayer invoked outside a Boss turn');
97
- }
98
- // PlayerRunResult per TMUX-033 already matches PlayerResult
99
- // (`status: 'ok' | 'aborted' | 'error'`, `finalText?`,
100
- // `error?`). cligent honors context.signal internally;
101
- // the runtime's signal is the same source forwarded
102
- // through handleBossInput, so dropping `_signal` is
103
- // safe.
104
- const r = await activeContext.callPlayer(playerId, prompt);
105
- return {
106
- status: r.status,
107
- finalText: r.finalText,
108
- error: r.error,
109
- };
110
- },
111
- callJudge: async (prompt, _signal) => {
112
- if (!activeContext) {
113
- throw new Error('callJudge invoked outside a Boss turn');
114
- }
115
- // PBRT-15 / DR-007: run the judge call hidden so its JSON
116
- // reply never reaches the Boss pane; the runtime composes the
117
- // human-readable pane lines (PBRT-3) from the parsed result.
118
- const r = await activeContext.callCaptain(prompt, {
119
- visibility: 'hidden',
120
- });
121
- if (r.status !== 'ok') {
122
- throw new Error(r.error ?? `callCaptain status "${r.status}"`);
123
- }
124
- if (r.finalText === undefined) {
125
- throw new Error('callCaptain returned status=ok with no finalText');
126
- }
127
- return r.finalText;
128
- },
129
- emitStatus: async (message, data) => {
130
- await session.emitStatus(message, data);
131
- },
132
- emitTelemetry: async (event) => {
133
- await session.emitTelemetry(event);
134
- },
135
- };
136
- await runtime.init(ports);
137
- },
138
- async handleBossTurn(turn, context) {
139
- if (!runtime) {
140
- throw new Error('init must be called first');
141
- }
142
- activeContext = context;
143
- try {
144
- // Forward the Boss prompt + cligent's per-turn signal into
145
- // the runtime; the runtime stashes the signal so the
146
- // captain bridge passes it down to callPlayer / callJudge.
147
- await runtime.handleBossInput({
148
- text: turn.prompt,
149
- signal: context.signal,
150
- });
151
- }
152
- finally {
153
- activeContext = undefined;
154
- }
155
- },
156
- async dispose() {
157
- await runtime?.dispose();
158
- },
159
- };
10
+ return createPlaybookCaptainShell(options);
160
11
  }