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.
Files changed (208) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +6 -0
  4. package/README.md +90 -9
  5. package/bin/tinker.js +39 -0
  6. package/node_modules/ansi-regex/index.d.ts +33 -0
  7. package/node_modules/ansi-regex/index.js +14 -0
  8. package/node_modules/ansi-regex/license +9 -0
  9. package/node_modules/ansi-regex/package.json +61 -0
  10. package/node_modules/ansi-regex/readme.md +66 -0
  11. package/node_modules/ansi-styles/index.d.ts +236 -0
  12. package/node_modules/ansi-styles/index.js +223 -0
  13. package/node_modules/ansi-styles/license +9 -0
  14. package/node_modules/ansi-styles/package.json +54 -0
  15. package/node_modules/ansi-styles/readme.md +173 -0
  16. package/node_modules/chalk/license +9 -0
  17. package/node_modules/chalk/package.json +83 -0
  18. package/node_modules/chalk/readme.md +297 -0
  19. package/node_modules/chalk/source/index.d.ts +325 -0
  20. package/node_modules/chalk/source/index.js +225 -0
  21. package/node_modules/chalk/source/utilities.js +33 -0
  22. package/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  23. package/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  24. package/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  25. package/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  26. package/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  27. package/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  28. package/node_modules/character-entities/index.d.ts +6 -0
  29. package/node_modules/character-entities/index.js +2132 -0
  30. package/node_modules/character-entities/license +22 -0
  31. package/node_modules/character-entities/package.json +78 -0
  32. package/node_modules/character-entities/readme.md +152 -0
  33. package/node_modules/decode-named-character-reference/index.d.ts +13 -0
  34. package/node_modules/decode-named-character-reference/index.d.ts.map +1 -0
  35. package/node_modules/decode-named-character-reference/index.dom.d.ts +6 -0
  36. package/node_modules/decode-named-character-reference/index.dom.d.ts.map +1 -0
  37. package/node_modules/decode-named-character-reference/index.dom.js +32 -0
  38. package/node_modules/decode-named-character-reference/index.js +19 -0
  39. package/node_modules/decode-named-character-reference/license +22 -0
  40. package/node_modules/decode-named-character-reference/package.json +90 -0
  41. package/node_modules/decode-named-character-reference/readme.md +136 -0
  42. package/node_modules/get-east-asian-width/index.d.ts +60 -0
  43. package/node_modules/get-east-asian-width/index.js +30 -0
  44. package/node_modules/get-east-asian-width/license +9 -0
  45. package/node_modules/get-east-asian-width/lookup-data.js +21 -0
  46. package/node_modules/get-east-asian-width/lookup.js +138 -0
  47. package/node_modules/get-east-asian-width/package.json +71 -0
  48. package/node_modules/get-east-asian-width/readme.md +65 -0
  49. package/node_modules/get-east-asian-width/utilities.js +24 -0
  50. package/node_modules/has-flag/index.d.ts +39 -0
  51. package/node_modules/has-flag/index.js +8 -0
  52. package/node_modules/has-flag/license +9 -0
  53. package/node_modules/has-flag/package.json +49 -0
  54. package/node_modules/has-flag/readme.md +74 -0
  55. package/node_modules/is-fullwidth-code-point/index.d.ts +17 -0
  56. package/node_modules/is-fullwidth-code-point/index.js +12 -0
  57. package/node_modules/is-fullwidth-code-point/license +9 -0
  58. package/node_modules/is-fullwidth-code-point/package.json +53 -0
  59. package/node_modules/is-fullwidth-code-point/readme.md +31 -0
  60. package/node_modules/markdansi/LICENSE +21 -0
  61. package/node_modules/markdansi/README.md +167 -0
  62. package/node_modules/markdansi/dist/ast.d.ts +99 -0
  63. package/node_modules/markdansi/dist/ast.js +1 -0
  64. package/node_modules/markdansi/dist/cli.d.ts +17 -0
  65. package/node_modules/markdansi/dist/cli.js +208 -0
  66. package/node_modules/markdansi/dist/hyperlink.d.ts +9 -0
  67. package/node_modules/markdansi/dist/hyperlink.js +26 -0
  68. package/node_modules/markdansi/dist/index.d.ts +10 -0
  69. package/node_modules/markdansi/dist/index.js +16 -0
  70. package/node_modules/markdansi/dist/parser.d.ts +2 -0
  71. package/node_modules/markdansi/dist/parser.js +205 -0
  72. package/node_modules/markdansi/dist/render.d.ts +9 -0
  73. package/node_modules/markdansi/dist/render.js +680 -0
  74. package/node_modules/markdansi/dist/stream.d.ts +39 -0
  75. package/node_modules/markdansi/dist/stream.js +192 -0
  76. package/node_modules/markdansi/dist/theme.d.ts +18 -0
  77. package/node_modules/markdansi/dist/theme.js +105 -0
  78. package/node_modules/markdansi/dist/types.d.ts +58 -0
  79. package/node_modules/markdansi/dist/types.js +1 -0
  80. package/node_modules/markdansi/dist/wrap.d.ts +10 -0
  81. package/node_modules/markdansi/dist/wrap.js +73 -0
  82. package/node_modules/markdansi/docs/spec.md +110 -0
  83. package/node_modules/markdansi/package.json +78 -0
  84. package/node_modules/markdansi/tsconfig.json +18 -0
  85. package/node_modules/marked/LICENSE +44 -0
  86. package/node_modules/marked/README.md +115 -0
  87. package/node_modules/marked/bin/main.js +284 -0
  88. package/node_modules/marked/bin/marked.js +16 -0
  89. package/node_modules/marked/lib/marked.d.ts +760 -0
  90. package/node_modules/marked/lib/marked.esm.js +77 -0
  91. package/node_modules/marked/lib/marked.esm.js.map +7 -0
  92. package/node_modules/marked/lib/marked.umd.js +79 -0
  93. package/node_modules/marked/lib/marked.umd.js.map +7 -0
  94. package/node_modules/marked/man/marked.1 +113 -0
  95. package/node_modules/marked/man/marked.1.md +93 -0
  96. package/node_modules/marked/package.json +103 -0
  97. package/node_modules/slice-ansi/index.d.ts +19 -0
  98. package/node_modules/slice-ansi/index.js +317 -0
  99. package/node_modules/slice-ansi/license +10 -0
  100. package/node_modules/slice-ansi/package.json +59 -0
  101. package/node_modules/slice-ansi/readme.md +55 -0
  102. package/node_modules/slice-ansi/tokenize-ansi.js +752 -0
  103. package/node_modules/string-width/index.d.ts +39 -0
  104. package/node_modules/string-width/index.js +203 -0
  105. package/node_modules/string-width/license +9 -0
  106. package/node_modules/string-width/package.json +65 -0
  107. package/node_modules/string-width/readme.md +66 -0
  108. package/node_modules/strip-ansi/index.d.ts +15 -0
  109. package/node_modules/strip-ansi/index.js +19 -0
  110. package/node_modules/strip-ansi/license +9 -0
  111. package/node_modules/strip-ansi/package.json +59 -0
  112. package/node_modules/strip-ansi/readme.md +37 -0
  113. package/node_modules/supports-color/browser.d.ts +1 -0
  114. package/node_modules/supports-color/browser.js +35 -0
  115. package/node_modules/supports-color/index.d.ts +55 -0
  116. package/node_modules/supports-color/index.js +202 -0
  117. package/node_modules/supports-color/license +9 -0
  118. package/node_modules/supports-color/package.json +64 -0
  119. package/node_modules/supports-color/readme.md +75 -0
  120. package/node_modules/supports-hyperlinks/browser.js +10 -0
  121. package/node_modules/supports-hyperlinks/index.d.ts +25 -0
  122. package/node_modules/supports-hyperlinks/index.js +163 -0
  123. package/node_modules/supports-hyperlinks/license +10 -0
  124. package/node_modules/supports-hyperlinks/package.json +47 -0
  125. package/node_modules/supports-hyperlinks/readme.md +45 -0
  126. package/package.json +41 -10
  127. package/patches/markdansi@0.3.2.patch +22 -2
  128. package/src/agent/context-builder.ts +24 -10
  129. package/src/agent/context-meter.ts +89 -5
  130. package/src/agent/loop.ts +107 -10
  131. package/src/agent/runtime-session.ts +1437 -82
  132. package/src/agent/session-ledger.ts +169 -29
  133. package/src/agent/types.ts +12 -1
  134. package/src/agent/user-prompt-projection.ts +62 -0
  135. package/src/cli/config.ts +57 -14
  136. package/src/cli/model-profiles.ts +124 -0
  137. package/src/cli/run-runner.ts +4 -1
  138. package/src/cli/tui-runner.tsx +68 -30
  139. package/src/context/compiled-context-hash.ts +1 -1
  140. package/src/context/compiled-context-validator.ts +92 -15
  141. package/src/context/context-automation-policy.ts +121 -0
  142. package/src/context/context-manager.ts +390 -11
  143. package/src/context/context-policy.ts +8 -0
  144. package/src/context/context-protocol-validator.ts +11 -1
  145. package/src/context/context-revision-compiler.ts +190 -27
  146. package/src/context/context-revision.ts +105 -19
  147. package/src/context/context-surface.ts +329 -0
  148. package/src/context/prefix-retirement-planner.ts +570 -0
  149. package/src/context/protocol-frame.ts +30 -3
  150. package/src/context/recall-retirement-contract.ts +27 -0
  151. package/src/context/swap-planner.ts +34 -10
  152. package/src/events/observation-text-log.ts +3 -3
  153. package/src/events/stdout-event-printer.ts +39 -3
  154. package/src/events/types.ts +172 -35
  155. package/src/ids/runtime-id.ts +6 -0
  156. package/src/ids/uuid-v7.ts +7 -0
  157. package/src/image/image-asset-store.ts +319 -0
  158. package/src/image/image-input-policy.ts +14 -0
  159. package/src/image/image-probe.ts +233 -0
  160. package/src/image/image-types.ts +262 -0
  161. package/src/instructions/project-instructions.ts +4 -1
  162. package/src/mcp/mcp-manager.ts +83 -19
  163. package/src/model/fake-model-client.ts +9 -1
  164. package/src/model/input-token-estimator.ts +25 -0
  165. package/src/model/model-client.ts +60 -0
  166. package/src/model/model-request-preflight.ts +1 -0
  167. package/src/model/moonshot-input-token-estimator.ts +107 -0
  168. package/src/model/openai-chat-mapping.ts +77 -1
  169. package/src/model/openai-chat-model-client.ts +389 -59
  170. package/src/model/openai-chat-stream.ts +272 -0
  171. package/src/model/prompt-prefix-hash.ts +2 -4
  172. package/src/model/token-estimator.ts +4 -0
  173. package/src/observation/observation-builder.ts +29 -0
  174. package/src/session/resume-projection.ts +94 -34
  175. package/src/session/session-catalog.ts +4 -4
  176. package/src/session/session-errors.ts +1 -1
  177. package/src/session/session-history-reader.ts +151 -4
  178. package/src/session/session-last-response-reader.ts +189 -0
  179. package/src/session/session-schema.ts +349 -35
  180. package/src/session/session-store.ts +3266 -255
  181. package/src/session/sqlite-session-ledger.ts +9 -4
  182. package/src/skills/skill-catalog.ts +182 -0
  183. package/src/skills/skill-context.ts +364 -0
  184. package/src/skills/skill-loader.ts +779 -0
  185. package/src/skills/skill-tool.ts +297 -0
  186. package/src/tools/edit.ts +51 -64
  187. package/src/tools/ensure-parent-directory.ts +6 -0
  188. package/src/tools/read.ts +2 -2
  189. package/src/tools/recall.ts +43 -40
  190. package/src/tools/registry.ts +24 -3
  191. package/src/tools/ripgrep.ts +3 -2
  192. package/src/tools/types.ts +43 -4
  193. package/src/tools/write.ts +20 -31
  194. package/src/tui/app.tsx +333 -96
  195. package/src/tui/clipboard.ts +5 -0
  196. package/src/tui/components/mcp-panel.tsx +28 -0
  197. package/src/tui/components/prompt-input.tsx +790 -257
  198. package/src/tui/components/skills-panel.tsx +31 -0
  199. package/src/tui/components/timeline.tsx +30 -1
  200. package/src/tui/event-store.ts +85 -2
  201. package/src/tui/file-mention.ts +234 -0
  202. package/src/tui/line-editor.ts +1 -0
  203. package/src/tui/project-slash-commands.ts +229 -0
  204. package/src/tui/prompt-draft.ts +510 -0
  205. package/src/tui/prompt-history.ts +294 -22
  206. package/src/tui/slash-commands.ts +46 -2
  207. package/src/tui/tui-session-controller.ts +110 -48
  208. package/src/tui/workspace-file-search.ts +94 -0
