openwolf-enhanced 1.27.0 → 1.28.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/CHANGELOG.md +131 -0
- package/dist/dashboard/assets/{AISuggestions-DBUq3WQW.js → AISuggestions-C7WFvzLO.js} +1 -1
- package/dist/dashboard/assets/{ActivityLog-DzvER0xw.js → ActivityLog-SsRbpBFt.js} +1 -1
- package/dist/dashboard/assets/{ActivityTimeline-DVx1o9Of.js → ActivityTimeline-BoV30nDY.js} +1 -1
- package/dist/dashboard/assets/{AnatomyBrowser-CQGTp2MR.js → AnatomyBrowser-DwzsHn4z.js} +1 -1
- package/dist/dashboard/assets/{BugLog-Ka23MuzX.js → BugLog-CPl4Q-JG.js} +1 -1
- package/dist/dashboard/assets/{CerebrumViewer-2SO_QPQn.js → CerebrumViewer-_f6fBsNF.js} +1 -1
- package/dist/dashboard/assets/{CronStatus-BSJ7o-Xc.js → CronStatus-1iBEPJK5.js} +1 -1
- package/dist/dashboard/assets/{DesignQC-D7Btjv4l.js → DesignQC-D4UU7lca.js} +1 -1
- package/dist/dashboard/assets/{LinkGraph-Cj0m-9HI.js → LinkGraph-BWtI8Gh3.js} +1 -1
- package/dist/dashboard/assets/{MemoryViewer-DQ_OGTaW.js → MemoryViewer-BYRVG34g.js} +1 -1
- package/dist/dashboard/assets/{NativeMemory-BLa2fOaz.js → NativeMemory-Dphtrqjt.js} +1 -1
- package/dist/dashboard/assets/{ProjectOverview-DK4qaJsJ.js → ProjectOverview-CTYohvjD.js} +1 -1
- package/dist/dashboard/assets/{ProjectsAggregate-DW8TBuH2.js → ProjectsAggregate-CMxNs35H.js} +1 -1
- package/dist/dashboard/assets/{StatusBadge-Bclc5R-9.js → StatusBadge-CjrFCDOI.js} +1 -1
- package/dist/dashboard/assets/{TokenUsage-BwhsNFH4.js → TokenUsage-C5wJpMl8.js} +1 -1
- package/dist/dashboard/assets/{index-DsimHkSD.js → index-C74qneS9.js} +3 -3
- package/dist/dashboard/index.html +1 -1
- package/dist/hooks/post-bash.js +4 -4
- package/dist/hooks/post-read.js +5 -7
- package/dist/hooks/post-write.js +114 -101
- package/dist/hooks/pre-read.js +4 -7
- package/dist/hooks/pre-write.js +3 -3
- package/dist/hooks/precompact.js +2 -2
- package/dist/hooks/session-start.js +11 -4
- package/dist/hooks/shared.js +139 -13
- package/dist/hooks/stop.js +9 -3
- package/dist/src/cli/cron-cmd.js +2 -2
- package/dist/src/cli/cron-cmd.js.map +1 -1
- package/dist/src/cli/daemon-cmd.js +40 -14
- package/dist/src/cli/daemon-cmd.js.map +1 -1
- package/dist/src/cli/init.js +11 -4
- package/dist/src/cli/init.js.map +1 -1
- package/dist/src/cli/registry.js +68 -33
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/cli/update.js +27 -4
- package/dist/src/cli/update.js.map +1 -1
- package/dist/src/daemon/cron-engine.js +23 -5
- package/dist/src/daemon/cron-engine.js.map +1 -1
- package/dist/src/daemon/file-watcher.js +4 -1
- package/dist/src/daemon/file-watcher.js.map +1 -1
- package/dist/src/daemon/wolf-daemon.js +17 -3
- package/dist/src/daemon/wolf-daemon.js.map +1 -1
- package/dist/src/hooks/post-bash.js +4 -4
- package/dist/src/hooks/post-bash.js.map +1 -1
- package/dist/src/hooks/post-read.js +5 -7
- package/dist/src/hooks/post-read.js.map +1 -1
- package/dist/src/hooks/post-write.js +114 -101
- package/dist/src/hooks/post-write.js.map +1 -1
- package/dist/src/hooks/pre-read.js +4 -7
- package/dist/src/hooks/pre-read.js.map +1 -1
- package/dist/src/hooks/pre-write.js +3 -3
- package/dist/src/hooks/pre-write.js.map +1 -1
- package/dist/src/hooks/precompact.js +2 -2
- package/dist/src/hooks/precompact.js.map +1 -1
- package/dist/src/hooks/session-start.js +11 -4
- package/dist/src/hooks/session-start.js.map +1 -1
- package/dist/src/hooks/shared.js +139 -13
- package/dist/src/hooks/shared.js.map +1 -1
- package/dist/src/hooks/stop.js +9 -3
- package/dist/src/hooks/stop.js.map +1 -1
- package/dist/src/scanner/anatomy-scanner.js +7 -3
- package/dist/src/scanner/anatomy-scanner.js.map +1 -1
- package/dist/src/utils/agent-hooks.js +50 -4
- package/dist/src/utils/agent-hooks.js.map +1 -1
- package/dist/src/utils/daemon-pid.js +56 -0
- package/dist/src/utils/daemon-pid.js.map +1 -0
- package/dist/src/utils/dashboard-auth.js +13 -1
- package/dist/src/utils/dashboard-auth.js.map +1 -1
- package/dist/src/utils/fs-safe.js +54 -10
- package/dist/src/utils/fs-safe.js.map +1 -1
- package/dist/src/utils/maintenance.js +56 -5
- package/dist/src/utils/maintenance.js.map +1 -1
- package/dist/src/utils/remote.js +2 -2
- package/dist/src/utils/remote.js.map +1 -1
- package/package.json +1 -1
- package/src/templates/config.json +32 -8
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getWolfDir, ensureWolfDir, readJSON, updateSession, readMarkdown, parseAnatomy, readStdin, normalizePath, loadIgnore, isSecretFile, matchesAnatomyEntry } from "./shared.js";
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, updateSession, readMarkdown, parseAnatomy, readStdin, normalizePath, loadIgnore, isSecretFile, matchesAnatomyEntry, relativeToProject, sessionFileFor } from "./shared.js";
|
|
4
4
|
const SESSION_FALLBACK = {
|
|
5
5
|
session_id: "", files_read: {}, anatomy_hits: 0, anatomy_misses: 0, repeated_reads_warned: 0,
|
|
6
6
|
};
|
|
@@ -8,7 +8,6 @@ async function main() {
|
|
|
8
8
|
ensureWolfDir();
|
|
9
9
|
const wolfDir = getWolfDir();
|
|
10
10
|
const hooksDir = path.join(wolfDir, "hooks");
|
|
11
|
-
const sessionFile = path.join(hooksDir, "_session.json");
|
|
12
11
|
const raw = await readStdin();
|
|
13
12
|
let input;
|
|
14
13
|
try {
|
|
@@ -18,6 +17,8 @@ async function main() {
|
|
|
18
17
|
process.exit(0);
|
|
19
18
|
return;
|
|
20
19
|
}
|
|
20
|
+
// After the parse, not before: the payload is what says which session this is.
|
|
21
|
+
const sessionFile = sessionFileFor(hooksDir, input.session_id);
|
|
21
22
|
const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
|
|
22
23
|
if (!filePath) {
|
|
23
24
|
process.exit(0);
|
|
@@ -33,11 +34,7 @@ async function main() {
|
|
|
33
34
|
// Skip tracking for .wolf/ internal files — they're infrastructure, not project files.
|
|
34
35
|
// Counting them inflates anatomy miss rates since .wolf/ is excluded from anatomy scanning.
|
|
35
36
|
const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
|
|
36
|
-
|
|
37
|
-
// sibling directories sharing the prefix (/root/orderflow2 counted as inside /root/orderflow).
|
|
38
|
-
const relToProject = normalizedFile.startsWith(projectDir + "/")
|
|
39
|
-
? normalizedFile.slice(projectDir.length + 1)
|
|
40
|
-
: "";
|
|
37
|
+
const relToProject = relativeToProject(filePath, projectDir);
|
|
41
38
|
// Don't track files outside the project root (upstream #56).
|
|
42
39
|
if (!relToProject) {
|
|
43
40
|
process.exit(0);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pre-read.js","sourceRoot":"","sources":["../../../src/hooks/pre-read.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EACL,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,YAAY,EAC9D,SAAS,EAAE,aAAa,EAAE,UAAU,EAAE,YAAY,EAAE,mBAAmB,
|
|
1
|
+
{"version":3,"file":"pre-read.js","sourceRoot":"","sources":["../../../src/hooks/pre-read.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, readStdin, readBugLog,
|
|
3
|
+
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, readStdin, readBugLog, tryWithLock, sessionFileFor } from "./shared.js";
|
|
4
4
|
async function main() {
|
|
5
5
|
ensureWolfDir();
|
|
6
6
|
const wolfDir = getWolfDir();
|
|
@@ -29,8 +29,8 @@ async function main() {
|
|
|
29
29
|
// ever incremented it, so the ledger/dashboard always showed 0.
|
|
30
30
|
if (cerebrumWarnings > 0) {
|
|
31
31
|
try {
|
|
32
|
-
const sessionFile = path.join(wolfDir, "hooks",
|
|
33
|
-
|
|
32
|
+
const sessionFile = sessionFileFor(path.join(wolfDir, "hooks"), input.session_id);
|
|
33
|
+
tryWithLock(sessionFile, () => {
|
|
34
34
|
const session = readJSON(sessionFile, {});
|
|
35
35
|
session.cerebrum_warnings = (session.cerebrum_warnings ?? 0) + cerebrumWarnings;
|
|
36
36
|
writeJSON(sessionFile, session);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pre-write.js","sourceRoot":"","sources":["../../../src/hooks/pre-write.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,
|
|
1
|
+
{"version":3,"file":"pre-write.js","sourceRoot":"","sources":["../../../src/hooks/pre-write.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readStdin, timestamp } from "./shared.js";
|
|
2
|
+
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readStdin, timestamp, sessionFileFor } from "./shared.js";
|
|
3
3
|
// PreCompact hook — compaction survival.
|
|
4
4
|
//
|
|
5
5
|
// Fires just before Claude Code / Codex compacts the context window. The in-flight session state
|
|
@@ -17,7 +17,7 @@ async function main() {
|
|
|
17
17
|
}
|
|
18
18
|
catch { }
|
|
19
19
|
try {
|
|
20
|
-
const session = readJSON(
|
|
20
|
+
const session = readJSON(sessionFileFor(hooksDir, input.session_id), {});
|
|
21
21
|
writeJSON(path.join(hooksDir, "_precompact-snapshot.json"), {
|
|
22
22
|
at: timestamp(),
|
|
23
23
|
trigger: input.trigger ?? "unknown",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"precompact.js","sourceRoot":"","sources":["../../../src/hooks/precompact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"precompact.js","sourceRoot":"","sources":["../../../src/hooks/precompact.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAEnH,yCAAyC;AACzC,EAAE;AACF,iGAAiG;AACjG,8FAA8F;AAC9F,6FAA6F;AAC7F,2FAA2F;AAC3F,wEAAwE;AAExE,KAAK,UAAU,IAAI;IACjB,aAAa,EAAE,CAAC;IAChB,MAAM,OAAO,GAAG,UAAU,EAAE,CAAC;IAC7B,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAE7C,IAAI,KAAK,GAA8C,EAAE,CAAC;IAC1D,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,SAAS,EAAE,CAAC,CAAC;IACxC,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,QAAQ,CAA0B,cAAc,CAAC,QAAQ,EAAE,KAAK,CAAC,UAAU,CAAC,EAAE,EAAE,CAAC,CAAC;QAClG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,2BAA2B,CAAC,EAAE;YAC1D,EAAE,EAAE,SAAS,EAAE;YACf,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,SAAS;YACnC,OAAO;SACR,CAAC,CAAC;IACL,CAAC;IAAC,MAAM,CAAC,CAAA,CAAC;IAEV,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
|
-
import { getWolfDir, ensureWolfDir, writeJSON, appendMarkdown, readJSON, timestamp, timeShort, buildResumeDigest, readStdin,
|
|
3
|
+
import { getWolfDir, ensureWolfDir, writeJSON, appendMarkdown, readJSON, timestamp, timeShort, buildResumeDigest, readStdin, tryWithLock, sessionFileFor, pruneOldSessionFiles, SESSION_SUMMARY_SCAFFOLD, bookInjection } from "./shared.js";
|
|
4
4
|
import { estimateTokens, getTokenRatios } from "./token-estimator.js";
|
|
5
5
|
async function main() {
|
|
6
6
|
ensureWolfDir();
|
|
@@ -19,7 +19,6 @@ async function main() {
|
|
|
19
19
|
}
|
|
20
20
|
catch { }
|
|
21
21
|
const hooksDir = path.join(wolfDir, "hooks");
|
|
22
|
-
const sessionFile = path.join(hooksDir, "_session.json");
|
|
23
22
|
const now = new Date();
|
|
24
23
|
const sessionId = `session-${now.toISOString().slice(0, 10)}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
|
|
25
24
|
// SessionStart fires for startup/resume/clear/compact. Only startup and clear begin a genuinely
|
|
@@ -27,18 +26,26 @@ async function main() {
|
|
|
27
26
|
// would wipe this session's read/write tracking mid-flight and append a spurious memory header
|
|
28
27
|
// on every compaction (compaction survival).
|
|
29
28
|
let source = "startup";
|
|
29
|
+
let harnessSessionId;
|
|
30
30
|
try {
|
|
31
31
|
const hookInput = JSON.parse(await readStdin());
|
|
32
32
|
if (typeof hookInput.source === "string")
|
|
33
33
|
source = hookInput.source;
|
|
34
|
+
if (typeof hookInput.session_id === "string")
|
|
35
|
+
harnessSessionId = hookInput.session_id;
|
|
34
36
|
}
|
|
35
37
|
catch { }
|
|
38
|
+
// One state file per session (see sessionFileFor). Two agents in the same project used to share
|
|
39
|
+
// one, so the second one's startup wiped the first one's in-flight tracking.
|
|
40
|
+
const sessionFile = sessionFileFor(hooksDir, harnessSessionId);
|
|
41
|
+
// One file per session accumulates; a finished session is already folded into the ledger.
|
|
42
|
+
pruneOldSessionFiles(hooksDir);
|
|
36
43
|
const continuing = (source === "compact" || source === "resume") && fs.existsSync(sessionFile);
|
|
37
44
|
if (!continuing) {
|
|
38
45
|
// Create fresh session state. Under the lock like every other writer of this file: the reset is
|
|
39
46
|
// a whole-file replacement, so an unlocked write here could land in the middle of another
|
|
40
47
|
// hook's read-modify-write and hand it a half-written file to parse.
|
|
41
|
-
|
|
48
|
+
tryWithLock(sessionFile, () => writeJSON(sessionFile, {
|
|
42
49
|
session_id: sessionId,
|
|
43
50
|
started: timestamp(),
|
|
44
51
|
files_read: {},
|
|
@@ -89,7 +96,7 @@ async function main() {
|
|
|
89
96
|
// Same lock as the stop hook's ledger write — a session ending in one process while another
|
|
90
97
|
// starts must not clobber the read-modify-write.
|
|
91
98
|
const ledgerPath = path.join(wolfDir, "token-ledger.json");
|
|
92
|
-
|
|
99
|
+
tryWithLock(ledgerPath, () => {
|
|
93
100
|
const ledger = readJSON(ledgerPath, { version: 1, lifetime: { total_sessions: 0 } });
|
|
94
101
|
ledger.lifetime.total_sessions++;
|
|
95
102
|
writeJSON(ledgerPath, ledger);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../../src/hooks/session-start.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,SAAS,EAAE,cAAc,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,iBAAiB,EAAE,SAAS,EAAE,
|
|
1
|
+
{"version":3,"file":"session-start.js","sourceRoot":"","sources":["../../../src/hooks/session-start.ts"],"names":[],"mappings":"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"}
|
package/dist/src/hooks/shared.js
CHANGED
|
@@ -74,10 +74,31 @@ export function ensureWolfDir() {
|
|
|
74
74
|
process.exit(0);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
//
|
|
77
|
+
// Advisory lock around a read-modify-write cycle so concurrent hook/daemon processes don't clobber
|
|
78
|
+
// each other's updates to the same file (M1). It waits, steals a lock left behind by a dead process
|
|
79
|
+
// (>5s old), and — if it still cannot acquire — FAILS instead of proceeding.
|
|
80
|
+
//
|
|
81
|
+
// [2026-08-31] It used to run the callback unlocked after the wait expired, which defeats the lock
|
|
82
|
+
// for every writer that respects it: the whole point is that exactly one process is inside the
|
|
83
|
+
// critical section, and a participant that proceeds anyway makes the guarantee vacuous for
|
|
84
|
+
// everybody else. Under contention that is not a rare fallback, it is the common path — contention
|
|
85
|
+
// is precisely when the timeout fires. Failing closed loses one update; proceeding unlocked loses
|
|
86
|
+
// somebody else's, and can hand a half-written file to the next reader.
|
|
87
|
+
//
|
|
88
|
+
// The poll also started at a flat 25ms. With seven hooks around one file, a herd needs several
|
|
89
|
+
// rounds to drain and each waiter burned 25ms per attempt whether or not the lock was free a
|
|
90
|
+
// microsecond later. It now starts at 2ms and backs off, so the common case (a lock held for the
|
|
91
|
+
// microseconds of one write) is picked up almost immediately and a herd drains inside the budget.
|
|
92
|
+
export class LockTimeoutError extends Error {
|
|
93
|
+
targetPath;
|
|
94
|
+
waitedMs;
|
|
95
|
+
constructor(targetPath, waitedMs) {
|
|
96
|
+
super(`could not acquire the lock on ${path.basename(targetPath)} within ${waitedMs}ms — update skipped`);
|
|
97
|
+
this.targetPath = targetPath;
|
|
98
|
+
this.waitedMs = waitedMs;
|
|
99
|
+
this.name = "LockTimeoutError";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
81
102
|
function sleepSync(ms) {
|
|
82
103
|
try {
|
|
83
104
|
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
@@ -86,10 +107,11 @@ function sleepSync(ms) {
|
|
|
86
107
|
}
|
|
87
108
|
export function withLock(targetPath, fn) {
|
|
88
109
|
const lockPath = targetPath + ".lock";
|
|
89
|
-
const MAX_WAIT_MS =
|
|
110
|
+
const MAX_WAIT_MS = 1500; // well inside the tightest hook budget (5s)
|
|
90
111
|
const STALE_MS = 5000;
|
|
91
112
|
const start = Date.now();
|
|
92
113
|
let held = false;
|
|
114
|
+
let backoff = 2;
|
|
93
115
|
while (Date.now() - start < MAX_WAIT_MS) {
|
|
94
116
|
try {
|
|
95
117
|
const fd = fs.openSync(lockPath, "wx");
|
|
@@ -111,19 +133,90 @@ export function withLock(targetPath, fn) {
|
|
|
111
133
|
catch {
|
|
112
134
|
continue;
|
|
113
135
|
} // lock vanished — retry immediately
|
|
114
|
-
sleepSync(
|
|
136
|
+
sleepSync(backoff);
|
|
137
|
+
backoff = Math.min(backoff * 2, 50);
|
|
115
138
|
}
|
|
116
139
|
}
|
|
140
|
+
if (!held)
|
|
141
|
+
throw new LockTimeoutError(targetPath, Date.now() - start);
|
|
117
142
|
try {
|
|
118
143
|
return fn();
|
|
119
144
|
}
|
|
120
145
|
finally {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
fs.unlinkSync(lockPath);
|
|
124
|
-
}
|
|
125
|
-
catch { /* ignore */ }
|
|
146
|
+
try {
|
|
147
|
+
fs.unlinkSync(lockPath);
|
|
126
148
|
}
|
|
149
|
+
catch { /* ignore */ }
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* withLock for callers that must never throw — a hook that dies takes the tool call with it.
|
|
154
|
+
* Returns whether the callback ran; a lost update is reported on stderr rather than silently
|
|
155
|
+
* dropped, because "the number is wrong and nobody knows why" is the failure this whole lock
|
|
156
|
+
* exists to prevent.
|
|
157
|
+
*/
|
|
158
|
+
export function tryWithLock(targetPath, fn) {
|
|
159
|
+
try {
|
|
160
|
+
withLock(targetPath, fn);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
if (e instanceof LockTimeoutError) {
|
|
165
|
+
process.stderr.write(`[openwolf] ${e.message}\n`);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
throw e;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Where this session's tracking state lives.
|
|
173
|
+
*
|
|
174
|
+
* There used to be exactly one `_session.json` per project, shared by every session running in it.
|
|
175
|
+
* Two agents in the same repo — two terminals, or an agent plus a scripted run — meant the second
|
|
176
|
+
* one's SessionStart reset the first one's tracking mid-flight, and from then on both wrote into
|
|
177
|
+
* the same file: reads counted against the wrong session, the ledger entry for the first one
|
|
178
|
+
* ending with whatever the second had accumulated.
|
|
179
|
+
*
|
|
180
|
+
* Every harness hands each hook invocation a `session_id`, so the state is keyed by it. When there
|
|
181
|
+
* is none (an agent that does not provide one), the legacy shared path is used exactly as before —
|
|
182
|
+
* no worse than it was, and consistent within that agent.
|
|
183
|
+
*/
|
|
184
|
+
export function sessionFileFor(hooksDir, sessionId) {
|
|
185
|
+
if (typeof sessionId === "string") {
|
|
186
|
+
// Same reasoning as the cron task slug: this string becomes a file name and arrives from
|
|
187
|
+
// outside, so it is reduced to something that can only ever name a file in this directory.
|
|
188
|
+
const safe = sessionId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, 64);
|
|
189
|
+
if (safe)
|
|
190
|
+
return path.join(hooksDir, `_session-${safe}.json`);
|
|
191
|
+
}
|
|
192
|
+
return path.join(hooksDir, "_session.json");
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Delete per-session state files nobody will read again.
|
|
196
|
+
*
|
|
197
|
+
* One file per session means the directory grows with every session, forever. The stop hook has
|
|
198
|
+
* already folded a finished session into token-ledger.json, so the file is spent; a week is
|
|
199
|
+
* generous room for a session that is somehow still live. The legacy shared `_session.json` is
|
|
200
|
+
* never removed — it has no session it belongs to, so age says nothing about it.
|
|
201
|
+
*/
|
|
202
|
+
export function pruneOldSessionFiles(hooksDir, maxAgeDays = 7) {
|
|
203
|
+
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
|
|
204
|
+
let names;
|
|
205
|
+
try {
|
|
206
|
+
names = fs.readdirSync(hooksDir);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
for (const name of names) {
|
|
212
|
+
if (!/^_session-.+\.json(\.lock)?$/.test(name))
|
|
213
|
+
continue;
|
|
214
|
+
const f = path.join(hooksDir, name);
|
|
215
|
+
try {
|
|
216
|
+
if (fs.statSync(f).mtimeMs < cutoff)
|
|
217
|
+
fs.unlinkSync(f);
|
|
218
|
+
}
|
|
219
|
+
catch { /* vanished or unreadable — nothing to clean */ }
|
|
127
220
|
}
|
|
128
221
|
}
|
|
129
222
|
/**
|
|
@@ -140,7 +233,10 @@ export function withLock(targetPath, fn) {
|
|
|
140
233
|
* captured before. That is what makes the update safe against a concurrent writer.
|
|
141
234
|
*/
|
|
142
235
|
export function updateSession(sessionFile, fallback, mutate) {
|
|
143
|
-
|
|
236
|
+
// tryWithLock, not withLock: a hook that throws takes the tool call with it. Under contention
|
|
237
|
+
// the update is dropped and SAID SO on stderr — the alternative (writing anyway) is what made
|
|
238
|
+
// the lock decorative in the first place.
|
|
239
|
+
return tryWithLock(sessionFile, () => {
|
|
144
240
|
const session = readJSON(sessionFile, fallback);
|
|
145
241
|
mutate(session);
|
|
146
242
|
writeJSON(sessionFile, session);
|
|
@@ -165,7 +261,7 @@ export function bookInjection(wolfDir, tokens) {
|
|
|
165
261
|
return;
|
|
166
262
|
const ledgerPath = path.join(wolfDir, "token-ledger.json");
|
|
167
263
|
try {
|
|
168
|
-
|
|
264
|
+
tryWithLock(ledgerPath, () => {
|
|
169
265
|
// [2026-08-20, review] The first cut used readJSON with a two-field fallback and then wrote
|
|
170
266
|
// it back. On an UNPARSEABLE ledger that produced a valid-looking file with no `sessions[]`,
|
|
171
267
|
// and the stop hook died on `ledger.sessions.findIndex(...)` from then on — silently, because
|
|
@@ -1093,6 +1189,36 @@ export function readStdin() {
|
|
|
1093
1189
|
export function normalizePath(p) {
|
|
1094
1190
|
return p.replace(/\\/g, "/");
|
|
1095
1191
|
}
|
|
1192
|
+
/**
|
|
1193
|
+
* The file's path relative to the project root, or "" when it is outside.
|
|
1194
|
+
*
|
|
1195
|
+
* Lexical first — cheap, and correct for the overwhelming majority. But a lexical comparison alone
|
|
1196
|
+
* silently drops a whole project whenever the root is reached through a symlink: /home/me/work is
|
|
1197
|
+
* a link to /mnt/data/work, the agent opens /mnt/data/work/src/a.ts, the prefix does not match, and
|
|
1198
|
+
* every read in that project goes untracked with no error anywhere. macOS makes this the default
|
|
1199
|
+
* case for anything under /tmp (-> /private/tmp).
|
|
1200
|
+
*
|
|
1201
|
+
* So a lexical miss gets a second opinion from realpath, on both sides. Resolution only runs when
|
|
1202
|
+
* the cheap answer already said no, so the common path costs nothing.
|
|
1203
|
+
*/
|
|
1204
|
+
export function relativeToProject(absFile, projectDir) {
|
|
1205
|
+
const file = normalizePath(absFile);
|
|
1206
|
+
const root = normalizePath(projectDir);
|
|
1207
|
+
// Require the SEPARATOR after the root — a bare startsWith also matched a sibling directory
|
|
1208
|
+
// sharing the prefix (/root/orderflow2 counted as inside /root/orderflow).
|
|
1209
|
+
if (file.startsWith(root + "/"))
|
|
1210
|
+
return file.slice(root.length + 1);
|
|
1211
|
+
if (file === root)
|
|
1212
|
+
return "";
|
|
1213
|
+
try {
|
|
1214
|
+
const realFile = normalizePath(fs.realpathSync(absFile));
|
|
1215
|
+
const realRoot = normalizePath(fs.realpathSync(projectDir));
|
|
1216
|
+
if (realFile.startsWith(realRoot + "/"))
|
|
1217
|
+
return realFile.slice(realRoot.length + 1);
|
|
1218
|
+
}
|
|
1219
|
+
catch { /* the file may not exist yet, or the root is gone — outside, then */ }
|
|
1220
|
+
return "";
|
|
1221
|
+
}
|
|
1096
1222
|
/**
|
|
1097
1223
|
* Does an absolute (forward-slashed) file path correspond to this anatomy.md entry?
|
|
1098
1224
|
*
|