tinker-agent 1.0.65 → 1.2.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 +36 -0
- package/LICENSE +201 -0
- package/NOTICE +6 -0
- package/README.md +90 -9
- package/bin/tinker.js +39 -0
- package/node_modules/ansi-regex/index.d.ts +33 -0
- package/node_modules/ansi-regex/index.js +14 -0
- package/node_modules/ansi-regex/license +9 -0
- package/node_modules/ansi-regex/package.json +61 -0
- package/node_modules/ansi-regex/readme.md +66 -0
- package/node_modules/ansi-styles/index.d.ts +236 -0
- package/node_modules/ansi-styles/index.js +223 -0
- package/node_modules/ansi-styles/license +9 -0
- package/node_modules/ansi-styles/package.json +54 -0
- package/node_modules/ansi-styles/readme.md +173 -0
- package/node_modules/chalk/license +9 -0
- package/node_modules/chalk/package.json +83 -0
- package/node_modules/chalk/readme.md +297 -0
- package/node_modules/chalk/source/index.d.ts +325 -0
- package/node_modules/chalk/source/index.js +225 -0
- package/node_modules/chalk/source/utilities.js +33 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
- package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
- package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
- package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
- package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
- package/node_modules/character-entities/index.d.ts +6 -0
- package/node_modules/character-entities/index.js +2132 -0
- package/node_modules/character-entities/license +22 -0
- package/node_modules/character-entities/package.json +78 -0
- package/node_modules/character-entities/readme.md +152 -0
- package/node_modules/decode-named-character-reference/index.d.ts +13 -0
- package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
- package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
- package/node_modules/decode-named-character-reference/index.dom.js +32 -0
- package/node_modules/decode-named-character-reference/index.js +19 -0
- package/node_modules/decode-named-character-reference/license +22 -0
- package/node_modules/decode-named-character-reference/package.json +90 -0
- package/node_modules/decode-named-character-reference/readme.md +136 -0
- package/node_modules/get-east-asian-width/index.d.ts +60 -0
- package/node_modules/get-east-asian-width/index.js +30 -0
- package/node_modules/get-east-asian-width/license +9 -0
- package/node_modules/get-east-asian-width/lookup-data.js +21 -0
- package/node_modules/get-east-asian-width/lookup.js +138 -0
- package/node_modules/get-east-asian-width/package.json +71 -0
- package/node_modules/get-east-asian-width/readme.md +65 -0
- package/node_modules/get-east-asian-width/utilities.js +24 -0
- package/node_modules/has-flag/index.d.ts +39 -0
- package/node_modules/has-flag/index.js +8 -0
- package/node_modules/has-flag/license +9 -0
- package/node_modules/has-flag/package.json +49 -0
- package/node_modules/has-flag/readme.md +74 -0
- package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
- package/node_modules/is-fullwidth-code-point/index.js +12 -0
- package/node_modules/is-fullwidth-code-point/license +9 -0
- package/node_modules/is-fullwidth-code-point/package.json +53 -0
- package/node_modules/is-fullwidth-code-point/readme.md +31 -0
- package/node_modules/markdansi/LICENSE +21 -0
- package/node_modules/markdansi/README.md +167 -0
- package/node_modules/markdansi/dist/ast.d.ts +99 -0
- package/node_modules/markdansi/dist/ast.js +1 -0
- package/node_modules/markdansi/dist/cli.d.ts +17 -0
- package/node_modules/markdansi/dist/cli.js +208 -0
- package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
- package/node_modules/markdansi/dist/hyperlink.js +26 -0
- package/node_modules/markdansi/dist/index.d.ts +10 -0
- package/node_modules/markdansi/dist/index.js +16 -0
- package/node_modules/markdansi/dist/parser.d.ts +2 -0
- package/node_modules/markdansi/dist/parser.js +205 -0
- package/node_modules/markdansi/dist/render.d.ts +9 -0
- package/node_modules/markdansi/dist/render.js +680 -0
- package/node_modules/markdansi/dist/stream.d.ts +39 -0
- package/node_modules/markdansi/dist/stream.js +192 -0
- package/node_modules/markdansi/dist/theme.d.ts +18 -0
- package/node_modules/markdansi/dist/theme.js +105 -0
- package/node_modules/markdansi/dist/types.d.ts +58 -0
- package/node_modules/markdansi/dist/types.js +1 -0
- package/node_modules/markdansi/dist/wrap.d.ts +10 -0
- package/node_modules/markdansi/dist/wrap.js +73 -0
- package/node_modules/markdansi/docs/spec.md +110 -0
- package/node_modules/markdansi/package.json +78 -0
- package/node_modules/markdansi/tsconfig.json +18 -0
- package/node_modules/marked/LICENSE +44 -0
- package/node_modules/marked/README.md +115 -0
- package/node_modules/marked/bin/main.js +284 -0
- package/node_modules/marked/bin/marked.js +16 -0
- package/node_modules/marked/lib/marked.d.ts +760 -0
- package/node_modules/marked/lib/marked.esm.js +77 -0
- package/node_modules/marked/lib/marked.esm.js.map +7 -0
- package/node_modules/marked/lib/marked.umd.js +79 -0
- package/node_modules/marked/lib/marked.umd.js.map +7 -0
- package/node_modules/marked/man/marked.1 +113 -0
- package/node_modules/marked/man/marked.1.md +93 -0
- package/node_modules/marked/package.json +103 -0
- package/node_modules/slice-ansi/index.d.ts +19 -0
- package/node_modules/slice-ansi/index.js +317 -0
- package/node_modules/slice-ansi/license +10 -0
- package/node_modules/slice-ansi/package.json +59 -0
- package/node_modules/slice-ansi/readme.md +55 -0
- package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
- package/node_modules/string-width/index.d.ts +39 -0
- package/node_modules/string-width/index.js +203 -0
- package/node_modules/string-width/license +9 -0
- package/node_modules/string-width/package.json +65 -0
- package/node_modules/string-width/readme.md +66 -0
- package/node_modules/strip-ansi/index.d.ts +15 -0
- package/node_modules/strip-ansi/index.js +19 -0
- package/node_modules/strip-ansi/license +9 -0
- package/node_modules/strip-ansi/package.json +59 -0
- package/node_modules/strip-ansi/readme.md +37 -0
- package/node_modules/supports-color/browser.d.ts +1 -0
- package/node_modules/supports-color/browser.js +35 -0
- package/node_modules/supports-color/index.d.ts +55 -0
- package/node_modules/supports-color/index.js +202 -0
- package/node_modules/supports-color/license +9 -0
- package/node_modules/supports-color/package.json +64 -0
- package/node_modules/supports-color/readme.md +75 -0
- package/node_modules/supports-hyperlinks/browser.js +10 -0
- package/node_modules/supports-hyperlinks/index.d.ts +25 -0
- package/node_modules/supports-hyperlinks/index.js +163 -0
- package/node_modules/supports-hyperlinks/license +10 -0
- package/node_modules/supports-hyperlinks/package.json +47 -0
- package/node_modules/supports-hyperlinks/readme.md +45 -0
- package/package.json +41 -10
- package/patches/markdansi@0.3.2.patch +22 -2
- package/src/agent/context-builder.ts +24 -10
- package/src/agent/context-meter.ts +89 -5
- package/src/agent/loop.ts +107 -10
- package/src/agent/runtime-session.ts +1437 -82
- package/src/agent/session-ledger.ts +169 -29
- package/src/agent/types.ts +12 -1
- package/src/agent/user-prompt-projection.ts +62 -0
- package/src/cli/config.ts +57 -14
- package/src/cli/model-profiles.ts +124 -0
- package/src/cli/run-runner.ts +4 -1
- package/src/cli/tui-runner.tsx +68 -30
- package/src/context/compiled-context-hash.ts +1 -1
- package/src/context/compiled-context-validator.ts +92 -15
- package/src/context/context-automation-policy.ts +121 -0
- package/src/context/context-manager.ts +390 -11
- package/src/context/context-policy.ts +8 -0
- package/src/context/context-protocol-validator.ts +11 -1
- package/src/context/context-revision-compiler.ts +190 -27
- package/src/context/context-revision.ts +105 -19
- package/src/context/context-surface.ts +329 -0
- package/src/context/prefix-retirement-planner.ts +570 -0
- package/src/context/protocol-frame.ts +30 -3
- package/src/context/recall-retirement-contract.ts +27 -0
- package/src/context/swap-planner.ts +34 -10
- package/src/events/observation-text-log.ts +3 -3
- package/src/events/stdout-event-printer.ts +39 -3
- package/src/events/types.ts +172 -35
- package/src/ids/runtime-id.ts +6 -0
- package/src/ids/uuid-v7.ts +7 -0
- package/src/image/image-asset-store.ts +319 -0
- package/src/image/image-input-policy.ts +14 -0
- package/src/image/image-probe.ts +233 -0
- package/src/image/image-types.ts +262 -0
- package/src/instructions/project-instructions.ts +4 -1
- package/src/mcp/mcp-manager.ts +83 -19
- package/src/model/fake-model-client.ts +9 -1
- package/src/model/input-token-estimator.ts +25 -0
- package/src/model/model-client.ts +60 -0
- package/src/model/model-request-preflight.ts +1 -0
- package/src/model/moonshot-input-token-estimator.ts +107 -0
- package/src/model/openai-chat-mapping.ts +77 -1
- package/src/model/openai-chat-model-client.ts +389 -59
- package/src/model/openai-chat-stream.ts +272 -0
- package/src/model/prompt-prefix-hash.ts +2 -4
- package/src/model/token-estimator.ts +4 -0
- package/src/observation/observation-builder.ts +29 -0
- package/src/session/resume-projection.ts +94 -34
- package/src/session/session-catalog.ts +4 -4
- package/src/session/session-errors.ts +1 -1
- package/src/session/session-history-reader.ts +151 -4
- package/src/session/session-last-response-reader.ts +189 -0
- package/src/session/session-schema.ts +349 -35
- package/src/session/session-store.ts +3266 -255
- package/src/session/sqlite-session-ledger.ts +9 -4
- package/src/skills/skill-catalog.ts +182 -0
- package/src/skills/skill-context.ts +364 -0
- package/src/skills/skill-loader.ts +779 -0
- package/src/skills/skill-tool.ts +297 -0
- package/src/tools/edit.ts +51 -64
- package/src/tools/ensure-parent-directory.ts +6 -0
- package/src/tools/read.ts +2 -2
- package/src/tools/recall.ts +43 -40
- package/src/tools/registry.ts +24 -3
- package/src/tools/ripgrep.ts +3 -2
- package/src/tools/types.ts +43 -4
- package/src/tools/write.ts +20 -31
- package/src/tui/app.tsx +333 -96
- package/src/tui/clipboard.ts +5 -0
- package/src/tui/components/mcp-panel.tsx +28 -0
- package/src/tui/components/prompt-input.tsx +790 -257
- package/src/tui/components/skills-panel.tsx +31 -0
- package/src/tui/components/timeline.tsx +30 -1
- package/src/tui/event-store.ts +85 -2
- package/src/tui/file-mention.ts +234 -0
- package/src/tui/line-editor.ts +1 -0
- package/src/tui/project-slash-commands.ts +229 -0
- package/src/tui/prompt-draft.ts +510 -0
- package/src/tui/prompt-history.ts +294 -22
- package/src/tui/slash-commands.ts +46 -2
- package/src/tui/tui-session-controller.ts +110 -48
- package/src/tui/workspace-file-search.ts +94 -0
|
@@ -15,15 +15,35 @@ import {
|
|
|
15
15
|
type SessionId,
|
|
16
16
|
} from "../ids/runtime-id";
|
|
17
17
|
import { loadMcpConfig } from "../mcp/mcp-config";
|
|
18
|
-
import {
|
|
19
|
-
|
|
18
|
+
import {
|
|
19
|
+
createMcpManager,
|
|
20
|
+
type McpInventorySnapshot,
|
|
21
|
+
type McpManager,
|
|
22
|
+
} from "../mcp/mcp-manager";
|
|
23
|
+
import {
|
|
24
|
+
materializeModelRequest,
|
|
25
|
+
ModelRequestMediaAggregateError,
|
|
26
|
+
type MaterializedModelRequest,
|
|
27
|
+
type ModelClient,
|
|
28
|
+
} from "../model/model-client";
|
|
29
|
+
import { ImageAssetStore, type ImportedImageAsset } from "../image/image-asset-store";
|
|
30
|
+
import { IMAGE_INPUT_POLICY } from "../image/image-input-policy";
|
|
31
|
+
import {
|
|
32
|
+
validateUserMessage,
|
|
33
|
+
type ImageAssetRef,
|
|
34
|
+
type UserMessage,
|
|
35
|
+
} from "../image/image-types";
|
|
36
|
+
import { projectUserMessage } from "./user-prompt-projection";
|
|
20
37
|
import { CommittedPrefixAuditor } from "../model/committed-prefix-auditor";
|
|
21
38
|
import { SwapPlanner } from "../context/swap-planner";
|
|
22
39
|
import {
|
|
40
|
+
commitAgentSkillsContextUpdate,
|
|
23
41
|
ContextManager,
|
|
24
42
|
ContextManagerError,
|
|
25
43
|
type ContextCompactionResult,
|
|
26
44
|
type ContextCompactionTrigger,
|
|
45
|
+
type ContextRetirementResult,
|
|
46
|
+
type ContextRetirementTrigger,
|
|
27
47
|
} from "../context/context-manager";
|
|
28
48
|
import {
|
|
29
49
|
assertMatchingContextBudget,
|
|
@@ -33,20 +53,45 @@ import {
|
|
|
33
53
|
import { ObservationBuilder } from "../observation/observation-builder";
|
|
34
54
|
import { ContextProtocolError } from "../context/context-protocol-validator";
|
|
35
55
|
import { CompiledContextError } from "../context/compiled-context-validator";
|
|
36
|
-
import {
|
|
56
|
+
import {
|
|
57
|
+
ContextRevisionCompiler,
|
|
58
|
+
ContextRevisionError,
|
|
59
|
+
} from "../context/context-revision-compiler";
|
|
60
|
+
import {
|
|
61
|
+
changedContextSurfaceComponents,
|
|
62
|
+
contextSurfaceChangeManifestHash,
|
|
63
|
+
contextSurfaceChanges,
|
|
64
|
+
createContextSurface,
|
|
65
|
+
sameContextSurface,
|
|
66
|
+
type ContextSurfaceComponent,
|
|
67
|
+
type StoredContextSurfaceV8,
|
|
68
|
+
} from "../context/context-surface";
|
|
69
|
+
import {
|
|
70
|
+
canonicalSequenceHash,
|
|
71
|
+
renderedMessageHash,
|
|
72
|
+
} from "../context/compiled-context-hash";
|
|
37
73
|
import { createDefaultTooling, type DefaultTooling } from "../tools/registry";
|
|
38
74
|
import type { Refiner } from "../tools/web-fetch/refiner";
|
|
39
75
|
import type { ProjectInstructionManifest } from "../instructions/project-instructions";
|
|
40
76
|
import {
|
|
41
77
|
SessionStore,
|
|
42
|
-
|
|
78
|
+
createSessionCompatibilityContract,
|
|
43
79
|
type SessionRecoveryResult,
|
|
80
|
+
type StoredSkillActivation,
|
|
44
81
|
} from "../session/session-store";
|
|
45
82
|
import { SqliteSessionLedger } from "../session/sqlite-session-ledger";
|
|
46
83
|
import { SessionError } from "../session/session-errors";
|
|
47
84
|
import { FatalAgentTurnError, runAgent, type RunAgentInput } from "./loop";
|
|
48
|
-
import {
|
|
85
|
+
import {
|
|
86
|
+
AdmissionStaleError,
|
|
87
|
+
SessionLedgerWriteError,
|
|
88
|
+
type AdmissionBaseToken,
|
|
89
|
+
type SessionLedger,
|
|
90
|
+
} from "./session-ledger";
|
|
49
91
|
import { TurnCancelledError } from "./turn-cancellation";
|
|
92
|
+
import type { ToolCompletionInput } from "../context/protocol-frame";
|
|
93
|
+
import type { BuiltContextRequest } from "../context/context-revision";
|
|
94
|
+
import type { CommittedToolCompletion } from "./session-ledger";
|
|
50
95
|
import type {
|
|
51
96
|
IterationIdentity,
|
|
52
97
|
RunAgentResult,
|
|
@@ -54,12 +99,35 @@ import type {
|
|
|
54
99
|
TurnIdentity,
|
|
55
100
|
} from "./types";
|
|
56
101
|
import { ContextMeter } from "./context-meter";
|
|
102
|
+
import { CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION } from "../context/recall-retirement-contract";
|
|
103
|
+
import {
|
|
104
|
+
selectContextAutomation,
|
|
105
|
+
type ContextAutomationDecision,
|
|
106
|
+
} from "../context/context-automation-policy";
|
|
107
|
+
import type { SkillCatalogSnapshot } from "../skills/skill-loader";
|
|
108
|
+
import {
|
|
109
|
+
activeSkillManifestEntry,
|
|
110
|
+
createSkillCatalogSnapshot,
|
|
111
|
+
skillCatalogManifest,
|
|
112
|
+
} from "../skills/skill-catalog";
|
|
113
|
+
import {
|
|
114
|
+
buildActiveSystemPrompt,
|
|
115
|
+
rebindActiveSkills,
|
|
116
|
+
renderSkillActivationReceipt,
|
|
117
|
+
SkillActivationCoordinator,
|
|
118
|
+
} from "../skills/skill-context";
|
|
57
119
|
|
|
58
120
|
export type ExecuteTurnInput = {
|
|
59
|
-
|
|
121
|
+
userMessage: UserMessage;
|
|
60
122
|
signal: AbortSignal;
|
|
61
123
|
};
|
|
62
124
|
|
|
125
|
+
export type AcceptedTurn = {
|
|
126
|
+
readonly turnId: TurnIdentity["turnId"];
|
|
127
|
+
readonly userMessage: UserMessage;
|
|
128
|
+
readonly completion: Promise<RunAgentResult>;
|
|
129
|
+
};
|
|
130
|
+
|
|
63
131
|
export type SessionDisposeReason =
|
|
64
132
|
| { type: "oneshot_complete" }
|
|
65
133
|
| { type: "tui_exit" }
|
|
@@ -71,12 +139,41 @@ export type RuntimeSession = {
|
|
|
71
139
|
readonly sessionId: SessionId;
|
|
72
140
|
readonly resumed: boolean;
|
|
73
141
|
readonly recovery: SessionRecoveryResult;
|
|
142
|
+
skills(): RuntimeSkillsSnapshot;
|
|
143
|
+
mcp(): McpInventorySnapshot;
|
|
144
|
+
supportsImageInput(): boolean;
|
|
145
|
+
importImage(
|
|
146
|
+
sourcePath: string,
|
|
147
|
+
signal: AbortSignal,
|
|
148
|
+
prospectiveMessageImageCount: number,
|
|
149
|
+
): Promise<ImportedImageAsset>;
|
|
150
|
+
verifyImageAssets(
|
|
151
|
+
assets: readonly ImageAssetRef[],
|
|
152
|
+
signal: AbortSignal,
|
|
153
|
+
): Promise<void>;
|
|
154
|
+
admitTurn(input: ExecuteTurnInput): Promise<AcceptedTurn>;
|
|
74
155
|
executeTurn(input: ExecuteTurnInput): Promise<RunAgentResult>;
|
|
75
156
|
compactContext(): Promise<ContextCompactionResult>;
|
|
157
|
+
retireContext(): Promise<ContextRetirementResult>;
|
|
158
|
+
cloneSession(targetSessionId: SessionId): Promise<void>;
|
|
76
159
|
canSwitchSession(): boolean;
|
|
77
160
|
dispose(reason: SessionDisposeReason): Promise<void>;
|
|
78
161
|
};
|
|
79
162
|
|
|
163
|
+
const EMPTY_MCP_INVENTORY: McpInventorySnapshot = Object.freeze({
|
|
164
|
+
servers: Object.freeze([]),
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
export type RuntimeSkillsSnapshot = {
|
|
168
|
+
readonly skills: readonly {
|
|
169
|
+
readonly name: string;
|
|
170
|
+
readonly description: string;
|
|
171
|
+
readonly scope: "project" | "user";
|
|
172
|
+
readonly active: boolean;
|
|
173
|
+
}[];
|
|
174
|
+
readonly shadowedNames: readonly string[];
|
|
175
|
+
};
|
|
176
|
+
|
|
80
177
|
export type RuntimeSessionContext = {
|
|
81
178
|
readonly sessionId: SessionId;
|
|
82
179
|
createIteration(turn: TurnIdentity, iterationNumber: number): IterationIdentity;
|
|
@@ -86,6 +183,32 @@ export type RuntimeSessionContext = {
|
|
|
86
183
|
): ToolCallIdentity;
|
|
87
184
|
finishIterationForContinuation(iteration: IterationIdentity): void;
|
|
88
185
|
append(input: AgentEventInput): Promise<void>;
|
|
186
|
+
onToolCompletionsCommitted?(input: {
|
|
187
|
+
completions: readonly ToolCompletionInput[];
|
|
188
|
+
committed: readonly CommittedToolCompletion[];
|
|
189
|
+
}): void;
|
|
190
|
+
prepareModelDispatch?(input: {
|
|
191
|
+
iteration: IterationIdentity;
|
|
192
|
+
built: BuiltContextRequest;
|
|
193
|
+
}): void;
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
export type ContextSurfaceRefreshSummary = {
|
|
197
|
+
readonly previousRevisionNumber: number;
|
|
198
|
+
readonly revisionNumber: number;
|
|
199
|
+
readonly changed: readonly ContextSurfaceComponent[];
|
|
200
|
+
readonly toolCountBefore: number;
|
|
201
|
+
readonly toolCountAfter: number;
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
export type SkillsUpdateSummary = {
|
|
205
|
+
readonly previousRevisionNumber: number;
|
|
206
|
+
readonly revisionNumber: number;
|
|
207
|
+
readonly activated: readonly string[];
|
|
208
|
+
readonly refreshed: readonly string[];
|
|
209
|
+
readonly deactivated: readonly string[];
|
|
210
|
+
readonly unavailable: readonly string[];
|
|
211
|
+
readonly addedOverrideCount: number;
|
|
89
212
|
};
|
|
90
213
|
|
|
91
214
|
type CommonRuntimeSessionInput = {
|
|
@@ -97,6 +220,9 @@ type CommonRuntimeSessionInput = {
|
|
|
97
220
|
contextProfile: ModelContextProfile;
|
|
98
221
|
contextBudget: ModelContextBudget;
|
|
99
222
|
modelClient: ModelClient;
|
|
223
|
+
systemPrompt: string;
|
|
224
|
+
projectInstruction?: ProjectInstructionManifest;
|
|
225
|
+
skillCatalog?: SkillCatalogSnapshot;
|
|
100
226
|
presentationSinks?: EventSink[];
|
|
101
227
|
persistence?:
|
|
102
228
|
| false
|
|
@@ -109,8 +235,6 @@ type CommonRuntimeSessionInput = {
|
|
|
109
235
|
|
|
110
236
|
type CreateNewRuntimeSessionInput = CommonRuntimeSessionInput & {
|
|
111
237
|
selection: { mode: "new"; sessionId: SessionId };
|
|
112
|
-
systemPrompt: string;
|
|
113
|
-
projectInstruction?: ProjectInstructionManifest;
|
|
114
238
|
};
|
|
115
239
|
|
|
116
240
|
type ResumeRuntimeSessionInput = CommonRuntimeSessionInput & {
|
|
@@ -135,7 +259,11 @@ export type RuntimeSessionFactoryDependencies = {
|
|
|
135
259
|
createEventSink: (input: CreateRuntimeSessionInput) => EventSink;
|
|
136
260
|
selectShadowPlanning: NonNullable<RunAgentInput["shadowPlanning"]>["select"];
|
|
137
261
|
onShadowPlanningResult?: NonNullable<RunAgentInput["shadowPlanning"]>["onResult"];
|
|
262
|
+
selectContextAutomation: typeof selectContextAutomation;
|
|
263
|
+
automaticCompactionTrigger: () => ContextCompactionTrigger;
|
|
264
|
+
automaticRetirementTrigger: () => ContextRetirementTrigger;
|
|
138
265
|
manualCompactionTrigger: () => ContextCompactionTrigger;
|
|
266
|
+
manualRetirementTrigger: () => ContextRetirementTrigger;
|
|
139
267
|
};
|
|
140
268
|
|
|
141
269
|
export class RuntimeEventAppendError extends Error {
|
|
@@ -150,9 +278,11 @@ export class RuntimeEventAppendError extends Error {
|
|
|
150
278
|
|
|
151
279
|
type RuntimeSessionState =
|
|
152
280
|
| "initializing"
|
|
281
|
+
| "admitting"
|
|
153
282
|
| "ready"
|
|
154
283
|
| "executing"
|
|
155
284
|
| "compacting"
|
|
285
|
+
| "maintaining_context"
|
|
156
286
|
| "faulted"
|
|
157
287
|
| "disposing"
|
|
158
288
|
| "disposed";
|
|
@@ -162,6 +292,12 @@ type ActiveTurn = {
|
|
|
162
292
|
completion: Promise<RunAgentResult>;
|
|
163
293
|
};
|
|
164
294
|
|
|
295
|
+
type ActiveAdmission = {
|
|
296
|
+
controller: AbortController;
|
|
297
|
+
settled: Promise<void>;
|
|
298
|
+
settle: () => void;
|
|
299
|
+
};
|
|
300
|
+
|
|
165
301
|
const defaultDependencies: RuntimeSessionFactoryDependencies = {
|
|
166
302
|
idFactory: runtimeIdFactory,
|
|
167
303
|
createTooling: createDefaultTooling,
|
|
@@ -186,7 +322,11 @@ const defaultDependencies: RuntimeSessionFactoryDependencies = {
|
|
|
186
322
|
createEventSink,
|
|
187
323
|
selectShadowPlanning: ({ preflight }) =>
|
|
188
324
|
preflight.pressure === "normal" ? undefined : { trigger: "runtime_pressure" },
|
|
325
|
+
selectContextAutomation,
|
|
326
|
+
automaticCompactionTrigger: () => ({ kind: "runtime_pressure" }),
|
|
327
|
+
automaticRetirementTrigger: () => ({ kind: "runtime_pressure" }),
|
|
189
328
|
manualCompactionTrigger: () => ({ kind: "manual" }),
|
|
329
|
+
manualRetirementTrigger: () => ({ kind: "manual" }),
|
|
190
330
|
};
|
|
191
331
|
|
|
192
332
|
class DefaultRuntimeSession implements RuntimeSession {
|
|
@@ -207,14 +347,21 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
207
347
|
private tooling?: DefaultTooling;
|
|
208
348
|
private mcpManager?: McpManager;
|
|
209
349
|
private ledger?: SessionLedger;
|
|
350
|
+
private activeAdmission?: ActiveAdmission;
|
|
210
351
|
private activeTurn?: ActiveTurn;
|
|
211
|
-
private
|
|
352
|
+
private activeContextRevision?: Promise<
|
|
353
|
+
ContextCompactionResult | ContextRetirementResult
|
|
354
|
+
>;
|
|
212
355
|
private disposePromise?: Promise<void>;
|
|
213
356
|
private faultCause?: unknown;
|
|
214
357
|
private readonly contextMeter: ContextMeter;
|
|
215
358
|
private readonly committedPrefixAuditor = new CommittedPrefixAuditor();
|
|
216
359
|
private readonly shadowPlanner: SwapPlanner;
|
|
217
360
|
private contextManager?: ContextManager;
|
|
361
|
+
private contextAutomationDecision?: ContextAutomationDecision;
|
|
362
|
+
private pendingAutomaticContextMaintenance = false;
|
|
363
|
+
private readonly skillCatalog: SkillCatalogSnapshot;
|
|
364
|
+
private skillCoordinator = new SkillActivationCoordinator();
|
|
218
365
|
|
|
219
366
|
private readonly context: RuntimeSessionContext;
|
|
220
367
|
|
|
@@ -224,9 +371,18 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
224
371
|
private readonly eventSink: EventSink,
|
|
225
372
|
private readonly observationBuilder: ObservationBuilder,
|
|
226
373
|
private readonly store: SessionStore,
|
|
374
|
+
private readonly assetStore: ImageAssetStore,
|
|
227
375
|
) {
|
|
228
376
|
this.sessionId = input.selection.sessionId;
|
|
229
377
|
this.resumed = input.selection.mode === "resume";
|
|
378
|
+
this.skillCatalog =
|
|
379
|
+
input.skillCatalog ??
|
|
380
|
+
createSkillCatalogSnapshot({
|
|
381
|
+
workspaceRoot: input.workspaceRoot,
|
|
382
|
+
homeRoot: input.workspaceRoot,
|
|
383
|
+
skills: [],
|
|
384
|
+
shadowed: [],
|
|
385
|
+
});
|
|
230
386
|
this.nextTurnNumber = store.nextTurnNumber();
|
|
231
387
|
this.contextMeter = new ContextMeter(input.contextBudget, {
|
|
232
388
|
onMeasuredAnchor: (anchor) => store.writeMeasuredContextAnchor(anchor),
|
|
@@ -241,6 +397,9 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
241
397
|
finishIterationForContinuation: (iteration) =>
|
|
242
398
|
this.finishIterationForContinuation(iteration),
|
|
243
399
|
append: (event) => this.append(event),
|
|
400
|
+
onToolCompletionsCommitted: (completion) =>
|
|
401
|
+
this.onToolCompletionsCommitted(completion),
|
|
402
|
+
prepareModelDispatch: (dispatch) => this.prepareModelDispatch(dispatch),
|
|
244
403
|
};
|
|
245
404
|
}
|
|
246
405
|
|
|
@@ -250,18 +409,28 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
250
409
|
): Promise<RuntimeSession> {
|
|
251
410
|
validateCreateInput(input);
|
|
252
411
|
const store = await dependencies.openStore(input, dependencies.idFactory);
|
|
412
|
+
let assetStore: ImageAssetStore;
|
|
413
|
+
try {
|
|
414
|
+
assetStore = await ImageAssetStore.open({ workspaceRoot: store.workspaceRoot });
|
|
415
|
+
} catch (error) {
|
|
416
|
+
if (isNewSessionInput(input)) {
|
|
417
|
+
await store
|
|
418
|
+
.deleteFromDisk()
|
|
419
|
+
.catch(() => store.abandon().catch(() => undefined));
|
|
420
|
+
} else {
|
|
421
|
+
await store.abandon().catch(() => undefined);
|
|
422
|
+
}
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
253
425
|
let session: DefaultRuntimeSession;
|
|
254
|
-
let systemPrompt: string;
|
|
255
426
|
try {
|
|
256
|
-
systemPrompt = isNewSessionInput(input)
|
|
257
|
-
? input.systemPrompt
|
|
258
|
-
: store.readStoredSystemPrompt();
|
|
259
427
|
session = new DefaultRuntimeSession(
|
|
260
428
|
input,
|
|
261
429
|
dependencies,
|
|
262
430
|
dependencies.createEventSink(input),
|
|
263
431
|
dependencies.createObservationBuilder(),
|
|
264
432
|
store,
|
|
433
|
+
assetStore,
|
|
265
434
|
);
|
|
266
435
|
} catch (error) {
|
|
267
436
|
if (isNewSessionInput(input)) {
|
|
@@ -276,6 +445,63 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
276
445
|
let started = false;
|
|
277
446
|
|
|
278
447
|
try {
|
|
448
|
+
const compatibility = createSessionCompatibilityContract({
|
|
449
|
+
modelName: input.modelName,
|
|
450
|
+
profileName: input.profileName,
|
|
451
|
+
includeReasoningContent: input.includeReasoningContent,
|
|
452
|
+
contextProfile: input.contextProfile,
|
|
453
|
+
messageProtocol: input.modelClient.messageProtocol,
|
|
454
|
+
inputModalities: input.modelClient.inputModalities ?? ["text"],
|
|
455
|
+
...(input.modelClient.inputTokenEstimator === undefined
|
|
456
|
+
? {}
|
|
457
|
+
: {
|
|
458
|
+
tokenEstimator: input.modelClient.inputTokenEstimator.compatibility,
|
|
459
|
+
}),
|
|
460
|
+
});
|
|
461
|
+
if (input.selection.mode === "resume") {
|
|
462
|
+
store.assertSessionCompatibility(compatibility);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
let resumeSkills:
|
|
466
|
+
| {
|
|
467
|
+
unresolved: readonly StoredSkillActivation[];
|
|
468
|
+
activated: readonly string[];
|
|
469
|
+
refreshed: readonly string[];
|
|
470
|
+
deactivated: readonly string[];
|
|
471
|
+
}
|
|
472
|
+
| undefined;
|
|
473
|
+
if (input.selection.mode === "resume") {
|
|
474
|
+
session.recovery = store.recoverInterruptedState(dependencies.idFactory);
|
|
475
|
+
const storedSurface = store.loadContextSnapshot().surface;
|
|
476
|
+
const unresolved = store.loadSkillActivations(["pending", "dispatched"]);
|
|
477
|
+
const promotionNames = unresolved
|
|
478
|
+
.filter((activation) => activation.state === "dispatched")
|
|
479
|
+
.map((activation) => ({
|
|
480
|
+
name: activation.name,
|
|
481
|
+
activationMessageId: activation.activationMessageId,
|
|
482
|
+
}));
|
|
483
|
+
const rebound = rebindActiveSkills({
|
|
484
|
+
catalog: session.skillCatalog,
|
|
485
|
+
active: storedSurface.activeSkills,
|
|
486
|
+
promotionNames,
|
|
487
|
+
});
|
|
488
|
+
session.skillCoordinator = new SkillActivationCoordinator({
|
|
489
|
+
active: rebound.active,
|
|
490
|
+
});
|
|
491
|
+
const activated = promotionNames
|
|
492
|
+
.filter((entry) => session.skillCatalog.skills.has(entry.name))
|
|
493
|
+
.map((entry) => entry.name)
|
|
494
|
+
.sort();
|
|
495
|
+
resumeSkills = {
|
|
496
|
+
unresolved,
|
|
497
|
+
activated: Object.freeze(activated),
|
|
498
|
+
refreshed: Object.freeze(
|
|
499
|
+
rebound.refreshed.filter((name) => !activated.includes(name)).sort(),
|
|
500
|
+
),
|
|
501
|
+
deactivated: rebound.deactivated,
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
|
|
279
505
|
if (isNewSessionInput(input)) {
|
|
280
506
|
await session.append({
|
|
281
507
|
type: "session.started",
|
|
@@ -305,6 +531,12 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
305
531
|
runtimeSession: session.context,
|
|
306
532
|
historyReader: store.historyReader(),
|
|
307
533
|
webFetchRefiner: input.webFetchRefiner,
|
|
534
|
+
...(session.skillCatalog.skills.size === 0
|
|
535
|
+
? {}
|
|
536
|
+
: {
|
|
537
|
+
skillCatalog: session.skillCatalog,
|
|
538
|
+
skillCoordinator: session.skillCoordinator,
|
|
539
|
+
}),
|
|
308
540
|
});
|
|
309
541
|
|
|
310
542
|
const mcpConfig = await dependencies.loadMcpConfig(input.workspaceRoot);
|
|
@@ -319,25 +551,53 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
319
551
|
}
|
|
320
552
|
|
|
321
553
|
const definitions = session.requireTooling().registry.definitions();
|
|
322
|
-
const
|
|
323
|
-
|
|
554
|
+
const activeSystemPrompt = buildActiveSystemPrompt({
|
|
555
|
+
baseSystemPrompt: input.systemPrompt,
|
|
556
|
+
activeSkills: session.skillCoordinator.activeEntries(),
|
|
557
|
+
});
|
|
558
|
+
const surfacePrepared = input.modelClient.prepare({
|
|
559
|
+
messages: [{ role: "system", content: activeSystemPrompt }],
|
|
324
560
|
tools: definitions,
|
|
325
561
|
});
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
562
|
+
const candidateSurface = createContextSurface({
|
|
563
|
+
surfaceId: dependencies.idFactory.createContextSurfaceId(),
|
|
564
|
+
sessionId: session.sessionId,
|
|
565
|
+
systemPrompt: activeSystemPrompt,
|
|
566
|
+
recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
|
|
567
|
+
...(input.projectInstruction === undefined
|
|
568
|
+
? {}
|
|
569
|
+
: { projectInstruction: input.projectInstruction }),
|
|
570
|
+
skillCatalog: skillCatalogManifest(session.skillCatalog.skills.values()),
|
|
571
|
+
activeSkills: session.skillCoordinator.activeManifest(),
|
|
572
|
+
toolDefinitions: definitions,
|
|
573
|
+
prepared: surfacePrepared,
|
|
574
|
+
createdAt: new Date().toISOString(),
|
|
335
575
|
});
|
|
336
576
|
if (input.selection.mode === "new") {
|
|
337
|
-
store.
|
|
577
|
+
store.finalizeInitialization({
|
|
578
|
+
contract: compatibility,
|
|
579
|
+
surface: candidateSurface,
|
|
580
|
+
revisionId: dependencies.idFactory.createContextRevisionId(),
|
|
581
|
+
});
|
|
338
582
|
} else {
|
|
339
|
-
|
|
340
|
-
|
|
583
|
+
if (resumeSkills === undefined) {
|
|
584
|
+
throw new Error("Resume Agent Skills staging state is missing.");
|
|
585
|
+
}
|
|
586
|
+
let skillsUpdate: SkillsUpdateSummary | undefined;
|
|
587
|
+
const refresh =
|
|
588
|
+
resumeSkills.unresolved.length === 0
|
|
589
|
+
? await session.refreshContextSurface(candidateSurface)
|
|
590
|
+
: undefined;
|
|
591
|
+
if (resumeSkills.unresolved.length > 0) {
|
|
592
|
+
skillsUpdate = await session.commitSkillSettlements({
|
|
593
|
+
reason: "resume",
|
|
594
|
+
candidateSurface,
|
|
595
|
+
unresolved: resumeSkills.unresolved,
|
|
596
|
+
activated: resumeSkills.activated,
|
|
597
|
+
refreshed: resumeSkills.refreshed,
|
|
598
|
+
deactivated: resumeSkills.deactivated,
|
|
599
|
+
});
|
|
600
|
+
}
|
|
341
601
|
const openCount = store.markResumed();
|
|
342
602
|
if (
|
|
343
603
|
session.recovery.recoveredTurnId !== undefined &&
|
|
@@ -353,22 +613,58 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
353
613
|
},
|
|
354
614
|
});
|
|
355
615
|
}
|
|
356
|
-
const projectInstruction = store.readProjectInstructionManifest();
|
|
357
616
|
await session.append({
|
|
358
617
|
type: "session.resumed",
|
|
359
618
|
sessionId: session.sessionId,
|
|
360
619
|
data: {
|
|
361
620
|
openCount,
|
|
362
621
|
...session.recovery,
|
|
363
|
-
|
|
622
|
+
contextProfile: input.contextProfile,
|
|
623
|
+
contextBudget: input.contextBudget,
|
|
624
|
+
...(input.projectInstruction === undefined
|
|
364
625
|
? {}
|
|
365
626
|
: {
|
|
366
|
-
projectInstructionFile: projectInstruction.path,
|
|
627
|
+
projectInstructionFile: input.projectInstruction.path,
|
|
367
628
|
}),
|
|
629
|
+
...(refresh === undefined ? {} : { contextRefresh: refresh }),
|
|
368
630
|
},
|
|
369
631
|
});
|
|
632
|
+
if (skillsUpdate !== undefined) {
|
|
633
|
+
await session.append({
|
|
634
|
+
type: "skills.updated",
|
|
635
|
+
sessionId: session.sessionId,
|
|
636
|
+
data: {
|
|
637
|
+
reason: "resume",
|
|
638
|
+
activated: skillsUpdate.activated,
|
|
639
|
+
refreshed: skillsUpdate.refreshed,
|
|
640
|
+
deactivated: skillsUpdate.deactivated,
|
|
641
|
+
unavailable: skillsUpdate.unavailable,
|
|
642
|
+
revisionNumber: skillsUpdate.revisionNumber,
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
} else if (
|
|
646
|
+
resumeSkills.refreshed.length > 0 ||
|
|
647
|
+
resumeSkills.deactivated.length > 0
|
|
648
|
+
) {
|
|
649
|
+
await session.append({
|
|
650
|
+
type: "skills.updated",
|
|
651
|
+
sessionId: session.sessionId,
|
|
652
|
+
data: {
|
|
653
|
+
reason: "resume",
|
|
654
|
+
activated: [],
|
|
655
|
+
refreshed: resumeSkills.refreshed,
|
|
656
|
+
deactivated: resumeSkills.deactivated,
|
|
657
|
+
unavailable: [],
|
|
658
|
+
...(refresh === undefined
|
|
659
|
+
? {}
|
|
660
|
+
: { revisionNumber: refresh.revisionNumber }),
|
|
661
|
+
},
|
|
662
|
+
});
|
|
663
|
+
}
|
|
370
664
|
}
|
|
371
665
|
|
|
666
|
+
await session.appendSkillsCatalogLoaded();
|
|
667
|
+
|
|
372
668
|
session.ledger = dependencies.createLedger(store, dependencies.idFactory);
|
|
373
669
|
session.contextManager = new ContextManager({
|
|
374
670
|
store,
|
|
@@ -389,6 +685,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
389
685
|
.requireLedger()
|
|
390
686
|
.buildCommittedModelRequest(definitions);
|
|
391
687
|
const initialPrepared = input.modelClient.prepare(initialBuilt.request);
|
|
688
|
+
assertPreparedMatchesSurface(initialPrepared, initialBuilt.surface);
|
|
392
689
|
session.committedPrefixAuditor.audit(
|
|
393
690
|
initialBuilt.compiled.revisionId,
|
|
394
691
|
initialPrepared,
|
|
@@ -409,6 +706,20 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
409
706
|
data: { phase: "initial", snapshot: initialSnapshot },
|
|
410
707
|
});
|
|
411
708
|
|
|
709
|
+
session.contextAutomationDecision = dependencies.selectContextAutomation({
|
|
710
|
+
...(input.profileName === undefined ? {} : { profileName: input.profileName }),
|
|
711
|
+
surface: store.loadContextSnapshot().surface,
|
|
712
|
+
});
|
|
713
|
+
if (
|
|
714
|
+
input.selection.mode === "resume" &&
|
|
715
|
+
initialSnapshot.pressure !== "normal" &&
|
|
716
|
+
session.contextAutomationDecision.automaticSwapOnly
|
|
717
|
+
) {
|
|
718
|
+
session.pendingAutomaticContextMaintenance = true;
|
|
719
|
+
session.state = "executing";
|
|
720
|
+
await session.performAutomaticContextMaintenance();
|
|
721
|
+
}
|
|
722
|
+
|
|
412
723
|
session.state = "ready";
|
|
413
724
|
return session;
|
|
414
725
|
} catch (error) {
|
|
@@ -416,58 +727,638 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
416
727
|
}
|
|
417
728
|
}
|
|
418
729
|
|
|
419
|
-
|
|
730
|
+
private async refreshContextSurface(
|
|
731
|
+
candidateSurface: StoredContextSurfaceV8,
|
|
732
|
+
): Promise<ContextSurfaceRefreshSummary | undefined> {
|
|
733
|
+
const snapshot = this.store.loadContextSnapshot();
|
|
734
|
+
if (sameContextSurface(snapshot.surface, candidateSurface)) {
|
|
735
|
+
return undefined;
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
const changes = contextSurfaceChanges(snapshot.surface, candidateSurface);
|
|
739
|
+
const changed = changedContextSurfaceComponents(changes);
|
|
740
|
+
if (changed.length === 0) {
|
|
741
|
+
throw new Error("Changed context surface has an empty change manifest.");
|
|
742
|
+
}
|
|
743
|
+
const startedAt = performance.now();
|
|
744
|
+
await this.append({
|
|
745
|
+
type: "context.revision.started",
|
|
746
|
+
sessionId: this.sessionId,
|
|
747
|
+
data: {
|
|
748
|
+
strategy: "surface_refresh",
|
|
749
|
+
reason: "resume",
|
|
750
|
+
baseRevisionNumber: snapshot.revision.revisionNumber,
|
|
751
|
+
changed,
|
|
752
|
+
},
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
let stage: "prepare" | "commit" | "activate" = "prepare";
|
|
756
|
+
let committed = false;
|
|
757
|
+
try {
|
|
758
|
+
const compiler = new ContextRevisionCompiler();
|
|
759
|
+
const active = compiler.compileActive(snapshot);
|
|
760
|
+
const candidateCompiled = compiler.compileProspective({
|
|
761
|
+
active,
|
|
762
|
+
canonical: snapshot.canonical,
|
|
763
|
+
activeOverrides: snapshot.activeOverrides,
|
|
764
|
+
addedOverrides: [],
|
|
765
|
+
activeSurface: snapshot.surface,
|
|
766
|
+
surface: candidateSurface,
|
|
767
|
+
});
|
|
768
|
+
const prepared = this.input.modelClient.prepare({
|
|
769
|
+
messages: candidateCompiled.entries.map((entry) => entry.message),
|
|
770
|
+
tools: [...candidateSurface.toolDefinitions],
|
|
771
|
+
});
|
|
772
|
+
assertPreparedMatchesSurface(prepared, candidateSurface);
|
|
773
|
+
|
|
774
|
+
stage = "commit";
|
|
775
|
+
const revision = this.store.commitSurfaceRefresh({
|
|
776
|
+
revisionId: this.dependencies.idFactory.createContextRevisionId(),
|
|
777
|
+
expectedBaseRevisionId: snapshot.revision.revisionId,
|
|
778
|
+
expectedBaseRevisionNumber: snapshot.revision.revisionNumber,
|
|
779
|
+
expectedCanonicalThroughOrdinal: snapshot.canonical.messages.length,
|
|
780
|
+
expectedBaseActiveOverrideManifestSha256:
|
|
781
|
+
snapshot.revision.activeOverrideManifestSha256,
|
|
782
|
+
surface: candidateSurface,
|
|
783
|
+
changes,
|
|
784
|
+
changeManifestSha256: contextSurfaceChangeManifestHash(changes),
|
|
785
|
+
canonicalSequenceSha256: canonicalSequenceHash(snapshot.canonical),
|
|
786
|
+
renderedMessageSha256: renderedMessageHash(candidateCompiled.entries),
|
|
787
|
+
});
|
|
788
|
+
committed = true;
|
|
789
|
+
|
|
790
|
+
stage = "activate";
|
|
791
|
+
this.contextMeter.startRevision({
|
|
792
|
+
reason: "context_rebuilt",
|
|
793
|
+
requestConfigHash: prepared.requestConfigHash,
|
|
794
|
+
toolSchemaHash: prepared.toolSchemaHash,
|
|
795
|
+
});
|
|
796
|
+
const summary = Object.freeze({
|
|
797
|
+
previousRevisionNumber: snapshot.revision.revisionNumber,
|
|
798
|
+
revisionNumber: revision.revisionNumber,
|
|
799
|
+
changed,
|
|
800
|
+
toolCountBefore: snapshot.surface.toolDefinitions.length,
|
|
801
|
+
toolCountAfter: candidateSurface.toolDefinitions.length,
|
|
802
|
+
});
|
|
803
|
+
await this.append({
|
|
804
|
+
type: "context.revision.finished",
|
|
805
|
+
sessionId: this.sessionId,
|
|
806
|
+
data: {
|
|
807
|
+
strategy: "surface_refresh",
|
|
808
|
+
reason: "resume",
|
|
809
|
+
baseRevisionNumber: summary.previousRevisionNumber,
|
|
810
|
+
revisionNumber: summary.revisionNumber,
|
|
811
|
+
changed: summary.changed,
|
|
812
|
+
toolCountBefore: summary.toolCountBefore,
|
|
813
|
+
toolCountAfter: summary.toolCountAfter,
|
|
814
|
+
measuredAnchorCleared: true,
|
|
815
|
+
durationMs: elapsedMs(startedAt),
|
|
816
|
+
},
|
|
817
|
+
});
|
|
818
|
+
return summary;
|
|
819
|
+
} catch (error) {
|
|
820
|
+
await this.append({
|
|
821
|
+
type: "context.revision.failed",
|
|
822
|
+
sessionId: this.sessionId,
|
|
823
|
+
data: {
|
|
824
|
+
strategy: "surface_refresh",
|
|
825
|
+
reason: "resume",
|
|
826
|
+
stage,
|
|
827
|
+
errorCode: boundedContextErrorCode(
|
|
828
|
+
error instanceof SessionError
|
|
829
|
+
? error.code
|
|
830
|
+
: error instanceof Error
|
|
831
|
+
? error.name
|
|
832
|
+
: "CONTEXT_SURFACE_REFRESH_FAILED",
|
|
833
|
+
),
|
|
834
|
+
error: `Context surface refresh failed at ${stage}.`,
|
|
835
|
+
committed,
|
|
836
|
+
},
|
|
837
|
+
}).catch(() => undefined);
|
|
838
|
+
throw error;
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
skills(): RuntimeSkillsSnapshot {
|
|
843
|
+
const activeNames = new Set(
|
|
844
|
+
this.skillCoordinator.activeEntries().map((entry) => entry.skill.name),
|
|
845
|
+
);
|
|
846
|
+
return Object.freeze({
|
|
847
|
+
skills: Object.freeze(
|
|
848
|
+
[...this.skillCatalog.skills.values()]
|
|
849
|
+
.sort((left, right) => compareText(left.name, right.name))
|
|
850
|
+
.map((skill) =>
|
|
851
|
+
Object.freeze({
|
|
852
|
+
name: skill.name,
|
|
853
|
+
description: skill.description,
|
|
854
|
+
scope: skill.scope,
|
|
855
|
+
active: activeNames.has(skill.name),
|
|
856
|
+
}),
|
|
857
|
+
),
|
|
858
|
+
),
|
|
859
|
+
shadowedNames: Object.freeze(
|
|
860
|
+
this.skillCatalog.shadowed.map((entry) => entry.name),
|
|
861
|
+
),
|
|
862
|
+
});
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
mcp(): McpInventorySnapshot {
|
|
866
|
+
return this.mcpManager?.inventory ?? EMPTY_MCP_INVENTORY;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
supportsImageInput(): boolean {
|
|
870
|
+
return this.input.modelClient.inputModalities?.includes("image") === true;
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
importImage(
|
|
874
|
+
sourcePath: string,
|
|
875
|
+
signal: AbortSignal,
|
|
876
|
+
prospectiveMessageImageCount: number,
|
|
877
|
+
): Promise<ImportedImageAsset> {
|
|
420
878
|
if (this.state !== "ready") {
|
|
421
|
-
throw new Error(`Cannot
|
|
879
|
+
throw new Error(`Cannot import an image while RuntimeSession is ${this.state}.`);
|
|
422
880
|
}
|
|
423
|
-
if (
|
|
424
|
-
throw new Error("
|
|
881
|
+
if (!this.supportsImageInput()) {
|
|
882
|
+
throw new Error("Current model profile does not support image input.");
|
|
425
883
|
}
|
|
426
|
-
if (
|
|
427
|
-
|
|
884
|
+
if (
|
|
885
|
+
!Number.isSafeInteger(prospectiveMessageImageCount) ||
|
|
886
|
+
prospectiveMessageImageCount < 1 ||
|
|
887
|
+
prospectiveMessageImageCount > IMAGE_INPUT_POLICY.maxImagesPerMessage
|
|
888
|
+
) {
|
|
889
|
+
throw new Error("Prospective Prompt image count is invalid.");
|
|
890
|
+
}
|
|
891
|
+
const activeImageCount = this.input.modelClient.prepare(
|
|
892
|
+
this.requireLedger().buildCommittedModelRequest(
|
|
893
|
+
this.requireTooling().registry.definitions(),
|
|
894
|
+
).request,
|
|
895
|
+
).mediaOccurrenceCount;
|
|
896
|
+
const aggregateImageCount = activeImageCount + prospectiveMessageImageCount;
|
|
897
|
+
if (aggregateImageCount > IMAGE_INPUT_POLICY.maxImagesPerRequest) {
|
|
898
|
+
throw new ModelRequestMediaAggregateError(
|
|
899
|
+
`Model request would have ${aggregateImageCount} images; maximum is ${IMAGE_INPUT_POLICY.maxImagesPerRequest}.`,
|
|
900
|
+
);
|
|
901
|
+
}
|
|
902
|
+
return this.assetStore.importWorkspaceFile(sourcePath, { signal });
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
async verifyImageAssets(
|
|
906
|
+
assets: readonly ImageAssetRef[],
|
|
907
|
+
signal: AbortSignal,
|
|
908
|
+
): Promise<void> {
|
|
909
|
+
if (!this.supportsImageInput()) {
|
|
910
|
+
throw new Error("Current model profile does not support image input.");
|
|
911
|
+
}
|
|
912
|
+
for (const asset of assets) {
|
|
913
|
+
signal.throwIfAborted();
|
|
914
|
+
await this.assetStore.readVerified(asset, { signal });
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
private requireContextAutomation(): ContextAutomationDecision {
|
|
919
|
+
if (this.contextAutomationDecision === undefined) {
|
|
920
|
+
throw new Error("RuntimeSession context automation is not initialized.");
|
|
921
|
+
}
|
|
922
|
+
return this.contextAutomationDecision;
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
private appendSkillsCatalogLoaded(): Promise<void> {
|
|
926
|
+
const activeNames = this.skillCoordinator
|
|
927
|
+
.activeEntries()
|
|
928
|
+
.map((entry) => entry.skill.name);
|
|
929
|
+
if (
|
|
930
|
+
this.skillCatalog.skills.size === 0 &&
|
|
931
|
+
activeNames.length === 0 &&
|
|
932
|
+
this.skillCatalog.shadowed.length === 0
|
|
933
|
+
) {
|
|
934
|
+
return Promise.resolve();
|
|
935
|
+
}
|
|
936
|
+
const skills = [...this.skillCatalog.skills.values()];
|
|
937
|
+
return this.append({
|
|
938
|
+
type: "skills.catalog.loaded",
|
|
939
|
+
sessionId: this.sessionId,
|
|
940
|
+
data: {
|
|
941
|
+
availableCount: skills.length,
|
|
942
|
+
projectCount: skills.filter((skill) => skill.scope === "project").length,
|
|
943
|
+
userCount: skills.filter((skill) => skill.scope === "user").length,
|
|
944
|
+
activeNames: Object.freeze(activeNames),
|
|
945
|
+
shadowedNames: Object.freeze(
|
|
946
|
+
this.skillCatalog.shadowed.map((entry) => entry.name),
|
|
947
|
+
),
|
|
948
|
+
},
|
|
949
|
+
});
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
private onToolCompletionsCommitted(input: {
|
|
953
|
+
completions: readonly ToolCompletionInput[];
|
|
954
|
+
committed: readonly CommittedToolCompletion[];
|
|
955
|
+
}): void {
|
|
956
|
+
if (input.completions.length !== input.committed.length) {
|
|
957
|
+
throw new Error("Committed tool completion identity count does not match.");
|
|
428
958
|
}
|
|
959
|
+
for (let index = 0; index < input.completions.length; index += 1) {
|
|
960
|
+
const completion = input.completions[index];
|
|
961
|
+
const committed = input.committed[index];
|
|
962
|
+
if (
|
|
963
|
+
completion === undefined ||
|
|
964
|
+
committed === undefined ||
|
|
965
|
+
completion.call.toolCallId !== committed.toolCallId
|
|
966
|
+
) {
|
|
967
|
+
throw new Error("Committed tool completion identity is invalid.");
|
|
968
|
+
}
|
|
969
|
+
if (
|
|
970
|
+
completion.kind === "returned" &&
|
|
971
|
+
completion.raw.kind === "skill" &&
|
|
972
|
+
completion.raw.ok &&
|
|
973
|
+
completion.raw.status === "loaded"
|
|
974
|
+
) {
|
|
975
|
+
this.skillCoordinator.markPending(completion.raw.name);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
}
|
|
429
979
|
|
|
430
|
-
|
|
980
|
+
private prepareModelDispatch(input: {
|
|
981
|
+
iteration: IterationIdentity;
|
|
982
|
+
built: BuiltContextRequest;
|
|
983
|
+
}): void {
|
|
984
|
+
const pending = this.store.loadSkillActivations(["pending"]);
|
|
985
|
+
if (pending.length === 0) {
|
|
986
|
+
return;
|
|
987
|
+
}
|
|
988
|
+
const visibleCanonicalMessageIds = new Set(
|
|
989
|
+
input.built.compiled.entries
|
|
990
|
+
.filter(
|
|
991
|
+
(entry) =>
|
|
992
|
+
entry.representation === "canonical" && entry.message.role === "tool",
|
|
993
|
+
)
|
|
994
|
+
.map((entry) => entry.messageId),
|
|
995
|
+
);
|
|
996
|
+
const included = pending.filter((activation) =>
|
|
997
|
+
visibleCanonicalMessageIds.has(activation.activationMessageId),
|
|
998
|
+
);
|
|
999
|
+
if (included.length === 0) {
|
|
1000
|
+
return;
|
|
1001
|
+
}
|
|
1002
|
+
const dispatched = this.store.markSkillActivationsDispatched({
|
|
1003
|
+
iterationId: input.iteration.iterationId,
|
|
1004
|
+
activationMessageIds: included.map(
|
|
1005
|
+
(activation) => activation.activationMessageId,
|
|
1006
|
+
),
|
|
1007
|
+
});
|
|
1008
|
+
this.skillCoordinator.markDispatched(
|
|
1009
|
+
dispatched.map((activation) => activation.name),
|
|
1010
|
+
);
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
private async commitSkillSettlements(input: {
|
|
1014
|
+
reason: "activation" | "resume";
|
|
1015
|
+
unresolved: readonly StoredSkillActivation[];
|
|
1016
|
+
candidateSurface?: StoredContextSurfaceV8;
|
|
1017
|
+
activated?: readonly string[];
|
|
1018
|
+
refreshed?: readonly string[];
|
|
1019
|
+
deactivated?: readonly string[];
|
|
1020
|
+
}): Promise<SkillsUpdateSummary> {
|
|
1021
|
+
if (input.unresolved.length === 0) {
|
|
1022
|
+
throw new Error("Agent Skills update requires unresolved activations.");
|
|
1023
|
+
}
|
|
1024
|
+
const snapshot = this.store.loadContextSnapshot();
|
|
1025
|
+
const canonicalMessages = new Map(
|
|
1026
|
+
snapshot.canonical.messages.map((message) => [message.messageId, message]),
|
|
1027
|
+
);
|
|
1028
|
+
const activeByName = new Map(
|
|
1029
|
+
this.skillCoordinator
|
|
1030
|
+
.activeEntries()
|
|
1031
|
+
.map((entry) => [entry.skill.name, entry] as const),
|
|
1032
|
+
);
|
|
1033
|
+
const activated = new Set(input.activated ?? []);
|
|
1034
|
+
const unavailable = new Set<string>();
|
|
1035
|
+
const settlements: Array<{
|
|
1036
|
+
activationMessageId: StoredSkillActivation["activationMessageId"];
|
|
1037
|
+
name: string;
|
|
1038
|
+
state: "promoted" | "rejected";
|
|
1039
|
+
rejectionReason?: string;
|
|
1040
|
+
}> = [];
|
|
1041
|
+
const receipts = [];
|
|
1042
|
+
for (const activation of [...input.unresolved].sort((left, right) =>
|
|
1043
|
+
compareText(left.name, right.name),
|
|
1044
|
+
)) {
|
|
1045
|
+
const skill = this.skillCatalog.skills.get(activation.name);
|
|
1046
|
+
const canPromote = activation.state === "dispatched" && skill !== undefined;
|
|
1047
|
+
if (canPromote) {
|
|
1048
|
+
const existing = activeByName.get(activation.name);
|
|
1049
|
+
if (
|
|
1050
|
+
existing !== undefined &&
|
|
1051
|
+
existing.activationMessageId !== activation.activationMessageId
|
|
1052
|
+
) {
|
|
1053
|
+
throw new Error(
|
|
1054
|
+
`Agent Skill ${activation.name} already has another active activation.`,
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
activeByName.set(activation.name, {
|
|
1058
|
+
skill,
|
|
1059
|
+
activationMessageId: activation.activationMessageId,
|
|
1060
|
+
});
|
|
1061
|
+
activated.add(activation.name);
|
|
1062
|
+
}
|
|
1063
|
+
const state = canPromote ? "promoted" : "rejected";
|
|
1064
|
+
const rejectionReason =
|
|
1065
|
+
state === "promoted"
|
|
1066
|
+
? undefined
|
|
1067
|
+
: activation.state === "pending"
|
|
1068
|
+
? "not_dispatched"
|
|
1069
|
+
: "unavailable";
|
|
1070
|
+
if (rejectionReason === "unavailable") {
|
|
1071
|
+
unavailable.add(activation.name);
|
|
1072
|
+
}
|
|
1073
|
+
settlements.push({
|
|
1074
|
+
activationMessageId: activation.activationMessageId,
|
|
1075
|
+
name: activation.name,
|
|
1076
|
+
state,
|
|
1077
|
+
...(rejectionReason === undefined ? {} : { rejectionReason }),
|
|
1078
|
+
});
|
|
1079
|
+
const message = canonicalMessages.get(activation.activationMessageId);
|
|
1080
|
+
if (message?.role !== "tool") {
|
|
1081
|
+
throw new Error(
|
|
1082
|
+
`Agent Skill activation message ${activation.activationMessageId} is missing.`,
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
receipts.push(
|
|
1086
|
+
renderSkillActivationReceipt({
|
|
1087
|
+
message: {
|
|
1088
|
+
messageId: message.messageId,
|
|
1089
|
+
frameId: message.frameId,
|
|
1090
|
+
ordinal: message.ordinal,
|
|
1091
|
+
content: message.content,
|
|
1092
|
+
contentSha256: message.contentSha256,
|
|
1093
|
+
},
|
|
1094
|
+
name: activation.name,
|
|
1095
|
+
outcome:
|
|
1096
|
+
state === "promoted"
|
|
1097
|
+
? "promoted"
|
|
1098
|
+
: rejectionReason === "unavailable"
|
|
1099
|
+
? "unavailable"
|
|
1100
|
+
: "rejected",
|
|
1101
|
+
}),
|
|
1102
|
+
);
|
|
1103
|
+
}
|
|
1104
|
+
const nextActive = Object.freeze(
|
|
1105
|
+
[...activeByName.values()].sort((left, right) =>
|
|
1106
|
+
compareText(left.skill.name, right.skill.name),
|
|
1107
|
+
),
|
|
1108
|
+
);
|
|
1109
|
+
const createdAt = new Date().toISOString();
|
|
1110
|
+
const definitions = this.requireTooling().registry.definitions();
|
|
1111
|
+
const renderedSystemPrompt = buildActiveSystemPrompt({
|
|
1112
|
+
baseSystemPrompt: this.input.systemPrompt,
|
|
1113
|
+
activeSkills: nextActive,
|
|
1114
|
+
});
|
|
1115
|
+
const surfacePrepared = this.input.modelClient.prepare({
|
|
1116
|
+
messages: [{ role: "system", content: renderedSystemPrompt }],
|
|
1117
|
+
tools: definitions,
|
|
1118
|
+
});
|
|
1119
|
+
const generatedSurface =
|
|
1120
|
+
input.candidateSurface ??
|
|
1121
|
+
createContextSurface({
|
|
1122
|
+
surfaceId: this.dependencies.idFactory.createContextSurfaceId(),
|
|
1123
|
+
sessionId: this.sessionId,
|
|
1124
|
+
systemPrompt: renderedSystemPrompt,
|
|
1125
|
+
recallContractVersion: CURRENT_RECALL_RETIREMENT_CONTRACT_VERSION,
|
|
1126
|
+
...(this.input.projectInstruction === undefined
|
|
1127
|
+
? {}
|
|
1128
|
+
: { projectInstruction: this.input.projectInstruction }),
|
|
1129
|
+
skillCatalog: skillCatalogManifest(this.skillCatalog.skills.values()),
|
|
1130
|
+
activeSkills: nextActive.map((entry) =>
|
|
1131
|
+
activeSkillManifestEntry(entry.skill, entry.activationMessageId),
|
|
1132
|
+
),
|
|
1133
|
+
toolDefinitions: definitions,
|
|
1134
|
+
prepared: surfacePrepared,
|
|
1135
|
+
createdAt,
|
|
1136
|
+
});
|
|
1137
|
+
assertPreparedMatchesSurface(surfacePrepared, generatedSurface);
|
|
1138
|
+
const surface = sameContextSurface(snapshot.surface, generatedSurface)
|
|
1139
|
+
? snapshot.surface
|
|
1140
|
+
: generatedSurface;
|
|
1141
|
+
const startedAt = performance.now();
|
|
1142
|
+
await this.append({
|
|
1143
|
+
type: "context.revision.started",
|
|
1144
|
+
sessionId: this.sessionId,
|
|
1145
|
+
data: {
|
|
1146
|
+
strategy: "skills_update",
|
|
1147
|
+
reason: input.reason,
|
|
1148
|
+
baseRevisionNumber: snapshot.revision.revisionNumber,
|
|
1149
|
+
names: Object.freeze(
|
|
1150
|
+
input.unresolved.map((entry) => entry.name).sort(compareText),
|
|
1151
|
+
),
|
|
1152
|
+
},
|
|
1153
|
+
});
|
|
1154
|
+
let stage: "prepare" | "commit" | "activate" = "prepare";
|
|
1155
|
+
let committed = false;
|
|
431
1156
|
try {
|
|
432
|
-
|
|
433
|
-
this.
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
1157
|
+
const revision = commitAgentSkillsContextUpdate({
|
|
1158
|
+
store: this.store,
|
|
1159
|
+
contextMeter: this.contextMeter,
|
|
1160
|
+
idFactory: this.dependencies.idFactory,
|
|
1161
|
+
snapshot,
|
|
1162
|
+
surface,
|
|
1163
|
+
addedOverrides: receipts,
|
|
1164
|
+
settlements,
|
|
1165
|
+
});
|
|
1166
|
+
committed = true;
|
|
1167
|
+
stage = "activate";
|
|
1168
|
+
this.skillCoordinator.replaceActive(nextActive);
|
|
1169
|
+
this.skillCoordinator.settle(
|
|
1170
|
+
input.unresolved.map((activation) => activation.name),
|
|
437
1171
|
);
|
|
1172
|
+
const summary = Object.freeze({
|
|
1173
|
+
previousRevisionNumber: snapshot.revision.revisionNumber,
|
|
1174
|
+
revisionNumber: revision.revisionNumber,
|
|
1175
|
+
activated: Object.freeze([...activated].sort()),
|
|
1176
|
+
refreshed: Object.freeze([...(input.refreshed ?? [])].sort()),
|
|
1177
|
+
deactivated: Object.freeze([...(input.deactivated ?? [])].sort()),
|
|
1178
|
+
unavailable: Object.freeze([...unavailable].sort()),
|
|
1179
|
+
addedOverrideCount: receipts.length,
|
|
1180
|
+
});
|
|
1181
|
+
await this.append({
|
|
1182
|
+
type: "context.revision.finished",
|
|
1183
|
+
sessionId: this.sessionId,
|
|
1184
|
+
data: {
|
|
1185
|
+
strategy: "skills_update",
|
|
1186
|
+
reason: input.reason,
|
|
1187
|
+
baseRevisionNumber: summary.previousRevisionNumber,
|
|
1188
|
+
revisionNumber: summary.revisionNumber,
|
|
1189
|
+
activated: summary.activated,
|
|
1190
|
+
refreshed: summary.refreshed,
|
|
1191
|
+
deactivated: summary.deactivated,
|
|
1192
|
+
unavailable: summary.unavailable,
|
|
1193
|
+
addedOverrideCount: summary.addedOverrideCount,
|
|
1194
|
+
measuredAnchorCleared: true,
|
|
1195
|
+
durationMs: elapsedMs(startedAt),
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
return summary;
|
|
438
1199
|
} catch (error) {
|
|
439
|
-
if (
|
|
440
|
-
|
|
1200
|
+
if (error instanceof ContextManagerError) {
|
|
1201
|
+
committed = error.committed;
|
|
1202
|
+
stage =
|
|
1203
|
+
error.stage === "commit"
|
|
1204
|
+
? "commit"
|
|
1205
|
+
: error.stage === "activate"
|
|
1206
|
+
? "activate"
|
|
1207
|
+
: "prepare";
|
|
441
1208
|
}
|
|
1209
|
+
await this.append({
|
|
1210
|
+
type: "context.revision.failed",
|
|
1211
|
+
sessionId: this.sessionId,
|
|
1212
|
+
data: {
|
|
1213
|
+
strategy: "skills_update",
|
|
1214
|
+
reason: input.reason,
|
|
1215
|
+
stage,
|
|
1216
|
+
errorCode: boundedContextErrorCode(
|
|
1217
|
+
error instanceof ContextManagerError
|
|
1218
|
+
? error.code
|
|
1219
|
+
: error instanceof SessionError
|
|
1220
|
+
? error.code
|
|
1221
|
+
: error instanceof Error
|
|
1222
|
+
? error.name
|
|
1223
|
+
: "SKILLS_UPDATE_VALIDATION_FAILED",
|
|
1224
|
+
),
|
|
1225
|
+
error: `Agent Skills update failed at ${stage}.`,
|
|
1226
|
+
committed,
|
|
1227
|
+
},
|
|
1228
|
+
}).catch(() => undefined);
|
|
442
1229
|
throw error;
|
|
443
1230
|
}
|
|
444
|
-
|
|
445
|
-
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
async executeTurn(input: ExecuteTurnInput): Promise<RunAgentResult> {
|
|
1234
|
+
return (await this.admitTurn(input)).completion;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
async admitTurn(input: ExecuteTurnInput): Promise<AcceptedTurn> {
|
|
1238
|
+
if (this.state !== "ready") {
|
|
1239
|
+
throw new Error(`Cannot execute a turn while RuntimeSession is ${this.state}.`);
|
|
1240
|
+
}
|
|
1241
|
+
validateUserMessage(input.userMessage);
|
|
1242
|
+
if (this.activeTurn !== undefined) {
|
|
1243
|
+
throw new Error("Cannot execute concurrent turns in one RuntimeSession.");
|
|
1244
|
+
}
|
|
446
1245
|
|
|
1246
|
+
this.state = "admitting";
|
|
447
1247
|
const controller = new AbortController();
|
|
448
1248
|
const removeExternalAbortListener = forwardExternalAbort(input.signal, controller);
|
|
449
|
-
const
|
|
450
|
-
|
|
1249
|
+
const admission = createActiveAdmission(controller);
|
|
1250
|
+
this.activeAdmission = admission;
|
|
1251
|
+
let pendingLedgerTurn: ReturnType<SessionLedger["beginTurn"]>;
|
|
1252
|
+
let admissionPrepared: MaterializedModelRequest;
|
|
1253
|
+
let admissionSnapshot;
|
|
451
1254
|
try {
|
|
1255
|
+
controller.signal.throwIfAborted();
|
|
1256
|
+
const built = this.requireLedger().buildCandidateModelRequest(
|
|
1257
|
+
input.userMessage,
|
|
1258
|
+
this.requireTooling().registry.definitions(),
|
|
1259
|
+
);
|
|
1260
|
+
const admissionBase = this.createAdmissionBaseToken(built);
|
|
1261
|
+
const prepared = this.input.modelClient.prepare(built.request);
|
|
1262
|
+
const localSnapshot = this.contextMeter.measure(prepared);
|
|
1263
|
+
this.contextMeter.assertWithinBudget(localSnapshot);
|
|
1264
|
+
admissionPrepared = await materializeModelRequest(
|
|
1265
|
+
this.input.modelClient,
|
|
1266
|
+
prepared,
|
|
1267
|
+
{ assetStore: this.assetStore, signal: controller.signal },
|
|
1268
|
+
);
|
|
1269
|
+
admissionSnapshot = this.contextMeter.measure(admissionPrepared);
|
|
1270
|
+
if (
|
|
1271
|
+
prepared.mediaOccurrenceCount > 0 &&
|
|
1272
|
+
admissionSnapshot.source !== "measured_plus_estimated_delta"
|
|
1273
|
+
) {
|
|
1274
|
+
const estimator = this.input.modelClient.inputTokenEstimator;
|
|
1275
|
+
if (estimator === undefined) {
|
|
1276
|
+
throw new Error("Image model request has no input token estimator.");
|
|
1277
|
+
}
|
|
1278
|
+
const estimate = await this.contextMeter.estimateProviderInput(
|
|
1279
|
+
admissionPrepared,
|
|
1280
|
+
estimator,
|
|
1281
|
+
{ signal: controller.signal },
|
|
1282
|
+
);
|
|
1283
|
+
admissionSnapshot = this.contextMeter.applyProviderEstimate(
|
|
1284
|
+
admissionPrepared,
|
|
1285
|
+
estimate,
|
|
1286
|
+
);
|
|
1287
|
+
}
|
|
1288
|
+
this.contextMeter.assertWithinBudget(admissionSnapshot);
|
|
1289
|
+
controller.signal.throwIfAborted();
|
|
1290
|
+
const turn = this.stageTurn(input.userMessage);
|
|
452
1291
|
pendingLedgerTurn = this.requireLedger().beginTurn({
|
|
453
1292
|
turn,
|
|
454
|
-
|
|
1293
|
+
userMessage: input.userMessage,
|
|
1294
|
+
admissionBase,
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
this.settleAdmission(admission);
|
|
1298
|
+
this.state = "executing";
|
|
1299
|
+
const completion = this.startAcceptedTurn({
|
|
1300
|
+
userMessage: input.userMessage,
|
|
1301
|
+
turn,
|
|
1302
|
+
pendingLedgerTurn,
|
|
1303
|
+
controller,
|
|
1304
|
+
removeExternalAbortListener,
|
|
1305
|
+
initialRequest: {
|
|
1306
|
+
prepared: admissionPrepared,
|
|
1307
|
+
usage: admissionSnapshot,
|
|
1308
|
+
},
|
|
1309
|
+
});
|
|
1310
|
+
this.activeTurn = { controller, completion };
|
|
1311
|
+
return Object.freeze({
|
|
1312
|
+
turnId: turn.turnId,
|
|
1313
|
+
userMessage: input.userMessage,
|
|
1314
|
+
completion,
|
|
455
1315
|
});
|
|
456
|
-
this.registerTurn(turn);
|
|
457
1316
|
} catch (error) {
|
|
458
|
-
this.
|
|
1317
|
+
this.settleAdmission(admission);
|
|
1318
|
+
removeExternalAbortListener();
|
|
1319
|
+
if (error instanceof AdmissionStaleError) {
|
|
1320
|
+
this.nextTurnNumber = this.store.nextTurnNumber();
|
|
1321
|
+
}
|
|
1322
|
+
if (this.state === "admitting") {
|
|
1323
|
+
this.state = "ready";
|
|
1324
|
+
}
|
|
1325
|
+
if (isCanonicalRuntimeFault(error)) {
|
|
1326
|
+
this.fault(error);
|
|
1327
|
+
}
|
|
459
1328
|
throw error;
|
|
460
1329
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
);
|
|
469
|
-
|
|
470
|
-
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
private settleAdmission(admission: ActiveAdmission): void {
|
|
1333
|
+
if (this.activeAdmission !== admission) {
|
|
1334
|
+
throw new Error("Runtime admission ownership was lost.");
|
|
1335
|
+
}
|
|
1336
|
+
this.activeAdmission = undefined;
|
|
1337
|
+
admission.settle();
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
private createAdmissionBaseToken(built: BuiltContextRequest): AdmissionBaseToken {
|
|
1341
|
+
const meta = this.store.readMeta();
|
|
1342
|
+
const head = built.canonical.messages.at(-1);
|
|
1343
|
+
if (
|
|
1344
|
+
head === undefined ||
|
|
1345
|
+
meta.sessionCompatibilitySha256 === null ||
|
|
1346
|
+
built.revision.revisionId !== meta.activeRevisionId ||
|
|
1347
|
+
built.surface.surfaceSha256 !== built.revision.surfaceSha256 ||
|
|
1348
|
+
meta.nextTurnNumber !== this.nextTurnNumber
|
|
1349
|
+
) {
|
|
1350
|
+
throw new Error("Cannot capture a consistent turn admission base.");
|
|
1351
|
+
}
|
|
1352
|
+
return Object.freeze({
|
|
1353
|
+
canonicalMessageCount: built.canonical.messages.length,
|
|
1354
|
+
canonicalHeadMessageId: head.messageId,
|
|
1355
|
+
canonicalHeadContentSha256: head.contentSha256,
|
|
1356
|
+
activeRevisionId: built.revision.revisionId,
|
|
1357
|
+
activeRevisionNumber: built.revision.revisionNumber,
|
|
1358
|
+
surfaceSha256: built.surface.surfaceSha256,
|
|
1359
|
+
sessionCompatibilitySha256: meta.sessionCompatibilitySha256,
|
|
1360
|
+
nextTurnNumber: meta.nextTurnNumber,
|
|
1361
|
+
});
|
|
471
1362
|
}
|
|
472
1363
|
|
|
473
1364
|
compactContext(): Promise<ContextCompactionResult> {
|
|
@@ -478,16 +1369,16 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
478
1369
|
throw new Error("Cannot compact context while a turn is active.");
|
|
479
1370
|
}
|
|
480
1371
|
const completion = this.performCompactContext();
|
|
481
|
-
this.
|
|
1372
|
+
this.activeContextRevision = completion;
|
|
482
1373
|
void completion.then(
|
|
483
1374
|
() => {
|
|
484
|
-
if (this.
|
|
485
|
-
this.
|
|
1375
|
+
if (this.activeContextRevision === completion) {
|
|
1376
|
+
this.activeContextRevision = undefined;
|
|
486
1377
|
}
|
|
487
1378
|
},
|
|
488
1379
|
() => {
|
|
489
|
-
if (this.
|
|
490
|
-
this.
|
|
1380
|
+
if (this.activeContextRevision === completion) {
|
|
1381
|
+
this.activeContextRevision = undefined;
|
|
491
1382
|
}
|
|
492
1383
|
},
|
|
493
1384
|
);
|
|
@@ -562,6 +1453,104 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
562
1453
|
}
|
|
563
1454
|
}
|
|
564
1455
|
|
|
1456
|
+
retireContext(): Promise<ContextRetirementResult> {
|
|
1457
|
+
if (this.state !== "ready") {
|
|
1458
|
+
throw new Error(
|
|
1459
|
+
`Cannot retire context prefix while RuntimeSession is ${this.state}.`,
|
|
1460
|
+
);
|
|
1461
|
+
}
|
|
1462
|
+
if (this.activeTurn !== undefined) {
|
|
1463
|
+
throw new Error("Cannot retire context prefix while a turn is active.");
|
|
1464
|
+
}
|
|
1465
|
+
const completion = this.performRetireContext();
|
|
1466
|
+
this.activeContextRevision = completion;
|
|
1467
|
+
void completion.then(
|
|
1468
|
+
() => {
|
|
1469
|
+
if (this.activeContextRevision === completion) {
|
|
1470
|
+
this.activeContextRevision = undefined;
|
|
1471
|
+
}
|
|
1472
|
+
},
|
|
1473
|
+
() => {
|
|
1474
|
+
if (this.activeContextRevision === completion) {
|
|
1475
|
+
this.activeContextRevision = undefined;
|
|
1476
|
+
}
|
|
1477
|
+
},
|
|
1478
|
+
);
|
|
1479
|
+
return completion;
|
|
1480
|
+
}
|
|
1481
|
+
|
|
1482
|
+
private async performRetireContext(): Promise<ContextRetirementResult> {
|
|
1483
|
+
if (this.state !== "ready") {
|
|
1484
|
+
throw new Error(
|
|
1485
|
+
`Cannot retire context prefix while RuntimeSession is ${this.state}.`,
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
if (this.activeTurn !== undefined) {
|
|
1489
|
+
throw new Error("Cannot retire context prefix while a turn is active.");
|
|
1490
|
+
}
|
|
1491
|
+
this.store.assertContextRevisionIdle();
|
|
1492
|
+
const baseRevisionNumber = this.store.loadContextSnapshot().revision.revisionNumber;
|
|
1493
|
+
this.state = "compacting";
|
|
1494
|
+
let started = false;
|
|
1495
|
+
try {
|
|
1496
|
+
await this.append({
|
|
1497
|
+
type: "context.revision.started",
|
|
1498
|
+
sessionId: this.sessionId,
|
|
1499
|
+
data: {
|
|
1500
|
+
strategy: "retire_prefix",
|
|
1501
|
+
reason: "manual",
|
|
1502
|
+
policyVersion: "recall-first-retirement-v1",
|
|
1503
|
+
baseRevisionNumber,
|
|
1504
|
+
},
|
|
1505
|
+
});
|
|
1506
|
+
started = true;
|
|
1507
|
+
const result = await this.requireContextManager().retirePrefix(
|
|
1508
|
+
this.dependencies.manualRetirementTrigger(),
|
|
1509
|
+
);
|
|
1510
|
+
await this.append({
|
|
1511
|
+
type: "context.revision.finished",
|
|
1512
|
+
sessionId: this.sessionId,
|
|
1513
|
+
data: contextRetirementFinishedData(result),
|
|
1514
|
+
});
|
|
1515
|
+
if (this.state === "compacting") {
|
|
1516
|
+
this.state = "ready";
|
|
1517
|
+
}
|
|
1518
|
+
return result;
|
|
1519
|
+
} catch (error) {
|
|
1520
|
+
if (started && !(error instanceof RuntimeEventAppendError)) {
|
|
1521
|
+
const failure =
|
|
1522
|
+
error instanceof ContextManagerError
|
|
1523
|
+
? error
|
|
1524
|
+
: new ContextManagerError(
|
|
1525
|
+
"activate",
|
|
1526
|
+
error instanceof Error ? error.name : "CONTEXT_RETIREMENT_FAILED",
|
|
1527
|
+
true,
|
|
1528
|
+
false,
|
|
1529
|
+
"Context prefix retirement failed.",
|
|
1530
|
+
{ cause: error },
|
|
1531
|
+
);
|
|
1532
|
+
await this.append({
|
|
1533
|
+
type: "context.revision.failed",
|
|
1534
|
+
sessionId: this.sessionId,
|
|
1535
|
+
data: {
|
|
1536
|
+
strategy: "retire_prefix",
|
|
1537
|
+
reason: "manual",
|
|
1538
|
+
stage: failure.stage,
|
|
1539
|
+
errorCode: boundedContextErrorCode(failure.code),
|
|
1540
|
+
error: `Context prefix retirement failed at ${failure.stage}.`,
|
|
1541
|
+
committed: failure.committed,
|
|
1542
|
+
},
|
|
1543
|
+
}).catch(() => undefined);
|
|
1544
|
+
}
|
|
1545
|
+
if (!(error instanceof ContextManagerError) || error.fatal) {
|
|
1546
|
+
this.fault(error);
|
|
1547
|
+
} else if (this.state === "compacting") {
|
|
1548
|
+
this.state = "ready";
|
|
1549
|
+
}
|
|
1550
|
+
throw error;
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
565
1554
|
dispose(reason: SessionDisposeReason): Promise<void> {
|
|
566
1555
|
this.disposePromise ??= this.performDispose(reason);
|
|
567
1556
|
return this.disposePromise;
|
|
@@ -578,12 +1567,65 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
578
1567
|
);
|
|
579
1568
|
}
|
|
580
1569
|
|
|
1570
|
+
async cloneSession(targetSessionId: SessionId): Promise<void> {
|
|
1571
|
+
if (!this.canSwitchSession()) {
|
|
1572
|
+
throw new Error(
|
|
1573
|
+
"Cannot clone the session while a turn, context operation, or background task is active.",
|
|
1574
|
+
);
|
|
1575
|
+
}
|
|
1576
|
+
await this.waitForStableEventTail();
|
|
1577
|
+
if (!this.canSwitchSession()) {
|
|
1578
|
+
throw new Error(
|
|
1579
|
+
"Cannot clone the session while a turn, context operation, or background task is active.",
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1582
|
+
await this.store.cloneTo({ targetSessionId });
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
private async waitForStableEventTail(): Promise<void> {
|
|
1586
|
+
for (;;) {
|
|
1587
|
+
const tail = this.eventTail;
|
|
1588
|
+
await tail;
|
|
1589
|
+
if (this.eventTail === tail) {
|
|
1590
|
+
return;
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
private async startAcceptedTurn(input: {
|
|
1596
|
+
userMessage: UserMessage;
|
|
1597
|
+
turn: TurnIdentity;
|
|
1598
|
+
pendingLedgerTurn: ReturnType<SessionLedger["beginTurn"]>;
|
|
1599
|
+
controller: AbortController;
|
|
1600
|
+
removeExternalAbortListener: () => void;
|
|
1601
|
+
initialRequest: NonNullable<RunAgentInput["initialRequest"]>;
|
|
1602
|
+
}): Promise<RunAgentResult> {
|
|
1603
|
+
try {
|
|
1604
|
+
this.registerTurn(input.turn);
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
input.removeExternalAbortListener();
|
|
1607
|
+
input.pendingLedgerTurn.fault(error);
|
|
1608
|
+
this.activeTurn = undefined;
|
|
1609
|
+
this.fault(error);
|
|
1610
|
+
throw error;
|
|
1611
|
+
}
|
|
1612
|
+
return this.performExecuteTurn(
|
|
1613
|
+
input.userMessage,
|
|
1614
|
+
input.turn,
|
|
1615
|
+
input.pendingLedgerTurn,
|
|
1616
|
+
input.controller.signal,
|
|
1617
|
+
input.removeExternalAbortListener,
|
|
1618
|
+
input.initialRequest,
|
|
1619
|
+
);
|
|
1620
|
+
}
|
|
1621
|
+
|
|
581
1622
|
private async performExecuteTurn(
|
|
582
|
-
|
|
1623
|
+
userMessage: UserMessage,
|
|
583
1624
|
turn: TurnIdentity,
|
|
584
1625
|
pendingLedgerTurn: ReturnType<SessionLedger["beginTurn"]>,
|
|
585
1626
|
signal: AbortSignal,
|
|
586
1627
|
removeExternalAbortListener: () => void,
|
|
1628
|
+
initialRequest: NonNullable<RunAgentInput["initialRequest"]>,
|
|
587
1629
|
): Promise<RunAgentResult> {
|
|
588
1630
|
let settled = false;
|
|
589
1631
|
|
|
@@ -591,7 +1633,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
591
1633
|
await this.append({
|
|
592
1634
|
type: "turn.started",
|
|
593
1635
|
...turn,
|
|
594
|
-
data: { userPrompt },
|
|
1636
|
+
data: { userPrompt: projectUserMessage(userMessage) },
|
|
595
1637
|
});
|
|
596
1638
|
|
|
597
1639
|
let result: RunAgentResult;
|
|
@@ -604,7 +1646,16 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
604
1646
|
committedPrefixAuditor: this.committedPrefixAuditor,
|
|
605
1647
|
shadowPlanning: {
|
|
606
1648
|
planner: this.shadowPlanner,
|
|
607
|
-
select:
|
|
1649
|
+
select: (planningInput) => {
|
|
1650
|
+
const selection = this.dependencies.selectShadowPlanning(planningInput);
|
|
1651
|
+
if (
|
|
1652
|
+
selection?.trigger === "runtime_pressure" &&
|
|
1653
|
+
this.requireContextAutomation().automaticSwapOnly
|
|
1654
|
+
) {
|
|
1655
|
+
this.pendingAutomaticContextMaintenance = true;
|
|
1656
|
+
}
|
|
1657
|
+
return selection;
|
|
1658
|
+
},
|
|
608
1659
|
...(this.dependencies.onShadowPlanningResult === undefined
|
|
609
1660
|
? {}
|
|
610
1661
|
: { onResult: this.dependencies.onShadowPlanningResult }),
|
|
@@ -615,6 +1666,8 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
615
1666
|
runtimeSession: this.context,
|
|
616
1667
|
turn,
|
|
617
1668
|
signal,
|
|
1669
|
+
assetStore: this.assetStore,
|
|
1670
|
+
initialRequest,
|
|
618
1671
|
});
|
|
619
1672
|
} catch (error) {
|
|
620
1673
|
if (error instanceof RuntimeEventAppendError) {
|
|
@@ -629,6 +1682,7 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
629
1682
|
);
|
|
630
1683
|
pendingLedgerTurn.finish(error.result);
|
|
631
1684
|
settled = true;
|
|
1685
|
+
await this.settleClosedTurnSkills();
|
|
632
1686
|
throw error;
|
|
633
1687
|
}
|
|
634
1688
|
|
|
@@ -644,6 +1698,10 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
644
1698
|
await this.appendTerminalEvent(turn, result, projectedMessageCount);
|
|
645
1699
|
pendingLedgerTurn.finish(result);
|
|
646
1700
|
settled = true;
|
|
1701
|
+
await this.settleClosedTurnSkills();
|
|
1702
|
+
if (result.status === "completed") {
|
|
1703
|
+
await this.performAutomaticContextMaintenance();
|
|
1704
|
+
}
|
|
647
1705
|
return result;
|
|
648
1706
|
} catch (error) {
|
|
649
1707
|
if (!settled) {
|
|
@@ -654,12 +1712,166 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
654
1712
|
} finally {
|
|
655
1713
|
removeExternalAbortListener();
|
|
656
1714
|
this.activeTurn = undefined;
|
|
1715
|
+
this.pendingAutomaticContextMaintenance = false;
|
|
657
1716
|
if (this.state === "executing") {
|
|
658
1717
|
this.state = "ready";
|
|
659
1718
|
}
|
|
660
1719
|
}
|
|
661
1720
|
}
|
|
662
1721
|
|
|
1722
|
+
private async performAutomaticContextMaintenance(): Promise<void> {
|
|
1723
|
+
if (!this.pendingAutomaticContextMaintenance) return;
|
|
1724
|
+
this.pendingAutomaticContextMaintenance = false;
|
|
1725
|
+
const automation = this.requireContextAutomation();
|
|
1726
|
+
if (!automation.automaticSwapOnly) return;
|
|
1727
|
+
if (this.state !== "executing") {
|
|
1728
|
+
throw new Error(
|
|
1729
|
+
`Cannot run automatic context maintenance while RuntimeSession is ${this.state}.`,
|
|
1730
|
+
);
|
|
1731
|
+
}
|
|
1732
|
+
this.store.assertContextRevisionIdle();
|
|
1733
|
+
const qualificationId = requireAutomationQualificationId(automation);
|
|
1734
|
+
this.state = "maintaining_context";
|
|
1735
|
+
try {
|
|
1736
|
+
const swap = await this.performAutomaticCompaction(qualificationId);
|
|
1737
|
+
if (swap === undefined) return;
|
|
1738
|
+
if (automation.automaticPrefixRetirement && automaticSwapNeedsRetirement(swap)) {
|
|
1739
|
+
await this.performAutomaticRetirement(qualificationId);
|
|
1740
|
+
}
|
|
1741
|
+
} finally {
|
|
1742
|
+
if (this.state === "maintaining_context") {
|
|
1743
|
+
this.state = "executing";
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
private async performAutomaticCompaction(
|
|
1749
|
+
qualificationId: string,
|
|
1750
|
+
): Promise<ContextCompactionResult | undefined> {
|
|
1751
|
+
let started = false;
|
|
1752
|
+
try {
|
|
1753
|
+
await this.append({
|
|
1754
|
+
type: "context.revision.started",
|
|
1755
|
+
sessionId: this.sessionId,
|
|
1756
|
+
data: {
|
|
1757
|
+
strategy: "swap",
|
|
1758
|
+
reason: "runtime_pressure",
|
|
1759
|
+
policyVersion: "swap-only-v1",
|
|
1760
|
+
rendererFormat: "swap-observation-v1",
|
|
1761
|
+
qualificationId,
|
|
1762
|
+
},
|
|
1763
|
+
});
|
|
1764
|
+
started = true;
|
|
1765
|
+
const result = await this.requireContextManager().compact(
|
|
1766
|
+
this.dependencies.automaticCompactionTrigger(),
|
|
1767
|
+
);
|
|
1768
|
+
await this.append({
|
|
1769
|
+
type: "context.revision.finished",
|
|
1770
|
+
sessionId: this.sessionId,
|
|
1771
|
+
data: contextRevisionFinishedData(result, "runtime_pressure", qualificationId),
|
|
1772
|
+
});
|
|
1773
|
+
return result;
|
|
1774
|
+
} catch (error) {
|
|
1775
|
+
if (started && !(error instanceof RuntimeEventAppendError)) {
|
|
1776
|
+
const failure = automaticContextFailure(error, "compaction");
|
|
1777
|
+
await this.append({
|
|
1778
|
+
type: "context.revision.failed",
|
|
1779
|
+
sessionId: this.sessionId,
|
|
1780
|
+
data: {
|
|
1781
|
+
strategy: "swap",
|
|
1782
|
+
reason: "runtime_pressure",
|
|
1783
|
+
stage: failure.stage,
|
|
1784
|
+
errorCode: boundedContextErrorCode(failure.code),
|
|
1785
|
+
error: `Automatic context compaction failed at ${failure.stage}.`,
|
|
1786
|
+
qualificationId,
|
|
1787
|
+
},
|
|
1788
|
+
}).catch(() => undefined);
|
|
1789
|
+
}
|
|
1790
|
+
if (error instanceof ContextManagerError && !error.fatal) {
|
|
1791
|
+
return undefined;
|
|
1792
|
+
}
|
|
1793
|
+
throw error;
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
private async performAutomaticRetirement(
|
|
1798
|
+
qualificationId: string,
|
|
1799
|
+
): Promise<ContextRetirementResult | undefined> {
|
|
1800
|
+
const baseRevisionNumber = this.store.loadContextSnapshot().revision.revisionNumber;
|
|
1801
|
+
let started = false;
|
|
1802
|
+
try {
|
|
1803
|
+
await this.append({
|
|
1804
|
+
type: "context.revision.started",
|
|
1805
|
+
sessionId: this.sessionId,
|
|
1806
|
+
data: {
|
|
1807
|
+
strategy: "retire_prefix",
|
|
1808
|
+
reason: "runtime_pressure",
|
|
1809
|
+
policyVersion: "recall-first-retirement-v1",
|
|
1810
|
+
baseRevisionNumber,
|
|
1811
|
+
qualificationId,
|
|
1812
|
+
},
|
|
1813
|
+
});
|
|
1814
|
+
started = true;
|
|
1815
|
+
const result = await this.requireContextManager().retirePrefix(
|
|
1816
|
+
this.dependencies.automaticRetirementTrigger(),
|
|
1817
|
+
);
|
|
1818
|
+
await this.append({
|
|
1819
|
+
type: "context.revision.finished",
|
|
1820
|
+
sessionId: this.sessionId,
|
|
1821
|
+
data: contextRetirementFinishedData(
|
|
1822
|
+
result,
|
|
1823
|
+
"runtime_pressure",
|
|
1824
|
+
qualificationId,
|
|
1825
|
+
),
|
|
1826
|
+
});
|
|
1827
|
+
return result;
|
|
1828
|
+
} catch (error) {
|
|
1829
|
+
if (started && !(error instanceof RuntimeEventAppendError)) {
|
|
1830
|
+
const failure = automaticContextFailure(error, "retirement");
|
|
1831
|
+
await this.append({
|
|
1832
|
+
type: "context.revision.failed",
|
|
1833
|
+
sessionId: this.sessionId,
|
|
1834
|
+
data: {
|
|
1835
|
+
strategy: "retire_prefix",
|
|
1836
|
+
reason: "runtime_pressure",
|
|
1837
|
+
stage: failure.stage,
|
|
1838
|
+
errorCode: boundedContextErrorCode(failure.code),
|
|
1839
|
+
error: `Automatic context retirement failed at ${failure.stage}.`,
|
|
1840
|
+
committed: failure.committed,
|
|
1841
|
+
qualificationId,
|
|
1842
|
+
},
|
|
1843
|
+
}).catch(() => undefined);
|
|
1844
|
+
}
|
|
1845
|
+
if (error instanceof ContextManagerError && !error.fatal) {
|
|
1846
|
+
return undefined;
|
|
1847
|
+
}
|
|
1848
|
+
throw error;
|
|
1849
|
+
}
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
private async settleClosedTurnSkills(): Promise<void> {
|
|
1853
|
+
const unresolved = this.store.loadSkillActivations(["pending", "dispatched"]);
|
|
1854
|
+
if (unresolved.length === 0) {
|
|
1855
|
+
return;
|
|
1856
|
+
}
|
|
1857
|
+
const summary = await this.commitSkillSettlements({
|
|
1858
|
+
reason: "activation",
|
|
1859
|
+
unresolved,
|
|
1860
|
+
});
|
|
1861
|
+
await this.append({
|
|
1862
|
+
type: "skills.updated",
|
|
1863
|
+
sessionId: this.sessionId,
|
|
1864
|
+
data: {
|
|
1865
|
+
reason: "activation",
|
|
1866
|
+
activated: summary.activated,
|
|
1867
|
+
refreshed: summary.refreshed,
|
|
1868
|
+
deactivated: summary.deactivated,
|
|
1869
|
+
unavailable: summary.unavailable,
|
|
1870
|
+
revisionNumber: summary.revisionNumber,
|
|
1871
|
+
},
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
|
|
663
1875
|
private async appendTerminalEvent(
|
|
664
1876
|
turn: TurnIdentity,
|
|
665
1877
|
result: RunAgentResult,
|
|
@@ -702,10 +1914,15 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
702
1914
|
|
|
703
1915
|
this.state = "disposing";
|
|
704
1916
|
const errors: unknown[] = this.faultCause === undefined ? [] : [this.faultCause];
|
|
705
|
-
const
|
|
706
|
-
if (
|
|
1917
|
+
const activeAdmission = this.activeAdmission;
|
|
1918
|
+
if (activeAdmission !== undefined) {
|
|
1919
|
+
activeAdmission.controller.abort(new TurnCancelledError("session_dispose"));
|
|
1920
|
+
await activeAdmission.settled;
|
|
1921
|
+
}
|
|
1922
|
+
const activeContextRevision = this.activeContextRevision;
|
|
1923
|
+
if (activeContextRevision !== undefined) {
|
|
707
1924
|
try {
|
|
708
|
-
await
|
|
1925
|
+
await activeContextRevision;
|
|
709
1926
|
} catch {
|
|
710
1927
|
// The compactContext caller owns its primary error. Disposal still continues.
|
|
711
1928
|
}
|
|
@@ -794,10 +2011,8 @@ class DefaultRuntimeSession implements RuntimeSession {
|
|
|
794
2011
|
throw new Error("Initialization error collection unexpectedly returned.");
|
|
795
2012
|
}
|
|
796
2013
|
|
|
797
|
-
private stageTurn(
|
|
798
|
-
|
|
799
|
-
throw new Error("Cannot create an AgentTurn for an empty prompt.");
|
|
800
|
-
}
|
|
2014
|
+
private stageTurn(userMessage: UserMessage): TurnIdentity {
|
|
2015
|
+
validateUserMessage(userMessage);
|
|
801
2016
|
|
|
802
2017
|
const identity: TurnIdentity = {
|
|
803
2018
|
sessionId: this.sessionId,
|
|
@@ -1074,18 +2289,26 @@ function validateCreateInput(input: CreateRuntimeSessionInput): void {
|
|
|
1074
2289
|
if (!path.isAbsolute(input.workspaceRoot)) {
|
|
1075
2290
|
throw new Error("RuntimeSession workspaceRoot must be an absolute path.");
|
|
1076
2291
|
}
|
|
2292
|
+
if (
|
|
2293
|
+
input.skillCatalog !== undefined &&
|
|
2294
|
+
input.skillCatalog.workspaceRoot !== input.workspaceRoot
|
|
2295
|
+
) {
|
|
2296
|
+
throw new Error("RuntimeSession Agent Skill catalog belongs to another workspace.");
|
|
2297
|
+
}
|
|
1077
2298
|
if (input.modelName.trim() === "") {
|
|
1078
2299
|
throw new Error("RuntimeSession modelName must not be empty.");
|
|
1079
2300
|
}
|
|
1080
2301
|
requirePositiveNumber(input.maxIterations, "maxIterations");
|
|
1081
|
-
if (
|
|
2302
|
+
if (input.systemPrompt.trim() === "") {
|
|
1082
2303
|
throw new Error("RuntimeSession systemPrompt must not be empty.");
|
|
1083
2304
|
}
|
|
1084
2305
|
if (
|
|
1085
2306
|
typeof input.modelClient !== "object" ||
|
|
1086
2307
|
input.modelClient === null ||
|
|
1087
2308
|
typeof input.modelClient.prepare !== "function" ||
|
|
1088
|
-
typeof input.modelClient.request !== "function"
|
|
2309
|
+
typeof input.modelClient.request !== "function" ||
|
|
2310
|
+
typeof input.modelClient.messageProtocol !== "object" ||
|
|
2311
|
+
input.modelClient.messageProtocol === null
|
|
1089
2312
|
) {
|
|
1090
2313
|
throw new Error(
|
|
1091
2314
|
"RuntimeSession modelClient must implement prepare() and request().",
|
|
@@ -1094,12 +2317,47 @@ function validateCreateInput(input: CreateRuntimeSessionInput): void {
|
|
|
1094
2317
|
assertMatchingContextBudget(input.contextProfile, input.contextBudget);
|
|
1095
2318
|
}
|
|
1096
2319
|
|
|
2320
|
+
function assertPreparedMatchesSurface(
|
|
2321
|
+
prepared: ReturnType<ModelClient["prepare"]>,
|
|
2322
|
+
surface: StoredContextSurfaceV8,
|
|
2323
|
+
): void {
|
|
2324
|
+
if (
|
|
2325
|
+
prepared.requestConfigHash !== surface.requestConfigSha256 ||
|
|
2326
|
+
prepared.toolSchemaHash !== surface.toolSchemaSha256 ||
|
|
2327
|
+
prepared.requestMaxOutputTokens !== surface.requestMaxOutputTokens
|
|
2328
|
+
) {
|
|
2329
|
+
throw new Error("Prepared model request does not match its context surface.");
|
|
2330
|
+
}
|
|
2331
|
+
}
|
|
2332
|
+
|
|
2333
|
+
function elapsedMs(startedAt: number): number {
|
|
2334
|
+
return Math.round((performance.now() - startedAt) * 100) / 100;
|
|
2335
|
+
}
|
|
2336
|
+
|
|
1097
2337
|
function isNewSessionInput(
|
|
1098
2338
|
input: CreateRuntimeSessionInput,
|
|
1099
2339
|
): input is CreateNewRuntimeSessionInput {
|
|
1100
2340
|
return input.selection.mode === "new";
|
|
1101
2341
|
}
|
|
1102
2342
|
|
|
2343
|
+
function createActiveAdmission(controller: AbortController): ActiveAdmission {
|
|
2344
|
+
let resolve!: () => void;
|
|
2345
|
+
const settled = new Promise<void>((settle) => {
|
|
2346
|
+
resolve = settle;
|
|
2347
|
+
});
|
|
2348
|
+
let didSettle = false;
|
|
2349
|
+
return {
|
|
2350
|
+
controller,
|
|
2351
|
+
settled,
|
|
2352
|
+
settle: () => {
|
|
2353
|
+
if (!didSettle) {
|
|
2354
|
+
didSettle = true;
|
|
2355
|
+
resolve();
|
|
2356
|
+
}
|
|
2357
|
+
},
|
|
2358
|
+
};
|
|
2359
|
+
}
|
|
2360
|
+
|
|
1103
2361
|
function forwardExternalAbort(
|
|
1104
2362
|
externalSignal: AbortSignal,
|
|
1105
2363
|
internalController: AbortController,
|
|
@@ -1150,33 +2408,36 @@ function errorMessage(error: unknown): string {
|
|
|
1150
2408
|
|
|
1151
2409
|
function contextRevisionFinishedData(
|
|
1152
2410
|
result: ContextCompactionResult,
|
|
2411
|
+
reason: "manual" | "runtime_pressure" = "manual",
|
|
2412
|
+
qualificationId?: string,
|
|
1153
2413
|
): ContextRevisionFinishedData {
|
|
1154
2414
|
if (result.status === "unchanged") {
|
|
1155
2415
|
return {
|
|
1156
2416
|
strategy: "swap",
|
|
1157
|
-
reason
|
|
2417
|
+
reason,
|
|
1158
2418
|
policyVersion: "swap-only-v1",
|
|
1159
2419
|
outcome: result.outcome,
|
|
1160
2420
|
baseRevisionNumber: result.revisionNumber,
|
|
1161
2421
|
addedOverrideCount: 0,
|
|
1162
|
-
|
|
2422
|
+
activeOverrideCount: result.activeOverrideCount,
|
|
1163
2423
|
originalObservationBytes: 0,
|
|
1164
2424
|
projectedObservationBytes: 0,
|
|
1165
2425
|
rawTokensBefore: result.rawTokensBefore,
|
|
1166
2426
|
guardedTokensBefore: result.guardedTokensBefore,
|
|
1167
2427
|
targetTokens: result.targetTokens,
|
|
1168
2428
|
durationMs: result.durationMs,
|
|
2429
|
+
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
1169
2430
|
};
|
|
1170
2431
|
}
|
|
1171
2432
|
return {
|
|
1172
2433
|
strategy: "swap",
|
|
1173
|
-
reason
|
|
2434
|
+
reason,
|
|
1174
2435
|
policyVersion: "swap-only-v1",
|
|
1175
2436
|
outcome: result.outcome,
|
|
1176
2437
|
baseRevisionNumber: result.previousRevisionNumber,
|
|
1177
2438
|
revisionNumber: result.revisionNumber,
|
|
1178
2439
|
addedOverrideCount: result.addedOverrideCount,
|
|
1179
|
-
|
|
2440
|
+
activeOverrideCount: result.activeOverrideCount,
|
|
1180
2441
|
originalObservationBytes: result.originalObservationBytes,
|
|
1181
2442
|
projectedObservationBytes: result.projectedObservationBytes,
|
|
1182
2443
|
rawTokensBefore: result.rawTokensBefore,
|
|
@@ -1186,9 +2447,99 @@ function contextRevisionFinishedData(
|
|
|
1186
2447
|
targetTokens: result.targetTokens,
|
|
1187
2448
|
planHash: result.planHash,
|
|
1188
2449
|
durationMs: result.durationMs,
|
|
2450
|
+
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
1189
2451
|
};
|
|
1190
2452
|
}
|
|
1191
2453
|
|
|
2454
|
+
function contextRetirementFinishedData(
|
|
2455
|
+
result: ContextRetirementResult,
|
|
2456
|
+
reason: "manual" | "runtime_pressure" = "manual",
|
|
2457
|
+
qualificationId?: string,
|
|
2458
|
+
): ContextRevisionFinishedData {
|
|
2459
|
+
if (result.status === "unchanged") {
|
|
2460
|
+
return {
|
|
2461
|
+
strategy: "retire_prefix",
|
|
2462
|
+
reason,
|
|
2463
|
+
policyVersion: "recall-first-retirement-v1",
|
|
2464
|
+
outcome: result.outcome,
|
|
2465
|
+
baseRevisionNumber: result.revisionNumber,
|
|
2466
|
+
previousKeepFromOrdinal: result.keepFromOrdinal,
|
|
2467
|
+
keepFromOrdinal: result.keepFromOrdinal,
|
|
2468
|
+
retiredTurnCount: 0,
|
|
2469
|
+
retiredFrameCount: 0,
|
|
2470
|
+
retiredMessageCount: 0,
|
|
2471
|
+
activeOverrideCount: result.activeOverrideCount,
|
|
2472
|
+
guardedTokensBefore: result.guardedTokensBefore,
|
|
2473
|
+
targetTokens: result.targetTokens,
|
|
2474
|
+
planningDurationMs: result.planningDurationMs,
|
|
2475
|
+
durationMs: result.durationMs,
|
|
2476
|
+
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
2477
|
+
};
|
|
2478
|
+
}
|
|
2479
|
+
return {
|
|
2480
|
+
strategy: "retire_prefix",
|
|
2481
|
+
reason,
|
|
2482
|
+
policyVersion: "recall-first-retirement-v1",
|
|
2483
|
+
outcome: result.outcome,
|
|
2484
|
+
baseRevisionNumber: result.previousRevisionNumber,
|
|
2485
|
+
revisionNumber: result.revisionNumber,
|
|
2486
|
+
previousKeepFromOrdinal: result.previousKeepFromOrdinal,
|
|
2487
|
+
keepFromOrdinal: result.keepFromOrdinal,
|
|
2488
|
+
retiredTurnCount: result.retiredTurnCount,
|
|
2489
|
+
retiredFrameCount: result.retiredFrameCount,
|
|
2490
|
+
retiredMessageCount: result.retiredMessageCount,
|
|
2491
|
+
activeOverrideCount: result.activeOverrideCount,
|
|
2492
|
+
rawTokensBefore: result.rawTokensBefore,
|
|
2493
|
+
rawTokensAfter: result.rawTokensAfter,
|
|
2494
|
+
guardedTokensBefore: result.guardedTokensBefore,
|
|
2495
|
+
guardedTokensAfter: result.guardedTokensAfter,
|
|
2496
|
+
targetTokens: result.targetTokens,
|
|
2497
|
+
planHash: result.planHash,
|
|
2498
|
+
planningDurationMs: result.planningDurationMs,
|
|
2499
|
+
validationDurationMs: result.validationDurationMs,
|
|
2500
|
+
transactionDurationMs: result.transactionDurationMs,
|
|
2501
|
+
activationDurationMs: result.activationDurationMs,
|
|
2502
|
+
durationMs: result.durationMs,
|
|
2503
|
+
...(qualificationId === undefined ? {} : { qualificationId }),
|
|
2504
|
+
};
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
function requireAutomationQualificationId(decision: ContextAutomationDecision): string {
|
|
2508
|
+
if (
|
|
2509
|
+
!decision.automaticSwapOnly ||
|
|
2510
|
+
(decision.reason !== "qualified" && decision.reason !== "swap_only_qualified") ||
|
|
2511
|
+
decision.qualificationId === undefined
|
|
2512
|
+
) {
|
|
2513
|
+
throw new Error("Automatic context maintenance has no qualification identity.");
|
|
2514
|
+
}
|
|
2515
|
+
return decision.qualificationId;
|
|
2516
|
+
}
|
|
2517
|
+
|
|
2518
|
+
function automaticSwapNeedsRetirement(result: ContextCompactionResult): boolean {
|
|
2519
|
+
return (
|
|
2520
|
+
result.outcome === "no_eligible_candidates" ||
|
|
2521
|
+
result.outcome === "insufficient_candidates"
|
|
2522
|
+
);
|
|
2523
|
+
}
|
|
2524
|
+
|
|
2525
|
+
function automaticContextFailure(
|
|
2526
|
+
error: unknown,
|
|
2527
|
+
strategy: "compaction" | "retirement",
|
|
2528
|
+
): ContextManagerError {
|
|
2529
|
+
return error instanceof ContextManagerError
|
|
2530
|
+
? error
|
|
2531
|
+
: new ContextManagerError(
|
|
2532
|
+
"activate",
|
|
2533
|
+
error instanceof Error
|
|
2534
|
+
? error.name
|
|
2535
|
+
: `AUTOMATIC_CONTEXT_${strategy.toUpperCase()}_FAILED`,
|
|
2536
|
+
true,
|
|
2537
|
+
false,
|
|
2538
|
+
`Automatic context ${strategy} failed.`,
|
|
2539
|
+
{ cause: error },
|
|
2540
|
+
);
|
|
2541
|
+
}
|
|
2542
|
+
|
|
1192
2543
|
function boundedContextErrorCode(code: string): string {
|
|
1193
2544
|
return /^[A-Za-z0-9_]+$/.test(code) && code.length <= 80
|
|
1194
2545
|
? code
|
|
@@ -1201,6 +2552,10 @@ function requirePositiveNumber(value: number, name: string): void {
|
|
|
1201
2552
|
}
|
|
1202
2553
|
}
|
|
1203
2554
|
|
|
2555
|
+
function compareText(left: string, right: string): number {
|
|
2556
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
2557
|
+
}
|
|
2558
|
+
|
|
1204
2559
|
function isCanonicalRuntimeFault(error: unknown): boolean {
|
|
1205
2560
|
return (
|
|
1206
2561
|
error instanceof ContextProtocolError ||
|