@sublang/playbook 17.8.1 → 17.10.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/configuration.md +29 -2
- package/package.json +2 -2
- package/reference/sdlc/captain.md +1 -1
- package/reference/sdlc/captain.playbook/captain.fsm.js +1 -1
- package/reference/sdlc/captain.playbook/captain.fsm.ts +1 -1
- package/reference/sdlc/captain.playbook/captain.gears.md +1 -1
- package/reference/sdlc/code.playbook/bin/launch-config.js +55 -13
- package/reference/sdlc/code.playbook/bin/repository-effects.js +26 -2
- package/reference/sdlc/code.playbook/bin/session-store.js +3 -0
- package/reference/sdlc/code.playbook/host-capabilities.d.ts +1 -0
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +2 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +170 -48
- package/reference/sdlc/code.playbook/playbook-captain.ts +227 -63
- package/slc/link.md +25 -3
- package/src/runtime.d.ts +13 -0
- package/src/runtime.ts +14 -0
- package/src/xstate-playbook-runtime.d.ts +4 -3
- package/src/xstate-playbook-runtime.js +163 -19
- package/src/xstate-playbook-runtime.ts +197 -18
- package/src/xstate-runtime.d.ts +7 -5
- package/src/xstate-runtime.js +40 -9
- package/src/xstate-runtime.ts +53 -24
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { posix, win32 } from 'node:path';
|
|
4
5
|
import { isDeepStrictEqual } from 'node:util';
|
|
5
6
|
import PQueue from 'p-queue';
|
|
6
7
|
import { isAgentCallSettingsError, } from '@sublang/cligent/tmux-play';
|
|
@@ -556,6 +557,29 @@ function boundedPathList(paths) {
|
|
|
556
557
|
? `${printed.join(', ')}, … and ${omitted} more`
|
|
557
558
|
: printed.join(', ');
|
|
558
559
|
}
|
|
560
|
+
// DR-090 §5: the report lines the shell appends to a reply carry marks and
|
|
561
|
+
// identifiers, never an English sentence, so a reply in any language reads
|
|
562
|
+
// them as structure. The settlement facts a model reads keep their words.
|
|
563
|
+
const MARK_DONE = '✓';
|
|
564
|
+
const MARK_FAILED = '✕';
|
|
565
|
+
const MARK_TO = '⟶';
|
|
566
|
+
const MARK_BRANCH = '⎇';
|
|
567
|
+
const MARK_SEP = '·';
|
|
568
|
+
const MARK_READY = '▸';
|
|
569
|
+
const MARK_NOT_READY = '▹';
|
|
570
|
+
/** At most 24 items, then `+<n>` for the rest (DR-090 §5). */
|
|
571
|
+
function markedList(items, separator = ', ', more = 0) {
|
|
572
|
+
const printed = items.slice(0, CARRIED_CHANGE_PATH_LIMIT);
|
|
573
|
+
const omitted = items.length - printed.length + more;
|
|
574
|
+
if (printed.length === 0)
|
|
575
|
+
return omitted > 0 ? `+${omitted}` : '';
|
|
576
|
+
return omitted > 0
|
|
577
|
+
? `${printed.join(separator)}${separator}+${omitted}`
|
|
578
|
+
: printed.join(separator);
|
|
579
|
+
}
|
|
580
|
+
function markedBranch(branch) {
|
|
581
|
+
return branch === undefined ? '' : ` ${MARK_BRANCH} ${branch}`;
|
|
582
|
+
}
|
|
559
583
|
/**
|
|
560
584
|
* The carried pre-existing changes of the boundaries completed after
|
|
561
585
|
* `boundaryPrefixCount`, one entry per commit OID in boundary sequence order.
|
|
@@ -621,6 +645,11 @@ function carriedPreExistingFacts(carried) {
|
|
|
621
645
|
return `The commit ${commitOid} carries changes that were uncommitted before the step: ${lists.join('; ')}.`;
|
|
622
646
|
});
|
|
623
647
|
}
|
|
648
|
+
/** DR-090 §4: where a step's commits landed, as its receipt names it. */
|
|
649
|
+
function onBranch(receipt) {
|
|
650
|
+
const branch = receipt.after?.branch;
|
|
651
|
+
return branch === undefined ? '' : ` on branch ${branch}`;
|
|
652
|
+
}
|
|
624
653
|
function commitsBeforeFailure(ledger, boundaryPrefixCount, unresolvedEffects) {
|
|
625
654
|
// An entry names a commit by its proven OID, or — for a receipt listing
|
|
626
655
|
// several commits, which projects as `multiple-commits` (DR-088 §3) — by
|
|
@@ -645,28 +674,32 @@ function commitsBeforeFailure(ledger, boundaryPrefixCount, unresolvedEffects) {
|
|
|
645
674
|
baselineHead: receipt.baseline.head,
|
|
646
675
|
commitOid: receipt.commitOid,
|
|
647
676
|
...(receipt.commits === undefined ? {} : { commits: receipt.commits }),
|
|
677
|
+
...(receipt.after?.branch === undefined
|
|
678
|
+
? {}
|
|
679
|
+
: { branch: receipt.after.branch }),
|
|
648
680
|
});
|
|
649
681
|
}
|
|
650
682
|
return commits;
|
|
651
683
|
}
|
|
652
684
|
/** What one step committed, with every commit where it made several (DR-088). */
|
|
653
|
-
function commitBeforeFailureClause({ baselineHead, commitOid, commits, }) {
|
|
685
|
+
function commitBeforeFailureClause({ baselineHead, commitOid, commits, branch, }) {
|
|
654
686
|
const made = commits === undefined
|
|
655
687
|
? 'made one new commit'
|
|
656
688
|
: `made ${commits.length} new commits, oldest first: ${boundedPathList(commits)}`;
|
|
657
|
-
|
|
689
|
+
const landed = branch === undefined ? '' : ` on branch ${branch}`;
|
|
690
|
+
return `${made}; HEAD moved from ${baselineHead} to the proven commit ${commitOid}${landed}.`;
|
|
658
691
|
}
|
|
659
692
|
function commitBeforeFailureFact(commit) {
|
|
660
693
|
return `Before the failure, a step ${commitBeforeFailureClause(commit)}`;
|
|
661
694
|
}
|
|
695
|
+
// DR-090 §5: one marked line per receipt, `✓ <baselineHead> ⟶ <commits>`
|
|
696
|
+
// and the branch they landed on.
|
|
662
697
|
function commitsBeforeFailureReport(commits) {
|
|
663
698
|
if (commits.length === 0)
|
|
664
699
|
return undefined;
|
|
665
|
-
return
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
'The failure does not undo these commits.',
|
|
669
|
-
].join('\n');
|
|
700
|
+
return commits
|
|
701
|
+
.map(({ baselineHead, commitOid, commits: listed, branch }) => `${MARK_DONE} ${baselineHead} ${MARK_TO} ${markedList(listed ?? [commitOid], ` ${MARK_SEP} `)}${markedBranch(branch)}`)
|
|
702
|
+
.join('\n');
|
|
670
703
|
}
|
|
671
704
|
/**
|
|
672
705
|
* DR-088 §2: whether a final text names one commit as a word, by its full OID
|
|
@@ -681,14 +714,16 @@ function finalTextNamesCommit(finalText, oid) {
|
|
|
681
714
|
return false;
|
|
682
715
|
}
|
|
683
716
|
/**
|
|
684
|
-
* DR-088 §3: what the steps completed after `boundaryPrefixCount`
|
|
685
|
-
* one statement each, in boundary sequence order
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
717
|
+
* DR-088 §3, DR-090 §4: what the steps completed after `boundaryPrefixCount`
|
|
718
|
+
* committed, one statement each, in boundary sequence order, naming the
|
|
719
|
+
* branch where the receipt names one: every new commit where a receipt
|
|
720
|
+
* proves more than one, the commit already at HEAD where a step whose
|
|
721
|
+
* outcomes all commit or ask named it over an `unchanged` receipt, and —
|
|
722
|
+
* as a fact for the closing reply only, and only where `oneCommit` asks —
|
|
723
|
+
* the one new commit of a step that made one. A step in a deferred chain
|
|
724
|
+
* reads the chain's cumulative receipt at its last boundary.
|
|
690
725
|
*/
|
|
691
|
-
function stepCommitStatements(ledger, boundaryPrefixCount) {
|
|
726
|
+
function stepCommitStatements(ledger, boundaryPrefixCount, oneCommit = true) {
|
|
692
727
|
const statements = [];
|
|
693
728
|
for (const boundary of ledger.boundaries) {
|
|
694
729
|
if (boundary.sequence <= boundaryPrefixCount)
|
|
@@ -704,7 +739,19 @@ function stepCommitStatements(ledger, boundaryPrefixCount) {
|
|
|
704
739
|
continue;
|
|
705
740
|
if (receipt.classification === 'one-descendant-commit' &&
|
|
706
741
|
receipt.commits !== undefined) {
|
|
707
|
-
statements.push(
|
|
742
|
+
statements.push({
|
|
743
|
+
text: `The step made ${receipt.commits.length} new commits${onBranch(receipt)}, oldest first: ${boundedPathList(receipt.commits)}.`,
|
|
744
|
+
shown: `${MARK_DONE} ${markedList(receipt.commits, ` ${MARK_SEP} `)}${markedBranch(receipt.after?.branch)}`,
|
|
745
|
+
});
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
if (receipt.classification === 'one-descendant-commit' &&
|
|
749
|
+
receipt.commitOid !== undefined) {
|
|
750
|
+
if (oneCommit) {
|
|
751
|
+
statements.push({
|
|
752
|
+
text: `The step made one new commit, ${receipt.commitOid}${receipt.after?.branch === undefined ? '' : `,${onBranch(receipt)}`}.`,
|
|
753
|
+
});
|
|
754
|
+
}
|
|
708
755
|
continue;
|
|
709
756
|
}
|
|
710
757
|
const candidate = boundary.semanticCandidate;
|
|
@@ -722,7 +769,10 @@ function stepCommitStatements(ledger, boundaryPrefixCount) {
|
|
|
722
769
|
typeof guard === 'string' &&
|
|
723
770
|
guard !== BOSS_QUESTION_OUTCOME &&
|
|
724
771
|
finalTextNamesCommit(boundary.finalText?.trim() ?? '', head)) {
|
|
725
|
-
statements.push(
|
|
772
|
+
statements.push({
|
|
773
|
+
text: `The step made no new commit; it named ${head}, already at HEAD${onBranch(receipt)}, as its work.`,
|
|
774
|
+
shown: `${MARK_DONE} = ${head}${markedBranch(receipt.after?.branch)}`,
|
|
775
|
+
});
|
|
726
776
|
}
|
|
727
777
|
}
|
|
728
778
|
return statements;
|
|
@@ -732,15 +782,6 @@ function stepCommitStatements(ledger, boundaryPrefixCount) {
|
|
|
732
782
|
// of asking a model to describe facts it may drop. The report exposes no file
|
|
733
783
|
// content, player prose, or internal identity: paths, revisions, dispositions,
|
|
734
784
|
// and the reported error message are what a cause carries.
|
|
735
|
-
/** The Boss-facing phrase for each closed control-action reason (DR-063 §3). */
|
|
736
|
-
const CONTROL_REASON_PHRASES = {
|
|
737
|
-
'receipt-complete': 'nothing has changed since it failed',
|
|
738
|
-
};
|
|
739
|
-
function controlReasonPhrase(reason) {
|
|
740
|
-
if (reason === undefined)
|
|
741
|
-
return 'no reason was published';
|
|
742
|
-
return CONTROL_REASON_PHRASES[reason] ?? reason;
|
|
743
|
-
}
|
|
744
785
|
function causePathList(paths, truncated) {
|
|
745
786
|
const list = paths ?? [];
|
|
746
787
|
const printed = list.slice(0, CARRIED_CHANGE_PATH_LIMIT);
|
|
@@ -1105,11 +1146,56 @@ function failureLine(reason) {
|
|
|
1105
1146
|
};
|
|
1106
1147
|
}
|
|
1107
1148
|
function failureControlLine(action) {
|
|
1108
|
-
// DR-063 §3: an action that publishes no standing is `ready`.
|
|
1149
|
+
// DR-063 §3: an action that publishes no standing is `ready`. DR-090 §5:
|
|
1150
|
+
// the standing and its reason read as their closed identifiers.
|
|
1109
1151
|
const standing = action.standing ?? 'ready';
|
|
1110
1152
|
return standing === 'ready'
|
|
1111
|
-
?
|
|
1112
|
-
:
|
|
1153
|
+
? `${MARK_READY} ${action.label}`
|
|
1154
|
+
: `${MARK_NOT_READY} ${action.label} ${MARK_SEP} ${standing}${action.reason === undefined ? '' : ` ${MARK_SEP} ${action.reason}`}`;
|
|
1155
|
+
}
|
|
1156
|
+
/**
|
|
1157
|
+
* DR-090 §5: the failure line's marked record of a cause — its code and the
|
|
1158
|
+
* identities its evidence carries, a nested cause after its playbook. No
|
|
1159
|
+
* recorded reason or message reaches it: the settlement facts the closing
|
|
1160
|
+
* reply reads state the failure in words (CAPTAIN-71).
|
|
1161
|
+
*/
|
|
1162
|
+
function markedFailure(cause, describeNestedPlaybook) {
|
|
1163
|
+
const { evidence } = cause;
|
|
1164
|
+
const parts = [cause.code];
|
|
1165
|
+
if (evidence.roleId !== undefined)
|
|
1166
|
+
parts.push(evidence.roleId);
|
|
1167
|
+
if (evidence.playbookId !== undefined) {
|
|
1168
|
+
parts.push(describeNestedPlaybook(evidence.playbookId));
|
|
1169
|
+
}
|
|
1170
|
+
if (cause.code === 'attribution-ambiguous' &&
|
|
1171
|
+
evidence.required !== undefined &&
|
|
1172
|
+
evidence.observed !== undefined) {
|
|
1173
|
+
parts.push(`${evidence.required} ${MARK_TO} ${evidence.observed}`);
|
|
1174
|
+
}
|
|
1175
|
+
if (evidence.baselineHead !== undefined) {
|
|
1176
|
+
parts.push(evidence.afterHead === undefined
|
|
1177
|
+
? evidence.baselineHead
|
|
1178
|
+
: `${evidence.baselineHead} ${MARK_TO} ${evidence.afterHead}`);
|
|
1179
|
+
}
|
|
1180
|
+
if (evidence.commitOid !== undefined &&
|
|
1181
|
+
evidence.commitOid !== evidence.afterHead) {
|
|
1182
|
+
parts.push(evidence.commitOid);
|
|
1183
|
+
}
|
|
1184
|
+
const paths = evidence.paths;
|
|
1185
|
+
if (paths !== undefined) {
|
|
1186
|
+
const listed = markedList([
|
|
1187
|
+
...(paths.uncommitted ?? []),
|
|
1188
|
+
...(paths.altered ?? []),
|
|
1189
|
+
...(paths.lost ?? []),
|
|
1190
|
+
...(paths.changed ?? []),
|
|
1191
|
+
], ', ', paths.truncated ?? 0);
|
|
1192
|
+
if (listed !== '')
|
|
1193
|
+
parts.push(listed);
|
|
1194
|
+
}
|
|
1195
|
+
if (evidence.cause !== undefined) {
|
|
1196
|
+
parts.push(markedFailure(evidence.cause, describeNestedPlaybook));
|
|
1197
|
+
}
|
|
1198
|
+
return parts.join(` ${MARK_SEP} `);
|
|
1113
1199
|
}
|
|
1114
1200
|
/**
|
|
1115
1201
|
* Whether `container` already states `statement`: the same words, case and
|
|
@@ -1170,10 +1256,7 @@ function guardedStatement(text, withholds, refuses, phrased = [], shellWords = N
|
|
|
1170
1256
|
for (const reason of phrased) {
|
|
1171
1257
|
if (!fails(reason))
|
|
1172
1258
|
continue;
|
|
1173
|
-
guarded =
|
|
1174
|
-
guarded === `Failure: ${reason}`
|
|
1175
|
-
? `Failure: the step failed with ${WITHHELD_REASON}.`
|
|
1176
|
-
: guarded.split(`: ${reason}`).join(` with ${WITHHELD_REASON}.`);
|
|
1259
|
+
guarded = guarded.split(`: ${reason}`).join(` with ${WITHHELD_REASON}.`);
|
|
1177
1260
|
}
|
|
1178
1261
|
for (const subject of shellWords.subjects) {
|
|
1179
1262
|
if (guarded.startsWith(`${subject} `) && fails(subject)) {
|
|
@@ -1199,8 +1282,10 @@ function guardedStatement(text, withholds, refuses, phrased = [], shellWords = N
|
|
|
1199
1282
|
*/
|
|
1200
1283
|
function assembleStatements(statements, reasons = [], guard = (text) => text) {
|
|
1201
1284
|
const rendered = [...new Set(reasons)];
|
|
1285
|
+
// DR-090 §5: the marked failure line is no sentence and gains no mark.
|
|
1286
|
+
const finish = (kind, text) => kind === 'failure' ? text.trim() : oneTerminalMark(text);
|
|
1202
1287
|
const weighed = statements.map(({ text, kind = 'fact', reason }) => {
|
|
1203
|
-
const statement =
|
|
1288
|
+
const statement = finish(kind, text);
|
|
1204
1289
|
if (reason !== undefined)
|
|
1205
1290
|
return { text: statement, kind, own: [reason] };
|
|
1206
1291
|
const held = rendered.filter((candidate) => alreadyStates(statement, candidate));
|
|
@@ -1221,7 +1306,7 @@ function assembleStatements(statements, reasons = [], guard = (text) => text) {
|
|
|
1221
1306
|
});
|
|
1222
1307
|
return weighed.map(({ text, kind, own }, index) => stays[index] &&
|
|
1223
1308
|
(kind !== 'failure' || !carried(own, [...entries, ...facts]))
|
|
1224
|
-
?
|
|
1309
|
+
? finish(kind, guard(text))
|
|
1225
1310
|
: undefined);
|
|
1226
1311
|
}
|
|
1227
1312
|
/** The statements of `statements` that stay, as CAPTAIN-69 assembles them. */
|
|
@@ -1248,11 +1333,12 @@ function presentationSuffix(turn, assembled) {
|
|
|
1248
1333
|
});
|
|
1249
1334
|
const failure = turn.failureReport;
|
|
1250
1335
|
if (failure !== undefined) {
|
|
1251
|
-
|
|
1336
|
+
const lines = [
|
|
1252
1337
|
...(assembled.failure === undefined ? [] : [assembled.failure]),
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1338
|
+
...failure.controls,
|
|
1339
|
+
];
|
|
1340
|
+
if (lines.length > 0)
|
|
1341
|
+
parts.push(lines.join('\n'));
|
|
1256
1342
|
}
|
|
1257
1343
|
return parts.length === 0 ? undefined : parts.join('\n\n');
|
|
1258
1344
|
}
|
|
@@ -2225,6 +2311,12 @@ function snapshotNonblankTuning(value, path) {
|
|
|
2225
2311
|
}
|
|
2226
2312
|
return value;
|
|
2227
2313
|
}
|
|
2314
|
+
function snapshotSkillLocators(value, path) {
|
|
2315
|
+
if (!Array.isArray(value) || value.some(item => typeof item !== 'string' || item.includes('\0') || !(posix.isAbsolute(item) || win32.isAbsolute(item)))) {
|
|
2316
|
+
throw new Error(`${path} must be an array of absolute skill paths`);
|
|
2317
|
+
}
|
|
2318
|
+
return [...value];
|
|
2319
|
+
}
|
|
2228
2320
|
function snapshotSessionAgent(value, path) {
|
|
2229
2321
|
const agent = snapshotRecord(value, path);
|
|
2230
2322
|
rejectSnapshotKeys(agent, [
|
|
@@ -2235,6 +2327,7 @@ function snapshotSessionAgent(value, path) {
|
|
|
2235
2327
|
'subagentModel',
|
|
2236
2328
|
'subagentEffort',
|
|
2237
2329
|
'browser',
|
|
2330
|
+
'skills',
|
|
2238
2331
|
'instruction',
|
|
2239
2332
|
'permissions',
|
|
2240
2333
|
], path);
|
|
@@ -2243,7 +2336,8 @@ function snapshotSessionAgent(value, path) {
|
|
|
2243
2336
|
key !== 'fastMode' &&
|
|
2244
2337
|
key !== 'subagentModel' &&
|
|
2245
2338
|
key !== 'subagentEffort' &&
|
|
2246
|
-
key !== 'browser'
|
|
2339
|
+
key !== 'browser' &&
|
|
2340
|
+
key !== 'skills')), path);
|
|
2247
2341
|
return {
|
|
2248
2342
|
adapter: fixed.adapter,
|
|
2249
2343
|
...(fixed.instruction === undefined
|
|
@@ -2252,6 +2346,7 @@ function snapshotSessionAgent(value, path) {
|
|
|
2252
2346
|
...(fixed.permissions === undefined
|
|
2253
2347
|
? {}
|
|
2254
2348
|
: { permissions: livePermissions(fixed.permissions) }),
|
|
2349
|
+
...(agent.skills === undefined ? {} : { skills: snapshotSkillLocators(agent.skills, `${path}.skills`) }),
|
|
2255
2350
|
...(agent.browser === undefined ? {} : { browser: snapshotFastMode(agent.browser, `${path}.browser`) }),
|
|
2256
2351
|
model: snapshotTuningSelection(agent.model, `${path}.model`),
|
|
2257
2352
|
effort: snapshotEffortSelection(agent.effort, `${path}.effort`),
|
|
@@ -2287,6 +2382,7 @@ function callSettings(agent, tuning = agent) {
|
|
|
2287
2382
|
model: tuning.model,
|
|
2288
2383
|
effort: tuning.effort,
|
|
2289
2384
|
browser: agent.browser ?? false,
|
|
2385
|
+
skills: agent.skills === undefined ? [] : [...agent.skills],
|
|
2290
2386
|
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
2291
2387
|
...(tuning.subagentModel === undefined
|
|
2292
2388
|
? {}
|
|
@@ -2299,7 +2395,7 @@ function callSettings(agent, tuning = agent) {
|
|
|
2299
2395
|
};
|
|
2300
2396
|
}
|
|
2301
2397
|
function controlCallSettings(agent) {
|
|
2302
|
-
return { ...callSettings(agent), browser: false, mcpServers: {} };
|
|
2398
|
+
return { ...callSettings(agent), browser: false, skills: [], mcpServers: {} };
|
|
2303
2399
|
}
|
|
2304
2400
|
function promptIdentity(binding) {
|
|
2305
2401
|
return binding.model.kind === 'value'
|
|
@@ -2917,7 +3013,9 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
2917
3013
|
try {
|
|
2918
3014
|
const ledger = assertPlaybookEffectLedger(currentEffectLedger());
|
|
2919
3015
|
carried = carriedPreExistingChanges(ledger, presentedEffectPrefix);
|
|
2920
|
-
|
|
3016
|
+
// DR-090 §4: a failed turn states its one-commit steps in the
|
|
3017
|
+
// commits-before-failure report instead (DR-089 §3), never twice.
|
|
3018
|
+
commitStatements = stepCommitStatements(ledger, presentedEffectPrefix, !leafParkedInFailure());
|
|
2921
3019
|
turn.carriedEffectPrefix = ledger.boundaries.length;
|
|
2922
3020
|
}
|
|
2923
3021
|
catch {
|
|
@@ -2931,12 +3029,15 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
2931
3029
|
joinSettlementFact(turn, fact);
|
|
2932
3030
|
}
|
|
2933
3031
|
}
|
|
2934
|
-
// DR-088 §3: the commits each step took, read from the same
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
}
|
|
3032
|
+
// DR-088 §3, DR-090 §4: the commits each step took, read from the same
|
|
3033
|
+
// boundaries; a step's one new commit reaches the closing reply's facts
|
|
3034
|
+
// only.
|
|
3035
|
+
const shownStatements = commitStatements.filter((statement) => statement.shown !== undefined);
|
|
3036
|
+
if (shownStatements.length > 0) {
|
|
3037
|
+
appendMandatoryPresentationSuffix(turn, shownStatements.map(({ shown }) => shown).join('\n'), shownStatements.map(({ text }) => text));
|
|
3038
|
+
}
|
|
3039
|
+
for (const { text } of commitStatements) {
|
|
3040
|
+
joinSettlementFact(turn, text);
|
|
2940
3041
|
}
|
|
2941
3042
|
};
|
|
2942
3043
|
/**
|
|
@@ -5455,7 +5556,11 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
5455
5556
|
? []
|
|
5456
5557
|
: [
|
|
5457
5558
|
{
|
|
5458
|
-
|
|
5559
|
+
// CAPTAIN-69: a mark whose words CAPTAIN-9 refuses — a path
|
|
5560
|
+
// naming control vocabulary, say — reads its code alone.
|
|
5561
|
+
text: `${MARK_FAILED} ${replyRejection(failure.mark) === undefined
|
|
5562
|
+
? failure.mark
|
|
5563
|
+
: failure.code}`,
|
|
5459
5564
|
kind: 'failure',
|
|
5460
5565
|
reason: failure.reason,
|
|
5461
5566
|
},
|
|
@@ -6291,6 +6396,21 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
6291
6396
|
// DR-063 §4: appended once, beside the unresolved-effect and pre-existing
|
|
6292
6397
|
// change reports, whenever the turn settles with the leaf parked in its
|
|
6293
6398
|
// failure state or behind the retained-effect fence.
|
|
6399
|
+
// The condition under which reportLeafFailure states a failure: the leaf
|
|
6400
|
+
// parked in its failure state, or behind the retained-effect fence.
|
|
6401
|
+
const leafParkedInFailure = () => {
|
|
6402
|
+
if (retainedEffectReconciliation !== undefined)
|
|
6403
|
+
return true;
|
|
6404
|
+
const leaf = leafFrame();
|
|
6405
|
+
if (!leaf || typeof leaf.runtime.describe !== 'function')
|
|
6406
|
+
return false;
|
|
6407
|
+
try {
|
|
6408
|
+
return leaf.runtime.describe().state.stateId === 'failed';
|
|
6409
|
+
}
|
|
6410
|
+
catch {
|
|
6411
|
+
return false;
|
|
6412
|
+
}
|
|
6413
|
+
};
|
|
6294
6414
|
const reportLeafFailure = (turn, unresolvedEffects) => {
|
|
6295
6415
|
if (turn.failureReported)
|
|
6296
6416
|
return;
|
|
@@ -6329,6 +6449,8 @@ export function createPlaybookCaptainShell(options, deps = {}) {
|
|
|
6329
6449
|
: statement.restate(failure);
|
|
6330
6450
|
const speak = statement?.speak ?? ((text) => text);
|
|
6331
6451
|
turn.failureReport = {
|
|
6452
|
+
mark: markedFailure(cause, describeNestedPlaybook),
|
|
6453
|
+
code: cause.code,
|
|
6332
6454
|
...failureLine(noteReason(failureReason(failure, describeNestedPlaybook, speakableWords))),
|
|
6333
6455
|
controls: failureControlLines(view, fenced),
|
|
6334
6456
|
};
|