@retaw106/mmc-pi 2.2.1 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +14 -3
- package/dist/config-init.js +14 -3
- package/dist/{index-v6scmxvt.js → index-1p318k54.js} +1 -1
- package/dist/{index-dx9vhsy8.js → index-4ktngyya.js} +1 -1
- package/dist/{index-54cgx9gm.js → index-83p1tc98.js} +6 -2
- package/dist/{index-ny303arh.js → index-cr4hzd76.js} +293 -36
- package/dist/{index-j0jjtcwm.js → index-zmx631ey.js} +1711 -219
- package/dist/index.js +26063 -20928
- package/dist/{message-time-backfill-a3p9r5rh.js → message-time-backfill-fvkrfyp4.js} +3 -3
- package/dist/{read-session-chunk-ktp56z65.js → read-session-chunk-g164kwxz.js} +3 -3
- package/dist/{safe-notification-target-hn43s7bc.js → safe-notification-target-5z28m0n9.js} +2 -2
- package/dist/web/main.js +424 -27
- package/package.json +7 -9
package/dist/cli.js
CHANGED
|
@@ -4981,6 +4981,15 @@ var HistorianModelEntryObjectSchema = object({
|
|
|
4981
4981
|
thinking_level: PiThinkingLevelSchema.describe("Pi thinking level for this entry.")
|
|
4982
4982
|
}).strict();
|
|
4983
4983
|
var HistorianModelEntrySchema = union([string2(), HistorianModelEntryObjectSchema]);
|
|
4984
|
+
var MaintenanceModelEntrySchema = HistorianModelEntrySchema.superRefine((entry, ctx) => {
|
|
4985
|
+
const model = typeof entry === "string" ? entry : entry.model;
|
|
4986
|
+
if (typeof model !== "string" || model.trim().length === 0) {
|
|
4987
|
+
ctx.addIssue({
|
|
4988
|
+
code: "custom",
|
|
4989
|
+
message: "maintenance model entries must be non-empty: a bare provider/model string or { model, variant?, thinking_level? }"
|
|
4990
|
+
});
|
|
4991
|
+
}
|
|
4992
|
+
});
|
|
4984
4993
|
var ProfileHistorianSchema = object({
|
|
4985
4994
|
model: HistorianModelEntrySchema.optional().describe("Primary historian model entry."),
|
|
4986
4995
|
fallback_models: array(HistorianModelEntrySchema).optional().describe("Ordered fallback historian model entries."),
|
|
@@ -5034,8 +5043,8 @@ var MagicContextConfigSchema = object({
|
|
|
5034
5043
|
object({ default: number2().min(0) }).catchall(number2().min(0))
|
|
5035
5044
|
]).optional().describe('User-only output-token reservation override. Number or per-model object ({ default: 16384, "provider/model": 8192 }); 0 disables reservation. Takes precedence over every derived source: an explicit value here always wins against catalog output limits, provider window-geometry facts, and the 25%-of-context fallback (usable window = context window minus this reserve). When unset, Magic Context reserves the catalog output limit (capped at 25% of context) for shared-window providers and keeps proven separate-quota Google/Gemini windows unchanged.'),
|
|
5036
5045
|
models: object({
|
|
5037
|
-
window_overlay_path:
|
|
5038
|
-
}).optional().describe("
|
|
5046
|
+
window_overlay_path: unknown().optional().describe("Deprecated: ignored. Fusiform no longer publishes window-overlay data, so this setting no longer changes model window geometry. Remove this key.").meta({ deprecated: true })
|
|
5047
|
+
}).optional().describe("Deprecated user-only Fusiform window-overlay settings; ignored."),
|
|
5039
5048
|
toast_duration_ms: number2().min(0).max(60000).default(5000).describe("TUI toast lifetime in milliseconds for Magic Context notifications. Set to 0 to disable Magic Context toasts entirely (min: 0, max: 60000, default: 5000)"),
|
|
5040
5049
|
execute_threshold_percentage: union([
|
|
5041
5050
|
number2().min(20).max(90, EXECUTE_THRESHOLD_CAP_MESSAGE),
|
|
@@ -5107,6 +5116,8 @@ var MagicContextConfigSchema = object({
|
|
|
5107
5116
|
}).describe("Cross-session memory configuration"),
|
|
5108
5117
|
maintenance: object({
|
|
5109
5118
|
enabled: boolean2().default(true).describe("Enable the automatic memory-maintenance task (default: true). USER-LEVEL ONLY — the whole maintenance group is stripped from project-tier config."),
|
|
5119
|
+
model: MaintenanceModelEntrySchema.optional().describe("Primary maintenance model entry: a bare provider/model string or { model, variant? (OpenCode), thinking_level? (Pi) }. USER-LEVEL ONLY. When absent, inherit historian model configuration. If neither has a primary, automatic passes skip and manual runs refuse. The main-session model is never inherited."),
|
|
5120
|
+
fallback_models: array(MaintenanceModelEntrySchema).optional().describe("Ordered fallback maintenance model entries, tried after CALL failures only. When absent and the primary is inherited, inherit historian fallbacks; explicit [] disables fallbacks. Illegal manifests, permission refusals, and exhausted budgets never switch models."),
|
|
5110
5121
|
budgets: object({
|
|
5111
5122
|
inputTokens: number2().int().min(1).default(400000).describe("Per-run input-token budget (input + cacheRead + cacheWrite pricing dimensions). Default 400000."),
|
|
5112
5123
|
outputTokens: number2().int().min(1).default(32000).describe("Per-run output-token budget. Default 32000."),
|
|
@@ -5148,7 +5159,7 @@ var MagicContextConfigSchema = object({
|
|
|
5148
5159
|
maxRetries: 3
|
|
5149
5160
|
},
|
|
5150
5161
|
anomalyThreshold: { absoluteRows: 10, poolFraction: 0.25 }
|
|
5151
|
-
}).describe("Automatic memory-maintenance run controls (dreamer replacement). USER-LEVEL ONLY:
|
|
5162
|
+
}).describe("Automatic memory-maintenance run controls (dreamer replacement). USER-LEVEL ONLY: project-tier config cannot pick maintenance models or change run controls. An absent model inherits historian model configuration, never the session model.")
|
|
5152
5163
|
});
|
|
5153
5164
|
var LIVE_RELOAD_CONFIG_PATHS = [
|
|
5154
5165
|
"cache_ttl",
|
package/dist/config-init.js
CHANGED
|
@@ -4998,6 +4998,15 @@ var HistorianModelEntryObjectSchema = object({
|
|
|
4998
4998
|
thinking_level: PiThinkingLevelSchema.describe("Pi thinking level for this entry.")
|
|
4999
4999
|
}).strict();
|
|
5000
5000
|
var HistorianModelEntrySchema = union([string2(), HistorianModelEntryObjectSchema]);
|
|
5001
|
+
var MaintenanceModelEntrySchema = HistorianModelEntrySchema.superRefine((entry, ctx) => {
|
|
5002
|
+
const model = typeof entry === "string" ? entry : entry.model;
|
|
5003
|
+
if (typeof model !== "string" || model.trim().length === 0) {
|
|
5004
|
+
ctx.addIssue({
|
|
5005
|
+
code: "custom",
|
|
5006
|
+
message: "maintenance model entries must be non-empty: a bare provider/model string or { model, variant?, thinking_level? }"
|
|
5007
|
+
});
|
|
5008
|
+
}
|
|
5009
|
+
});
|
|
5001
5010
|
var ProfileHistorianSchema = object({
|
|
5002
5011
|
model: HistorianModelEntrySchema.optional().describe("Primary historian model entry."),
|
|
5003
5012
|
fallback_models: array(HistorianModelEntrySchema).optional().describe("Ordered fallback historian model entries."),
|
|
@@ -5051,8 +5060,8 @@ var MagicContextConfigSchema = object({
|
|
|
5051
5060
|
object({ default: number2().min(0) }).catchall(number2().min(0))
|
|
5052
5061
|
]).optional().describe('User-only output-token reservation override. Number or per-model object ({ default: 16384, "provider/model": 8192 }); 0 disables reservation. Takes precedence over every derived source: an explicit value here always wins against catalog output limits, provider window-geometry facts, and the 25%-of-context fallback (usable window = context window minus this reserve). When unset, Magic Context reserves the catalog output limit (capped at 25% of context) for shared-window providers and keeps proven separate-quota Google/Gemini windows unchanged.'),
|
|
5053
5062
|
models: object({
|
|
5054
|
-
window_overlay_path:
|
|
5055
|
-
}).optional().describe("
|
|
5063
|
+
window_overlay_path: unknown().optional().describe("Deprecated: ignored. Fusiform no longer publishes window-overlay data, so this setting no longer changes model window geometry. Remove this key.").meta({ deprecated: true })
|
|
5064
|
+
}).optional().describe("Deprecated user-only Fusiform window-overlay settings; ignored."),
|
|
5056
5065
|
toast_duration_ms: number2().min(0).max(60000).default(5000).describe("TUI toast lifetime in milliseconds for Magic Context notifications. Set to 0 to disable Magic Context toasts entirely (min: 0, max: 60000, default: 5000)"),
|
|
5057
5066
|
execute_threshold_percentage: union([
|
|
5058
5067
|
number2().min(20).max(90, EXECUTE_THRESHOLD_CAP_MESSAGE),
|
|
@@ -5124,6 +5133,8 @@ var MagicContextConfigSchema = object({
|
|
|
5124
5133
|
}).describe("Cross-session memory configuration"),
|
|
5125
5134
|
maintenance: object({
|
|
5126
5135
|
enabled: boolean2().default(true).describe("Enable the automatic memory-maintenance task (default: true). USER-LEVEL ONLY — the whole maintenance group is stripped from project-tier config."),
|
|
5136
|
+
model: MaintenanceModelEntrySchema.optional().describe("Primary maintenance model entry: a bare provider/model string or { model, variant? (OpenCode), thinking_level? (Pi) }. USER-LEVEL ONLY. When absent, inherit historian model configuration. If neither has a primary, automatic passes skip and manual runs refuse. The main-session model is never inherited."),
|
|
5137
|
+
fallback_models: array(MaintenanceModelEntrySchema).optional().describe("Ordered fallback maintenance model entries, tried after CALL failures only. When absent and the primary is inherited, inherit historian fallbacks; explicit [] disables fallbacks. Illegal manifests, permission refusals, and exhausted budgets never switch models."),
|
|
5127
5138
|
budgets: object({
|
|
5128
5139
|
inputTokens: number2().int().min(1).default(400000).describe("Per-run input-token budget (input + cacheRead + cacheWrite pricing dimensions). Default 400000."),
|
|
5129
5140
|
outputTokens: number2().int().min(1).default(32000).describe("Per-run output-token budget. Default 32000."),
|
|
@@ -5165,7 +5176,7 @@ var MagicContextConfigSchema = object({
|
|
|
5165
5176
|
maxRetries: 3
|
|
5166
5177
|
},
|
|
5167
5178
|
anomalyThreshold: { absoluteRows: 10, poolFraction: 0.25 }
|
|
5168
|
-
}).describe("Automatic memory-maintenance run controls (dreamer replacement). USER-LEVEL ONLY:
|
|
5179
|
+
}).describe("Automatic memory-maintenance run controls (dreamer replacement). USER-LEVEL ONLY: project-tier config cannot pick maintenance models or change run controls. An absent model inherits historian model configuration, never the session model.")
|
|
5169
5180
|
});
|
|
5170
5181
|
var LIVE_RELOAD_CONFIG_PATHS = [
|
|
5171
5182
|
"cache_ttl",
|
|
@@ -431,8 +431,12 @@ function flush() {
|
|
|
431
431
|
let currentSize = getCurrentLogSize(logFile);
|
|
432
432
|
const dataSize = Buffer.byteLength(data);
|
|
433
433
|
if (currentSize > 0 && currentSize + dataSize > MAX_LOG_FILE_BYTES) {
|
|
434
|
-
|
|
435
|
-
currentSize =
|
|
434
|
+
flushesSinceSizeCheck = SIZE_CHECK_INTERVAL_FLUSHES;
|
|
435
|
+
currentSize = getCurrentLogSize(logFile);
|
|
436
|
+
if (currentSize > 0 && currentSize + dataSize > MAX_LOG_FILE_BYTES) {
|
|
437
|
+
rotateLogFile(logFile, currentSize);
|
|
438
|
+
currentSize = 0;
|
|
439
|
+
}
|
|
436
440
|
}
|
|
437
441
|
fs.appendFileSync(logFile, data, { encoding: "utf8", mode: 384 });
|
|
438
442
|
buffer = [];
|
|
@@ -1,16 +1,23 @@
|
|
|
1
1
|
// ../plugin/src/shared/sqlite.ts
|
|
2
|
-
import { AsyncLocalStorage } from "node:async_hooks";
|
|
2
|
+
import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
|
|
3
|
+
var resourceRoot = new AsyncResource("magic-context:root");
|
|
4
|
+
function inRootAsyncContext(operation) {
|
|
5
|
+
return resourceRoot.runInAsyncScope(operation);
|
|
6
|
+
}
|
|
3
7
|
var reportSlowPrivilegedWrite;
|
|
4
8
|
function registerSlowWriteReporter(reporter) {
|
|
5
9
|
reportSlowPrivilegedWrite = reporter;
|
|
6
10
|
}
|
|
7
11
|
var sqliteDiagnosticSink;
|
|
8
|
-
|
|
12
|
+
var sqliteDiagnosticHost = () => "unknown";
|
|
13
|
+
function registerSqliteDiagnosticSink(sink, host) {
|
|
9
14
|
sqliteDiagnosticSink = sink;
|
|
15
|
+
if (host)
|
|
16
|
+
sqliteDiagnosticHost = host;
|
|
10
17
|
}
|
|
11
18
|
function reportSqliteDiagnostic(message) {
|
|
12
19
|
try {
|
|
13
|
-
sqliteDiagnosticSink?.(`[magic-context] ${message}`);
|
|
20
|
+
sqliteDiagnosticSink?.(`[magic-context] ${message} pid=${process.pid} host=${sqliteDiagnosticHost()}`);
|
|
14
21
|
} catch {}
|
|
15
22
|
}
|
|
16
23
|
function detectSqliteRuntime() {
|
|
@@ -46,10 +53,31 @@ class SqliteRuntimeUnavailableError extends Error {
|
|
|
46
53
|
this.specifier = specifier;
|
|
47
54
|
}
|
|
48
55
|
}
|
|
56
|
+
function isNodeSqliteExperimentalWarning(args) {
|
|
57
|
+
const [warning, options] = args;
|
|
58
|
+
const message = warning instanceof Error ? warning.message : warning;
|
|
59
|
+
const type = typeof options === "string" ? options : options && typeof options === "object" ? options.type : undefined;
|
|
60
|
+
return type === "ExperimentalWarning" && typeof message === "string" && message.startsWith("SQLite is an experimental feature");
|
|
61
|
+
}
|
|
62
|
+
async function importSqliteModuleForRuntime(runtime, specifier, importer) {
|
|
63
|
+
if (runtime !== "Node.js")
|
|
64
|
+
return importer(specifier);
|
|
65
|
+
const originalEmitWarning = process.emitWarning;
|
|
66
|
+
process.emitWarning = function(...args) {
|
|
67
|
+
if (isNodeSqliteExperimentalWarning(args))
|
|
68
|
+
return;
|
|
69
|
+
Reflect.apply(originalEmitWarning, this, args);
|
|
70
|
+
};
|
|
71
|
+
try {
|
|
72
|
+
return await importer(specifier);
|
|
73
|
+
} finally {
|
|
74
|
+
process.emitWarning = originalEmitWarning;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
49
77
|
async function loadSqliteModule(runtime = detectSqliteRuntime(), importer = importSqliteModule) {
|
|
50
78
|
const specifier = runtime === "Bun" ? bunSpec : nodeSpec;
|
|
51
79
|
try {
|
|
52
|
-
return await
|
|
80
|
+
return await importSqliteModuleForRuntime(runtime, specifier, importer);
|
|
53
81
|
} catch (error) {
|
|
54
82
|
if (isModuleNotFoundError(error, specifier)) {
|
|
55
83
|
throw new SqliteRuntimeUnavailableError(runtime, specifier, error);
|
|
@@ -63,6 +91,81 @@ var sqliteModule = await loadSqliteModule(detectedRuntime);
|
|
|
63
91
|
var DatabaseImpl = isBun ? sqliteModule.Database : buildNodeSqliteDatabaseClass(sqliteModule.DatabaseSync);
|
|
64
92
|
var trackedSqliteConnections = new Map;
|
|
65
93
|
var nextSqliteConnectionSequence = 1;
|
|
94
|
+
var TAG_COLUMNS_OUTSIDE_IDENTITY = new Set([
|
|
95
|
+
"status",
|
|
96
|
+
"drop_mode",
|
|
97
|
+
"byte_size",
|
|
98
|
+
"input_byte_size",
|
|
99
|
+
"reasoning_byte_size",
|
|
100
|
+
"token_count",
|
|
101
|
+
"input_token_count",
|
|
102
|
+
"caveman_depth",
|
|
103
|
+
"entry_fingerprint",
|
|
104
|
+
"tool_name",
|
|
105
|
+
"harness"
|
|
106
|
+
]);
|
|
107
|
+
var tagIdentityWriteGeneration = 0;
|
|
108
|
+
function mayChangeTagIdentity(sql) {
|
|
109
|
+
if (!/\btags\b/i.test(sql) || /^\s*(?:SELECT|EXPLAIN|PRAGMA)\b/i.test(sql))
|
|
110
|
+
return false;
|
|
111
|
+
if (/^\s*WITH\b/i.test(sql) && !/\b(?:INSERT|UPDATE|DELETE|REPLACE)\b/i.test(sql))
|
|
112
|
+
return false;
|
|
113
|
+
if (/;\s*\S/.test(sql) || /\bREPLACE\b/i.test(sql) || /\bON\s+CONFLICT\b/i.test(sql))
|
|
114
|
+
return true;
|
|
115
|
+
const insert = /^\s*INSERT\s+INTO\s+(?:main\.)?["`]?tags["`]?\s*\(([^)]*)\)/i.exec(sql);
|
|
116
|
+
if (insert) {
|
|
117
|
+
const columns = (insert[1] ?? "").split(",").map((column) => column.trim().replace(/["`[\]]/g, "").toLowerCase());
|
|
118
|
+
return columns.includes("id") || columns.includes("rowid");
|
|
119
|
+
}
|
|
120
|
+
const update = /^\s*UPDATE\s+(?:main\.)?["`]?tags["`]?\s+SET\s+([\s\S]*?)\bWHERE\b/i.exec(sql);
|
|
121
|
+
if (update && !/\bSELECT\b/i.test(sql)) {
|
|
122
|
+
const assignments = topLevelCommaSegments(update[1] ?? "");
|
|
123
|
+
if (!assignments)
|
|
124
|
+
return true;
|
|
125
|
+
return !assignments.every((assignment) => {
|
|
126
|
+
const column = /^\s*["`]?(\w+)["`]?\s*=(?!=)/.exec(assignment)?.[1];
|
|
127
|
+
return column !== undefined && TAG_COLUMNS_OUTSIDE_IDENTITY.has(column.toLowerCase());
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
function topLevelCommaSegments(text) {
|
|
133
|
+
const segments = [];
|
|
134
|
+
let depth = 0;
|
|
135
|
+
let quote = null;
|
|
136
|
+
let start = 0;
|
|
137
|
+
for (let index = 0;index < text.length; index += 1) {
|
|
138
|
+
const char = text[index];
|
|
139
|
+
if (quote) {
|
|
140
|
+
if (char === quote) {
|
|
141
|
+
if (text[index + 1] === quote)
|
|
142
|
+
index += 1;
|
|
143
|
+
else
|
|
144
|
+
quote = null;
|
|
145
|
+
}
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
if (char === "'" || char === '"' || char === "`")
|
|
149
|
+
quote = char;
|
|
150
|
+
else if (char === "(")
|
|
151
|
+
depth += 1;
|
|
152
|
+
else if (char === ")") {
|
|
153
|
+
depth -= 1;
|
|
154
|
+
if (depth < 0)
|
|
155
|
+
return null;
|
|
156
|
+
} else if (char === "," && depth === 0) {
|
|
157
|
+
segments.push(text.slice(start, index));
|
|
158
|
+
start = index + 1;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (quote || depth !== 0)
|
|
162
|
+
return null;
|
|
163
|
+
segments.push(text.slice(start));
|
|
164
|
+
return segments;
|
|
165
|
+
}
|
|
166
|
+
function getTagIdentityWriteGeneration() {
|
|
167
|
+
return tagIdentityWriteGeneration;
|
|
168
|
+
}
|
|
66
169
|
function installTransactionRouting(db, readonly) {
|
|
67
170
|
const nativeExec = db.exec.bind(db);
|
|
68
171
|
Object.defineProperty(db, "exec", {
|
|
@@ -73,16 +176,30 @@ function installTransactionRouting(db, readonly) {
|
|
|
73
176
|
assertTransformWrite();
|
|
74
177
|
if (/^\s*BEGIN\s+(?:IMMEDIATE|EXCLUSIVE)(?:\s+TRANSACTION)?\s*;?\s*$/i.test(sql)) {
|
|
75
178
|
openWriterTransactions.delete(db);
|
|
76
|
-
if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore())
|
|
179
|
+
if (transformPassScope.getStore()?.active || backgroundWriterScope.getStore()) {
|
|
77
180
|
acquireShort(db, () => nativeExec(sql), sql.trim());
|
|
78
|
-
else
|
|
181
|
+
} else {
|
|
79
182
|
nativeExec(sql);
|
|
183
|
+
openWriterTransactions.set(db, {
|
|
184
|
+
site: sql.trim(),
|
|
185
|
+
lane: "background",
|
|
186
|
+
acquireMs: 0,
|
|
187
|
+
attempts: 1,
|
|
188
|
+
acquiredAt: performance.now()
|
|
189
|
+
});
|
|
190
|
+
}
|
|
80
191
|
return db;
|
|
81
192
|
}
|
|
82
193
|
if (WRITER_TRANSACTION_END.test(sql) && openWriterTransactions.has(db))
|
|
83
194
|
return endWriterTransaction(db, () => nativeExec(sql), sql);
|
|
84
|
-
if (!/^\s*(?:SELECT|EXPLAIN|PRAGMA|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE)\b/i.test(sql))
|
|
85
|
-
|
|
195
|
+
if (!/^\s*(?:SELECT|EXPLAIN|PRAGMA|COMMIT|END|ROLLBACK|SAVEPOINT|RELEASE)\b/i.test(sql)) {
|
|
196
|
+
try {
|
|
197
|
+
return withAutocommitTimeout(db, () => nativeExec(sql), false);
|
|
198
|
+
} finally {
|
|
199
|
+
if (mayChangeTagIdentity(sql))
|
|
200
|
+
tagIdentityWriteGeneration += 1;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
86
203
|
return nativeExec(sql);
|
|
87
204
|
}
|
|
88
205
|
});
|
|
@@ -93,13 +210,23 @@ function installTransactionRouting(db, readonly) {
|
|
|
93
210
|
value: (sql) => {
|
|
94
211
|
const statement = nativePrepare(sql);
|
|
95
212
|
if (!readonly && !/^\s*(?:SELECT|EXPLAIN|PRAGMA)\b/i.test(sql)) {
|
|
213
|
+
const changesTagIdentity = mayChangeTagIdentity(sql);
|
|
96
214
|
for (const method of ["run", "get", "all"]) {
|
|
97
215
|
const execute = statement[method].bind(statement);
|
|
98
216
|
Object.defineProperty(statement, method, {
|
|
99
217
|
configurable: true,
|
|
100
|
-
value: (...args) =>
|
|
218
|
+
value: (...args) => {
|
|
219
|
+
try {
|
|
220
|
+
return withAutocommitTimeout(db, () => execute(...args));
|
|
221
|
+
} finally {
|
|
222
|
+
if (changesTagIdentity)
|
|
223
|
+
tagIdentityWriteGeneration += 1;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
101
226
|
});
|
|
102
227
|
}
|
|
228
|
+
if (changesTagIdentity)
|
|
229
|
+
wrapRemainingTagWriteForms(statement);
|
|
103
230
|
}
|
|
104
231
|
return statement;
|
|
105
232
|
}
|
|
@@ -147,6 +274,35 @@ function installTransactionRouting(db, readonly) {
|
|
|
147
274
|
}
|
|
148
275
|
});
|
|
149
276
|
}
|
|
277
|
+
function wrapRemainingTagWriteForms(statement) {
|
|
278
|
+
if (typeof statement.iterate === "function") {
|
|
279
|
+
const iterate = statement.iterate.bind(statement);
|
|
280
|
+
Object.defineProperty(statement, "iterate", {
|
|
281
|
+
configurable: true,
|
|
282
|
+
value: function* (...args) {
|
|
283
|
+
tagIdentityWriteGeneration += 1;
|
|
284
|
+
try {
|
|
285
|
+
yield* iterate(...args);
|
|
286
|
+
} finally {
|
|
287
|
+
tagIdentityWriteGeneration += 1;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
if (typeof statement.values === "function") {
|
|
293
|
+
const values = statement.values.bind(statement);
|
|
294
|
+
Object.defineProperty(statement, "values", {
|
|
295
|
+
configurable: true,
|
|
296
|
+
value: (...args) => {
|
|
297
|
+
try {
|
|
298
|
+
return values(...args);
|
|
299
|
+
} finally {
|
|
300
|
+
tagIdentityWriteGeneration += 1;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
}
|
|
150
306
|
function trackSqliteConnection(db, filename, options) {
|
|
151
307
|
const originalClose = db.close.bind(db);
|
|
152
308
|
const sequence = nextSqliteConnectionSequence++;
|
|
@@ -180,7 +336,7 @@ function trackSqliteConnection(db, filename, options) {
|
|
|
180
336
|
}
|
|
181
337
|
var TrackedDatabase = new Proxy(DatabaseImpl, {
|
|
182
338
|
construct(target, args) {
|
|
183
|
-
const db = Reflect.construct(target, args, target);
|
|
339
|
+
const db = inRootAsyncContext(() => Reflect.construct(target, args, target));
|
|
184
340
|
return trackSqliteConnection(db, args[0], args[1]);
|
|
185
341
|
}
|
|
186
342
|
});
|
|
@@ -207,6 +363,18 @@ function buildNodeSqliteDatabaseClass(DatabaseSync) {
|
|
|
207
363
|
return NodeSqliteDatabase;
|
|
208
364
|
}
|
|
209
365
|
var Database = TrackedDatabase;
|
|
366
|
+
function getSqliteDatabasePath(db) {
|
|
367
|
+
for (const connection of trackedSqliteConnections.values()) {
|
|
368
|
+
if (connection.reference.deref() === db) {
|
|
369
|
+
return connection.filename === ":memory:" || connection.filename === "<buffer>" ? null : connection.filename;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
const native = db;
|
|
373
|
+
const path = native.filename ?? native.name;
|
|
374
|
+
if (typeof path === "string" && path !== ":memory:" && path !== "")
|
|
375
|
+
return path;
|
|
376
|
+
return null;
|
|
377
|
+
}
|
|
210
378
|
function pragmaValue(db, name) {
|
|
211
379
|
const row = db.prepare(`PRAGMA ${name}`).get();
|
|
212
380
|
if (!row)
|
|
@@ -289,22 +457,29 @@ function withSqliteTransformPass(operation) {
|
|
|
289
457
|
const existing = transformPassScope.getStore();
|
|
290
458
|
if (existing?.active)
|
|
291
459
|
return operation();
|
|
292
|
-
const
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
460
|
+
const reservation = reserveForegroundSqliteTurn();
|
|
461
|
+
const run = (release) => {
|
|
462
|
+
const lease = { active: true, remainingWaitMs: FOREGROUND_IN_PASS_BUSY_TIMEOUT_MS };
|
|
463
|
+
return transformPassScope.run(lease, () => {
|
|
464
|
+
try {
|
|
465
|
+
const result = operation();
|
|
466
|
+
if (result && typeof result.then === "function") {
|
|
467
|
+
return Promise.resolve(result).finally(() => {
|
|
468
|
+
lease.active = false;
|
|
469
|
+
release();
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
lease.active = false;
|
|
473
|
+
release();
|
|
474
|
+
return result;
|
|
475
|
+
} catch (error) {
|
|
476
|
+
lease.active = false;
|
|
477
|
+
release();
|
|
478
|
+
throw error;
|
|
300
479
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
lease.active = false;
|
|
305
|
-
throw error;
|
|
306
|
-
}
|
|
307
|
-
});
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
return typeof reservation === "function" ? run(reservation) : reservation.then(run);
|
|
308
483
|
}
|
|
309
484
|
function withoutSqliteTransformPass(operation) {
|
|
310
485
|
return transformPassScope.run(undefined, operation);
|
|
@@ -335,9 +510,9 @@ class SqliteAcquisitionBusyError extends Error {
|
|
|
335
510
|
}
|
|
336
511
|
}
|
|
337
512
|
var SHORT_BUSY_TIMEOUT_MS = 25;
|
|
338
|
-
var FOREGROUND_IN_PASS_BUSY_TIMEOUT_MS =
|
|
513
|
+
var FOREGROUND_IN_PASS_BUSY_TIMEOUT_MS = 2000;
|
|
339
514
|
var FOREGROUND_ACQUISITION_BUDGET_MS = 16500;
|
|
340
|
-
var SLOW_WRITER_LOG_MS =
|
|
515
|
+
var SLOW_WRITER_LOG_MS = 100;
|
|
341
516
|
var WRITER_TRANSACTION_END = /^\s*(?:COMMIT|END|ROLLBACK)(?:\s+TRANSACTION)?\s*;?\s*$/i;
|
|
342
517
|
var writerAcquisitionScope = new AsyncLocalStorage;
|
|
343
518
|
var openWriterTransactions = new WeakMap;
|
|
@@ -345,25 +520,52 @@ function foregroundWaitLease() {
|
|
|
345
520
|
const lease = transformPassScope.getStore();
|
|
346
521
|
return lease?.active && !admissionScope.getStore() && !backgroundWriterScope.getStore() ? lease : undefined;
|
|
347
522
|
}
|
|
348
|
-
function withAutocommitTimeout(db, operation) {
|
|
523
|
+
function withAutocommitTimeout(db, operation, singleStatement = true) {
|
|
349
524
|
assertTransformWrite();
|
|
350
525
|
const lease = foregroundWaitLease();
|
|
351
|
-
if (isInTransaction(db)
|
|
526
|
+
if (isInTransaction(db))
|
|
352
527
|
return operation();
|
|
353
|
-
const
|
|
354
|
-
|
|
528
|
+
const scoped = lease || backgroundWriterScope.getStore();
|
|
529
|
+
const previous = scoped ? pragmaNumber(db, "busy_timeout") : null;
|
|
355
530
|
const started = performance.now();
|
|
531
|
+
let completed = false;
|
|
532
|
+
const execute = () => {
|
|
533
|
+
const result = operation();
|
|
534
|
+
completed = true;
|
|
535
|
+
return result;
|
|
536
|
+
};
|
|
356
537
|
try {
|
|
357
|
-
|
|
538
|
+
if (!scoped)
|
|
539
|
+
return execute();
|
|
540
|
+
if (singleStatement) {
|
|
541
|
+
db.exec("PRAGMA busy_timeout=0");
|
|
542
|
+
try {
|
|
543
|
+
return execute();
|
|
544
|
+
} catch (error) {
|
|
545
|
+
if (!isTransientSqliteError(error) || isInTransaction(db))
|
|
546
|
+
throw error;
|
|
547
|
+
}
|
|
548
|
+
assertTransformWrite();
|
|
549
|
+
}
|
|
550
|
+
const timeout = Math.floor(Math.min(lease?.remainingWaitMs ?? SHORT_BUSY_TIMEOUT_MS, lease?.guard?.remainingMs() ?? Infinity));
|
|
551
|
+
db.exec(`PRAGMA busy_timeout=${timeout}`);
|
|
552
|
+
const waitStarted = performance.now();
|
|
553
|
+
try {
|
|
554
|
+
return execute();
|
|
555
|
+
} finally {
|
|
556
|
+
if (lease)
|
|
557
|
+
lease.remainingWaitMs = Math.max(0, lease.remainingWaitMs - (performance.now() - waitStarted));
|
|
558
|
+
}
|
|
358
559
|
} catch (error) {
|
|
359
560
|
if (lease && isTransientSqliteError(error))
|
|
360
561
|
throw new SqliteAcquisitionBusyError(error, "autocommit");
|
|
361
562
|
throw error;
|
|
362
563
|
} finally {
|
|
363
|
-
|
|
364
|
-
lease.remainingWaitMs = Math.max(0, lease.remainingWaitMs - (performance.now() - started));
|
|
564
|
+
const elapsed = performance.now() - started;
|
|
365
565
|
if (previous !== null)
|
|
366
566
|
db.exec(`PRAGMA busy_timeout=${previous}`);
|
|
567
|
+
if (elapsed >= SLOW_WRITER_LOG_MS)
|
|
568
|
+
reportSqliteDiagnostic(`sqlite writer site=autocommit lane=${lease ? "foreground" : "background"} elapsed_ms=${Math.round(elapsed)} timing=statement_including_acquisition outcome=${completed ? "completed" : "failed"} caller=${writerCaller()}`);
|
|
367
569
|
}
|
|
368
570
|
}
|
|
369
571
|
function acquireShort(db, acquire, site) {
|
|
@@ -420,11 +622,15 @@ function endWriterTransaction(db, end, sql) {
|
|
|
420
622
|
const holdMs = performance.now() - open.acquiredAt;
|
|
421
623
|
if (open.acquireMs >= SLOW_WRITER_LOG_MS || holdMs >= SLOW_WRITER_LOG_MS) {
|
|
422
624
|
const outcome = /^\s*ROLLBACK/i.test(sql) ? "rolled_back" : ended ? "committed" : "failed";
|
|
423
|
-
reportSqliteDiagnostic(`sqlite writer site=${open.site} lane=${open.lane} acquire_ms=${Math.round(open.acquireMs)} hold_ms=${Math.round(holdMs)} attempts=${open.attempts} outcome=${outcome}`);
|
|
625
|
+
reportSqliteDiagnostic(`sqlite writer site=${open.site} lane=${open.lane} acquire_ms=${Math.round(open.acquireMs)} hold_ms=${Math.round(holdMs)} attempts=${open.attempts} outcome=${outcome} timing=through_commit_upper_bound caller=${writerCaller()}`);
|
|
424
626
|
}
|
|
425
627
|
}
|
|
426
628
|
}
|
|
427
629
|
}
|
|
630
|
+
function writerCaller() {
|
|
631
|
+
return new Error().stack?.split(`
|
|
632
|
+
`).find((line) => line.includes(" at ") && !/[/\\]sqlite\.[tj]s[:)]/.test(line) && !/\b(?:writerCaller|endWriterTransaction|withAutocommitTimeout|value)\b/.test(line))?.trim().replace(/\s+/g, "_") ?? "unknown";
|
|
633
|
+
}
|
|
428
634
|
async function withAsyncPrivilegedWriter(db, operation, options = {}) {
|
|
429
635
|
const started = performance.now();
|
|
430
636
|
const budgetMs = Math.max(0, Math.min(FOREGROUND_ACQUISITION_BUDGET_MS, options.budgetMs ?? Infinity));
|
|
@@ -533,5 +739,56 @@ function withPrivilegedWriter(db, operation) {
|
|
|
533
739
|
throw error;
|
|
534
740
|
}
|
|
535
741
|
}
|
|
742
|
+
var indexCoordinationKey = Symbol.for("magic-context.index-foreground-coordination.v1");
|
|
743
|
+
var indexCoordinationHeap = globalThis;
|
|
744
|
+
var indexCoordinationBuffer = indexCoordinationHeap[indexCoordinationKey] ?? new SharedArrayBuffer(3 * Int32Array.BYTES_PER_ELEMENT);
|
|
745
|
+
indexCoordinationHeap[indexCoordinationKey] = indexCoordinationBuffer;
|
|
746
|
+
var indexCoordinationState = new Int32Array(indexCoordinationBuffer);
|
|
747
|
+
function indexCoordinationInput() {
|
|
748
|
+
return {
|
|
749
|
+
indexCoordination: indexCoordinationBuffer,
|
|
750
|
+
indexOwner: Atomics.add(indexCoordinationState, 2, 1) + 1
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
var yieldIndexTurn = () => new Promise((resolve) => setTimeout(resolve, 1));
|
|
754
|
+
function reserveForegroundSqliteTurn() {
|
|
755
|
+
Atomics.add(indexCoordinationState, 0, 1);
|
|
756
|
+
const release = () => {
|
|
757
|
+
Atomics.sub(indexCoordinationState, 0, 1);
|
|
758
|
+
Atomics.notify(indexCoordinationState, 0);
|
|
759
|
+
};
|
|
760
|
+
if (Atomics.load(indexCoordinationState, 1) === 0)
|
|
761
|
+
return release;
|
|
762
|
+
return (async () => {
|
|
763
|
+
while (Atomics.load(indexCoordinationState, 1) !== 0)
|
|
764
|
+
await yieldIndexTurn();
|
|
765
|
+
return release;
|
|
766
|
+
})();
|
|
767
|
+
}
|
|
768
|
+
function releaseIndexOwner(input) {
|
|
769
|
+
if (!input.indexCoordination || !input.indexOwner)
|
|
770
|
+
return;
|
|
771
|
+
const shared = new Int32Array(input.indexCoordination);
|
|
772
|
+
Atomics.compareExchange(shared, 1, input.indexOwner, 0);
|
|
773
|
+
Atomics.notify(shared, 1);
|
|
774
|
+
}
|
|
775
|
+
async function withIndexChunk(input, operation) {
|
|
776
|
+
if (!input.indexCoordination || !input.indexOwner)
|
|
777
|
+
return operation();
|
|
778
|
+
const shared = new Int32Array(input.indexCoordination);
|
|
779
|
+
for (;; ) {
|
|
780
|
+
if (Atomics.load(shared, 0) === 0 && Atomics.compareExchange(shared, 1, 0, input.indexOwner) === 0) {
|
|
781
|
+
if (Atomics.load(shared, 0) === 0)
|
|
782
|
+
break;
|
|
783
|
+
releaseIndexOwner(input);
|
|
784
|
+
}
|
|
785
|
+
await yieldIndexTurn();
|
|
786
|
+
}
|
|
787
|
+
try {
|
|
788
|
+
return await operation();
|
|
789
|
+
} finally {
|
|
790
|
+
releaseIndexOwner(input);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
536
793
|
|
|
537
|
-
export { registerSlowWriteReporter, registerSqliteDiagnosticSink, detectSqliteRuntime, Database, prepareCachedStatement, withSqliteBackgroundWriter, withSqliteTransformPass, withoutSqliteTransformPass, guardSqliteTransformPass, isTransientSqliteError, withAsyncPrivilegedWriter, withPrivilegedWriter };
|
|
794
|
+
export { inRootAsyncContext, registerSlowWriteReporter, registerSqliteDiagnosticSink, detectSqliteRuntime, getTagIdentityWriteGeneration, Database, getSqliteDatabasePath, prepareCachedStatement, withSqliteBackgroundWriter, withSqliteTransformPass, withoutSqliteTransformPass, guardSqliteTransformPass, isTransientSqliteError, withAsyncPrivilegedWriter, withPrivilegedWriter, indexCoordinationInput, withIndexChunk };
|