tinker-agent 2.8.0 → 2.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/CHANGELOG.md +79 -1
  2. package/README.md +81 -11
  3. package/package.json +5 -3
  4. package/src/agent/runtime-context-capabilities.ts +19 -0
  5. package/src/agent/runtime-context-events.ts +127 -0
  6. package/src/agent/runtime-context-maintenance.ts +780 -0
  7. package/src/agent/runtime-hosted-session.ts +443 -0
  8. package/src/agent/runtime-interactions.ts +291 -0
  9. package/src/agent/runtime-prompt-scheduler.ts +182 -0
  10. package/src/agent/runtime-session-contracts.ts +317 -0
  11. package/src/agent/runtime-session.ts +250 -2130
  12. package/src/agent/runtime-skills.ts +544 -0
  13. package/src/cli/command-line.ts +26 -2
  14. package/src/cli/connect-runner.tsx +26 -0
  15. package/src/cli/main.ts +26 -0
  16. package/src/cli/output.ts +1 -1
  17. package/src/cli/public-cli-contract.ts +18 -0
  18. package/src/cli/public-config-contract.ts +1 -1
  19. package/src/cli/runner-dependencies.ts +6 -5
  20. package/src/cli/serve-runner.ts +45 -0
  21. package/src/cli/serve-runtime.ts +100 -0
  22. package/src/context/context-automation-policy.ts +12 -118
  23. package/src/context/context-swap-renderer.ts +14 -0
  24. package/src/events/types.ts +12 -0
  25. package/src/memory/memory-get-tool.ts +1 -1
  26. package/src/observation/observation-builder.ts +128 -48
  27. package/src/remote/client.ts +350 -0
  28. package/src/remote/config.ts +95 -0
  29. package/src/remote/http-server.ts +240 -0
  30. package/src/remote/protocol.ts +228 -0
  31. package/src/remote/service-store.ts +175 -0
  32. package/src/remote/service.ts +219 -0
  33. package/src/remote/sync-hub.ts +95 -0
  34. package/src/session/remote-history-reader.ts +143 -0
  35. package/src/session/resume-projection.ts +47 -21
  36. package/src/session/session-history-access.ts +238 -0
  37. package/src/session/session-store-context-readers.ts +183 -0
  38. package/src/session/session-store-ledger-writer.ts +315 -0
  39. package/src/session/session-store-record-writer.ts +318 -0
  40. package/src/session/session-store-recovery.ts +225 -0
  41. package/src/session/session-store-revisions.ts +1004 -0
  42. package/src/session/session-store-sql.ts +40 -0
  43. package/src/session/session-store-validation.ts +657 -0
  44. package/src/session/session-store.ts +756 -3186
  45. package/src/tools/bash-task.ts +46 -18
  46. package/src/tools/bash.ts +44 -2
  47. package/src/tools/glob.ts +107 -19
  48. package/src/tools/grep-output.ts +130 -0
  49. package/src/tools/grep-pagination.ts +73 -0
  50. package/src/tools/grep-path.ts +11 -0
  51. package/src/tools/grep-snippets.ts +111 -0
  52. package/src/tools/grep.ts +139 -154
  53. package/src/tools/read.ts +0 -9
  54. package/src/tools/recall.ts +106 -50
  55. package/src/tools/registry.ts +4 -6
  56. package/src/tools/ripgrep.ts +19 -26
  57. package/src/tools/shell-process.ts +30 -4
  58. package/src/tools/task-output-range.ts +146 -0
  59. package/src/tools/task-output-tool.ts +35 -5
  60. package/src/tools/task-output.ts +35 -0
  61. package/src/tools/task-stop.ts +2 -1
  62. package/src/tools/task-tool-args.ts +34 -0
  63. package/src/tools/terminal-screen.ts +11 -2
  64. package/src/tools/types.ts +39 -2
  65. package/src/tui/event-store.ts +23 -5
  66. package/src/tui/remote-app.tsx +210 -0
