@brainervirus/workit-codex 1.0.9 → 1.0.11
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 +366 -222
- package/dist/workit-hook.js +309 -152
- 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,10 +7573,12 @@ var decisionSchema = object({
|
|
|
7573
7573
|
scope: scopeSchema,
|
|
7574
7574
|
presented: text,
|
|
7575
7575
|
approvedContent: text,
|
|
7576
|
-
|
|
7576
|
+
displayed: text.optional(),
|
|
7577
|
+
contentRefs: array(refSchema),
|
|
7578
|
+
statedChoice: object({ ref: text, text }).strict().optional()
|
|
7577
7579
|
}).strict(),
|
|
7578
7580
|
digest,
|
|
7579
|
-
response: _enum(["approved", "rejected"]),
|
|
7581
|
+
response: _enum(["approved", "rejected", "stated"]),
|
|
7580
7582
|
requirementIds: array(digest),
|
|
7581
7583
|
revoked: object({ at: utc, reason: text }).strict().nullable(),
|
|
7582
7584
|
consumption: object({
|
|
@@ -7651,6 +7653,10 @@ var actionProgressListSchema = array(actionProgressSchema).check((ctx) => {
|
|
|
7651
7653
|
path: ["decisionId"]
|
|
7652
7654
|
});
|
|
7653
7655
|
});
|
|
7656
|
+
var runtimeSchema = object({
|
|
7657
|
+
createdWith: text.nullable(),
|
|
7658
|
+
updatedWith: text
|
|
7659
|
+
}).strict();
|
|
7654
7660
|
var taskRecordSchema = object({
|
|
7655
7661
|
schemaVersion: literal(1),
|
|
7656
7662
|
id,
|
|
@@ -7664,6 +7670,8 @@ var taskRecordSchema = object({
|
|
|
7664
7670
|
status: _enum(["active", "paused", "closed"]),
|
|
7665
7671
|
closure: closureSchema.nullable(),
|
|
7666
7672
|
progress: progressSchema,
|
|
7673
|
+
pauseReason: text.nullable().optional(),
|
|
7674
|
+
runtime: runtimeSchema.optional(),
|
|
7667
7675
|
assessments: array(entrySchema(assessmentSchema)),
|
|
7668
7676
|
policy: policySchema.nullable(),
|
|
7669
7677
|
policyChanges: array(policyChangeSchema),
|
|
@@ -7679,6 +7687,7 @@ var workspaceRecordSchema = object({
|
|
|
7679
7687
|
id,
|
|
7680
7688
|
revision,
|
|
7681
7689
|
root: nonEmpty,
|
|
7690
|
+
runtime: runtimeSchema.optional(),
|
|
7682
7691
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
7683
7692
|
}).strict();
|
|
7684
7693
|
var capabilitySchema = object({
|
|
@@ -7706,6 +7715,9 @@ var taskSummarySchema = object({
|
|
|
7706
7715
|
id,
|
|
7707
7716
|
revision,
|
|
7708
7717
|
workspaceRevision: revision,
|
|
7718
|
+
createdAt: utc,
|
|
7719
|
+
updatedAt: utc,
|
|
7720
|
+
runtime: runtimeSchema.nullable(),
|
|
7709
7721
|
objective: text,
|
|
7710
7722
|
status: _enum(["active", "paused", "closed"]),
|
|
7711
7723
|
closure: closureSchema.nullable(),
|
|
@@ -7776,7 +7788,7 @@ var taskOperations = {
|
|
|
7776
7788
|
expectedWorkspaceRevision: revision.optional(),
|
|
7777
7789
|
outcome: outcomeSchema,
|
|
7778
7790
|
summary: text,
|
|
7779
|
-
decisionIds: array(id)
|
|
7791
|
+
decisionIds: array(id).optional()
|
|
7780
7792
|
})
|
|
7781
7793
|
};
|
|
7782
7794
|
var policyOperations = {
|
|
@@ -7868,14 +7880,15 @@ var writerOperations = {
|
|
|
7868
7880
|
...taskId,
|
|
7869
7881
|
expectedRevision: revision.optional(),
|
|
7870
7882
|
expectedWorkspaceRevision: revision.optional(),
|
|
7871
|
-
workerId: nullableId.optional()
|
|
7883
|
+
workerId: nullableId.optional(),
|
|
7884
|
+
reason: text.optional()
|
|
7872
7885
|
}),
|
|
7873
7886
|
release: operation({
|
|
7874
7887
|
action: literal("release"),
|
|
7875
7888
|
...taskId,
|
|
7876
7889
|
expectedRevision: revision.optional(),
|
|
7877
7890
|
expectedWorkspaceRevision: revision.optional(),
|
|
7878
|
-
reason: text
|
|
7891
|
+
reason: text.optional()
|
|
7879
7892
|
})
|
|
7880
7893
|
};
|
|
7881
7894
|
var stateOperations = {
|
|
@@ -8026,7 +8039,21 @@ function requirementId(input) {
|
|
|
8026
8039
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
8027
8040
|
import * as fs4 from "node:fs";
|
|
8028
8041
|
import { hostname } from "node:os";
|
|
8029
|
-
import
|
|
8042
|
+
import path4 from "node:path";
|
|
8043
|
+
|
|
8044
|
+
// packages/workit-core/src/core/package-root.ts
|
|
8045
|
+
import { existsSync } from "node:fs";
|
|
8046
|
+
import path from "node:path";
|
|
8047
|
+
import { fileURLToPath } from "node:url";
|
|
8048
|
+
var packageRoot = () => {
|
|
8049
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
8050
|
+
while (true) {
|
|
8051
|
+
const parent = path.dirname(dir);
|
|
8052
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
8053
|
+
return dir;
|
|
8054
|
+
dir = parent;
|
|
8055
|
+
}
|
|
8056
|
+
};
|
|
8030
8057
|
|
|
8031
8058
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
8032
8059
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -8077,7 +8104,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
8077
8104
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
8078
8105
|
import { randomBytes } from "node:crypto";
|
|
8079
8106
|
import fsSync from "node:fs";
|
|
8080
|
-
import
|
|
8107
|
+
import path2 from "node:path";
|
|
8081
8108
|
|
|
8082
8109
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
8083
8110
|
import fs from "node:fs";
|
|
@@ -8167,13 +8194,13 @@ ${ownershipToken}
|
|
|
8167
8194
|
return { raw, ownershipToken };
|
|
8168
8195
|
}
|
|
8169
8196
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
8170
|
-
const resolved =
|
|
8171
|
-
const lexicalRelative =
|
|
8172
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
8173
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
8197
|
+
const resolved = path2.resolve(lockPath);
|
|
8198
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
8199
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
8200
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
8174
8201
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
8175
8202
|
}
|
|
8176
|
-
return relative.split(
|
|
8203
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
8177
8204
|
}
|
|
8178
8205
|
function parseSidecarLockPayload(raw, parser) {
|
|
8179
8206
|
if (parser) {
|
|
@@ -8320,7 +8347,7 @@ function getFsSafeLockConfig() {
|
|
|
8320
8347
|
|
|
8321
8348
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
8322
8349
|
import fs3 from "node:fs";
|
|
8323
|
-
import
|
|
8350
|
+
import path3 from "node:path";
|
|
8324
8351
|
|
|
8325
8352
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
8326
8353
|
function sleepSync(ms) {
|
|
@@ -8411,26 +8438,26 @@ function canonicalLockParentSync(parent) {
|
|
|
8411
8438
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
8412
8439
|
}
|
|
8413
8440
|
function normalizeTargetPath(targetPath) {
|
|
8414
|
-
const resolved =
|
|
8415
|
-
fs3.mkdirSync(
|
|
8441
|
+
const resolved = path3.resolve(targetPath);
|
|
8442
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
8416
8443
|
try {
|
|
8417
|
-
return
|
|
8444
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
8418
8445
|
} catch {
|
|
8419
8446
|
return resolved;
|
|
8420
8447
|
}
|
|
8421
8448
|
}
|
|
8422
8449
|
function boundedLockPath(lockPath, lockRoot) {
|
|
8423
|
-
const resolved =
|
|
8450
|
+
const resolved = path3.resolve(lockPath);
|
|
8424
8451
|
if (!lockRoot)
|
|
8425
8452
|
return resolved;
|
|
8426
8453
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
8427
|
-
const parent =
|
|
8454
|
+
const parent = path3.dirname(resolved);
|
|
8428
8455
|
const parentReal = canonicalLockParentSync(parent);
|
|
8429
|
-
const parentRelative =
|
|
8430
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
8456
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
8457
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
8431
8458
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
8432
8459
|
}
|
|
8433
|
-
return
|
|
8460
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
8434
8461
|
}
|
|
8435
8462
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
8436
8463
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -8643,6 +8670,33 @@ var metadataLockSchema = object({
|
|
|
8643
8670
|
nonce: string2().min(1)
|
|
8644
8671
|
}).strict();
|
|
8645
8672
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
8673
|
+
var cachedRuntimeVersion = null;
|
|
8674
|
+
var runtimeVersion = () => {
|
|
8675
|
+
if (cachedRuntimeVersion === null) {
|
|
8676
|
+
try {
|
|
8677
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
8678
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
8679
|
+
} catch {
|
|
8680
|
+
cachedRuntimeVersion = "0.0.0";
|
|
8681
|
+
}
|
|
8682
|
+
}
|
|
8683
|
+
return cachedRuntimeVersion;
|
|
8684
|
+
};
|
|
8685
|
+
var stampNew = (record) => ({
|
|
8686
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
8687
|
+
updatedWith: runtimeVersion()
|
|
8688
|
+
});
|
|
8689
|
+
var isNewerVersion = (candidate, current) => {
|
|
8690
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
8691
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
8692
|
+
for (let index = 0;index < 3; index += 1) {
|
|
8693
|
+
const left = next[index] ?? 0;
|
|
8694
|
+
const right = now[index] ?? 0;
|
|
8695
|
+
if (left !== right)
|
|
8696
|
+
return left > right;
|
|
8697
|
+
}
|
|
8698
|
+
return false;
|
|
8699
|
+
};
|
|
8646
8700
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
8647
8701
|
`;
|
|
8648
8702
|
var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
@@ -8669,12 +8723,12 @@ var sameMetadataLock = (left, right) => {
|
|
|
8669
8723
|
class TaskStore {
|
|
8670
8724
|
root;
|
|
8671
8725
|
constructor(root) {
|
|
8672
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
8726
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
8673
8727
|
}
|
|
8674
8728
|
readTask(taskId) {
|
|
8675
8729
|
if (!validId(taskId))
|
|
8676
8730
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
8677
|
-
const result = this.readRecord(
|
|
8731
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
8678
8732
|
if (!result.exists)
|
|
8679
8733
|
return failure("not_found", "task not found", { taskId });
|
|
8680
8734
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -8708,7 +8762,7 @@ class TaskStore {
|
|
|
8708
8762
|
});
|
|
8709
8763
|
const tasks = [];
|
|
8710
8764
|
for (const name of names.sort()) {
|
|
8711
|
-
const item = this.readRecord(
|
|
8765
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
8712
8766
|
if (!item.exists)
|
|
8713
8767
|
continue;
|
|
8714
8768
|
if (!item.result.ok)
|
|
@@ -8742,7 +8796,7 @@ class TaskStore {
|
|
|
8742
8796
|
if (!current.ok)
|
|
8743
8797
|
return current;
|
|
8744
8798
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
8745
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
8799
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
8746
8800
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
8747
8801
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
8748
8802
|
});
|
|
@@ -8754,11 +8808,17 @@ class TaskStore {
|
|
|
8754
8808
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
8755
8809
|
if (current.data && !previousWorkspaceBytes)
|
|
8756
8810
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
8757
|
-
const workspace = current.data ? {
|
|
8811
|
+
const workspace = current.data ? {
|
|
8812
|
+
...current.data,
|
|
8813
|
+
revision: newRevision(),
|
|
8814
|
+
root: this.root,
|
|
8815
|
+
runtime: stampNew(current.data)
|
|
8816
|
+
} : {
|
|
8758
8817
|
schemaVersion: SCHEMA_VERSION,
|
|
8759
8818
|
id: newId(),
|
|
8760
8819
|
revision: newRevision(),
|
|
8761
8820
|
root: this.root,
|
|
8821
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8762
8822
|
writer: null
|
|
8763
8823
|
};
|
|
8764
8824
|
const timestamp = value.now ?? now();
|
|
@@ -8780,6 +8840,7 @@ class TaskStore {
|
|
|
8780
8840
|
status: "active",
|
|
8781
8841
|
closure: null,
|
|
8782
8842
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
8843
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8783
8844
|
assessments: [],
|
|
8784
8845
|
policy: null,
|
|
8785
8846
|
policyChanges: [],
|
|
@@ -8810,7 +8871,7 @@ class TaskStore {
|
|
|
8810
8871
|
if (!current.ok)
|
|
8811
8872
|
return current;
|
|
8812
8873
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
8813
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
8874
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
8814
8875
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
8815
8876
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
8816
8877
|
});
|
|
@@ -8818,11 +8879,17 @@ class TaskStore {
|
|
|
8818
8879
|
if (current.data && !previousWorkspaceBytes)
|
|
8819
8880
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
8820
8881
|
const timestamp = input.now ?? now();
|
|
8821
|
-
const workspace = current.data ? {
|
|
8882
|
+
const workspace = current.data ? {
|
|
8883
|
+
...current.data,
|
|
8884
|
+
revision: newRevision(),
|
|
8885
|
+
root: this.root,
|
|
8886
|
+
runtime: stampNew(current.data)
|
|
8887
|
+
} : {
|
|
8822
8888
|
schemaVersion: SCHEMA_VERSION,
|
|
8823
8889
|
id: input.workspaceId ?? newId(),
|
|
8824
8890
|
revision: newRevision(),
|
|
8825
8891
|
root: this.root,
|
|
8892
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8826
8893
|
writer: null
|
|
8827
8894
|
};
|
|
8828
8895
|
const task = {
|
|
@@ -8830,7 +8897,11 @@ class TaskStore {
|
|
|
8830
8897
|
workspaceId: workspace.id,
|
|
8831
8898
|
revision: newRevision(),
|
|
8832
8899
|
createdAt: timestamp,
|
|
8833
|
-
updatedAt: timestamp
|
|
8900
|
+
updatedAt: timestamp,
|
|
8901
|
+
runtime: {
|
|
8902
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
8903
|
+
updatedWith: runtimeVersion()
|
|
8904
|
+
}
|
|
8834
8905
|
};
|
|
8835
8906
|
const validTask = taskRecordSchema.safeParse(task);
|
|
8836
8907
|
if (!validTask.success)
|
|
@@ -8879,7 +8950,8 @@ class TaskStore {
|
|
|
8879
8950
|
workspaceId: current.data.workspaceId,
|
|
8880
8951
|
createdAt: current.data.createdAt,
|
|
8881
8952
|
revision: context.revision,
|
|
8882
|
-
updatedAt: context.now
|
|
8953
|
+
updatedAt: context.now,
|
|
8954
|
+
runtime: stampNew(current.data)
|
|
8883
8955
|
};
|
|
8884
8956
|
const valid = taskRecordSchema.safeParse(record);
|
|
8885
8957
|
if (!valid.success)
|
|
@@ -8913,7 +8985,8 @@ class TaskStore {
|
|
|
8913
8985
|
...changed.data,
|
|
8914
8986
|
id: current.data.id,
|
|
8915
8987
|
root: this.root,
|
|
8916
|
-
revision: context.revision
|
|
8988
|
+
revision: context.revision,
|
|
8989
|
+
runtime: stampNew(current.data)
|
|
8917
8990
|
};
|
|
8918
8991
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
8919
8992
|
if (!valid.success)
|
|
@@ -8956,7 +9029,8 @@ class TaskStore {
|
|
|
8956
9029
|
...changedWorkspace.data,
|
|
8957
9030
|
id: workspace.data.id,
|
|
8958
9031
|
root: this.root,
|
|
8959
|
-
revision: workspaceContext.revision
|
|
9032
|
+
revision: workspaceContext.revision,
|
|
9033
|
+
runtime: stampNew(workspace.data)
|
|
8960
9034
|
});
|
|
8961
9035
|
if (!nextWorkspace.success)
|
|
8962
9036
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -8981,7 +9055,8 @@ class TaskStore {
|
|
|
8981
9055
|
workspaceId: task.data.workspaceId,
|
|
8982
9056
|
createdAt: task.data.createdAt,
|
|
8983
9057
|
revision: taskContext.revision,
|
|
8984
|
-
updatedAt: taskContext.now
|
|
9058
|
+
updatedAt: taskContext.now,
|
|
9059
|
+
runtime: stampNew(task.data)
|
|
8985
9060
|
});
|
|
8986
9061
|
if (!nextTask.success) {
|
|
8987
9062
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -9008,7 +9083,7 @@ class TaskStore {
|
|
|
9008
9083
|
if (match)
|
|
9009
9084
|
candidates.push({
|
|
9010
9085
|
target: match[1],
|
|
9011
|
-
path:
|
|
9086
|
+
path: path4.join(this.recoveryDir, name),
|
|
9012
9087
|
digest: match[3]
|
|
9013
9088
|
});
|
|
9014
9089
|
}
|
|
@@ -9201,7 +9276,8 @@ class TaskStore {
|
|
|
9201
9276
|
const value = {
|
|
9202
9277
|
...workspace,
|
|
9203
9278
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
9204
|
-
revision: newRevision()
|
|
9279
|
+
revision: newRevision(),
|
|
9280
|
+
runtime: stampNew(workspace)
|
|
9205
9281
|
};
|
|
9206
9282
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
9207
9283
|
}
|
|
@@ -9279,7 +9355,7 @@ class TaskStore {
|
|
|
9279
9355
|
}
|
|
9280
9356
|
fs4.renameSync(temporary, file);
|
|
9281
9357
|
temporary = undefined;
|
|
9282
|
-
this.fsyncDirectory(
|
|
9358
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
9283
9359
|
return success(null, null, value);
|
|
9284
9360
|
} catch (error) {
|
|
9285
9361
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -9293,9 +9369,9 @@ class TaskStore {
|
|
|
9293
9369
|
}
|
|
9294
9370
|
}
|
|
9295
9371
|
saveRecovery(file, bytes) {
|
|
9296
|
-
const target =
|
|
9297
|
-
const id = target === "task" ?
|
|
9298
|
-
const destination =
|
|
9372
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
9373
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
9374
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
9299
9375
|
if (fs4.existsSync(destination)) {
|
|
9300
9376
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
9301
9377
|
return;
|
|
@@ -9373,7 +9449,7 @@ class TaskStore {
|
|
|
9373
9449
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
9374
9450
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
9375
9451
|
return null;
|
|
9376
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
9452
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
9377
9453
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
9378
9454
|
return null;
|
|
9379
9455
|
ids.add(parsed.data.workspaceId);
|
|
@@ -9415,7 +9491,12 @@ class TaskStore {
|
|
|
9415
9491
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
9416
9492
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
9417
9493
|
const parsed = schema.safeParse(value);
|
|
9418
|
-
|
|
9494
|
+
if (parsed.success)
|
|
9495
|
+
return success(null, null, parsed.data);
|
|
9496
|
+
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
9497
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
9498
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
9499
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
9419
9500
|
}
|
|
9420
9501
|
processStart(pid) {
|
|
9421
9502
|
try {
|
|
@@ -9425,43 +9506,98 @@ class TaskStore {
|
|
|
9425
9506
|
}
|
|
9426
9507
|
}
|
|
9427
9508
|
conflict(expected, actual) {
|
|
9428
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
9509
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
9429
9510
|
expectedRevision: expected,
|
|
9430
9511
|
actualRevision: actual
|
|
9431
9512
|
});
|
|
9432
9513
|
}
|
|
9433
9514
|
taskPath(taskId) {
|
|
9434
|
-
return
|
|
9515
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
9435
9516
|
}
|
|
9436
9517
|
get workitDir() {
|
|
9437
|
-
return
|
|
9518
|
+
return path4.join(this.root, ".workit");
|
|
9438
9519
|
}
|
|
9439
9520
|
get tasksDir() {
|
|
9440
|
-
return
|
|
9521
|
+
return path4.join(this.workitDir, "tasks");
|
|
9441
9522
|
}
|
|
9442
9523
|
get recoveryDir() {
|
|
9443
|
-
return
|
|
9524
|
+
return path4.join(this.workitDir, "recovery");
|
|
9444
9525
|
}
|
|
9445
9526
|
get workspacePath() {
|
|
9446
|
-
return
|
|
9527
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
9447
9528
|
}
|
|
9448
9529
|
get lockPath() {
|
|
9449
|
-
return
|
|
9530
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
9450
9531
|
}
|
|
9451
9532
|
get gitignorePath() {
|
|
9452
|
-
return
|
|
9533
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
9453
9534
|
}
|
|
9454
9535
|
}
|
|
9536
|
+
// packages/workit-core/src/core/workers.ts
|
|
9537
|
+
var same = (left, right) => {
|
|
9538
|
+
try {
|
|
9539
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
9540
|
+
} catch {
|
|
9541
|
+
return false;
|
|
9542
|
+
}
|
|
9543
|
+
};
|
|
9544
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
9545
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
9546
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
9547
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
9548
|
+
const code = char.charCodeAt(0);
|
|
9549
|
+
return code < 32 || code === 127;
|
|
9550
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
9551
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
9552
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
9553
|
+
function assertProductWriteAllowed(input) {
|
|
9554
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
9555
|
+
return failure("invalid_input", "invalid product write path");
|
|
9556
|
+
if (!input.store)
|
|
9557
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
9558
|
+
const task = input.store.readTask(input.task.id);
|
|
9559
|
+
if (!task.ok)
|
|
9560
|
+
return task;
|
|
9561
|
+
const workspace = input.store.readWorkspace();
|
|
9562
|
+
if (!workspace.ok)
|
|
9563
|
+
return workspace;
|
|
9564
|
+
if (!workspace.data)
|
|
9565
|
+
return failure("not_found", "workspace not found");
|
|
9566
|
+
const workspaceRecord = workspace.data;
|
|
9567
|
+
if (task.data.status !== "active")
|
|
9568
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
9569
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
9570
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
9571
|
+
const writer = workspaceRecord.writer;
|
|
9572
|
+
if (!writer)
|
|
9573
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
9574
|
+
if (writer.state === "uncertain")
|
|
9575
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
9576
|
+
owner: writer.owner
|
|
9577
|
+
});
|
|
9578
|
+
const caller = callerValue(input.caller);
|
|
9579
|
+
const workerId = workerIdOf(input.caller);
|
|
9580
|
+
const owner = writer.owner;
|
|
9581
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
9582
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
9583
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
9584
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
9585
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
9586
|
+
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))))
|
|
9587
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
9588
|
+
return success(null, workspaceRecord.revision, owner);
|
|
9589
|
+
}
|
|
9590
|
+
|
|
9455
9591
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
9456
9592
|
import { createHash as createHash4 } from "node:crypto";
|
|
9457
9593
|
import * as fs6 from "node:fs";
|
|
9458
9594
|
import { spawnSync } from "node:child_process";
|
|
9459
|
-
import
|
|
9595
|
+
import path6 from "node:path";
|
|
9460
9596
|
|
|
9461
9597
|
// packages/workit-core/src/core/authority.ts
|
|
9462
9598
|
import { createHash as createHash3 } from "node:crypto";
|
|
9463
9599
|
import * as fs5 from "node:fs";
|
|
9464
|
-
import
|
|
9600
|
+
import path5 from "node:path";
|
|
9465
9601
|
var verifiedAuthorities = new WeakMap;
|
|
9466
9602
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
9467
9603
|
const token = {};
|
|
@@ -9588,20 +9724,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
9588
9724
|
continue;
|
|
9589
9725
|
if (!reference.digest)
|
|
9590
9726
|
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"
|
|
9727
|
+
const target = path5.resolve(root, reference.path);
|
|
9728
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
9729
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
9730
|
+
fields: [
|
|
9731
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
9732
|
+
]
|
|
9733
|
+
});
|
|
9594
9734
|
try {
|
|
9595
|
-
const relative =
|
|
9735
|
+
const relative = path5.relative(root, target);
|
|
9596
9736
|
let current = root;
|
|
9597
|
-
for (const segment of relative.split(
|
|
9737
|
+
for (const segment of relative.split(path5.sep)) {
|
|
9598
9738
|
if (!segment)
|
|
9599
9739
|
continue;
|
|
9600
|
-
current =
|
|
9740
|
+
current = path5.join(current, segment);
|
|
9601
9741
|
const stat = fs5.lstatSync(current);
|
|
9602
9742
|
const real = fs5.realpathSync(current);
|
|
9603
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
9604
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
9743
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
9744
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
9745
|
+
fields: [
|
|
9746
|
+
{
|
|
9747
|
+
path: "contentRefs.path",
|
|
9748
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
9749
|
+
}
|
|
9750
|
+
]
|
|
9751
|
+
});
|
|
9605
9752
|
if (stat.isSymbolicLink())
|
|
9606
9753
|
return failure("invalid_input", "document reference uses a symlink");
|
|
9607
9754
|
if (current === target && !stat.isFile())
|
|
@@ -9948,14 +10095,14 @@ var verifyDecisionContent = verifyContentRefs;
|
|
|
9948
10095
|
|
|
9949
10096
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
9950
10097
|
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
9951
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
9952
|
-
var relative = (root, candidate) =>
|
|
10098
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
|
|
10099
|
+
var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
|
|
9953
10100
|
var canonicalPath = (value) => {
|
|
9954
10101
|
if (typeof value !== "string" || !value)
|
|
9955
10102
|
return null;
|
|
9956
10103
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
9957
10104
|
return null;
|
|
9958
|
-
const normalized =
|
|
10105
|
+
const normalized = path6.posix.normalize(value);
|
|
9959
10106
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
9960
10107
|
return null;
|
|
9961
10108
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -10003,14 +10150,14 @@ var gitPaths = (root) => {
|
|
|
10003
10150
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
10004
10151
|
};
|
|
10005
10152
|
}
|
|
10006
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10153
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
10007
10154
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
10008
10155
|
if (stagedDeleted.status !== 0) {
|
|
10009
10156
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
10010
10157
|
if (!/not a git repository/i.test(stderr))
|
|
10011
10158
|
return { paths, uncertain: true, git: true };
|
|
10012
10159
|
} else if (stagedDeleted.stdout) {
|
|
10013
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10160
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
10014
10161
|
}
|
|
10015
10162
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
10016
10163
|
};
|
|
@@ -10025,7 +10172,7 @@ var walk = (root, directory, output) => {
|
|
|
10025
10172
|
for (const name of names) {
|
|
10026
10173
|
if (name === ".git" || name === ".workit")
|
|
10027
10174
|
continue;
|
|
10028
|
-
const target =
|
|
10175
|
+
const target = path6.join(directory, name);
|
|
10029
10176
|
let stat;
|
|
10030
10177
|
try {
|
|
10031
10178
|
stat = fs6.lstatSync(target);
|
|
@@ -10043,17 +10190,17 @@ var walk = (root, directory, output) => {
|
|
|
10043
10190
|
var scopeRoots = (root, scope) => {
|
|
10044
10191
|
const roots = [];
|
|
10045
10192
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
10046
|
-
const target =
|
|
10193
|
+
const target = path6.resolve(root, item);
|
|
10047
10194
|
if (!inside(root, target))
|
|
10048
|
-
return failure("invalid_input",
|
|
10195
|
+
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
10196
|
let ancestor = target;
|
|
10050
10197
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
10051
|
-
ancestor =
|
|
10198
|
+
ancestor = path6.dirname(ancestor);
|
|
10052
10199
|
try {
|
|
10053
10200
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10054
|
-
return failure("invalid_input",
|
|
10201
|
+
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
10202
|
} catch {
|
|
10056
|
-
return failure("invalid_input",
|
|
10203
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
10057
10204
|
}
|
|
10058
10205
|
roots.push(target);
|
|
10059
10206
|
}
|
|
@@ -10068,7 +10215,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10068
10215
|
}
|
|
10069
10216
|
const normalizedScope = canonicalScope(scope);
|
|
10070
10217
|
if (!normalizedScope)
|
|
10071
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
10218
|
+
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
10219
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
10073
10220
|
if (!roots.ok)
|
|
10074
10221
|
return roots;
|
|
@@ -10100,7 +10247,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10100
10247
|
for (const item of [...names].sort()) {
|
|
10101
10248
|
if (!scopeMatches(item, normalizedScope))
|
|
10102
10249
|
continue;
|
|
10103
|
-
const target =
|
|
10250
|
+
const target = path6.join(checkout, item);
|
|
10104
10251
|
try {
|
|
10105
10252
|
const stat = fs6.lstatSync(target);
|
|
10106
10253
|
if (stat.isSymbolicLink()) {
|
|
@@ -10222,7 +10369,7 @@ function evaluateEvidence(task, candidate) {
|
|
|
10222
10369
|
}
|
|
10223
10370
|
var scopeCovers2 = scopeCovers;
|
|
10224
10371
|
var applicableDecision2 = (task, workspace, requirement, checkoutRoot) => task.decisions.filter((entry) => entry.provenance.kind !== "imported").map((entry) => entry.data).filter((decision) => decision.purpose === "limitation" && decision.response === "approved" && decision.revoked === null && decision.binding.taskId === task.id && decision.binding.workspaceId === workspace.id && decision.requirementIds.includes(requirement.id) && decision.binding.scope && scopeCovers2(decision.binding.scope, requirement.scope) && decision.digest === decisionDigest(decision) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, decision.binding).ok : decision.binding.contentRefs.every((reference) => reference.kind !== "file")));
|
|
10225
|
-
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && data.response === "approved" && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
10372
|
+
var applicableRequirementDecision = (task, workspace, requirement, checkoutRoot) => task.decisions.filter(({ data, provenance }) => provenance.kind !== "imported" && data.purpose !== "limitation" && (data.response === "approved" || data.response === "stated") && data.revoked === null && data.binding.taskId === task.id && data.binding.workspaceId === workspace.id && data.requirementIds.includes(requirement.id) && scopeCovers2(data.binding.scope, requirement.scope) && data.digest === decisionDigest(data) && (checkoutRoot ? verifyDecisionContentAtRoot(checkoutRoot, data.binding).ok : data.binding.contentRefs.every((reference) => reference.kind !== "file"))).map(({ id }) => ({ id }));
|
|
10226
10373
|
var evidenceMatchesRequirement = (kind, evidenceKind, dimension) => {
|
|
10227
10374
|
if (kind !== "passed")
|
|
10228
10375
|
return false;
|
|
@@ -10257,7 +10404,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10257
10404
|
return true;
|
|
10258
10405
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
10259
10406
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
10260
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
10407
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
10261
10408
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
10262
10409
|
});
|
|
10263
10410
|
if (passed.length) {
|
|
@@ -10298,7 +10445,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10298
10445
|
status: "satisfied",
|
|
10299
10446
|
evidenceIds: [],
|
|
10300
10447
|
decisionIds: decisions.map((decision) => decision.id),
|
|
10301
|
-
reason: "an applicable approved decision satisfies the requirement"
|
|
10448
|
+
reason: "an applicable approved or stated decision satisfies the requirement"
|
|
10302
10449
|
};
|
|
10303
10450
|
const limitations = requirement.acceptanceAllowed ? applicableDecision2(task, workspace, requirement, checkoutRoot) : [];
|
|
10304
10451
|
if (limitations.length)
|
|
@@ -10353,7 +10500,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10353
10500
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
10354
10501
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
10355
10502
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
10356
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
10503
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
10504
|
+
requirements: blocking.map((item) => {
|
|
10505
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
10506
|
+
return {
|
|
10507
|
+
requirementId: item.requirementId,
|
|
10508
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
10509
|
+
reason: item.reason,
|
|
10510
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
10511
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
10512
|
+
};
|
|
10513
|
+
})
|
|
10357
10514
|
});
|
|
10358
10515
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
10359
10516
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -10384,7 +10541,7 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10384
10541
|
var METHODS = {
|
|
10385
10542
|
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10386
10543
|
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10387
|
-
"workit-review": { dimensions: ["review"] },
|
|
10544
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
10388
10545
|
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10389
10546
|
"workit-implement": { dimensions: ["delegation"] },
|
|
10390
10547
|
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
@@ -10446,7 +10603,18 @@ decision.record {binding with taskId/workspaceId from inspect plus presented and
|
|
|
10446
10603
|
approvedContent} only through a native approval question asked receipt-shaped:
|
|
10447
10604
|
header Workit decision: <purpose>, the same label repeated in the question
|
|
10448
10605
|
text (some host UIs do not render headers), exactly two options approved and
|
|
10449
|
-
rejected; ask once, never re-ask to mint a receipt.
|
|
10606
|
+
rejected; ask once, never re-ask to mint a receipt. Before asking, present the
|
|
10607
|
+
item in the conversation (durable artifacts: complete digest plus exact path;
|
|
10608
|
+
inline plans: the content) and keep the question to one short scoped sentence;
|
|
10609
|
+
never ask to approve something unseen. An answer with custom text is steering,
|
|
10610
|
+
not approval: adjust, re-present, then ask a new scoped question. After the user
|
|
10611
|
+
approves a spec or plan, execute the remaining tasks continuously with one
|
|
10612
|
+
atomic commit per task and do not ask whether to continue; stop only for a new
|
|
10613
|
+
product decision, a failed safety or verification gate, a conflicting edit, or
|
|
10614
|
+
missing authority. Record the plan's commit list once through the plan-scoped
|
|
10615
|
+
action approval (git.commit with plan_steps and plan_branch) so each listed
|
|
10616
|
+
commit executes without a new question; an unlisted message needs a fresh
|
|
10617
|
+
approval. task.close {outcome,
|
|
10450
10618
|
summary, decisionIds}. A completed native subagent run stops its bound worker
|
|
10451
10619
|
by itself; if a worker strands in cancelling with its run verifiably over,
|
|
10452
10620
|
repeat worker.cancel {reason} to confirm the stop. Check and review evidence
|
|
@@ -10478,7 +10646,7 @@ function reconcileResume(view, observations = []) {
|
|
|
10478
10646
|
return candidate;
|
|
10479
10647
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
10480
10648
|
const blockers = [...view.task.progress.blockers];
|
|
10481
|
-
if (view.task.workers.some((entry) =>
|
|
10649
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
10482
10650
|
blockers.push({
|
|
10483
10651
|
reason: "worker state requires reconciliation",
|
|
10484
10652
|
dependentAction: "resume",
|
|
@@ -10610,6 +10778,23 @@ function shellRouteIntent(command) {
|
|
|
10610
10778
|
}
|
|
10611
10779
|
return null;
|
|
10612
10780
|
}
|
|
10781
|
+
function hasLiveWorkitTask(dir) {
|
|
10782
|
+
try {
|
|
10783
|
+
if (typeof dir !== "string" || dir === "")
|
|
10784
|
+
return false;
|
|
10785
|
+
const listed = new TaskStore(dir).listTasks();
|
|
10786
|
+
if (!listed.ok)
|
|
10787
|
+
return false;
|
|
10788
|
+
return listed.data.some((task) => task.status === "active" || task.status === "paused");
|
|
10789
|
+
} catch {
|
|
10790
|
+
return false;
|
|
10791
|
+
}
|
|
10792
|
+
}
|
|
10793
|
+
function shouldDenyShellRoute(dir, command) {
|
|
10794
|
+
if (!hasLiveWorkitTask(dir))
|
|
10795
|
+
return null;
|
|
10796
|
+
return shellRouteIntent(command);
|
|
10797
|
+
}
|
|
10613
10798
|
// packages/workit-core/src/core/external-action.ts
|
|
10614
10799
|
var externalActionSchema = discriminatedUnion("operation", [
|
|
10615
10800
|
object({
|
|
@@ -10623,7 +10808,11 @@ var externalActionSchema = discriminatedUnion("operation", [
|
|
|
10623
10808
|
}).strict(),
|
|
10624
10809
|
object({
|
|
10625
10810
|
operation: literal("git.commit"),
|
|
10626
|
-
payload: object({
|
|
10811
|
+
payload: object({
|
|
10812
|
+
message: string2().min(1).optional(),
|
|
10813
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
10814
|
+
plan_branch: string2().min(1).optional()
|
|
10815
|
+
}).strict()
|
|
10627
10816
|
}).strict(),
|
|
10628
10817
|
object({
|
|
10629
10818
|
operation: literal("git.push"),
|
|
@@ -10858,7 +11047,7 @@ function resolveRequirements(input) {
|
|
|
10858
11047
|
].join(", ")}`,
|
|
10859
11048
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
10860
11049
|
before: "dependent_action",
|
|
10861
|
-
dependentAction:
|
|
11050
|
+
dependentAction: null,
|
|
10862
11051
|
acceptanceAllowed: false
|
|
10863
11052
|
}));
|
|
10864
11053
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -10869,7 +11058,7 @@ function resolveRequirements(input) {
|
|
|
10869
11058
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
10870
11059
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
10871
11060
|
before: "dependent_action",
|
|
10872
|
-
dependentAction:
|
|
11061
|
+
dependentAction: null,
|
|
10873
11062
|
acceptanceAllowed: false
|
|
10874
11063
|
}));
|
|
10875
11064
|
if (signals.behaviorChange.value === true)
|
|
@@ -11027,59 +11216,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
11027
11216
|
return parsed.data;
|
|
11028
11217
|
}
|
|
11029
11218
|
|
|
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
11219
|
// packages/workit-core/src/core/task-engine.ts
|
|
11084
11220
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
11085
11221
|
kind,
|
|
@@ -11210,15 +11346,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
11210
11346
|
return failure("invalid_transition", "worker has not started");
|
|
11211
11347
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
11212
11348
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
11349
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
11213
11350
|
const nextEntry = {
|
|
11214
11351
|
...entry,
|
|
11215
11352
|
recordedAt: input.now ?? entry.recordedAt,
|
|
11216
11353
|
provenance: authority.provenance,
|
|
11217
|
-
data: {
|
|
11354
|
+
data: {
|
|
11355
|
+
...entry.data,
|
|
11356
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
11357
|
+
session: input.session
|
|
11358
|
+
}
|
|
11218
11359
|
};
|
|
11219
11360
|
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
11361
|
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 ===
|
|
11362
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
11222
11363
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
11223
11364
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
11224
11365
|
taskId: task.data.id,
|
|
@@ -11588,7 +11729,7 @@ class WorkitCore {
|
|
|
11588
11729
|
activeWorkerBlocker(task, workspace) {
|
|
11589
11730
|
if (workspace.writer)
|
|
11590
11731
|
return failure("recovery_required", "writer ownership must be released first");
|
|
11591
|
-
if (task.workers.some((entry) =>
|
|
11732
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
11592
11733
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
11593
11734
|
return null;
|
|
11594
11735
|
}
|
|
@@ -11825,11 +11966,6 @@ class WorkitCore {
|
|
|
11825
11966
|
if (helper && !helper.ok)
|
|
11826
11967
|
return helper;
|
|
11827
11968
|
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
11969
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
11834
11970
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
11835
11971
|
if (evidence.kind === "review") {
|
|
@@ -11846,6 +11982,11 @@ class WorkitCore {
|
|
|
11846
11982
|
evidence.beforeCandidateId ??= null;
|
|
11847
11983
|
evidence.candidateId ??= null;
|
|
11848
11984
|
}
|
|
11985
|
+
if (helper?.ok) {
|
|
11986
|
+
const assignment = helper.data.data.assignment;
|
|
11987
|
+
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))
|
|
11988
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
11989
|
+
}
|
|
11849
11990
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
11850
11991
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
11851
11992
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -11899,7 +12040,12 @@ class WorkitCore {
|
|
|
11899
12040
|
const input = parsed.data;
|
|
11900
12041
|
if (nativeRequired && input.action !== "record")
|
|
11901
12042
|
return failure("permission_denied", "native observation is only valid for recording decisions");
|
|
11902
|
-
|
|
12043
|
+
const stated = input.action === "record" && input.response === "stated";
|
|
12044
|
+
if (stated && input.purpose === "action")
|
|
12045
|
+
return failure("permission_denied", "stated choices cannot authorize mutating actions");
|
|
12046
|
+
if (stated && !input.binding?.statedChoice)
|
|
12047
|
+
return failure("invalid_input", "stated choices require binding.statedChoice");
|
|
12048
|
+
if (nativeRequired && nativeObservation === undefined && !stated)
|
|
11903
12049
|
return failure("permission_denied", "native decision observation is required");
|
|
11904
12050
|
const task = this.store.readTask(input.taskId);
|
|
11905
12051
|
if (!task.ok)
|
|
@@ -11932,7 +12078,7 @@ class WorkitCore {
|
|
|
11932
12078
|
consumption: null
|
|
11933
12079
|
};
|
|
11934
12080
|
const data = { ...base, digest: decisionDigest(base) };
|
|
11935
|
-
const native = nativeRequired ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
12081
|
+
const native = nativeRequired && !stated ? verifyNativeDecision(this.context.nativeAuthority, {
|
|
11936
12082
|
observation: nativeObservation,
|
|
11937
12083
|
expected: {
|
|
11938
12084
|
taskId: task.data.id,
|
|
@@ -12741,6 +12887,9 @@ class WorkitCore {
|
|
|
12741
12887
|
id: task.id,
|
|
12742
12888
|
revision: task.revision,
|
|
12743
12889
|
workspaceRevision: workspace.data.revision,
|
|
12890
|
+
createdAt: task.createdAt,
|
|
12891
|
+
updatedAt: task.updatedAt,
|
|
12892
|
+
runtime: task.runtime ?? null,
|
|
12744
12893
|
objective: task.intent.data.objective,
|
|
12745
12894
|
status: task.status,
|
|
12746
12895
|
closure: task.closure,
|
|
@@ -12826,7 +12975,7 @@ class WorkitCore {
|
|
|
12826
12975
|
const base = reconcileResume(effectiveView);
|
|
12827
12976
|
if (!base.ok)
|
|
12828
12977
|
return base;
|
|
12829
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
12978
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
12830
12979
|
const blockers = [...base.data.blockers];
|
|
12831
12980
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
12832
12981
|
blockers.push({
|
|
@@ -12893,7 +13042,8 @@ class WorkitCore {
|
|
|
12893
13042
|
...current,
|
|
12894
13043
|
status,
|
|
12895
13044
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
12896
|
-
progress:
|
|
13045
|
+
progress: current.progress,
|
|
13046
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
12897
13047
|
})
|
|
12898
13048
|
});
|
|
12899
13049
|
if (!changed.ok)
|
|
@@ -12929,7 +13079,7 @@ class WorkitCore {
|
|
|
12929
13079
|
return workspace;
|
|
12930
13080
|
if (!workspace.data)
|
|
12931
13081
|
return failure("not_found", "workspace not found");
|
|
12932
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
13082
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
12933
13083
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
12934
13084
|
this.fillRevisions(input, task.data, workspace.data);
|
|
12935
13085
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -13040,6 +13190,13 @@ var codexCapabilities = (host, availability = {}) => {
|
|
|
13040
13190
|
reason: "file writes are host-policy; PreToolUse allows write tools",
|
|
13041
13191
|
refs: [ref(host, "PreToolUse")]
|
|
13042
13192
|
},
|
|
13193
|
+
{
|
|
13194
|
+
name: "fresh-context-review",
|
|
13195
|
+
surface: "SubagentStart",
|
|
13196
|
+
assurance: has("subagentStart") ? "agent_guided" : "unavailable",
|
|
13197
|
+
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",
|
|
13198
|
+
refs: [ref(host, "SubagentStart")]
|
|
13199
|
+
},
|
|
13043
13200
|
{
|
|
13044
13201
|
name: "native_subagents",
|
|
13045
13202
|
surface: "SubagentStart/SubagentStop",
|
|
@@ -13143,7 +13300,7 @@ var parseCodexHookInput = (value) => {
|
|
|
13143
13300
|
return { ok: false, error: "permission_mode is invalid" };
|
|
13144
13301
|
if (!(value.transcript_path === null || nonEmpty2(value.transcript_path)))
|
|
13145
13302
|
return { ok: false, error: "transcript_path must be a string or null" };
|
|
13146
|
-
if (!nonEmpty2(value.cwd) || !
|
|
13303
|
+
if (!nonEmpty2(value.cwd) || !path7.isAbsolute(value.cwd) || !existsSync4(value.cwd))
|
|
13147
13304
|
return { ok: false, error: "cwd must be an existing absolute path" };
|
|
13148
13305
|
try {
|
|
13149
13306
|
if (!statSync(value.cwd).isDirectory())
|
|
@@ -13252,7 +13409,7 @@ var handleCodexHook = (raw) => {
|
|
|
13252
13409
|
if (input.hook_event_name === "PreToolUse") {
|
|
13253
13410
|
if (["bash", "unified-exec"].includes(String(input.tool_name).toLowerCase())) {
|
|
13254
13411
|
const command = record2(input.tool_input) ? input.tool_input.command : undefined;
|
|
13255
|
-
const route = typeof command === "string" ?
|
|
13412
|
+
const route = typeof command === "string" ? shouldDenyShellRoute(input.cwd, command) : null;
|
|
13256
13413
|
if (route)
|
|
13257
13414
|
return denied("PreToolUse", `direct branch or PR creation bypasses the Workit route; ${route.guidance}`);
|
|
13258
13415
|
}
|