@cortexkit/opencode-magic-context 0.46.1 → 0.47.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 (203) hide show
  1. package/dist/auto-search-worker.js +115 -0
  2. package/dist/config/index.d.ts.map +1 -1
  3. package/dist/config/live-run-config.d.ts.map +1 -1
  4. package/dist/config/migrate-config-location.d.ts.map +1 -1
  5. package/dist/config/schema/magic-context.d.ts +11 -3
  6. package/dist/config/schema/magic-context.d.ts.map +1 -1
  7. package/dist/{conflict-warning-hook-7fj2b9jw.js → conflict-warning-hook-z44ynfz7.js} +5 -4
  8. package/dist/{context-sh2m2vde.js → context-wq4ffwyb.js} +108 -23
  9. package/dist/{derive-budgets-7nj7r1rv.js → derive-budgets-ke12jnnh.js} +1 -1
  10. package/dist/embedding-worker.js +2 -2
  11. package/dist/features/magic-context/dreamer/task-gates.d.ts.map +1 -1
  12. package/dist/features/magic-context/overflow-detection.d.ts +2 -0
  13. package/dist/features/magic-context/overflow-detection.d.ts.map +1 -1
  14. package/dist/features/magic-context/project-embedding-registry.d.ts +7 -0
  15. package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
  16. package/dist/features/magic-context/search.d.ts +2 -0
  17. package/dist/features/magic-context/search.d.ts.map +1 -1
  18. package/dist/features/magic-context/session-project-storage.d.ts +8 -2
  19. package/dist/features/magic-context/session-project-storage.d.ts.map +1 -1
  20. package/dist/features/magic-context/storage-historian-runs.d.ts +2 -5
  21. package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
  22. package/dist/features/magic-context/storage-meta-persisted.d.ts +12 -0
  23. package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
  24. package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
  25. package/dist/features/magic-context/storage-tags.d.ts +7 -2
  26. package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
  27. package/dist/features/magic-context/storage.d.ts +1 -0
  28. package/dist/features/magic-context/storage.d.ts.map +1 -1
  29. package/dist/features/magic-context/transform-decision-log.d.ts +3 -1
  30. package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
  31. package/dist/hooks/magic-context/active-anthropic-turn.d.ts +8 -0
  32. package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -0
  33. package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
  34. package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts +2 -0
  35. package/dist/hooks/magic-context/auto-search-blocking.fixture.d.ts.map +1 -0
  36. package/dist/hooks/magic-context/auto-search-deadline.d.ts +7 -0
  37. package/dist/hooks/magic-context/auto-search-deadline.d.ts.map +1 -1
  38. package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts +3 -0
  39. package/dist/hooks/magic-context/auto-search-host-review.fixture.d.ts.map +1 -0
  40. package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts +2 -0
  41. package/dist/hooks/magic-context/auto-search-lock-review.fixture.d.ts.map +1 -0
  42. package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts +2 -0
  43. package/dist/hooks/magic-context/auto-search-long-lock-review.fixture.d.ts.map +1 -0
  44. package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts +4 -0
  45. package/dist/hooks/magic-context/auto-search-omp-host-review.fixture.d.ts.map +1 -0
  46. package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts +3 -0
  47. package/dist/hooks/magic-context/auto-search-omp-loader.fixture.d.ts.map +1 -0
  48. package/dist/hooks/magic-context/auto-search-runner.d.ts +1 -1
  49. package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
  50. package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts +4 -0
  51. package/dist/hooks/magic-context/auto-search-snapshot.fixture.d.ts.map +1 -0
  52. package/dist/hooks/magic-context/auto-search-worker-client.d.ts +69 -0
  53. package/dist/hooks/magic-context/auto-search-worker-client.d.ts.map +1 -0
  54. package/dist/hooks/magic-context/auto-search-worker.d.ts +2 -0
  55. package/dist/hooks/magic-context/auto-search-worker.d.ts.map +1 -0
  56. package/dist/hooks/magic-context/caveman-cleanup.d.ts.map +1 -1
  57. package/dist/hooks/magic-context/compartment-runner-types.d.ts +3 -5
  58. package/dist/hooks/magic-context/compartment-runner-types.d.ts.map +1 -1
  59. package/dist/hooks/magic-context/compartment-runner.d.ts +2 -1
  60. package/dist/hooks/magic-context/compartment-runner.d.ts.map +1 -1
  61. package/dist/hooks/magic-context/compartment-trigger.d.ts +3 -1
  62. package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
  63. package/dist/hooks/magic-context/event-handler.d.ts +1 -1
  64. package/dist/hooks/magic-context/event-handler.d.ts.map +1 -1
  65. package/dist/hooks/magic-context/event-resolvers.d.ts.map +1 -1
  66. package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
  67. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts +2 -0
  68. package/dist/hooks/magic-context/final-wire-token-estimate.d.ts.map +1 -1
  69. package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
  70. package/dist/hooks/magic-context/historian-drain-gate.d.ts +11 -0
  71. package/dist/hooks/magic-context/historian-drain-gate.d.ts.map +1 -0
  72. package/dist/hooks/magic-context/hook.d.ts +2 -1
  73. package/dist/hooks/magic-context/hook.d.ts.map +1 -1
  74. package/dist/hooks/magic-context/latest-assistant-turn.d.ts +25 -0
  75. package/dist/hooks/magic-context/latest-assistant-turn.d.ts.map +1 -0
  76. package/dist/hooks/magic-context/latest-thinking-recovery.d.ts +61 -0
  77. package/dist/hooks/magic-context/latest-thinking-recovery.d.ts.map +1 -0
  78. package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
  79. package/dist/hooks/magic-context/opencode-proven-floor.d.ts +6 -0
  80. package/dist/hooks/magic-context/opencode-proven-floor.d.ts.map +1 -0
  81. package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
  82. package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
  83. package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
  84. package/dist/hooks/magic-context/reasoning-budget-status.d.ts +15 -0
  85. package/dist/hooks/magic-context/reasoning-budget-status.d.ts.map +1 -0
  86. package/dist/hooks/magic-context/reasoning-budget.d.ts +40 -0
  87. package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -0
  88. package/dist/hooks/magic-context/reasoning-removal.d.ts +6 -6
  89. package/dist/hooks/magic-context/reasoning-removal.d.ts.map +1 -1
  90. package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
  91. package/dist/hooks/magic-context/sentinel.d.ts +4 -0
  92. package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
  93. package/dist/hooks/magic-context/strip-content.d.ts +7 -3
  94. package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
  95. package/dist/hooks/magic-context/tag-messages.d.ts +13 -0
  96. package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
  97. package/dist/hooks/magic-context/tool-drop-target.d.ts +5 -0
  98. package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
  99. package/dist/hooks/magic-context/transform-compartment-phase.d.ts +3 -28
  100. package/dist/hooks/magic-context/transform-compartment-phase.d.ts.map +1 -1
  101. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +28 -6
  102. package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
  103. package/dist/hooks/magic-context/transform.d.ts +3 -1
  104. package/dist/hooks/magic-context/transform.d.ts.map +1 -1
  105. package/dist/{index-0d3g3461.js → index-0c82743p.js} +2 -1
  106. package/dist/{index-5p8jdswx.js → index-5a2r0p9f.js} +3 -3
  107. package/dist/{index-sjthx78w.js → index-6d9e1m08.js} +4647 -11000
  108. package/dist/{index-majxda46.js → index-6wkcswft.js} +1 -1
  109. package/dist/index-92bp57hg.js +2063 -0
  110. package/dist/{index-sfnj6h8h.js → index-9pk9exrb.js} +3 -3
  111. package/dist/{index-7qw742ev.js → index-adwfkpex.js} +5 -24
  112. package/dist/{index-3h0ybwah.js → index-bb36vgr3.js} +80 -12
  113. package/dist/{index-pkbcrdqs.js → index-cwf6s3ay.js} +1072 -2422
  114. package/dist/index-fnn5pjtt.js +6554 -0
  115. package/dist/{index-sgthv3f7.js → index-gg8j07zz.js} +1 -1
  116. package/dist/{index-8x2v7gg9.js → index-m5fe6jch.js} +7 -3
  117. package/dist/{v2/server-g0fjrkta.js → index-rrbbspwh.js} +42 -27
  118. package/dist/index-rrrsepyw.js +3464 -0
  119. package/dist/{index-hpecdzec.js → index-w2t4ah5j.js} +948 -501
  120. package/dist/{v2/server-fg34g6a5.js → index-wx8a6bw3.js} +48 -8
  121. package/dist/{index-r5hmbx3t.js → index-x3kwrmqk.js} +146 -3571
  122. package/dist/index-yr3zj7e7.js +23 -0
  123. package/dist/index.d.ts.map +1 -1
  124. package/dist/index.js +624 -65
  125. package/dist/{message-time-backfill-68gj6t3e.js → message-time-backfill-r5epkzj3.js} +1 -1
  126. package/dist/migration-worker.js +7 -5
  127. package/dist/plugin/hooks/create-session-hooks.d.ts +2 -1
  128. package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
  129. package/dist/plugin/host-stall-profiler-report.d.ts +67 -0
  130. package/dist/plugin/host-stall-profiler-report.d.ts.map +1 -0
  131. package/dist/plugin/host-stall-profiler.d.ts +96 -0
  132. package/dist/plugin/host-stall-profiler.d.ts.map +1 -0
  133. package/dist/plugin/messages-transform.d.ts +21 -6
  134. package/dist/plugin/messages-transform.d.ts.map +1 -1
  135. package/dist/{read-session-chunk-egz7r68d.js → read-session-chunk-4jey7hpt.js} +4 -4
  136. package/dist/{recomp-orchestrator-y15j0rb9.js → recomp-orchestrator-rervg6q4.js} +13 -10
  137. package/dist/{send-session-notification-dyw42dtr.js → send-session-notification-0m68ms3n.js} +4 -3
  138. package/dist/shared/auto-search-lifecycle.d.ts +7 -0
  139. package/dist/shared/auto-search-lifecycle.d.ts.map +1 -0
  140. package/dist/shared/historian-tool-expansions.d.ts +1 -1
  141. package/dist/shared/historian-tool-expansions.d.ts.map +1 -1
  142. package/dist/shared/historian-tool-template.d.ts +2 -2
  143. package/dist/shared/historian-tool-template.d.ts.map +1 -1
  144. package/dist/shared/sqlite.d.ts +16 -1
  145. package/dist/shared/sqlite.d.ts.map +1 -1
  146. package/dist/shared/status-view.d.ts.map +1 -1
  147. package/dist/shared/window-geometry.d.ts +4 -0
  148. package/dist/shared/window-geometry.d.ts.map +1 -1
  149. package/dist/tools/ctx-expand/render.d.ts.map +1 -1
  150. package/dist/{transform-postprocess-phase-bqhz6vdr.js → transform-postprocess-phase-z5hzpsxv.js} +13 -10
  151. package/dist/v2/auto-search-worker.js +115 -0
  152. package/dist/v2/{context-zecmzk5s.js → context-14v1hr8b.js} +3783 -4668
  153. package/dist/v2/{derive-budgets-kdgp4s3r.js → derive-budgets-nc9avxz8.js} +2 -2
  154. package/dist/v2/embedding-worker.js +3 -3
  155. package/dist/v2/hidden-completion.d.ts.map +1 -1
  156. package/dist/v2/hooks/context.d.ts.map +1 -1
  157. package/dist/v2/{message-time-backfill-r6qkem1x.js → message-time-backfill-1f9tzvcs.js} +1 -1
  158. package/dist/v2/migration-worker.js +7 -5
  159. package/dist/v2/{read-session-chunk-8vjp6581.js → read-session-chunk-gyyv7w2r.js} +4 -4
  160. package/dist/v2/{recomp-orchestrator-8ejxvrzz.js → recomp-orchestrator-42vjba7p.js} +17 -14
  161. package/dist/v2/{send-session-notification-qs0j78v6.js → send-session-notification-c0wtscea.js} +5 -4
  162. package/dist/v2/server-3trjk1hx.js +6360 -0
  163. package/dist/v2/{server-e29d7p7c.js → server-50hvkg3v.js} +7 -3
  164. package/dist/v2/{server-y8maxeft.js → server-cpepxy2q.js} +2 -1
  165. package/dist/v2/server-d0sraw7m.js +3403 -0
  166. package/dist/v2/server-e0c1b7a7.js +2063 -0
  167. package/dist/v2/{server-mcfawjgc.js → server-efhwzx05.js} +5 -24
  168. package/dist/v2/{server-3n4q9xme.js → server-fcdehk1s.js} +1 -1
  169. package/dist/{index-q4yp4b52.js → v2/server-fq7kq32k.js} +42 -27
  170. package/dist/v2/{server-jc7y02q0.js → server-nf82q6pt.js} +1117 -7290
  171. package/dist/v2/{server-frbm99q5.js → server-njt3np1k.js} +99 -3465
  172. package/dist/v2/{server-qp5hx1ad.js → server-nm9n3aqq.js} +107 -12
  173. package/dist/v2/{server-c5gz8wph.js → server-rz514f14.js} +7 -2
  174. package/dist/v2/{server-wvjvfhck.js → server-vd4e7174.js} +1 -1
  175. package/dist/{index-fg34g6a5.js → v2/server-wx8a6bw3.js} +48 -8
  176. package/dist/v2/{server-xa4xe3rv.js → server-y10vbq6y.js} +1 -1
  177. package/dist/v2/server-yr3zj7e7.js +23 -0
  178. package/dist/v2/server.js +5 -5
  179. package/dist/v2/{wrapup-orchestrator-0snhvwcv.js → wrapup-orchestrator-nbz854vc.js} +16 -13
  180. package/dist/wrapup-orchestrator-b2dztrar.js +29 -0
  181. package/package.json +3 -3
  182. package/src/config/index.ts +6 -0
  183. package/src/config/live-run-config.test.ts +37 -1
  184. package/src/config/live-run-config.ts +28 -0
  185. package/src/config/migrate-config-location.ts +10 -3
  186. package/src/config/schema/magic-context.test.ts +4 -3
  187. package/src/config/schema/magic-context.ts +18 -6
  188. package/src/shared/auto-search-lifecycle.ts +27 -0
  189. package/src/shared/historian-tool-defaults.json +6 -6
  190. package/src/shared/historian-tool-expansions.ts +65 -8
  191. package/src/shared/historian-tool-preview-defaults.json +13 -0
  192. package/src/shared/historian-tool-template.ts +48 -14
  193. package/src/shared/sqlite-640-review-r2.test.ts +136 -0
  194. package/src/shared/sqlite-640-review.test.ts +183 -0
  195. package/src/shared/sqlite-acquisition-routing.test.ts +18 -0
  196. package/src/shared/sqlite.ts +84 -14
  197. package/src/shared/status-view.test.ts +6 -1
  198. package/src/shared/status-view.ts +15 -1
  199. package/src/shared/tag-transcript.ts +6 -0
  200. package/src/shared/window-geometry.ts +10 -0
  201. package/dist/wrapup-orchestrator-39amhv9s.js +0 -26
  202. /package/dist/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
  203. /package/dist/v2/{index-en1k8h8m.js → index-5prbx026.js} +0 -0
