@sublang/playbook 17.3.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/configuration.md +45 -4
- 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 +2 -0
- package/reference/sdlc/code.playbook/bin/portable-codec.js +12 -5
- package/reference/sdlc/code.playbook/bin/run.js +68 -4
- 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 +139 -13
- package/reference/sdlc/code.playbook/playbook-captain.d.ts +17 -1
- package/reference/sdlc/code.playbook/playbook-captain.js +159 -18
- package/reference/sdlc/code.playbook/playbook-captain.ts +176 -23
- package/reference/sdlc/code.playbook/playbook.config.template.yaml +16 -3
- 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,8 +61,14 @@ 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>;
|
|
@@ -121,13 +127,13 @@ interface PlaybookCaptainUnresolvedEffectSettlementInput {
|
|
|
121
127
|
readonly unresolvedEffects: readonly PlaybookCaptainUnresolvedEffect[];
|
|
122
128
|
}
|
|
123
129
|
|
|
124
|
-
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey>>;
|
|
130
|
+
type SnapshotAgentEnvelope = DeepReadonly<Omit<SessionAgent, TuningKey | 'browser'>>;
|
|
125
131
|
|
|
126
132
|
type PlayerLedgerSnapshotEntry = DeepReadonly<PlayerLedgerEntry>;
|
|
127
133
|
|
|
128
134
|
export interface ProgressChange {
|
|
129
135
|
snapshot?: PlaybookCaptainShellSnapshot | null;
|
|
130
|
-
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 } };
|
|
131
137
|
}
|
|
132
138
|
export interface InterruptedReport {
|
|
133
139
|
text: string;
|
|
@@ -137,6 +143,9 @@ export interface InterruptedReport {
|
|
|
137
143
|
}
|
|
138
144
|
|
|
139
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[]>;
|
|
140
149
|
/** Stop the host's active turn, including admitted tool calls, on preparation expiry or a required save failure. */
|
|
141
150
|
abortPreparation?: (reason?: string) => void;
|
|
142
151
|
recordProgress?: (change: ProgressChange) => Promise<void>;
|
|
@@ -259,6 +268,7 @@ export interface PlaybookCaptainFrameSnapshot {
|
|
|
259
268
|
readonly parentCallId?: string;
|
|
260
269
|
readonly request?: string;
|
|
261
270
|
readonly inputs?: readonly string[];
|
|
271
|
+
readonly attachments?: readonly SessionAssetRef[];
|
|
262
272
|
readonly options: JsonValue;
|
|
263
273
|
readonly roleBindings: Readonly<Record<string, string>>;
|
|
264
274
|
readonly runtime: DeepReadonly<PlaybookRuntimeSnapshot>;
|
|
@@ -282,6 +292,7 @@ interface PlaybookCaptainShellSnapshotFields {
|
|
|
282
292
|
readonly journal: number;
|
|
283
293
|
};
|
|
284
294
|
readonly journal: readonly PlaybookCaptainJournalRecord[];
|
|
295
|
+
readonly pendingAttachments?: readonly SessionAssetRef[];
|
|
285
296
|
readonly lastAction?:
|
|
286
297
|
| 'respond'
|
|
287
298
|
| 'start'
|
|
@@ -388,6 +399,9 @@ export interface PlaybookCaptainSettlement {
|
|
|
388
399
|
|
|
389
400
|
/** tmux and headless front ends share this one durable Captain shell API. */
|
|
390
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;
|
|
391
405
|
installRetainedGenerations(
|
|
392
406
|
generations: Readonly<
|
|
393
407
|
Record<string, PlaybookCaptainRetainedGeneration>
|
|
@@ -488,6 +502,8 @@ type PlayerTransaction =
|
|
|
488
502
|
});
|
|
489
503
|
|
|
490
504
|
interface EngagementFrame {
|
|
505
|
+
effectOwnerSessionId?: string;
|
|
506
|
+
attachments?: readonly SessionAssetRef[];
|
|
491
507
|
request?: string;
|
|
492
508
|
inputs?: string[];
|
|
493
509
|
pauseCancellation?: () => () => void;
|
|
@@ -677,6 +693,12 @@ interface ActiveTurnSummary {
|
|
|
677
693
|
|
|
678
694
|
/** The shell state of one Boss turn (DR-029). */
|
|
679
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[];
|
|
680
702
|
appliedActionCount: number;
|
|
681
703
|
startedEngagement?: boolean;
|
|
682
704
|
countedSummary?: ActiveTurnSummary;
|
|
@@ -847,7 +869,6 @@ function quoteEvidence(text: string): string {
|
|
|
847
869
|
// into a fact and still knows it is foreign — never at the digest renderer,
|
|
848
870
|
// which sees an opaque payload and cannot tell quoted output from the Boss
|
|
849
871
|
// text, captain speech, or settlement facts the shell authored itself.
|
|
850
|
-
const QUOTED_EVIDENCE_LIMIT = 400;
|
|
851
872
|
|
|
852
873
|
// The same guard for strings the shell interpolates into a single-line fact:
|
|
853
874
|
// control characters collapse to spaces so a quoted message can never open a
|
|
@@ -3011,6 +3032,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3011
3032
|
'issuedSessionIds',
|
|
3012
3033
|
'sequences',
|
|
3013
3034
|
'journal',
|
|
3035
|
+
'pendingAttachments',
|
|
3014
3036
|
'lastAction',
|
|
3015
3037
|
'lastSettlementStatus',
|
|
3016
3038
|
'mode',
|
|
@@ -3339,6 +3361,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3339
3361
|
issuedSessionIds: issued,
|
|
3340
3362
|
sequences: { turn: turnSequence, journal: journalSequence },
|
|
3341
3363
|
journal: normalizedJournal,
|
|
3364
|
+
...(snapshot.pendingAttachments === undefined ? {} : { pendingAttachments: validateAssetRefs(snapshot.pendingAttachments) }),
|
|
3342
3365
|
...(lastAction === undefined ? {} : { lastAction }),
|
|
3343
3366
|
...(lastSettlementStatus === undefined
|
|
3344
3367
|
? {}
|
|
@@ -3390,6 +3413,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3390
3413
|
'parentCallId',
|
|
3391
3414
|
'request',
|
|
3392
3415
|
'inputs',
|
|
3416
|
+
'attachments',
|
|
3393
3417
|
'options',
|
|
3394
3418
|
'roleBindings',
|
|
3395
3419
|
'runtime',
|
|
@@ -3447,6 +3471,7 @@ export function assertPlaybookCaptainShellSnapshot(
|
|
|
3447
3471
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
3448
3472
|
...(frame.request === undefined ? {} : { request: frame.request as string }),
|
|
3449
3473
|
...(frame.inputs === undefined ? {} : { inputs: frame.inputs as string[] }),
|
|
3474
|
+
...(frame.attachments === undefined ? {} : { attachments: validateAssetRefs(frame.attachments) }),
|
|
3450
3475
|
options,
|
|
3451
3476
|
roleBindings,
|
|
3452
3477
|
runtime,
|
|
@@ -3763,6 +3788,7 @@ function snapshotSessionAgent(
|
|
|
3763
3788
|
'fastMode',
|
|
3764
3789
|
'subagentModel',
|
|
3765
3790
|
'subagentEffort',
|
|
3791
|
+
'browser',
|
|
3766
3792
|
'instruction',
|
|
3767
3793
|
'permissions',
|
|
3768
3794
|
],
|
|
@@ -3776,7 +3802,8 @@ function snapshotSessionAgent(
|
|
|
3776
3802
|
key !== 'effort' &&
|
|
3777
3803
|
key !== 'fastMode' &&
|
|
3778
3804
|
key !== 'subagentModel' &&
|
|
3779
|
-
key !== 'subagentEffort'
|
|
3805
|
+
key !== 'subagentEffort' &&
|
|
3806
|
+
key !== 'browser',
|
|
3780
3807
|
),
|
|
3781
3808
|
) as JsonValue,
|
|
3782
3809
|
path,
|
|
@@ -3789,6 +3816,7 @@ function snapshotSessionAgent(
|
|
|
3789
3816
|
...(fixed.permissions === undefined
|
|
3790
3817
|
? {}
|
|
3791
3818
|
: { permissions: livePermissions(fixed.permissions) }),
|
|
3819
|
+
...(agent.browser === undefined ? {} : { browser: snapshotFastMode(agent.browser, `${path}.browser`) }),
|
|
3792
3820
|
model: snapshotTuningSelection(agent.model, `${path}.model`),
|
|
3793
3821
|
effort: snapshotEffortSelection(agent.effort, `${path}.effort`),
|
|
3794
3822
|
...(agent.fastMode === undefined
|
|
@@ -3813,7 +3841,7 @@ function snapshotSessionAgent(
|
|
|
3813
3841
|
};
|
|
3814
3842
|
}
|
|
3815
3843
|
|
|
3816
|
-
function fixedAgent(agent: SessionAgent): Omit<SessionAgent, TuningKey> {
|
|
3844
|
+
function fixedAgent(agent: SessionAgent): Omit<SessionAgent, TuningKey | 'browser'> {
|
|
3817
3845
|
return {
|
|
3818
3846
|
adapter: agent.adapter,
|
|
3819
3847
|
...(agent.instruction === undefined ? {} : { instruction: agent.instruction }),
|
|
@@ -3833,6 +3861,7 @@ function callSettings(
|
|
|
3833
3861
|
return {
|
|
3834
3862
|
model: tuning.model,
|
|
3835
3863
|
effort: tuning.effort,
|
|
3864
|
+
browser: agent.browser ?? false,
|
|
3836
3865
|
...(tuning.fastMode === undefined ? {} : { fastMode: tuning.fastMode }),
|
|
3837
3866
|
...(tuning.subagentModel === undefined
|
|
3838
3867
|
? {}
|
|
@@ -3845,6 +3874,10 @@ function callSettings(
|
|
|
3845
3874
|
};
|
|
3846
3875
|
}
|
|
3847
3876
|
|
|
3877
|
+
function controlCallSettings(agent: SessionAgent): AgentCallSettings {
|
|
3878
|
+
return { ...callSettings(agent), browser: false, mcpServers: {} };
|
|
3879
|
+
}
|
|
3880
|
+
|
|
3848
3881
|
function promptIdentity(binding: EffectivePlayerBinding): string {
|
|
3849
3882
|
return binding.model.kind === 'value'
|
|
3850
3883
|
? binding.model.value
|
|
@@ -4421,6 +4454,17 @@ export function createPlaybookCaptainShell(
|
|
|
4421
4454
|
> = deps.createCaptainRuntime ?? createDefaultCaptainRuntime;
|
|
4422
4455
|
const unresolvedEffectSettlement = deps.unresolvedEffectSettlement;
|
|
4423
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
|
+
};
|
|
4424
4468
|
const abortPreparation = deps.abortPreparation;
|
|
4425
4469
|
let pendingHostCapabilities = deps.hostCapabilities;
|
|
4426
4470
|
let currentEffectLedger = () => emptyPlaybookEffectLedger();
|
|
@@ -4879,6 +4923,7 @@ export function createPlaybookCaptainShell(
|
|
|
4879
4923
|
'parentCallId',
|
|
4880
4924
|
'request',
|
|
4881
4925
|
'inputs',
|
|
4926
|
+
'attachments',
|
|
4882
4927
|
'options',
|
|
4883
4928
|
'roleBindings',
|
|
4884
4929
|
'runtime',
|
|
@@ -5028,6 +5073,7 @@ export function createPlaybookCaptainShell(
|
|
|
5028
5073
|
...(parentCallId === undefined ? {} : { parentCallId }),
|
|
5029
5074
|
...(frame.request === undefined ? {} : { request: frame.request as string }),
|
|
5030
5075
|
...(frame.inputs === undefined ? {} : { inputs: frame.inputs as string[] }),
|
|
5076
|
+
...(frame.attachments === undefined ? {} : { attachments: validateAssetRefs(frame.attachments) }),
|
|
5031
5077
|
options,
|
|
5032
5078
|
roleBindings,
|
|
5033
5079
|
runtime,
|
|
@@ -5527,15 +5573,21 @@ export function createPlaybookCaptainShell(
|
|
|
5527
5573
|
prompt: string,
|
|
5528
5574
|
options: Parameters<CaptainContext['callCaptain']>[1],
|
|
5529
5575
|
signal: AbortSignal,
|
|
5576
|
+
media?: { readonly attachments?: readonly SessionAssetRef[]; readonly preparation?: true },
|
|
5530
5577
|
): ReturnType<CaptainContext['callCaptain']> => {
|
|
5531
5578
|
const queued = captainQueue.add(async () => {
|
|
5532
5579
|
signal.throwIfAborted();
|
|
5533
|
-
const
|
|
5534
|
-
frame,
|
|
5535
|
-
context.callCaptain(prompt, options),
|
|
5536
|
-
);
|
|
5580
|
+
const attachments = media?.attachments?.length ? await attachmentPaths(media.attachments, signal) : undefined;
|
|
5537
5581
|
signal.throwIfAborted();
|
|
5538
|
-
|
|
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?.(); }
|
|
5539
5591
|
});
|
|
5540
5592
|
return trackHostCall(frame, queued);
|
|
5541
5593
|
};
|
|
@@ -5621,12 +5673,20 @@ export function createPlaybookCaptainShell(
|
|
|
5621
5673
|
!Number.isSafeInteger(trace.turnId) ||
|
|
5622
5674
|
(trace.turnId as number) <= 0 ||
|
|
5623
5675
|
!Number.isSafeInteger(trace.sequence) ||
|
|
5624
|
-
(trace.sequence as number) <= 0
|
|
5625
|
-
summary === undefined ||
|
|
5626
|
-
!summaryIncludes(frame)
|
|
5676
|
+
(trace.sequence as number) <= 0
|
|
5627
5677
|
) {
|
|
5628
5678
|
return;
|
|
5629
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
|
+
}
|
|
5630
5690
|
if (trace.type !== 'outcome.accepted') return;
|
|
5631
5691
|
const receipt = exactOwnDataRecord(trace.payload, [
|
|
5632
5692
|
'source',
|
|
@@ -5644,6 +5704,21 @@ export function createPlaybookCaptainShell(
|
|
|
5644
5704
|
) {
|
|
5645
5705
|
return;
|
|
5646
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;
|
|
5647
5722
|
const traceKey = `${frame.sessionId}:${trace.sequence}`;
|
|
5648
5723
|
if (summary.acceptedOutcomeTraceKeys.has(traceKey)) return;
|
|
5649
5724
|
summary.acceptedOutcomeTraceKeys.add(traceKey);
|
|
@@ -5671,8 +5746,28 @@ export function createPlaybookCaptainShell(
|
|
|
5671
5746
|
const createPorts = (frame: EngagementFrame): PlaybookPorts => ({
|
|
5672
5747
|
...(recordProgress === undefined ? {} : { recordStep: async (step: PlaybookStepRecord, position?: PlaybookRuntimeSnapshot) => {
|
|
5673
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
|
+
}
|
|
5674
5769
|
await saveProgress(step.result === undefined ? { frame, position } : undefined,
|
|
5675
|
-
{ ...step, runtimeSessionId: frame.sessionId, playbookId: frame.entry.id });
|
|
5770
|
+
{ ...step, runtimeSessionId: frame.sessionId, playbookId: frame.entry.id, ...(workerEvidence === undefined ? {} : { workerEvidence }) });
|
|
5676
5771
|
} }),
|
|
5677
5772
|
callPlayer: async (roleId, prompt, signal, options) => {
|
|
5678
5773
|
admitHostBoundary();
|
|
@@ -5705,6 +5800,7 @@ export function createPlaybookCaptainShell(
|
|
|
5705
5800
|
);
|
|
5706
5801
|
}
|
|
5707
5802
|
const settings = callSettings(binding.agent, binding);
|
|
5803
|
+
const attachments = await attachmentPaths(frame.attachments, signal);
|
|
5708
5804
|
const calling: PlayerTransaction = {
|
|
5709
5805
|
phase: 'calling',
|
|
5710
5806
|
frame,
|
|
@@ -5731,7 +5827,7 @@ export function createPlaybookCaptainShell(
|
|
|
5731
5827
|
const raw = await trackHostCall(
|
|
5732
5828
|
frame,
|
|
5733
5829
|
classifySettingsCall(() =>
|
|
5734
|
-
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 }) }),
|
|
5735
5831
|
),
|
|
5736
5832
|
);
|
|
5737
5833
|
hostResolved = true;
|
|
@@ -5862,9 +5958,11 @@ export function createPlaybookCaptainShell(
|
|
|
5862
5958
|
{
|
|
5863
5959
|
visibility: options.visibility,
|
|
5864
5960
|
resume: options.resume,
|
|
5961
|
+
settings: options.allowedTools?.length === 0 ? controlCallSettings(captainAgent!) : callSettings(captainAgent!),
|
|
5865
5962
|
...forwardedToolOptions(options.allowedTools, captainAdapter),
|
|
5866
5963
|
},
|
|
5867
5964
|
signal,
|
|
5965
|
+
{ attachments: options.allowedTools?.length === 0 ? undefined : frame.attachments },
|
|
5868
5966
|
);
|
|
5869
5967
|
const output = {
|
|
5870
5968
|
status: result.status,
|
|
@@ -5886,6 +5984,7 @@ export function createPlaybookCaptainShell(
|
|
|
5886
5984
|
activeContext,
|
|
5887
5985
|
hiddenJudgeEnvelope(prompt),
|
|
5888
5986
|
{
|
|
5987
|
+
settings: controlCallSettings(captainAgent!),
|
|
5889
5988
|
visibility: 'hidden',
|
|
5890
5989
|
resume: false,
|
|
5891
5990
|
...controlCallToolOptions(captainAdapter),
|
|
@@ -6049,6 +6148,7 @@ export function createPlaybookCaptainShell(
|
|
|
6049
6148
|
runtime,
|
|
6050
6149
|
sessionId,
|
|
6051
6150
|
rootSessionId: parent?.frame.rootSessionId ?? sessionId,
|
|
6151
|
+
...((parent?.frame.attachments ?? activeTurn?.selectedAttachments ?? activeTurn?.attachments)?.length ? { attachments: [...(parent?.frame.attachments ?? activeTurn?.selectedAttachments ?? activeTurn!.attachments)] } : {}),
|
|
6052
6152
|
depth: parent ? parent.frame.depth + 1 : 0,
|
|
6053
6153
|
playerBindings,
|
|
6054
6154
|
...(parent ? { parent } : {}),
|
|
@@ -6072,12 +6172,14 @@ export function createPlaybookCaptainShell(
|
|
|
6072
6172
|
enablement,
|
|
6073
6173
|
runtime,
|
|
6074
6174
|
sessionId: snapshot.sessionId,
|
|
6175
|
+
effectOwnerSessionId: snapshot.runtime.retainedEffectSourceSessionId ?? snapshot.sessionId,
|
|
6075
6176
|
rootSessionId: snapshot.rootSessionId,
|
|
6076
6177
|
depth: snapshot.depth,
|
|
6077
6178
|
playerBindings,
|
|
6078
6179
|
...(parent ? { parent } : {}),
|
|
6079
6180
|
...(snapshot.request === undefined ? {} : { request: snapshot.request }),
|
|
6080
6181
|
...(snapshot.inputs === undefined ? {} : { inputs: [...snapshot.inputs] }),
|
|
6182
|
+
...(snapshot.attachments === undefined ? {} : { attachments: [...snapshot.attachments] }),
|
|
6081
6183
|
state: snapshot.runtime.state,
|
|
6082
6184
|
inFlightHostCalls: new Set(),
|
|
6083
6185
|
};
|
|
@@ -6384,6 +6486,7 @@ export function createPlaybookCaptainShell(
|
|
|
6384
6486
|
} finally {
|
|
6385
6487
|
if (leafFrame() === frame) {
|
|
6386
6488
|
frames.pop();
|
|
6489
|
+
if (!frame.parent) pendingAttachments = [];
|
|
6387
6490
|
if (frame.parent) {
|
|
6388
6491
|
pendingChildParents.delete(frame.parent.frame);
|
|
6389
6492
|
}
|
|
@@ -7899,11 +8002,11 @@ export function createPlaybookCaptainShell(
|
|
|
7899
8002
|
context.signal.throwIfAborted();
|
|
7900
8003
|
attempt.providerBoundaryEntered = true;
|
|
7901
8004
|
const result = await classifySettingsCall(() =>
|
|
7902
|
-
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, {
|
|
7903
8006
|
visibility: 'hidden',
|
|
7904
8007
|
resume,
|
|
7905
8008
|
...controlCallToolOptions(captainAdapter),
|
|
7906
|
-
settings:
|
|
8009
|
+
settings: controlCallSettings(captainAgent!),
|
|
7907
8010
|
}),
|
|
7908
8011
|
);
|
|
7909
8012
|
context.signal.throwIfAborted();
|
|
@@ -8122,6 +8225,9 @@ export function createPlaybookCaptainShell(
|
|
|
8122
8225
|
...(kind === 'decision'
|
|
8123
8226
|
? [labeledBlock('Catalog digest', catalogDigest())]
|
|
8124
8227
|
: []),
|
|
8228
|
+
...(kind === 'closingReply' && turn?.workerReports.size
|
|
8229
|
+
? [workerReportBlock([...turn.workerReports.values()])!]
|
|
8230
|
+
: []),
|
|
8125
8231
|
...(kind === 'closingReply' && turn?.report
|
|
8126
8232
|
? [
|
|
8127
8233
|
labeledBlock('ControlView digest', controlViewDigest()),
|
|
@@ -8506,6 +8612,7 @@ export function createPlaybookCaptainShell(
|
|
|
8506
8612
|
enablement,
|
|
8507
8613
|
runtime: offer.runtimes[index]!,
|
|
8508
8614
|
sessionId: targetSessionIds[index]!,
|
|
8615
|
+
effectOwnerSessionId: sourceFrame.runtime.retainedEffectSourceSessionId ?? sourceFrame.sessionId,
|
|
8509
8616
|
rootSessionId: targetRootSessionId,
|
|
8510
8617
|
depth: index,
|
|
8511
8618
|
playerBindings: makePlayerBindings(enablement),
|
|
@@ -8514,6 +8621,7 @@ export function createPlaybookCaptainShell(
|
|
|
8514
8621
|
: {}),
|
|
8515
8622
|
...(sourceFrame.request === undefined ? {} : { request: sourceFrame.request }),
|
|
8516
8623
|
...(sourceFrame.inputs === undefined ? {} : { inputs: [...sourceFrame.inputs] }),
|
|
8624
|
+
...(sourceFrame.attachments === undefined ? {} : { attachments: [...sourceFrame.attachments] }),
|
|
8517
8625
|
state: sourceFrame.runtime.state,
|
|
8518
8626
|
inFlightHostCalls: new Set(),
|
|
8519
8627
|
});
|
|
@@ -9177,6 +9285,11 @@ export function createPlaybookCaptainShell(
|
|
|
9177
9285
|
);
|
|
9178
9286
|
}
|
|
9179
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 = [];
|
|
9180
9293
|
turn.settled = true;
|
|
9181
9294
|
journalAction({
|
|
9182
9295
|
action: selection.action,
|
|
@@ -9324,7 +9437,7 @@ export function createPlaybookCaptainShell(
|
|
|
9324
9437
|
`Later Boss input (context only): ${JSON.stringify(laterInputs)}`,
|
|
9325
9438
|
`Existing instructions: ${JSON.stringify(instructions)}`,
|
|
9326
9439
|
`Pending questions (quoted evidence): ${JSON.stringify(before.pendingQuestions.map(({ question }) => question))}`,
|
|
9327
|
-
].join('\n')), { visibility: 'hidden', resume: false, ...controlCallToolOptions(captainAdapter), settings:
|
|
9440
|
+
].join('\n')), { visibility: 'hidden', resume: false, ...controlCallToolOptions(captainAdapter), settings: controlCallSettings(captainAgent!) }, signal);
|
|
9328
9441
|
const answer = result.status === 'ok' ? controlReplyJson(result.finalText) : undefined;
|
|
9329
9442
|
const index = answer && typeof answer === 'object' && !Array.isArray(answer) && Object.keys(answer).join(',') === 'instructionIndex'
|
|
9330
9443
|
? (answer as { instructionIndex: unknown }).instructionIndex : undefined;
|
|
@@ -9383,8 +9496,9 @@ export function createPlaybookCaptainShell(
|
|
|
9383
9496
|
claim = await capability.acquire({ signal: preparationSignal }) as NonNullable<typeof claim>;
|
|
9384
9497
|
await claim.assertOwner();
|
|
9385
9498
|
const result = await runEffect(() => callCaptainQueued(
|
|
9386
|
-
|
|
9387
|
-
|
|
9499
|
+
leaf, context, preparationPrompt,
|
|
9500
|
+
{ visibility: 'hidden', resume: false, settings: callSettings(captainAgent!) }, preparationSignal,
|
|
9501
|
+
{ attachments: leaf.attachments, preparation: true },
|
|
9388
9502
|
));
|
|
9389
9503
|
preparationSignal.throwIfAborted();
|
|
9390
9504
|
await claim.assertOwner();
|
|
@@ -9429,6 +9543,11 @@ export function createPlaybookCaptainShell(
|
|
|
9429
9543
|
}
|
|
9430
9544
|
|
|
9431
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;
|
|
9432
9551
|
// CAPTAIN-8: delivery carries text only, and the shell is authoritative
|
|
9433
9552
|
// for that text — any text carried on the selection is ignored.
|
|
9434
9553
|
turn.settled = true;
|
|
@@ -10077,6 +10196,7 @@ export function createPlaybookCaptainShell(
|
|
|
10077
10196
|
: {}),
|
|
10078
10197
|
...(frame.request === undefined ? {} : { request: frame.request }),
|
|
10079
10198
|
...(frame.inputs === undefined ? {} : { inputs: [...frame.inputs] }),
|
|
10199
|
+
...(frame.attachments === undefined ? {} : { attachments: [...frame.attachments] }),
|
|
10080
10200
|
options: frame.enablement.options,
|
|
10081
10201
|
roleBindings: Object.fromEntries(
|
|
10082
10202
|
[...frame.playerBindings].map(([role, binding]) => [
|
|
@@ -10345,6 +10465,7 @@ export function createPlaybookCaptainShell(
|
|
|
10345
10465
|
issuedSessionIds: [...issuedSessionIds],
|
|
10346
10466
|
sequences: { turn: turnSequence, journal: journalSeq },
|
|
10347
10467
|
journal,
|
|
10468
|
+
...(pendingAttachments.length ? { pendingAttachments } : {}),
|
|
10348
10469
|
...(lastAction === undefined ? {} : { lastAction }),
|
|
10349
10470
|
...(lastSettlementStatus === undefined
|
|
10350
10471
|
? {}
|
|
@@ -10683,6 +10804,7 @@ export function createPlaybookCaptainShell(
|
|
|
10683
10804
|
}
|
|
10684
10805
|
for (const id of snapshot.issuedSessionIds) issuedSessionIds.add(id);
|
|
10685
10806
|
journal.push(...snapshot.journal);
|
|
10807
|
+
pendingAttachments = [...(snapshot.pendingAttachments ?? [])];
|
|
10686
10808
|
journalSeq = snapshot.sequences.journal;
|
|
10687
10809
|
turnSequence = snapshot.sequences.turn;
|
|
10688
10810
|
conversation = snapshot.captain.conversation;
|
|
@@ -10841,10 +10963,29 @@ export function createPlaybookCaptainShell(
|
|
|
10841
10963
|
submitShellAction: selectShellAction,
|
|
10842
10964
|
selectInterruptedReport(input, report) {
|
|
10843
10965
|
if (activeTurn) throw new Error('A Captain turn is already active');
|
|
10844
|
-
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');
|
|
10845
10967
|
pendingHostSelection = { kind: 'report', text: input, report };
|
|
10846
10968
|
},
|
|
10847
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
|
+
|
|
10848
10989
|
async handleBossTurn(
|
|
10849
10990
|
turn: BossTurn,
|
|
10850
10991
|
context: CaptainContext,
|
|
@@ -10870,7 +11011,9 @@ export function createPlaybookCaptainShell(
|
|
|
10870
11011
|
// telemetry (CAPTAIN-7).
|
|
10871
11012
|
retentionSettlementReady = false;
|
|
10872
11013
|
abandonmentSettlementUnsafe = false;
|
|
10873
|
-
|
|
11014
|
+
const attachments = nextTurnAttachments;
|
|
11015
|
+
nextTurnAttachments = [];
|
|
11016
|
+
if (turn.prompt.trim().length === 0 && attachments.length === 0) return;
|
|
10874
11017
|
const progressBase = exportShellSnapshot();
|
|
10875
11018
|
settledTurnUnresolvedEffects = undefined;
|
|
10876
11019
|
retainedGenerationInstallationClosed = true;
|
|
@@ -10900,6 +11043,10 @@ export function createPlaybookCaptainShell(
|
|
|
10900
11043
|
const parsed =
|
|
10901
11044
|
decided === undefined ? resolveCommandTurn(turn.prompt) : undefined;
|
|
10902
11045
|
activeTurn = {
|
|
11046
|
+
attachments,
|
|
11047
|
+
workerReports: new Map(),
|
|
11048
|
+
workerStepStarts: new Map(),
|
|
11049
|
+
workerCalls: new Map(),
|
|
10903
11050
|
appliedActionCount: 0,
|
|
10904
11051
|
stoppedFrames: new Set<string>(),
|
|
10905
11052
|
...(progressBase === undefined ? {} : { progressBase }),
|
|
@@ -10932,7 +11079,8 @@ export function createPlaybookCaptainShell(
|
|
|
10932
11079
|
}
|
|
10933
11080
|
}
|
|
10934
11081
|
decisionCall = undefined;
|
|
10935
|
-
appendJournal('boss', turn.prompt);
|
|
11082
|
+
appendJournal('boss', attachments.length ? { text: turn.prompt, attachments } as unknown as JsonValue : turn.prompt);
|
|
11083
|
+
pendingAttachments = mergeAttachments(pendingAttachments, attachments);
|
|
10936
11084
|
try {
|
|
10937
11085
|
await drainRetiredRetainedRuntimes();
|
|
10938
11086
|
await prepareRetainedGenerationOffers();
|
|
@@ -10941,6 +11089,11 @@ export function createPlaybookCaptainShell(
|
|
|
10941
11089
|
await settleSelection({ action: 'respond', text: activeTurn.interruptedReport.text }, context.signal);
|
|
10942
11090
|
return;
|
|
10943
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
|
+
}
|
|
10944
11097
|
const result = await captainRuntime.handleBossInput({
|
|
10945
11098
|
text: turn.prompt,
|
|
10946
11099
|
signal: context.signal,
|
|
@@ -8,15 +8,15 @@
|
|
|
8
8
|
|
|
9
9
|
# The Captain and each stable session player carries its own defaults inline:
|
|
10
10
|
# an adapter shorthand (claude, codex) or a block with
|
|
11
|
-
# adapter/model/effort/fastMode/subagentModel/subagentEffort/permissions. A
|
|
11
|
+
# adapter/model/effort/fastMode/subagentModel/subagentEffort/browser/permissions. A
|
|
12
12
|
# role may override only model, effort, fastMode, subagentModel, or
|
|
13
13
|
# subagentEffort.
|
|
14
14
|
# Omit an override to inherit the player default. For model, effort,
|
|
15
15
|
# subagentModel, and subagentEffort, false selects the provider default;
|
|
16
16
|
# fastMode false is a literal disabled request.
|
|
17
17
|
# A Claude agent delegates by default: its subagents run on its own model
|
|
18
|
-
# (subagentModel: inherit) at efforts it chooses per task;
|
|
19
|
-
# on its block switches delegation off.
|
|
18
|
+
# (subagentModel: inherit), its delegates at efforts it chooses per task;
|
|
19
|
+
# subagentModel: false on its block switches delegation off.
|
|
20
20
|
|
|
21
21
|
# Each adapter needs its vendor SDK installed as its own top-level
|
|
22
22
|
# install root — they are optional peer dependencies, so you pay only
|
|
@@ -77,6 +77,14 @@ players:
|
|
|
77
77
|
permissions:
|
|
78
78
|
mode: auto
|
|
79
79
|
|
|
80
|
+
# Inspection has its own conversation. Browser tools stay off until enabled.
|
|
81
|
+
inspect.inspector:
|
|
82
|
+
adapter: claude
|
|
83
|
+
model: claude-opus-5-5
|
|
84
|
+
effort: high
|
|
85
|
+
permissions:
|
|
86
|
+
mode: auto
|
|
87
|
+
|
|
80
88
|
# Enabled playbooks. Each is loaded from its explicit `from` module; the
|
|
81
89
|
# `<id>` key must equal that module's manifest id. `from`, `command`, and
|
|
82
90
|
# `roles` are launcher-owned; every other key is that playbook's option
|
|
@@ -121,3 +129,8 @@ playbooks:
|
|
|
121
129
|
from: "@sublang/playbook/pr/registry"
|
|
122
130
|
roles:
|
|
123
131
|
coder: dev.coder
|
|
132
|
+
|
|
133
|
+
inspect:
|
|
134
|
+
from: "@sublang/playbook/inspect/registry"
|
|
135
|
+
roles:
|
|
136
|
+
inspector: inspect.inspector
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// SPDX-FileCopyrightText: 2026 SubLang International <https://sublang.ai>
|
|
3
|
+
|
|
4
|
+
import type { AgentEvent, Attachment } from '@sublang/cligent';
|
|
5
|
+
import type { TmuxPlayRecord } from '@sublang/cligent/tmux-play';
|
|
6
|
+
|
|
7
|
+
import type { AssetId, AssetImport, AssetFile, AssetReader, SessionAssetRef, SessionTurnInput } from './asset-types.js';
|
|
8
|
+
export type { AssetId, AssetImport, AssetFile, AssetReader, SessionAssetRef, SessionTurnInput } from './asset-types.js';
|
|
9
|
+
|
|
10
|
+
export interface OwnerAssetStore {
|
|
11
|
+
readonly directory: string;
|
|
12
|
+
/** Verify ownership and tighten Git checkout permissions without changing content. */
|
|
13
|
+
prepare(): Promise<void>;
|
|
14
|
+
importAsset(input: AssetImport): Promise<SessionAssetRef>;
|
|
15
|
+
describeAsset(assetId: AssetId): Promise<SessionAssetRef>;
|
|
16
|
+
openAsset(reference: SessionAssetRef | AssetId, options?: { readonly signal?: AbortSignal }): Promise<AssetReader>;
|
|
17
|
+
readAsset(reference: SessionAssetRef | AssetId, options?: { readonly signal?: AbortSignal }): Promise<Uint8Array>;
|
|
18
|
+
resolveAttachment(reference: SessionAssetRef, options?: { readonly signal?: AbortSignal }): Promise<Attachment>;
|
|
19
|
+
copyAsset(source: Pick<OwnerAssetStore, 'readAsset'>, reference: SessionAssetRef, options?: { readonly signal?: AbortSignal }): Promise<SessionAssetRef>;
|
|
20
|
+
listAssets(): Promise<readonly AssetFile[]>;
|
|
21
|
+
}
|
|
22
|
+
export declare const DEFAULT_MAX_ASSET_BYTES: number;
|
|
23
|
+
export declare function createAssetStore(options: { readonly directory: string; readonly maxAssetBytes?: number }): OwnerAssetStore;
|
|
24
|
+
export declare function validateAssetRef(value: unknown): SessionAssetRef;
|
|
25
|
+
export declare function validateAssetRefs(value: unknown): readonly SessionAssetRef[];
|
|
26
|
+
export declare function normalizeSessionTurnInput(value: string | SessionTurnInput): { readonly text: string; readonly attachments: readonly SessionAssetRef[] };
|
|
27
|
+
export declare function assetUri(reference: SessionAssetRef): string;
|
|
28
|
+
export declare function parseAssetUri(value: string): AssetId | undefined;
|
|
29
|
+
/** Throws if native content cannot be persisted; the caller must report it as unavailable. */
|
|
30
|
+
export declare function externalizeAgentEvent(store: Pick<OwnerAssetStore, 'importAsset'>, event: AgentEvent): Promise<{ readonly event: AgentEvent; readonly asset?: SessionAssetRef }>;
|
|
31
|
+
|
|
32
|
+
export type SessionTurnStartedRecord = Omit<Extract<TmuxPlayRecord, { type: 'turn_started' }>, 'turn'> & {
|
|
33
|
+
readonly turn: Extract<TmuxPlayRecord, { type: 'turn_started' }>['turn'] & {
|
|
34
|
+
readonly attachments?: readonly SessionAssetRef[];
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export interface PlaybookEvidenceRecord {
|
|
38
|
+
readonly type: 'playbook_evidence';
|
|
39
|
+
readonly timestamp: number;
|
|
40
|
+
readonly turnId: number;
|
|
41
|
+
/** Playbook trace call identity, or a host-owned invocation-scope identity. */
|
|
42
|
+
readonly callId: string;
|
|
43
|
+
readonly origin: {
|
|
44
|
+
readonly kind: 'player' | 'preparation';
|
|
45
|
+
readonly actorId: string;
|
|
46
|
+
/** Producing Playbook runtime identity, not the native provider session. */
|
|
47
|
+
readonly runtimeSessionId?: string;
|
|
48
|
+
readonly toolUseId?: string;
|
|
49
|
+
};
|
|
50
|
+
readonly asset: SessionAssetRef;
|
|
51
|
+
}
|
|
52
|
+
/** Known host presentation records. Unknown replay records remain opaque JSON. */
|
|
53
|
+
export type SessionRecord = Exclude<TmuxPlayRecord, { type: 'turn_started' }> | SessionTurnStartedRecord | PlaybookEvidenceRecord;
|