@sublang/playbook 8.0.0 → 9.0.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 +3 -3
- package/docs/cli.md +15 -15
- package/docs/configuration.md +13 -8
- package/docs/embedding.md +7 -2
- package/package.json +1 -1
- package/reference/sdlc/captain.playbook/captain.playbook.js +14 -3
- package/reference/sdlc/captain.playbook/captain.playbook.ts +18 -4
- package/reference/sdlc/code.playbook/code.fsm.d.ts +4 -1
- package/reference/sdlc/code.playbook/code.fsm.js +11 -4
- package/reference/sdlc/code.playbook/code.fsm.ts +12 -4
- package/reference/sdlc/code.playbook/code.playbook.js +14 -3
- package/reference/sdlc/code.playbook/code.playbook.ts +13 -3
- package/reference/sdlc/code.playbook/playbook-captain.js +44 -10
- package/reference/sdlc/code.playbook/playbook-captain.ts +47 -10
- package/reference/sdlc/decide.playbook/decide.fsm.d.ts +1 -1
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +2 -0
- package/reference/sdlc/decide.playbook/decide.playbook.js +299 -117
- package/reference/sdlc/decide.playbook/decide.playbook.ts +395 -131
- package/reference/sdlc/review.playbook/review.playbook.js +14 -3
- package/reference/sdlc/review.playbook/review.playbook.ts +13 -3
- package/slc/gears2fsm.md +19 -2
- package/slc/link.md +184 -42
- package/src/runtime.d.ts +1 -0
- package/src/runtime.ts +1 -0
- package/src/xstate-playbook-runtime.d.ts +13 -3
- package/src/xstate-playbook-runtime.js +732 -251
- package/src/xstate-playbook-runtime.ts +873 -280
- package/src/xstate-runtime.d.ts +17 -7
- package/src/xstate-runtime.js +135 -57
- package/src/xstate-runtime.ts +243 -84
package/README.md
CHANGED
|
@@ -81,7 +81,7 @@ playbook run --session 4f2c0000-0000-4000-8000-000000009ab1 "continue"
|
|
|
81
81
|
|
|
82
82
|
`playbook run` prints the one Boss-visible Captain reply to stdout and operational status to stderr; CODE and DECIDE can complete their nested REVIEW calls there too.
|
|
83
83
|
|
|
84
|
-
See [Using the CLI](docs/cli.md) for flags and durable continuation, [Configuring agents](docs/configuration.md) for the shared lineup, [Embedding](docs/embedding.md) for custom hosts, and the [changelog](CHANGELOG.md) for releases.
|
|
84
|
+
See [Using the CLI](docs/cli.md) for flags and durable continuation, [Configuring agents](docs/configuration.md) for the shared lineup, [Embedding](docs/embedding.md) for custom hosts, and the [changelog](https://github.com/sublang-ai/playbook/blob/main/CHANGELOG.md) for releases.
|
|
85
85
|
|
|
86
86
|
## Create your own playbook
|
|
87
87
|
|
|
@@ -129,8 +129,8 @@ pnpm playbook # drive a Boss turn against the source tree
|
|
|
129
129
|
```
|
|
130
130
|
|
|
131
131
|
Playbook is itself spec-driven: the compiler phases are specs in [`slc/`](slc), and the reference playbooks are regenerated from their prose sources.
|
|
132
|
-
Edit a source, regenerate its GEARS, FSM, and runtime artifacts, sync the tests and downstream specs until `pnpm test` passes, and commit with co-author trailers per [`specs/packages/git.md`](specs/packages/git.md).
|
|
133
|
-
The gears↔FSM contract ([the playbook package](specs/packages/playbook.md)) and runtime contract ([the playbook-runtime package](specs/packages/playbook-runtime.md)) are pinned in [`specs/packages/`](specs/packages) and verified by the test suite.
|
|
132
|
+
Edit a source, regenerate its GEARS, FSM, and runtime artifacts, sync the tests and downstream specs until `pnpm test` passes, and commit with co-author trailers per [`specs/packages/git.md`](https://github.com/sublang-ai/playbook/blob/main/specs/packages/git.md).
|
|
133
|
+
The gears↔FSM contract ([the playbook package](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook.md)) and runtime contract ([the playbook-runtime package](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md)) are pinned in [`specs/packages/`](https://github.com/sublang-ai/playbook/tree/main/specs/packages) and verified by the test suite.
|
|
134
134
|
|
|
135
135
|
## License
|
|
136
136
|
|
package/docs/cli.md
CHANGED
|
@@ -18,7 +18,7 @@ Each adapter is backed by a vendor runtime that installing
|
|
|
18
18
|
agent stack you did not ask for. Which versions each adapter supports
|
|
19
19
|
is [cligent](https://github.com/sublang-ai/cligent)'s knowledge and
|
|
20
20
|
ships with it
|
|
21
|
-
([DR-027](
|
|
21
|
+
([DR-027](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/027-runtime-compatibility-from-cligent.md));
|
|
22
22
|
the commands below install the latest, which cligent accepts from its
|
|
23
23
|
supported floor up. Install the SDKs your config names, each as its
|
|
24
24
|
own top-level install root:
|
|
@@ -45,7 +45,7 @@ finds a bare specifier by walking *up* from there — which reaches the
|
|
|
45
45
|
install prefix's own `node_modules`, but never into a sibling package's
|
|
46
46
|
subtree. An SDK that landed inside some other package is invisible to
|
|
47
47
|
the adapter even though it is on disk
|
|
48
|
-
([DR-026](
|
|
48
|
+
([DR-026](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/026-optional-adapter-sdks.md)).
|
|
49
49
|
|
|
50
50
|
Both surfaces check this before doing any work: a declared adapter
|
|
51
51
|
whose runtime is not loadable — or is installed below the version
|
|
@@ -54,7 +54,7 @@ runtime is reported as not installed; a stale one with its installed
|
|
|
54
54
|
and required versions, never as absent. Either way the remedy printed
|
|
55
55
|
is cligent's pinned install, `npm install -g <package>@<version>`, so
|
|
56
56
|
following it cannot install a version the gate refuses again
|
|
57
|
-
([[playbook-cli-40](
|
|
57
|
+
([[playbook-cli-40](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-40)]).
|
|
58
58
|
|
|
59
59
|
## Interactive
|
|
60
60
|
|
|
@@ -98,8 +98,8 @@ later signals use native client detach or termination semantics and do not
|
|
|
98
98
|
retire the pane child's session. Only `--config` and composed
|
|
99
99
|
`--theme-diagnostics` use the stock subprocess boundary: those forms mirror
|
|
100
100
|
its exit status or signal and exit `127` when it cannot be spawned
|
|
101
|
-
([[playbook-cli-1](
|
|
102
|
-
[[playbook-cli-2](
|
|
101
|
+
([[playbook-cli-1](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-1)],
|
|
102
|
+
[[playbook-cli-2](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-2)]).
|
|
103
103
|
|
|
104
104
|
### Running a Boss turn
|
|
105
105
|
|
|
@@ -118,8 +118,8 @@ currently offers. It never does the specialized work itself, and a
|
|
|
118
118
|
conversational turn — including a progress or status question — leaves
|
|
119
119
|
the engagement, its parked state, and any pending player question
|
|
120
120
|
untouched
|
|
121
|
-
([[playbook-captain-1](
|
|
122
|
-
[[playbook-captain-2](
|
|
121
|
+
([[playbook-captain-1](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-1)],
|
|
122
|
+
[[playbook-captain-2](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-2)]).
|
|
123
123
|
|
|
124
124
|
The current CODE, REVIEW, and DECIDE workflows take their deterministic
|
|
125
125
|
initial event from the selecting Boss turn. CODE and DECIDE then call REVIEW
|
|
@@ -131,16 +131,16 @@ identical. When a player surfaces a
|
|
|
131
131
|
clarifying question the FSM parks, the pane shows the question, and a
|
|
132
132
|
judge classifies your next turn as its reply or a fresh directive that
|
|
133
133
|
abandons it
|
|
134
|
-
([[playbook-runtime-2](
|
|
134
|
+
([[playbook-runtime-2](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md#playbook-runtime-2)]).
|
|
135
135
|
|
|
136
136
|
The Captain pane shows start/stop/finished status with `◇` lines and
|
|
137
137
|
streams progress with captain-speech classification and questions
|
|
138
|
-
([[playbook-runtime-3](
|
|
138
|
+
([[playbook-runtime-3](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md#playbook-runtime-3)]), while player
|
|
139
139
|
prompts ride their own panes. A turn that actually did something ends
|
|
140
140
|
with one Captain reply summarizing what changed, composed only from that
|
|
141
141
|
turn's reported outcome; a turn that changed nothing ends with an
|
|
142
142
|
ordinary reply and no saved-counts line
|
|
143
|
-
([[playbook-captain-19](
|
|
143
|
+
([[playbook-captain-19](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-captain.md#playbook-captain-19)]).
|
|
144
144
|
|
|
145
145
|
## Headless
|
|
146
146
|
|
|
@@ -182,7 +182,7 @@ Captain reply. Argument, config, catalog, readiness, or pre-turn setup errors
|
|
|
182
182
|
exit `1`; a started-turn, persistence, lease-release, or presentation failure
|
|
183
183
|
exits `2` with stdout empty. SIGINT, SIGTERM, and SIGHUP preserve the
|
|
184
184
|
uncertain boundary, withhold stdout, and are re-raised after lease retirement
|
|
185
|
-
([[playbook-cli-18](
|
|
185
|
+
([[playbook-cli-18](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-18)]).
|
|
186
186
|
|
|
187
187
|
The former positional `<from>`, `resume`, `--player`, `--captain`,
|
|
188
188
|
`--option`, `--cwd`, `--last`, run-only `--config`, and top-level `run:`
|
|
@@ -221,8 +221,8 @@ the primary config file. Before either front end imports a configured
|
|
|
221
221
|
filesystem registry, the shared launcher checks whether that module can
|
|
222
222
|
resolve `xstate` and `@sublang/playbook/xstate-runtime`. When needed, it
|
|
223
223
|
creates engine symlinks beside the module and prints one provisioning line
|
|
224
|
-
([[playbook-cli-36](
|
|
225
|
-
[DR-024](
|
|
224
|
+
([[playbook-cli-36](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-36)],
|
|
225
|
+
[DR-024](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/024-runtime-engine-provisioning.md)).
|
|
226
226
|
|
|
227
227
|
A directory where both imports already resolve is untouched, and
|
|
228
228
|
`--no-provision` disables new links for either fresh front end. If the
|
|
@@ -261,8 +261,8 @@ current `model` and `effort` selections apply to the next call, including an
|
|
|
261
261
|
explicit boolean `false` provider-default reset. The retained provider token
|
|
262
262
|
is never silently replaced by a fresh conversation if that selection is not
|
|
263
263
|
supported
|
|
264
|
-
([[playbook-cli-22](
|
|
265
|
-
[DR-032](
|
|
264
|
+
([[playbook-cli-22](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-22)],
|
|
265
|
+
[DR-032](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/032-explicit-roles-session-players.md)).
|
|
266
266
|
|
|
267
267
|
### Recovering an uncertain turn
|
|
268
268
|
|
package/docs/configuration.md
CHANGED
|
@@ -30,7 +30,7 @@ names, nesting, and ancestry never infer a binding.
|
|
|
30
30
|
Each `captain` or `players.<player-id>` value is either an adapter shorthand
|
|
31
31
|
(`claude`, `codex`) or a block carrying that agent's own `adapter`, `model`,
|
|
32
32
|
`effort`, `instruction`, and `permissions`. Settings are inline per stable
|
|
33
|
-
agent ([DR-021](
|
|
33
|
+
agent ([DR-021](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/021-inline-agent-settings.md)). Dots in a
|
|
34
34
|
player ID are literal characters, not YAML hierarchy. Other adapter IDs pass
|
|
35
35
|
through to `tmux-play` with a warning because `playbook` cannot preflight their
|
|
36
36
|
auth.
|
|
@@ -91,7 +91,7 @@ playbooks:
|
|
|
91
91
|
The current bundled workflows accept no workflow-specific options.
|
|
92
92
|
Each role's per-call prompt names its current `model`, else its player's
|
|
93
93
|
`adapter`
|
|
94
|
-
([[playbook-runtime-4](
|
|
94
|
+
([[playbook-runtime-4](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-runtime.md#playbook-runtime-4)]),
|
|
95
95
|
so commit trailers credit the concrete model rather than the adapter
|
|
96
96
|
family.
|
|
97
97
|
|
|
@@ -160,7 +160,7 @@ instead of doing the work itself. Claude and Gemini enforce that at the
|
|
|
160
160
|
provider level. The Codex, Kimi, and OpenCode adapters cannot — they
|
|
161
161
|
reject any tool list — so a `captain:` using one of them falls back to a
|
|
162
162
|
prompt-level restriction
|
|
163
|
-
([DR-013](
|
|
163
|
+
([DR-013](https://github.com/sublang-ai/playbook/blob/main/specs/decisions/013-routing-only-captain-control.md) A1).
|
|
164
164
|
Those adapters remain good choices for *players*, where full tools are wanted.
|
|
165
165
|
|
|
166
166
|
Adapter readiness is intentionally light: `claude` is ready with local
|
|
@@ -203,7 +203,7 @@ playbooks:
|
|
|
203
203
|
```
|
|
204
204
|
|
|
205
205
|
The global file is never modified, and `--with` is not forwarded to
|
|
206
|
-
`tmux-play` ([[playbook-cli-25](
|
|
206
|
+
`tmux-play` ([[playbook-cli-25](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-25)]).
|
|
207
207
|
Overlays apply when creating a fresh session and as current-config input for a
|
|
208
208
|
compatible ordinary reopen. A selected session keeps its stored catalog,
|
|
209
209
|
player roster, role bindings, adapter, instruction, permissions, and working
|
|
@@ -250,6 +250,11 @@ playbooks:
|
|
|
250
250
|
playbook run "/my-workflow perform the task"
|
|
251
251
|
```
|
|
252
252
|
|
|
253
|
+
Importing a `.ts` registry uses Node's native type stripping, available
|
|
254
|
+
unflagged on Node 22.18+ and 23.6+; on the older Node versions this
|
|
255
|
+
package supports (>= 20.6), compile the registry and point `from` at the
|
|
256
|
+
emitted `.js` module instead.
|
|
257
|
+
|
|
253
258
|
A relative path-shaped `from` is resolved relative to the primary config
|
|
254
259
|
file, not the invocation directory; an absolute path is clearest for an SLC
|
|
255
260
|
entry emitted in a project working tree.
|
|
@@ -318,9 +323,9 @@ or `--session`.
|
|
|
318
323
|
|
|
319
324
|
The JSON response is now exactly `{ "sessionId": "…", "reply": "…" }`.
|
|
320
325
|
Released direct-run session records are not complete Captain sessions and
|
|
321
|
-
cannot be continued by the new host ([[playbook-cli-19](
|
|
322
|
-
[[playbook-cli-22](
|
|
323
|
-
[[playbook-cli-28](
|
|
326
|
+
cannot be continued by the new host ([[playbook-cli-19](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-19)],
|
|
327
|
+
[[playbook-cli-22](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-22)],
|
|
328
|
+
[[playbook-cli-28](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-28)]).
|
|
324
329
|
|
|
325
330
|
## Migrating from `profiles`
|
|
326
331
|
|
|
@@ -334,7 +339,7 @@ do by hand.
|
|
|
334
339
|
|
|
335
340
|
For a one-off, pass a raw `tmux-play` config explicitly. This bypasses
|
|
336
341
|
the seed, composition, and readiness gate, forwarding arguments to
|
|
337
|
-
`tmux-play` verbatim ([[playbook-cli-1](
|
|
342
|
+
`tmux-play` verbatim ([[playbook-cli-1](https://github.com/sublang-ai/playbook/blob/main/specs/packages/playbook-cli.md#playbook-cli-1)]):
|
|
338
343
|
|
|
339
344
|
```sh
|
|
340
345
|
playbook --config ./tmux-play.config.yaml
|
package/docs/embedding.md
CHANGED
|
@@ -9,7 +9,7 @@ app) is another. This guide shows how to wire a playbook runtime into
|
|
|
9
9
|
your own host.
|
|
10
10
|
|
|
11
11
|
> **Release note:** this guide targets the current semver-stable six-port
|
|
12
|
-
> contract; see the [CHANGELOG](
|
|
12
|
+
> contract; see the [CHANGELOG](https://github.com/sublang-ai/playbook/blob/main/CHANGELOG.md) for migration details.
|
|
13
13
|
|
|
14
14
|
## The runtime contract
|
|
15
15
|
|
|
@@ -31,6 +31,11 @@ nested-playbook bridge.
|
|
|
31
31
|
|
|
32
32
|
## Constructing a runtime against your own ports
|
|
33
33
|
|
|
34
|
+
`p-queue` is your host's own dependency here — declare it in your
|
|
35
|
+
application's `dependencies` (the same library `@sublang/playbook` itself
|
|
36
|
+
depends on) rather than relying on it resolving through the package's
|
|
37
|
+
tree, which pnpm's strict linking will not allow.
|
|
38
|
+
|
|
34
39
|
```ts
|
|
35
40
|
import createPlaybookRuntime from '@sublang/playbook/review/playbook';
|
|
36
41
|
import type {
|
|
@@ -190,7 +195,7 @@ Boss-visible status text. Because trace observers do receive opaque resume
|
|
|
190
195
|
tokens, persisted traces should be protected as sensitive data.
|
|
191
196
|
|
|
192
197
|
See
|
|
193
|
-
[`code.playbook.test.ts`](
|
|
198
|
+
[`code.playbook.test.ts`](https://github.com/sublang-ai/playbook/blob/main/reference/sdlc/code.playbook/code.playbook.test.ts)
|
|
194
199
|
for the full range of port shapes (classifier, judge, abort, interrupt,
|
|
195
200
|
status/telemetry) the runtime is contract-tested against.
|
|
196
201
|
|
package/package.json
CHANGED
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
// controller-port submission, and status formatting.
|
|
34
34
|
// Compat: spec.compat = { artifactSchema: 2, runtimeAbi: 1 }
|
|
35
35
|
// (DR-022; checked at construction by the loading engine).
|
|
36
|
-
import { createXStatePlaybookRuntime, defaultComposeCaptainPrompt, normalizeError, normalizeErrorCompact, parseJudgeJson, snapshotJsonValue,
|
|
36
|
+
import { createXStatePlaybookRuntime, defaultComposeCaptainPrompt, normalizeError, normalizeErrorCompact, parseJudgeJson, snapshotJsonValue, } from '../../../src/xstate-runtime.js';
|
|
37
37
|
import { captainMachine, } from './captain.fsm.js';
|
|
38
38
|
function assertNonEmptyString(value, label) {
|
|
39
39
|
if (typeof value !== 'string' || value.trim().length === 0) {
|
|
@@ -516,7 +516,11 @@ export const _internal = {
|
|
|
516
516
|
// describe/apply control surface — lives in @sublang/playbook/xstate-runtime.
|
|
517
517
|
const runtimeSpec = {
|
|
518
518
|
label: 'CAPTAIN',
|
|
519
|
-
|
|
519
|
+
// DR-022 / slc/link.md: the declaration carries the value current at link
|
|
520
|
+
// time — a literal, never the loading engine's RUNTIME_ABI self-report,
|
|
521
|
+
// which would follow whatever engine loads the module and make the
|
|
522
|
+
// factory's skew check compare that engine with itself.
|
|
523
|
+
compat: { artifactSchema: 2, runtimeAbi: 1 },
|
|
520
524
|
snapshotOptions: snapshotCaptainOptions,
|
|
521
525
|
machineInput: (options) => ({ enabledPlaybooks: options.enabledPlaybooks }),
|
|
522
526
|
classifyBossText: (text, ports, signal, snapshotOrState, boundary, options) => classifyControllerTurn(text, ports, signal, snapshotOrState, boundary, options),
|
|
@@ -543,8 +547,15 @@ const runtimeSpec = {
|
|
|
543
547
|
],
|
|
544
548
|
statusesForState,
|
|
545
549
|
};
|
|
546
|
-
|
|
550
|
+
// DR-022's compat check fails fast at factory construction. The Captain is
|
|
551
|
+
// statically imported by the shell and both CLI front ends, so constructing
|
|
552
|
+
// here at module evaluation would turn a future compat mismatch into an
|
|
553
|
+
// uncaught ESM-load error that takes even `--help` down; constructing on
|
|
554
|
+
// the first runtime request keeps the failure inside the caught
|
|
555
|
+
// host-construction boundary that owes the Boss a setup diagnostic.
|
|
556
|
+
let createCaptainPlaybookRuntime;
|
|
547
557
|
export function createPlaybookRuntime(options) {
|
|
558
|
+
createCaptainPlaybookRuntime ??= createXStatePlaybookRuntime(captainMachine, runtimeSpec);
|
|
548
559
|
return createCaptainPlaybookRuntime(options);
|
|
549
560
|
}
|
|
550
561
|
const factory = createPlaybookRuntime;
|
|
@@ -41,7 +41,6 @@ import {
|
|
|
41
41
|
normalizeErrorCompact,
|
|
42
42
|
parseJudgeJson,
|
|
43
43
|
snapshotJsonValue,
|
|
44
|
-
RUNTIME_ABI,
|
|
45
44
|
type PlaybookActorOutput,
|
|
46
45
|
type PlaybookCaptainInput,
|
|
47
46
|
type ScheduledStatus,
|
|
@@ -798,7 +797,11 @@ export const _internal = {
|
|
|
798
797
|
// describe/apply control surface — lives in @sublang/playbook/xstate-runtime.
|
|
799
798
|
const runtimeSpec: XStatePlaybookRuntimeSpec<ValidatedCaptainOptions> = {
|
|
800
799
|
label: 'CAPTAIN',
|
|
801
|
-
|
|
800
|
+
// DR-022 / slc/link.md: the declaration carries the value current at link
|
|
801
|
+
// time — a literal, never the loading engine's RUNTIME_ABI self-report,
|
|
802
|
+
// which would follow whatever engine loads the module and make the
|
|
803
|
+
// factory's skew check compare that engine with itself.
|
|
804
|
+
compat: { artifactSchema: 2, runtimeAbi: 1 },
|
|
802
805
|
snapshotOptions: snapshotCaptainOptions,
|
|
803
806
|
machineInput: (options) => ({ enabledPlaybooks: options.enabledPlaybooks }),
|
|
804
807
|
classifyBossText: (text, ports, signal, snapshotOrState, boundary, options) =>
|
|
@@ -834,12 +837,23 @@ const runtimeSpec: XStatePlaybookRuntimeSpec<ValidatedCaptainOptions> = {
|
|
|
834
837
|
statusesForState,
|
|
835
838
|
};
|
|
836
839
|
|
|
837
|
-
|
|
838
|
-
|
|
840
|
+
// DR-022's compat check fails fast at factory construction. The Captain is
|
|
841
|
+
// statically imported by the shell and both CLI front ends, so constructing
|
|
842
|
+
// here at module evaluation would turn a future compat mismatch into an
|
|
843
|
+
// uncaught ESM-load error that takes even `--help` down; constructing on
|
|
844
|
+
// the first runtime request keeps the failure inside the caught
|
|
845
|
+
// host-construction boundary that owes the Boss a setup diagnostic.
|
|
846
|
+
let createCaptainPlaybookRuntime:
|
|
847
|
+
| PlaybookRuntimeFactory<ValidatedCaptainOptions>
|
|
848
|
+
| undefined;
|
|
839
849
|
|
|
840
850
|
export function createPlaybookRuntime(
|
|
841
851
|
options: PlaybookRuntimeOptions,
|
|
842
852
|
): PlaybookRuntime {
|
|
853
|
+
createCaptainPlaybookRuntime ??= createXStatePlaybookRuntime(
|
|
854
|
+
captainMachine,
|
|
855
|
+
runtimeSpec,
|
|
856
|
+
);
|
|
843
857
|
return createCaptainPlaybookRuntime(options);
|
|
844
858
|
}
|
|
845
859
|
|
|
@@ -202,7 +202,7 @@ export declare const codingMachine: import("xstate").StateMachine<CodingContext,
|
|
|
202
202
|
} | {
|
|
203
203
|
type: "resumesIrTask";
|
|
204
204
|
params: unknown;
|
|
205
|
-
}, never, "done" | "failed" | "awaitBossReply" | "ready" | "runFirstPhase" | "reviewFirstCommit" | "runIrTask" | "reviewIrTask", string, CodingInput, {
|
|
205
|
+
}, never, "done" | "failed" | "awaitBossReply" | "ready" | "runFirstPhase" | "reviewFirstCommit" | "runIrTask" | "reviewIrTask" | "reportedReviewFailure", string, CodingInput, {
|
|
206
206
|
readonly status: "complete";
|
|
207
207
|
/** Exact identity of the latest CODE-owned commit. */
|
|
208
208
|
readonly lastCodeCommit: string;
|
|
@@ -239,6 +239,9 @@ export declare const codingMachine: import("xstate").StateMachine<CodingContext,
|
|
|
239
239
|
readonly failed: {
|
|
240
240
|
id: "failed";
|
|
241
241
|
};
|
|
242
|
+
readonly reportedReviewFailure: {
|
|
243
|
+
id: "reportedReviewFailure";
|
|
244
|
+
};
|
|
242
245
|
readonly done: {
|
|
243
246
|
id: "done";
|
|
244
247
|
};
|
|
@@ -53,6 +53,7 @@ const STATE_DESCRIPTIONS = {
|
|
|
53
53
|
reviewIrTask: 'The REVIEW playbook is checking the latest task commit.',
|
|
54
54
|
awaitBossReply: 'Waiting for Boss to answer Coder.',
|
|
55
55
|
failed: 'The coding workflow failed and is waiting for a new coding intent.',
|
|
56
|
+
reportedReviewFailure: 'The coding workflow reported a REVIEW failure and the last code-owned commit.',
|
|
56
57
|
done: 'The coding workflow completed after REVIEW found no unsettled findings.',
|
|
57
58
|
};
|
|
58
59
|
function playbookMeta(stateId, role) {
|
|
@@ -530,14 +531,14 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
530
531
|
target: 'runIrTask',
|
|
531
532
|
},
|
|
532
533
|
{
|
|
533
|
-
target: '
|
|
534
|
+
target: 'reportedReviewFailure',
|
|
534
535
|
actions: 'completeWithInvalidReviewOutput',
|
|
535
536
|
},
|
|
536
537
|
],
|
|
537
538
|
onError: [
|
|
538
539
|
{
|
|
539
540
|
guard: 'authoredReviewFailure',
|
|
540
|
-
target: '
|
|
541
|
+
target: 'reportedReviewFailure',
|
|
541
542
|
actions: 'completeWithReviewFailure',
|
|
542
543
|
},
|
|
543
544
|
{ target: 'failed', actions: 'rememberActorError' },
|
|
@@ -619,14 +620,14 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
619
620
|
actions: 'completeSuccessfully',
|
|
620
621
|
},
|
|
621
622
|
{
|
|
622
|
-
target: '
|
|
623
|
+
target: 'reportedReviewFailure',
|
|
623
624
|
actions: 'completeWithInvalidReviewOutput',
|
|
624
625
|
},
|
|
625
626
|
],
|
|
626
627
|
onError: [
|
|
627
628
|
{
|
|
628
629
|
guard: 'authoredReviewFailure',
|
|
629
|
-
target: '
|
|
630
|
+
target: 'reportedReviewFailure',
|
|
630
631
|
actions: 'completeWithReviewFailure',
|
|
631
632
|
},
|
|
632
633
|
{ target: 'failed', actions: 'rememberActorError' },
|
|
@@ -670,6 +671,12 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
670
671
|
START_CODE: { target: 'runFirstPhase', actions: 'startCoding' },
|
|
671
672
|
},
|
|
672
673
|
},
|
|
674
|
+
reportedReviewFailure: {
|
|
675
|
+
id: 'reportedReviewFailure',
|
|
676
|
+
description: STATE_DESCRIPTIONS.reportedReviewFailure,
|
|
677
|
+
meta: playbookMeta('reportedReviewFailure'),
|
|
678
|
+
type: 'final',
|
|
679
|
+
},
|
|
673
680
|
done: {
|
|
674
681
|
id: 'done',
|
|
675
682
|
description: STATE_DESCRIPTIONS.done,
|
|
@@ -202,6 +202,8 @@ const STATE_DESCRIPTIONS = {
|
|
|
202
202
|
reviewIrTask: 'The REVIEW playbook is checking the latest task commit.',
|
|
203
203
|
awaitBossReply: 'Waiting for Boss to answer Coder.',
|
|
204
204
|
failed: 'The coding workflow failed and is waiting for a new coding intent.',
|
|
205
|
+
reportedReviewFailure:
|
|
206
|
+
'The coding workflow reported a REVIEW failure and the last code-owned commit.',
|
|
205
207
|
done: 'The coding workflow completed after REVIEW found no unsettled findings.',
|
|
206
208
|
} as const;
|
|
207
209
|
|
|
@@ -768,14 +770,14 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
768
770
|
target: 'runIrTask',
|
|
769
771
|
},
|
|
770
772
|
{
|
|
771
|
-
target: '
|
|
773
|
+
target: 'reportedReviewFailure',
|
|
772
774
|
actions: 'completeWithInvalidReviewOutput',
|
|
773
775
|
},
|
|
774
776
|
],
|
|
775
777
|
onError: [
|
|
776
778
|
{
|
|
777
779
|
guard: 'authoredReviewFailure',
|
|
778
|
-
target: '
|
|
780
|
+
target: 'reportedReviewFailure',
|
|
779
781
|
actions: 'completeWithReviewFailure',
|
|
780
782
|
},
|
|
781
783
|
{ target: 'failed', actions: 'rememberActorError' },
|
|
@@ -857,14 +859,14 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
857
859
|
actions: 'completeSuccessfully',
|
|
858
860
|
},
|
|
859
861
|
{
|
|
860
|
-
target: '
|
|
862
|
+
target: 'reportedReviewFailure',
|
|
861
863
|
actions: 'completeWithInvalidReviewOutput',
|
|
862
864
|
},
|
|
863
865
|
],
|
|
864
866
|
onError: [
|
|
865
867
|
{
|
|
866
868
|
guard: 'authoredReviewFailure',
|
|
867
|
-
target: '
|
|
869
|
+
target: 'reportedReviewFailure',
|
|
868
870
|
actions: 'completeWithReviewFailure',
|
|
869
871
|
},
|
|
870
872
|
{ target: 'failed', actions: 'rememberActorError' },
|
|
@@ -908,6 +910,12 @@ export const codingMachine = machineSetup.createMachine({
|
|
|
908
910
|
START_CODE: { target: 'runFirstPhase', actions: 'startCoding' },
|
|
909
911
|
},
|
|
910
912
|
},
|
|
913
|
+
reportedReviewFailure: {
|
|
914
|
+
id: 'reportedReviewFailure',
|
|
915
|
+
description: STATE_DESCRIPTIONS.reportedReviewFailure,
|
|
916
|
+
meta: playbookMeta('reportedReviewFailure'),
|
|
917
|
+
type: 'final',
|
|
918
|
+
},
|
|
911
919
|
done: {
|
|
912
920
|
id: 'done',
|
|
913
921
|
description: STATE_DESCRIPTIONS.done,
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// Adjudication: LLM judge per player state; coderOutput is carried verbatim
|
|
10
10
|
// Nested call: literal review target through the shared bridge
|
|
11
11
|
// Compat: artifact schema 2 / runtime ABI 1
|
|
12
|
-
import {
|
|
12
|
+
import { createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
|
|
13
13
|
import { codingMachine, } from './code.fsm.js';
|
|
14
14
|
const OPTION_KEYS = new Set(['runResults']);
|
|
15
15
|
const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
|
|
@@ -81,9 +81,20 @@ export const _internal = {
|
|
|
81
81
|
};
|
|
82
82
|
const runtimeSpec = {
|
|
83
83
|
label: 'CODE',
|
|
84
|
-
|
|
84
|
+
// DR-022 / slc/link.md: the declaration carries the value current at link
|
|
85
|
+
// time — a literal, never the loading engine's RUNTIME_ABI self-report,
|
|
86
|
+
// which would follow whatever engine loads the module and make the
|
|
87
|
+
// factory's skew check compare that engine with itself.
|
|
88
|
+
compat: { artifactSchema: 2, runtimeAbi: 1 },
|
|
85
89
|
snapshotOptions: snapshotCodeOptions,
|
|
86
|
-
entryEvent: {
|
|
90
|
+
entryEvent: {
|
|
91
|
+
type: 'START_CODE',
|
|
92
|
+
textField: 'callerInput',
|
|
93
|
+
// `startCoding` copies the entry text here, so the failure-state retry
|
|
94
|
+
// reads it back from the persisted machine snapshot and survives a
|
|
95
|
+
// continued session (DR-034).
|
|
96
|
+
contextField: 'callerInput',
|
|
97
|
+
},
|
|
87
98
|
roleStates: {
|
|
88
99
|
runFirstPhase: {
|
|
89
100
|
role: 'coder',
|
|
@@ -11,7 +11,6 @@
|
|
|
11
11
|
// Compat: artifact schema 2 / runtime ABI 1
|
|
12
12
|
|
|
13
13
|
import {
|
|
14
|
-
RUNTIME_ABI,
|
|
15
14
|
createXStatePlaybookRuntime,
|
|
16
15
|
snapshotJsonValue,
|
|
17
16
|
type PlaybookPlayerInput,
|
|
@@ -168,9 +167,20 @@ export const _internal = {
|
|
|
168
167
|
|
|
169
168
|
const runtimeSpec = {
|
|
170
169
|
label: 'CODE',
|
|
171
|
-
|
|
170
|
+
// DR-022 / slc/link.md: the declaration carries the value current at link
|
|
171
|
+
// time — a literal, never the loading engine's RUNTIME_ABI self-report,
|
|
172
|
+
// which would follow whatever engine loads the module and make the
|
|
173
|
+
// factory's skew check compare that engine with itself.
|
|
174
|
+
compat: { artifactSchema: 2, runtimeAbi: 1 },
|
|
172
175
|
snapshotOptions: snapshotCodeOptions,
|
|
173
|
-
entryEvent: {
|
|
176
|
+
entryEvent: {
|
|
177
|
+
type: 'START_CODE',
|
|
178
|
+
textField: 'callerInput',
|
|
179
|
+
// `startCoding` copies the entry text here, so the failure-state retry
|
|
180
|
+
// reads it back from the persisted machine snapshot and survives a
|
|
181
|
+
// continued session (DR-034).
|
|
182
|
+
contextField: 'callerInput',
|
|
183
|
+
},
|
|
174
184
|
roleStates: {
|
|
175
185
|
runFirstPhase: {
|
|
176
186
|
role: 'coder',
|
|
@@ -126,6 +126,34 @@ function stateDigestLine(state, description) {
|
|
|
126
126
|
digestLine `status ${state.status}`,
|
|
127
127
|
].join('; ');
|
|
128
128
|
}
|
|
129
|
+
// CAPTAIN-5's mirrored ledger member holds the runtime snapshot's
|
|
130
|
+
// pending-question projection — entries of `{ questionId, asker, question,
|
|
131
|
+
// sourceItem }` — never a raw telemetry payload: the linked runtime's state
|
|
132
|
+
// telemetry carries the singular full-context question, whose extra
|
|
133
|
+
// runtime-internal fields (`resumeStateId`) fail the durable snapshot's
|
|
134
|
+
// leaf-projection equality (CAPTAIN-41) and with it headless settlement.
|
|
135
|
+
function mirroredBossQuestions(value) {
|
|
136
|
+
if (value === undefined || value === null)
|
|
137
|
+
return undefined;
|
|
138
|
+
const entries = Array.isArray(value) ? value : [value];
|
|
139
|
+
return entries.map((entry) => {
|
|
140
|
+
if (typeof entry !== 'object' || entry === null)
|
|
141
|
+
return entry;
|
|
142
|
+
const record = entry;
|
|
143
|
+
const projected = {};
|
|
144
|
+
if (record.questionId !== undefined) {
|
|
145
|
+
projected.questionId = record.questionId;
|
|
146
|
+
}
|
|
147
|
+
if (record.asker !== undefined)
|
|
148
|
+
projected.asker = record.asker;
|
|
149
|
+
if (record.question !== undefined)
|
|
150
|
+
projected.question = record.question;
|
|
151
|
+
if (record.sourceItem !== undefined) {
|
|
152
|
+
projected.sourceItem = record.sourceItem;
|
|
153
|
+
}
|
|
154
|
+
return projected;
|
|
155
|
+
});
|
|
156
|
+
}
|
|
129
157
|
function pendingQuestionLines(pending) {
|
|
130
158
|
const list = Array.isArray(pending)
|
|
131
159
|
? pending
|
|
@@ -1491,8 +1519,7 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
1491
1519
|
}
|
|
1492
1520
|
}
|
|
1493
1521
|
if (leafFrame() === frame) {
|
|
1494
|
-
pendingBossQuestions =
|
|
1495
|
-
record.pendingBossQuestions ?? record.pendingBossQuestion;
|
|
1522
|
+
pendingBossQuestions = mirroredBossQuestions(record.pendingBossQuestions ?? record.pendingBossQuestion);
|
|
1496
1523
|
lastError = normalizeErrorCompact(record.lastError);
|
|
1497
1524
|
if (state.quiescent && state.tags.includes('playbook.parked')) {
|
|
1498
1525
|
await setMode('engaged.parked', `sub-runtime:${state.stateId ?? 'structured'}`);
|
|
@@ -2308,7 +2335,7 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
2308
2335
|
// runtime publishes before disposal removes the frame. The opaque run
|
|
2309
2336
|
// output remains runtime-to-runtime data and never becomes Captain
|
|
2310
2337
|
// evidence (CAPPLAY-10).
|
|
2311
|
-
activeTurn?.settlementFacts.push(rootCompletionFact(frame));
|
|
2338
|
+
activeTurn?.settlementFacts.push(rootCompletionFact(frame, result));
|
|
2312
2339
|
await runEffect(() => disposeStack('final'));
|
|
2313
2340
|
}
|
|
2314
2341
|
return;
|
|
@@ -3200,10 +3227,10 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
3200
3227
|
// -------------------------------------------------------------------------
|
|
3201
3228
|
// The controller port (DR-029): host validation is the sole effector.
|
|
3202
3229
|
// -------------------------------------------------------------------------
|
|
3203
|
-
// The
|
|
3204
|
-
// same way the digest reads it.
|
|
3205
|
-
//
|
|
3206
|
-
//
|
|
3230
|
+
// The legacy state-description channel, read from the live control view the
|
|
3231
|
+
// same way the digest reads it. DR-037 makes the terminal result authoritative
|
|
3232
|
+
// for completion; this remains only for an older runtime that omits the new
|
|
3233
|
+
// optional member.
|
|
3207
3234
|
const leafStateDescription = (frame) => {
|
|
3208
3235
|
if (typeof frame.runtime.describe !== 'function')
|
|
3209
3236
|
return undefined;
|
|
@@ -3214,9 +3241,16 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
3214
3241
|
return undefined;
|
|
3215
3242
|
}
|
|
3216
3243
|
};
|
|
3217
|
-
const rootCompletionFact = (frame) => {
|
|
3218
|
-
const
|
|
3219
|
-
|
|
3244
|
+
const rootCompletionFact = (frame, result) => {
|
|
3245
|
+
const returned = result.stateDescription === undefined
|
|
3246
|
+
? ''
|
|
3247
|
+
: compactEvidence(result.stateDescription);
|
|
3248
|
+
const legacy = returned === '' ? leafStateDescription(frame) : undefined;
|
|
3249
|
+
const description = returned !== ''
|
|
3250
|
+
? returned
|
|
3251
|
+
: legacy === undefined
|
|
3252
|
+
? ''
|
|
3253
|
+
: compactEvidence(legacy);
|
|
3220
3254
|
return description === ''
|
|
3221
3255
|
? `${frameLabel(frame)} completed; its runtime published no result description.`
|
|
3222
3256
|
: `${frameLabel(frame)} completed; its runtime-published result meaning was ${quoteEvidence(description)}.`;
|