@sublang/playbook 13.0.0 → 13.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -5
- package/docs/cli.md +28 -9
- package/docs/configuration.md +21 -5
- package/docs/embedding.md +1 -1
- package/package.json +41 -3
- package/reference/sdlc/branch.md +37 -0
- package/reference/sdlc/branch.playbook/branch.fsm.d.ts +169 -0
- package/reference/sdlc/branch.playbook/branch.fsm.js +325 -0
- package/reference/sdlc/branch.playbook/branch.fsm.ts +458 -0
- package/reference/sdlc/branch.playbook/branch.gears.md +44 -0
- package/reference/sdlc/branch.playbook/branch.playbook.d.ts +20 -0
- package/reference/sdlc/branch.playbook/branch.playbook.js +121 -0
- package/reference/sdlc/branch.playbook/branch.playbook.ts +217 -0
- package/reference/sdlc/branch.playbook/branch.registry.d.ts +40 -0
- package/reference/sdlc/branch.playbook/branch.registry.js +63 -0
- package/reference/sdlc/branch.playbook/branch.registry.ts +121 -0
- package/reference/sdlc/code.md +7 -0
- package/reference/sdlc/code.playbook/code.fsm.js +5 -5
- package/reference/sdlc/code.playbook/code.fsm.ts +5 -5
- package/reference/sdlc/code.playbook/code.gears.md +5 -5
- package/reference/sdlc/code.playbook/code.playbook.d.ts +1 -1
- package/reference/sdlc/code.playbook/code.playbook.js +6 -16
- package/reference/sdlc/code.playbook/code.playbook.ts +7 -17
- package/reference/sdlc/code.playbook/playbook-captain.js +4 -1
- package/reference/sdlc/code.playbook/playbook-captain.ts +4 -1
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +16 -2
- package/reference/sdlc/decide.md +2 -0
- package/reference/sdlc/decide.playbook/decide.fsm.js +1 -1
- package/reference/sdlc/decide.playbook/decide.fsm.ts +1 -1
- package/reference/sdlc/decide.playbook/decide.gears.md +2 -2
- package/reference/sdlc/decide.playbook/decide.playbook.d.ts +1 -2
- package/reference/sdlc/decide.playbook/decide.playbook.js +8 -22
- package/reference/sdlc/decide.playbook/decide.playbook.ts +8 -25
- package/reference/sdlc/dev.md +45 -4
- package/reference/sdlc/dev.playbook/dev.fsm.d.ts +100 -9
- package/reference/sdlc/dev.playbook/dev.fsm.js +245 -28
- package/reference/sdlc/dev.playbook/dev.fsm.ts +381 -37
- package/reference/sdlc/dev.playbook/dev.gears.md +60 -22
- package/reference/sdlc/dev.playbook/dev.playbook.d.ts +1 -1
- package/reference/sdlc/dev.playbook/dev.playbook.js +20 -18
- package/reference/sdlc/dev.playbook/dev.playbook.ts +21 -21
- package/reference/sdlc/dev.playbook/dev.registry.d.ts +1 -1
- package/reference/sdlc/dev.playbook/dev.registry.js +7 -2
- package/reference/sdlc/dev.playbook/dev.registry.ts +7 -2
- package/reference/sdlc/pr.md +108 -0
- package/reference/sdlc/pr.playbook/pr.fsm.d.ts +352 -0
- package/reference/sdlc/pr.playbook/pr.fsm.js +902 -0
- package/reference/sdlc/pr.playbook/pr.fsm.ts +1251 -0
- package/reference/sdlc/pr.playbook/pr.gears.md +155 -0
- package/reference/sdlc/pr.playbook/pr.playbook.d.ts +27 -0
- package/reference/sdlc/pr.playbook/pr.playbook.js +133 -0
- package/reference/sdlc/pr.playbook/pr.playbook.ts +235 -0
- package/reference/sdlc/pr.playbook/pr.registry.d.ts +40 -0
- package/reference/sdlc/pr.playbook/pr.registry.js +68 -0
- package/reference/sdlc/pr.playbook/pr.registry.ts +124 -0
- package/reference/sdlc/review.md +18 -0
- package/reference/sdlc/review.playbook/review.fsm.js +8 -8
- package/reference/sdlc/review.playbook/review.fsm.ts +8 -8
- package/reference/sdlc/review.playbook/review.gears.md +8 -8
- package/reference/sdlc/review.playbook/review.playbook.d.ts +1 -1
- package/reference/sdlc/review.playbook/review.playbook.js +5 -15
- package/reference/sdlc/review.playbook/review.playbook.ts +6 -16
- package/slc/link.md +12 -24
- package/src/runtime.d.ts +2 -0
- package/src/runtime.ts +2 -0
- package/src/xstate-playbook-runtime.d.ts +4 -2
- package/src/xstate-playbook-runtime.js +18 -13
- package/src/xstate-playbook-runtime.ts +26 -14
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
|
|
4
|
+
import createPlaybookRuntime, {
|
|
5
|
+
type PrPlaybookHostCapabilities,
|
|
6
|
+
type PlaybookRuntime,
|
|
7
|
+
} from './pr.playbook.js';
|
|
8
|
+
|
|
9
|
+
export interface PlaybookSummaryPolicy {
|
|
10
|
+
stateCountLabels: Readonly<Record<string, string>>;
|
|
11
|
+
copyPasteGuardNames: readonly string[];
|
|
12
|
+
savedCountsLine(
|
|
13
|
+
counts: { interruptions: number; copyPastes: number },
|
|
14
|
+
rounds: number,
|
|
15
|
+
): string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type PrOptions = Readonly<Record<string, never>>;
|
|
19
|
+
|
|
20
|
+
export interface PrPlaybookRegistryEntry {
|
|
21
|
+
id: 'pr';
|
|
22
|
+
command: 'pr';
|
|
23
|
+
intent: string;
|
|
24
|
+
artifactSchema: 3;
|
|
25
|
+
runtimeProfile: {
|
|
26
|
+
readonly kind: 'shared-factory';
|
|
27
|
+
readonly compat: {
|
|
28
|
+
readonly artifactSchema: 3;
|
|
29
|
+
readonly runtimeAbi: number;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
requiredRoleIds: readonly ['coder'];
|
|
33
|
+
concurrentRoleSets: readonly [];
|
|
34
|
+
summaryPolicy: PlaybookSummaryPolicy;
|
|
35
|
+
validateOptions(optionSlice: unknown): PrOptions;
|
|
36
|
+
createRuntime(
|
|
37
|
+
options: PrOptions,
|
|
38
|
+
hostCapabilities: PrPlaybookHostCapabilities,
|
|
39
|
+
): PlaybookRuntime;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// CODE owns and labels the rounds of the one fix it may make. PR's suspended
|
|
43
|
+
// call state only delegates to that child and must not double-count its
|
|
44
|
+
// rounds; its script states run no agent at all. Only the Coder publication
|
|
45
|
+
// round is PR's own.
|
|
46
|
+
export const prStateCountLabels = {
|
|
47
|
+
openPullRequest: 'publication round',
|
|
48
|
+
} as const;
|
|
49
|
+
|
|
50
|
+
export const prCopyPasteGuardNames = ['opened'] as const;
|
|
51
|
+
|
|
52
|
+
function countNoun(
|
|
53
|
+
count: number,
|
|
54
|
+
singular: string,
|
|
55
|
+
plural = `${singular}s`,
|
|
56
|
+
): string {
|
|
57
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function prSavedCountsLine(
|
|
61
|
+
counts: { interruptions: number; copyPastes: number },
|
|
62
|
+
rounds: number,
|
|
63
|
+
): string {
|
|
64
|
+
return [
|
|
65
|
+
'Saved you',
|
|
66
|
+
countNoun(counts.interruptions, 'interruption'),
|
|
67
|
+
'and',
|
|
68
|
+
countNoun(counts.copyPastes, 'copy-paste'),
|
|
69
|
+
'across',
|
|
70
|
+
countNoun(rounds, 'round'),
|
|
71
|
+
'of delivery.',
|
|
72
|
+
].join(' ');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const prSummaryPolicy: PlaybookSummaryPolicy = {
|
|
76
|
+
stateCountLabels: prStateCountLabels,
|
|
77
|
+
copyPasteGuardNames: prCopyPasteGuardNames,
|
|
78
|
+
savedCountsLine: prSavedCountsLine,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export function validatePrOptions(optionSlice: unknown): PrOptions {
|
|
82
|
+
if (optionSlice === undefined) return Object.freeze({});
|
|
83
|
+
if (
|
|
84
|
+
optionSlice === null ||
|
|
85
|
+
typeof optionSlice !== 'object' ||
|
|
86
|
+
Array.isArray(optionSlice)
|
|
87
|
+
) {
|
|
88
|
+
throw new Error('captain.options.playbooks.pr.options must be an object');
|
|
89
|
+
}
|
|
90
|
+
const keys = Object.keys(optionSlice);
|
|
91
|
+
if (keys.length > 0) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
`Unknown config field captain.options.playbooks.pr.options.${keys[0]}`,
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return Object.freeze({});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const prPlaybookRegistryEntry: PrPlaybookRegistryEntry = {
|
|
100
|
+
id: 'pr',
|
|
101
|
+
command: 'pr',
|
|
102
|
+
intent:
|
|
103
|
+
'publish a reviewed branch as a pull request, wait for its checks, fix red checks once through code, and merge it',
|
|
104
|
+
artifactSchema: 3,
|
|
105
|
+
runtimeProfile: Object.freeze({
|
|
106
|
+
kind: 'shared-factory',
|
|
107
|
+
compat: createPlaybookRuntime.compat,
|
|
108
|
+
}),
|
|
109
|
+
requiredRoleIds: ['coder'],
|
|
110
|
+
concurrentRoleSets: [],
|
|
111
|
+
summaryPolicy: prSummaryPolicy,
|
|
112
|
+
validateOptions: validatePrOptions,
|
|
113
|
+
createRuntime(options, hostCapabilities) {
|
|
114
|
+
// The five script states run `gh` and `git` in the repository the host
|
|
115
|
+
// governs: the linked `cwd` option is composed here from the host's
|
|
116
|
+
// authority working directory (DR-040 §2), never from configuration.
|
|
117
|
+
return createPlaybookRuntime({
|
|
118
|
+
configuredOptions: { ...options, cwd: hostCapabilities.authority.cwd },
|
|
119
|
+
hostCapabilities,
|
|
120
|
+
});
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export default prPlaybookRegistryEntry;
|
package/reference/sdlc/review.md
CHANGED
|
@@ -27,6 +27,10 @@ Keep to the original intent and follow what it asks.
|
|
|
27
27
|
When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.
|
|
28
28
|
```
|
|
29
29
|
|
|
30
|
+
Captain shall append the caller’s input in quotes (`>`):
|
|
31
|
+
|
|
32
|
+
> Original request: <caller-input>
|
|
33
|
+
|
|
30
34
|
After every review-fix commit, Captain shall give Reviewer the following instruction:
|
|
31
35
|
|
|
32
36
|
```markdown
|
|
@@ -35,6 +39,12 @@ Keep to the original intent and follow what it asks.
|
|
|
35
39
|
Read the latest review-fix commit's message and see Coder's feedback below.
|
|
36
40
|
```
|
|
37
41
|
|
|
42
|
+
Captain shall append the round’s context in quotes (`>`):
|
|
43
|
+
|
|
44
|
+
> Original request: <caller-input>
|
|
45
|
+
> Latest commit: <latest-commit>
|
|
46
|
+
> Coder output: <coder-output>
|
|
47
|
+
|
|
38
48
|
When Coder rejects every finding and makes no commit, Captain shall give Reviewer the following instruction:
|
|
39
49
|
|
|
40
50
|
```markdown
|
|
@@ -42,6 +52,11 @@ No new commit was made because Coder rejected every finding.
|
|
|
42
52
|
See Coder's feedback below.
|
|
43
53
|
```
|
|
44
54
|
|
|
55
|
+
Captain shall append the round’s context in quotes (`>`):
|
|
56
|
+
|
|
57
|
+
> Original request: <caller-input>
|
|
58
|
+
> Coder output: <coder-output>
|
|
59
|
+
|
|
45
60
|
At the start of *every* review round, Captain shall relay to Reviewer the original intent, the review scope and context, the exact repository revision being evaluated, any Coder feedback from the preceding round, and any relevant run results, in quotes (`>`) after the instruction.
|
|
46
61
|
|
|
47
62
|
At the start of *every* review round, Captain shall append the following instruction to the end of the prompt:
|
|
@@ -70,6 +85,9 @@ No review transition shall depend on numbering or any fixed presentation format
|
|
|
70
85
|
|
|
71
86
|
When Reviewer raises or keeps any finding, Captain shall relay the original intent, the review scope and context, the exact repository revision being evaluated, the Reviewer's findings, and any relevant run results to Coder, in quotes (`>`), along with the following prompt:
|
|
72
87
|
|
|
88
|
+
> Original request: <caller-input>
|
|
89
|
+
> Reviewer findings: <reviewer-output>
|
|
90
|
+
|
|
73
91
|
```markdown
|
|
74
92
|
For each review item, accept or reject it.
|
|
75
93
|
Before deciding, understand the full picture and think systematically about the underlying design.
|
|
@@ -27,7 +27,7 @@ const INITIAL_REVIEW_PROMPT = [
|
|
|
27
27
|
'Keep to the original intent and follow what it asks.',
|
|
28
28
|
'When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.',
|
|
29
29
|
'',
|
|
30
|
-
'> <caller-input>',
|
|
30
|
+
'> Original request: <caller-input>',
|
|
31
31
|
'',
|
|
32
32
|
SHARED_REVIEW_INSTRUCTION,
|
|
33
33
|
].join('\n');
|
|
@@ -36,9 +36,9 @@ const POST_COMMIT_REVIEW_PROMPT = [
|
|
|
36
36
|
'Keep to the original intent and follow what it asks.',
|
|
37
37
|
"Read the latest review-fix commit's message and see Coder's feedback below.",
|
|
38
38
|
'',
|
|
39
|
-
'> <caller-input>',
|
|
40
|
-
'> <latest-commit>',
|
|
41
|
-
'> <coder-output>',
|
|
39
|
+
'> Original request: <caller-input>',
|
|
40
|
+
'> Latest commit: <latest-commit>',
|
|
41
|
+
'> Coder output: <coder-output>',
|
|
42
42
|
'',
|
|
43
43
|
SHARED_REVIEW_INSTRUCTION,
|
|
44
44
|
].join('\n');
|
|
@@ -46,14 +46,14 @@ const REBUTTAL_REVIEW_PROMPT = [
|
|
|
46
46
|
'No new commit was made because Coder rejected every finding.',
|
|
47
47
|
"See Coder's feedback below.",
|
|
48
48
|
'',
|
|
49
|
-
'> <caller-input>',
|
|
50
|
-
'> <coder-output>',
|
|
49
|
+
'> Original request: <caller-input>',
|
|
50
|
+
'> Coder output: <coder-output>',
|
|
51
51
|
'',
|
|
52
52
|
SHARED_REVIEW_INSTRUCTION,
|
|
53
53
|
].join('\n');
|
|
54
54
|
const CODER_DISPOSITION_PROMPT = [
|
|
55
|
-
'> <caller-input>',
|
|
56
|
-
'> <reviewer-output>',
|
|
55
|
+
'> Original request: <caller-input>',
|
|
56
|
+
'> Reviewer findings: <reviewer-output>',
|
|
57
57
|
'',
|
|
58
58
|
'For each review item, accept or reject it.',
|
|
59
59
|
'Before deciding, understand the full picture and think systematically about the underlying design.',
|
|
@@ -113,7 +113,7 @@ const INITIAL_REVIEW_PROMPT = [
|
|
|
113
113
|
'Keep to the original intent and follow what it asks.',
|
|
114
114
|
'When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.',
|
|
115
115
|
'',
|
|
116
|
-
'> <caller-input>',
|
|
116
|
+
'> Original request: <caller-input>',
|
|
117
117
|
'',
|
|
118
118
|
SHARED_REVIEW_INSTRUCTION,
|
|
119
119
|
].join('\n');
|
|
@@ -123,9 +123,9 @@ const POST_COMMIT_REVIEW_PROMPT = [
|
|
|
123
123
|
'Keep to the original intent and follow what it asks.',
|
|
124
124
|
"Read the latest review-fix commit's message and see Coder's feedback below.",
|
|
125
125
|
'',
|
|
126
|
-
'> <caller-input>',
|
|
127
|
-
'> <latest-commit>',
|
|
128
|
-
'> <coder-output>',
|
|
126
|
+
'> Original request: <caller-input>',
|
|
127
|
+
'> Latest commit: <latest-commit>',
|
|
128
|
+
'> Coder output: <coder-output>',
|
|
129
129
|
'',
|
|
130
130
|
SHARED_REVIEW_INSTRUCTION,
|
|
131
131
|
].join('\n');
|
|
@@ -134,15 +134,15 @@ const REBUTTAL_REVIEW_PROMPT = [
|
|
|
134
134
|
'No new commit was made because Coder rejected every finding.',
|
|
135
135
|
"See Coder's feedback below.",
|
|
136
136
|
'',
|
|
137
|
-
'> <caller-input>',
|
|
138
|
-
'> <coder-output>',
|
|
137
|
+
'> Original request: <caller-input>',
|
|
138
|
+
'> Coder output: <coder-output>',
|
|
139
139
|
'',
|
|
140
140
|
SHARED_REVIEW_INSTRUCTION,
|
|
141
141
|
].join('\n');
|
|
142
142
|
|
|
143
143
|
const CODER_DISPOSITION_PROMPT = [
|
|
144
|
-
'> <caller-input>',
|
|
145
|
-
'> <reviewer-output>',
|
|
144
|
+
'> Original request: <caller-input>',
|
|
145
|
+
'> Reviewer findings: <reviewer-output>',
|
|
146
146
|
'',
|
|
147
147
|
'For each review item, accept or reject it.',
|
|
148
148
|
'Before deciding, understand the full picture and think systematically about the underlying design.',
|
|
@@ -23,7 +23,7 @@ When the caller starts a review, Captain shall relay the complete caller input t
|
|
|
23
23
|
> Keep to the original intent and follow what it asks.
|
|
24
24
|
> When the scope names commits, read each commit message for its context and rationale; otherwise use repository history and commit messages wherever they help establish that context.
|
|
25
25
|
>
|
|
26
|
-
> > <caller-input>
|
|
26
|
+
> > Original request: <caller-input>
|
|
27
27
|
>
|
|
28
28
|
> Understand the full picture and think systematically about the underlying design.
|
|
29
29
|
> Continue to identify issues or improvements, if any, without duplication.
|
|
@@ -50,8 +50,8 @@ Results:
|
|
|
50
50
|
|
|
51
51
|
When Reviewer raises or keeps any finding, Captain shall relay the caller input and Reviewer's findings to Coder with the disposition prompt:
|
|
52
52
|
|
|
53
|
-
> > <caller-input>
|
|
54
|
-
> > <reviewer-output>
|
|
53
|
+
> > Original request: <caller-input>
|
|
54
|
+
> > Reviewer findings: <reviewer-output>
|
|
55
55
|
>
|
|
56
56
|
> For each review item, accept or reject it.
|
|
57
57
|
> Before deciding, understand the full picture and think systematically about the underlying design.
|
|
@@ -82,9 +82,9 @@ When Coder makes a review-fix commit, Captain shall relay the caller input, the
|
|
|
82
82
|
> Keep to the original intent and follow what it asks.
|
|
83
83
|
> Read the latest review-fix commit's message and see Coder's feedback below.
|
|
84
84
|
>
|
|
85
|
-
> > <caller-input>
|
|
86
|
-
> > <latest-commit>
|
|
87
|
-
> > <coder-output>
|
|
85
|
+
> > Original request: <caller-input>
|
|
86
|
+
> > Latest commit: <latest-commit>
|
|
87
|
+
> > Coder output: <coder-output>
|
|
88
88
|
>
|
|
89
89
|
> Understand the full picture and think systematically about the underlying design.
|
|
90
90
|
> Continue to identify issues or improvements, if any, without duplication.
|
|
@@ -114,8 +114,8 @@ When Coder rejects every finding and makes no commit, Captain shall relay the ca
|
|
|
114
114
|
> No new commit was made because Coder rejected every finding.
|
|
115
115
|
> See Coder's feedback below.
|
|
116
116
|
>
|
|
117
|
-
> > <caller-input>
|
|
118
|
-
> > <coder-output>
|
|
117
|
+
> > Original request: <caller-input>
|
|
118
|
+
> > Coder output: <coder-output>
|
|
119
119
|
>
|
|
120
120
|
> Understand the full picture and think systematically about the underlying design.
|
|
121
121
|
> Continue to identify issues or improvements, if any, without duplication.
|
|
@@ -6,7 +6,7 @@ export type { CaptainCallOptions, CaptainResult, JsonValue, NormalizedError, Pla
|
|
|
6
6
|
export type ReviewPlaybookOptions = ReviewInput;
|
|
7
7
|
export type ReviewPlaybookHostCapabilities = PlaybookHostConstructionCapabilities & XStatePlaybookRuntimeConstruction<ReviewPlaybookOptions, object>['hostCapabilities'];
|
|
8
8
|
/** Keep every line of a relayed runtime value inside its authored quote. */
|
|
9
|
-
declare function composePlayerPrompt(input: PlayerInput, promptIdentity: XStatePromptIdentity): string;
|
|
9
|
+
declare function composePlayerPrompt(input: PlayerInput, promptIdentity: XStatePromptIdentity, resuming?: boolean): string;
|
|
10
10
|
export declare const _internal: {
|
|
11
11
|
composePlayerPrompt: typeof composePlayerPrompt;
|
|
12
12
|
VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string>;
|
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
// carried verbatim; latestCommit is receipt-owned effect
|
|
11
11
|
// evidence, never taken from either player's prose
|
|
12
12
|
// Compat: artifact schema 3 / runtime ABI 1
|
|
13
|
-
import { createXStatePlaybookRuntime, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
|
|
13
|
+
import { createXStatePlaybookRuntime, composePlayerContinuation, snapshotJsonValue, } from '@sublang/playbook/xstate-runtime';
|
|
14
14
|
import { reviewMachine, } from './review.fsm.js';
|
|
15
15
|
const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
|
|
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
16
|
const VERBATIM_PAYLOAD_FIELDS = new Set([
|
|
18
17
|
'reviewerOutput',
|
|
19
18
|
'coderOutput',
|
|
@@ -41,7 +40,7 @@ function quotedContinuation(value) {
|
|
|
41
40
|
return value.replaceAll('\n', '\n> ');
|
|
42
41
|
}
|
|
43
42
|
/** Keep every line of a relayed runtime value inside its authored quote. */
|
|
44
|
-
function composePlayerPrompt(input, promptIdentity) {
|
|
43
|
+
function composePlayerPrompt(input, promptIdentity, resuming = false) {
|
|
45
44
|
const fields = input;
|
|
46
45
|
const body = input.prompt.replace(PLACEHOLDER, (match, token, offset, source) => {
|
|
47
46
|
const value = token === 'coder-llm'
|
|
@@ -52,20 +51,11 @@ function composePlayerPrompt(input, promptIdentity) {
|
|
|
52
51
|
if (typeof value !== 'string')
|
|
53
52
|
return match;
|
|
54
53
|
const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
|
|
55
|
-
return source.slice(lineStart, offset)
|
|
54
|
+
return source.slice(lineStart, offset).startsWith('> ')
|
|
56
55
|
? quotedContinuation(value)
|
|
57
56
|
: value;
|
|
58
57
|
});
|
|
59
|
-
|
|
60
|
-
input.bossReply === undefined) {
|
|
61
|
-
return body;
|
|
62
|
-
}
|
|
63
|
-
return [
|
|
64
|
-
CONTINUATION_PREAMBLE,
|
|
65
|
-
`Boss question:\n${input.pendingBossQuestion.question}`,
|
|
66
|
-
`Boss reply:\n${input.bossReply}`,
|
|
67
|
-
body,
|
|
68
|
-
].join('\n\n');
|
|
58
|
+
return composePlayerContinuation(input, body, resuming);
|
|
69
59
|
}
|
|
70
60
|
export const _internal = {
|
|
71
61
|
composePlayerPrompt,
|
|
@@ -175,7 +165,7 @@ const runtimeSpec = {
|
|
|
175
165
|
},
|
|
176
166
|
},
|
|
177
167
|
},
|
|
178
|
-
composePlayerPrompt: (input, promptIdentity) => composePlayerPrompt(input, promptIdentity),
|
|
168
|
+
composePlayerPrompt: (input, promptIdentity, resuming) => composePlayerPrompt(input, promptIdentity, resuming),
|
|
179
169
|
verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
|
|
180
170
|
controlContextFields: [],
|
|
181
171
|
unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import {
|
|
15
15
|
createXStatePlaybookRuntime,
|
|
16
|
+
composePlayerContinuation,
|
|
16
17
|
snapshotJsonValue,
|
|
17
18
|
type PlaybookPlayerInput,
|
|
18
19
|
type XStatePlaybookRuntimeFactory,
|
|
@@ -84,8 +85,6 @@ export type ReviewPlaybookHostCapabilities =
|
|
|
84
85
|
XStatePlaybookRuntimeConstruction<ReviewPlaybookOptions, object>['hostCapabilities'];
|
|
85
86
|
|
|
86
87
|
const PLACEHOLDER = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
|
|
87
|
-
const CONTINUATION_PREAMBLE =
|
|
88
|
-
'You previously paused this task to ask Boss a question; Boss has now replied. Continue the same task using the reply below.';
|
|
89
88
|
|
|
90
89
|
const VERBATIM_PAYLOAD_FIELDS: ReadonlySet<string> = new Set([
|
|
91
90
|
'reviewerOutput',
|
|
@@ -124,6 +123,7 @@ function quotedContinuation(value: string): string {
|
|
|
124
123
|
function composePlayerPrompt(
|
|
125
124
|
input: PlayerInput,
|
|
126
125
|
promptIdentity: XStatePromptIdentity,
|
|
126
|
+
resuming = false,
|
|
127
127
|
): string {
|
|
128
128
|
const fields = input as unknown as Record<string, unknown>;
|
|
129
129
|
const body = input.prompt.replace(
|
|
@@ -137,23 +137,12 @@ function composePlayerPrompt(
|
|
|
137
137
|
: fields[placeholderField(token)];
|
|
138
138
|
if (typeof value !== 'string') return match;
|
|
139
139
|
const lineStart = source.lastIndexOf('\n', offset - 1) + 1;
|
|
140
|
-
return source.slice(lineStart, offset)
|
|
140
|
+
return source.slice(lineStart, offset).startsWith('> ')
|
|
141
141
|
? quotedContinuation(value)
|
|
142
142
|
: value;
|
|
143
143
|
},
|
|
144
144
|
);
|
|
145
|
-
|
|
146
|
-
input.pendingBossQuestion === undefined ||
|
|
147
|
-
input.bossReply === undefined
|
|
148
|
-
) {
|
|
149
|
-
return body;
|
|
150
|
-
}
|
|
151
|
-
return [
|
|
152
|
-
CONTINUATION_PREAMBLE,
|
|
153
|
-
`Boss question:\n${input.pendingBossQuestion.question}`,
|
|
154
|
-
`Boss reply:\n${input.bossReply}`,
|
|
155
|
-
body,
|
|
156
|
-
].join('\n\n');
|
|
145
|
+
return composePlayerContinuation(input, body, resuming);
|
|
157
146
|
}
|
|
158
147
|
|
|
159
148
|
export const _internal = {
|
|
@@ -271,7 +260,8 @@ const runtimeSpec = {
|
|
|
271
260
|
composePlayerPrompt: (
|
|
272
261
|
input: PlaybookPlayerInput,
|
|
273
262
|
promptIdentity: XStatePromptIdentity,
|
|
274
|
-
|
|
263
|
+
resuming?: boolean,
|
|
264
|
+
) => composePlayerPrompt(input as PlayerInput, promptIdentity, resuming),
|
|
275
265
|
verbatimPayloadFields: VERBATIM_PAYLOAD_FIELDS,
|
|
276
266
|
controlContextFields: [],
|
|
277
267
|
unfinishedFinalStateIds: UNFINISHED_FINAL_STATE_IDS,
|
package/slc/link.md
CHANGED
|
@@ -489,6 +489,7 @@ interface PlaybookPorts {
|
|
|
489
489
|
|
|
490
490
|
interface PlayerCallOptions {
|
|
491
491
|
resume: string | false;
|
|
492
|
+
freshPrompt?: string;
|
|
492
493
|
}
|
|
493
494
|
|
|
494
495
|
interface CaptainCallOptions {
|
|
@@ -865,30 +866,17 @@ Those blocks are outside the domain prompt body.
|
|
|
865
866
|
The composer shall not inject a player-visible Boss-question instruction.
|
|
866
867
|
Boss-question detection is adjudicator-facing: it comes from the state's `needsBossReply` result description, not from extra prompt text.
|
|
867
868
|
|
|
868
|
-
When `PlayerInput` carries both `pendingBossQuestion` and `bossReply`, the
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
<bossReply>
|
|
880
|
-
|
|
881
|
-
```
|
|
882
|
-
|
|
883
|
-
The continuation preamble is framework text supplied by the runtime.
|
|
884
|
-
It is not part of the GEARS blockquote and shall not appear in `invoke.input.prompt`.
|
|
885
|
-
The composer shall retain the blank line after the Boss reply before the next
|
|
886
|
-
structured block or domain prompt, producing exactly two newline characters at
|
|
887
|
-
that boundary.
|
|
888
|
-
When implementing the prefix as an array joined with `"\n"`, the array needs
|
|
889
|
-
two trailing empty strings after `bossReply`; one trailing empty string emits
|
|
890
|
-
only one newline and is nonconformant. Equivalently, append `"\n\n"` exactly
|
|
891
|
-
once before the following block or domain body.
|
|
869
|
+
When `PlayerInput` carries both `pendingBossQuestion` and `bossReply`, the composer receives an optional third `resuming` boolean after `promptIdentity`; absent means fresh.
|
|
870
|
+
The shared `composePlayerContinuation(input, body, resuming)` helper prefixes the verbatim Boss reply and, only for a fresh conversation, the pending question labeled `Your previous question:`.
|
|
871
|
+
A resumed call omits that question; a fresh call includes it before `Boss reply:`.
|
|
872
|
+
Both retain the authored task body exactly once, with two newlines between blocks.
|
|
873
|
+
These framework blocks never enter `invoke.input.prompt` or persisted FSM context.
|
|
874
|
+
|
|
875
|
+
The runtime chooses the compact prompt only after selecting a conversation token and carries the complete prompt as optional `PlayerCallOptions.freshPrompt`.
|
|
876
|
+
A host starting fresh after a definite pre-execution token rejection uses `freshPrompt`, when supplied, instead of the compact prompt.
|
|
877
|
+
Without that option, the given prompt remains complete.
|
|
878
|
+
Traces and host observations record the respective prompts actually sent.
|
|
879
|
+
Old composers may ignore the optional argument and retain their full prompt.
|
|
892
880
|
|
|
893
881
|
## Captain prompt composition
|
|
894
882
|
|
package/src/runtime.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export interface PlayerResult {
|
|
|
6
6
|
}
|
|
7
7
|
export interface PlayerCallOptions {
|
|
8
8
|
resume: string | false;
|
|
9
|
+
/** Complete task and clarification context if the host must start fresh. */
|
|
10
|
+
freshPrompt?: string;
|
|
9
11
|
}
|
|
10
12
|
export interface PlayerSessionStore {
|
|
11
13
|
select(roleId: string): string | false;
|
package/src/runtime.ts
CHANGED
|
@@ -17,6 +17,8 @@ export interface PlayerResult {
|
|
|
17
17
|
|
|
18
18
|
export interface PlayerCallOptions {
|
|
19
19
|
resume: string | false;
|
|
20
|
+
/** Complete task and clarification context if the host must start fresh. */
|
|
21
|
+
freshPrompt?: string;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
// DR-032: a composing host may supply one frame-local role view of the
|
|
@@ -329,7 +329,7 @@ interface XStatePlaybookRuntimeSpecBase<TOptions> {
|
|
|
329
329
|
/** Complete FSM-derived Boss-facing metadata for every `player` state. */
|
|
330
330
|
roleStates?: Readonly<Record<string, XStateRoleStateStatus>>;
|
|
331
331
|
/** Compose the player prompt. Default: continuation blocks + `<field>` placeholder substitution. */
|
|
332
|
-
composePlayerPrompt?: (input: PlaybookPlayerInput, promptIdentity: XStatePromptIdentity) => string;
|
|
332
|
+
composePlayerPrompt?: (input: PlaybookPlayerInput, promptIdentity: XStatePromptIdentity, resuming?: boolean) => string;
|
|
333
333
|
/** Compose the direct-Captain prompt. Default: continuation blocks + placeholder substitution with deterministic JSON rendering. */
|
|
334
334
|
composeCaptainPrompt?: (input: PlaybookCaptainInput) => string;
|
|
335
335
|
/** Linker-known exceptions to the default kebab-token → camel-field mapping. */
|
|
@@ -388,6 +388,8 @@ export declare function normalizeErrorFull(err: unknown): {
|
|
|
388
388
|
} | undefined;
|
|
389
389
|
/** Read the FSM context's single pending Boss question, when well-formed. */
|
|
390
390
|
export declare function pendingBossQuestionFromContext(context: Record<string, unknown>): PlaybookPendingBossQuestionContext | undefined;
|
|
391
|
+
/** Add clarification context without repeating the question in a live conversation. */
|
|
392
|
+
export declare function composePlayerContinuation(input: Pick<PlaybookPlayerInput, 'pendingBossQuestion' | 'bossReply'>, body: string, resuming?: boolean): string;
|
|
391
393
|
/**
|
|
392
394
|
* Default player-prompt composer (slc/link.md §Player prompt composition).
|
|
393
395
|
* One callback-based pass substitutes each `<fieldName>` placeholder whose
|
|
@@ -395,7 +397,7 @@ export declare function pendingBossQuestionFromContext(context: Record<string, u
|
|
|
395
397
|
* placeholder-looking text inside a value is never re-substituted. The
|
|
396
398
|
* continuation preamble and Q/A blocks precede the domain body on resume.
|
|
397
399
|
*/
|
|
398
|
-
export declare function defaultComposePlayerPrompt(input: PlaybookPlayerInput, placeholderFields?: Readonly<Record<string, string
|
|
400
|
+
export declare function defaultComposePlayerPrompt(input: PlaybookPlayerInput, placeholderFields?: Readonly<Record<string, string>>, resuming?: boolean): string;
|
|
399
401
|
/**
|
|
400
402
|
* Default direct-Captain prompt composer (slc/link.md §Captain prompt
|
|
401
403
|
* composition). Placeholder substitution is presence-based: string fields
|
|
@@ -402,17 +402,21 @@ export function pendingBossQuestionFromContext(context) {
|
|
|
402
402
|
// ---------------------------------------------------------------------------
|
|
403
403
|
// Generic strategy defaults.
|
|
404
404
|
// ---------------------------------------------------------------------------
|
|
405
|
-
const CONTINUATION_PREAMBLE = '
|
|
406
|
-
function continuationBlocks(input) {
|
|
405
|
+
const CONTINUATION_PREAMBLE = 'Continue the same task using Boss’s reply below.';
|
|
406
|
+
function continuationBlocks(input, resuming = false) {
|
|
407
407
|
if (input.pendingBossQuestion === undefined || input.bossReply === undefined) {
|
|
408
408
|
return [];
|
|
409
409
|
}
|
|
410
410
|
return [
|
|
411
411
|
CONTINUATION_PREAMBLE,
|
|
412
|
-
`
|
|
412
|
+
...(resuming ? [] : [`Your previous question:\n${input.pendingBossQuestion.question}`]),
|
|
413
413
|
`Boss reply:\n${input.bossReply}`,
|
|
414
414
|
];
|
|
415
415
|
}
|
|
416
|
+
/** Add clarification context without repeating the question in a live conversation. */
|
|
417
|
+
export function composePlayerContinuation(input, body, resuming = false) {
|
|
418
|
+
return [...continuationBlocks(input, resuming), body].join('\n\n');
|
|
419
|
+
}
|
|
416
420
|
const PLACEHOLDER_PATTERN = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
|
|
417
421
|
function placeholderFieldName(token, fields) {
|
|
418
422
|
const explicit = fields[token];
|
|
@@ -429,15 +433,13 @@ function placeholderFieldName(token, fields) {
|
|
|
429
433
|
* placeholder-looking text inside a value is never re-substituted. The
|
|
430
434
|
* continuation preamble and Q/A blocks precede the domain body on resume.
|
|
431
435
|
*/
|
|
432
|
-
export function defaultComposePlayerPrompt(input, placeholderFields = {}) {
|
|
433
|
-
const blocks = continuationBlocks(input);
|
|
436
|
+
export function defaultComposePlayerPrompt(input, placeholderFields = {}, resuming = false) {
|
|
434
437
|
const fields = input;
|
|
435
438
|
const body = input.prompt.replace(PLACEHOLDER_PATTERN, (match, token) => {
|
|
436
439
|
const value = fields[placeholderFieldName(token, placeholderFields)];
|
|
437
440
|
return typeof value === 'string' ? value : match;
|
|
438
441
|
});
|
|
439
|
-
|
|
440
|
-
return blocks.join('\n\n');
|
|
442
|
+
return composePlayerContinuation(input, body, resuming);
|
|
441
443
|
}
|
|
442
444
|
function sortJson(value) {
|
|
443
445
|
if (Array.isArray(value))
|
|
@@ -1901,7 +1903,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
1901
1903
|
}
|
|
1902
1904
|
}
|
|
1903
1905
|
const composePlayerPrompt = spec.composePlayerPrompt ??
|
|
1904
|
-
((input) => defaultComposePlayerPrompt(input, spec.placeholderFields));
|
|
1906
|
+
((input, _identity, resuming = false) => defaultComposePlayerPrompt(input, spec.placeholderFields, resuming));
|
|
1905
1907
|
const composeCaptainPrompt = spec.composeCaptainPrompt ??
|
|
1906
1908
|
((input) => defaultComposeCaptainPrompt(input, spec.placeholderFields));
|
|
1907
1909
|
const extractFields = spec.extractRequiredFields ?? defaultExtractRequiredFields;
|
|
@@ -2522,7 +2524,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
2522
2524
|
}
|
|
2523
2525
|
return session?.roleBindings?.[roleId]?.promptIdentity ?? roleId;
|
|
2524
2526
|
}
|
|
2525
|
-
function composeBoundPlayerPrompt(input) {
|
|
2527
|
+
function composeBoundPlayerPrompt(input, resuming = false) {
|
|
2526
2528
|
let active = true;
|
|
2527
2529
|
const lookup = (roleId) => {
|
|
2528
2530
|
if (!active) {
|
|
@@ -2531,7 +2533,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
2531
2533
|
return promptIdentity(roleId);
|
|
2532
2534
|
};
|
|
2533
2535
|
try {
|
|
2534
|
-
return composePlayerPrompt(input, lookup);
|
|
2536
|
+
return composePlayerPrompt(input, lookup, resuming);
|
|
2535
2537
|
}
|
|
2536
2538
|
finally {
|
|
2537
2539
|
active = false;
|
|
@@ -3372,7 +3374,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3372
3374
|
failedGovernedAttemptId = attemptIds.values().next().value;
|
|
3373
3375
|
}
|
|
3374
3376
|
const boundary = {
|
|
3375
|
-
async callPlayer(input, roleId,
|
|
3377
|
+
async callPlayer(input, roleId, freshPrompt, signal) {
|
|
3376
3378
|
// State-entry telemetry/status must precede the call they describe.
|
|
3377
3379
|
await drainEmissions();
|
|
3378
3380
|
signal.throwIfAborted();
|
|
@@ -3399,6 +3401,8 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3399
3401
|
controlPlaneError ??= error;
|
|
3400
3402
|
throw error;
|
|
3401
3403
|
}
|
|
3404
|
+
const prompt = selectedResume === false
|
|
3405
|
+
? freshPrompt : composeBoundPlayerPrompt(input, true);
|
|
3402
3406
|
const callId = deferredContinuation?.effectBoundary.callId ??
|
|
3403
3407
|
`player-${++playerCallSequence}`;
|
|
3404
3408
|
const callIdentity = (resume) => ({
|
|
@@ -3427,7 +3431,8 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3427
3431
|
try {
|
|
3428
3432
|
const runTracedPlayerCall = async (resume = selectedResume) => {
|
|
3429
3433
|
const identity = callIdentity(resume);
|
|
3430
|
-
|
|
3434
|
+
const callPrompt = resume === false ? freshPrompt : prompt;
|
|
3435
|
+
await emitCallStarted('player.call.started', 'player.call.finished', { ...identity, prompt: callPrompt }, position, signal);
|
|
3431
3436
|
let rawResult;
|
|
3432
3437
|
try {
|
|
3433
3438
|
// An abort may land while the awaited started emission drains
|
|
@@ -3435,7 +3440,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
3435
3440
|
// never start after abort, so settle the already-started pair
|
|
3436
3441
|
// as `aborted` through the catch below.
|
|
3437
3442
|
signal.throwIfAborted();
|
|
3438
|
-
rawResult = await requireHostPorts().callPlayer(roleId,
|
|
3443
|
+
rawResult = await requireHostPorts().callPlayer(roleId, callPrompt, signal, { resume, ...(callPrompt === freshPrompt ? {} : { freshPrompt }) });
|
|
3439
3444
|
// A host promise is not required to honor cancellation. Do not
|
|
3440
3445
|
// let a late result mutate continuity or publish a successful
|
|
3441
3446
|
// finish.
|