@brainervirus/workit-opencode 1.3.2 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -51,14 +51,23 @@ no Workit task, writer or decision is needed merely to run an ordinary command.
51
51
  Old action/decision history is preserved. Inspect and reconcile any uncertain
52
52
  effect before retrying; removal does not settle or migrate it.
53
53
 
54
+ ## Upgrade from Workit 1.x
55
+
56
+ Workit 2.0 removes the managed external-action tool on both OpenCode versions.
57
+ Use native host tools for mutations and `workit_context` for read-only context.
58
+ Existing task and action history remains untouched; reconcile uncertain effects
59
+ before retrying them.
60
+
54
61
  ## Bundle / runtime model
55
62
 
56
63
  The build bundles the `@opencode-ai/plugin` (V1) and `@opencode/plugin` (V2) SDK surfaces used by the adapters into `dist/plugin.js`, so the published plugin has **no** runtime dependency on either SDK (both stay development/build-only pins). The plugin loads through its real package entry `dist/plugin.js`; only the fourteen method skills ship under `assets/`.
57
64
 
58
65
  The V2 entry registers the same ten tools with `codemode: false`, the fourteen
59
- skills and `wk-*` commands (user collisions preserved), question receipts for
60
- `decision.record`, direct-child subagent lineage with durable dispatch claims,
61
- and the bootstrap/task/worker context plus compaction injection. Its shell
66
+ skills and up to fourteen `wk-*` commands. Existing user skills and commands
67
+ are preserved; an alias is added only when its Workit skill is registered. It
68
+ also provides question receipts for `decision.record`, direct-child subagent
69
+ lineage with durable dispatch claims, and bootstrap/task/worker context plus
70
+ compaction injection. Its shell
62
71
  permission hook adds branch-name policy denials for direct, unquoted literal
63
72
  branch-creation forms only. It leaves compliant branches, PR and worktree
64
73
  commands, and unsupported shell syntax to OpenCode's native permission rules.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: workit-implement
3
- description: Use when implementation benefits from bounded delegation or shared checkout coordination
3
+ description: Use when implementing requested code changes in a repository. Follow its rules and host permissions; use Workit tracking and delegation only when continuity or coordination helps.
4
4
  ---
5
5
 
6
6
  # Implement within authority
