@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.
- package/.codex-plugin/plugin.json +1 -1
- package/dist/launch-mcp.js +354 -216
- package/dist/workit-hook.js +279 -145
- package/package.json +3 -3
- package/skills/workit-plan/SKILL.md +14 -2
package/dist/workit-hook.js
CHANGED
|
@@ -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
|
|
7
|
-
import
|
|
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
|
|
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
|
|
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 =
|
|
8171
|
-
const lexicalRelative =
|
|
8172
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
8173
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
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(
|
|
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
|
|
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 =
|
|
8415
|
-
fs3.mkdirSync(
|
|
8440
|
+
const resolved = path3.resolve(targetPath);
|
|
8441
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
8416
8442
|
try {
|
|
8417
|
-
return
|
|
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 =
|
|
8449
|
+
const resolved = path3.resolve(lockPath);
|
|
8424
8450
|
if (!lockRoot)
|
|
8425
8451
|
return resolved;
|
|
8426
8452
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
8427
|
-
const parent =
|
|
8453
|
+
const parent = path3.dirname(resolved);
|
|
8428
8454
|
const parentReal = canonicalLockParentSync(parent);
|
|
8429
|
-
const parentRelative =
|
|
8430
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
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
|
|
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) :
|
|
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(
|
|
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(
|
|
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 ? {
|
|
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 ? {
|
|
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:
|
|
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(
|
|
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 =
|
|
9297
|
-
const id = target === "task" ?
|
|
9298
|
-
const destination =
|
|
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(
|
|
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
|
-
|
|
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
|
|
9514
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
9435
9515
|
}
|
|
9436
9516
|
get workitDir() {
|
|
9437
|
-
return
|
|
9517
|
+
return path4.join(this.root, ".workit");
|
|
9438
9518
|
}
|
|
9439
9519
|
get tasksDir() {
|
|
9440
|
-
return
|
|
9520
|
+
return path4.join(this.workitDir, "tasks");
|
|
9441
9521
|
}
|
|
9442
9522
|
get recoveryDir() {
|
|
9443
|
-
return
|
|
9523
|
+
return path4.join(this.workitDir, "recovery");
|
|
9444
9524
|
}
|
|
9445
9525
|
get workspacePath() {
|
|
9446
|
-
return
|
|
9526
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
9447
9527
|
}
|
|
9448
9528
|
get lockPath() {
|
|
9449
|
-
return
|
|
9529
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
9450
9530
|
}
|
|
9451
9531
|
get gitignorePath() {
|
|
9452
|
-
return
|
|
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
|
|
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
|
|
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 =
|
|
9592
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
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 =
|
|
9734
|
+
const relative = path5.relative(root, target);
|
|
9596
9735
|
let current = root;
|
|
9597
|
-
for (const segment of relative.split(
|
|
9736
|
+
for (const segment of relative.split(path5.sep)) {
|
|
9598
9737
|
if (!segment)
|
|
9599
9738
|
continue;
|
|
9600
|
-
current =
|
|
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}${
|
|
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}${
|
|
9952
|
-
var relative = (root, candidate) =>
|
|
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 =
|
|
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(
|
|
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(
|
|
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 =
|
|
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 =
|
|
10192
|
+
const target = path6.resolve(root, item);
|
|
10047
10193
|
if (!inside(root, target))
|
|
10048
|
-
return failure("invalid_input",
|
|
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 =
|
|
10197
|
+
ancestor = path6.dirname(ancestor);
|
|
10052
10198
|
try {
|
|
10053
10199
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10054
|
-
return failure("invalid_input",
|
|
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",
|
|
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 =
|
|
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.
|
|
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) =>
|
|
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({
|
|
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:
|
|
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:
|
|
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: {
|
|
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 ===
|
|
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) =>
|
|
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) =>
|
|
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:
|
|
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
|
|
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) || !
|
|
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())
|