@brainervirus/workit-codex 8.13.0 → 8.14.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/.codex-plugin/plugin.json +1 -1
- package/agents/workit-reviewer.toml +2 -2
- package/agents/workit-verifier.toml +2 -2
- package/dist/launch-mcp.js +688 -401
- package/dist/{workit-hook-sc45d7h1.js → workit-hook-s8c0mzcq.js} +687 -65
- package/dist/workit-hook.js +1 -1
- package/package.json +4 -4
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/orchestration.md +8 -5
- package/skills/workit-review/SKILL.md +1 -1
|
@@ -9789,7 +9789,7 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9789
9789
|
}
|
|
9790
9790
|
}
|
|
9791
9791
|
// packages/workit-core/src/host-session.ts
|
|
9792
|
-
import { createHash as createHash4 } from "node:crypto";
|
|
9792
|
+
import { createHash as createHash4, randomBytes as randomBytes2, timingSafeEqual } from "node:crypto";
|
|
9793
9793
|
import fs7 from "node:fs";
|
|
9794
9794
|
import path7 from "node:path";
|
|
9795
9795
|
|
|
@@ -9881,9 +9881,10 @@ function hostSessionFromEnv(env) {
|
|
|
9881
9881
|
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9882
9882
|
}
|
|
9883
9883
|
var ISSUED_PER_LEAD = 64;
|
|
9884
|
-
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/
|
|
9884
|
+
var ISSUED_SESSIONS_MARKER = "workit-issued-sessions/3";
|
|
9885
9885
|
var hash = (value) => createHash4("sha256").update(value).digest("hex").slice(0, 32);
|
|
9886
9886
|
var userIssuedRoot = () => path7.join(resolveStateDir(), "issued");
|
|
9887
|
+
var userTokenRoot = () => path7.join(resolveStateDir(), "issued-tokens");
|
|
9887
9888
|
function migrateRepoIssued(cwd) {
|
|
9888
9889
|
if (cwd === undefined)
|
|
9889
9890
|
return;
|
|
@@ -9906,7 +9907,12 @@ function migrateRepoIssued(cwd) {
|
|
|
9906
9907
|
const to = path7.join(userIssuedRoot(), lead);
|
|
9907
9908
|
fs7.mkdirSync(to, { recursive: true, mode: 448 });
|
|
9908
9909
|
for (const id of fs7.readdirSync(from)) {
|
|
9909
|
-
|
|
9910
|
+
try {
|
|
9911
|
+
fs7.copyFileSync(path7.join(from, id), path7.join(to, id), fs7.constants.COPYFILE_EXCL);
|
|
9912
|
+
} catch (error) {
|
|
9913
|
+
if (error.code !== "EEXIST")
|
|
9914
|
+
throw error;
|
|
9915
|
+
}
|
|
9910
9916
|
fs7.rmSync(path7.join(from, id), { force: true });
|
|
9911
9917
|
}
|
|
9912
9918
|
removeIfEmpty(from);
|
|
@@ -9926,21 +9932,71 @@ var removeIfEmpty = (dir) => {
|
|
|
9926
9932
|
fs7.rmdirSync(dir);
|
|
9927
9933
|
} catch {}
|
|
9928
9934
|
};
|
|
9935
|
+
var tokenHash = (token) => createHash4("sha256").update(token).digest("hex");
|
|
9929
9936
|
function recordIssuedSession(cwd, lead, issued) {
|
|
9937
|
+
const token = randomBytes2(18).toString("base64url");
|
|
9930
9938
|
try {
|
|
9931
9939
|
migrateRepoIssued(cwd);
|
|
9940
|
+
const own = hash(issued);
|
|
9941
|
+
const tokens = path7.join(userTokenRoot(), hash(lead));
|
|
9942
|
+
fs7.mkdirSync(tokens, { recursive: true, mode: 448 });
|
|
9943
|
+
fs7.writeFileSync(path7.join(tokens, own), `${tokenHash(token)}
|
|
9944
|
+
`, { mode: 384, flag: "wx" });
|
|
9932
9945
|
const dir = path7.join(userIssuedRoot(), hash(lead));
|
|
9933
9946
|
fs7.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
9934
|
-
|
|
9935
|
-
|
|
9947
|
+
fs7.writeFileSync(path7.join(dir, own), `${ISSUED_SESSIONS_MARKER}
|
|
9948
|
+
`, { mode: 384 });
|
|
9949
|
+
pruneIssued(dir, own, tokens);
|
|
9950
|
+
return token;
|
|
9951
|
+
} catch {
|
|
9952
|
+
return null;
|
|
9953
|
+
}
|
|
9954
|
+
}
|
|
9955
|
+
function pruneIssued(dir, own, tokens) {
|
|
9956
|
+
try {
|
|
9936
9957
|
const entries = fs7.readdirSync(dir);
|
|
9937
9958
|
if (entries.length <= ISSUED_PER_LEAD)
|
|
9938
9959
|
return;
|
|
9939
9960
|
const byAge = entries.filter((name) => name !== own).map((name) => ({ name, at: fs7.statSync(path7.join(dir, name)).mtimeMs })).toSorted((a, b) => a.at - b.at);
|
|
9940
|
-
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD))
|
|
9961
|
+
for (const { name } of byAge.slice(0, entries.length - ISSUED_PER_LEAD)) {
|
|
9941
9962
|
fs7.rmSync(path7.join(dir, name), { force: true });
|
|
9963
|
+
fs7.rmSync(path7.join(tokens, name), { force: true });
|
|
9964
|
+
}
|
|
9942
9965
|
} catch {}
|
|
9943
9966
|
}
|
|
9967
|
+
var issuedRecord = (cwd, claimed) => {
|
|
9968
|
+
const cut = claimed.lastIndexOf(":");
|
|
9969
|
+
if (cut <= 0)
|
|
9970
|
+
return null;
|
|
9971
|
+
migrateRepoIssued(cwd);
|
|
9972
|
+
try {
|
|
9973
|
+
return fs7.readFileSync(path7.join(userIssuedRoot(), hash(claimed.slice(0, cut)), hash(claimed)), "utf8");
|
|
9974
|
+
} catch {
|
|
9975
|
+
return null;
|
|
9976
|
+
}
|
|
9977
|
+
};
|
|
9978
|
+
var TOKEN_HASH = /^([0-9a-f]{64})$/mu;
|
|
9979
|
+
var issuedToken = (claimed) => {
|
|
9980
|
+
const cut = claimed.lastIndexOf(":");
|
|
9981
|
+
if (cut <= 0)
|
|
9982
|
+
return { stored: null, leadBound: false };
|
|
9983
|
+
const dir = path7.join(userTokenRoot(), hash(claimed.slice(0, cut)));
|
|
9984
|
+
try {
|
|
9985
|
+
const text = fs7.readFileSync(path7.join(dir, hash(claimed)), "utf8");
|
|
9986
|
+
return { stored: TOKEN_HASH.exec(text)?.[1] ?? null, leadBound: true };
|
|
9987
|
+
} catch {
|
|
9988
|
+
return { stored: null, leadBound: safeList(dir).length > 0 };
|
|
9989
|
+
}
|
|
9990
|
+
};
|
|
9991
|
+
function issuedBinding(cwd, claimed) {
|
|
9992
|
+
const record = issuedRecord(cwd, claimed);
|
|
9993
|
+
const { stored, leadBound } = issuedToken(claimed);
|
|
9994
|
+
if (stored)
|
|
9995
|
+
return "token";
|
|
9996
|
+
if (record === null)
|
|
9997
|
+
return null;
|
|
9998
|
+
return leadBound ? "token" : "legacy";
|
|
9999
|
+
}
|
|
9944
10000
|
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
9945
10001
|
|
|
9946
10002
|
// packages/workit-core/src/core/package-root.ts
|
|
@@ -9971,8 +10027,10 @@ var metadataLockSchema = object({
|
|
|
9971
10027
|
var FOREIGN_LOCK_TTL_MS = 10 * 60000;
|
|
9972
10028
|
var UNREADABLE_LOCK_TTL_MS = 30000;
|
|
9973
10029
|
var RECLAIM_GUARD_TTL_MS = 30000;
|
|
9974
|
-
var
|
|
10030
|
+
var IN_PROCESS_LOCK_TIMEOUT_MS = 250;
|
|
10031
|
+
var defaultLockTimeoutMs = IN_PROCESS_LOCK_TIMEOUT_MS;
|
|
9975
10032
|
var defaultLockTimeout = () => defaultLockTimeoutMs;
|
|
10033
|
+
var hostAwareLockTimeout = (cliMs) => defaultLockTimeoutMs > IN_PROCESS_LOCK_TIMEOUT_MS ? cliMs : IN_PROCESS_LOCK_TIMEOUT_MS;
|
|
9976
10034
|
var parseMetadataLock = (raw) => {
|
|
9977
10035
|
let value;
|
|
9978
10036
|
try {
|
|
@@ -12245,7 +12303,7 @@ import path15 from "node:path";
|
|
|
12245
12303
|
|
|
12246
12304
|
// packages/workit-core/src/ledger.ts
|
|
12247
12305
|
import { spawnSync as spawnSync9 } from "node:child_process";
|
|
12248
|
-
import { createHash as createHash9, randomBytes as
|
|
12306
|
+
import { createHash as createHash9, randomBytes as randomBytes3 } from "node:crypto";
|
|
12249
12307
|
import fs12 from "node:fs";
|
|
12250
12308
|
import path14 from "node:path";
|
|
12251
12309
|
|
|
@@ -12614,6 +12672,8 @@ var workspaceConfigSchema = object({
|
|
|
12614
12672
|
defaultEndpoint: unknown().optional(),
|
|
12615
12673
|
verification: _enum(["self", "independent"]).optional(),
|
|
12616
12674
|
trailers: boolean2().optional(),
|
|
12675
|
+
liveSessionGuard: boolean2().optional(),
|
|
12676
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
12617
12677
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
12618
12678
|
kind,
|
|
12619
12679
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -12931,24 +12991,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
12931
12991
|
const matched = matchRuntimeWorkspace(cwd);
|
|
12932
12992
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
12933
12993
|
};
|
|
12934
|
-
var
|
|
12994
|
+
var inheritedSetting = (cwd, dir, has) => {
|
|
12935
12995
|
let matched;
|
|
12936
12996
|
try {
|
|
12937
12997
|
matched = matchRuntimeWorkspace(cwd, dir);
|
|
12938
12998
|
} catch {
|
|
12939
|
-
return
|
|
12999
|
+
return null;
|
|
12940
13000
|
}
|
|
12941
13001
|
if (!matched)
|
|
12942
|
-
return
|
|
13002
|
+
return null;
|
|
12943
13003
|
const { selected, matches } = matched;
|
|
12944
13004
|
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
12945
|
-
|
|
13005
|
+
return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
|
|
13006
|
+
};
|
|
13007
|
+
var workspaceTrailers = (cwd, dir) => {
|
|
13008
|
+
const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
|
|
12946
13009
|
return {
|
|
12947
|
-
enabled: source?.trailers !== false,
|
|
12948
|
-
workspace:
|
|
12949
|
-
from: source?.name ?? null
|
|
13010
|
+
enabled: found?.source?.trailers !== false,
|
|
13011
|
+
workspace: found?.workspace ?? null,
|
|
13012
|
+
from: found?.source?.name ?? null
|
|
12950
13013
|
};
|
|
12951
13014
|
};
|
|
13015
|
+
var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
|
|
13016
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
13017
|
+
var TIER_HOSTS = new Set([
|
|
13018
|
+
"claude_code",
|
|
13019
|
+
"opencode",
|
|
13020
|
+
"codex",
|
|
13021
|
+
"codex_cli",
|
|
13022
|
+
"codex_desktop",
|
|
13023
|
+
"cursor",
|
|
13024
|
+
"pi",
|
|
13025
|
+
"default"
|
|
13026
|
+
]);
|
|
12952
13027
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
12953
13028
|
const matched = matchRuntimeWorkspace(cwd);
|
|
12954
13029
|
if (!matched)
|
|
@@ -13636,7 +13711,7 @@ function ledgerPath(cwd) {
|
|
|
13636
13711
|
const root = storeRoot(cwd);
|
|
13637
13712
|
return root.ok ? { ok: true, value: path14.join(root.value.root, "ledger", "ledger.jsonl") } : root;
|
|
13638
13713
|
}
|
|
13639
|
-
var newId2 = () => `${Date.now().toString(36)}-${
|
|
13714
|
+
var newId2 = () => `${Date.now().toString(36)}-${randomBytes3(6).toString("hex")}`;
|
|
13640
13715
|
var NETWORK_FS_TYPES = new Set([
|
|
13641
13716
|
26985,
|
|
13642
13717
|
20859,
|
|
@@ -13686,14 +13761,14 @@ var ledgerLock = {
|
|
|
13686
13761
|
return null;
|
|
13687
13762
|
throw error;
|
|
13688
13763
|
}
|
|
13689
|
-
const token = `${process.pid}-${
|
|
13764
|
+
const token = `${process.pid}-${randomBytes3(8).toString("hex")}`;
|
|
13690
13765
|
fs12.writeFileSync(path14.join(lock, LOCK_OWNER), token);
|
|
13691
13766
|
return token;
|
|
13692
13767
|
},
|
|
13693
13768
|
release(lock, token) {
|
|
13694
13769
|
if (lockToken(lock) !== token)
|
|
13695
13770
|
return false;
|
|
13696
|
-
const tomb = `${lock}.release-${
|
|
13771
|
+
const tomb = `${lock}.release-${randomBytes3(6).toString("hex")}`;
|
|
13697
13772
|
try {
|
|
13698
13773
|
fs12.renameSync(lock, tomb);
|
|
13699
13774
|
} catch {
|
|
@@ -13710,7 +13785,7 @@ var ledgerLock = {
|
|
|
13710
13785
|
},
|
|
13711
13786
|
token: lockToken,
|
|
13712
13787
|
reap(lock, observed) {
|
|
13713
|
-
const tomb = `${lock}.reap-${
|
|
13788
|
+
const tomb = `${lock}.reap-${randomBytes3(6).toString("hex")}`;
|
|
13714
13789
|
try {
|
|
13715
13790
|
fs12.renameSync(lock, tomb);
|
|
13716
13791
|
} catch {
|
|
@@ -13728,7 +13803,7 @@ var ledgerLock = {
|
|
|
13728
13803
|
return false;
|
|
13729
13804
|
}
|
|
13730
13805
|
};
|
|
13731
|
-
function withLedgerLock(file, fn, waitMs = LOCK_WAIT_MS) {
|
|
13806
|
+
function withLedgerLock(file, fn, waitMs = hostAwareLockTimeout(LOCK_WAIT_MS)) {
|
|
13732
13807
|
const lock = `${file}.lock`;
|
|
13733
13808
|
const deadline = Date.now() + waitMs;
|
|
13734
13809
|
let token = null;
|
|
@@ -13786,10 +13861,18 @@ var lastLine = (fd, size) => {
|
|
|
13786
13861
|
}
|
|
13787
13862
|
return null;
|
|
13788
13863
|
};
|
|
13864
|
+
var tighten = (fd) => {
|
|
13865
|
+
try {
|
|
13866
|
+
if ((fs12.fstatSync(fd).mode & 63) !== 0)
|
|
13867
|
+
fs12.fchmodSync(fd, 384);
|
|
13868
|
+
} catch {}
|
|
13869
|
+
};
|
|
13789
13870
|
function appendLine(file, build, options) {
|
|
13790
13871
|
const write = () => {
|
|
13791
|
-
const fd = fs12.openSync(file, "a+");
|
|
13872
|
+
const fd = fs12.openSync(file, "a+", 384);
|
|
13792
13873
|
try {
|
|
13874
|
+
if (process.platform !== "win32")
|
|
13875
|
+
tighten(fd);
|
|
13793
13876
|
const size = fs12.fstatSync(fd).size;
|
|
13794
13877
|
let fence = "";
|
|
13795
13878
|
if (size > 0) {
|
|
@@ -13884,7 +13967,9 @@ function readLedger(cwd, options = {}) {
|
|
|
13884
13967
|
if (!file.ok)
|
|
13885
13968
|
return file;
|
|
13886
13969
|
let text = "";
|
|
13970
|
+
let stat = null;
|
|
13887
13971
|
try {
|
|
13972
|
+
stat = fs12.statSync(file.value);
|
|
13888
13973
|
text = fs12.readFileSync(file.value, "utf8");
|
|
13889
13974
|
} catch (error) {
|
|
13890
13975
|
if (error.code === "ENOENT")
|
|
@@ -13904,6 +13989,7 @@ function readLedger(cwd, options = {}) {
|
|
|
13904
13989
|
const integrity = { chained: 0, legacy: 0, unverified: [] };
|
|
13905
13990
|
const earlier = new Set([null]);
|
|
13906
13991
|
let chainStarted = false;
|
|
13992
|
+
const ids = new Set;
|
|
13907
13993
|
for (const raw of text.split(`
|
|
13908
13994
|
`)) {
|
|
13909
13995
|
const trimmed = raw.trim();
|
|
@@ -13937,6 +14023,10 @@ function readLedger(cwd, options = {}) {
|
|
|
13937
14023
|
reason = "unsigned";
|
|
13938
14024
|
else
|
|
13939
14025
|
rowIntegrity = "legacy";
|
|
14026
|
+
if (options.integrity && !reason && ids.has(parsed.id))
|
|
14027
|
+
reason = "duplicate_id";
|
|
14028
|
+
if (options.integrity)
|
|
14029
|
+
ids.add(parsed.id);
|
|
13940
14030
|
if (reason)
|
|
13941
14031
|
rowIntegrity = "unverified";
|
|
13942
14032
|
const pr = typeof parsed.pr === "number" && Number.isInteger(parsed.pr) ? parsed.pr : undefined;
|
|
@@ -13960,7 +14050,8 @@ function readLedger(cwd, options = {}) {
|
|
|
13960
14050
|
seq: rows.length + 1,
|
|
13961
14051
|
superseded: false,
|
|
13962
14052
|
supersedeIgnored: false,
|
|
13963
|
-
...rowIntegrity ? { integrity: rowIntegrity } : {}
|
|
14053
|
+
...rowIntegrity ? { integrity: rowIntegrity } : {},
|
|
14054
|
+
...reason ? { integrityReason: reason } : {}
|
|
13964
14055
|
};
|
|
13965
14056
|
rows.push(row);
|
|
13966
14057
|
if (!options.integrity)
|
|
@@ -13988,18 +14079,48 @@ function readLedger(cwd, options = {}) {
|
|
|
13988
14079
|
const byId = new Map;
|
|
13989
14080
|
for (const row of rows) {
|
|
13990
14081
|
const target = row.supersedes ? byId.get(row.supersedes) : undefined;
|
|
13991
|
-
if (target && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
14082
|
+
if (target && row.integrity !== "unverified" && supersedeAllowed(row, target, renameAwareBranch(scopeOf, row.branch)) === null)
|
|
13992
14083
|
target.superseded = true;
|
|
13993
14084
|
else if (row.supersedes)
|
|
13994
14085
|
row.supersedeIgnored = true;
|
|
13995
14086
|
if (!byId.has(row.id))
|
|
13996
14087
|
byId.set(row.id, row);
|
|
13997
14088
|
}
|
|
14089
|
+
if (options.integrity && stat && Buffer.byteLength(text) === stat.size)
|
|
14090
|
+
tamperCache.set(file.value, {
|
|
14091
|
+
size: stat.size,
|
|
14092
|
+
stamp: fileStamp(stat),
|
|
14093
|
+
breaks: integrity.unverified.filter((row) => TAMPER_REASONS.has(row.reason))
|
|
14094
|
+
});
|
|
13998
14095
|
return {
|
|
13999
14096
|
ok: true,
|
|
14000
14097
|
value: { path: file.value, rows, skipped, integrity: options.integrity ? integrity : null }
|
|
14001
14098
|
};
|
|
14002
14099
|
}
|
|
14100
|
+
var TAMPER_REASONS = new Set(["hash_mismatch", "broken_link"]);
|
|
14101
|
+
var tamperCache = new Map;
|
|
14102
|
+
var fileStamp = (stat) => `${stat.ino}:${stat.mtimeMs}:${stat.ctimeMs}`;
|
|
14103
|
+
function ledgerTampering(cwd) {
|
|
14104
|
+
const file = ledgerPath(cwd);
|
|
14105
|
+
if (!file.ok)
|
|
14106
|
+
return [];
|
|
14107
|
+
try {
|
|
14108
|
+
const stat = fs12.statSync(file.value);
|
|
14109
|
+
const cached = tamperCache.get(file.value);
|
|
14110
|
+
if (cached && cached.size === stat.size && cached.stamp === fileStamp(stat))
|
|
14111
|
+
return cached.breaks;
|
|
14112
|
+
} catch {
|
|
14113
|
+
return [];
|
|
14114
|
+
}
|
|
14115
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14116
|
+
return read.ok ? (read.value.integrity?.unverified ?? []).filter((row) => TAMPER_REASONS.has(row.reason)) : [];
|
|
14117
|
+
}
|
|
14118
|
+
function tamperedUnblock(cwd) {
|
|
14119
|
+
const file = ledgerPath(cwd);
|
|
14120
|
+
const where = file.ok ? file.value : "the ledger file";
|
|
14121
|
+
return `workit ledger verify-integrity # lists the edited or missing rows; if the edit is understood, move ${where} aside (mv ${where} ${where}.tampered-<date>) and record the verdicts again`;
|
|
14122
|
+
}
|
|
14123
|
+
var tamperedWarning = (cwd, breaks) => `the ledger was edited, or a row was removed or moved (${breaks.slice(0, 3).map((row) => `${row.reason} at row ${row.seq}`).join(", ")}${breaks.length > 3 ? ", …" : ""}): no verdict is accepted until it is repaired. ${tamperedUnblock(cwd)}`;
|
|
14003
14124
|
function verdictBasis(verdict, current) {
|
|
14004
14125
|
if (verdict.head && verdict.head === current.head)
|
|
14005
14126
|
return "fresh";
|
|
@@ -14007,7 +14128,7 @@ function verdictBasis(verdict, current) {
|
|
|
14007
14128
|
return "carried";
|
|
14008
14129
|
return "stale";
|
|
14009
14130
|
}
|
|
14010
|
-
var isLiveVerdict = (row) => row.type === "verdict" && typeof row.result === "string" && !row.
|
|
14131
|
+
var isLiveVerdict = (row, retired) => row.type === "verdict" && typeof row.result === "string" && !retired.has(row.id);
|
|
14011
14132
|
function trailerSessions(cwd, base, head) {
|
|
14012
14133
|
if (!base || !head || !safeRef(base) || !safeRef(head))
|
|
14013
14134
|
return [];
|
|
@@ -14088,6 +14209,56 @@ var authoredBy = (authors, session) => {
|
|
|
14088
14209
|
return true;
|
|
14089
14210
|
return false;
|
|
14090
14211
|
};
|
|
14212
|
+
var TRUST_REASONS = new Set([
|
|
14213
|
+
"unsigned",
|
|
14214
|
+
"hash_mismatch",
|
|
14215
|
+
"broken_link",
|
|
14216
|
+
"duplicate_id",
|
|
14217
|
+
"unissued_session",
|
|
14218
|
+
"unbound_session"
|
|
14219
|
+
]);
|
|
14220
|
+
var SHELL_VOUCHED_HOSTS = new Set([
|
|
14221
|
+
"cli",
|
|
14222
|
+
"opencode",
|
|
14223
|
+
"pi",
|
|
14224
|
+
"codex_cli",
|
|
14225
|
+
"codex_desktop"
|
|
14226
|
+
]);
|
|
14227
|
+
function sessionTrust(cwd, row) {
|
|
14228
|
+
const session = row.actor.session;
|
|
14229
|
+
if (!session || row.self === true)
|
|
14230
|
+
return null;
|
|
14231
|
+
const source = typeof row.sessionSource === "string" ? row.sessionSource : "env";
|
|
14232
|
+
const host = row.actor.host;
|
|
14233
|
+
const issuedOnly = source === "flag" ? host !== "cli" : source === "env" && session.includes(":") && !SHELL_VOUCHED_HOSTS.has(host);
|
|
14234
|
+
if (!issuedOnly)
|
|
14235
|
+
return null;
|
|
14236
|
+
const binding = issuedBinding(cwd, session);
|
|
14237
|
+
if (binding === null)
|
|
14238
|
+
return "unissued_session";
|
|
14239
|
+
return binding === "token" && row.sessionBound !== true ? "unbound_session" : null;
|
|
14240
|
+
}
|
|
14241
|
+
function trustReasons(cwd, row) {
|
|
14242
|
+
if (row.integrity === "unverified")
|
|
14243
|
+
return [row.integrityReason ?? "unsigned"];
|
|
14244
|
+
if (row.integrity !== "verified")
|
|
14245
|
+
return ["unsigned"];
|
|
14246
|
+
const session = sessionTrust(cwd, row);
|
|
14247
|
+
return session ? [session] : [];
|
|
14248
|
+
}
|
|
14249
|
+
function withIntegrity(cwd, rows) {
|
|
14250
|
+
if (rows.every((row) => row.integrity !== undefined))
|
|
14251
|
+
return rows;
|
|
14252
|
+
const read = readLedger(cwd, { integrity: true });
|
|
14253
|
+
const bySeq = new Map(read.ok ? read.value.rows.map((row) => [row.seq, row]) : []);
|
|
14254
|
+
return rows.map((row) => {
|
|
14255
|
+
if (row.integrity !== undefined)
|
|
14256
|
+
return row;
|
|
14257
|
+
const fresh = bySeq.get(row.seq);
|
|
14258
|
+
return fresh && fresh.id === row.id && fresh.at === row.at ? fresh : row;
|
|
14259
|
+
});
|
|
14260
|
+
}
|
|
14261
|
+
var untrusted = (reasons) => reasons.some((reason) => TRUST_REASONS.has(reason));
|
|
14091
14262
|
var independenceReasons = (row, authors) => {
|
|
14092
14263
|
const reasons = [];
|
|
14093
14264
|
if (!row.actor.session)
|
|
@@ -14098,15 +14269,17 @@ var independenceReasons = (row, authors) => {
|
|
|
14098
14269
|
reasons.push("self");
|
|
14099
14270
|
return reasons;
|
|
14100
14271
|
};
|
|
14101
|
-
function effectiveOf(rows,
|
|
14272
|
+
function effectiveOf(rows, reasonsOf, sameCode, isCurrent) {
|
|
14102
14273
|
let effective = null;
|
|
14103
14274
|
for (const row of rows) {
|
|
14104
14275
|
if (!effective) {
|
|
14105
14276
|
effective = row;
|
|
14106
14277
|
continue;
|
|
14107
14278
|
}
|
|
14108
|
-
|
|
14109
|
-
|
|
14279
|
+
if (untrusted(reasonsOf(row)) && !untrusted(reasonsOf(effective)))
|
|
14280
|
+
continue;
|
|
14281
|
+
const rowIndependent = reasonsOf(row).length === 0;
|
|
14282
|
+
const effIndependent = reasonsOf(effective).length === 0;
|
|
14110
14283
|
if (effIndependent && !rowIndependent && isCurrent(effective))
|
|
14111
14284
|
continue;
|
|
14112
14285
|
if (effIndependent && FAILING.has(String(effective.result)) && row.actor.session === effective.actor.session && sameCode(effective, row))
|
|
@@ -14120,7 +14293,7 @@ function restackCarries(rows, branch, from, to) {
|
|
|
14120
14293
|
return false;
|
|
14121
14294
|
const edges = new Map;
|
|
14122
14295
|
for (const row of rows)
|
|
14123
|
-
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
14296
|
+
if (row.type === "stack.restacked" && row.observer === "workit_cli" && row.integrity === "verified" && row.branch === branch && row.patchEqual === true && typeof row.fromHead === "string" && row.head)
|
|
14124
14297
|
edges.set(row.fromHead, [...edges.get(row.fromHead) ?? [], row.head]);
|
|
14125
14298
|
const seen = new Set([from]);
|
|
14126
14299
|
const queue = [from];
|
|
@@ -14136,7 +14309,8 @@ function restackCarries(rows, branch, from, to) {
|
|
|
14136
14309
|
}
|
|
14137
14310
|
return false;
|
|
14138
14311
|
}
|
|
14139
|
-
function checkVerdicts(cwd, branch,
|
|
14312
|
+
function checkVerdicts(cwd, branch, ledgerRows) {
|
|
14313
|
+
const rows = withIntegrity(cwd, ledgerRows);
|
|
14140
14314
|
const head = branchHead(cwd, branch);
|
|
14141
14315
|
const fallbackBase = defaultBase(cwd);
|
|
14142
14316
|
const scope = branchScope(cwd, branch);
|
|
@@ -14166,21 +14340,47 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
14166
14340
|
return verdictBasis(failed, { head: row.head, ...keys }) !== "stale" || restackCarries(rows, branch, failed.head, row.head);
|
|
14167
14341
|
};
|
|
14168
14342
|
const authors = authorSessions(cwd, rows, branch, { base: fallbackBase, head }, scope);
|
|
14343
|
+
const failureAuthorCache = new Map;
|
|
14344
|
+
const failureAuthors = (row) => {
|
|
14345
|
+
let set = failureAuthorCache.get(row.seq);
|
|
14346
|
+
if (!set) {
|
|
14347
|
+
set = authorSessions(cwd, rows.filter((other) => other.integrity === "verified" && other.seq < row.seq), branch, { base: null, head: null }, scope);
|
|
14348
|
+
failureAuthorCache.set(row.seq, set);
|
|
14349
|
+
}
|
|
14350
|
+
return set;
|
|
14351
|
+
};
|
|
14352
|
+
const reasonCache = new Map;
|
|
14353
|
+
const reasonsOf = (row) => {
|
|
14354
|
+
let reasons = reasonCache.get(row);
|
|
14355
|
+
if (!reasons) {
|
|
14356
|
+
const failing = FAILING.has(String(row.result));
|
|
14357
|
+
reasons = [
|
|
14358
|
+
...trustReasons(cwd, row),
|
|
14359
|
+
...independenceReasons(row, failing ? failureAuthors(row) : authors)
|
|
14360
|
+
];
|
|
14361
|
+
reasonCache.set(row, reasons);
|
|
14362
|
+
}
|
|
14363
|
+
return reasons;
|
|
14364
|
+
};
|
|
14365
|
+
const retired = new Set;
|
|
14366
|
+
for (const row of rows)
|
|
14367
|
+
if (row.type === "verdict" && row.supersedes && !row.supersedeIgnored && trustReasons(cwd, row).length === 0)
|
|
14368
|
+
retired.add(row.supersedes);
|
|
14169
14369
|
const byKind = new Map;
|
|
14170
14370
|
for (const row of rows)
|
|
14171
|
-
if (isLiveVerdict(row) && scope(row)) {
|
|
14371
|
+
if (isLiveVerdict(row, retired) && scope(row)) {
|
|
14172
14372
|
const kind = typeof row.kind === "string" ? row.kind : "review";
|
|
14173
14373
|
byKind.set(kind, [...byKind.get(kind) ?? [], row]);
|
|
14174
14374
|
}
|
|
14175
14375
|
const verdicts = [];
|
|
14176
14376
|
const warnings = [];
|
|
14177
14377
|
for (const [kind, list] of byKind) {
|
|
14178
|
-
const row = effectiveOf(list,
|
|
14378
|
+
const row = effectiveOf(list, reasonsOf, sameCode, (candidate) => basisOf(candidate) !== "stale");
|
|
14179
14379
|
if (!row)
|
|
14180
14380
|
continue;
|
|
14181
14381
|
const basis = basisOf(row);
|
|
14182
14382
|
const current = basis !== "stale";
|
|
14183
|
-
const independence =
|
|
14383
|
+
const independence = reasonsOf(row);
|
|
14184
14384
|
const reasons = [
|
|
14185
14385
|
...current ? [] : ["stale"],
|
|
14186
14386
|
...PASSING.has(String(row.result)) ? [] : ["not_passing"],
|
|
@@ -14197,23 +14397,31 @@ function checkVerdicts(cwd, branch, rows) {
|
|
|
14197
14397
|
});
|
|
14198
14398
|
if (reasons.length === 0) {
|
|
14199
14399
|
for (const later of list)
|
|
14200
|
-
if (later.seq > row.seq &&
|
|
14400
|
+
if (later.seq > row.seq && reasonsOf(later).length > 0 && !untrusted(reasonsOf(later)) && FAILING.has(String(later.result)) && basisOf(later) !== "stale")
|
|
14201
14401
|
warnings.push(`the author's own (self) ${String(later.result)} ${kind} verdict ${later.id} by ${later.actor.session ?? "no session"} on the current code is newer than the accepted pass ${row.id}: read it before merging`);
|
|
14202
14402
|
}
|
|
14203
14403
|
}
|
|
14204
14404
|
for (const row of rows)
|
|
14205
14405
|
if (row.integrity === "unverified" && scope(row))
|
|
14206
14406
|
warnings.push(`ledger row ${row.id} (${row.type}) on ${row.branch ?? branch} is unverified: the workit CLI did not write it or it changed afterwards (workit ledger verify-integrity)`);
|
|
14407
|
+
const tampered = ledgerTampering(cwd);
|
|
14408
|
+
if (tampered.length) {
|
|
14409
|
+
warnings.push(tamperedWarning(cwd, tampered));
|
|
14410
|
+
for (const entry of verdicts) {
|
|
14411
|
+
entry.accepted = false;
|
|
14412
|
+
entry.reasons = [...entry.reasons, "ledger_tampered"];
|
|
14413
|
+
}
|
|
14414
|
+
}
|
|
14207
14415
|
verdicts.sort((a, b) => a.verdict.seq - b.verdict.seq);
|
|
14208
14416
|
const newestCurrent = verdicts.findLast((entry) => entry.current) ?? verdicts.at(-1) ?? null;
|
|
14209
14417
|
const failing = verdicts.find((entry) => entry.current && entry.independent && FAILING.has(String(entry.verdict.result)));
|
|
14210
14418
|
const newestAccepted = verdicts.findLast((entry) => entry.accepted && STRONG_RESULTS.has(String(entry.verdict.result))) ?? null;
|
|
14211
|
-
const accepted = newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
14419
|
+
const accepted = tampered.length ? { accepted: false, verdict: null, reasons: ["ledger_tampered"] } : newestAccepted && !failing ? { accepted: true, verdict: newestAccepted.verdict, reasons: [] } : {
|
|
14212
14420
|
accepted: false,
|
|
14213
14421
|
verdict: failing?.verdict ?? null,
|
|
14214
14422
|
reasons: failing ? ["failing_verdict"] : !newestCurrent ? ["no_verdict"] : newestCurrent.reasons.length ? newestCurrent.reasons : ["type_check_only"]
|
|
14215
14423
|
};
|
|
14216
|
-
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
14424
|
+
const selfEntry = failing ? undefined : verdicts.findLast((entry) => entry.current && !entry.independent && !untrusted(entry.reasons) && STRONG_RESULTS.has(String(entry.verdict.result)));
|
|
14217
14425
|
return {
|
|
14218
14426
|
branch,
|
|
14219
14427
|
head,
|
|
@@ -20027,27 +20235,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
20027
20235
|
};
|
|
20028
20236
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
20029
20237
|
import { realpathSync as realpathSync5 } from "node:fs";
|
|
20238
|
+
import os7 from "node:os";
|
|
20239
|
+
import path23 from "node:path";
|
|
20240
|
+
|
|
20241
|
+
// packages/workit-core/src/core/live-session.ts
|
|
20242
|
+
import { spawnSync as spawnSync13 } from "node:child_process";
|
|
20243
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
20244
|
+
import fs17 from "node:fs";
|
|
20245
|
+
import os6 from "node:os";
|
|
20030
20246
|
import path22 from "node:path";
|
|
20247
|
+
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
20248
|
+
var HEARTBEAT_EVERY_MS = 30000;
|
|
20249
|
+
var liveProbe = {
|
|
20250
|
+
platform: process.platform,
|
|
20251
|
+
run: (args) => spawnSync13("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
|
|
20252
|
+
start: (pid) => processStartOf(pid)
|
|
20253
|
+
};
|
|
20254
|
+
var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
|
|
20255
|
+
var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
20256
|
+
var processAlive = (pid) => {
|
|
20257
|
+
try {
|
|
20258
|
+
process.kill(pid, 0);
|
|
20259
|
+
return true;
|
|
20260
|
+
} catch (error) {
|
|
20261
|
+
return error.code === "EPERM";
|
|
20262
|
+
}
|
|
20263
|
+
};
|
|
20264
|
+
var processInfo = (pid) => {
|
|
20265
|
+
try {
|
|
20266
|
+
if (liveProbe.platform === "linux") {
|
|
20267
|
+
const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
20268
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
20269
|
+
const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
20270
|
+
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
20271
|
+
}
|
|
20272
|
+
if (liveProbe.platform === "win32")
|
|
20273
|
+
return null;
|
|
20274
|
+
const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
|
|
20275
|
+
return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
|
|
20276
|
+
} catch {
|
|
20277
|
+
return null;
|
|
20278
|
+
}
|
|
20279
|
+
};
|
|
20280
|
+
var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
|
|
20281
|
+
var PIDS = new Map;
|
|
20282
|
+
var hostPid = (transport) => {
|
|
20283
|
+
if (transport !== "hook-process")
|
|
20284
|
+
return process.pid;
|
|
20285
|
+
const key = `${liveProbe.platform}\x00${process.ppid}`;
|
|
20286
|
+
if (PIDS.has(key))
|
|
20287
|
+
return PIDS.get(key) ?? null;
|
|
20288
|
+
if (liveProbe.platform === "win32") {
|
|
20289
|
+
PIDS.set(key, null);
|
|
20290
|
+
return null;
|
|
20291
|
+
}
|
|
20292
|
+
let pid = process.ppid;
|
|
20293
|
+
for (let hop = 0;hop < 4; hop++) {
|
|
20294
|
+
const info = processInfo(pid);
|
|
20295
|
+
if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
|
|
20296
|
+
break;
|
|
20297
|
+
pid = info.ppid;
|
|
20298
|
+
}
|
|
20299
|
+
PIDS.set(key, pid);
|
|
20300
|
+
return pid;
|
|
20301
|
+
};
|
|
20302
|
+
var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
|
|
20303
|
+
var liveOwner = (input, transport, env = process.env) => {
|
|
20304
|
+
const session = input.session.id?.trim();
|
|
20305
|
+
if (!session)
|
|
20306
|
+
return null;
|
|
20307
|
+
const leads = new Set([leadOf(sessionSafe(session))]);
|
|
20308
|
+
if (input.session.parentId)
|
|
20309
|
+
leads.add(leadOf(sessionSafe(input.session.parentId)));
|
|
20310
|
+
if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
|
|
20311
|
+
leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
|
|
20312
|
+
return {
|
|
20313
|
+
host: input.host,
|
|
20314
|
+
session: sessionSafe(session),
|
|
20315
|
+
leads: [...leads],
|
|
20316
|
+
pid: () => hostPid(transport)
|
|
20317
|
+
};
|
|
20318
|
+
};
|
|
20319
|
+
var checkoutOf = (target) => {
|
|
20320
|
+
let dir = path22.resolve(target);
|
|
20321
|
+
while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
|
|
20322
|
+
dir = path22.dirname(dir);
|
|
20323
|
+
if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
|
|
20324
|
+
dir = path22.dirname(dir);
|
|
20325
|
+
const parts = dir.split(path22.sep);
|
|
20326
|
+
const dotGit = parts.lastIndexOf(".git");
|
|
20327
|
+
if (dotGit > 0)
|
|
20328
|
+
dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
|
|
20329
|
+
const location = resolveStore(dir);
|
|
20330
|
+
if (location instanceof Error || !location.shared || !location.top)
|
|
20331
|
+
return null;
|
|
20332
|
+
let top = location.top;
|
|
20333
|
+
try {
|
|
20334
|
+
top = fs17.realpathSync.native(top);
|
|
20335
|
+
} catch {}
|
|
20336
|
+
return top;
|
|
20337
|
+
};
|
|
20338
|
+
var markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
20339
|
+
var checkoutKey = (top, platform = process.platform) => {
|
|
20340
|
+
let key = path22.resolve(top);
|
|
20341
|
+
try {
|
|
20342
|
+
key = fs17.realpathSync.native(key);
|
|
20343
|
+
} catch {}
|
|
20344
|
+
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
20345
|
+
};
|
|
20346
|
+
var readMarker = (file) => {
|
|
20347
|
+
try {
|
|
20348
|
+
const value = JSON.parse(fs17.readFileSync(file, "utf8"));
|
|
20349
|
+
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
20350
|
+
return null;
|
|
20351
|
+
return {
|
|
20352
|
+
version: 1,
|
|
20353
|
+
session: value.session,
|
|
20354
|
+
leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
|
|
20355
|
+
host: value.host,
|
|
20356
|
+
pid: typeof value.pid === "number" ? value.pid : null,
|
|
20357
|
+
pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
|
|
20358
|
+
machine: typeof value.machine === "string" ? value.machine : "",
|
|
20359
|
+
checkout: typeof value.checkout === "string" ? value.checkout : "",
|
|
20360
|
+
heartbeatAt: value.heartbeatAt
|
|
20361
|
+
};
|
|
20362
|
+
} catch {
|
|
20363
|
+
return null;
|
|
20364
|
+
}
|
|
20365
|
+
};
|
|
20366
|
+
var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
|
|
20367
|
+
var isLive = (marker, deps) => {
|
|
20368
|
+
const age = ageOf2(marker, deps.now ?? new Date);
|
|
20369
|
+
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
20370
|
+
return false;
|
|
20371
|
+
if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
|
|
20372
|
+
return true;
|
|
20373
|
+
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
20374
|
+
return false;
|
|
20375
|
+
if (marker.pidStart === null)
|
|
20376
|
+
return true;
|
|
20377
|
+
const start = liveProbe.start(marker.pid);
|
|
20378
|
+
return start === null || start === marker.pidStart;
|
|
20379
|
+
};
|
|
20380
|
+
var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
|
|
20381
|
+
var sameOwner = (marker, owner, machine) => {
|
|
20382
|
+
if (sameLead(marker, owner))
|
|
20383
|
+
return true;
|
|
20384
|
+
if (marker.machine !== machine)
|
|
20385
|
+
return false;
|
|
20386
|
+
const pid = owner.pid();
|
|
20387
|
+
if (pid === null || marker.pid === null || marker.pidStart === null)
|
|
20388
|
+
return familyOf(marker.host) === familyOf(owner.host);
|
|
20389
|
+
return marker.pid === pid;
|
|
20390
|
+
};
|
|
20391
|
+
var liveConflict = (target, owner, deps = {}) => {
|
|
20392
|
+
try {
|
|
20393
|
+
const checkout = checkoutOf(target);
|
|
20394
|
+
if (!checkout)
|
|
20395
|
+
return null;
|
|
20396
|
+
const file = markerFile2(checkout);
|
|
20397
|
+
const marker = readMarker(file);
|
|
20398
|
+
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
20399
|
+
return null;
|
|
20400
|
+
if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
|
|
20401
|
+
return null;
|
|
20402
|
+
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
20403
|
+
} catch {
|
|
20404
|
+
return null;
|
|
20405
|
+
}
|
|
20406
|
+
};
|
|
20407
|
+
var claimLiveSession = (target, owner, deps = {}) => {
|
|
20408
|
+
try {
|
|
20409
|
+
const checkout = checkoutOf(target);
|
|
20410
|
+
if (!checkout)
|
|
20411
|
+
return;
|
|
20412
|
+
const file = markerFile2(checkout);
|
|
20413
|
+
const now = deps.now ?? new Date;
|
|
20414
|
+
const machine = deps.machine ?? os6.hostname();
|
|
20415
|
+
const current = readMarker(file);
|
|
20416
|
+
if (current) {
|
|
20417
|
+
const age = ageOf2(current, now);
|
|
20418
|
+
if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
|
|
20419
|
+
return;
|
|
20420
|
+
if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
|
|
20421
|
+
return;
|
|
20422
|
+
}
|
|
20423
|
+
const pid = owner.pid();
|
|
20424
|
+
const marker = {
|
|
20425
|
+
version: 1,
|
|
20426
|
+
session: owner.session,
|
|
20427
|
+
leads: owner.leads,
|
|
20428
|
+
host: owner.host,
|
|
20429
|
+
pid,
|
|
20430
|
+
pidStart: pid === null ? null : liveProbe.start(pid),
|
|
20431
|
+
machine,
|
|
20432
|
+
checkout,
|
|
20433
|
+
heartbeatAt: now.toISOString()
|
|
20434
|
+
};
|
|
20435
|
+
fs17.mkdirSync(path22.dirname(file), { recursive: true });
|
|
20436
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
20437
|
+
fs17.writeFileSync(temp, JSON.stringify(marker));
|
|
20438
|
+
fs17.renameSync(temp, file);
|
|
20439
|
+
} catch {}
|
|
20440
|
+
};
|
|
20441
|
+
|
|
20442
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
20031
20443
|
var NONE3 = { kind: "none" };
|
|
20032
20444
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20033
20445
|
var canonical2 = (file) => {
|
|
20034
20446
|
let rest = "";
|
|
20035
|
-
for (let dir =
|
|
20447
|
+
for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
|
|
20036
20448
|
try {
|
|
20037
|
-
return
|
|
20449
|
+
return path23.join(realpathSync5(dir), rest);
|
|
20038
20450
|
} catch {
|
|
20039
|
-
if (
|
|
20040
|
-
return
|
|
20041
|
-
rest = rest ?
|
|
20451
|
+
if (path23.dirname(dir) === dir)
|
|
20452
|
+
return path23.resolve(file);
|
|
20453
|
+
rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
|
|
20042
20454
|
}
|
|
20043
20455
|
}
|
|
20044
20456
|
};
|
|
20045
20457
|
var exempt = (cwd, root, file, plans) => {
|
|
20046
20458
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
20047
|
-
const relative =
|
|
20048
|
-
if (relative.startsWith("..") ||
|
|
20459
|
+
const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
|
|
20460
|
+
if (relative.startsWith("..") || path23.isAbsolute(relative))
|
|
20049
20461
|
return true;
|
|
20050
|
-
const posix = relative.split(
|
|
20462
|
+
const posix = relative.split(path23.sep).join("/");
|
|
20051
20463
|
const segments = posix.split("/");
|
|
20052
20464
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20053
20465
|
};
|
|
@@ -20145,7 +20557,7 @@ var shellWrites = (command) => {
|
|
|
20145
20557
|
return { writes: true, targets: unknown ? null : targets };
|
|
20146
20558
|
};
|
|
20147
20559
|
var cache2 = new Map;
|
|
20148
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
20560
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
|
|
20149
20561
|
var gateFor = (cwd) => {
|
|
20150
20562
|
const store = new TaskStore(cwd);
|
|
20151
20563
|
const listed = store.listTaskIndex();
|
|
@@ -20183,7 +20595,218 @@ var gateFor = (cwd) => {
|
|
|
20183
20595
|
cache2.delete(cache2.keys().next().value);
|
|
20184
20596
|
return gate;
|
|
20185
20597
|
};
|
|
20186
|
-
|
|
20598
|
+
var MUTATING_GIT = new Set([
|
|
20599
|
+
"add",
|
|
20600
|
+
"am",
|
|
20601
|
+
"apply",
|
|
20602
|
+
"checkout",
|
|
20603
|
+
"cherry-pick",
|
|
20604
|
+
"clean",
|
|
20605
|
+
"commit",
|
|
20606
|
+
"merge",
|
|
20607
|
+
"mv",
|
|
20608
|
+
"pull",
|
|
20609
|
+
"rebase",
|
|
20610
|
+
"reset",
|
|
20611
|
+
"restore",
|
|
20612
|
+
"revert",
|
|
20613
|
+
"rm",
|
|
20614
|
+
"stash",
|
|
20615
|
+
"switch"
|
|
20616
|
+
]);
|
|
20617
|
+
var DRY_N = new Set(["add", "clean", "rm", "mv"]);
|
|
20618
|
+
var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
|
|
20619
|
+
var DIR_TARGET = ".workit-live-target";
|
|
20620
|
+
var OVERRIDE = "WORKIT_LIVE_SESSION=override";
|
|
20621
|
+
var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
|
|
20622
|
+
var gitMutationDirs = (cwd, command, dialect) => {
|
|
20623
|
+
let invocations;
|
|
20624
|
+
try {
|
|
20625
|
+
invocations = rawInvocations(command, dialect);
|
|
20626
|
+
} catch {
|
|
20627
|
+
return [];
|
|
20628
|
+
}
|
|
20629
|
+
return invocations.flatMap((invocation) => {
|
|
20630
|
+
if (invocation.tool !== "git" || invocation.dir === undefined)
|
|
20631
|
+
return [];
|
|
20632
|
+
const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
|
|
20633
|
+
const sub = invocation.args[at] ?? "";
|
|
20634
|
+
const rest = invocation.args.slice(at + 1);
|
|
20635
|
+
if (!MUTATING_GIT.has(sub) || inert(rest, sub))
|
|
20636
|
+
return [];
|
|
20637
|
+
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
20638
|
+
return [];
|
|
20639
|
+
return [path23.resolve(cwd, invocation.dir ?? ".")];
|
|
20640
|
+
});
|
|
20641
|
+
};
|
|
20642
|
+
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
20643
|
+
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
20644
|
+
var POPD2 = new Set(["popd", "pop-location"]);
|
|
20645
|
+
var cdInto = (dir, words) => {
|
|
20646
|
+
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
20647
|
+
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
20648
|
+
return;
|
|
20649
|
+
return target === undefined ? os7.homedir() : path23.resolve(dir, target);
|
|
20650
|
+
};
|
|
20651
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
20652
|
+
var segmentTargets = (cwd, command, dialect) => {
|
|
20653
|
+
const targets = [];
|
|
20654
|
+
let dir = cwd;
|
|
20655
|
+
let stack = [];
|
|
20656
|
+
const groups = [];
|
|
20657
|
+
const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
|
|
20658
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
20659
|
+
if (segment.group === "open") {
|
|
20660
|
+
groups.push({ dir, stack: [...stack] });
|
|
20661
|
+
continue;
|
|
20662
|
+
}
|
|
20663
|
+
if (segment.group === "close") {
|
|
20664
|
+
const saved = groups.pop();
|
|
20665
|
+
if (saved)
|
|
20666
|
+
({ dir, stack } = saved);
|
|
20667
|
+
continue;
|
|
20668
|
+
}
|
|
20669
|
+
const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
|
|
20670
|
+
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
20671
|
+
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
20672
|
+
if (CD2.has(name)) {
|
|
20673
|
+
dir = cdInto(dir, words);
|
|
20674
|
+
continue;
|
|
20675
|
+
}
|
|
20676
|
+
if (PUSHD2.has(name)) {
|
|
20677
|
+
stack.push(dir);
|
|
20678
|
+
dir = cdInto(dir, words);
|
|
20679
|
+
continue;
|
|
20680
|
+
}
|
|
20681
|
+
if (POPD2.has(name)) {
|
|
20682
|
+
dir = stack.length ? stack.pop() : undefined;
|
|
20683
|
+
continue;
|
|
20684
|
+
}
|
|
20685
|
+
for (const file of segment.redirects)
|
|
20686
|
+
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
20687
|
+
const resolved = at(file);
|
|
20688
|
+
if (resolved)
|
|
20689
|
+
targets.push(resolved);
|
|
20690
|
+
}
|
|
20691
|
+
const edits = editsOf(segment.words);
|
|
20692
|
+
if (edits === null) {
|
|
20693
|
+
if (dir !== undefined)
|
|
20694
|
+
targets.push(path23.join(dir, DIR_TARGET));
|
|
20695
|
+
} else
|
|
20696
|
+
for (const file of edits) {
|
|
20697
|
+
const resolved = at(file);
|
|
20698
|
+
if (resolved)
|
|
20699
|
+
targets.push(resolved);
|
|
20700
|
+
}
|
|
20701
|
+
if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
|
|
20702
|
+
targets.push(path23.join(dir, DIR_TARGET));
|
|
20703
|
+
}
|
|
20704
|
+
return targets;
|
|
20705
|
+
};
|
|
20706
|
+
var overridden = (command, dialect) => {
|
|
20707
|
+
const segments = segmentsOf(command, dialect);
|
|
20708
|
+
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
20709
|
+
return false;
|
|
20710
|
+
const words = segments[0].words;
|
|
20711
|
+
const lead = words.findIndex((word) => !/^\w+=/.test(word));
|
|
20712
|
+
return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
|
|
20713
|
+
};
|
|
20714
|
+
var HOST_LABELS = {
|
|
20715
|
+
claude_code: "Claude Code",
|
|
20716
|
+
opencode: "OpenCode",
|
|
20717
|
+
codex_cli: "Codex CLI",
|
|
20718
|
+
codex_desktop: "Codex Desktop",
|
|
20719
|
+
cursor: "Cursor",
|
|
20720
|
+
pi: "Pi"
|
|
20721
|
+
};
|
|
20722
|
+
var ageText = (ms) => {
|
|
20723
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
20724
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
|
|
20725
|
+
};
|
|
20726
|
+
var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
20727
|
+
const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
|
|
20728
|
+
return {
|
|
20729
|
+
kind: "deny",
|
|
20730
|
+
reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
|
|
20731
|
+
unblock
|
|
20732
|
+
};
|
|
20733
|
+
};
|
|
20734
|
+
var guarded = (targets) => {
|
|
20735
|
+
const seen = new Set;
|
|
20736
|
+
return targets.filter((target) => {
|
|
20737
|
+
const dir = path23.dirname(target);
|
|
20738
|
+
if (seen.has(dir))
|
|
20739
|
+
return false;
|
|
20740
|
+
seen.add(dir);
|
|
20741
|
+
return true;
|
|
20742
|
+
});
|
|
20743
|
+
};
|
|
20744
|
+
var liveGate = (owner, targets, deps) => {
|
|
20745
|
+
try {
|
|
20746
|
+
for (const target of guarded(targets)) {
|
|
20747
|
+
const conflict = liveConflict(target, owner, deps);
|
|
20748
|
+
if (conflict && workspaceLiveSessionGuard(conflict.checkout))
|
|
20749
|
+
return liveDeny(conflict);
|
|
20750
|
+
}
|
|
20751
|
+
} catch {}
|
|
20752
|
+
return NONE3;
|
|
20753
|
+
};
|
|
20754
|
+
var claimAll = (owner, targets, deps) => {
|
|
20755
|
+
for (const target of guarded(targets))
|
|
20756
|
+
claimLiveSession(target, owner, deps);
|
|
20757
|
+
};
|
|
20758
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)];
|
|
20759
|
+
function writeGate(cwd, paths, owner, deps) {
|
|
20760
|
+
if (!owner)
|
|
20761
|
+
return beforeWriteGate(cwd, paths);
|
|
20762
|
+
const targets = liveTargets(cwd, paths);
|
|
20763
|
+
const live = liveGate(owner, targets, deps);
|
|
20764
|
+
if (live.kind !== "none")
|
|
20765
|
+
return live;
|
|
20766
|
+
const decision = beforeWriteGate(cwd, paths);
|
|
20767
|
+
if (decision.kind === "none")
|
|
20768
|
+
claimAll(owner, targets, deps);
|
|
20769
|
+
return decision;
|
|
20770
|
+
}
|
|
20771
|
+
function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
20772
|
+
const writes = shellWrites(command);
|
|
20773
|
+
let targets = [];
|
|
20774
|
+
if (owner) {
|
|
20775
|
+
try {
|
|
20776
|
+
targets = [
|
|
20777
|
+
...segmentTargets(cwd, command, dialect),
|
|
20778
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
|
|
20779
|
+
];
|
|
20780
|
+
} catch {
|
|
20781
|
+
targets = [];
|
|
20782
|
+
}
|
|
20783
|
+
}
|
|
20784
|
+
if (owner && targets.length) {
|
|
20785
|
+
const live = liveGate(owner, targets, deps);
|
|
20786
|
+
if (live.kind !== "none") {
|
|
20787
|
+
if (!overridden(command, dialect))
|
|
20788
|
+
return live;
|
|
20789
|
+
noteOverride(cwd, owner, command);
|
|
20790
|
+
}
|
|
20791
|
+
}
|
|
20792
|
+
const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
|
|
20793
|
+
if (owner && targets.length && decision.kind === "none")
|
|
20794
|
+
claimAll(owner, targets, deps);
|
|
20795
|
+
return decision;
|
|
20796
|
+
}
|
|
20797
|
+
var noteOverride = (cwd, owner, command) => {
|
|
20798
|
+
try {
|
|
20799
|
+
if (!inWorkitWorkspace(cwd))
|
|
20800
|
+
return;
|
|
20801
|
+
appendHookObserved(cwd, {
|
|
20802
|
+
type: "live_session.override",
|
|
20803
|
+
actor: { host: owner.host, session: owner.session, agentId: null },
|
|
20804
|
+
session: owner.session,
|
|
20805
|
+
command: command.slice(0, 200)
|
|
20806
|
+
});
|
|
20807
|
+
} catch {}
|
|
20808
|
+
};
|
|
20809
|
+
function beforeWriteGate(cwd, paths) {
|
|
20187
20810
|
try {
|
|
20188
20811
|
const gate = gateFor(cwd);
|
|
20189
20812
|
if (!gate || gate.blockers.length === 0)
|
|
@@ -20201,7 +20824,7 @@ function writeGate(cwd, paths) {
|
|
|
20201
20824
|
}
|
|
20202
20825
|
}
|
|
20203
20826
|
// packages/workit-core/src/hooks/stop.ts
|
|
20204
|
-
import { spawnSync as
|
|
20827
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
20205
20828
|
var NONE4 = { kind: "none" };
|
|
20206
20829
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20207
20830
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -20222,7 +20845,7 @@ var asksUser = (message) => {
|
|
|
20222
20845
|
};
|
|
20223
20846
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
20224
20847
|
var gitList = (cwd, args) => {
|
|
20225
|
-
const run =
|
|
20848
|
+
const run = spawnSync14("git", args, {
|
|
20226
20849
|
cwd,
|
|
20227
20850
|
encoding: "utf8",
|
|
20228
20851
|
timeout: 3000,
|
|
@@ -20313,10 +20936,10 @@ function stopObligation(input, descriptor) {
|
|
|
20313
20936
|
const branch = currentBranch(input.cwd);
|
|
20314
20937
|
if (!branch || guardedBranch(input.cwd, branch))
|
|
20315
20938
|
return null;
|
|
20316
|
-
const ledger = readLedger(input.cwd);
|
|
20939
|
+
const ledger = readLedger(input.cwd, { integrity: true });
|
|
20317
20940
|
if (!ledger.ok)
|
|
20318
20941
|
return null;
|
|
20319
|
-
const all = ledger.value.rows;
|
|
20942
|
+
const all = ledger.value.rows.filter((row) => row.integrity !== "unverified");
|
|
20320
20943
|
const rows = all.filter((row) => row.branch === branch);
|
|
20321
20944
|
const mine = rows.filter((row) => sessionOf(row) === session);
|
|
20322
20945
|
const commits = new Set(mine.filter((row) => row.type === "commit.recorded").map((row) => typeof row.sha === "string" ? row.sha : row.head).filter((sha) => typeof sha === "string"));
|
|
@@ -20586,12 +21209,12 @@ ${line}` };
|
|
|
20586
21209
|
};
|
|
20587
21210
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20588
21211
|
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
20589
|
-
import
|
|
21212
|
+
import path24 from "node:path";
|
|
20590
21213
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20591
21214
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
20592
21215
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
20593
21216
|
var existingDirectory = (value) => {
|
|
20594
|
-
if (!nonEmpty2(value) || !
|
|
21217
|
+
if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
|
|
20595
21218
|
return null;
|
|
20596
21219
|
try {
|
|
20597
21220
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -20692,9 +21315,9 @@ ${subagentStartText({ ...input, cwd: "" }, descriptor, event)}`;
|
|
|
20692
21315
|
if (role !== "judge" || !event.agentId)
|
|
20693
21316
|
return readOnly;
|
|
20694
21317
|
const own = subagentSession(session, event.agentId);
|
|
20695
|
-
|
|
20696
|
-
|
|
20697
|
-
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result>
|
|
21318
|
+
const token = session ? recordIssuedSession(input.cwd || process.cwd(), session, own) : null;
|
|
21319
|
+
const flags = token ? `--session ${own} --token ${token}` : `--session ${own}`;
|
|
21320
|
+
return `${readOnly} Its own Workit session is ${own}: record verdicts with \`workit ledger verdict <result> ${flags} ...\` so the ledger tells it apart from the author.${token ? " The token is this subagent's alone: never write it into a report, a file or a commit. --token needs a workit CLI at least as new as this plugin; if the CLI answers that --token is an unknown option, update the workit CLI (workit upgrade --cli) and record again." : ""}`;
|
|
20698
21321
|
};
|
|
20699
21322
|
function handleHook(input, deps) {
|
|
20700
21323
|
const { descriptor } = deps;
|
|
@@ -20736,8 +21359,7 @@ function handleHook(input, deps) {
|
|
|
20736
21359
|
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
|
|
20737
21360
|
if (raw.kind === "deny")
|
|
20738
21361
|
return raw;
|
|
20739
|
-
const
|
|
20740
|
-
const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
|
|
21362
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
20741
21363
|
if (gate.kind === "deny")
|
|
20742
21364
|
return gate;
|
|
20743
21365
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
@@ -20750,7 +21372,7 @@ function handleHook(input, deps) {
|
|
|
20750
21372
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
20751
21373
|
return NONE5;
|
|
20752
21374
|
case "write.pre":
|
|
20753
|
-
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
|
|
21375
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
|
|
20754
21376
|
case "subagent.start":
|
|
20755
21377
|
return {
|
|
20756
21378
|
kind: "context",
|
|
@@ -21329,8 +21951,8 @@ var codexAdapter = {
|
|
|
21329
21951
|
};
|
|
21330
21952
|
|
|
21331
21953
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21332
|
-
import
|
|
21333
|
-
import
|
|
21954
|
+
import fs18 from "node:fs";
|
|
21955
|
+
import path25 from "node:path";
|
|
21334
21956
|
var undocumented = { support: "undocumented", native: null };
|
|
21335
21957
|
var none = { support: "none", native: null };
|
|
21336
21958
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -21527,17 +22149,17 @@ var protocolEvent2 = (input) => {
|
|
|
21527
22149
|
};
|
|
21528
22150
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
21529
22151
|
var lastAssistantText = (file) => {
|
|
21530
|
-
if (!file || !
|
|
22152
|
+
if (!file || !path25.isAbsolute(file))
|
|
21531
22153
|
return null;
|
|
21532
22154
|
try {
|
|
21533
|
-
const size =
|
|
22155
|
+
const size = fs18.statSync(file).size;
|
|
21534
22156
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
21535
22157
|
const buffer = Buffer.alloc(size - start);
|
|
21536
|
-
const fd =
|
|
22158
|
+
const fd = fs18.openSync(file, "r");
|
|
21537
22159
|
try {
|
|
21538
|
-
|
|
22160
|
+
fs18.readSync(fd, buffer, 0, buffer.length, start);
|
|
21539
22161
|
} finally {
|
|
21540
|
-
|
|
22162
|
+
fs18.closeSync(fd);
|
|
21541
22163
|
}
|
|
21542
22164
|
const lines = buffer.toString("utf8").split(`
|
|
21543
22165
|
`);
|
|
@@ -21564,7 +22186,7 @@ var lastAssistantText = (file) => {
|
|
|
21564
22186
|
return null;
|
|
21565
22187
|
}
|
|
21566
22188
|
};
|
|
21567
|
-
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(
|
|
22189
|
+
var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
|
|
21568
22190
|
var hookCwd = (input) => {
|
|
21569
22191
|
const cwd = existingDirectory(input.cwd);
|
|
21570
22192
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|