package/dist/plugin.js CHANGED
@@ -31346,7 +31346,6 @@ var createV2Lifecycle = (deps) => {
31346
31346
  const dispatches = new Map;
31347
31347
  const inflight = new Map;
31348
31348
  const lifecycleBindings = new Map;
31349
- const unresolvedTaskLaunches = new Set;
31350
31349
  const now = () => new Date().toISOString().replace(/\.\d{3}Z$/, "Z");
31351
31350
  const readSession = async (sessionID) => {
31352
31351
  try {
@@ -31443,7 +31442,7 @@ var createV2Lifecycle = (deps) => {
31443
31442
  });
31444
31443
  return "prepared";
31445
31444
  };
31446
- const commitDispatchStart = (coordinator, childID) => {
31445
+ const commitDispatchStart = (coordinator, childID, retainPending = false) => {
31447
31446
  const pending = dispatches.get(coordinator);
31448
31447
  if (!pending)
31449
31448
  return false;
@@ -31463,9 +31462,11 @@ var createV2Lifecycle = (deps) => {
31463
31462
  });
31464
31463
  if (!committed.ok)
31465
31464
  return false;
31466
- dispatches.delete(coordinator);
31467
- if (inflight.get(coordinator) === pending.callID)
31468
- inflight.delete(coordinator);
31465
+ if (!retainPending) {
31466
+ dispatches.delete(coordinator);
31467
+ if (inflight.get(coordinator) === pending.callID)
31468
+ inflight.delete(coordinator);
31469
+ }
31469
31470
  lifecycleBindings.set(childID, {
31470
31471
  parentID: coordinator,
31471
31472
  taskId: pending.taskId,
@@ -31478,32 +31479,32 @@ var createV2Lifecycle = (deps) => {
31478
31479
  const listed = store.listTasks();
31479
31480
  const workspace = store.readWorkspace();
31480
31481
  if (!listed.ok || !workspace.ok || !workspace.data)
31481
- return;
31482
+ return false;
31482
31483
  const persisted = listed.data.flatMap((task) => task.status === "active" ? task.workers.filter((entry) => entry.id === binding?.workerId || entry.data.session?.kind === "host" && entry.data.session.handle === sessionID).map((entry) => ({ task, entry })) : []);
31483
31484
  const matches = persisted.filter(({ entry }) => entry.data.session?.kind === "host");
31484
31485
  if (binding && matches.length !== 1)
31485
- return;
31486
+ return false;
31486
31487
  const selected = binding ? persisted.find(({ task, entry }) => task.id === binding.taskId && entry.id === binding.workerId) : matches.length === 1 ? matches[0] : undefined;
31487
31488
  if (!selected)
31488
- return;
31489
+ return false;
31489
31490
  if (selected.entry.data.session?.kind !== "host")
31490
- return;
31491
+ return false;
31491
31492
  if (selected.entry.provenance.session?.kind !== "host")
31492
- return;
31493
+ return false;
31493
31494
  if (!binding && deleted)
31494
- return;
31495
+ return false;
31495
31496
  if (!deleted) {
31496
31497
  const observed = await readSession(sessionID);
31497
31498
  const observedParent = parentOf(observed);
31498
31499
  if (!observed || !sameWorkspace(root, observed.directory) || !observedParent)
31499
- return;
31500
+ return false;
31500
31501
  if (!binding) {
31501
31502
  const coordinator = workerCoordinatorFor(selected.task, selected.entry);
31502
31503
  if (!coordinator || observedParent !== coordinator)
31503
- return;
31504
+ return false;
31504
31505
  parentID = coordinator;
31505
31506
  } else if (observedParent !== parentID)
31506
- return;
31507
+ return false;
31507
31508
  }
31508
31509
  directChildren.set(sessionID, parentID);
31509
31510
  const core = new WorkitCore(store, {
@@ -31525,34 +31526,51 @@ var createV2Lifecycle = (deps) => {
31525
31526
  observation: { event: state, sessionID }
31526
31527
  });
31527
31528
  if (!result.ok)
31528
- return;
31529
+ return false;
31529
31530
  lifecycleBindings.set(sessionID, {
31530
31531
  parentID,
31531
31532
  taskId: selected.task.id,
31532
31533
  workerId: selected.entry.id
31533
31534
  });
31535
+ return true;
31534
31536
  };
31535
31537
  const reconcilePending = async (coordinator) => {
31536
31538
  const pending = dispatches.get(coordinator);
31537
- if (!pending?.childID)
31539
+ if (!pending)
31540
+ return;
31541
+ const task = new TaskStore(root).readTask(pending.taskId);
31542
+ if (task.ok && task.data.workers.find((worker) => worker.id === pending.workerId)?.data.state === "stopped") {
31543
+ dispatches.delete(coordinator);
31544
+ if (inflight.get(coordinator) === pending.callID)
31545
+ inflight.delete(coordinator);
31546
+ return;
31547
+ }
31548
+ if (!pending.childID)
31538
31549
  return;
31539
31550
  const info = await readSession(pending.childID);
31540
- if (!info || info.id !== pending.childID || !info.outcome)
31551
+ if (!info || info.id !== pending.childID || parentOf(info) !== coordinator || !sameWorkspace(root, info.directory))
31541
31552
  return;
31542
- await observeLifecycle(pending.childID, coordinator, "stopped", {
31553
+ directChildren.set(info.id, coordinator);
31554
+ const alreadyStarted = lifecycleBindings.get(info.id);
31555
+ if ((!alreadyStarted || alreadyStarted.parentID !== coordinator || alreadyStarted.taskId !== pending.taskId || alreadyStarted.workerId !== pending.workerId) && !commitDispatchStart(coordinator, info.id, Boolean(info.outcome)))
31556
+ return;
31557
+ const terminal = info.outcome !== undefined || pending.completionReport !== undefined;
31558
+ if (!terminal)
31559
+ return;
31560
+ const stopped = await observeLifecycle(info.id, coordinator, "stopped", {
31543
31561
  taskId: pending.taskId,
31544
31562
  workerId: pending.workerId
31545
- });
31546
- dispatches.delete(coordinator);
31547
- if (inflight.get(coordinator) === pending.callID)
31548
- inflight.delete(coordinator);
31563
+ }, false, info.outcome === "failed" || info.outcome === "interrupted" ? undefined : pending.completionReport);
31564
+ if (stopped) {
31565
+ dispatches.delete(coordinator);
31566
+ if (inflight.get(coordinator) === pending.callID)
31567
+ inflight.delete(coordinator);
31568
+ }
31549
31569
  };
31550
31570
  const coordinatorVeto = (coordinator) => {
31551
31571
  const listed = new TaskStore(root).listTasks();
31552
31572
  if (listed.ok && listed.data.some((task) => task.workers.some((worker) => (worker.data.state === "cancelling" || worker.data.state === "unknown") && worker.provenance.session?.kind === "host" && worker.provenance.session.handle === coordinator)))
31553
31573
  return "recovery_required: a cancelled worker remains uncertain; repeat worker.cancel on the ended worker to confirm its stop, then retry";
31554
- if (unresolvedTaskLaunches.delete(coordinator))
31555
- return "recovery_required: a cancelled worker remains uncertain";
31556
31574
  return null;
31557
31575
  };
31558
31576
  const executeBefore = async (event) => {
@@ -31616,34 +31634,50 @@ var createV2Lifecycle = (deps) => {
31616
31634
  const coordinator = event.sessionID;
31617
31635
  const pending = dispatches.get(coordinator);
31618
31636
  const generation = pending?.callID === event.id ? pending : undefined;
31637
+ let started = false;
31638
+ let terminalObserved = false;
31639
+ let terminalObservationRequired = false;
31640
+ let childSession = null;
31619
31641
  if (event.status === "completed") {
31620
31642
  const metadata = record3(event.result.metadata);
31621
31643
  const child = sessionIDOf(metadata.sessionID ?? metadata.sessionId);
31644
+ const output = contentText(event.result.content);
31645
+ const completedOutput = /<subagent\b[^>]*\bstate="completed"/.test(output);
31622
31646
  if (child) {
31623
- const childSession = await readSession(child);
31624
- if (parentOf(childSession) === coordinator) {
31647
+ if (generation)
31648
+ generation.childID = child;
31649
+ childSession = await readSession(child);
31650
+ if (parentOf(childSession) === coordinator && sameWorkspace(root, childSession?.directory)) {
31625
31651
  directChildren.set(child, coordinator);
31652
+ const terminal = childSession?.outcome !== undefined || completedOutput;
31653
+ terminalObservationRequired = terminal;
31626
31654
  if (generation)
31627
- commitDispatchStart(coordinator, child);
31655
+ started = commitDispatchStart(coordinator, child, terminal);
31628
31656
  }
31629
31657
  }
31630
- const output = contentText(event.result.content);
31631
- const completedChild = child && /<subagent\b[^>]*\bstate="completed"/.test(output) && lifecycleBindings.has(child) ? child : null;
31658
+ const completedChild = child && (childSession?.outcome !== undefined || completedOutput) && lifecycleBindings.has(child) ? child : null;
31632
31659
  if (completedChild) {
31633
31660
  const binding = lifecycleBindings.get(completedChild);
31634
- if (binding)
31635
- await observeLifecycle(completedChild, coordinator, "stopped", binding, false, {
31661
+ if (binding) {
31662
+ const canReportCompletion = completedOutput && childSession?.outcome !== "failed" && childSession?.outcome !== "interrupted";
31663
+ if (canReportCompletion && generation)
31664
+ generation.completionReport = {
31665
+ outcome: "completed",
31666
+ summary: output.trim() || "Native worker completed.",
31667
+ evidenceIds: [],
31668
+ findingIds: []
31669
+ };
31670
+ terminalObserved = canReportCompletion ? await observeLifecycle(completedChild, coordinator, "stopped", binding, false, generation?.completionReport ?? {
31636
31671
  outcome: "completed",
31637
31672
  summary: output.trim() || "Native worker completed.",
31638
31673
  evidenceIds: [],
31639
31674
  findingIds: []
31640
- });
31675
+ }) : await observeLifecycle(completedChild, coordinator, "stopped", binding);
31676
+ }
31641
31677
  }
31642
- } else {
31643
- const message = String(event.error?.message ?? event.error ?? "").toLowerCase();
31644
- if (generation && /(?:cancel|interrupt|unknown|uncertain)/.test(message))
31645
- unresolvedTaskLaunches.add(coordinator);
31646
31678
  }
31679
+ if (generation && (event.status === "error" || !started || terminalObservationRequired && !terminalObserved))
31680
+ return;
31647
31681
  if (generation) {
31648
31682
  dispatches.delete(coordinator);
31649
31683
  if (inflight.get(coordinator) === event.id)
@@ -31804,7 +31838,8 @@ var parseFrontmatter = (markdown) => {
31804
31838
  var registerSkills = async (ctx) => {
31805
31839
  const skillsDir = path27.join(assetsRoot(), "skills");
31806
31840
  if (!existsSync14(skillsDir))
31807
- return;
31841
+ return new Set;
31842
+ const registered = new Set;
31808
31843
  await ctx.skill.transform((editor) => {
31809
31844
  const existing = new Set(editor.list().map((skill) => skill.id));
31810
31845
  for (const id of WORKIT_METHOD_SKILLS) {
@@ -31821,15 +31856,17 @@ var registerSkills = async (ctx) => {
31821
31856
  path: file,
31822
31857
  content: parsed.body
31823
31858
  });
31859
+ registered.add(id);
31824
31860
  }
31825
31861
  });
31862
+ return registered;
31826
31863
  };
31827
- var registerCommands = async (ctx) => {
31864
+ var registerCommands = async (ctx, registeredSkills) => {
31828
31865
  const listed = await ctx.command.list();
31829
31866
  const existing = new Set((listed?.data ?? []).map((command) => command.name));
31830
31867
  await ctx.command.transform((editor) => {
31831
31868
  for (const [alias, skill] of Object.entries(WORKIT_SKILL_ALIASES)) {
31832
- if (existing.has(alias))
31869
+ if (!registeredSkills.has(skill) || existing.has(alias))
31833
31870
  continue;
31834
31871
  editor.add({
31835
31872
  name: alias,
@@ -32061,8 +32098,8 @@ var setup = async (ctx) => {
32061
32098
  await ctx.session.hook("compaction", (event) => {
32062
32099
  injectCompactionContext(root, String(event.sessionID), event.system);
32063
32100
  });
32064
- await registerSkills(ctx);
32065
- await registerCommands(ctx);
32101
+ const registeredSkills = await registerSkills(ctx);
32102
+ await registerCommands(ctx, registeredSkills);
32066
32103
  const subscription = new AbortController;
32067
32104
  (async () => {
32068
32105
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "1.3.2",
3
+ "version": "2.0.1",
4
4
  "private": false,
5
5
  "description": "Workit OpenCode plugin — native tools, adaptive policy, and fourteen method skills",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "dependencies": {
42
- "@brainervirus/workit-core": "^1.3.2"
42
+ "@brainervirus/workit-core": "^2.0.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@opencode-ai/plugin": "1.18.30",