@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 gets <lead>-v<n>)
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`; that one wins over any
76
- `<lead>-v<n>` in a brief. An `implementer` uses the session its brief
77
- exports for its workit commands.
78
- - Other hosts: the session the brief sets wins: `export
79
- WORKIT_SESSION_ID=<lead>-w-<slice>` from `workit fanout brief` for a
80
- worker, `<lead>-v<n>` (or `--as verifier`) for a verifier you start.
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 (`WORKIT_SESSION_ID=<yours>-v1`;
37
- Claude Code: the `verifier` agent). Before saying done, reconcile
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 (`WORKIT_SESSION_ID=<lead>-v<n>`;
11
- Claude Code: the `reviewer` or `verifier` agent).
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 path4 from "node:path";
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(path4.join(dir, "HEAD")).isFile() && fs4.statSync(path4.join(dir, "objects")).isDirectory() && fs4.statSync(path4.join(dir, "refs")).isDirectory();
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(path4.join(common, "config"), "utf8"));
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 = path4.join(dir, ".git");
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 = path4.resolve(dir, match[1].trim());
9694
+ gitDir = path3.resolve(dir, match[1].trim());
9748
9695
  } else
9749
9696
  return null;
9750
- if (!fs4.existsSync(path4.join(gitDir, "HEAD")))
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 = path4.resolve(gitDir, fs4.readFileSync(path4.join(gitDir, "commondir"), "utf8").trim());
9704
+ common = path3.resolve(gitDir, fs4.readFileSync(path3.join(gitDir, "commondir"), "utf8").trim());
9758
9705
  } catch {}
9759
- if (!fs4.existsSync(path4.join(common, "objects")))
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 = path4.dirname(dir);
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 = path4.resolve(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: path4.join(root, ".workit"), shared: false, top: null };
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: path4.join(root, ".workit"), shared: false, top: null };
9732
+ return { dir: path3.join(root, ".workit"), shared: false, top: null };
9786
9733
  if (fast?.kind === "repo")
9787
9734
  return {
9788
- dir: path4.join(fast.common, "workit"),
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: path4.join(real(path4.resolve(root, common)), "workit"),
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: path4.join(root, ".workit"), shared: false, top: null };
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(path4.join(gitDir, state, "head-name"), "utf8").trim();
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(path4.join(location.git.gitDir, "HEAD"), "utf8").trim();
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 = path4.resolve(root);
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(path4.join(dir, entry.name));
9835
+ walk(path3.join(dir, entry.name));
9889
9836
  else
9890
- files.push(path4.join(dir, entry.name));
9837
+ files.push(path3.join(dir, entry.name));
9891
9838
  };
9892
- walk(path4.join(path4.dirname(location.dir), "logs", "refs", "heads"));
9839
+ walk(path3.join(path3.dirname(location.dir), "logs", "refs", "heads"));
9893
9840
  if (location.git)
9894
- files.push(path4.join(location.git.gitDir, "logs", "HEAD"));
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 = path4.basename(canonical).replace(/[^A-Za-z0-9._-]/g, "_").slice(0, 40) || "root";
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.ok ? branchRenames(location.data) : [];
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.has(row.actor.session))
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 (\`WORKIT_SESSION_ID=<lead>-v<n>\`).
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
- function compactTaskContext(view) {
15691
- const decisions = view.task.decisions.slice().toSorted((left, right) => {
15692
- const leftAt = compactTimestamp(left.recordedAt);
15693
- const rightAt = compactTimestamp(right.recordedAt);
15694
- if (leftAt !== rightAt)
15695
- return leftAt > rightAt ? -1 : 1;
15696
- return left.id < right.id ? -1 : left.id > right.id ? 1 : 0;
15697
- }).slice(0, COMPACT_MAX_ITEMS).map((entry) => ({
15698
- id: entry.id,
15699
- purpose: entry.data.purpose,
15700
- status: entry.data.response,
15701
- digest: entry.data.digest,
15702
- choice: compactDecisionChoice(entry) || `task.inspect taskId=${view.task.id} view=full; decision=${entry.id}`,
15703
- references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
15704
- }));
15705
- const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
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
- ...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
15712
- ...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
15713
- ...view.task.progress.blockers.map((entry) => entry.reason)
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.task.status,
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.task.progress.nextAction, COMPACT_TEXT_BYTES)
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
- return this.retryOmittedRevisions(value, () => this.workerOnce(value));
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.sessions.some((item) => item.host !== "workit_cli" && !(item.host === session.host && item.handle === session.handle)));
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 entry ? entryCompactContext(store, entry, session, context) : null;
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.id,
19128
- revision: entry.revision,
19129
- file: entry.file,
19130
- workspace: workspace.data.revision,
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.data.evidence.some(isObservedCheck);
19143
- const files = task.data.decisions.flatMap((decision) => decision.data.binding.contentRefs.flatMap((ref) => ref.kind === "file" ? [path20.resolve(store.root, ref.path)] : []));
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.id, freshness);
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 oldestOfSingleTask = (items) => {
21180
- const tasks = new Set(items.map((item) => item.task.id));
21181
- if (tasks.size !== 1 || items.length === 0)
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 next = oldestOfSingleTask(eligible);
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("recovery_required: no assigned Workit worker is attributable to this coordinator; assign one with workit_worker before launching a native task");
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.9.0",
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.9.0"
42
+ "@brainervirus/workit-core": "^8.10.0"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@opencode/plugin": "2.0.18"