@brainervirus/workit-codex 8.13.1 → 8.14.1

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "workit",
3
- "version": "8.13.1",
3
+ "version": "8.14.1",
4
4
  "description": "Adaptive engineering workflow rails for Codex",
5
5
  "author": {
6
6
  "name": "BrainerVirus"
@@ -14236,6 +14236,8 @@ var text = string2().check((ctx) => {
14236
14236
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
14237
14237
  });
14238
14238
  var id = text.regex(uuidPattern, "expected lowercase UUID");
14239
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
14240
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
14239
14241
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
14240
14242
  var revision = text.regex(uuidPattern, "expected UUID revision");
14241
14243
  var validUtc = (value) => {
@@ -14613,7 +14615,7 @@ var workerReportSchema = object({
14613
14615
  outcome: _enum(["completed", "failed", "cancelled"]),
14614
14616
  summary: text,
14615
14617
  evidenceIds: array(id),
14616
- findingIds: array(id)
14618
+ findingIds: array(idRef)
14617
14619
  }).strict();
14618
14620
  var workerSchema = object({
14619
14621
  assignment: assignmentSchema,
@@ -14859,7 +14861,7 @@ var findingOperations = {
14859
14861
  action: literal("resolve"),
14860
14862
  ...taskId,
14861
14863
  expectedRevision: revision.optional(),
14862
- findingId: id,
14864
+ findingId: idRef,
14863
14865
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
14864
14866
  reason: text,
14865
14867
  evidenceIds: array(id),
@@ -14900,7 +14902,7 @@ var workerOperations = {
14900
14902
  ...taskId,
14901
14903
  expectedRevision: revision.optional(),
14902
14904
  expectedWorkspaceRevision: revision.optional(),
14903
- workerId: id,
14905
+ workerId: idRef,
14904
14906
  report: workerReportSchema
14905
14907
  }),
14906
14908
  cancel: operation({
@@ -14908,7 +14910,7 @@ var workerOperations = {
14908
14910
  ...taskId,
14909
14911
  ...revisions,
14910
14912
  expectedWorkspaceRevision: revision.optional(),
14911
- workerId: id,
14913
+ workerId: idRef,
14912
14914
  reason: text
14913
14915
  })
14914
14916
  };
@@ -14958,6 +14960,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
14958
14960
  data
14959
14961
  });
14960
14962
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
14963
+ function resolveIdRef(kind, ref, ids) {
14964
+ if (ids.includes(ref))
14965
+ return success(null, null, ref);
14966
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
14967
+ if (matches.length === 1)
14968
+ return success(null, null, matches[0]);
14969
+ if (matches.length === 0)
14970
+ return failure2("not_found", `${kind} not found`);
14971
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
14972
+ }
14961
14973
  function parseOperation(family, input) {
14962
14974
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
14963
14975
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -17476,20 +17488,26 @@ class TaskStore {
17476
17488
  return success(null, null, { key: key.data, bound: bound.data });
17477
17489
  }
17478
17490
  taskForWorker(workerId) {
17479
- if (typeof workerId !== "string" || !validId(workerId))
17491
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
17480
17492
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
17481
17493
  const listed = this.listTaskIndex();
17482
17494
  if (!listed.ok)
17483
17495
  return listed;
17496
+ const owners = new Map;
17484
17497
  for (const entry of listed.data) {
17485
17498
  if (entry.status === "closed")
17486
17499
  continue;
17487
17500
  const loaded = this.load(entry.id);
17488
17501
  if (!loaded.ok)
17489
17502
  return loaded;
17490
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
17491
- return success(null, null, entry.id);
17503
+ for (const worker of loaded.data?.record.workers ?? [])
17504
+ owners.set(worker.id, entry.id);
17492
17505
  }
17506
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
17507
+ if (resolved.ok)
17508
+ return success(null, null, owners.get(resolved.data) ?? null);
17509
+ if (resolved.code !== "not_found")
17510
+ return resolved;
17493
17511
  return success(null, null, null);
17494
17512
  }
17495
17513
  originTaskEntry() {
@@ -19602,6 +19620,8 @@ var workspaceConfigSchema = object({
19602
19620
  defaultEndpoint: unknown().optional(),
19603
19621
  verification: _enum(["self", "independent"]).optional(),
19604
19622
  trailers: boolean2().optional(),
19623
+ liveSessionGuard: boolean2().optional(),
19624
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
19605
19625
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
19606
19626
  kind,
19607
19627
  union([boolean2(), literal("verified")]).optional()
@@ -19919,6 +19939,17 @@ var resolveRuntimeReleaseTracks = (cwd) => {
19919
19939
  const matched = matchRuntimeWorkspace(cwd);
19920
19940
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
19921
19941
  };
19942
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
19943
+ var TIER_HOSTS = new Set([
19944
+ "claude_code",
19945
+ "opencode",
19946
+ "codex",
19947
+ "codex_cli",
19948
+ "codex_desktop",
19949
+ "cursor",
19950
+ "pi",
19951
+ "default"
19952
+ ]);
19922
19953
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
19923
19954
  const matched = matchRuntimeWorkspace(cwd);
19924
19955
  if (!matched)
@@ -23194,6 +23225,8 @@ var TASK_SCOPED_ACTIONS = new Set([
23194
23225
  "worker.cancel",
23195
23226
  "state.export"
23196
23227
  ]);
23228
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
23229
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
23197
23230
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
23198
23231
  var workerCommand = (taskId, request) => {
23199
23232
  const { action, workerId, reason, report } = request;
@@ -24091,6 +24124,10 @@ class WorkitCore {
24091
24124
  }
24092
24125
  if (helper?.ok)
24093
24126
  return failure2("permission_denied", "helpers cannot resolve findings");
24127
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
24128
+ if (!findingRef.ok)
24129
+ return findingRef;
24130
+ input.findingId = findingRef.data;
24094
24131
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
24095
24132
  if (!finding)
24096
24133
  return failure2("not_found", "finding not found");
@@ -24242,15 +24279,31 @@ class WorkitCore {
24242
24279
  return changed;
24243
24280
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
24244
24281
  }
24282
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
24283
+ if (!workerRef.ok)
24284
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
24285
+ input.workerId = workerRef.data;
24245
24286
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
24246
24287
  if (!entry)
24247
24288
  return failure2("not_found", "worker not found");
24248
24289
  if (input.action === "report") {
24249
24290
  if (helperId === null || helperId !== input.workerId)
24250
- return failure2("permission_denied", "workers can submit only their own report");
24291
+ return failure2("permission_denied", ONLY_OWN_REPORT);
24251
24292
  const helper = this.helperEntry(task.data, true, true);
24252
24293
  if (!helper.ok)
24253
24294
  return helper;
24295
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
24296
+ const reported = [];
24297
+ for (const findingRef of input.report.findingIds) {
24298
+ const found = resolveIdRef("finding", findingRef, ownFindings);
24299
+ if (found.ok)
24300
+ reported.push(found.data);
24301
+ else if (found.code === "not_found")
24302
+ reported.push(findingRef);
24303
+ else
24304
+ return found;
24305
+ }
24306
+ input.report.findingIds = reported;
24254
24307
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
24255
24308
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
24256
24309
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -24278,7 +24331,7 @@ class WorkitCore {
24278
24331
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
24279
24332
  }
24280
24333
  if (helperId !== null)
24281
- return failure2("permission_denied", "helpers cannot cancel or control workers");
24334
+ return failure2("permission_denied", NO_HELPER_CONTROL);
24282
24335
  if (task.data.status === "closed")
24283
24336
  return failure2("invalid_transition", "closed task cannot cancel workers");
24284
24337
  if (entry.data.state === "stopped") {
@@ -28451,10 +28504,44 @@ var capabilitiesFor = (descriptor, observed = {}) => descriptor.capabilities.map
28451
28504
  var signals = new Map;
28452
28505
  var cache = new Map;
28453
28506
  var TURN_TTL_MS = 7 * 24 * 60 * 60 * 1000;
28507
+ // packages/workit-core/src/core/live-session.ts
28508
+ import { spawnSync as spawnSync15 } from "node:child_process";
28509
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
28510
+ var liveProbe = {
28511
+ platform: process.platform,
28512
+ run: (args) => spawnSync15("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
28513
+ start: (pid) => processStartOf(pid)
28514
+ };
28515
+ var PIDS = new Map;
28516
+
28454
28517
  // packages/workit-core/src/hooks/write-gate.ts
28455
28518
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
28456
28519
  var PREFIXES2 = new Set(["sudo", "command", "env", "nohup", "time", "exec", "xargs"]);
28457
28520
  var cache2 = new Map;
28521
+ var MUTATING_GIT = new Set([
28522
+ "add",
28523
+ "am",
28524
+ "apply",
28525
+ "checkout",
28526
+ "cherry-pick",
28527
+ "clean",
28528
+ "commit",
28529
+ "merge",
28530
+ "mv",
28531
+ "pull",
28532
+ "rebase",
28533
+ "reset",
28534
+ "restore",
28535
+ "revert",
28536
+ "rm",
28537
+ "stash",
28538
+ "switch"
28539
+ ]);
28540
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
28541
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
28542
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
28543
+ var PUSHD2 = new Set(["pushd", "push-location"]);
28544
+ var POPD2 = new Set(["popd", "pop-location"]);
28458
28545
  // packages/workit-core/src/core/skill-manifests.ts
28459
28546
  if (false) {}
28460
28547
 
@@ -7966,6 +7966,8 @@ var text = string2().check((ctx) => {
7966
7966
  ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
7967
7967
  });
7968
7968
  var id = text.regex(uuidPattern, "expected lowercase UUID");
7969
+ var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
7970
+ var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
7969
7971
  var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
7970
7972
  var revision = text.regex(uuidPattern, "expected UUID revision");
7971
7973
  var validUtc = (value) => {
@@ -8343,7 +8345,7 @@ var workerReportSchema = object({
8343
8345
  outcome: _enum(["completed", "failed", "cancelled"]),
8344
8346
  summary: text,
8345
8347
  evidenceIds: array(id),
8346
- findingIds: array(id)
8348
+ findingIds: array(idRef)
8347
8349
  }).strict();
8348
8350
  var workerSchema = object({
8349
8351
  assignment: assignmentSchema,
@@ -8589,7 +8591,7 @@ var findingOperations = {
8589
8591
  action: literal("resolve"),
8590
8592
  ...taskId,
8591
8593
  expectedRevision: revision.optional(),
8592
- findingId: id,
8594
+ findingId: idRef,
8593
8595
  disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
8594
8596
  reason: text,
8595
8597
  evidenceIds: array(id),
@@ -8630,7 +8632,7 @@ var workerOperations = {
8630
8632
  ...taskId,
8631
8633
  expectedRevision: revision.optional(),
8632
8634
  expectedWorkspaceRevision: revision.optional(),
8633
- workerId: id,
8635
+ workerId: idRef,
8634
8636
  report: workerReportSchema
8635
8637
  }),
8636
8638
  cancel: operation({
@@ -8638,7 +8640,7 @@ var workerOperations = {
8638
8640
  ...taskId,
8639
8641
  ...revisions,
8640
8642
  expectedWorkspaceRevision: revision.optional(),
8641
- workerId: id,
8643
+ workerId: idRef,
8642
8644
  reason: text
8643
8645
  })
8644
8646
  };
@@ -8688,6 +8690,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
8688
8690
  data
8689
8691
  });
8690
8692
  var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
8693
+ function resolveIdRef(kind, ref, ids) {
8694
+ if (ids.includes(ref))
8695
+ return success(null, null, ref);
8696
+ const matches = ids.filter((candidate) => candidate.startsWith(ref));
8697
+ if (matches.length === 1)
8698
+ return success(null, null, matches[0]);
8699
+ if (matches.length === 0)
8700
+ return failure2("not_found", `${kind} not found`);
8701
+ return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
8702
+ }
8691
8703
  function parseOperation(family, input) {
8692
8704
  if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
8693
8705
  return failure2("unsupported_version", "unsupported schema version", { operation: family });
@@ -10937,20 +10949,26 @@ class TaskStore {
10937
10949
  return success(null, null, { key: key.data, bound: bound.data });
10938
10950
  }
10939
10951
  taskForWorker(workerId) {
10940
- if (typeof workerId !== "string" || !validId(workerId))
10952
+ if (typeof workerId !== "string" || !idRefPattern.test(workerId))
10941
10953
  return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
10942
10954
  const listed = this.listTaskIndex();
10943
10955
  if (!listed.ok)
10944
10956
  return listed;
10957
+ const owners = new Map;
10945
10958
  for (const entry of listed.data) {
10946
10959
  if (entry.status === "closed")
10947
10960
  continue;
10948
10961
  const loaded = this.load(entry.id);
10949
10962
  if (!loaded.ok)
10950
10963
  return loaded;
10951
- if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
10952
- return success(null, null, entry.id);
10964
+ for (const worker of loaded.data?.record.workers ?? [])
10965
+ owners.set(worker.id, entry.id);
10953
10966
  }
10967
+ const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
10968
+ if (resolved.ok)
10969
+ return success(null, null, owners.get(resolved.data) ?? null);
10970
+ if (resolved.code !== "not_found")
10971
+ return resolved;
10954
10972
  return success(null, null, null);
10955
10973
  }
10956
10974
  originTaskEntry() {
@@ -12672,6 +12690,8 @@ var workspaceConfigSchema = object({
12672
12690
  defaultEndpoint: unknown().optional(),
12673
12691
  verification: _enum(["self", "independent"]).optional(),
12674
12692
  trailers: boolean2().optional(),
12693
+ liveSessionGuard: boolean2().optional(),
12694
+ fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
12675
12695
  autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
12676
12696
  kind,
12677
12697
  union([boolean2(), literal("verified")]).optional()
@@ -12989,24 +13009,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
12989
13009
  const matched = matchRuntimeWorkspace(cwd);
12990
13010
  return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
12991
13011
  };
12992
- var workspaceTrailers = (cwd, dir) => {
13012
+ var inheritedSetting = (cwd, dir, has) => {
12993
13013
  let matched;
12994
13014
  try {
12995
13015
  matched = matchRuntimeWorkspace(cwd, dir);
12996
13016
  } catch {
12997
- return { enabled: true, workspace: null, from: null };
13017
+ return null;
12998
13018
  }
12999
13019
  if (!matched)
13000
- return { enabled: true, workspace: null, from: null };
13020
+ return null;
13001
13021
  const { selected, matches } = matched;
13002
13022
  const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
13003
- const source = [selected, ...enclosing].find((entry) => typeof entry.trailers === "boolean");
13023
+ return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
13024
+ };
13025
+ var workspaceTrailers = (cwd, dir) => {
13026
+ const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
13004
13027
  return {
13005
- enabled: source?.trailers !== false,
13006
- workspace: selected.name,
13007
- from: source?.name ?? null
13028
+ enabled: found?.source?.trailers !== false,
13029
+ workspace: found?.workspace ?? null,
13030
+ from: found?.source?.name ?? null
13008
13031
  };
13009
13032
  };
13033
+ var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
13034
+ var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
13035
+ var TIER_HOSTS = new Set([
13036
+ "claude_code",
13037
+ "opencode",
13038
+ "codex",
13039
+ "codex_cli",
13040
+ "codex_desktop",
13041
+ "cursor",
13042
+ "pi",
13043
+ "default"
13044
+ ]);
13010
13045
  var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
13011
13046
  const matched = matchRuntimeWorkspace(cwd);
13012
13047
  if (!matched)
@@ -16277,6 +16312,8 @@ var TASK_SCOPED_ACTIONS = new Set([
16277
16312
  "worker.cancel",
16278
16313
  "state.export"
16279
16314
  ]);
16315
+ var ONLY_OWN_REPORT = "workers can submit only their own report";
16316
+ var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
16280
16317
  var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
16281
16318
  var workerCommand = (taskId, request) => {
16282
16319
  const { action, workerId, reason, report } = request;
@@ -17174,6 +17211,10 @@ class WorkitCore {
17174
17211
  }
17175
17212
  if (helper?.ok)
17176
17213
  return failure2("permission_denied", "helpers cannot resolve findings");
17214
+ const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
17215
+ if (!findingRef.ok)
17216
+ return findingRef;
17217
+ input.findingId = findingRef.data;
17177
17218
  const finding = task.data.findings.find((entry) => entry.id === input.findingId);
17178
17219
  if (!finding)
17179
17220
  return failure2("not_found", "finding not found");
@@ -17325,15 +17366,31 @@ class WorkitCore {
17325
17366
  return changed;
17326
17367
  return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
17327
17368
  }
17369
+ const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
17370
+ if (!workerRef.ok)
17371
+ return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
17372
+ input.workerId = workerRef.data;
17328
17373
  const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
17329
17374
  if (!entry)
17330
17375
  return failure2("not_found", "worker not found");
17331
17376
  if (input.action === "report") {
17332
17377
  if (helperId === null || helperId !== input.workerId)
17333
- return failure2("permission_denied", "workers can submit only their own report");
17378
+ return failure2("permission_denied", ONLY_OWN_REPORT);
17334
17379
  const helper = this.helperEntry(task.data, true, true);
17335
17380
  if (!helper.ok)
17336
17381
  return helper;
17382
+ const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
17383
+ const reported = [];
17384
+ for (const findingRef of input.report.findingIds) {
17385
+ const found = resolveIdRef("finding", findingRef, ownFindings);
17386
+ if (found.ok)
17387
+ reported.push(found.data);
17388
+ else if (found.code === "not_found")
17389
+ reported.push(findingRef);
17390
+ else
17391
+ return found;
17392
+ }
17393
+ input.report.findingIds = reported;
17337
17394
  const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
17338
17395
  const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
17339
17396
  if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
@@ -17361,7 +17418,7 @@ class WorkitCore {
17361
17418
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
17362
17419
  }
17363
17420
  if (helperId !== null)
17364
- return failure2("permission_denied", "helpers cannot cancel or control workers");
17421
+ return failure2("permission_denied", NO_HELPER_CONTROL);
17365
17422
  if (task.data.status === "closed")
17366
17423
  return failure2("invalid_transition", "closed task cannot cancel workers");
17367
17424
  if (entry.data.state === "stopped") {
@@ -20218,27 +20275,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
20218
20275
  };
20219
20276
  // packages/workit-core/src/hooks/write-gate.ts
20220
20277
  import { realpathSync as realpathSync5 } from "node:fs";
20278
+ import os7 from "node:os";
20279
+ import path23 from "node:path";
20280
+
20281
+ // packages/workit-core/src/core/live-session.ts
20282
+ import { spawnSync as spawnSync13 } from "node:child_process";
20283
+ import { createHash as createHash15 } from "node:crypto";
20284
+ import fs17 from "node:fs";
20285
+ import os6 from "node:os";
20221
20286
  import path22 from "node:path";
20287
+ var LIVE_SESSION_TTL_MS = 15 * 60000;
20288
+ var HEARTBEAT_EVERY_MS = 30000;
20289
+ var liveProbe = {
20290
+ platform: process.platform,
20291
+ run: (args) => spawnSync13("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
20292
+ start: (pid) => processStartOf(pid)
20293
+ };
20294
+ var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
20295
+ var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
20296
+ var processAlive = (pid) => {
20297
+ try {
20298
+ process.kill(pid, 0);
20299
+ return true;
20300
+ } catch (error) {
20301
+ return error.code === "EPERM";
20302
+ }
20303
+ };
20304
+ var processInfo = (pid) => {
20305
+ try {
20306
+ if (liveProbe.platform === "linux") {
20307
+ const stat = fs17.readFileSync(`/proc/${pid}/stat`, "utf8");
20308
+ const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
20309
+ const command = fs17.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
20310
+ return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
20311
+ }
20312
+ if (liveProbe.platform === "win32")
20313
+ return null;
20314
+ const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
20315
+ return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
20316
+ } catch {
20317
+ return null;
20318
+ }
20319
+ };
20320
+ 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;
20321
+ var PIDS = new Map;
20322
+ var hostPid = (transport) => {
20323
+ if (transport !== "hook-process")
20324
+ return process.pid;
20325
+ const key = `${liveProbe.platform}\x00${process.ppid}`;
20326
+ if (PIDS.has(key))
20327
+ return PIDS.get(key) ?? null;
20328
+ if (liveProbe.platform === "win32") {
20329
+ PIDS.set(key, null);
20330
+ return null;
20331
+ }
20332
+ let pid = process.ppid;
20333
+ for (let hop = 0;hop < 4; hop++) {
20334
+ const info = processInfo(pid);
20335
+ if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
20336
+ break;
20337
+ pid = info.ppid;
20338
+ }
20339
+ PIDS.set(key, pid);
20340
+ return pid;
20341
+ };
20342
+ var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
20343
+ var liveOwner = (input, transport, env = process.env) => {
20344
+ const session = input.session.id?.trim();
20345
+ if (!session)
20346
+ return null;
20347
+ const leads = new Set([leadOf(sessionSafe(session))]);
20348
+ if (input.session.parentId)
20349
+ leads.add(leadOf(sessionSafe(input.session.parentId)));
20350
+ if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
20351
+ leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
20352
+ return {
20353
+ host: input.host,
20354
+ session: sessionSafe(session),
20355
+ leads: [...leads],
20356
+ pid: () => hostPid(transport)
20357
+ };
20358
+ };
20359
+ var checkoutOf = (target) => {
20360
+ let dir = path22.resolve(target);
20361
+ while (!fs17.existsSync(dir) && path22.dirname(dir) !== dir)
20362
+ dir = path22.dirname(dir);
20363
+ if (fs17.existsSync(dir) && !fs17.statSync(dir).isDirectory())
20364
+ dir = path22.dirname(dir);
20365
+ const parts = dir.split(path22.sep);
20366
+ const dotGit = parts.lastIndexOf(".git");
20367
+ if (dotGit > 0)
20368
+ dir = parts.slice(0, dotGit).join(path22.sep) || path22.sep;
20369
+ const location = resolveStore(dir);
20370
+ if (location instanceof Error || !location.shared || !location.top)
20371
+ return null;
20372
+ let top = location.top;
20373
+ try {
20374
+ top = fs17.realpathSync.native(top);
20375
+ } catch {}
20376
+ return top;
20377
+ };
20378
+ var markerFile2 = (top) => path22.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
20379
+ var checkoutKey = (top, platform = process.platform) => {
20380
+ let key = path22.resolve(top);
20381
+ try {
20382
+ key = fs17.realpathSync.native(key);
20383
+ } catch {}
20384
+ return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
20385
+ };
20386
+ var readMarker = (file) => {
20387
+ try {
20388
+ const value = JSON.parse(fs17.readFileSync(file, "utf8"));
20389
+ if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
20390
+ return null;
20391
+ return {
20392
+ version: 1,
20393
+ session: value.session,
20394
+ leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
20395
+ host: value.host,
20396
+ pid: typeof value.pid === "number" ? value.pid : null,
20397
+ pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
20398
+ machine: typeof value.machine === "string" ? value.machine : "",
20399
+ checkout: typeof value.checkout === "string" ? value.checkout : "",
20400
+ heartbeatAt: value.heartbeatAt
20401
+ };
20402
+ } catch {
20403
+ return null;
20404
+ }
20405
+ };
20406
+ var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
20407
+ var isLive = (marker, deps) => {
20408
+ const age = ageOf2(marker, deps.now ?? new Date);
20409
+ if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
20410
+ return false;
20411
+ if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
20412
+ return true;
20413
+ if (!(deps.alive ?? processAlive)(marker.pid))
20414
+ return false;
20415
+ if (marker.pidStart === null)
20416
+ return true;
20417
+ const start = liveProbe.start(marker.pid);
20418
+ return start === null || start === marker.pidStart;
20419
+ };
20420
+ var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
20421
+ var sameOwner = (marker, owner, machine) => {
20422
+ if (sameLead(marker, owner))
20423
+ return true;
20424
+ if (marker.machine !== machine)
20425
+ return false;
20426
+ const pid = owner.pid();
20427
+ if (pid === null || marker.pid === null || marker.pidStart === null)
20428
+ return familyOf(marker.host) === familyOf(owner.host);
20429
+ return marker.pid === pid;
20430
+ };
20431
+ var liveConflict = (target, owner, deps = {}) => {
20432
+ try {
20433
+ const checkout = checkoutOf(target);
20434
+ if (!checkout)
20435
+ return null;
20436
+ const file = markerFile2(checkout);
20437
+ const marker = readMarker(file);
20438
+ if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
20439
+ return null;
20440
+ if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
20441
+ return null;
20442
+ return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
20443
+ } catch {
20444
+ return null;
20445
+ }
20446
+ };
20447
+ var claimLiveSession = (target, owner, deps = {}) => {
20448
+ try {
20449
+ const checkout = checkoutOf(target);
20450
+ if (!checkout)
20451
+ return;
20452
+ const file = markerFile2(checkout);
20453
+ const now = deps.now ?? new Date;
20454
+ const machine = deps.machine ?? os6.hostname();
20455
+ const current = readMarker(file);
20456
+ if (current) {
20457
+ const age = ageOf2(current, now);
20458
+ if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
20459
+ return;
20460
+ if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
20461
+ return;
20462
+ }
20463
+ const pid = owner.pid();
20464
+ const marker = {
20465
+ version: 1,
20466
+ session: owner.session,
20467
+ leads: owner.leads,
20468
+ host: owner.host,
20469
+ pid,
20470
+ pidStart: pid === null ? null : liveProbe.start(pid),
20471
+ machine,
20472
+ checkout,
20473
+ heartbeatAt: now.toISOString()
20474
+ };
20475
+ fs17.mkdirSync(path22.dirname(file), { recursive: true });
20476
+ const temp = `${file}.${process.pid}.tmp`;
20477
+ fs17.writeFileSync(temp, JSON.stringify(marker));
20478
+ fs17.renameSync(temp, file);
20479
+ } catch {}
20480
+ };
20481
+
20482
+ // packages/workit-core/src/hooks/write-gate.ts
20222
20483
  var NONE3 = { kind: "none" };
20223
20484
  var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
20224
20485
  var canonical2 = (file) => {
20225
20486
  let rest = "";
20226
- for (let dir = path22.resolve(file);; dir = path22.dirname(dir)) {
20487
+ for (let dir = path23.resolve(file);; dir = path23.dirname(dir)) {
20227
20488
  try {
20228
- return path22.join(realpathSync5(dir), rest);
20489
+ return path23.join(realpathSync5(dir), rest);
20229
20490
  } catch {
20230
- if (path22.dirname(dir) === dir)
20231
- return path22.resolve(file);
20232
- rest = rest ? path22.join(path22.basename(dir), rest) : path22.basename(dir);
20491
+ if (path23.dirname(dir) === dir)
20492
+ return path23.resolve(file);
20493
+ rest = rest ? path23.join(path23.basename(dir), rest) : path23.basename(dir);
20233
20494
  }
20234
20495
  }
20235
20496
  };
20236
20497
  var exempt = (cwd, root, file, plans) => {
20237
20498
  const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
20238
- const relative = path22.relative(fold(canonical2(root)), fold(canonical2(path22.resolve(cwd, file))));
20239
- if (relative.startsWith("..") || path22.isAbsolute(relative))
20499
+ const relative = path23.relative(fold(canonical2(root)), fold(canonical2(path23.resolve(cwd, file))));
20500
+ if (relative.startsWith("..") || path23.isAbsolute(relative))
20240
20501
  return true;
20241
- const posix = relative.split(path22.sep).join("/");
20502
+ const posix = relative.split(path23.sep).join("/");
20242
20503
  const segments = posix.split("/");
20243
20504
  return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
20244
20505
  };
@@ -20336,7 +20597,7 @@ var shellWrites = (command) => {
20336
20597
  return { writes: true, targets: unknown ? null : targets };
20337
20598
  };
20338
20599
  var cache2 = new Map;
20339
- var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path22.resolve(root, file)) ?? "-").join("\x00");
20600
+ var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path23.resolve(root, file)) ?? "-").join("\x00");
20340
20601
  var gateFor = (cwd) => {
20341
20602
  const store = new TaskStore(cwd);
20342
20603
  const listed = store.listTaskIndex();
@@ -20374,7 +20635,218 @@ var gateFor = (cwd) => {
20374
20635
  cache2.delete(cache2.keys().next().value);
20375
20636
  return gate;
20376
20637
  };
20377
- function writeGate(cwd, paths) {
20638
+ var MUTATING_GIT = new Set([
20639
+ "add",
20640
+ "am",
20641
+ "apply",
20642
+ "checkout",
20643
+ "cherry-pick",
20644
+ "clean",
20645
+ "commit",
20646
+ "merge",
20647
+ "mv",
20648
+ "pull",
20649
+ "rebase",
20650
+ "reset",
20651
+ "restore",
20652
+ "revert",
20653
+ "rm",
20654
+ "stash",
20655
+ "switch"
20656
+ ]);
20657
+ var DRY_N = new Set(["add", "clean", "rm", "mv"]);
20658
+ var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
20659
+ var DIR_TARGET = ".workit-live-target";
20660
+ var OVERRIDE = "WORKIT_LIVE_SESSION=override";
20661
+ 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("--"));
20662
+ var gitMutationDirs = (cwd, command, dialect) => {
20663
+ let invocations;
20664
+ try {
20665
+ invocations = rawInvocations(command, dialect);
20666
+ } catch {
20667
+ return [];
20668
+ }
20669
+ return invocations.flatMap((invocation) => {
20670
+ if (invocation.tool !== "git" || invocation.dir === undefined)
20671
+ return [];
20672
+ const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
20673
+ const sub = invocation.args[at] ?? "";
20674
+ const rest = invocation.args.slice(at + 1);
20675
+ if (!MUTATING_GIT.has(sub) || inert(rest, sub))
20676
+ return [];
20677
+ if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
20678
+ return [];
20679
+ return [path23.resolve(cwd, invocation.dir ?? ".")];
20680
+ });
20681
+ };
20682
+ var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
20683
+ var PUSHD2 = new Set(["pushd", "push-location"]);
20684
+ var POPD2 = new Set(["popd", "pop-location"]);
20685
+ var cdInto = (dir, words) => {
20686
+ const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
20687
+ if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
20688
+ return;
20689
+ return target === undefined ? os7.homedir() : path23.resolve(dir, target);
20690
+ };
20691
+ var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
20692
+ var segmentTargets = (cwd, command, dialect) => {
20693
+ const targets = [];
20694
+ let dir = cwd;
20695
+ let stack = [];
20696
+ const groups = [];
20697
+ const at = (file) => path23.isAbsolute(file) ? file : dir === undefined ? null : path23.resolve(dir, file);
20698
+ for (const segment of segmentsOf(command, dialect)) {
20699
+ if (segment.group === "open") {
20700
+ groups.push({ dir, stack: [...stack] });
20701
+ continue;
20702
+ }
20703
+ if (segment.group === "close") {
20704
+ const saved = groups.pop();
20705
+ if (saved)
20706
+ ({ dir, stack } = saved);
20707
+ continue;
20708
+ }
20709
+ const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
20710
+ const words = lead < 0 ? [] : segment.words.slice(lead);
20711
+ const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
20712
+ if (CD2.has(name)) {
20713
+ dir = cdInto(dir, words);
20714
+ continue;
20715
+ }
20716
+ if (PUSHD2.has(name)) {
20717
+ stack.push(dir);
20718
+ dir = cdInto(dir, words);
20719
+ continue;
20720
+ }
20721
+ if (POPD2.has(name)) {
20722
+ dir = stack.length ? stack.pop() : undefined;
20723
+ continue;
20724
+ }
20725
+ for (const file of segment.redirects)
20726
+ if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
20727
+ const resolved = at(file);
20728
+ if (resolved)
20729
+ targets.push(resolved);
20730
+ }
20731
+ const edits = editsOf(segment.words);
20732
+ if (edits === null) {
20733
+ if (dir !== undefined)
20734
+ targets.push(path23.join(dir, DIR_TARGET));
20735
+ } else
20736
+ for (const file of edits) {
20737
+ const resolved = at(file);
20738
+ if (resolved)
20739
+ targets.push(resolved);
20740
+ }
20741
+ 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), "")))
20742
+ targets.push(path23.join(dir, DIR_TARGET));
20743
+ }
20744
+ return targets;
20745
+ };
20746
+ var overridden = (command, dialect) => {
20747
+ const segments = segmentsOf(command, dialect);
20748
+ if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
20749
+ return false;
20750
+ const words = segments[0].words;
20751
+ const lead = words.findIndex((word) => !/^\w+=/.test(word));
20752
+ return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
20753
+ };
20754
+ var HOST_LABELS = {
20755
+ claude_code: "Claude Code",
20756
+ opencode: "OpenCode",
20757
+ codex_cli: "Codex CLI",
20758
+ codex_desktop: "Codex Desktop",
20759
+ cursor: "Cursor",
20760
+ pi: "Pi"
20761
+ };
20762
+ var ageText = (ms) => {
20763
+ const seconds = Math.max(0, Math.round(ms / 1000));
20764
+ return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
20765
+ };
20766
+ var liveDeny = ({ marker, checkout, ageMs, file }) => {
20767
+ 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}.`;
20768
+ return {
20769
+ kind: "deny",
20770
+ 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}`,
20771
+ unblock
20772
+ };
20773
+ };
20774
+ var guarded = (targets) => {
20775
+ const seen = new Set;
20776
+ return targets.filter((target) => {
20777
+ const dir = path23.dirname(target);
20778
+ if (seen.has(dir))
20779
+ return false;
20780
+ seen.add(dir);
20781
+ return true;
20782
+ });
20783
+ };
20784
+ var liveGate = (owner, targets, deps) => {
20785
+ try {
20786
+ for (const target of guarded(targets)) {
20787
+ const conflict = liveConflict(target, owner, deps);
20788
+ if (conflict && workspaceLiveSessionGuard(conflict.checkout))
20789
+ return liveDeny(conflict);
20790
+ }
20791
+ } catch {}
20792
+ return NONE3;
20793
+ };
20794
+ var claimAll = (owner, targets, deps) => {
20795
+ for (const target of guarded(targets))
20796
+ claimLiveSession(target, owner, deps);
20797
+ };
20798
+ var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path23.resolve(cwd, file)) : [path23.join(cwd, DIR_TARGET)];
20799
+ function writeGate(cwd, paths, owner, deps) {
20800
+ if (!owner)
20801
+ return beforeWriteGate(cwd, paths);
20802
+ const targets = liveTargets(cwd, paths);
20803
+ const live = liveGate(owner, targets, deps);
20804
+ if (live.kind !== "none")
20805
+ return live;
20806
+ const decision = beforeWriteGate(cwd, paths);
20807
+ if (decision.kind === "none")
20808
+ claimAll(owner, targets, deps);
20809
+ return decision;
20810
+ }
20811
+ function shellGate(cwd, command, owner, dialect = "posix", deps) {
20812
+ const writes = shellWrites(command);
20813
+ let targets = [];
20814
+ if (owner) {
20815
+ try {
20816
+ targets = [
20817
+ ...segmentTargets(cwd, command, dialect),
20818
+ ...gitMutationDirs(cwd, command, dialect).map((dir) => path23.join(dir, DIR_TARGET))
20819
+ ];
20820
+ } catch {
20821
+ targets = [];
20822
+ }
20823
+ }
20824
+ if (owner && targets.length) {
20825
+ const live = liveGate(owner, targets, deps);
20826
+ if (live.kind !== "none") {
20827
+ if (!overridden(command, dialect))
20828
+ return live;
20829
+ noteOverride(cwd, owner, command);
20830
+ }
20831
+ }
20832
+ const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
20833
+ if (owner && targets.length && decision.kind === "none")
20834
+ claimAll(owner, targets, deps);
20835
+ return decision;
20836
+ }
20837
+ var noteOverride = (cwd, owner, command) => {
20838
+ try {
20839
+ if (!inWorkitWorkspace(cwd))
20840
+ return;
20841
+ appendHookObserved(cwd, {
20842
+ type: "live_session.override",
20843
+ actor: { host: owner.host, session: owner.session, agentId: null },
20844
+ session: owner.session,
20845
+ command: command.slice(0, 200)
20846
+ });
20847
+ } catch {}
20848
+ };
20849
+ function beforeWriteGate(cwd, paths) {
20378
20850
  try {
20379
20851
  const gate = gateFor(cwd);
20380
20852
  if (!gate || gate.blockers.length === 0)
@@ -20392,7 +20864,7 @@ function writeGate(cwd, paths) {
20392
20864
  }
20393
20865
  }
20394
20866
  // packages/workit-core/src/hooks/stop.ts
20395
- import { spawnSync as spawnSync13 } from "node:child_process";
20867
+ import { spawnSync as spawnSync14 } from "node:child_process";
20396
20868
  var NONE4 = { kind: "none" };
20397
20869
  var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
20398
20870
  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 +20885,7 @@ var asksUser = (message) => {
20413
20885
  };
20414
20886
  var short = (sha) => (sha ?? "?").slice(0, 12);
20415
20887
  var gitList = (cwd, args) => {
20416
- const run = spawnSync13("git", args, {
20888
+ const run = spawnSync14("git", args, {
20417
20889
  cwd,
20418
20890
  encoding: "utf8",
20419
20891
  timeout: 3000,
@@ -20777,12 +21249,12 @@ ${line}` };
20777
21249
  };
20778
21250
  // packages/workit-core/src/hooks/hosts/fields.ts
20779
21251
  import { existsSync as existsSync7, realpathSync as realpathSync6, statSync as statSync2 } from "node:fs";
20780
- import path23 from "node:path";
21252
+ import path24 from "node:path";
20781
21253
  var isRecord4 = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
20782
21254
  var nonEmpty2 = (value) => typeof value === "string" && value.trim() !== "";
20783
21255
  var optionalText = (value) => typeof value === "string" ? value : null;
20784
21256
  var existingDirectory = (value) => {
20785
- if (!nonEmpty2(value) || !path23.isAbsolute(value) || !existsSync7(value))
21257
+ if (!nonEmpty2(value) || !path24.isAbsolute(value) || !existsSync7(value))
20786
21258
  return null;
20787
21259
  try {
20788
21260
  return statSync2(value).isDirectory() ? realpathSync6(value) : null;
@@ -20927,8 +21399,7 @@ function handleHook(input, deps) {
20927
21399
  const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
20928
21400
  if (raw.kind === "deny")
20929
21401
  return raw;
20930
- const writes = usable2(descriptor.events["write.pre"].support) ? shellWrites(event.command) : { writes: false, targets: null };
20931
- const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
21402
+ const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
20932
21403
  if (gate.kind === "deny")
20933
21404
  return gate;
20934
21405
  noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
@@ -20941,7 +21412,7 @@ function handleHook(input, deps) {
20941
21412
  rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
20942
21413
  return NONE5;
20943
21414
  case "write.pre":
20944
- return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
21415
+ return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
20945
21416
  case "subagent.start":
20946
21417
  return {
20947
21418
  kind: "context",
@@ -21520,8 +21991,8 @@ var codexAdapter = {
21520
21991
  };
21521
21992
 
21522
21993
  // packages/workit-core/src/hooks/hosts/cursor.ts
21523
- import fs17 from "node:fs";
21524
- import path24 from "node:path";
21994
+ import fs18 from "node:fs";
21995
+ import path25 from "node:path";
21525
21996
  var undocumented = { support: "undocumented", native: null };
21526
21997
  var none = { support: "none", native: null };
21527
21998
  var CURSOR_DESCRIPTOR = {
@@ -21718,17 +22189,17 @@ var protocolEvent2 = (input) => {
21718
22189
  };
21719
22190
  var TRANSCRIPT_TAIL_BYTES = 256 * 1024;
21720
22191
  var lastAssistantText = (file) => {
21721
- if (!file || !path24.isAbsolute(file))
22192
+ if (!file || !path25.isAbsolute(file))
21722
22193
  return null;
21723
22194
  try {
21724
- const size = fs17.statSync(file).size;
22195
+ const size = fs18.statSync(file).size;
21725
22196
  const start = Math.max(0, size - TRANSCRIPT_TAIL_BYTES);
21726
22197
  const buffer = Buffer.alloc(size - start);
21727
- const fd = fs17.openSync(file, "r");
22198
+ const fd = fs18.openSync(file, "r");
21728
22199
  try {
21729
- fs17.readSync(fd, buffer, 0, buffer.length, start);
22200
+ fs18.readSync(fd, buffer, 0, buffer.length, start);
21730
22201
  } finally {
21731
- fs17.closeSync(fd);
22202
+ fs18.closeSync(fd);
21732
22203
  }
21733
22204
  const lines = buffer.toString("utf8").split(`
21734
22205
  `);
@@ -21755,7 +22226,7 @@ var lastAssistantText = (file) => {
21755
22226
  return null;
21756
22227
  }
21757
22228
  };
21758
- var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path24.sep) ? root : `${root}${path24.sep}`);
22229
+ var within = (root, dir) => dir === root || dir.startsWith(root.endsWith(path25.sep) ? root : `${root}${path25.sep}`);
21759
22230
  var hookCwd = (input) => {
21760
22231
  const cwd = existingDirectory(input.cwd);
21761
22232
  if (cwd && input.hook_event_name === "beforeShellExecution")
@@ -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-j1zd509p.js");
40
+ const { runCodexHook } = await import("./workit-hook-m4bfzykw.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.13.1",
3
+ "version": "8.14.1",
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.13.1",
43
- "@brainervirus/workit-mcp": "^8.13.1"
42
+ "@brainervirus/workit-core": "^8.14.1",
43
+ "@brainervirus/workit-mcp": "^8.14.1"
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. Pick the model from TIER (`references/orchestration.md`).
29
- Claude Code: the `implementer` agent; elsewhere first `workit fanout
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. Map it where the host lets you
9
- pick a model per spawn; nothing in the workspace config routes it.
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
  | --- | --- | --- |