@brainervirus/workit-codex 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/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +47 -0
- package/dist/{workit-hook-j1zd509p.js → workit-hook-s8c0mzcq.js} +463 -32
- package/dist/workit-hook.js +1 -1
- package/package.json +3 -3
- package/skills/workit-fanout/SKILL.md +4 -4
- package/skills/workit-fanout/references/orchestration.md +4 -2
package/dist/launch-mcp.js
CHANGED
|
@@ -19602,6 +19602,8 @@ var workspaceConfigSchema = object({
|
|
|
19602
19602
|
defaultEndpoint: unknown().optional(),
|
|
19603
19603
|
verification: _enum(["self", "independent"]).optional(),
|
|
19604
19604
|
trailers: boolean2().optional(),
|
|
19605
|
+
liveSessionGuard: boolean2().optional(),
|
|
19606
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
19605
19607
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
19606
19608
|
kind,
|
|
19607
19609
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -19919,6 +19921,17 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
19919
19921
|
const matched = matchRuntimeWorkspace(cwd);
|
|
19920
19922
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
19921
19923
|
};
|
|
19924
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
19925
|
+
var TIER_HOSTS = new Set([
|
|
19926
|
+
"claude_code",
|
|
19927
|
+
"opencode",
|
|
19928
|
+
"codex",
|
|
19929
|
+
"codex_cli",
|
|
19930
|
+
"codex_desktop",
|
|
19931
|
+
"cursor",
|
|
19932
|
+
"pi",
|
|
19933
|
+
"default"
|
|
19934
|
+
]);
|
|
19922
19935
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
19923
19936
|
const matched = matchRuntimeWorkspace(cwd);
|
|
19924
19937
|
if (!matched)
|
|
@@ -28451,10 +28464,44 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
|
|
|
28451
28464
|
var signals = new Map;
|
|
28452
28465
|
var cache = new Map;
|
|
28453
28466
|
var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
|
|
28467
|
+
// packages/workit-core/src/core/live-session.ts
|
|
28468
|
+
import { spawnSync as spawnSync15 } from "node:child_process";
|
|
28469
|
+
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
28470
|
+
var liveProbe = {
|
|
28471
|
+
platform: process.platform,
|
|
28472
|
+
run: (args) => spawnSync15("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
|
|
28473
|
+
start: (pid) => processStartOf(pid)
|
|
28474
|
+
};
|
|
28475
|
+
var PIDS = new Map;
|
|
28476
|
+
|
|
28454
28477
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
28455
28478
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
28456
28479
|
var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
|
|
28457
28480
|
var cache2 = new Map;
|
|
28481
|
+
var MUTATING_GIT = new Set([
|
|
28482
|
+
"add",
|
|
28483
|
+
"am",
|
|
28484
|
+
"apply",
|
|
28485
|
+
"checkout",
|
|
28486
|
+
"cherry-pick",
|
|
28487
|
+
"clean",
|
|
28488
|
+
"commit",
|
|
28489
|
+
"merge",
|
|
28490
|
+
"mv",
|
|
28491
|
+
"pull",
|
|
28492
|
+
"rebase",
|
|
28493
|
+
"reset",
|
|
28494
|
+
"restore",
|
|
28495
|
+
"revert",
|
|
28496
|
+
"rm",
|
|
28497
|
+
"stash",
|
|
28498
|
+
"switch"
|
|
28499
|
+
]);
|
|
28500
|
+
var DRY_N = new Set(["add", "clean", "rm", "mv"]);
|
|
28501
|
+
var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
|
|
28502
|
+
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
28503
|
+
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
28504
|
+
var POPD2 = new Set(["popd", "pop-location"]);
|
|
28458
28505
|
// packages/workit-core/src/core/skill-manifests.ts
|
|
28459
28506
|
if (false) {}
|
|
28460
28507
|
|
|
@@ -12672,6 +12672,8 @@ var workspaceConfigSchema = object({
|
|
|
12672
12672
|
defaultEndpoint: unknown().optional(),
|
|
12673
12673
|
verification: _enum(["self", "independent"]).optional(),
|
|
12674
12674
|
trailers: boolean2().optional(),
|
|
12675
|
+
liveSessionGuard: boolean2().optional(),
|
|
12676
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
12675
12677
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
12676
12678
|
kind,
|
|
12677
12679
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -12989,24 +12991,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
12989
12991
|
const matched = matchRuntimeWorkspace(cwd);
|
|
12990
12992
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
12991
12993
|
};
|
|
12992
|
-
var
|
|
12994
|
+
var inheritedSetting = (cwd, dir, has) => {
|
|
12993
12995
|
let matched;
|
|
12994
12996
|
try {
|
|
12995
12997
|
matched = matchRuntimeWorkspace(cwd, dir);
|
|
12996
12998
|
} catch {
|
|
12997
|
-
return
|
|
12999
|
+
return null;
|
|
12998
13000
|
}
|
|
12999
13001
|
if (!matched)
|
|
13000
|
-
return
|
|
13002
|
+
return null;
|
|
13001
13003
|
const { selected, matches } = matched;
|
|
13002
13004
|
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
13003
|
-
|
|
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");
|
|
13004
13009
|
return {
|
|
13005
|
-
enabled: source?.trailers !== false,
|
|
13006
|
-
workspace:
|
|
13007
|
-
from: source?.name ?? null
|
|
13010
|
+
enabled: found?.source?.trailers !== false,
|
|
13011
|
+
workspace: found?.workspace ?? null,
|
|
13012
|
+
from: found?.source?.name ?? null
|
|
13008
13013
|
};
|
|
13009
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
|
+
]);
|
|
13010
13027
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
13011
13028
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13012
13029
|
if (!matched)
|
|
@@ -20218,27 +20235,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
20218
20235
|
};
|
|
20219
20236
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
20220
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";
|
|
20221
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
|
|
20222
20443
|
var NONE3 = { kind: "none" };
|
|
20223
20444
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
20224
20445
|
var canonical2 = (file) => {
|
|
20225
20446
|
let rest = "";
|
|
20226
|
-
for (let dir =
|
|
20447
|
+
for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
|
|
20227
20448
|
try {
|
|
20228
|
-
return
|
|
20449
|
+
return path23.join(realpathSync5(dir), rest);
|
|
20229
20450
|
} catch {
|
|
20230
|
-
if (
|
|
20231
|
-
return
|
|
20232
|
-
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);
|
|
20233
20454
|
}
|
|
20234
20455
|
}
|
|
20235
20456
|
};
|
|
20236
20457
|
var exempt = (cwd, root, file, plans) => {
|
|
20237
20458
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
20238
|
-
const relative =
|
|
20239
|
-
if (relative.startsWith("..") ||
|
|
20459
|
+
const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
|
|
20460
|
+
if (relative.startsWith("..") || path23.isAbsolute(relative))
|
|
20240
20461
|
return true;
|
|
20241
|
-
const posix = relative.split(
|
|
20462
|
+
const posix = relative.split(path23.sep).join("/");
|
|
20242
20463
|
const segments = posix.split("/");
|
|
20243
20464
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
20244
20465
|
};
|
|
@@ -20336,7 +20557,7 @@ var shellWrites = (command) => {
|
|
|
20336
20557
|
return { writes: true, targets: unknown ? null : targets };
|
|
20337
20558
|
};
|
|
20338
20559
|
var cache2 = new Map;
|
|
20339
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
20560
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
|
|
20340
20561
|
var gateFor = (cwd) => {
|
|
20341
20562
|
const store = new TaskStore(cwd);
|
|
20342
20563
|
const listed = store.listTaskIndex();
|
|
@@ -20374,7 +20595,218 @@ var gateFor = (cwd) => {
|
|
|
20374
20595
|
cache2.delete(cache2.keys().next().value);
|
|
20375
20596
|
return gate;
|
|
20376
20597
|
};
|
|
20377
|
-
|
|
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) {
|
|
20378
20810
|
try {
|
|
20379
20811
|
const gate = gateFor(cwd);
|
|
20380
20812
|
if (!gate || gate.blockers.length === 0)
|
|
@@ -20392,7 +20824,7 @@ function writeGate(cwd, paths) {
|
|
|
20392
20824
|
}
|
|
20393
20825
|
}
|
|
20394
20826
|
// packages/workit-core/src/hooks/stop.ts
|
|
20395
|
-
import { spawnSync as
|
|
20827
|
+
import { spawnSync as spawnSync14 } from "node:child_process";
|
|
20396
20828
|
var NONE4 = { kind: "none" };
|
|
20397
20829
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
20398
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;
|
|
@@ -20413,7 +20845,7 @@ var asksUser = (message) => {
|
|
|
20413
20845
|
};
|
|
20414
20846
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
20415
20847
|
var gitList = (cwd, args) => {
|
|
20416
|
-
const run =
|
|
20848
|
+
const run = spawnSync14("git", args, {
|
|
20417
20849
|
cwd,
|
|
20418
20850
|
encoding: "utf8",
|
|
20419
20851
|
timeout: 3000,
|
|
@@ -20777,12 +21209,12 @@ ${line}` };
|
|
|
20777
21209
|
};
|
|
20778
21210
|
// packages/workit-core/src/hooks/hosts/fields.ts
|
|
20779
21211
|
import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
|
|
20780
|
-
import
|
|
21212
|
+
import path24 from "node:path";
|
|
20781
21213
|
var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
|
|
20782
21214
|
var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
|
|
20783
21215
|
var optionalText = (value) => typeof value === "string" ? value : null;
|
|
20784
21216
|
var existingDirectory = (value) => {
|
|
20785
|
-
if (!nonEmpty2(value) || !
|
|
21217
|
+
if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
|
|
20786
21218
|
return null;
|
|
20787
21219
|
try {
|
|
20788
21220
|
return statSync2(value).isDirectory() ? realpathSync6(value) : null;
|
|
@@ -20927,8 +21359,7 @@ function handleHook(input, deps) {
|
|
|
20927
21359
|
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
|
|
20928
21360
|
if (raw.kind === "deny")
|
|
20929
21361
|
return raw;
|
|
20930
|
-
const
|
|
20931
|
-
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;
|
|
20932
21363
|
if (gate.kind === "deny")
|
|
20933
21364
|
return gate;
|
|
20934
21365
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
@@ -20941,7 +21372,7 @@ function handleHook(input, deps) {
|
|
|
20941
21372
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
20942
21373
|
return NONE5;
|
|
20943
21374
|
case "write.pre":
|
|
20944
|
-
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;
|
|
20945
21376
|
case "subagent.start":
|
|
20946
21377
|
return {
|
|
20947
21378
|
kind: "context",
|
|
@@ -21520,8 +21951,8 @@ var codexAdapter = {
|
|
|
21520
21951
|
};
|
|
21521
21952
|
|
|
21522
21953
|
// packages/workit-core/src/hooks/hosts/cursor.ts
|
|
21523
|
-
import
|
|
21524
|
-
import
|
|
21954
|
+
import fs18 from "node:fs";
|
|
21955
|
+
import path25 from "node:path";
|
|
21525
21956
|
var undocumented = { support: "undocumented", native: null };
|
|
21526
21957
|
var none = { support: "none", native: null };
|
|
21527
21958
|
var CURSOR_DESCRIPTOR = {
|
|
@@ -21718,17 +22149,17 @@ var protocolEvent2 = (input) => {
|
|
|
21718
22149
|
};
|
|
21719
22150
|
var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
|
|
21720
22151
|
var lastAssistantText = (file) => {
|
|
21721
|
-
if (!file || !
|
|
22152
|
+
if (!file || !path25.isAbsolute(file))
|
|
21722
22153
|
return null;
|
|
21723
22154
|
try {
|
|
21724
|
-
const size =
|
|
22155
|
+
const size = fs18.statSync(file).size;
|
|
21725
22156
|
const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
|
|
21726
22157
|
const buffer = Buffer.alloc(size - start);
|
|
21727
|
-
const fd =
|
|
22158
|
+
const fd = fs18.openSync(file, "r");
|
|
21728
22159
|
try {
|
|
21729
|
-
|
|
22160
|
+
fs18.readSync(fd, buffer, 0, buffer.length, start);
|
|
21730
22161
|
} finally {
|
|
21731
|
-
|
|
22162
|
+
fs18.closeSync(fd);
|
|
21732
22163
|
}
|
|
21733
22164
|
const lines = buffer.toString("utf8").split(`
|
|
21734
22165
|
`);
|
|
@@ -21755,7 +22186,7 @@ var lastAssistantText = (file) => {
|
|
|
21755
22186
|
return null;
|
|
21756
22187
|
}
|
|
21757
22188
|
};
|
|
21758
|
-
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}`);
|
|
21759
22190
|
var hookCwd = (input) => {
|
|
21760
22191
|
const cwd = existingDirectory(input.cwd);
|
|
21761
22192
|
if (cwd && input.hook_event_name === "beforeShellExecution")
|
package/dist/workit-hook.js
CHANGED
|
@@ -37,6 +37,6 @@ if (quiet)
|
|
|
37
37
|
process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: quiet } })}
|
|
38
38
|
`);
|
|
39
39
|
else {
|
|
40
|
-
const { runCodexHook } = await import("./workit-hook-
|
|
40
|
+
const { runCodexHook } = await import("./workit-hook-s8c0mzcq.js");
|
|
41
41
|
await runCodexHook([payload]);
|
|
42
42
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-codex",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.14.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit Codex CLI/desktop plugin — shared MCP transport, hooks, method skills, and verifier/reviewer/implementer agents",
|
|
6
6
|
"keywords": [
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"build": "bun scripts/build.ts"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@brainervirus/workit-core": "^8.
|
|
43
|
-
"@brainervirus/workit-mcp": "^8.
|
|
42
|
+
"@brainervirus/workit-core": "^8.14.0",
|
|
43
|
+
"@brainervirus/workit-mcp": "^8.14.0"
|
|
44
44
|
},
|
|
45
45
|
"engines": {
|
|
46
46
|
"node": ">=24"
|
|
@@ -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
|
| --- | --- | --- |
|