@sublang/playbook 17.2.0 → 17.4.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 +5 -2
- package/docs/cli.md +6 -5
- package/docs/configuration.md +72 -18
- package/docs/embedding.md +94 -0
- package/package.json +34 -5
- package/reference/sdlc/captain.md +6 -4
- package/reference/sdlc/captain.playbook/captain.fsm.d.ts +5 -0
- package/reference/sdlc/captain.playbook/captain.fsm.js +4 -3
- package/reference/sdlc/captain.playbook/captain.fsm.ts +9 -4
- package/reference/sdlc/captain.playbook/captain.gears.md +6 -3
- package/reference/sdlc/captain.playbook/captain.playbook.d.ts +2 -0
- package/reference/sdlc/captain.playbook/captain.playbook.js +7 -3
- package/reference/sdlc/captain.playbook/captain.playbook.ts +8 -4
- package/reference/sdlc/code.playbook/asset-types.d.ts +32 -0
- package/reference/sdlc/code.playbook/bin/launch-config.js +84 -31
- package/reference/sdlc/code.playbook/bin/playbook.js +5 -3
- package/reference/sdlc/code.playbook/bin/portable-codec.js +12 -5
- package/reference/sdlc/code.playbook/bin/run.js +74 -7
- package/reference/sdlc/code.playbook/bin/session-host.js +16 -4
- package/reference/sdlc/code.playbook/bin/session-media.js +46 -0
- package/reference/sdlc/code.playbook/bin/session-store.js +164 -25
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +23 -4
- package/reference/sdlc/code.playbook/playbook-captain.js +188 -23
- package/reference/sdlc/code.playbook/playbook-captain.ts +223 -31
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +22 -5
- package/reference/sdlc/code.playbook/session-assets.d.ts +53 -0
- package/reference/sdlc/code.playbook/session-assets.js +302 -0
- package/reference/sdlc/code.playbook/session-host.d.ts +9 -1
- package/reference/sdlc/code.playbook/session-store.d.ts +26 -2
- package/reference/sdlc/code.playbook/worker-evidence.d.ts +8 -0
- package/reference/sdlc/code.playbook/worker-evidence.js +51 -0
- package/reference/sdlc/inspect.md +40 -0
- package/reference/sdlc/inspect.playbook/inspect.fsm.d.ts +168 -0
- package/reference/sdlc/inspect.playbook/inspect.fsm.js +354 -0
- package/reference/sdlc/inspect.playbook/inspect.fsm.ts +455 -0
- package/reference/sdlc/inspect.playbook/inspect.gears.md +36 -0
- package/reference/sdlc/inspect.playbook/inspect.playbook.d.ts +17 -0
- package/reference/sdlc/inspect.playbook/inspect.playbook.js +142 -0
- package/reference/sdlc/inspect.playbook/inspect.playbook.ts +184 -0
- package/reference/sdlc/inspect.playbook/inspect.registry.d.ts +41 -0
- package/reference/sdlc/inspect.playbook/inspect.registry.js +51 -0
- package/reference/sdlc/inspect.playbook/inspect.registry.ts +110 -0
- package/slc/workflow-contracts.json +43 -1
|
@@ -61,23 +61,37 @@ import createDefaultCaptainRuntime, {
|
|
|
61
61
|
type SettlementEvidence,
|
|
62
62
|
} from '../captain.playbook/captain.playbook.js';
|
|
63
63
|
import type { PlaybookSummaryPolicy } from './code.registry.js';
|
|
64
|
+
import type { Attachment } from '@sublang/cligent';
|
|
65
|
+
import { validateAssetRef, validateAssetRefs } from './session-assets.js';
|
|
66
|
+
import type { SessionAssetRef } from './session-assets.js';
|
|
67
|
+
import { QUOTED_EVIDENCE_LIMIT, workerReportExcerpt, workerReportBlock } from './worker-evidence.js';
|
|
64
68
|
|
|
65
69
|
interface SessionAgent {
|
|
70
|
+
/** Mutable next-turn execution capability; never structural identity. */
|
|
71
|
+
readonly browser?: boolean;
|
|
66
72
|
readonly adapter: string;
|
|
67
73
|
readonly model: TuningSelection;
|
|
68
74
|
readonly effort: TuningSelection<Effort>;
|
|
69
75
|
/** Adapter-scoped fast mode. Absence is the provider default; `false` is a
|
|
70
76
|
* literal request, so this carries no provider-default sentinel. */
|
|
71
77
|
readonly fastMode?: boolean;
|
|
72
|
-
/** The model every subagent of this agent runs on (DR-075)
|
|
73
|
-
* provider default. */
|
|
78
|
+
/** The model every subagent of this agent runs on (DR-075), or `inherit`
|
|
79
|
+
* for the agent's own model (DR-076). Absence is the provider default. */
|
|
74
80
|
readonly subagentModel?: string;
|
|
81
|
+
/** The effort every subagent of this agent runs at (DR-076). Absence leaves
|
|
82
|
+
* each subagent's effort to the agent. */
|
|
83
|
+
readonly subagentEffort?: string;
|
|
75
84
|
readonly instruction?: string;
|
|
76
85
|
readonly permissions?: PermissionPolicy;
|
|
77
86
|
}
|
|
78
87
|
|
|
79
88
|
/** Per-call tuning: erased from every structural envelope and snapshot. */
|
|
80
|
-
type TuningKey =
|
|
89
|
+
type TuningKey =
|
|
90
|
+
| 'model'
|
|
91
|
+
| 'effort'
|
|
92
|
+
| 'fastMode'
|
|
93
|
+
| 'subagentModel'
|
|
94
|
+
| 'subagentEffort';
|
|
81
95
|
|
|
82
96
|
interface PlayerLedgerEntry {
|
|
83
97
|
readonly adapter: string;
|
|
@@ -113,13 +127,13 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
113
127
|
readonly unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[];
|
|
114
128
|
}
|
|
115
129
|
|
|
116
|
-
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey>>;
|
|
130
|
+
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey | 'browser'>>;
|
|
117
131
|
|
|
118
132
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
119
133
|
|
|
120
134
|
export interface ProgressChange {
|
|
121
135
|
snapshot?: PlaybookCaptainShellSnapshot | null;
|
|
122
|
-
step?: Omit<PlaybookStepRecord, 'kind'> & { kind: PlaybookStepRecord['kind'] | 'preparation' | 'completion' | 'answer'; runtimeSessionId: string; playbookId: string };
|
|
136
|
+
step?: Omit<PlaybookStepRecord, 'kind'> & { kind: PlaybookStepRecord['kind'] | 'preparation' | 'completion' | 'answer'; runtimeSessionId: string; playbookId: string; workerEvidence?: { boundaryId: string; playerId: string } };
|
|
123
137
|
}
|
|
124
138
|
export interface InterruptedReport {
|
|
125
139
|
text: string;
|
|
@@ -129,6 +143,9 @@ export interface InterruptedReport {
|
|
|
129
143
|
}
|
|
130
144
|
|
|
131
145
|
export interface PlaybookCaptainDeps {
|
|
146
|
+
onTurnAttachments?: (references: readonly SessionAssetRef[]) => void;
|
|
147
|
+
beginPreparationMedia?: (runtimeSessionId: string) => () => void;
|
|
148
|
+
resolveAttachments?: (references: readonly SessionAssetRef[], signal: AbortSignal) => Promise<readonly Attachment[]>;
|
|
132
149
|
/** Stop the host's active turn, including admitted tool calls, on preparation expiry or a required save failure. */
|
|
133
150
|
abortPreparation?: (reason?: string) => void;
|
|
134
151
|
recordProgress?: (change: ProgressChange) => Promise<void>;
|
|
@@ -251,6 +268,7 @@ export interface PlaybookCaptainFrameSnapshot {
|
|
|
251
268
|
readonly parentCallId?: string;
|
|
252
269
|
readonly request?: string;
|
|
253
270
|
readonly inputs?: readonly string[];
|
|
271
|
+
readonly attachments?: readonly SessionAssetRef[];
|
|
254
272
|
readonly options: JsonValue;
|
|
255
273
|
readonly roleBindings: Readonly<Record<string, string>>;
|
|
256
274
|
readonly runtime: DeepReadonly<PlaybookRuntimeSnapshot>;
|
|
@@ -274,6 +292,7 @@ interface PlaybookCaptainShellSnapshotFields {
|
|
|
274
292
|
readonly journal: number;
|
|
275
293
|
};
|
|
276
294
|
readonly journal: readonly PlaybookCaptainJournalRecord[];
|
|
295
|
+
readonly pendingAttachments?: readonly SessionAssetRef[];
|
|
277
296
|
readonly lastAction?:
|
|
278
297
|
| 'respond'
|
|
279
298
|
| 'start'
|
|
@@ -380,6 +399,9 @@ export interface PlaybookCaptainSettlement {
|
|
|
380
399
|
|
|
381
400
|
/** tmux and headless front ends share this one durable Captain shell API. */
|
|
382
401
|
export interface PlaybookCaptainShell extends Captain {
|
|
402
|
+
/** Bind already-owned evidence to the next visible Boss turn without changing its text. */
|
|
403
|
+
setTurnAttachments(references: readonly SessionAssetRef[]): void;
|
|
404
|
+
recordMediaEvidence(reference: SessionAssetRef, runtimeSessionId?: string): void;
|
|
383
405
|
installRetainedGenerations(
|
|
384
406
|
generations: Readonly<
|
|
385
407
|
Record<string, PlaybookCaptainRetainedGeneration>
|
|
@@ -452,6 +474,7 @@ interface EffectivePlayerBinding {
|
|
|
452
474
|
readonly effort: TuningSelection<Effort>;
|
|
453
475
|
readonly fastMode?: boolean;
|
|
454
476
|
readonly subagentModel?: string;
|
|
477
|
+
readonly subagentEffort?: string;
|
|
455
478
|
readonly agent: SessionAgent;
|
|
456
479
|
}
|
|
457
480
|
|
|
@@ -479,6 +502,8 @@ type PlayerTransaction =
|
|
|
479
502
|
});
|
|
480
503
|
|
|
481
504
|
interface EngagementFrame {
|
|
505
|
+
effectOwnerSessionId?: string;
|
|
506
|
+
attachments?: readonly SessionAssetRef[];
|
|
482
507
|
request?: string;
|
|
483
508
|
inputs?: string[];
|
|
484
509
|
pauseCancellation?: () => () => void;
|
|
@@ -668,6 +693,12 @@ interface ActiveTurnSummary {
|
|
|
668
693
|
|
|
669
694
|
/** The shell state of one Boss turn (DR-029). */
|
|
670
695
|
interface ActiveTurn {
|
|
696
|
+
readonly workerReports: Map<string, string>;
|
|
697
|
+
readonly workerStepStarts: Map<string, ReadonlySet<string>>;
|
|
698
|
+
readonly workerCalls: Map<string, { frame: EngagementFrame; callId: string; roleId: string; playerId: string; stateId: string; finalText: string }>;
|
|
699
|
+
evidence?: readonly SessionAssetRef[];
|
|
700
|
+
attachments: readonly SessionAssetRef[];
|
|
701
|
+
selectedAttachments?: readonly SessionAssetRef[];
|
|
671
702
|
appliedActionCount: number;
|
|
672
703
|
startedEngagement?: boolean;
|
|
673
704
|
countedSummary?: ActiveTurnSummary;
|
|
@@ -838,7 +869,6 @@ function quoteEvidence(text: string): string {
|
|
|
838
869
|
// into a fact and still knows it is foreign — never at the digest renderer,
|
|
839
870
|
// which sees an opaque payload and cannot tell quoted output from the Boss
|
|
840
871
|
// text, captain speech, or settlement facts the shell authored itself.
|
|
841
|
-
const QUOTED_EVIDENCE_LIMIT = 400;
|
|
842
872
|
|
|
843
873
|
// The same guard for strings the shell interpolates into a single-line fact:
|
|
844
874
|
// control characters collapse to spaces so a quoted message can never open a
|
|
@@ -3002,6 +3032,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3002
3032
|
'issuedSessionIds',
|
|
3003
3033
|
'sequences',
|
|
3004
3034
|
'journal',
|
|
3035
|
+
'pendingAttachments',
|
|
3005
3036
|
'lastAction',
|
|
3006
3037
|
'lastSettlementStatus',
|
|
3007
3038
|
'mode',
|
|
@@ -3330,6 +3361,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3330
3361
|
issuedSessionIds: issued,
|
|
3331
3362
|
sequences: { turn: turnSequence, journal: journalSequence },
|
|
3332
3363
|
journal: normalizedJournal,
|
|
3364
|
+
...(snapshot.pendingAttachments === undefined ? {} : { pendingAttachments: validateAssetRefs(snapshot.pendingAttachments) }),
|
|
3333
3365
|
...(lastAction === undefined ? {} : { lastAction }),
|
|
3334
3366
|
...(lastSettlementStatus === undefined
|
|
3335
3367
|
? {}
|
|
@@ -3381,6 +3413,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3381
3413
|
'parentCallId',
|
|
3382
3414
|
'request',
|
|
3383
3415
|
'inputs',
|
|
3416
|
+
'attachments',
|
|
3384
3417
|
'options',
|
|
3385
3418
|
'roleBindings',
|
|
3386
3419
|
'runtime',
|
|
@@ -3438,6 +3471,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3438
3471
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
3439
3472
|
...(frame.request === undefined ? {} : { request: frame.request as string }),
|
|
3440
3473
|
...(frame.inputs === undefined ? {} : { inputs: frame.inputs as string[] }),
|
|
3474
|
+
...(frame.attachments === undefined ? {} : { attachments: validateAssetRefs(frame.attachments) }),
|
|
3441
3475
|
options,
|
|
3442
3476
|
roleBindings,
|
|
3443
3477
|
runtime,
|
|
@@ -3727,7 +3761,7 @@ function snapshotFastMode(
|
|
|
3727
3761
|
return value;
|
|
3728
3762
|
}
|
|
3729
3763
|
|
|
3730
|
-
function
|
|
3764
|
+
function snapshotNonblankTuning(
|
|
3731
3765
|
value: JsonValue | undefined,
|
|
3732
3766
|
path: string,
|
|
3733
3767
|
): string | undefined {
|
|
@@ -3753,6 +3787,8 @@ function snapshotSessionAgent(
|
|
|
3753
3787
|
'effort',
|
|
3754
3788
|
'fastMode',
|
|
3755
3789
|
'subagentModel',
|
|
3790
|
+
'subagentEffort',
|
|
3791
|
+
'browser',
|
|
3756
3792
|
'instruction',
|
|
3757
3793
|
'permissions',
|
|
3758
3794
|
],
|
|
@@ -3765,7 +3801,9 @@ function snapshotSessionAgent(
|
|
|
3765
3801
|
key !== 'model' &&
|
|
3766
3802
|
key !== 'effort' &&
|
|
3767
3803
|
key !== 'fastMode' &&
|
|
3768
|
-
key !== 'subagentModel'
|
|
3804
|
+
key !== 'subagentModel' &&
|
|
3805
|
+
key !== 'subagentEffort' &&
|
|
3806
|
+
key !== 'browser',
|
|
3769
3807
|
),
|
|
3770
3808
|
) as JsonValue,
|
|
3771
3809
|
path,
|
|
@@ -3778,6 +3816,7 @@ function snapshotSessionAgent(
|
|
|
3778
3816
|
...(fixed.permissions === undefined
|
|
3779
3817
|
? {}
|
|
3780
3818
|
: { permissions: livePermissions(fixed.permissions) }),
|
|
3819
|
+
...(agent.browser === undefined ? {} : { browser: snapshotFastMode(agent.browser, `${path}.browser`) }),
|
|
3781
3820
|
model: snapshotTuningSelection(agent.model, `${path}.model`),
|
|
3782
3821
|
effort: snapshotEffortSelection(agent.effort, `${path}.effort`),
|
|
3783
3822
|
...(agent.fastMode === undefined
|
|
@@ -3786,15 +3825,23 @@ function snapshotSessionAgent(
|
|
|
3786
3825
|
...(agent.subagentModel === undefined
|
|
3787
3826
|
? {}
|
|
3788
3827
|
: {
|
|
3789
|
-
subagentModel:
|
|
3828
|
+
subagentModel: snapshotNonblankTuning(
|
|
3790
3829
|
agent.subagentModel,
|
|
3791
3830
|
`${path}.subagentModel`,
|
|
3792
3831
|
),
|
|
3793
3832
|
}),
|
|
3833
|
+
...(agent.subagentEffort === undefined
|
|
3834
|
+
? {}
|
|
3835
|
+
: {
|
|
3836
|
+
subagentEffort: snapshotNonblankTuning(
|
|
3837
|
+
agent.subagentEffort,
|
|
3838
|
+
`${path}.subagentEffort`,
|
|
3839
|
+
),
|
|
3840
|
+
}),
|
|
3794
3841
|
};
|
|
3795
3842
|
}
|
|
3796
3843
|
|
|
3797
|
-
function fixedAgent(agent: SessionAgent): Omit<SessionAgent, TuningKey> {
|
|
3844
|
+
function fixedAgent(agent: SessionAgent): Omit<SessionAgent, TuningKey | 'browser'> {
|
|
3798
3845
|
return {
|
|
3799
3846
|
adapter: agent.adapter,
|
|
3800
3847
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
@@ -3808,19 +3855,29 @@ function callSettings(
|
|
|
3808
3855
|
): AgentCallSettings {
|
|
3809
3856
|
// cligent treats supplied call settings as a complete replacement, so an
|
|
3810
3857
|
// omitted fastMode or subagentModel here is a request for the provider
|
|
3811
|
-
// default,
|
|
3858
|
+
// default, and an omitted subagentEffort leaves each subagent's effort to
|
|
3859
|
+
// the agent — never an inheritance of whatever the previous call left
|
|
3860
|
+
// behind.
|
|
3812
3861
|
return {
|
|
3813
3862
|
model: tuning.model,
|
|
3814
3863
|
effort: tuning.effort,
|
|
3864
|
+
browser: agent.browser ?? false,
|
|
3815
3865
|
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
3816
3866
|
...(tuning.subagentModel === undefined
|
|
3817
3867
|
? {}
|
|
3818
3868
|
: { subagentModel: tuning.subagentModel }),
|
|
3869
|
+
...(tuning.subagentEffort === undefined
|
|
3870
|
+
? {}
|
|
3871
|
+
: { subagentEffort: tuning.subagentEffort as Effort }),
|
|
3819
3872
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
3820
3873
|
...(agent.permissions === undefined ? {} : { permissions: agent.permissions }),
|
|
3821
3874
|
};
|
|
3822
3875
|
}
|
|
3823
3876
|
|
|
3877
|
+
function controlCallSettings(agent: SessionAgent): AgentCallSettings {
|
|
3878
|
+
return { ...callSettings(agent), browser: false, mcpServers: {} };
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3824
3881
|
function promptIdentity(binding: EffectivePlayerBinding): string {
|
|
3825
3882
|
return binding.model.kind === 'value'
|
|
3826
3883
|
? binding.model.value
|
|
@@ -4043,7 +4100,14 @@ async function buildEnablements(
|
|
|
4043
4100
|
const rawBinding = snapshotRecord(roleRecord[role], path);
|
|
4044
4101
|
rejectSnapshotKeys(
|
|
4045
4102
|
rawBinding,
|
|
4046
|
-
[
|
|
4103
|
+
[
|
|
4104
|
+
'playerId',
|
|
4105
|
+
'model',
|
|
4106
|
+
'effort',
|
|
4107
|
+
'fastMode',
|
|
4108
|
+
'subagentModel',
|
|
4109
|
+
'subagentEffort',
|
|
4110
|
+
],
|
|
4047
4111
|
path,
|
|
4048
4112
|
);
|
|
4049
4113
|
const playerId = snapshotString(rawBinding.playerId, `${path}.playerId`);
|
|
@@ -4071,11 +4135,19 @@ async function buildEnablements(
|
|
|
4071
4135
|
...(rawBinding.subagentModel === undefined
|
|
4072
4136
|
? {}
|
|
4073
4137
|
: {
|
|
4074
|
-
subagentModel:
|
|
4138
|
+
subagentModel: snapshotNonblankTuning(
|
|
4075
4139
|
rawBinding.subagentModel,
|
|
4076
4140
|
`${path}.subagentModel`,
|
|
4077
4141
|
),
|
|
4078
4142
|
}),
|
|
4143
|
+
...(rawBinding.subagentEffort === undefined
|
|
4144
|
+
? {}
|
|
4145
|
+
: {
|
|
4146
|
+
subagentEffort: snapshotNonblankTuning(
|
|
4147
|
+
rawBinding.subagentEffort,
|
|
4148
|
+
`${path}.subagentEffort`,
|
|
4149
|
+
),
|
|
4150
|
+
}),
|
|
4079
4151
|
agent,
|
|
4080
4152
|
});
|
|
4081
4153
|
}
|
|
@@ -4382,6 +4454,17 @@ export function createPlaybookCaptainShell(
|
|
|
4382
4454
|
> = deps.createCaptainRuntime ?? createDefaultCaptainRuntime;
|
|
4383
4455
|
const unresolvedEffectSettlement = deps.unresolvedEffectSettlement;
|
|
4384
4456
|
const continuity = deps.continuity;
|
|
4457
|
+
const resolveAttachments = deps.resolveAttachments;
|
|
4458
|
+
const beginPreparationMedia = deps.beginPreparationMedia;
|
|
4459
|
+
const onTurnAttachments = deps.onTurnAttachments;
|
|
4460
|
+
let nextTurnAttachments: readonly SessionAssetRef[] = [];
|
|
4461
|
+
let pendingAttachments: readonly SessionAssetRef[] = [];
|
|
4462
|
+
const mergeAttachments = (...groups: readonly (readonly SessionAssetRef[])[]) => [...new Map(groups.flat().map((reference) => [reference.assetId, reference])).values()];
|
|
4463
|
+
const attachmentPaths = async (references: readonly SessionAssetRef[] | undefined, signal: AbortSignal) => {
|
|
4464
|
+
if (!references?.length) return undefined;
|
|
4465
|
+
if (!resolveAttachments) throw new Error('The session host cannot materialize required attachments');
|
|
4466
|
+
return resolveAttachments(references, signal);
|
|
4467
|
+
};
|
|
4385
4468
|
const abortPreparation = deps.abortPreparation;
|
|
4386
4469
|
let pendingHostCapabilities = deps.hostCapabilities;
|
|
4387
4470
|
let currentEffectLedger = () => emptyPlaybookEffectLedger();
|
|
@@ -4840,6 +4923,7 @@ export function createPlaybookCaptainShell(
|
|
|
4840
4923
|
'parentCallId',
|
|
4841
4924
|
'request',
|
|
4842
4925
|
'inputs',
|
|
4926
|
+
'attachments',
|
|
4843
4927
|
'options',
|
|
4844
4928
|
'roleBindings',
|
|
4845
4929
|
'runtime',
|
|
@@ -4989,6 +5073,7 @@ export function createPlaybookCaptainShell(
|
|
|
4989
5073
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
4990
5074
|
...(frame.request === undefined ? {} : { request: frame.request as string }),
|
|
4991
5075
|
...(frame.inputs === undefined ? {} : { inputs: frame.inputs as string[] }),
|
|
5076
|
+
...(frame.attachments === undefined ? {} : { attachments: validateAssetRefs(frame.attachments) }),
|
|
4992
5077
|
options,
|
|
4993
5078
|
roleBindings,
|
|
4994
5079
|
runtime,
|
|
@@ -5488,15 +5573,21 @@ export function createPlaybookCaptainShell(
|
|
|
5488
5573
|
prompt: string,
|
|
5489
5574
|
options: Parameters<CaptainContext['callCaptain']>[1],
|
|
5490
5575
|
signal: AbortSignal,
|
|
5576
|
+
media?: { readonly attachments?: readonly SessionAssetRef[]; readonly preparation?: true },
|
|
5491
5577
|
): ReturnType<CaptainContext['callCaptain']> => {
|
|
5492
5578
|
const queued = captainQueue.add(async () => {
|
|
5493
5579
|
signal.throwIfAborted();
|
|
5494
|
-
const
|
|
5495
|
-
frame,
|
|
5496
|
-
context.callCaptain(prompt, options),
|
|
5497
|
-
);
|
|
5580
|
+
const attachments = media?.attachments?.length ? await attachmentPaths(media.attachments, signal) : undefined;
|
|
5498
5581
|
signal.throwIfAborted();
|
|
5499
|
-
|
|
5582
|
+
const endMedia = media?.preparation ? beginPreparationMedia?.(frame.sessionId) : undefined;
|
|
5583
|
+
try {
|
|
5584
|
+
const result = await trackHostCall(
|
|
5585
|
+
frame,
|
|
5586
|
+
context.callCaptain(prompt, { ...options, ...(attachments === undefined ? {} : { attachments }) }),
|
|
5587
|
+
);
|
|
5588
|
+
signal.throwIfAborted();
|
|
5589
|
+
return result;
|
|
5590
|
+
} finally { endMedia?.(); }
|
|
5500
5591
|
});
|
|
5501
5592
|
return trackHostCall(frame, queued);
|
|
5502
5593
|
};
|
|
@@ -5582,12 +5673,20 @@ export function createPlaybookCaptainShell(
|
|
|
5582
5673
|
!Number.isSafeInteger(trace.turnId) ||
|
|
5583
5674
|
(trace.turnId as number) <= 0 ||
|
|
5584
5675
|
!Number.isSafeInteger(trace.sequence) ||
|
|
5585
|
-
(trace.sequence as number) <= 0
|
|
5586
|
-
summary === undefined ||
|
|
5587
|
-
!summaryIncludes(frame)
|
|
5676
|
+
(trace.sequence as number) <= 0
|
|
5588
5677
|
) {
|
|
5589
5678
|
return;
|
|
5590
5679
|
}
|
|
5680
|
+
if (trace.type === 'player.call.finished') {
|
|
5681
|
+
const call = payloadRecord(trace.payload);
|
|
5682
|
+
if (typeof trace.callId === 'string' && call?.status === 'ok' &&
|
|
5683
|
+
typeof call.roleId === 'string' && typeof call.playerId === 'string' &&
|
|
5684
|
+
frame.playerBindings.get(call.roleId)?.playerId === call.playerId &&
|
|
5685
|
+
typeof call.stateId === 'string' && typeof call.finalText === 'string') {
|
|
5686
|
+
turn.workerCalls.set(`${frame.sessionId}:${trace.callId}`, { frame, callId: trace.callId, roleId: call.roleId, playerId: call.playerId, stateId: call.stateId, finalText: call.finalText });
|
|
5687
|
+
}
|
|
5688
|
+
return;
|
|
5689
|
+
}
|
|
5591
5690
|
if (trace.type !== 'outcome.accepted') return;
|
|
5592
5691
|
const receipt = exactOwnDataRecord(trace.payload, [
|
|
5593
5692
|
'source',
|
|
@@ -5605,6 +5704,21 @@ export function createPlaybookCaptainShell(
|
|
|
5605
5704
|
) {
|
|
5606
5705
|
return;
|
|
5607
5706
|
}
|
|
5707
|
+
const ledger = currentEffectLedger();
|
|
5708
|
+
const previous = turn.progressBase?.effectLedger;
|
|
5709
|
+
for (const call of turn.workerCalls.values()) {
|
|
5710
|
+
if (call.frame !== frame || call.stateId !== receipt.source) continue;
|
|
5711
|
+
const boundary = [...ledger.boundaries].reverse().find((entry) =>
|
|
5712
|
+
entry.playbookId === frame.entry.id &&
|
|
5713
|
+
entry.runtimeSessionId === (frame.effectOwnerSessionId ?? frame.sessionId) &&
|
|
5714
|
+
entry.callId === call.callId && entry.roleId === call.roleId &&
|
|
5715
|
+
entry.sourceStateId === call.stateId && entry.finalText === call.finalText &&
|
|
5716
|
+
!isDeepStrictEqual(entry, previous?.boundaries[entry.sequence - 1]));
|
|
5717
|
+
if (!boundary || turn.workerReports.has(boundary.boundaryId)) continue;
|
|
5718
|
+
const report = workerReportExcerpt(boundary, call.playerId, receipt.acceptedOutcome);
|
|
5719
|
+
if (report !== undefined) turn.workerReports.set(boundary.boundaryId, report);
|
|
5720
|
+
}
|
|
5721
|
+
if (summary === undefined || !summaryIncludes(frame)) return;
|
|
5608
5722
|
const traceKey = `${frame.sessionId}:${trace.sequence}`;
|
|
5609
5723
|
if (summary.acceptedOutcomeTraceKeys.has(traceKey)) return;
|
|
5610
5724
|
summary.acceptedOutcomeTraceKeys.add(traceKey);
|
|
@@ -5632,8 +5746,28 @@ export function createPlaybookCaptainShell(
|
|
|
5632
5746
|
const createPorts = (frame: EngagementFrame): PlaybookPorts => ({
|
|
5633
5747
|
...(recordProgress === undefined ? {} : { recordStep: async (step: PlaybookStepRecord, position?: PlaybookRuntimeSnapshot) => {
|
|
5634
5748
|
journalledFrames.add(frame);
|
|
5749
|
+
const turn = activeTurn;
|
|
5750
|
+
if (step.kind === 'player' && step.result === undefined) {
|
|
5751
|
+
turn?.workerStepStarts.set(step.id, new Set(turn.workerCalls.keys()));
|
|
5752
|
+
}
|
|
5753
|
+
let workerEvidence: { boundaryId: string; playerId: string } | undefined;
|
|
5754
|
+
const guard = payloadRecord(step.result)?.guard;
|
|
5755
|
+
const before = turn?.workerStepStarts.get(step.id);
|
|
5756
|
+
if (step.kind === 'player' && typeof guard === 'string' && before !== undefined && turn) {
|
|
5757
|
+
const candidates = [...turn.workerCalls.entries()].flatMap(([key, call]) => {
|
|
5758
|
+
if (before.has(key) || call.frame !== frame || call.stateId !== step.stateId) return [];
|
|
5759
|
+
return currentEffectLedger().boundaries.filter((boundary) =>
|
|
5760
|
+
boundary.playbookId === frame.entry.id && boundary.sourceStateId === step.stateId &&
|
|
5761
|
+
boundary.runtimeSessionId === (frame.effectOwnerSessionId ?? frame.sessionId) &&
|
|
5762
|
+
boundary.callId === call.callId && boundary.roleId === call.roleId && boundary.finalText === call.finalText &&
|
|
5763
|
+
!isDeepStrictEqual(boundary, turn.progressBase?.effectLedger.boundaries[boundary.sequence - 1]) &&
|
|
5764
|
+
workerReportExcerpt(boundary, call.playerId, guard) !== undefined
|
|
5765
|
+
).map((boundary) => ({ boundaryId: boundary.boundaryId, playerId: call.playerId }));
|
|
5766
|
+
});
|
|
5767
|
+
if (candidates.length === 1) workerEvidence = candidates[0];
|
|
5768
|
+
}
|
|
5635
5769
|
await saveProgress(step.result === undefined ? { frame, position } : undefined,
|
|
5636
|
-
{ ...step, runtimeSessionId: frame.sessionId, playbookId: frame.entry.id });
|
|
5770
|
+
{ ...step, runtimeSessionId: frame.sessionId, playbookId: frame.entry.id, ...(workerEvidence === undefined ? {} : { workerEvidence }) });
|
|
5637
5771
|
} }),
|
|
5638
5772
|
callPlayer: async (roleId, prompt, signal, options) => {
|
|
5639
5773
|
admitHostBoundary();
|
|
@@ -5666,6 +5800,7 @@ export function createPlaybookCaptainShell(
|
|
|
5666
5800
|
);
|
|
5667
5801
|
}
|
|
5668
5802
|
const settings = callSettings(binding.agent, binding);
|
|
5803
|
+
const attachments = await attachmentPaths(frame.attachments, signal);
|
|
5669
5804
|
const calling: PlayerTransaction = {
|
|
5670
5805
|
phase: 'calling',
|
|
5671
5806
|
frame,
|
|
@@ -5692,7 +5827,7 @@ export function createPlaybookCaptainShell(
|
|
|
5692
5827
|
const raw = await trackHostCall(
|
|
5693
5828
|
frame,
|
|
5694
5829
|
classifySettingsCall(() =>
|
|
5695
|
-
context.callPlayer(binding.playerId, resume === false ? options.freshPrompt ?? prompt : prompt, { resume, settings }),
|
|
5830
|
+
context.callPlayer(binding.playerId, resume === false ? options.freshPrompt ?? prompt : prompt, { resume, settings, ...(attachments === undefined ? {} : { attachments }) }),
|
|
5696
5831
|
),
|
|
5697
5832
|
);
|
|
5698
5833
|
hostResolved = true;
|
|
@@ -5823,9 +5958,11 @@ export function createPlaybookCaptainShell(
|
|
|
5823
5958
|
{
|
|
5824
5959
|
visibility: options.visibility,
|
|
5825
5960
|
resume: options.resume,
|
|
5961
|
+
settings: options.allowedTools?.length === 0 ? controlCallSettings(captainAgent!) : callSettings(captainAgent!),
|
|
5826
5962
|
...forwardedToolOptions(options.allowedTools, captainAdapter),
|
|
5827
5963
|
},
|
|
5828
5964
|
signal,
|
|
5965
|
+
{ attachments: options.allowedTools?.length === 0 ? undefined : frame.attachments },
|
|
5829
5966
|
);
|
|
5830
5967
|
const output = {
|
|
5831
5968
|
status: result.status,
|
|
@@ -5847,6 +5984,7 @@ export function createPlaybookCaptainShell(
|
|
|
5847
5984
|
activeContext,
|
|
5848
5985
|
hiddenJudgeEnvelope(prompt),
|
|
5849
5986
|
{
|
|
5987
|
+
settings: controlCallSettings(captainAgent!),
|
|
5850
5988
|
visibility: 'hidden',
|
|
5851
5989
|
resume: false,
|
|
5852
5990
|
...controlCallToolOptions(captainAdapter),
|
|
@@ -6010,6 +6148,7 @@ export function createPlaybookCaptainShell(
|
|
|
6010
6148
|
runtime,
|
|
6011
6149
|
sessionId,
|
|
6012
6150
|
rootSessionId: parent?.frame.rootSessionId ?? sessionId,
|
|
6151
|
+
...((parent?.frame.attachments ?? activeTurn?.selectedAttachments ?? activeTurn?.attachments)?.length ? { attachments: [...(parent?.frame.attachments ?? activeTurn?.selectedAttachments ?? activeTurn!.attachments)] } : {}),
|
|
6013
6152
|
depth: parent ? parent.frame.depth + 1 : 0,
|
|
6014
6153
|
playerBindings,
|
|
6015
6154
|
...(parent ? { parent } : {}),
|
|
@@ -6033,12 +6172,14 @@ export function createPlaybookCaptainShell(
|
|
|
6033
6172
|
enablement,
|
|
6034
6173
|
runtime,
|
|
6035
6174
|
sessionId: snapshot.sessionId,
|
|
6175
|
+
effectOwnerSessionId: snapshot.runtime.retainedEffectSourceSessionId ?? snapshot.sessionId,
|
|
6036
6176
|
rootSessionId: snapshot.rootSessionId,
|
|
6037
6177
|
depth: snapshot.depth,
|
|
6038
6178
|
playerBindings,
|
|
6039
6179
|
...(parent ? { parent } : {}),
|
|
6040
6180
|
...(snapshot.request === undefined ? {} : { request: snapshot.request }),
|
|
6041
6181
|
...(snapshot.inputs === undefined ? {} : { inputs: [...snapshot.inputs] }),
|
|
6182
|
+
...(snapshot.attachments === undefined ? {} : { attachments: [...snapshot.attachments] }),
|
|
6042
6183
|
state: snapshot.runtime.state,
|
|
6043
6184
|
inFlightHostCalls: new Set(),
|
|
6044
6185
|
};
|
|
@@ -6345,6 +6486,7 @@ export function createPlaybookCaptainShell(
|
|
|
6345
6486
|
} finally {
|
|
6346
6487
|
if (leafFrame() === frame) {
|
|
6347
6488
|
frames.pop();
|
|
6489
|
+
if (!frame.parent) pendingAttachments = [];
|
|
6348
6490
|
if (frame.parent) {
|
|
6349
6491
|
pendingChildParents.delete(frame.parent.frame);
|
|
6350
6492
|
}
|
|
@@ -7860,11 +8002,11 @@ export function createPlaybookCaptainShell(
|
|
|
7860
8002
|
context.signal.throwIfAborted();
|
|
7861
8003
|
attempt.providerBoundaryEntered = true;
|
|
7862
8004
|
const result = await classifySettingsCall(() =>
|
|
7863
|
-
context.callCaptain(prompt, {
|
|
8005
|
+
context.callCaptain(pendingAttachments.length || activeTurn?.attachments.length || activeTurn?.evidence?.length || leafFrame()?.attachments?.length ? `${prompt}\n\nAttachment context (metadata only; do not claim to have viewed these bytes): ${JSON.stringify({ current: activeTurn?.attachments ?? [], pending: pendingAttachments, engagement: leafFrame()?.attachments ?? [], evidence: activeTurn?.evidence ?? [] })}` : prompt, {
|
|
7864
8006
|
visibility: 'hidden',
|
|
7865
8007
|
resume,
|
|
7866
8008
|
...controlCallToolOptions(captainAdapter),
|
|
7867
|
-
settings:
|
|
8009
|
+
settings: controlCallSettings(captainAgent!),
|
|
7868
8010
|
}),
|
|
7869
8011
|
);
|
|
7870
8012
|
context.signal.throwIfAborted();
|
|
@@ -8083,6 +8225,9 @@ export function createPlaybookCaptainShell(
|
|
|
8083
8225
|
...(kind === 'decision'
|
|
8084
8226
|
? [labeledBlock('Catalog digest', catalogDigest())]
|
|
8085
8227
|
: []),
|
|
8228
|
+
...(kind === 'closingReply' && turn?.workerReports.size
|
|
8229
|
+
? [workerReportBlock([...turn.workerReports.values()])!]
|
|
8230
|
+
: []),
|
|
8086
8231
|
...(kind === 'closingReply' && turn?.report
|
|
8087
8232
|
? [
|
|
8088
8233
|
labeledBlock('ControlView digest', controlViewDigest()),
|
|
@@ -8467,6 +8612,7 @@ export function createPlaybookCaptainShell(
|
|
|
8467
8612
|
enablement,
|
|
8468
8613
|
runtime: offer.runtimes[index]!,
|
|
8469
8614
|
sessionId: targetSessionIds[index]!,
|
|
8615
|
+
effectOwnerSessionId: sourceFrame.runtime.retainedEffectSourceSessionId ?? sourceFrame.sessionId,
|
|
8470
8616
|
rootSessionId: targetRootSessionId,
|
|
8471
8617
|
depth: index,
|
|
8472
8618
|
playerBindings: makePlayerBindings(enablement),
|
|
@@ -8475,6 +8621,7 @@ export function createPlaybookCaptainShell(
|
|
|
8475
8621
|
: {}),
|
|
8476
8622
|
...(sourceFrame.request === undefined ? {} : { request: sourceFrame.request }),
|
|
8477
8623
|
...(sourceFrame.inputs === undefined ? {} : { inputs: [...sourceFrame.inputs] }),
|
|
8624
|
+
...(sourceFrame.attachments === undefined ? {} : { attachments: [...sourceFrame.attachments] }),
|
|
8478
8625
|
state: sourceFrame.runtime.state,
|
|
8479
8626
|
inFlightHostCalls: new Set(),
|
|
8480
8627
|
});
|
|
@@ -9138,6 +9285,11 @@ export function createPlaybookCaptainShell(
|
|
|
9138
9285
|
);
|
|
9139
9286
|
}
|
|
9140
9287
|
}
|
|
9288
|
+
const selectedIds = selection.attachmentIds;
|
|
9289
|
+
const available = new Map(mergeAttachments(pendingAttachments, turn.attachments, leafFrame()?.attachments ?? []).map((reference) => [reference.assetId, reference]));
|
|
9290
|
+
if (selectedIds?.some((id) => !available.has(id as SessionAssetRef['assetId'])) || (selectedIds && new Set(selectedIds).size !== selectedIds.length)) return rejectSelection(selection, 'the request selects unavailable attachment evidence');
|
|
9291
|
+
turn.selectedAttachments = selectedIds === undefined ? turn.attachments : selectedIds.map((id) => available.get(id as SessionAssetRef['assetId'])!);
|
|
9292
|
+
pendingAttachments = [];
|
|
9141
9293
|
turn.settled = true;
|
|
9142
9294
|
journalAction({
|
|
9143
9295
|
action: selection.action,
|
|
@@ -9285,7 +9437,7 @@ export function createPlaybookCaptainShell(
|
|
|
9285
9437
|
`Later Boss input (context only): ${JSON.stringify(laterInputs)}`,
|
|
9286
9438
|
`Existing instructions: ${JSON.stringify(instructions)}`,
|
|
9287
9439
|
`Pending questions (quoted evidence): ${JSON.stringify(before.pendingQuestions.map(({ question }) => question))}`,
|
|
9288
|
-
].join('\n')), { visibility: 'hidden', resume: false, ...controlCallToolOptions(captainAdapter), settings:
|
|
9440
|
+
].join('\n')), { visibility: 'hidden', resume: false, ...controlCallToolOptions(captainAdapter), settings: controlCallSettings(captainAgent!) }, signal);
|
|
9289
9441
|
const answer = result.status === 'ok' ? controlReplyJson(result.finalText) : undefined;
|
|
9290
9442
|
const index = answer && typeof answer === 'object' && !Array.isArray(answer) && Object.keys(answer).join(',') === 'instructionIndex'
|
|
9291
9443
|
? (answer as { instructionIndex: unknown }).instructionIndex : undefined;
|
|
@@ -9344,8 +9496,9 @@ export function createPlaybookCaptainShell(
|
|
|
9344
9496
|
claim = await capability.acquire({ signal: preparationSignal }) as NonNullable<typeof claim>;
|
|
9345
9497
|
await claim.assertOwner();
|
|
9346
9498
|
const result = await runEffect(() => callCaptainQueued(
|
|
9347
|
-
|
|
9348
|
-
|
|
9499
|
+
leaf, context, preparationPrompt,
|
|
9500
|
+
{ visibility: 'hidden', resume: false, settings: callSettings(captainAgent!) }, preparationSignal,
|
|
9501
|
+
{ attachments: leaf.attachments, preparation: true },
|
|
9349
9502
|
));
|
|
9350
9503
|
preparationSignal.throwIfAborted();
|
|
9351
9504
|
await claim.assertOwner();
|
|
@@ -9390,6 +9543,11 @@ export function createPlaybookCaptainShell(
|
|
|
9390
9543
|
}
|
|
9391
9544
|
|
|
9392
9545
|
if (selection.action === 'deliver') {
|
|
9546
|
+
const available = new Map(mergeAttachments(pendingAttachments, turn.attachments, leaf.attachments ?? []).map((reference) => [reference.assetId, reference]));
|
|
9547
|
+
if (selection.attachmentIds?.some((id) => !available.has(id as SessionAssetRef['assetId'])) || (selection.attachmentIds && new Set(selection.attachmentIds).size !== selection.attachmentIds.length)) return rejectSelection(selection, 'the request selects unavailable attachment evidence');
|
|
9548
|
+
const selected = selection.attachmentIds === undefined ? turn.attachments : selection.attachmentIds.map((id) => available.get(id as SessionAssetRef['assetId'])!);
|
|
9549
|
+
const nextAttachments = mergeAttachments(leaf.attachments ?? [], selected);
|
|
9550
|
+
if (nextAttachments.length) leaf.attachments = nextAttachments;
|
|
9393
9551
|
// CAPTAIN-8: delivery carries text only, and the shell is authoritative
|
|
9394
9552
|
// for that text — any text carried on the selection is ignored.
|
|
9395
9553
|
turn.settled = true;
|
|
@@ -10038,6 +10196,7 @@ export function createPlaybookCaptainShell(
|
|
|
10038
10196
|
: {}),
|
|
10039
10197
|
...(frame.request === undefined ? {} : { request: frame.request }),
|
|
10040
10198
|
...(frame.inputs === undefined ? {} : { inputs: [...frame.inputs] }),
|
|
10199
|
+
...(frame.attachments === undefined ? {} : { attachments: [...frame.attachments] }),
|
|
10041
10200
|
options: frame.enablement.options,
|
|
10042
10201
|
roleBindings: Object.fromEntries(
|
|
10043
10202
|
[...frame.playerBindings].map(([role, binding]) => [
|
|
@@ -10306,6 +10465,7 @@ export function createPlaybookCaptainShell(
|
|
|
10306
10465
|
issuedSessionIds: [...issuedSessionIds],
|
|
10307
10466
|
sequences: { turn: turnSequence, journal: journalSeq },
|
|
10308
10467
|
journal,
|
|
10468
|
+
...(pendingAttachments.length ? { pendingAttachments } : {}),
|
|
10309
10469
|
...(lastAction === undefined ? {} : { lastAction }),
|
|
10310
10470
|
...(lastSettlementStatus === undefined
|
|
10311
10471
|
? {}
|
|
@@ -10644,6 +10804,7 @@ export function createPlaybookCaptainShell(
|
|
|
10644
10804
|
}
|
|
10645
10805
|
for (const id of snapshot.issuedSessionIds) issuedSessionIds.add(id);
|
|
10646
10806
|
journal.push(...snapshot.journal);
|
|
10807
|
+
pendingAttachments = [...(snapshot.pendingAttachments ?? [])];
|
|
10647
10808
|
journalSeq = snapshot.sequences.journal;
|
|
10648
10809
|
turnSequence = snapshot.sequences.turn;
|
|
10649
10810
|
conversation = snapshot.captain.conversation;
|
|
@@ -10802,10 +10963,29 @@ export function createPlaybookCaptainShell(
|
|
|
10802
10963
|
submitShellAction: selectShellAction,
|
|
10803
10964
|
selectInterruptedReport(input, report) {
|
|
10804
10965
|
if (activeTurn) throw new Error('A Captain turn is already active');
|
|
10805
|
-
if (!input.trim() || !report.text.trim()) throw new Error('Interruption report requires its recorded input and explanation');
|
|
10966
|
+
if ((!input.trim() && nextTurnAttachments.length === 0) || !report.text.trim()) throw new Error('Interruption report requires its recorded input and explanation');
|
|
10806
10967
|
pendingHostSelection = { kind: 'report', text: input, report };
|
|
10807
10968
|
},
|
|
10808
10969
|
|
|
10970
|
+
recordMediaEvidence(reference, runtimeSessionId) {
|
|
10971
|
+
const asset = validateAssetRef(reference);
|
|
10972
|
+
if (!activeTurn) throw new Error('Media evidence arrived outside an active turn');
|
|
10973
|
+
activeTurn.evidence = mergeAttachments(activeTurn.evidence ?? [], [asset]);
|
|
10974
|
+
const producingIndex = frames.findIndex((candidate) => candidate.sessionId === runtimeSessionId);
|
|
10975
|
+
// Observed output belongs to this engagement's reasoning context: a
|
|
10976
|
+
// resumed parent may inspect its child's figure. Boss input additions
|
|
10977
|
+
// still target only the addressed leaf, and a new root inherits none.
|
|
10978
|
+
for (const frame of frames.slice(0, producingIndex + 1)) {
|
|
10979
|
+
frame.attachments = mergeAttachments(frame.attachments ?? [], [asset]);
|
|
10980
|
+
}
|
|
10981
|
+
},
|
|
10982
|
+
|
|
10983
|
+
setTurnAttachments(references) {
|
|
10984
|
+
if (activeTurn || activeTurnHostCalls) throw new Error('A Captain turn is already active');
|
|
10985
|
+
nextTurnAttachments = validateAssetRefs(references);
|
|
10986
|
+
onTurnAttachments?.(nextTurnAttachments);
|
|
10987
|
+
},
|
|
10988
|
+
|
|
10809
10989
|
async handleBossTurn(
|
|
10810
10990
|
turn: BossTurn,
|
|
10811
10991
|
context: CaptainContext,
|
|
@@ -10831,7 +11011,9 @@ export function createPlaybookCaptainShell(
|
|
|
10831
11011
|
// telemetry (CAPTAIN-7).
|
|
10832
11012
|
retentionSettlementReady = false;
|
|
10833
11013
|
abandonmentSettlementUnsafe = false;
|
|
10834
|
-
|
|
11014
|
+
const attachments = nextTurnAttachments;
|
|
11015
|
+
nextTurnAttachments = [];
|
|
11016
|
+
if (turn.prompt.trim().length === 0 && attachments.length === 0) return;
|
|
10835
11017
|
const progressBase = exportShellSnapshot();
|
|
10836
11018
|
settledTurnUnresolvedEffects = undefined;
|
|
10837
11019
|
retainedGenerationInstallationClosed = true;
|
|
@@ -10861,6 +11043,10 @@ export function createPlaybookCaptainShell(
|
|
|
10861
11043
|
const parsed =
|
|
10862
11044
|
decided === undefined ? resolveCommandTurn(turn.prompt) : undefined;
|
|
10863
11045
|
activeTurn = {
|
|
11046
|
+
attachments,
|
|
11047
|
+
workerReports: new Map(),
|
|
11048
|
+
workerStepStarts: new Map(),
|
|
11049
|
+
workerCalls: new Map(),
|
|
10864
11050
|
appliedActionCount: 0,
|
|
10865
11051
|
stoppedFrames: new Set<string>(),
|
|
10866
11052
|
...(progressBase === undefined ? {} : { progressBase }),
|
|
@@ -10893,7 +11079,8 @@ export function createPlaybookCaptainShell(
|
|
|
10893
11079
|
}
|
|
10894
11080
|
}
|
|
10895
11081
|
decisionCall = undefined;
|
|
10896
|
-
appendJournal('boss', turn.prompt);
|
|
11082
|
+
appendJournal('boss', attachments.length ? { text: turn.prompt, attachments } as unknown as JsonValue : turn.prompt);
|
|
11083
|
+
pendingAttachments = mergeAttachments(pendingAttachments, attachments);
|
|
10897
11084
|
try {
|
|
10898
11085
|
await drainRetiredRetainedRuntimes();
|
|
10899
11086
|
await prepareRetainedGenerationOffers();
|
|
@@ -10902,6 +11089,11 @@ export function createPlaybookCaptainShell(
|
|
|
10902
11089
|
await settleSelection({ action: 'respond', text: activeTurn.interruptedReport.text }, context.signal);
|
|
10903
11090
|
return;
|
|
10904
11091
|
}
|
|
11092
|
+
if (!turn.prompt.trim() && attachments.length) {
|
|
11093
|
+
markConversationCatchUp();
|
|
11094
|
+
await settleSelection({ action: 'respond', text: 'What would you like me to do with the attached material?' }, context.signal);
|
|
11095
|
+
return;
|
|
11096
|
+
}
|
|
10905
11097
|
const result = await captainRuntime.handleBossInput({
|
|
10906
11098
|
text: turn.prompt,
|
|
10907
11099
|
signal: context.signal,
|