@brainervirus/workit-pi 8.13.1 → 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/dist/worker.js
CHANGED
|
@@ -8537,6 +8537,8 @@ var workspaceConfigSchema = object({
|
|
|
8537
8537
|
defaultEndpoint: unknown().optional(),
|
|
8538
8538
|
verification: _enum(["self", "independent"]).optional(),
|
|
8539
8539
|
trailers: boolean2().optional(),
|
|
8540
|
+
liveSessionGuard: boolean2().optional(),
|
|
8541
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
8540
8542
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
8541
8543
|
kind,
|
|
8542
8544
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -8581,6 +8583,17 @@ var mainCheckouts = new Map;
|
|
|
8581
8583
|
var runtimeWorkspaceIndexSchema = object({
|
|
8582
8584
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
8583
8585
|
}).passthrough();
|
|
8586
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
8587
|
+
var TIER_HOSTS = new Set([
|
|
8588
|
+
"claude_code",
|
|
8589
|
+
"opencode",
|
|
8590
|
+
"codex",
|
|
8591
|
+
"codex_cli",
|
|
8592
|
+
"codex_desktop",
|
|
8593
|
+
"cursor",
|
|
8594
|
+
"pi",
|
|
8595
|
+
"default"
|
|
8596
|
+
]);
|
|
8584
8597
|
|
|
8585
8598
|
// packages/workit-core/src/core/release-tracks.ts
|
|
8586
8599
|
var KNOWN_FIELDS = new Set([
|
package/dist/workit.js
CHANGED
|
@@ -13167,6 +13167,8 @@ var workspaceConfigSchema = object({
|
|
|
13167
13167
|
defaultEndpoint: unknown().optional(),
|
|
13168
13168
|
verification: _enum(["self", "independent"]).optional(),
|
|
13169
13169
|
trailers: boolean2().optional(),
|
|
13170
|
+
liveSessionGuard: boolean2().optional(),
|
|
13171
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
13170
13172
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13171
13173
|
kind,
|
|
13172
13174
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -13484,24 +13486,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
13484
13486
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13485
13487
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
13486
13488
|
};
|
|
13487
|
-
var
|
|
13489
|
+
var inheritedSetting = (cwd, dir, has) => {
|
|
13488
13490
|
let matched;
|
|
13489
13491
|
try {
|
|
13490
13492
|
matched = matchRuntimeWorkspace(cwd, dir);
|
|
13491
13493
|
} catch {
|
|
13492
|
-
return
|
|
13494
|
+
return null;
|
|
13493
13495
|
}
|
|
13494
13496
|
if (!matched)
|
|
13495
|
-
return
|
|
13497
|
+
return null;
|
|
13496
13498
|
const { selected, matches } = matched;
|
|
13497
13499
|
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
13498
|
-
|
|
13500
|
+
return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
|
|
13501
|
+
};
|
|
13502
|
+
var workspaceTrailers = (cwd, dir) => {
|
|
13503
|
+
const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
|
|
13499
13504
|
return {
|
|
13500
|
-
enabled: source?.trailers !== false,
|
|
13501
|
-
workspace:
|
|
13502
|
-
from: source?.name ?? null
|
|
13505
|
+
enabled: found?.source?.trailers !== false,
|
|
13506
|
+
workspace: found?.workspace ?? null,
|
|
13507
|
+
from: found?.source?.name ?? null
|
|
13503
13508
|
};
|
|
13504
13509
|
};
|
|
13510
|
+
var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
|
|
13511
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
13512
|
+
var TIER_HOSTS = new Set([
|
|
13513
|
+
"claude_code",
|
|
13514
|
+
"opencode",
|
|
13515
|
+
"codex",
|
|
13516
|
+
"codex_cli",
|
|
13517
|
+
"codex_desktop",
|
|
13518
|
+
"cursor",
|
|
13519
|
+
"pi",
|
|
13520
|
+
"default"
|
|
13521
|
+
]);
|
|
13505
13522
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
13506
13523
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13507
13524
|
if (!matched)
|
|
@@ -21803,27 +21820,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21803
21820
|
};
|
|
21804
21821
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
21805
21822
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
21823
|
+
import os7 from "node:os";
|
|
21824
|
+
import path27 from "node:path";
|
|
21825
|
+
|
|
21826
|
+
// packages/workit-core/src/core/live-session.ts
|
|
21827
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21828
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
21829
|
+
import fs19 from "node:fs";
|
|
21830
|
+
import os6 from "node:os";
|
|
21806
21831
|
import path26 from "node:path";
|
|
21832
|
+
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21833
|
+
var HEARTBEAT_EVERY_MS = 30000;
|
|
21834
|
+
var liveProbe = {
|
|
21835
|
+
platform: process.platform,
|
|
21836
|
+
run: (args) => spawnSync17("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
|
|
21837
|
+
start: (pid) => processStartOf(pid)
|
|
21838
|
+
};
|
|
21839
|
+
var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
|
|
21840
|
+
var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
21841
|
+
var processAlive = (pid) => {
|
|
21842
|
+
try {
|
|
21843
|
+
process.kill(pid, 0);
|
|
21844
|
+
return true;
|
|
21845
|
+
} catch (error) {
|
|
21846
|
+
return error.code === "EPERM";
|
|
21847
|
+
}
|
|
21848
|
+
};
|
|
21849
|
+
var processInfo = (pid) => {
|
|
21850
|
+
try {
|
|
21851
|
+
if (liveProbe.platform === "linux") {
|
|
21852
|
+
const stat = fs19.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
21853
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
21854
|
+
const command = fs19.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
21855
|
+
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
21856
|
+
}
|
|
21857
|
+
if (liveProbe.platform === "win32")
|
|
21858
|
+
return null;
|
|
21859
|
+
const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
|
|
21860
|
+
return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
|
|
21861
|
+
} catch {
|
|
21862
|
+
return null;
|
|
21863
|
+
}
|
|
21864
|
+
};
|
|
21865
|
+
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;
|
|
21866
|
+
var PIDS = new Map;
|
|
21867
|
+
var hostPid = (transport) => {
|
|
21868
|
+
if (transport !== "hook-process")
|
|
21869
|
+
return process.pid;
|
|
21870
|
+
const key = `${liveProbe.platform}\x00${process.ppid}`;
|
|
21871
|
+
if (PIDS.has(key))
|
|
21872
|
+
return PIDS.get(key) ?? null;
|
|
21873
|
+
if (liveProbe.platform === "win32") {
|
|
21874
|
+
PIDS.set(key, null);
|
|
21875
|
+
return null;
|
|
21876
|
+
}
|
|
21877
|
+
let pid = process.ppid;
|
|
21878
|
+
for (let hop = 0;hop < 4; hop++) {
|
|
21879
|
+
const info = processInfo(pid);
|
|
21880
|
+
if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
|
|
21881
|
+
break;
|
|
21882
|
+
pid = info.ppid;
|
|
21883
|
+
}
|
|
21884
|
+
PIDS.set(key, pid);
|
|
21885
|
+
return pid;
|
|
21886
|
+
};
|
|
21887
|
+
var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
|
|
21888
|
+
var liveOwner = (input, transport, env = process.env) => {
|
|
21889
|
+
const session = input.session.id?.trim();
|
|
21890
|
+
if (!session)
|
|
21891
|
+
return null;
|
|
21892
|
+
const leads = new Set([leadOf(sessionSafe(session))]);
|
|
21893
|
+
if (input.session.parentId)
|
|
21894
|
+
leads.add(leadOf(sessionSafe(input.session.parentId)));
|
|
21895
|
+
if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
|
|
21896
|
+
leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
|
|
21897
|
+
return {
|
|
21898
|
+
host: input.host,
|
|
21899
|
+
session: sessionSafe(session),
|
|
21900
|
+
leads: [...leads],
|
|
21901
|
+
pid: () => hostPid(transport)
|
|
21902
|
+
};
|
|
21903
|
+
};
|
|
21904
|
+
var checkoutOf = (target) => {
|
|
21905
|
+
let dir = path26.resolve(target);
|
|
21906
|
+
while (!fs19.existsSync(dir) && path26.dirname(dir) !== dir)
|
|
21907
|
+
dir = path26.dirname(dir);
|
|
21908
|
+
if (fs19.existsSync(dir) && !fs19.statSync(dir).isDirectory())
|
|
21909
|
+
dir = path26.dirname(dir);
|
|
21910
|
+
const parts = dir.split(path26.sep);
|
|
21911
|
+
const dotGit = parts.lastIndexOf(".git");
|
|
21912
|
+
if (dotGit > 0)
|
|
21913
|
+
dir = parts.slice(0, dotGit).join(path26.sep) || path26.sep;
|
|
21914
|
+
const location = resolveStore(dir);
|
|
21915
|
+
if (location instanceof Error || !location.shared || !location.top)
|
|
21916
|
+
return null;
|
|
21917
|
+
let top = location.top;
|
|
21918
|
+
try {
|
|
21919
|
+
top = fs19.realpathSync.native(top);
|
|
21920
|
+
} catch {}
|
|
21921
|
+
return top;
|
|
21922
|
+
};
|
|
21923
|
+
var markerFile2 = (top) => path26.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
21924
|
+
var checkoutKey = (top, platform = process.platform) => {
|
|
21925
|
+
let key = path26.resolve(top);
|
|
21926
|
+
try {
|
|
21927
|
+
key = fs19.realpathSync.native(key);
|
|
21928
|
+
} catch {}
|
|
21929
|
+
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
21930
|
+
};
|
|
21931
|
+
var readMarker = (file) => {
|
|
21932
|
+
try {
|
|
21933
|
+
const value = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
21934
|
+
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
21935
|
+
return null;
|
|
21936
|
+
return {
|
|
21937
|
+
version: 1,
|
|
21938
|
+
session: value.session,
|
|
21939
|
+
leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
|
|
21940
|
+
host: value.host,
|
|
21941
|
+
pid: typeof value.pid === "number" ? value.pid : null,
|
|
21942
|
+
pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
|
|
21943
|
+
machine: typeof value.machine === "string" ? value.machine : "",
|
|
21944
|
+
checkout: typeof value.checkout === "string" ? value.checkout : "",
|
|
21945
|
+
heartbeatAt: value.heartbeatAt
|
|
21946
|
+
};
|
|
21947
|
+
} catch {
|
|
21948
|
+
return null;
|
|
21949
|
+
}
|
|
21950
|
+
};
|
|
21951
|
+
var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
|
|
21952
|
+
var isLive = (marker, deps) => {
|
|
21953
|
+
const age = ageOf2(marker, deps.now ?? new Date);
|
|
21954
|
+
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
21955
|
+
return false;
|
|
21956
|
+
if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
|
|
21957
|
+
return true;
|
|
21958
|
+
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
21959
|
+
return false;
|
|
21960
|
+
if (marker.pidStart === null)
|
|
21961
|
+
return true;
|
|
21962
|
+
const start = liveProbe.start(marker.pid);
|
|
21963
|
+
return start === null || start === marker.pidStart;
|
|
21964
|
+
};
|
|
21965
|
+
var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
|
|
21966
|
+
var sameOwner = (marker, owner, machine) => {
|
|
21967
|
+
if (sameLead(marker, owner))
|
|
21968
|
+
return true;
|
|
21969
|
+
if (marker.machine !== machine)
|
|
21970
|
+
return false;
|
|
21971
|
+
const pid = owner.pid();
|
|
21972
|
+
if (pid === null || marker.pid === null || marker.pidStart === null)
|
|
21973
|
+
return familyOf(marker.host) === familyOf(owner.host);
|
|
21974
|
+
return marker.pid === pid;
|
|
21975
|
+
};
|
|
21976
|
+
var liveConflict = (target, owner, deps = {}) => {
|
|
21977
|
+
try {
|
|
21978
|
+
const checkout = checkoutOf(target);
|
|
21979
|
+
if (!checkout)
|
|
21980
|
+
return null;
|
|
21981
|
+
const file = markerFile2(checkout);
|
|
21982
|
+
const marker = readMarker(file);
|
|
21983
|
+
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
21984
|
+
return null;
|
|
21985
|
+
if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
|
|
21986
|
+
return null;
|
|
21987
|
+
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
21988
|
+
} catch {
|
|
21989
|
+
return null;
|
|
21990
|
+
}
|
|
21991
|
+
};
|
|
21992
|
+
var claimLiveSession = (target, owner, deps = {}) => {
|
|
21993
|
+
try {
|
|
21994
|
+
const checkout = checkoutOf(target);
|
|
21995
|
+
if (!checkout)
|
|
21996
|
+
return;
|
|
21997
|
+
const file = markerFile2(checkout);
|
|
21998
|
+
const now = deps.now ?? new Date;
|
|
21999
|
+
const machine = deps.machine ?? os6.hostname();
|
|
22000
|
+
const current = readMarker(file);
|
|
22001
|
+
if (current) {
|
|
22002
|
+
const age = ageOf2(current, now);
|
|
22003
|
+
if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
|
|
22004
|
+
return;
|
|
22005
|
+
if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
|
|
22006
|
+
return;
|
|
22007
|
+
}
|
|
22008
|
+
const pid = owner.pid();
|
|
22009
|
+
const marker = {
|
|
22010
|
+
version: 1,
|
|
22011
|
+
session: owner.session,
|
|
22012
|
+
leads: owner.leads,
|
|
22013
|
+
host: owner.host,
|
|
22014
|
+
pid,
|
|
22015
|
+
pidStart: pid === null ? null : liveProbe.start(pid),
|
|
22016
|
+
machine,
|
|
22017
|
+
checkout,
|
|
22018
|
+
heartbeatAt: now.toISOString()
|
|
22019
|
+
};
|
|
22020
|
+
fs19.mkdirSync(path26.dirname(file), { recursive: true });
|
|
22021
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
22022
|
+
fs19.writeFileSync(temp, JSON.stringify(marker));
|
|
22023
|
+
fs19.renameSync(temp, file);
|
|
22024
|
+
} catch {}
|
|
22025
|
+
};
|
|
22026
|
+
|
|
22027
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
21807
22028
|
var NONE3 = { kind: "none" };
|
|
21808
22029
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
21809
22030
|
var canonical2 = (file) => {
|
|
21810
22031
|
let rest = "";
|
|
21811
|
-
for (let dir =
|
|
22032
|
+
for (let dir = path27.resolve(file);; dir = path27.dirname(dir)) {
|
|
21812
22033
|
try {
|
|
21813
|
-
return
|
|
22034
|
+
return path27.join(realpathSync6(dir), rest);
|
|
21814
22035
|
} catch {
|
|
21815
|
-
if (
|
|
21816
|
-
return
|
|
21817
|
-
rest = rest ?
|
|
22036
|
+
if (path27.dirname(dir) === dir)
|
|
22037
|
+
return path27.resolve(file);
|
|
22038
|
+
rest = rest ? path27.join(path27.basename(dir), rest) : path27.basename(dir);
|
|
21818
22039
|
}
|
|
21819
22040
|
}
|
|
21820
22041
|
};
|
|
21821
22042
|
var exempt = (cwd, root, file, plans) => {
|
|
21822
22043
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
21823
|
-
const relative =
|
|
21824
|
-
if (relative.startsWith("..") ||
|
|
22044
|
+
const relative = path27.relative(fold(canonical2(root)), fold(canonical2(path27.resolve(cwd, file))));
|
|
22045
|
+
if (relative.startsWith("..") || path27.isAbsolute(relative))
|
|
21825
22046
|
return true;
|
|
21826
|
-
const posix = relative.split(
|
|
22047
|
+
const posix = relative.split(path27.sep).join("/");
|
|
21827
22048
|
const segments = posix.split("/");
|
|
21828
22049
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
21829
22050
|
};
|
|
@@ -21921,7 +22142,7 @@ var shellWrites = (command) => {
|
|
|
21921
22142
|
return { writes: true, targets: unknown ? null : targets };
|
|
21922
22143
|
};
|
|
21923
22144
|
var cache2 = new Map;
|
|
21924
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
22145
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path27.resolve(root, file)) ?? "-").join("\x00");
|
|
21925
22146
|
var gateFor = (cwd) => {
|
|
21926
22147
|
const store = new TaskStore(cwd);
|
|
21927
22148
|
const listed = store.listTaskIndex();
|
|
@@ -21959,7 +22180,218 @@ var gateFor = (cwd) => {
|
|
|
21959
22180
|
cache2.delete(cache2.keys().next().value);
|
|
21960
22181
|
return gate;
|
|
21961
22182
|
};
|
|
21962
|
-
|
|
22183
|
+
var MUTATING_GIT = new Set([
|
|
22184
|
+
"add",
|
|
22185
|
+
"am",
|
|
22186
|
+
"apply",
|
|
22187
|
+
"checkout",
|
|
22188
|
+
"cherry-pick",
|
|
22189
|
+
"clean",
|
|
22190
|
+
"commit",
|
|
22191
|
+
"merge",
|
|
22192
|
+
"mv",
|
|
22193
|
+
"pull",
|
|
22194
|
+
"rebase",
|
|
22195
|
+
"reset",
|
|
22196
|
+
"restore",
|
|
22197
|
+
"revert",
|
|
22198
|
+
"rm",
|
|
22199
|
+
"stash",
|
|
22200
|
+
"switch"
|
|
22201
|
+
]);
|
|
22202
|
+
var DRY_N = new Set(["add", "clean", "rm", "mv"]);
|
|
22203
|
+
var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
|
|
22204
|
+
var DIR_TARGET = ".workit-live-target";
|
|
22205
|
+
var OVERRIDE = "WORKIT_LIVE_SESSION=override";
|
|
22206
|
+
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("--"));
|
|
22207
|
+
var gitMutationDirs = (cwd, command, dialect) => {
|
|
22208
|
+
let invocations;
|
|
22209
|
+
try {
|
|
22210
|
+
invocations = rawInvocations(command, dialect);
|
|
22211
|
+
} catch {
|
|
22212
|
+
return [];
|
|
22213
|
+
}
|
|
22214
|
+
return invocations.flatMap((invocation) => {
|
|
22215
|
+
if (invocation.tool !== "git" || invocation.dir === undefined)
|
|
22216
|
+
return [];
|
|
22217
|
+
const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
|
|
22218
|
+
const sub = invocation.args[at] ?? "";
|
|
22219
|
+
const rest = invocation.args.slice(at + 1);
|
|
22220
|
+
if (!MUTATING_GIT.has(sub) || inert(rest, sub))
|
|
22221
|
+
return [];
|
|
22222
|
+
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
22223
|
+
return [];
|
|
22224
|
+
return [path27.resolve(cwd, invocation.dir ?? ".")];
|
|
22225
|
+
});
|
|
22226
|
+
};
|
|
22227
|
+
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
22228
|
+
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
22229
|
+
var POPD2 = new Set(["popd", "pop-location"]);
|
|
22230
|
+
var cdInto = (dir, words) => {
|
|
22231
|
+
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
22232
|
+
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22233
|
+
return;
|
|
22234
|
+
return target === undefined ? os7.homedir() : path27.resolve(dir, target);
|
|
22235
|
+
};
|
|
22236
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
22237
|
+
var segmentTargets = (cwd, command, dialect) => {
|
|
22238
|
+
const targets = [];
|
|
22239
|
+
let dir = cwd;
|
|
22240
|
+
let stack = [];
|
|
22241
|
+
const groups = [];
|
|
22242
|
+
const at = (file) => path27.isAbsolute(file) ? file : dir === undefined ? null : path27.resolve(dir, file);
|
|
22243
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
22244
|
+
if (segment.group === "open") {
|
|
22245
|
+
groups.push({ dir, stack: [...stack] });
|
|
22246
|
+
continue;
|
|
22247
|
+
}
|
|
22248
|
+
if (segment.group === "close") {
|
|
22249
|
+
const saved = groups.pop();
|
|
22250
|
+
if (saved)
|
|
22251
|
+
({ dir, stack } = saved);
|
|
22252
|
+
continue;
|
|
22253
|
+
}
|
|
22254
|
+
const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
|
|
22255
|
+
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
22256
|
+
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
22257
|
+
if (CD2.has(name)) {
|
|
22258
|
+
dir = cdInto(dir, words);
|
|
22259
|
+
continue;
|
|
22260
|
+
}
|
|
22261
|
+
if (PUSHD2.has(name)) {
|
|
22262
|
+
stack.push(dir);
|
|
22263
|
+
dir = cdInto(dir, words);
|
|
22264
|
+
continue;
|
|
22265
|
+
}
|
|
22266
|
+
if (POPD2.has(name)) {
|
|
22267
|
+
dir = stack.length ? stack.pop() : undefined;
|
|
22268
|
+
continue;
|
|
22269
|
+
}
|
|
22270
|
+
for (const file of segment.redirects)
|
|
22271
|
+
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
22272
|
+
const resolved = at(file);
|
|
22273
|
+
if (resolved)
|
|
22274
|
+
targets.push(resolved);
|
|
22275
|
+
}
|
|
22276
|
+
const edits = editsOf(segment.words);
|
|
22277
|
+
if (edits === null) {
|
|
22278
|
+
if (dir !== undefined)
|
|
22279
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22280
|
+
} else
|
|
22281
|
+
for (const file of edits) {
|
|
22282
|
+
const resolved = at(file);
|
|
22283
|
+
if (resolved)
|
|
22284
|
+
targets.push(resolved);
|
|
22285
|
+
}
|
|
22286
|
+
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), "")))
|
|
22287
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22288
|
+
}
|
|
22289
|
+
return targets;
|
|
22290
|
+
};
|
|
22291
|
+
var overridden = (command, dialect) => {
|
|
22292
|
+
const segments = segmentsOf(command, dialect);
|
|
22293
|
+
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
22294
|
+
return false;
|
|
22295
|
+
const words = segments[0].words;
|
|
22296
|
+
const lead = words.findIndex((word) => !/^\w+=/.test(word));
|
|
22297
|
+
return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
|
|
22298
|
+
};
|
|
22299
|
+
var HOST_LABELS = {
|
|
22300
|
+
claude_code: "Claude Code",
|
|
22301
|
+
opencode: "OpenCode",
|
|
22302
|
+
codex_cli: "Codex CLI",
|
|
22303
|
+
codex_desktop: "Codex Desktop",
|
|
22304
|
+
cursor: "Cursor",
|
|
22305
|
+
pi: "Pi"
|
|
22306
|
+
};
|
|
22307
|
+
var ageText = (ms) => {
|
|
22308
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
22309
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
|
|
22310
|
+
};
|
|
22311
|
+
var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
22312
|
+
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}.`;
|
|
22313
|
+
return {
|
|
22314
|
+
kind: "deny",
|
|
22315
|
+
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}`,
|
|
22316
|
+
unblock
|
|
22317
|
+
};
|
|
22318
|
+
};
|
|
22319
|
+
var guarded = (targets) => {
|
|
22320
|
+
const seen = new Set;
|
|
22321
|
+
return targets.filter((target) => {
|
|
22322
|
+
const dir = path27.dirname(target);
|
|
22323
|
+
if (seen.has(dir))
|
|
22324
|
+
return false;
|
|
22325
|
+
seen.add(dir);
|
|
22326
|
+
return true;
|
|
22327
|
+
});
|
|
22328
|
+
};
|
|
22329
|
+
var liveGate = (owner, targets, deps) => {
|
|
22330
|
+
try {
|
|
22331
|
+
for (const target of guarded(targets)) {
|
|
22332
|
+
const conflict = liveConflict(target, owner, deps);
|
|
22333
|
+
if (conflict && workspaceLiveSessionGuard(conflict.checkout))
|
|
22334
|
+
return liveDeny(conflict);
|
|
22335
|
+
}
|
|
22336
|
+
} catch {}
|
|
22337
|
+
return NONE3;
|
|
22338
|
+
};
|
|
22339
|
+
var claimAll = (owner, targets, deps) => {
|
|
22340
|
+
for (const target of guarded(targets))
|
|
22341
|
+
claimLiveSession(target, owner, deps);
|
|
22342
|
+
};
|
|
22343
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path27.resolve(cwd, file)) : [path27.join(cwd, DIR_TARGET)];
|
|
22344
|
+
function writeGate(cwd, paths, owner, deps) {
|
|
22345
|
+
if (!owner)
|
|
22346
|
+
return beforeWriteGate(cwd, paths);
|
|
22347
|
+
const targets = liveTargets(cwd, paths);
|
|
22348
|
+
const live = liveGate(owner, targets, deps);
|
|
22349
|
+
if (live.kind !== "none")
|
|
22350
|
+
return live;
|
|
22351
|
+
const decision = beforeWriteGate(cwd, paths);
|
|
22352
|
+
if (decision.kind === "none")
|
|
22353
|
+
claimAll(owner, targets, deps);
|
|
22354
|
+
return decision;
|
|
22355
|
+
}
|
|
22356
|
+
function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
22357
|
+
const writes = shellWrites(command);
|
|
22358
|
+
let targets = [];
|
|
22359
|
+
if (owner) {
|
|
22360
|
+
try {
|
|
22361
|
+
targets = [
|
|
22362
|
+
...segmentTargets(cwd, command, dialect),
|
|
22363
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
22364
|
+
];
|
|
22365
|
+
} catch {
|
|
22366
|
+
targets = [];
|
|
22367
|
+
}
|
|
22368
|
+
}
|
|
22369
|
+
if (owner && targets.length) {
|
|
22370
|
+
const live = liveGate(owner, targets, deps);
|
|
22371
|
+
if (live.kind !== "none") {
|
|
22372
|
+
if (!overridden(command, dialect))
|
|
22373
|
+
return live;
|
|
22374
|
+
noteOverride(cwd, owner, command);
|
|
22375
|
+
}
|
|
22376
|
+
}
|
|
22377
|
+
const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
|
|
22378
|
+
if (owner && targets.length && decision.kind === "none")
|
|
22379
|
+
claimAll(owner, targets, deps);
|
|
22380
|
+
return decision;
|
|
22381
|
+
}
|
|
22382
|
+
var noteOverride = (cwd, owner, command) => {
|
|
22383
|
+
try {
|
|
22384
|
+
if (!inWorkitWorkspace(cwd))
|
|
22385
|
+
return;
|
|
22386
|
+
appendHookObserved(cwd, {
|
|
22387
|
+
type: "live_session.override",
|
|
22388
|
+
actor: { host: owner.host, session: owner.session, agentId: null },
|
|
22389
|
+
session: owner.session,
|
|
22390
|
+
command: command.slice(0, 200)
|
|
22391
|
+
});
|
|
22392
|
+
} catch {}
|
|
22393
|
+
};
|
|
22394
|
+
function beforeWriteGate(cwd, paths) {
|
|
21963
22395
|
try {
|
|
21964
22396
|
const gate = gateFor(cwd);
|
|
21965
22397
|
if (!gate || gate.blockers.length === 0)
|
|
@@ -21977,7 +22409,7 @@ function writeGate(cwd, paths) {
|
|
|
21977
22409
|
}
|
|
21978
22410
|
}
|
|
21979
22411
|
// packages/workit-core/src/hooks/stop.ts
|
|
21980
|
-
import { spawnSync as
|
|
22412
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
21981
22413
|
var NONE4 = { kind: "none" };
|
|
21982
22414
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
21983
22415
|
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;
|
|
@@ -21998,7 +22430,7 @@ var asksUser = (message) => {
|
|
|
21998
22430
|
};
|
|
21999
22431
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
22000
22432
|
var gitList = (cwd, args) => {
|
|
22001
|
-
const run =
|
|
22433
|
+
const run = spawnSync18("git", args, {
|
|
22002
22434
|
cwd,
|
|
22003
22435
|
encoding: "utf8",
|
|
22004
22436
|
timeout: 3000,
|
|
@@ -22475,8 +22907,7 @@ function handleHook(input, deps) {
|
|
|
22475
22907
|
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
|
|
22476
22908
|
if (raw.kind === "deny")
|
|
22477
22909
|
return raw;
|
|
22478
|
-
const
|
|
22479
|
-
const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
|
|
22910
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
22480
22911
|
if (gate.kind === "deny")
|
|
22481
22912
|
return gate;
|
|
22482
22913
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
@@ -22489,7 +22920,7 @@ function handleHook(input, deps) {
|
|
|
22489
22920
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
22490
22921
|
return NONE5;
|
|
22491
22922
|
case "write.pre":
|
|
22492
|
-
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
|
|
22923
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
|
|
22493
22924
|
case "subagent.start":
|
|
22494
22925
|
return {
|
|
22495
22926
|
kind: "context",
|
|
@@ -22793,13 +23224,13 @@ var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? n
|
|
|
22793
23224
|
|
|
22794
23225
|
// packages/workit-pi/src/launches.ts
|
|
22795
23226
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
22796
|
-
import
|
|
22797
|
-
import
|
|
23227
|
+
import os8 from "node:os";
|
|
23228
|
+
import path28 from "node:path";
|
|
22798
23229
|
var launchDir = (root) => {
|
|
22799
23230
|
const location = resolveStore(root);
|
|
22800
|
-
return location instanceof Error ? null :
|
|
23231
|
+
return location instanceof Error ? null : path28.join(location.dir, "pi-launches");
|
|
22801
23232
|
};
|
|
22802
|
-
var fileFor = (dir, workerId) =>
|
|
23233
|
+
var fileFor = (dir, workerId) => path28.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
22803
23234
|
var recordLaunch = (root, attempt) => {
|
|
22804
23235
|
const dir = launchDir(root);
|
|
22805
23236
|
if (!dir)
|
|
@@ -22809,7 +23240,7 @@ var recordLaunch = (root, attempt) => {
|
|
|
22809
23240
|
writeFileSync3(fileFor(dir, attempt.workerId), JSON.stringify({
|
|
22810
23241
|
...attempt,
|
|
22811
23242
|
pid: process.pid,
|
|
22812
|
-
host:
|
|
23243
|
+
host: os8.hostname(),
|
|
22813
23244
|
at: new Date().toISOString()
|
|
22814
23245
|
}), { flag: "wx" });
|
|
22815
23246
|
return true;
|
|
@@ -22823,7 +23254,7 @@ var clearLaunch = (root, workerId) => {
|
|
|
22823
23254
|
rmSync2(fileFor(dir, workerId), { force: true });
|
|
22824
23255
|
};
|
|
22825
23256
|
var alive = (attempt) => {
|
|
22826
|
-
if (attempt.host !==
|
|
23257
|
+
if (attempt.host !== os8.hostname())
|
|
22827
23258
|
return true;
|
|
22828
23259
|
try {
|
|
22829
23260
|
process.kill(attempt.pid, 0);
|
|
@@ -22845,7 +23276,7 @@ var abandonedLaunches = (root) => {
|
|
|
22845
23276
|
const found = [];
|
|
22846
23277
|
for (const name of names) {
|
|
22847
23278
|
try {
|
|
22848
|
-
const attempt = JSON.parse(readFileSync6(
|
|
23279
|
+
const attempt = JSON.parse(readFileSync6(path28.join(dir, name), "utf8"));
|
|
22849
23280
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
22850
23281
|
if (!alive(attempt))
|
|
22851
23282
|
found.push(attempt);
|
package/package.json
CHANGED
|
@@ -25,9 +25,9 @@ ceremony; do it yourself. Workit never spawns agents; you do.
|
|
|
25
25
|
it; change the plan or the standing orders and render again.
|
|
26
26
|
4. **Spawn in a rolling window**, in the background, each in its own worktree:
|
|
27
27
|
keep 4-6 in flight and refill from `spawnable` in `workit fanout status` as
|
|
28
|
-
each one finishes.
|
|
29
|
-
Claude Code: the `implementer` agent;
|
|
30
|
-
worktree create <slice>`.
|
|
28
|
+
each one finishes. Model: the brief's `model:` line, else TIER
|
|
29
|
+
(`references/orchestration.md`). Claude Code: the `implementer` agent;
|
|
30
|
+
elsewhere first `workit fanout worktree create <slice>`.
|
|
31
31
|
5. **Judge liveness by side effects only:** `workit fanout status` (head age,
|
|
32
32
|
PR, CI, verdict, landed; STUCK past the TIMEBOX). Stop a stuck worker and
|
|
33
33
|
observe that it exited (a timeout is not proof). Removing a worktree drops
|
|
@@ -53,7 +53,7 @@ ceremony; do it yourself. Workit never spawns agents; you do.
|
|
|
53
53
|
status` order, release the worktrees you made, and `workit ledger standing
|
|
54
54
|
clear`. Only you touch topology: workers never rebase, retarget or merge,
|
|
55
55
|
except the integration-tip merge in integration mode. Then ship
|
|
56
|
-
(read the `workit-ship` skill's SKILL.md and follow it)
|
|
56
|
+
(read the `workit-ship` skill's SKILL.md and follow it) up to `defaultEndpoint` from `workit grant show`.
|
|
57
57
|
|
|
58
58
|
## Example
|
|
59
59
|
|
|
@@ -5,8 +5,10 @@ and fans in. Workit records and checks; the host's own subagents run the work.
|
|
|
5
5
|
|
|
6
6
|
## TIER to model
|
|
7
7
|
|
|
8
|
-
TIER is a field of each slice and of its brief.
|
|
9
|
-
|
|
8
|
+
TIER is a field of each slice and of its brief. When the workspace sets
|
|
9
|
+
`fanout.tierModels`, the brief has a `model:` line under TIER and `workit
|
|
10
|
+
fanout status` names the model next to each spawnable slice: use that model.
|
|
11
|
+
Without one, map TIER where the host lets you pick a model per spawn:
|
|
10
12
|
|
|
11
13
|
| TIER | Claude Code (`model` on the Agent call) | Hosts without a per-spawn model |
|
|
12
14
|
| --- | --- | --- |
|