@@ -4,7 +4,7 @@ import type { SessionId } from "../ids/runtime-id";
4
4
  import { SessionError } from "./session-errors";
5
5
 
6
6
  export const SESSION_APPLICATION_ID = 0x544b5231;
7
- export const SESSION_SCHEMA_VERSION = 5;
7
+ export const SESSION_SCHEMA_VERSION = 9;
8
8
 
9
9
  type SchemaDefinition = {
10
10
  type: "table" | "index" | "trigger" | "view";
@@ -27,7 +27,7 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
27
27
  name: "session_meta",
28
28
  sql: `CREATE TABLE session_meta (
29
29
  singleton INTEGER PRIMARY KEY CHECK (singleton = 1),
30
- schema_version INTEGER NOT NULL CHECK (schema_version = 5),
30
+ schema_version INTEGER NOT NULL CHECK (schema_version = 9),
31
31
  schema_fingerprint TEXT NOT NULL,
32
32
  initialization_state TEXT NOT NULL CHECK (initialization_state IN ('creating', 'ready')),
33
33
  session_id TEXT NOT NULL UNIQUE,
@@ -37,10 +37,9 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
37
37
  project_instruction_file TEXT CHECK (project_instruction_file IS NULL OR project_instruction_file IN ('AGENTS.md', 'CLAUDE.md')),
38
38
  project_instruction_byte_length INTEGER CHECK (project_instruction_byte_length IS NULL OR project_instruction_byte_length >= 1),
39
39
  project_instruction_sha256 TEXT CHECK (project_instruction_sha256 IS NULL OR length(project_instruction_sha256) = 64),
40
- tool_schema_sha256 TEXT,
41
- runtime_contract_json TEXT,
42
- runtime_contract_sha256 TEXT,
43
- active_revision_id TEXT NOT NULL,
40
+ session_compatibility_json TEXT,
41
+ session_compatibility_sha256 TEXT,
42
+ active_revision_id TEXT,
44
43
  next_turn_number INTEGER NOT NULL CHECK (next_turn_number >= 1),
45
44
  next_event_sequence INTEGER NOT NULL CHECK (next_event_sequence >= 1),
46
45
  open_count INTEGER NOT NULL CHECK (open_count >= 1),
@@ -51,10 +50,13 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
51
50
  last_close_reason TEXT CHECK (last_close_reason IS NULL OR last_close_reason IN (
52
51
  'oneshot_complete', 'tui_exit', 'session_switch', 'runner_failed', 'initialization_failed'
53
52
  )),
54
- CHECK ((runtime_contract_json IS NULL) = (runtime_contract_sha256 IS NULL)),
53
+ CHECK ((session_compatibility_json IS NULL) = (session_compatibility_sha256 IS NULL)),
55
54
  CHECK ((project_instruction_file IS NULL) = (project_instruction_byte_length IS NULL)),
56
55
  CHECK ((project_instruction_file IS NULL) = (project_instruction_sha256 IS NULL)),
57
- CHECK ((initialization_state = 'creating') OR runtime_contract_json IS NOT NULL)
56
+ CHECK (
57
+ (initialization_state = 'creating' AND session_compatibility_json IS NULL AND active_revision_id IS NULL) OR
58
+ (initialization_state = 'ready' AND session_compatibility_json IS NOT NULL AND active_revision_id IS NOT NULL)
59
+ )
58
60
  ) STRICT`,
59
61
  },
