@brainervirus/workit-pi 8.13.1 → 8.14.1
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/worker.js
CHANGED
|
@@ -7546,6 +7546,8 @@ var text = string2().check((ctx) => {
|
|
|
7546
7546
|
ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
|
|
7547
7547
|
});
|
|
7548
7548
|
var id = text.regex(uuidPattern, "expected lowercase UUID");
|
|
7549
|
+
var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
|
|
7550
|
+
var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
|
|
7549
7551
|
var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
|
|
7550
7552
|
var revision = text.regex(uuidPattern, "expected UUID revision");
|
|
7551
7553
|
var validUtc = (value) => {
|
|
@@ -7825,7 +7827,7 @@ var workerReportSchema = object({
|
|
|
7825
7827
|
outcome: _enum(["completed", "failed", "cancelled"]),
|
|
7826
7828
|
summary: text,
|
|
7827
7829
|
evidenceIds: array(id),
|
|
7828
|
-
findingIds: array(
|
|
7830
|
+
findingIds: array(idRef)
|
|
7829
7831
|
}).strict();
|
|
7830
7832
|
var workerSchema = object({
|
|
7831
7833
|
assignment: assignmentSchema,
|
|
@@ -8018,7 +8020,7 @@ var findingOperations = {
|
|
|
8018
8020
|
action: literal("resolve"),
|
|
8019
8021
|
...taskId,
|
|
8020
8022
|
expectedRevision: revision.optional(),
|
|
8021
|
-
findingId:
|
|
8023
|
+
findingId: idRef,
|
|
8022
8024
|
disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
|
|
8023
8025
|
reason: text,
|
|
8024
8026
|
evidenceIds: array(id),
|
|
@@ -8059,7 +8061,7 @@ var workerOperations = {
|
|
|
8059
8061
|
...taskId,
|
|
8060
8062
|
expectedRevision: revision.optional(),
|
|
8061
8063
|
expectedWorkspaceRevision: revision.optional(),
|
|
8062
|
-
workerId:
|
|
8064
|
+
workerId: idRef,
|
|
8063
8065
|
report: workerReportSchema
|
|
8064
8066
|
}),
|
|
8065
8067
|
cancel: operation({
|
|
@@ -8067,7 +8069,7 @@ var workerOperations = {
|
|
|
8067
8069
|
...taskId,
|
|
8068
8070
|
...revisions,
|
|
8069
8071
|
expectedWorkspaceRevision: revision.optional(),
|
|
8070
|
-
workerId:
|
|
8072
|
+
workerId: idRef,
|
|
8071
8073
|
reason: text
|
|
8072
8074
|
})
|
|
8073
8075
|
};
|
|
@@ -8537,6 +8539,8 @@ var workspaceConfigSchema = object({
|
|
|
8537
8539
|
defaultEndpoint: unknown().optional(),
|
|
8538
8540
|
verification: _enum(["self", "independent"]).optional(),
|
|
8539
8541
|
trailers: boolean2().optional(),
|
|
8542
|
+
liveSessionGuard: boolean2().optional(),
|
|
8543
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
8540
8544
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
8541
8545
|
kind,
|
|
8542
8546
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -8581,6 +8585,17 @@ var mainCheckouts = new Map;
|
|
|
8581
8585
|
var runtimeWorkspaceIndexSchema = object({
|
|
8582
8586
|
workspaces: array(object({ name: nonBlank, glob: nonBlank }).passthrough()).optional()
|
|
8583
8587
|
}).passthrough();
|
|
8588
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
8589
|
+
var TIER_HOSTS = new Set([
|
|
8590
|
+
"claude_code",
|
|
8591
|
+
"opencode",
|
|
8592
|
+
"codex",
|
|
8593
|
+
"codex_cli",
|
|
8594
|
+
"codex_desktop",
|
|
8595
|
+
"cursor",
|
|
8596
|
+
"pi",
|
|
8597
|
+
"default"
|
|
8598
|
+
]);
|
|
8584
8599
|
|
|
8585
8600
|
// packages/workit-core/src/core/release-tracks.ts
|
|
8586
8601
|
var KNOWN_FIELDS = new Set([
|
package/dist/workit.js
CHANGED
|
@@ -7771,6 +7771,8 @@ var text = string2().check((ctx) => {
|
|
|
7771
7771
|
ctx.issues.push({ code: "custom", input: ctx.value, message: "invalid Unicode", path: [] });
|
|
7772
7772
|
});
|
|
7773
7773
|
var id = text.regex(uuidPattern, "expected lowercase UUID");
|
|
7774
|
+
var idRefPattern = /^[0-9a-f][0-9a-f-]{7,35}$/;
|
|
7775
|
+
var idRef = text.regex(idRefPattern, "expected a UUID or an id prefix of 8+ characters");
|
|
7774
7776
|
var digest = text.regex(digestPattern, "expected lowercase SHA-256 digest");
|
|
7775
7777
|
var revision = text.regex(uuidPattern, "expected UUID revision");
|
|
7776
7778
|
var validUtc = (value) => {
|
|
@@ -8148,7 +8150,7 @@ var workerReportSchema = object({
|
|
|
8148
8150
|
outcome: _enum(["completed", "failed", "cancelled"]),
|
|
8149
8151
|
summary: text,
|
|
8150
8152
|
evidenceIds: array(id),
|
|
8151
|
-
findingIds: array(
|
|
8153
|
+
findingIds: array(idRef)
|
|
8152
8154
|
}).strict();
|
|
8153
8155
|
var workerSchema = object({
|
|
8154
8156
|
assignment: assignmentSchema,
|
|
@@ -8394,7 +8396,7 @@ var findingOperations = {
|
|
|
8394
8396
|
action: literal("resolve"),
|
|
8395
8397
|
...taskId,
|
|
8396
8398
|
expectedRevision: revision.optional(),
|
|
8397
|
-
findingId:
|
|
8399
|
+
findingId: idRef,
|
|
8398
8400
|
disposition: _enum(["open", "fixed", "dismissed", "deferred"]),
|
|
8399
8401
|
reason: text,
|
|
8400
8402
|
evidenceIds: array(id),
|
|
@@ -8435,7 +8437,7 @@ var workerOperations = {
|
|
|
8435
8437
|
...taskId,
|
|
8436
8438
|
expectedRevision: revision.optional(),
|
|
8437
8439
|
expectedWorkspaceRevision: revision.optional(),
|
|
8438
|
-
workerId:
|
|
8440
|
+
workerId: idRef,
|
|
8439
8441
|
report: workerReportSchema
|
|
8440
8442
|
}),
|
|
8441
8443
|
cancel: operation({
|
|
@@ -8443,7 +8445,7 @@ var workerOperations = {
|
|
|
8443
8445
|
...taskId,
|
|
8444
8446
|
...revisions,
|
|
8445
8447
|
expectedWorkspaceRevision: revision.optional(),
|
|
8446
|
-
workerId:
|
|
8448
|
+
workerId: idRef,
|
|
8447
8449
|
reason: text
|
|
8448
8450
|
})
|
|
8449
8451
|
};
|
|
@@ -8493,6 +8495,16 @@ var success = (revisionValue, workspaceRevisionValue, data) => ({
|
|
|
8493
8495
|
data
|
|
8494
8496
|
});
|
|
8495
8497
|
var failure2 = (code, error, details = {}) => ({ ok: false, schemaVersion: 1, code, error, details });
|
|
8498
|
+
function resolveIdRef(kind, ref, ids) {
|
|
8499
|
+
if (ids.includes(ref))
|
|
8500
|
+
return success(null, null, ref);
|
|
8501
|
+
const matches = ids.filter((candidate) => candidate.startsWith(ref));
|
|
8502
|
+
if (matches.length === 1)
|
|
8503
|
+
return success(null, null, matches[0]);
|
|
8504
|
+
if (matches.length === 0)
|
|
8505
|
+
return failure2("not_found", `${kind} not found`);
|
|
8506
|
+
return failure2("invalid_input", `${kind} prefix ${ref} is ambiguous; candidates: ${matches.join(", ")}`);
|
|
8507
|
+
}
|
|
8496
8508
|
function parseOperation(family, input) {
|
|
8497
8509
|
if (typeof input === "object" && input !== null && "schemaVersion" in input && input.schemaVersion !== SCHEMA_VERSION)
|
|
8498
8510
|
return failure2("unsupported_version", "unsupported schema version", { operation: family });
|
|
@@ -11036,20 +11048,26 @@ class TaskStore {
|
|
|
11036
11048
|
return success(null, null, { key: key.data, bound: bound.data });
|
|
11037
11049
|
}
|
|
11038
11050
|
taskForWorker(workerId) {
|
|
11039
|
-
if (typeof workerId !== "string" || !
|
|
11051
|
+
if (typeof workerId !== "string" || !idRefPattern.test(workerId))
|
|
11040
11052
|
return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
|
|
11041
11053
|
const listed = this.listTaskIndex();
|
|
11042
11054
|
if (!listed.ok)
|
|
11043
11055
|
return listed;
|
|
11056
|
+
const owners = new Map;
|
|
11044
11057
|
for (const entry of listed.data) {
|
|
11045
11058
|
if (entry.status === "closed")
|
|
11046
11059
|
continue;
|
|
11047
11060
|
const loaded = this.load(entry.id);
|
|
11048
11061
|
if (!loaded.ok)
|
|
11049
11062
|
return loaded;
|
|
11050
|
-
|
|
11051
|
-
|
|
11063
|
+
for (const worker of loaded.data?.record.workers ?? [])
|
|
11064
|
+
owners.set(worker.id, entry.id);
|
|
11052
11065
|
}
|
|
11066
|
+
const resolved = resolveIdRef("worker", workerId, [...owners.keys()]);
|
|
11067
|
+
if (resolved.ok)
|
|
11068
|
+
return success(null, null, owners.get(resolved.data) ?? null);
|
|
11069
|
+
if (resolved.code !== "not_found")
|
|
11070
|
+
return resolved;
|
|
11053
11071
|
return success(null, null, null);
|
|
11054
11072
|
}
|
|
11055
11073
|
originTaskEntry() {
|
|
@@ -13167,6 +13185,8 @@ var workspaceConfigSchema = object({
|
|
|
13167
13185
|
defaultEndpoint: unknown().optional(),
|
|
13168
13186
|
verification: _enum(["self", "independent"]).optional(),
|
|
13169
13187
|
trailers: boolean2().optional(),
|
|
13188
|
+
liveSessionGuard: boolean2().optional(),
|
|
13189
|
+
fanout: object({ tierModels: unknown().optional() }).passthrough().optional(),
|
|
13170
13190
|
autonomy: object(Object.fromEntries(["push", "pr", "merge", "release", "rerun"].map((kind) => [
|
|
13171
13191
|
kind,
|
|
13172
13192
|
union([boolean2(), literal("verified")]).optional()
|
|
@@ -13484,24 +13504,39 @@ var resolveRuntimeReleaseTracks = (cwd) => {
|
|
|
13484
13504
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13485
13505
|
return matched ? { workspace: matched.selected.name, releaseTracks: matched.selected.releaseTracks } : null;
|
|
13486
13506
|
};
|
|
13487
|
-
var
|
|
13507
|
+
var inheritedSetting = (cwd, dir, has) => {
|
|
13488
13508
|
let matched;
|
|
13489
13509
|
try {
|
|
13490
13510
|
matched = matchRuntimeWorkspace(cwd, dir);
|
|
13491
13511
|
} catch {
|
|
13492
|
-
return
|
|
13512
|
+
return null;
|
|
13493
13513
|
}
|
|
13494
13514
|
if (!matched)
|
|
13495
|
-
return
|
|
13515
|
+
return null;
|
|
13496
13516
|
const { selected, matches } = matched;
|
|
13497
13517
|
const enclosing = matches.filter((entry) => entry !== selected).toSorted((a, b) => globSpecificity(b.glob) - globSpecificity(a.glob));
|
|
13498
|
-
|
|
13518
|
+
return { workspace: selected.name, source: [selected, ...enclosing].find(has) ?? null };
|
|
13519
|
+
};
|
|
13520
|
+
var workspaceTrailers = (cwd, dir) => {
|
|
13521
|
+
const found = inheritedSetting(cwd, dir, (entry) => typeof entry.trailers === "boolean");
|
|
13499
13522
|
return {
|
|
13500
|
-
enabled: source?.trailers !== false,
|
|
13501
|
-
workspace:
|
|
13502
|
-
from: source?.name ?? null
|
|
13523
|
+
enabled: found?.source?.trailers !== false,
|
|
13524
|
+
workspace: found?.workspace ?? null,
|
|
13525
|
+
from: found?.source?.name ?? null
|
|
13503
13526
|
};
|
|
13504
13527
|
};
|
|
13528
|
+
var workspaceLiveSessionGuard = (cwd, dir) => inheritedSetting(cwd, dir, (entry) => typeof entry.liveSessionGuard === "boolean")?.source?.liveSessionGuard !== false;
|
|
13529
|
+
var TIER_KEYS = new Set(["mundane", "standard", "hard"]);
|
|
13530
|
+
var TIER_HOSTS = new Set([
|
|
13531
|
+
"claude_code",
|
|
13532
|
+
"opencode",
|
|
13533
|
+
"codex",
|
|
13534
|
+
"codex_cli",
|
|
13535
|
+
"codex_desktop",
|
|
13536
|
+
"cursor",
|
|
13537
|
+
"pi",
|
|
13538
|
+
"default"
|
|
13539
|
+
]);
|
|
13505
13540
|
var resolveRuntimeWorkspaceCandidate = (cwd, kind) => {
|
|
13506
13541
|
const matched = matchRuntimeWorkspace(cwd);
|
|
13507
13542
|
if (!matched)
|
|
@@ -18981,6 +19016,8 @@ var TASK_SCOPED_ACTIONS = new Set([
|
|
|
18981
19016
|
"worker.cancel",
|
|
18982
19017
|
"state.export"
|
|
18983
19018
|
]);
|
|
19019
|
+
var ONLY_OWN_REPORT = "workers can submit only their own report";
|
|
19020
|
+
var NO_HELPER_CONTROL = "helpers cannot cancel or control workers";
|
|
18984
19021
|
var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
|
|
18985
19022
|
var workerCommand = (taskId, request) => {
|
|
18986
19023
|
const { action, workerId, reason, report } = request;
|
|
@@ -19878,6 +19915,10 @@ class WorkitCore {
|
|
|
19878
19915
|
}
|
|
19879
19916
|
if (helper?.ok)
|
|
19880
19917
|
return failure2("permission_denied", "helpers cannot resolve findings");
|
|
19918
|
+
const findingRef = resolveIdRef("finding", input.findingId, task.data.findings.map((entry) => entry.id));
|
|
19919
|
+
if (!findingRef.ok)
|
|
19920
|
+
return findingRef;
|
|
19921
|
+
input.findingId = findingRef.data;
|
|
19881
19922
|
const finding = task.data.findings.find((entry) => entry.id === input.findingId);
|
|
19882
19923
|
if (!finding)
|
|
19883
19924
|
return failure2("not_found", "finding not found");
|
|
@@ -20029,15 +20070,31 @@ class WorkitCore {
|
|
|
20029
20070
|
return changed;
|
|
20030
20071
|
return success(changed.data.task.revision, changed.data.workspace.revision, changed.data.task.workers.at(-1));
|
|
20031
20072
|
}
|
|
20073
|
+
const workerRef = resolveIdRef("worker", input.workerId, helperId === null ? task.data.workers.map((candidate) => candidate.id) : [helperId]);
|
|
20074
|
+
if (!workerRef.ok)
|
|
20075
|
+
return helperId === null ? workerRef : failure2("permission_denied", input.action === "report" ? ONLY_OWN_REPORT : NO_HELPER_CONTROL);
|
|
20076
|
+
input.workerId = workerRef.data;
|
|
20032
20077
|
const entry = task.data.workers.find((candidate) => candidate.id === input.workerId);
|
|
20033
20078
|
if (!entry)
|
|
20034
20079
|
return failure2("not_found", "worker not found");
|
|
20035
20080
|
if (input.action === "report") {
|
|
20036
20081
|
if (helperId === null || helperId !== input.workerId)
|
|
20037
|
-
return failure2("permission_denied",
|
|
20082
|
+
return failure2("permission_denied", ONLY_OWN_REPORT);
|
|
20038
20083
|
const helper = this.helperEntry(task.data, true, true);
|
|
20039
20084
|
if (!helper.ok)
|
|
20040
20085
|
return helper;
|
|
20086
|
+
const ownFindings = task.data.findings.filter((candidate) => candidate.provenance.workerId === helperId).map((candidate) => candidate.id);
|
|
20087
|
+
const reported = [];
|
|
20088
|
+
for (const findingRef of input.report.findingIds) {
|
|
20089
|
+
const found = resolveIdRef("finding", findingRef, ownFindings);
|
|
20090
|
+
if (found.ok)
|
|
20091
|
+
reported.push(found.data);
|
|
20092
|
+
else if (found.code === "not_found")
|
|
20093
|
+
reported.push(findingRef);
|
|
20094
|
+
else
|
|
20095
|
+
return found;
|
|
20096
|
+
}
|
|
20097
|
+
input.report.findingIds = reported;
|
|
20041
20098
|
const evidence = task.data.evidence.filter((candidate) => input.report.evidenceIds.includes(candidate.id));
|
|
20042
20099
|
const findings = task.data.findings.filter((candidate) => input.report.findingIds.includes(candidate.id));
|
|
20043
20100
|
if (evidence.length !== input.report.evidenceIds.length || findings.length !== input.report.findingIds.length)
|
|
@@ -20065,7 +20122,7 @@ class WorkitCore {
|
|
|
20065
20122
|
return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during report");
|
|
20066
20123
|
}
|
|
20067
20124
|
if (helperId !== null)
|
|
20068
|
-
return failure2("permission_denied",
|
|
20125
|
+
return failure2("permission_denied", NO_HELPER_CONTROL);
|
|
20069
20126
|
if (task.data.status === "closed")
|
|
20070
20127
|
return failure2("invalid_transition", "closed task cannot cancel workers");
|
|
20071
20128
|
if (entry.data.state === "stopped") {
|
|
@@ -21803,27 +21860,231 @@ var shellPolicy = (cwd, command, dialect = "posix") => {
|
|
|
21803
21860
|
};
|
|
21804
21861
|
// packages/workit-core/src/hooks/write-gate.ts
|
|
21805
21862
|
import { realpathSync as realpathSync6 } from "node:fs";
|
|
21863
|
+
import os7 from "node:os";
|
|
21864
|
+
import path27 from "node:path";
|
|
21865
|
+
|
|
21866
|
+
// packages/workit-core/src/core/live-session.ts
|
|
21867
|
+
import { spawnSync as spawnSync17 } from "node:child_process";
|
|
21868
|
+
import { createHash as createHash15 } from "node:crypto";
|
|
21869
|
+
import fs19 from "node:fs";
|
|
21870
|
+
import os6 from "node:os";
|
|
21806
21871
|
import path26 from "node:path";
|
|
21872
|
+
var LIVE_SESSION_TTL_MS = 15 * 60000;
|
|
21873
|
+
var HEARTBEAT_EVERY_MS = 30000;
|
|
21874
|
+
var liveProbe = {
|
|
21875
|
+
platform: process.platform,
|
|
21876
|
+
run: (args) => spawnSync17("ps", args, { encoding: "utf8", timeout: 2000 }).stdout ?? "",
|
|
21877
|
+
start: (pid) => processStartOf(pid)
|
|
21878
|
+
};
|
|
21879
|
+
var leadOf = (session) => session.split(":")[0].replace(/-w-.*$/su, "") || session;
|
|
21880
|
+
var familyOf = (host) => host.replace(/_(?:cli|desktop)$/u, "");
|
|
21881
|
+
var processAlive = (pid) => {
|
|
21882
|
+
try {
|
|
21883
|
+
process.kill(pid, 0);
|
|
21884
|
+
return true;
|
|
21885
|
+
} catch (error) {
|
|
21886
|
+
return error.code === "EPERM";
|
|
21887
|
+
}
|
|
21888
|
+
};
|
|
21889
|
+
var processInfo = (pid) => {
|
|
21890
|
+
try {
|
|
21891
|
+
if (liveProbe.platform === "linux") {
|
|
21892
|
+
const stat = fs19.readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
21893
|
+
const ppid = Number(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1]);
|
|
21894
|
+
const command = fs19.readFileSync(`/proc/${pid}/cmdline`, "utf8").replaceAll("\x00", " ");
|
|
21895
|
+
return Number.isInteger(ppid) ? { ppid, command: command.trim() } : null;
|
|
21896
|
+
}
|
|
21897
|
+
if (liveProbe.platform === "win32")
|
|
21898
|
+
return null;
|
|
21899
|
+
const match = /^\s*(\d+)\s+(.*)$/su.exec(liveProbe.run(["-o", "ppid=,command=", "-p", String(pid)]));
|
|
21900
|
+
return match ? { ppid: Number(match[1]), command: match[2].trim() } : null;
|
|
21901
|
+
} catch {
|
|
21902
|
+
return null;
|
|
21903
|
+
}
|
|
21904
|
+
};
|
|
21905
|
+
var WRAPPER = /^(?:\S*\/)?(?:sh|bash|dash|zsh|ash|ksh|fish|env|timeout|npx|npm|bunx|cmd\.exe|pwsh|powershell)(?:\s|$)|launch\.mjs|npx-cli\.js/u;
|
|
21906
|
+
var PIDS = new Map;
|
|
21907
|
+
var hostPid = (transport) => {
|
|
21908
|
+
if (transport !== "hook-process")
|
|
21909
|
+
return process.pid;
|
|
21910
|
+
const key = `${liveProbe.platform}\x00${process.ppid}`;
|
|
21911
|
+
if (PIDS.has(key))
|
|
21912
|
+
return PIDS.get(key) ?? null;
|
|
21913
|
+
if (liveProbe.platform === "win32") {
|
|
21914
|
+
PIDS.set(key, null);
|
|
21915
|
+
return null;
|
|
21916
|
+
}
|
|
21917
|
+
let pid = process.ppid;
|
|
21918
|
+
for (let hop = 0;hop < 4; hop++) {
|
|
21919
|
+
const info = processInfo(pid);
|
|
21920
|
+
if (!info || !WRAPPER.test(info.command) || info.ppid <= 1)
|
|
21921
|
+
break;
|
|
21922
|
+
pid = info.ppid;
|
|
21923
|
+
}
|
|
21924
|
+
PIDS.set(key, pid);
|
|
21925
|
+
return pid;
|
|
21926
|
+
};
|
|
21927
|
+
var sessionSafe = (value) => value.replace(/[^A-Za-z0-9_.:@/+-]/gu, "-");
|
|
21928
|
+
var liveOwner = (input, transport, env = process.env) => {
|
|
21929
|
+
const session = input.session.id?.trim();
|
|
21930
|
+
if (!session)
|
|
21931
|
+
return null;
|
|
21932
|
+
const leads = new Set([leadOf(sessionSafe(session))]);
|
|
21933
|
+
if (input.session.parentId)
|
|
21934
|
+
leads.add(leadOf(sessionSafe(input.session.parentId)));
|
|
21935
|
+
if (env.WORKIT_PI_WORKER_SESSION && env.WORKIT_SESSION_ID)
|
|
21936
|
+
leads.add(leadOf(sessionSafe(env.WORKIT_SESSION_ID)));
|
|
21937
|
+
return {
|
|
21938
|
+
host: input.host,
|
|
21939
|
+
session: sessionSafe(session),
|
|
21940
|
+
leads: [...leads],
|
|
21941
|
+
pid: () => hostPid(transport)
|
|
21942
|
+
};
|
|
21943
|
+
};
|
|
21944
|
+
var checkoutOf = (target) => {
|
|
21945
|
+
let dir = path26.resolve(target);
|
|
21946
|
+
while (!fs19.existsSync(dir) && path26.dirname(dir) !== dir)
|
|
21947
|
+
dir = path26.dirname(dir);
|
|
21948
|
+
if (fs19.existsSync(dir) && !fs19.statSync(dir).isDirectory())
|
|
21949
|
+
dir = path26.dirname(dir);
|
|
21950
|
+
const parts = dir.split(path26.sep);
|
|
21951
|
+
const dotGit = parts.lastIndexOf(".git");
|
|
21952
|
+
if (dotGit > 0)
|
|
21953
|
+
dir = parts.slice(0, dotGit).join(path26.sep) || path26.sep;
|
|
21954
|
+
const location = resolveStore(dir);
|
|
21955
|
+
if (location instanceof Error || !location.shared || !location.top)
|
|
21956
|
+
return null;
|
|
21957
|
+
let top = location.top;
|
|
21958
|
+
try {
|
|
21959
|
+
top = fs19.realpathSync.native(top);
|
|
21960
|
+
} catch {}
|
|
21961
|
+
return top;
|
|
21962
|
+
};
|
|
21963
|
+
var markerFile2 = (top) => path26.join(resolveStateDir(), "live-sessions", `${createHash15("sha256").update(checkoutKey(top)).digest("hex").slice(0, 32)}.json`);
|
|
21964
|
+
var checkoutKey = (top, platform = process.platform) => {
|
|
21965
|
+
let key = path26.resolve(top);
|
|
21966
|
+
try {
|
|
21967
|
+
key = fs19.realpathSync.native(key);
|
|
21968
|
+
} catch {}
|
|
21969
|
+
return platform === "win32" ? key.replaceAll("/", "\\").toLowerCase() : key;
|
|
21970
|
+
};
|
|
21971
|
+
var readMarker = (file) => {
|
|
21972
|
+
try {
|
|
21973
|
+
const value = JSON.parse(fs19.readFileSync(file, "utf8"));
|
|
21974
|
+
if (value?.version !== 1 || typeof value.session !== "string" || typeof value.host !== "string" || typeof value.heartbeatAt !== "string" || Number.isNaN(Date.parse(value.heartbeatAt)))
|
|
21975
|
+
return null;
|
|
21976
|
+
return {
|
|
21977
|
+
version: 1,
|
|
21978
|
+
session: value.session,
|
|
21979
|
+
leads: Array.isArray(value.leads) ? value.leads.filter((item) => typeof item === "string") : [],
|
|
21980
|
+
host: value.host,
|
|
21981
|
+
pid: typeof value.pid === "number" ? value.pid : null,
|
|
21982
|
+
pidStart: typeof value.pidStart === "string" ? value.pidStart : null,
|
|
21983
|
+
machine: typeof value.machine === "string" ? value.machine : "",
|
|
21984
|
+
checkout: typeof value.checkout === "string" ? value.checkout : "",
|
|
21985
|
+
heartbeatAt: value.heartbeatAt
|
|
21986
|
+
};
|
|
21987
|
+
} catch {
|
|
21988
|
+
return null;
|
|
21989
|
+
}
|
|
21990
|
+
};
|
|
21991
|
+
var ageOf2 = (marker, now) => now.getTime() - Date.parse(marker.heartbeatAt);
|
|
21992
|
+
var isLive = (marker, deps) => {
|
|
21993
|
+
const age = ageOf2(marker, deps.now ?? new Date);
|
|
21994
|
+
if (age > (deps.ttlMs ?? LIVE_SESSION_TTL_MS))
|
|
21995
|
+
return false;
|
|
21996
|
+
if (marker.machine !== (deps.machine ?? os6.hostname()) || marker.pid === null)
|
|
21997
|
+
return true;
|
|
21998
|
+
if (!(deps.alive ?? processAlive)(marker.pid))
|
|
21999
|
+
return false;
|
|
22000
|
+
if (marker.pidStart === null)
|
|
22001
|
+
return true;
|
|
22002
|
+
const start = liveProbe.start(marker.pid);
|
|
22003
|
+
return start === null || start === marker.pidStart;
|
|
22004
|
+
};
|
|
22005
|
+
var sameLead = (marker, owner) => marker.session === owner.session || owner.leads.some((lead) => lead === marker.session || marker.leads.includes(lead));
|
|
22006
|
+
var sameOwner = (marker, owner, machine) => {
|
|
22007
|
+
if (sameLead(marker, owner))
|
|
22008
|
+
return true;
|
|
22009
|
+
if (marker.machine !== machine)
|
|
22010
|
+
return false;
|
|
22011
|
+
const pid = owner.pid();
|
|
22012
|
+
if (pid === null || marker.pid === null || marker.pidStart === null)
|
|
22013
|
+
return familyOf(marker.host) === familyOf(owner.host);
|
|
22014
|
+
return marker.pid === pid;
|
|
22015
|
+
};
|
|
22016
|
+
var liveConflict = (target, owner, deps = {}) => {
|
|
22017
|
+
try {
|
|
22018
|
+
const checkout = checkoutOf(target);
|
|
22019
|
+
if (!checkout)
|
|
22020
|
+
return null;
|
|
22021
|
+
const file = markerFile2(checkout);
|
|
22022
|
+
const marker = readMarker(file);
|
|
22023
|
+
if (!marker || sameLead(marker, owner) || !isLive(marker, deps))
|
|
22024
|
+
return null;
|
|
22025
|
+
if (sameOwner(marker, owner, deps.machine ?? os6.hostname()))
|
|
22026
|
+
return null;
|
|
22027
|
+
return { marker, file, ageMs: ageOf2(marker, deps.now ?? new Date), checkout };
|
|
22028
|
+
} catch {
|
|
22029
|
+
return null;
|
|
22030
|
+
}
|
|
22031
|
+
};
|
|
22032
|
+
var claimLiveSession = (target, owner, deps = {}) => {
|
|
22033
|
+
try {
|
|
22034
|
+
const checkout = checkoutOf(target);
|
|
22035
|
+
if (!checkout)
|
|
22036
|
+
return;
|
|
22037
|
+
const file = markerFile2(checkout);
|
|
22038
|
+
const now = deps.now ?? new Date;
|
|
22039
|
+
const machine = deps.machine ?? os6.hostname();
|
|
22040
|
+
const current = readMarker(file);
|
|
22041
|
+
if (current) {
|
|
22042
|
+
const age = ageOf2(current, now);
|
|
22043
|
+
if (sameLead(current, owner) && age >= 0 && age < HEARTBEAT_EVERY_MS)
|
|
22044
|
+
return;
|
|
22045
|
+
if (!sameLead(current, owner) && isLive(current, deps) && !sameOwner(current, owner, machine))
|
|
22046
|
+
return;
|
|
22047
|
+
}
|
|
22048
|
+
const pid = owner.pid();
|
|
22049
|
+
const marker = {
|
|
22050
|
+
version: 1,
|
|
22051
|
+
session: owner.session,
|
|
22052
|
+
leads: owner.leads,
|
|
22053
|
+
host: owner.host,
|
|
22054
|
+
pid,
|
|
22055
|
+
pidStart: pid === null ? null : liveProbe.start(pid),
|
|
22056
|
+
machine,
|
|
22057
|
+
checkout,
|
|
22058
|
+
heartbeatAt: now.toISOString()
|
|
22059
|
+
};
|
|
22060
|
+
fs19.mkdirSync(path26.dirname(file), { recursive: true });
|
|
22061
|
+
const temp = `${file}.${process.pid}.tmp`;
|
|
22062
|
+
fs19.writeFileSync(temp, JSON.stringify(marker));
|
|
22063
|
+
fs19.renameSync(temp, file);
|
|
22064
|
+
} catch {}
|
|
22065
|
+
};
|
|
22066
|
+
|
|
22067
|
+
// packages/workit-core/src/hooks/write-gate.ts
|
|
21807
22068
|
var NONE3 = { kind: "none" };
|
|
21808
22069
|
var CASE_INSENSITIVE = process.platform === "darwin" || process.platform === "win32";
|
|
21809
22070
|
var canonical2 = (file) => {
|
|
21810
22071
|
let rest = "";
|
|
21811
|
-
for (let dir =
|
|
22072
|
+
for (let dir = path27.resolve(file);; dir = path27.dirname(dir)) {
|
|
21812
22073
|
try {
|
|
21813
|
-
return
|
|
22074
|
+
return path27.join(realpathSync6(dir), rest);
|
|
21814
22075
|
} catch {
|
|
21815
|
-
if (
|
|
21816
|
-
return
|
|
21817
|
-
rest = rest ?
|
|
22076
|
+
if (path27.dirname(dir) === dir)
|
|
22077
|
+
return path27.resolve(file);
|
|
22078
|
+
rest = rest ? path27.join(path27.basename(dir), rest) : path27.basename(dir);
|
|
21818
22079
|
}
|
|
21819
22080
|
}
|
|
21820
22081
|
};
|
|
21821
22082
|
var exempt = (cwd, root, file, plans) => {
|
|
21822
22083
|
const fold = (value) => CASE_INSENSITIVE ? value.toLowerCase() : value;
|
|
21823
|
-
const relative =
|
|
21824
|
-
if (relative.startsWith("..") ||
|
|
22084
|
+
const relative = path27.relative(fold(canonical2(root)), fold(canonical2(path27.resolve(cwd, file))));
|
|
22085
|
+
if (relative.startsWith("..") || path27.isAbsolute(relative))
|
|
21825
22086
|
return true;
|
|
21826
|
-
const posix = relative.split(
|
|
22087
|
+
const posix = relative.split(path27.sep).join("/");
|
|
21827
22088
|
const segments = posix.split("/");
|
|
21828
22089
|
return /\.mdx?$/i.test(posix) || segments[0] === "docs" || segments.includes("plans") || [...plans].some((plan) => fold(plan) === posix);
|
|
21829
22090
|
};
|
|
@@ -21921,7 +22182,7 @@ var shellWrites = (command) => {
|
|
|
21921
22182
|
return { writes: true, targets: unknown ? null : targets };
|
|
21922
22183
|
};
|
|
21923
22184
|
var cache2 = new Map;
|
|
21924
|
-
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(
|
|
22185
|
+
var planKeyOf = (root, plans) => [...plans].map((file) => fileSignature(path27.resolve(root, file)) ?? "-").join("\x00");
|
|
21925
22186
|
var gateFor = (cwd) => {
|
|
21926
22187
|
const store = new TaskStore(cwd);
|
|
21927
22188
|
const listed = store.listTaskIndex();
|
|
@@ -21959,7 +22220,218 @@ var gateFor = (cwd) => {
|
|
|
21959
22220
|
cache2.delete(cache2.keys().next().value);
|
|
21960
22221
|
return gate;
|
|
21961
22222
|
};
|
|
21962
|
-
|
|
22223
|
+
var MUTATING_GIT = new Set([
|
|
22224
|
+
"add",
|
|
22225
|
+
"am",
|
|
22226
|
+
"apply",
|
|
22227
|
+
"checkout",
|
|
22228
|
+
"cherry-pick",
|
|
22229
|
+
"clean",
|
|
22230
|
+
"commit",
|
|
22231
|
+
"merge",
|
|
22232
|
+
"mv",
|
|
22233
|
+
"pull",
|
|
22234
|
+
"rebase",
|
|
22235
|
+
"reset",
|
|
22236
|
+
"restore",
|
|
22237
|
+
"revert",
|
|
22238
|
+
"rm",
|
|
22239
|
+
"stash",
|
|
22240
|
+
"switch"
|
|
22241
|
+
]);
|
|
22242
|
+
var DRY_N = new Set(["add", "clean", "rm", "mv"]);
|
|
22243
|
+
var MUTATING_WORKIT_GIT = new Set(["commit", "branch"]);
|
|
22244
|
+
var DIR_TARGET = ".workit-live-target";
|
|
22245
|
+
var OVERRIDE = "WORKIT_LIVE_SESSION=override";
|
|
22246
|
+
var inert = (args, sub) => args.some((arg) => arg === "--help" || arg === "-h" || arg === "--dry-run" || DRY_N.has(sub) && /^-[a-zA-Z]*n[a-zA-Z]*$/.test(arg) && !arg.startsWith("--"));
|
|
22247
|
+
var gitMutationDirs = (cwd, command, dialect) => {
|
|
22248
|
+
let invocations;
|
|
22249
|
+
try {
|
|
22250
|
+
invocations = rawInvocations(command, dialect);
|
|
22251
|
+
} catch {
|
|
22252
|
+
return [];
|
|
22253
|
+
}
|
|
22254
|
+
return invocations.flatMap((invocation) => {
|
|
22255
|
+
if (invocation.tool !== "git" || invocation.dir === undefined)
|
|
22256
|
+
return [];
|
|
22257
|
+
const at = invocation.args.findIndex((arg) => !arg.startsWith("-"));
|
|
22258
|
+
const sub = invocation.args[at] ?? "";
|
|
22259
|
+
const rest = invocation.args.slice(at + 1);
|
|
22260
|
+
if (!MUTATING_GIT.has(sub) || inert(rest, sub))
|
|
22261
|
+
return [];
|
|
22262
|
+
if (sub === "stash" && ["list", "show"].includes(rest[0] ?? ""))
|
|
22263
|
+
return [];
|
|
22264
|
+
return [path27.resolve(cwd, invocation.dir ?? ".")];
|
|
22265
|
+
});
|
|
22266
|
+
};
|
|
22267
|
+
var CD2 = new Set(["cd", "chdir", "set-location", "sl"]);
|
|
22268
|
+
var PUSHD2 = new Set(["pushd", "push-location"]);
|
|
22269
|
+
var POPD2 = new Set(["popd", "pop-location"]);
|
|
22270
|
+
var cdInto = (dir, words) => {
|
|
22271
|
+
const target = words.slice(1).find((word) => !word.startsWith("-") || word === "-");
|
|
22272
|
+
if (dir === undefined || target === "-" || target && /^~|[$`*?]/.test(target))
|
|
22273
|
+
return;
|
|
22274
|
+
return target === undefined ? os7.homedir() : path27.resolve(dir, target);
|
|
22275
|
+
};
|
|
22276
|
+
var quoteWord = (word) => /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replaceAll("'", "'\\''")}'`;
|
|
22277
|
+
var segmentTargets = (cwd, command, dialect) => {
|
|
22278
|
+
const targets = [];
|
|
22279
|
+
let dir = cwd;
|
|
22280
|
+
let stack = [];
|
|
22281
|
+
const groups = [];
|
|
22282
|
+
const at = (file) => path27.isAbsolute(file) ? file : dir === undefined ? null : path27.resolve(dir, file);
|
|
22283
|
+
for (const segment of segmentsOf(command, dialect)) {
|
|
22284
|
+
if (segment.group === "open") {
|
|
22285
|
+
groups.push({ dir, stack: [...stack] });
|
|
22286
|
+
continue;
|
|
22287
|
+
}
|
|
22288
|
+
if (segment.group === "close") {
|
|
22289
|
+
const saved = groups.pop();
|
|
22290
|
+
if (saved)
|
|
22291
|
+
({ dir, stack } = saved);
|
|
22292
|
+
continue;
|
|
22293
|
+
}
|
|
22294
|
+
const lead = segment.words.findIndex((word) => !/^\w+=/.test(word));
|
|
22295
|
+
const words = lead < 0 ? [] : segment.words.slice(lead);
|
|
22296
|
+
const name = (words[0]?.split(/[\\/]/).at(-1) ?? "").toLowerCase();
|
|
22297
|
+
if (CD2.has(name)) {
|
|
22298
|
+
dir = cdInto(dir, words);
|
|
22299
|
+
continue;
|
|
22300
|
+
}
|
|
22301
|
+
if (PUSHD2.has(name)) {
|
|
22302
|
+
stack.push(dir);
|
|
22303
|
+
dir = cdInto(dir, words);
|
|
22304
|
+
continue;
|
|
22305
|
+
}
|
|
22306
|
+
if (POPD2.has(name)) {
|
|
22307
|
+
dir = stack.length ? stack.pop() : undefined;
|
|
22308
|
+
continue;
|
|
22309
|
+
}
|
|
22310
|
+
for (const file of segment.redirects)
|
|
22311
|
+
if (!/^\/dev\/(?:null|stdout|stderr|tty)$/.test(file)) {
|
|
22312
|
+
const resolved = at(file);
|
|
22313
|
+
if (resolved)
|
|
22314
|
+
targets.push(resolved);
|
|
22315
|
+
}
|
|
22316
|
+
const edits = editsOf(segment.words);
|
|
22317
|
+
if (edits === null) {
|
|
22318
|
+
if (dir !== undefined)
|
|
22319
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22320
|
+
} else
|
|
22321
|
+
for (const file of edits) {
|
|
22322
|
+
const resolved = at(file);
|
|
22323
|
+
if (resolved)
|
|
22324
|
+
targets.push(resolved);
|
|
22325
|
+
}
|
|
22326
|
+
if (dir !== undefined && workitInvocations(segment.words.map(quoteWord).join(" "), "posix").some((args) => args[0] === "git" && MUTATING_WORKIT_GIT.has(args[1] ?? "") && !inert(args.slice(2), "")))
|
|
22327
|
+
targets.push(path27.join(dir, DIR_TARGET));
|
|
22328
|
+
}
|
|
22329
|
+
return targets;
|
|
22330
|
+
};
|
|
22331
|
+
var overridden = (command, dialect) => {
|
|
22332
|
+
const segments = segmentsOf(command, dialect);
|
|
22333
|
+
if (segments.length !== 1 || segments[0].group || segments[0].subs?.length)
|
|
22334
|
+
return false;
|
|
22335
|
+
const words = segments[0].words;
|
|
22336
|
+
const lead = words.findIndex((word) => !/^\w+=/.test(word));
|
|
22337
|
+
return lead > 0 && words.slice(0, lead).includes(OVERRIDE);
|
|
22338
|
+
};
|
|
22339
|
+
var HOST_LABELS = {
|
|
22340
|
+
claude_code: "Claude Code",
|
|
22341
|
+
opencode: "OpenCode",
|
|
22342
|
+
codex_cli: "Codex CLI",
|
|
22343
|
+
codex_desktop: "Codex Desktop",
|
|
22344
|
+
cursor: "Cursor",
|
|
22345
|
+
pi: "Pi"
|
|
22346
|
+
};
|
|
22347
|
+
var ageText = (ms) => {
|
|
22348
|
+
const seconds = Math.max(0, Math.round(ms / 1000));
|
|
22349
|
+
return seconds < 120 ? `${seconds}s` : `${Math.round(seconds / 60)} min`;
|
|
22350
|
+
};
|
|
22351
|
+
var liveDeny = ({ marker, checkout, ageMs, file }) => {
|
|
22352
|
+
const unblock = `Work in your own worktree (\`git worktree add <path> -b <branch>\`, or \`workit fanout worktree create <slice>\`). Only if the user confirms that session is finished: release it with \`rm '${file}'\`, or prefix one command with ${OVERRIDE}.`;
|
|
22353
|
+
return {
|
|
22354
|
+
kind: "deny",
|
|
22355
|
+
reason: `workit live-session guard: ${checkout} belongs to another live agent session, ${HOST_LABELS[marker.host] ?? marker.host} session ${marker.session}${marker.pid === null ? "" : ` (pid ${marker.pid})`}, last active ${ageText(ageMs)} ago. Do not edit a checkout another session is working in; read-only commands stay allowed. ${unblock}`,
|
|
22356
|
+
unblock
|
|
22357
|
+
};
|
|
22358
|
+
};
|
|
22359
|
+
var guarded = (targets) => {
|
|
22360
|
+
const seen = new Set;
|
|
22361
|
+
return targets.filter((target) => {
|
|
22362
|
+
const dir = path27.dirname(target);
|
|
22363
|
+
if (seen.has(dir))
|
|
22364
|
+
return false;
|
|
22365
|
+
seen.add(dir);
|
|
22366
|
+
return true;
|
|
22367
|
+
});
|
|
22368
|
+
};
|
|
22369
|
+
var liveGate = (owner, targets, deps) => {
|
|
22370
|
+
try {
|
|
22371
|
+
for (const target of guarded(targets)) {
|
|
22372
|
+
const conflict = liveConflict(target, owner, deps);
|
|
22373
|
+
if (conflict && workspaceLiveSessionGuard(conflict.checkout))
|
|
22374
|
+
return liveDeny(conflict);
|
|
22375
|
+
}
|
|
22376
|
+
} catch {}
|
|
22377
|
+
return NONE3;
|
|
22378
|
+
};
|
|
22379
|
+
var claimAll = (owner, targets, deps) => {
|
|
22380
|
+
for (const target of guarded(targets))
|
|
22381
|
+
claimLiveSession(target, owner, deps);
|
|
22382
|
+
};
|
|
22383
|
+
var liveTargets = (cwd, paths) => paths?.length ? paths.map((file) => path27.resolve(cwd, file)) : [path27.join(cwd, DIR_TARGET)];
|
|
22384
|
+
function writeGate(cwd, paths, owner, deps) {
|
|
22385
|
+
if (!owner)
|
|
22386
|
+
return beforeWriteGate(cwd, paths);
|
|
22387
|
+
const targets = liveTargets(cwd, paths);
|
|
22388
|
+
const live = liveGate(owner, targets, deps);
|
|
22389
|
+
if (live.kind !== "none")
|
|
22390
|
+
return live;
|
|
22391
|
+
const decision = beforeWriteGate(cwd, paths);
|
|
22392
|
+
if (decision.kind === "none")
|
|
22393
|
+
claimAll(owner, targets, deps);
|
|
22394
|
+
return decision;
|
|
22395
|
+
}
|
|
22396
|
+
function shellGate(cwd, command, owner, dialect = "posix", deps) {
|
|
22397
|
+
const writes = shellWrites(command);
|
|
22398
|
+
let targets = [];
|
|
22399
|
+
if (owner) {
|
|
22400
|
+
try {
|
|
22401
|
+
targets = [
|
|
22402
|
+
...segmentTargets(cwd, command, dialect),
|
|
22403
|
+
...gitMutationDirs(cwd, command, dialect).map((dir) => path27.join(dir, DIR_TARGET))
|
|
22404
|
+
];
|
|
22405
|
+
} catch {
|
|
22406
|
+
targets = [];
|
|
22407
|
+
}
|
|
22408
|
+
}
|
|
22409
|
+
if (owner && targets.length) {
|
|
22410
|
+
const live = liveGate(owner, targets, deps);
|
|
22411
|
+
if (live.kind !== "none") {
|
|
22412
|
+
if (!overridden(command, dialect))
|
|
22413
|
+
return live;
|
|
22414
|
+
noteOverride(cwd, owner, command);
|
|
22415
|
+
}
|
|
22416
|
+
}
|
|
22417
|
+
const decision = writes.writes ? beforeWriteGate(cwd, writes.targets) : NONE3;
|
|
22418
|
+
if (owner && targets.length && decision.kind === "none")
|
|
22419
|
+
claimAll(owner, targets, deps);
|
|
22420
|
+
return decision;
|
|
22421
|
+
}
|
|
22422
|
+
var noteOverride = (cwd, owner, command) => {
|
|
22423
|
+
try {
|
|
22424
|
+
if (!inWorkitWorkspace(cwd))
|
|
22425
|
+
return;
|
|
22426
|
+
appendHookObserved(cwd, {
|
|
22427
|
+
type: "live_session.override",
|
|
22428
|
+
actor: { host: owner.host, session: owner.session, agentId: null },
|
|
22429
|
+
session: owner.session,
|
|
22430
|
+
command: command.slice(0, 200)
|
|
22431
|
+
});
|
|
22432
|
+
} catch {}
|
|
22433
|
+
};
|
|
22434
|
+
function beforeWriteGate(cwd, paths) {
|
|
21963
22435
|
try {
|
|
21964
22436
|
const gate = gateFor(cwd);
|
|
21965
22437
|
if (!gate || gate.blockers.length === 0)
|
|
@@ -21977,7 +22449,7 @@ function writeGate(cwd, paths) {
|
|
|
21977
22449
|
}
|
|
21978
22450
|
}
|
|
21979
22451
|
// packages/workit-core/src/hooks/stop.ts
|
|
21980
|
-
import { spawnSync as
|
|
22452
|
+
import { spawnSync as spawnSync18 } from "node:child_process";
|
|
21981
22453
|
var NONE4 = { kind: "none" };
|
|
21982
22454
|
var QUESTION = /[??؟](?=[\s*_`"')\]>]*(?:\s|$))|¿/;
|
|
21983
22455
|
var OFFER = /\b(?:let me know|want me to|should I|shall I|would you like|do you want|if you(?:'d| would) like|may I|can I go ahead)\b/i;
|
|
@@ -21998,7 +22470,7 @@ var asksUser = (message) => {
|
|
|
21998
22470
|
};
|
|
21999
22471
|
var short = (sha) => (sha ?? "?").slice(0, 12);
|
|
22000
22472
|
var gitList = (cwd, args) => {
|
|
22001
|
-
const run =
|
|
22473
|
+
const run = spawnSync18("git", args, {
|
|
22002
22474
|
cwd,
|
|
22003
22475
|
encoding: "utf8",
|
|
22004
22476
|
timeout: 3000,
|
|
@@ -22475,8 +22947,7 @@ function handleHook(input, deps) {
|
|
|
22475
22947
|
const raw = canDeny ? rawGitPre(input, event.command, { nudge: false }) : NONE5;
|
|
22476
22948
|
if (raw.kind === "deny")
|
|
22477
22949
|
return raw;
|
|
22478
|
-
const
|
|
22479
|
-
const gate = writes.writes ? writeGate(input.cwd, writes.targets) : NONE5;
|
|
22950
|
+
const gate = usable2(descriptor.events["write.pre"].support) ? shellGate(input.cwd, event.command, liveOwner(input, descriptor.transport), event.dialect) : NONE5;
|
|
22480
22951
|
if (gate.kind === "deny")
|
|
22481
22952
|
return gate;
|
|
22482
22953
|
noteRawCommit(input, event.command, postTool ? callKey(event.toolUseId, event.command) : NEXT_COMMAND);
|
|
@@ -22489,7 +22960,7 @@ function handleHook(input, deps) {
|
|
|
22489
22960
|
rawGitPost(input, event.command, callKey(event.toolUseId, event.command));
|
|
22490
22961
|
return NONE5;
|
|
22491
22962
|
case "write.pre":
|
|
22492
|
-
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths) : NONE5;
|
|
22963
|
+
return usable2(descriptor.events["write.pre"].support) ? writeGate(input.cwd, event.paths, liveOwner(input, descriptor.transport)) : NONE5;
|
|
22493
22964
|
case "subagent.start":
|
|
22494
22965
|
return {
|
|
22495
22966
|
kind: "context",
|
|
@@ -22793,13 +23264,13 @@ var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? n
|
|
|
22793
23264
|
|
|
22794
23265
|
// packages/workit-pi/src/launches.ts
|
|
22795
23266
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
22796
|
-
import
|
|
22797
|
-
import
|
|
23267
|
+
import os8 from "node:os";
|
|
23268
|
+
import path28 from "node:path";
|
|
22798
23269
|
var launchDir = (root) => {
|
|
22799
23270
|
const location = resolveStore(root);
|
|
22800
|
-
return location instanceof Error ? null :
|
|
23271
|
+
return location instanceof Error ? null : path28.join(location.dir, "pi-launches");
|
|
22801
23272
|
};
|
|
22802
|
-
var fileFor = (dir, workerId) =>
|
|
23273
|
+
var fileFor = (dir, workerId) => path28.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
|
|
22803
23274
|
var recordLaunch = (root, attempt) => {
|
|
22804
23275
|
const dir = launchDir(root);
|
|
22805
23276
|
if (!dir)
|
|
@@ -22809,7 +23280,7 @@ var recordLaunch = (root, attempt) => {
|
|
|
22809
23280
|
writeFileSync3(fileFor(dir, attempt.workerId), JSON.stringify({
|
|
22810
23281
|
...attempt,
|
|
22811
23282
|
pid: process.pid,
|
|
22812
|
-
host:
|
|
23283
|
+
host: os8.hostname(),
|
|
22813
23284
|
at: new Date().toISOString()
|
|
22814
23285
|
}), { flag: "wx" });
|
|
22815
23286
|
return true;
|
|
@@ -22823,7 +23294,7 @@ var clearLaunch = (root, workerId) => {
|
|
|
22823
23294
|
rmSync2(fileFor(dir, workerId), { force: true });
|
|
22824
23295
|
};
|
|
22825
23296
|
var alive = (attempt) => {
|
|
22826
|
-
if (attempt.host !==
|
|
23297
|
+
if (attempt.host !== os8.hostname())
|
|
22827
23298
|
return true;
|
|
22828
23299
|
try {
|
|
22829
23300
|
process.kill(attempt.pid, 0);
|
|
@@ -22845,7 +23316,7 @@ var abandonedLaunches = (root) => {
|
|
|
22845
23316
|
const found = [];
|
|
22846
23317
|
for (const name of names) {
|
|
22847
23318
|
try {
|
|
22848
|
-
const attempt = JSON.parse(readFileSync6(
|
|
23319
|
+
const attempt = JSON.parse(readFileSync6(path28.join(dir, name), "utf8"));
|
|
22849
23320
|
if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
|
|
22850
23321
|
if (!alive(attempt))
|
|
22851
23322
|
found.push(attempt);
|
|
@@ -23348,7 +23819,10 @@ var readAssignment = (ctx, request) => {
|
|
|
23348
23819
|
return failure2("permission_denied", "worker assignment is not bound to this workspace");
|
|
23349
23820
|
if (task.data.status !== "active")
|
|
23350
23821
|
return failure2("invalid_transition", "paused or closed tasks cannot supervise workers");
|
|
23351
|
-
const
|
|
23822
|
+
const workerId = resolveIdRef("worker", request.workerId, task.data.workers.map((candidate) => candidate.id));
|
|
23823
|
+
if (!workerId.ok)
|
|
23824
|
+
return workerId;
|
|
23825
|
+
const worker = task.data.workers.find((candidate) => candidate.id === workerId.data);
|
|
23352
23826
|
if (!worker)
|
|
23353
23827
|
return failure2("not_found", "worker assignment not found");
|
|
23354
23828
|
return success(null, workspace.data.revision, {
|
|
@@ -23495,7 +23969,7 @@ function extension(pi) {
|
|
|
23495
23969
|
if (!validated.ok)
|
|
23496
23970
|
return validated;
|
|
23497
23971
|
const { store, task, workspace, worker } = validated.data;
|
|
23498
|
-
const current = [...workers.values()].find((handle) => handle.workerId ===
|
|
23972
|
+
const current = [...workers.values()].find((handle) => handle.workerId === worker.id);
|
|
23499
23973
|
if (request.action === "cancel") {
|
|
23500
23974
|
if (!current)
|
|
23501
23975
|
return failure2("recovery_required", "worker process is not live in this session");
|
package/package.json
CHANGED
|
@@ -25,9 +25,9 @@ ceremony; do it yourself. Workit never spawns agents; you do.
|
|
|
25
25
|
it; change the plan or the standing orders and render again.
|
|
26
26
|
4. **Spawn in a rolling window**, in the background, each in its own worktree:
|
|
27
27
|
keep 4-6 in flight and refill from `spawnable` in `workit fanout status` as
|
|
28
|
-
each one finishes.
|
|
29
|
-
Claude Code: the `implementer` agent;
|
|
30
|
-
worktree create <slice>`.
|
|
28
|
+
each one finishes. Model: the brief's `model:` line, else TIER
|
|
29
|
+
(`references/orchestration.md`). Claude Code: the `implementer` agent;
|
|
30
|
+
elsewhere first `workit fanout worktree create <slice>`.
|
|
31
31
|
5. **Judge liveness by side effects only:** `workit fanout status` (head age,
|
|
32
32
|
PR, CI, verdict, landed; STUCK past the TIMEBOX). Stop a stuck worker and
|
|
33
33
|
observe that it exited (a timeout is not proof). Removing a worktree drops
|
|
@@ -53,7 +53,7 @@ ceremony; do it yourself. Workit never spawns agents; you do.
|
|
|
53
53
|
status` order, release the worktrees you made, and `workit ledger standing
|
|
54
54
|
clear`. Only you touch topology: workers never rebase, retarget or merge,
|
|
55
55
|
except the integration-tip merge in integration mode. Then ship
|
|
56
|
-
(read the `workit-ship` skill's SKILL.md and follow it)
|
|
56
|
+
(read the `workit-ship` skill's SKILL.md and follow it) up to `defaultEndpoint` from `workit grant show`.
|
|
57
57
|
|
|
58
58
|
## Example
|
|
59
59
|
|
|
@@ -5,8 +5,10 @@ and fans in. Workit records and checks; the host's own subagents run the work.
|
|
|
5
5
|
|
|
6
6
|
## TIER to model
|
|
7
7
|
|
|
8
|
-
TIER is a field of each slice and of its brief.
|
|
9
|
-
|
|
8
|
+
TIER is a field of each slice and of its brief. When the workspace sets
|
|
9
|
+
`fanout.tierModels`, the brief has a `model:` line under TIER and `workit
|
|
10
|
+
fanout status` names the model next to each spawnable slice: use that model.
|
|
11
|
+
Without one, map TIER where the host lets you pick a model per spawn:
|
|
10
12
|
|
|
11
13
|
| TIER | Claude Code (`model` on the Agent call) | Hosts without a per-spawn model |
|
|
12
14
|
| --- | --- | --- |
|