@brainervirus/workit-codex 1.0.9 → 1.0.10

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.
@@ -3,8 +3,8 @@ import { createRequire } from "node:module";
3
3
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
4
4
 
5
5
  // packages/workit-codex/hooks/workit-hook.ts
6
- import { existsSync as existsSync3, realpathSync as realpathSync5, statSync } from "node:fs";
7
- import path6 from "node:path";
6
+ import { existsSync as existsSync4, realpathSync as realpathSync5, statSync } from "node:fs";
7
+ import path7 from "node:path";
8
8
 
9
9
  // packages/workit-core/src/core/task-contract.ts
10
10
  import { createHash, randomUUID } from "node:crypto";
@@ -7573,6 +7573,7 @@ var decisionSchema = object({
7573
7573
  scope: scopeSchema,
7574
7574
  presented: text,
7575
7575
  approvedContent: text,
7576
+ displayed: text.optional(),
7576
7577
  contentRefs: array(refSchema)
7577
7578
  }).strict(),
7578
7579
  digest,
@@ -7651,6 +7652,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
7651
7652
  path: ["decisionId"]
7652
7653
  });
7653
7654
  });
7655
+ var runtimeSchema = object({
7656
+ createdWith: text.nullable(),
7657
+ updatedWith: text
7658
+ }).strict();
7654
7659
  var taskRecordSchema = object({
7655
7660
  schemaVersion: literal(1),
7656
7661
  id,
@@ -7664,6 +7669,8 @@ var taskRecordSchema = object({
7664
7669
  status: _enum(["active", "paused", "closed"]),
7665
7670
  closure: closureSchema.nullable(),
7666
7671
  progress: progressSchema,
7672
+ pauseReason: text.nullable().optional(),
7673
+ runtime: runtimeSchema.optional(),
7667
7674
  assessments: array(entrySchema(assessmentSchema)),
7668
7675
  policy: policySchema.nullable(),
7669
7676
  policyChanges: array(policyChangeSchema),
@@ -7679,6 +7686,7 @@ var workspaceRecordSchema = object({
7679
7686
  id,
7680
7687
  revision,
7681
7688
  root: nonEmpty,
7689
+ runtime: runtimeSchema.optional(),
7682
7690
  writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
7683
7691
  }).strict();
7684
7692
  var capabilitySchema = object({
@@ -7706,6 +7714,9 @@ var taskSummarySchema = object({
7706
7714
  id,
7707
7715
  revision,
7708
7716
  workspaceRevision: revision,
7717
+ createdAt: utc,
7718
+ updatedAt: utc,
7719
+ runtime: runtimeSchema.nullable(),
7709
7720
  objective: text,
7710
7721
  status: _enum(["active", "paused", "closed"]),
7711
7722
  closure: closureSchema.nullable(),
@@ -7776,7 +7787,7 @@ var taskOperations = {
7776
7787
  expectedWorkspaceRevision: revision.optional(),
7777
7788
  outcome: outcomeSchema,
7778
7789
  summary: text,
7779
- decisionIds: array(id)
7790
+ decisionIds: array(id).optional()
7780
7791
  })
7781
7792
  };
7782
7793
  var policyOperations = {
@@ -7868,14 +7879,15 @@ var writerOperations = {
7868
7879
  ...taskId,
7869
7880
  expectedRevision: revision.optional(),
7870
7881
  expectedWorkspaceRevision: revision.optional(),
7871
- workerId: nullableId.optional()
7882
+ workerId: nullableId.optional(),
7883
+ reason: text.optional()
7872
7884
  }),
7873
7885
  release: operation({
7874
7886
  action: literal("release"),
7875
7887
  ...taskId,
7876
7888
  expectedRevision: revision.optional(),
7877
7889
  expectedWorkspaceRevision: revision.optional(),
7878
- reason: text
7890
+ reason: text.optional()
7879
7891
  })
7880
7892
  };
7881
7893
  var stateOperations = {
@@ -8026,7 +8038,21 @@ function requirementId(input) {
8026
8038
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
8027
8039
  import * as fs4 from "node:fs";
8028
8040
  import { hostname } from "node:os";
8029
- import path3 from "node:path";
8041
+ import path4 from "node:path";
8042
+
8043
+ // packages/workit-core/src/core/package-root.ts
8044
+ import { existsSync } from "node:fs";
8045
+ import path from "node:path";
8046
+ import { fileURLToPath } from "node:url";
8047
+ var packageRoot = () => {
8048
+ let dir = path.dirname(fileURLToPath(import.meta.url));
8049
+ while (true) {
8050
+ const parent = path.dirname(dir);
8051
+ if (existsSync(path.join(dir, "package.json")) || parent === dir)
8052
+ return dir;
8053
+ dir = parent;
8054
+ }
8055
+ };
8030
8056
 
8031
8057
  // node_modules/@openclaw/fs-safe/dist/errors.js
8032
8058
  var OPERATIONAL_CODES = new Set([
@@ -8077,7 +8103,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
8077
8103
  // node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
8078
8104
  import { randomBytes } from "node:crypto";
8079
8105
  import fsSync from "node:fs";
8080
- import path from "node:path";
8106
+ import path2 from "node:path";
8081
8107
 
8082
8108
  // node_modules/@openclaw/fs-safe/dist/bounded-read.js
8083
8109
  import fs from "node:fs";
@@ -8167,13 +8193,13 @@ ${ownershipToken}
8167
8193
  return { raw, ownershipToken };
8168
8194
  }
8169
8195
  function relativeSidecarLockPath(lockRoot, lockPath) {
8170
- const resolved = path.resolve(lockPath);
8171
- const lexicalRelative = path.relative(lockRoot.rootDir, resolved);
8172
- const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path.sep}`) && !path.isAbsolute(lexicalRelative) ? lexicalRelative : path.relative(lockRoot.rootReal, resolved);
8173
- if (!relative || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
8196
+ const resolved = path2.resolve(lockPath);
8197
+ const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
8198
+ const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
8199
+ if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
8174
8200
  throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
8175
8201
  }
8176
- return relative.split(path.sep).join(path.posix.sep);
8202
+ return relative.split(path2.sep).join(path2.posix.sep);
8177
8203
  }
8178
8204
  function parseSidecarLockPayload(raw, parser) {
8179
8205
  if (parser) {
@@ -8320,7 +8346,7 @@ function getFsSafeLockConfig() {
8320
8346
 
8321
8347
  // node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
8322
8348
  import fs3 from "node:fs";
8323
- import path2 from "node:path";
8349
+ import path3 from "node:path";
8324
8350
 
8325
8351
  // node_modules/@openclaw/fs-safe/dist/timing.js
8326
8352
  function sleepSync(ms) {
@@ -8411,26 +8437,26 @@ function canonicalLockParentSync(parent) {
8411
8437
  return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
8412
8438
  }
8413
8439
  function normalizeTargetPath(targetPath) {
8414
- const resolved = path2.resolve(targetPath);
8415
- fs3.mkdirSync(path2.dirname(resolved), { recursive: true });
8440
+ const resolved = path3.resolve(targetPath);
8441
+ fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
8416
8442
  try {
8417
- return path2.join(canonicalLockParentSync(path2.dirname(resolved)), path2.basename(resolved));
8443
+ return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
8418
8444
  } catch {
8419
8445
  return resolved;
8420
8446
  }
8421
8447
  }
8422
8448
  function boundedLockPath(lockPath, lockRoot) {
8423
- const resolved = path2.resolve(lockPath);
8449
+ const resolved = path3.resolve(lockPath);
8424
8450
  if (!lockRoot)
8425
8451
  return resolved;
8426
8452
  relativeSidecarLockPath(lockRoot, resolved);
8427
- const parent = path2.dirname(resolved);
8453
+ const parent = path3.dirname(resolved);
8428
8454
  const parentReal = canonicalLockParentSync(parent);
8429
- const parentRelative = path2.relative(lockRoot.rootReal, parentReal);
8430
- if (parentRelative === ".." || parentRelative.startsWith(`..${path2.sep}`) || path2.isAbsolute(parentRelative)) {
8455
+ const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
8456
+ if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
8431
8457
  throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
8432
8458
  }
8433
- return path2.join(parentReal, path2.basename(resolved));
8459
+ return path3.join(parentReal, path3.basename(resolved));
8434
8460
  }
8435
8461
  function defaultShouldReclaim(snapshot, staleMs, nowMs) {
8436
8462
  const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
@@ -8643,6 +8669,33 @@ var metadataLockSchema = object({
8643
8669
  nonce: string2().min(1)
8644
8670
  }).strict();
8645
8671
  var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
8672
+ var cachedRuntimeVersion = null;
8673
+ var runtimeVersion = () => {
8674
+ if (cachedRuntimeVersion === null) {
8675
+ try {
8676
+ const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
8677
+ cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
8678
+ } catch {
8679
+ cachedRuntimeVersion = "0.0.0";
8680
+ }
8681
+ }
8682
+ return cachedRuntimeVersion;
8683
+ };
8684
+ var stampNew = (record) => ({
8685
+ createdWith: record.runtime?.createdWith ?? null,
8686
+ updatedWith: runtimeVersion()
8687
+ });
8688
+ var isNewerVersion = (candidate, current) => {
8689
+ const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
8690
+ const [next, now] = [parts(candidate), parts(current)];
8691
+ for (let index = 0;index < 3; index += 1) {
8692
+ const left = next[index] ?? 0;
8693
+ const right = now[index] ?? 0;
8694
+ if (left !== right)
8695
+ return left > right;
8696
+ }
8697
+ return false;
8698
+ };
8646
8699
  var jsonBytes = (value) => `${canonicalJson(value)}
8647
8700
  `;
8648
8701
  var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
@@ -8669,12 +8722,12 @@ var sameMetadataLock = (left, right) => {
8669
8722
  class TaskStore {
8670
8723
  root;
8671
8724
  constructor(root) {
8672
- this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path3.resolve(root);
8725
+ this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
8673
8726
  }
8674
8727
  readTask(taskId) {
8675
8728
  if (!validId(taskId))
8676
8729
  return failure("invalid_input", "task ID is invalid", { taskId });
8677
- const result = this.readRecord(path3.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8730
+ const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
8678
8731
  if (!result.exists)
8679
8732
  return failure("not_found", "task not found", { taskId });
8680
8733
  if (result.result.ok && result.result.data.id !== taskId)
@@ -8708,7 +8761,7 @@ class TaskStore {
8708
8761
  });
8709
8762
  const tasks = [];
8710
8763
  for (const name of names.sort()) {
8711
- const item = this.readRecord(path3.join(this.tasksDir, name), taskRecordSchema);
8764
+ const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
8712
8765
  if (!item.exists)
8713
8766
  continue;
8714
8767
  if (!item.result.ok)
@@ -8742,7 +8795,7 @@ class TaskStore {
8742
8795
  if (!current.ok)
8743
8796
  return current;
8744
8797
  if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
8745
- return failure("revision_conflict", "workspace revision does not match", {
8798
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8746
8799
  expectedWorkspaceRevision: value.expectedWorkspaceRevision,
8747
8800
  actualWorkspaceRevision: current.data?.revision ?? null
8748
8801
  });
@@ -8754,11 +8807,17 @@ class TaskStore {
8754
8807
  const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
8755
8808
  if (current.data && !previousWorkspaceBytes)
8756
8809
  return failure("storage_error", "workspace snapshot disappeared during creation");
8757
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8810
+ const workspace = current.data ? {
8811
+ ...current.data,
8812
+ revision: newRevision(),
8813
+ root: this.root,
8814
+ runtime: stampNew(current.data)
8815
+ } : {
8758
8816
  schemaVersion: SCHEMA_VERSION,
8759
8817
  id: newId(),
8760
8818
  revision: newRevision(),
8761
8819
  root: this.root,
8820
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8762
8821
  writer: null
8763
8822
  };
8764
8823
  const timestamp = value.now ?? now();
@@ -8780,6 +8839,7 @@ class TaskStore {
8780
8839
  status: "active",
8781
8840
  closure: null,
8782
8841
  progress: { summary: "", nextAction: null, blockers: [] },
8842
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8783
8843
  assessments: [],
8784
8844
  policy: null,
8785
8845
  policyChanges: [],
@@ -8810,7 +8870,7 @@ class TaskStore {
8810
8870
  if (!current.ok)
8811
8871
  return current;
8812
8872
  if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
8813
- return failure("revision_conflict", "workspace revision does not match", {
8873
+ return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
8814
8874
  expectedWorkspaceRevision: input.expectedWorkspaceRevision,
8815
8875
  actualWorkspaceRevision: current.data?.revision ?? null
8816
8876
  });
@@ -8818,11 +8878,17 @@ class TaskStore {
8818
8878
  if (current.data && !previousWorkspaceBytes)
8819
8879
  return failure("storage_error", "workspace snapshot disappeared during import");
8820
8880
  const timestamp = input.now ?? now();
8821
- const workspace = current.data ? { ...current.data, revision: newRevision(), root: this.root } : {
8881
+ const workspace = current.data ? {
8882
+ ...current.data,
8883
+ revision: newRevision(),
8884
+ root: this.root,
8885
+ runtime: stampNew(current.data)
8886
+ } : {
8822
8887
  schemaVersion: SCHEMA_VERSION,
8823
8888
  id: input.workspaceId ?? newId(),
8824
8889
  revision: newRevision(),
8825
8890
  root: this.root,
8891
+ runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
8826
8892
  writer: null
8827
8893
  };
8828
8894
  const task = {
@@ -8830,7 +8896,11 @@ class TaskStore {
8830
8896
  workspaceId: workspace.id,
8831
8897
  revision: newRevision(),
8832
8898
  createdAt: timestamp,
8833
- updatedAt: timestamp
8899
+ updatedAt: timestamp,
8900
+ runtime: {
8901
+ createdWith: input.task.runtime?.createdWith ?? null,
8902
+ updatedWith: runtimeVersion()
8903
+ }
8834
8904
  };
8835
8905
  const validTask = taskRecordSchema.safeParse(task);
8836
8906
  if (!validTask.success)
@@ -8879,7 +8949,8 @@ class TaskStore {
8879
8949
  workspaceId: current.data.workspaceId,
8880
8950
  createdAt: current.data.createdAt,
8881
8951
  revision: context.revision,
8882
- updatedAt: context.now
8952
+ updatedAt: context.now,
8953
+ runtime: stampNew(current.data)
8883
8954
  };
8884
8955
  const valid = taskRecordSchema.safeParse(record);
8885
8956
  if (!valid.success)
@@ -8913,7 +8984,8 @@ class TaskStore {
8913
8984
  ...changed.data,
8914
8985
  id: current.data.id,
8915
8986
  root: this.root,
8916
- revision: context.revision
8987
+ revision: context.revision,
8988
+ runtime: stampNew(current.data)
8917
8989
  };
8918
8990
  const valid = workspaceRecordSchema.safeParse(record);
8919
8991
  if (!valid.success)
@@ -8956,7 +9028,8 @@ class TaskStore {
8956
9028
  ...changedWorkspace.data,
8957
9029
  id: workspace.data.id,
8958
9030
  root: this.root,
8959
- revision: workspaceContext.revision
9031
+ revision: workspaceContext.revision,
9032
+ runtime: stampNew(workspace.data)
8960
9033
  });
8961
9034
  if (!nextWorkspace.success)
8962
9035
  return failure("invalid_input", "workspace mutation produced an invalid record");
@@ -8981,7 +9054,8 @@ class TaskStore {
8981
9054
  workspaceId: task.data.workspaceId,
8982
9055
  createdAt: task.data.createdAt,
8983
9056
  revision: taskContext.revision,
8984
- updatedAt: taskContext.now
9057
+ updatedAt: taskContext.now,
9058
+ runtime: stampNew(task.data)
8985
9059
  });
8986
9060
  if (!nextTask.success) {
8987
9061
  this.markUncertain(nextWorkspace.data);
@@ -9008,7 +9082,7 @@ class TaskStore {
9008
9082
  if (match)
9009
9083
  candidates.push({
9010
9084
  target: match[1],
9011
- path: path3.join(this.recoveryDir, name),
9085
+ path: path4.join(this.recoveryDir, name),
9012
9086
  digest: match[3]
9013
9087
  });
9014
9088
  }
@@ -9201,7 +9275,8 @@ class TaskStore {
9201
9275
  const value = {
9202
9276
  ...workspace,
9203
9277
  writer: { ...workspace.writer, state: "uncertain" },
9204
- revision: newRevision()
9278
+ revision: newRevision(),
9279
+ runtime: stampNew(workspace)
9205
9280
  };
9206
9281
  this.replaceSnapshot(this.workspacePath, value, workspace);
9207
9282
  }
@@ -9279,7 +9354,7 @@ class TaskStore {
9279
9354
  }
9280
9355
  fs4.renameSync(temporary, file);
9281
9356
  temporary = undefined;
9282
- this.fsyncDirectory(path3.dirname(file));
9357
+ this.fsyncDirectory(path4.dirname(file));
9283
9358
  return success(null, null, value);
9284
9359
  } catch (error) {
9285
9360
  return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
@@ -9293,9 +9368,9 @@ class TaskStore {
9293
9368
  }
9294
9369
  }
9295
9370
  saveRecovery(file, bytes) {
9296
- const target = path3.basename(file) === "workspace.json" ? "workspace" : "task";
9297
- const id = target === "task" ? path3.basename(file, ".json") : "workspace";
9298
- const destination = path3.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
9371
+ const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
9372
+ const id = target === "task" ? path4.basename(file, ".json") : "workspace";
9373
+ const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
9299
9374
  if (fs4.existsSync(destination)) {
9300
9375
  if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
9301
9376
  return;
@@ -9373,7 +9448,7 @@ class TaskStore {
9373
9448
  for (const name of fs4.readdirSync(this.tasksDir)) {
9374
9449
  if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
9375
9450
  return null;
9376
- const parsed = this.parseBytes(fs4.readFileSync(path3.join(this.tasksDir, name)), taskRecordSchema);
9451
+ const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
9377
9452
  if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
9378
9453
  return null;
9379
9454
  ids.add(parsed.data.workspaceId);
@@ -9415,7 +9490,12 @@ class TaskStore {
9415
9490
  if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
9416
9491
  return failure("unsupported_version", "unsupported snapshot schema version");
9417
9492
  const parsed = schema.safeParse(value);
9418
- return parsed.success ? success(null, null, parsed.data) : failure("recovery_required", "snapshot does not satisfy its schema");
9493
+ if (parsed.success)
9494
+ return success(null, null, parsed.data);
9495
+ const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
9496
+ if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
9497
+ return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
9498
+ return failure("recovery_required", "snapshot does not satisfy its schema");
9419
9499
  }
9420
9500
  processStart(pid) {
9421
9501
  try {
@@ -9425,43 +9505,98 @@ class TaskStore {
9425
9505
  }
9426
9506
  }
9427
9507
  conflict(expected, actual) {
9428
- return failure("revision_conflict", "snapshot revision does not match", {
9508
+ return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
9429
9509
  expectedRevision: expected,
9430
9510
  actualRevision: actual
9431
9511
  });
9432
9512
  }
9433
9513
  taskPath(taskId) {
9434
- return path3.join(this.tasksDir, `${taskId}.json`);
9514
+ return path4.join(this.tasksDir, `${taskId}.json`);
9435
9515
  }
9436
9516
  get workitDir() {
9437
- return path3.join(this.root, ".workit");
9517
+ return path4.join(this.root, ".workit");
9438
9518
  }
9439
9519
  get tasksDir() {
9440
- return path3.join(this.workitDir, "tasks");
9520
+ return path4.join(this.workitDir, "tasks");
9441
9521
  }
9442
9522
  get recoveryDir() {
9443
- return path3.join(this.workitDir, "recovery");
9523
+ return path4.join(this.workitDir, "recovery");
9444
9524
  }
9445
9525
  get workspacePath() {
9446
- return path3.join(this.workitDir, "workspace.json");
9526
+ return path4.join(this.workitDir, "workspace.json");
9447
9527
  }
9448
9528
  get lockPath() {
9449
- return path3.join(this.workitDir, "metadata.lock");
9529
+ return path4.join(this.workitDir, "metadata.lock");
9450
9530
  }
9451
9531
  get gitignorePath() {
9452
- return path3.join(this.workitDir, ".gitignore");
9532
+ return path4.join(this.workitDir, ".gitignore");
9533
+ }
9534
+ }
9535
+ // packages/workit-core/src/core/workers.ts
9536
+ var same = (left, right) => {
9537
+ try {
9538
+ return canonicalJson(left) === canonicalJson(right);
9539
+ } catch {
9540
+ return false;
9453
9541
  }
9542
+ };
9543
+ var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
9544
+ var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
9545
+ var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
9546
+ var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
9547
+ const code = char.charCodeAt(0);
9548
+ return code < 32 || code === 127;
9549
+ }) && !value.split(/[\\/]/).includes("..");
9550
+ var callerValue = (value) => ({ host: value.host, actor: value.actor });
9551
+ var workerIdOf = (value) => value.workerId ?? null;
9552
+ function assertProductWriteAllowed(input) {
9553
+ if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
9554
+ return failure("invalid_input", "invalid product write path");
9555
+ if (!input.store)
9556
+ return failure("permission_denied", "write authorization requires core state");
9557
+ const task = input.store.readTask(input.task.id);
9558
+ if (!task.ok)
9559
+ return task;
9560
+ const workspace = input.store.readWorkspace();
9561
+ if (!workspace.ok)
9562
+ return workspace;
9563
+ if (!workspace.data)
9564
+ return failure("not_found", "workspace not found");
9565
+ const workspaceRecord = workspace.data;
9566
+ if (task.data.status !== "active")
9567
+ return failure("invalid_transition", "paused or closed tasks cannot own product writes");
9568
+ if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
9569
+ return failure("recovery_required", "task and workspace bindings are invalid");
9570
+ const writer = workspaceRecord.writer;
9571
+ if (!writer)
9572
+ return failure("permission_denied", "checkout has no writer owner");
9573
+ if (writer.state === "uncertain")
9574
+ return failure("recovery_required", "checkout writer requires recovery", {
9575
+ owner: writer.owner
9576
+ });
9577
+ const caller = callerValue(input.caller);
9578
+ const workerId = workerIdOf(input.caller);
9579
+ const owner = writer.owner;
9580
+ if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
9581
+ return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
9582
+ const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
9583
+ if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
9584
+ return failure("permission_denied", "only implementers can own product writes");
9585
+ if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
9586
+ return failure("recovery_required", "worker session is not an active writer");
9587
+ return success(null, workspaceRecord.revision, owner);
9454
9588
  }
9589
+
9455
9590
  // packages/workit-core/src/core/task-evaluation.ts
9456
9591
  import { createHash as createHash4 } from "node:crypto";
9457
9592
  import * as fs6 from "node:fs";
9458
9593
  import { spawnSync } from "node:child_process";
9459
- import path5 from "node:path";
9594
+ import path6 from "node:path";
9460
9595
 
9461
9596
  // packages/workit-core/src/core/authority.ts
9462
9597
  import { createHash as createHash3 } from "node:crypto";
9463
9598
  import * as fs5 from "node:fs";
9464
- import path4 from "node:path";
9599
+ import path5 from "node:path";
9465
9600
  var verifiedAuthorities = new WeakMap;
9466
9601
  var trustedAuthority = (kind, provenance, expected, binding, caller) => {
9467
9602
  const token = {};
@@ -9588,20 +9723,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
9588
9723
  continue;
9589
9724
  if (!reference.digest)
9590
9725
  return failure("invalid_input", "document references require a byte digest");
9591
- const target = path4.resolve(root, reference.path);
9592
- if (target !== root && !target.startsWith(`${root}${path4.sep}`))
9593
- return failure("invalid_input", "document reference escapes checkout");
9726
+ const target = path5.resolve(root, reference.path);
9727
+ if (target !== root && !target.startsWith(`${root}${path5.sep}`))
9728
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9729
+ fields: [
9730
+ { path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
9731
+ ]
9732
+ });
9594
9733
  try {
9595
- const relative = path4.relative(root, target);
9734
+ const relative = path5.relative(root, target);
9596
9735
  let current = root;
9597
- for (const segment of relative.split(path4.sep)) {
9736
+ for (const segment of relative.split(path5.sep)) {
9598
9737
  if (!segment)
9599
9738
  continue;
9600
- current = path4.join(current, segment);
9739
+ current = path5.join(current, segment);
9601
9740
  const stat = fs5.lstatSync(current);
9602
9741
  const real = fs5.realpathSync(current);
9603
- if (real !== root && !real.startsWith(`${root}${path4.sep}`))
9604
- return failure("invalid_input", "document reference escapes checkout");
9742
+ if (real !== root && !real.startsWith(`${root}${path5.sep}`))
9743
+ return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
9744
+ fields: [
9745
+ {
9746
+ path: "contentRefs.path",
9747
+ reason: `${reference.path} resolves outside the checkout`
9748
+ }
9749
+ ]
9750
+ });
9605
9751
  if (stat.isSymbolicLink())
9606
9752
  return failure("invalid_input", "document reference uses a symlink");
9607
9753
  if (current === target && !stat.isFile())
@@ -9948,14 +10094,14 @@ var verifyDecisionContent = verifyContentRefs;
9948
10094
 
9949
10095
  // packages/workit-core/src/core/task-evaluation.ts
9950
10096
  var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
9951
- var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path5.sep}`);
9952
- var relative = (root, candidate) => path5.relative(root, candidate).split(path5.sep).join("/") || ".";
10097
+ var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
10098
+ var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
9953
10099
  var canonicalPath = (value) => {
9954
10100
  if (typeof value !== "string" || !value)
9955
10101
  return null;
9956
10102
  if (value.includes("\\") || value.split("/").includes(".."))
9957
10103
  return null;
9958
- const normalized = path5.posix.normalize(value);
10104
+ const normalized = path6.posix.normalize(value);
9959
10105
  if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
9960
10106
  return null;
9961
10107
  return normalized === "." ? "." : normalized.replace(/\/$/, "");
@@ -10003,14 +10149,14 @@ var gitPaths = (root) => {
10003
10149
  git: /not a git repository/i.test(stderr) ? false : true
10004
10150
  };
10005
10151
  }
10006
- const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")) : [];
10152
+ const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
10007
10153
  const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
10008
10154
  if (stagedDeleted.status !== 0) {
10009
10155
  const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
10010
10156
  if (!/not a git repository/i.test(stderr))
10011
10157
  return { paths, uncertain: true, git: true };
10012
10158
  } else if (stagedDeleted.stdout) {
10013
- paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path5.sep).join("/")));
10159
+ paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
10014
10160
  }
10015
10161
  return { paths: [...new Set(paths)], uncertain: false, git: true };
10016
10162
  };
@@ -10025,7 +10171,7 @@ var walk = (root, directory, output) => {
10025
10171
  for (const name of names) {
10026
10172
  if (name === ".git" || name === ".workit")
10027
10173
  continue;
10028
- const target = path5.join(directory, name);
10174
+ const target = path6.join(directory, name);
10029
10175
  let stat;
10030
10176
  try {
10031
10177
  stat = fs6.lstatSync(target);
@@ -10043,17 +10189,17 @@ var walk = (root, directory, output) => {
10043
10189
  var scopeRoots = (root, scope) => {
10044
10190
  const roots = [];
10045
10191
  for (const item of scope.paths.length ? scope.paths : ["."]) {
10046
- const target = path5.resolve(root, item);
10192
+ const target = path6.resolve(root, item);
10047
10193
  if (!inside(root, target))
10048
- return failure("invalid_input", "candidate scope escapes checkout");
10194
+ return failure("invalid_input", `candidate scope escapes checkout: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} is outside the checkout` }] });
10049
10195
  let ancestor = target;
10050
10196
  while (!fs6.existsSync(ancestor) && ancestor !== root)
10051
- ancestor = path5.dirname(ancestor);
10197
+ ancestor = path6.dirname(ancestor);
10052
10198
  try {
10053
10199
  if (!inside(root, fs6.realpathSync(ancestor)))
10054
- return failure("invalid_input", "candidate scope escapes checkout");
10200
+ return failure("invalid_input", `candidate scope escapes checkout through a symlink: ${item} — keep one task per repository and coordinate a separate linked task in the other checkout`, { fields: [{ path: "scope.paths", reason: `${item} resolves outside the checkout` }] });
10055
10201
  } catch {
10056
- return failure("invalid_input", "candidate scope cannot be inspected");
10202
+ return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
10057
10203
  }
10058
10204
  roots.push(target);
10059
10205
  }
@@ -10068,7 +10214,7 @@ function captureCandidate(root, scope, environment = []) {
10068
10214
  }
10069
10215
  const normalizedScope = canonicalScope(scope);
10070
10216
  if (!normalizedScope)
10071
- return failure("invalid_input", "candidate scope is invalid");
10217
+ return failure("invalid_input", "candidate scope is invalid: paths must be checkout-relative — to work in another repository, keep this task here and coordinate a separate linked task in that checkout", { fields: [{ path: "scope.paths", reason: "must be checkout-relative" }] });
10072
10218
  const roots = scopeRoots(checkout, normalizedScope);
10073
10219
  if (!roots.ok)
10074
10220
  return roots;
@@ -10100,7 +10246,7 @@ function captureCandidate(root, scope, environment = []) {
10100
10246
  for (const item of [...names].sort()) {
10101
10247
  if (!scopeMatches(item, normalizedScope))
10102
10248
  continue;
10103
- const target = path5.join(checkout, item);
10249
+ const target = path6.join(checkout, item);
10104
10250
  try {
10105
10251
  const stat = fs6.lstatSync(target);
10106
10252
  if (stat.isSymbolicLink()) {
@@ -10257,7 +10403,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
10257
10403
  return true;
10258
10404
  const reviewSession = sessionFromRef(entry.data.reviewContext);
10259
10405
  const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
10260
- const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
10406
+ const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
10261
10407
  return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
10262
10408
  });
10263
10409
  if (passed.length) {
@@ -10353,7 +10499,17 @@ function evaluateClosure(requestedOutcome, view) {
10353
10499
  const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
10354
10500
  if (requestedOutcome !== "stopped" && blocking.length)
10355
10501
  return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
10356
- requirementIds: blocking.map((item) => item.requirementId)
10502
+ requirementIds: blocking.map((item) => item.requirementId),
10503
+ requirements: blocking.map((item) => {
10504
+ const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
10505
+ return {
10506
+ requirementId: item.requirementId,
10507
+ ruleId: requirement?.ruleId ?? "unknown",
10508
+ reason: item.reason,
10509
+ satisfaction: requirement?.satisfaction ?? "",
10510
+ dependentAction: requirement?.dependentAction ?? null
10511
+ };
10512
+ })
10357
10513
  });
10358
10514
  const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
10359
10515
  if (requestedOutcome === "verified" && accepted.length)
@@ -10384,7 +10540,7 @@ function evaluateClosure(requestedOutcome, view) {
10384
10540
  var METHODS = {
10385
10541
  "workit-challenge": { dimensions: ["challenge", "decisions"] },
10386
10542
  "workit-behavioral-tdd": { dimensions: ["testing"] },
10387
- "workit-review": { dimensions: ["review"] },
10543
+ "workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
10388
10544
  "workit-plan": { dimensions: ["artifacts", "continuity"] },
10389
10545
  "workit-implement": { dimensions: ["delegation"] },
10390
10546
  "workit-debug": { ruleIds: ["root-cause-investigation"] },
@@ -10446,7 +10602,18 @@ decision.record {binding with taskId/workspaceId from inspect plus presented and
10446
10602
  approvedContent} only through a native approval question asked receipt-shaped:
10447
10603
  header Workit decision: <purpose>, the same label repeated in the question
10448
10604
  text (some host UIs do not render headers), exactly two options approved and
10449
- rejected; ask once, never re-ask to mint a receipt. task.close {outcome,
10605
+ rejected; ask once, never re-ask to mint a receipt. Before asking, present the
10606
+ item in the conversation (durable artifacts: complete digest plus exact path;
10607
+ inline plans: the content) and keep the question to one short scoped sentence;
10608
+ never ask to approve something unseen. An answer with custom text is steering,
10609
+ not approval: adjust, re-present, then ask a new scoped question. After the user
10610
+ approves a spec or plan, execute the remaining tasks continuously with one
10611
+ atomic commit per task and do not ask whether to continue; stop only for a new
10612
+ product decision, a failed safety or verification gate, a conflicting edit, or
10613
+ missing authority. Record the plan's commit list once through the plan-scoped
10614
+ action approval (git.commit with plan_steps and plan_branch) so each listed
10615
+ commit executes without a new question; an unlisted message needs a fresh
10616
+ approval. task.close {outcome,
10450
10617
  summary, decisionIds}. A completed native subagent run stops its bound worker
10451
10618
  by itself; if a worker strands in cancelling with its run verifiably over,
10452
10619
  repeat worker.cancel {reason} to confirm the stop. Check and review evidence
@@ -10478,7 +10645,7 @@ function reconcileResume(view, observations = []) {
10478
10645
  return candidate;
10479
10646
  const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
10480
10647
  const blockers = [...view.task.progress.blockers];
10481
- if (view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
10648
+ if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
10482
10649
  blockers.push({
10483
10650
  reason: "worker state requires reconciliation",
10484
10651
  dependentAction: "resume",
@@ -10623,7 +10790,11 @@ var externalActionSchema = discriminatedUnion("operation", [
10623
10790
  }).strict(),
10624
10791
  object({
10625
10792
  operation: literal("git.commit"),
10626
- payload: object({ message: string2().min(1) }).strict()
10793
+ payload: object({
10794
+ message: string2().min(1).optional(),
10795
+ plan_steps: array(string2().min(1)).optional(),
10796
+ plan_branch: string2().min(1).optional()
10797
+ }).strict()
10627
10798
  }).strict(),
10628
10799
  object({
10629
10800
  operation: literal("git.push"),
@@ -10858,7 +11029,7 @@ function resolveRequirements(input) {
10858
11029
  ].join(", ")}`,
10859
11030
  satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
10860
11031
  before: "dependent_action",
10861
- dependentAction: "dependent-action",
11032
+ dependentAction: null,
10862
11033
  acceptanceAllowed: false
10863
11034
  }));
10864
11035
  if (signals.productChoiceOpen.value === true)
@@ -10869,7 +11040,7 @@ function resolveRequirements(input) {
10869
11040
  reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
10870
11041
  satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
10871
11042
  before: "dependent_action",
10872
- dependentAction: "dependent-action",
11043
+ dependentAction: null,
10873
11044
  acceptanceAllowed: false
10874
11045
  }));
10875
11046
  if (signals.behaviorChange.value === true)
@@ -11027,59 +11198,6 @@ function diffPolicy(previous, next, reason, now) {
11027
11198
  return parsed.data;
11028
11199
  }
11029
11200
 
11030
- // packages/workit-core/src/core/workers.ts
11031
- var same = (left, right) => {
11032
- try {
11033
- return canonicalJson(left) === canonicalJson(right);
11034
- } catch {
11035
- return false;
11036
- }
11037
- };
11038
- var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
11039
- var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
11040
- const code = char.charCodeAt(0);
11041
- return code < 32 || code === 127;
11042
- }) && !value.split(/[\\/]/).includes("..");
11043
- var callerValue = (value) => ({ host: value.host, actor: value.actor });
11044
- var workerIdOf = (value) => value.workerId ?? null;
11045
- function assertProductWriteAllowed(input) {
11046
- if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
11047
- return failure("invalid_input", "invalid product write path");
11048
- if (!input.store)
11049
- return failure("permission_denied", "write authorization requires core state");
11050
- const task = input.store.readTask(input.task.id);
11051
- if (!task.ok)
11052
- return task;
11053
- const workspace = input.store.readWorkspace();
11054
- if (!workspace.ok)
11055
- return workspace;
11056
- if (!workspace.data)
11057
- return failure("not_found", "workspace not found");
11058
- const workspaceRecord = workspace.data;
11059
- if (task.data.status !== "active")
11060
- return failure("invalid_transition", "paused or closed tasks cannot own product writes");
11061
- if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
11062
- return failure("recovery_required", "task and workspace bindings are invalid");
11063
- const writer = workspaceRecord.writer;
11064
- if (!writer)
11065
- return failure("permission_denied", "checkout has no writer owner");
11066
- if (writer.state === "uncertain")
11067
- return failure("recovery_required", "checkout writer requires recovery", {
11068
- owner: writer.owner
11069
- });
11070
- const caller = callerValue(input.caller);
11071
- const workerId = workerIdOf(input.caller);
11072
- const owner = writer.owner;
11073
- if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
11074
- return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
11075
- const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
11076
- if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
11077
- return failure("permission_denied", "only implementers can own product writes");
11078
- if (workerId !== null && (!worker?.data.session || worker.data.state === "cancelling" || worker.data.state === "unknown" || worker.data.state === "stopped" || !same(worker.data.session, callerSession(input.caller))))
11079
- return failure("recovery_required", "worker session is not an active writer");
11080
- return success(null, workspaceRecord.revision, owner);
11081
- }
11082
-
11083
11201
  // packages/workit-core/src/core/task-engine.ts
11084
11202
  var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
11085
11203
  kind,
@@ -11210,15 +11328,20 @@ var applyWorkerLifecycle = (input) => {
11210
11328
  return failure("invalid_transition", "worker has not started");
11211
11329
  if (input.state === "running" && entry.data.state === "stopped")
11212
11330
  return failure("invalid_transition", "stopped worker cannot run again");
11331
+ const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
11213
11332
  const nextEntry = {
11214
11333
  ...entry,
11215
11334
  recordedAt: input.now ?? entry.recordedAt,
11216
11335
  provenance: authority.provenance,
11217
- data: { ...entry.data, state: input.state, session: input.session }
11336
+ data: {
11337
+ ...entry.data,
11338
+ state: terminalCancel ? "cancelling" : input.state,
11339
+ session: input.session
11340
+ }
11218
11341
  };
11219
11342
  const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
11220
11343
  const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
11221
- if (entry.data.state === input.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
11344
+ if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
11222
11345
  return success(task.data.revision, workspace.data.revision, entry);
11223
11346
  const changed = input.store.mutateTaskAndWorkspace({
11224
11347
  taskId: task.data.id,
@@ -11588,7 +11711,7 @@ class WorkitCore {
11588
11711
  activeWorkerBlocker(task, workspace) {
11589
11712
  if (workspace.writer)
11590
11713
  return failure("recovery_required", "writer ownership must be released first");
11591
- if (task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
11714
+ if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
11592
11715
  return failure("recovery_required", "worker state requires reconciliation");
11593
11716
  return null;
11594
11717
  }
@@ -11825,11 +11948,6 @@ class WorkitCore {
11825
11948
  if (helper && !helper.ok)
11826
11949
  return helper;
11827
11950
  const evidence = input.evidence;
11828
- if (helper?.ok) {
11829
- const assignment = helper.data.data.assignment;
11830
- if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
11831
- return failure("permission_denied", "evidence is outside the worker assignment");
11832
- }
11833
11951
  if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
11834
11952
  return failure("invalid_input", "missing or skipped evidence requires a reason");
11835
11953
  if (evidence.kind === "review") {
@@ -11846,6 +11964,11 @@ class WorkitCore {
11846
11964
  evidence.beforeCandidateId ??= null;
11847
11965
  evidence.candidateId ??= null;
11848
11966
  }
11967
+ if (helper?.ok) {
11968
+ const assignment = helper.data.data.assignment;
11969
+ if (evidence.requirementIds.some((id) => !assignment.requirementIds.includes(id)) || assignment.candidateId !== null && evidence.candidateId !== assignment.candidateId && evidence.beforeCandidateId !== assignment.candidateId || !refsWithinScope(evidence.refs, assignment.scope) || !refsWithinScope(evidence.refs, task.data.intent.data.scope))
11970
+ return failure("permission_denied", "evidence is outside the worker assignment");
11971
+ }
11849
11972
  const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
11850
11973
  for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
11851
11974
  if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
@@ -12741,6 +12864,9 @@ class WorkitCore {
12741
12864
  id: task.id,
12742
12865
  revision: task.revision,
12743
12866
  workspaceRevision: workspace.data.revision,
12867
+ createdAt: task.createdAt,
12868
+ updatedAt: task.updatedAt,
12869
+ runtime: task.runtime ?? null,
12744
12870
  objective: task.intent.data.objective,
12745
12871
  status: task.status,
12746
12872
  closure: task.closure,
@@ -12826,7 +12952,7 @@ class WorkitCore {
12826
12952
  const base = reconcileResume(effectiveView);
12827
12953
  if (!base.ok)
12828
12954
  return base;
12829
- const needsReconciliation = view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
12955
+ const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
12830
12956
  const blockers = [...base.data.blockers];
12831
12957
  if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
12832
12958
  blockers.push({
@@ -12893,7 +13019,8 @@ class WorkitCore {
12893
13019
  ...current,
12894
13020
  status,
12895
13021
  candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
12896
- progress: status === "paused" ? { ...current.progress, summary: input.reason } : current.progress
13022
+ progress: current.progress,
13023
+ pauseReason: status === "paused" ? input.reason ?? null : null
12897
13024
  })
12898
13025
  });
12899
13026
  if (!changed.ok)
@@ -12929,7 +13056,7 @@ class WorkitCore {
12929
13056
  return workspace;
12930
13057
  if (!workspace.data)
12931
13058
  return failure("not_found", "workspace not found");
12932
- if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state !== "stopped"))
13059
+ if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
12933
13060
  return failure("recovery_required", "scope revision requires worker ownership reconciliation");
12934
13061
  this.fillRevisions(input, task.data, workspace.data);
12935
13062
  const changed = this.store.mutateTaskAndWorkspace({
@@ -13040,6 +13167,13 @@ var codexCapabilities = (host, availability = {}) => {
13040
13167
  reason: "file writes are host-policy; PreToolUse allows write tools",
13041
13168
  refs: [ref(host, "PreToolUse")]
13042
13169
  },
13170
+ {
13171
+ name: "fresh-context-review",
13172
+ surface: "SubagentStart",
13173
+ assurance: has("subagentStart") ? "agent_guided" : "unavailable",
13174
+ reason: has("subagentStart") ? "independent review runs as a bounded subagent; evidence evaluation enforces reviewer exclusivity, and stops remain untrusted" : "Codex subagent lifecycle hooks are unavailable for independent review",
13175
+ refs: [ref(host, "SubagentStart")]
13176
+ },
13043
13177
  {
13044
13178
  name: "native_subagents",
13045
13179
  surface: "SubagentStart/SubagentStop",
@@ -13143,7 +13277,7 @@ var parseCodexHookInput = (value) => {
13143
13277
  return { ok: false, error: "permission_mode is invalid" };
13144
13278
  if (!(value.transcript_path === null || nonEmpty2(value.transcript_path)))
13145
13279
  return { ok: false, error: "transcript_path must be a string or null" };
13146
- if (!nonEmpty2(value.cwd) || !path6.isAbsolute(value.cwd) || !existsSync3(value.cwd))
13280
+ if (!nonEmpty2(value.cwd) || !path7.isAbsolute(value.cwd) || !existsSync4(value.cwd))
13147
13281
  return { ok: false, error: "cwd must be an existing absolute path" };
13148
13282
  try {
13149
13283
  if (!statSync(value.cwd).isDirectory())