@@ -0,0 +1,183 @@
1
+ import { expect, spyOn, test } from "bun:test";
2
+ import { openDatabase } from "../features/magic-context/storage-db";
3
+ import { getOrCreateSessionMeta, updateSessionMeta } from "../features/magic-context/storage-meta";
4
+ import { recordToolDefinition } from "../features/magic-context/tool-definition-tokens";
5
+ import { captureLkgSlot } from "../hooks/magic-context/lkg-replay";
6
+ import { lkgReplayFits } from "../hooks/magic-context/lkg-replay-fit";
7
+ import { resetLkgSlotsForTest } from "../hooks/magic-context/lkg-slot";
8
+ import type { MessageLike } from "../hooks/magic-context/transform-operations";
9
+ import { createMessagesTransformHandler } from "../plugin/messages-transform";
10
+ import { refreshModelLimitsFromApi } from "./models-dev-cache";
11
+ import { Database, SqliteAcquisitionBusyError, withAsyncPrivilegedWriter } from "./sqlite";
12
+
13
+ function fixture() {
14
+ const db = new Database(":memory:");
15
+ db.exec("CREATE TABLE context_privilege_state(id INTEGER PRIMARY KEY, enabled INTEGER)");
16
+ db.exec("CREATE TABLE writes(value INTEGER)");
17
+ return db;
18
+ }
19
+
20
+ test("review: shared async admission rolls back callback busy without retrying side effects", async () => {
21
+ const db = fixture();
22
+ let callbacks = 0;
23
+ let recoveries = 0;
24
+ try {
25
+ await expect(
26
+ withAsyncPrivilegedWriter(
27
+ db,
28
+ () => {
29
+ callbacks++;
30
+ db.prepare("INSERT INTO writes VALUES (1)").run();
31
+ throw new SqliteAcquisitionBusyError(new Error("nested acquisition busy"));
32
+ },
33
+ {
34
+ beforeRetry: () => {
35
+ recoveries++;
36
+ },
37
+ },
38
+ ),
39
+ ).rejects.toThrow("SQLite writer acquisition remained busy");
40
+ expect(callbacks).toBe(1);
41
+ expect(recoveries).toBe(0);
42
+ expect(db.prepare("SELECT * FROM writes").all()).toEqual([]);
43
+ expect(db.inTransaction).toBe(false);
44
+ } finally {
45
+ db.close();
46
+ }
47
+ });
48
+
49
+ test("review: default OpenCode empty admission retries BEGIN only and preserves output bytes", async () => {
50
+ const db = fixture();
51
+ const exec = db.exec.bind(db);
52
+ let attempts = 0;
53
+ const spy = spyOn(db, "exec").mockImplementation((sql) => {
54
+ if (sql === "BEGIN IMMEDIATE" && ++attempts === 1)
55
+ throw Object.assign(new Error("busy"), { code: "SQLITE_BUSY" });
56
+ return exec(sql);
57
+ });
58
+ try {
59
+ const output = [{ role: "user", content: "§1§ managed input" }];
60
+ const before = JSON.stringify(output);
61
+ await withAsyncPrivilegedWriter(db, () => undefined);
62
+ expect(attempts).toBe(2);
63
+ expect(JSON.stringify(output)).toBe(before);
64
+ expect(db.inTransaction).toBe(false);
65
+ } finally {
66
+ spy.mockRestore();
67
+ db.close();
68
+ }
69
+ });
70
+
71
+ test("review: COMMIT busy after empty admission is surfaced once and transaction is released", async () => {
72
+ const db = fixture();
73
+ const exec = db.exec.bind(db);
74
+ let callbacks = 0;
75
+ let commits = 0;
76
+ const spy = spyOn(db, "exec").mockImplementation((sql) => {
77
+ if (sql === "COMMIT") {
78
+ commits++;
79
+ throw Object.assign(new Error("reader holds rollback-journal lock"), {
80
+ code: "SQLITE_BUSY",
81
+ });
82
+ }
83
+ return exec(sql);
84
+ });
85
+ try {
86
+ await expect(
87
+ withAsyncPrivilegedWriter(db, () => {
88
+ callbacks++;
89
+ }),
90
+ ).rejects.toThrow("reader holds rollback-journal lock");
91
+ expect(callbacks).toBe(1);
92
+ expect(commits).toBe(1);
93
+ expect(db.inTransaction).toBe(false);
94
+ } finally {
95
+ spy.mockRestore();
96
+ db.close();
97
+ }
98
+ });
99
+
100
+ test("review: OpenCode tries a valid saved request before backed-off writer retry", async () => {
101
+ resetLkgSlotsForTest();
102
+ const db = openDatabase();
103
+ if (!db) throw new Error("isolated test database unavailable");
104
+ const sessionId = "review-opencode-order";
105
+ const model = { providerID: "openai", modelID: "gpt-4.1" };
106
+ await refreshModelLimitsFromApi({
107
+ config: {
108
+ providers: async () => ({
109
+ data: {
110
+ providers: [
111
+ {
112
+ id: model.providerID,
113
+ models: {
114
+ [model.modelID]: { limit: { context: 1000000, output: 8192 } },
115
+ },
116
+ },
117
+ ],
118
+ },
119
+ }),
120
+ },
121
+ });
122
+ getOrCreateSessionMeta(db, sessionId);
123
+ updateSessionMeta(db, sessionId, { systemPromptTokens: 100 });
124
+ recordToolDefinition(model.providerID, model.modelID, undefined, "read", "read file", {
125
+ type: "object",
126
+ });
127
+ const messages = [
128
+ {
129
+ info: { id: "u0", role: "user", sessionID: sessionId, model },
130
+ parts: [{ type: "text", text: "old raw history" }],
131
+ },
132
+ ] as MessageLike[];
133
+ const managed = structuredClone(messages);
134
+ (managed[0].parts[0] as { text: string }).text = "saved managed prefix";
135
+ expect(
136
+ captureLkgSlot({
137
+ sessionId,
138
+ input: messages,
139
+ output: managed,
140
+ modelKey: "openai/gpt-4.1",
141
+ providerKey: "openai",
142
+ }),
143
+ ).toBe(true);
144
+ expect(
145
+ lkgReplayFits({
146
+ db,
147
+ sessionId,
148
+ messages: managed,
149
+ model,
150
+ modelKey: "openai/gpt-4.1",
151
+ systemPromptTokens: 100,
152
+ }).fits,
153
+ ).toBe(true);
154
+ const exec = db.exec.bind(db);
155
+ let attempts = 0;
156
+ let callbacks = 0;
157
+ const spy = spyOn(db, "exec").mockImplementation((sql) => {
158
+ if (sql === "BEGIN IMMEDIATE" && ++attempts === 1)
159
+ throw Object.assign(new Error("writer busy"), { code: "SQLITE_BUSY" });
160
+ return exec(sql);
161
+ });
162
+ try {
163
+ const output = { messages: structuredClone(messages) };
164
+ const handler = createMessagesTransformHandler({
165
+ magicContext: {
166
+ "experimental.chat.messages.transform": async (_input, result) => {
167
+ callbacks++;
168
+ (result.messages[0].parts[0] as { text: string }).text =
169
+ "recomputed after retry";
170
+ },
171
+ },
172
+ });
173
+ await handler({}, output as Parameters<typeof handler>[1]);
174
+ expect({ callbacks, attempts, messages: output.messages }).toEqual({
175
+ callbacks: 0,
176
+ attempts: 1,
177
+ messages: managed,
178
+ });
179
+ } finally {
180
+ spy.mockRestore();
181
+ resetLkgSlotsForTest();
182
+ }
183
+ });
@@ -4,6 +4,7 @@ import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
5
  import {
6
6
  Database,
7
+ SqliteAcquisitionBusyError,
7
8
  withAsyncPrivilegedWriter,
8
9
  withPrivilegedWriter,
9
10
  withSqliteTransformPass,
@@ -11,6 +12,23 @@ import {
11
12
  import { startSqliteWriteLocker } from "./sqlite-write-locker-test-support";
12
13
  import { createTestTempDirFromPath } from "./test-temp-dir";
13
14
 
15
+ test("async writer never retries callback acquisition errors after work began", async () => {
16
+ const db = new Database(":memory:");
17
+ db.exec("CREATE TABLE context_privilege_state(id INTEGER PRIMARY KEY, enabled INTEGER)");
18
+ let calls = 0;
19
+ try {
20
+ await expect(
21
+ withAsyncPrivilegedWriter(db, () => {
22
+ calls++;
23
+ throw new SqliteAcquisitionBusyError(new Error("nested BEGIN"));
24
+ }),
25
+ ).rejects.toThrow("acquisition remained busy");
26
+ expect(calls).toBe(1);
27
+ } finally {
28
+ db.close();
29
+ }
30
+ });
31
+
14
32
  for (const mode of ["default", "immediate", "exclusive", "literal"] as const) {
15
33
  test(`shared SQLite retries ${mode} acquisition before running any writes`, async () => {
16
34
  const dir = createTestTempDirFromPath(join(tmpdir(), "mc-routed-acquisition-"));
@@ -245,6 +245,7 @@ function installTransactionRouting(db: BetterSqlite3.Database, readonly: boolean
245
245
  configurable: true,
246
246
  writable: true,
247
247
  value: (sql: string) => {
248
+ if (/^\s*(?:COMMIT|END)\b/i.test(sql)) assertTransformWrite();
248
249
  if (/^\s*BEGIN\s+(?:IMMEDIATE|EXCLUSIVE)(?:\s+TRANSACTION)?\s*;?\s*$/i.test(sql)) {
249
250
  // A transaction that ended without passing through exec (for
250
251
  // example an automatic rollback) must not be timed as this one.
@@ -430,6 +431,18 @@ function buildNodeSqliteDatabaseClass(DatabaseSync: any): typeof BetterSqlite3 {
430
431
 
431
432
  export const Database: typeof BetterSqlite3 = TrackedDatabase;
432
433
 
434
+ /** The constructor path, without querying SQLite on the prompt thread. */
435
+ export function getSqliteDatabasePath(db: Database): string | null {
436
+ for (const connection of trackedSqliteConnections.values()) {
437
+ if (connection.reference.deref() === db) {
438
+ return connection.filename === ":memory:" || connection.filename === "<buffer>"
439
+ ? null
440
+ : connection.filename;
441
+ }
442
+ }
443
+ return null;
444
+ }
445
+
433
446
  function pragmaValue(db: BetterSqlite3.Database, name: string): unknown {
434
447
  const row = db.prepare(`PRAGMA ${name}`).get() as Record<string, unknown> | undefined;
435
448
  if (!row) return undefined;
@@ -617,7 +630,12 @@ export function prepareCachedStatement(db: Database, sql: string): Statement {
617
630
 
618
631
  const privilegeDepth = new WeakMap<Database, number>();
619
632
  const transformPassScope = new AsyncLocalStorage<
620
- { active: boolean; remainingWaitMs: number } | undefined
633
+ | {
634
+ active: boolean;
635
+ remainingWaitMs: number;
636
+ guard?: { assert(): void; remainingMs(): number };
637
+ }
638
+ | undefined
621
639
  >();
622
640
  const admissionScope = new AsyncLocalStorage<boolean>();
623
641
  const backgroundWriterScope = new AsyncLocalStorage<boolean>();
@@ -655,6 +673,17 @@ export function withoutSqliteTransformPass<T>(operation: () => T): T {
655
673
  return transformPassScope.run(undefined, operation);
656
674
  }
657
675
 
676
+ /** Attach the adapter's existing generation/cancellation/deadline guard to writes. */
677
+ export function guardSqliteTransformPass(guard: { assert(): void; remainingMs(): number }): void {
678
+ const lease = transformPassScope.getStore();
679
+ if (lease) lease.guard = guard;
680
+ }
681
+
682
+ /** Check the attached generation/cancellation/deadline guard before SQL or cache publication. */
683
+ export function assertTransformWrite(): void {
684
+ if (!backgroundWriterScope.getStore()) transformPassScope.getStore()?.guard?.assert();
685
+ }
686
+
658
687
  /** Bun names SQLite codes; node:sqlite exposes the numeric (possibly extended) errcode. */
659
688
  export function isTransientSqliteError(error: unknown): boolean {
660
689
  if (!error || typeof error !== "object") return false;
@@ -698,6 +727,7 @@ interface WriterAcquisition {
698
727
  startedAt: number;
699
728
  attempts: number;
700
729
  claimed: boolean;
730
+ remainingMs?: () => number;
701
731
  }
702
732
 
703
733
  /** A write transaction whose BEGIN went through `acquireShort` and has not ended yet. */
@@ -724,6 +754,7 @@ function foregroundWaitLease() {
724
754
  * native API cannot separate lock waiting from statement execution. Exhaustion
725
755
  * switches to zero-wait acquisition, not a refusal when the writer is free. */
726
756
  function withAutocommitTimeout<T>(db: Database, operation: () => T): T {
757
+ assertTransformWrite();
727
758
  const lease = foregroundWaitLease();
728
759
  if (isInTransaction(db) || (!lease && !backgroundWriterScope.getStore())) return operation();
729
760
  const previous = pragmaNumber(db, "busy_timeout");
@@ -749,6 +780,7 @@ function withAutocommitTimeout<T>(db: Database, operation: () => T): T {
749
780
 
750
781
  /** The connection is never handed back to another caller with a shortened timeout. */
751
782
  function acquireShort(db: Database, acquire: () => unknown, site: string): void {
783
+ assertTransformWrite();
752
784
  const started = performance.now();
753
785
  const previous = pragmaNumber(db, "busy_timeout");
754
786
  const outer = writerAcquisitionScope.getStore();
@@ -763,7 +795,12 @@ function acquireShort(db: Database, acquire: () => unknown, site: string): void
763
795
  // stays short. An ordinary BEGIN inside a transform pass cannot yield;
764
796
  // give that single attempt enough time for a brief writer to finish.
765
797
  const lease = foregroundWaitLease();
766
- const timeout = lease ? Math.floor(lease.remainingWaitMs) : SHORT_BUSY_TIMEOUT_MS;
798
+ const timeout = Math.floor(
799
+ Math.min(
800
+ lease ? lease.remainingWaitMs : SHORT_BUSY_TIMEOUT_MS,
801
+ owner?.remainingMs?.() ?? lease?.guard?.remainingMs() ?? Infinity,
802
+ ),
803
+ );
767
804
  db.exec(`PRAGMA busy_timeout=${timeout}`);
768
805
  acquire();
769
806
  acquiredAt = performance.now();
@@ -871,11 +908,28 @@ export async function beginSqliteWriterAsync(db: Database, site: string): Promis
871
908
  }
872
909
 
873
910
  /** Retry admission before any turn work, yielding between short lock attempts. */
874
- export async function withAsyncPrivilegedWriter<T>(db: Database, operation: () => T): Promise<T> {
911
+ export async function withAsyncPrivilegedWriter<T>(
912
+ db: Database,
913
+ operation: () => T,
914
+ options: {
915
+ signal?: AbortSignal;
916
+ beforeRetry?: (error: SqliteAcquisitionBusyError) => void;
917
+ jitter?: boolean;
918
+ budgetMs?: number;
919
+ assertCurrentPass?: () => void;
920
+ } = {},
921
+ ): Promise<T> {
875
922
  const started = performance.now();
923
+ const budgetMs = Math.max(
924
+ 0,
925
+ Math.min(FOREGROUND_ACQUISITION_BUDGET_MS, options.budgetMs ?? Infinity),
926
+ );
876
927
  let attempts = 0;
877
928
  for (;;) {
929
+ options.signal?.throwIfAborted();
930
+ options.assertCurrentPass?.();
878
931
  attempts++;
932
+ let entered = false;
879
933
  try {
880
934
  const acquisition: WriterAcquisition = {
881
935
  site: "privileged_writer",
@@ -883,35 +937,51 @@ export async function withAsyncPrivilegedWriter<T>(db: Database, operation: () =
883
937
  startedAt: started,
884
938
  attempts,
885
939
  claimed: false,
940
+ remainingMs: () => Math.max(0, budgetMs - (performance.now() - started)),
886
941
  };
887
942
  // The acquisition and hold of this transaction are reported when it
888
943
  // commits or rolls back (see endWriterTransaction).
889
944
  return admissionScope.run(true, () =>
890
945
  withSqliteTransformPass(() =>
891
946
  writerAcquisitionScope.run(acquisition, () =>
892
- withPrivilegedWriter(db, operation),
947
+ withPrivilegedWriter(db, () => {
948
+ entered = true;
949
+ return operation();
950
+ }),
893
951
  ),
894
952
  ),
895
953
  );
896
954
  } catch (error) {
897
955
  // A callback failure can occur after writes; only retry a failed BEGIN.
898
- if (!(error instanceof SqliteAcquisitionBusyError)) throw error;
956
+ if (entered || !(error instanceof SqliteAcquisitionBusyError)) throw error;
957
+ // Adapters may serve a validated saved request instead of waiting.
958
+ // This runs only after a failed BEGIN, never after callback writes.
959
+ options.beforeRetry?.(error);
899
960
  const elapsed = performance.now() - started;
900
- if (elapsed >= FOREGROUND_ACQUISITION_BUDGET_MS) {
961
+ if (elapsed >= budgetMs) {
901
962
  reportSqliteDiagnostic(
902
963
  `sqlite writer site=privileged_writer lane=foreground acquire_ms=${Math.round(elapsed)} attempts=${attempts} outcome=busy`,
903
964
  );
904
965
  throw error;
905
966
  }
906
- await new Promise<void>((resolve) =>
907
- setTimeout(
908
- resolve,
909
- Math.min(
910
- attempts === 1 ? 500 : 1000,
911
- FOREGROUND_ACQUISITION_BUDGET_MS - elapsed,
912
- ),
913
- ),
967
+ const backoff = attempts === 1 ? 500 : 1000;
968
+ const delay = Math.min(
969
+ options.jitter ? backoff * (0.8 + Math.random() * 0.4) : backoff,
970
+ budgetMs - elapsed,
914
971
  );
972
+ await new Promise<void>((resolve, reject) => {
973
+ const abort = () => {
974
+ clearTimeout(timer);
975
+ options.signal?.removeEventListener("abort", abort);
976
+ reject(options.signal?.reason ?? new Error("Writer wait aborted"));
977
+ };
978
+ const timer = setTimeout(() => {
979
+ options.signal?.removeEventListener("abort", abort);
980
+ resolve();
981
+ }, delay);
982
+ options.signal?.addEventListener("abort", abort, { once: true });
983
+ if (options.signal?.aborted) abort();
984
+ });
915
985
  }
916
986
  }
917
987
  }
@@ -118,7 +118,12 @@ describe("status view model", () => {
118
118
  expect(rowLabels("Tags")).toEqual(["Active", "Dropped", "Total"]);
119
119
  expect(rowLabels("Reductions")).toEqual(["Execute threshold", "Last reduce anchor"]);
120
120
  expect(rowLabels("Pending Queue")).toEqual(["Drops", "Marker"]);
121
- expect(rowLabels("Context Details")).toEqual(["Protected tags", "Subagent"]);
121
+ expect(rowLabels("Context Details")).toEqual([
122
+ "Protected tags",
123
+ "Subagent",
124
+ "Denominator",
125
+ "Window source",
126
+ ]);
122
127
  expect(rowLabels("Cache TTL")).toEqual([
123
128
  "Configured",
124
129
  "Last response",
@@ -31,7 +31,11 @@ import {
31
31
  type UserFacingFailureKey,
32
32
  userFacingFailureCode,
33
33
  } from "./user-facing-codes";
34
- import { formatWindowDerivationLine, type WindowGeometryResult } from "./window-geometry";
34
+ import {
35
+ formatWindowDerivationLine,
36
+ formatWindowSource,
37
+ type WindowGeometryResult,
38
+ } from "./window-geometry";
35
39
 
36
40
  /** Theme-independent colour role; each host maps these onto its own palette. */
37
41
  export type StatusTone = "accent" | "text" | "muted" | "warning" | "error";
@@ -656,6 +660,16 @@ function statusSections(source: StatusViewSource, now: number): StatusSection[]
656
660
  tone: "muted",
657
661
  },
658
662
  { label: "Subagent", value: source.isSubagent ? "yes" : "no", tone: "muted" },
663
+ {
664
+ label: "Denominator",
665
+ value: `${Math.round(source.contextLimit)} tokens`,
666
+ tone: "muted",
667
+ },
668
+ {
669
+ label: "Window source",
670
+ value: formatWindowSource(source.windowGeometry),
671
+ tone: "muted",
672
+ },
659
673
  ],
660
674
  },
661
675
  { title: "Cache TTL", labelWidth: 14, rows: cacheRows(source, now) },
@@ -1157,6 +1157,10 @@ function buildAggregateTarget(
1157
1157
  };
1158
1158
 
1159
1159
  return {
1160
+ mutationParts: occurrences.map((occ) => ({
1161
+ message: { info: occ.message.info, parts: [] },
1162
+ part: occ.part,
1163
+ })),
1160
1164
  measureReclaim(skeleton) {
1161
1165
  let beforeTools = 0;
1162
1166
  let afterTools = 0;
@@ -1319,6 +1323,7 @@ function buildTextTarget(
1319
1323
  textPrefix: string,
1320
1324
  ): TagTarget {
1321
1325
  return {
1326
+ mutationParts: [{ message: { info: message.info, parts: [] }, part }],
1322
1327
  textPrefix,
1323
1328
  setContent(content: string): boolean {
1324
1329
  return part.setText(content);
@@ -1351,6 +1356,7 @@ function buildToolTarget(
1351
1356
  tagId: number,
1352
1357
  ): TagTarget {
1353
1358
  return {
1359
+ mutationParts: [{ message: { info: message.info, parts: [] }, part }],
1354
1360
  setContent(content: string): boolean {
1355
1361
  return setToolContentOrText(part, content);
1356
1362
  },
@@ -89,6 +89,8 @@ export interface WindowDerivation {
89
89
  geometry: WindowGeometry;
90
90
  windowSource: WindowLimitSource;
91
91
  absoluteWall: number;
92
+ /** Accepted provider input used to raise the usable denominator above metadata. */
93
+ provenInputTokens?: number;
92
94
  }
93
95
 
94
96
  export interface WindowGeometryResult {
@@ -691,6 +693,7 @@ export function applyProvenInputFloor(
691
693
  absoluteWall: trustedAbsoluteWall
692
694
  ? geometry.derivation.absoluteWall
693
695
  : Math.max(geometry.derivation.absoluteWall, provenInputTokens),
696
+ provenInputTokens,
694
697
  },
695
698
  },
696
699
  };
@@ -715,6 +718,13 @@ export function formatWindowDerivationLine(
715
718
  return `${formatCompactTokens(inputTokens)} / ${formatCompactTokens(result.usableSoft)} usable · window ${formatCompactTokens(result.derivation.window)} · ${formatCompactTokens(result.derivation.reserve)} ${reserveLabel}`;
716
719
  }
717
720
 
721
+ /** Distinguish metadata from a denominator raised by an accepted input measurement. */
722
+ export function formatWindowSource(result: WindowGeometryResult | undefined): string {
723
+ return result?.derivation.provenInputTokens
724
+ ? `provider-measured floor (${result.derivation.windowSource})`
725
+ : (result?.derivation.windowSource ?? "unknown");
726
+ }
727
+
718
728
  export function formatCompactTokens(value: number): string {
719
729
  if (Math.abs(value) >= 1_000_000) {
720
730
  return `${(value / 1_000_000).toFixed(1).replace(/\.0$/, "")}m`;
@@ -1,26 +0,0 @@
1
- import {
2
- runManagedWrapup2
3
- } from "./index-5p8jdswx.js";
4
- import"./index-sgthv3f7.js";
5
- import"./index-sjthx78w.js";
6
- import"./index-sd3egckz.js";
7
- import"./index-7qw742ev.js";
8
- import"./index-0d3g3461.js";
9
- import"./index-8x2v7gg9.js";
10
- import"./index-r5hmbx3t.js";
11
- import"./index-3h0ybwah.js";
12
- import"./index-majxda46.js";
13
- import"./index-1zx87sdm.js";
14
- import"./index-q4yp4b52.js";
15
- import"./index-5gq3b2ya.js";
16
- import"./index-b35pxds2.js";
17
- import"./index-5eqk6z3s.js";
18
- import"./index-sj29b3z2.js";
19
- import"./index-fg34g6a5.js";
20
- import"./index-x8a7jmkh.js";
21
- import"./index-fzbvakh0.js";
22
- import"./index-hybnrwq0.js";
23
- import"./index-q6hef7fh.js";
24
- export {
25
- runManagedWrapup2 as runManagedWrapup
26
- };
File without changes