60
62
  {
@@ -188,13 +190,72 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
188
190
  CHECK (raw_json IS NULL OR json_valid(raw_json)),
189
191
  CHECK (
190
192
  (completion_kind = 'returned' AND raw_json IS NOT NULL AND raw_sha256 IS NOT NULL AND
191
- observation_format = 'tool-observation-v2' AND synthetic_reason IS NULL AND synthetic_detail IS NULL) OR
193
+ observation_format IS NOT NULL AND length(observation_format) > 0 AND synthetic_reason IS NULL AND synthetic_detail IS NULL) OR
192
194
  (completion_kind = 'synthetic' AND raw_json IS NULL AND raw_sha256 IS NULL AND
193
195
  observation_format IS NULL AND synthetic_reason IS NOT NULL)
194
196
  ),
195
197
  CHECK ((synthetic_reason = 'failed_active') = (synthetic_detail IS NOT NULL))
196
198
  ) STRICT`,
197
199
  },
200
+ {
201
+ type: "table",
202
+ name: "image_assets",
203
+ sql: `CREATE TABLE image_assets (
204
+ asset_id TEXT PRIMARY KEY
205
+ CHECK (length(asset_id) = 64 AND asset_id NOT GLOB '*[^0-9a-f]*'),
206
+ mime_type TEXT NOT NULL
207
+ CHECK (mime_type IN ('image/png', 'image/jpeg', 'image/webp')),
208
+ byte_length INTEGER NOT NULL CHECK (byte_length > 0),
209
+ width INTEGER NOT NULL CHECK (width > 0),
210
+ height INTEGER NOT NULL CHECK (height > 0),
211
+ created_at TEXT NOT NULL
212
+ ) STRICT`,
213
+ },
214
+ {
215
+ type: "table",
216
+ name: "message_image_attachments",
217
+ sql: `CREATE TABLE message_image_attachments (
218
+ message_id TEXT NOT NULL,
219
+ attachment_id TEXT NOT NULL CHECK (length(attachment_id) = 36),
220
+ asset_id TEXT NOT NULL,
221
+ position INTEGER NOT NULL CHECK (position >= 0),
222
+ label TEXT NOT NULL CHECK (length(label) > 0),
223
+ range_start INTEGER NOT NULL CHECK (range_start >= 0),
224
+ range_end INTEGER NOT NULL CHECK (range_end > range_start),
225
+ original_name TEXT NOT NULL CHECK (length(original_name) > 0),
226
+ PRIMARY KEY (message_id, attachment_id),
227
+ UNIQUE (message_id, position),
228
+ UNIQUE (message_id, label),
229
+ UNIQUE (message_id, range_start, range_end),
230
+ FOREIGN KEY (message_id) REFERENCES messages(message_id),
231
+ FOREIGN KEY (asset_id) REFERENCES image_assets(asset_id)
232
+ ) STRICT`,
233
+ },
234
+ {
235
+ type: "table",
236
+ name: "context_surfaces",
237
+ sql: `CREATE TABLE context_surfaces (
238
+ surface_id TEXT PRIMARY KEY,
239
+ session_id TEXT NOT NULL,
240
+ system_prompt TEXT NOT NULL CHECK (length(system_prompt) > 0),
241
+ system_prompt_sha256 TEXT NOT NULL CHECK (length(system_prompt_sha256) = 64),
242
+ recall_contract_version TEXT NOT NULL CHECK (length(recall_contract_version) BETWEEN 1 AND 64),
243
+ project_instruction_json TEXT CHECK (project_instruction_json IS NULL OR json_valid(project_instruction_json)),
244
+ skill_catalog_json TEXT NOT NULL CHECK (json_valid(skill_catalog_json)),
245
+ skill_catalog_sha256 TEXT NOT NULL CHECK (length(skill_catalog_sha256) = 64),
246
+ active_skills_json TEXT NOT NULL CHECK (json_valid(active_skills_json)),
247
+ active_skills_sha256 TEXT NOT NULL CHECK (length(active_skills_sha256) = 64),
248
+ tool_definitions_json TEXT NOT NULL CHECK (json_valid(tool_definitions_json)),
249
+ tool_definitions_sha256 TEXT NOT NULL CHECK (length(tool_definitions_sha256) = 64),
250
+ tool_schema_sha256 TEXT NOT NULL CHECK (length(tool_schema_sha256) = 64),
251
+ request_config_sha256 TEXT NOT NULL CHECK (length(request_config_sha256) = 64),
252
+ request_max_output_tokens INTEGER NOT NULL CHECK (request_max_output_tokens >= 1),
253
+ surface_sha256 TEXT NOT NULL CHECK (length(surface_sha256) = 64),
254
+ created_at TEXT NOT NULL,
255
+ UNIQUE (session_id, surface_id),
256
+ FOREIGN KEY (session_id) REFERENCES session_meta(session_id)
257
+ ) STRICT`,
258
+ },
198
259
  {
199
260
  type: "table",
200
261
  name: "context_revisions",
@@ -203,30 +264,63 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
203
264
  session_id TEXT NOT NULL,
204
265
  revision_number INTEGER NOT NULL CHECK (revision_number >= 1),
205
266
  parent_revision_id TEXT,
206
- kind TEXT NOT NULL CHECK (kind IN ('initial_full', 'swap_only')),
207
- keep_from_ordinal INTEGER NOT NULL CHECK (keep_from_ordinal = 1),
267
+ kind TEXT NOT NULL CHECK (kind IN ('initial_full', 'swap_only', 'surface_refresh', 'prefix_retirement', 'skills_update')),
268
+ surface_id TEXT NOT NULL,
269
+ surface_sha256 TEXT NOT NULL CHECK (length(surface_sha256) = 64),
270
+ keep_from_ordinal INTEGER NOT NULL CHECK (keep_from_ordinal >= 1),
208
271
  source_through_ordinal INTEGER NOT NULL CHECK (source_through_ordinal >= 1),
209
272
  added_override_count INTEGER NOT NULL CHECK (added_override_count >= 0),
210
- total_override_count INTEGER NOT NULL CHECK (total_override_count >= 0),
211
- override_manifest_sha256 TEXT NOT NULL CHECK (length(override_manifest_sha256) = 64),
273
+ active_override_count INTEGER NOT NULL CHECK (active_override_count >= 0),
274
+ active_override_manifest_sha256 TEXT NOT NULL CHECK (length(active_override_manifest_sha256) = 64),
212
275
  canonical_sequence_sha256 TEXT NOT NULL CHECK (length(canonical_sequence_sha256) = 64),
213
276
  rendered_message_sha256 TEXT NOT NULL CHECK (length(rendered_message_sha256) = 64),
214
277
  policy_version TEXT,
215
278
  renderer_format TEXT,
216
279
  plan_sha256 TEXT CHECK (plan_sha256 IS NULL OR length(plan_sha256) = 64),
280
+ change_manifest_sha256 TEXT CHECK (change_manifest_sha256 IS NULL OR length(change_manifest_sha256) = 64),
281
+ activation_manifest_sha256 TEXT CHECK (activation_manifest_sha256 IS NULL OR length(activation_manifest_sha256) = 64),
282
+ retired_through_ordinal INTEGER CHECK (retired_through_ordinal IS NULL OR retired_through_ordinal >= 2),
283
+ retired_turn_count INTEGER CHECK (retired_turn_count IS NULL OR retired_turn_count >= 1),
284
+ retired_frame_count INTEGER CHECK (retired_frame_count IS NULL OR retired_frame_count >= 1),
285
+ retired_message_count INTEGER CHECK (retired_message_count IS NULL OR retired_message_count >= 1),
217
286
  created_at TEXT NOT NULL,
218
287
  UNIQUE (session_id, revision_number),
219
288
  FOREIGN KEY (session_id) REFERENCES session_meta(session_id),
220
289
  FOREIGN KEY (parent_revision_id) REFERENCES context_revisions(revision_id),
290
+ FOREIGN KEY (session_id, surface_id) REFERENCES context_surfaces(session_id, surface_id),
221
291
  CHECK (
222
292
  (kind = 'initial_full' AND revision_number = 1 AND parent_revision_id IS NULL AND
223
293
  source_through_ordinal = 1 AND added_override_count = 0 AND
224
- total_override_count = 0 AND policy_version IS NULL AND
225
- renderer_format IS NULL AND plan_sha256 IS NULL) OR
294
+ active_override_count = 0 AND policy_version IS NULL AND
295
+ renderer_format IS NULL AND plan_sha256 IS NULL AND change_manifest_sha256 IS NULL AND activation_manifest_sha256 IS NULL AND
296
+ retired_through_ordinal IS NULL AND retired_turn_count IS NULL AND
297
+ retired_frame_count IS NULL AND retired_message_count IS NULL) OR
226
298
  (kind = 'swap_only' AND revision_number >= 2 AND parent_revision_id IS NOT NULL AND
227
- added_override_count >= 1 AND total_override_count >= added_override_count AND
299
+ added_override_count >= 1 AND active_override_count >= added_override_count AND
228
300
  policy_version = 'swap-only-v1' AND
229
- renderer_format = 'swap-observation-v1' AND plan_sha256 IS NOT NULL)
301
+ renderer_format = 'swap-observation-v1' AND plan_sha256 IS NOT NULL AND
302
+ change_manifest_sha256 IS NULL AND activation_manifest_sha256 IS NULL AND retired_through_ordinal IS NULL AND
303
+ retired_turn_count IS NULL AND retired_frame_count IS NULL AND
304
+ retired_message_count IS NULL) OR
305
+ (kind = 'surface_refresh' AND revision_number >= 2 AND parent_revision_id IS NOT NULL AND
306
+ added_override_count = 0 AND policy_version IS NULL AND renderer_format IS NULL AND
307
+ plan_sha256 IS NULL AND change_manifest_sha256 IS NOT NULL AND activation_manifest_sha256 IS NULL AND
308
+ retired_through_ordinal IS NULL AND retired_turn_count IS NULL AND
309
+ retired_frame_count IS NULL AND retired_message_count IS NULL) OR
310
+ (kind = 'prefix_retirement' AND revision_number >= 2 AND parent_revision_id IS NOT NULL AND
311
+ keep_from_ordinal > 1 AND retired_through_ordinal = keep_from_ordinal - 1 AND
312
+ added_override_count = 0 AND policy_version = 'recall-first-retirement-v1' AND
313
+ renderer_format IS NULL AND plan_sha256 IS NOT NULL AND
314
+ change_manifest_sha256 IS NULL AND activation_manifest_sha256 IS NULL AND retired_turn_count >= 1 AND
315
+ retired_frame_count >= 1 AND retired_message_count >= 1)
316
+ OR
317
+ (kind = 'skills_update' AND revision_number >= 2 AND parent_revision_id IS NOT NULL AND
318
+ added_override_count >= 1 AND active_override_count >= added_override_count AND
319
+ policy_version = 'agent-skills-v1' AND
320
+ renderer_format = 'skill-activation-receipt-v1' AND plan_sha256 IS NULL AND
321
+ change_manifest_sha256 IS NOT NULL AND activation_manifest_sha256 IS NOT NULL AND
322
+ retired_through_ordinal IS NULL AND retired_turn_count IS NULL AND
323
+ retired_frame_count IS NULL AND retired_message_count IS NULL)
230
324
  )
231
325
  ) STRICT`,
