draken 0.6.0 → 0.8.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 (70) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/MEMORY.md +64 -13
  3. package/README.md +25 -19
  4. package/SETUP.md +26 -3
  5. package/TRAY.md +3 -3
  6. package/VOICE.md +8 -6
  7. package/dist/db/schema.d.ts.map +1 -1
  8. package/dist/db/schema.js +65 -0
  9. package/dist/db/schema.js.map +1 -1
  10. package/dist/memory/readiness.d.ts +4 -3
  11. package/dist/memory/readiness.d.ts.map +1 -1
  12. package/dist/memory/readiness.js +128 -35
  13. package/dist/memory/readiness.js.map +1 -1
  14. package/dist/memory/storage.d.ts.map +1 -1
  15. package/dist/memory/storage.js +1 -0
  16. package/dist/memory/storage.js.map +1 -1
  17. package/dist/routes/sync.d.ts.map +1 -1
  18. package/dist/routes/sync.js +9 -0
  19. package/dist/routes/sync.js.map +1 -1
  20. package/dist/sync/activity.d.ts +11 -1
  21. package/dist/sync/activity.d.ts.map +1 -1
  22. package/dist/sync/activity.js +18 -3
  23. package/dist/sync/activity.js.map +1 -1
  24. package/dist/sync/background.d.ts +7 -2
  25. package/dist/sync/background.d.ts.map +1 -1
  26. package/dist/sync/background.js +66 -19
  27. package/dist/sync/background.js.map +1 -1
  28. package/dist/sync/conversation.d.ts +20 -0
  29. package/dist/sync/conversation.d.ts.map +1 -0
  30. package/dist/sync/conversation.js +127 -0
  31. package/dist/sync/conversation.js.map +1 -0
  32. package/dist/sync/manager.d.ts.map +1 -1
  33. package/dist/sync/manager.js +56 -16
  34. package/dist/sync/manager.js.map +1 -1
  35. package/dist/sync/preparation.d.ts +1 -1
  36. package/dist/sync/preparation.d.ts.map +1 -1
  37. package/dist/sync/preparation.js +23 -5
  38. package/dist/sync/preparation.js.map +1 -1
  39. package/dist/sync/store.d.ts +4 -1
  40. package/dist/sync/store.d.ts.map +1 -1
  41. package/dist/sync/store.js +24 -7
  42. package/dist/sync/store.js.map +1 -1
  43. package/dist/sync/summary-preparation.d.ts +14 -0
  44. package/dist/sync/summary-preparation.d.ts.map +1 -0
  45. package/dist/sync/summary-preparation.js +205 -0
  46. package/dist/sync/summary-preparation.js.map +1 -0
  47. package/dist/sync/tree.d.ts +17 -1
  48. package/dist/sync/tree.d.ts.map +1 -1
  49. package/dist/sync/tree.js +94 -29
  50. package/dist/sync/tree.js.map +1 -1
  51. package/dist/sync/update-status.d.ts +9 -0
  52. package/dist/sync/update-status.d.ts.map +1 -0
  53. package/dist/sync/update-status.js +21 -0
  54. package/dist/sync/update-status.js.map +1 -0
  55. package/dist/sync/usage.d.ts +3 -1
  56. package/dist/sync/usage.d.ts.map +1 -1
  57. package/dist/sync/usage.js +24 -6
  58. package/dist/sync/usage.js.map +1 -1
  59. package/dist/types.d.ts +21 -1
  60. package/dist/types.d.ts.map +1 -1
  61. package/dist/voice/live-context.d.ts +1 -1
  62. package/dist/voice/live-context.d.ts.map +1 -1
  63. package/dist/voice/live-context.js +1 -1
  64. package/dist/voice/live.d.ts.map +1 -1
  65. package/dist/voice/live.js +265 -32
  66. package/dist/voice/live.js.map +1 -1
  67. package/package.json +1 -1
  68. package/public/assets/index-sHcfjDUD.js +131 -0
  69. package/public/index.html +1 -1
  70. package/public/assets/index-DXPfCm7z.js +0 -131
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.summaryPreparationEnabled = void 0;
4
+ exports.cancelSummaryPreparation = cancelSummaryPreparation;
5
+ exports.configureAutoSummary = configureAutoSummary;
6
+ exports.summaryPreparationSettings = summaryPreparationSettings;
7
+ exports.warmSummaryCache = warmSummaryCache;
8
+ const schema_1 = require("../db/schema");
9
+ const errors_1 = require("../errors");
10
+ const activity_1 = require("./activity");
11
+ const service_1 = require("../memory/service");
12
+ const store_1 = require("./store");
13
+ const tree_1 = require("./tree");
14
+ const normalize_1 = require("./normalize");
15
+ const BATCH = 64;
16
+ const FAILURE = 'Background summaries could not finish. Check provider settings, invocation limits and original session files, then explicitly enable summaries to retry. Cached work is retained.';
17
+ const setting = (key) => (0, schema_1.getDb)().prepare('SELECT value FROM sync_settings WHERE key=?').get(key)?.value;
18
+ function save(key, value) {
19
+ (0, schema_1.getDb)().prepare('INSERT INTO sync_settings(key,value) VALUES (?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value').run(key, value);
20
+ }
21
+ const summaryPreparationEnabled = () => setting('auto_summary') === 'true';
22
+ exports.summaryPreparationEnabled = summaryPreparationEnabled;
23
+ const indexingEnabled = () => setting('auto_index') === 'true';
24
+ const idle = () => activity_1.activity.backgroundIdle && !(0, service_1.historyProgress)().active && !(0, service_1.historyProgress)().queued;
25
+ let consentRevision = 0;
26
+ let active;
27
+ /** Cancellation is irrevocable for this job even if consent is immediately re-enabled. */
28
+ function cancelSummaryPreparation() {
29
+ const prior = active;
30
+ prior?.controller.abort();
31
+ // Return the owned promise, not a detached rejecting wrapper. The scheduler
32
+ // retains its rejection handler even when a synchronous setter only aborts.
33
+ return prior?.done || Promise.resolve();
34
+ }
35
+ /** Called by the scheduler's lifecycle owner; reads never start the scheduler. */
36
+ function configureAutoSummary(value) {
37
+ if (typeof value !== 'boolean')
38
+ throw new errors_1.HttpError(400, 'enabled must be a boolean');
39
+ if (value && !indexingEnabled())
40
+ throw new errors_1.HttpError(409, 'Enable automatic indexing before enabling paid background summaries.');
41
+ consentRevision++;
42
+ active?.controller.abort();
43
+ (0, schema_1.getDb)().transaction(() => {
44
+ save('auto_summary', String(value));
45
+ if (value)
46
+ save('auto_summary_error', '');
47
+ })();
48
+ }
49
+ function checkpoint(input) {
50
+ // Canonical recipe/input hashes only; no original text, paths or provider output.
51
+ // SQL sequence IDs can be reassigned by a full rescan; that does not turn
52
+ // unchanged original records into another paid batch. Receipt identity still
53
+ // includes full ordering, while this delta counts canonical source changes.
54
+ return { keys: input.items.map(item => [(0, normalize_1.digest)(JSON.stringify([item.agent || 'pi', item.session_id, item.entry_id])),
55
+ (0, normalize_1.digest)(JSON.stringify([(0, normalize_1.sourceKey)(item), item.agent, item.session_id, item.cwd, item.timestamp, item.role]))]),
56
+ recipe: (0, tree_1.summaryPreparationKey)({ ...input, items: [] }) };
57
+ }
58
+ function completed() {
59
+ return (0, schema_1.getDb)().prepare("SELECT id,input_key,source_keys_json,cache_receipt_json,total_entries,completed_at FROM background_runs WHERE status='completed' ORDER BY id DESC LIMIT 1").get();
60
+ }
61
+ function matching(key) {
62
+ return (0, schema_1.getDb)().prepare("SELECT id,input_key,source_keys_json,cache_receipt_json,total_entries,completed_at FROM background_runs WHERE status='completed' AND input_key=? ORDER BY id DESC LIMIT 1").get(key);
63
+ }
64
+ function cacheIntact(value) {
65
+ let pairs;
66
+ try {
67
+ pairs = typeof value === 'string' ? JSON.parse(value) : value;
68
+ }
69
+ catch {
70
+ return false;
71
+ }
72
+ if (!Array.isArray(pairs) || !pairs.length || pairs.some(pair => !Array.isArray(pair) || pair.length !== 2 || pair.some(hash => typeof hash !== 'string' || !/^[a-f0-9]{64}$/.test(hash))))
73
+ return false;
74
+ const lookup = (0, schema_1.getDb)().prepare('SELECT output_json FROM summary_cache WHERE key=?');
75
+ return pairs.every(([key, hash]) => {
76
+ const row = lookup.get(key);
77
+ return row !== undefined && (0, normalize_1.digest)(row.output_json) === hash;
78
+ });
79
+ }
80
+ /** Historical progress, not a live readiness claim. Polling must not reopen
81
+ * transcript bodies; the preparing owner and explicit sync validate originals. */
82
+ function summaryPreparationSettings() {
83
+ const enabled = (0, exports.summaryPreparationEnabled)(), error = setting('auto_summary_error') || '';
84
+ const last = (0, schema_1.getDb)().prepare("SELECT total_entries,completed_at FROM background_runs WHERE status='completed' ORDER BY id DESC LIMIT 1").get();
85
+ return { enabled, status: !enabled ? 'off' : error ? 'error' : !indexingEnabled() || !idle() || active?.controller.signal.aborted ? 'paused' : active?.working ? 'running' : 'waiting',
86
+ prepared_entries: last?.total_entries || 0, last_completed_at: last?.completed_at || null, error };
87
+ }
88
+ /** Prepare only reusable summaries. No sync identity, snapshot, briefing or verdict.
89
+ * A successful receipt suppresses repeated work; a failure blocks all automatic
90
+ * paid retries until explicit consent/retry. Cancellation retains its allowance. */
91
+ async function warmSummaryCache(signal, onWork) {
92
+ if (signal.aborted || !(0, exports.summaryPreparationEnabled)() || !indexingEnabled() || !idle() || setting('auto_summary_error'))
93
+ return;
94
+ if (active)
95
+ return active.done;
96
+ const controller = new AbortController();
97
+ const abort = () => controller.abort();
98
+ signal.addEventListener('abort', abort, { once: true });
99
+ const revision = consentRevision;
100
+ const owner = { controller, done: Promise.resolve(), working: false };
101
+ active = owner;
102
+ owner.done = Promise.resolve().then(async () => {
103
+ let run;
104
+ const admitted = () => !controller.signal.aborted && (0, exports.summaryPreparationEnabled)() && indexingEnabled() && idle();
105
+ const check = () => { if (!admitted()) {
106
+ controller.abort();
107
+ throw new errors_1.HttpError(409, 'Background preparation paused');
108
+ } };
109
+ try {
110
+ check();
111
+ const db = (0, schema_1.getDb)();
112
+ // Startup recovery is responsible for interrupted ownership. A running row
113
+ // with no process-local job can also mean failure metadata could not be
114
+ // committed. Never silently replace that run with a fresh allowance.
115
+ const orphan = db.prepare("SELECT id,invocations,max_invocations FROM background_runs WHERE status='running' LIMIT 1").get();
116
+ if (orphan) {
117
+ run = orphan;
118
+ throw new Error('Background ownership was not recovered');
119
+ }
120
+ const input = (0, store_1.previewSummaryInput)();
121
+ (0, store_1.assertEvidenceAvailable)(input);
122
+ const key = (0, tree_1.summaryPreparationKey)(input);
123
+ const receipt = matching(key);
124
+ if (receipt && cacheIntact(receipt.cache_receipt_json))
125
+ return;
126
+ const sources = checkpoint(input), last = completed();
127
+ let prior;
128
+ if (last) {
129
+ prior = JSON.parse(last.source_keys_json);
130
+ if (!Array.isArray(prior.keys) || prior.keys.some(pair => !Array.isArray(pair) || pair.length !== 2 || pair.some(value => typeof value !== 'string')) || typeof prior.recipe !== 'string')
131
+ throw new Error('Invalid receipt metadata');
132
+ }
133
+ const old = new Map(prior?.keys), current = new Map(sources.keys);
134
+ const changed = sources.keys.filter(([id, hash]) => old.get(id) !== hash).length + [...old.keys()].filter(id => !current.has(id)).length;
135
+ // Model/recipe/context changes need a fresh full receipt even when no IDs arrived.
136
+ if (input.items.length < BATCH || (!receipt && changed < BATCH && (!prior || prior.recipe === sources.recipe)))
137
+ return;
138
+ check();
139
+ run = db.prepare(`SELECT id,invocations,max_invocations FROM background_runs WHERE input_key=?
140
+ AND status IN ('cancelled','interrupted') ORDER BY id DESC LIMIT 1`).get(key);
141
+ // Never resurrect an older interrupted allowance after an explicitly retried attempt.
142
+ const latest = db.prepare('SELECT id FROM background_runs WHERE input_key=? ORDER BY id DESC LIMIT 1').get(key);
143
+ if (run && latest?.id !== run.id)
144
+ run = undefined;
145
+ if (run) {
146
+ db.prepare("UPDATE background_runs SET status='running',stage='Planning',processed_entries=0,cache_hits=0,error='',completed_at=NULL WHERE id=?").run(run.id);
147
+ input.models = { ...input.models, max_invocations: run.max_invocations };
148
+ }
149
+ else {
150
+ const result = db.prepare(`INSERT INTO background_runs(summary_model,final_model,max_invocations,total_entries,started_at,input_key,source_keys_json)
151
+ VALUES (?,?,?,?,?,?,?)`).run(input.models.summary, input.models.final, input.models.max_invocations, input.items.length, new Date().toISOString(), key, JSON.stringify(sources));
152
+ run = { id: Number(result.lastInsertRowid), invocations: 0, max_invocations: input.models.max_invocations };
153
+ }
154
+ owner.working = true;
155
+ onWork?.();
156
+ const tree = tree_1.SummaryTree.forBackground(input, run.id, controller.signal);
157
+ // Recheck at the last boundary before *each* provider call, including repairs
158
+ // and reconciliation after an await. Route-level yielding also drains this owner.
159
+ const call = tree.call.bind(tree);
160
+ tree.call = async (payload, system, final = false) => {
161
+ check();
162
+ if (final)
163
+ throw new Error('Background preparation cannot request a verdict');
164
+ return call(payload, system, false);
165
+ };
166
+ await tree.build();
167
+ check();
168
+ // Inputs can change while a provider is in flight. Keep useful cache fragments,
169
+ // but never publish a receipt for sources/models no longer admitted or available.
170
+ const latestInput = (0, store_1.previewSummaryInput)();
171
+ (0, store_1.assertEvidenceAvailable)(latestInput);
172
+ if ((0, tree_1.summaryPreparationKey)(latestInput) !== key) {
173
+ controller.abort();
174
+ throw new Error('Input changed');
175
+ }
176
+ const cacheReceipt = tree.cacheReceipt();
177
+ if (!cacheIntact(cacheReceipt))
178
+ throw new Error('Summary cache changed before completion');
179
+ db.prepare("UPDATE background_runs SET status='completed',stage='Cached',error='',completed_at=?,cache_receipt_json=? WHERE id=?")
180
+ .run(new Date().toISOString(), JSON.stringify(cacheReceipt), run.id);
181
+ }
182
+ catch {
183
+ const db = (0, schema_1.getDb)();
184
+ const counters = run && db.prepare('SELECT invocations,max_invocations FROM background_runs WHERE id=?').get(run.id);
185
+ const cancelled = controller.signal.aborted;
186
+ // Exhausted cancellations are also blocked: do not spin a quota 409 every minute.
187
+ const exhausted = Boolean(counters && counters.invocations >= counters.max_invocations);
188
+ const status = cancelled && !exhausted ? 'cancelled' : 'failed', at = new Date().toISOString();
189
+ db.transaction(() => {
190
+ if (run)
191
+ db.prepare(`UPDATE background_runs SET status=?,error=?,completed_at=?,
192
+ failure_events_json=json_insert(failure_events_json,'$[#]',json(?)) WHERE id=?`)
193
+ .run(status, cancelled && !exhausted ? '' : FAILURE, at, JSON.stringify({ status, at }), run.id);
194
+ if (revision === consentRevision && (!cancelled || exhausted))
195
+ save('auto_summary_error', FAILURE);
196
+ })();
197
+ }
198
+ }).finally(() => {
199
+ signal.removeEventListener('abort', abort);
200
+ if (active === owner)
201
+ active = undefined;
202
+ });
203
+ return owner.done;
204
+ }
205
+ //# sourceMappingURL=summary-preparation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"summary-preparation.js","sourceRoot":"","sources":["../../src/sync/summary-preparation.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,13 @@ export declare function repairSummaryCitations(candidate: NodeSummary, value: un
18
18
  characters: number;
