@retaw106/mmc-oc 2.2.1 → 2.3.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 +2 -2
- package/dist/config/deprecated-config.d.ts +4 -0
- package/dist/config/deprecated-config.d.ts.map +1 -0
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/project-security.d.ts +2 -2
- package/dist/config/schema/magic-context.d.ts +3 -3
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/config-init.js +2 -2
- package/dist/{context-mrvm92n8.js → context-wt7g4f8k.js} +1641 -1543
- package/dist/{derive-budgets-rjjz5f7y.js → derive-budgets-s1p37t2g.js} +2 -2
- package/dist/features/magic-context/compaction-marker.d.ts.map +1 -1
- package/dist/features/magic-context/historian-failure-reason.d.ts +7 -0
- package/dist/features/magic-context/historian-failure-reason.d.ts.map +1 -0
- package/dist/features/magic-context/message-index-async.d.ts +21 -3
- package/dist/features/magic-context/message-index-async.d.ts.map +1 -1
- package/dist/features/magic-context/message-index.d.ts +7 -1
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/pi-content-decisions.d.ts +7 -1
- package/dist/features/magic-context/pi-content-decisions.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts +29 -0
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/storage-clone.d.ts +2 -0
- package/dist/features/magic-context/storage-clone.d.ts.map +1 -1
- package/dist/features/magic-context/storage-compartment-history-version.d.ts +4 -0
- package/dist/features/magic-context/storage-compartment-history-version.d.ts.map +1 -0
- package/dist/features/magic-context/storage-db.d.ts +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-historian-runs.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +23 -5
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts +7 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-session-tables.d.ts +5 -1
- package/dist/features/magic-context/storage-session-tables.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tag-identity-revision.d.ts +50 -0
- package/dist/features/magic-context/storage-tag-identity-revision.d.ts.map +1 -0
- package/dist/features/magic-context/storage-tags.d.ts +155 -5
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/tagger.d.ts +2 -0
- package/dist/features/magic-context/tagger.d.ts.map +1 -1
- package/dist/features/magic-context/transform-decision-log.d.ts +3 -0
- package/dist/features/magic-context/transform-decision-log.d.ts.map +1 -1
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts +2 -0
- package/dist/hooks/magic-context/active-anthropic-turn.d.ts.map +1 -1
- package/dist/hooks/magic-context/apply-operations.d.ts +7 -0
- package/dist/hooks/magic-context/apply-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/auto-search-runner.d.ts +2 -2
- package/dist/hooks/magic-context/auto-search-runner.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/history-boundary-repair.d.ts +11 -2
- package/dist/hooks/magic-context/history-boundary-repair.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-measured-request.d.ts +3 -1
- package/dist/hooks/magic-context/lkg-measured-request.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-persist.d.ts +10 -13
- package/dist/hooks/magic-context/lkg-persist.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-recovery-fence.d.ts +62 -0
- package/dist/hooks/magic-context/lkg-recovery-fence.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts +38 -0
- package/dist/hooks/magic-context/lkg-recovery-prefix.d.ts.map +1 -0
- package/dist/hooks/magic-context/lkg-replay.d.ts +16 -9
- package/dist/hooks/magic-context/lkg-replay.d.ts.map +1 -1
- package/dist/hooks/magic-context/lkg-slot.d.ts +141 -2
- package/dist/hooks/magic-context/lkg-slot.d.ts.map +1 -1
- package/dist/hooks/magic-context/opaque-thinking.d.ts +12 -0
- package/dist/hooks/magic-context/opaque-thinking.d.ts.map +1 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts +1 -0
- package/dist/hooks/magic-context/pass-outcome.d.ts.map +1 -1
- package/dist/hooks/magic-context/pending-materialization.d.ts +8 -0
- package/dist/hooks/magic-context/pending-materialization.d.ts.map +1 -0
- package/dist/hooks/magic-context/postprocess-read-cache.d.ts.map +1 -1
- package/dist/hooks/magic-context/provider-visible-parts.d.ts +45 -0
- package/dist/hooks/magic-context/provider-visible-parts.d.ts.map +1 -0
- package/dist/hooks/magic-context/read-session-chunk.d.ts +22 -1
- package/dist/hooks/magic-context/read-session-chunk.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-db.d.ts +3 -0
- package/dist/hooks/magic-context/read-session-db.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-formatting.d.ts +5 -0
- package/dist/hooks/magic-context/read-session-formatting.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts +123 -1
- package/dist/hooks/magic-context/read-session-raw.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/reasoning-budget.d.ts.map +1 -1
- package/dist/hooks/magic-context/sentinel.d.ts +41 -1
- package/dist/hooks/magic-context/sentinel.d.ts.map +1 -1
- package/dist/hooks/magic-context/strict-thinking-prefix.d.ts +8 -0
- package/dist/hooks/magic-context/strict-thinking-prefix.d.ts.map +1 -0
- package/dist/hooks/magic-context/strip-content.d.ts +38 -24
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/system-date-refresh.d.ts +29 -0
- package/dist/hooks/magic-context/system-date-refresh.d.ts.map +1 -0
- package/dist/hooks/magic-context/system-prompt-dates.d.ts +6 -0
- package/dist/hooks/magic-context/system-prompt-dates.d.ts.map +1 -0
- package/dist/hooks/magic-context/system-prompt-delivery.d.ts +47 -0
- package/dist/hooks/magic-context/system-prompt-delivery.d.ts.map +1 -0
- package/dist/hooks/magic-context/system-prompt-hash.d.ts +18 -5
- package/dist/hooks/magic-context/system-prompt-hash.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +19 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-owner-warmup.d.ts +14 -0
- package/dist/hooks/magic-context/tag-owner-warmup.d.ts.map +1 -0
- package/dist/hooks/magic-context/token-count-exact.d.ts +35 -0
- package/dist/hooks/magic-context/token-count-exact.d.ts.map +1 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts +2 -0
- package/dist/hooks/magic-context/tool-drop-target.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +7 -0
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts +21 -4
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/index-18yrysyj.js +1477 -0
- package/dist/{index-g2z2k5m4.js → index-3gj1ak4c.js} +2 -2
- package/dist/{index-5cep2r9n.js → index-5ty9rnbx.js} +60 -258
- package/dist/{index-amycb7b1.js → index-a5mw0xp3.js} +3 -3
- package/dist/{index-1j34a56j.js → index-czba8tw3.js} +266 -32
- package/dist/{index-qbkzdkk0.js → index-k59af64t.js} +6 -2
- package/dist/{index-53g38ggt.js → index-kmbme1v1.js} +1 -1
- package/dist/{index-5b7x80ec.js → index-kw21z5pe.js} +6055 -3973
- package/dist/{index-33gprt9g.js → index-yjv8dpht.js} +1 -1
- package/dist/{index-btp5tdc8.js → index-z2w1eq3y.js} +840 -456
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +114 -2
- package/dist/{message-time-backfill-phrwp2fz.js → message-time-backfill-sykevdax.js} +3 -3
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{read-session-chunk-2xm4sc4y.js → read-session-chunk-863d6c06.js} +6 -4
- package/dist/recomp-orchestrator-wrjy3qym.js +21 -0
- package/dist/{safe-notification-target-ph3xd1ve.js → safe-notification-target-1jkf0m82.js} +2 -2
- package/dist/{send-session-notification-rnc4nrsr.js → send-session-notification-cz6ev088.js} +5 -5
- package/dist/shared/auto-search-lifecycle.d.ts +14 -0
- package/dist/shared/auto-search-lifecycle.d.ts.map +1 -1
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/models-dev-cache.d.ts.map +1 -1
- package/dist/shared/models-dev-temperature.d.ts +18 -0
- package/dist/shared/models-dev-temperature.d.ts.map +1 -0
- package/dist/shared/processed-image-eligibility.d.ts +22 -0
- package/dist/shared/processed-image-eligibility.d.ts.map +1 -0
- package/dist/shared/project-directory-key.d.ts +16 -0
- package/dist/shared/project-directory-key.d.ts.map +1 -1
- package/dist/shared/recovery-reasoning-prefix.d.ts +9 -0
- package/dist/shared/recovery-reasoning-prefix.d.ts.map +1 -0
- package/dist/shared/sqlite.d.ts +33 -1
- package/dist/shared/sqlite.d.ts.map +1 -1
- package/dist/shared/stable-json.d.ts.map +1 -1
- package/dist/shared/storage-file-mutex.d.ts +9 -0
- package/dist/shared/storage-file-mutex.d.ts.map +1 -0
- package/dist/shared/storage-permissions.d.ts +6 -0
- package/dist/shared/storage-permissions.d.ts.map +1 -1
- package/dist/shared/subagent-temperature.d.ts +46 -0
- package/dist/shared/subagent-temperature.d.ts.map +1 -0
- package/dist/shared/tag-owner-id.d.ts +1 -0
- package/dist/shared/tag-owner-id.d.ts.map +1 -1
- package/dist/shared/tag-transcript.d.ts +9 -0
- package/dist/shared/tag-transcript.d.ts.map +1 -1
- package/dist/shared/window-geometry.d.ts +9 -66
- package/dist/shared/window-geometry.d.ts.map +1 -1
- package/dist/standalone-plugin-marker.d.ts +8 -0
- package/dist/standalone-plugin-marker.d.ts.map +1 -0
- package/dist/tools/ctx-expand/search-archive.d.ts +4 -0
- package/dist/tools/ctx-expand/search-archive.d.ts.map +1 -0
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/constants.d.ts +1 -2
- package/dist/tools/ctx-reduce/constants.d.ts.map +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts +1 -1
- package/dist/tools/ctx-reduce/tools.d.ts.map +1 -1
- package/dist/v2/hooks/context.d.ts +1 -1
- package/dist/v2/hooks/context.d.ts.map +1 -1
- package/dist/v2/hooks/copied-tag-notation.d.ts +19 -0
- package/dist/v2/hooks/copied-tag-notation.d.ts.map +1 -0
- package/dist/v2/hooks/generate.d.ts +9 -2
- package/dist/v2/hooks/generate.d.ts.map +1 -1
- package/dist/v2/hooks/payload.d.ts.map +1 -1
- package/dist/v2/hooks/system-prompt-response.d.ts +5 -0
- package/dist/v2/hooks/system-prompt-response.d.ts.map +1 -0
- package/dist/v2/server.d.ts.map +1 -1
- package/dist/v2/server.js +34550 -30855
- package/dist/web/main.js +373 -19
- package/dist/{wrapup-orchestrator-yknks3ad.js → wrapup-orchestrator-bmybc5p5.js} +10 -10
- package/package.json +1 -1
- package/src/config/deprecated-config.ts +18 -0
- package/src/config/index.test.ts +13 -37
- package/src/config/index.ts +2 -2
- package/src/config/project-security.ts +3 -3
- package/src/config/schema/magic-context.ts +10 -6
- package/src/shared/auto-search-lifecycle.ts +43 -0
- package/src/shared/logger.ts +10 -2
- package/src/shared/models-dev-cache.ts +1 -7
- package/src/shared/models-dev-temperature.ts +107 -0
- package/src/shared/processed-image-eligibility.test.ts +68 -0
- package/src/shared/processed-image-eligibility.ts +41 -0
- package/src/shared/processed-image-position-review.json +20 -0
- package/src/shared/processed-image-position-review.test.ts +19 -0
- package/src/shared/project-directory-key.ts +20 -0
- package/src/shared/recovery-reasoning-prefix.test.ts +34 -0
- package/src/shared/recovery-reasoning-prefix.ts +35 -0
- package/src/shared/sqlite-acquisition-routing.test.ts +4 -3
- package/src/shared/sqlite-foreground-wait.test.ts +68 -0
- package/src/shared/sqlite-pass-scope.test.ts +4 -4
- package/src/shared/sqlite-writer-diagnostics.test.ts +52 -12
- package/src/shared/sqlite.ts +380 -39
- package/src/shared/stable-json.ts +37 -7
- package/src/shared/storage-file-mutex.test.ts +50 -0
- package/src/shared/storage-file-mutex.ts +130 -0
- package/src/shared/storage-permissions.ts +46 -0
- package/src/shared/subagent-temperature.ts +110 -0
- package/src/shared/tag-owner-id.ts +13 -0
- package/src/shared/tag-transcript.ts +118 -0
- package/src/shared/window-geometry.test.ts +102 -236
- package/src/shared/window-geometry.ts +23 -413
- package/dist/index-ramz5kkm.js +0 -514
- package/dist/recomp-orchestrator-frmwj9hz.js +0 -21
- package/src/shared/__fixtures__/window-overlay.v1.json +0 -337
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
|
+
import { readdirSync, readFileSync, rmSync } from "node:fs";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { isPidAlive, readProcessStartTime } from "./rpc-utils";
|
|
6
|
+
import {
|
|
7
|
+
ensurePrivateStorageDirectorySync,
|
|
8
|
+
writeStorageFileAtomicSync,
|
|
9
|
+
writeStorageFileSync,
|
|
10
|
+
} from "./storage-permissions";
|
|
11
|
+
|
|
12
|
+
interface Ticket {
|
|
13
|
+
choosing: boolean;
|
|
14
|
+
number: number;
|
|
15
|
+
birth: string | null;
|
|
16
|
+
checksum: string;
|
|
17
|
+
}
|
|
18
|
+
const ticketChecksum = (name: string, ticket: Ticket) =>
|
|
19
|
+
createHash("sha256")
|
|
20
|
+
.update(JSON.stringify([name, ticket.choosing, ticket.number, ticket.birth]))
|
|
21
|
+
.digest("hex");
|
|
22
|
+
function processBirth(pid: number): string | null {
|
|
23
|
+
if (process.platform === "linux") {
|
|
24
|
+
try {
|
|
25
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
26
|
+
const ticks = stat
|
|
27
|
+
.slice(stat.lastIndexOf(")") + 2)
|
|
28
|
+
.trim()
|
|
29
|
+
.split(/\s+/)[19];
|
|
30
|
+
if (!ticks || !/^\d+$/.test(ticks)) return null;
|
|
31
|
+
return `${readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim()}:${ticks}`;
|
|
32
|
+
} catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (process.platform === "darwin") {
|
|
37
|
+
try {
|
|
38
|
+
return (
|
|
39
|
+
execFileSync("ps", ["-p", String(pid), "-o", "lstart="], {
|
|
40
|
+
encoding: "utf8",
|
|
41
|
+
timeout: 1000,
|
|
42
|
+
windowsHide: true,
|
|
43
|
+
env: { ...process.env, TZ: "UTC", LC_ALL: "C" },
|
|
44
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
45
|
+
}).trim() || null
|
|
46
|
+
);
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const start = readProcessStartTime(pid);
|
|
52
|
+
return start === null ? null : String(start);
|
|
53
|
+
}
|
|
54
|
+
const pause = new Int32Array(new SharedArrayBuffer(4));
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Lamport's bakery mutex using unique contender files on a local filesystem.
|
|
58
|
+
* Exclusive creation and atomic replacement work on POSIX and Windows. A dead
|
|
59
|
+
* owner is removed by PID/start-time evidence, never by age: a paused live
|
|
60
|
+
* publisher must not lose its lock. Uncertain liveness refuses after the budget.
|
|
61
|
+
* Unique names mean reclaiming a dead ticket cannot unlink a successor's lock.
|
|
62
|
+
*/
|
|
63
|
+
export function withStorageFileMutex<T>(directory: string, work: () => T, timeoutMs = 1000): T {
|
|
64
|
+
ensurePrivateStorageDirectorySync(directory);
|
|
65
|
+
const name = `${process.pid}-${randomUUID()}.lock`;
|
|
66
|
+
const ownPath = join(directory, name);
|
|
67
|
+
const birth = processBirth(process.pid);
|
|
68
|
+
const ticket: Ticket = { choosing: true, number: 0, birth, checksum: "" };
|
|
69
|
+
const encodedTicket = () =>
|
|
70
|
+
JSON.stringify({ ...ticket, checksum: ticketChecksum(name, ticket) });
|
|
71
|
+
writeStorageFileSync(ownPath, encodedTicket(), { flag: "wx", forcePrivate: true });
|
|
72
|
+
const deadline = performance.now() + timeoutMs;
|
|
73
|
+
const peers = (): [string, Ticket | null][] => {
|
|
74
|
+
const found: [string, Ticket | null][] = [];
|
|
75
|
+
for (const other of readdirSync(directory)) {
|
|
76
|
+
if (other === name || !/^\d+-[\da-f-]+\.lock$/.test(other)) continue;
|
|
77
|
+
const pid = Number(other.split("-")[0]);
|
|
78
|
+
const otherPath = join(directory, other);
|
|
79
|
+
let value: Ticket | null = null;
|
|
80
|
+
try {
|
|
81
|
+
const parsed = JSON.parse(readFileSync(otherPath, "utf8")) as Ticket;
|
|
82
|
+
if (
|
|
83
|
+
typeof parsed.choosing === "boolean" &&
|
|
84
|
+
Number.isSafeInteger(parsed.number) &&
|
|
85
|
+
parsed.number >= 0 &&
|
|
86
|
+
(parsed.birth === null || typeof parsed.birth === "string") &&
|
|
87
|
+
parsed.checksum === ticketChecksum(other, parsed)
|
|
88
|
+
)
|
|
89
|
+
value = parsed;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") continue;
|
|
92
|
+
}
|
|
93
|
+
const alive = isPidAlive(pid);
|
|
94
|
+
const actualBirth = alive === "alive" && value?.birth ? processBirth(pid) : null;
|
|
95
|
+
if (
|
|
96
|
+
alive === "dead" ||
|
|
97
|
+
(actualBirth !== null && value?.birth && actualBirth !== value.birth)
|
|
98
|
+
) {
|
|
99
|
+
rmSync(otherPath, { force: true });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
found.push([other, value]);
|
|
103
|
+
}
|
|
104
|
+
return found;
|
|
105
|
+
};
|
|
106
|
+
try {
|
|
107
|
+
ticket.number = 1 + Math.max(0, ...peers().map(([, value]) => value?.number ?? 0));
|
|
108
|
+
ticket.choosing = false;
|
|
109
|
+
writeStorageFileAtomicSync(ownPath, encodedTicket(), true);
|
|
110
|
+
while (
|
|
111
|
+
peers().some(
|
|
112
|
+
([other, value]) =>
|
|
113
|
+
!value ||
|
|
114
|
+
value.choosing ||
|
|
115
|
+
value.number < ticket.number ||
|
|
116
|
+
(value.number === ticket.number && other < name),
|
|
117
|
+
)
|
|
118
|
+
) {
|
|
119
|
+
if (performance.now() >= deadline)
|
|
120
|
+
throw new Error("Storage safety publication lock is unavailable");
|
|
121
|
+
Atomics.wait(pause, 0, 0, 10);
|
|
122
|
+
}
|
|
123
|
+
const result = work();
|
|
124
|
+
if (result && typeof (result as { then?: unknown }).then === "function")
|
|
125
|
+
throw new TypeError("Storage publication mutex requires synchronous work");
|
|
126
|
+
return result;
|
|
127
|
+
} finally {
|
|
128
|
+
rmSync(ownPath, { force: true });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
chmodSync,
|
|
11
11
|
closeSync,
|
|
12
12
|
createWriteStream,
|
|
13
|
+
fsyncSync,
|
|
13
14
|
lstatSync,
|
|
14
15
|
mkdirSync,
|
|
15
16
|
openSync,
|
|
@@ -54,6 +55,15 @@ export function ensureStorageDirectorySync(
|
|
|
54
55
|
});
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
/** Replay-authorizing files require a private real directory, even after a permissive restore. */
|
|
59
|
+
export function ensurePrivateStorageDirectorySync(directory: string): void {
|
|
60
|
+
ensureStorageDirectorySync(directory, true);
|
|
61
|
+
const stat = lstatSync(directory);
|
|
62
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
63
|
+
throw new Error("Private storage directory is not a real directory");
|
|
64
|
+
if (process.platform !== "win32") chmodSync(directory, 0o700);
|
|
65
|
+
}
|
|
66
|
+
|
|
57
67
|
/** Create or replace a storage file using a private mode from the first byte. */
|
|
58
68
|
export function writeStorageFileSync(
|
|
59
69
|
filePath: string,
|
|
@@ -85,6 +95,42 @@ export function writeStorageFileAtomicSync(
|
|
|
85
95
|
}
|
|
86
96
|
}
|
|
87
97
|
|
|
98
|
+
/** Flush the namespace containing a safety-critical sidecar (POSIX only). */
|
|
99
|
+
export function syncStorageDirectorySync(directory: string): void {
|
|
100
|
+
const fd = openSync(directory, "r");
|
|
101
|
+
try {
|
|
102
|
+
fsyncSync(fd);
|
|
103
|
+
} finally {
|
|
104
|
+
closeSync(fd);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Publish a safety record only after both its bytes and directory entry are durable. */
|
|
109
|
+
export function writeStorageFileDurableAtomicSync(
|
|
110
|
+
filePath: string,
|
|
111
|
+
content: string,
|
|
112
|
+
afterTempSync?: () => void,
|
|
113
|
+
): void {
|
|
114
|
+
const directory = dirname(filePath);
|
|
115
|
+
ensurePrivateStorageDirectorySync(directory);
|
|
116
|
+
const tempPath = `${filePath}.${process.pid}.${randomBytes(12).toString("hex")}.tmp`;
|
|
117
|
+
let fd: number | undefined;
|
|
118
|
+
try {
|
|
119
|
+
fd = openStorageFileSync(tempPath, "wx", true);
|
|
120
|
+
writeFileSync(fd, content, "utf8");
|
|
121
|
+
fsyncSync(fd);
|
|
122
|
+
closeSync(fd);
|
|
123
|
+
fd = undefined;
|
|
124
|
+
afterTempSync?.();
|
|
125
|
+
renameSync(tempPath, filePath);
|
|
126
|
+
syncStorageDirectorySync(directory);
|
|
127
|
+
syncStorageDirectorySync(dirname(directory));
|
|
128
|
+
} finally {
|
|
129
|
+
if (fd !== undefined) closeSync(fd);
|
|
130
|
+
rmSync(tempPath, { force: true });
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
88
134
|
export function copyStorageFileSync(
|
|
89
135
|
sourcePath: string,
|
|
90
136
|
destinationPath: string,
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { __catalogTest, catalogTemperature } from "./models-dev-temperature";
|
|
2
|
+
|
|
3
|
+
export type TemperatureMetadata = {
|
|
4
|
+
temperature?: boolean;
|
|
5
|
+
capabilities?: { temperature?: boolean };
|
|
6
|
+
compat?: { supportsTemperature?: boolean; supportsSamplingParams?: boolean };
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const loggedDecisions = new Set<string>();
|
|
10
|
+
|
|
11
|
+
export function temperatureCapability(metadata: unknown): boolean | undefined {
|
|
12
|
+
if (!metadata || typeof metadata !== "object") return undefined;
|
|
13
|
+
const model = metadata as TemperatureMetadata;
|
|
14
|
+
const supported =
|
|
15
|
+
model.capabilities?.temperature ??
|
|
16
|
+
model.temperature ??
|
|
17
|
+
model.compat?.supportsTemperature ??
|
|
18
|
+
model.compat?.supportsSamplingParams;
|
|
19
|
+
return typeof supported === "boolean" ? supported : undefined;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Where a temperature decision came from: the host's own model metadata, the models.dev
|
|
24
|
+
* catalog OpenCode caches on disk, or nothing (which omits temperature).
|
|
25
|
+
*/
|
|
26
|
+
export type TemperatureSource = "host" | "catalog" | "none";
|
|
27
|
+
|
|
28
|
+
export interface TemperatureDecision {
|
|
29
|
+
supported: boolean | undefined;
|
|
30
|
+
source: TemperatureSource;
|
|
31
|
+
/** The models.dev entry (`provider/model`) when the catalog decided. */
|
|
32
|
+
entry?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Host metadata wins whenever it states a boolean. Only when the host is silent do we
|
|
37
|
+
* consult the cached models.dev catalog, matched by provider and model id.
|
|
38
|
+
*/
|
|
39
|
+
export function resolveTemperatureCapability(
|
|
40
|
+
model: string,
|
|
41
|
+
metadata: unknown,
|
|
42
|
+
): TemperatureDecision {
|
|
43
|
+
const host = temperatureCapability(metadata);
|
|
44
|
+
if (host !== undefined) return { supported: host, source: "host" };
|
|
45
|
+
const catalog = catalogTemperature(model);
|
|
46
|
+
if (catalog) return { supported: catalog.temperature, source: "catalog", entry: catalog.entry };
|
|
47
|
+
return { supported: undefined, source: "none" };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function describeTemperatureDecision(model: string, decision: TemperatureDecision): string {
|
|
51
|
+
const verdict = decision.supported === true ? "sent" : "omitted";
|
|
52
|
+
const answer = decision.supported === true ? "yes" : "no";
|
|
53
|
+
const reason =
|
|
54
|
+
decision.source === "host"
|
|
55
|
+
? `host model metadata says ${answer}`
|
|
56
|
+
: decision.source === "catalog"
|
|
57
|
+
? `models.dev cache entry ${decision.entry ?? "unknown"} says ${answer}`
|
|
58
|
+
: "no host or models.dev metadata";
|
|
59
|
+
return `Subagent temperature ${verdict} for ${model}: ${reason}`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Log each model's decision once per process, and again only if the decision changes. */
|
|
63
|
+
export function logTemperatureDecision(
|
|
64
|
+
model: string,
|
|
65
|
+
decision: TemperatureDecision,
|
|
66
|
+
log: (message: string, decision: TemperatureDecision) => void,
|
|
67
|
+
): void {
|
|
68
|
+
const key = `${model}\u0000${decision.source}\u0000${decision.supported}\u0000${decision.entry ?? ""}`;
|
|
69
|
+
if (loggedDecisions.has(key)) return;
|
|
70
|
+
loggedDecisions.add(key);
|
|
71
|
+
log(describeTemperatureDecision(model, decision), decision);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Only a positive capability permits sampling; reasoning alone is not evidence. */
|
|
75
|
+
export function subagentTemperature(
|
|
76
|
+
model: string,
|
|
77
|
+
metadata: unknown,
|
|
78
|
+
temperature: number | undefined,
|
|
79
|
+
log: (message: string, decision: TemperatureDecision) => void,
|
|
80
|
+
): number | undefined {
|
|
81
|
+
if (temperature === undefined) return undefined;
|
|
82
|
+
const decision = resolveTemperatureCapability(model, metadata);
|
|
83
|
+
logTemperatureDecision(model, decision, log);
|
|
84
|
+
return decision.supported === true ? temperature : undefined;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** OpenCode 1 computes agent sampling before chat.params resolves the final model. */
|
|
88
|
+
export function applyHiddenSubagentTemperature(
|
|
89
|
+
input: { agent: string; model?: TemperatureMetadata & { providerID?: string; id?: string } },
|
|
90
|
+
output: { temperature?: number },
|
|
91
|
+
log: (message: string) => void,
|
|
92
|
+
): void {
|
|
93
|
+
if (!/^(?:historian(?:-|$)|dreamer(?:-|$)|smart-note-compiler$)/.test(input.agent)) return;
|
|
94
|
+
const temperature = subagentTemperature(
|
|
95
|
+
`${input.model?.providerID ?? "unknown"}/${input.model?.id ?? "unknown"}`,
|
|
96
|
+
input.model,
|
|
97
|
+
output.temperature,
|
|
98
|
+
// Host loggers treat a second argument as structured data; the message says it all.
|
|
99
|
+
(message) => log(message),
|
|
100
|
+
);
|
|
101
|
+
if (temperature === undefined) delete output.temperature;
|
|
102
|
+
else output.temperature = temperature;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export const __temperatureTest = {
|
|
106
|
+
reset(): void {
|
|
107
|
+
loggedDecisions.clear();
|
|
108
|
+
__catalogTest.reset();
|
|
109
|
+
},
|
|
110
|
+
};
|
|
@@ -13,6 +13,19 @@
|
|
|
13
13
|
|
|
14
14
|
export const TEXT_TAG_IDENTITY_MARKER = ":mc-text-v1:";
|
|
15
15
|
|
|
16
|
+
// SQLite LIKE folds ASCII case, but its binary ownership indexes cannot seek
|
|
17
|
+
// that prefix. Enumerate the 32 spellings so range seeks retain LIKE's matches
|
|
18
|
+
// without building a second index over every persisted tag.
|
|
19
|
+
export const PI_FALLBACK_PREFIXES_JSON = JSON.stringify(
|
|
20
|
+
[..."pi-msg-"].reduce<string[]>(
|
|
21
|
+
(prefixes, char) =>
|
|
22
|
+
prefixes.flatMap((prefix) =>
|
|
23
|
+
/[a-z]/.test(char) ? [prefix + char, prefix + char.toUpperCase()] : [prefix + char],
|
|
24
|
+
),
|
|
25
|
+
[""],
|
|
26
|
+
),
|
|
27
|
+
);
|
|
28
|
+
|
|
16
29
|
const POSITIONAL_CONTENT_SUFFIX = /:(?:p|file)\d+$/;
|
|
17
30
|
const CONTENT_DERIVED_TEXT_SUFFIX = /:mc-text-v1:[0-9a-f]+:[0-9a-f]+:o\d+$/;
|
|
18
31
|
|
|
@@ -55,6 +55,7 @@ import {
|
|
|
55
55
|
updateTagTokenCount,
|
|
56
56
|
} from "../features/magic-context/storage-tags";
|
|
57
57
|
import { makeToolCompositeKey, type Tagger } from "../features/magic-context/tagger";
|
|
58
|
+
import type { TagEntry } from "../features/magic-context/types";
|
|
58
59
|
import { droppedInputMarker } from "../hooks/magic-context/dropped-input-guard";
|
|
59
60
|
import { applyEditMarkerToInput } from "../hooks/magic-context/edit-marker";
|
|
60
61
|
import { estimateImageTokensFromDataUrl } from "../hooks/magic-context/image-token-estimate";
|
|
@@ -121,6 +122,122 @@ export interface TagTranscriptResult {
|
|
|
121
122
|
targets: Map<number, TagTarget>;
|
|
122
123
|
}
|
|
123
124
|
|
|
125
|
+
/**
|
|
126
|
+
* Bind durable drops to a saved request without tagging it again. Recovery must
|
|
127
|
+
* not allocate identities, inject prefixes, or write accounting while the store
|
|
128
|
+
* is failing. Reuse the normal targets so persisted modes render identically.
|
|
129
|
+
*/
|
|
130
|
+
export function buildPersistedDropTargets(
|
|
131
|
+
transcript: Transcript,
|
|
132
|
+
tags: readonly TagEntry[],
|
|
133
|
+
options: { allowReasoningPrefixRewrite?: boolean } = {},
|
|
134
|
+
): Map<number, TagTarget> {
|
|
135
|
+
const targets = new Map<number, TagTarget>();
|
|
136
|
+
const tools = new Map(
|
|
137
|
+
tags
|
|
138
|
+
.filter((tag) => tag.type === "tool")
|
|
139
|
+
.map((tag) => [makeToolCompositeKey(tag.toolOwnerMessageId ?? "", tag.messageId), tag]),
|
|
140
|
+
);
|
|
141
|
+
const textTags = new Map(
|
|
142
|
+
tags.filter((tag) => tag.type !== "tool").map((tag) => [tag.messageId, tag]),
|
|
143
|
+
);
|
|
144
|
+
const byNumber = new Map(tags.map((tag) => [tag.tagNumber, tag]));
|
|
145
|
+
const aggregates = new Map<
|
|
146
|
+
string,
|
|
147
|
+
{ tag?: TagEntry; occurrences: ToolOccurrence[]; reasoning: boolean }
|
|
148
|
+
>();
|
|
149
|
+
const pending = new Map<string, string[]>();
|
|
150
|
+
const resolved = new Map<string, string>();
|
|
151
|
+
const ambiguousCalls = new Set<string>();
|
|
152
|
+
const end = new ConversationEndGuard(transcript.messages);
|
|
153
|
+
for (const message of transcript.messages) {
|
|
154
|
+
let ordinal = 0;
|
|
155
|
+
let resultRun: { callId: string; key: string } | undefined;
|
|
156
|
+
const parts = message.parts;
|
|
157
|
+
const reasoning =
|
|
158
|
+
message.requiresToolArcSkeleton ??
|
|
159
|
+
(message.info.role === "assistant" && parts.some((part) => part.kind === "thinking"));
|
|
160
|
+
for (const part of parts) {
|
|
161
|
+
if (part.kind !== "tool_result") resultRun = undefined;
|
|
162
|
+
if (part.kind === "text") {
|
|
163
|
+
const prefix = /^§(\d+)§\s/.exec(part.getText() ?? "");
|
|
164
|
+
const tag =
|
|
165
|
+
(prefix ? byNumber.get(Number(prefix[1])) : undefined) ??
|
|
166
|
+
textTags.get(`${message.info.id}:p${ordinal}`);
|
|
167
|
+
ordinal++;
|
|
168
|
+
if (tag && tag.type !== "tool")
|
|
169
|
+
targets.set(tag.tagNumber, buildTextTarget(part, message, ""));
|
|
170
|
+
} else if ((part.kind === "tool_use" || part.kind === "tool_result") && part.id) {
|
|
171
|
+
const open = pending.get(part.id) ?? [];
|
|
172
|
+
if (open.length > 1) ambiguousCalls.add(part.id);
|
|
173
|
+
const key =
|
|
174
|
+
(part.kind === "tool_result"
|
|
175
|
+
? resultRun?.callId === part.id
|
|
176
|
+
? resultRun.key
|
|
177
|
+
: (open.at(-1) ?? resolved.get(part.id))
|
|
178
|
+
: undefined) ?? makeToolCompositeKey(message.info.id ?? "", part.id);
|
|
179
|
+
if (part.kind === "tool_use") open.push(key);
|
|
180
|
+
else {
|
|
181
|
+
const position = open.lastIndexOf(key);
|
|
182
|
+
if (position >= 0) open.splice(position, 1);
|
|
183
|
+
resultRun = { callId: part.id, key };
|
|
184
|
+
resolved.set(part.id, key);
|
|
185
|
+
}
|
|
186
|
+
pending.set(part.id, open);
|
|
187
|
+
let aggregate = aggregates.get(key);
|
|
188
|
+
if (!aggregate) {
|
|
189
|
+
aggregate = {
|
|
190
|
+
tag: tools.get(key),
|
|
191
|
+
occurrences: [],
|
|
192
|
+
reasoning: false,
|
|
193
|
+
};
|
|
194
|
+
aggregates.set(key, aggregate);
|
|
195
|
+
}
|
|
196
|
+
aggregate.occurrences.push({ message, part, kind: part.kind });
|
|
197
|
+
aggregate.reasoning ||= reasoning;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const arcsByCall = new Map<string, number>();
|
|
202
|
+
for (const { occurrences } of aggregates.values()) {
|
|
203
|
+
const callId = occurrences[0]?.part.id ?? "";
|
|
204
|
+
arcsByCall.set(callId, (arcsByCall.get(callId) ?? 0) + 1);
|
|
205
|
+
}
|
|
206
|
+
for (const aggregate of aggregates.values()) {
|
|
207
|
+
const { occurrences, reasoning } = aggregate;
|
|
208
|
+
const callId = occurrences[0]?.part.id ?? "";
|
|
209
|
+
if (ambiguousCalls.has(callId)) continue;
|
|
210
|
+
const tag =
|
|
211
|
+
aggregate.tag ??
|
|
212
|
+
(arcsByCall.get(callId) === 1
|
|
213
|
+
? tools.get(makeToolCompositeKey("", callId))
|
|
214
|
+
: undefined);
|
|
215
|
+
if (tag)
|
|
216
|
+
targets.set(
|
|
217
|
+
tag.tagNumber,
|
|
218
|
+
buildAggregateTarget(tag.tagNumber, occurrences, reasoning, end),
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
if (!options.allowReasoningPrefixRewrite) {
|
|
222
|
+
// This target builder cannot persist removal of thinking. By default,
|
|
223
|
+
// withhold edits before reasoning rather than invalidate its signature.
|
|
224
|
+
// The Pi recovery handler opts in only to test edits on a saved copy;
|
|
225
|
+
// it must compare final request prefixes and refuse unsafe changes.
|
|
226
|
+
let lastThinking = -1;
|
|
227
|
+
transcript.messages.forEach((message, index) => {
|
|
228
|
+
if (message.parts.some((part) => part.kind === "thinking")) lastThinking = index;
|
|
229
|
+
});
|
|
230
|
+
const protectedIds = new Set(
|
|
231
|
+
transcript.messages.slice(0, lastThinking + 1).map((message) => message.info.id),
|
|
232
|
+
);
|
|
233
|
+
for (const [number, target] of targets) {
|
|
234
|
+
if (target.mutationParts?.some(({ message }) => protectedIds.has(message.info.id)))
|
|
235
|
+
targets.delete(number);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
return targets;
|
|
239
|
+
}
|
|
240
|
+
|
|
124
241
|
/**
|
|
125
242
|
* Tag eligible parts of a transcript and build TagTargets for them.
|
|
126
243
|
*
|
|
@@ -1288,6 +1405,7 @@ function buildAggregateTarget(
|
|
|
1288
1405
|
return any ? "truncated" : "absent";
|
|
1289
1406
|
},
|
|
1290
1407
|
// Open invocations still belong to the current turn; only complete arcs reclaim.
|
|
1408
|
+
prepareDrop: () => !occurrences.some((occ) => occ.part.canRemove?.() === "defer"),
|
|
1291
1409
|
canDrop: () => complete() && !occurrences.some((occ) => occ.part.hasUserAnswer?.()),
|
|
1292
1410
|
requiresToolArcSkeleton,
|
|
1293
1411
|
// Non-mutating read of the invocation input (the tool_use occurrence
|