232
326
  },
@@ -240,14 +334,14 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
240
334
  frame_id TEXT NOT NULL,
241
335
  ordinal INTEGER NOT NULL CHECK (ordinal >= 1),
242
336
  representation TEXT NOT NULL CHECK (representation = 'swapped'),
243
- renderer_format TEXT NOT NULL CHECK (renderer_format = 'swap-observation-v1'),
337
+ renderer_format TEXT NOT NULL CHECK (renderer_format IN ('swap-observation-v1', 'skill-activation-receipt-v1')),
244
338
  source TEXT NOT NULL,
245
339
  original_content_sha256 TEXT NOT NULL CHECK (length(original_content_sha256) = 64),
246
340
  rendered_content TEXT NOT NULL CHECK (length(rendered_content) > 0),
247
341
  rendered_content_sha256 TEXT NOT NULL CHECK (length(rendered_content_sha256) = 64),
248
342
  original_bytes INTEGER NOT NULL CHECK (original_bytes > 0),
249
343
  rendered_bytes INTEGER NOT NULL CHECK (rendered_bytes > 0),
250
- byte_savings INTEGER NOT NULL CHECK (byte_savings > 0),
344
+ byte_savings INTEGER NOT NULL,
251
345
  created_at TEXT NOT NULL,
252
346
  PRIMARY KEY (introduced_revision_id, message_id),