@@ -0,0 +1,1004 @@
1
+ import type { Database } from "bun:sqlite";
2
+ import {
3
+ activeOverrideManifestHash,
4
+ canonicalSequenceHash,
5
+ renderedMessageHash,
6
+ } from "../context/compiled-context-hash";
7
+ import type {
8
+ StoredContextRevisionV8,
9
+ SwapOverride,
10
+ } from "../context/context-revision";
11
+ import { ContextRevisionCompiler } from "../context/context-revision-compiler";
12
+ import {
13
+ contextSurfaceChangeManifestHash,
14
+ contextSurfaceChanges,
15
+ validateStoredContextSurface,
16
+ } from "../context/context-surface";
17
+ import { SWAP_OBSERVATION_FORMAT } from "../context/context-swap-renderer";
18
+ import { type ProtocolContextView } from "../context/protocol-frame";
19
+ import type { SessionId } from "../ids/runtime-id";
20
+ import { stableJsonStringify } from "../model/model-request-preflight";
21
+ import {
22
+ renderSkillActivationReceipt,
23
+ SKILL_ACTIVATION_RECEIPT_FORMAT,
24
+ SKILL_POLICY_VERSION,
25
+ } from "../skills/skill-context";
26
+ import { sessionWriteError } from "./session-errors";
27
+ import type { SessionStore } from "./session-store";
28
+ import {
29
+ loadMeasuredContextState,
30
+ readRetirementBoundaries,
31
+ requireActiveRevisionId,
32
+ } from "./session-store-context-readers";
33
+ import {
34
+ skillActivationManifestSha256,
35
+ type CommitPrefixRetirementRevisionInput,
36
+ type CommitPrefixRetirementRevisionOptions,
37
+ type CommitSkillsUpdateInput,
38
+ type CommitSkillsUpdateOptions,
39
+ type CommitSurfaceRefreshInput,
40
+ type CommitSurfaceRefreshOptions,
41
+ type CommitSwapRevisionInput,
42
+ type CommitSwapRevisionOptions,
43
+ } from "./session-store-contracts";
44
+ import { decodeStoredSwapOverride } from "./session-store-record-codecs";
45
+ import { insertContextSurface } from "./session-store-record-writer";
46
+ import { requireItem, requireSingleChange, runTransaction } from "./session-store-sql";
47
+
48
+ type RevisionStore = Pick<
49
+ SessionStore,
50
+ | "loadContextSnapshot"
51
+ | "assertContextRevisionBoundary"
52
+ | "assertContextRevisionIdle"
53
+ | "readMeta"
54
+ | "loadSkillActivations"
55
+ >;
56
+ /** Each operation owns its complete validation-and-write transaction. */
57
+ export class SessionStoreRevisions {
58
+ private readonly revisionCompiler = new ContextRevisionCompiler();
59
+ constructor(
60
+ private readonly database: Database,
61
+ private readonly sessionId: SessionId,
62
+ private readonly clock: () => string,
63
+ private readonly store: RevisionStore,
64
+ private readonly requireOpen: () => void,
65
+ private readonly validateAddedOverrides: (
66
+ overrides: readonly SwapOverride[],
67
+ canonical: ProtocolContextView,
68
+ ) => void,
69
+ ) {}
70
+
71
+ commitSwapRevision(
72
+ input: CommitSwapRevisionInput,
73
+ options: CommitSwapRevisionOptions = {},
74
+ ): Extract<StoredContextRevisionV8, { kind: "swap_only" }> {
75
+ this.requireOpen();
76
+ assertCommitSwapRevisionInput(input);
77
+ const now = this.clock();
78
+ try {
79
+ return runTransaction(this.database, () => {
80
+ const snapshot = this.store.loadContextSnapshot();
81
+ const baseRevision = snapshot.revision;
82
+ if (
83
+ baseRevision.revisionId !== input.expectedBaseRevisionId ||
84
+ baseRevision.revisionNumber !== input.expectedBaseRevisionNumber ||
85
+ snapshot.canonical.messages.length !==
86
+ input.expectedCanonicalThroughOrdinal ||
87
+ baseRevision.activeOverrideManifestSha256 !==
88
+ input.expectedBaseActiveOverrideManifestSha256
89
+ ) {
90
+ throw new Error("Context revision commit base is stale.");
91
+ }
92
+ this.store.assertContextRevisionBoundary(input.activeTurnId);
93
+
94
+ const active = this.revisionCompiler.compileActive(snapshot);
95
+ const candidateOverrides = [
96
+ ...snapshot.activeOverrides,
97
+ ...input.addedOverrides,
98
+ ];
99
+ if (
100
+ new Set(candidateOverrides.map((override) => override.messageId)).size !==
101
+ candidateOverrides.length ||
102
+ activeOverrideManifestHash(candidateOverrides) !==
103
+ input.nextActiveOverrideManifestSha256
104
+ ) {
105
+ throw new Error("Candidate override manifest is invalid.");
106
+ }
107
+ const candidate = this.revisionCompiler.compileProspective({
108
+ active,
109
+ canonical: snapshot.canonical,
110
+ activeOverrides: snapshot.activeOverrides,
111
+ addedOverrides: input.addedOverrides,
112
+ activeSurface: snapshot.surface,
113
+ });
114
+ if (
115
+ canonicalSequenceHash(
116
+ snapshot.canonical,
117
+ input.expectedCanonicalThroughOrdinal,
118
+ ) !== input.canonicalSequenceSha256 ||
119
+ renderedMessageHash(
120
+ candidate.entries,
121
+ input.expectedCanonicalThroughOrdinal,
122
+ ) !== input.renderedMessageSha256
123
+ ) {
124
+ throw new Error("Candidate context revision prefix hash is invalid.");
125
+ }
126
+ this.validateAddedOverrides(input.addedOverrides, snapshot.canonical);
127
+
128
+ const revisionNumber = baseRevision.revisionNumber + 1;
129
+ const activeOverrideCount =
130
+ baseRevision.activeOverrideCount + input.addedOverrides.length;
131
+ options.faultInjector?.("before_revision_insert");
132
+ this.database
133
+ .query(
134
+ `INSERT INTO context_revisions (
135
+ revision_id, session_id, revision_number, parent_revision_id, kind,
136
+ surface_id, surface_sha256, keep_from_ordinal,
137
+ source_through_ordinal, added_override_count,
138
+ active_override_count, active_override_manifest_sha256,
139
+ canonical_sequence_sha256, rendered_message_sha256, policy_version,
140
+ renderer_format, plan_sha256, change_manifest_sha256, created_at
141
+ ) VALUES (?, ?, ?, ?, 'swap_only', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?)`,
142
+ )
143
+ .run(
144
+ input.revisionId,
145
+ this.sessionId,
146
+ revisionNumber,
147
+ baseRevision.revisionId,
148
+ baseRevision.surfaceId,
149
+ baseRevision.surfaceSha256,
150
+ baseRevision.keepFromOrdinal,
151
+ input.expectedCanonicalThroughOrdinal,
152
+ input.addedOverrides.length,
153
+ activeOverrideCount,
154
+ input.nextActiveOverrideManifestSha256,
155
+ input.canonicalSequenceSha256,
156
+ input.renderedMessageSha256,
157
+ input.policyVersion,
158
+ input.rendererFormat,
159
+ input.planHash,
160
+ now,
161
+ );
162
+ options.faultInjector?.("after_revision_insert");
163
+
164
+ for (let index = 0; index < input.addedOverrides.length; index += 1) {
165
+ const override = requireItem(
166
+ input.addedOverrides,
167
+ index,
168
+ "added context override",
169
+ );
170
+ this.database
171
+ .query(
172
+ `INSERT INTO context_overrides (
173
+ introduced_revision_id, session_id, message_id, frame_id, ordinal,
174
+ representation, renderer_format, source, original_content_sha256,
175
+ rendered_content, rendered_content_sha256, original_bytes,
176
+ rendered_bytes, byte_savings, created_at
177
+ ) VALUES (?, ?, ?, ?, ?, 'swapped', ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
178
+ )
179
+ .run(
180
+ input.revisionId,
181
+ this.sessionId,
182
+ override.messageId,
183
+ override.frameId,
184
+ override.ordinal,
185
+ input.rendererFormat,
186
+ override.source,
187
+ override.originalContentSha256,
188
+ override.renderedContent,
189
+ override.renderedContentSha256,
190
+ override.originalBytes,
191
+ override.renderedBytes,
192
+ override.byteSavings,
193
+ now,
194
+ );
195
+ if (index === 0) {
196
+ options.faultInjector?.("after_first_override_insert");
197
+ }
198
+ }
199
+ options.faultInjector?.("after_overrides_insert");
200
+
201
+ const storedCandidateOverrides = this.database
202
+ .query(
203
+ `SELECT co.* FROM context_overrides co
204
+ JOIN context_revisions cr
205
+ ON cr.revision_id = co.introduced_revision_id
206
+ WHERE cr.revision_number <= ? AND co.ordinal >= ?
207
+ ORDER BY co.ordinal`,
208
+ )
209
+ .all(revisionNumber, baseRevision.keepFromOrdinal)
210
+ .map(decodeStoredSwapOverride);
211
+ if (
212
+ storedCandidateOverrides.length !== activeOverrideCount ||
213
+ activeOverrideManifestHash(storedCandidateOverrides) !==
214
+ input.nextActiveOverrideManifestSha256
215
+ ) {
216
+ throw new Error("Stored candidate override readback is invalid.");
217
+ }
218
+
219
+ this.database.query("DELETE FROM context_measurement_state").run();
220
+ options.faultInjector?.("after_measurement_delete");
221
+ const switched = this.database
222
+ .query(
223
+ `UPDATE session_meta
224
+ SET active_revision_id = ?, updated_at = ?
225
+ WHERE singleton = 1 AND active_revision_id = ?`,
226
+ )
227
+ .run(input.revisionId, now, baseRevision.revisionId);
228
+ requireSingleChange(
229
+ this.database,
230
+ switched.changes,
231
+ "activate context revision",
232
+ );
233
+ options.faultInjector?.("after_active_update");
234
+
235
+ const readback = this.store.loadContextSnapshot();
236
+ if (
237
+ readback.revision.kind !== "swap_only" ||
238
+ readback.revision.revisionId !== input.revisionId ||
239
+ loadMeasuredContextState(this.database, this.sessionId) !== undefined
240
+ ) {
241
+ throw new Error("Committed context revision readback failed.");
242
+ }
243
+ return readback.revision;
244
+ });
245
+ } catch (error) {
246
+ if (
247
+ requireActiveRevisionId(this.store.readMeta()) !== input.expectedBaseRevisionId
248
+ ) {
249
+ throw new Error("Failed context revision transaction changed active state.", {
250
+ cause: error,
251
+ });
252
+ }
253
+ throw sessionWriteError("commit_context_revision", this.sessionId, error);
254
+ }
255
+ }
256
+
257
+ commitPrefixRetirementRevision(
258
+ input: CommitPrefixRetirementRevisionInput,
259
+ options: CommitPrefixRetirementRevisionOptions = {},
260
+ ): Extract<StoredContextRevisionV8, { kind: "prefix_retirement" }> {
261
+ this.requireOpen();
262
+ assertCommitPrefixRetirementRevisionInput(input);
263
+ const now = this.clock();
264
+ try {
265
+ return runTransaction(this.database, () => {
266
+ const snapshot = this.store.loadContextSnapshot();
267
+ const baseRevision = snapshot.revision;
268
+ if (
269
+ baseRevision.revisionId !== input.expectedBaseRevisionId ||
270
+ baseRevision.revisionNumber !== input.expectedBaseRevisionNumber ||
271
+ baseRevision.keepFromOrdinal !== input.expectedBaseKeepFromOrdinal ||
272
+ snapshot.canonical.messages.length !==
273
+ input.expectedCanonicalThroughOrdinal ||
274
+ snapshot.surface.surfaceSha256 !== input.expectedSurfaceSha256 ||
275
+ baseRevision.activeOverrideManifestSha256 !==
276
+ input.expectedBaseActiveOverrideManifestSha256
277
+ ) {
278
+ throw new Error("Prefix retirement commit base is stale.");
279
+ }
280
+ this.store.assertContextRevisionBoundary(input.activeTurnId);
281
+ const retirementBoundaries = readRetirementBoundaries(
282
+ this.database,
283
+ snapshot.canonical,
284
+ input.activeTurnId,
285
+ );
286
+ const closedTurns = retirementBoundaries.closedTurns;
287
+ const activeTurns = closedTurns.filter(
288
+ (turn) => turn.firstOrdinal >= baseRevision.keepFromOrdinal,
289
+ );
290
+ const nextBoundary = [
291
+ ...activeTurns,
292
+ ...(retirementBoundaries.activeTurn === undefined
293
+ ? []
294
+ : [retirementBoundaries.activeTurn]),
295
+ ].find((turn) => turn.firstOrdinal === input.nextKeepFromOrdinal);
296
+ const retiredTurns = activeTurns.filter(
297
+ (turn) => turn.lastOrdinal < input.nextKeepFromOrdinal,
298
+ );
299
+ if (
300
+ nextBoundary === undefined ||
301
+ input.nextKeepFromOrdinal <= baseRevision.keepFromOrdinal ||
302
+ retiredTurns.length !== input.retiredTurnCount ||
303
+ retiredTurns.reduce((total, turn) => total + turn.frameCount, 0) !==
304
+ input.retiredFrameCount ||
305
+ retiredTurns.reduce((total, turn) => total + turn.messageCount, 0) !==
306
+ input.retiredMessageCount
307
+ ) {
308
+ throw new Error("Prefix retirement turn boundary is invalid.");
309
+ }
310
+
311
+ const active = this.revisionCompiler.compileActive(snapshot);
312
+ const nextActiveOverrides = snapshot.activeOverrides.filter(
313
+ (override) => override.ordinal >= input.nextKeepFromOrdinal,
314
+ );
315
+ const candidate = this.revisionCompiler.compileProspective({
316
+ active,
317
+ canonical: snapshot.canonical,
318
+ activeOverrides: snapshot.activeOverrides,
319
+ addedOverrides: [],
320
+ activeSurface: snapshot.surface,
321
+ keepFromOrdinal: input.nextKeepFromOrdinal,
322
+ });
323
+ if (
324
+ input.retiredThroughOrdinal !== input.nextKeepFromOrdinal - 1 ||
325
+ nextActiveOverrides.length !== input.nextActiveOverrideCount ||
326
+ activeOverrideManifestHash(nextActiveOverrides) !==
327
+ input.nextActiveOverrideManifestSha256 ||
328
+ canonicalSequenceHash(
329
+ snapshot.canonical,
330
+ input.expectedCanonicalThroughOrdinal,
331
+ ) !== input.canonicalSequenceSha256 ||
332
+ renderedMessageHash(
333
+ candidate.entries,
334
+ input.expectedCanonicalThroughOrdinal,
335
+ ) !== input.renderedMessageSha256
336
+ ) {
337
+ throw new Error("Prefix retirement candidate is invalid.");
338
+ }
339
+
340
+ const revisionNumber = baseRevision.revisionNumber + 1;
341
+ options.faultInjector?.("before_revision_insert");
342
+ this.database
343
+ .query(
344
+ `INSERT INTO context_revisions (
345
+ revision_id, session_id, revision_number, parent_revision_id, kind,
346
+ surface_id, surface_sha256, keep_from_ordinal,
347
+ source_through_ordinal, added_override_count, active_override_count,
348
+ active_override_manifest_sha256, canonical_sequence_sha256,
349
+ rendered_message_sha256, policy_version, renderer_format,
350
+ plan_sha256, change_manifest_sha256, retired_through_ordinal,
351
+ retired_turn_count, retired_frame_count, retired_message_count,
352
+ created_at
353
+ ) VALUES (?, ?, ?, ?, 'prefix_retirement', ?, ?, ?, ?, 0, ?, ?, ?, ?, ?, NULL, ?, NULL, ?, ?, ?, ?, ?)`,
354
+ )
355
+ .run(
356
+ input.revisionId,
357
+ this.sessionId,
358
+ revisionNumber,
359
+ baseRevision.revisionId,
360
+ baseRevision.surfaceId,
361
+ baseRevision.surfaceSha256,
362
+ input.nextKeepFromOrdinal,
363
+ input.expectedCanonicalThroughOrdinal,
364
+ input.nextActiveOverrideCount,
365
+ input.nextActiveOverrideManifestSha256,
366
+ input.canonicalSequenceSha256,
367
+ input.renderedMessageSha256,
368
+ input.policyVersion,
369
+ input.planHash,
370
+ input.retiredThroughOrdinal,
371
+ input.retiredTurnCount,
372
+ input.retiredFrameCount,
373
+ input.retiredMessageCount,
374
+ now,
375
+ );
376
+ options.faultInjector?.("after_revision_insert");
377
+
378
+ const storedActiveOverrides = this.database
379
+ .query(
380
+ `SELECT co.* FROM context_overrides co
381
+ JOIN context_revisions introduced
382
+ ON introduced.revision_id = co.introduced_revision_id
383
+ WHERE introduced.revision_number <= ? AND co.ordinal >= ?
384
+ ORDER BY co.ordinal`,
385
+ )
386
+ .all(revisionNumber, input.nextKeepFromOrdinal)
387
+ .map(decodeStoredSwapOverride);
388
+ if (
389
+ storedActiveOverrides.length !== input.nextActiveOverrideCount ||
390
+ activeOverrideManifestHash(storedActiveOverrides) !==
391
+ input.nextActiveOverrideManifestSha256
392
+ ) {
393
+ throw new Error("Prefix retirement override readback is invalid.");
394
+ }
395
+ options.faultInjector?.("after_override_readback");
396
+
397
+ this.database.query("DELETE FROM context_measurement_state").run();
398
+ options.faultInjector?.("after_measurement_delete");
399
+ const switched = this.database
400
+ .query(
401
+ `UPDATE session_meta SET active_revision_id = ?, updated_at = ?
402
+ WHERE singleton = 1 AND active_revision_id = ?`,
403
+ )
404
+ .run(input.revisionId, now, baseRevision.revisionId);
405
+ requireSingleChange(
406
+ this.database,
407
+ switched.changes,
408
+ "activate prefix retirement revision",
409
+ );
410
+ options.faultInjector?.("after_active_update");
411
+
412
+ const readback = this.store.loadContextSnapshot();
413
+ if (
414
+ readback.revision.kind !== "prefix_retirement" ||
415
+ readback.revision.revisionId !== input.revisionId ||
416
+ readback.revision.keepFromOrdinal !== input.nextKeepFromOrdinal ||
417
+ loadMeasuredContextState(this.database, this.sessionId) !== undefined
418
+ ) {
419
+ throw new Error("Committed prefix retirement readback failed.");
420
+ }
421
+ options.faultInjector?.("after_snapshot_readback");
422
+ return readback.revision;
423
+ });
424
+ } catch (error) {
425
+ if (
426
+ requireActiveRevisionId(this.store.readMeta()) !== input.expectedBaseRevisionId
427
+ ) {
428
+ throw new Error("Failed prefix retirement changed active state.", {
429
+ cause: error,
430
+ });
431
+ }
432
+ throw sessionWriteError("commit_prefix_retirement", this.sessionId, error);
433
+ }
434
+ }
435
+
436
+ commitSurfaceRefresh(
437
+ input: CommitSurfaceRefreshInput,
438
+ options: CommitSurfaceRefreshOptions = {},
439
+ ): Extract<StoredContextRevisionV8, { kind: "surface_refresh" }> {
440
+ this.requireOpen();
441
+ assertCommitSurfaceRefreshInput(input);
442
+ const now = this.clock();
443
+ try {
444
+ return runTransaction(this.database, () => {
445
+ const snapshot = this.store.loadContextSnapshot();
446
+ const baseRevision = snapshot.revision;
447
+ if (
448
+ baseRevision.revisionId !== input.expectedBaseRevisionId ||
449
+ baseRevision.revisionNumber !== input.expectedBaseRevisionNumber ||
450
+ snapshot.canonical.messages.length !==
451
+ input.expectedCanonicalThroughOrdinal ||
452
+ baseRevision.activeOverrideManifestSha256 !==
453
+ input.expectedBaseActiveOverrideManifestSha256
454
+ ) {
455
+ throw new Error("Context surface refresh base is stale.");
456
+ }
457
+ this.store.assertContextRevisionIdle();
458
+ validateStoredContextSurface(input.surface);
459
+ if (
460
+ input.surface.sessionId !== this.sessionId ||
461
+ input.surface.surfaceId === snapshot.surface.surfaceId ||
462
+ input.surface.surfaceSha256 === snapshot.surface.surfaceSha256
463
+ ) {
464
+ throw new Error("Context surface refresh does not introduce a new surface.");
465
+ }
466
+ const actualChanges = contextSurfaceChanges(snapshot.surface, input.surface);
467
+ if (
468
+ stableJsonStringify(actualChanges) !== stableJsonStringify(input.changes) ||
469
+ contextSurfaceChangeManifestHash(actualChanges) !==
470
+ input.changeManifestSha256 ||
471
+ !Object.values(actualChanges).some(Boolean)
472
+ ) {
473
+ throw new Error("Context surface refresh change manifest is invalid.");
474
+ }
475
+
476
+ const active = this.revisionCompiler.compileActive(snapshot);
477
+ const candidate = this.revisionCompiler.compileProspective({
478
+ active,
479
+ canonical: snapshot.canonical,
480
+ activeOverrides: snapshot.activeOverrides,
481
+ addedOverrides: [],
482
+ activeSurface: snapshot.surface,
483
+ surface: input.surface,
484
+ });
485
+ if (
486
+ canonicalSequenceHash(
487
+ snapshot.canonical,
488
+ input.expectedCanonicalThroughOrdinal,
489
+ ) !== input.canonicalSequenceSha256 ||
490
+ renderedMessageHash(
491
+ candidate.entries,
492
+ input.expectedCanonicalThroughOrdinal,
493
+ ) !== input.renderedMessageSha256
494
+ ) {
495
+ throw new Error("Candidate context surface prefix hash is invalid.");
496
+ }
497
+
498
+ const revisionNumber = baseRevision.revisionNumber + 1;
499
+ options.faultInjector?.("before_surface_insert");
500
+ insertContextSurface(this.database, input.surface);
501
+ options.faultInjector?.("after_surface_insert");
502
+ this.database
503
+ .query(
504
+ `INSERT INTO context_revisions (
505
+ revision_id, session_id, revision_number, parent_revision_id, kind,
506
+ surface_id, surface_sha256, keep_from_ordinal,
507
+ source_through_ordinal, added_override_count, active_override_count,
508
+ active_override_manifest_sha256, canonical_sequence_sha256,
509
+ rendered_message_sha256, policy_version, renderer_format,
510
+ plan_sha256, change_manifest_sha256, created_at
511
+ ) VALUES (?, ?, ?, ?, 'surface_refresh', ?, ?, ?, ?, 0, ?, ?, ?, ?, NULL, NULL, NULL, ?, ?)`,
512
+ )
513
+ .run(
514
+ input.revisionId,
515
+ this.sessionId,
516
+ revisionNumber,
517
+ baseRevision.revisionId,
518
+ input.surface.surfaceId,
519
+ input.surface.surfaceSha256,
520
+ baseRevision.keepFromOrdinal,
521
+ input.expectedCanonicalThroughOrdinal,
522
+ baseRevision.activeOverrideCount,
523
+ baseRevision.activeOverrideManifestSha256,
524
+ input.canonicalSequenceSha256,
525
+ input.renderedMessageSha256,
526
+ input.changeManifestSha256,
527
+ now,
528
+ );
529
+ options.faultInjector?.("after_revision_insert");
530
+
531
+ this.database.query("DELETE FROM context_measurement_state").run();
532
+ options.faultInjector?.("after_measurement_delete");
533
+ const switched = this.database
534
+ .query(
535
+ `UPDATE session_meta SET active_revision_id = ?, updated_at = ?
536
+ WHERE singleton = 1 AND active_revision_id = ?`,
537
+ )
538
+ .run(input.revisionId, now, baseRevision.revisionId);
539
+ requireSingleChange(
540
+ this.database,
541
+ switched.changes,
542
+ "activate context surface revision",
543
+ );
544
+ options.faultInjector?.("after_active_update");
545
+
546
+ const readback = this.store.loadContextSnapshot();
547
+ if (
548
+ readback.revision.kind !== "surface_refresh" ||
549
+ readback.revision.revisionId !== input.revisionId ||
550
+ readback.surface.surfaceId !== input.surface.surfaceId ||
551
+ loadMeasuredContextState(this.database, this.sessionId) !== undefined
552
+ ) {
553
+ throw new Error("Committed context surface revision readback failed.");
554
+ }
555
+ return readback.revision;
556
+ });
557
+ } catch (error) {
558
+ if (
559
+ requireActiveRevisionId(this.store.readMeta()) !== input.expectedBaseRevisionId
560
+ ) {
561
+ throw new Error("Failed context surface transaction changed active state.", {
562
+ cause: error,
563
+ });
564
+ }
565
+ throw sessionWriteError("commit_context_surface", this.sessionId, error);
566
+ }
567
+ }
568
+
569
+ commitSkillsUpdate(
570
+ input: CommitSkillsUpdateInput,
571
+ options: CommitSkillsUpdateOptions = {},
572
+ ): Extract<StoredContextRevisionV8, { kind: "skills_update" }> {
573
+ this.requireOpen();
574
+ assertCommitSkillsUpdateInput(input);
575
+ const now = this.clock();
576
+ try {
577
+ return runTransaction(this.database, () => {
578
+ const snapshot = this.store.loadContextSnapshot();
579
+ const baseRevision = snapshot.revision;
580
+ if (
581
+ baseRevision.revisionId !== input.expectedBaseRevisionId ||
582
+ baseRevision.revisionNumber !== input.expectedBaseRevisionNumber ||
583
+ snapshot.canonical.messages.length !==
584
+ input.expectedCanonicalThroughOrdinal ||
585
+ baseRevision.activeOverrideManifestSha256 !==
586
+ input.expectedBaseActiveOverrideManifestSha256
587
+ ) {
588
+ throw new Error("Agent Skills update base is stale.");
589
+ }
590
+ this.store.assertContextRevisionIdle();
591
+ validateStoredContextSurface(input.surface);
592
+
593
+ const surfaceChanged =
594
+ input.surface.surfaceSha256 !== snapshot.surface.surfaceSha256;
595
+ const actualChanges = contextSurfaceChanges(snapshot.surface, input.surface);
596
+ if (
597
+ input.surface.sessionId !== this.sessionId ||
598
+ stableJsonStringify(actualChanges) !== stableJsonStringify(input.changes) ||
599
+ contextSurfaceChangeManifestHash(actualChanges) !==
600
+ input.changeManifestSha256 ||
601
+ (surfaceChanged && input.surface.surfaceId === snapshot.surface.surfaceId) ||
602
+ (!surfaceChanged && input.surface.surfaceId !== snapshot.surface.surfaceId) ||
603
+ (!surfaceChanged && Object.values(actualChanges).some(Boolean))
604
+ ) {
605
+ throw new Error("Agent Skills surface change manifest is invalid.");
606
+ }
607
+
608
+ const unresolved = new Map(
609
+ this.store
610
+ .loadSkillActivations(["pending", "dispatched"])
611
+ .map((entry) => [entry.activationMessageId, entry]),
612
+ );
613
+ if (
614
+ input.settlements.length !== input.addedOverrides.length ||
615
+ input.settlements.length !== unresolved.size ||
616
+ new Set(input.settlements.map((entry) => entry.activationMessageId)).size !==
617
+ input.settlements.length ||
618
+ skillActivationManifestSha256(input.settlements) !==
619
+ input.activationManifestSha256
620
+ ) {
621
+ throw new Error("Agent Skills settlement manifest is invalid.");
622
+ }
623
+ const canonicalMessages = new Map(
624
+ snapshot.canonical.messages.map((message) => [message.messageId, message]),
625
+ );
626
+ const providedOverrides = new Map(
627
+ input.addedOverrides.map((override) => [override.messageId, override]),
628
+ );
629
+ for (const settlement of input.settlements) {
630
+ const activation = unresolved.get(settlement.activationMessageId);
631
+ const message = canonicalMessages.get(settlement.activationMessageId);
632
+ const override = providedOverrides.get(settlement.activationMessageId);
633
+ if (
634
+ activation === undefined ||
635
+ message?.role !== "tool" ||
636
+ message.name !== "Skill" ||
637
+ override === undefined ||
638
+ settlement.name !== activation.name ||
639
+ (settlement.state === "promoted" &&
640
+ (activation.state !== "dispatched" ||
641
+ settlement.rejectionReason !== undefined)) ||
642
+ (settlement.state === "rejected" &&
643
+ (settlement.rejectionReason === undefined ||
644
+ settlement.rejectionReason.trim() === "" ||
645
+ settlement.rejectionReason.length > 256))
646
+ ) {
647
+ throw new Error(
648
+ `Agent Skill activation ${settlement.activationMessageId} cannot be settled.`,
649
+ );
650
+ }
651
+ const activeManifest = input.surface.activeSkills.find(
652
+ (entry) =>
653
+ entry.name === activation.name &&
654
+ entry.activationMessageId === activation.activationMessageId,
655
+ );
656
+ if (
657
+ (settlement.state === "promoted" && activeManifest === undefined) ||
658
+ (settlement.state === "rejected" && activeManifest !== undefined)
659
+ ) {
660
+ throw new Error("Agent Skills active manifest does not match settlements.");
661
+ }
662
+ const expected = renderSkillActivationReceipt({
663
+ message: {
664
+ messageId: message.messageId,
665
+ frameId: message.frameId,
666
+ ordinal: message.ordinal,
667
+ content: message.displayText,
668
+ contentSha256: message.contentSha256,
669
+ },
670
+ name: activation.name,
671
+ outcome:
672
+ settlement.state === "promoted"
673
+ ? "promoted"
674
+ : settlement.rejectionReason === "unavailable"
675
+ ? "unavailable"
676
+ : "rejected",
677
+ });
678
+ if (stableJsonStringify(expected) !== stableJsonStringify(override)) {
679
+ throw new Error("Agent Skill activation receipt is not deterministic.");
680
+ }
681
+ }
682
+
683
+ const active = this.revisionCompiler.compileActive(snapshot);
684
+ const candidateOverrides = [
685
+ ...snapshot.activeOverrides,
686
+ ...input.addedOverrides,
687
+ ];
688
+ if (
689
+ new Set(candidateOverrides.map((override) => override.messageId)).size !==
690
+ candidateOverrides.length ||
691
+ activeOverrideManifestHash(candidateOverrides) !==
692
+ input.nextActiveOverrideManifestSha256
693
+ ) {
694
+ throw new Error("Agent Skills override manifest is invalid.");
695
+ }
696
+ const candidate = this.revisionCompiler.compileProspective({
697
+ active,
698
+ canonical: snapshot.canonical,
699
+ activeOverrides: snapshot.activeOverrides,
700
+ addedOverrides: input.addedOverrides,
701
+ activeSurface: snapshot.surface,
702
+ ...(surfaceChanged ? { surface: input.surface } : {}),
703
+ allowCombinedSurfaceAndOverrides: true,
704
+ });
705
+ if (
706
+ canonicalSequenceHash(
707
+ snapshot.canonical,
708
+ input.expectedCanonicalThroughOrdinal,
709
+ ) !== input.canonicalSequenceSha256 ||
710
+ renderedMessageHash(
711
+ candidate.entries,
712
+ input.expectedCanonicalThroughOrdinal,
713
+ ) !== input.renderedMessageSha256
714
+ ) {
715
+ throw new Error("Agent Skills compiled context hashes are invalid.");
716
+ }
717
+
718
+ options.faultInjector?.("before_surface_insert");
719
+ if (surfaceChanged) {
720
+ insertContextSurface(this.database, input.surface);
721
+ }
722
+ options.faultInjector?.("after_surface_insert");
723
+ const revisionNumber = baseRevision.revisionNumber + 1;
724
+ const activeOverrideCount =
725
+ baseRevision.activeOverrideCount + input.addedOverrides.length;
726
+ this.database
727
+ .query(
728
+ `INSERT INTO context_revisions (
729
+ revision_id, session_id, revision_number, parent_revision_id, kind,
730
+ surface_id, surface_sha256, keep_from_ordinal,
731
+ source_through_ordinal, added_override_count, active_override_count,
732
+ active_override_manifest_sha256, canonical_sequence_sha256,
733
+ rendered_message_sha256, policy_version, renderer_format,
734
+ plan_sha256, change_manifest_sha256, activation_manifest_sha256,
735
+ retired_through_ordinal, retired_turn_count, retired_frame_count,
736
+ retired_message_count, created_at
737
+ ) VALUES (?, ?, ?, ?, 'skills_update', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, NULL, NULL, NULL, NULL, ?)`,
738
+ )
739
+ .run(
740
+ input.revisionId,
741
+ this.sessionId,
742
+ revisionNumber,
743
+ baseRevision.revisionId,
744
+ input.surface.surfaceId,
745
+ input.surface.surfaceSha256,
746
+ baseRevision.keepFromOrdinal,
747
+ input.expectedCanonicalThroughOrdinal,
748
+ input.addedOverrides.length,
749
+ activeOverrideCount,
750
+ input.nextActiveOverrideManifestSha256,
751
+ input.canonicalSequenceSha256,
752
+ input.renderedMessageSha256,
753
+ SKILL_POLICY_VERSION,
754
+ SKILL_ACTIVATION_RECEIPT_FORMAT,
755
+ input.changeManifestSha256,
756
+ input.activationManifestSha256,
757
+ now,
758
+ );
759
+ options.faultInjector?.("after_revision_insert");
760
+
761
+ for (let index = 0; index < input.addedOverrides.length; index += 1) {
762
+ const override = requireItem(
763
+ input.addedOverrides,
764
+ index,
765
+ "Agent Skill receipt override",
766
+ );
767
+ this.database
768
+ .query(
769
+ `INSERT INTO context_overrides (
770
+ introduced_revision_id, session_id, message_id, frame_id, ordinal,
771
+ representation, renderer_format, source, original_content_sha256,
772
+ rendered_content, rendered_content_sha256, original_bytes,
773
+ rendered_bytes, byte_savings, created_at
774
+ ) VALUES (?, ?, ?, ?, ?, 'swapped', ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
775
+ )
776
+ .run(
777
+ input.revisionId,
778
+ this.sessionId,
779
+ override.messageId,
780
+ override.frameId,
781
+ override.ordinal,
782
+ SKILL_ACTIVATION_RECEIPT_FORMAT,
783
+ override.source,
784
+ override.originalContentSha256,
785
+ override.renderedContent,
786
+ override.renderedContentSha256,
787
+ override.originalBytes,
788
+ override.renderedBytes,
789
+ override.byteSavings,
790
+ now,
791
+ );
792
+ if (index === 0) {
793
+ options.faultInjector?.("after_first_override_insert");
794
+ }
795
+ }
796
+ options.faultInjector?.("after_overrides_insert");
797
+
798
+ for (const settlement of input.settlements) {
799
+ const activation = unresolved.get(settlement.activationMessageId)!;
800
+ const updated =
801
+ settlement.state === "promoted"
802
+ ? this.database
803
+ .query(
804
+ `UPDATE skill_activations
805
+ SET state = 'promoted', settled_revision_id = ?, updated_at = ?
806
+ WHERE activation_message_id = ? AND state = 'dispatched'`,
807
+ )
808
+ .run(input.revisionId, now, settlement.activationMessageId)
809
+ : this.database
810
+ .query(
811
+ `UPDATE skill_activations
812
+ SET state = 'rejected', settled_revision_id = ?, rejection_reason = ?, updated_at = ?
813
+ WHERE activation_message_id = ? AND state = ?`,
814
+ )
815
+ .run(
816
+ input.revisionId,
817
+ settlement.rejectionReason!,
818
+ now,
819
+ settlement.activationMessageId,
820
+ activation.state,
821
+ );
822
+ requireSingleChange(
823
+ this.database,
824
+ updated.changes,
825
+ `settle Agent Skill activation ${settlement.activationMessageId}`,
826
+ );
827
+ }
828
+ options.faultInjector?.("after_activations_update");
829
+
830
+ this.database.query("DELETE FROM context_measurement_state").run();
831
+ options.faultInjector?.("after_measurement_delete");
832
+ const switched = this.database
833
+ .query(
834
+ `UPDATE session_meta SET active_revision_id = ?, updated_at = ?
835
+ WHERE singleton = 1 AND active_revision_id = ?`,
836
+ )
837
+ .run(input.revisionId, now, baseRevision.revisionId);
838
+ requireSingleChange(
839
+ this.database,
840
+ switched.changes,
841
+ "activate Agent Skills context revision",
842
+ );
843
+ options.faultInjector?.("after_active_update");
844
+
845
+ const readback = this.store.loadContextSnapshot();
846
+ if (
847
+ readback.revision.kind !== "skills_update" ||
848
+ readback.revision.revisionId !== input.revisionId ||
849
+ readback.surface.surfaceId !== input.surface.surfaceId ||
850
+ loadMeasuredContextState(this.database, this.sessionId) !== undefined ||
851
+ this.store
852
+ .loadSkillActivations(["pending", "dispatched"])
853
+ .some((entry) =>
854
+ input.settlements.some(
855
+ (settlement) =>
856
+ settlement.activationMessageId === entry.activationMessageId,
857
+ ),
858
+ )
859
+ ) {
860
+ throw new Error("Committed Agent Skills update readback failed.");
861
+ }
862
+ return readback.revision;
863
+ });
864
+ } catch (error) {
865
+ if (
866
+ requireActiveRevisionId(this.store.readMeta()) !== input.expectedBaseRevisionId
867
+ ) {
868
+ throw new Error("Failed Agent Skills transaction changed active state.", {
869
+ cause: error,
870
+ });
871
+ }
872
+ throw sessionWriteError("commit_skills_update", this.sessionId, error);
873
+ }
874
+ }
875
+ }
876
+ function assertCommitSwapRevisionInput(input: CommitSwapRevisionInput): void {
877
+ if (
878
+ input.revisionId.trim() === "" ||
879
+ input.expectedBaseRevisionId.trim() === "" ||
880
+ !Number.isSafeInteger(input.expectedBaseRevisionNumber) ||
881
+ input.expectedBaseRevisionNumber < 1 ||
882
+ !Number.isSafeInteger(input.expectedCanonicalThroughOrdinal) ||
883
+ input.expectedCanonicalThroughOrdinal < 1 ||
884
+ input.addedOverrides.length < 1 ||
885
+ input.policyVersion !== "swap-only-v1" ||
886
+ input.rendererFormat !== SWAP_OBSERVATION_FORMAT
887
+ ) {
888
+ throw new Error("Commit swap revision input is invalid.");
889
+ }
890
+ for (const [name, hash] of [
891
+ [
892
+ "expectedBaseActiveOverrideManifestSha256",
893
+ input.expectedBaseActiveOverrideManifestSha256,
894
+ ],
895
+ ["planHash", input.planHash],
896
+ ["nextActiveOverrideManifestSha256", input.nextActiveOverrideManifestSha256],
897
+ ["canonicalSequenceSha256", input.canonicalSequenceSha256],
898
+ ["renderedMessageSha256", input.renderedMessageSha256],
899
+ ] as const) {
900
+ if (!/^[0-9a-f]{64}$/.test(hash)) {
901
+ throw new Error(`Commit swap revision ${name} must be a SHA-256 hash.`);
902
+ }
903
+ }
904
+ }
905
+
906
+ function assertCommitSurfaceRefreshInput(input: CommitSurfaceRefreshInput): void {
907
+ if (
908
+ input.revisionId.trim() === "" ||
909
+ input.expectedBaseRevisionId.trim() === "" ||
910
+ !Number.isSafeInteger(input.expectedBaseRevisionNumber) ||
911
+ input.expectedBaseRevisionNumber < 1 ||
912
+ !Number.isSafeInteger(input.expectedCanonicalThroughOrdinal) ||
913
+ input.expectedCanonicalThroughOrdinal < 1
914
+ ) {
915
+ throw new Error("Commit surface refresh input is invalid.");
916
+ }
917
+ for (const [name, hash] of [
918
+ [
919
+ "expectedBaseActiveOverrideManifestSha256",
920
+ input.expectedBaseActiveOverrideManifestSha256,
921
+ ],
922
+ ["changeManifestSha256", input.changeManifestSha256],
923
+ ["canonicalSequenceSha256", input.canonicalSequenceSha256],
924
+ ["renderedMessageSha256", input.renderedMessageSha256],
925
+ ] as const) {
926
+ if (!/^[0-9a-f]{64}$/.test(hash)) {
927
+ throw new Error(`Commit surface refresh ${name} must be a SHA-256 hash.`);
928
+ }
929
+ }
930
+ }
931
+
932
+ function assertCommitSkillsUpdateInput(input: CommitSkillsUpdateInput): void {
933
+ if (
934
+ input.revisionId.trim() === "" ||
935
+ input.expectedBaseRevisionId.trim() === "" ||
936
+ !Number.isSafeInteger(input.expectedBaseRevisionNumber) ||
937
+ input.expectedBaseRevisionNumber < 1 ||
938
+ !Number.isSafeInteger(input.expectedCanonicalThroughOrdinal) ||
939
+ input.expectedCanonicalThroughOrdinal < 1 ||
940
+ input.addedOverrides.length < 1 ||
941
+ input.settlements.length !== input.addedOverrides.length
942
+ ) {
943
+ throw new Error("Commit Agent Skills update input is invalid.");
944
+ }
945
+ for (const [name, hash] of [
946
+ [
947
+ "expectedBaseActiveOverrideManifestSha256",
948
+ input.expectedBaseActiveOverrideManifestSha256,
949
+ ],
950
+ ["changeManifestSha256", input.changeManifestSha256],
951
+ ["activationManifestSha256", input.activationManifestSha256],
952
+ ["nextActiveOverrideManifestSha256", input.nextActiveOverrideManifestSha256],
953
+ ["canonicalSequenceSha256", input.canonicalSequenceSha256],
954
+ ["renderedMessageSha256", input.renderedMessageSha256],
955
+ ] as const) {
956
+ if (!/^[0-9a-f]{64}$/.test(hash)) {
957
+ throw new Error(`Commit Agent Skills update ${name} must be a SHA-256 hash.`);
958
+ }
959
+ }
960
+ }
961
+
962
+ function assertCommitPrefixRetirementRevisionInput(
963
+ input: CommitPrefixRetirementRevisionInput,
964
+ ): void {
965
+ if (
966
+ input.revisionId.trim() === "" ||
967
+ input.expectedBaseRevisionId.trim() === "" ||
968
+ input.policyVersion !== "recall-first-retirement-v1" ||
969
+ !Number.isSafeInteger(input.expectedBaseRevisionNumber) ||
970
+ input.expectedBaseRevisionNumber < 1 ||
971
+ !Number.isSafeInteger(input.expectedBaseKeepFromOrdinal) ||
972
+ input.expectedBaseKeepFromOrdinal < 1 ||
973
+ !Number.isSafeInteger(input.expectedCanonicalThroughOrdinal) ||
974
+ input.expectedCanonicalThroughOrdinal < 1 ||
975
+ !Number.isSafeInteger(input.nextKeepFromOrdinal) ||
976
+ input.nextKeepFromOrdinal <= input.expectedBaseKeepFromOrdinal ||
977
+ input.retiredThroughOrdinal !== input.nextKeepFromOrdinal - 1 ||
978
+ !Number.isSafeInteger(input.retiredTurnCount) ||
979
+ input.retiredTurnCount < 1 ||
980
+ !Number.isSafeInteger(input.retiredFrameCount) ||
981
+ input.retiredFrameCount < 1 ||
982
+ !Number.isSafeInteger(input.retiredMessageCount) ||
983
+ input.retiredMessageCount < 1 ||
984
+ !Number.isSafeInteger(input.nextActiveOverrideCount) ||
985
+ input.nextActiveOverrideCount < 0
986
+ ) {
987
+ throw new Error("Commit prefix retirement input is invalid.");
988
+ }
989
+ for (const [name, hash] of [
990
+ ["expectedSurfaceSha256", input.expectedSurfaceSha256],
991
+ [
992
+ "expectedBaseActiveOverrideManifestSha256",
993
+ input.expectedBaseActiveOverrideManifestSha256,
994
+ ],
995
+ ["planHash", input.planHash],
996
+ ["nextActiveOverrideManifestSha256", input.nextActiveOverrideManifestSha256],
997
+ ["canonicalSequenceSha256", input.canonicalSequenceSha256],
998
+ ["renderedMessageSha256", input.renderedMessageSha256],
999
+ ] as const) {
1000
+ if (!/^[0-9a-f]{64}$/.test(hash)) {
1001
+ throw new Error(`Commit prefix retirement ${name} must be a SHA-256 hash.`);
1002
+ }
1003
+ }
1004
+ }