@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 +12 -3
- package/assets/skills/workit-implement/SKILL.md +1 -1
- package/dist/plugin.js +78 -41
- package/package.json +2 -2
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
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
-
|
|
31467
|
-
|
|
31468
|
-
inflight.
|
|
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
|
|
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.
|
|
31551
|
+
if (!info || info.id !== pending.childID || parentOf(info) !== coordinator || !sameWorkspace(root, info.directory))
|
|
31541
31552
|
return;
|
|
31542
|
-
|
|
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
|
-
|
|
31547
|
-
|
|
31548
|
-
inflight.
|
|
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
|
-
|
|
31624
|
-
|
|
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
|
|
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
|
-
|
|
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": "
|
|
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": "^
|
|
42
|
+
"@brainervirus/workit-core": "^2.0.1"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@opencode-ai/plugin": "1.18.30",
|