19
19
  }): NodeSummary;
20
20
  export declare function validateGroups(value: unknown, allowed: Set<string>): TopicGroups;
21
+ /** Evidence and models only: background preparation does not own a sync. */
22
+ export type SummaryInput = Pick<SyncDetail, 'items' | 'context' | 'models'>;
23
+ declare const BACKGROUND: unique symbol;
24
+ /** A receipt identity, not a persisted copy of the evidence. Keep this recipe
25
+ * version in step with planning/validation/transport changes, even when old
26
+ * validated leaf cache keys intentionally remain compatible. */
27
+ export declare function summaryPreparationKey(input: SummaryInput): string;
21
28
  export declare class SummaryTree {
22
29
  private sync;
23
30
  private runId;
@@ -26,7 +33,15 @@ export declare class SummaryTree {
26
33
  private readonly sourceMap;
27
34
  private readonly primary;
28
35
  private readonly accountAggregation;
29
- constructor(sync: SyncDetail, runId: number, signal: AbortSignal);
36
+ private readonly runTable;
37
+ private readonly background;
38
+ private readonly cacheHashes;
39
+ static forBackground(input: SummaryInput, runId: number, signal: AbortSignal): SummaryTree;
40
+ constructor(sync: SummaryInput & {
41
+ id?: number;
42
+ }, runId: number, signal: AbortSignal, mode?: typeof BACKGROUND);
43
+ /** Hashes of the validated outputs consumed by this background build. */
44
+ cacheReceipt(): [string, string][];
30
45
  private stage;
31
46
  call(payload: unknown, system: string, final?: boolean): Promise<string>;
32
47
  private register;
@@ -66,4 +81,5 @@ export declare class SummaryTree {
66
81
  topic: string;
67
82
  }[];
68
83
  }
84
+ export {};
69
85
  //# sourceMappingURL=tree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/sync/tree.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAiDrG,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA4BD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM;;;;CAAgB,GAAG,WAAW,CAmBzG;AAED,iGAAiG;AACjG,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM;;;;CAAgB,GAAG,WAAW,CAiBxI;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,CAehF;AAoBD,qBAAa,WAAW;IAKV,OAAO,CAAC,IAAI;IAAc,OAAO,CAAC,KAAK;IAAU,OAAO,CAAC,MAAM;IAJ3E,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;gBACzB,IAAI,EAAE,UAAU,EAAU,KAAK,EAAE,MAAM,EAAU,MAAM,EAAE,WAAW;IAQxF,OAAO,CAAC,KAAK;IAKP,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAoC5E,OAAO,CAAC,QAAQ;IAIhB,OAAO,CAAC,IAAI;YAYE,IAAI;IAqElB,OAAO,CAAC,UAAU;IA0BlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,KAAK;YAYC,UAAU;YAeV,MAAM;IAQpB,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,UAAU;IAsClB,OAAO,CAAC,YAAY;YAKN,cAAc;YAed,YAAY;IA+BpB,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAkErC,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;CACpC"}
1
+ {"version":3,"file":"tree.d.ts","sourceRoot":"","sources":["../../src/sync/tree.ts"],"names":[],"mappings":"AACA,OAAO,EAA6B,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAiDrG,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,WAAW,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AA4BD,wBAAgB,eAAe,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM;;;;CAAgB,GAAG,WAAW,CAmBzG;AAED,iGAAiG;AACjG,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,WAAW,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,EAAE,MAAM;;;;CAAgB,GAAG,WAAW,CAiBxI;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,WAAW,CAehF;AAoBD,4EAA4E;AAC5E,MAAM,MAAM,YAAY,GAAG,IAAI,CAAC,UAAU,EAAE,OAAO,GAAG,SAAS,GAAG,QAAQ,CAAC,CAAC;AAC5E,QAAA,MAAM,UAAU,eAA2C,CAAC;AAE5D;;gEAEgE;AAChE,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,YAAY,GAAG,MAAM,CAajE;AAED,qBAAa,WAAW;IAaV,OAAO,CAAC,IAAI;IAAkC,OAAO,CAAC,KAAK;IAAU,OAAO,CAAC,MAAM;IAZ/F,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAW;IAC9B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAc;IACtC,OAAO,CAAC,QAAQ,CAAC,kBAAkB,CAAU;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkC;IAC3D,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAU;IACrC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAA6B;IAEzD,MAAM,CAAC,aAAa,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,WAAW;gBAItE,IAAI,EAAE,YAAY,GAAG;QAAE,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,EAAU,KAAK,EAAE,MAAM,EAAU,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,EAAE,OAAO,UAAU;IAWtI,yEAAyE;IACzE,YAAY,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAElC,OAAO,CAAC,KAAK;IAOP,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,OAAO,CAAC,MAAM,CAAC;IAsC5E,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,IAAI;YAYE,IAAI;IA4ElB,OAAO,CAAC,UAAU;IA0BlB,OAAO,CAAC,YAAY;IAIpB,OAAO,CAAC,KAAK;YAYC,UAAU;YAeV,MAAM;IAQpB,OAAO,CAAC,MAAM;IAUd,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,UAAU;IAoDlB,OAAO,CAAC,YAAY;YAKN,cAAc;YAed,YAAY;IA+BpB,KAAK,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;IAmErC,cAAc,CAAC,KAAK,EAAE,WAAW,EAAE;;;;;;;;;;;;;;;;;;;;;CACpC"}
package/dist/sync/tree.js CHANGED
@@ -4,6 +4,7 @@ exports.SummaryTree = void 0;
4
4
  exports.validateSummary = validateSummary;
5
5
  exports.repairSummaryCitations = repairSummaryCitations;
6
6
  exports.validateGroups = validateGroups;
7
+ exports.summaryPreparationKey = summaryPreparationKey;
7
8
  const schema_1 = require("../db/schema");
8
9
  const normalize_1 = require("./normalize");
9
10
  const store_1 = require("./store");
@@ -164,6 +165,24 @@ function pack(items, cost, maxChars, maxItems) {
164
165
  function sourceData(item, background = false) {
165
166
  return { ...(item.agent && item.agent !== 'pi' ? { agent: item.agent } : {}), id: (0, normalize_1.sourceKey)(item), workspace: item.cwd, timestamp: item.timestamp, sequence: item.sequence, role: item.role, background, text: (0, normalize_1.cleanText)(item.text, Number.MAX_SAFE_INTEGER) };
166
167
  }
168
+ const BACKGROUND = Symbol('background summary preparation');
169
+ /** A receipt identity, not a persisted copy of the evidence. Keep this recipe
170
+ * version in step with planning/validation/transport changes, even when old
171
+ * validated leaf cache keys intentionally remain compatible. */
172
+ function summaryPreparationKey(input) {
173
+ const sources = (items, background) => items.map(item => ({
174
+ session: item.session_id, label: item.session_name, ...sourceData(item, background),
175
+ }));
176
+ return (0, normalize_1.digest)(JSON.stringify({
177
+ recipe: 'summary-preparation-v1-stable-prefix',
178
+ prompts: { PROMPT_VERSION, legacySummarySystem, summarySystem, batchSummarySystem, citationRepairSystem, groupingSystem },
179
+ budgets: { CHUNK_CHARS, MERGE_CHARS, OUTPUT_CHARS, BATCH_OUTPUT, LEGACY_OUTPUT, MAX_EVIDENCE, LUNA_BATCH: summary_budget_1.LUNA_BATCH },
180
+ // Versions of the shared normalization, short-label transport and token estimator.
181
+ dependencies: ['normalization-v2', 'references-v2', 'o200k_base-gpt-tokenizer-4.1.0', 'prompt-headroom-1.2-8192'],
182
+ models: { summary: input.models.summary, final: input.models.final, max_invocations: input.models.max_invocations },
183
+ items: sources(input.items, false), context: sources(input.context, true),
184
+ }));
185
+ }
167
186
  class SummaryTree {
168
187
  sync;
169
188
  runId;
@@ -172,23 +191,39 @@ class SummaryTree {
172
191
  sourceMap;
173
192
  primary;
174
193
  accountAggregation;
175
- constructor(sync, runId, signal) {
194
+ runTable;
195
+ background;
196
+ cacheHashes = new Map();
197
+ static forBackground(input, runId, signal) {
198
+ return new SummaryTree(input, runId, signal, BACKGROUND);
199
+ }
200
+ constructor(sync, runId, signal, mode) {
176
201
  this.sync = sync;
177
202
  this.runId = runId;
178
203
  this.signal = signal;
204
+ this.background = mode === BACKGROUND;
205
+ this.runTable = this.background ? 'background_runs' : 'sync_runs';
206
+ if (!this.background && !Number.isSafeInteger(sync.id))
207
+ throw new Error('Foreground summary preparation requires a sync ID');
179
208
  (0, store_1.assertEvidenceAvailable)(sync);
180
- this.accountAggregation = this.db.prepare('SELECT question FROM sync_runs WHERE id=?').get(runId)?.question === null;
209
+ this.accountAggregation = this.background || this.db.prepare('SELECT question FROM sync_runs WHERE id=?').get(runId)?.question === null;
181
210
  this.sourceMap = new Map([...sync.context, ...sync.items].map(item => [(0, normalize_1.sourceKey)(item), item]));
182
211
  this.primary = new Set(sync.items.map(normalize_1.sourceKey));
183
212
  if (this.primary.size !== sync.items.length)
184
213
  throw new Error('Duplicate canonical source IDs in snapshot');
185
214
  }
186
- stage(stage) {
215
+ /** Hashes of the validated outputs consumed by this background build. */
216
+ cacheReceipt() { return [...this.cacheHashes]; }
217
+ stage(stage, detail) {
187
218
  if (this.signal.aborted)
188
219
  throw new errors_1.HttpError(409, 'Generation cancelled');
189
- this.db.prepare('UPDATE sync_runs SET stage = ? WHERE id = ?').run(stage, this.runId);
220
+ // Background accounting must never retain session titles, paths or model prose.
221
+ const label = !this.background && detail ? `${stage}: ${detail}` : stage;
222
+ this.db.prepare(`UPDATE ${this.runTable} SET stage = ? WHERE id = ?`).run(label, this.runId);
190
223
  }
191
224
  async call(payload, system, final = false) {
225
+ if (this.background && final)
226
+ throw new errors_1.HttpError(409, 'Background preparation cannot call the final provider');
192
227
  if (this.signal.aborted)
193
228
  throw new errors_1.HttpError(409, 'Generation cancelled');
194
229
  const model = final ? this.sync.models.final : this.sync.models.summary;
@@ -199,14 +234,14 @@ class SummaryTree {
199
234
  throw new errors_1.HttpError(400, 'Summary request exceeds its token safety budget; cached work is intact.');
200
235
  if (final && (0, summary_budget_1.isBatchSummaryModel)(this.sync.models.summary) && estimatedTokens > summary_budget_1.LUNA_BATCH.finalTokens)
201
236
  throw new errors_1.HttpError(400, 'The final input exceeds its compact token budget; no larger or more expensive request was substituted. Cached summaries are intact.');
202
- const limit = this.sync.models.max_invocations - (final ? 0 : 1);
237
+ const limit = this.sync.models.max_invocations - (final || this.background ? 0 : 1);
203
238
  const started = Date.now(), request = payload;
204
239
  const pending = { started_at: new Date(started).toISOString(), task: request.task || 'unknown', stage: request.stage || null,
205
240
  repair: Boolean(request.repair), model, elapsed_ms: 0, input_characters: prompt.length, input_tokens_estimate: estimatedTokens,
206
241
  output_characters: null, output_tokens_estimate: null, status: 'running' };
207
242
  // Reserve budget and the timestamped metadata slot atomically. An interrupted
208
243
  // request remains explicitly unpriced rather than disappearing from usage.
209
- const claimed = this.db.prepare(`UPDATE sync_runs SET invocations=invocations+1,
244
+ const claimed = this.db.prepare(`UPDATE ${this.runTable} SET invocations=invocations+1,
210
245
  metrics_json=json_insert(metrics_json,'$[#]',json(?)) WHERE id=? AND invocations<?
211
246
  RETURNING json_array_length(metrics_json)-1 AS metric_index`).get(JSON.stringify(pending), this.runId, limit);
212
247
  if (!claimed)
@@ -216,6 +251,8 @@ class SummaryTree {
216
251
  try {
217
252
  response = await (0, pi_1.invokePi)(prompt, { model, system, signal: this.signal, thinking: final ? 'medium' : 'off', largeSummary: large,
218
253
  onAccounting: this.accountAggregation ? value => { accounting = value; } : undefined });
254
+ if (this.signal.aborted)
255
+ throw new errors_1.HttpError(409, 'Generation cancelled');
219
256
  return response;
220
257
  }
221
258
  finally {
@@ -223,12 +260,14 @@ class SummaryTree {
223
260
  elapsed_ms: Date.now() - started, input_characters: prompt.length,
224
261
  input_tokens_estimate: estimatedTokens, output_characters: response?.length ?? null,
225
262
  output_tokens_estimate: response === undefined ? null : (0, summary_budget_1.summaryTokens)(response),
226
- status: response !== undefined ? 'completed' : this.signal.aborted ? 'cancelled' : 'failed' };
263
+ status: this.signal.aborted ? 'cancelled' : response !== undefined ? 'completed' : 'failed' };
227
264
  // Metadata only: no prompts, rejected prose, source text, paths or credentials.
228
- this.db.prepare('UPDATE sync_runs SET metrics_json=json_set(metrics_json,?,json(?)) WHERE id=?').run(`$[${claimed.metric_index}]`, JSON.stringify(metric), this.runId);
265
+ this.db.prepare(`UPDATE ${this.runTable} SET metrics_json=json_set(metrics_json,?,json(?)) WHERE id=?`).run(`$[${claimed.metric_index}]`, JSON.stringify(metric), this.runId);
229
266
  }
230
267
  }
231
268
  register(spec) {
269
+ if (this.background)
270
+ return;
232
271
  this.db.prepare(`INSERT OR IGNORE INTO sync_tree_nodes(sync_id, key, kind, label, children_json, source_count)
233
272
  VALUES (?, ?, ?, ?, ?, ?)`).run(this.sync.id, spec.key, spec.kind, spec.label, JSON.stringify(spec.children), spec.sources.length);
234
273
  }
@@ -247,13 +286,22 @@ class SummaryTree {
247
286
  async node(spec, validate, derived) {
248
287
  this.register(spec);
249
288
  const limits = spec.batch ? BATCH_OUTPUT : LEGACY_OUTPUT;
250
- const setStatus = (status, error = '') => this.db.prepare('UPDATE sync_tree_nodes SET status = ?, error = ? WHERE sync_id = ? AND key = ?').run(status, error, this.sync.id, spec.key);
289
+ const setStatus = (status, error = '') => {
290
+ if (!this.background)
291
+ this.db.prepare('UPDATE sync_tree_nodes SET status = ?, error = ? WHERE sync_id = ? AND key = ?').run(status, error, this.sync.id, spec.key);
292
+ };
251
293
  const finish = (cached) => {
294
+ if (this.background) {
295
+ const row = this.db.prepare('SELECT output_json FROM summary_cache WHERE key=?').get(spec.key);
296
+ if (!row)
297
+ throw new Error('A validated summary cache entry disappeared');
298
+ this.cacheHashes.set(spec.key, (0, normalize_1.digest)(row.output_json));
299
+ }
252
300
  setStatus(cached ? 'cached' : 'done');
253
- this.db.prepare('UPDATE sync_runs SET cache_hits = cache_hits + ?, processed_entries = processed_entries + ? WHERE id = ?')
301
+ this.db.prepare(`UPDATE ${this.runTable} SET cache_hits = cache_hits + ?, processed_entries = processed_entries + ? WHERE id = ?`)
254
302
  .run(Number(cached), spec.kind === 'chunk' ? spec.sources.length : 0, this.runId);
255
303
  };
256
- this.stage(`${spec.kind}: ${spec.label}`);
304
+ this.stage(spec.kind, spec.label);
257
305
  for (const cacheKey of [spec.key, spec.legacyKey].filter((key) => Boolean(key))) {
258
306
  const cache = this.db.prepare('SELECT output_json FROM summary_cache WHERE key = ?').get(cacheKey);
259
307
  if (!cache)
@@ -286,7 +334,7 @@ class SummaryTree {
286
334
  let parsed;
287
335
  for (let attempt = 0; attempt < 2; attempt++) {
288
336
  if (attempt)
289
- this.stage(`Repairing ${spec.kind}: ${spec.label} (attempt 2 of 2)`);
337
+ this.stage(`Repairing ${spec.kind}`, `${spec.label} (attempt 2 of 2)`);
290
338
  const citationRepair = attempt > 0 && citationCandidate !== undefined;
291
339
  const response = await this.call({ task: citationRepair ? 'repair_summary_citations' : spec.kind === 'topics' ? 'group_topics' : 'summarize', stage: spec.kind,
292
340
  input: wire.input,
@@ -440,15 +488,31 @@ class SummaryTree {
440
488
  : this.sync.context.filter(item => item.session_id === session).slice(-6).map(item => sourceData(item, true));
441
489
  const input = { session, workspace: batch[0].cwd, evidence: batch.map(item => sourceData(item)), background };
442
490
  const allowed = new Set([...input.evidence, ...background].map(item => item.id));
443
- const wire = (0, references_1.prepareModelInput)('chunk', input, allowed);
444
- const prompt = JSON.stringify({ task: 'summarize', stage: 'chunk', allowed_source_ids: [...wire.allowed], input: wire.input });
445
- if ((0, summary_budget_1.promptTokens)(prompt, batchSummarySystem) > summary_budget_1.LUNA_BATCH.plannedRequestTokens ||
446
- Buffer.byteLength(prompt) + Buffer.byteLength(batchSummarySystem) > summary_budget_1.LUNA_BATCH.inputBytes) {
447
- if (batch.length === 1)
491
+ const fits = (length) => {
492
+ const prefix = { ...input, evidence: input.evidence.slice(0, length) };
493
+ const refs = new Set([...prefix.evidence, ...background].map(item => item.id));
494
+ const wire = (0, references_1.prepareModelInput)('chunk', prefix, refs);
495
+ const prompt = JSON.stringify({ task: 'summarize', stage: 'chunk', allowed_source_ids: [...wire.allowed], input: wire.input });
496
+ return (0, summary_budget_1.promptTokens)(prompt, batchSummarySystem) <= summary_budget_1.LUNA_BATCH.plannedRequestTokens &&
497
+ Buffer.byteLength(prompt) + Buffer.byteLength(batchSummarySystem) <= summary_budget_1.LUNA_BATCH.inputBytes;
498
+ };
499
+ if (!fits(batch.length)) {
500
+ if (!fits(1))
448
501
  throw new errors_1.HttpError(400, 'One entry and its background exceed the summary budget; no evidence was dropped.');
449
- const middle = Math.ceil(batch.length / 2);
450
- append(batch.slice(0, middle));
451
- append(batch.slice(middle));
502
+ // Never bisect by the current tail length: its midpoint moves when
503
+ // history is appended, invalidating completed large leaves. Fill a
504
+ // deterministic prefix instead, then plan the remainder with its
505
+ // own adjacent evidence. Existing unsplit keys remain unchanged.
506
+ let low = 1, high = batch.length;
507
+ while (low + 1 < high) {
508
+ const middle = Math.floor((low + high) / 2);
509
+ if (fits(middle))
510
+ low = middle;
511
+ else
512
+ high = middle;
513
+ }
514
+ append(batch.slice(0, low));
515
+ append(batch.slice(low));
452
516
  return;
453
517
  }
454
518
  result.push(this.spec('chunk', `${batch[0].session_name || batch[0].cwd} · batch ${first + 1}–${first + batch.length}`, input, allowed, batch.map(normalize_1.sourceKey), [], true));
@@ -496,14 +560,14 @@ class SummaryTree {
496
560
  const missing = specs.filter(spec => !this.cached(spec)).length;
497
561
  // A lower bound: reconciliation may add calls, or reuse a cached parent.
498
562
  // Do not reject an all-cached tree that only needs its reserved final slot.
499
- const minimum = missing + 1;
563
+ const minimum = missing + (this.background ? 0 : 1);
500
564
  if (minimum > this.sync.models.max_invocations)
501
- throw new errors_1.HttpError(409, `At least ${minimum} invocations are needed for uncached batches and the verdict, before any uncached reconciliation. Raise the invocation limit; no model calls have been made.`);
565
+ throw new errors_1.HttpError(409, `At least ${minimum} invocations are needed for uncached batches${this.background ? '' : ' and the verdict'}, before any uncached reconciliation. Raise the invocation limit; no model calls have been made.`);
502
566
  let nodes = [];
503
567
  for (const spec of specs)
504
568
  nodes.push({ key: spec.key, kind: 'chunk', label: spec.label, sources: spec.sources,
505
569
  summary: await this.node(spec, (value, refs) => validateSummary(value, refs, spec.batch ? BATCH_OUTPUT : LEGACY_OUTPUT)) });
506
- const processed = this.db.prepare('SELECT processed_entries FROM sync_runs WHERE id=?').get(this.runId).processed_entries;
570
+ const processed = this.db.prepare(`SELECT processed_entries FROM ${this.runTable} WHERE id=?`).get(this.runId).processed_entries;
507
571
  if (processed !== this.sync.items.length)
508
572
  throw new Error('Incomplete source coverage: refusing to generate a final verdict');
509
573
  for (let depth = 0; nodes.length > 1; depth++) {
@@ -524,7 +588,7 @@ class SummaryTree {
524
588
  nodes = [await this.reconcileBatch(nodes, 'Compact final input', true)];
525
589
  if (!this.compactRoots(nodes))
526
590
  throw new errors_1.HttpError(400, 'The reconciled summary cannot fit the compact final budget. Cached results remain inspectable; no evidence was marked reviewed.');
527
- this.stage('Final verdict');
591
+ this.stage(this.background ? 'Summaries prepared' : 'Final verdict');
528
592
  return nodes;
529
593
  }
530
594
  async build() {
@@ -532,9 +596,10 @@ class SummaryTree {
532
596
  return this.buildBatched();
533
597
  // Unknown/operator-selected models retain their established conservative limits.
534
598
  const specs = this.chunkSpecs();
535
- const missing = [...specs.values()].flat().filter(spec => !this.db.prepare('SELECT 1 FROM summary_cache WHERE key = ? OR key = ?').get(spec.key, spec.legacyKey || spec.key)).length;
536
- if (missing + 1 > this.sync.models.max_invocations)
537
- throw new errors_1.HttpError(409, `At least ${missing + 1} invocations are needed just for uncached chunks and the verdict, before merges. Raise the invocation limit; no model calls have been made.`);
599
+ const missing = [...specs.values()].flat().filter(spec => !this.cached(spec)).length;
600
+ const minimum = missing + (this.background ? 0 : 1);
601
+ if (minimum > this.sync.models.max_invocations)
602
+ throw new errors_1.HttpError(409, `At least ${minimum} invocations are needed just for uncached chunks${this.background ? '' : ' and the verdict'}, before merges. Raise the invocation limit; no model calls have been made.`);
538
603
  const sessionRoots = [];
539
604
  // Deliberately serial for predictable quotas/cancellation. Cached nodes are instant.
540
605
  for (const [session, chunks] of specs) {
@@ -543,7 +608,7 @@ class SummaryTree {
543
608
  nodes.push({ key: spec.key, kind: 'chunk', label: spec.label, sources: spec.sources, summary: await this.node(spec, validateSummary) });
544
609
  sessionRoots.push(await this.reduce(nodes, 'session', `Session ${session.slice(0, 8)}`));
545
610
  }
546
- const processed = this.db.prepare('SELECT processed_entries FROM sync_runs WHERE id = ?').get(this.runId).processed_entries;
611
+ const processed = this.db.prepare(`SELECT processed_entries FROM ${this.runTable} WHERE id = ?`).get(this.runId).processed_entries;
547
612
  if (processed !== this.sync.items.length)
548
613
  throw new Error('Incomplete source coverage: refusing to generate a final verdict');
549
614
  const descriptors = sessionRoots.flatMap(node => node.summary.observations.map((observation, index) => ({
@@ -589,7 +654,7 @@ class SummaryTree {
589
654
  throw new Error('Cross-topic tree depth exceeded');
590
655
  roots = await this.mergeLevel(roots, 'overview', `Cross-topic reconciliation ${depth + 1}`);
591
656
  }
592
- this.stage('Final verdict');
657
+ this.stage(this.background ? 'Summaries prepared' : 'Final verdict');
593
658
  return roots;
594
659
  }
595
660
  // Final verification records supply roles/session/sequence once per distinct