253
347
  UNIQUE (session_id, message_id),
@@ -256,7 +350,32 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
256
350
  FOREIGN KEY (session_id) REFERENCES session_meta(session_id),
257
351
  FOREIGN KEY (message_id) REFERENCES messages(message_id),
258
352
  FOREIGN KEY (frame_id) REFERENCES protocol_frames(frame_id),
259
- CHECK (original_bytes = rendered_bytes + byte_savings)
353
+ CHECK (original_bytes = rendered_bytes + byte_savings),
354
+ CHECK (renderer_format <> 'swap-observation-v1' OR byte_savings > 0)
355
+ ) STRICT`,
356
+ },
357
+ {
358
+ type: "table",
359
+ name: "skill_activations",
360
+ sql: `CREATE TABLE skill_activations (
361
+ activation_message_id TEXT PRIMARY KEY REFERENCES messages(message_id),
362
+ tool_call_id TEXT NOT NULL UNIQUE REFERENCES tool_results(tool_call_id),
363
+ session_id TEXT NOT NULL REFERENCES session_meta(session_id),
364
+ name TEXT NOT NULL,
365
+ scope TEXT NOT NULL CHECK (scope IN ('project', 'user')),
366
+ skill_file_sha256 TEXT NOT NULL CHECK (length(skill_file_sha256) = 64),
367
+ state TEXT NOT NULL CHECK (state IN ('pending', 'dispatched', 'promoted', 'rejected')),
368
+ dispatched_iteration_id TEXT REFERENCES iterations(iteration_id),
369
+ settled_revision_id TEXT REFERENCES context_revisions(revision_id),
370
+ rejection_reason TEXT,
371
+ created_at TEXT NOT NULL,
372
+ updated_at TEXT NOT NULL,
373
+ CHECK (
374
+ (state = 'pending' AND dispatched_iteration_id IS NULL AND settled_revision_id IS NULL AND rejection_reason IS NULL) OR
375
+ (state = 'dispatched' AND dispatched_iteration_id IS NOT NULL AND settled_revision_id IS NULL AND rejection_reason IS NULL) OR
376
+ (state = 'promoted' AND dispatched_iteration_id IS NOT NULL AND settled_revision_id IS NOT NULL AND rejection_reason IS NULL) OR
377
+ (state = 'rejected' AND settled_revision_id IS NOT NULL AND rejection_reason IS NOT NULL)
378
+ )
260
379
  ) STRICT`,
261
380
  },
262
381
  {
@@ -289,6 +408,11 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
289
408
  name: "idx_context_overrides_revision_ordinal",
290
409
  sql: "CREATE INDEX idx_context_overrides_revision_ordinal ON context_overrides(introduced_revision_id, ordinal)",
291
410
  },
411
+ {
412
+ type: "index",
413
+ name: "idx_skill_activations_session_state",
414
+ sql: "CREATE INDEX idx_skill_activations_session_state ON skill_activations(session_id, state, activation_message_id)",
415
+ },
292
416
  {
293
417
  type: "index",
294
418
  name: "idx_frames_session_ordinal",
@@ -299,6 +423,11 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
299
423
  name: "idx_messages_frame_ordinal",
300
424
  sql: "CREATE INDEX idx_messages_frame_ordinal ON messages(frame_id, ordinal)",
301
425
  },
426
+ {
427
+ type: "index",
428
+ name: "idx_message_image_attachments_asset",
429
+ sql: "CREATE INDEX idx_message_image_attachments_asset ON message_image_attachments(asset_id)",
430
+ },
302
431
  {
303
432
  type: "index",
304
433
  name: "idx_turns_session_recent",
@@ -341,8 +470,64 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
341
470
  },
342
471
  ...immutableTriggers("messages"),
343
472
  ...immutableTriggers("tool_results"),
473
+ ...immutableTriggers("image_assets"),
474
+ ...immutableTriggers("message_image_attachments"),
475
+ ...immutableTriggers("context_surfaces"),
344
476
  ...immutableTriggers("context_revisions"),
345
477
  ...immutableTriggers("context_overrides"),
478
+ {
479
+ type: "trigger",
480
+ name: "skill_activations_no_delete",
481
+ sql: `CREATE TRIGGER skill_activations_no_delete BEFORE DELETE ON skill_activations
482
+ BEGIN SELECT RAISE(ABORT, 'skill_activations cannot be deleted'); END`,
483
+ },
484
+ {
485
+ type: "trigger",
486
+ name: "skill_activations_validate_insert",
487
+ sql: `CREATE TRIGGER skill_activations_validate_insert BEFORE INSERT ON skill_activations
488
+ WHEN NOT (
489
+ NEW.session_id = (SELECT session_id FROM session_meta WHERE singleton = 1) AND
490
+ NEW.state = 'pending' AND NEW.dispatched_iteration_id IS NULL AND
491
+ NEW.settled_revision_id IS NULL AND NEW.rejection_reason IS NULL AND
492
+ EXISTS (
493
+ SELECT 1 FROM messages m
494
+ JOIN tool_results tr ON tr.tool_message_id = m.message_id
495
+ WHERE m.message_id = NEW.activation_message_id
496
+ AND m.session_id = NEW.session_id AND m.tool_call_id = NEW.tool_call_id
497
+ AND m.name = 'Skill' AND m.role = 'tool' AND m.origin = 'tool'
498
+ AND tr.tool_call_id = NEW.tool_call_id AND tr.session_id = NEW.session_id
499
+ AND tr.completion_kind = 'returned'
500
+ AND json_extract(tr.raw_json, '$.kind') = 'skill'
501
+ AND json_extract(tr.raw_json, '$.ok') = 1
502
+ AND json_extract(tr.raw_json, '$.status') = 'loaded'
503
+ AND json_extract(tr.raw_json, '$.name') = NEW.name
504
+ AND json_extract(tr.raw_json, '$.scope') = NEW.scope
505
+ AND json_extract(tr.raw_json, '$.sha256') = NEW.skill_file_sha256
506
+ )
507
+ )
508
+ BEGIN SELECT RAISE(ABORT, 'invalid skill activation insert'); END`,
509
+ },
510
+ {
511
+ type: "trigger",
512
+ name: "skill_activations_monotonic_update",
513
+ sql: `CREATE TRIGGER skill_activations_monotonic_update BEFORE UPDATE ON skill_activations
514
+ WHEN NOT (
515
+ OLD.activation_message_id = NEW.activation_message_id AND
516
+ OLD.tool_call_id = NEW.tool_call_id AND OLD.session_id = NEW.session_id AND
517
+ OLD.name = NEW.name AND OLD.scope = NEW.scope AND
518
+ OLD.skill_file_sha256 = NEW.skill_file_sha256 AND OLD.created_at = NEW.created_at AND
519
+ ((OLD.state = 'pending' AND NEW.state = 'dispatched' AND
520
+ NEW.dispatched_iteration_id IS NOT NULL AND NEW.settled_revision_id IS NULL AND NEW.rejection_reason IS NULL) OR
521
+ (OLD.state = 'pending' AND NEW.state = 'rejected' AND
522
+ NEW.dispatched_iteration_id IS NULL AND NEW.settled_revision_id IS NOT NULL AND NEW.rejection_reason IS NOT NULL) OR
523
+ (OLD.state = 'dispatched' AND NEW.state IN ('promoted', 'rejected') AND
524
+ NEW.dispatched_iteration_id = OLD.dispatched_iteration_id AND
525
+ NEW.settled_revision_id IS NOT NULL AND
526
+ ((NEW.state = 'promoted' AND NEW.rejection_reason IS NULL) OR
527
+ (NEW.state = 'rejected' AND NEW.rejection_reason IS NOT NULL))))
528
+ )
529
+ BEGIN SELECT RAISE(ABORT, 'invalid skill activation transition'); END`,
530
+ },
346
531
  {
347
532
  type: "trigger",
348
533
  name: "context_revisions_validate_insert",
@@ -350,16 +535,85 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
350
535
  BEFORE INSERT ON context_revisions
351
536
  WHEN NOT (
352
537
  NEW.session_id = (SELECT session_id FROM session_meta WHERE singleton = 1) AND
353
- ((NEW.kind = 'initial_full' AND
354
- NEW.revision_id = (SELECT active_revision_id FROM session_meta WHERE singleton = 1) AND
355
- NOT EXISTS (SELECT 1 FROM context_revisions WHERE session_id = NEW.session_id)) OR
538
+ ((NEW.kind = 'initial_full' AND NEW.revision_number = 1 AND
539
+ (SELECT initialization_state FROM session_meta WHERE singleton = 1) = 'creating' AND
540
+ (SELECT active_revision_id FROM session_meta WHERE singleton = 1) IS NULL AND
541
+ NOT EXISTS (SELECT 1 FROM context_revisions WHERE session_id = NEW.session_id) AND
542
+ NEW.surface_sha256 = (SELECT surface_sha256 FROM context_surfaces WHERE surface_id = NEW.surface_id)) OR
356
543
  (NEW.kind = 'swap_only' AND
357
544
  NEW.parent_revision_id = (SELECT active_revision_id FROM session_meta WHERE singleton = 1) AND
358
545
  NEW.revision_number = 1 + COALESCE((
359
546
  SELECT revision_number FROM context_revisions
360
547
  WHERE revision_id = NEW.parent_revision_id AND session_id = NEW.session_id
361
548
  ), 0) AND
362
- NEW.source_through_ordinal = COALESCE((SELECT MAX(ordinal) FROM messages), 0)))
549
+ NEW.source_through_ordinal = COALESCE((SELECT MAX(ordinal) FROM messages), 0) AND
550
+ EXISTS (SELECT 1 FROM protocol_frames WHERE state = 'closed' AND last_ordinal = NEW.source_through_ordinal) AND
551
+ NEW.surface_id = (SELECT surface_id FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
552
+ NEW.surface_sha256 = (SELECT surface_sha256 FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
553
+ NEW.keep_from_ordinal = (SELECT keep_from_ordinal FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
554
+ NEW.active_override_count = (SELECT active_override_count FROM context_revisions WHERE revision_id = NEW.parent_revision_id) + NEW.added_override_count) OR
555
+ (NEW.kind = 'surface_refresh' AND
556
+ NEW.parent_revision_id = (SELECT active_revision_id FROM session_meta WHERE singleton = 1) AND
557
+ NEW.revision_number = 1 + COALESCE((
558
+ SELECT revision_number FROM context_revisions
559
+ WHERE revision_id = NEW.parent_revision_id AND session_id = NEW.session_id
560
+ ), 0) AND
561
+ NEW.source_through_ordinal = COALESCE((SELECT MAX(ordinal) FROM messages), 0) AND
562
+ EXISTS (SELECT 1 FROM protocol_frames WHERE state = 'closed' AND last_ordinal = NEW.source_through_ordinal) AND
563
+ NEW.surface_id <> (SELECT surface_id FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
564
+ NEW.surface_sha256 = (SELECT surface_sha256 FROM context_surfaces WHERE surface_id = NEW.surface_id) AND
565
+ NEW.keep_from_ordinal = (SELECT keep_from_ordinal FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
566
+ NEW.active_override_count = (SELECT active_override_count FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
567
+ NEW.active_override_manifest_sha256 = (SELECT active_override_manifest_sha256 FROM context_revisions WHERE revision_id = NEW.parent_revision_id)) OR
568
+ (NEW.kind = 'skills_update' AND
569
+ NEW.parent_revision_id = (SELECT active_revision_id FROM session_meta WHERE singleton = 1) AND
570
+ NEW.revision_number = 1 + COALESCE((
571
+ SELECT revision_number FROM context_revisions
572
+ WHERE revision_id = NEW.parent_revision_id AND session_id = NEW.session_id
573
+ ), 0) AND
574
+ NEW.source_through_ordinal = COALESCE((SELECT MAX(ordinal) FROM messages), 0) AND
575
+ EXISTS (SELECT 1 FROM protocol_frames WHERE state = 'closed' AND last_ordinal = NEW.source_through_ordinal) AND
576
+ NEW.surface_sha256 = (SELECT surface_sha256 FROM context_surfaces WHERE surface_id = NEW.surface_id) AND
577
+ NEW.keep_from_ordinal = (SELECT keep_from_ordinal FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
578
+ NEW.active_override_count = (SELECT active_override_count FROM context_revisions WHERE revision_id = NEW.parent_revision_id) + NEW.added_override_count) OR
579
+ (NEW.kind = 'prefix_retirement' AND
580
+ NEW.parent_revision_id = (SELECT active_revision_id FROM session_meta WHERE singleton = 1) AND
581
+ NEW.revision_number = 1 + COALESCE((
582
+ SELECT revision_number FROM context_revisions
583
+ WHERE revision_id = NEW.parent_revision_id AND session_id = NEW.session_id
584
+ ), 0) AND
585
+ NEW.source_through_ordinal = COALESCE((SELECT MAX(ordinal) FROM messages), 0) AND
586
+ EXISTS (SELECT 1 FROM protocol_frames WHERE state = 'closed' AND last_ordinal = NEW.source_through_ordinal) AND
587
+ NEW.surface_id = (SELECT surface_id FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
588
+ NEW.surface_sha256 = (SELECT surface_sha256 FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
589
+ NEW.keep_from_ordinal > (SELECT keep_from_ordinal FROM context_revisions WHERE revision_id = NEW.parent_revision_id) AND
590
+ EXISTS (
591
+ SELECT 1 FROM messages m
592
+ JOIN protocol_frames pf ON pf.frame_id = m.frame_id
593
+ JOIN turns t ON t.turn_id = m.turn_id
594
+ WHERE m.ordinal = NEW.keep_from_ordinal AND m.role = 'user'
595
+ AND pf.kind = 'user' AND pf.state = 'closed'
596
+ AND pf.first_ordinal = NEW.keep_from_ordinal
597
+ AND pf.last_ordinal = NEW.keep_from_ordinal
598
+ AND t.status <> 'open'
599
+ ) AND
600
+ EXISTS (
601
+ SELECT 1 FROM messages m
602
+ JOIN protocol_frames pf ON pf.frame_id = m.frame_id
603
+ JOIN turns t ON t.turn_id = m.turn_id
604
+ WHERE m.ordinal = NEW.keep_from_ordinal - 1
605
+ AND pf.state = 'closed' AND pf.last_ordinal = NEW.keep_from_ordinal - 1
606
+ AND t.status <> 'open'
607
+ ) AND
608
+ NEW.active_override_count = (
609
+ SELECT COUNT(*) FROM context_overrides co
610
+ JOIN context_revisions introduced
611
+ ON introduced.revision_id = co.introduced_revision_id
612
+ JOIN context_revisions parent
613
+ ON parent.revision_id = NEW.parent_revision_id
614
+ WHERE introduced.revision_number <= parent.revision_number
615
+ AND co.ordinal >= NEW.keep_from_ordinal
616
+ )))
363
617
  )
364
618
  BEGIN SELECT RAISE(ABORT, 'invalid context revision insert'); END`,
