@sublang/playbook 7.0.0 → 8.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.
Files changed (61) hide show
  1. package/README.md +17 -4
  2. package/docs/cli.md +74 -29
  3. package/docs/configuration.md +209 -112
  4. package/docs/embedding.md +71 -25
  5. package/package.json +4 -3
  6. package/reference/sdlc/captain.playbook/captain.playbook.js +3 -3
  7. package/reference/sdlc/captain.playbook/captain.playbook.ts +3 -3
  8. package/reference/sdlc/code.md +1 -1
  9. package/reference/sdlc/code.playbook/bin/interactive-session.js +816 -0
  10. package/reference/sdlc/code.playbook/bin/launch-config.js +1078 -116
  11. package/reference/sdlc/code.playbook/bin/playbook.js +489 -34
  12. package/reference/sdlc/code.playbook/bin/run.js +283 -298
  13. package/reference/sdlc/code.playbook/bin/session-store.js +818 -26
  14. package/reference/sdlc/code.playbook/code.fsm.d.ts +5 -5
  15. package/reference/sdlc/code.playbook/code.fsm.introspect.js +2 -2
  16. package/reference/sdlc/code.playbook/code.fsm.introspect.ts +2 -2
  17. package/reference/sdlc/code.playbook/code.fsm.js +7 -11
  18. package/reference/sdlc/code.playbook/code.fsm.ts +9 -17
  19. package/reference/sdlc/code.playbook/code.gears.md +1 -1
  20. package/reference/sdlc/code.playbook/code.playbook.d.ts +2 -1
  21. package/reference/sdlc/code.playbook/code.playbook.js +12 -13
  22. package/reference/sdlc/code.playbook/code.playbook.ts +22 -15
  23. package/reference/sdlc/code.playbook/code.registry.d.ts +5 -13
  24. package/reference/sdlc/code.playbook/code.registry.js +3 -10
  25. package/reference/sdlc/code.playbook/code.registry.ts +7 -32
  26. package/reference/sdlc/code.playbook/playbook-captain.d.ts +39 -14
  27. package/reference/sdlc/code.playbook/playbook-captain.js +970 -289
  28. package/reference/sdlc/code.playbook/playbook-captain.ts +1403 -396
  29. package/reference/sdlc/code.playbook/playbook.config.template.yaml +41 -49
  30. package/reference/sdlc/decide.md +4 -4
  31. package/reference/sdlc/decide.playbook/decide.fsm.d.ts +9 -9
  32. package/reference/sdlc/decide.playbook/decide.fsm.js +21 -14
  33. package/reference/sdlc/decide.playbook/decide.fsm.ts +27 -23
  34. package/reference/sdlc/decide.playbook/decide.gears.md +3 -5
  35. package/reference/sdlc/decide.playbook/decide.playbook.d.ts +9 -13
  36. package/reference/sdlc/decide.playbook/decide.playbook.js +171 -134
  37. package/reference/sdlc/decide.playbook/decide.playbook.ts +238 -162
  38. package/reference/sdlc/decide.playbook/decide.registry.d.ts +5 -13
  39. package/reference/sdlc/decide.playbook/decide.registry.js +3 -9
  40. package/reference/sdlc/decide.playbook/decide.registry.ts +7 -31
  41. package/reference/sdlc/review.md +4 -5
  42. package/reference/sdlc/review.playbook/review.fsm.d.ts +9 -11
  43. package/reference/sdlc/review.playbook/review.fsm.js +30 -24
  44. package/reference/sdlc/review.playbook/review.fsm.ts +39 -35
  45. package/reference/sdlc/review.playbook/review.gears.md +6 -5
  46. package/reference/sdlc/review.playbook/review.playbook.d.ts +2 -1
  47. package/reference/sdlc/review.playbook/review.playbook.js +16 -21
  48. package/reference/sdlc/review.playbook/review.playbook.ts +26 -26
  49. package/reference/sdlc/review.playbook/review.registry.d.ts +5 -13
  50. package/reference/sdlc/review.playbook/review.registry.js +3 -16
  51. package/reference/sdlc/review.playbook/review.registry.ts +7 -38
  52. package/slc/gears2fsm.md +27 -23
  53. package/slc/link.md +113 -93
  54. package/slc/text2gears.md +19 -18
  55. package/src/runtime.d.ts +20 -16
  56. package/src/runtime.ts +19 -23
  57. package/src/xstate-playbook-runtime.d.ts +21 -17
  58. package/src/xstate-playbook-runtime.js +241 -149
  59. package/src/xstate-playbook-runtime.ts +331 -178
  60. package/src/xstate-runtime.js +63 -24
  61. package/src/xstate-runtime.ts +96 -28
