@brainervirus/workit-mcp 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/dist/index.js +357 -221
- package/package.json +2 -2
- package/src/server.ts +51 -8
package/dist/index.js
CHANGED
|
@@ -12477,8 +12477,8 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
12477
12477
|
|
|
12478
12478
|
// packages/workit-core/src/core.ts
|
|
12479
12479
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
12480
|
-
import { existsSync as
|
|
12481
|
-
import
|
|
12480
|
+
import { existsSync as existsSync4, realpathSync as realpathSync5 } from "node:fs";
|
|
12481
|
+
import path7 from "node:path";
|
|
12482
12482
|
|
|
12483
12483
|
// packages/workit-core/src/core/task-contract.ts
|
|
12484
12484
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -20270,6 +20270,7 @@ var decisionSchema = object4({
|
|
|
20270
20270
|
scope: scopeSchema,
|
|
20271
20271
|
presented: text,
|
|
20272
20272
|
approvedContent: text,
|
|
20273
|
+
displayed: text.optional(),
|
|
20273
20274
|
contentRefs: array3(refSchema)
|
|
20274
20275
|
}).strict(),
|
|
20275
20276
|
digest,
|
|
@@ -20308,7 +20309,7 @@ var workerReportSchema = object4({
|
|
|
20308
20309
|
}).strict();
|
|
20309
20310
|
var workerSchema = object4({
|
|
20310
20311
|
assignment: assignmentSchema,
|
|
20311
|
-
state: _enum2(["assigned", "running", "cancelling", "stopped", "unknown"]),
|
|
20312
|
+
state: _enum2(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
20312
20313
|
session: refSchema.nullable(),
|
|
20313
20314
|
report: workerReportSchema.nullable()
|
|
20314
20315
|
}).strict();
|
|
@@ -20348,6 +20349,10 @@ var actionProgressListSchema = array3(actionProgressSchema).check((ctx) => {
|
|
|
20348
20349
|
path: ["decisionId"]
|
|
20349
20350
|
});
|
|
20350
20351
|
});
|
|
20352
|
+
var runtimeSchema = object4({
|
|
20353
|
+
createdWith: text.nullable(),
|
|
20354
|
+
updatedWith: text
|
|
20355
|
+
}).strict();
|
|
20351
20356
|
var taskRecordSchema = object4({
|
|
20352
20357
|
schemaVersion: literal3(1),
|
|
20353
20358
|
id,
|
|
@@ -20361,6 +20366,8 @@ var taskRecordSchema = object4({
|
|
|
20361
20366
|
status: _enum2(["active", "paused", "closed"]),
|
|
20362
20367
|
closure: closureSchema.nullable(),
|
|
20363
20368
|
progress: progressSchema,
|
|
20369
|
+
pauseReason: text.nullable().optional(),
|
|
20370
|
+
runtime: runtimeSchema.optional(),
|
|
20364
20371
|
assessments: array3(entrySchema(assessmentSchema)),
|
|
20365
20372
|
policy: policySchema.nullable(),
|
|
20366
20373
|
policyChanges: array3(policyChangeSchema),
|
|
@@ -20376,6 +20383,7 @@ var workspaceRecordSchema = object4({
|
|
|
20376
20383
|
id,
|
|
20377
20384
|
revision,
|
|
20378
20385
|
root: nonEmpty,
|
|
20386
|
+
runtime: runtimeSchema.optional(),
|
|
20379
20387
|
writer: object4({ state: _enum2(["held", "uncertain"]), owner: ownerSchema, acquiredAt: utc }).strict().nullable()
|
|
20380
20388
|
}).strict();
|
|
20381
20389
|
var capabilitySchema = object4({
|
|
@@ -20403,6 +20411,9 @@ var taskSummarySchema = object4({
|
|
|
20403
20411
|
id,
|
|
20404
20412
|
revision,
|
|
20405
20413
|
workspaceRevision: revision,
|
|
20414
|
+
createdAt: utc,
|
|
20415
|
+
updatedAt: utc,
|
|
20416
|
+
runtime: runtimeSchema.nullable(),
|
|
20406
20417
|
objective: text,
|
|
20407
20418
|
status: _enum2(["active", "paused", "closed"]),
|
|
20408
20419
|
closure: closureSchema.nullable(),
|
|
@@ -20473,7 +20484,7 @@ var taskOperations = {
|
|
|
20473
20484
|
expectedWorkspaceRevision: revision.optional(),
|
|
20474
20485
|
outcome: outcomeSchema,
|
|
20475
20486
|
summary: text,
|
|
20476
|
-
decisionIds: array3(id)
|
|
20487
|
+
decisionIds: array3(id).optional()
|
|
20477
20488
|
})
|
|
20478
20489
|
};
|
|
20479
20490
|
var policyOperations = {
|
|
@@ -20565,14 +20576,15 @@ var writerOperations = {
|
|
|
20565
20576
|
...taskId,
|
|
20566
20577
|
expectedRevision: revision.optional(),
|
|
20567
20578
|
expectedWorkspaceRevision: revision.optional(),
|
|
20568
|
-
workerId: nullableId.optional()
|
|
20579
|
+
workerId: nullableId.optional(),
|
|
20580
|
+
reason: text.optional()
|
|
20569
20581
|
}),
|
|
20570
20582
|
release: operation({
|
|
20571
20583
|
action: literal3("release"),
|
|
20572
20584
|
...taskId,
|
|
20573
20585
|
expectedRevision: revision.optional(),
|
|
20574
20586
|
expectedWorkspaceRevision: revision.optional(),
|
|
20575
|
-
reason: text
|
|
20587
|
+
reason: text.optional()
|
|
20576
20588
|
})
|
|
20577
20589
|
};
|
|
20578
20590
|
var stateOperations = {
|
|
@@ -20779,7 +20791,26 @@ function requirementId(input) {
|
|
|
20779
20791
|
import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
|
|
20780
20792
|
import * as fs4 from "node:fs";
|
|
20781
20793
|
import { hostname as hostname2 } from "node:os";
|
|
20782
|
-
import
|
|
20794
|
+
import path4 from "node:path";
|
|
20795
|
+
|
|
20796
|
+
// packages/workit-core/src/core/package-root.ts
|
|
20797
|
+
import { existsSync } from "node:fs";
|
|
20798
|
+
import path from "node:path";
|
|
20799
|
+
import { fileURLToPath } from "node:url";
|
|
20800
|
+
var packageRoot = () => {
|
|
20801
|
+
let dir = path.dirname(fileURLToPath(import.meta.url));
|
|
20802
|
+
while (true) {
|
|
20803
|
+
const parent = path.dirname(dir);
|
|
20804
|
+
if (existsSync(path.join(dir, "package.json")) || parent === dir)
|
|
20805
|
+
return dir;
|
|
20806
|
+
dir = parent;
|
|
20807
|
+
}
|
|
20808
|
+
};
|
|
20809
|
+
var assetRoot = () => {
|
|
20810
|
+
const root = packageRoot();
|
|
20811
|
+
const assets = path.join(root, "assets");
|
|
20812
|
+
return existsSync(assets) ? assets : root;
|
|
20813
|
+
};
|
|
20783
20814
|
|
|
20784
20815
|
// node_modules/@openclaw/fs-safe/dist/errors.js
|
|
20785
20816
|
var OPERATIONAL_CODES = new Set([
|
|
@@ -20830,7 +20861,7 @@ function sameFileIdentity(left, right, platform = process.platform) {
|
|
|
20830
20861
|
// node_modules/@openclaw/fs-safe/dist/sidecar-lock-reclaim.js
|
|
20831
20862
|
import { randomBytes } from "node:crypto";
|
|
20832
20863
|
import fsSync from "node:fs";
|
|
20833
|
-
import
|
|
20864
|
+
import path2 from "node:path";
|
|
20834
20865
|
|
|
20835
20866
|
// node_modules/@openclaw/fs-safe/dist/bounded-read.js
|
|
20836
20867
|
import fs from "node:fs";
|
|
@@ -20920,13 +20951,13 @@ ${ownershipToken}
|
|
|
20920
20951
|
return { raw, ownershipToken };
|
|
20921
20952
|
}
|
|
20922
20953
|
function relativeSidecarLockPath(lockRoot, lockPath) {
|
|
20923
|
-
const resolved =
|
|
20924
|
-
const lexicalRelative =
|
|
20925
|
-
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${
|
|
20926
|
-
if (!relative || relative === ".." || relative.startsWith(`..${
|
|
20954
|
+
const resolved = path2.resolve(lockPath);
|
|
20955
|
+
const lexicalRelative = path2.relative(lockRoot.rootDir, resolved);
|
|
20956
|
+
const relative = lexicalRelative !== ".." && !lexicalRelative.startsWith(`..${path2.sep}`) && !path2.isAbsolute(lexicalRelative) ? lexicalRelative : path2.relative(lockRoot.rootReal, resolved);
|
|
20957
|
+
if (!relative || relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
20927
20958
|
throw new FsSafeError("outside-workspace", "sidecar lock path is outside lockRoot");
|
|
20928
20959
|
}
|
|
20929
|
-
return relative.split(
|
|
20960
|
+
return relative.split(path2.sep).join(path2.posix.sep);
|
|
20930
20961
|
}
|
|
20931
20962
|
function parseSidecarLockPayload(raw, parser) {
|
|
20932
20963
|
if (parser) {
|
|
@@ -21073,7 +21104,7 @@ function getFsSafeLockConfig() {
|
|
|
21073
21104
|
|
|
21074
21105
|
// node_modules/@openclaw/fs-safe/dist/file-lock-sync.js
|
|
21075
21106
|
import fs3 from "node:fs";
|
|
21076
|
-
import
|
|
21107
|
+
import path3 from "node:path";
|
|
21077
21108
|
|
|
21078
21109
|
// node_modules/@openclaw/fs-safe/dist/timing.js
|
|
21079
21110
|
function sleepSync(ms) {
|
|
@@ -21164,26 +21195,26 @@ function canonicalLockParentSync(parent) {
|
|
|
21164
21195
|
return process.platform === "win32" ? fs3.realpathSync.native(parent) : fs3.realpathSync(parent);
|
|
21165
21196
|
}
|
|
21166
21197
|
function normalizeTargetPath(targetPath) {
|
|
21167
|
-
const resolved =
|
|
21168
|
-
fs3.mkdirSync(
|
|
21198
|
+
const resolved = path3.resolve(targetPath);
|
|
21199
|
+
fs3.mkdirSync(path3.dirname(resolved), { recursive: true });
|
|
21169
21200
|
try {
|
|
21170
|
-
return
|
|
21201
|
+
return path3.join(canonicalLockParentSync(path3.dirname(resolved)), path3.basename(resolved));
|
|
21171
21202
|
} catch {
|
|
21172
21203
|
return resolved;
|
|
21173
21204
|
}
|
|
21174
21205
|
}
|
|
21175
21206
|
function boundedLockPath(lockPath, lockRoot) {
|
|
21176
|
-
const resolved =
|
|
21207
|
+
const resolved = path3.resolve(lockPath);
|
|
21177
21208
|
if (!lockRoot)
|
|
21178
21209
|
return resolved;
|
|
21179
21210
|
relativeSidecarLockPath(lockRoot, resolved);
|
|
21180
|
-
const parent =
|
|
21211
|
+
const parent = path3.dirname(resolved);
|
|
21181
21212
|
const parentReal = canonicalLockParentSync(parent);
|
|
21182
|
-
const parentRelative =
|
|
21183
|
-
if (parentRelative === ".." || parentRelative.startsWith(`..${
|
|
21213
|
+
const parentRelative = path3.relative(lockRoot.rootReal, parentReal);
|
|
21214
|
+
if (parentRelative === ".." || parentRelative.startsWith(`..${path3.sep}`) || path3.isAbsolute(parentRelative)) {
|
|
21184
21215
|
throw new FsSafeError("outside-workspace", "sidecar lock parent is outside lockRoot");
|
|
21185
21216
|
}
|
|
21186
|
-
return
|
|
21217
|
+
return path3.join(parentReal, path3.basename(resolved));
|
|
21187
21218
|
}
|
|
21188
21219
|
function defaultShouldReclaim(snapshot, staleMs, nowMs) {
|
|
21189
21220
|
const createdAtMs = sidecarLockPayloadCreatedAtMs(snapshot.payload);
|
|
@@ -21396,6 +21427,33 @@ var metadataLockSchema = object4({
|
|
|
21396
21427
|
nonce: string5().min(1)
|
|
21397
21428
|
}).strict();
|
|
21398
21429
|
var now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
|
|
21430
|
+
var cachedRuntimeVersion = null;
|
|
21431
|
+
var runtimeVersion = () => {
|
|
21432
|
+
if (cachedRuntimeVersion === null) {
|
|
21433
|
+
try {
|
|
21434
|
+
const pkg = JSON.parse(fs4.readFileSync(path4.join(packageRoot(), "package.json"), "utf8"));
|
|
21435
|
+
cachedRuntimeVersion = typeof pkg.version === "string" && pkg.version ? pkg.version : "0.0.0";
|
|
21436
|
+
} catch {
|
|
21437
|
+
cachedRuntimeVersion = "0.0.0";
|
|
21438
|
+
}
|
|
21439
|
+
}
|
|
21440
|
+
return cachedRuntimeVersion;
|
|
21441
|
+
};
|
|
21442
|
+
var stampNew = (record) => ({
|
|
21443
|
+
createdWith: record.runtime?.createdWith ?? null,
|
|
21444
|
+
updatedWith: runtimeVersion()
|
|
21445
|
+
});
|
|
21446
|
+
var isNewerVersion = (candidate, current) => {
|
|
21447
|
+
const parts = (value) => value.split("-")[0].split(".").map((item) => Number.parseInt(item, 10) || 0);
|
|
21448
|
+
const [next, now] = [parts(candidate), parts(current)];
|
|
21449
|
+
for (let index = 0;index < 3; index += 1) {
|
|
21450
|
+
const left = next[index] ?? 0;
|
|
21451
|
+
const right = now[index] ?? 0;
|
|
21452
|
+
if (left !== right)
|
|
21453
|
+
return left > right;
|
|
21454
|
+
}
|
|
21455
|
+
return false;
|
|
21456
|
+
};
|
|
21399
21457
|
var jsonBytes = (value) => `${canonicalJson(value)}
|
|
21400
21458
|
`;
|
|
21401
21459
|
var digestBytes = (value) => createHash2("sha256").update(value).digest("hex");
|
|
@@ -21422,12 +21480,12 @@ var sameMetadataLock = (left, right) => {
|
|
|
21422
21480
|
class TaskStore {
|
|
21423
21481
|
root;
|
|
21424
21482
|
constructor(root) {
|
|
21425
|
-
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) :
|
|
21483
|
+
this.root = fs4.existsSync(root) ? fs4.realpathSync(root) : path4.resolve(root);
|
|
21426
21484
|
}
|
|
21427
21485
|
readTask(taskId) {
|
|
21428
21486
|
if (!validId(taskId))
|
|
21429
21487
|
return failure("invalid_input", "task ID is invalid", { taskId });
|
|
21430
|
-
const result = this.readRecord(
|
|
21488
|
+
const result = this.readRecord(path4.join(this.tasksDir, `${taskId}.json`), taskRecordSchema);
|
|
21431
21489
|
if (!result.exists)
|
|
21432
21490
|
return failure("not_found", "task not found", { taskId });
|
|
21433
21491
|
if (result.result.ok && result.result.data.id !== taskId)
|
|
@@ -21461,7 +21519,7 @@ class TaskStore {
|
|
|
21461
21519
|
});
|
|
21462
21520
|
const tasks = [];
|
|
21463
21521
|
for (const name of names.sort()) {
|
|
21464
|
-
const item = this.readRecord(
|
|
21522
|
+
const item = this.readRecord(path4.join(this.tasksDir, name), taskRecordSchema);
|
|
21465
21523
|
if (!item.exists)
|
|
21466
21524
|
continue;
|
|
21467
21525
|
if (!item.result.ok)
|
|
@@ -21495,7 +21553,7 @@ class TaskStore {
|
|
|
21495
21553
|
if (!current.ok)
|
|
21496
21554
|
return current;
|
|
21497
21555
|
if (current.data ? value.expectedWorkspaceRevision !== current.data.revision : value.expectedWorkspaceRevision !== null) {
|
|
21498
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
21556
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21499
21557
|
expectedWorkspaceRevision: value.expectedWorkspaceRevision,
|
|
21500
21558
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
21501
21559
|
});
|
|
@@ -21507,11 +21565,17 @@ class TaskStore {
|
|
|
21507
21565
|
const previousWorkspaceBytes = current.data ? this.snapshotBytes(this.workspacePath) : null;
|
|
21508
21566
|
if (current.data && !previousWorkspaceBytes)
|
|
21509
21567
|
return failure("storage_error", "workspace snapshot disappeared during creation");
|
|
21510
|
-
const workspace = current.data ? {
|
|
21568
|
+
const workspace = current.data ? {
|
|
21569
|
+
...current.data,
|
|
21570
|
+
revision: newRevision(),
|
|
21571
|
+
root: this.root,
|
|
21572
|
+
runtime: stampNew(current.data)
|
|
21573
|
+
} : {
|
|
21511
21574
|
schemaVersion: SCHEMA_VERSION,
|
|
21512
21575
|
id: newId(),
|
|
21513
21576
|
revision: newRevision(),
|
|
21514
21577
|
root: this.root,
|
|
21578
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21515
21579
|
writer: null
|
|
21516
21580
|
};
|
|
21517
21581
|
const timestamp = value.now ?? now();
|
|
@@ -21533,6 +21597,7 @@ class TaskStore {
|
|
|
21533
21597
|
status: "active",
|
|
21534
21598
|
closure: null,
|
|
21535
21599
|
progress: { summary: "", nextAction: null, blockers: [] },
|
|
21600
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21536
21601
|
assessments: [],
|
|
21537
21602
|
policy: null,
|
|
21538
21603
|
policyChanges: [],
|
|
@@ -21563,7 +21628,7 @@ class TaskStore {
|
|
|
21563
21628
|
if (!current.ok)
|
|
21564
21629
|
return current;
|
|
21565
21630
|
if (current.data ? input.expectedWorkspaceRevision !== current.data.revision : input.expectedWorkspaceRevision !== null)
|
|
21566
|
-
return failure("revision_conflict", "workspace revision does not match", {
|
|
21631
|
+
return failure("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", {
|
|
21567
21632
|
expectedWorkspaceRevision: input.expectedWorkspaceRevision,
|
|
21568
21633
|
actualWorkspaceRevision: current.data?.revision ?? null
|
|
21569
21634
|
});
|
|
@@ -21571,11 +21636,17 @@ class TaskStore {
|
|
|
21571
21636
|
if (current.data && !previousWorkspaceBytes)
|
|
21572
21637
|
return failure("storage_error", "workspace snapshot disappeared during import");
|
|
21573
21638
|
const timestamp = input.now ?? now();
|
|
21574
|
-
const workspace = current.data ? {
|
|
21639
|
+
const workspace = current.data ? {
|
|
21640
|
+
...current.data,
|
|
21641
|
+
revision: newRevision(),
|
|
21642
|
+
root: this.root,
|
|
21643
|
+
runtime: stampNew(current.data)
|
|
21644
|
+
} : {
|
|
21575
21645
|
schemaVersion: SCHEMA_VERSION,
|
|
21576
21646
|
id: input.workspaceId ?? newId(),
|
|
21577
21647
|
revision: newRevision(),
|
|
21578
21648
|
root: this.root,
|
|
21649
|
+
runtime: { createdWith: runtimeVersion(), updatedWith: runtimeVersion() },
|
|
21579
21650
|
writer: null
|
|
21580
21651
|
};
|
|
21581
21652
|
const task = {
|
|
@@ -21583,7 +21654,11 @@ class TaskStore {
|
|
|
21583
21654
|
workspaceId: workspace.id,
|
|
21584
21655
|
revision: newRevision(),
|
|
21585
21656
|
createdAt: timestamp,
|
|
21586
|
-
updatedAt: timestamp
|
|
21657
|
+
updatedAt: timestamp,
|
|
21658
|
+
runtime: {
|
|
21659
|
+
createdWith: input.task.runtime?.createdWith ?? null,
|
|
21660
|
+
updatedWith: runtimeVersion()
|
|
21661
|
+
}
|
|
21587
21662
|
};
|
|
21588
21663
|
const validTask = taskRecordSchema.safeParse(task);
|
|
21589
21664
|
if (!validTask.success)
|
|
@@ -21632,7 +21707,8 @@ class TaskStore {
|
|
|
21632
21707
|
workspaceId: current.data.workspaceId,
|
|
21633
21708
|
createdAt: current.data.createdAt,
|
|
21634
21709
|
revision: context.revision,
|
|
21635
|
-
updatedAt: context.now
|
|
21710
|
+
updatedAt: context.now,
|
|
21711
|
+
runtime: stampNew(current.data)
|
|
21636
21712
|
};
|
|
21637
21713
|
const valid = taskRecordSchema.safeParse(record);
|
|
21638
21714
|
if (!valid.success)
|
|
@@ -21666,7 +21742,8 @@ class TaskStore {
|
|
|
21666
21742
|
...changed.data,
|
|
21667
21743
|
id: current.data.id,
|
|
21668
21744
|
root: this.root,
|
|
21669
|
-
revision: context.revision
|
|
21745
|
+
revision: context.revision,
|
|
21746
|
+
runtime: stampNew(current.data)
|
|
21670
21747
|
};
|
|
21671
21748
|
const valid = workspaceRecordSchema.safeParse(record);
|
|
21672
21749
|
if (!valid.success)
|
|
@@ -21709,7 +21786,8 @@ class TaskStore {
|
|
|
21709
21786
|
...changedWorkspace.data,
|
|
21710
21787
|
id: workspace.data.id,
|
|
21711
21788
|
root: this.root,
|
|
21712
|
-
revision: workspaceContext.revision
|
|
21789
|
+
revision: workspaceContext.revision,
|
|
21790
|
+
runtime: stampNew(workspace.data)
|
|
21713
21791
|
});
|
|
21714
21792
|
if (!nextWorkspace.success)
|
|
21715
21793
|
return failure("invalid_input", "workspace mutation produced an invalid record");
|
|
@@ -21734,7 +21812,8 @@ class TaskStore {
|
|
|
21734
21812
|
workspaceId: task.data.workspaceId,
|
|
21735
21813
|
createdAt: task.data.createdAt,
|
|
21736
21814
|
revision: taskContext.revision,
|
|
21737
|
-
updatedAt: taskContext.now
|
|
21815
|
+
updatedAt: taskContext.now,
|
|
21816
|
+
runtime: stampNew(task.data)
|
|
21738
21817
|
});
|
|
21739
21818
|
if (!nextTask.success) {
|
|
21740
21819
|
this.markUncertain(nextWorkspace.data);
|
|
@@ -21761,7 +21840,7 @@ class TaskStore {
|
|
|
21761
21840
|
if (match)
|
|
21762
21841
|
candidates.push({
|
|
21763
21842
|
target: match[1],
|
|
21764
|
-
path:
|
|
21843
|
+
path: path4.join(this.recoveryDir, name),
|
|
21765
21844
|
digest: match[3]
|
|
21766
21845
|
});
|
|
21767
21846
|
}
|
|
@@ -21954,7 +22033,8 @@ class TaskStore {
|
|
|
21954
22033
|
const value = {
|
|
21955
22034
|
...workspace,
|
|
21956
22035
|
writer: { ...workspace.writer, state: "uncertain" },
|
|
21957
|
-
revision: newRevision()
|
|
22036
|
+
revision: newRevision(),
|
|
22037
|
+
runtime: stampNew(workspace)
|
|
21958
22038
|
};
|
|
21959
22039
|
this.replaceSnapshot(this.workspacePath, value, workspace);
|
|
21960
22040
|
}
|
|
@@ -22032,7 +22112,7 @@ class TaskStore {
|
|
|
22032
22112
|
}
|
|
22033
22113
|
fs4.renameSync(temporary, file);
|
|
22034
22114
|
temporary = undefined;
|
|
22035
|
-
this.fsyncDirectory(
|
|
22115
|
+
this.fsyncDirectory(path4.dirname(file));
|
|
22036
22116
|
return success(null, null, value);
|
|
22037
22117
|
} catch (error) {
|
|
22038
22118
|
return failure("storage_error", `snapshot replacement failed: ${String(error)}`, {
|
|
@@ -22046,9 +22126,9 @@ class TaskStore {
|
|
|
22046
22126
|
}
|
|
22047
22127
|
}
|
|
22048
22128
|
saveRecovery(file, bytes) {
|
|
22049
|
-
const target =
|
|
22050
|
-
const id = target === "task" ?
|
|
22051
|
-
const destination =
|
|
22129
|
+
const target = path4.basename(file) === "workspace.json" ? "workspace" : "task";
|
|
22130
|
+
const id = target === "task" ? path4.basename(file, ".json") : "workspace";
|
|
22131
|
+
const destination = path4.join(this.recoveryDir, `${target}.${id}.${digestBytes(bytes)}.json`);
|
|
22052
22132
|
if (fs4.existsSync(destination)) {
|
|
22053
22133
|
if (digestBytes(fs4.readFileSync(destination)) === digestBytes(bytes))
|
|
22054
22134
|
return;
|
|
@@ -22126,7 +22206,7 @@ class TaskStore {
|
|
|
22126
22206
|
for (const name of fs4.readdirSync(this.tasksDir)) {
|
|
22127
22207
|
if (!name.endsWith(".json") || !validId(name.slice(0, -5)))
|
|
22128
22208
|
return null;
|
|
22129
|
-
const parsed = this.parseBytes(fs4.readFileSync(
|
|
22209
|
+
const parsed = this.parseBytes(fs4.readFileSync(path4.join(this.tasksDir, name)), taskRecordSchema);
|
|
22130
22210
|
if (!parsed.ok || parsed.data.id !== name.slice(0, -5))
|
|
22131
22211
|
return null;
|
|
22132
22212
|
ids.add(parsed.data.workspaceId);
|
|
@@ -22168,7 +22248,12 @@ class TaskStore {
|
|
|
22168
22248
|
if (isObject3(value) && "schemaVersion" in value && value.schemaVersion !== SCHEMA_VERSION)
|
|
22169
22249
|
return failure("unsupported_version", "unsupported snapshot schema version");
|
|
22170
22250
|
const parsed = schema.safeParse(value);
|
|
22171
|
-
|
|
22251
|
+
if (parsed.success)
|
|
22252
|
+
return success(null, null, parsed.data);
|
|
22253
|
+
const writerVersion = isObject3(value) && isObject3(value.runtime) ? value.runtime.updatedWith : null;
|
|
22254
|
+
if (typeof writerVersion === "string" && isNewerVersion(writerVersion, runtimeVersion()))
|
|
22255
|
+
return failure("recovery_required", `snapshot was written by workit ${writerVersion}; upgrade Workit before mutating this checkout`);
|
|
22256
|
+
return failure("recovery_required", "snapshot does not satisfy its schema");
|
|
22172
22257
|
}
|
|
22173
22258
|
processStart(pid) {
|
|
22174
22259
|
try {
|
|
@@ -22178,43 +22263,98 @@ class TaskStore {
|
|
|
22178
22263
|
}
|
|
22179
22264
|
}
|
|
22180
22265
|
conflict(expected, actual) {
|
|
22181
|
-
return failure("revision_conflict", "snapshot revision does not match", {
|
|
22266
|
+
return failure("revision_conflict", "snapshot revision does not match; omit expectedRevision to use the current record", {
|
|
22182
22267
|
expectedRevision: expected,
|
|
22183
22268
|
actualRevision: actual
|
|
22184
22269
|
});
|
|
22185
22270
|
}
|
|
22186
22271
|
taskPath(taskId) {
|
|
22187
|
-
return
|
|
22272
|
+
return path4.join(this.tasksDir, `${taskId}.json`);
|
|
22188
22273
|
}
|
|
22189
22274
|
get workitDir() {
|
|
22190
|
-
return
|
|
22275
|
+
return path4.join(this.root, ".workit");
|
|
22191
22276
|
}
|
|
22192
22277
|
get tasksDir() {
|
|
22193
|
-
return
|
|
22278
|
+
return path4.join(this.workitDir, "tasks");
|
|
22194
22279
|
}
|
|
22195
22280
|
get recoveryDir() {
|
|
22196
|
-
return
|
|
22281
|
+
return path4.join(this.workitDir, "recovery");
|
|
22197
22282
|
}
|
|
22198
22283
|
get workspacePath() {
|
|
22199
|
-
return
|
|
22284
|
+
return path4.join(this.workitDir, "workspace.json");
|
|
22200
22285
|
}
|
|
22201
22286
|
get lockPath() {
|
|
22202
|
-
return
|
|
22287
|
+
return path4.join(this.workitDir, "metadata.lock");
|
|
22203
22288
|
}
|
|
22204
22289
|
get gitignorePath() {
|
|
22205
|
-
return
|
|
22290
|
+
return path4.join(this.workitDir, ".gitignore");
|
|
22206
22291
|
}
|
|
22207
22292
|
}
|
|
22293
|
+
// packages/workit-core/src/core/workers.ts
|
|
22294
|
+
var same = (left, right) => {
|
|
22295
|
+
try {
|
|
22296
|
+
return canonicalJson(left) === canonicalJson(right);
|
|
22297
|
+
} catch {
|
|
22298
|
+
return false;
|
|
22299
|
+
}
|
|
22300
|
+
};
|
|
22301
|
+
var UNCERTAIN_WORKER_STATES = ["dispatching", "running", "cancelling", "unknown"];
|
|
22302
|
+
var isUncertainWorker = (state) => UNCERTAIN_WORKER_STATES.includes(state);
|
|
22303
|
+
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
22304
|
+
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
22305
|
+
const code = char.charCodeAt(0);
|
|
22306
|
+
return code < 32 || code === 127;
|
|
22307
|
+
}) && !value.split(/[\\/]/).includes("..");
|
|
22308
|
+
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
22309
|
+
var workerIdOf = (value) => value.workerId ?? null;
|
|
22310
|
+
function assertProductWriteAllowed(input) {
|
|
22311
|
+
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
22312
|
+
return failure("invalid_input", "invalid product write path");
|
|
22313
|
+
if (!input.store)
|
|
22314
|
+
return failure("permission_denied", "write authorization requires core state");
|
|
22315
|
+
const task = input.store.readTask(input.task.id);
|
|
22316
|
+
if (!task.ok)
|
|
22317
|
+
return task;
|
|
22318
|
+
const workspace = input.store.readWorkspace();
|
|
22319
|
+
if (!workspace.ok)
|
|
22320
|
+
return workspace;
|
|
22321
|
+
if (!workspace.data)
|
|
22322
|
+
return failure("not_found", "workspace not found");
|
|
22323
|
+
const workspaceRecord = workspace.data;
|
|
22324
|
+
if (task.data.status !== "active")
|
|
22325
|
+
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
22326
|
+
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
22327
|
+
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
22328
|
+
const writer = workspaceRecord.writer;
|
|
22329
|
+
if (!writer)
|
|
22330
|
+
return failure("permission_denied", "checkout has no writer owner");
|
|
22331
|
+
if (writer.state === "uncertain")
|
|
22332
|
+
return failure("recovery_required", "checkout writer requires recovery", {
|
|
22333
|
+
owner: writer.owner
|
|
22334
|
+
});
|
|
22335
|
+
const caller = callerValue(input.caller);
|
|
22336
|
+
const workerId = workerIdOf(input.caller);
|
|
22337
|
+
const owner = writer.owner;
|
|
22338
|
+
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
22339
|
+
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
22340
|
+
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
22341
|
+
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
22342
|
+
return failure("permission_denied", "only implementers can own product writes");
|
|
22343
|
+
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))))
|
|
22344
|
+
return failure("recovery_required", "worker session is not an active writer");
|
|
22345
|
+
return success(null, workspaceRecord.revision, owner);
|
|
22346
|
+
}
|
|
22347
|
+
|
|
22208
22348
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22209
22349
|
import { createHash as createHash4 } from "node:crypto";
|
|
22210
22350
|
import * as fs6 from "node:fs";
|
|
22211
22351
|
import { spawnSync } from "node:child_process";
|
|
22212
|
-
import
|
|
22352
|
+
import path6 from "node:path";
|
|
22213
22353
|
|
|
22214
22354
|
// packages/workit-core/src/core/authority.ts
|
|
22215
22355
|
import { createHash as createHash3 } from "node:crypto";
|
|
22216
22356
|
import * as fs5 from "node:fs";
|
|
22217
|
-
import
|
|
22357
|
+
import path5 from "node:path";
|
|
22218
22358
|
var verifiedAuthorities = new WeakMap;
|
|
22219
22359
|
var trustedAuthority = (kind, provenance, expected, binding, caller) => {
|
|
22220
22360
|
const token = {};
|
|
@@ -22341,20 +22481,31 @@ var verifyDecisionContentAtRoot = (checkoutRoot, binding) => {
|
|
|
22341
22481
|
continue;
|
|
22342
22482
|
if (!reference.digest)
|
|
22343
22483
|
return failure("invalid_input", "document references require a byte digest");
|
|
22344
|
-
const target =
|
|
22345
|
-
if (target !== root && !target.startsWith(`${root}${
|
|
22346
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
22484
|
+
const target = path5.resolve(root, reference.path);
|
|
22485
|
+
if (target !== root && !target.startsWith(`${root}${path5.sep}`))
|
|
22486
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
22487
|
+
fields: [
|
|
22488
|
+
{ path: "contentRefs.path", reason: `${reference.path} is outside the checkout` }
|
|
22489
|
+
]
|
|
22490
|
+
});
|
|
22347
22491
|
try {
|
|
22348
|
-
const relative =
|
|
22492
|
+
const relative = path5.relative(root, target);
|
|
22349
22493
|
let current = root;
|
|
22350
|
-
for (const segment of relative.split(
|
|
22494
|
+
for (const segment of relative.split(path5.sep)) {
|
|
22351
22495
|
if (!segment)
|
|
22352
22496
|
continue;
|
|
22353
|
-
current =
|
|
22497
|
+
current = path5.join(current, segment);
|
|
22354
22498
|
const stat = fs5.lstatSync(current);
|
|
22355
22499
|
const real = fs5.realpathSync(current);
|
|
22356
|
-
if (real !== root && !real.startsWith(`${root}${
|
|
22357
|
-
return failure("invalid_input", "document reference escapes checkout"
|
|
22500
|
+
if (real !== root && !real.startsWith(`${root}${path5.sep}`))
|
|
22501
|
+
return failure("invalid_input", "document reference escapes checkout; cite it as an external file:// reference instead", {
|
|
22502
|
+
fields: [
|
|
22503
|
+
{
|
|
22504
|
+
path: "contentRefs.path",
|
|
22505
|
+
reason: `${reference.path} resolves outside the checkout`
|
|
22506
|
+
}
|
|
22507
|
+
]
|
|
22508
|
+
});
|
|
22358
22509
|
if (stat.isSymbolicLink())
|
|
22359
22510
|
return failure("invalid_input", "document reference uses a symlink");
|
|
22360
22511
|
if (current === target && !stat.isFile())
|
|
@@ -22701,14 +22852,14 @@ var verifyDecisionContent = verifyContentRefs;
|
|
|
22701
22852
|
|
|
22702
22853
|
// packages/workit-core/src/core/task-evaluation.ts
|
|
22703
22854
|
var digestBytes3 = (value) => createHash4("sha256").update(value).digest("hex");
|
|
22704
|
-
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${
|
|
22705
|
-
var relative = (root, candidate) =>
|
|
22855
|
+
var inside = (root, candidate) => candidate === root || candidate.startsWith(`${root}${path6.sep}`);
|
|
22856
|
+
var relative = (root, candidate) => path6.relative(root, candidate).split(path6.sep).join("/") || ".";
|
|
22706
22857
|
var canonicalPath = (value) => {
|
|
22707
22858
|
if (typeof value !== "string" || !value)
|
|
22708
22859
|
return null;
|
|
22709
22860
|
if (value.includes("\\") || value.split("/").includes(".."))
|
|
22710
22861
|
return null;
|
|
22711
|
-
const normalized =
|
|
22862
|
+
const normalized = path6.posix.normalize(value);
|
|
22712
22863
|
if (normalized === ".." || normalized.startsWith("../") || normalized.startsWith("/"))
|
|
22713
22864
|
return null;
|
|
22714
22865
|
return normalized === "." ? "." : normalized.replace(/\/$/, "");
|
|
@@ -22756,14 +22907,14 @@ var gitPaths = (root) => {
|
|
|
22756
22907
|
git: /not a git repository/i.test(stderr) ? false : true
|
|
22757
22908
|
};
|
|
22758
22909
|
}
|
|
22759
|
-
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
22910
|
+
const paths = result.stdout ? result.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")) : [];
|
|
22760
22911
|
const stagedDeleted = spawnSync("git", ["diff", "--cached", "--name-only", "--diff-filter=D", "-z"], { cwd: root, encoding: "buffer" });
|
|
22761
22912
|
if (stagedDeleted.status !== 0) {
|
|
22762
22913
|
const stderr = stagedDeleted.stderr?.toString("utf8") ?? "";
|
|
22763
22914
|
if (!/not a git repository/i.test(stderr))
|
|
22764
22915
|
return { paths, uncertain: true, git: true };
|
|
22765
22916
|
} else if (stagedDeleted.stdout) {
|
|
22766
|
-
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(
|
|
22917
|
+
paths.push(...stagedDeleted.stdout.toString("utf8").split("\x00").filter(Boolean).map((item) => item.split(path6.sep).join("/")));
|
|
22767
22918
|
}
|
|
22768
22919
|
return { paths: [...new Set(paths)], uncertain: false, git: true };
|
|
22769
22920
|
};
|
|
@@ -22778,7 +22929,7 @@ var walk = (root, directory, output) => {
|
|
|
22778
22929
|
for (const name of names) {
|
|
22779
22930
|
if (name === ".git" || name === ".workit")
|
|
22780
22931
|
continue;
|
|
22781
|
-
const target =
|
|
22932
|
+
const target = path6.join(directory, name);
|
|
22782
22933
|
let stat;
|
|
22783
22934
|
try {
|
|
22784
22935
|
stat = fs6.lstatSync(target);
|
|
@@ -22796,17 +22947,17 @@ var walk = (root, directory, output) => {
|
|
|
22796
22947
|
var scopeRoots = (root, scope) => {
|
|
22797
22948
|
const roots = [];
|
|
22798
22949
|
for (const item of scope.paths.length ? scope.paths : ["."]) {
|
|
22799
|
-
const target =
|
|
22950
|
+
const target = path6.resolve(root, item);
|
|
22800
22951
|
if (!inside(root, target))
|
|
22801
|
-
return failure("invalid_input",
|
|
22952
|
+
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` }] });
|
|
22802
22953
|
let ancestor = target;
|
|
22803
22954
|
while (!fs6.existsSync(ancestor) && ancestor !== root)
|
|
22804
|
-
ancestor =
|
|
22955
|
+
ancestor = path6.dirname(ancestor);
|
|
22805
22956
|
try {
|
|
22806
22957
|
if (!inside(root, fs6.realpathSync(ancestor)))
|
|
22807
|
-
return failure("invalid_input",
|
|
22958
|
+
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` }] });
|
|
22808
22959
|
} catch {
|
|
22809
|
-
return failure("invalid_input",
|
|
22960
|
+
return failure("invalid_input", `candidate scope cannot be inspected: ${item}`);
|
|
22810
22961
|
}
|
|
22811
22962
|
roots.push(target);
|
|
22812
22963
|
}
|
|
@@ -22821,7 +22972,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
22821
22972
|
}
|
|
22822
22973
|
const normalizedScope = canonicalScope(scope);
|
|
22823
22974
|
if (!normalizedScope)
|
|
22824
|
-
return failure("invalid_input", "candidate scope is invalid");
|
|
22975
|
+
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" }] });
|
|
22825
22976
|
const roots = scopeRoots(checkout, normalizedScope);
|
|
22826
22977
|
if (!roots.ok)
|
|
22827
22978
|
return roots;
|
|
@@ -22853,7 +23004,7 @@ function captureCandidate(root, scope, environment = []) {
|
|
|
22853
23004
|
for (const item of [...names].sort()) {
|
|
22854
23005
|
if (!scopeMatches(item, normalizedScope))
|
|
22855
23006
|
continue;
|
|
22856
|
-
const target =
|
|
23007
|
+
const target = path6.join(checkout, item);
|
|
22857
23008
|
try {
|
|
22858
23009
|
const stat = fs6.lstatSync(target);
|
|
22859
23010
|
if (stat.isSymbolicLink()) {
|
|
@@ -23010,7 +23161,7 @@ function evaluateRequirements(task, workspace, capabilities, candidate, checkout
|
|
|
23010
23161
|
return true;
|
|
23011
23162
|
const reviewSession = sessionFromRef(entry.data.reviewContext);
|
|
23012
23163
|
const sameImplementation = sameSession(reviewSession, task.intent.provenance.session);
|
|
23013
|
-
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && sameSession(reviewSession, other.provenance.session));
|
|
23164
|
+
const sameEvidenceSession = task.evidence.some((other) => other.id !== entry.id && other.data.kind === "review" && sameSession(reviewSession, other.provenance.session));
|
|
23014
23165
|
return entry.data.kind === "review" && reviewSession !== null && sameSession(reviewSession, entry.provenance.session) && (requirement.ruleId === "self-review" || !sameImplementation && !sameEvidenceSession);
|
|
23015
23166
|
});
|
|
23016
23167
|
if (passed.length) {
|
|
@@ -23106,7 +23257,17 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
23106
23257
|
const blocking = evaluations.filter((item) => item.status === "unsatisfied" || item.status === "unavailable");
|
|
23107
23258
|
if (requestedOutcome !== "stopped" && blocking.length)
|
|
23108
23259
|
return failure("requirements_unsatisfied", "applicable requirements are unsatisfied", {
|
|
23109
|
-
requirementIds: blocking.map((item) => item.requirementId)
|
|
23260
|
+
requirementIds: blocking.map((item) => item.requirementId),
|
|
23261
|
+
requirements: blocking.map((item) => {
|
|
23262
|
+
const requirement = view.task.policy?.requirements.find((candidate) => candidate.id === item.requirementId);
|
|
23263
|
+
return {
|
|
23264
|
+
requirementId: item.requirementId,
|
|
23265
|
+
ruleId: requirement?.ruleId ?? "unknown",
|
|
23266
|
+
reason: item.reason,
|
|
23267
|
+
satisfaction: requirement?.satisfaction ?? "",
|
|
23268
|
+
dependentAction: requirement?.dependentAction ?? null
|
|
23269
|
+
};
|
|
23270
|
+
})
|
|
23110
23271
|
});
|
|
23111
23272
|
const accepted = evaluations.filter((item) => item.status === "accepted_limitation");
|
|
23112
23273
|
if (requestedOutcome === "verified" && accepted.length)
|
|
@@ -23142,7 +23303,7 @@ function reconcileResume(view, observations = []) {
|
|
|
23142
23303
|
return candidate;
|
|
23143
23304
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
23144
23305
|
const blockers = [...view.task.progress.blockers];
|
|
23145
|
-
if (view.task.workers.some((entry) =>
|
|
23306
|
+
if (view.task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
23146
23307
|
blockers.push({
|
|
23147
23308
|
reason: "worker state requires reconciliation",
|
|
23148
23309
|
dependentAction: "resume",
|
|
@@ -23175,7 +23336,11 @@ var externalActionSchema = discriminatedUnion2("operation", [
|
|
|
23175
23336
|
}).strict(),
|
|
23176
23337
|
object4({
|
|
23177
23338
|
operation: literal3("git.commit"),
|
|
23178
|
-
payload: object4({
|
|
23339
|
+
payload: object4({
|
|
23340
|
+
message: string5().min(1).optional(),
|
|
23341
|
+
plan_steps: array3(string5().min(1)).optional(),
|
|
23342
|
+
plan_branch: string5().min(1).optional()
|
|
23343
|
+
}).strict()
|
|
23179
23344
|
}).strict(),
|
|
23180
23345
|
object4({
|
|
23181
23346
|
operation: literal3("git.push"),
|
|
@@ -23410,7 +23575,7 @@ function resolveRequirements(input) {
|
|
|
23410
23575
|
].join(", ")}`,
|
|
23411
23576
|
satisfaction: "An observed or inferred fact resolves the uncertainty and records its supporting references.",
|
|
23412
23577
|
before: "dependent_action",
|
|
23413
|
-
dependentAction:
|
|
23578
|
+
dependentAction: null,
|
|
23414
23579
|
acceptanceAllowed: false
|
|
23415
23580
|
}));
|
|
23416
23581
|
if (signals.productChoiceOpen.value === true)
|
|
@@ -23421,7 +23586,7 @@ function resolveRequirements(input) {
|
|
|
23421
23586
|
reason: `A consequential product choice remains open: ${signalReason(input, "productChoiceOpen")}`,
|
|
23422
23587
|
satisfaction: "Present the material alternatives and record the user's decision before the dependent action.",
|
|
23423
23588
|
before: "dependent_action",
|
|
23424
|
-
dependentAction:
|
|
23589
|
+
dependentAction: null,
|
|
23425
23590
|
acceptanceAllowed: false
|
|
23426
23591
|
}));
|
|
23427
23592
|
if (signals.behaviorChange.value === true)
|
|
@@ -23579,59 +23744,6 @@ function diffPolicy(previous, next, reason, now) {
|
|
|
23579
23744
|
return parsed.data;
|
|
23580
23745
|
}
|
|
23581
23746
|
|
|
23582
|
-
// packages/workit-core/src/core/workers.ts
|
|
23583
|
-
var same = (left, right) => {
|
|
23584
|
-
try {
|
|
23585
|
-
return canonicalJson(left) === canonicalJson(right);
|
|
23586
|
-
} catch {
|
|
23587
|
-
return false;
|
|
23588
|
-
}
|
|
23589
|
-
};
|
|
23590
|
-
var callerSession = (caller) => caller.session ?? { kind: "host", host: caller.host, handle: caller.actor };
|
|
23591
|
-
var validPath = (value) => typeof value === "string" && value.length > 0 && ![...value].some((char) => {
|
|
23592
|
-
const code = char.charCodeAt(0);
|
|
23593
|
-
return code < 32 || code === 127;
|
|
23594
|
-
}) && !value.split(/[\\/]/).includes("..");
|
|
23595
|
-
var callerValue = (value) => ({ host: value.host, actor: value.actor });
|
|
23596
|
-
var workerIdOf = (value) => value.workerId ?? null;
|
|
23597
|
-
function assertProductWriteAllowed(input) {
|
|
23598
|
-
if (!Array.isArray(input.paths) || input.paths.some((path) => !validPath(path)))
|
|
23599
|
-
return failure("invalid_input", "invalid product write path");
|
|
23600
|
-
if (!input.store)
|
|
23601
|
-
return failure("permission_denied", "write authorization requires core state");
|
|
23602
|
-
const task = input.store.readTask(input.task.id);
|
|
23603
|
-
if (!task.ok)
|
|
23604
|
-
return task;
|
|
23605
|
-
const workspace = input.store.readWorkspace();
|
|
23606
|
-
if (!workspace.ok)
|
|
23607
|
-
return workspace;
|
|
23608
|
-
if (!workspace.data)
|
|
23609
|
-
return failure("not_found", "workspace not found");
|
|
23610
|
-
const workspaceRecord = workspace.data;
|
|
23611
|
-
if (task.data.status !== "active")
|
|
23612
|
-
return failure("invalid_transition", "paused or closed tasks cannot own product writes");
|
|
23613
|
-
if (workspaceRecord.id !== task.data.workspaceId || workspaceRecord.root.length === 0)
|
|
23614
|
-
return failure("recovery_required", "task and workspace bindings are invalid");
|
|
23615
|
-
const writer = workspaceRecord.writer;
|
|
23616
|
-
if (!writer)
|
|
23617
|
-
return failure("permission_denied", "checkout has no writer owner");
|
|
23618
|
-
if (writer.state === "uncertain")
|
|
23619
|
-
return failure("recovery_required", "checkout writer requires recovery", {
|
|
23620
|
-
owner: writer.owner
|
|
23621
|
-
});
|
|
23622
|
-
const caller = callerValue(input.caller);
|
|
23623
|
-
const workerId = workerIdOf(input.caller);
|
|
23624
|
-
const owner = writer.owner;
|
|
23625
|
-
if (owner.taskId !== task.data.id || owner.workerId !== workerId || owner.session.host !== caller.host || owner.session.handle !== caller.actor)
|
|
23626
|
-
return failure("writer_conflict", "checkout is owned by another validated actor", { owner });
|
|
23627
|
-
const worker = workerId === null ? null : task.data.workers.find((entry) => entry.id === workerId);
|
|
23628
|
-
if (workerId !== null && (!worker || worker.data.assignment.role !== "implementer"))
|
|
23629
|
-
return failure("permission_denied", "only implementers can own product writes");
|
|
23630
|
-
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))))
|
|
23631
|
-
return failure("recovery_required", "worker session is not an active writer");
|
|
23632
|
-
return success(null, workspaceRecord.revision, owner);
|
|
23633
|
-
}
|
|
23634
|
-
|
|
23635
23747
|
// packages/workit-core/src/core/task-engine.ts
|
|
23636
23748
|
var provenance = (context, kind = context.provenanceKind ?? "host_observed") => ({
|
|
23637
23749
|
kind,
|
|
@@ -23750,10 +23862,10 @@ var applyWorkerLifecycle = (input) => {
|
|
|
23750
23862
|
const notStarted = input.dispatch === true;
|
|
23751
23863
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
23752
23864
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
23753
|
-
if (notStarted &&
|
|
23754
|
-
return failure("invalid_transition", "worker is no longer awaiting dispatch");
|
|
23755
|
-
if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
|
|
23865
|
+
if (notStarted && entry.data.session !== null)
|
|
23756
23866
|
return failure("invalid_transition", "worker already has an observed session");
|
|
23867
|
+
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
23868
|
+
return failure("invalid_transition", "worker launch was not claimed");
|
|
23757
23869
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
23758
23870
|
return failure("invalid_input", "running worker observations require a session");
|
|
23759
23871
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -23762,15 +23874,20 @@ var applyWorkerLifecycle = (input) => {
|
|
|
23762
23874
|
return failure("invalid_transition", "worker has not started");
|
|
23763
23875
|
if (input.state === "running" && entry.data.state === "stopped")
|
|
23764
23876
|
return failure("invalid_transition", "stopped worker cannot run again");
|
|
23877
|
+
const terminalCancel = entry.data.state === "cancelling" && input.state === "running";
|
|
23765
23878
|
const nextEntry = {
|
|
23766
23879
|
...entry,
|
|
23767
23880
|
recordedAt: input.now ?? entry.recordedAt,
|
|
23768
23881
|
provenance: authority.provenance,
|
|
23769
|
-
data: {
|
|
23882
|
+
data: {
|
|
23883
|
+
...entry.data,
|
|
23884
|
+
state: terminalCancel ? "cancelling" : input.state,
|
|
23885
|
+
session: input.session
|
|
23886
|
+
}
|
|
23770
23887
|
};
|
|
23771
23888
|
const shouldClear = input.state === "stopped" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
23772
23889
|
const shouldUncertain = input.state === "unknown" && workspace.data.writer !== null && workspace.data.writer.owner.taskId === task.data.id && workspace.data.writer.owner.workerId === input.workerId;
|
|
23773
|
-
if (entry.data.state ===
|
|
23890
|
+
if (entry.data.state === nextEntry.data.state && sameValue(entry.data.session, input.session) && !shouldClear && !shouldUncertain)
|
|
23774
23891
|
return success(task.data.revision, workspace.data.revision, entry);
|
|
23775
23892
|
const changed = input.store.mutateTaskAndWorkspace({
|
|
23776
23893
|
taskId: task.data.id,
|
|
@@ -24140,7 +24257,7 @@ class WorkitCore {
|
|
|
24140
24257
|
activeWorkerBlocker(task, workspace) {
|
|
24141
24258
|
if (workspace.writer)
|
|
24142
24259
|
return failure("recovery_required", "writer ownership must be released first");
|
|
24143
|
-
if (task.workers.some((entry) =>
|
|
24260
|
+
if (task.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
24144
24261
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
24145
24262
|
return null;
|
|
24146
24263
|
}
|
|
@@ -24377,11 +24494,6 @@ class WorkitCore {
|
|
|
24377
24494
|
if (helper && !helper.ok)
|
|
24378
24495
|
return helper;
|
|
24379
24496
|
const evidence = input.evidence;
|
|
24380
|
-
if (helper?.ok) {
|
|
24381
|
-
const assignment = helper.data.data.assignment;
|
|
24382
|
-
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))
|
|
24383
|
-
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
24384
|
-
}
|
|
24385
24497
|
if ((evidence.result === "missing" || evidence.result === "skipped") && !evidence.summary.trim())
|
|
24386
24498
|
return failure("invalid_input", "missing or skipped evidence requires a reason");
|
|
24387
24499
|
if (evidence.kind === "review") {
|
|
@@ -24398,6 +24510,11 @@ class WorkitCore {
|
|
|
24398
24510
|
evidence.beforeCandidateId ??= null;
|
|
24399
24511
|
evidence.candidateId ??= null;
|
|
24400
24512
|
}
|
|
24513
|
+
if (helper?.ok) {
|
|
24514
|
+
const assignment = helper.data.data.assignment;
|
|
24515
|
+
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))
|
|
24516
|
+
return failure("permission_denied", "evidence is outside the worker assignment");
|
|
24517
|
+
}
|
|
24401
24518
|
const knownCandidates = new Set(task.data.candidates.map((candidate) => candidate.id));
|
|
24402
24519
|
for (const candidateId of [evidence.beforeCandidateId, evidence.candidateId]) {
|
|
24403
24520
|
if (candidateId && candidateId !== currentCandidate.data.id && !knownCandidates.has(candidateId))
|
|
@@ -24904,7 +25021,12 @@ class WorkitCore {
|
|
|
24904
25021
|
return failure("invalid_transition", "worker is not awaiting dispatch");
|
|
24905
25022
|
return success(mutation.revision, null, {
|
|
24906
25023
|
...current,
|
|
24907
|
-
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
25024
|
+
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
25025
|
+
...item,
|
|
25026
|
+
recordedAt: mutation.now,
|
|
25027
|
+
provenance: attested.data,
|
|
25028
|
+
data: { ...item.data, state: "dispatching" }
|
|
25029
|
+
} : item)
|
|
24908
25030
|
});
|
|
24909
25031
|
}
|
|
24910
25032
|
});
|
|
@@ -25288,6 +25410,9 @@ class WorkitCore {
|
|
|
25288
25410
|
id: task.id,
|
|
25289
25411
|
revision: task.revision,
|
|
25290
25412
|
workspaceRevision: workspace.data.revision,
|
|
25413
|
+
createdAt: task.createdAt,
|
|
25414
|
+
updatedAt: task.updatedAt,
|
|
25415
|
+
runtime: task.runtime ?? null,
|
|
25291
25416
|
objective: task.intent.data.objective,
|
|
25292
25417
|
status: task.status,
|
|
25293
25418
|
closure: task.closure,
|
|
@@ -25373,7 +25498,7 @@ class WorkitCore {
|
|
|
25373
25498
|
const base = reconcileResume(effectiveView);
|
|
25374
25499
|
if (!base.ok)
|
|
25375
25500
|
return base;
|
|
25376
|
-
const needsReconciliation = view.task.workers.some((entry) =>
|
|
25501
|
+
const needsReconciliation = view.task.workers.some((entry) => isUncertainWorker(observedStates.get(entry.id) ?? entry.data.state));
|
|
25377
25502
|
const blockers = [...base.data.blockers];
|
|
25378
25503
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
25379
25504
|
blockers.push({
|
|
@@ -25440,7 +25565,8 @@ class WorkitCore {
|
|
|
25440
25565
|
...current,
|
|
25441
25566
|
status,
|
|
25442
25567
|
candidates: resumeCandidate && !current.candidates.some((candidate) => candidate.id === resumeCandidate.id) ? [...current.candidates, resumeCandidate] : current.candidates,
|
|
25443
|
-
progress:
|
|
25568
|
+
progress: current.progress,
|
|
25569
|
+
pauseReason: status === "paused" ? input.reason ?? null : null
|
|
25444
25570
|
})
|
|
25445
25571
|
});
|
|
25446
25572
|
if (!changed.ok)
|
|
@@ -25476,7 +25602,7 @@ class WorkitCore {
|
|
|
25476
25602
|
return workspace;
|
|
25477
25603
|
if (!workspace.data)
|
|
25478
25604
|
return failure("not_found", "workspace not found");
|
|
25479
|
-
if (workspace.data.writer || task.data.workers.some((entry) => entry.data.state
|
|
25605
|
+
if (workspace.data.writer || task.data.workers.some((entry) => isUncertainWorker(entry.data.state)))
|
|
25480
25606
|
return failure("recovery_required", "scope revision requires worker ownership reconciliation");
|
|
25481
25607
|
this.fillRevisions(input, task.data, workspace.data);
|
|
25482
25608
|
const changed = this.store.mutateTaskAndWorkspace({
|
|
@@ -25587,15 +25713,15 @@ function resolveGitRevision(root, value) {
|
|
|
25587
25713
|
}
|
|
25588
25714
|
function resolveInside(root, candidate) {
|
|
25589
25715
|
const base = realpathSync5(root);
|
|
25590
|
-
const target =
|
|
25591
|
-
if (target !== base && !target.startsWith(base +
|
|
25716
|
+
const target = path7.resolve(base, candidate);
|
|
25717
|
+
if (target !== base && !target.startsWith(base + path7.sep)) {
|
|
25592
25718
|
throw new Error("path must stay inside repository root");
|
|
25593
25719
|
}
|
|
25594
25720
|
let ancestor = target;
|
|
25595
|
-
while (!
|
|
25596
|
-
ancestor =
|
|
25721
|
+
while (!existsSync4(ancestor))
|
|
25722
|
+
ancestor = path7.dirname(ancestor);
|
|
25597
25723
|
const canonicalAncestor = realpathSync5(ancestor);
|
|
25598
|
-
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base +
|
|
25724
|
+
if (canonicalAncestor !== base && !canonicalAncestor.startsWith(base + path7.sep)) {
|
|
25599
25725
|
throw new Error("path must stay inside repository root");
|
|
25600
25726
|
}
|
|
25601
25727
|
return target;
|
|
@@ -25780,14 +25906,14 @@ var gitContext = (workspaceRoot, paths = []) => {
|
|
|
25780
25906
|
import {
|
|
25781
25907
|
copyFileSync,
|
|
25782
25908
|
cpSync,
|
|
25783
|
-
existsSync as
|
|
25909
|
+
existsSync as existsSync5,
|
|
25784
25910
|
mkdirSync as mkdirSync2,
|
|
25785
25911
|
readFileSync as readFileSync4,
|
|
25786
25912
|
readdirSync as readdirSync3,
|
|
25787
25913
|
writeFileSync as writeFileSync2
|
|
25788
25914
|
} from "node:fs";
|
|
25789
25915
|
import os2 from "node:os";
|
|
25790
|
-
import
|
|
25916
|
+
import path8 from "node:path";
|
|
25791
25917
|
var diagnosticLogger;
|
|
25792
25918
|
var PRESETS = {
|
|
25793
25919
|
gitflow: {
|
|
@@ -25808,7 +25934,7 @@ var mergePreset = (preset, input = {}, current = {
|
|
|
25808
25934
|
protected: preset === "custom" ? input.protectedNames ?? current.branchPolicy.protected : [...defs.protected]
|
|
25809
25935
|
};
|
|
25810
25936
|
};
|
|
25811
|
-
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ??
|
|
25937
|
+
var resolveConfigDir = () => process.env.WORKFLOW_TOOLKIT_CONFIG ?? process.env.WORKFLOW_TOOLKIT_CONFIG_DIR ?? path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workit");
|
|
25812
25938
|
var migratedDir = null;
|
|
25813
25939
|
var migrationFailed = false;
|
|
25814
25940
|
var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
@@ -25818,12 +25944,12 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25818
25944
|
migratedDir = dir;
|
|
25819
25945
|
return dir;
|
|
25820
25946
|
}
|
|
25821
|
-
const legacy =
|
|
25822
|
-
if (!
|
|
25947
|
+
const legacy = path8.join(process.env.XDG_CONFIG_HOME || path8.join(os2.homedir(), ".config"), "workflow-toolkit");
|
|
25948
|
+
if (!existsSync5(legacy)) {
|
|
25823
25949
|
migratedDir = dir;
|
|
25824
25950
|
return dir;
|
|
25825
25951
|
}
|
|
25826
|
-
if (!migrationFailed &&
|
|
25952
|
+
if (!migrationFailed && existsSync5(dir)) {
|
|
25827
25953
|
migratedDir = dir;
|
|
25828
25954
|
return dir;
|
|
25829
25955
|
}
|
|
@@ -25831,9 +25957,9 @@ var ensureConfigDir = (dir = resolveConfigDir()) => {
|
|
|
25831
25957
|
mkdirSync2(dir, { recursive: true });
|
|
25832
25958
|
diagnosticLogger?.info(EVENT.migration, { from: legacy, to: dir });
|
|
25833
25959
|
for (const entry of readdirSync3(legacy, { withFileTypes: true })) {
|
|
25834
|
-
const src =
|
|
25835
|
-
const dest =
|
|
25836
|
-
if (
|
|
25960
|
+
const src = path8.join(legacy, entry.name);
|
|
25961
|
+
const dest = path8.join(dir, entry.name);
|
|
25962
|
+
if (existsSync5(dest))
|
|
25837
25963
|
continue;
|
|
25838
25964
|
try {
|
|
25839
25965
|
if (entry.isDirectory())
|
|
@@ -25906,7 +26032,7 @@ var parseConfigResult = (raw, file) => {
|
|
|
25906
26032
|
};
|
|
25907
26033
|
};
|
|
25908
26034
|
var readConfigTyped = (dir) => {
|
|
25909
|
-
const file =
|
|
26035
|
+
const file = path8.join(dir ?? configDir(), "config.json");
|
|
25910
26036
|
return parseConfigResult(readSafe(file), file);
|
|
25911
26037
|
};
|
|
25912
26038
|
var readConfig = () => {
|
|
@@ -25942,9 +26068,9 @@ var COMMIT_PRESETS = [
|
|
|
25942
26068
|
|
|
25943
26069
|
// packages/workit-core/src/core/workspaces.ts
|
|
25944
26070
|
import { readFileSync as readFileSync5, realpathSync as realpathSync6 } from "node:fs";
|
|
25945
|
-
import
|
|
26071
|
+
import path9 from "node:path";
|
|
25946
26072
|
var readWorkspacesResult = (dir = configDir()) => {
|
|
25947
|
-
const file =
|
|
26073
|
+
const file = path9.join(dir, "workspaces.json");
|
|
25948
26074
|
let raw;
|
|
25949
26075
|
try {
|
|
25950
26076
|
raw = readFileSync5(file, "utf8");
|
|
@@ -26042,10 +26168,10 @@ var resolveWorkspace = (cwd) => resolveWorkspaceFrom(cwd, configDir());
|
|
|
26042
26168
|
|
|
26043
26169
|
// packages/workit-core/src/core/vcs-config.ts
|
|
26044
26170
|
import fs7 from "node:fs";
|
|
26045
|
-
import
|
|
26171
|
+
import path10 from "node:path";
|
|
26046
26172
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
26047
26173
|
var TOKEN_PLACEHOLDER = "YOUR_TOKEN_HERE";
|
|
26048
|
-
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ??
|
|
26174
|
+
var vcsConfigPath = () => process.env.WORKFLOW_VCS_CONFIG ?? path10.join(configDir(), "vcs.json");
|
|
26049
26175
|
var vcsCwd = (cwd) => cwd ?? process.env.WORKFLOW_WORKSPACE_ROOT ?? process.cwd();
|
|
26050
26176
|
var remoteProvider = (cwd) => {
|
|
26051
26177
|
const r = spawnSync3("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
@@ -26128,8 +26254,8 @@ function vcsConfig(mode, cwd) {
|
|
|
26128
26254
|
}
|
|
26129
26255
|
const prov = cfg[provider] ?? {};
|
|
26130
26256
|
const wsTokenFile = typeof wsVcs.tokenFile === "string" && wsVcs.tokenFile.trim() !== "" ? wsVcs.tokenFile : null;
|
|
26131
|
-
const tokenFile = String(wsTokenFile ?? prov.tokenFile ??
|
|
26132
|
-
const tokenPath =
|
|
26257
|
+
const tokenFile = String(wsTokenFile ?? prov.tokenFile ?? path10.join(configDir(), `${provider}.token`));
|
|
26258
|
+
const tokenPath = path10.resolve(tokenFile);
|
|
26133
26259
|
let tokenOk = false;
|
|
26134
26260
|
if (fs7.existsSync(tokenPath)) {
|
|
26135
26261
|
const token = fs7.readFileSync(tokenPath, "utf8").trim();
|
|
@@ -26138,7 +26264,7 @@ function vcsConfig(mode, cwd) {
|
|
|
26138
26264
|
}
|
|
26139
26265
|
const out = {
|
|
26140
26266
|
ok: true,
|
|
26141
|
-
configPath:
|
|
26267
|
+
configPath: path10.resolve(cfgPath),
|
|
26142
26268
|
provider,
|
|
26143
26269
|
defaultTargetBranch: defaultTarget,
|
|
26144
26270
|
pr: cfg.pr ?? {},
|
|
@@ -26231,18 +26357,18 @@ var resolveBranchPolicyFor = (workspaceRoot) => resolveBranchPolicy(readConfig()
|
|
|
26231
26357
|
|
|
26232
26358
|
// packages/workit-core/src/core/pr-create.ts
|
|
26233
26359
|
import fs8 from "node:fs";
|
|
26234
|
-
import
|
|
26360
|
+
import path11 from "node:path";
|
|
26235
26361
|
function parseGhIssue(value) {
|
|
26236
26362
|
const m = /issues\/(\d+)/.exec(value);
|
|
26237
26363
|
return m ? m[1] : String(value).trim().replace(/^#/, "");
|
|
26238
26364
|
}
|
|
26239
26365
|
function whichOnPath(tool) {
|
|
26240
26366
|
const names = process.platform === "win32" ? [tool, `${tool}.exe`, `${tool}.cmd`] : [tool];
|
|
26241
|
-
for (const dir of (process.env.PATH ?? "").split(
|
|
26367
|
+
for (const dir of (process.env.PATH ?? "").split(path11.delimiter)) {
|
|
26242
26368
|
if (!dir)
|
|
26243
26369
|
continue;
|
|
26244
26370
|
for (const name of names) {
|
|
26245
|
-
const candidate =
|
|
26371
|
+
const candidate = path11.join(dir, name);
|
|
26246
26372
|
try {
|
|
26247
26373
|
fs8.accessSync(candidate, fs8.constants.X_OK);
|
|
26248
26374
|
return candidate;
|
|
@@ -26255,8 +26381,8 @@ var hostingCliAvailable = (provider) => whichOnPath(provider === "gitlab" ? "gla
|
|
|
26255
26381
|
|
|
26256
26382
|
// packages/workit-core/src/core/repo-context.ts
|
|
26257
26383
|
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
26258
|
-
import { existsSync as
|
|
26259
|
-
import
|
|
26384
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, readdirSync as readdirSync4 } from "node:fs";
|
|
26385
|
+
import path12 from "node:path";
|
|
26260
26386
|
var isSafeContextRange = (value) => value.length <= 4096 && (() => {
|
|
26261
26387
|
try {
|
|
26262
26388
|
gitRevisionParts(value);
|
|
@@ -26390,7 +26516,7 @@ var findPrTemplate = (cwd) => {
|
|
|
26390
26516
|
]) {
|
|
26391
26517
|
const parts = rel.split("/");
|
|
26392
26518
|
const base = parts[parts.length - 1];
|
|
26393
|
-
const dir = parts.length > 1 ?
|
|
26519
|
+
const dir = parts.length > 1 ? path12.join(cwd, ...parts.slice(0, -1)) : cwd;
|
|
26394
26520
|
const actual = probeCaseInsensitive(dir, base);
|
|
26395
26521
|
if (actual)
|
|
26396
26522
|
return [...parts.slice(0, -1), actual].join("/");
|
|
@@ -26421,8 +26547,8 @@ var diffStatForRange = (cwd, range) => {
|
|
|
26421
26547
|
return runGit(cwd, ["diff", "--stat", "--"]).stdout;
|
|
26422
26548
|
};
|
|
26423
26549
|
var packageScripts = (cwd, keys) => {
|
|
26424
|
-
const pkgPath =
|
|
26425
|
-
if (!
|
|
26550
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26551
|
+
if (!existsSync6(pkgPath))
|
|
26426
26552
|
return [];
|
|
26427
26553
|
try {
|
|
26428
26554
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
@@ -26447,9 +26573,9 @@ var documentationFiles = (cwd) => {
|
|
|
26447
26573
|
if (entry.isDirectory()) {
|
|
26448
26574
|
if (excluded.has(entry.name))
|
|
26449
26575
|
continue;
|
|
26450
|
-
walk(
|
|
26576
|
+
walk(path12.join(dir, entry.name), depth + 1);
|
|
26451
26577
|
} else if (entry.isFile() && (entry.name === "README.md" || entry.name.endsWith(".md"))) {
|
|
26452
|
-
matches.push("./" +
|
|
26578
|
+
matches.push("./" + path12.relative(cwd, path12.join(dir, entry.name)).replaceAll("\\", "/"));
|
|
26453
26579
|
}
|
|
26454
26580
|
}
|
|
26455
26581
|
};
|
|
@@ -26457,7 +26583,7 @@ var documentationFiles = (cwd) => {
|
|
|
26457
26583
|
return matches.sort().slice(0, 200);
|
|
26458
26584
|
};
|
|
26459
26585
|
var readTrimmed = (file, maxLines) => {
|
|
26460
|
-
if (!
|
|
26586
|
+
if (!existsSync6(file))
|
|
26461
26587
|
return "";
|
|
26462
26588
|
const lines = readFileSync6(file, "utf8").split(`
|
|
26463
26589
|
`);
|
|
@@ -26465,7 +26591,7 @@ var readTrimmed = (file, maxLines) => {
|
|
|
26465
26591
|
`);
|
|
26466
26592
|
};
|
|
26467
26593
|
function prReadyContext(root, range) {
|
|
26468
|
-
const cwd =
|
|
26594
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26469
26595
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26470
26596
|
return invalidRange(cwd);
|
|
26471
26597
|
let stdout = "";
|
|
@@ -26523,7 +26649,7 @@ ${status.stderr}` : "");
|
|
|
26523
26649
|
stdout += `template_path: ${template}
|
|
26524
26650
|
|
|
26525
26651
|
`;
|
|
26526
|
-
stdout += readTrimmed(
|
|
26652
|
+
stdout += readTrimmed(path12.join(cwd, template), 220);
|
|
26527
26653
|
} else {
|
|
26528
26654
|
stdout += `template_path: none
|
|
26529
26655
|
|
|
@@ -26533,7 +26659,7 @@ ${status.stderr}` : "");
|
|
|
26533
26659
|
stdout += `
|
|
26534
26660
|
`;
|
|
26535
26661
|
stdout += printSection("Recent Validation Signals");
|
|
26536
|
-
if (
|
|
26662
|
+
if (existsSync6(path12.join(cwd, "package.json"))) {
|
|
26537
26663
|
stdout += `package.json detected. Common scripts:
|
|
26538
26664
|
`;
|
|
26539
26665
|
const found = packageScripts(cwd, ["lint", "format:check", "test", "build"]);
|
|
@@ -26542,7 +26668,7 @@ ${status.stderr}` : "");
|
|
|
26542
26668
|
`) + `
|
|
26543
26669
|
`;
|
|
26544
26670
|
}
|
|
26545
|
-
if (
|
|
26671
|
+
if (existsSync6(path12.join(cwd, "Cargo.toml")) || existsSync6(path12.join(cwd, "src-tauri/Cargo.toml"))) {
|
|
26546
26672
|
stdout += `Rust project detected.
|
|
26547
26673
|
`;
|
|
26548
26674
|
}
|
|
@@ -26575,7 +26701,7 @@ var CHANGELOG_RULES = `- Use an [Unreleased] section.
|
|
|
26575
26701
|
- Apply with the native workit_changelog_apply tool only (not hand-edits under Unreleased).
|
|
26576
26702
|
- If Unreleased already has duplicate category headings, normalize_only first.`;
|
|
26577
26703
|
function changelogContext(root, range) {
|
|
26578
|
-
const cwd =
|
|
26704
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26579
26705
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26580
26706
|
return invalidRange(cwd);
|
|
26581
26707
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26593,8 +26719,8 @@ function changelogContext(root, range) {
|
|
|
26593
26719
|
stdout += CHANGELOG_RULES + `
|
|
26594
26720
|
`;
|
|
26595
26721
|
stdout += printSection("Existing CHANGELOG.md");
|
|
26596
|
-
const changelogPath =
|
|
26597
|
-
if (
|
|
26722
|
+
const changelogPath = path12.join(cwd, "CHANGELOG.md");
|
|
26723
|
+
if (existsSync6(changelogPath)) {
|
|
26598
26724
|
stdout += readTrimmed(changelogPath, 260) + `
|
|
26599
26725
|
`;
|
|
26600
26726
|
} else {
|
|
@@ -26613,7 +26739,7 @@ function changelogContext(root, range) {
|
|
|
26613
26739
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26614
26740
|
}
|
|
26615
26741
|
function docsRefreshContext(root, range) {
|
|
26616
|
-
const cwd =
|
|
26742
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26617
26743
|
if (range !== undefined && !isResolvableContextRange(cwd, range))
|
|
26618
26744
|
return invalidRange(cwd);
|
|
26619
26745
|
const resolvedRange = rangeArgOrDefault(range, cwd);
|
|
@@ -26635,8 +26761,8 @@ function docsRefreshContext(root, range) {
|
|
|
26635
26761
|
`) + `
|
|
26636
26762
|
`;
|
|
26637
26763
|
stdout += printSection("README Preview");
|
|
26638
|
-
const readme =
|
|
26639
|
-
if (
|
|
26764
|
+
const readme = path12.join(cwd, "README.md");
|
|
26765
|
+
if (existsSync6(readme)) {
|
|
26640
26766
|
stdout += readTrimmed(readme, 220) + `
|
|
26641
26767
|
`;
|
|
26642
26768
|
} else {
|
|
@@ -26644,8 +26770,8 @@ function docsRefreshContext(root, range) {
|
|
|
26644
26770
|
`;
|
|
26645
26771
|
}
|
|
26646
26772
|
stdout += printSection("Package Scripts");
|
|
26647
|
-
const pkgPath =
|
|
26648
|
-
if (
|
|
26773
|
+
const pkgPath = path12.join(cwd, "package.json");
|
|
26774
|
+
if (existsSync6(pkgPath)) {
|
|
26649
26775
|
try {
|
|
26650
26776
|
const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
|
|
26651
26777
|
stdout += JSON.stringify({ name: pkg.name, version: pkg.version, scripts: pkg.scripts }, null, 2) + `
|
|
@@ -26658,7 +26784,7 @@ function docsRefreshContext(root, range) {
|
|
|
26658
26784
|
return { stdout, stderr: "", exitCode: 0, cwd };
|
|
26659
26785
|
}
|
|
26660
26786
|
function releaseNotesContext(root, rangeOrTag) {
|
|
26661
|
-
const cwd =
|
|
26787
|
+
const cwd = path12.resolve(repoRoot(root));
|
|
26662
26788
|
if (!rangeOrTag) {
|
|
26663
26789
|
return { stdout: "", stderr: `ERROR: release tag or range required
|
|
26664
26790
|
`, exitCode: 1, cwd };
|
|
@@ -26696,7 +26822,7 @@ function releaseNotesContext(root, rangeOrTag) {
|
|
|
26696
26822
|
`;
|
|
26697
26823
|
stdout += printSection("Existing Release Files");
|
|
26698
26824
|
for (const rel of ["CHANGELOG.md", "RELEASE_NOTES.md", ".github/releases.md"]) {
|
|
26699
|
-
if (
|
|
26825
|
+
if (existsSync6(path12.join(cwd, rel)))
|
|
26700
26826
|
stdout += rel + `
|
|
26701
26827
|
`;
|
|
26702
26828
|
}
|
|
@@ -26887,25 +27013,6 @@ var fetchGitLabIssueBody = async (ref, root, deps = {}) => {
|
|
|
26887
27013
|
import fs10 from "node:fs";
|
|
26888
27014
|
import path13 from "node:path";
|
|
26889
27015
|
|
|
26890
|
-
// packages/workit-core/src/core/package-root.ts
|
|
26891
|
-
import { existsSync as existsSync6 } from "node:fs";
|
|
26892
|
-
import path12 from "node:path";
|
|
26893
|
-
import { fileURLToPath } from "node:url";
|
|
26894
|
-
var packageRoot = () => {
|
|
26895
|
-
let dir = path12.dirname(fileURLToPath(import.meta.url));
|
|
26896
|
-
while (true) {
|
|
26897
|
-
const parent = path12.dirname(dir);
|
|
26898
|
-
if (existsSync6(path12.join(dir, "package.json")) || parent === dir)
|
|
26899
|
-
return dir;
|
|
26900
|
-
dir = parent;
|
|
26901
|
-
}
|
|
26902
|
-
};
|
|
26903
|
-
var assetRoot = () => {
|
|
26904
|
-
const root = packageRoot();
|
|
26905
|
-
const assets = path12.join(root, "assets");
|
|
26906
|
-
return existsSync6(assets) ? assets : root;
|
|
26907
|
-
};
|
|
26908
|
-
|
|
26909
27016
|
// packages/workit-core/src/core/templates.ts
|
|
26910
27017
|
var repoRoot2 = assetRoot();
|
|
26911
27018
|
|
|
@@ -27409,6 +27516,32 @@ var toolInputSchema = (family) => {
|
|
|
27409
27516
|
const schema = boundedOperationJsonSchema(family);
|
|
27410
27517
|
return { type: "object", ...schema };
|
|
27411
27518
|
};
|
|
27519
|
+
var schemaBranches = (schema) => Array.isArray(schema.oneOf) ? schema.oneOf : Array.isArray(schema.anyOf) ? schema.anyOf : [schema];
|
|
27520
|
+
var readOnlyBranches = (family) => schemaBranches(boundedOperationJsonSchema(family)).filter((branch) => {
|
|
27521
|
+
const action = branch.properties?.action?.const;
|
|
27522
|
+
return typeof action === "string" && READ_ONLY_ACTIONS.has(action);
|
|
27523
|
+
});
|
|
27524
|
+
var advertisedToolSchemas = (attested) => {
|
|
27525
|
+
if (attested)
|
|
27526
|
+
return OPERATION_FAMILIES.map((family) => ({
|
|
27527
|
+
name: `workit_${family}`,
|
|
27528
|
+
description: operationDescription(family),
|
|
27529
|
+
inputSchema: toolInputSchema(family)
|
|
27530
|
+
}));
|
|
27531
|
+
return OPERATION_FAMILIES.flatMap((family) => {
|
|
27532
|
+
const branches = readOnlyBranches(family);
|
|
27533
|
+
if (!branches.length)
|
|
27534
|
+
return [];
|
|
27535
|
+
const schema = boundedOperationJsonSchema(family);
|
|
27536
|
+
return [
|
|
27537
|
+
{
|
|
27538
|
+
name: `workit_${family}`,
|
|
27539
|
+
description: `${operationDescription(family)} Unattested MCP callers can only run read-only actions; mutating operations require an attested host session.`,
|
|
27540
|
+
inputSchema: { type: "object", ...schema, oneOf: branches }
|
|
27541
|
+
}
|
|
27542
|
+
];
|
|
27543
|
+
});
|
|
27544
|
+
};
|
|
27412
27545
|
var sanitizeFailure = (result, workspaceRoot) => {
|
|
27413
27546
|
if (result.ok)
|
|
27414
27547
|
return result;
|
|
@@ -27463,13 +27596,16 @@ function createMcpServer(host, contextProvider) {
|
|
|
27463
27596
|
].filter((file) => existsSync7(file)));
|
|
27464
27597
|
let staleWarned = false;
|
|
27465
27598
|
const server = new Server({ name: "workit", version: VERSION }, { capabilities: { tools: {}, resources: {} } });
|
|
27466
|
-
server.setRequestHandler(ListToolsRequestSchema, async () =>
|
|
27467
|
-
|
|
27468
|
-
|
|
27469
|
-
|
|
27470
|
-
|
|
27471
|
-
}
|
|
27472
|
-
|
|
27599
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
27600
|
+
let attested = false;
|
|
27601
|
+
try {
|
|
27602
|
+
const context = await contextProvider.current();
|
|
27603
|
+
attested = context.callerAttested === true;
|
|
27604
|
+
} catch {
|
|
27605
|
+
attested = false;
|
|
27606
|
+
}
|
|
27607
|
+
return { tools: advertisedToolSchemas(attested) };
|
|
27608
|
+
});
|
|
27473
27609
|
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
|
|
27474
27610
|
resources: CONTEXT_KINDS.map((kind) => ({
|
|
27475
27611
|
uri: `workit://context/${kind}`,
|
|
@@ -27584,7 +27720,7 @@ function createMcpServer(host, contextProvider) {
|
|
|
27584
27720
|
const parsed = parseOperation(family, request.params.arguments);
|
|
27585
27721
|
if (!parsed.ok)
|
|
27586
27722
|
return resultForClient(parsed, workspaceRoot);
|
|
27587
|
-
if (context.callerAttested
|
|
27723
|
+
if (context.callerAttested !== true && requiresCallerIdentity(parsed.data))
|
|
27588
27724
|
return resultForClient({
|
|
27589
27725
|
ok: false,
|
|
27590
27726
|
schemaVersion: 1,
|