365
619
  },
@@ -373,7 +627,9 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
373
627
  SELECT 1 FROM context_revisions cr
374
628
  WHERE cr.revision_id = NEW.introduced_revision_id
375
629
  AND cr.session_id = NEW.session_id
376
- AND cr.kind = 'swap_only'
630
+ AND ((cr.kind = 'swap_only' AND NEW.renderer_format = 'swap-observation-v1') OR
631
+ (cr.kind = 'skills_update' AND NEW.renderer_format = 'skill-activation-receipt-v1'))
632
+ AND NEW.ordinal >= cr.keep_from_ordinal
377
633
  AND NEW.ordinal <= cr.source_through_ordinal
378
634
  ) AND
379
635
  EXISTS (
@@ -394,10 +650,15 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
394
650
  AND tr.session_id = NEW.session_id
395
651
  AND tr.frame_id = NEW.frame_id
396
652
  AND tr.observation_sha256 = NEW.original_content_sha256
653
+ AND (NEW.renderer_format <> 'skill-activation-receipt-v1' OR
654
+ (m.name = 'Skill' AND tr.completion_kind = 'returned' AND
655
+ json_extract(tr.raw_json, '$.kind') = 'skill' AND
656
+ json_extract(tr.raw_json, '$.ok') = 1 AND
657
+ json_extract(tr.raw_json, '$.status') = 'loaded'))
397
658
  ) AND