@@ -8,18 +8,20 @@ export type PendingBossQuestion = {
8
8
  readonly questionId: 'runFirstPhase' | 'runIrTask';
9
9
  readonly resumeStateId: 'runFirstPhase' | 'runIrTask';
10
10
  readonly sourceItem: 'CODE-1' | 'CODE-3';
11
- readonly player: 'Coder';
11
+ readonly asker: {
12
+ readonly kind: 'role';
13
+ readonly roleId: 'coder';
14
+ };
12
15
  readonly question: string;
13
16
  };
14
17
  export type PlayerInput = {
15
18
  readonly stateId: 'runFirstPhase' | 'runIrTask';
16
- readonly player: 'Coder';
19
+ readonly role: 'coder';
17
20
  readonly sourceItem: 'CODE-1' | 'CODE-3';
18
21
  readonly prompt: string;
19
22
  readonly result: Readonly<Record<string, string>>;
20
23
  readonly callerInput: string;
21
24
  readonly runResults: string;
22
- readonly coderPlayer: string;
23
25
  readonly irNumber?: string;
24
26
  readonly irTask?: string;
25
27
  readonly pendingBossQuestion?: PendingBossQuestion;
@@ -77,11 +79,9 @@ export type CodePlaybookOutput = {
77
79
  readonly error: CompactError;
78
80
  };
79
81
  export type CodingInput = {
80
- readonly coderPlayer?: string;
81
82
  readonly runResults?: string;
82
83
  };
83
84
  export type CodingContext = {
84
- readonly coderPlayer: string;
85
85
  readonly runResults: string;
86
86
  readonly callerInput?: string;
87
87
  readonly coderOutput?: string;
@@ -27,7 +27,7 @@ export function enumeratePlayerStates(machine) {
27
27
  if (invoke?.src !== 'player' || invoke.input === undefined)
28
28
  return [];
29
29
  const getInput = (context) => invoke.input?.({ context });
30
- const input = getInput({ coderPlayer: 'Coder', runResults: '' });
30
+ const input = getInput({ runResults: '' });
31
31
  return [
32
32
  {
33
33
  stateId,
@@ -47,7 +47,7 @@ export function enumerateNestedPlaybookStates(machine) {
47
47
  if (invoke?.src !== 'playbook' || invoke.input === undefined)
48
48
  return [];
49
49
  const getInput = (context) => invoke.input?.({ context });
50
- const input = getInput({ coderPlayer: 'Coder', runResults: '' });
50
+ const input = getInput({ runResults: '' });
51
51
  return [
52
52
  {
53
53
  stateId,
@@ -92,7 +92,7 @@ export function enumeratePlayerStates(
92
92
  if (invoke?.src !== 'player' || invoke.input === undefined) return [];
93
93
  const getInput = (context: CodingContext): PlayerInput =>
94
94
  invoke.input?.({ context }) as PlayerInput;
95
- const input = getInput({ coderPlayer: 'Coder', runResults: '' });
95
+ const input = getInput({ runResults: '' });
96
96
  return [
97
97
  {
98
98
  stateId,
@@ -116,7 +116,7 @@ export function enumerateNestedPlaybookStates(
116
116
  if (invoke?.src !== 'playbook' || invoke.input === undefined) return [];
117
117
  const getInput = (context: CodingContext): PlaybookInput =>
118
118
  invoke.input?.({ context }) as PlaybookInput;
119
- const input = getInput({ coderPlayer: 'Coder', runResults: '' });
119
+ const input = getInput({ runResults: '' });
120
120
  return [
121
121
  {
122
122
  stateId,
@@ -55,12 +55,12 @@ const STATE_DESCRIPTIONS = {
55
55
  failed: 'The coding workflow failed and is waiting for a new coding intent.',
56
56
  done: 'The coding workflow completed after REVIEW found no unsettled findings.',
57
57
  };
58
- function playbookMeta(stateId, player) {
58
+ function playbookMeta(stateId, role) {
59
59
  return {
60
60
  playbook: {
61
61
  stateId,
62
62
  description: STATE_DESCRIPTIONS[stateId],
63
- ...(player === undefined ? {} : { player }),
63
+ ...(role === undefined ? {} : { role }),
64
64
  },
65
65
  };
66
66
  }
@@ -324,7 +324,6 @@ const machineSetup = setup({
324
324
  if (event.type !== 'START_CODE')
325
325
  return {};
326
326
  return {
327
- coderPlayer: context.coderPlayer,
328
327
  runResults: context.runResults,
329
328
  callerInput: event.callerInput,
330
329
  coderOutput: undefined,
@@ -389,7 +388,7 @@ const machineSetup = setup({
389
388
  questionId: stateId,
390
389
  resumeStateId: stateId,
391
390
  sourceItem: stateId === 'runFirstPhase' ? 'CODE-1' : 'CODE-3',
392
- player: 'Coder',
391
+ asker: { kind: 'role', roleId: 'coder' },
393
392
  question,
394
393
  },
395
394
  bossReply: undefined,
@@ -430,7 +429,6 @@ export const codingMachine = machineSetup.createMachine({
430
429
  id: 'code',
431
430
  initial: 'ready',
432
431
  context: ({ input }) => ({
433
- coderPlayer: isNonEmptyString(input.coderPlayer) ? input.coderPlayer : 'Coder',
434
432
  runResults: typeof input.runResults === 'string' ? input.runResults : '',
435
433
  }),
436
434
  output: ({ context }) => {
@@ -465,19 +463,18 @@ export const codingMachine = machineSetup.createMachine({
465
463
  runFirstPhase: {
466
464
  id: 'runFirstPhase',
467
465
  description: STATE_DESCRIPTIONS.runFirstPhase,
468
- meta: playbookMeta('runFirstPhase', 'Coder'),
466
+ meta: playbookMeta('runFirstPhase', 'coder'),
469
467
  tags: ['playbook.busy'],
470
468
  invoke: {
471
469
  src: 'player',
472
470
  input: ({ context }) => ({
473
471
  stateId: 'runFirstPhase',
474
- player: 'Coder',
472
+ role: 'coder',
475
473
  sourceItem: 'CODE-1',
476
474
  prompt: FIRST_PHASE_PROMPT,
477
475
  result: FIRST_PHASE_RESULTS,
478
476
  callerInput: context.callerInput ?? '',
479
477
  runResults: context.runResults,
480
- coderPlayer: context.coderPlayer,
481
478
  ...(context.pendingBossQuestion === undefined
482
479
  ? {}
483
480
  : { pendingBossQuestion: context.pendingBossQuestion }),
@@ -550,19 +547,18 @@ export const codingMachine = machineSetup.createMachine({
550
547
  runIrTask: {
551
548
  id: 'runIrTask',
552
549
  description: STATE_DESCRIPTIONS.runIrTask,
553
- meta: playbookMeta('runIrTask', 'Coder'),
550
+ meta: playbookMeta('runIrTask', 'coder'),
554
551
  tags: ['playbook.busy'],
555
552
  invoke: {
556
553
  src: 'player',
557
554
  input: ({ context }) => ({
558
555
  stateId: 'runIrTask',
559
- player: 'Coder',
556
+ role: 'coder',
560
557
  sourceItem: 'CODE-3',
561
558
  prompt: IR_TASK_PROMPT,
562
559
  result: IR_TASK_RESULTS,
563
560
  callerInput: context.callerInput ?? '',
564
561
  runResults: context.runResults,
565
- coderPlayer: context.coderPlayer,
566
562
  ...(context.irNumber === undefined
567
563
  ? {}
568
564
  : { irNumber: context.irNumber }),
@@ -31,19 +31,18 @@ export type PendingBossQuestion = {
31
31
  readonly questionId: 'runFirstPhase' | 'runIrTask';
32
32
  readonly resumeStateId: 'runFirstPhase' | 'runIrTask';
33
33
  readonly sourceItem: 'CODE-1' | 'CODE-3';
34
- readonly player: 'Coder';
34
+ readonly asker: { readonly kind: 'role'; readonly roleId: 'coder' };
35
35
  readonly question: string;
36
36
  };
37
37
 
38
38
  export type PlayerInput = {
39
39
  readonly stateId: 'runFirstPhase' | 'runIrTask';
40
- readonly player: 'Coder';
40
+ readonly role: 'coder';
41
41
  readonly sourceItem: 'CODE-1' | 'CODE-3';
42
42
  readonly prompt: string;
43
43
  readonly result: Readonly<Record<string, string>>;
44
44
  readonly callerInput: string;
45
45
  readonly runResults: string;
46
- readonly coderPlayer: string;
47
46
  readonly irNumber?: string;
48
47
  readonly irTask?: string;
49
48
  readonly pendingBossQuestion?: PendingBossQuestion;
@@ -114,12 +113,10 @@ export type CodePlaybookOutput =
114
113
  };
115
114
 
116
115
  export type CodingInput = {
117
- readonly coderPlayer?: string;
118
116
  readonly runResults?: string;
119
117
  };
120
118
 
121
119
  export type CodingContext = {
122
- readonly coderPlayer: string;
123
120
  readonly runResults: string;
124
121
  readonly callerInput?: string;
125
122
  readonly coderOutput?: string;
@@ -210,13 +207,13 @@ const STATE_DESCRIPTIONS = {
210
207
 
211
208
  function playbookMeta<StateId extends keyof typeof STATE_DESCRIPTIONS>(
212
209
  stateId: StateId,
213
- player?: 'Coder',
210
+ role?: 'coder',
214
211
  ) {
215
212
  return {
216
213
  playbook: {
217
214
  stateId,
218
215
  description: STATE_DESCRIPTIONS[stateId],
219
- ...(player === undefined ? {} : { player }),
216
+ ...(role === undefined ? {} : { role }),
220
217
  },
221
218
  };
222
219
  }
@@ -557,7 +554,6 @@ const machineSetup = setup({
557
554
  startCoding: assign(({ context, event }) => {
558
555
  if (event.type !== 'START_CODE') return {};
559
556
  return {
560
- coderPlayer: context.coderPlayer,
561
557
  runResults: context.runResults,
562
558
  callerInput: event.callerInput,
563
559
  coderOutput: undefined,
@@ -623,7 +619,7 @@ const machineSetup = setup({
623
619
  questionId: stateId,
624
620
  resumeStateId: stateId,
625
621
  sourceItem: stateId === 'runFirstPhase' ? 'CODE-1' : 'CODE-3',
626
- player: 'Coder',
622
+ asker: { kind: 'role', roleId: 'coder' },
627
623
  question,
628
624
  },
629
625
  bossReply: undefined,
@@ -670,8 +666,6 @@ export const codingMachine = machineSetup.createMachine({
670
666
  id: 'code',
671
667
  initial: 'ready',
672
668
  context: ({ input }) => ({
673
- coderPlayer:
674
- isNonEmptyString(input.coderPlayer) ? input.coderPlayer : 'Coder',
675
669
  runResults: typeof input.runResults === 'string' ? input.runResults : '',
676
670
  }),
677
671
  output: ({ context }): CodePlaybookOutput => {
@@ -707,19 +701,18 @@ export const codingMachine = machineSetup.createMachine({
707
701
  runFirstPhase: {
708
702
  id: 'runFirstPhase',
709
703
  description: STATE_DESCRIPTIONS.runFirstPhase,
710
- meta: playbookMeta('runFirstPhase', 'Coder'),
704
+ meta: playbookMeta('runFirstPhase', 'coder'),
711
705
  tags: ['playbook.busy'],
712
706
  invoke: {
713
707
  src: 'player',
714
708
  input: ({ context }): PlayerInput => ({
715
709
  stateId: 'runFirstPhase',
716
- player: 'Coder',
710
+ role: 'coder',
717
711
  sourceItem: 'CODE-1',
718
712
  prompt: FIRST_PHASE_PROMPT,
719
713
  result: FIRST_PHASE_RESULTS,
720
714
  callerInput: context.callerInput ?? '',
721
715
  runResults: context.runResults,
722
- coderPlayer: context.coderPlayer,
723
716
  ...(context.pendingBossQuestion === undefined
724
717
  ? {}
725
718
  : { pendingBossQuestion: context.pendingBossQuestion }),
@@ -792,19 +785,18 @@ export const codingMachine = machineSetup.createMachine({
792
785
  runIrTask: {
793
786
  id: 'runIrTask',
794
787
  description: STATE_DESCRIPTIONS.runIrTask,
795
- meta: playbookMeta('runIrTask', 'Coder'),
788
+ meta: playbookMeta('runIrTask', 'coder'),
796
789
  tags: ['playbook.busy'],
797
790
  invoke: {
798
791
  src: 'player',
799
792
  input: ({ context }): PlayerInput => ({
800
793
  stateId: 'runIrTask',
801
- player: 'Coder',
794
+ role: 'coder',
802
795
  sourceItem: 'CODE-3',
803
796
  prompt: IR_TASK_PROMPT,
804
797
  result: IR_TASK_RESULTS,
805
798
  callerInput: context.callerInput ?? '',
806
799
  runResults: context.runResults,
807
- coderPlayer: context.coderPlayer,
808
800
  ...(context.irNumber === undefined
809
801
  ? {}
810
802
  : { irNumber: context.irNumber }),
@@ -3,7 +3,7 @@
3
3
 
4
4
  # CODE: Commit-Based Coding Workflow
5
5
 
6
- Players:
6
+ Roles:
7
7
 
8
8
  - Coder
9
9
 
@@ -1,3 +1,4 @@
1
+ import { type XStatePromptIdentity } from '@sublang/playbook/xstate-runtime';
1
2
  import { type CodingInput, type PlayerInput } from './code.fsm.js';
2
3
  import type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, PlayerCallOptions, PlaybookCallRequest, PlaybookCallResult, PlaybookCallStart, PlaybookControlReceipt, PlaybookControlView, PlaybookPendingCall, PlaybookPorts, PlaybookRunResult, PlaybookRuntime, PlaybookRuntimeFactory, PlaybookRuntimeSnapshot, PlaybookSession, PlaybookState, PlaybookStateValue, PlaybookTraceEvent, PlaybookTraceType, PlayerResult, PlayerSessionStore } from '@sublang/playbook/runtime';
3
4
  export type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, PlayerCallOptions, PlaybookCallRequest, PlaybookCallResult, PlaybookCallStart, PlaybookControlReceipt, PlaybookControlView, PlaybookPendingCall, PlaybookPorts, PlaybookRunResult, PlaybookRuntime, PlaybookRuntimeFactory, PlaybookRuntimeSnapshot, PlaybookSession, PlaybookState, PlaybookStateValue, PlaybookTraceEvent, PlaybookTraceType, PlayerResult, PlayerSessionStore, };
@@ -7,7 +8,7 @@ export type CodePlaybookOptions = CodingInput;
7
8
  * runtime text. The generic composer preserves the marker itself; CODE's
8
9
  * override additionally keeps a multiline value inside that quote.
9
10
  */
10
- declare function composePlayerPrompt(input: PlayerInput): string;
11
+ declare function composePlayerPrompt(input: PlayerInput, promptIdentity: XStatePromptIdentity): string;
11
12
  export declare const _internal: {
12
13
  composePlayerPrompt: typeof composePlayerPrompt;
13
14
  VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string>;
@@ -3,15 +3,15 @@
3
3
  //
4
4
  // Generated by slc/link.md (FSM-to-Runtime linker).
5
5
  // Source FSM: ./code.fsm.ts
6
- // Player bind: Coder→coder (default lowercased binding)
6
+ // Role metadata: Coder→coder (canonical local role)
7
7
  // Boss event: deterministic START_CODE entry; exact Boss text becomes
8
8
  // callerInput; pending Coder questions retain BOSS_REPLY
9
9
  // Adjudication: LLM judge per player state; coderOutput is carried verbatim
10
10
  // Nested call: literal review target through the shared bridge
11
- // Compat: artifact schema 1 / runtime ABI 1
11
+ // Compat: artifact schema 2 / runtime ABI 1
12
12
  import { RUNTIME_ABI, createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
13
13
  import { codingMachine, } from './code.fsm.js';
14
- const OPTION_KEYS = new Set(['coderPlayer', 'runResults']);
14
+ const OPTION_KEYS = new Set(['runResults']);
15
15
  const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
16
16
  const CONTINUATION_PREAMBLE = 'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
17
17
  const VERBATIM_PAYLOAD_FIELDS = new Set([
@@ -38,8 +38,6 @@ function snapshotCodeOptions(value) {
38
38
  function placeholderField(token) {
39
39
  if (token === '#')
40
40
  return 'irNumber';
41
- if (token === 'coder-llm')
42
- return 'coderPlayer';
43
41
  return token.replace(/-([A-Za-z0-9_$])/g, (_match, next) => next.toUpperCase());
44
42
  }
45
43
  function quoteContinuation(value) {
@@ -50,14 +48,16 @@ function quoteContinuation(value) {
50
48
  * runtime text. The generic composer preserves the marker itself; CODE's
51
49
  * override additionally keeps a multiline value inside that quote.
52
50
  */
53
- function composePlayerPrompt(input) {
51
+ function composePlayerPrompt(input, promptIdentity) {
54
52
  const fields = input;
55
53
  const template = input.prompt
56
54
  .split('\n')
57
55
  .filter((line) => !(line === '> <run-results>' && input.runResults.length === 0))
58
56
  .join('\n');
59
57
  const body = template.replace(PLACEHOLDER, (match, token, offset, source) => {
60
- const value = fields[placeholderField(token)];
58
+ const value = token === 'coder-llm'
59
+ ? promptIdentity('coder')
60
+ : fields[placeholderField(token)];
61
61
  if (typeof value !== 'string')
62
62
  return match;
63
63
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
@@ -81,21 +81,20 @@ export const _internal = {
81
81
  };
82
82
  const runtimeSpec = {
83
83
  label: 'CODE',
84
- compat: { artifactSchema: 1, runtimeAbi: RUNTIME_ABI },
84
+ compat: { artifactSchema: 2, runtimeAbi: RUNTIME_ABI },
85
85
  snapshotOptions: snapshotCodeOptions,
86
86
  entryEvent: { type: 'START_CODE', textField: 'callerInput' },
87
- playerStates: {
87
+ roleStates: {
88
88
  runFirstPhase: {
89
- player: 'Coder',
89
+ role: 'coder',
90
90
  label: 'Coder is implementing one direct phase or committing a new intent record.',
91
91
  },
92
92
  runIrTask: {
93
- player: 'Coder',
93
+ role: 'coder',
94
94
  label: 'Coder is implementing exactly one unfinished intent-record task.',
95
95
  },
96
96
  },
97
- placeholderFields: { 'coder-llm': 'coderPlayer' },
98
- composePlayerPrompt: (input) => composePlayerPrompt(input),
97
+ composePlayerPrompt: (input, promptIdentity) => composePlayerPrompt(input, promptIdentity),
99
98
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
100
99
  controlContextFields: ['phase'],
101
100
  transitionEventFields: ['callerInput', 'answer', 'questionId'],
@@ -3,18 +3,19 @@
3
3
  //
4
4
  // Generated by slc/link.md (FSM-to-Runtime linker).
5
5
  // Source FSM: ./code.fsm.ts
6
- // Player bind: Coder→coder (default lowercased binding)
6
+ // Role metadata: Coder→coder (canonical local role)
7
7
  // Boss event: deterministic START_CODE entry; exact Boss text becomes
8
8
  // callerInput; pending Coder questions retain BOSS_REPLY
9
9
  // Adjudication: LLM judge per player state; coderOutput is carried verbatim
10
10
  // Nested call: literal review target through the shared bridge
11
- // Compat: artifact schema 1 / runtime ABI 1
11
+ // Compat: artifact schema 2 / runtime ABI 1
12
12
 
13
13
  import {
14
14
  RUNTIME_ABI,
15
15
  createXStatePlaybookRuntime,
16
16
  snapshotJsonValue,
17
17
  type PlaybookPlayerInput,
18
+ type XStatePromptIdentity,
18
19
  type XStatePlaybookRuntimeSpec,
19
20
  } from '@sublang/playbook/xstate-runtime';
20
21
  import {
@@ -76,7 +77,7 @@ export type {
76
77
 
77
78
  export type CodePlaybookOptions = CodingInput;
78
79
 
79
- const OPTION_KEYS = new Set(['coderPlayer', 'runResults']);
80
+ const OPTION_KEYS = new Set(['runResults']);
80
81
  const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
81
82
  const CONTINUATION_PREAMBLE =
82
83
  'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
@@ -108,7 +109,6 @@ function snapshotCodeOptions(value: unknown): CodePlaybookOptions {
108
109
 
109
110
  function placeholderField(token: string): string {
110
111
  if (token === '#') return 'irNumber';
111
- if (token === 'coder-llm') return 'coderPlayer';
112
112
  return token.replace(/-([A-Za-z0-9_$])/g, (_match, next: string) =>
113
113
  next.toUpperCase(),
114
114
  );
@@ -123,7 +123,10 @@ function quoteContinuation(value: string): string {
123
123
  * runtime text. The generic composer preserves the marker itself; CODE's
124
124
  * override additionally keeps a multiline value inside that quote.
125
125
  */
126
- function composePlayerPrompt(input: PlayerInput): string {
126
+ function composePlayerPrompt(
127
+ input: PlayerInput,
128
+ promptIdentity: XStatePromptIdentity,
129
+ ): string {
127
130
  const fields = input as unknown as Record<string, unknown>;
128
131
  const template = input.prompt
129
132
  .split('\n')
@@ -134,7 +137,10 @@ function composePlayerPrompt(input: PlayerInput): string {
134
137
  const body = template.replace(
135
138
  PLACEHOLDER,
136
139
  (match: string, token: string, offset: number, source: string) => {
137
- const value = fields[placeholderField(token)];
140
+ const value =
141
+ token === 'coder-llm'
142
+ ? promptIdentity('coder')
143
+ : fields[placeholderField(token)];
138
144
  if (typeof value !== 'string') return match;
139
145
  const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
140
146
  const literal = source.slice(lineStart, offset);
@@ -160,29 +166,30 @@ export const _internal = {
160
166
  VERBATIM_PAYLOAD_FIELDS,
161
167
  };
162
168
 
163
- const runtimeSpec: XStatePlaybookRuntimeSpec<CodePlaybookOptions> = {
169
+ const runtimeSpec = {
164
170
  label: 'CODE',
165
- compat: { artifactSchema: 1, runtimeAbi: RUNTIME_ABI },
171
+ compat: { artifactSchema: 2, runtimeAbi: RUNTIME_ABI },
166
172
  snapshotOptions: snapshotCodeOptions,
167
173
  entryEvent: { type: 'START_CODE', textField: 'callerInput' },
168
- playerStates: {
174
+ roleStates: {
169
175
  runFirstPhase: {
170
- player: 'Coder',
176
+ role: 'coder',
171
177
  label:
172
178
  'Coder is implementing one direct phase or committing a new intent record.',
173
179
  },
174
180
  runIrTask: {
175
- player: 'Coder',
181
+ role: 'coder',
176
182
  label: 'Coder is implementing exactly one unfinished intent-record task.',
177
183
  },
178
184
  },
179
- placeholderFields: { 'coder-llm': 'coderPlayer' },
180
- composePlayerPrompt: (input: PlaybookPlayerInput) =>
181
- composePlayerPrompt(input as PlayerInput),
185
+ composePlayerPrompt: (
186
+ input: PlaybookPlayerInput,
187
+ promptIdentity: XStatePromptIdentity,
188
+ ) => composePlayerPrompt(input as PlayerInput, promptIdentity),
182
189
  verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
183
190
  controlContextFields: ['phase'],
184
191
  transitionEventFields: ['callerInput', 'answer', 'questionId'],
185
- };
192
+ } satisfies XStatePlaybookRuntimeSpec<CodePlaybookOptions>;
186
193
 
187
194
  const createPlaybookRuntime: PlaybookRuntimeFactory<CodePlaybookOptions> =
188
195
  createXStatePlaybookRuntime(codingMachine, runtimeSpec);
@@ -1,4 +1,4 @@
1
- import { type CodePlaybookOptions, type PlaybookRuntime } from './code.playbook.js';
1
+ import { type PlaybookRuntime } from './code.playbook.js';
2
2
  export interface PlaybookSummaryPolicy {
3
3
  stateCountLabels: Readonly<Record<string, string>>;
4
4
  copyPasteGuardNames: readonly string[];
@@ -7,24 +7,17 @@ export interface PlaybookSummaryPolicy {
7
7
  copyPastes: number;
8
8
  }, rounds: number): string;
9
9
  }
10
- export interface RegistryPlayer {
11
- id: string;
12
- adapter?: string;
13
- model?: string;
14
- }
15
- export interface CreateCodeRuntimeOptions {
16
- captainOptions: unknown;
17
- players: readonly RegistryPlayer[];
18
- }
19
10
  export type CodeOptions = Readonly<Record<string, never>>;
20
11
  export interface CodePlaybookRegistryEntry {
21
12
  id: 'code';
22
13
  command: 'code';
23
14
  intent: string;
15
+ artifactSchema: 2;
24
16
  requiredRoleIds: readonly ['coder'];
17
+ concurrentRoleSets: readonly [];
25
18
  summaryPolicy: PlaybookSummaryPolicy;
26
- validateOptions(captainOptions: unknown): CodeOptions;
27
- createRuntime(options: CreateCodeRuntimeOptions): PlaybookRuntime;
19
+ validateOptions(optionSlice: unknown): CodeOptions;
20
+ createRuntime(options: CodeOptions): PlaybookRuntime;
28
21
  }
29
22
  export declare const codeStateCountLabels: {};
30
23
  export declare const codeCopyPasteGuardNames: readonly ["directCommit", "irCommit", "moreTasks", "finalTask"];
@@ -34,6 +27,5 @@ export declare function codeSavedCountsLine(counts: {
34
27
  }, rounds: number): string;
35
28
  export declare const codeSummaryPolicy: PlaybookSummaryPolicy;
36
29
  export declare function validateCodeOptions(optionSlice: unknown): CodeOptions;
37
- export declare function createCodeRuntimeOptions({ captainOptions, players, }: CreateCodeRuntimeOptions): CodePlaybookOptions;
38
30
  export declare const codePlaybookRegistryEntry: CodePlaybookRegistryEntry;
39
31
  export default codePlaybookRegistryEntry;
@@ -43,24 +43,17 @@ export function validateCodeOptions(optionSlice) {
43
43
  }
44
44
  return Object.freeze({});
45
45
  }
46
- function playerIdentity(players, id) {
47
- const player = players.find((entry) => entry.id === id);
48
- return player?.model ?? player?.adapter;
49
- }
50
- export function createCodeRuntimeOptions({ captainOptions, players, }) {
51
- validateCodeOptions(captainOptions);
52
- const coderPlayer = playerIdentity(players, 'coder');
53
- return coderPlayer === undefined ? {} : { coderPlayer };
54
- }
55
46
  export const codePlaybookRegistryEntry = {
56
47
  id: 'code',
57
48
  command: 'code',
58
49
  intent: 'implement a coding intent in reviewed, one-commit phases, using an intent record when needed',
50
+ artifactSchema: 2,
59
51
  requiredRoleIds: ['coder'],
52
+ concurrentRoleSets: [],
60
53
  summaryPolicy: codeSummaryPolicy,
61
54
  validateOptions: validateCodeOptions,
62
55
  createRuntime(options) {
63
- return createPlaybookRuntime(createCodeRuntimeOptions(options));
56
+ return createPlaybookRuntime(options);
64
57
  },
65
58
  };
66
59
  export default codePlaybookRegistryEntry;
@@ -2,7 +2,6 @@
2
2
  // SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
3
3
 
4
4
  import createPlaybookRuntime, {
5
- type CodePlaybookOptions,
6
5
  type PlaybookRuntime,
7
6
  } from './code.playbook.js';
8
7
 
@@ -15,27 +14,18 @@ export interface PlaybookSummaryPolicy {
15
14
  ): string;
16
15
  }
17
16
 
18
- export interface RegistryPlayer {
19
- id: string;
20
- adapter?: string;
21
- model?: string;
22
- }
23
-
24
- export interface CreateCodeRuntimeOptions {
25
- captainOptions: unknown;
26
- players: readonly RegistryPlayer[];
27
- }
28
-
29
17
  export type CodeOptions = Readonly<Record<string, never>>;
30
18
 
31
19
  export interface CodePlaybookRegistryEntry {
32
20
  id: 'code';
33
21
  command: 'code';
34
22
  intent: string;
23
+ artifactSchema: 2;
35
24
  requiredRoleIds: readonly ['coder'];
25
+ concurrentRoleSets: readonly [];
36
26
  summaryPolicy: PlaybookSummaryPolicy;
37
- validateOptions(captainOptions: unknown): CodeOptions;
38
- createRuntime(options: CreateCodeRuntimeOptions): PlaybookRuntime;
27
+ validateOptions(optionSlice: unknown): CodeOptions;
28
+ createRuntime(options: CodeOptions): PlaybookRuntime;
39
29
  }
40
30
 
41
31
  // REVIEW owns and labels its real review rounds. CODE's two suspended wrapper
@@ -96,33 +86,18 @@ export function validateCodeOptions(optionSlice: unknown): CodeOptions {
96
86
  return Object.freeze({});
97
87
  }
98
88
 
99
- function playerIdentity(
100
- players: readonly RegistryPlayer[],
101
- id: string,
102
- ): string | undefined {
103
- const player = players.find((entry) => entry.id === id);
104
- return player?.model ?? player?.adapter;
105
- }
106
-
107
- export function createCodeRuntimeOptions({
108
- captainOptions,
109
- players,
110
- }: CreateCodeRuntimeOptions): CodePlaybookOptions {
111
- validateCodeOptions(captainOptions);
112
- const coderPlayer = playerIdentity(players, 'coder');
113
- return coderPlayer === undefined ? {} : { coderPlayer };
114
- }
115
-
116
89
  export const codePlaybookRegistryEntry: CodePlaybookRegistryEntry = {
117
90
  id: 'code',
118
91
  command: 'code',
119
92
  intent:
120
93
  'implement a coding intent in reviewed, one-commit phases, using an intent record when needed',
94
+ artifactSchema: 2,
121
95
  requiredRoleIds: ['coder'],
96
+ concurrentRoleSets: [],
122
97
  summaryPolicy: codeSummaryPolicy,
123
98
  validateOptions: validateCodeOptions,
124
99
  createRuntime(options) {
125
- return createPlaybookRuntime(createCodeRuntimeOptions(options));
100
+ return createPlaybookRuntime(options);
126
101
  },
127
102
  };
128
103