tinker-agent 2.8.0 → 2.9.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/CHANGELOG.md +39 -1
- package/README.md +64 -10
- package/package.json +4 -3
- package/src/agent/runtime-context-capabilities.ts +19 -0
- package/src/agent/runtime-context-events.ts +127 -0
- package/src/agent/runtime-context-maintenance.ts +780 -0
- package/src/agent/runtime-interactions.ts +291 -0
- package/src/agent/runtime-prompt-scheduler.ts +182 -0
- package/src/agent/runtime-session-contracts.ts +317 -0
- package/src/agent/runtime-session.ts +250 -2130
- package/src/agent/runtime-skills.ts +544 -0
- package/src/cli/runner-dependencies.ts +6 -5
- package/src/context/context-automation-policy.ts +12 -118
- package/src/events/types.ts +12 -0
- package/src/memory/memory-get-tool.ts +1 -1
- package/src/observation/observation-builder.ts +41 -11
- package/src/session/resume-projection.ts +47 -21
- package/src/session/session-history-access.ts +238 -0
- package/src/session/session-store-context-readers.ts +183 -0
- package/src/session/session-store-ledger-writer.ts +315 -0
- package/src/session/session-store-record-writer.ts +318 -0
- package/src/session/session-store-recovery.ts +225 -0
- package/src/session/session-store-revisions.ts +1004 -0
- package/src/session/session-store-sql.ts +40 -0
- package/src/session/session-store-validation.ts +657 -0
- package/src/session/session-store.ts +756 -3186
- package/src/tools/bash-task.ts +20 -2
- package/src/tools/recall.ts +106 -50
- package/src/tools/registry.ts +4 -6
- package/src/tools/task-output-range.ts +146 -0
- package/src/tools/task-output-tool.ts +35 -5
- package/src/tools/task-output.ts +35 -0
- package/src/tools/task-tool-args.ts +34 -0
- package/src/tools/types.ts +9 -0
- package/src/tui/event-store.ts +8 -3
|
@@ -1,118 +1,108 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { assertContextMaintenanceCapabilities } from "./runtime-context-capabilities";
|
|
3
|
+
import { CompiledContextError } from "../context/compiled-context-validator";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_CONTEXT_AUTOMATION_POLICY,
|
|
6
|
+
type ContextAutomationPolicy,
|
|
7
|
+
} from "../context/context-automation-policy";
|
|
8
|
+
import {
|
|
9
|
+
ContextManager,
|
|
10
|
+
type ContextCompactionResult,
|
|
11
|
+
type ContextRetirementResult,
|
|
12
|
+
} from "../context/context-manager";
|
|
13
|
+
import { ContextProtocolError } from "../context/context-protocol-validator";
|
|
14
|
+
import type { BuiltContextRequest } from "../context/context-revision";
|
|
15
|
+
import { ContextRevisionError } from "../context/context-revision-compiler";
|
|
16
|
+
import { createContextSurface } from "../context/context-surface";
|
|
17
|
+
import { CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION } from "../context/recall-retirement-contract";
|
|
18
|
+
import { SwapPlanner } from "../context/swap-planner";
|
|
2
19
|
import { CompositeEventSink } from "../events/composite-event-sink";
|
|
3
20
|
import type { EventSink } from "../events/event-sink";
|
|
4
21
|
import { JsonlEventLog } from "../events/jsonl-event-log";
|
|
5
22
|
import { ObservationTextLog } from "../events/observation-text-log";
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
ContextRevisionFinishedData,
|
|
11
|
-
} from "../events/types";
|
|
23
|
+
import type { AgentEvent, AgentEventInput } from "../events/types";
|
|
24
|
+
import { runtimeIdFactory, type SessionId, type TurnId } from "../ids/runtime-id";
|
|
25
|
+
import { ImageAssetStore, type ImportedImageAsset } from "../image/image-asset-store";
|
|
26
|
+
import { IMAGE_INPUT_POLICY } from "../image/image-input-policy";
|
|
12
27
|
import {
|
|
13
|
-
|
|
14
|
-
type
|
|
15
|
-
type
|
|
16
|
-
|
|
17
|
-
type TurnId,
|
|
18
|
-
} from "../ids/runtime-id";
|
|
28
|
+
validateUserMessage,
|
|
29
|
+
type ImageAssetRef,
|
|
30
|
+
type UserMessage,
|
|
31
|
+
} from "../image/image-types";
|
|
19
32
|
import { loadMcpConfig } from "../mcp/mcp-config";
|
|
20
33
|
import {
|
|
21
34
|
createMcpManager,
|
|
22
35
|
type McpInventorySnapshot,
|
|
23
36
|
type McpManager,
|
|
24
37
|
} from "../mcp/mcp-manager";
|
|
38
|
+
import { CommittedPrefixAuditor } from "../model/committed-prefix-auditor";
|
|
25
39
|
import {
|
|
26
40
|
materializeModelRequest,
|
|
27
41
|
ModelRequestMediaAggregateError,
|
|
28
42
|
type MaterializedModelRequest,
|
|
29
|
-
type ModelClient,
|
|
30
43
|
} from "../model/model-client";
|
|
31
|
-
import
|
|
32
|
-
import {
|
|
33
|
-
import { IMAGE_INPUT_POLICY } from "../image/image-input-policy";
|
|
34
|
-
import {
|
|
35
|
-
validateUserMessage,
|
|
36
|
-
type ImageAssetRef,
|
|
37
|
-
type UserMessage,
|
|
38
|
-
} from "../image/image-types";
|
|
39
|
-
import { projectUserMessage } from "./user-prompt-projection";
|
|
40
|
-
import { CommittedPrefixAuditor } from "../model/committed-prefix-auditor";
|
|
41
|
-
import { SwapPlanner } from "../context/swap-planner";
|
|
42
|
-
import {
|
|
43
|
-
commitAgentSkillsContextUpdate,
|
|
44
|
-
ContextManager,
|
|
45
|
-
ContextManagerError,
|
|
46
|
-
type ContextCompactionResult,
|
|
47
|
-
type ContextCompactionTrigger,
|
|
48
|
-
type ContextRetirementResult,
|
|
49
|
-
type ContextRetirementTrigger,
|
|
50
|
-
} from "../context/context-manager";
|
|
51
|
-
import {
|
|
52
|
-
assertMatchingContextBudget,
|
|
53
|
-
type ModelContextBudget,
|
|
54
|
-
type ModelContextProfile,
|
|
55
|
-
} from "../model/model-context-profile";
|
|
44
|
+
import { assertMatchingContextBudget } from "../model/model-context-profile";
|
|
45
|
+
import type { ReasoningEffortSnapshot } from "../model/reasoning-effort";
|
|
56
46
|
import { ObservationBuilder } from "../observation/observation-builder";
|
|
57
|
-
import {
|
|
58
|
-
import { CompiledContextError } from "../context/compiled-context-validator";
|
|
59
|
-
import {
|
|
60
|
-
ContextRevisionCompiler,
|
|
61
|
-
ContextRevisionError,
|
|
62
|
-
} from "../context/context-revision-compiler";
|
|
63
|
-
import {
|
|
64
|
-
changedContextSurfaceComponents,
|
|
65
|
-
contextSurfaceChangeManifestHash,
|
|
66
|
-
contextSurfaceChanges,
|
|
67
|
-
createContextSurface,
|
|
68
|
-
sameContextSurface,
|
|
69
|
-
type ContextSurfaceComponent,
|
|
70
|
-
type StoredContextSurfaceV8,
|
|
71
|
-
} from "../context/context-surface";
|
|
72
|
-
import {
|
|
73
|
-
canonicalSequenceHash,
|
|
74
|
-
renderedMessageHash,
|
|
75
|
-
} from "../context/compiled-context-hash";
|
|
76
|
-
import { createDefaultTooling, type DefaultTooling } from "../tools/registry";
|
|
77
|
-
import {
|
|
78
|
-
ToolExecutionFatalError,
|
|
79
|
-
type ContextMaintenanceHandle,
|
|
80
|
-
type ContextStatusRawResult,
|
|
81
|
-
type ContextSwapCandidatesRawResult,
|
|
82
|
-
type ContextSwapRawResult,
|
|
83
|
-
type AskUserRequest,
|
|
84
|
-
type AskUserResponse,
|
|
85
|
-
type ToolExecutor,
|
|
86
|
-
} from "../tools/types";
|
|
87
|
-
import type { TurnUndoResult } from "../tools/turn-undo-manager";
|
|
88
|
-
import type { Refiner } from "../tools/web-fetch/refiner";
|
|
89
|
-
import type { ProjectInstructionManifest } from "../instructions/project-instructions";
|
|
90
|
-
import type {
|
|
91
|
-
AssistantTextDeltaSink,
|
|
92
|
-
AssistantTextDeltaUpdate,
|
|
93
|
-
} from "./assistant-text-delta";
|
|
47
|
+
import { SessionError } from "../session/session-errors";
|
|
94
48
|
import {
|
|
95
|
-
SessionStore,
|
|
96
49
|
createSessionCompatibilityContract,
|
|
50
|
+
SessionStore,
|
|
97
51
|
type CompletedTurnSnapshot,
|
|
98
52
|
type SessionRecoveryResult,
|
|
99
53
|
type StoredSkillActivation,
|
|
100
54
|
} from "../session/session-store";
|
|
101
55
|
import { SqliteSessionLedger } from "../session/sqlite-session-ledger";
|
|
102
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
createSkillCatalogSnapshot,
|
|
58
|
+
skillCatalogManifest,
|
|
59
|
+
} from "../skills/skill-catalog";
|
|
60
|
+
import {
|
|
61
|
+
buildActiveSystemPrompt,
|
|
62
|
+
rebindActiveSkills,
|
|
63
|
+
SkillActivationCoordinator,
|
|
64
|
+
} from "../skills/skill-context";
|
|
65
|
+
import type { SkillCatalogSnapshot } from "../skills/skill-loader";
|
|
66
|
+
import { createDefaultTooling, type DefaultTooling } from "../tools/registry";
|
|
67
|
+
import type { TurnUndoResult } from "../tools/turn-undo-manager";
|
|
68
|
+
import { ToolExecutionFatalError, type AskUserRequest } from "../tools/types";
|
|
69
|
+
import type { AssistantTextDeltaUpdate } from "./assistant-text-delta";
|
|
70
|
+
import { ContextMeter } from "./context-meter";
|
|
103
71
|
import { FatalAgentTurnError, runAgent, type RunAgentInput } from "./loop";
|
|
72
|
+
import { assertPreparedMatchesSurface } from "./runtime-context-events";
|
|
73
|
+
import { RuntimeContextMaintenance } from "./runtime-context-maintenance";
|
|
74
|
+
import { RuntimeInteractions } from "./runtime-interactions";
|
|
75
|
+
import { RuntimePromptScheduler } from "./runtime-prompt-scheduler";
|
|
76
|
+
import {
|
|
77
|
+
RuntimeEventAppendError,
|
|
78
|
+
type AcceptedTurn,
|
|
79
|
+
type AskUserResolution,
|
|
80
|
+
type AskUserSnapshot,
|
|
81
|
+
type BashGuardSnapshot,
|
|
82
|
+
type CompletedTurnHook,
|
|
83
|
+
type CompletedTurnHookFailure,
|
|
84
|
+
type CreateNewRuntimeSessionInput,
|
|
85
|
+
type CreateRuntimeSessionInput,
|
|
86
|
+
type ExecuteTurnInput,
|
|
87
|
+
type PromptSchedulerSnapshot,
|
|
88
|
+
type QueueFollowUpResult,
|
|
89
|
+
type RuntimeSession,
|
|
90
|
+
type RuntimeSessionContext,
|
|
91
|
+
type RuntimeSessionFactoryDependencies,
|
|
92
|
+
type RuntimeSessionState,
|
|
93
|
+
type RuntimeSkillsSnapshot,
|
|
94
|
+
type SessionDisposeReason,
|
|
95
|
+
type SkillsUpdateSummary,
|
|
96
|
+
} from "./runtime-session-contracts";
|
|
97
|
+
import { RuntimeSkills } from "./runtime-skills";
|
|
104
98
|
import {
|
|
105
99
|
AdmissionStaleError,
|
|
106
100
|
SessionLedgerWriteError,
|
|
107
|
-
type AgentTurnLedger,
|
|
108
101
|
type AdmissionBaseToken,
|
|
102
|
+
type AgentTurnLedger,
|
|
109
103
|
type SessionLedger,
|
|
110
104
|
} from "./session-ledger";
|
|
111
|
-
import {
|
|
112
|
-
import type { ToolCompletionInput } from "../context/protocol-frame";
|
|
113
|
-
import type { BuiltContextRequest } from "../context/context-revision";
|
|
114
|
-
import type { CommittedToolCompletion } from "./session-ledger";
|
|
115
|
-
import type { PublicToolingConfig } from "../cli/public-config-contract";
|
|
105
|
+
import { TurnCancelledError } from "./turn-cancellation";
|
|
116
106
|
import type {
|
|
117
107
|
IterationIdentity,
|
|
118
108
|
RunAgentResult,
|
|
@@ -120,292 +110,35 @@ import type {
|
|
|
120
110
|
ToolCallIdentity,
|
|
121
111
|
TurnIdentity,
|
|
122
112
|
} from "./types";
|
|
123
|
-
import {
|
|
124
|
-
import { contextPressureNoticeText } from "./context-pressure-notice";
|
|
125
|
-
import { CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION } from "../context/recall-retirement-contract";
|
|
126
|
-
import {
|
|
127
|
-
selectContextAutomation,
|
|
128
|
-
type ContextAutomationDecision,
|
|
129
|
-
} from "../context/context-automation-policy";
|
|
130
|
-
import type { SkillCatalogSnapshot } from "../skills/skill-loader";
|
|
131
|
-
import type { ReasoningEffortSnapshot } from "../model/reasoning-effort";
|
|
132
|
-
import {
|
|
133
|
-
activeSkillManifestEntry,
|
|
134
|
-
createSkillCatalogSnapshot,
|
|
135
|
-
skillCatalogManifest,
|
|
136
|
-
} from "../skills/skill-catalog";
|
|
137
|
-
import {
|
|
138
|
-
buildActiveSystemPrompt,
|
|
139
|
-
rebindActiveSkills,
|
|
140
|
-
renderSkillActivationReceipt,
|
|
141
|
-
SkillActivationCoordinator,
|
|
142
|
-
} from "../skills/skill-context";
|
|
143
|
-
|
|
144
|
-
export type ExecuteTurnInput = {
|
|
145
|
-
userMessage: UserMessage;
|
|
146
|
-
signal: AbortSignal;
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
export type AcceptedTurn = {
|
|
150
|
-
readonly turnId: TurnIdentity["turnId"];
|
|
151
|
-
readonly userMessage: UserMessage;
|
|
152
|
-
readonly completion: Promise<RunAgentResult>;
|
|
153
|
-
};
|
|
154
|
-
|
|
155
|
-
export type PromptSchedulerSnapshot = {
|
|
156
|
-
readonly state: "idle" | "running";
|
|
157
|
-
readonly activeTurnId?: TurnIdentity["turnId"];
|
|
158
|
-
readonly pendingCount: number;
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
export type QueueFollowUpResult = {
|
|
162
|
-
readonly kind: "queued";
|
|
163
|
-
readonly pendingCount: number;
|
|
164
|
-
readonly activeTurnId?: TurnIdentity["turnId"];
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
export type SessionDisposeReason =
|
|
168
|
-
| { type: "oneshot_complete" }
|
|
169
|
-
| { type: "tui_exit" }
|
|
170
|
-
| { type: "session_switch" }
|
|
171
|
-
| { type: "runner_failed"; error: string }
|
|
172
|
-
| { type: "initialization_failed"; error: string };
|
|
173
|
-
|
|
174
|
-
export type RuntimeSession = {
|
|
175
|
-
readonly sessionId: SessionId;
|
|
176
|
-
readonly resumed: boolean;
|
|
177
|
-
readonly recovery: SessionRecoveryResult;
|
|
178
|
-
skills(): RuntimeSkillsSnapshot;
|
|
179
|
-
mcp(): McpInventorySnapshot;
|
|
180
|
-
supportsImageInput(): boolean;
|
|
181
|
-
reasoningEffort(): ReasoningEffortSnapshot | undefined;
|
|
182
|
-
setReasoningEffort(effort: string): ReasoningEffortSnapshot;
|
|
183
|
-
resetReasoningEffort(): ReasoningEffortSnapshot;
|
|
184
|
-
importImage(
|
|
185
|
-
sourcePath: string,
|
|
186
|
-
signal: AbortSignal,
|
|
187
|
-
prospectiveMessageImageCount: number,
|
|
188
|
-
): Promise<ImportedImageAsset>;
|
|
189
|
-
verifyImageAssets(
|
|
190
|
-
assets: readonly ImageAssetRef[],
|
|
191
|
-
signal: AbortSignal,
|
|
192
|
-
): Promise<void>;
|
|
193
|
-
admitTurn(input: ExecuteTurnInput): Promise<AcceptedTurn>;
|
|
194
|
-
executeTurn(input: ExecuteTurnInput): Promise<RunAgentResult>;
|
|
195
|
-
promptScheduler(): PromptSchedulerSnapshot;
|
|
196
|
-
subscribePromptScheduler(listener: () => void): () => void;
|
|
197
|
-
queueFollowUp(userMessage: UserMessage): QueueFollowUpResult;
|
|
198
|
-
compactContext(): Promise<ContextCompactionResult>;
|
|
199
|
-
retireContext(): Promise<ContextRetirementResult>;
|
|
200
|
-
undoLatestFileMutationTurn(): Promise<TurnUndoResult>;
|
|
201
|
-
cloneSession(targetSessionId: SessionId): Promise<void>;
|
|
202
|
-
canSwitchSession(): boolean;
|
|
203
|
-
bashGuard(): BashGuardSnapshot;
|
|
204
|
-
subscribeBashGuard(listener: () => void): () => void;
|
|
205
|
-
setYoloMode(enabled: boolean): void;
|
|
206
|
-
resolveBashConfirmation(decision: "allow" | "deny"): Promise<void>;
|
|
207
|
-
askUser(): AskUserSnapshot;
|
|
208
|
-
subscribeAskUser(listener: () => void): () => void;
|
|
209
|
-
resolveAskUser(response: AskUserResolution): Promise<void>;
|
|
210
|
-
dispose(reason: SessionDisposeReason): Promise<void>;
|
|
211
|
-
};
|
|
212
|
-
|
|
213
|
-
export type AskUserSnapshot = {
|
|
214
|
-
readonly pending?: AskUserRequest;
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
export type AskUserResolution =
|
|
218
|
-
| { readonly outcome: "selected"; readonly selectedIndex: number }
|
|
219
|
-
| { readonly outcome: "dismissed" };
|
|
220
|
-
|
|
221
|
-
export type BashGuardSource = "default" | "environment" | "cli" | "session";
|
|
113
|
+
import { projectUserMessage } from "./user-prompt-projection";
|
|
222
114
|
|
|
223
|
-
export
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
115
|
+
export {
|
|
116
|
+
RuntimeEventAppendError,
|
|
117
|
+
type AcceptedTurn,
|
|
118
|
+
type AskUserResolution,
|
|
119
|
+
type AskUserSnapshot,
|
|
120
|
+
type BashGuardSnapshot,
|
|
121
|
+
type BashGuardSource,
|
|
122
|
+
type CompletedTurnHook,
|
|
123
|
+
type CompletedTurnHookFailure,
|
|
124
|
+
type CompletedTurnHookInput,
|
|
125
|
+
type ContextSurfaceRefreshSummary,
|
|
126
|
+
type CreateRuntimeSessionInput,
|
|
127
|
+
type ExecuteTurnInput,
|
|
128
|
+
type PromptSchedulerSnapshot,
|
|
129
|
+
type QueueFollowUpResult,
|
|
130
|
+
type RuntimeSession,
|
|
131
|
+
type RuntimeSessionContext,
|
|
132
|
+
type RuntimeSessionFactoryDependencies,
|
|
133
|
+
type RuntimeSkillsSnapshot,
|
|
134
|
+
type SessionDisposeReason,
|
|
135
|
+
type SkillsUpdateSummary,
|
|
136
|
+
} from "./runtime-session-contracts";
|
|
231
137
|
|
|
232
138
|
const EMPTY_MCP_INVENTORY: McpInventorySnapshot = Object.freeze({
|
|
233
139
|
servers: Object.freeze([]),
|
|
234
140
|
});
|
|
235
141
|
|
|
236
|
-
export type RuntimeSkillsSnapshot = {
|
|
237
|
-
readonly skills: readonly {
|
|
238
|
-
readonly name: string;
|
|
239
|
-
readonly description: string;
|
|
240
|
-
readonly scope: "project" | "user";
|
|
241
|
-
readonly active: boolean;
|
|
242
|
-
}[];
|
|
243
|
-
readonly shadowedNames: readonly string[];
|
|
244
|
-
};
|
|
245
|
-
|
|
246
|
-
export type RuntimeSessionContext = {
|
|
247
|
-
readonly sessionId: SessionId;
|
|
248
|
-
readonly contextMaintenance: ContextMaintenanceHandle;
|
|
249
|
-
createIteration(turn: TurnIdentity, iterationNumber: number): IterationIdentity;
|
|
250
|
-
createToolCall(
|
|
251
|
-
iteration: IterationIdentity,
|
|
252
|
-
toolCallNumber: number,
|
|
253
|
-
): ToolCallIdentity;
|
|
254
|
-
finishIterationForContinuation(iteration: IterationIdentity): void;
|
|
255
|
-
append(input: AgentEventInput): Promise<void>;
|
|
256
|
-
updateAssistantTextDelta?(update: AssistantTextDeltaUpdate): void;
|
|
257
|
-
onToolCompletionsCommitted?(input: {
|
|
258
|
-
completions: readonly ToolCompletionInput[];
|
|
259
|
-
committed: readonly CommittedToolCompletion[];
|
|
260
|
-
}): void;
|
|
261
|
-
prepareModelDispatch?(input: {
|
|
262
|
-
iteration: IterationIdentity;
|
|
263
|
-
built: BuiltContextRequest;
|
|
264
|
-
}): void;
|
|
265
|
-
maintainContextAfterIteration?(input: {
|
|
266
|
-
turn: TurnIdentity;
|
|
267
|
-
consumedThroughOrdinal: number;
|
|
268
|
-
ledger: AgentTurnLedger;
|
|
269
|
-
}): Promise<void>;
|
|
270
|
-
applyQueuedSteering?(input: {
|
|
271
|
-
turn: TurnIdentity;
|
|
272
|
-
ledger: AgentTurnLedger;
|
|
273
|
-
}): Promise<number>;
|
|
274
|
-
};
|
|
275
|
-
|
|
276
|
-
export type ContextSurfaceRefreshSummary = {
|
|
277
|
-
readonly previousRevisionNumber: number;
|
|
278
|
-
readonly revisionNumber: number;
|
|
279
|
-
readonly changed: readonly ContextSurfaceComponent[];
|
|
280
|
-
readonly toolCountBefore: number;
|
|
281
|
-
readonly toolCountAfter: number;
|
|
282
|
-
};
|
|
283
|
-
|
|
284
|
-
export type SkillsUpdateSummary = {
|
|
285
|
-
readonly previousRevisionNumber: number;
|
|
286
|
-
readonly revisionNumber: number;
|
|
287
|
-
readonly activated: readonly string[];
|
|
288
|
-
readonly refreshed: readonly string[];
|
|
289
|
-
readonly deactivated: readonly string[];
|
|
290
|
-
readonly unavailable: readonly string[];
|
|
291
|
-
readonly addedOverrideCount: number;
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
export type CompletedTurnHookInput = {
|
|
295
|
-
readonly workspaceRoot: string;
|
|
296
|
-
readonly sessionId: SessionId;
|
|
297
|
-
readonly turnId: TurnId;
|
|
298
|
-
readonly snapshot: CompletedTurnSnapshot;
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
export type CompletedTurnHookFailure = {
|
|
302
|
-
readonly workspaceRoot: string;
|
|
303
|
-
readonly sessionId: SessionId;
|
|
304
|
-
readonly turnId: TurnId;
|
|
305
|
-
readonly reason: "completed_turn_snapshot_failed" | "completed_turn_enqueue_failed";
|
|
306
|
-
};
|
|
307
|
-
|
|
308
|
-
export type CompletedTurnHook = {
|
|
309
|
-
enqueue(input: CompletedTurnHookInput): void;
|
|
310
|
-
recordFailure(input: CompletedTurnHookFailure): void;
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
type CommonRuntimeSessionInput = {
|
|
314
|
-
workspaceRoot: string;
|
|
315
|
-
homeRoot?: string;
|
|
316
|
-
modelName: string;
|
|
317
|
-
profileName?: string;
|
|
318
|
-
maxIterations: number;
|
|
319
|
-
includeReasoningContent: boolean;
|
|
320
|
-
contextProfile: ModelContextProfile;
|
|
321
|
-
contextBudget: ModelContextBudget;
|
|
322
|
-
modelClient: ModelClient;
|
|
323
|
-
systemPrompt: string;
|
|
324
|
-
projectInstruction?: ProjectInstructionManifest;
|
|
325
|
-
skillCatalog?: SkillCatalogSnapshot;
|
|
326
|
-
presentationSinks?: EventSink[];
|
|
327
|
-
assistantTextDeltaSink?: AssistantTextDeltaSink;
|
|
328
|
-
persistence?:
|
|
329
|
-
| false
|
|
330
|
-
| {
|
|
331
|
-
eventLogPath?: string;
|
|
332
|
-
observationLogPath?: string;
|
|
333
|
-
};
|
|
334
|
-
webFetchRefiner?: Refiner;
|
|
335
|
-
toolingConfig?: PublicToolingConfig;
|
|
336
|
-
memorySearch?: ToolExecutor;
|
|
337
|
-
memoryGet?: ToolExecutor;
|
|
338
|
-
memoryCreate?: ToolExecutor;
|
|
339
|
-
memoryUpdate?: ToolExecutor;
|
|
340
|
-
memoryDelete?: ToolExecutor;
|
|
341
|
-
completedTurnHook?: CompletedTurnHook;
|
|
342
|
-
enableTurnUndo?: boolean;
|
|
343
|
-
enableAskUser?: boolean;
|
|
344
|
-
bashGuard?: {
|
|
345
|
-
readonly mode: "guard" | "yolo";
|
|
346
|
-
readonly source: Exclude<BashGuardSource, "session">;
|
|
347
|
-
readonly surface: "tui" | "one-shot";
|
|
348
|
-
};
|
|
349
|
-
};
|
|
350
|
-
|
|
351
|
-
type CreateNewRuntimeSessionInput = CommonRuntimeSessionInput & {
|
|
352
|
-
selection: { mode: "new"; sessionId: SessionId };
|
|
353
|
-
};
|
|
354
|
-
|
|
355
|
-
type ResumeRuntimeSessionInput = CommonRuntimeSessionInput & {
|
|
356
|
-
selection: { mode: "resume"; sessionId: SessionId };
|
|
357
|
-
};
|
|
358
|
-
|
|
359
|
-
export type CreateRuntimeSessionInput =
|
|
360
|
-
| CreateNewRuntimeSessionInput
|
|
361
|
-
| ResumeRuntimeSessionInput;
|
|
362
|
-
|
|
363
|
-
export type RuntimeSessionFactoryDependencies = {
|
|
364
|
-
idFactory: RuntimeIdFactory;
|
|
365
|
-
createTooling: typeof createDefaultTooling;
|
|
366
|
-
loadMcpConfig: typeof loadMcpConfig;
|
|
367
|
-
createMcpManager: typeof createMcpManager;
|
|
368
|
-
createObservationBuilder: () => ObservationBuilder;
|
|
369
|
-
openStore: (
|
|
370
|
-
input: CreateRuntimeSessionInput,
|
|
371
|
-
idFactory: RuntimeIdFactory,
|
|
372
|
-
) => Promise<SessionStore>;
|
|
373
|
-
createLedger: (store: SessionStore, idFactory: RuntimeIdFactory) => SessionLedger;
|
|
374
|
-
createEventSink: (
|
|
375
|
-
input: CreateRuntimeSessionInput,
|
|
376
|
-
sessionDirectory: string,
|
|
377
|
-
) => EventSink;
|
|
378
|
-
selectShadowPlanning: NonNullable<RunAgentInput["shadowPlanning"]>["select"];
|
|
379
|
-
onShadowPlanningResult?: NonNullable<RunAgentInput["shadowPlanning"]>["onResult"];
|
|
380
|
-
selectContextAutomation: typeof selectContextAutomation;
|
|
381
|
-
automaticCompactionTrigger: () => ContextCompactionTrigger;
|
|
382
|
-
automaticRetirementTrigger: () => ContextRetirementTrigger;
|
|
383
|
-
manualCompactionTrigger: () => ContextCompactionTrigger;
|
|
384
|
-
manualRetirementTrigger: () => ContextRetirementTrigger;
|
|
385
|
-
};
|
|
386
|
-
|
|
387
|
-
export class RuntimeEventAppendError extends Error {
|
|
388
|
-
readonly eventType: AgentEventType;
|
|
389
|
-
|
|
390
|
-
constructor(eventType: AgentEventType, options?: ErrorOptions) {
|
|
391
|
-
super(`Failed to append runtime event ${eventType}.`, options);
|
|
392
|
-
this.name = "RuntimeEventAppendError";
|
|
393
|
-
this.eventType = eventType;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
type RuntimeSessionState =
|
|
398
|
-
| "initializing"
|
|
399
|
-
| "admitting"
|
|
400
|
-
| "ready"
|
|
401
|
-
| "executing"
|
|
402
|
-
| "compacting"
|
|
403
|
-
| "maintaining_context"
|
|
404
|
-
| "undoing"
|
|
405
|
-
| "faulted"
|
|
406
|
-
| "disposing"
|
|
407
|
-
| "disposed";
|
|
408
|
-
|
|
409
142
|
type ActiveTurn = {
|
|
410
143
|
turn: TurnIdentity;
|
|
411
144
|
ledger: AgentTurnLedger;
|
|
@@ -414,13 +147,6 @@ type ActiveTurn = {
|
|
|
414
147
|
completion: Promise<RunAgentResult>;
|
|
415
148
|
};
|
|
416
149
|
|
|
417
|
-
type QueuedPrompt = {
|
|
418
|
-
readonly userMessage: UserMessage;
|
|
419
|
-
};
|
|
420
|
-
|
|
421
|
-
const MAX_QUEUED_PROMPTS = 8;
|
|
422
|
-
const MAX_QUEUED_PROMPT_TEXT_BYTES = 64 * 1024;
|
|
423
|
-
|
|
424
150
|
type ActiveAdmission = {
|
|
425
151
|
controller: AbortController;
|
|
426
152
|
settled: Promise<void>;
|
|
@@ -453,7 +179,7 @@ const defaultDependencies: RuntimeSessionFactoryDependencies = {
|
|
|
453
179
|
createEventSink,
|
|
454
180
|
selectShadowPlanning: ({ preflight }) =>
|
|
455
181
|
preflight.pressure === "normal" ? undefined : { trigger: "runtime_pressure" },
|
|
456
|
-
|
|
182
|
+
contextAutomationPolicy: DEFAULT_CONTEXT_AUTOMATION_POLICY,
|
|
457
183
|
automaticCompactionTrigger: () => ({ kind: "runtime_pressure" }),
|
|
458
184
|
automaticRetirementTrigger: () => ({ kind: "runtime_pressure" }),
|
|
459
185
|
manualCompactionTrigger: () => ({ kind: "manual" }),
|
|
@@ -467,6 +193,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
467
193
|
syntheticCompletionCount: 0,
|
|
468
194
|
recallIndexRebuilt: false,
|
|
469
195
|
};
|
|
196
|
+
|
|
470
197
|
private state: RuntimeSessionState = "initializing";
|
|
471
198
|
private nextTurnNumber: number;
|
|
472
199
|
private readonly turns = new Map<string, TurnIdentity>();
|
|
@@ -480,13 +207,6 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
480
207
|
private ledger?: SessionLedger;
|
|
481
208
|
private activeAdmission?: ActiveAdmission;
|
|
482
209
|
private activeTurn?: ActiveTurn;
|
|
483
|
-
private executionChainRunning = false;
|
|
484
|
-
private readonly queuedPrompts: QueuedPrompt[] = [];
|
|
485
|
-
private promptSchedulerSnapshot: PromptSchedulerSnapshot = Object.freeze({
|
|
486
|
-
state: "idle",
|
|
487
|
-
pendingCount: 0,
|
|
488
|
-
});
|
|
489
|
-
private readonly promptSchedulerListeners = new Set<() => void>();
|
|
490
210
|
private activeContextRevision?: Promise<
|
|
491
211
|
ContextCompactionResult | ContextRetirementResult
|
|
492
212
|
>;
|
|
@@ -496,38 +216,14 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
496
216
|
private readonly committedPrefixAuditor = new CommittedPrefixAuditor();
|
|
497
217
|
private readonly shadowPlanner: SwapPlanner;
|
|
498
218
|
private contextManager?: ContextManager;
|
|
499
|
-
private
|
|
500
|
-
private pendingAutomaticContextMaintenance = false;
|
|
501
|
-
private pendingModelDirectedSwap?: Set<MessageId>;
|
|
502
|
-
private modelDirectedSwapLease = false;
|
|
503
|
-
private pressureNoticeSentThisTurn = false;
|
|
504
|
-
private readonly skillCatalog: SkillCatalogSnapshot;
|
|
505
|
-
private skillCoordinator = new SkillActivationCoordinator();
|
|
506
|
-
private bashGuardMode: "guard" | "yolo";
|
|
507
|
-
private bashGuardSource: BashGuardSource;
|
|
508
|
-
private bashGuardSnapshot: BashGuardSnapshot;
|
|
509
|
-
private readonly bashGuardListeners = new Set<() => void>();
|
|
510
|
-
private askUserSnapshot: AskUserSnapshot = Object.freeze({});
|
|
511
|
-
private readonly askUserListeners = new Set<() => void>();
|
|
512
|
-
private pendingAskUser?: {
|
|
513
|
-
readonly request: AskUserRequest;
|
|
514
|
-
readonly startedAt: number;
|
|
515
|
-
readonly call: ToolCallIdentity;
|
|
516
|
-
readonly resolve: (response: AskUserResponse) => void;
|
|
517
|
-
readonly reject: (error: unknown) => void;
|
|
518
|
-
readonly removeAbortListener: () => void;
|
|
519
|
-
};
|
|
219
|
+
private contextAutomationPolicy?: ContextAutomationPolicy;
|
|
520
220
|
private assistantTextDeltaSinkDisabled = false;
|
|
521
|
-
private pendingBashConfirmation?: {
|
|
522
|
-
readonly command: string;
|
|
523
|
-
readonly reason: string;
|
|
524
|
-
readonly startedAt: number;
|
|
525
|
-
readonly call: ToolCallIdentity;
|
|
526
|
-
readonly resolve: (decision: "allow" | "deny") => void;
|
|
527
|
-
readonly reject: (error: unknown) => void;
|
|
528
|
-
readonly removeAbortListener: () => void;
|
|
529
|
-
};
|
|
530
221
|
|
|
222
|
+
private readonly skillCatalog: SkillCatalogSnapshot;
|
|
223
|
+
private readonly interactions: RuntimeInteractions;
|
|
224
|
+
private readonly scheduler: RuntimePromptScheduler;
|
|
225
|
+
private readonly contextMaintenance: RuntimeContextMaintenance;
|
|
226
|
+
private readonly runtimeSkills: RuntimeSkills;
|
|
531
227
|
private readonly context: RuntimeSessionContext;
|
|
532
228
|
|
|
533
229
|
private constructor(
|
|
@@ -540,12 +236,15 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
540
236
|
) {
|
|
541
237
|
this.sessionId = input.selection.sessionId;
|
|
542
238
|
this.resumed = input.selection.mode === "resume";
|
|
543
|
-
this.
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
239
|
+
this.scheduler = new RuntimePromptScheduler(
|
|
240
|
+
() => this.state,
|
|
241
|
+
() => this.activeTurn,
|
|
242
|
+
(input) => this.admitSingleTurn(input),
|
|
243
|
+
(event) => this.append(event),
|
|
244
|
+
);
|
|
245
|
+
this.interactions = new RuntimeInteractions(input.bashGuard, (event) =>
|
|
246
|
+
this.append(event),
|
|
247
|
+
);
|
|
549
248
|
this.skillCatalog =
|
|
550
249
|
input.skillCatalog ??
|
|
551
250
|
createSkillCatalogSnapshot({
|
|
@@ -559,12 +258,40 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
559
258
|
onMeasuredAnchor: (anchor) => store.writeMeasuredContextAnchor(anchor),
|
|
560
259
|
});
|
|
561
260
|
this.shadowPlanner = new SwapPlanner(input.modelClient);
|
|
261
|
+
this.contextMaintenance = new RuntimeContextMaintenance(
|
|
262
|
+
this.sessionId,
|
|
263
|
+
store,
|
|
264
|
+
() => this.requireContextManager(),
|
|
265
|
+
() => this.requireContextAutomation(),
|
|
266
|
+
(call, expectedName) => this.requireActiveContextTool(call, expectedName),
|
|
267
|
+
(event) => this.append(event),
|
|
268
|
+
{
|
|
269
|
+
getState: () => this.state,
|
|
270
|
+
setState: (state) => {
|
|
271
|
+
this.state = state;
|
|
272
|
+
},
|
|
273
|
+
hasActiveTurn: () => this.activeTurn !== undefined,
|
|
274
|
+
fault: (error) => this.fault(error),
|
|
275
|
+
},
|
|
276
|
+
dependencies,
|
|
277
|
+
);
|
|
278
|
+
this.runtimeSkills = new RuntimeSkills(
|
|
279
|
+
this.sessionId,
|
|
280
|
+
store,
|
|
281
|
+
input,
|
|
282
|
+
this.skillCatalog,
|
|
283
|
+
dependencies.idFactory,
|
|
284
|
+
this.contextMeter,
|
|
285
|
+
() => this.requireTooling().registry.definitions(),
|
|
286
|
+
(event) => this.append(event),
|
|
287
|
+
);
|
|
562
288
|
this.context = {
|
|
563
289
|
sessionId: this.sessionId,
|
|
564
290
|
contextMaintenance: {
|
|
565
|
-
status: (call) => this.contextStatus(call),
|
|
566
|
-
candidates: (call, page) =>
|
|
567
|
-
|
|
291
|
+
status: (call) => this.contextMaintenance.contextStatus(call),
|
|
292
|
+
candidates: (call, page) =>
|
|
293
|
+
this.contextMaintenance.contextSwapCandidates(call, page),
|
|
294
|
+
swap: (call, selection) => this.contextMaintenance.contextSwap(call, selection),
|
|
568
295
|
},
|
|
569
296
|
createIteration: (turn, iterationNumber) =>
|
|
570
297
|
this.createIteration(turn, iterationNumber),
|
|
@@ -580,11 +307,11 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
580
307
|
this.updateAssistantTextDelta(update),
|
|
581
308
|
}),
|
|
582
309
|
onToolCompletionsCommitted: (completion) =>
|
|
583
|
-
this.onToolCompletionsCommitted(completion),
|
|
310
|
+
this.runtimeSkills.onToolCompletionsCommitted(completion),
|
|
584
311
|
prepareModelDispatch: (dispatch) => this.prepareModelDispatch(dispatch),
|
|
585
312
|
maintainContextAfterIteration: (maintenance) =>
|
|
586
|
-
this.performActiveTurnContextMaintenance(maintenance),
|
|
587
|
-
applyQueuedSteering: (steering) => this.applyQueuedSteering(steering),
|
|
313
|
+
this.contextMaintenance.performActiveTurnContextMaintenance(maintenance),
|
|
314
|
+
applyQueuedSteering: (steering) => this.scheduler.applyQueuedSteering(steering),
|
|
588
315
|
};
|
|
589
316
|
}
|
|
590
317
|
|
|
@@ -669,9 +396,9 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
669
396
|
active: storedSurface.activeSkills,
|
|
670
397
|
promotionNames,
|
|
671
398
|
});
|
|
672
|
-
session.
|
|
673
|
-
active: rebound.active,
|
|
674
|
-
|
|
399
|
+
session.runtimeSkills.restoreCoordinator(
|
|
400
|
+
new SkillActivationCoordinator({ active: rebound.active }),
|
|
401
|
+
);
|
|
675
402
|
const activated = promotionNames
|
|
676
403
|
.filter((entry) => session.skillCatalog.skills.has(entry.name))
|
|
677
404
|
.map((entry) => entry.name)
|
|
@@ -711,7 +438,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
711
438
|
}
|
|
712
439
|
|
|
713
440
|
session.tooling = dependencies.createTooling({
|
|
714
|
-
workspaceRoot:
|
|
441
|
+
workspaceRoot: store.workspaceRoot,
|
|
715
442
|
...(input.homeRoot === undefined ? {} : { homeRoot: input.homeRoot }),
|
|
716
443
|
runtimeSession: session.context,
|
|
717
444
|
historyReader: store.historyReader(),
|
|
@@ -728,13 +455,13 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
728
455
|
call: ToolCallIdentity,
|
|
729
456
|
request: AskUserRequest,
|
|
730
457
|
signal: AbortSignal,
|
|
731
|
-
) => session.requestUserAnswer(call, request, signal),
|
|
458
|
+
) => session.interactions.requestUserAnswer(call, request, signal),
|
|
732
459
|
}
|
|
733
460
|
: {}),
|
|
734
461
|
bashGuard: {
|
|
735
462
|
surface: input.bashGuard?.surface ?? "one-shot",
|
|
736
463
|
confirm: (call, request, signal) =>
|
|
737
|
-
session.confirmBashCommand(call, request, signal),
|
|
464
|
+
session.interactions.confirmBashCommand(call, request, signal),
|
|
738
465
|
},
|
|
739
466
|
...(input.memorySearch === undefined
|
|
740
467
|
? {}
|
|
@@ -753,7 +480,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
753
480
|
? {}
|
|
754
481
|
: {
|
|
755
482
|
skillCatalog: session.skillCatalog,
|
|
756
|
-
skillCoordinator: session.
|
|
483
|
+
skillCoordinator: session.runtimeSkills.coordinator,
|
|
757
484
|
}),
|
|
758
485
|
});
|
|
759
486
|
|
|
@@ -774,7 +501,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
774
501
|
const definitions = session.requireTooling().registry.definitions();
|
|
775
502
|
const activeSystemPrompt = buildActiveSystemPrompt({
|
|
776
503
|
baseSystemPrompt: input.systemPrompt,
|
|
777
|
-
activeSkills: session.
|
|
504
|
+
activeSkills: session.runtimeSkills.coordinator.activeEntries(),
|
|
778
505
|
});
|
|
779
506
|
const surfacePrepared = input.modelClient.prepare({
|
|
780
507
|
messages: [{ role: "system", content: activeSystemPrompt }],
|
|
@@ -789,7 +516,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
789
516
|
? {}
|
|
790
517
|
: { projectInstruction: input.projectInstruction }),
|
|
791
518
|
skillCatalog: skillCatalogManifest(session.skillCatalog.skills.values()),
|
|
792
|
-
activeSkills: session.
|
|
519
|
+
activeSkills: session.runtimeSkills.coordinator.activeManifest(),
|
|
793
520
|
toolDefinitions: definitions,
|
|
794
521
|
prepared: surfacePrepared,
|
|
795
522
|
createdAt: new Date().toISOString(),
|
|
@@ -807,10 +534,10 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
807
534
|
let skillsUpdate: SkillsUpdateSummary | undefined;
|
|
808
535
|
const refresh =
|
|
809
536
|
resumeSkills.unresolved.length === 0
|
|
810
|
-
? await session.refreshContextSurface(candidateSurface)
|
|
537
|
+
? await session.runtimeSkills.refreshContextSurface(candidateSurface)
|
|
811
538
|
: undefined;
|
|
812
539
|
if (resumeSkills.unresolved.length > 0) {
|
|
813
|
-
skillsUpdate = await session.commitSkillSettlements({
|
|
540
|
+
skillsUpdate = await session.runtimeSkills.commitSkillSettlements({
|
|
814
541
|
reason: "resume",
|
|
815
542
|
candidateSurface,
|
|
816
543
|
unresolved: resumeSkills.unresolved,
|
|
@@ -884,8 +611,12 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
884
611
|
}
|
|
885
612
|
}
|
|
886
613
|
|
|
887
|
-
await session.appendSkillsCatalogLoaded();
|
|
614
|
+
await session.runtimeSkills.appendSkillsCatalogLoaded();
|
|
888
615
|
|
|
616
|
+
assertContextMaintenanceCapabilities(
|
|
617
|
+
session.requireTooling().registry,
|
|
618
|
+
store.loadContextSnapshot().surface.recallContractVersion,
|
|
619
|
+
);
|
|
889
620
|
session.ledger = dependencies.createLedger(store, dependencies.idFactory);
|
|
890
621
|
session.contextManager = new ContextManager({
|
|
891
622
|
store,
|
|
@@ -927,18 +658,17 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
927
658
|
data: { phase: "initial", snapshot: initialSnapshot },
|
|
928
659
|
});
|
|
929
660
|
|
|
930
|
-
session.
|
|
931
|
-
...
|
|
932
|
-
surface: store.loadContextSnapshot().surface,
|
|
661
|
+
session.contextAutomationPolicy = Object.freeze({
|
|
662
|
+
...dependencies.contextAutomationPolicy,
|
|
933
663
|
});
|
|
934
664
|
if (
|
|
935
665
|
input.selection.mode === "resume" &&
|
|
936
666
|
initialSnapshot.pressure !== "normal" &&
|
|
937
|
-
session.
|
|
667
|
+
session.contextAutomationPolicy.automaticSwap
|
|
938
668
|
) {
|
|
939
|
-
session.
|
|
669
|
+
session.contextMaintenance.scheduleAutomaticMaintenance();
|
|
940
670
|
session.state = "executing";
|
|
941
|
-
await session.performAutomaticContextMaintenance();
|
|
671
|
+
await session.contextMaintenance.performAutomaticContextMaintenance();
|
|
942
672
|
}
|
|
943
673
|
|
|
944
674
|
session.state = "ready";
|
|
@@ -948,139 +678,36 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
948
678
|
}
|
|
949
679
|
}
|
|
950
680
|
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
const snapshot = this.store.loadContextSnapshot();
|
|
955
|
-
if (sameContextSurface(snapshot.surface, candidateSurface)) {
|
|
956
|
-
return undefined;
|
|
957
|
-
}
|
|
681
|
+
bashGuard(): BashGuardSnapshot {
|
|
682
|
+
return this.interactions.bashGuard();
|
|
683
|
+
}
|
|
958
684
|
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
throw new Error("Changed context surface has an empty change manifest.");
|
|
963
|
-
}
|
|
964
|
-
const startedAt = performance.now();
|
|
965
|
-
await this.append({
|
|
966
|
-
type: "context.revision.started",
|
|
967
|
-
sessionId: this.sessionId,
|
|
968
|
-
data: {
|
|
969
|
-
strategy: "surface_refresh",
|
|
970
|
-
reason: "resume",
|
|
971
|
-
baseRevisionNumber: snapshot.revision.revisionNumber,
|
|
972
|
-
changed,
|
|
973
|
-
},
|
|
974
|
-
});
|
|
685
|
+
subscribeBashGuard(listener: () => void): () => void {
|
|
686
|
+
return this.interactions.subscribeBashGuard(listener);
|
|
687
|
+
}
|
|
975
688
|
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
const compiler = new ContextRevisionCompiler();
|
|
980
|
-
const active = compiler.compileActive(snapshot);
|
|
981
|
-
const candidateCompiled = compiler.compileProspective({
|
|
982
|
-
active,
|
|
983
|
-
canonical: snapshot.canonical,
|
|
984
|
-
activeOverrides: snapshot.activeOverrides,
|
|
985
|
-
addedOverrides: [],
|
|
986
|
-
activeSurface: snapshot.surface,
|
|
987
|
-
surface: candidateSurface,
|
|
988
|
-
});
|
|
989
|
-
const prepared = this.input.modelClient.prepare({
|
|
990
|
-
messages: candidateCompiled.entries.map((entry) => entry.message),
|
|
991
|
-
tools: [...candidateSurface.toolDefinitions],
|
|
992
|
-
});
|
|
993
|
-
assertPreparedMatchesSurface(prepared, candidateSurface);
|
|
994
|
-
|
|
995
|
-
stage = "commit";
|
|
996
|
-
const revision = this.store.commitSurfaceRefresh({
|
|
997
|
-
revisionId: this.dependencies.idFactory.createContextRevisionId(),
|
|
998
|
-
expectedBaseRevisionId: snapshot.revision.revisionId,
|
|
999
|
-
expectedBaseRevisionNumber: snapshot.revision.revisionNumber,
|
|
1000
|
-
expectedCanonicalThroughOrdinal: snapshot.canonical.messages.length,
|
|
1001
|
-
expectedBaseActiveOverrideManifestSha256:
|
|
1002
|
-
snapshot.revision.activeOverrideManifestSha256,
|
|
1003
|
-
surface: candidateSurface,
|
|
1004
|
-
changes,
|
|
1005
|
-
changeManifestSha256: contextSurfaceChangeManifestHash(changes),
|
|
1006
|
-
canonicalSequenceSha256: canonicalSequenceHash(snapshot.canonical),
|
|
1007
|
-
renderedMessageSha256: renderedMessageHash(candidateCompiled.entries),
|
|
1008
|
-
});
|
|
1009
|
-
committed = true;
|
|
689
|
+
setYoloMode(enabled: boolean): void {
|
|
690
|
+
return this.interactions.setYoloMode(enabled);
|
|
691
|
+
}
|
|
1010
692
|
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
type: "context.revision.finished",
|
|
1026
|
-
sessionId: this.sessionId,
|
|
1027
|
-
data: {
|
|
1028
|
-
strategy: "surface_refresh",
|
|
1029
|
-
reason: "resume",
|
|
1030
|
-
baseRevisionNumber: summary.previousRevisionNumber,
|
|
1031
|
-
revisionNumber: summary.revisionNumber,
|
|
1032
|
-
changed: summary.changed,
|
|
1033
|
-
toolCountBefore: summary.toolCountBefore,
|
|
1034
|
-
toolCountAfter: summary.toolCountAfter,
|
|
1035
|
-
measuredAnchorCleared: true,
|
|
1036
|
-
durationMs: elapsedMs(startedAt),
|
|
1037
|
-
},
|
|
1038
|
-
});
|
|
1039
|
-
return summary;
|
|
1040
|
-
} catch (error) {
|
|
1041
|
-
await this.append({
|
|
1042
|
-
type: "context.revision.failed",
|
|
1043
|
-
sessionId: this.sessionId,
|
|
1044
|
-
data: {
|
|
1045
|
-
strategy: "surface_refresh",
|
|
1046
|
-
reason: "resume",
|
|
1047
|
-
stage,
|
|
1048
|
-
errorCode: boundedContextErrorCode(
|
|
1049
|
-
error instanceof SessionError
|
|
1050
|
-
? error.code
|
|
1051
|
-
: error instanceof Error
|
|
1052
|
-
? error.name
|
|
1053
|
-
: "CONTEXT_SURFACE_REFRESH_FAILED",
|
|
1054
|
-
),
|
|
1055
|
-
error: `Context surface refresh failed at ${stage}.`,
|
|
1056
|
-
committed,
|
|
1057
|
-
},
|
|
1058
|
-
}).catch(() => undefined);
|
|
1059
|
-
throw error;
|
|
1060
|
-
}
|
|
693
|
+
resolveBashConfirmation(decision: "allow" | "deny"): Promise<void> {
|
|
694
|
+
return this.interactions.resolveBashConfirmation(decision);
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
askUser(): AskUserSnapshot {
|
|
698
|
+
return this.interactions.askUser();
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
subscribeAskUser(listener: () => void): () => void {
|
|
702
|
+
return this.interactions.subscribeAskUser(listener);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
resolveAskUser(response: AskUserResolution): Promise<void> {
|
|
706
|
+
return this.interactions.resolveAskUser(response);
|
|
1061
707
|
}
|
|
1062
708
|
|
|
1063
709
|
skills(): RuntimeSkillsSnapshot {
|
|
1064
|
-
|
|
1065
|
-
this.skillCoordinator.activeEntries().map((entry) => entry.skill.name),
|
|
1066
|
-
);
|
|
1067
|
-
return Object.freeze({
|
|
1068
|
-
skills: Object.freeze(
|
|
1069
|
-
[...this.skillCatalog.skills.values()]
|
|
1070
|
-
.sort((left, right) => compareText(left.name, right.name))
|
|
1071
|
-
.map((skill) =>
|
|
1072
|
-
Object.freeze({
|
|
1073
|
-
name: skill.name,
|
|
1074
|
-
description: skill.description,
|
|
1075
|
-
scope: skill.scope,
|
|
1076
|
-
active: activeNames.has(skill.name),
|
|
1077
|
-
}),
|
|
1078
|
-
),
|
|
1079
|
-
),
|
|
1080
|
-
shadowedNames: Object.freeze(
|
|
1081
|
-
this.skillCatalog.shadowed.map((entry) => entry.name),
|
|
1082
|
-
),
|
|
1083
|
-
});
|
|
710
|
+
return this.runtimeSkills.skills();
|
|
1084
711
|
}
|
|
1085
712
|
|
|
1086
713
|
mcp(): McpInventorySnapshot {
|
|
@@ -1121,261 +748,20 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1121
748
|
return reasoningEffort.reset();
|
|
1122
749
|
}
|
|
1123
750
|
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
}
|
|
1127
|
-
|
|
1128
|
-
private refreshBashGuardSnapshot(): void {
|
|
1129
|
-
this.bashGuardSnapshot = Object.freeze({
|
|
1130
|
-
mode: this.bashGuardMode,
|
|
1131
|
-
source: this.bashGuardSource,
|
|
1132
|
-
...(this.pendingBashConfirmation === undefined
|
|
1133
|
-
? {}
|
|
1134
|
-
: {
|
|
1135
|
-
pending: Object.freeze({
|
|
1136
|
-
command: this.pendingBashConfirmation.command,
|
|
1137
|
-
reason: this.pendingBashConfirmation.reason,
|
|
1138
|
-
}),
|
|
1139
|
-
}),
|
|
1140
|
-
});
|
|
1141
|
-
}
|
|
1142
|
-
|
|
1143
|
-
subscribeBashGuard(listener: () => void): () => void {
|
|
1144
|
-
this.bashGuardListeners.add(listener);
|
|
1145
|
-
return () => this.bashGuardListeners.delete(listener);
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
setYoloMode(enabled: boolean): void {
|
|
1149
|
-
this.bashGuardMode = enabled ? "yolo" : "guard";
|
|
1150
|
-
this.bashGuardSource = "session";
|
|
1151
|
-
this.refreshBashGuardSnapshot();
|
|
1152
|
-
this.notifyBashGuardListeners();
|
|
1153
|
-
}
|
|
1154
|
-
|
|
1155
|
-
async resolveBashConfirmation(decision: "allow" | "deny"): Promise<void> {
|
|
1156
|
-
const pending = this.pendingBashConfirmation;
|
|
1157
|
-
if (pending === undefined) {
|
|
1158
|
-
throw new Error("No Bash confirmation is pending.");
|
|
1159
|
-
}
|
|
1160
|
-
this.pendingBashConfirmation = undefined;
|
|
1161
|
-
this.refreshBashGuardSnapshot();
|
|
1162
|
-
pending.removeAbortListener();
|
|
1163
|
-
await this.append({
|
|
1164
|
-
type: "tool.confirmation.resolved",
|
|
1165
|
-
...pending.call,
|
|
1166
|
-
data: {
|
|
1167
|
-
command: pending.command,
|
|
1168
|
-
reason: pending.reason,
|
|
1169
|
-
decision,
|
|
1170
|
-
durationMs: Date.now() - pending.startedAt,
|
|
1171
|
-
},
|
|
1172
|
-
});
|
|
1173
|
-
pending.resolve(decision);
|
|
1174
|
-
this.notifyBashGuardListeners();
|
|
1175
|
-
}
|
|
1176
|
-
|
|
1177
|
-
private async confirmBashCommand(
|
|
1178
|
-
call: ToolCallIdentity,
|
|
1179
|
-
request: { command: string; reason: string },
|
|
751
|
+
async importImage(
|
|
752
|
+
sourcePath: string,
|
|
1180
753
|
signal: AbortSignal,
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
...call,
|
|
1186
|
-
data: request,
|
|
1187
|
-
});
|
|
1188
|
-
|
|
1189
|
-
const surface = this.input.bashGuard?.surface ?? "one-shot";
|
|
1190
|
-
if (this.bashGuardMode === "yolo" || surface === "one-shot") {
|
|
1191
|
-
const decision = this.bashGuardMode === "yolo" ? "allow" : "deny";
|
|
1192
|
-
await this.append({
|
|
1193
|
-
type: "tool.confirmation.resolved",
|
|
1194
|
-
...call,
|
|
1195
|
-
data: {
|
|
1196
|
-
...request,
|
|
1197
|
-
decision,
|
|
1198
|
-
durationMs: Date.now() - startedAt,
|
|
1199
|
-
},
|
|
1200
|
-
});
|
|
1201
|
-
return decision;
|
|
754
|
+
prospectiveMessageImageCount: number,
|
|
755
|
+
): Promise<ImportedImageAsset> {
|
|
756
|
+
if (this.state !== "ready") {
|
|
757
|
+
throw new Error(`Cannot import an image while RuntimeSession is ${this.state}.`);
|
|
1202
758
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
const onAbort = () => {
|
|
1210
|
-
const pending = this.pendingBashConfirmation;
|
|
1211
|
-
if (pending?.call.toolCallId !== call.toolCallId) {
|
|
1212
|
-
return;
|
|
1213
|
-
}
|
|
1214
|
-
this.pendingBashConfirmation = undefined;
|
|
1215
|
-
this.refreshBashGuardSnapshot();
|
|
1216
|
-
void this.append({
|
|
1217
|
-
type: "tool.confirmation.resolved",
|
|
1218
|
-
...call,
|
|
1219
|
-
data: {
|
|
1220
|
-
...request,
|
|
1221
|
-
decision: "cancelled",
|
|
1222
|
-
durationMs: Date.now() - startedAt,
|
|
1223
|
-
},
|
|
1224
|
-
}).finally(() => {
|
|
1225
|
-
reject(cancellationError(signal));
|
|
1226
|
-
this.notifyBashGuardListeners();
|
|
1227
|
-
});
|
|
1228
|
-
};
|
|
1229
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1230
|
-
this.pendingBashConfirmation = {
|
|
1231
|
-
...request,
|
|
1232
|
-
startedAt,
|
|
1233
|
-
call,
|
|
1234
|
-
resolve,
|
|
1235
|
-
reject,
|
|
1236
|
-
removeAbortListener: () => signal.removeEventListener("abort", onAbort),
|
|
1237
|
-
};
|
|
1238
|
-
this.refreshBashGuardSnapshot();
|
|
1239
|
-
this.notifyBashGuardListeners();
|
|
1240
|
-
if (signal.aborted) {
|
|
1241
|
-
onAbort();
|
|
1242
|
-
}
|
|
1243
|
-
});
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
private notifyBashGuardListeners(): void {
|
|
1247
|
-
for (const listener of this.bashGuardListeners) {
|
|
1248
|
-
listener();
|
|
1249
|
-
}
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
askUser(): AskUserSnapshot {
|
|
1253
|
-
return this.askUserSnapshot;
|
|
1254
|
-
}
|
|
1255
|
-
|
|
1256
|
-
subscribeAskUser(listener: () => void): () => void {
|
|
1257
|
-
this.askUserListeners.add(listener);
|
|
1258
|
-
return () => this.askUserListeners.delete(listener);
|
|
1259
|
-
}
|
|
1260
|
-
|
|
1261
|
-
async resolveAskUser(response: AskUserResolution): Promise<void> {
|
|
1262
|
-
const pending = this.pendingAskUser;
|
|
1263
|
-
if (pending === undefined) {
|
|
1264
|
-
throw new Error("No AskUser question is pending.");
|
|
1265
|
-
}
|
|
1266
|
-
let result: AskUserResponse;
|
|
1267
|
-
if (response.outcome === "selected") {
|
|
1268
|
-
if (!Number.isSafeInteger(response.selectedIndex)) {
|
|
1269
|
-
throw new Error("AskUser selectedIndex must be an integer.");
|
|
1270
|
-
}
|
|
1271
|
-
const option = pending.request.options[response.selectedIndex];
|
|
1272
|
-
if (option === undefined) {
|
|
1273
|
-
throw new Error("AskUser selectedIndex is out of range.");
|
|
1274
|
-
}
|
|
1275
|
-
result = { outcome: "selected", answer: option.description };
|
|
1276
|
-
} else {
|
|
1277
|
-
result = { outcome: "dismissed" };
|
|
1278
|
-
}
|
|
1279
|
-
this.pendingAskUser = undefined;
|
|
1280
|
-
this.askUserSnapshot = Object.freeze({});
|
|
1281
|
-
pending.removeAbortListener();
|
|
1282
|
-
await this.append({
|
|
1283
|
-
type: "tool.user_question.resolved",
|
|
1284
|
-
...pending.call,
|
|
1285
|
-
data: {
|
|
1286
|
-
...result,
|
|
1287
|
-
durationMs: Date.now() - pending.startedAt,
|
|
1288
|
-
},
|
|
1289
|
-
});
|
|
1290
|
-
pending.resolve(result);
|
|
1291
|
-
this.notifyAskUserListeners();
|
|
1292
|
-
}
|
|
1293
|
-
|
|
1294
|
-
private async requestUserAnswer(
|
|
1295
|
-
call: ToolCallIdentity,
|
|
1296
|
-
request: AskUserRequest,
|
|
1297
|
-
signal: AbortSignal,
|
|
1298
|
-
): Promise<AskUserResponse> {
|
|
1299
|
-
if (this.pendingAskUser !== undefined) {
|
|
1300
|
-
throw new Error("Another AskUser question is already pending.");
|
|
1301
|
-
}
|
|
1302
|
-
if (this.pendingBashConfirmation !== undefined) {
|
|
1303
|
-
throw new Error("Cannot ask the user while a Bash confirmation is pending.");
|
|
1304
|
-
}
|
|
1305
|
-
if (signal.aborted) {
|
|
1306
|
-
throw cancellationError(signal);
|
|
1307
|
-
}
|
|
1308
|
-
const startedAt = Date.now();
|
|
1309
|
-
await this.append({
|
|
1310
|
-
type: "tool.user_question.requested",
|
|
1311
|
-
...call,
|
|
1312
|
-
data: request,
|
|
1313
|
-
});
|
|
1314
|
-
return new Promise<AskUserResponse>((resolve, reject) => {
|
|
1315
|
-
const onAbort = () => {
|
|
1316
|
-
const pending = this.pendingAskUser;
|
|
1317
|
-
if (pending?.call.toolCallId !== call.toolCallId) {
|
|
1318
|
-
return;
|
|
1319
|
-
}
|
|
1320
|
-
this.pendingAskUser = undefined;
|
|
1321
|
-
this.askUserSnapshot = Object.freeze({});
|
|
1322
|
-
void this.append({
|
|
1323
|
-
type: "tool.user_question.resolved",
|
|
1324
|
-
...call,
|
|
1325
|
-
data: {
|
|
1326
|
-
outcome: "cancelled",
|
|
1327
|
-
durationMs: Date.now() - startedAt,
|
|
1328
|
-
},
|
|
1329
|
-
}).finally(() => {
|
|
1330
|
-
reject(cancellationError(signal));
|
|
1331
|
-
this.notifyAskUserListeners();
|
|
1332
|
-
});
|
|
1333
|
-
};
|
|
1334
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
1335
|
-
const immutableRequest = Object.freeze({
|
|
1336
|
-
question: request.question,
|
|
1337
|
-
options: Object.freeze(
|
|
1338
|
-
request.options.map((option) =>
|
|
1339
|
-
Object.freeze({ description: option.description }),
|
|
1340
|
-
),
|
|
1341
|
-
),
|
|
1342
|
-
});
|
|
1343
|
-
this.pendingAskUser = {
|
|
1344
|
-
request: immutableRequest,
|
|
1345
|
-
startedAt,
|
|
1346
|
-
call,
|
|
1347
|
-
resolve,
|
|
1348
|
-
reject,
|
|
1349
|
-
removeAbortListener: () => signal.removeEventListener("abort", onAbort),
|
|
1350
|
-
};
|
|
1351
|
-
this.askUserSnapshot = Object.freeze({ pending: immutableRequest });
|
|
1352
|
-
this.notifyAskUserListeners();
|
|
1353
|
-
if (signal.aborted) {
|
|
1354
|
-
onAbort();
|
|
1355
|
-
}
|
|
1356
|
-
});
|
|
1357
|
-
}
|
|
1358
|
-
|
|
1359
|
-
private notifyAskUserListeners(): void {
|
|
1360
|
-
for (const listener of this.askUserListeners) {
|
|
1361
|
-
listener();
|
|
1362
|
-
}
|
|
1363
|
-
}
|
|
1364
|
-
|
|
1365
|
-
async importImage(
|
|
1366
|
-
sourcePath: string,
|
|
1367
|
-
signal: AbortSignal,
|
|
1368
|
-
prospectiveMessageImageCount: number,
|
|
1369
|
-
): Promise<ImportedImageAsset> {
|
|
1370
|
-
if (this.state !== "ready") {
|
|
1371
|
-
throw new Error(`Cannot import an image while RuntimeSession is ${this.state}.`);
|
|
1372
|
-
}
|
|
1373
|
-
if (
|
|
1374
|
-
!Number.isSafeInteger(prospectiveMessageImageCount) ||
|
|
1375
|
-
prospectiveMessageImageCount < 1 ||
|
|
1376
|
-
prospectiveMessageImageCount > IMAGE_INPUT_POLICY.maxImagesPerMessage
|
|
1377
|
-
) {
|
|
1378
|
-
throw new Error("Prospective Prompt image count is invalid.");
|
|
759
|
+
if (
|
|
760
|
+
!Number.isSafeInteger(prospectiveMessageImageCount) ||
|
|
761
|
+
prospectiveMessageImageCount < 1 ||
|
|
762
|
+
prospectiveMessageImageCount > IMAGE_INPUT_POLICY.maxImagesPerMessage
|
|
763
|
+
) {
|
|
764
|
+
throw new Error("Prospective Prompt image count is invalid.");
|
|
1379
765
|
}
|
|
1380
766
|
const assertImageAllowed = () => {
|
|
1381
767
|
if (this.state !== "ready") {
|
|
@@ -1421,108 +807,11 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1421
807
|
}
|
|
1422
808
|
}
|
|
1423
809
|
|
|
1424
|
-
private requireContextAutomation():
|
|
1425
|
-
if (this.
|
|
810
|
+
private requireContextAutomation(): ContextAutomationPolicy {
|
|
811
|
+
if (this.contextAutomationPolicy === undefined) {
|
|
1426
812
|
throw new Error("RuntimeSession context automation is not initialized.");
|
|
1427
813
|
}
|
|
1428
|
-
return this.
|
|
1429
|
-
}
|
|
1430
|
-
|
|
1431
|
-
private async contextStatus(call: ToolCall): Promise<ContextStatusRawResult> {
|
|
1432
|
-
const active = this.requireActiveContextTool(call, "ContextStatus");
|
|
1433
|
-
try {
|
|
1434
|
-
const usage = this.requireContextManager().measureActive(
|
|
1435
|
-
active.turn.turnId,
|
|
1436
|
-
active.ledger,
|
|
1437
|
-
);
|
|
1438
|
-
return Object.freeze({
|
|
1439
|
-
ok: true,
|
|
1440
|
-
operation: "status",
|
|
1441
|
-
usedInputTokens: usage.usedInputTokens,
|
|
1442
|
-
inputBudgetTokens: usage.inputBudgetTokens,
|
|
1443
|
-
pressure: toolContextPressure(usage.pressure),
|
|
1444
|
-
triggerTokens: usage.triggerTokens,
|
|
1445
|
-
source: usage.source,
|
|
1446
|
-
});
|
|
1447
|
-
} catch (error) {
|
|
1448
|
-
return {
|
|
1449
|
-
ok: false,
|
|
1450
|
-
operation: "status",
|
|
1451
|
-
error: this.contextToolFailure("status", error),
|
|
1452
|
-
};
|
|
1453
|
-
}
|
|
1454
|
-
}
|
|
1455
|
-
|
|
1456
|
-
private async contextSwapCandidates(
|
|
1457
|
-
call: ToolCall,
|
|
1458
|
-
page: { readonly limit: number; readonly offset: number },
|
|
1459
|
-
): Promise<ContextSwapCandidatesRawResult> {
|
|
1460
|
-
const active = this.requireActiveContextTool(call, "ContextSwapCandidates");
|
|
1461
|
-
try {
|
|
1462
|
-
const result = this.requireContextManager().listActiveSwapCandidates({
|
|
1463
|
-
turnId: active.turn.turnId,
|
|
1464
|
-
consumedThroughOrdinal: active.consumedThroughOrdinal,
|
|
1465
|
-
activeLedger: active.ledger,
|
|
1466
|
-
limit: page.limit,
|
|
1467
|
-
offset: page.offset,
|
|
1468
|
-
});
|
|
1469
|
-
if (result.total > 0 && result.usage.pressure !== "normal") {
|
|
1470
|
-
this.modelDirectedSwapLease = true;
|
|
1471
|
-
}
|
|
1472
|
-
return Object.freeze({
|
|
1473
|
-
ok: true,
|
|
1474
|
-
operation: "candidates",
|
|
1475
|
-
total: result.total,
|
|
1476
|
-
candidates: result.candidates,
|
|
1477
|
-
});
|
|
1478
|
-
} catch (error) {
|
|
1479
|
-
return {
|
|
1480
|
-
ok: false,
|
|
1481
|
-
operation: "candidates",
|
|
1482
|
-
error: this.contextToolFailure("candidate listing", error),
|
|
1483
|
-
};
|
|
1484
|
-
}
|
|
1485
|
-
}
|
|
1486
|
-
|
|
1487
|
-
private async contextSwap(
|
|
1488
|
-
call: ToolCall,
|
|
1489
|
-
selection: { readonly candidateIds: readonly MessageId[] },
|
|
1490
|
-
): Promise<ContextSwapRawResult> {
|
|
1491
|
-
const active = this.requireActiveContextTool(call, "ContextSwap");
|
|
1492
|
-
try {
|
|
1493
|
-
const result = this.requireContextManager().validateActiveSwapSelection({
|
|
1494
|
-
turnId: active.turn.turnId,
|
|
1495
|
-
consumedThroughOrdinal: active.consumedThroughOrdinal,
|
|
1496
|
-
activeLedger: active.ledger,
|
|
1497
|
-
messageIds: selection.candidateIds,
|
|
1498
|
-
});
|
|
1499
|
-
if (result.scheduled.length === 0) {
|
|
1500
|
-
return Object.freeze({
|
|
1501
|
-
ok: false,
|
|
1502
|
-
operation: "swap",
|
|
1503
|
-
scheduled: Object.freeze([]),
|
|
1504
|
-
rejected: result.rejected,
|
|
1505
|
-
});
|
|
1506
|
-
}
|
|
1507
|
-
const pending = (this.pendingModelDirectedSwap ??= new Set<MessageId>());
|
|
1508
|
-
for (const candidate of result.scheduled) pending.add(candidate.candidateId);
|
|
1509
|
-
this.modelDirectedSwapLease = false;
|
|
1510
|
-
return Object.freeze({
|
|
1511
|
-
ok: true,
|
|
1512
|
-
operation: "swap",
|
|
1513
|
-
scheduled: result.scheduled,
|
|
1514
|
-
rejected: result.rejected,
|
|
1515
|
-
note: "Swap executes when this iteration's tool frames close.",
|
|
1516
|
-
});
|
|
1517
|
-
} catch (error) {
|
|
1518
|
-
return {
|
|
1519
|
-
ok: false,
|
|
1520
|
-
operation: "swap",
|
|
1521
|
-
scheduled: [],
|
|
1522
|
-
rejected: [],
|
|
1523
|
-
error: this.contextToolFailure("swap scheduling", error),
|
|
1524
|
-
};
|
|
1525
|
-
}
|
|
814
|
+
return this.contextAutomationPolicy;
|
|
1526
815
|
}
|
|
1527
816
|
|
|
1528
817
|
private requireActiveContextTool(
|
|
@@ -1545,71 +834,6 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1545
834
|
return active as ActiveTurn & { consumedThroughOrdinal: number };
|
|
1546
835
|
}
|
|
1547
836
|
|
|
1548
|
-
private contextToolFailure(operation: string, error: unknown): string {
|
|
1549
|
-
if (error instanceof ContextManagerError && !error.fatal) {
|
|
1550
|
-
return `Context ${operation} failed (${boundedContextErrorCode(error.code)}).`;
|
|
1551
|
-
}
|
|
1552
|
-
throw new ToolExecutionFatalError(
|
|
1553
|
-
`Context ${operation} required canonical session state that could not be read safely.`,
|
|
1554
|
-
{ cause: error },
|
|
1555
|
-
);
|
|
1556
|
-
}
|
|
1557
|
-
|
|
1558
|
-
private appendSkillsCatalogLoaded(): Promise<void> {
|
|
1559
|
-
const activeNames = this.skillCoordinator
|
|
1560
|
-
.activeEntries()
|
|
1561
|
-
.map((entry) => entry.skill.name);
|
|
1562
|
-
if (
|
|
1563
|
-
this.skillCatalog.skills.size === 0 &&
|
|
1564
|
-
activeNames.length === 0 &&
|
|
1565
|
-
this.skillCatalog.shadowed.length === 0
|
|
1566
|
-
) {
|
|
1567
|
-
return Promise.resolve();
|
|
1568
|
-
}
|
|
1569
|
-
const skills = [...this.skillCatalog.skills.values()];
|
|
1570
|
-
return this.append({
|
|
1571
|
-
type: "skills.catalog.loaded",
|
|
1572
|
-
sessionId: this.sessionId,
|
|
1573
|
-
data: {
|
|
1574
|
-
availableCount: skills.length,
|
|
1575
|
-
projectCount: skills.filter((skill) => skill.scope === "project").length,
|
|
1576
|
-
userCount: skills.filter((skill) => skill.scope === "user").length,
|
|
1577
|
-
activeNames: Object.freeze(activeNames),
|
|
1578
|
-
shadowedNames: Object.freeze(
|
|
1579
|
-
this.skillCatalog.shadowed.map((entry) => entry.name),
|
|
1580
|
-
),
|
|
1581
|
-
},
|
|
1582
|
-
});
|
|
1583
|
-
}
|
|
1584
|
-
|
|
1585
|
-
private onToolCompletionsCommitted(input: {
|
|
1586
|
-
completions: readonly ToolCompletionInput[];
|
|
1587
|
-
committed: readonly CommittedToolCompletion[];
|
|
1588
|
-
}): void {
|
|
1589
|
-
if (input.completions.length !== input.committed.length) {
|
|
1590
|
-
throw new Error("Committed tool completion identity count does not match.");
|
|
1591
|
-
}
|
|
1592
|
-
for (let index = 0; index < input.completions.length; index += 1) {
|
|
1593
|
-
const completion = input.completions[index];
|
|
1594
|
-
const committed = input.committed[index];
|
|
1595
|
-
if (
|
|
1596
|
-
completion === undefined ||
|
|
1597
|
-
committed === undefined ||
|
|
1598
|
-
completion.call.toolCallId !== committed.toolCallId
|
|
1599
|
-
) {
|
|
1600
|
-
throw new Error("Committed tool completion identity is invalid.");
|
|
1601
|
-
}
|
|
1602
|
-
if (
|
|
1603
|
-
completion.kind === "returned" &&
|
|
1604
|
-
completion.raw.kind === "skill" &&
|
|
1605
|
-
completion.raw.ok &&
|
|
1606
|
-
completion.raw.status === "loaded"
|
|
1607
|
-
) {
|
|
1608
|
-
this.skillCoordinator.markPending(completion.raw.name);
|
|
1609
|
-
}
|
|
1610
|
-
}
|
|
1611
|
-
}
|
|
1612
|
-
|
|
1613
837
|
private prepareModelDispatch(input: {
|
|
1614
838
|
iteration: IterationIdentity;
|
|
1615
839
|
built: BuiltContextRequest;
|
|
@@ -1623,317 +847,27 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1623
847
|
throw new Error("Model dispatch does not belong to the active runtime turn.");
|
|
1624
848
|
}
|
|
1625
849
|
active.consumedThroughOrdinal = input.built.canonical.messages.length;
|
|
1626
|
-
|
|
1627
|
-
if (pending.length === 0) {
|
|
1628
|
-
return;
|
|
1629
|
-
}
|
|
1630
|
-
const visibleCanonicalMessageIds = new Set(
|
|
1631
|
-
input.built.compiled.entries
|
|
1632
|
-
.filter(
|
|
1633
|
-
(entry) =>
|
|
1634
|
-
entry.representation === "canonical" && entry.message.role === "tool",
|
|
1635
|
-
)
|
|
1636
|
-
.map((entry) => entry.messageId),
|
|
1637
|
-
);
|
|
1638
|
-
const included = pending.filter((activation) =>
|
|
1639
|
-
visibleCanonicalMessageIds.has(activation.activationMessageId),
|
|
1640
|
-
);
|
|
1641
|
-
if (included.length === 0) {
|
|
1642
|
-
return;
|
|
1643
|
-
}
|
|
1644
|
-
const dispatched = this.store.markSkillActivationsDispatched({
|
|
1645
|
-
iterationId: input.iteration.iterationId,
|
|
1646
|
-
activationMessageIds: included.map(
|
|
1647
|
-
(activation) => activation.activationMessageId,
|
|
1648
|
-
),
|
|
1649
|
-
});
|
|
1650
|
-
this.skillCoordinator.markDispatched(
|
|
1651
|
-
dispatched.map((activation) => activation.name),
|
|
1652
|
-
);
|
|
1653
|
-
}
|
|
1654
|
-
|
|
1655
|
-
private async commitSkillSettlements(input: {
|
|
1656
|
-
reason: "activation" | "resume";
|
|
1657
|
-
unresolved: readonly StoredSkillActivation[];
|
|
1658
|
-
candidateSurface?: StoredContextSurfaceV8;
|
|
1659
|
-
activated?: readonly string[];
|
|
1660
|
-
refreshed?: readonly string[];
|
|
1661
|
-
deactivated?: readonly string[];
|
|
1662
|
-
}): Promise<SkillsUpdateSummary> {
|
|
1663
|
-
if (input.unresolved.length === 0) {
|
|
1664
|
-
throw new Error("Agent Skills update requires unresolved activations.");
|
|
1665
|
-
}
|
|
1666
|
-
const snapshot = this.store.loadContextSnapshot();
|
|
1667
|
-
const canonicalMessages = new Map(
|
|
1668
|
-
snapshot.canonical.messages.map((message) => [message.messageId, message]),
|
|
1669
|
-
);
|
|
1670
|
-
const activeByName = new Map(
|
|
1671
|
-
this.skillCoordinator
|
|
1672
|
-
.activeEntries()
|
|
1673
|
-
.map((entry) => [entry.skill.name, entry] as const),
|
|
1674
|
-
);
|
|
1675
|
-
const activated = new Set(input.activated ?? []);
|
|
1676
|
-
const unavailable = new Set<string>();
|
|
1677
|
-
const settlements: Array<{
|
|
1678
|
-
activationMessageId: StoredSkillActivation["activationMessageId"];
|
|
1679
|
-
name: string;
|
|
1680
|
-
state: "promoted" | "rejected";
|
|
1681
|
-
rejectionReason?: string;
|
|
1682
|
-
}> = [];
|
|
1683
|
-
const receipts = [];
|
|
1684
|
-
for (const activation of [...input.unresolved].sort((left, right) =>
|
|
1685
|
-
compareText(left.name, right.name),
|
|
1686
|
-
)) {
|
|
1687
|
-
const skill = this.skillCatalog.skills.get(activation.name);
|
|
1688
|
-
const canPromote = activation.state === "dispatched" && skill !== undefined;
|
|
1689
|
-
if (canPromote) {
|
|
1690
|
-
const existing = activeByName.get(activation.name);
|
|
1691
|
-
if (
|
|
1692
|
-
existing !== undefined &&
|
|
1693
|
-
existing.activationMessageId !== activation.activationMessageId
|
|
1694
|
-
) {
|
|
1695
|
-
throw new Error(
|
|
1696
|
-
`Agent Skill ${activation.name} already has another active activation.`,
|
|
1697
|
-
);
|
|
1698
|
-
}
|
|
1699
|
-
activeByName.set(activation.name, {
|
|
1700
|
-
skill,
|
|
1701
|
-
activationMessageId: activation.activationMessageId,
|
|
1702
|
-
});
|
|
1703
|
-
activated.add(activation.name);
|
|
1704
|
-
}
|
|
1705
|
-
const state = canPromote ? "promoted" : "rejected";
|
|
1706
|
-
const rejectionReason =
|
|
1707
|
-
state === "promoted"
|
|
1708
|
-
? undefined
|
|
1709
|
-
: activation.state === "pending"
|
|
1710
|
-
? "not_dispatched"
|
|
1711
|
-
: "unavailable";
|
|
1712
|
-
if (rejectionReason === "unavailable") {
|
|
1713
|
-
unavailable.add(activation.name);
|
|
1714
|
-
}
|
|
1715
|
-
settlements.push({
|
|
1716
|
-
activationMessageId: activation.activationMessageId,
|
|
1717
|
-
name: activation.name,
|
|
1718
|
-
state,
|
|
1719
|
-
...(rejectionReason === undefined ? {} : { rejectionReason }),
|
|
1720
|
-
});
|
|
1721
|
-
const message = canonicalMessages.get(activation.activationMessageId);
|
|
1722
|
-
if (message?.role !== "tool") {
|
|
1723
|
-
throw new Error(
|
|
1724
|
-
`Agent Skill activation message ${activation.activationMessageId} is missing.`,
|
|
1725
|
-
);
|
|
1726
|
-
}
|
|
1727
|
-
receipts.push(
|
|
1728
|
-
renderSkillActivationReceipt({
|
|
1729
|
-
message: {
|
|
1730
|
-
messageId: message.messageId,
|
|
1731
|
-
frameId: message.frameId,
|
|
1732
|
-
ordinal: message.ordinal,
|
|
1733
|
-
content: message.displayText,
|
|
1734
|
-
contentSha256: message.contentSha256,
|
|
1735
|
-
},
|
|
1736
|
-
name: activation.name,
|
|
1737
|
-
outcome:
|
|
1738
|
-
state === "promoted"
|
|
1739
|
-
? "promoted"
|
|
1740
|
-
: rejectionReason === "unavailable"
|
|
1741
|
-
? "unavailable"
|
|
1742
|
-
: "rejected",
|
|
1743
|
-
}),
|
|
1744
|
-
);
|
|
1745
|
-
}
|
|
1746
|
-
const nextActive = Object.freeze(
|
|
1747
|
-
[...activeByName.values()].sort((left, right) =>
|
|
1748
|
-
compareText(left.skill.name, right.skill.name),
|
|
1749
|
-
),
|
|
1750
|
-
);
|
|
1751
|
-
const createdAt = new Date().toISOString();
|
|
1752
|
-
const definitions = this.requireTooling().registry.definitions();
|
|
1753
|
-
const renderedSystemPrompt = buildActiveSystemPrompt({
|
|
1754
|
-
baseSystemPrompt: this.input.systemPrompt,
|
|
1755
|
-
activeSkills: nextActive,
|
|
1756
|
-
});
|
|
1757
|
-
const surfacePrepared = this.input.modelClient.prepare({
|
|
1758
|
-
messages: [{ role: "system", content: renderedSystemPrompt }],
|
|
1759
|
-
tools: definitions,
|
|
1760
|
-
});
|
|
1761
|
-
const generatedSurface =
|
|
1762
|
-
input.candidateSurface ??
|
|
1763
|
-
createContextSurface({
|
|
1764
|
-
surfaceId: this.dependencies.idFactory.createContextSurfaceId(),
|
|
1765
|
-
sessionId: this.sessionId,
|
|
1766
|
-
systemPrompt: renderedSystemPrompt,
|
|
1767
|
-
recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
|
|
1768
|
-
...(this.input.projectInstruction === undefined
|
|
1769
|
-
? {}
|
|
1770
|
-
: { projectInstruction: this.input.projectInstruction }),
|
|
1771
|
-
skillCatalog: skillCatalogManifest(this.skillCatalog.skills.values()),
|
|
1772
|
-
activeSkills: nextActive.map((entry) =>
|
|
1773
|
-
activeSkillManifestEntry(entry.skill, entry.activationMessageId),
|
|
1774
|
-
),
|
|
1775
|
-
toolDefinitions: definitions,
|
|
1776
|
-
prepared: surfacePrepared,
|
|
1777
|
-
createdAt,
|
|
1778
|
-
});
|
|
1779
|
-
assertPreparedMatchesSurface(surfacePrepared, generatedSurface);
|
|
1780
|
-
const surface = sameContextSurface(snapshot.surface, generatedSurface)
|
|
1781
|
-
? snapshot.surface
|
|
1782
|
-
: generatedSurface;
|
|
1783
|
-
const startedAt = performance.now();
|
|
1784
|
-
await this.append({
|
|
1785
|
-
type: "context.revision.started",
|
|
1786
|
-
sessionId: this.sessionId,
|
|
1787
|
-
data: {
|
|
1788
|
-
strategy: "skills_update",
|
|
1789
|
-
reason: input.reason,
|
|
1790
|
-
baseRevisionNumber: snapshot.revision.revisionNumber,
|
|
1791
|
-
names: Object.freeze(
|
|
1792
|
-
input.unresolved.map((entry) => entry.name).sort(compareText),
|
|
1793
|
-
),
|
|
1794
|
-
},
|
|
1795
|
-
});
|
|
1796
|
-
let stage: "prepare" | "commit" | "activate" = "prepare";
|
|
1797
|
-
let committed = false;
|
|
1798
|
-
try {
|
|
1799
|
-
const revision = commitAgentSkillsContextUpdate({
|
|
1800
|
-
store: this.store,
|
|
1801
|
-
contextMeter: this.contextMeter,
|
|
1802
|
-
idFactory: this.dependencies.idFactory,
|
|
1803
|
-
snapshot,
|
|
1804
|
-
surface,
|
|
1805
|
-
addedOverrides: receipts,
|
|
1806
|
-
settlements,
|
|
1807
|
-
});
|
|
1808
|
-
committed = true;
|
|
1809
|
-
stage = "activate";
|
|
1810
|
-
this.skillCoordinator.replaceActive(nextActive);
|
|
1811
|
-
this.skillCoordinator.settle(
|
|
1812
|
-
input.unresolved.map((activation) => activation.name),
|
|
1813
|
-
);
|
|
1814
|
-
const summary = Object.freeze({
|
|
1815
|
-
previousRevisionNumber: snapshot.revision.revisionNumber,
|
|
1816
|
-
revisionNumber: revision.revisionNumber,
|
|
1817
|
-
activated: Object.freeze([...activated].sort()),
|
|
1818
|
-
refreshed: Object.freeze([...(input.refreshed ?? [])].sort()),
|
|
1819
|
-
deactivated: Object.freeze([...(input.deactivated ?? [])].sort()),
|
|
1820
|
-
unavailable: Object.freeze([...unavailable].sort()),
|
|
1821
|
-
addedOverrideCount: receipts.length,
|
|
1822
|
-
});
|
|
1823
|
-
await this.append({
|
|
1824
|
-
type: "context.revision.finished",
|
|
1825
|
-
sessionId: this.sessionId,
|
|
1826
|
-
data: {
|
|
1827
|
-
strategy: "skills_update",
|
|
1828
|
-
reason: input.reason,
|
|
1829
|
-
baseRevisionNumber: summary.previousRevisionNumber,
|
|
1830
|
-
revisionNumber: summary.revisionNumber,
|
|
1831
|
-
activated: summary.activated,
|
|
1832
|
-
refreshed: summary.refreshed,
|
|
1833
|
-
deactivated: summary.deactivated,
|
|
1834
|
-
unavailable: summary.unavailable,
|
|
1835
|
-
addedOverrideCount: summary.addedOverrideCount,
|
|
1836
|
-
measuredAnchorCleared: true,
|
|
1837
|
-
durationMs: elapsedMs(startedAt),
|
|
1838
|
-
},
|
|
1839
|
-
});
|
|
1840
|
-
return summary;
|
|
1841
|
-
} catch (error) {
|
|
1842
|
-
if (error instanceof ContextManagerError) {
|
|
1843
|
-
committed = error.committed;
|
|
1844
|
-
stage =
|
|
1845
|
-
error.stage === "commit"
|
|
1846
|
-
? "commit"
|
|
1847
|
-
: error.stage === "activate"
|
|
1848
|
-
? "activate"
|
|
1849
|
-
: "prepare";
|
|
1850
|
-
}
|
|
1851
|
-
await this.append({
|
|
1852
|
-
type: "context.revision.failed",
|
|
1853
|
-
sessionId: this.sessionId,
|
|
1854
|
-
data: {
|
|
1855
|
-
strategy: "skills_update",
|
|
1856
|
-
reason: input.reason,
|
|
1857
|
-
stage,
|
|
1858
|
-
errorCode: boundedContextErrorCode(
|
|
1859
|
-
error instanceof ContextManagerError
|
|
1860
|
-
? error.code
|
|
1861
|
-
: error instanceof SessionError
|
|
1862
|
-
? error.code
|
|
1863
|
-
: error instanceof Error
|
|
1864
|
-
? error.name
|
|
1865
|
-
: "SKILLS_UPDATE_VALIDATION_FAILED",
|
|
1866
|
-
),
|
|
1867
|
-
error: `Agent Skills update failed at ${stage}.`,
|
|
1868
|
-
committed,
|
|
1869
|
-
},
|
|
1870
|
-
}).catch(() => undefined);
|
|
1871
|
-
throw error;
|
|
1872
|
-
}
|
|
850
|
+
this.runtimeSkills.markModelDispatch(input);
|
|
1873
851
|
}
|
|
1874
852
|
|
|
1875
853
|
promptScheduler(): PromptSchedulerSnapshot {
|
|
1876
|
-
return this.
|
|
854
|
+
return this.scheduler.promptScheduler();
|
|
1877
855
|
}
|
|
1878
856
|
|
|
1879
857
|
subscribePromptScheduler(listener: () => void): () => void {
|
|
1880
|
-
this.
|
|
1881
|
-
return () => this.promptSchedulerListeners.delete(listener);
|
|
858
|
+
return this.scheduler.subscribePromptScheduler(listener);
|
|
1882
859
|
}
|
|
1883
860
|
|
|
1884
861
|
queueFollowUp(userMessage: UserMessage): QueueFollowUpResult {
|
|
1885
|
-
|
|
1886
|
-
throw new Error("Cannot queue a follow-up while no execution chain is running.");
|
|
1887
|
-
}
|
|
1888
|
-
validateUserMessage(userMessage);
|
|
1889
|
-
if (userMessage.attachments !== undefined) {
|
|
1890
|
-
throw new Error("Active-turn follow-ups do not support image attachments.");
|
|
1891
|
-
}
|
|
1892
|
-
if (this.queuedPrompts.length >= MAX_QUEUED_PROMPTS) {
|
|
1893
|
-
throw new Error(`At most ${MAX_QUEUED_PROMPTS} follow-ups may be queued.`);
|
|
1894
|
-
}
|
|
1895
|
-
const queuedBytes = this.queuedPrompts.reduce(
|
|
1896
|
-
(total, entry) => total + Buffer.byteLength(entry.userMessage.content, "utf8"),
|
|
1897
|
-
0,
|
|
1898
|
-
);
|
|
1899
|
-
const nextBytes = Buffer.byteLength(userMessage.content, "utf8");
|
|
1900
|
-
if (queuedBytes + nextBytes > MAX_QUEUED_PROMPT_TEXT_BYTES) {
|
|
1901
|
-
throw new Error("Queued follow-ups exceed the 64 KiB text limit.");
|
|
1902
|
-
}
|
|
1903
|
-
this.queuedPrompts.push({
|
|
1904
|
-
userMessage: Object.freeze({ ...userMessage }),
|
|
1905
|
-
});
|
|
1906
|
-
this.notifyPromptScheduler();
|
|
1907
|
-
return Object.freeze({
|
|
1908
|
-
kind: "queued",
|
|
1909
|
-
pendingCount: this.queuedPrompts.length,
|
|
1910
|
-
...(this.activeTurn === undefined
|
|
1911
|
-
? {}
|
|
1912
|
-
: { activeTurnId: this.activeTurn.turn.turnId }),
|
|
1913
|
-
});
|
|
862
|
+
return this.scheduler.queueFollowUp(userMessage);
|
|
1914
863
|
}
|
|
1915
864
|
|
|
1916
|
-
|
|
1917
|
-
return
|
|
865
|
+
admitTurn(input: ExecuteTurnInput): Promise<AcceptedTurn> {
|
|
866
|
+
return this.scheduler.admitTurn(input);
|
|
1918
867
|
}
|
|
1919
868
|
|
|
1920
|
-
async
|
|
1921
|
-
|
|
1922
|
-
throw new Error(
|
|
1923
|
-
`Cannot execute a turn while RuntimeSession is ${this.state}; a prompt chain is already executing.`,
|
|
1924
|
-
);
|
|
1925
|
-
}
|
|
1926
|
-
this.executionChainRunning = true;
|
|
1927
|
-
this.notifyPromptScheduler();
|
|
1928
|
-
try {
|
|
1929
|
-
const accepted = await this.admitSingleTurn(input);
|
|
1930
|
-
const completion = this.continueExecutionChain(accepted.completion, input.signal);
|
|
1931
|
-
return Object.freeze({ ...accepted, completion });
|
|
1932
|
-
} catch (error) {
|
|
1933
|
-
this.executionChainRunning = false;
|
|
1934
|
-
this.notifyPromptScheduler();
|
|
1935
|
-
throw error;
|
|
1936
|
-
}
|
|
869
|
+
async executeTurn(input: ExecuteTurnInput): Promise<RunAgentResult> {
|
|
870
|
+
return (await this.admitTurn(input)).completion;
|
|
1937
871
|
}
|
|
1938
872
|
|
|
1939
873
|
private async admitSingleTurn(input: ExecuteTurnInput): Promise<AcceptedTurn> {
|
|
@@ -1997,7 +931,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
1997
931
|
controller,
|
|
1998
932
|
completion,
|
|
1999
933
|
};
|
|
2000
|
-
this.notifyPromptScheduler();
|
|
934
|
+
this.scheduler.notifyPromptScheduler();
|
|
2001
935
|
return Object.freeze({
|
|
2002
936
|
turnId: turn.turnId,
|
|
2003
937
|
userMessage: input.userMessage,
|
|
@@ -2019,79 +953,6 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2019
953
|
}
|
|
2020
954
|
}
|
|
2021
955
|
|
|
2022
|
-
private async continueExecutionChain(
|
|
2023
|
-
initialCompletion: Promise<RunAgentResult>,
|
|
2024
|
-
signal: AbortSignal,
|
|
2025
|
-
): Promise<RunAgentResult> {
|
|
2026
|
-
let completion = initialCompletion;
|
|
2027
|
-
let finalResult: RunAgentResult;
|
|
2028
|
-
try {
|
|
2029
|
-
for (;;) {
|
|
2030
|
-
finalResult = await completion;
|
|
2031
|
-
if (finalResult.status !== "completed" || this.queuedPrompts.length === 0) {
|
|
2032
|
-
return finalResult;
|
|
2033
|
-
}
|
|
2034
|
-
const next = this.queuedPrompts[0];
|
|
2035
|
-
if (next === undefined) {
|
|
2036
|
-
return finalResult;
|
|
2037
|
-
}
|
|
2038
|
-
const accepted = await this.admitSingleTurn({
|
|
2039
|
-
userMessage: next.userMessage,
|
|
2040
|
-
signal,
|
|
2041
|
-
});
|
|
2042
|
-
this.queuedPrompts.shift();
|
|
2043
|
-
this.notifyPromptScheduler();
|
|
2044
|
-
completion = accepted.completion;
|
|
2045
|
-
}
|
|
2046
|
-
} finally {
|
|
2047
|
-
this.queuedPrompts.splice(0);
|
|
2048
|
-
this.executionChainRunning = false;
|
|
2049
|
-
this.notifyPromptScheduler();
|
|
2050
|
-
}
|
|
2051
|
-
}
|
|
2052
|
-
|
|
2053
|
-
private notifyPromptScheduler(): void {
|
|
2054
|
-
this.promptSchedulerSnapshot = Object.freeze({
|
|
2055
|
-
state: this.executionChainRunning ? "running" : "idle",
|
|
2056
|
-
...(this.activeTurn === undefined
|
|
2057
|
-
? {}
|
|
2058
|
-
: { activeTurnId: this.activeTurn.turn.turnId }),
|
|
2059
|
-
pendingCount: this.queuedPrompts.length,
|
|
2060
|
-
});
|
|
2061
|
-
for (const listener of this.promptSchedulerListeners) listener();
|
|
2062
|
-
}
|
|
2063
|
-
|
|
2064
|
-
private async applyQueuedSteering(input: {
|
|
2065
|
-
turn: TurnIdentity;
|
|
2066
|
-
ledger: AgentTurnLedger;
|
|
2067
|
-
}): Promise<number> {
|
|
2068
|
-
if (this.activeTurn?.turn.turnId !== input.turn.turnId) {
|
|
2069
|
-
throw new Error("Cannot apply steering outside the active turn.");
|
|
2070
|
-
}
|
|
2071
|
-
if (this.queuedPrompts.length === 0) return 0;
|
|
2072
|
-
const drained = this.queuedPrompts.splice(0);
|
|
2073
|
-
const records = input.ledger.appendSteeringUserMessages(
|
|
2074
|
-
drained.map((entry) => entry.userMessage),
|
|
2075
|
-
);
|
|
2076
|
-
this.notifyPromptScheduler();
|
|
2077
|
-
for (let index = 0; index < records.length; index += 1) {
|
|
2078
|
-
const record = records[index];
|
|
2079
|
-
const queued = drained[index];
|
|
2080
|
-
if (record === undefined || queued === undefined) {
|
|
2081
|
-
throw new Error("Steering ledger result did not match the drained queue.");
|
|
2082
|
-
}
|
|
2083
|
-
await this.append({
|
|
2084
|
-
type: "turn.steering.applied",
|
|
2085
|
-
...input.turn,
|
|
2086
|
-
data: {
|
|
2087
|
-
userPrompt: projectUserMessage(queued.userMessage),
|
|
2088
|
-
ordinal: record.ordinal,
|
|
2089
|
-
},
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
|
-
return records.length;
|
|
2093
|
-
}
|
|
2094
|
-
|
|
2095
956
|
private settleAdmission(admission: ActiveAdmission): void {
|
|
2096
957
|
if (this.activeAdmission !== admission) {
|
|
2097
958
|
throw new Error("Runtime admission ownership was lost.");
|
|
@@ -2124,108 +985,14 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2124
985
|
});
|
|
2125
986
|
}
|
|
2126
987
|
|
|
2127
|
-
compactContext(): Promise<ContextCompactionResult> {
|
|
2128
|
-
if (this.state !== "ready") {
|
|
2129
|
-
throw new Error(`Cannot compact context while RuntimeSession is ${this.state}.`);
|
|
2130
|
-
}
|
|
2131
|
-
if (this.activeTurn !== undefined) {
|
|
2132
|
-
throw new Error("Cannot compact context while a turn is active.");
|
|
2133
|
-
}
|
|
2134
|
-
const completion = this.performCompactContext();
|
|
2135
|
-
this.activeContextRevision = completion;
|
|
2136
|
-
void completion.then(
|
|
2137
|
-
() => {
|
|
2138
|
-
if (this.activeContextRevision === completion) {
|
|
2139
|
-
this.activeContextRevision = undefined;
|
|
2140
|
-
}
|
|
2141
|
-
},
|
|
2142
|
-
() => {
|
|
2143
|
-
if (this.activeContextRevision === completion) {
|
|
2144
|
-
this.activeContextRevision = undefined;
|
|
2145
|
-
}
|
|
2146
|
-
},
|
|
2147
|
-
);
|
|
2148
|
-
return completion;
|
|
2149
|
-
}
|
|
2150
|
-
|
|
2151
|
-
private async performCompactContext(): Promise<ContextCompactionResult> {
|
|
2152
|
-
if (this.state !== "ready") {
|
|
2153
|
-
throw new Error(`Cannot compact context while RuntimeSession is ${this.state}.`);
|
|
2154
|
-
}
|
|
2155
|
-
if (this.activeTurn !== undefined) {
|
|
2156
|
-
throw new Error("Cannot compact context while a turn is active.");
|
|
2157
|
-
}
|
|
2158
|
-
this.store.assertContextRevisionIdle();
|
|
2159
|
-
this.state = "compacting";
|
|
2160
|
-
let started = false;
|
|
2161
|
-
try {
|
|
2162
|
-
await this.append({
|
|
2163
|
-
type: "context.revision.started",
|
|
2164
|
-
sessionId: this.sessionId,
|
|
2165
|
-
data: {
|
|
2166
|
-
strategy: "swap",
|
|
2167
|
-
reason: "manual",
|
|
2168
|
-
policyVersion: "swap-only-v1",
|
|
2169
|
-
rendererFormat: "swap-observation-v1",
|
|
2170
|
-
},
|
|
2171
|
-
});
|
|
2172
|
-
started = true;
|
|
2173
|
-
const result = await this.requireContextManager().compact(
|
|
2174
|
-
this.dependencies.manualCompactionTrigger(),
|
|
2175
|
-
);
|
|
2176
|
-
await this.append({
|
|
2177
|
-
type: "context.revision.finished",
|
|
2178
|
-
sessionId: this.sessionId,
|
|
2179
|
-
data: contextRevisionFinishedData(result),
|
|
2180
|
-
});
|
|
2181
|
-
if (this.state === "compacting") {
|
|
2182
|
-
this.state = "ready";
|
|
2183
|
-
}
|
|
2184
|
-
return result;
|
|
2185
|
-
} catch (error) {
|
|
2186
|
-
if (started && !(error instanceof RuntimeEventAppendError)) {
|
|
2187
|
-
const failure =
|
|
2188
|
-
error instanceof ContextManagerError
|
|
2189
|
-
? error
|
|
2190
|
-
: new ContextManagerError(
|
|
2191
|
-
"activate",
|
|
2192
|
-
error instanceof Error ? error.name : "CONTEXT_COMPACTION_FAILED",
|
|
2193
|
-
true,
|
|
2194
|
-
false,
|
|
2195
|
-
"Context compaction failed.",
|
|
2196
|
-
{ cause: error },
|
|
2197
|
-
);
|
|
2198
|
-
await this.append({
|
|
2199
|
-
type: "context.revision.failed",
|
|
2200
|
-
sessionId: this.sessionId,
|
|
2201
|
-
data: {
|
|
2202
|
-
strategy: "swap",
|
|
2203
|
-
reason: "manual",
|
|
2204
|
-
stage: failure.stage,
|
|
2205
|
-
errorCode: boundedContextErrorCode(failure.code),
|
|
2206
|
-
error: `Context compaction failed at ${failure.stage}.`,
|
|
2207
|
-
},
|
|
2208
|
-
}).catch(() => undefined);
|
|
2209
|
-
}
|
|
2210
|
-
if (!(error instanceof ContextManagerError) || error.fatal) {
|
|
2211
|
-
this.fault(error);
|
|
2212
|
-
} else if (this.state === "compacting") {
|
|
2213
|
-
this.state = "ready";
|
|
2214
|
-
}
|
|
2215
|
-
throw error;
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
|
|
2219
|
-
retireContext(): Promise<ContextRetirementResult> {
|
|
2220
|
-
if (this.state !== "ready") {
|
|
2221
|
-
throw new Error(
|
|
2222
|
-
`Cannot retire context prefix while RuntimeSession is ${this.state}.`,
|
|
2223
|
-
);
|
|
988
|
+
compactContext(): Promise<ContextCompactionResult> {
|
|
989
|
+
if (this.state !== "ready") {
|
|
990
|
+
throw new Error(`Cannot compact context while RuntimeSession is ${this.state}.`);
|
|
2224
991
|
}
|
|
2225
992
|
if (this.activeTurn !== undefined) {
|
|
2226
|
-
throw new Error("Cannot
|
|
993
|
+
throw new Error("Cannot compact context while a turn is active.");
|
|
2227
994
|
}
|
|
2228
|
-
const completion = this.
|
|
995
|
+
const completion = this.contextMaintenance.performCompactContext();
|
|
2229
996
|
this.activeContextRevision = completion;
|
|
2230
997
|
void completion.then(
|
|
2231
998
|
() => {
|
|
@@ -2242,7 +1009,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2242
1009
|
return completion;
|
|
2243
1010
|
}
|
|
2244
1011
|
|
|
2245
|
-
|
|
1012
|
+
retireContext(): Promise<ContextRetirementResult> {
|
|
2246
1013
|
if (this.state !== "ready") {
|
|
2247
1014
|
throw new Error(
|
|
2248
1015
|
`Cannot retire context prefix while RuntimeSession is ${this.state}.`,
|
|
@@ -2251,67 +1018,21 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2251
1018
|
if (this.activeTurn !== undefined) {
|
|
2252
1019
|
throw new Error("Cannot retire context prefix while a turn is active.");
|
|
2253
1020
|
}
|
|
2254
|
-
this.
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
started = true;
|
|
2270
|
-
const result = await this.requireContextManager().retirePrefix(
|
|
2271
|
-
this.dependencies.manualRetirementTrigger(),
|
|
2272
|
-
);
|
|
2273
|
-
await this.append({
|
|
2274
|
-
type: "context.revision.finished",
|
|
2275
|
-
sessionId: this.sessionId,
|
|
2276
|
-
data: contextRetirementFinishedData(result),
|
|
2277
|
-
});
|
|
2278
|
-
if (this.state === "compacting") {
|
|
2279
|
-
this.state = "ready";
|
|
2280
|
-
}
|
|
2281
|
-
return result;
|
|
2282
|
-
} catch (error) {
|
|
2283
|
-
if (started && !(error instanceof RuntimeEventAppendError)) {
|
|
2284
|
-
const failure =
|
|
2285
|
-
error instanceof ContextManagerError
|
|
2286
|
-
? error
|
|
2287
|
-
: new ContextManagerError(
|
|
2288
|
-
"activate",
|
|
2289
|
-
error instanceof Error ? error.name : "CONTEXT_RETIREMENT_FAILED",
|
|
2290
|
-
true,
|
|
2291
|
-
false,
|
|
2292
|
-
"Context prefix retirement failed.",
|
|
2293
|
-
{ cause: error },
|
|
2294
|
-
);
|
|
2295
|
-
await this.append({
|
|
2296
|
-
type: "context.revision.failed",
|
|
2297
|
-
sessionId: this.sessionId,
|
|
2298
|
-
data: {
|
|
2299
|
-
strategy: "retire_prefix",
|
|
2300
|
-
reason: "manual",
|
|
2301
|
-
stage: failure.stage,
|
|
2302
|
-
errorCode: boundedContextErrorCode(failure.code),
|
|
2303
|
-
error: `Context prefix retirement failed at ${failure.stage}.`,
|
|
2304
|
-
committed: failure.committed,
|
|
2305
|
-
},
|
|
2306
|
-
}).catch(() => undefined);
|
|
2307
|
-
}
|
|
2308
|
-
if (!(error instanceof ContextManagerError) || error.fatal) {
|
|
2309
|
-
this.fault(error);
|
|
2310
|
-
} else if (this.state === "compacting") {
|
|
2311
|
-
this.state = "ready";
|
|
2312
|
-
}
|
|
2313
|
-
throw error;
|
|
2314
|
-
}
|
|
1021
|
+
const completion = this.contextMaintenance.performRetireContext();
|
|
1022
|
+
this.activeContextRevision = completion;
|
|
1023
|
+
void completion.then(
|
|
1024
|
+
() => {
|
|
1025
|
+
if (this.activeContextRevision === completion) {
|
|
1026
|
+
this.activeContextRevision = undefined;
|
|
1027
|
+
}
|
|
1028
|
+
},
|
|
1029
|
+
() => {
|
|
1030
|
+
if (this.activeContextRevision === completion) {
|
|
1031
|
+
this.activeContextRevision = undefined;
|
|
1032
|
+
}
|
|
1033
|
+
},
|
|
1034
|
+
);
|
|
1035
|
+
return completion;
|
|
2315
1036
|
}
|
|
2316
1037
|
|
|
2317
1038
|
dispose(reason: SessionDisposeReason): Promise<void> {
|
|
@@ -2322,8 +1043,8 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2322
1043
|
canSwitchSession(): boolean {
|
|
2323
1044
|
return (
|
|
2324
1045
|
this.state === "ready" &&
|
|
2325
|
-
!this.
|
|
2326
|
-
this.
|
|
1046
|
+
!this.scheduler.isRunning &&
|
|
1047
|
+
this.scheduler.pendingCount === 0 &&
|
|
2327
1048
|
this.activeTurn === undefined &&
|
|
2328
1049
|
(this.tooling?.taskManager
|
|
2329
1050
|
.listBackgroundTasks()
|
|
@@ -2445,9 +1166,9 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2445
1166
|
const selection = this.dependencies.selectShadowPlanning(planningInput);
|
|
2446
1167
|
if (
|
|
2447
1168
|
selection?.trigger === "runtime_pressure" &&
|
|
2448
|
-
this.requireContextAutomation().
|
|
1169
|
+
this.requireContextAutomation().automaticSwap
|
|
2449
1170
|
) {
|
|
2450
|
-
this.
|
|
1171
|
+
this.contextMaintenance.scheduleAutomaticMaintenance();
|
|
2451
1172
|
}
|
|
2452
1173
|
return selection;
|
|
2453
1174
|
},
|
|
@@ -2477,7 +1198,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2477
1198
|
);
|
|
2478
1199
|
pendingLedgerTurn.finish(error.result);
|
|
2479
1200
|
settled = true;
|
|
2480
|
-
await this.settleClosedTurnSkills();
|
|
1201
|
+
await this.runtimeSkills.settleClosedTurnSkills();
|
|
2481
1202
|
throw error;
|
|
2482
1203
|
}
|
|
2483
1204
|
|
|
@@ -2497,10 +1218,10 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2497
1218
|
if (result.status === "completed") {
|
|
2498
1219
|
this.notifyCompletedTurn(turn);
|
|
2499
1220
|
}
|
|
2500
|
-
await this.settleClosedTurnSkills();
|
|
1221
|
+
await this.runtimeSkills.settleClosedTurnSkills();
|
|
2501
1222
|
if (result.status === "completed") {
|
|
2502
|
-
await this.evaluateClosedTurnContextPressure();
|
|
2503
|
-
await this.performAutomaticContextMaintenance();
|
|
1223
|
+
await this.contextMaintenance.evaluateClosedTurnContextPressure();
|
|
1224
|
+
await this.contextMaintenance.performAutomaticContextMaintenance();
|
|
2504
1225
|
}
|
|
2505
1226
|
return result;
|
|
2506
1227
|
} catch (error) {
|
|
@@ -2512,317 +1233,14 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2512
1233
|
} finally {
|
|
2513
1234
|
removeExternalAbortListener();
|
|
2514
1235
|
this.activeTurn = undefined;
|
|
2515
|
-
this.notifyPromptScheduler();
|
|
2516
|
-
this.
|
|
2517
|
-
this.pendingModelDirectedSwap = undefined;
|
|
2518
|
-
this.modelDirectedSwapLease = false;
|
|
2519
|
-
this.pressureNoticeSentThisTurn = false;
|
|
1236
|
+
this.scheduler.notifyPromptScheduler();
|
|
1237
|
+
this.contextMaintenance.finishTurn();
|
|
2520
1238
|
if (this.state === "executing") {
|
|
2521
1239
|
this.state = "ready";
|
|
2522
1240
|
}
|
|
2523
1241
|
}
|
|
2524
1242
|
}
|
|
2525
1243
|
|
|
2526
|
-
private async evaluateClosedTurnContextPressure(): Promise<void> {
|
|
2527
|
-
const automation = this.requireContextAutomation();
|
|
2528
|
-
if (!automation.automaticSwapOnly) return;
|
|
2529
|
-
|
|
2530
|
-
const snapshot = this.requireContextManager().measureCurrent();
|
|
2531
|
-
await this.append({
|
|
2532
|
-
type: "context.usage.updated",
|
|
2533
|
-
sessionId: this.sessionId,
|
|
2534
|
-
data: { phase: "turn_close", snapshot },
|
|
2535
|
-
});
|
|
2536
|
-
if (snapshot.pressure !== "normal") {
|
|
2537
|
-
this.pendingAutomaticContextMaintenance = true;
|
|
2538
|
-
}
|
|
2539
|
-
}
|
|
2540
|
-
|
|
2541
|
-
private async performAutomaticContextMaintenance(): Promise<void> {
|
|
2542
|
-
if (!this.pendingAutomaticContextMaintenance) return;
|
|
2543
|
-
this.pendingAutomaticContextMaintenance = false;
|
|
2544
|
-
const automation = this.requireContextAutomation();
|
|
2545
|
-
if (!automation.automaticSwapOnly) return;
|
|
2546
|
-
if (this.state !== "executing") {
|
|
2547
|
-
throw new Error(
|
|
2548
|
-
`Cannot run automatic context maintenance while RuntimeSession is ${this.state}.`,
|
|
2549
|
-
);
|
|
2550
|
-
}
|
|
2551
|
-
this.store.assertContextRevisionIdle();
|
|
2552
|
-
const qualificationId = requireAutomationQualificationId(automation);
|
|
2553
|
-
this.state = "maintaining_context";
|
|
2554
|
-
try {
|
|
2555
|
-
const swap = await this.performAutomaticCompaction(qualificationId);
|
|
2556
|
-
if (swap === undefined) return;
|
|
2557
|
-
if (automation.automaticPrefixRetirement && automaticSwapNeedsRetirement(swap)) {
|
|
2558
|
-
await this.performAutomaticRetirement(qualificationId);
|
|
2559
|
-
}
|
|
2560
|
-
} finally {
|
|
2561
|
-
if (this.state === "maintaining_context") {
|
|
2562
|
-
this.state = "executing";
|
|
2563
|
-
}
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
|
|
2567
|
-
private async performActiveTurnContextMaintenance(input: {
|
|
2568
|
-
turn: TurnIdentity;
|
|
2569
|
-
consumedThroughOrdinal: number;
|
|
2570
|
-
ledger: AgentTurnLedger;
|
|
2571
|
-
}): Promise<void> {
|
|
2572
|
-
if (this.state !== "executing") {
|
|
2573
|
-
throw new Error(
|
|
2574
|
-
`Cannot maintain active-turn context while RuntimeSession is ${this.state}.`,
|
|
2575
|
-
);
|
|
2576
|
-
}
|
|
2577
|
-
const pendingModelDirectedSwap = this.pendingModelDirectedSwap;
|
|
2578
|
-
this.pendingModelDirectedSwap = undefined;
|
|
2579
|
-
|
|
2580
|
-
const automation = this.requireContextAutomation();
|
|
2581
|
-
const manager = this.requireContextManager();
|
|
2582
|
-
this.pendingAutomaticContextMaintenance = false;
|
|
2583
|
-
|
|
2584
|
-
let suppressAutomaticSwap = this.modelDirectedSwapLease;
|
|
2585
|
-
this.modelDirectedSwapLease = false;
|
|
2586
|
-
|
|
2587
|
-
if (pendingModelDirectedSwap !== undefined) {
|
|
2588
|
-
suppressAutomaticSwap = false;
|
|
2589
|
-
this.state = "maintaining_context";
|
|
2590
|
-
try {
|
|
2591
|
-
await this.performModelDirectedCompaction({
|
|
2592
|
-
turn: input.turn,
|
|
2593
|
-
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2594
|
-
ledger: input.ledger,
|
|
2595
|
-
messageIds: Object.freeze([...pendingModelDirectedSwap]),
|
|
2596
|
-
});
|
|
2597
|
-
} finally {
|
|
2598
|
-
if (this.state === "maintaining_context") {
|
|
2599
|
-
this.state = "executing";
|
|
2600
|
-
}
|
|
2601
|
-
}
|
|
2602
|
-
}
|
|
2603
|
-
|
|
2604
|
-
let measured: ContextUsageSnapshot | undefined;
|
|
2605
|
-
if (
|
|
2606
|
-
pendingModelDirectedSwap === undefined &&
|
|
2607
|
-
(suppressAutomaticSwap ||
|
|
2608
|
-
!this.pressureNoticeSentThisTurn ||
|
|
2609
|
-
automation.automaticSwapOnly)
|
|
2610
|
-
) {
|
|
2611
|
-
measured = manager.measureCurrent(input.turn.turnId, input.ledger);
|
|
2612
|
-
if (!this.pressureNoticeSentThisTurn && measured.pressure !== "normal") {
|
|
2613
|
-
await this.injectContextPressureNotice({
|
|
2614
|
-
turn: input.turn,
|
|
2615
|
-
ledger: input.ledger,
|
|
2616
|
-
usage: measured,
|
|
2617
|
-
automaticSwapEnabled: automation.automaticSwapOnly,
|
|
2618
|
-
});
|
|
2619
|
-
this.pressureNoticeSentThisTurn = true;
|
|
2620
|
-
suppressAutomaticSwap = true;
|
|
2621
|
-
}
|
|
2622
|
-
if (measured.pressure === "blocked") {
|
|
2623
|
-
// Emergency override: a lease or notice must never hold automatic
|
|
2624
|
-
// compaction past the budget line; the next preflight would fail the
|
|
2625
|
-
// turn before the model could act.
|
|
2626
|
-
suppressAutomaticSwap = false;
|
|
2627
|
-
}
|
|
2628
|
-
}
|
|
2629
|
-
|
|
2630
|
-
if (suppressAutomaticSwap || !automation.automaticSwapOnly) {
|
|
2631
|
-
return;
|
|
2632
|
-
}
|
|
2633
|
-
|
|
2634
|
-
this.state = "maintaining_context";
|
|
2635
|
-
try {
|
|
2636
|
-
const usage = measured ?? manager.measureCurrent(input.turn.turnId, input.ledger);
|
|
2637
|
-
if (usage.pressure === "normal") return;
|
|
2638
|
-
|
|
2639
|
-
const qualificationId = requireAutomationQualificationId(automation);
|
|
2640
|
-
const compactionTrigger = {
|
|
2641
|
-
kind: "runtime_pressure",
|
|
2642
|
-
activeTurn: {
|
|
2643
|
-
turnId: input.turn.turnId,
|
|
2644
|
-
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2645
|
-
},
|
|
2646
|
-
} as const;
|
|
2647
|
-
await this.append({
|
|
2648
|
-
type: "context.revision.started",
|
|
2649
|
-
sessionId: this.sessionId,
|
|
2650
|
-
data: {
|
|
2651
|
-
strategy: "swap",
|
|
2652
|
-
reason: "runtime_pressure",
|
|
2653
|
-
policyVersion: "swap-only-v1",
|
|
2654
|
-
rendererFormat: "swap-observation-v1",
|
|
2655
|
-
qualificationId,
|
|
2656
|
-
},
|
|
2657
|
-
});
|
|
2658
|
-
let swap: ContextCompactionResult;
|
|
2659
|
-
try {
|
|
2660
|
-
swap = await manager.compact(compactionTrigger, input.ledger);
|
|
2661
|
-
await this.append({
|
|
2662
|
-
type: "context.revision.finished",
|
|
2663
|
-
sessionId: this.sessionId,
|
|
2664
|
-
data: contextRevisionFinishedData(swap, "runtime_pressure", qualificationId),
|
|
2665
|
-
});
|
|
2666
|
-
} catch (error) {
|
|
2667
|
-
const failure = automaticContextFailure(error, "compaction");
|
|
2668
|
-
await this.append({
|
|
2669
|
-
type: "context.revision.failed",
|
|
2670
|
-
sessionId: this.sessionId,
|
|
2671
|
-
data: {
|
|
2672
|
-
strategy: "swap",
|
|
2673
|
-
reason: "runtime_pressure",
|
|
2674
|
-
stage: failure.stage,
|
|
2675
|
-
errorCode: boundedContextErrorCode(failure.code),
|
|
2676
|
-
error: `Automatic context compaction failed at ${failure.stage}.`,
|
|
2677
|
-
qualificationId,
|
|
2678
|
-
},
|
|
2679
|
-
}).catch(() => undefined);
|
|
2680
|
-
if (failure.fatal) throw error;
|
|
2681
|
-
return;
|
|
2682
|
-
}
|
|
2683
|
-
|
|
2684
|
-
if (
|
|
2685
|
-
!automation.automaticPrefixRetirement ||
|
|
2686
|
-
!automaticSwapNeedsRetirement(swap)
|
|
2687
|
-
) {
|
|
2688
|
-
return;
|
|
2689
|
-
}
|
|
2690
|
-
|
|
2691
|
-
await this.append({
|
|
2692
|
-
type: "context.revision.started",
|
|
2693
|
-
sessionId: this.sessionId,
|
|
2694
|
-
data: {
|
|
2695
|
-
strategy: "retire_prefix",
|
|
2696
|
-
reason: "runtime_pressure",
|
|
2697
|
-
policyVersion: "recall-first-retirement-v1",
|
|
2698
|
-
baseRevisionNumber: this.store.loadContextSnapshot().revision.revisionNumber,
|
|
2699
|
-
qualificationId,
|
|
2700
|
-
},
|
|
2701
|
-
});
|
|
2702
|
-
try {
|
|
2703
|
-
const retirement = await manager.retirePrefix(
|
|
2704
|
-
{
|
|
2705
|
-
kind: "runtime_pressure",
|
|
2706
|
-
activeTurnId: input.turn.turnId,
|
|
2707
|
-
},
|
|
2708
|
-
input.ledger,
|
|
2709
|
-
);
|
|
2710
|
-
await this.append({
|
|
2711
|
-
type: "context.revision.finished",
|
|
2712
|
-
sessionId: this.sessionId,
|
|
2713
|
-
data: contextRetirementFinishedData(
|
|
2714
|
-
retirement,
|
|
2715
|
-
"runtime_pressure",
|
|
2716
|
-
qualificationId,
|
|
2717
|
-
),
|
|
2718
|
-
});
|
|
2719
|
-
} catch (error) {
|
|
2720
|
-
const failure = automaticContextFailure(error, "retirement");
|
|
2721
|
-
await this.append({
|
|
2722
|
-
type: "context.revision.failed",
|
|
2723
|
-
sessionId: this.sessionId,
|
|
2724
|
-
data: {
|
|
2725
|
-
strategy: "retire_prefix",
|
|
2726
|
-
reason: "runtime_pressure",
|
|
2727
|
-
stage: failure.stage,
|
|
2728
|
-
errorCode: boundedContextErrorCode(failure.code),
|
|
2729
|
-
error: `Automatic context retirement failed at ${failure.stage}.`,
|
|
2730
|
-
committed: failure.committed,
|
|
2731
|
-
qualificationId,
|
|
2732
|
-
},
|
|
2733
|
-
}).catch(() => undefined);
|
|
2734
|
-
if (failure.fatal) throw error;
|
|
2735
|
-
}
|
|
2736
|
-
} finally {
|
|
2737
|
-
if (this.state === "maintaining_context") {
|
|
2738
|
-
this.state = "executing";
|
|
2739
|
-
}
|
|
2740
|
-
}
|
|
2741
|
-
}
|
|
2742
|
-
|
|
2743
|
-
private async injectContextPressureNotice(input: {
|
|
2744
|
-
turn: TurnIdentity;
|
|
2745
|
-
ledger: AgentTurnLedger;
|
|
2746
|
-
usage: ContextUsageSnapshot;
|
|
2747
|
-
automaticSwapEnabled: boolean;
|
|
2748
|
-
}): Promise<void> {
|
|
2749
|
-
const userMessage: UserMessage = Object.freeze({
|
|
2750
|
-
role: "user",
|
|
2751
|
-
content: contextPressureNoticeText({
|
|
2752
|
-
usage: input.usage,
|
|
2753
|
-
toolPressure: toolContextPressure(input.usage.pressure) as "high" | "critical",
|
|
2754
|
-
automaticSwapEnabled: input.automaticSwapEnabled,
|
|
2755
|
-
}),
|
|
2756
|
-
});
|
|
2757
|
-
const records = input.ledger.appendSteeringUserMessages([userMessage]);
|
|
2758
|
-
const record = records[0];
|
|
2759
|
-
if (records.length !== 1 || record === undefined) {
|
|
2760
|
-
throw new Error("Pressure notice steering did not append exactly one message.");
|
|
2761
|
-
}
|
|
2762
|
-
await this.append({
|
|
2763
|
-
type: "context.pressure_notice.sent",
|
|
2764
|
-
...input.turn,
|
|
2765
|
-
data: {
|
|
2766
|
-
usedInputTokens: input.usage.usedInputTokens,
|
|
2767
|
-
inputBudgetTokens: input.usage.inputBudgetTokens,
|
|
2768
|
-
triggerTokens: input.usage.triggerTokens,
|
|
2769
|
-
pressure: input.usage.pressure === "blocked" ? "blocked" : "triggered",
|
|
2770
|
-
automaticSwapEnabled: input.automaticSwapEnabled,
|
|
2771
|
-
ordinal: record.ordinal,
|
|
2772
|
-
},
|
|
2773
|
-
});
|
|
2774
|
-
}
|
|
2775
|
-
|
|
2776
|
-
private async performModelDirectedCompaction(input: {
|
|
2777
|
-
turn: TurnIdentity;
|
|
2778
|
-
consumedThroughOrdinal: number;
|
|
2779
|
-
ledger: AgentTurnLedger;
|
|
2780
|
-
messageIds: readonly MessageId[];
|
|
2781
|
-
}): Promise<void> {
|
|
2782
|
-
await this.append({
|
|
2783
|
-
type: "context.revision.started",
|
|
2784
|
-
sessionId: this.sessionId,
|
|
2785
|
-
data: {
|
|
2786
|
-
strategy: "swap",
|
|
2787
|
-
reason: "model_directed",
|
|
2788
|
-
policyVersion: "swap-only-v1",
|
|
2789
|
-
rendererFormat: "swap-observation-v1",
|
|
2790
|
-
},
|
|
2791
|
-
});
|
|
2792
|
-
try {
|
|
2793
|
-
const result = await this.requireContextManager().compact(
|
|
2794
|
-
{
|
|
2795
|
-
kind: "model_directed",
|
|
2796
|
-
messageIds: input.messageIds,
|
|
2797
|
-
activeTurn: {
|
|
2798
|
-
turnId: input.turn.turnId,
|
|
2799
|
-
consumedThroughOrdinal: input.consumedThroughOrdinal,
|
|
2800
|
-
},
|
|
2801
|
-
},
|
|
2802
|
-
input.ledger,
|
|
2803
|
-
);
|
|
2804
|
-
await this.append({
|
|
2805
|
-
type: "context.revision.finished",
|
|
2806
|
-
sessionId: this.sessionId,
|
|
2807
|
-
data: contextRevisionFinishedData(result, "model_directed"),
|
|
2808
|
-
});
|
|
2809
|
-
} catch (error) {
|
|
2810
|
-
const failure = automaticContextFailure(error, "compaction");
|
|
2811
|
-
await this.append({
|
|
2812
|
-
type: "context.revision.failed",
|
|
2813
|
-
sessionId: this.sessionId,
|
|
2814
|
-
data: {
|
|
2815
|
-
strategy: "swap",
|
|
2816
|
-
reason: "model_directed",
|
|
2817
|
-
stage: failure.stage,
|
|
2818
|
-
errorCode: boundedContextErrorCode(failure.code),
|
|
2819
|
-
error: `Model-directed context compaction failed at ${failure.stage}.`,
|
|
2820
|
-
},
|
|
2821
|
-
}).catch(() => undefined);
|
|
2822
|
-
if (failure.fatal) throw error;
|
|
2823
|
-
}
|
|
2824
|
-
}
|
|
2825
|
-
|
|
2826
1244
|
private notifyCompletedTurn(turn: TurnIdentity): void {
|
|
2827
1245
|
const hook = this.input.completedTurnHook;
|
|
2828
1246
|
if (hook === undefined) {
|
|
@@ -2872,133 +1290,6 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
2872
1290
|
}
|
|
2873
1291
|
}
|
|
2874
1292
|
|
|
2875
|
-
private async performAutomaticCompaction(
|
|
2876
|
-
qualificationId: string,
|
|
2877
|
-
): Promise<ContextCompactionResult | undefined> {
|
|
2878
|
-
let started = false;
|
|
2879
|
-
try {
|
|
2880
|
-
await this.append({
|
|
2881
|
-
type: "context.revision.started",
|
|
2882
|
-
sessionId: this.sessionId,
|
|
2883
|
-
data: {
|
|
2884
|
-
strategy: "swap",
|
|
2885
|
-
reason: "runtime_pressure",
|
|
2886
|
-
policyVersion: "swap-only-v1",
|
|
2887
|
-
rendererFormat: "swap-observation-v1",
|
|
2888
|
-
qualificationId,
|
|
2889
|
-
},
|
|
2890
|
-
});
|
|
2891
|
-
started = true;
|
|
2892
|
-
const result = await this.requireContextManager().compact(
|
|
2893
|
-
this.dependencies.automaticCompactionTrigger(),
|
|
2894
|
-
);
|
|
2895
|
-
await this.append({
|
|
2896
|
-
type: "context.revision.finished",
|
|
2897
|
-
sessionId: this.sessionId,
|
|
2898
|
-
data: contextRevisionFinishedData(result, "runtime_pressure", qualificationId),
|
|
2899
|
-
});
|
|
2900
|
-
return result;
|
|
2901
|
-
} catch (error) {
|
|
2902
|
-
if (started && !(error instanceof RuntimeEventAppendError)) {
|
|
2903
|
-
const failure = automaticContextFailure(error, "compaction");
|
|
2904
|
-
await this.append({
|
|
2905
|
-
type: "context.revision.failed",
|
|
2906
|
-
sessionId: this.sessionId,
|
|
2907
|
-
data: {
|
|
2908
|
-
strategy: "swap",
|
|
2909
|
-
reason: "runtime_pressure",
|
|
2910
|
-
stage: failure.stage,
|
|
2911
|
-
errorCode: boundedContextErrorCode(failure.code),
|
|
2912
|
-
error: `Automatic context compaction failed at ${failure.stage}.`,
|
|
2913
|
-
qualificationId,
|
|
2914
|
-
},
|
|
2915
|
-
}).catch(() => undefined);
|
|
2916
|
-
}
|
|
2917
|
-
if (error instanceof ContextManagerError && !error.fatal) {
|
|
2918
|
-
return undefined;
|
|
2919
|
-
}
|
|
2920
|
-
throw error;
|
|
2921
|
-
}
|
|
2922
|
-
}
|
|
2923
|
-
|
|
2924
|
-
private async performAutomaticRetirement(
|
|
2925
|
-
qualificationId: string,
|
|
2926
|
-
): Promise<ContextRetirementResult | undefined> {
|
|
2927
|
-
const baseRevisionNumber = this.store.loadContextSnapshot().revision.revisionNumber;
|
|
2928
|
-
let started = false;
|
|
2929
|
-
try {
|
|
2930
|
-
await this.append({
|
|
2931
|
-
type: "context.revision.started",
|
|
2932
|
-
sessionId: this.sessionId,
|
|
2933
|
-
data: {
|
|
2934
|
-
strategy: "retire_prefix",
|
|
2935
|
-
reason: "runtime_pressure",
|
|
2936
|
-
policyVersion: "recall-first-retirement-v1",
|
|
2937
|
-
baseRevisionNumber,
|
|
2938
|
-
qualificationId,
|
|
2939
|
-
},
|
|
2940
|
-
});
|
|
2941
|
-
started = true;
|
|
2942
|
-
const result = await this.requireContextManager().retirePrefix(
|
|
2943
|
-
this.dependencies.automaticRetirementTrigger(),
|
|
2944
|
-
);
|
|
2945
|
-
await this.append({
|
|
2946
|
-
type: "context.revision.finished",
|
|
2947
|
-
sessionId: this.sessionId,
|
|
2948
|
-
data: contextRetirementFinishedData(
|
|
2949
|
-
result,
|
|
2950
|
-
"runtime_pressure",
|
|
2951
|
-
qualificationId,
|
|
2952
|
-
),
|
|
2953
|
-
});
|
|
2954
|
-
return result;
|
|
2955
|
-
} catch (error) {
|
|
2956
|
-
if (started && !(error instanceof RuntimeEventAppendError)) {
|
|
2957
|
-
const failure = automaticContextFailure(error, "retirement");
|
|
2958
|
-
await this.append({
|
|
2959
|
-
type: "context.revision.failed",
|
|
2960
|
-
sessionId: this.sessionId,
|
|
2961
|
-
data: {
|
|
2962
|
-
strategy: "retire_prefix",
|
|
2963
|
-
reason: "runtime_pressure",
|
|
2964
|
-
stage: failure.stage,
|
|
2965
|
-
errorCode: boundedContextErrorCode(failure.code),
|
|
2966
|
-
error: `Automatic context retirement failed at ${failure.stage}.`,
|
|
2967
|
-
committed: failure.committed,
|
|
2968
|
-
qualificationId,
|
|
2969
|
-
},
|
|
2970
|
-
}).catch(() => undefined);
|
|
2971
|
-
}
|
|
2972
|
-
if (error instanceof ContextManagerError && !error.fatal) {
|
|
2973
|
-
return undefined;
|
|
2974
|
-
}
|
|
2975
|
-
throw error;
|
|
2976
|
-
}
|
|
2977
|
-
}
|
|
2978
|
-
|
|
2979
|
-
private async settleClosedTurnSkills(): Promise<void> {
|
|
2980
|
-
const unresolved = this.store.loadSkillActivations(["pending", "dispatched"]);
|
|
2981
|
-
if (unresolved.length === 0) {
|
|
2982
|
-
return;
|
|
2983
|
-
}
|
|
2984
|
-
const summary = await this.commitSkillSettlements({
|
|
2985
|
-
reason: "activation",
|
|
2986
|
-
unresolved,
|
|
2987
|
-
});
|
|
2988
|
-
await this.append({
|
|
2989
|
-
type: "skills.updated",
|
|
2990
|
-
sessionId: this.sessionId,
|
|
2991
|
-
data: {
|
|
2992
|
-
reason: "activation",
|
|
2993
|
-
activated: summary.activated,
|
|
2994
|
-
refreshed: summary.refreshed,
|
|
2995
|
-
deactivated: summary.deactivated,
|
|
2996
|
-
unavailable: summary.unavailable,
|
|
2997
|
-
revisionNumber: summary.revisionNumber,
|
|
2998
|
-
},
|
|
2999
|
-
});
|
|
3000
|
-
}
|
|
3001
|
-
|
|
3002
1293
|
private async appendTerminalEvent(
|
|
3003
1294
|
turn: TurnIdentity,
|
|
3004
1295
|
result: RunAgentResult,
|
|
@@ -3040,9 +1331,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
3040
1331
|
}
|
|
3041
1332
|
|
|
3042
1333
|
this.state = "disposing";
|
|
3043
|
-
this.
|
|
3044
|
-
this.executionChainRunning = false;
|
|
3045
|
-
this.notifyPromptScheduler();
|
|
1334
|
+
this.scheduler.clear();
|
|
3046
1335
|
const errors: unknown[] = this.faultCause === undefined ? [] : [this.faultCause];
|
|
3047
1336
|
const activeAdmission = this.activeAdmission;
|
|
3048
1337
|
if (activeAdmission !== undefined) {
|
|
@@ -3452,6 +1741,7 @@ function validateCreateInput(input: CreateRuntimeSessionInput): void {
|
|
|
3452
1741
|
if (input.modelName.trim() === "") {
|
|
3453
1742
|
throw new Error("RuntimeSession modelName must not be empty.");
|
|
3454
1743
|
}
|
|
1744
|
+
|
|
3455
1745
|
requirePositiveNumber(input.maxIterations, "maxIterations");
|
|
3456
1746
|
if (input.systemPrompt.trim() === "") {
|
|
3457
1747
|
throw new Error("RuntimeSession systemPrompt must not be empty.");
|
|
@@ -3468,24 +1758,8 @@ function validateCreateInput(input: CreateRuntimeSessionInput): void {
|
|
|
3468
1758
|
"RuntimeSession modelClient must implement prepare() and request().",
|
|
3469
1759
|
);
|
|
3470
1760
|
}
|
|
3471
|
-
assertMatchingContextBudget(input.contextProfile, input.contextBudget);
|
|
3472
|
-
}
|
|
3473
|
-
|
|
3474
|
-
function assertPreparedMatchesSurface(
|
|
3475
|
-
prepared: ReturnType<ModelClient["prepare"]>,
|
|
3476
|
-
surface: StoredContextSurfaceV8,
|
|
3477
|
-
): void {
|
|
3478
|
-
if (
|
|
3479
|
-
prepared.requestConfigHash !== surface.requestConfigSha256 ||
|
|
3480
|
-
prepared.toolSchemaHash !== surface.toolSchemaSha256 ||
|
|
3481
|
-
prepared.requestMaxOutputTokens !== surface.requestMaxOutputTokens
|
|
3482
|
-
) {
|
|
3483
|
-
throw new Error("Prepared model request does not match its context surface.");
|
|
3484
|
-
}
|
|
3485
|
-
}
|
|
3486
1761
|
|
|
3487
|
-
|
|
3488
|
-
return Math.round((performance.now() - startedAt) * 100) / 100;
|
|
1762
|
+
assertMatchingContextBudget(input.contextProfile, input.contextBudget);
|
|
3489
1763
|
}
|
|
3490
1764
|
|
|
3491
1765
|
function isNewSessionInput(
|
|
@@ -3560,166 +1834,12 @@ function errorMessage(error: unknown): string {
|
|
|
3560
1834
|
return error instanceof Error ? error.message : String(error);
|
|
3561
1835
|
}
|
|
3562
1836
|
|
|
3563
|
-
function contextRevisionFinishedData(
|
|
3564
|
-
result: ContextCompactionResult,
|
|
3565
|
-
reason: "manual" | "runtime_pressure" | "model_directed" = "manual",
|
|
3566
|
-
qualificationId?: string,
|
|
3567
|
-
): ContextRevisionFinishedData {
|
|
3568
|
-
if (result.status === "unchanged") {
|
|
3569
|
-
return {
|
|
3570
|
-
strategy: "swap",
|
|
3571
|
-
reason,
|
|
3572
|
-
policyVersion: "swap-only-v1",
|
|
3573
|
-
outcome: result.outcome,
|
|
3574
|
-
baseRevisionNumber: result.revisionNumber,
|
|
3575
|
-
addedOverrideCount: 0,
|
|
3576
|
-
activeOverrideCount: result.activeOverrideCount,
|
|
3577
|
-
originalObservationBytes: 0,
|
|
3578
|
-
projectedObservationBytes: 0,
|
|
3579
|
-
rawTokensBefore: result.rawTokensBefore,
|
|
3580
|
-
guardedTokensBefore: result.guardedTokensBefore,
|
|
3581
|
-
targetTokens: result.targetTokens,
|
|
3582
|
-
durationMs: result.durationMs,
|
|
3583
|
-
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
3584
|
-
};
|
|
3585
|
-
}
|
|
3586
|
-
return {
|
|
3587
|
-
strategy: "swap",
|
|
3588
|
-
reason,
|
|
3589
|
-
policyVersion: "swap-only-v1",
|
|
3590
|
-
outcome: result.outcome,
|
|
3591
|
-
baseRevisionNumber: result.previousRevisionNumber,
|
|
3592
|
-
revisionNumber: result.revisionNumber,
|
|
3593
|
-
addedOverrideCount: result.addedOverrideCount,
|
|
3594
|
-
activeOverrideCount: result.activeOverrideCount,
|
|
3595
|
-
originalObservationBytes: result.originalObservationBytes,
|
|
3596
|
-
projectedObservationBytes: result.projectedObservationBytes,
|
|
3597
|
-
rawTokensBefore: result.rawTokensBefore,
|
|
3598
|
-
rawTokensAfter: result.rawTokensAfter,
|
|
3599
|
-
guardedTokensBefore: result.guardedTokensBefore,
|
|
3600
|
-
guardedTokensAfter: result.guardedTokensAfter,
|
|
3601
|
-
targetTokens: result.targetTokens,
|
|
3602
|
-
planHash: result.planHash,
|
|
3603
|
-
durationMs: result.durationMs,
|
|
3604
|
-
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
3605
|
-
};
|
|
3606
|
-
}
|
|
3607
|
-
|
|
3608
|
-
function contextRetirementFinishedData(
|
|
3609
|
-
result: ContextRetirementResult,
|
|
3610
|
-
reason: "manual" | "runtime_pressure" = "manual",
|
|
3611
|
-
qualificationId?: string,
|
|
3612
|
-
): ContextRevisionFinishedData {
|
|
3613
|
-
if (result.status === "unchanged") {
|
|
3614
|
-
return {
|
|
3615
|
-
strategy: "retire_prefix",
|
|
3616
|
-
reason,
|
|
3617
|
-
policyVersion: "recall-first-retirement-v1",
|
|
3618
|
-
outcome: result.outcome,
|
|
3619
|
-
baseRevisionNumber: result.revisionNumber,
|
|
3620
|
-
previousKeepFromOrdinal: result.keepFromOrdinal,
|
|
3621
|
-
keepFromOrdinal: result.keepFromOrdinal,
|
|
3622
|
-
retiredTurnCount: 0,
|
|
3623
|
-
retiredFrameCount: 0,
|
|
3624
|
-
retiredMessageCount: 0,
|
|
3625
|
-
activeOverrideCount: result.activeOverrideCount,
|
|
3626
|
-
guardedTokensBefore: result.guardedTokensBefore,
|
|
3627
|
-
targetTokens: result.targetTokens,
|
|
3628
|
-
planningDurationMs: result.planningDurationMs,
|
|
3629
|
-
durationMs: result.durationMs,
|
|
3630
|
-
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
3631
|
-
};
|
|
3632
|
-
}
|
|
3633
|
-
return {
|
|
3634
|
-
strategy: "retire_prefix",
|
|
3635
|
-
reason,
|
|
3636
|
-
policyVersion: "recall-first-retirement-v1",
|
|
3637
|
-
outcome: result.outcome,
|
|
3638
|
-
baseRevisionNumber: result.previousRevisionNumber,
|
|
3639
|
-
revisionNumber: result.revisionNumber,
|
|
3640
|
-
previousKeepFromOrdinal: result.previousKeepFromOrdinal,
|
|
3641
|
-
keepFromOrdinal: result.keepFromOrdinal,
|
|
3642
|
-
retiredTurnCount: result.retiredTurnCount,
|
|
3643
|
-
retiredFrameCount: result.retiredFrameCount,
|
|
3644
|
-
retiredMessageCount: result.retiredMessageCount,
|
|
3645
|
-
activeOverrideCount: result.activeOverrideCount,
|
|
3646
|
-
rawTokensBefore: result.rawTokensBefore,
|
|
3647
|
-
rawTokensAfter: result.rawTokensAfter,
|
|
3648
|
-
guardedTokensBefore: result.guardedTokensBefore,
|
|
3649
|
-
guardedTokensAfter: result.guardedTokensAfter,
|
|
3650
|
-
targetTokens: result.targetTokens,
|
|
3651
|
-
planHash: result.planHash,
|
|
3652
|
-
planningDurationMs: result.planningDurationMs,
|
|
3653
|
-
validationDurationMs: result.validationDurationMs,
|
|
3654
|
-
transactionDurationMs: result.transactionDurationMs,
|
|
3655
|
-
activationDurationMs: result.activationDurationMs,
|
|
3656
|
-
durationMs: result.durationMs,
|
|
3657
|
-
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
3658
|
-
};
|
|
3659
|
-
}
|
|
3660
|
-
|
|
3661
|
-
function requireAutomationQualificationId(decision: ContextAutomationDecision): string {
|
|
3662
|
-
if (
|
|
3663
|
-
!decision.automaticSwapOnly ||
|
|
3664
|
-
(decision.reason !== "qualified" && decision.reason !== "swap_only_qualified") ||
|
|
3665
|
-
decision.qualificationId === undefined
|
|
3666
|
-
) {
|
|
3667
|
-
throw new Error("Automatic context maintenance has no qualification identity.");
|
|
3668
|
-
}
|
|
3669
|
-
return decision.qualificationId;
|
|
3670
|
-
}
|
|
3671
|
-
|
|
3672
|
-
function automaticSwapNeedsRetirement(result: ContextCompactionResult): boolean {
|
|
3673
|
-
return (
|
|
3674
|
-
result.outcome === "no_eligible_candidates" ||
|
|
3675
|
-
result.outcome === "insufficient_candidates"
|
|
3676
|
-
);
|
|
3677
|
-
}
|
|
3678
|
-
|
|
3679
|
-
function automaticContextFailure(
|
|
3680
|
-
error: unknown,
|
|
3681
|
-
strategy: "compaction" | "retirement",
|
|
3682
|
-
): ContextManagerError {
|
|
3683
|
-
return error instanceof ContextManagerError
|
|
3684
|
-
? error
|
|
3685
|
-
: new ContextManagerError(
|
|
3686
|
-
"activate",
|
|
3687
|
-
error instanceof Error
|
|
3688
|
-
? error.name
|
|
3689
|
-
: `AUTOMATIC_CONTEXT_${strategy.toUpperCase()}_FAILED`,
|
|
3690
|
-
true,
|
|
3691
|
-
false,
|
|
3692
|
-
`Automatic context ${strategy} failed.`,
|
|
3693
|
-
{ cause: error },
|
|
3694
|
-
);
|
|
3695
|
-
}
|
|
3696
|
-
|
|
3697
|
-
function boundedContextErrorCode(code: string): string {
|
|
3698
|
-
return /^[A-Za-z0-9_]+$/.test(code) && code.length <= 80
|
|
3699
|
-
? code
|
|
3700
|
-
: "CONTEXT_COMPACTION_FAILED";
|
|
3701
|
-
}
|
|
3702
|
-
|
|
3703
|
-
function toolContextPressure(
|
|
3704
|
-
pressure: ContextPressure,
|
|
3705
|
-
): "normal" | "high" | "critical" {
|
|
3706
|
-
return pressure === "triggered"
|
|
3707
|
-
? "high"
|
|
3708
|
-
: pressure === "blocked"
|
|
3709
|
-
? "critical"
|
|
3710
|
-
: "normal";
|
|
3711
|
-
}
|
|
3712
|
-
|
|
3713
1837
|
function requirePositiveNumber(value: number, name: string): void {
|
|
3714
1838
|
if (!Number.isInteger(value) || value < 1) {
|
|
3715
1839
|
throw new Error(`${name} must be a positive integer; received ${value}.`);
|
|
3716
1840
|
}
|
|
3717
1841
|
}
|
|
3718
1842
|
|
|
3719
|
-
function compareText(left: string, right: string): number {
|
|
3720
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
3721
|
-
}
|
|
3722
|
-
|
|
3723
1843
|
function isCanonicalRuntimeFault(error: unknown): boolean {
|
|
3724
1844
|
return (
|
|
3725
1845
|
error instanceof ContextProtocolError ||
|