@brainervirus/workit-opencode 8.9.0 → 8.10.0
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.
|
@@ -25,7 +25,7 @@ REPORT: branch, head SHA, files changed, each VERIFY command with its exit code,
|
|
|
25
25
|
each ACCEPTANCE line met / not met, rulings you made (`workit ledger ruling`),
|
|
26
26
|
anything out of scope as a follow-up, not a diff.
|
|
27
27
|
STANDING: <the standing orders, verbatim: `workit ledger standing add` records each one>
|
|
28
|
-
export WORKIT_SESSION_ID=<lead>-w<n> (set by the lead; a verifier brief
|
|
28
|
+
export WORKIT_SESSION_ID=<lead>-w<n> (set by the lead for a worker's commits; a verifier brief sets none: the host derives it)
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## Worked example
|
|
@@ -72,12 +72,22 @@ Which session id a verdict carries, per host:
|
|
|
72
72
|
|
|
73
73
|
- Claude Code: subagents inherit your `WORKIT_SESSION_ID`, so the
|
|
74
74
|
SubagentStart hook names each `verifier` and `reviewer` its own session
|
|
75
|
-
(`<lead>:<agent id>`), passed as `--session
|
|
76
|
-
|
|
77
|
-
exports for its workit commands.
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
75
|
+
(`<lead>:<agent id>`), passed exactly as `--session`: the hook records the
|
|
76
|
+
ids it issues, and only those are accepted. An `implementer` uses the
|
|
77
|
+
session its brief exports for its workit commands.
|
|
78
|
+
- Codex: the `workit-verifier` and `workit-reviewer` agents get the same
|
|
79
|
+
issued `--session` from SubagentStart; any other verifier passes
|
|
80
|
+
`--as verifier`.
|
|
81
|
+
- OpenCode: a child session's shell already has
|
|
82
|
+
`WORKIT_SESSION_ID=<lead>:<child>`; Pi: a supervised worker's shell has
|
|
83
|
+
`<lead>:<worker session>`. Set nothing for a verifier there.
|
|
84
|
+
- Cursor (no session id in the shell): the brief sets the verifier's
|
|
85
|
+
`WORKIT_SESSION_ID=<lead>:verifier-<random hex>`, a fresh suffix per
|
|
86
|
+
verifier. Cursor cannot check it: it is declared, not derived.
|
|
87
|
+
- A worker's commits use `export WORKIT_SESSION_ID=<lead>-w-<slice>` from
|
|
88
|
+
`workit fanout brief`. A verdict under an id you made up (`<lead>-v1`,
|
|
89
|
+
`<lead>:v1`) is refused as `forged_session` on Claude Code, Codex,
|
|
90
|
+
OpenCode and Pi.
|
|
81
91
|
|
|
82
92
|
A batch verifier takes several slices on one surface in one session: for
|
|
83
93
|
each branch it checks out the head, runs that slice's VERIFY, and records its
|
|
@@ -33,8 +33,9 @@ description: Build a requested change in small verified steps - follow local pat
|
|
|
33
33
|
`workit ledger verdict tests-verified --self --how "<what you ran>"`; it
|
|
34
34
|
reads self-reviewed, never verified. High risk, a workspace with
|
|
35
35
|
`verification: independent` (`workit grant show`) or a verified merge needs
|
|
36
|
-
a fresh verifier with its own session (
|
|
37
|
-
|
|
36
|
+
a fresh verifier with its own host-derived session (Claude Code and Codex:
|
|
37
|
+
the `verifier` agent; OpenCode: a child session; Pi: a supervised worker),
|
|
38
|
+
never a hand-minted id. Before saying done, reconcile
|
|
38
39
|
every named deliverable against the target checkout and observe it (for a push:
|
|
39
40
|
`workit verify-delivery push`).
|
|
40
41
|
|
|
@@ -7,8 +7,10 @@ description: Independent review of a diff, branch or PR - intent fidelity and st
|
|
|
7
7
|
|
|
8
8
|
Review the candidate, never the author's summary of it. A session that wrote a
|
|
9
9
|
commit on the branch cannot record a passing verdict: if that is you, hand the
|
|
10
|
-
review to a fresh agent with its own session
|
|
11
|
-
|
|
10
|
+
review to a fresh agent with its own host-derived session: Claude Code and
|
|
11
|
+
Codex, the `reviewer` or `verifier` agent (`--session` from SubagentStart);
|
|
12
|
+
OpenCode, a child session; Pi, a supervised worker. Never hand-mint one such
|
|
13
|
+
as `<lead>-v1`: it is refused as `forged_session`.
|
|
12
14
|
An author's `--self` verdict reads self-reviewed and never counts as
|
|
13
15
|
independent; `type-check-only` never proves a behavior change.
|
|
14
16
|
|
package/dist/plugin.js
CHANGED
|
@@ -9597,64 +9597,11 @@ function acquireFileLockSync(targetPath, options) {
|
|
|
9597
9597
|
}
|
|
9598
9598
|
}
|
|
9599
9599
|
}
|
|
9600
|
-
// packages/workit-core/src/host-session.ts
|
|
9601
|
-
var HOST_SESSION_VARS = [
|
|
9602
|
-
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
9603
|
-
["PI_SESSION_ID", () => "pi"],
|
|
9604
|
-
[
|
|
9605
|
-
"CODEX_THREAD_ID",
|
|
9606
|
-
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
9607
|
-
]
|
|
9608
|
-
];
|
|
9609
|
-
var family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
|
|
9610
|
-
function hostSessionFromEnv(env) {
|
|
9611
|
-
const value = (key) => env[key]?.trim() || null;
|
|
9612
|
-
let inner = null;
|
|
9613
|
-
for (const [key, host] of HOST_SESSION_VARS) {
|
|
9614
|
-
const session = value(key);
|
|
9615
|
-
if (session) {
|
|
9616
|
-
inner = { host: host(env), session };
|
|
9617
|
-
break;
|
|
9618
|
-
}
|
|
9619
|
-
}
|
|
9620
|
-
const declared = value("WORKIT_HOST");
|
|
9621
|
-
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
9622
|
-
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
9623
|
-
if (!inherited)
|
|
9624
|
-
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
9625
|
-
}
|
|
9626
|
-
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9627
|
-
}
|
|
9628
|
-
|
|
9629
|
-
// packages/workit-core/src/core/package-root.ts
|
|
9630
|
-
import { existsSync } from "node:fs";
|
|
9631
|
-
import path3 from "node:path";
|
|
9632
|
-
import { fileURLToPath } from "node:url";
|
|
9633
|
-
var packageRoot = () => {
|
|
9634
|
-
let dir = path3.dirname(fileURLToPath(import.meta.url));
|
|
9635
|
-
while (true) {
|
|
9636
|
-
const parent = path3.dirname(dir);
|
|
9637
|
-
if (existsSync(path3.join(dir, "package.json")) || parent === dir)
|
|
9638
|
-
return dir;
|
|
9639
|
-
dir = parent;
|
|
9640
|
-
}
|
|
9641
|
-
};
|
|
9642
|
-
var assetRoot = () => {
|
|
9643
|
-
const root = packageRoot();
|
|
9644
|
-
const assets = path3.join(root, "assets");
|
|
9645
|
-
return existsSync(assets) ? assets : root;
|
|
9646
|
-
};
|
|
9647
|
-
|
|
9648
|
-
// packages/workit-core/src/core/store-lock.ts
|
|
9649
|
-
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9650
|
-
import fs5 from "node:fs";
|
|
9651
|
-
import { hostname } from "node:os";
|
|
9652
|
-
|
|
9653
9600
|
// packages/workit-core/src/store/paths.ts
|
|
9654
9601
|
import { spawnSync } from "node:child_process";
|
|
9655
9602
|
import { createHash as createHash2 } from "node:crypto";
|
|
9656
9603
|
import fs4 from "node:fs";
|
|
9657
|
-
import
|
|
9604
|
+
import path3 from "node:path";
|
|
9658
9605
|
var NOT_A_REPO = /not a git repository \(or any (of the parent directories|parent up to)/u;
|
|
9659
9606
|
var gitRun = (cwd, args) => {
|
|
9660
9607
|
const run = spawnSync("git", args, {
|
|
@@ -9696,14 +9643,14 @@ var DISCOVERY_ENV = [
|
|
|
9696
9643
|
];
|
|
9697
9644
|
var isGitDir = (dir) => {
|
|
9698
9645
|
try {
|
|
9699
|
-
return fs4.statSync(
|
|
9646
|
+
return fs4.statSync(path3.join(dir, "HEAD")).isFile() && fs4.statSync(path3.join(dir, "objects")).isDirectory() && fs4.statSync(path3.join(dir, "refs")).isDirectory();
|
|
9700
9647
|
} catch {
|
|
9701
9648
|
return false;
|
|
9702
9649
|
}
|
|
9703
9650
|
};
|
|
9704
9651
|
var reftableRefs = (common) => {
|
|
9705
9652
|
try {
|
|
9706
|
-
return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs4.readFileSync(
|
|
9653
|
+
return /^\s*refstorage\s*=\s*reftable\s*$/imu.test(fs4.readFileSync(path3.join(common, "config"), "utf8"));
|
|
9707
9654
|
} catch {
|
|
9708
9655
|
return false;
|
|
9709
9656
|
}
|
|
@@ -9723,7 +9670,7 @@ function fastGit(root) {
|
|
|
9723
9670
|
if (device !== null && stat.dev !== device)
|
|
9724
9671
|
return { kind: "none" };
|
|
9725
9672
|
device = stat.dev;
|
|
9726
|
-
const dotGit =
|
|
9673
|
+
const dotGit = path3.join(dir, ".git");
|
|
9727
9674
|
let entry = null;
|
|
9728
9675
|
try {
|
|
9729
9676
|
entry = fs4.statSync(dotGit);
|
|
@@ -9744,19 +9691,19 @@ function fastGit(root) {
|
|
|
9744
9691
|
const match = /^gitdir: (.+)$/mu.exec(text);
|
|
9745
9692
|
if (!match)
|
|
9746
9693
|
return null;
|
|
9747
|
-
gitDir =
|
|
9694
|
+
gitDir = path3.resolve(dir, match[1].trim());
|
|
9748
9695
|
} else
|
|
9749
9696
|
return null;
|
|
9750
|
-
if (!fs4.existsSync(
|
|
9697
|
+
if (!fs4.existsSync(path3.join(gitDir, "HEAD")))
|
|
9751
9698
|
return null;
|
|
9752
9699
|
} else if (isGitDir(dir))
|
|
9753
9700
|
gitDir = dir;
|
|
9754
9701
|
if (gitDir) {
|
|
9755
9702
|
let common = gitDir;
|
|
9756
9703
|
try {
|
|
9757
|
-
common =
|
|
9704
|
+
common = path3.resolve(gitDir, fs4.readFileSync(path3.join(gitDir, "commondir"), "utf8").trim());
|
|
9758
9705
|
} catch {}
|
|
9759
|
-
if (!fs4.existsSync(
|
|
9706
|
+
if (!fs4.existsSync(path3.join(common, "objects")))
|
|
9760
9707
|
return null;
|
|
9761
9708
|
return {
|
|
9762
9709
|
kind: "repo",
|
|
@@ -9766,26 +9713,26 @@ function fastGit(root) {
|
|
|
9766
9713
|
reftable: reftableRefs(common)
|
|
9767
9714
|
};
|
|
9768
9715
|
}
|
|
9769
|
-
const parent =
|
|
9716
|
+
const parent = path3.dirname(dir);
|
|
9770
9717
|
if (parent === dir)
|
|
9771
9718
|
return { kind: "none" };
|
|
9772
9719
|
dir = parent;
|
|
9773
9720
|
}
|
|
9774
9721
|
}
|
|
9775
9722
|
function resolveStore(root) {
|
|
9776
|
-
root =
|
|
9723
|
+
root = path3.resolve(root);
|
|
9777
9724
|
let directory = false;
|
|
9778
9725
|
try {
|
|
9779
9726
|
directory = fs4.statSync(root).isDirectory();
|
|
9780
9727
|
} catch {}
|
|
9781
9728
|
if (!directory)
|
|
9782
|
-
return { dir:
|
|
9729
|
+
return { dir: path3.join(root, ".workit"), shared: false, top: null };
|
|
9783
9730
|
const fast = fastGit(root);
|
|
9784
9731
|
if (fast?.kind === "none")
|
|
9785
|
-
return { dir:
|
|
9732
|
+
return { dir: path3.join(root, ".workit"), shared: false, top: null };
|
|
9786
9733
|
if (fast?.kind === "repo")
|
|
9787
9734
|
return {
|
|
9788
|
-
dir:
|
|
9735
|
+
dir: path3.join(fast.common, "workit"),
|
|
9789
9736
|
shared: true,
|
|
9790
9737
|
top: fast.top,
|
|
9791
9738
|
git: { gitDir: fast.gitDir, top: fast.top, reftable: fast.reftable }
|
|
@@ -9795,20 +9742,20 @@ function resolveStore(root) {
|
|
|
9795
9742
|
if (run.ok && common) {
|
|
9796
9743
|
const top = gitRun(root, ["rev-parse", "--path-format=absolute", "--show-toplevel"]);
|
|
9797
9744
|
return {
|
|
9798
|
-
dir:
|
|
9745
|
+
dir: path3.join(real(path3.resolve(root, common)), "workit"),
|
|
9799
9746
|
shared: true,
|
|
9800
9747
|
top: top.ok && top.stdout.trim() ? real(top.stdout.trim()) : null
|
|
9801
9748
|
};
|
|
9802
9749
|
}
|
|
9803
9750
|
if (run.missing || !run.ok && NOT_A_REPO.test(run.stderr))
|
|
9804
|
-
return { dir:
|
|
9751
|
+
return { dir: path3.join(root, ".workit"), shared: false, top: null };
|
|
9805
9752
|
return new Error(`cannot locate the workit store: git rev-parse failed (${run.stderr.trim().split(`
|
|
9806
9753
|
`)[0] || "no output"}); fix the repository (git status should work here)`);
|
|
9807
9754
|
}
|
|
9808
9755
|
var rebasingBranch = (gitDir) => {
|
|
9809
9756
|
for (const state of ["rebase-merge", "rebase-apply"]) {
|
|
9810
9757
|
try {
|
|
9811
|
-
const ref = fs4.readFileSync(
|
|
9758
|
+
const ref = fs4.readFileSync(path3.join(gitDir, state, "head-name"), "utf8").trim();
|
|
9812
9759
|
const match = /^refs\/heads\/(.+)$/u.exec(ref);
|
|
9813
9760
|
if (match)
|
|
9814
9761
|
return match[1];
|
|
@@ -9823,7 +9770,7 @@ function resolveTaskKey(root, location) {
|
|
|
9823
9770
|
if (location.git && !location.git.reftable) {
|
|
9824
9771
|
let head = "";
|
|
9825
9772
|
try {
|
|
9826
|
-
head = fs4.readFileSync(
|
|
9773
|
+
head = fs4.readFileSync(path3.join(location.git.gitDir, "HEAD"), "utf8").trim();
|
|
9827
9774
|
} catch {}
|
|
9828
9775
|
const ref = /^ref: refs\/heads\/(.+)$/u.exec(head);
|
|
9829
9776
|
if (ref && ref[1] !== ".invalid")
|
|
@@ -9854,7 +9801,7 @@ function resolveTaskKey(root, location) {
|
|
|
9854
9801
|
return { key: `detached-${shortHash(real(worktree))}`, kind: "detached", branch: null };
|
|
9855
9802
|
}
|
|
9856
9803
|
function checkoutRootOf(root, location) {
|
|
9857
|
-
const resolved =
|
|
9804
|
+
const resolved = path3.resolve(root);
|
|
9858
9805
|
let canonical = resolved;
|
|
9859
9806
|
try {
|
|
9860
9807
|
canonical = fs4.realpathSync(resolved);
|
|
@@ -9885,13 +9832,13 @@ function branchRenames(location) {
|
|
|
9885
9832
|
}
|
|
9886
9833
|
for (const entry of entries)
|
|
9887
9834
|
if (entry.isDirectory())
|
|
9888
|
-
walk(
|
|
9835
|
+
walk(path3.join(dir, entry.name));
|
|
9889
9836
|
else
|
|
9890
|
-
files.push(
|
|
9837
|
+
files.push(path3.join(dir, entry.name));
|
|
9891
9838
|
};
|
|
9892
|
-
walk(
|
|
9839
|
+
walk(path3.join(path3.dirname(location.dir), "logs", "refs", "heads"));
|
|
9893
9840
|
if (location.git)
|
|
9894
|
-
files.push(
|
|
9841
|
+
files.push(path3.join(location.git.gitDir, "logs", "HEAD"));
|
|
9895
9842
|
const seen = new Set;
|
|
9896
9843
|
const renames = [];
|
|
9897
9844
|
for (const file of files) {
|
|
@@ -9926,11 +9873,69 @@ var checkoutSlug = (root) => {
|
|
|
9926
9873
|
let canonical = real(root);
|
|
9927
9874
|
if (process.platform === "win32")
|
|
9928
9875
|
canonical = canonical.toLowerCase();
|
|
9929
|
-
const base =
|
|
9876
|
+
const base = path3.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
|
|
9930
9877
|
return `${base}-${shortHash(canonical)}`;
|
|
9931
9878
|
};
|
|
9932
9879
|
|
|
9880
|
+
// packages/workit-core/src/host-session.ts
|
|
9881
|
+
var HOST_SESSION_VARS = [
|
|
9882
|
+
["OPENCODE_SESSION_ID", () => "opencode"],
|
|
9883
|
+
["PI_SESSION_ID", () => "pi"],
|
|
9884
|
+
[
|
|
9885
|
+
"CODEX_THREAD_ID",
|
|
9886
|
+
(env) => env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE === "Codex Desktop" || env.CODEX_ELECTRON_RESOURCES_PATH ? "codex_desktop" : "codex_cli"
|
|
9887
|
+
]
|
|
9888
|
+
];
|
|
9889
|
+
var family = (host) => host?.replace(/_(?:cli|desktop)$/, "") ?? null;
|
|
9890
|
+
function hostSessionFromEnv(env) {
|
|
9891
|
+
const value = (key) => env[key]?.trim() || null;
|
|
9892
|
+
let inner = null;
|
|
9893
|
+
for (const [key, host] of HOST_SESSION_VARS) {
|
|
9894
|
+
const session = value(key);
|
|
9895
|
+
if (session) {
|
|
9896
|
+
inner = { host: host(env), session };
|
|
9897
|
+
break;
|
|
9898
|
+
}
|
|
9899
|
+
}
|
|
9900
|
+
const declared = value("WORKIT_HOST");
|
|
9901
|
+
if (env.WORKIT_SESSION_ID !== undefined) {
|
|
9902
|
+
const inherited = inner !== null && declared !== null && family(declared) !== family(inner.host);
|
|
9903
|
+
if (!inherited)
|
|
9904
|
+
return { host: declared, session: value("WORKIT_SESSION_ID") };
|
|
9905
|
+
}
|
|
9906
|
+
return inner ? { host: inner.host, session: inner.session } : { host: declared, session: null };
|
|
9907
|
+
}
|
|
9908
|
+
var OWN_CHILD_SHELL = new Set(["opencode", "pi"]);
|
|
9909
|
+
var SESSION_UNSAFE = /[^A-Za-z0-9_.:@/+-]/g;
|
|
9910
|
+
function childSessionId(lead, child) {
|
|
9911
|
+
const leaf = child.replace(SESSION_UNSAFE, "-").slice(-120);
|
|
9912
|
+
const prefix = lead.replace(SESSION_UNSAFE, "-").slice(0, 127 - leaf.length);
|
|
9913
|
+
return prefix ? `${prefix}:${leaf}` : leaf;
|
|
9914
|
+
}
|
|
9915
|
+
|
|
9916
|
+
// packages/workit-core/src/core/package-root.ts
|
|
9917
|
+
import { existsSync } from "node:fs";
|
|
9918
|
+
import path4 from "node:path";
|
|
9919
|
+
import { fileURLToPath } from "node:url";
|
|
9920
|
+
var packageRoot = () => {
|
|
9921
|
+
let dir = path4.dirname(fileURLToPath(import.meta.url));
|
|
9922
|
+
while (true) {
|
|
9923
|
+
const parent = path4.dirname(dir);
|
|
9924
|
+
if (existsSync(path4.join(dir, "package.json")) || parent === dir)
|
|
9925
|
+
return dir;
|
|
9926
|
+
dir = parent;
|
|
9927
|
+
}
|
|
9928
|
+
};
|
|
9929
|
+
var assetRoot = () => {
|
|
9930
|
+
const root = packageRoot();
|
|
9931
|
+
const assets = path4.join(root, "assets");
|
|
9932
|
+
return existsSync(assets) ? assets : root;
|
|
9933
|
+
};
|
|
9934
|
+
|
|
9933
9935
|
// packages/workit-core/src/core/store-lock.ts
|
|
9936
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
9937
|
+
import fs5 from "node:fs";
|
|
9938
|
+
import { hostname } from "node:os";
|
|
9934
9939
|
var metadataLockSchema = object({
|
|
9935
9940
|
pid: number2().int().nonnegative().safe(),
|
|
9936
9941
|
processStart: string2().nullable(),
|
|
@@ -10564,6 +10569,38 @@ var isNewerVersion = (candidate, current) => {
|
|
|
10564
10569
|
return false;
|
|
10565
10570
|
};
|
|
10566
10571
|
var validId = (value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/.test(value);
|
|
10572
|
+
var readText = (file) => {
|
|
10573
|
+
try {
|
|
10574
|
+
return fs7.readFileSync(file, "utf8");
|
|
10575
|
+
} catch {
|
|
10576
|
+
return "";
|
|
10577
|
+
}
|
|
10578
|
+
};
|
|
10579
|
+
var REFLOG_LINE = /^[0-9a-f]+ ([0-9a-f]+) .* (\d+) [+-]\d{4}\t(.*)$/u;
|
|
10580
|
+
var branchStart = (location, branch, aliases) => {
|
|
10581
|
+
const first = readText(path6.join(path6.dirname(location.dir), "logs", "refs", "heads", branch)).split(`
|
|
10582
|
+
`, 1)[0];
|
|
10583
|
+
const created = REFLOG_LINE.exec(first);
|
|
10584
|
+
const start = created && /^branch: Created from (\S+)$/u.exec(created[3])?.[1];
|
|
10585
|
+
if (!created || !start)
|
|
10586
|
+
return null;
|
|
10587
|
+
if (start !== "HEAD")
|
|
10588
|
+
return start;
|
|
10589
|
+
if (!location.git)
|
|
10590
|
+
return null;
|
|
10591
|
+
for (const line of readText(path6.join(location.git.gitDir, "logs", "HEAD")).split(`
|
|
10592
|
+
`)) {
|
|
10593
|
+
const entry = REFLOG_LINE.exec(line);
|
|
10594
|
+
const moved = entry && /^checkout: moving from (\S+) to (\S+)$/u.exec(entry[3]);
|
|
10595
|
+
const seconds = entry ? Number(entry[2]) - Number(created[2]) : -1;
|
|
10596
|
+
if (moved && entry[1] === created[1] && seconds >= 0 && seconds <= 1 && aliases.includes(moved[2]))
|
|
10597
|
+
return moved[1];
|
|
10598
|
+
}
|
|
10599
|
+
return null;
|
|
10600
|
+
};
|
|
10601
|
+
var refExists = (common, ref) => fs7.existsSync(path6.join(common, ref)) || readText(path6.join(common, "packed-refs")).includes(` ${ref}
|
|
10602
|
+
`);
|
|
10603
|
+
var origins = new Map;
|
|
10567
10604
|
var sameDirectoryIdentity = (left, right) => {
|
|
10568
10605
|
if (!path6.isAbsolute(left) || !path6.isAbsolute(right))
|
|
10569
10606
|
return false;
|
|
@@ -10816,6 +10853,66 @@ class TaskStore {
|
|
|
10816
10853
|
return bound;
|
|
10817
10854
|
return success(null, null, { key: key.data, bound: bound.data });
|
|
10818
10855
|
}
|
|
10856
|
+
taskForWorker(workerId) {
|
|
10857
|
+
if (typeof workerId !== "string" || !validId(workerId))
|
|
10858
|
+
return failure2("invalid_input", "needs a worker id: `workit worker cancel|report <workerId>`");
|
|
10859
|
+
const listed = this.listTaskIndex();
|
|
10860
|
+
if (!listed.ok)
|
|
10861
|
+
return listed;
|
|
10862
|
+
for (const entry of listed.data) {
|
|
10863
|
+
if (entry.status === "closed")
|
|
10864
|
+
continue;
|
|
10865
|
+
const loaded = this.load(entry.id);
|
|
10866
|
+
if (!loaded.ok)
|
|
10867
|
+
return loaded;
|
|
10868
|
+
if (loaded.data?.record.workers.some((worker) => worker.id === workerId))
|
|
10869
|
+
return success(null, null, entry.id);
|
|
10870
|
+
}
|
|
10871
|
+
return success(null, null, null);
|
|
10872
|
+
}
|
|
10873
|
+
originTaskEntry() {
|
|
10874
|
+
const key = this.currentKey();
|
|
10875
|
+
const location = this.location();
|
|
10876
|
+
const branch = key.ok ? key.data.branch : null;
|
|
10877
|
+
if (!branch || !location.ok)
|
|
10878
|
+
return success(null, null, null);
|
|
10879
|
+
const common = path6.dirname(location.data.dir);
|
|
10880
|
+
const heads = path6.join(common, "logs", "refs", "heads");
|
|
10881
|
+
const headLog = location.data.git ? path6.join(location.data.git.gitDir, "logs", "HEAD") : "";
|
|
10882
|
+
const signatures = [
|
|
10883
|
+
path6.join(heads, branch),
|
|
10884
|
+
headLog,
|
|
10885
|
+
heads,
|
|
10886
|
+
path6.join(common, "packed-refs")
|
|
10887
|
+
].map((file) => fileSignature(file) ?? "-");
|
|
10888
|
+
const slot = `${location.data.dir}\x00${headLog}\x00${branch}`;
|
|
10889
|
+
let resolved = origins.get(slot);
|
|
10890
|
+
if (resolved?.signature !== signatures.join("|")) {
|
|
10891
|
+
const renames = branchRenames(location.data);
|
|
10892
|
+
const aliases = [branch];
|
|
10893
|
+
for (const rename of renames.toReversed())
|
|
10894
|
+
if (rename.to === aliases.at(-1))
|
|
10895
|
+
aliases.push(rename.from);
|
|
10896
|
+
const start = branchStart(location.data, branch, aliases);
|
|
10897
|
+
const slash = start?.indexOf("/") ?? -1;
|
|
10898
|
+
const remote = slash > 0 && !refExists(common, `refs/heads/${start}`) && (refExists(common, `refs/remotes/${start}`) || readText(path6.join(common, "config")).includes(`[remote "${start.slice(0, slash)}"]`));
|
|
10899
|
+
const name = start && (remote ? start.slice(slash + 1) : start);
|
|
10900
|
+
resolved = {
|
|
10901
|
+
signature: signatures.join("|"),
|
|
10902
|
+
name: name && !aliases.includes(name) ? name : null,
|
|
10903
|
+
renames
|
|
10904
|
+
};
|
|
10905
|
+
origins.set(slot, resolved);
|
|
10906
|
+
}
|
|
10907
|
+
const entry = resolved.name ? this.openTasksForKey(resolved.name, resolved.renames) : null;
|
|
10908
|
+
if (entry && !entry.ok)
|
|
10909
|
+
return entry;
|
|
10910
|
+
return success(null, null, entry?.data[0] ? { origin: `branch ${resolved.name}`, entry: entry.data[0] } : null);
|
|
10911
|
+
}
|
|
10912
|
+
readStoreTask(taskId) {
|
|
10913
|
+
const loaded = this.load(taskId);
|
|
10914
|
+
return loaded.ok ? success(null, null, loaded.data?.record ?? null) : loaded;
|
|
10915
|
+
}
|
|
10819
10916
|
create(input, provenance, expectedWorkspaceRevision) {
|
|
10820
10917
|
const value = "intent" in input ? input : {
|
|
10821
10918
|
intent: input,
|
|
@@ -11485,12 +11582,12 @@ class TaskStore {
|
|
|
11485
11582
|
return 0;
|
|
11486
11583
|
}
|
|
11487
11584
|
}
|
|
11488
|
-
openTasksForKey(key) {
|
|
11585
|
+
openTasksForKey(key, known) {
|
|
11489
11586
|
const all = this.listStoreIndex();
|
|
11490
11587
|
if (!all.ok)
|
|
11491
11588
|
return all;
|
|
11492
|
-
const location = this.location();
|
|
11493
|
-
const renames = location
|
|
11589
|
+
const location = known ? null : this.location();
|
|
11590
|
+
const renames = known ?? (location?.ok ? branchRenames(location.data) : []);
|
|
11494
11591
|
return success(null, null, all.data.filter((entry) => entry.key !== null && entry.status !== "closed").filter((entry) => (renames.length ? followRenames(entry.key, entry.boundAt ?? entry.createdAt, renames) : entry.key) === key).toSorted(oldestFirst));
|
|
11495
11592
|
}
|
|
11496
11593
|
openTaskForKey(key) {
|
|
@@ -14509,11 +14606,20 @@ function authorSessions(cwd, rows, branch, range = { base: null, head: null }, s
|
|
|
14509
14606
|
sessions.add(session);
|
|
14510
14607
|
return sessions;
|
|
14511
14608
|
}
|
|
14609
|
+
var derivesFrom = (derived, bare) => !bare.includes(":") && derived.endsWith(`:${bare}`);
|
|
14610
|
+
var authoredBy = (authors, session) => {
|
|
14611
|
+
if (authors.has(session))
|
|
14612
|
+
return true;
|
|
14613
|
+
for (const author of authors)
|
|
14614
|
+
if (derivesFrom(author, session) || derivesFrom(session, author))
|
|
14615
|
+
return true;
|
|
14616
|
+
return false;
|
|
14617
|
+
};
|
|
14512
14618
|
var independenceReasons = (row, authors) => {
|
|
14513
14619
|
const reasons = [];
|
|
14514
14620
|
if (!row.actor.session)
|
|
14515
14621
|
reasons.push("no_session");
|
|
14516
|
-
else if (authors
|
|
14622
|
+
else if (authoredBy(authors, row.actor.session))
|
|
14517
14623
|
reasons.push("author_session");
|
|
14518
14624
|
if (row.self === true && !reasons.includes("no_session"))
|
|
14519
14625
|
reasons.push("self");
|
|
@@ -15594,7 +15700,8 @@ Autonomy contract:
|
|
|
15594
15700
|
satisfies a gate; an ad-hoc \`workit check -- <cmd>\` never does.
|
|
15595
15701
|
- Author is not verifier: your own verdict is \`--self\`, shown as
|
|
15596
15702
|
self-reviewed, never verified. High risk, \`verification: independent\` and
|
|
15597
|
-
merge need a verifier with its own session (
|
|
15703
|
+
merge need a verifier with its own host-derived session (a subagent's
|
|
15704
|
+
\`<lead>:<child>\`, set by the host); never hand-mint one such as \`<lead>-v1\`.
|
|
15598
15705
|
- A local-commit endpoint does not imply PR readiness, and a local commit alone
|
|
15599
15706
|
is not evidence of a requested remote push. Before reporting delivery,
|
|
15600
15707
|
reconcile every requested item and observe it (\`workit verify-delivery\`).
|
|
@@ -15687,38 +15794,45 @@ var compactReference = (ref) => {
|
|
|
15687
15794
|
};
|
|
15688
15795
|
return { kind: ref.kind };
|
|
15689
15796
|
};
|
|
15690
|
-
|
|
15691
|
-
const
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
|
|
15797
|
+
var newestDecisionFirst = (left, right) => {
|
|
15798
|
+
const leftAt = compactTimestamp(left.recordedAt);
|
|
15799
|
+
const rightAt = compactTimestamp(right.recordedAt);
|
|
15800
|
+
if (leftAt !== rightAt)
|
|
15801
|
+
return leftAt > rightAt ? -1 : 1;
|
|
15802
|
+
return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
|
|
15803
|
+
};
|
|
15804
|
+
var compactDecisions = (sources) => sources.flatMap((source) => source ? source.decisions.toSorted(newestDecisionFirst).map((entry) => ({
|
|
15805
|
+
id: entry.id,
|
|
15806
|
+
purpose: entry.data.purpose,
|
|
15807
|
+
status: entry.data.response,
|
|
15808
|
+
digest: entry.data.digest,
|
|
15809
|
+
choice: compactDecisionChoice(entry) || `task.inspect taskId=${source.taskId} view=full; decision=${entry.id}`,
|
|
15810
|
+
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference),
|
|
15811
|
+
origin: source.origin ?? undefined
|
|
15812
|
+
})) : []).slice(0, COMPACT_MAX_ITEMS);
|
|
15813
|
+
function compactTaskContext(view, inherited = null) {
|
|
15814
|
+
const decisions = compactDecisions([
|
|
15815
|
+
view && { origin: null, taskId: view.task.id, decisions: view.task.decisions },
|
|
15816
|
+
inherited
|
|
15817
|
+
]);
|
|
15818
|
+
const methods = view?.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
15706
15819
|
id: method.id,
|
|
15707
15820
|
assurance: method.assurance,
|
|
15708
15821
|
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
15709
15822
|
})) : [];
|
|
15823
|
+
const stale = view?.evidence.filter((entry) => entry.status === "stale").length;
|
|
15710
15824
|
const gaps = Array.from(new Set([
|
|
15711
|
-
...
|
|
15712
|
-
...view
|
|
15713
|
-
...view
|
|
15825
|
+
...stale ? [`stale evidence: ${stale}; workit task inspect --task ${view.task.id} --view full`] : [],
|
|
15826
|
+
...(view?.requirements ?? []).filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
15827
|
+
...(view?.task.progress.blockers ?? []).map((entry) => entry.reason)
|
|
15714
15828
|
])).toSorted().slice(0, COMPACT_MAX_ITEMS).map((gap) => compactText(gap, COMPACT_TEXT_BYTES));
|
|
15715
15829
|
const context = {
|
|
15716
|
-
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
15717
|
-
status: view
|
|
15830
|
+
objective: view ? compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "" : "no task on this branch yet",
|
|
15831
|
+
status: view?.task.status ?? null,
|
|
15718
15832
|
decisions,
|
|
15719
15833
|
methods,
|
|
15720
15834
|
gaps,
|
|
15721
|
-
nextAction: compactText(view
|
|
15835
|
+
nextAction: compactText(view?.task.progress.nextAction ?? null, COMPACT_TEXT_BYTES)
|
|
15722
15836
|
};
|
|
15723
15837
|
let encoded = JSON.stringify(context);
|
|
15724
15838
|
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
@@ -16461,6 +16575,13 @@ var TASK_SCOPED_ACTIONS = new Set([
|
|
|
16461
16575
|
"worker.cancel",
|
|
16462
16576
|
"state.export"
|
|
16463
16577
|
]);
|
|
16578
|
+
var WORKER_ADDRESSED_ACTIONS = new Set(["worker.cancel", "worker.report"]);
|
|
16579
|
+
var workerCommand = (taskId, request) => {
|
|
16580
|
+
const { action, workerId, reason, report } = request;
|
|
16581
|
+
const rest = JSON.stringify({ reason, report });
|
|
16582
|
+
const payload = rest === "{}" ? "" : ` --payload '${rest.replace(/'/gu, "'\\''")}'`;
|
|
16583
|
+
return `workit worker ${String(action)} ${String(workerId)} --task ${taskId}${payload}`;
|
|
16584
|
+
};
|
|
16464
16585
|
var CREATING_ACTIONS = new Set([
|
|
16465
16586
|
"task.revise",
|
|
16466
16587
|
"task.progress",
|
|
@@ -16711,6 +16832,17 @@ class WorkitCore {
|
|
|
16711
16832
|
const root = this.contextRootError();
|
|
16712
16833
|
if (!root.ok)
|
|
16713
16834
|
return root;
|
|
16835
|
+
const addressed = WORKER_ADDRESSED_ACTIONS.has(action);
|
|
16836
|
+
const workerId = addressed ? value.workerId : this.context.workerId ?? null;
|
|
16837
|
+
if (workerId !== null) {
|
|
16838
|
+
const owner = this.store.taskForWorker(workerId);
|
|
16839
|
+
if (!owner.ok)
|
|
16840
|
+
return owner;
|
|
16841
|
+
if (owner.data)
|
|
16842
|
+
return success(null, null, { ...request, taskId: owner.data });
|
|
16843
|
+
if (addressed)
|
|
16844
|
+
return failure2("not_found", `worker ${workerId} is on no open task; find its task with \`workit task status --all\`, then run \`${workerCommand("<task-id>", request)}\``);
|
|
16845
|
+
}
|
|
16714
16846
|
const create = CREATING_ACTIONS.has(action) && (this.context.workerId ?? null) === null;
|
|
16715
16847
|
const found = this.store.implicitTask({
|
|
16716
16848
|
provenance: provenance(this.context),
|
|
@@ -17417,7 +17549,9 @@ class WorkitCore {
|
|
|
17417
17549
|
if (!resolved.ok)
|
|
17418
17550
|
return resolved;
|
|
17419
17551
|
const value = resolved.data;
|
|
17420
|
-
|
|
17552
|
+
const result = this.retryOmittedRevisions(value, () => this.workerOnce(value));
|
|
17553
|
+
const { action, taskId } = value ?? {};
|
|
17554
|
+
return result.ok || request?.taskId !== undefined || typeof taskId !== "string" || !WORKER_ADDRESSED_ACTIONS.has(`worker.${String(action)}`) ? result : { ...result, error: `${result.error}; run \`${workerCommand(taskId, value)}\`` };
|
|
17421
17555
|
}
|
|
17422
17556
|
workerOnce(request) {
|
|
17423
17557
|
const root = this.contextRootError();
|
|
@@ -17622,10 +17756,18 @@ class WorkitCore {
|
|
|
17622
17756
|
requirements
|
|
17623
17757
|
});
|
|
17624
17758
|
}
|
|
17625
|
-
compactContext(taskId, freshness) {
|
|
17759
|
+
compactContext(taskId, freshness, origin = null) {
|
|
17626
17760
|
const root = this.contextRootError();
|
|
17627
17761
|
if (!root.ok)
|
|
17628
17762
|
return root;
|
|
17763
|
+
const base = origin && this.store.readStoreTask(origin.taskId);
|
|
17764
|
+
const inherited = base?.ok && base.data ? {
|
|
17765
|
+
origin: origin.origin,
|
|
17766
|
+
taskId: base.data.id,
|
|
17767
|
+
decisions: base.data.decisions.filter((entry) => entry.data.revoked === null)
|
|
17768
|
+
} : null;
|
|
17769
|
+
if (taskId === null)
|
|
17770
|
+
return success(null, null, inherited?.decisions.length ? compactTaskContext(null, inherited) : "");
|
|
17629
17771
|
const task = this.store.readTask(taskId);
|
|
17630
17772
|
if (!task.ok)
|
|
17631
17773
|
return task;
|
|
@@ -17635,7 +17777,7 @@ class WorkitCore {
|
|
|
17635
17777
|
const view = this.view(task.data, false, freshness ?? signalFreshness(this.store.root));
|
|
17636
17778
|
if (!view.ok)
|
|
17637
17779
|
return view;
|
|
17638
|
-
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data));
|
|
17780
|
+
return success(view.revision, view.workspaceRevision, compactTaskContext(view.data, inherited));
|
|
17639
17781
|
}
|
|
17640
17782
|
view(task, capture = true, freshness = treeFreshness(this.store.root)) {
|
|
17641
17783
|
const workspace = this.store.readWorkspace();
|
|
@@ -19074,8 +19216,9 @@ var currentTaskEntry = (entries, session, selection) => {
|
|
|
19074
19216
|
const active = entries.filter((entry) => entry.status === "active");
|
|
19075
19217
|
return active.length === 1 ? active[0] : null;
|
|
19076
19218
|
};
|
|
19219
|
+
var otherConversation = (entry, session) => entry.sessions.some((item) => item.host !== "workit_cli" && !(item.host === session.host && item.handle === session.handle));
|
|
19077
19220
|
var implicitTaskEntry = (store, entries, session) => {
|
|
19078
|
-
const open = entries.filter((entry) => entry.key !== null && entry.status !== "closed" && !entry
|
|
19221
|
+
const open = entries.filter((entry) => entry.key !== null && entry.status !== "closed" && !otherConversation(entry, session));
|
|
19079
19222
|
if (open.length === 0)
|
|
19080
19223
|
return null;
|
|
19081
19224
|
const key = store.currentKey();
|
|
@@ -19116,18 +19259,23 @@ function sessionCompactContext(store, session, context, selection = "session-bou
|
|
|
19116
19259
|
if (!listed.ok)
|
|
19117
19260
|
return migrationPending(listed) ? MIGRATION_PENDING_NOTE : null;
|
|
19118
19261
|
const entry = currentTaskEntry(listed.data, session, selection) ?? implicitTaskEntry(store, listed.data, session);
|
|
19119
|
-
return
|
|
19262
|
+
return entryCompactContext(store, entry, session, context);
|
|
19120
19263
|
}
|
|
19121
19264
|
function entryCompactContext(store, entry, session, context) {
|
|
19265
|
+
const found = store.originTaskEntry();
|
|
19266
|
+
const origin = found.ok && found.data && found.data.entry.id !== entry?.id && !otherConversation(found.data.entry, session) ? found.data : null;
|
|
19267
|
+
if (!entry && !origin)
|
|
19268
|
+
return null;
|
|
19122
19269
|
const workspace = store.readWorkspace();
|
|
19123
|
-
if (!workspace.ok || !workspace.data)
|
|
19270
|
+
if (!workspace.ok || entry && !workspace.data)
|
|
19124
19271
|
return null;
|
|
19125
19272
|
const slot = `${store.root}\x00${session.host}\x00${session.handle}`;
|
|
19126
19273
|
const key = canonicalJson({
|
|
19127
|
-
task: entry
|
|
19128
|
-
revision: entry
|
|
19129
|
-
file: entry
|
|
19130
|
-
|
|
19274
|
+
task: entry?.id ?? null,
|
|
19275
|
+
revision: entry?.revision ?? null,
|
|
19276
|
+
file: entry?.file ?? null,
|
|
19277
|
+
origin: origin && origin.entry.file,
|
|
19278
|
+
workspace: workspace.data?.revision ?? null,
|
|
19131
19279
|
caller: context.caller,
|
|
19132
19280
|
workerId: context.workerId ?? null,
|
|
19133
19281
|
capabilities: context.capabilities
|
|
@@ -19135,15 +19283,15 @@ function entryCompactContext(store, entry, session, context) {
|
|
|
19135
19283
|
const hit = cache.get(slot);
|
|
19136
19284
|
if (hit && hit.key === key && hit.filesKey !== null && hit.filesKey === filesKey(hit.files) && (hit.signal === null || hit.signal === recentSignal(store.root)))
|
|
19137
19285
|
return hit.value;
|
|
19138
|
-
const task = store.readTask(entry.id);
|
|
19139
|
-
if (!task.ok)
|
|
19286
|
+
const task = entry ? store.readTask(entry.id) : null;
|
|
19287
|
+
if (task && !task.ok)
|
|
19140
19288
|
return null;
|
|
19141
19289
|
const freshness = { mode: "signal", current: () => recentSignal(store.root) };
|
|
19142
|
-
const observed = task
|
|
19143
|
-
const files = task
|
|
19290
|
+
const observed = task?.data.evidence.some(isObservedCheck) ?? false;
|
|
19291
|
+
const files = (task?.data.decisions ?? []).flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
|
|
19144
19292
|
const observedFiles = filesKey(files);
|
|
19145
|
-
const compact = new WorkitCore(store, context).compactContext(entry
|
|
19146
|
-
if (!compact.ok)
|
|
19293
|
+
const compact = new WorkitCore(store, context).compactContext(entry?.id ?? null, freshness, origin && { origin: origin.origin, taskId: origin.entry.id });
|
|
19294
|
+
if (!compact.ok || !compact.data)
|
|
19147
19295
|
return null;
|
|
19148
19296
|
cache.delete(slot);
|
|
19149
19297
|
cache.set(slot, {
|
|
@@ -21176,9 +21324,12 @@ var createV2Lifecycle = (deps) => {
|
|
|
21176
21324
|
expectedWorkspaceRevision: workspace.data.revision
|
|
21177
21325
|
} : null;
|
|
21178
21326
|
};
|
|
21179
|
-
const
|
|
21180
|
-
const
|
|
21181
|
-
|
|
21327
|
+
const currentTaskId = (store) => {
|
|
21328
|
+
const report = store.keyReport();
|
|
21329
|
+
return report.ok ? report.data.bound[0]?.id ?? null : null;
|
|
21330
|
+
};
|
|
21331
|
+
const oldest = (items) => {
|
|
21332
|
+
if (items.length === 0)
|
|
21182
21333
|
return null;
|
|
21183
21334
|
return [...items].toSorted((a, b) => a.entry.recordedAt < b.entry.recordedAt ? -1 : a.entry.recordedAt > b.entry.recordedAt ? 1 : a.entry.id < b.entry.id ? -1 : 1)[0];
|
|
21184
21335
|
};
|
|
@@ -21187,6 +21338,16 @@ var createV2Lifecycle = (deps) => {
|
|
|
21187
21338
|
const cancel = workerId ? `workit_worker {"action":"cancel","workerId":"${workerId}","reason":"launch not observed"}` : 'workit_worker {"action":"cancel","workerId":"<id>","reason":"launch not observed"} for the stuck worker';
|
|
21188
21339
|
return `recovery_required: a previous native subagent launch is unsettled; wait for it to settle, or ${cancel} before launching again`;
|
|
21189
21340
|
};
|
|
21341
|
+
const unboundLaunch = (coordinator) => {
|
|
21342
|
+
const store = new TaskStore(root);
|
|
21343
|
+
const current = currentTaskId(store);
|
|
21344
|
+
const base = current === null ? 'recovery_required: this branch has no Workit task yet; start it (workit_task start, or `workit task start "<objective>"`), then assign a worker on it with workit_worker before launching a native task' : "recovery_required: no assigned Workit worker is attributable to this coordinator on the current branch's task; assign one with workit_worker before launching a native task";
|
|
21345
|
+
const listed = store.listTasks();
|
|
21346
|
+
if (!listed.ok)
|
|
21347
|
+
return base;
|
|
21348
|
+
const stale = listed.data.flatMap((task) => task.status === "active" && task.id !== current ? task.workers.filter((worker) => (worker.data.state === "assigned" || worker.data.state === "running") && worker.provenance.session?.kind === "host" && worker.provenance.session.handle === coordinator).map((worker) => `workit_worker {"action":"cancel","taskId":"${task.id}","workerId":"${worker.id}","reason":"stale worker from another branch"}`) : []);
|
|
21349
|
+
return stale.length > 0 ? `${base}. Workers of this coordinator are still open on other tasks; cancel any that are stale: ${stale.join("; ")}` : base;
|
|
21350
|
+
};
|
|
21190
21351
|
const prepareDispatch = (coordinator, callID) => {
|
|
21191
21352
|
if (dispatches.has(coordinator))
|
|
21192
21353
|
return "unsettled";
|
|
@@ -21199,7 +21360,8 @@ var createV2Lifecycle = (deps) => {
|
|
|
21199
21360
|
if (managed.length === 0)
|
|
21200
21361
|
return "unmanaged";
|
|
21201
21362
|
const eligible = managed.flatMap((task) => task.workers.filter((entry) => entry.data.state === "assigned" && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === coordinator).map((entry) => ({ task, entry })));
|
|
21202
|
-
const
|
|
21363
|
+
const current = currentTaskId(store);
|
|
21364
|
+
const next = oldest(eligible.filter((item) => item.task.id === current));
|
|
21203
21365
|
if (!next)
|
|
21204
21366
|
return "unbound";
|
|
21205
21367
|
dispatches.set(coordinator, {
|
|
@@ -21393,9 +21555,8 @@ var createV2Lifecycle = (deps) => {
|
|
|
21393
21555
|
if (prepared === "unsettled") {
|
|
21394
21556
|
throw new Error(unsettledLaunch(coordinator));
|
|
21395
21557
|
}
|
|
21396
|
-
if (prepared === "unbound")
|
|
21397
|
-
throw new Error(
|
|
21398
|
-
}
|
|
21558
|
+
if (prepared === "unbound")
|
|
21559
|
+
throw new Error(unboundLaunch(coordinator));
|
|
21399
21560
|
} catch (error) {
|
|
21400
21561
|
if (inflight.get(coordinator) === event.id)
|
|
21401
21562
|
inflight.delete(coordinator);
|
|
@@ -21649,6 +21810,24 @@ var evaluateShellPermission = (root, event, workdirs = new Map) => {
|
|
|
21649
21810
|
}
|
|
21650
21811
|
};
|
|
21651
21812
|
|
|
21813
|
+
// packages/workit-opencode/src/v2/child-identity.ts
|
|
21814
|
+
var CHILD_RECORDS = {
|
|
21815
|
+
evidence: new Set(["record"]),
|
|
21816
|
+
finding: new Set(["record"])
|
|
21817
|
+
};
|
|
21818
|
+
var fields = (input) => typeof input === "object" && input !== null ? input : {};
|
|
21819
|
+
var childMayRecord = (family, input) => {
|
|
21820
|
+
const action = fields(input).action;
|
|
21821
|
+
return family !== null && typeof action === "string" && CHILD_RECORDS[family]?.has(action) === true;
|
|
21822
|
+
};
|
|
21823
|
+
var childTaskMissing = (store, input) => {
|
|
21824
|
+
if (fields(input).taskId !== undefined)
|
|
21825
|
+
return false;
|
|
21826
|
+
const report = store.keyReport();
|
|
21827
|
+
return !report.ok || report.data.bound.length === 0;
|
|
21828
|
+
};
|
|
21829
|
+
var childDenial = (identity) => `OpenCode child session has no validated Workit worker: as ${identity} it may only record evidence or findings (workit_evidence/workit_finding action "record") on an existing task; for anything else the lead assigns it a worker with workit_worker before launching it`;
|
|
21830
|
+
|
|
21652
21831
|
// packages/workit-opencode/src/v2/shell.ts
|
|
21653
21832
|
import path31 from "node:path";
|
|
21654
21833
|
var commandOf = (input) => {
|
|
@@ -21705,6 +21884,54 @@ ${nudge.text}` };
|
|
|
21705
21884
|
} catch {}
|
|
21706
21885
|
};
|
|
21707
21886
|
|
|
21887
|
+
// packages/workit-opencode/src/v2/shell-session.ts
|
|
21888
|
+
var PENDING_LIMIT = 256;
|
|
21889
|
+
var commandText2 = (input) => {
|
|
21890
|
+
if (input === null || typeof input !== "object")
|
|
21891
|
+
return null;
|
|
21892
|
+
const command = input.command;
|
|
21893
|
+
return typeof command === "string" && command.trim() ? command : null;
|
|
21894
|
+
};
|
|
21895
|
+
var noteShellCall = (pending, event) => {
|
|
21896
|
+
const command = commandText2(event.input);
|
|
21897
|
+
if (!command)
|
|
21898
|
+
return;
|
|
21899
|
+
const sessionID = String(event.sessionID);
|
|
21900
|
+
const queue = pending.get(command) ?? { sessions: [], mixed: false };
|
|
21901
|
+
if (queue.sessions.some((other) => other !== sessionID))
|
|
21902
|
+
queue.mixed = true;
|
|
21903
|
+
queue.sessions.push(sessionID);
|
|
21904
|
+
pending.set(command, queue);
|
|
21905
|
+
if (pending.size > PENDING_LIMIT)
|
|
21906
|
+
pending.delete(pending.keys().next().value);
|
|
21907
|
+
};
|
|
21908
|
+
var settleShellCall = (pending, event) => {
|
|
21909
|
+
const command = commandText2(event.input);
|
|
21910
|
+
const queue = command ? pending.get(command) : undefined;
|
|
21911
|
+
if (!command || !queue)
|
|
21912
|
+
return;
|
|
21913
|
+
const index = queue.sessions.indexOf(String(event.sessionID));
|
|
21914
|
+
if (index >= 0)
|
|
21915
|
+
queue.sessions.splice(index, 1);
|
|
21916
|
+
if (queue.sessions.length === 0)
|
|
21917
|
+
pending.delete(command);
|
|
21918
|
+
};
|
|
21919
|
+
var applyShellSession = async (pending, event, parentOf) => {
|
|
21920
|
+
event.env.WORKIT_HOST = "opencode";
|
|
21921
|
+
delete event.env.WORKIT_SESSION_ID;
|
|
21922
|
+
const queue = pending.get(event.command);
|
|
21923
|
+
if (!queue || queue.sessions.length === 0)
|
|
21924
|
+
return;
|
|
21925
|
+
const sessionID = queue.sessions.shift();
|
|
21926
|
+
if (queue.sessions.length === 0)
|
|
21927
|
+
pending.delete(event.command);
|
|
21928
|
+
if (queue.mixed)
|
|
21929
|
+
return;
|
|
21930
|
+
const parent = await parentOf(sessionID);
|
|
21931
|
+
if (parent)
|
|
21932
|
+
event.env.WORKIT_SESSION_ID = childSessionId(parent, sessionID);
|
|
21933
|
+
};
|
|
21934
|
+
|
|
21708
21935
|
// packages/workit-opencode/src/v2/skills.ts
|
|
21709
21936
|
var hookInput2 = (root, sessionID, subagent) => ({
|
|
21710
21937
|
host: "opencode",
|
|
@@ -22008,9 +22235,17 @@ var setup = async (ctx) => {
|
|
|
22008
22235
|
};
|
|
22009
22236
|
const store = new TaskStore(root);
|
|
22010
22237
|
const workerId = workerIdFor(store, session.id, session.parentID, lifecycle.directChildren);
|
|
22011
|
-
if (session.parentID !== undefined && workerId === null)
|
|
22012
|
-
return resultContent(failure2("permission_denied", "OpenCode child session has no validated Workit worker"));
|
|
22013
22238
|
const family = workitFamilyOf(spec.name);
|
|
22239
|
+
if (session.parentID !== undefined && workerId === null) {
|
|
22240
|
+
const identity = childSessionId(session.parentID, session.id);
|
|
22241
|
+
const lead = await sessionFacts(ctx, session.parentID);
|
|
22242
|
+
const directChild = lead !== null && lead.parentID === undefined && sameWorkspace(root, lead.directory);
|
|
22243
|
+
if (!directChild || family === null || !childMayRecord(family, input))
|
|
22244
|
+
return resultContent(failure2("permission_denied", childDenial(identity)));
|
|
22245
|
+
if (childTaskMissing(store, input))
|
|
22246
|
+
return resultContent(failure2("not_found", "no task for this branch yet: the lead starts one (workit_task start) before a child records on it, or pass taskId"));
|
|
22247
|
+
return resultContent(runFamily(family, input, { ...session, id: identity }, store, null));
|
|
22248
|
+
}
|
|
22014
22249
|
if (family !== null)
|
|
22015
22250
|
return resultContent(runFamily(family, input, session, store, workerId));
|
|
22016
22251
|
if (spec.name === "workit_init_apply") {
|
|
@@ -22022,9 +22257,12 @@ var setup = async (ctx) => {
|
|
|
22022
22257
|
}
|
|
22023
22258
|
});
|
|
22024
22259
|
const shellWorkdirs = new Map;
|
|
22260
|
+
const pendingShells = new Map;
|
|
22025
22261
|
await ctx.tool.hook("execute.before", async (event) => {
|
|
22026
|
-
if (event.tool === "shell")
|
|
22262
|
+
if (event.tool === "shell") {
|
|
22263
|
+
noteShellCall(pendingShells, { sessionID: String(event.sessionID), input: event.input });
|
|
22027
22264
|
return observeShellBefore(root, event, shellWorkdirs);
|
|
22265
|
+
}
|
|
22028
22266
|
if (event.tool === "skill")
|
|
22029
22267
|
return observeSkillLoad(root, event);
|
|
22030
22268
|
if (event.tool !== "subagent")
|
|
@@ -22036,9 +22274,17 @@ var setup = async (ctx) => {
|
|
|
22036
22274
|
input: event.input
|
|
22037
22275
|
});
|
|
22038
22276
|
});
|
|
22277
|
+
const shellDomain = ctx.shell;
|
|
22278
|
+
if (shellDomain)
|
|
22279
|
+
await shellDomain.hook("create.before", (event) => applyShellSession(pendingShells, event, async (sessionID) => {
|
|
22280
|
+
const known = lifecycle.directChildren.get(sessionID);
|
|
22281
|
+
return known ?? (await sessionFacts(ctx, sessionID))?.parentID;
|
|
22282
|
+
}));
|
|
22039
22283
|
await ctx.tool.hook("execute.after", async (event) => {
|
|
22040
|
-
if (event.tool === "shell")
|
|
22284
|
+
if (event.tool === "shell") {
|
|
22285
|
+
settleShellCall(pendingShells, { sessionID: String(event.sessionID), input: event.input });
|
|
22041
22286
|
return observeShellResult(root, event);
|
|
22287
|
+
}
|
|
22042
22288
|
if (event.tool !== "subagent")
|
|
22043
22289
|
return;
|
|
22044
22290
|
await lifecycle.executeAfter({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-opencode",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.10.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit OpenCode plugin — native tools, adaptive policy, and sixteen method skills",
|
|
6
6
|
"keywords": [
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"typecheck": "tsc --noEmit"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@brainervirus/workit-core": "^8.
|
|
42
|
+
"@brainervirus/workit-core": "^8.10.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@opencode/plugin": "2.0.18"
|