@brainervirus/workit-opencode 1.0.8 → 1.0.9
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/assets/skills/workit-babysit/SKILL.md +3 -1
- package/dist/plugin.js +195 -80
- package/package.json +2 -2
|
@@ -7,7 +7,9 @@ description: Use after creating a PR (auto-starts, drive default), when a PR nee
|
|
|
7
7
|
|
|
8
8
|
Babysit starts automatically on PR creation unless declined (`babysit:false`).
|
|
9
9
|
One babysitter per PR; never mutate PR topology (no rebase strategy changes,
|
|
10
|
-
no force-push).
|
|
10
|
+
no force-push). When a PR URL is observed from a route Workit did not enforce
|
|
11
|
+
(for example a raw `gh pr create` the host allowed), load this skill and drive
|
|
12
|
+
that PR anyway; do not claim the Workit route was enforced.
|
|
11
13
|
|
|
12
14
|
## Before method work
|
|
13
15
|
|
package/dist/plugin.js
CHANGED
|
@@ -7628,7 +7628,7 @@ var workerReportSchema = object({
|
|
|
7628
7628
|
}).strict();
|
|
7629
7629
|
var workerSchema = object({
|
|
7630
7630
|
assignment: assignmentSchema,
|
|
7631
|
-
state: _enum(["assigned", "running", "cancelling", "stopped", "unknown"]),
|
|
7631
|
+
state: _enum(["assigned", "dispatching", "running", "cancelling", "stopped", "unknown"]),
|
|
7632
7632
|
session: refSchema.nullable(),
|
|
7633
7633
|
report: workerReportSchema.nullable()
|
|
7634
7634
|
}).strict();
|
|
@@ -10399,6 +10399,95 @@ function evaluateClosure(requestedOutcome, view) {
|
|
|
10399
10399
|
});
|
|
10400
10400
|
}
|
|
10401
10401
|
|
|
10402
|
+
// packages/workit-core/src/core/methods.ts
|
|
10403
|
+
var METHODS = {
|
|
10404
|
+
"workit-challenge": { dimensions: ["challenge", "decisions"] },
|
|
10405
|
+
"workit-behavioral-tdd": { dimensions: ["testing"] },
|
|
10406
|
+
"workit-review": { dimensions: ["review"] },
|
|
10407
|
+
"workit-plan": { dimensions: ["artifacts", "continuity"] },
|
|
10408
|
+
"workit-implement": { dimensions: ["delegation"] },
|
|
10409
|
+
"workit-debug": { ruleIds: ["root-cause-investigation"] },
|
|
10410
|
+
"workit-handoff": { ruleIds: ["durable-handoff"] },
|
|
10411
|
+
"workit-deslop": { ruleIds: ["pre-pr-cleanup"] }
|
|
10412
|
+
};
|
|
10413
|
+
var methodMatches = (id, definition, requirement) => definition.dimensions?.includes(requirement.dimension) === true && !(id === "workit-review" && requirement.ruleId === "self-review") || definition.ruleIds?.includes(requirement.ruleId) === true;
|
|
10414
|
+
var assuranceFor = (id, definition, capabilities) => {
|
|
10415
|
+
const keys = new Set([
|
|
10416
|
+
id,
|
|
10417
|
+
...definition.dimensions ?? [],
|
|
10418
|
+
...definition.ruleIds ?? []
|
|
10419
|
+
]);
|
|
10420
|
+
const matches = capabilities.filter((capability) => keys.has(capability.name) || keys.has(capability.surface));
|
|
10421
|
+
if (id === "workit-review" && matches.length === 0)
|
|
10422
|
+
return { assurance: "unavailable", reasons: ["independent review capability is unavailable"] };
|
|
10423
|
+
if (matches.some((capability) => capability.assurance === "unavailable"))
|
|
10424
|
+
return {
|
|
10425
|
+
assurance: "unavailable",
|
|
10426
|
+
reasons: matches.filter((capability) => capability.assurance === "unavailable").map((capability) => capability.reason)
|
|
10427
|
+
};
|
|
10428
|
+
if (matches.some((capability) => capability.assurance === "enforced"))
|
|
10429
|
+
return { assurance: "enforced", reasons: matches.map((capability) => capability.reason) };
|
|
10430
|
+
return { assurance: "agent_guided", reasons: matches.map((capability) => capability.reason) };
|
|
10431
|
+
};
|
|
10432
|
+
function selectMethods(policy, capabilities) {
|
|
10433
|
+
return Object.entries(METHODS).flatMap(([id, definition]) => {
|
|
10434
|
+
const requirements = policy.requirements.filter((requirement) => methodMatches(id, definition, requirement));
|
|
10435
|
+
if (requirements.length === 0)
|
|
10436
|
+
return [];
|
|
10437
|
+
const capability = assuranceFor(id, definition, capabilities);
|
|
10438
|
+
const reasons = [
|
|
10439
|
+
...new Set(requirements.map((requirement) => requirement.reason)),
|
|
10440
|
+
...capability.reasons
|
|
10441
|
+
];
|
|
10442
|
+
if (capability.assurance === "unavailable")
|
|
10443
|
+
reasons.unshift("required capability is unavailable");
|
|
10444
|
+
return [{ id, reason: reasons.join("; "), assurance: capability.assurance }];
|
|
10445
|
+
});
|
|
10446
|
+
}
|
|
10447
|
+
var invariantBootstrap = () => `
|
|
10448
|
+
Workit keeps one accountable lead and one shared task state. Inspect current task
|
|
10449
|
+
state before acting. An empty task.list means no session yet, not permission to
|
|
10450
|
+
skip Workit; for user-requested product, debug, or behavior work with no active
|
|
10451
|
+
or paused task, run task.start then policy.assess before other product mutations.
|
|
10452
|
+
Omitted expectedRevision, expectedWorkspaceRevision, and writer workerId default
|
|
10453
|
+
to the current records; explicit values are still concurrency-checked, so never
|
|
10454
|
+
copy revisions between calls. Happy path: task.list, task.start {intent} with no
|
|
10455
|
+
revisions, policy.assess {assessment: {facts, signals, consequences,
|
|
10456
|
+
verification}} where facts are inferred or observed with file refs,
|
|
10457
|
+
writer.acquire {taskId} before product writes, evidence.record {evidence} where
|
|
10458
|
+
check and review kinds auto-bind the current tree and need no digests. Record
|
|
10459
|
+
self-review requirements with kind review and reviewContext matching your own
|
|
10460
|
+
host session; the lead may also record checks. Fresh-context review requires a
|
|
10461
|
+
session distinct from the task creator and every other evidence recorder. Record
|
|
10462
|
+
verification evidence last: later evidence or commits can reopen fixed findings
|
|
10463
|
+
for re-verification, so verify, resolve, then close with nothing in between.
|
|
10464
|
+
decision.record {binding with taskId/workspaceId from inspect plus presented and
|
|
10465
|
+
approvedContent} only through a native approval question asked receipt-shaped:
|
|
10466
|
+
header Workit decision: <purpose>, the same label repeated in the question
|
|
10467
|
+
text (some host UIs do not render headers), exactly two options approved and
|
|
10468
|
+
rejected; ask once, never re-ask to mint a receipt. task.close {outcome,
|
|
10469
|
+
summary, decisionIds}. A completed native subagent run stops its bound worker
|
|
10470
|
+
by itself; if a worker strands in cancelling with its run verifiably over,
|
|
10471
|
+
repeat worker.cancel {reason} to confirm the stop. Check and review evidence
|
|
10472
|
+
without a bound candidate goes stale; findings close only as fixed with passing
|
|
10473
|
+
verification, dismissed with supporting evidence, or deferred under an approved
|
|
10474
|
+
limitation. Use only the
|
|
10475
|
+
shared operations for task, policy, evidence, finding, decision, worker, writer,
|
|
10476
|
+
and state changes. Authority is bounded by the requested scope, current
|
|
10477
|
+
revision, caller/session provenance, and observed capabilities. Never claim host
|
|
10478
|
+
enforcement or evidence that the host cannot provide. Shell-executed writes are
|
|
10479
|
+
unattested agent-guided work even when a writer is held. Preserve unresolved
|
|
10480
|
+
requirements, gaps, and uncertain workers. Skill routing: slash aliases /wk-*
|
|
10481
|
+
always load on demand. Model-invoked: new instructions, interruptions, or
|
|
10482
|
+
forgotten items mid-task → load workit-steer before responding; about to open
|
|
10483
|
+
a PR or fresh from implementing → load workit-deslop; CI red → load
|
|
10484
|
+
workit-green-run; small-looking change before close or merge → load
|
|
10485
|
+
workit-blast-radius; ambiguous, consequential, or disputed proposal before
|
|
10486
|
+
recommending → load workit-challenge; spec or plan needs a flow diagram →
|
|
10487
|
+
load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
|
|
10488
|
+
act from memory of it.
|
|
10489
|
+
`.trim();
|
|
10490
|
+
|
|
10402
10491
|
// packages/workit-core/src/core/task-context.ts
|
|
10403
10492
|
function reconcileResume(view, observations = []) {
|
|
10404
10493
|
if (observations.length > 0)
|
|
@@ -10408,7 +10497,7 @@ function reconcileResume(view, observations = []) {
|
|
|
10408
10497
|
return candidate;
|
|
10409
10498
|
const staleEvidenceIds = evaluateEvidence(view.task, candidate.data).filter((entry) => entry.status === "stale").map((entry) => entry.evidenceId);
|
|
10410
10499
|
const blockers = [...view.task.progress.blockers];
|
|
10411
|
-
if (view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
|
|
10500
|
+
if (view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
|
|
10412
10501
|
blockers.push({
|
|
10413
10502
|
reason: "worker state requires reconciliation",
|
|
10414
10503
|
dependentAction: "resume",
|
|
@@ -10458,6 +10547,11 @@ function compactTaskContext(view) {
|
|
|
10458
10547
|
digest: entry.data.digest,
|
|
10459
10548
|
references: entry.data.binding.contentRefs.slice(0, 1).map(compactReference)
|
|
10460
10549
|
}));
|
|
10550
|
+
const methods = view.task.policy ? selectMethods(view.task.policy, view.capabilities).map((method) => ({
|
|
10551
|
+
id: method.id,
|
|
10552
|
+
assurance: method.assurance,
|
|
10553
|
+
reason: compactText(method.reason, COMPACT_TEXT_BYTES) ?? ""
|
|
10554
|
+
})) : [];
|
|
10461
10555
|
const gaps = Array.from(new Set([
|
|
10462
10556
|
...view.evidence.filter((entry) => entry.status === "stale").map((entry) => `stale evidence: ${entry.evidenceId}`),
|
|
10463
10557
|
...view.requirements.filter((entry) => entry.status !== "satisfied").map((entry) => entry.reason),
|
|
@@ -10467,15 +10561,18 @@ function compactTaskContext(view) {
|
|
|
10467
10561
|
objective: compactText(view.task.intent.data.objective, COMPACT_TEXT_BYTES) ?? "",
|
|
10468
10562
|
status: view.task.status,
|
|
10469
10563
|
decisions,
|
|
10564
|
+
methods,
|
|
10470
10565
|
gaps,
|
|
10471
10566
|
nextAction: compactText(view.task.progress.nextAction, COMPACT_TEXT_BYTES)
|
|
10472
10567
|
};
|
|
10473
10568
|
let encoded = JSON.stringify(context);
|
|
10474
|
-
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.gaps.length)) {
|
|
10569
|
+
while (compactBytes(encoded) > COMPACT_MAX_BYTES && (context.decisions.length || context.methods.length || context.gaps.length)) {
|
|
10475
10570
|
if (context.gaps.length)
|
|
10476
10571
|
context.gaps.pop();
|
|
10477
|
-
else
|
|
10572
|
+
else if (context.decisions.length)
|
|
10478
10573
|
context.decisions.pop();
|
|
10574
|
+
else
|
|
10575
|
+
context.methods.pop();
|
|
10479
10576
|
encoded = JSON.stringify(context);
|
|
10480
10577
|
}
|
|
10481
10578
|
if (compactBytes(encoded) > COMPACT_MAX_BYTES) {
|
|
@@ -10491,50 +10588,47 @@ var exportDigest = (bundle) => sha256({
|
|
|
10491
10588
|
sourceWorkspaceId: bundle.sourceWorkspaceId,
|
|
10492
10589
|
task: bundle.task
|
|
10493
10590
|
});
|
|
10494
|
-
// packages/workit-core/src/core/
|
|
10495
|
-
var
|
|
10496
|
-
Workit
|
|
10497
|
-
|
|
10498
|
-
|
|
10499
|
-
|
|
10500
|
-
|
|
10501
|
-
|
|
10502
|
-
|
|
10503
|
-
|
|
10504
|
-
|
|
10505
|
-
|
|
10506
|
-
|
|
10507
|
-
|
|
10508
|
-
|
|
10509
|
-
|
|
10510
|
-
|
|
10511
|
-
|
|
10512
|
-
|
|
10513
|
-
|
|
10514
|
-
|
|
10515
|
-
|
|
10516
|
-
|
|
10517
|
-
|
|
10518
|
-
|
|
10519
|
-
|
|
10520
|
-
|
|
10521
|
-
|
|
10522
|
-
|
|
10523
|
-
|
|
10524
|
-
|
|
10525
|
-
|
|
10526
|
-
|
|
10527
|
-
|
|
10528
|
-
|
|
10529
|
-
|
|
10530
|
-
|
|
10531
|
-
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
load workit-diagram, a UI sketch → load workit-mockup. Load the skill; never
|
|
10536
|
-
act from memory of it.
|
|
10537
|
-
`.trim();
|
|
10591
|
+
// packages/workit-core/src/core/route-intent.ts
|
|
10592
|
+
var GUIDANCE = {
|
|
10593
|
+
"git.branch_setup": "use the Workit git.branch_setup action (workit_external_action in hosts, or `workit action git.branch_setup --payload '{}' --confirm --json` in the CLI)",
|
|
10594
|
+
"hosting.pull_request": "use the Workit hosting.pull_request action (workit_external_action in hosts, or `workit action hosting.pull_request --payload '{...}' --confirm --json` in the CLI), then drive the created PR through workit-babysit"
|
|
10595
|
+
};
|
|
10596
|
+
var unbalancedQuotes = (segment) => {
|
|
10597
|
+
const text = segment.replace(/\\./gu, "");
|
|
10598
|
+
return (text.match(/"/gu)?.length ?? 0) % 2 !== 0 || (text.match(/'/gu)?.length ?? 0) % 2 !== 0;
|
|
10599
|
+
};
|
|
10600
|
+
var segmentTokens = (segment) => {
|
|
10601
|
+
if (segment.includes("`") || segment.includes("$("))
|
|
10602
|
+
return null;
|
|
10603
|
+
if (unbalancedQuotes(segment))
|
|
10604
|
+
return null;
|
|
10605
|
+
const parts = segment.trim().split(/\s+/u);
|
|
10606
|
+
while (parts.length > 0 && /^[A-Za-z_][A-Za-z0-9_]*=/u.test(parts[0]))
|
|
10607
|
+
parts.shift();
|
|
10608
|
+
return parts.length > 0 ? parts : null;
|
|
10609
|
+
};
|
|
10610
|
+
var unquote = (token) => token.replace(/^['"]|['"]$/gu, "");
|
|
10611
|
+
function shellRouteIntent(command) {
|
|
10612
|
+
if (typeof command !== "string" || command.trim() === "")
|
|
10613
|
+
return null;
|
|
10614
|
+
for (const raw of command.split(/&&|\|\||;|\n/u)) {
|
|
10615
|
+
const parts = segmentTokens(raw);
|
|
10616
|
+
if (!parts)
|
|
10617
|
+
continue;
|
|
10618
|
+
const head = unquote(parts[0]);
|
|
10619
|
+
const second = unquote(parts[1] ?? "");
|
|
10620
|
+
const third = unquote(parts[2] ?? "");
|
|
10621
|
+
if (head === "git" && second === "switch" && (third === "-c" || third === "--create"))
|
|
10622
|
+
return { route: "git.branch_setup", guidance: GUIDANCE["git.branch_setup"] };
|
|
10623
|
+
if (head === "git" && second === "checkout" && (third === "-b" || third === "-B"))
|
|
10624
|
+
return { route: "git.branch_setup", guidance: GUIDANCE["git.branch_setup"] };
|
|
10625
|
+
if (head === "gh" && second === "pr" && third === "create")
|
|
10626
|
+
return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
|
|
10627
|
+
if (head === "glab" && second === "mr" && third === "create")
|
|
10628
|
+
return { route: "hosting.pull_request", guidance: GUIDANCE["hosting.pull_request"] };
|
|
10629
|
+
}
|
|
10630
|
+
return null;
|
|
10631
|
+
}
|
|
10538
10632
|
// packages/workit-core/src/core/external-action.ts
|
|
10539
10633
|
var externalActionSchema = discriminatedUnion("operation", [
|
|
10540
10634
|
object({
|
|
@@ -11313,10 +11407,10 @@ var applyWorkerLifecycle = (input) => {
|
|
|
11313
11407
|
const notStarted = input.dispatch === true;
|
|
11314
11408
|
if (notStarted && (input.state !== "stopped" || input.session !== null))
|
|
11315
11409
|
return failure("invalid_input", "a never-dispatched worker stops with no session");
|
|
11316
|
-
if (notStarted &&
|
|
11317
|
-
return failure("invalid_transition", "worker is no longer awaiting dispatch");
|
|
11318
|
-
if (notStarted && (entry.data.session !== null || entry.data.state === "running"))
|
|
11410
|
+
if (notStarted && entry.data.session !== null)
|
|
11319
11411
|
return failure("invalid_transition", "worker already has an observed session");
|
|
11412
|
+
if (notStarted && !["dispatching", "cancelling"].includes(entry.data.state))
|
|
11413
|
+
return failure("invalid_transition", "worker launch was not claimed");
|
|
11320
11414
|
if ((input.state === "running" || input.state === "cancelling" || input.state === "stopped" && !notStarted) && !input.session)
|
|
11321
11415
|
return failure("invalid_input", "running worker observations require a session");
|
|
11322
11416
|
if (entry.data.session && !sameValue(entry.data.session, input.session))
|
|
@@ -11703,7 +11797,7 @@ class WorkitCore {
|
|
|
11703
11797
|
activeWorkerBlocker(task, workspace) {
|
|
11704
11798
|
if (workspace.writer)
|
|
11705
11799
|
return failure("recovery_required", "writer ownership must be released first");
|
|
11706
|
-
if (task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(entry.data.state)))
|
|
11800
|
+
if (task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(entry.data.state)))
|
|
11707
11801
|
return failure("recovery_required", "worker state requires reconciliation");
|
|
11708
11802
|
return null;
|
|
11709
11803
|
}
|
|
@@ -12467,7 +12561,12 @@ class WorkitCore {
|
|
|
12467
12561
|
return failure("invalid_transition", "worker is not awaiting dispatch");
|
|
12468
12562
|
return success(mutation.revision, null, {
|
|
12469
12563
|
...current,
|
|
12470
|
-
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
12564
|
+
workers: current.workers.map((item) => item.id === input.workerId ? {
|
|
12565
|
+
...item,
|
|
12566
|
+
recordedAt: mutation.now,
|
|
12567
|
+
provenance: attested.data,
|
|
12568
|
+
data: { ...item.data, state: "dispatching" }
|
|
12569
|
+
} : item)
|
|
12471
12570
|
});
|
|
12472
12571
|
}
|
|
12473
12572
|
});
|
|
@@ -12936,7 +13035,7 @@ class WorkitCore {
|
|
|
12936
13035
|
const base = reconcileResume(effectiveView);
|
|
12937
13036
|
if (!base.ok)
|
|
12938
13037
|
return base;
|
|
12939
|
-
const needsReconciliation = view.task.workers.some((entry) => ["running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
|
|
13038
|
+
const needsReconciliation = view.task.workers.some((entry) => ["dispatching", "running", "cancelling", "unknown"].includes(observedStates.get(entry.id) ?? entry.data.state));
|
|
12940
13039
|
const blockers = [...base.data.blockers];
|
|
12941
13040
|
if (needsReconciliation && !blockers.some((entry) => entry.reason === "worker state requires reconciliation"))
|
|
12942
13041
|
blockers.push({
|
|
@@ -31040,11 +31139,12 @@ var decisionContent = (purpose, question, approvedContent) => ({
|
|
|
31040
31139
|
]
|
|
31041
31140
|
});
|
|
31042
31141
|
var decisionOptions = (options) => Array.isArray(options) && options.length === 2 && options.every((option) => typeof option === "object" && option !== null && typeof option.label === "string" && typeof option.description === "string" && Object.keys(option).length === 2 && Object.keys(option).every((key) => key === "label" || key === "description")) ? options : null;
|
|
31142
|
+
var normalizeReceiptLabel = (label) => label.trim().replace(/(?:\s*[([]\s*[^()[\]]*\s*[)\]]\s*)+$/u, "").trim().toLowerCase();
|
|
31043
31143
|
var purposeForQuestion = (question) => {
|
|
31044
31144
|
const header = typeof question.header === "string" ? question.header.trim() : "";
|
|
31045
31145
|
const options = decisionOptions(question.options);
|
|
31046
31146
|
const decisionPurpose = header.match(/^Workit decision: (design|action|limitation|preference)$/)?.[1];
|
|
31047
|
-
if (decisionPurpose && options !== null && options[0].label === "approved" && options[1].label === "rejected"
|
|
31147
|
+
if (decisionPurpose && options !== null && normalizeReceiptLabel(options[0].label) === "approved" && normalizeReceiptLabel(options[1].label) === "rejected")
|
|
31048
31148
|
return "decision";
|
|
31049
31149
|
return;
|
|
31050
31150
|
};
|
|
@@ -31053,6 +31153,10 @@ class NativeReceiptStore {
|
|
|
31053
31153
|
#receipts = new Map;
|
|
31054
31154
|
#observations = new WeakSet;
|
|
31055
31155
|
#pending = new Map;
|
|
31156
|
+
#now;
|
|
31157
|
+
constructor(options = {}) {
|
|
31158
|
+
this.#now = options.now ?? Date.now;
|
|
31159
|
+
}
|
|
31056
31160
|
recordRequest(requestID, sessionID, callID, questions) {
|
|
31057
31161
|
if (typeof requestID !== "string" || !requestID)
|
|
31058
31162
|
return;
|
|
@@ -31090,7 +31194,7 @@ class NativeReceiptStore {
|
|
|
31090
31194
|
const options = decisionOptions(question.options);
|
|
31091
31195
|
if (!options)
|
|
31092
31196
|
return false;
|
|
31093
|
-
const selected = options
|
|
31197
|
+
const selected = options.find((option) => normalizeReceiptLabel(option.label) === normalizeReceiptLabel(answer));
|
|
31094
31198
|
if (!selected)
|
|
31095
31199
|
return false;
|
|
31096
31200
|
const decisionPurpose = typeof question.header === "string" ? question.header.match(/^Workit decision: (design|action|limitation|preference)$/)?.[1] : undefined;
|
|
@@ -31106,7 +31210,7 @@ class NativeReceiptStore {
|
|
|
31106
31210
|
question: content.question,
|
|
31107
31211
|
purpose,
|
|
31108
31212
|
contentDigest: sha256(canonicalJson(content)),
|
|
31109
|
-
recordedAt:
|
|
31213
|
+
recordedAt: this.#now()
|
|
31110
31214
|
};
|
|
31111
31215
|
const queue = this.#receipts.get(sessionID) ?? [];
|
|
31112
31216
|
queue.push(receipt);
|
|
@@ -31126,15 +31230,12 @@ class NativeReceiptStore {
|
|
|
31126
31230
|
const receipt = queue[i];
|
|
31127
31231
|
if (receipt.purpose !== purpose)
|
|
31128
31232
|
continue;
|
|
31129
|
-
if (expected.callID !== undefined && receipt.callID !== expected.callID || expected.selectedLabel !== undefined && receipt.selectedLabel !== expected.selectedLabel || expected.selectedDescription !== undefined && receipt.selectedDescription !== expected.selectedDescription || expected.decisionPurpose !== undefined && receipt.decisionPurpose !== expected.decisionPurpose || expected.contentDigest !== undefined && receipt.contentDigest !== expected.contentDigest || expected.question !== undefined && receipt.question !== expected.question)
|
|
31130
|
-
|
|
31131
|
-
ok: false,
|
|
31132
|
-
error: "permission_denied: native question receipt does not match the requested content " + "(binding questions must be asked receipt-shaped: header `Workit decision: <purpose>` " + "with the same label in the question text and exactly approved/rejected options)"
|
|
31133
|
-
};
|
|
31233
|
+
if (expected.callID !== undefined && receipt.callID !== expected.callID || expected.selectedLabel !== undefined && normalizeReceiptLabel(receipt.selectedLabel) !== normalizeReceiptLabel(expected.selectedLabel) || expected.selectedDescription !== undefined && receipt.selectedDescription !== expected.selectedDescription || expected.decisionPurpose !== undefined && receipt.decisionPurpose !== expected.decisionPurpose || expected.contentDigest !== undefined && receipt.contentDigest !== expected.contentDigest || expected.question !== undefined && receipt.question !== expected.question)
|
|
31234
|
+
continue;
|
|
31134
31235
|
queue.splice(i, 1);
|
|
31135
31236
|
if (!queue.length)
|
|
31136
31237
|
this.#receipts.delete(sessionID);
|
|
31137
|
-
if (
|
|
31238
|
+
if (this.#now() - receipt.recordedAt > freshMs)
|
|
31138
31239
|
return { ok: false, error: "permission_denied: native question receipt is stale" };
|
|
31139
31240
|
const observation = { receipt };
|
|
31140
31241
|
this.#observations.add(observation);
|
|
@@ -31142,7 +31243,7 @@ class NativeReceiptStore {
|
|
|
31142
31243
|
}
|
|
31143
31244
|
return {
|
|
31144
31245
|
ok: false,
|
|
31145
|
-
error: "permission_denied: no native question receipt for this purpose " + "
|
|
31246
|
+
error: "permission_denied: no matching native question receipt for this purpose; if the user " + "already settled this choice in conversation, do not ask again — record the settled " + "choice in task progress and reassess so the requirement can retire. Otherwise ask " + "once, receipt-shaped: header `Workit decision: <purpose>`, the same label repeated in " + "the question text, with exactly approved/rejected options"
|
|
31146
31247
|
};
|
|
31147
31248
|
}
|
|
31148
31249
|
verify(observation, sessionID, purpose) {
|
|
@@ -31262,7 +31363,7 @@ var opencodeCapabilities = () => [
|
|
|
31262
31363
|
var nativeAuthority = (receipts, actor, reconciliationTokens = new WeakSet) => ({
|
|
31263
31364
|
verifyDecision: ({ observation, expected, caller }) => {
|
|
31264
31365
|
const receipt = receipts.verify(observation, actor, "decision");
|
|
31265
|
-
if (!receipt || caller.host !== "opencode" || caller.actor !== actor || receipt.selectedLabel !== expected.response ||
|
|
31366
|
+
if (!receipt || caller.host !== "opencode" || caller.actor !== actor || normalizeReceiptLabel(receipt.selectedLabel) !== normalizeReceiptLabel(expected.response) || expected.response === "approved" && receipt.selectedDescription !== expected.binding.approvedContent || receipt.decisionPurpose !== expected.purpose || receipt.contentDigest !== sha256(canonicalJson(decisionContent(expected.purpose, expected.binding.presented, expected.binding.approvedContent))) || receipt.question !== expected.binding.presented)
|
|
31266
31367
|
return failure("permission_denied", "native decision receipt is not bound to this session");
|
|
31267
31368
|
return success(null, null, {
|
|
31268
31369
|
kind: "host_observed",
|
|
@@ -31417,13 +31518,15 @@ var createWorkitTools = ({
|
|
|
31417
31518
|
let result;
|
|
31418
31519
|
if (family === "decision" && args.action === "record") {
|
|
31419
31520
|
const decision = parsed.data;
|
|
31420
|
-
const
|
|
31521
|
+
const expectation = {
|
|
31421
31522
|
selectedLabel: decision.response,
|
|
31422
|
-
selectedDescription: decision.response === "approved" ? decision.binding.approvedContent : rejectedDescription,
|
|
31423
31523
|
decisionPurpose: decision.purpose,
|
|
31424
31524
|
contentDigest: sha256(canonicalJson(decisionContent(decision.purpose, decision.binding.presented, decision.binding.approvedContent))),
|
|
31425
31525
|
question: decision.binding.presented
|
|
31426
|
-
}
|
|
31526
|
+
};
|
|
31527
|
+
if (decision.response === "approved")
|
|
31528
|
+
expectation.selectedDescription = decision.binding.approvedContent;
|
|
31529
|
+
const observed = receipts.consume(context.sessionID, "decision", expectation);
|
|
31427
31530
|
if (!observed.ok)
|
|
31428
31531
|
return output2(failure("permission_denied", observed.error));
|
|
31429
31532
|
result = core.observeDecision(parsed.data, observed.observation);
|
|
@@ -31685,7 +31788,6 @@ var plugin = async ({ client, directory }) => {
|
|
|
31685
31788
|
const lifecycleBindings = new Map;
|
|
31686
31789
|
const unresolvedTaskLaunches = new Set;
|
|
31687
31790
|
const dispatches = new Map;
|
|
31688
|
-
const bootstrapped = new Set;
|
|
31689
31791
|
try {
|
|
31690
31792
|
logger.info(EVENT.initialization, { host: "opencode", plugin_root: root });
|
|
31691
31793
|
logger.info(EVENT.provenance, loadProvenance(logger, new URL("../package.json", import.meta.url)));
|
|
@@ -31713,16 +31815,20 @@ var plugin = async ({ client, directory }) => {
|
|
|
31713
31815
|
return [...items].sort((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];
|
|
31714
31816
|
};
|
|
31715
31817
|
const prepareDispatch = (coordinator, callID) => {
|
|
31716
|
-
dispatches.
|
|
31818
|
+
if (dispatches.has(coordinator))
|
|
31819
|
+
return "unsettled";
|
|
31717
31820
|
const store = new TaskStore(directory);
|
|
31718
31821
|
const workspace = store.readWorkspace();
|
|
31719
31822
|
const listed = store.listTasks();
|
|
31720
31823
|
if (!workspace.ok || !workspace.data || !listed.ok)
|
|
31721
|
-
return;
|
|
31722
|
-
const
|
|
31824
|
+
return "unmanaged";
|
|
31825
|
+
const owned = listed.data.filter((task) => task.status === "active" && task.workspaceId === workspace.data?.id && task.intent.provenance.session?.kind === "host" && task.intent.provenance.session.host === "opencode" && task.intent.provenance.session.handle === coordinator);
|
|
31826
|
+
if (owned.length === 0)
|
|
31827
|
+
return "unmanaged";
|
|
31828
|
+
const eligible = owned.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 })));
|
|
31723
31829
|
const next = oldestOfSingleTask(eligible);
|
|
31724
31830
|
if (!next)
|
|
31725
|
-
return;
|
|
31831
|
+
return "unbound";
|
|
31726
31832
|
const generation = {
|
|
31727
31833
|
coordinator,
|
|
31728
31834
|
callID,
|
|
@@ -31745,7 +31851,7 @@ var plugin = async ({ client, directory }) => {
|
|
|
31745
31851
|
observation: { stage: "prepare", sessionID: coordinator, callID }
|
|
31746
31852
|
});
|
|
31747
31853
|
if (!prepared.ok)
|
|
31748
|
-
return;
|
|
31854
|
+
return "unbound";
|
|
31749
31855
|
dispatches.set(coordinator, {
|
|
31750
31856
|
generation,
|
|
31751
31857
|
dispatch: prepared.data,
|
|
@@ -31753,6 +31859,7 @@ var plugin = async ({ client, directory }) => {
|
|
|
31753
31859
|
taskId: next.task.id,
|
|
31754
31860
|
workerId: next.entry.id
|
|
31755
31861
|
});
|
|
31862
|
+
return "prepared";
|
|
31756
31863
|
};
|
|
31757
31864
|
const commitDispatchStart = (coordinator, childID) => {
|
|
31758
31865
|
const pending = dispatches.get(coordinator);
|
|
@@ -31888,7 +31995,7 @@ var plugin = async ({ client, directory }) => {
|
|
|
31888
31995
|
const listed = store.listTasks();
|
|
31889
31996
|
if (!workspace.ok || !workspace.data || !listed.ok)
|
|
31890
31997
|
return;
|
|
31891
|
-
const candidates = listed.data.flatMap((task) => task.status === "active" && task.workspaceId === workspace.data?.id ? 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 === info.parentID).map((entry) => ({ task, entry })) : []);
|
|
31998
|
+
const candidates = listed.data.flatMap((task) => task.status === "active" && task.workspaceId === workspace.data?.id ? task.workers.filter((entry) => (entry.data.state === "assigned" || entry.data.state === "dispatching") && entry.data.session === null && entry.provenance.session?.kind === "host" && entry.provenance.session.host === "opencode" && entry.provenance.session.handle === info.parentID).map((entry) => ({ task, entry })) : []);
|
|
31892
31999
|
const next = oldestOfSingleTask(candidates);
|
|
31893
32000
|
if (!next)
|
|
31894
32001
|
return;
|
|
@@ -31951,8 +32058,15 @@ var plugin = async ({ client, directory }) => {
|
|
|
31951
32058
|
unresolvedTaskLaunches.add(input.sessionID);
|
|
31952
32059
|
}
|
|
31953
32060
|
},
|
|
31954
|
-
"tool.execute.before": async (input,
|
|
32061
|
+
"tool.execute.before": async (input, output) => {
|
|
31955
32062
|
warnStaleSources();
|
|
32063
|
+
if (input.tool === "bash") {
|
|
32064
|
+
const command = output?.args?.command;
|
|
32065
|
+
const route = typeof command === "string" ? shellRouteIntent(command) : null;
|
|
32066
|
+
if (route)
|
|
32067
|
+
throw new Error(`recovery_required: direct branch or PR creation bypasses the Workit route; ${route.guidance}`);
|
|
32068
|
+
return;
|
|
32069
|
+
}
|
|
31956
32070
|
if (input.tool === "task") {
|
|
31957
32071
|
const listed = new TaskStore(directory).listTasks();
|
|
31958
32072
|
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 === input.sessionID)))
|
|
@@ -31962,7 +32076,11 @@ var plugin = async ({ client, directory }) => {
|
|
|
31962
32076
|
const session = await sessionData2(client, input.sessionID);
|
|
31963
32077
|
if (!client || !trustedSession(directory, input.sessionID, session) || session.parentID)
|
|
31964
32078
|
throw new Error("delegation_lineage_denied: native task workers must be direct children of the coordinator");
|
|
31965
|
-
prepareDispatch(input.sessionID, input.callID);
|
|
32079
|
+
const prepared = prepareDispatch(input.sessionID, input.callID);
|
|
32080
|
+
if (prepared === "unsettled")
|
|
32081
|
+
throw new Error("recovery_required: a previous native task launch is unsettled; wait for it to settle or reconcile its worker before launching again");
|
|
32082
|
+
if (prepared === "unbound")
|
|
32083
|
+
throw new Error("recovery_required: no assigned Workit worker is attributable to this coordinator; assign one with workit_worker before launching a native task");
|
|
31966
32084
|
}
|
|
31967
32085
|
},
|
|
31968
32086
|
config: async (config) => {
|
|
@@ -32011,8 +32129,6 @@ var plugin = async ({ client, directory }) => {
|
|
|
32011
32129
|
if (!first || !first.parts.length)
|
|
32012
32130
|
return;
|
|
32013
32131
|
const sessionID = first.info.sessionID;
|
|
32014
|
-
if (bootstrapped.has(sessionID))
|
|
32015
|
-
return;
|
|
32016
32132
|
const anchor = first.parts[0];
|
|
32017
32133
|
const session = await sessionData2(client, sessionID);
|
|
32018
32134
|
if (!trustedSession(directory, sessionID, session))
|
|
@@ -32037,7 +32153,6 @@ var plugin = async ({ client, directory }) => {
|
|
|
32037
32153
|
text: `<workit-worker-context>${workerContext}</workit-worker-context>`
|
|
32038
32154
|
});
|
|
32039
32155
|
}
|
|
32040
|
-
bootstrapped.add(sessionID);
|
|
32041
32156
|
}
|
|
32042
32157
|
};
|
|
32043
32158
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@brainervirus/workit-opencode",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.9",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Workit OpenCode plugin — native tools, adaptive policy, and fourteen method skills",
|
|
6
6
|
"keywords": [
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"typecheck": "tsc --noEmit"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@brainervirus/workit-core": "^1.0.
|
|
41
|
+
"@brainervirus/workit-core": "^1.0.9"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@opencode-ai/plugin": "1.18.30"
|