@brainervirus/workit-cursor 1.0.8 → 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/.cursor-plugin/plugin.json +1 -1
- package/dist/cursor-session-start.js +388 -195
- package/dist/mcp-server.js +364 -221
- package/dist/workit-hook.js +386 -194
- package/package.json +3 -3
- package/skills/workit-babysit/SKILL.md +3 -1
- package/skills/workit-plan/SKILL.md +14 -2
|
@@ -7492,7 +7492,7 @@ var SCHEMA_VERSION = 1, POLICY_VERSION = "1.0.0", OPERATION_FAMILIES, uuidPatter
|
|
|
7492
7492
|
const covers = (path) => normOuter.paths.some((base) => base === "." || path === base || path.startsWith(`${base}/`)) && !excluded(path, normOuter);
|
|
7493
7493
|
const innerExcludes = (path) => innerExclusions.some((excludedPath) => excludedPath === path || path.startsWith(`${excludedPath}/`));
|
|
7494
7494
|
return innerPaths.every(covers) && !normOuter.exclusions.some((excludedPath) => innerPaths.some((base) => (base === "." || excludedPath === base || excludedPath.startsWith(`${base}/`)) && !innerExcludes(excludedPath)));
|
|
7495
|
-
}, refSchema, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, taskRecordSchema, workspaceRecordSchema, capabilitySchema, requirementEvaluationSchema, taskViewSchema, taskSummarySchema, exportBundleSchema, callerSchema, base, revisions, taskId, operation = (shape) => object({ ...base, ...shape }).strict(), taskOperations, policyOperations, evidenceOperations, findingOperations, decisionOperations, workerOperations, writerOperations, stateOperations, operationSchemas, compiledOperationSchemas, success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
7495
|
+
}, refSchema, provenanceSchema, entrySchema = (data) => object({ id, recordedAt: utc, provenance: provenanceSchema, data }).strict(), factSchema, signalSchema, signalSet, consequenceSchema, verificationSchema, assessmentSchema, constraintSchema, intentSchema, progressSchema, dimensionSchema, policySchema, requirementSchema, policyChangeSchema, candidateSchema, evidenceSchema, evidenceEvaluationSchema, decisionSchema, findingSchema, assignmentSchema, workerReportSchema, workerSchema, ownerSchema, closureSchema, actionProgressSchema, actionProgressListSchema, runtimeSchema, taskRecordSchema, workspaceRecordSchema, capabilitySchema, requirementEvaluationSchema, taskViewSchema, taskSummarySchema, exportBundleSchema, callerSchema, base, revisions, taskId, operation = (shape) => object({ ...base, ...shape }).strict(), taskOperations, policyOperations, evidenceOperations, findingOperations, decisionOperations, workerOperations, writerOperations, stateOperations, operationSchemas, compiledOperationSchemas, success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
7496
7496
|
ok: true,
|
|
7497
7497
|
schemaVersion: 1,
|
|
7498
7498
|
revision: revisionValue,
|
|
@@ -7794,6 +7794,7 @@ var init_task_contract = __esm(() => {
|
|
|
7794
7794
|
scope: scopeSchema,
|
|
7795
7795
|
presented: text,
|
|
7796
7796
|
approvedContent: text,
|
|
7797
|
+
displayed: text.optional(),
|
|
7797
7798
|
contentRefs: array(refSchema)
|
|
7798
7799
|
}).strict(),
|
|
7799
7800
|
digest,
|
|
@@ -7832,7 +7833,7 @@ var init_task_contract = __esm(() => {
|
|
|
7832
7833
|
}).strict();
|
|
7833
7834
|
workerSchema = object({
|
|
7834
7835
|
assignment: assignmentSchema,
|
|
7835
|
-
state: _enum(["assigned", "running", "cancelling", "stopped", "unknown"]),
|
|
7836
|
+
state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
7836
7837
|
session: refSchema.nullable(),
|
|
7837
7838
|
report: workerReportSchema.nullable()
|
|
7838
7839
|
}).strict();
|
|
@@ -7872,6 +7873,10 @@ var init_task_contract = __esm(() => {
|
|
|
7872
7873
|
path: ["decisionId"]
|
|
7873
7874
|
});
|
|
7874
7875
|
});
|
|
7876
|
+
runtimeSchema = object({
|
|
7877
|
+
createdWith: text.nullable(),
|
|
7878
|
+
updatedWith: text
|
|
7879
|
+
}).strict();
|
|
7875
7880
|
taskRecordSchema = object({
|
|
7876
7881
|
schemaVersion: literal(1),
|
|
7877
7882
|
id,
|
|
@@ -7885,6 +7890,8 @@ var init_task_contract = __esm(() => {
|
|
|
7885
7890
|
status: _enum(["active", "paused", "closed"]),
|
|
7886
7891
|
closure: closureSchema.nullable(),
|
|
7887
7892
|
progress: progressSchema,
|
|
7893
|
+
pauseReason: text.nullable().optional(),
|
|
7894
|
+
runtime: runtimeSchema.optional(),
|
|
7888
7895
|
assessments: array(entrySchema(assessmentSchema)),
|
|
7889
7896
|
policy: policySchema.nullable(),
|
|
7890
7897
|
policyChanges: array(policyChangeSchema),
|
|
@@ -7900,6 +7907,7 @@ var init_task_contract = __esm(() => {
|
|
|
7900
7907
|
id,
|
|
7901
7908
|
revision,
|
|
7902
7909
|
root: nonEmpty,
|
|
7910
|
+
runtime: runtimeSchema.optional(),
|
|
7903
7911
|
writer: object({ state: _enum(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
7904
7912
|
}).strict();
|
|
7905
7913
|
capabilitySchema = object({
|
|
@@ -7927,6 +7935,9 @@ var init_task_contract = __esm(() => {
|
|
|
7927
7935
|
id,
|
|
7928
7936
|
revision,
|
|
7929
7937
|
workspaceRevision: revision,
|
|
7938
|
+
createdAt: utc,
|
|
7939
|
+
updatedAt: utc,
|
|
7940
|
+
runtime: runtimeSchema.nullable(),
|
|
7930
7941
|
objective: text,
|
|
7931
7942
|
status: _enum(["active", "paused", "closed"]),
|
|
7932
7943
|
closure: closureSchema.nullable(),
|
|
@@ -7996,7 +8007,7 @@ var init_task_contract = __esm(() => {
|
|
|
7996
8007
|
expectedWorkspaceRevision: revision.optional(),
|
|
7997
8008
|
outcome: outcomeSchema,
|
|
7998
8009
|
summary: text,
|
|
7999
|
-
decisionIds: array(id)
|
|
8010
|
+
decisionIds: array(id).optional()
|
|
8000
8011
|
})
|
|
8001
8012
|
};
|
|
8002
8013
|
policyOperations = {
|
|
@@ -8088,14 +8099,15 @@ var init_task_contract = __esm(() => {
|
|
|
8088
8099
|
...taskId,
|
|
8089
8100
|
expectedRevision: revision.optional(),
|
|
8090
8101
|
expectedWorkspaceRevision: revision.optional(),
|
|
8091
|
-
workerId: nullableId.optional()
|
|
8102
|
+
workerId: nullableId.optional(),
|
|
8103
|
+
reason: text.optional()
|
|
8092
8104
|
}),
|
|
8093
8105
|
release: operation({
|
|
8094
8106
|
action: literal("release"),
|
|
8095
8107
|
...taskId,
|
|
8096
8108
|
expectedRevision: revision.optional(),
|
|
8097
8109
|
expectedWorkspaceRevision: revision.optional(),
|
|
8098
|
-
reason: text
|
|
8110
|
+
reason: text.optional()
|
|
8099
8111
|
})
|
|
8100
8112
|
};
|
|
8101
8113
|
stateOperations = {
|
|
@@ -8130,6 +8142,21 @@ var init_task_contract = __esm(() => {
|
|
|
8130
8142
|
compiledOperationSchemas = Object.fromEntries(OPERATION_FAMILIES.map((family) => [family, compile(operationSchemas[family])]));
|
|
8131
8143
|
});
|
|
8132
8144
|
|
|
8145
|
+
// packages/workit-core/src/core/package-root.ts
|
|
8146
|
+
import { existsSync } from "node:fs";
|
|
8147
|
+
import path from "node:path";
|
|
8148
|
+
import { fileURLToPath } from "node:url";
|
|
8149
|
+
var packageRoot = () => {
|
|
8150
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
8151
|
+
while (true) {
|
|
8152
|
+
const parent = path.dirname(dir);
|
|
8153
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
8154
|
+
return dir;
|
|
8155
|
+
dir = parent;
|
|
8156
|
+
}
|
|
8157
|
+
};
|
|
8158
|
+
var init_package_root = () => {};
|
|
8159
|
+
|
|
8133
8160
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
8134
8161
|
function categorizeFsSafeError(code) {
|
|
8135
8162
|
return OPERATIONAL_CODES.has(code) ? "operational" : "policy";
|
|
@@ -8240,7 +8267,7 @@ var init_read_open_flags = () => {};
|
|
|
8240
8267
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
8241
8268
|
import { randomBytes } from "node:crypto";
|
|
8242
8269
|
import fsSync from "node:fs";
|
|
8243
|
-
import
|
|
8270
|
+
import path2 from "node:path";
|
|
8244
8271
|
function parseSidecarLockSnapshot(snapshot, parser) {
|
|
8245
8272
|
return snapshot && { ...snapshot, payload: parseSidecarLockPayload(snapshot.raw, parser) };
|
|
8246
8273
|
}
|
|
@@ -8268,13 +8295,13 @@ ${ownershipToken}
|
|
|
8268
8295
|
return { raw, ownershipToken };
|
|
8269
8296
|
}
|
|
8270
8297
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
8271
|
-
const resolved =
|
|
8272
|
-
const lexicalRelative =
|
|
8273
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
8274
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
8298
|
+
const resolved = path2.resolve(lockPath);
|
|
8299
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
8300
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
8301
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
8275
8302
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
8276
8303
|
}
|
|
8277
|
-
return relative.split(
|
|
8304
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
8278
8305
|
}
|
|
8279
8306
|
function parseSidecarLockPayload(raw, parser) {
|
|
8280
8307
|
if (parser) {
|
|
@@ -8444,7 +8471,7 @@ function sleepSync(ms) {
|
|
|
8444
8471
|
|
|
8445
8472
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
8446
8473
|
import fs3 from "node:fs";
|
|
8447
|
-
import
|
|
8474
|
+
import path3 from "node:path";
|
|
8448
8475
|
function getSyncHeldLocks() {
|
|
8449
8476
|
const globalWithState = globalThis;
|
|
8450
8477
|
if (!globalWithState[SYNC_HELD_LOCKS_KEY]) {
|
|
@@ -8522,26 +8549,26 @@ function canonicalLockParentSync(parent) {
|
|
|
8522
8549
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
8523
8550
|
}
|
|
8524
8551
|
function normalizeTargetPath(targetPath) {
|
|
8525
|
-
const resolved =
|
|
8526
|
-
fs3.mkdirSync(
|
|
8552
|
+
const resolved = path3.resolve(targetPath);
|
|
8553
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
8527
8554
|
try {
|
|
8528
|
-
return
|
|
8555
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
8529
8556
|
} catch {
|
|
8530
8557
|
return resolved;
|
|
8531
8558
|
}
|
|
8532
8559
|
}
|
|
8533
8560
|
function boundedLockPath(lockPath, lockRoot) {
|
|
8534
|
-
const resolved =
|
|
8561
|
+
const resolved = path3.resolve(lockPath);
|
|
8535
8562
|
if (!lockRoot)
|
|
8536
8563
|
return resolved;
|
|
8537
8564
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
8538
|
-
const parent =
|
|
8565
|
+
const parent = path3.dirname(resolved);
|
|
8539
8566
|
const parentReal = canonicalLockParentSync(parent);
|
|
8540
|
-
const parentRelative =
|
|
8541
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
8567
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
8568
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
8542
8569
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
8543
8570
|
}
|
|
8544
|
-
return
|
|
8571
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
8545
8572
|
}
|
|
8546
8573
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
8547
8574
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -8760,17 +8787,17 @@ var init_file_lock = __esm(() => {
|
|
|
8760
8787
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
8761
8788
|
import * as fs4 from "node:fs";
|
|
8762
8789
|
import { hostname } from "node:os";
|
|
8763
|
-
import
|
|
8790
|
+
import path4 from "node:path";
|
|
8764
8791
|
|
|
8765
8792
|
class TaskStore {
|
|
8766
8793
|
root;
|
|
8767
8794
|
constructor(root) {
|
|
8768
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
8795
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
8769
8796
|
}
|
|
8770
8797
|
readTask(taskId) {
|
|
8771
8798
|
if (!validId(taskId))
|
|
8772
8799
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
8773
|
-
const result = this.readRecord(
|
|
8800
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
8774
8801
|
if (!result.exists)
|
|
8775
8802
|
return failure("not_found", "task not found", { taskId });
|
|
8776
8803
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -8804,7 +8831,7 @@ class TaskStore {
|
|
|
8804
8831
|
});
|
|
8805
8832
|
const tasks = [];
|
|
8806
8833
|
for (const name of names.sort()) {
|
|
8807
|
-
const item = this.readRecord(
|
|
8834
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
8808
8835
|
if (!item.exists)
|
|
8809
8836
|
continue;
|
|
8810
8837
|
if (!item.result.ok)
|
|
@@ -8838,7 +8865,7 @@ class TaskStore {
|
|
|
8838
8865
|
if (!current.ok)
|
|
8839
8866
|
return current;
|
|
8840
8867
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
8841
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
8868
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
8842
8869
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
8843
8870
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
8844
8871
|
});
|
|
@@ -8850,11 +8877,17 @@ class TaskStore {
|
|
|
8850
8877
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
8851
8878
|
if (current.data && !previousWorkspaceBytes)
|
|
8852
8879
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
8853
|
-
const workspace = current.data ? {
|
|
8880
|
+
const workspace = current.data ? {
|
|
8881
|
+
...current.data,
|
|
8882
|
+
revision: newRevision(),
|
|
8883
|
+
root: this.root,
|
|
8884
|
+
runtime: stampNew(current.data)
|
|
8885
|
+
} : {
|
|
8854
8886
|
schemaVersion: SCHEMA_VERSION,
|
|
8855
8887
|
id: newId(),
|
|
8856
8888
|
revision: newRevision(),
|
|
8857
8889
|
root: this.root,
|
|
8890
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8858
8891
|
writer: null
|
|
8859
8892
|
};
|
|
8860
8893
|
const timestamp = value.now ?? now();
|
|
@@ -8876,6 +8909,7 @@ class TaskStore {
|
|
|
8876
8909
|
status: "active",
|
|
8877
8910
|
closure: null,
|
|
8878
8911
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
8912
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8879
8913
|
assessments: [],
|
|
8880
8914
|
policy: null,
|
|
8881
8915
|
policyChanges: [],
|
|
@@ -8906,7 +8940,7 @@ class TaskStore {
|
|
|
8906
8940
|
if (!current.ok)
|
|
8907
8941
|
return current;
|
|
8908
8942
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
8909
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
8943
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
8910
8944
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
8911
8945
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
8912
8946
|
});
|
|
@@ -8914,11 +8948,17 @@ class TaskStore {
|
|
|
8914
8948
|
if (current.data && !previousWorkspaceBytes)
|
|
8915
8949
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
8916
8950
|
const timestamp = input.now ?? now();
|
|
8917
|
-
const workspace = current.data ? {
|
|
8951
|
+
const workspace = current.data ? {
|
|
8952
|
+
...current.data,
|
|
8953
|
+
revision: newRevision(),
|
|
8954
|
+
root: this.root,
|
|
8955
|
+
runtime: stampNew(current.data)
|
|
8956
|
+
} : {
|
|
8918
8957
|
schemaVersion: SCHEMA_VERSION,
|
|
8919
8958
|
id: input.workspaceId ?? newId(),
|
|
8920
8959
|
revision: newRevision(),
|
|
8921
8960
|
root: this.root,
|
|
8961
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
8922
8962
|
writer: null
|
|
8923
8963
|
};
|
|
8924
8964
|
const task = {
|
|
@@ -8926,7 +8966,11 @@ class TaskStore {
|
|
|
8926
8966
|
workspaceId: workspace.id,
|
|
8927
8967
|
revision: newRevision(),
|
|
8928
8968
|
createdAt: timestamp,
|
|
8929
|
-
updatedAt: timestamp
|
|
8969
|
+
updatedAt: timestamp,
|
|
8970
|
+
runtime: {
|
|
8971
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
8972
|
+
updatedWith: runtimeVersion()
|
|
8973
|
+
}
|
|
8930
8974
|
};
|
|
8931
8975
|
const validTask = taskRecordSchema.safeParse(task);
|
|
8932
8976
|
if (!validTask.success)
|
|
@@ -8975,7 +9019,8 @@ class TaskStore {
|
|
|
8975
9019
|
workspaceId: current.data.workspaceId,
|
|
8976
9020
|
createdAt: current.data.createdAt,
|
|
8977
9021
|
revision: context.revision,
|
|
8978
|
-
updatedAt: context.now
|
|
9022
|
+
updatedAt: context.now,
|
|
9023
|
+
runtime: stampNew(current.data)
|
|
8979
9024
|
};
|
|
8980
9025
|
const valid = taskRecordSchema.safeParse(record);
|
|
8981
9026
|
if (!valid.success)
|
|
@@ -9009,7 +9054,8 @@ class TaskStore {
|
|
|
9009
9054
|
...changed.data,
|
|
9010
9055
|
id: current.data.id,
|
|
9011
9056
|
root: this.root,
|
|
9012
|
-
revision: context.revision
|
|
9057
|
+
revision: context.revision,
|
|
9058
|
+
runtime: stampNew(current.data)
|
|
9013
9059
|
};
|
|
9014
9060
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
9015
9061
|
if (!valid.success)
|
|
@@ -9052,7 +9098,8 @@ class TaskStore {
|
|
|
9052
9098
|
...changedWorkspace.data,
|
|
9053
9099
|
id: workspace.data.id,
|
|
9054
9100
|
root: this.root,
|
|
9055
|
-
revision: workspaceContext.revision
|
|
9101
|
+
revision: workspaceContext.revision,
|
|
9102
|
+
runtime: stampNew(workspace.data)
|
|
9056
9103
|
});
|
|
9057
9104
|
if (!nextWorkspace.success)
|
|
9058
9105
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -9077,7 +9124,8 @@ class TaskStore {
|
|
|
9077
9124
|
workspaceId: task.data.workspaceId,
|
|
9078
9125
|
createdAt: task.data.createdAt,
|
|
9079
9126
|
revision: taskContext.revision,
|
|
9080
|
-
updatedAt: taskContext.now
|
|
9127
|
+
updatedAt: taskContext.now,
|
|
9128
|
+
runtime: stampNew(task.data)
|
|
9081
9129
|
});
|
|
9082
9130
|
if (!nextTask.success) {
|
|
9083
9131
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -9104,7 +9152,7 @@ class TaskStore {
|
|
|
9104
9152
|
if (match)
|
|
9105
9153
|
candidates.push({
|
|
9106
9154
|
target: match[1],
|
|
9107
|
-
path:
|
|
9155
|
+
path: path4.join(this.recoveryDir, name),
|
|
9108
9156
|
digest: match[3]
|
|
9109
9157
|
});
|
|
9110
9158
|
}
|
|
@@ -9297,7 +9345,8 @@ class TaskStore {
|
|
|
9297
9345
|
const value = {
|
|
9298
9346
|
...workspace,
|
|
9299
9347
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
9300
|
-
revision: newRevision()
|
|
9348
|
+
revision: newRevision(),
|
|
9349
|
+
runtime: stampNew(workspace)
|
|
9301
9350
|
};
|
|
9302
9351
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
9303
9352
|
}
|
|
@@ -9375,7 +9424,7 @@ class TaskStore {
|
|
|
9375
9424
|
}
|
|
9376
9425
|
fs4.renameSync(temporary, file);
|
|
9377
9426
|
temporary = undefined;
|
|
9378
|
-
this.fsyncDirectory(
|
|
9427
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
9379
9428
|
return success(null, null, value);
|
|
9380
9429
|
} catch (error) {
|
|
9381
9430
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -9389,9 +9438,9 @@ class TaskStore {
|
|
|
9389
9438
|
}
|
|
9390
9439
|
}
|
|
9391
9440
|
saveRecovery(file, bytes) {
|
|
9392
|
-
const target =
|
|
9393
|
-
const id = target === "task" ?
|
|
9394
|
-
const destination =
|
|
9441
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
9442
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
9443
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
9395
9444
|
if (fs4.existsSync(destination)) {
|
|
9396
9445
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
9397
9446
|
return;
|
|
@@ -9469,7 +9518,7 @@ class TaskStore {
|
|
|
9469
9518
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
9470
9519
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
9471
9520
|
return null;
|
|
9472
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
9521
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
9473
9522
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
9474
9523
|
return null;
|
|
9475
9524
|
ids.add(parsed.data.workspaceId);
|
|
@@ -9511,7 +9560,12 @@ class TaskStore {
|
|
|
9511
9560
|
if (isObject2(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
9512
9561
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
9513
9562
|
const parsed = schema.safeParse(value);
|
|
9514
|
-
|
|
9563
|
+
if (parsed.success)
|
|
9564
|
+
return success(null, null, parsed.data);
|
|
9565
|
+
const writerVersion = isObject2(value) && isObject2(value.runtime) ? value.runtime.updatedWith : null;
|
|
9566
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
9567
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
9568
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
9515
9569
|
}
|
|
9516
9570
|
processStart(pid) {
|
|
9517
9571
|
try {
|
|
@@ -9521,34 +9575,57 @@ class TaskStore {
|
|
|
9521
9575
|
}
|
|
9522
9576
|
}
|
|
9523
9577
|
conflict(expected, actual) {
|
|
9524
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
9578
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
9525
9579
|
expectedRevision: expected,
|
|
9526
9580
|
actualRevision: actual
|
|
9527
9581
|
});
|
|
9528
9582
|
}
|
|
9529
9583
|
taskPath(taskId) {
|
|
9530
|
-
return
|
|
9584
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
9531
9585
|
}
|
|
9532
9586
|
get workitDir() {
|
|
9533
|
-
return
|
|
9587
|
+
return path4.join(this.root, ".workit");
|
|
9534
9588
|
}
|
|
9535
9589
|
get tasksDir() {
|
|
9536
|
-
return
|
|
9590
|
+
return path4.join(this.workitDir, "tasks");
|
|
9537
9591
|
}
|
|
9538
9592
|
get recoveryDir() {
|
|
9539
|
-
return
|
|
9593
|
+
return path4.join(this.workitDir, "recovery");
|
|
9540
9594
|
}
|
|
9541
9595
|
get workspacePath() {
|
|
9542
|
-
return
|
|
9596
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
9543
9597
|
}
|
|
9544
9598
|
get lockPath() {
|
|
9545
|
-
return
|
|
9599
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
9546
9600
|
}
|
|
9547
9601
|
get gitignorePath() {
|
|
9548
|
-
return
|
|
9602
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
9549
9603
|
}
|
|
9550
9604
|
}
|
|
9551
|
-
var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"),
|
|
9605
|
+
var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z"), cachedRuntimeVersion = null, runtimeVersion = () => {
|
|
9606
|
+
if (cachedRuntimeVersion === null) {
|
|
9607
|
+
try {
|
|
9608
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
9609
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
9610
|
+
} catch {
|
|
9611
|
+
cachedRuntimeVersion = "0.0.0";
|
|
9612
|
+
}
|
|
9613
|
+
}
|
|
9614
|
+
return cachedRuntimeVersion;
|
|
9615
|
+
}, stampNew = (record) => ({
|
|
9616
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
9617
|
+
updatedWith: runtimeVersion()
|
|
9618
|
+
}), isNewerVersion = (candidate, current) => {
|
|
9619
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
9620
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
9621
|
+
for (let index = 0;index < 3; index += 1) {
|
|
9622
|
+
const left = next[index] ?? 0;
|
|
9623
|
+
const right = now[index] ?? 0;
|
|
9624
|
+
if (left !== right)
|
|
9625
|
+
return left > right;
|
|
9626
|
+
}
|
|
9627
|
+
return false;
|
|
9628
|
+
}, jsonBytes = (value) => `${canonicalJson(value)}
|
|
9552
9629
|
`, digestBytes = (value) => createHash2("sha256").update(value).digest("hex"), isObject2 = (value) => typeof value === "object" && value !== null, validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value), validDigest = (value) => /^[0-9a-f]{64}$/.test(value), parseMetadataLock = (raw) => {
|
|
9553
9630
|
let value;
|
|
9554
9631
|
try {
|
|
@@ -9566,6 +9643,7 @@ var processEvidenceSchema, metadataLockSchema, now = () => new Date().toISOStrin
|
|
|
9566
9643
|
};
|
|
9567
9644
|
var init_task_store = __esm(() => {
|
|
9568
9645
|
init_zod();
|
|
9646
|
+
init_package_root();
|
|
9569
9647
|
init_file_lock();
|
|
9570
9648
|
init_task_contract();
|
|
9571
9649
|
processEvidenceSchema = object({
|
|
@@ -9582,10 +9660,63 @@ var init_task_store = __esm(() => {
|
|
|
9582
9660
|
}).strict();
|
|
9583
9661
|
});
|
|
9584
9662
|
|
|
9663
|
+
// packages/workit-core/src/core/workers.ts
|
|
9664
|
+
function assertProductWriteAllowed(input) {
|
|
9665
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
9666
|
+
return failure("invalid_input", "invalid product write path");
|
|
9667
|
+
if (!input.store)
|
|
9668
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
9669
|
+
const task = input.store.readTask(input.task.id);
|
|
9670
|
+
if (!task.ok)
|
|
9671
|
+
return task;
|
|
9672
|
+
const workspace = input.store.readWorkspace();
|
|
9673
|
+
if (!workspace.ok)
|
|
9674
|
+
return workspace;
|
|
9675
|
+
if (!workspace.data)
|
|
9676
|
+
return failure("not_found", "workspace not found");
|
|
9677
|
+
const workspaceRecord = workspace.data;
|
|
9678
|
+
if (task.data.status !== "active")
|
|
9679
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
9680
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
9681
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
9682
|
+
const writer = workspaceRecord.writer;
|
|
9683
|
+
if (!writer)
|
|
9684
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
9685
|
+
if (writer.state === "uncertain")
|
|
9686
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
9687
|
+
owner: writer.owner
|
|
9688
|
+
});
|
|
9689
|
+
const caller = callerValue(input.caller);
|
|
9690
|
+
const workerId = workerIdOf(input.caller);
|
|
9691
|
+
const owner = writer.owner;
|
|
9692
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
9693
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
9694
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
9695
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
9696
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
9697
|
+
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))))
|
|
9698
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
9699
|
+
return success(null, workspaceRecord.revision, owner);
|
|
9700
|
+
}
|
|
9701
|
+
var same = (left, right) => {
|
|
9702
|
+
try {
|
|
9703
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
9704
|
+
} catch {
|
|
9705
|
+
return false;
|
|
9706
|
+
}
|
|
9707
|
+
}, UNCERTAIN_WORKER_STATES, isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state), callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
9708
|
+
const code = char.charCodeAt(0);
|
|
9709
|
+
return code < 32 || code === 127;
|
|
9710
|
+
}) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null;
|
|
9711
|
+
var init_workers = __esm(() => {
|
|
9712
|
+
init_task_contract();
|
|
9713
|
+
UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
9714
|
+
});
|
|
9715
|
+
|
|
9585
9716
|
// packages/workit-core/src/core/authority.ts
|
|
9586
9717
|
import { createHash as createHash3 } from "node:crypto";
|
|
9587
9718
|
import * as fs5 from "node:fs";
|
|
9588
|
-
import
|
|
9719
|
+
import path5 from "node:path";
|
|
9589
9720
|
function applicableDecision(task, purpose, binding, checkoutRoot) {
|
|
9590
9721
|
if (binding.taskId !== task.id || binding.workspaceId !== task.workspaceId)
|
|
9591
9722
|
return [];
|
|
@@ -9969,20 +10100,31 @@ var verifiedAuthorities, trustedAuthority = (kind, provenance, expected, binding
|
|
|
9969
10100
|
continue;
|
|
9970
10101
|
if (!reference.digest)
|
|
9971
10102
|
return failure("invalid_input", "document references require a byte digest");
|
|
9972
|
-
const target =
|
|
9973
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
9974
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
10103
|
+
const target = path5.resolve(root, reference.path);
|
|
10104
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
10105
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
10106
|
+
fields: [
|
|
10107
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
10108
|
+
]
|
|
10109
|
+
});
|
|
9975
10110
|
try {
|
|
9976
|
-
const relative =
|
|
10111
|
+
const relative = path5.relative(root, target);
|
|
9977
10112
|
let current = root;
|
|
9978
|
-
for (const segment of relative.split(
|
|
10113
|
+
for (const segment of relative.split(path5.sep)) {
|
|
9979
10114
|
if (!segment)
|
|
9980
10115
|
continue;
|
|
9981
|
-
current =
|
|
10116
|
+
current = path5.join(current, segment);
|
|
9982
10117
|
const stat = fs5.lstatSync(current);
|
|
9983
10118
|
const real = fs5.realpathSync(current);
|
|
9984
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
9985
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
10119
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
10120
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
10121
|
+
fields: [
|
|
10122
|
+
{
|
|
10123
|
+
path: "contentRefs.path",
|
|
10124
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
10125
|
+
}
|
|
10126
|
+
]
|
|
10127
|
+
});
|
|
9986
10128
|
if (stat.isSymbolicLink())
|
|
9987
10129
|
return failure("invalid_input", "document reference uses a symlink");
|
|
9988
10130
|
if (current === target && !stat.isFile())
|
|
@@ -10059,7 +10201,7 @@ var init_authority = __esm(() => {
|
|
|
10059
10201
|
import { createHash as createHash4 } from "node:crypto";
|
|
10060
10202
|
import * as fs6 from "node:fs";
|
|
10061
10203
|
import { spawnSync } from "node:child_process";
|
|
10062
|
-
import
|
|
10204
|
+
import path6 from "node:path";
|
|
10063
10205
|
function captureCandidate(root, scope, environment = []) {
|
|
10064
10206
|
let checkout;
|
|
10065
10207
|
try {
|
|
@@ -10069,7 +10211,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10069
10211
|
}
|
|
10070
10212
|
const normalizedScope = canonicalScope(scope);
|
|
10071
10213
|
if (!normalizedScope)
|
|
10072
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
10214
|
+
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" }] });
|
|
10073
10215
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
10074
10216
|
if (!roots.ok)
|
|
10075
10217
|
return roots;
|
|
@@ -10101,7 +10243,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
10101
10243
|
for (const item of [...names].sort()) {
|
|
10102
10244
|
if (!scopeMatches(item, normalizedScope))
|
|
10103
10245
|
continue;
|
|
10104
|
-
const target =
|
|
10246
|
+
const target = path6.join(checkout, item);
|
|
10105
10247
|
try {
|
|
10106
10248
|
const stat = fs6.lstatSync(target);
|
|
10107
10249
|
if (stat.isSymbolicLink()) {
|
|
@@ -10220,7 +10362,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
10220
10362
|
return true;
|
|
10221
10363
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
10222
10364
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
10223
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
10365
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
10224
10366
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
10225
10367
|
});
|
|
10226
10368
|
if (passed.length) {
|
|
@@ -10316,7 +10458,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10316
10458
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
10317
10459
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
10318
10460
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
10319
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
10461
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
10462
|
+
requirements: blocking.map((item) => {
|
|
10463
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
10464
|
+
return {
|
|
10465
|
+
requirementId: item.requirementId,
|
|
10466
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
10467
|
+
reason: item.reason,
|
|
10468
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
10469
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
10470
|
+
};
|
|
10471
|
+
})
|
|
10320
10472
|
});
|
|
10321
10473
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
10322
10474
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -10342,12 +10494,12 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10342
10494
|
requirementIds: evaluations.map((item) => item.requirementId)
|
|
10343
10495
|
});
|
|
10344
10496
|
}
|
|
10345
|
-
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
10497
|
+
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"), inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`), relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".", canonicalPath = (value) => {
|
|
10346
10498
|
if (typeof value !== "string" || !value)
|
|
10347
10499
|
return null;
|
|
10348
10500
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
10349
10501
|
return null;
|
|
10350
|
-
const normalized =
|
|
10502
|
+
const normalized = path6.posix.normalize(value);
|
|
10351
10503
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
10352
10504
|
return null;
|
|
10353
10505
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -10389,14 +10541,14 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
|
|
|
10389
10541
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
10390
10542
|
};
|
|
10391
10543
|
}
|
|
10392
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10544
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
10393
10545
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
10394
10546
|
if (stagedDeleted.status !== 0) {
|
|
10395
10547
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
10396
10548
|
if (!/not a git repository/i.test(stderr))
|
|
10397
10549
|
return { paths, uncertain: true, git: true };
|
|
10398
10550
|
} else if (stagedDeleted.stdout) {
|
|
10399
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
10551
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
10400
10552
|
}
|
|
10401
10553
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
10402
10554
|
}, walk = (root, directory, output) => {
|
|
@@ -10410,7 +10562,7 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
|
|
|
10410
10562
|
for (const name of names) {
|
|
10411
10563
|
if (name === ".git" || name === ".workit")
|
|
10412
10564
|
continue;
|
|
10413
|
-
const target =
|
|
10565
|
+
const target = path6.join(directory, name);
|
|
10414
10566
|
let stat;
|
|
10415
10567
|
try {
|
|
10416
10568
|
stat = fs6.lstatSync(target);
|
|
@@ -10427,17 +10579,17 @@ var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex"),
|
|
|
10427
10579
|
}, scopeRoots = (root, scope) => {
|
|
10428
10580
|
const roots = [];
|
|
10429
10581
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
10430
|
-
const target =
|
|
10582
|
+
const target = path6.resolve(root, item);
|
|
10431
10583
|
if (!inside(root, target))
|
|
10432
|
-
return failure("invalid_input",
|
|
10584
|
+
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` }] });
|
|
10433
10585
|
let ancestor = target;
|
|
10434
10586
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
10435
|
-
ancestor =
|
|
10587
|
+
ancestor = path6.dirname(ancestor);
|
|
10436
10588
|
try {
|
|
10437
10589
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
10438
|
-
return failure("invalid_input",
|
|
10590
|
+
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` }] });
|
|
10439
10591
|
} catch {
|
|
10440
|
-
return failure("invalid_input",
|
|
10592
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
10441
10593
|
}
|
|
10442
10594
|
roots.push(target);
|
|
10443
10595
|
}
|
|
@@ -10478,6 +10630,106 @@ var init_task_evaluation = __esm(() => {
|
|
|
10478
10630
|
scopeCovers2 = scopeCovers;
|
|
10479
10631
|
});
|
|
10480
10632
|
|
|
10633
|
+
// packages/workit-core/src/core/methods.ts
|
|
10634
|
+
function selectMethods(policy, capabilities) {
|
|
10635
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
10636
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(id, definition, requirement));
|
|
10637
|
+
if (requirements.length === 0)
|
|
10638
|
+
return [];
|
|
10639
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
10640
|
+
const reasons = [
|
|
10641
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
10642
|
+
...capability.reasons
|
|
10643
|
+
];
|
|
10644
|
+
if (capability.assurance === "unavailable")
|
|
10645
|
+
reasons.unshift("required capability is unavailable");
|
|
10646
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
10647
|
+
});
|
|
10648
|
+
}
|
|
10649
|
+
var METHODS, methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true, assuranceFor = (id, definition, capabilities) => {
|
|
10650
|
+
const keys = new Set([
|
|
10651
|
+
id,
|
|
10652
|
+
...definition.dimensions ?? [],
|
|
10653
|
+
...definition.ruleIds ?? []
|
|
10654
|
+
]);
|
|
10655
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
10656
|
+
if (id === "workit-review" && matches.length === 0)
|
|
10657
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
10658
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
10659
|
+
return {
|
|
10660
|
+
assurance: "unavailable",
|
|
10661
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
10662
|
+
};
|
|
10663
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
10664
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
10665
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
10666
|
+
}, invariantBootstrap = () => `
|
|
10667
|
+
Workit keeps one accountable lead and one shared task state. Inspect current task
|
|
10668
|
+
state before acting. An empty task.list means no session yet, not permission to
|
|
10669
|
+
skip Workit; for user-requested product, debug, or behavior work with no active
|
|
10670
|
+
or paused task, run task.start then policy.assess before other product mutations.
|
|
10671
|
+
Omitted expectedRevision, expectedWorkspaceRevision, and writer workerId default
|
|
10672
|
+
to the current records; explicit values are still concurrency-checked, so never
|
|
10673
|
+
copy revisions between calls. Happy path: task.list, task.start {intent} with no
|
|
10674
|
+
revisions, policy.assess {assessment: {facts, signals, consequences,
|
|
10675
|
+
verification}} where facts are inferred or observed with file refs,
|
|
10676
|
+
writer.acquire {taskId} before product writes, evidence.record {evidence} where
|
|
10677
|
+
check and review kinds auto-bind the current tree and need no digests. Record
|
|
10678
|
+
self-review requirements with kind review and reviewContext matching your own
|
|
10679
|
+
host session; the lead may also record checks. Fresh-context review requires a
|
|
10680
|
+
session distinct from the task creator and every other evidence recorder. Record
|
|
10681
|
+
verification evidence last: later evidence or commits can reopen fixed findings
|
|
10682
|
+
for re-verification, so verify, resolve, then close with nothing in between.
|
|
10683
|
+
decision.record {binding with taskId/workspaceId from inspect plus presented and
|
|
10684
|
+
approvedContent} only through a native approval question asked receipt-shaped:
|
|
10685
|
+
header Workit decision: <purpose>, the same label repeated in the question
|
|
10686
|
+
text (some host UIs do not render headers), exactly two options approved and
|
|
10687
|
+
rejected; ask once, never re-ask to mint a receipt. Before asking, present the
|
|
10688
|
+
item in the conversation (durable artifacts: complete digest plus exact path;
|
|
10689
|
+
inline plans: the content) and keep the question to one short scoped sentence;
|
|
10690
|
+
never ask to approve something unseen. An answer with custom text is steering,
|
|
10691
|
+
not approval: adjust, re-present, then ask a new scoped question. After the user
|
|
10692
|
+
approves a spec or plan, execute the remaining tasks continuously with one
|
|
10693
|
+
atomic commit per task and do not ask whether to continue; stop only for a new
|
|
10694
|
+
product decision, a failed safety or verification gate, a conflicting edit, or
|
|
10695
|
+
missing authority. Record the plan's commit list once through the plan-scoped
|
|
10696
|
+
action approval (git.commit with plan_steps and plan_branch) so each listed
|
|
10697
|
+
commit executes without a new question; an unlisted message needs a fresh
|
|
10698
|
+
approval. task.close {outcome,
|
|
10699
|
+
summary, decisionIds}. A completed native subagent run stops its bound worker
|
|
10700
|
+
by itself; if a worker strands in cancelling with its run verifiably over,
|
|
10701
|
+
repeat worker.cancel {reason} to confirm the stop. Check and review evidence
|
|
10702
|
+
without a bound candidate goes stale; findings close only as fixed with passing
|
|
10703
|
+
verification, dismissed with supporting evidence, or deferred under an approved
|
|
10704
|
+
limitation. Use only the
|
|
10705
|
+
shared operations for task, policy, evidence, finding, decision, worker, writer,
|
|
10706
|
+
and state changes. Authority is bounded by the requested scope, current
|
|
10707
|
+
revision, caller/session provenance, and observed capabilities. Never claim host
|
|
10708
|
+
enforcement or evidence that the host cannot provide. Shell-executed writes are
|
|
10709
|
+
unattested agent-guided work even when a writer is held. Preserve unresolved
|
|
10710
|
+
requirements, gaps, and uncertain workers. Skill routing: slash aliases /wk-*
|
|
10711
|
+
always load on demand. Model-invoked: new instructions, interruptions, or
|
|
10712
|
+
forgotten items mid-task → load workit-steer before responding; about to open
|
|
10713
|
+
a PR or fresh from implementing → load workit-deslop; CI red → load
|
|
10714
|
+
workit-green-run; small-looking change before close or merge → load
|
|
10715
|
+
workit-blast-radius; ambiguous, consequential, or disputed proposal before
|
|
10716
|
+
recommending → load workit-challenge; spec or plan needs a flow diagram →
|
|
10717
|
+
load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
|
|
10718
|
+
act from memory of it.
|
|
10719
|
+
`.trim();
|
|
10720
|
+
var init_methods = __esm(() => {
|
|
10721
|
+
METHODS = {
|
|
10722
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10723
|
+
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10724
|
+
"workit-review": { dimensions: ["review"], ruleIds: ["fresh-context-review"] },
|
|
10725
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10726
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
10727
|
+
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
10728
|
+
"workit-handoff": { ruleIds: ["durable-handoff"] },
|
|
10729
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
10730
|
+
};
|
|
10731
|
+
});
|
|
10732
|
+
|
|
10481
10733
|
// packages/workit-core/src/core/task-context.ts
|
|
10482
10734
|
function reconcileResume(view, observations = []) {
|
|
10483
10735
|
if (observations.length > 0)
|
|
@@ -10487,7 +10739,7 @@ function reconcileResume(view, observations = []) {
|
|
|
10487
10739
|
return candidate;
|
|
10488
10740
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
10489
10741
|
const blockers = [...view.task.progress.blockers];
|
|
10490
|
-
if (view.task.workers.some((entry) =>
|
|
10742
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
10491
10743
|
blockers.push({
|
|
10492
10744
|
reason: "worker state requires reconciliation",
|
|
10493
10745
|
dependentAction: "resume",
|
|
@@ -10509,6 +10761,11 @@ function compactTaskContext(view) {
|
|
|
10509
10761
|
digest: entry.data.digest,
|
|
10510
10762
|
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
10511
10763
|
}));
|
|
10764
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
10765
|
+
id: method.id,
|
|
10766
|
+
assurance: method.assurance,
|
|
10767
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
10768
|
+
})) : [];
|
|
10512
10769
|
const gaps = Array.from(new Set([
|
|
10513
10770
|
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
10514
10771
|
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
@@ -10518,15 +10775,18 @@ function compactTaskContext(view) {
|
|
|
10518
10775
|
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
10519
10776
|
status: view.task.status,
|
|
10520
10777
|
decisions,
|
|
10778
|
+
methods,
|
|
10521
10779
|
gaps,
|
|
10522
10780
|
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
10523
10781
|
};
|
|
10524
10782
|
let encoded = JSON.stringify(context);
|
|
10525
|
-
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.gaps.length)) {
|
|
10783
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
10526
10784
|
if (context.gaps.length)
|
|
10527
10785
|
context.gaps.pop();
|
|
10528
|
-
else
|
|
10786
|
+
else if (context.decisions.length)
|
|
10529
10787
|
context.decisions.pop();
|
|
10788
|
+
else
|
|
10789
|
+
context.methods.pop();
|
|
10530
10790
|
encoded = JSON.stringify(context);
|
|
10531
10791
|
}
|
|
10532
10792
|
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
@@ -10562,58 +10822,17 @@ var COMPACT_MAX_BYTES = 4096, COMPACT_MAX_ITEMS = 8, COMPACT_TEXT_BYTES = 128, s
|
|
|
10562
10822
|
task: bundle.task
|
|
10563
10823
|
});
|
|
10564
10824
|
var init_task_context = __esm(() => {
|
|
10825
|
+
init_workers();
|
|
10565
10826
|
init_task_contract();
|
|
10566
10827
|
init_task_evaluation();
|
|
10828
|
+
init_methods();
|
|
10567
10829
|
sensitiveUrl = /\bhttps?:\/\/[^/\s:@]+:[^@\s]+@/gi;
|
|
10568
10830
|
sensitiveValue = /(?:token|secret|password|passwd|api[-_ ]?key|private[-_ ]?key|authorization)\s*[:=]\s*(?:bearer\s+)?\S+|\bbearer\s+\S+/gi;
|
|
10569
10831
|
privateKey = /-----BEGIN [^-]*PRIVATE KEY-----[\s\S]*?-----END [^-]*PRIVATE KEY-----/gi;
|
|
10570
10832
|
});
|
|
10571
10833
|
|
|
10572
|
-
// packages/workit-core/src/core/
|
|
10573
|
-
var
|
|
10574
|
-
Workit keeps one accountable lead and one shared task state. Inspect current task
|
|
10575
|
-
state before acting. An empty task.list means no session yet, not permission to
|
|
10576
|
-
skip Workit; for user-requested product, debug, or behavior work with no active
|
|
10577
|
-
or paused task, run task.start then policy.assess before other product mutations.
|
|
10578
|
-
Omitted expectedRevision, expectedWorkspaceRevision, and writer workerId default
|
|
10579
|
-
to the current records; explicit values are still concurrency-checked, so never
|
|
10580
|
-
copy revisions between calls. Happy path: task.list, task.start {intent} with no
|
|
10581
|
-
revisions, policy.assess {assessment: {facts, signals, consequences,
|
|
10582
|
-
verification}} where facts are inferred or observed with file refs,
|
|
10583
|
-
writer.acquire {taskId} before product writes, evidence.record {evidence} where
|
|
10584
|
-
check and review kinds auto-bind the current tree and need no digests. Record
|
|
10585
|
-
self-review requirements with kind review and reviewContext matching your own
|
|
10586
|
-
host session; the lead may also record checks. Fresh-context review requires a
|
|
10587
|
-
session distinct from the task creator and every other evidence recorder. Record
|
|
10588
|
-
verification evidence last: later evidence or commits can reopen fixed findings
|
|
10589
|
-
for re-verification, so verify, resolve, then close with nothing in between.
|
|
10590
|
-
decision.record {binding with taskId/workspaceId from inspect plus presented and
|
|
10591
|
-
approvedContent} only through a native approval question asked receipt-shaped:
|
|
10592
|
-
header Workit decision: <purpose>, the same label repeated in the question
|
|
10593
|
-
text (some host UIs do not render headers), exactly two options approved and
|
|
10594
|
-
rejected; ask once, never re-ask to mint a receipt. task.close {outcome,
|
|
10595
|
-
summary, decisionIds}. A completed native subagent run stops its bound worker
|
|
10596
|
-
by itself; if a worker strands in cancelling with its run verifiably over,
|
|
10597
|
-
repeat worker.cancel {reason} to confirm the stop. Check and review evidence
|
|
10598
|
-
without a bound candidate goes stale; findings close only as fixed with passing
|
|
10599
|
-
verification, dismissed with supporting evidence, or deferred under an approved
|
|
10600
|
-
limitation. Use only the
|
|
10601
|
-
shared operations for task, policy, evidence, finding, decision, worker, writer,
|
|
10602
|
-
and state changes. Authority is bounded by the requested scope, current
|
|
10603
|
-
revision, caller/session provenance, and observed capabilities. Never claim host
|
|
10604
|
-
enforcement or evidence that the host cannot provide. Shell-executed writes are
|
|
10605
|
-
unattested agent-guided work even when a writer is held. Preserve unresolved
|
|
10606
|
-
requirements, gaps, and uncertain workers. Skill routing: slash aliases /wk-*
|
|
10607
|
-
always load on demand. Model-invoked: new instructions, interruptions, or
|
|
10608
|
-
forgotten items mid-task → load workit-steer before responding; about to open
|
|
10609
|
-
a PR or fresh from implementing → load workit-deslop; CI red → load
|
|
10610
|
-
workit-green-run; small-looking change before close or merge → load
|
|
10611
|
-
workit-blast-radius; ambiguous, consequential, or disputed proposal before
|
|
10612
|
-
recommending → load workit-challenge; spec or plan needs a flow diagram →
|
|
10613
|
-
load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
|
|
10614
|
-
act from memory of it.
|
|
10615
|
-
`.trim();
|
|
10616
|
-
var init_methods = () => {};
|
|
10834
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
10835
|
+
var init_route_intent = () => {};
|
|
10617
10836
|
|
|
10618
10837
|
// packages/workit-core/src/core/external-action.ts
|
|
10619
10838
|
var externalActionSchema;
|
|
@@ -10632,7 +10851,11 @@ var init_external_action = __esm(() => {
|
|
|
10632
10851
|
}).strict(),
|
|
10633
10852
|
object({
|
|
10634
10853
|
operation: literal("git.commit"),
|
|
10635
|
-
payload: object({
|
|
10854
|
+
payload: object({
|
|
10855
|
+
message: string2().min(1).optional(),
|
|
10856
|
+
plan_steps: array(string2().min(1)).optional(),
|
|
10857
|
+
plan_branch: string2().min(1).optional()
|
|
10858
|
+
}).strict()
|
|
10636
10859
|
}).strict(),
|
|
10637
10860
|
object({
|
|
10638
10861
|
operation: literal("git.push"),
|
|
@@ -10764,7 +10987,7 @@ function resolveRequirements(input) {
|
|
|
10764
10987
|
].join(", ")}`,
|
|
10765
10988
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
10766
10989
|
before: "dependent_action",
|
|
10767
|
-
dependentAction:
|
|
10990
|
+
dependentAction: null,
|
|
10768
10991
|
acceptanceAllowed: false
|
|
10769
10992
|
}));
|
|
10770
10993
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -10775,7 +10998,7 @@ function resolveRequirements(input) {
|
|
|
10775
10998
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
10776
10999
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
10777
11000
|
before: "dependent_action",
|
|
10778
|
-
dependentAction:
|
|
11001
|
+
dependentAction: null,
|
|
10779
11002
|
acceptanceAllowed: false
|
|
10780
11003
|
}));
|
|
10781
11004
|
if (signals.behaviorChange.value === true)
|
|
@@ -11027,58 +11250,6 @@ var init_policy_resolver = __esm(() => {
|
|
|
11027
11250
|
}).strict();
|
|
11028
11251
|
});
|
|
11029
11252
|
|
|
11030
|
-
// packages/workit-core/src/core/workers.ts
|
|
11031
|
-
function assertProductWriteAllowed(input) {
|
|
11032
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
11033
|
-
return failure("invalid_input", "invalid product write path");
|
|
11034
|
-
if (!input.store)
|
|
11035
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
11036
|
-
const task = input.store.readTask(input.task.id);
|
|
11037
|
-
if (!task.ok)
|
|
11038
|
-
return task;
|
|
11039
|
-
const workspace = input.store.readWorkspace();
|
|
11040
|
-
if (!workspace.ok)
|
|
11041
|
-
return workspace;
|
|
11042
|
-
if (!workspace.data)
|
|
11043
|
-
return failure("not_found", "workspace not found");
|
|
11044
|
-
const workspaceRecord = workspace.data;
|
|
11045
|
-
if (task.data.status !== "active")
|
|
11046
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
11047
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
11048
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
11049
|
-
const writer = workspaceRecord.writer;
|
|
11050
|
-
if (!writer)
|
|
11051
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
11052
|
-
if (writer.state === "uncertain")
|
|
11053
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
11054
|
-
owner: writer.owner
|
|
11055
|
-
});
|
|
11056
|
-
const caller = callerValue(input.caller);
|
|
11057
|
-
const workerId = workerIdOf(input.caller);
|
|
11058
|
-
const owner = writer.owner;
|
|
11059
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
11060
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
11061
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
11062
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
11063
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
11064
|
-
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))))
|
|
11065
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
11066
|
-
return success(null, workspaceRecord.revision, owner);
|
|
11067
|
-
}
|
|
11068
|
-
var same = (left, right) => {
|
|
11069
|
-
try {
|
|
11070
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
11071
|
-
} catch {
|
|
11072
|
-
return false;
|
|
11073
|
-
}
|
|
11074
|
-
}, callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor }, validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
11075
|
-
const code = char.charCodeAt(0);
|
|
11076
|
-
return code < 32 || code === 127;
|
|
11077
|
-
}) && !value.split(/[\\/]/).includes(".."), callerValue = (value) => ({ host: value.host, actor: value.actor }), workerIdOf = (value) => value.workerId ?? null;
|
|
11078
|
-
var init_workers = __esm(() => {
|
|
11079
|
-
init_task_contract();
|
|
11080
|
-
});
|
|
11081
|
-
|
|
11082
11253
|
// packages/workit-core/src/core/task-engine.ts
|
|
11083
11254
|
import { realpathSync as realpathSync4 } from "node:fs";
|
|
11084
11255
|
|
|
@@ -11140,7 +11311,7 @@ class WorkitCore {
|
|
|
11140
11311
|
activeWorkerBlocker(task, workspace) {
|
|
11141
11312
|
if (workspace.writer)
|
|
11142
11313
|
return failure("recovery_required", "writer ownership must be released first");
|
|
11143
|
-
if (task.workers.some((entry) =>
|
|
11314
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
11144
11315
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
11145
11316
|
return null;
|
|
11146
11317
|
}
|
|
@@ -11377,11 +11548,6 @@ class WorkitCore {
|
|
|
11377
11548
|
if (helper && !helper.ok)
|
|
11378
11549
|
return helper;
|
|
11379
11550
|
const evidence = input.evidence;
|
|
11380
|
-
if (helper?.ok) {
|
|
11381
|
-
const assignment = helper.data.data.assignment;
|
|
11382
|
-
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))
|
|
11383
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
11384
|
-
}
|
|
11385
11551
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
11386
11552
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
11387
11553
|
if (evidence.kind === "review") {
|
|
@@ -11398,6 +11564,11 @@ class WorkitCore {
|
|
|
11398
11564
|
evidence.beforeCandidateId ??= null;
|
|
11399
11565
|
evidence.candidateId ??= null;
|
|
11400
11566
|
}
|
|
11567
|
+
if (helper?.ok) {
|
|
11568
|
+
const assignment = helper.data.data.assignment;
|
|
11569
|
+
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))
|
|
11570
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
11571
|
+
}
|
|
11401
11572
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
11402
11573
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
11403
11574
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -11904,7 +12075,12 @@ class WorkitCore {
|
|
|
11904
12075
|
return failure("invalid_transition", "worker is not awaiting dispatch");
|
|
11905
12076
|
return success(mutation.revision, null, {
|
|
11906
12077
|
...current,
|
|
11907
|
-
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
12078
|
+
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
12079
|
+
...item,
|
|
12080
|
+
recordedAt: mutation.now,
|
|
12081
|
+
provenance: attested.data,
|
|
12082
|
+
data: { ...item.data, state: "dispatching" }
|
|
12083
|
+
} : item)
|
|
11908
12084
|
});
|
|
11909
12085
|
}
|
|
11910
12086
|
});
|
|
@@ -12288,6 +12464,9 @@ class WorkitCore {
|
|
|
12288
12464
|
id: task.id,
|
|
12289
12465
|
revision: task.revision,
|
|
12290
12466
|
workspaceRevision: workspace.data.revision,
|
|
12467
|
+
createdAt: task.createdAt,
|
|
12468
|
+
updatedAt: task.updatedAt,
|
|
12469
|
+
runtime: task.runtime ?? null,
|
|
12291
12470
|
objective: task.intent.data.objective,
|
|
12292
12471
|
status: task.status,
|
|
12293
12472
|
closure: task.closure,
|
|
@@ -12373,7 +12552,7 @@ class WorkitCore {
|
|
|
12373
12552
|
const base = reconcileResume(effectiveView);
|
|
12374
12553
|
if (!base.ok)
|
|
12375
12554
|
return base;
|
|
12376
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
12555
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
12377
12556
|
const blockers = [...base.data.blockers];
|
|
12378
12557
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
12379
12558
|
blockers.push({
|
|
@@ -12440,7 +12619,8 @@ class WorkitCore {
|
|
|
12440
12619
|
...current,
|
|
12441
12620
|
status,
|
|
12442
12621
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
12443
|
-
progress:
|
|
12622
|
+
progress: current.progress,
|
|
12623
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
12444
12624
|
})
|
|
12445
12625
|
});
|
|
12446
12626
|
if (!changed.ok)
|
|
@@ -12476,7 +12656,7 @@ class WorkitCore {
|
|
|
12476
12656
|
return workspace;
|
|
12477
12657
|
if (!workspace.data)
|
|
12478
12658
|
return failure("not_found", "workspace not found");
|
|
12479
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
12659
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
12480
12660
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
12481
12661
|
this.fillRevisions(input, task.data, workspace.data);
|
|
12482
12662
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -12664,10 +12844,10 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
|
|
|
12664
12844
|
const notStarted = input.dispatch === true;
|
|
12665
12845
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
12666
12846
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
12667
|
-
if (notStarted &&
|
|
12668
|
-
return failure("invalid_transition", "worker is no longer awaiting dispatch");
|
|
12669
|
-
if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
|
|
12847
|
+
if (notStarted && entry.data.session !== null)
|
|
12670
12848
|
return failure("invalid_transition", "worker already has an observed session");
|
|
12849
|
+
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
12850
|
+
return failure("invalid_transition", "worker launch was not claimed");
|
|
12671
12851
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
12672
12852
|
return failure("invalid_input", "running worker observations require a session");
|
|
12673
12853
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -12676,15 +12856,20 @@ var provenance = (context, kind = context.provenanceKind ?? "host_observed") =>
|
|
|
12676
12856
|
return failure("invalid_transition", "worker has not started");
|
|
12677
12857
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
12678
12858
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
12859
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
12679
12860
|
const nextEntry = {
|
|
12680
12861
|
...entry,
|
|
12681
12862
|
recordedAt: input.now ?? entry.recordedAt,
|
|
12682
12863
|
provenance: authority.provenance,
|
|
12683
|
-
data: {
|
|
12864
|
+
data: {
|
|
12865
|
+
...entry.data,
|
|
12866
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
12867
|
+
session: input.session
|
|
12868
|
+
}
|
|
12684
12869
|
};
|
|
12685
12870
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
12686
12871
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
12687
|
-
if (entry.data.state ===
|
|
12872
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
12688
12873
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
12689
12874
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
12690
12875
|
taskId: task.data.id,
|
|
@@ -13002,6 +13187,7 @@ var init_core3 = __esm(() => {
|
|
|
13002
13187
|
init_task_store();
|
|
13003
13188
|
init_task_context();
|
|
13004
13189
|
init_methods();
|
|
13190
|
+
init_route_intent();
|
|
13005
13191
|
init_authority();
|
|
13006
13192
|
init_external_action();
|
|
13007
13193
|
init_external_action();
|
|
@@ -13011,8 +13197,8 @@ var init_core3 = __esm(() => {
|
|
|
13011
13197
|
});
|
|
13012
13198
|
|
|
13013
13199
|
// packages/workit-cursor/hooks/workit-hook.ts
|
|
13014
|
-
import { existsSync as
|
|
13015
|
-
import
|
|
13200
|
+
import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
|
|
13201
|
+
import path7 from "node:path";
|
|
13016
13202
|
var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapabilities = (availability = {}) => {
|
|
13017
13203
|
const has = (name) => availability[name] === true;
|
|
13018
13204
|
return [
|
|
@@ -13044,6 +13230,13 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
13044
13230
|
reason: has("subagentStart") ? "Cursor subagentStart enforces explicit reviewer/investigator markers; implementer delegation is unavailable" : "Cursor subagentStart is absent",
|
|
13045
13231
|
refs: [hostRef("subagentStart")]
|
|
13046
13232
|
},
|
|
13233
|
+
{
|
|
13234
|
+
name: "fresh-context-review",
|
|
13235
|
+
surface: "subagentStart",
|
|
13236
|
+
assurance: has("subagentStart") ? "agent_guided" : "unavailable",
|
|
13237
|
+
reason: has("subagentStart") ? "independent review runs as a bounded reviewer subagent; stops lack stable identity, so reviewer exclusivity is evaluated from recorded evidence" : "Cursor subagentStart is unavailable for independent review",
|
|
13238
|
+
refs: [hostRef("subagentStart")]
|
|
13239
|
+
},
|
|
13047
13240
|
{
|
|
13048
13241
|
name: "arbitrary_shell_write",
|
|
13049
13242
|
surface: "unobservable_shell",
|
|
@@ -13066,7 +13259,7 @@ var hostRef = (handle) => ({ kind: "host", host: "cursor", handle }), cursorCapa
|
|
|
13066
13259
|
if (!Array.isArray(roots) || roots.length !== 1 || !roots.every(nonEmpty2))
|
|
13067
13260
|
return { ok: false, error: "exactly one workspace root is required" };
|
|
13068
13261
|
const root = roots[0];
|
|
13069
|
-
if (!
|
|
13262
|
+
if (!path7.isAbsolute(root) || !existsSync4(root))
|
|
13070
13263
|
return { ok: false, error: "workspace root must be an existing absolute path" };
|
|
13071
13264
|
const event = value.hook_event_name;
|
|
13072
13265
|
const conversationId = nonEmpty2(value.conversation_id) ? value.conversation_id : undefined;
|