@sublang/playbook 12.3.0 → 13.1.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/docs/cli.md +56 -52
- package/docs/configuration.md +2 -2
- package/docs/embedding.md +68 -32
- package/package.json +11 -3
- package/reference/sdlc/code.md +7 -0
- package/reference/sdlc/code.playbook/bin/interactive-session.js +1 -0
- package/reference/sdlc/code.playbook/bin/launch-config.js +2 -0
- package/reference/sdlc/code.playbook/bin/playbook.js +42 -4
- package/reference/sdlc/code.playbook/bin/portable-codec.js +190 -0
- package/reference/sdlc/code.playbook/bin/replay-observer.js +18 -2
- package/reference/sdlc/code.playbook/bin/run.js +62 -18
- package/reference/sdlc/code.playbook/bin/session-host.js +104 -0
- package/reference/sdlc/code.playbook/bin/session-store.js +622 -65
- 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.d.ts +5 -0
- package/reference/sdlc/code.playbook/playbook-captain.js +56 -14
- package/reference/sdlc/code.playbook/playbook-captain.ts +71 -21
- package/reference/sdlc/code.playbook/session-host.d.ts +75 -0
- package/reference/sdlc/code.playbook/session-host.js +18 -0
- package/reference/sdlc/code.playbook/session-store.d.ts +176 -1
- package/reference/sdlc/code.playbook/session-store.js +22 -6
- 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 +21 -1
- package/reference/sdlc/dev.playbook/dev.fsm.js +10 -10
- package/reference/sdlc/dev.playbook/dev.fsm.ts +13 -13
- package/reference/sdlc/dev.playbook/dev.gears.md +15 -15
- package/reference/sdlc/dev.playbook/dev.playbook.d.ts +1 -1
- package/reference/sdlc/dev.playbook/dev.playbook.js +7 -17
- package/reference/sdlc/dev.playbook/dev.playbook.ts +8 -20
- 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 +30 -26
- package/src/xstate-playbook-runtime.ts +40 -35
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;
|
|
@@ -2847,7 +2849,7 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
2847
2849
|
};
|
|
2848
2850
|
}
|
|
2849
2851
|
function detachedPlayerContinuation(roleId, playerId) {
|
|
2850
|
-
return snapshotJsonValue(
|
|
2852
|
+
return snapshotJsonValue({ v: 1, playerId: playerId ?? roleId }, `${label} deferred player continuation`);
|
|
2851
2853
|
}
|
|
2852
2854
|
function completionEvidenceFor(input, roleId, playerId, signal, operationId) {
|
|
2853
2855
|
return async (completion) => {
|
|
@@ -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.
|
|
@@ -4979,6 +4984,15 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
4979
4984
|
throw new Error(`${label} deferred continuation requires repository.runDeferred`);
|
|
4980
4985
|
}
|
|
4981
4986
|
const effectBoundary = continuationBoundarySeed(operation, turnId);
|
|
4987
|
+
const boundPlayerId = resolvedPlayerId(effectBoundary.roleId);
|
|
4988
|
+
const validateBinding = (value) => {
|
|
4989
|
+
if (!isPlainObject(value) || Object.keys(value).length !== 2 ||
|
|
4990
|
+
value.v !== 1 ||
|
|
4991
|
+
value.playerId !== (boundPlayerId ?? effectBoundary.roleId)) {
|
|
4992
|
+
throw new TypeError(`${label} bound deferred player continuation is invalid`);
|
|
4993
|
+
}
|
|
4994
|
+
};
|
|
4995
|
+
validateBinding(operation.playerContinuation);
|
|
4982
4996
|
const continuation = {
|
|
4983
4997
|
operationId: operation.operationId,
|
|
4984
4998
|
effectBoundary,
|
|
@@ -5004,18 +5018,8 @@ export function createXStatePlaybookRuntime(machine, spec) {
|
|
|
5004
5018
|
operationId: operation.operationId,
|
|
5005
5019
|
effectBoundary,
|
|
5006
5020
|
operation: async ({ playerContinuation }) => {
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
: selectPlayerResume(effectBoundary.roleId, resolvedPlayerId(effectBoundary.roleId));
|
|
5010
|
-
if (selectedContinuation !== false &&
|
|
5011
|
-
(typeof selectedContinuation !== 'string' ||
|
|
5012
|
-
selectedContinuation.trim().length === 0)) {
|
|
5013
|
-
throw new TypeError(`${label} bound deferred player continuation is invalid`);
|
|
5014
|
-
}
|
|
5015
|
-
// Retained adoption owns a fresh Captain-session player ledger;
|
|
5016
|
-
// no source token becomes target ownership. Same-engagement
|
|
5017
|
-
// continuation still uses the exact durable binding.
|
|
5018
|
-
continuation.playerContinuation = selectedContinuation;
|
|
5021
|
+
validateBinding(playerContinuation);
|
|
5022
|
+
continuation.playerContinuation = selectPlayerResume(effectBoundary.roleId, boundPlayerId);
|
|
5019
5023
|
continuationStarted = true;
|
|
5020
5024
|
actor.send(event);
|
|
5021
5025
|
// The host has durably started this boundary and the FSM has
|
|
@@ -776,6 +776,7 @@ interface XStatePlaybookRuntimeSpecBase<TOptions> {
|
|
|
776
776
|
composePlayerPrompt?: (
|
|
777
777
|
input: PlaybookPlayerInput,
|
|
778
778
|
promptIdentity: XStatePromptIdentity,
|
|
779
|
+
resuming?: boolean,
|
|
779
780
|
) => string;
|
|
780
781
|
/** Compose the direct-Captain prompt. Default: continuation blocks + placeholder substitution with deterministic JSON rendering. */
|
|
781
782
|
composeCaptainPrompt?: (input: PlaybookCaptainInput) => string;
|
|
@@ -1064,22 +1065,31 @@ export function pendingBossQuestionFromContext(
|
|
|
1064
1065
|
// ---------------------------------------------------------------------------
|
|
1065
1066
|
|
|
1066
1067
|
const CONTINUATION_PREAMBLE =
|
|
1067
|
-
'
|
|
1068
|
+
'Continue the same task using Boss’s reply below.';
|
|
1068
1069
|
|
|
1069
1070
|
function continuationBlocks(input: {
|
|
1070
1071
|
pendingBossQuestion?: { readonly question: string };
|
|
1071
1072
|
bossReply?: string;
|
|
1072
|
-
}): string[] {
|
|
1073
|
+
}, resuming = false): string[] {
|
|
1073
1074
|
if (input.pendingBossQuestion === undefined || input.bossReply === undefined) {
|
|
1074
1075
|
return [];
|
|
1075
1076
|
}
|
|
1076
1077
|
return [
|
|
1077
1078
|
CONTINUATION_PREAMBLE,
|
|
1078
|
-
`
|
|
1079
|
+
...(resuming ? [] : [`Your previous question:\n${input.pendingBossQuestion.question}`]),
|
|
1079
1080
|
`Boss reply:\n${input.bossReply}`,
|
|
1080
1081
|
];
|
|
1081
1082
|
}
|
|
1082
1083
|
|
|
1084
|
+
/** Add clarification context without repeating the question in a live conversation. */
|
|
1085
|
+
export function composePlayerContinuation(
|
|
1086
|
+
input: Pick<PlaybookPlayerInput, 'pendingBossQuestion' | 'bossReply'>,
|
|
1087
|
+
body: string,
|
|
1088
|
+
resuming = false,
|
|
1089
|
+
): string {
|
|
1090
|
+
return [...continuationBlocks(input, resuming), body].join('\n\n');
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1083
1093
|
const PLACEHOLDER_PATTERN = /<(#|[A-Za-z_$][A-Za-z0-9_$-]*)>/g;
|
|
1084
1094
|
|
|
1085
1095
|
function placeholderFieldName(
|
|
@@ -1104,16 +1114,15 @@ function placeholderFieldName(
|
|
|
1104
1114
|
export function defaultComposePlayerPrompt(
|
|
1105
1115
|
input: PlaybookPlayerInput,
|
|
1106
1116
|
placeholderFields: Readonly<Record<string, string>> = {},
|
|
1117
|
+
resuming = false,
|
|
1107
1118
|
): string {
|
|
1108
|
-
const blocks = continuationBlocks(input);
|
|
1109
1119
|
const fields = input as unknown as Record<string, unknown>;
|
|
1110
1120
|
const body = input.prompt.replace(PLACEHOLDER_PATTERN, (match, token) => {
|
|
1111
1121
|
const value =
|
|
1112
1122
|
fields[placeholderFieldName(token as string, placeholderFields)];
|
|
1113
1123
|
return typeof value === 'string' ? value : match;
|
|
1114
1124
|
});
|
|
1115
|
-
|
|
1116
|
-
return blocks.join('\n\n');
|
|
1125
|
+
return composePlayerContinuation(input, body, resuming);
|
|
1117
1126
|
}
|
|
1118
1127
|
|
|
1119
1128
|
function sortJson(value: JsonValue): JsonValue {
|
|
@@ -3158,8 +3167,8 @@ export function createXStatePlaybookRuntime<
|
|
|
3158
3167
|
}
|
|
3159
3168
|
const composePlayerPrompt =
|
|
3160
3169
|
spec.composePlayerPrompt ??
|
|
3161
|
-
((input: PlaybookPlayerInput) =>
|
|
3162
|
-
defaultComposePlayerPrompt(input, spec.placeholderFields));
|
|
3170
|
+
((input: PlaybookPlayerInput, _identity: XStatePromptIdentity, resuming = false) =>
|
|
3171
|
+
defaultComposePlayerPrompt(input, spec.placeholderFields, resuming));
|
|
3163
3172
|
const composeCaptainPrompt =
|
|
3164
3173
|
spec.composeCaptainPrompt ??
|
|
3165
3174
|
((input: PlaybookCaptainInput) =>
|
|
@@ -4074,7 +4083,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4074
4083
|
return session?.roleBindings?.[roleId]?.promptIdentity ?? roleId;
|
|
4075
4084
|
}
|
|
4076
4085
|
|
|
4077
|
-
function composeBoundPlayerPrompt(input: PlaybookPlayerInput): string {
|
|
4086
|
+
function composeBoundPlayerPrompt(input: PlaybookPlayerInput, resuming = false): string {
|
|
4078
4087
|
let active = true;
|
|
4079
4088
|
const lookup: XStatePromptIdentity = (roleId) => {
|
|
4080
4089
|
if (!active) {
|
|
@@ -4085,7 +4094,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4085
4094
|
return promptIdentity(roleId);
|
|
4086
4095
|
};
|
|
4087
4096
|
try {
|
|
4088
|
-
return composePlayerPrompt(input, lookup);
|
|
4097
|
+
return composePlayerPrompt(input, lookup, resuming);
|
|
4089
4098
|
} finally {
|
|
4090
4099
|
active = false;
|
|
4091
4100
|
}
|
|
@@ -4523,7 +4532,7 @@ export function createXStatePlaybookRuntime<
|
|
|
4523
4532
|
playerId: string | undefined,
|
|
4524
4533
|
): JsonValue {
|
|
4525
4534
|
return snapshotJsonValue(
|
|
4526
|
-
|
|
4535
|
+
{ v: 1, playerId: playerId ?? roleId },
|
|
4527
4536
|
`${label} deferred player continuation`,
|
|
4528
4537
|
);
|
|
4529
4538
|
}
|
|
@@ -5321,7 +5330,7 @@ export function createXStatePlaybookRuntime<
|
|
|
5321
5330
|
async callPlayer(
|
|
5322
5331
|
input,
|
|
5323
5332
|
roleId,
|
|
5324
|
-
|
|
5333
|
+
freshPrompt,
|
|
5325
5334
|
signal,
|
|
5326
5335
|
): Promise<PlayerResult> {
|
|
5327
5336
|
// State-entry telemetry/status must precede the call they describe.
|
|
@@ -5356,6 +5365,8 @@ export function createXStatePlaybookRuntime<
|
|
|
5356
5365
|
if (!isAbortFailure(error, signal)) controlPlaneError ??= error;
|
|
5357
5366
|
throw error;
|
|
5358
5367
|
}
|
|
5368
|
+
const prompt = selectedResume === false
|
|
5369
|
+
? freshPrompt : composeBoundPlayerPrompt(input, true);
|
|
5359
5370
|
const callId =
|
|
5360
5371
|
deferredContinuation?.effectBoundary.callId ??
|
|
5361
5372
|
`player-${++playerCallSequence}`;
|
|
@@ -5401,10 +5412,11 @@ export function createXStatePlaybookRuntime<
|
|
|
5401
5412
|
resume: string | false = selectedResume,
|
|
5402
5413
|
): Promise<PlayerResult> => {
|
|
5403
5414
|
const identity = callIdentity(resume);
|
|
5415
|
+
const callPrompt = resume === false ? freshPrompt : prompt;
|
|
5404
5416
|
await emitCallStarted(
|
|
5405
5417
|
'player.call.started',
|
|
5406
5418
|
'player.call.finished',
|
|
5407
|
-
{ ...identity, prompt },
|
|
5419
|
+
{ ...identity, prompt: callPrompt },
|
|
5408
5420
|
position,
|
|
5409
5421
|
signal,
|
|
5410
5422
|
);
|
|
@@ -5418,9 +5430,9 @@ export function createXStatePlaybookRuntime<
|
|
|
5418
5430
|
signal.throwIfAborted();
|
|
5419
5431
|
rawResult = await requireHostPorts().callPlayer(
|
|
5420
5432
|
roleId,
|
|
5421
|
-
|
|
5433
|
+
callPrompt,
|
|
5422
5434
|
signal,
|
|
5423
|
-
{ resume },
|
|
5435
|
+
{ resume, ...(callPrompt === freshPrompt ? {} : { freshPrompt }) },
|
|
5424
5436
|
);
|
|
5425
5437
|
// A host promise is not required to honor cancellation. Do not
|
|
5426
5438
|
// let a late result mutate continuity or publish a successful
|
|
@@ -7324,6 +7336,15 @@ export function createXStatePlaybookRuntime<
|
|
|
7324
7336
|
);
|
|
7325
7337
|
}
|
|
7326
7338
|
const effectBoundary = continuationBoundarySeed(operation, turnId);
|
|
7339
|
+
const boundPlayerId = resolvedPlayerId(effectBoundary.roleId);
|
|
7340
|
+
const validateBinding = (value: unknown): void => {
|
|
7341
|
+
if (!isPlainObject(value) || Object.keys(value).length !== 2 ||
|
|
7342
|
+
value.v !== 1 ||
|
|
7343
|
+
value.playerId !== (boundPlayerId ?? effectBoundary.roleId)) {
|
|
7344
|
+
throw new TypeError(`${label} bound deferred player continuation is invalid`);
|
|
7345
|
+
}
|
|
7346
|
+
};
|
|
7347
|
+
validateBinding(operation.playerContinuation);
|
|
7327
7348
|
const continuation: NonNullable<typeof activeDeferredContinuation> = {
|
|
7328
7349
|
operationId: operation.operationId,
|
|
7329
7350
|
effectBoundary,
|
|
@@ -7349,26 +7370,10 @@ export function createXStatePlaybookRuntime<
|
|
|
7349
7370
|
operationId: operation.operationId,
|
|
7350
7371
|
effectBoundary,
|
|
7351
7372
|
operation: async ({ playerContinuation }) => {
|
|
7352
|
-
|
|
7353
|
-
|
|
7354
|
-
|
|
7355
|
-
|
|
7356
|
-
effectBoundary.roleId,
|
|
7357
|
-
resolvedPlayerId(effectBoundary.roleId),
|
|
7358
|
-
);
|
|
7359
|
-
if (
|
|
7360
|
-
selectedContinuation !== false &&
|
|
7361
|
-
(typeof selectedContinuation !== 'string' ||
|
|
7362
|
-
selectedContinuation.trim().length === 0)
|
|
7363
|
-
) {
|
|
7364
|
-
throw new TypeError(
|
|
7365
|
-
`${label} bound deferred player continuation is invalid`,
|
|
7366
|
-
);
|
|
7367
|
-
}
|
|
7368
|
-
// Retained adoption owns a fresh Captain-session player ledger;
|
|
7369
|
-
// no source token becomes target ownership. Same-engagement
|
|
7370
|
-
// continuation still uses the exact durable binding.
|
|
7371
|
-
continuation.playerContinuation = selectedContinuation;
|
|
7373
|
+
validateBinding(playerContinuation);
|
|
7374
|
+
continuation.playerContinuation = selectPlayerResume(
|
|
7375
|
+
effectBoundary.roleId, boundPlayerId,
|
|
7376
|
+
);
|
|
7372
7377
|
continuationStarted = true;
|
|
7373
7378
|
actor!.send(event);
|
|
7374
7379
|
// The host has durably started this boundary and the FSM has
|