398
659
  NEW.source = 'ctx://message/' || NEW.message_id AND
399
660
  length(CAST(NEW.rendered_content AS BLOB)) = NEW.rendered_bytes AND
400
- NEW.rendered_bytes < NEW.original_bytes
661
+ (NEW.renderer_format <> 'swap-observation-v1' OR NEW.rendered_bytes < NEW.original_bytes)
401
662
  )
402
663
  BEGIN SELECT RAISE(ABORT, 'invalid context override insert'); END`,
403
664
  },
@@ -519,10 +780,16 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
519
780
  NEW.open_count >= OLD.open_count AND
520
781
  ((OLD.initialization_state = 'creating' AND NEW.initialization_state IN ('creating', 'ready')) OR
521
782
  (OLD.initialization_state = 'ready' AND NEW.initialization_state = 'ready')) AND
522
- (OLD.tool_schema_sha256 IS NULL OR NEW.tool_schema_sha256 = OLD.tool_schema_sha256) AND
523
- (OLD.runtime_contract_json IS NULL OR NEW.runtime_contract_json = OLD.runtime_contract_json) AND
524
- (OLD.runtime_contract_sha256 IS NULL OR NEW.runtime_contract_sha256 = OLD.runtime_contract_sha256) AND
525
- (OLD.active_revision_id = NEW.active_revision_id OR EXISTS (
783
+ (OLD.session_compatibility_json IS NULL OR NEW.session_compatibility_json = OLD.session_compatibility_json) AND
784
+ (OLD.session_compatibility_sha256 IS NULL OR NEW.session_compatibility_sha256 = OLD.session_compatibility_sha256) AND
785
+ ((OLD.active_revision_id IS NULL AND NEW.active_revision_id IS NOT NULL AND
786
+ OLD.initialization_state = 'creating' AND NEW.initialization_state = 'ready' AND
787
+ EXISTS (
788
+ SELECT 1 FROM context_revisions initial
789
+ WHERE initial.revision_id = NEW.active_revision_id
790
+ AND initial.session_id = OLD.session_id
791
+ AND initial.kind = 'initial_full'
792
+ )) OR OLD.active_revision_id = NEW.active_revision_id OR EXISTS (
526
793
  SELECT 1
527
794
  FROM context_revisions previous
528
795
  JOIN context_revisions next
@@ -532,12 +799,36 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
532
799
  WHERE previous.revision_id = OLD.active_revision_id
533
800
  AND next.revision_id = NEW.active_revision_id
534
801
  AND next.session_id = OLD.session_id
535
- AND next.kind = 'swap_only'
802
+ AND next.kind IN ('swap_only', 'surface_refresh', 'prefix_retirement', 'skills_update')
536
803
  AND next.added_override_count = (
537
804
  SELECT COUNT(*) FROM context_overrides
538
805
  WHERE introduced_revision_id = next.revision_id
539
806
  )
540
- AND next.total_override_count = previous.total_override_count + next.added_override_count
807
+ AND ((next.kind = 'swap_only' AND
808
+ next.keep_from_ordinal = previous.keep_from_ordinal AND
809
+ next.active_override_count = previous.active_override_count + next.added_override_count AND
810
+ next.surface_id = previous.surface_id) OR
811
+ (next.kind = 'surface_refresh' AND
812
+ next.keep_from_ordinal = previous.keep_from_ordinal AND
813
+ next.active_override_count = previous.active_override_count AND
814
+ next.added_override_count = 0 AND
815
+ next.active_override_manifest_sha256 = previous.active_override_manifest_sha256 AND
816
+ next.surface_id <> previous.surface_id) OR
817
+ (next.kind = 'skills_update' AND
818
+ next.keep_from_ordinal = previous.keep_from_ordinal AND
819
+ next.active_override_count = previous.active_override_count + next.added_override_count AND
820
+ next.surface_sha256 = (SELECT surface_sha256 FROM context_surfaces WHERE surface_id = next.surface_id)) OR
821
+ (next.kind = 'prefix_retirement' AND
822
+ next.keep_from_ordinal > previous.keep_from_ordinal AND
823
+ next.added_override_count = 0 AND
824
+ next.surface_id = previous.surface_id AND
825
+ next.active_override_count = (
826
+ SELECT COUNT(*) FROM context_overrides co
827
+ JOIN context_revisions introduced
828
+ ON introduced.revision_id = co.introduced_revision_id
829
+ WHERE introduced.revision_number <= previous.revision_number
830
+ AND co.ordinal >= next.keep_from_ordinal
831
+ )))
541
832
  AND NOT EXISTS (SELECT 1 FROM context_measurement_state)
542
833
  ))
543
834
  )
@@ -545,7 +836,7 @@ const schemaDefinitions: readonly SchemaDefinition[] = [
545
836
  },
546
837
  ];
547
838
 
548
- export const SESSION_SCHEMA_V5_FINGERPRINT = sha256(
839
+ export const SESSION_SCHEMA_V9_FINGERPRINT = sha256(
549
840
  stableJsonStringify({
550
841
  definitions: schemaDefinitions.map((definition) => ({
551
842
  type: definition.type,
@@ -575,7 +866,30 @@ export function createSessionSchema(database: Database): void {
575
866
  database.exec(`PRAGMA application_id = ${SESSION_APPLICATION_ID}`);
576
867
  database.exec(`PRAGMA user_version = ${SESSION_SCHEMA_VERSION}`);
577
868
  for (const definition of schemaDefinitions) {
578
- database.exec(definition.sql);
869
+ try {
870
+ database.exec(definition.sql);
871
+ } catch (error) {
872
+ throw new Error(
873
+ `Failed to create session schema ${definition.type} ${definition.name}.`,
874
+ { cause: error },
875
+ );
876
+ }
877
+ }
878
+ }
879
+
880
+ export function dropSessionCloneTriggers(database: Database): void {
881
+ for (const definition of schemaDefinitions) {
882
+ if (definition.type === "trigger") {
883
+ database.exec(`DROP TRIGGER ${definition.name}`);
884
+ }
885
+ }
886
+ }
887
+
888
+ export function reinstallSessionCloneTriggers(database: Database): void {
889
+ for (const definition of schemaDefinitions) {
890
+ if (definition.type === "trigger") {
891
+ database.exec(definition.sql);
892
+ }
579
893
  }
580
894
  }
581
895
 
@@ -648,7 +962,7 @@ export function verifySessionSchema(database: Database, sessionId?: SessionId):
648
962
  throw new SessionError(
649
963
  "SESSION_SCHEMA_INVALID",
650
964
  "verify_schema",
651
- "Session FTS configuration does not match schema v5.",
965
+ "Session FTS configuration does not match schema v9.",
652
966
  { sessionId },
653
967
  );
654
968
  }