@brainervirus/workit-opencode 2.2.0 → 2.2.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.
Files changed (2) hide show
  1. package/dist/plugin.js +92 -9
  2. package/package.json +2 -2
package/dist/plugin.js CHANGED
@@ -10038,7 +10038,7 @@ class TaskStore {
10038
10038
  if (!current.data)
10039
10039
  return failure2("not_found", "workspace not found");
10040
10040
  if (current.data.revision !== expected)
10041
- return this.conflict(expected, current.data.revision);
10041
+ return this.workspaceConflict(expected, current.data.revision);
10042
10042
  const previousBytes = this.snapshotBytes(this.workspacePath);
10043
10043
  if (!previousBytes)
10044
10044
  return failure2("storage_error", "workspace snapshot disappeared during mutation");
@@ -10081,7 +10081,7 @@ class TaskStore {
10081
10081
  if (task.data.revision !== expected)
10082
10082
  return this.conflict(expected, task.data.revision);
10083
10083
  if (workspace.data.revision !== input.expectedWorkspaceRevision)
10084
- return this.conflict(input.expectedWorkspaceRevision, workspace.data.revision);
10084
+ return this.workspaceConflict(input.expectedWorkspaceRevision, workspace.data.revision);
10085
10085
  const previousWorkspaceBytes = this.snapshotBytes(this.workspacePath);
10086
10086
  const previousTaskBytes = this.snapshotBytes(this.taskPath(input.taskId));
10087
10087
  if (!previousWorkspaceBytes || !previousTaskBytes)
@@ -10891,6 +10891,9 @@ class TaskStore {
10891
10891
  actualRevision: actual
10892
10892
  });
10893
10893
  }
10894
+ workspaceConflict(expected, actual) {
10895
+ return failure2("revision_conflict", "workspace revision does not match; omit expectedWorkspaceRevision to use the current record", { expectedWorkspaceRevision: expected, actualWorkspaceRevision: actual });
10896
+ }
10894
10897
  taskPath(taskId) {
10895
10898
  return path4.join(this.tasksDir, `${taskId}.json`);
10896
10899
  }
@@ -13312,8 +13315,10 @@ values are still concurrency-checked, so never copy revisions between calls.
13312
13315
  A busy result means another live Workit call holds the checkout lock: retry the
13313
13316
  same call; it is not a recovery condition. A lock left by a dead process is
13314
13317
  reclaimed on the next write, and \`workit doctor --fix-lock\` clears it on demand.
13315
- A revision_conflict on a call that omitted expectedRevision is contention too:
13316
- re-read the record and retry the call.
13318
+ An omitted revision absorbs a concurrent write: Workit re-reads, re-checks
13319
+ policy, and reapplies the call, returning busy under persistent contention. A
13320
+ revision_conflict means a revision you passed is stale: re-read the record
13321
+ before deciding whether to retry.
13317
13322
  A solo edit does not need writer acquisition; use it when concurrent checkout
13318
13323
  writers need coordination. Record only observed facts and checks. Evidence can
13319
13324
  become stale when its bound candidate changes; reconcile findings against the
@@ -14126,6 +14131,29 @@ var applyWorkerLifecycle = (input) => {
14126
14131
  return updated ? success(changed.data.task.revision, changed.data.workspace.revision, updated) : failure2("recovery_required", "worker disappeared during lifecycle observation");
14127
14132
  };
14128
14133
  var refsWithinScope = (refs, scope) => refs.every((ref) => ref.kind !== "file" || bindingCovers(scope, { description: "", paths: [ref.path], exclusions: [] }));
14134
+ var REVISION_RETRY_ATTEMPTS = 8;
14135
+ var retryPause = new Int32Array(new SharedArrayBuffer(4));
14136
+ var filledRevisions = (request) => {
14137
+ const value = typeof request === "object" && request !== null ? request : {};
14138
+ return {
14139
+ task: value.expectedRevision === undefined,
14140
+ workspace: value.expectedWorkspaceRevision === undefined
14141
+ };
14142
+ };
14143
+ var filledConflict = (result, filled) => !result.ok && result.code === "revision_conflict" && (("actualRevision" in result.details) && filled.task || ("actualWorkspaceRevision" in result.details) && filled.workspace);
14144
+ var retryFilledRevisions = (filled, run) => {
14145
+ if (!filled.task && !filled.workspace)
14146
+ return run();
14147
+ let result = run();
14148
+ const deadline = Date.now() + defaultLockTimeout();
14149
+ for (let attempt = 1;filledConflict(result, filled); attempt += 1) {
14150
+ if (attempt >= REVISION_RETRY_ATTEMPTS || Date.now() >= deadline)
14151
+ return failure2("busy", "records kept changing under concurrent writers; retry the call");
14152
+ Atomics.wait(retryPause, 0, 0, Math.floor(Math.random() * 4 * attempt) + 1);
14153
+ result = run();
14154
+ }
14155
+ return result;
14156
+ };
14129
14157
  var trustedNow = (context) => typeof context.now === "function" ? context.now() : context.now;
14130
14158
  var mapRef = (ref, ids) => {
14131
14159
  if (ref.kind !== "record")
@@ -14373,6 +14401,9 @@ class WorkitCore {
14373
14401
  if (input.expectedWorkspaceRevision === undefined)
14374
14402
  input.expectedWorkspaceRevision = workspace ? workspace.revision : null;
14375
14403
  }
14404
+ retryOmittedRevisions(request, run) {
14405
+ return retryFilledRevisions(filledRevisions(request), run);
14406
+ }
14376
14407
  helperEntry(task, requireSession = false, requireActive = false) {
14377
14408
  const workerId = this.context.workerId ?? null;
14378
14409
  if (workerId === null)
@@ -14404,6 +14435,9 @@ class WorkitCore {
14404
14435
  return null;
14405
14436
  }
14406
14437
  state(request) {
14438
+ return request?.action === "import" ? this.retryOmittedRevisions(request, () => this.stateOnce(request)) : this.stateOnce(request);
14439
+ }
14440
+ stateOnce(request) {
14407
14441
  const root = this.contextRootError();
14408
14442
  if (!root.ok)
14409
14443
  return root;
@@ -14505,6 +14539,9 @@ class WorkitCore {
14505
14539
  });
14506
14540
  }
14507
14541
  task(request) {
14542
+ return this.retryOmittedRevisions(request, () => this.taskOnce(request));
14543
+ }
14544
+ taskOnce(request) {
14508
14545
  const root = this.contextRootError();
14509
14546
  if (!root.ok)
14510
14547
  return root;
@@ -14604,6 +14641,9 @@ class WorkitCore {
14604
14641
  }
14605
14642
  }
14606
14643
  policy(request) {
14644
+ return this.retryOmittedRevisions(request, () => this.policyOnce(request));
14645
+ }
14646
+ policyOnce(request) {
14607
14647
  const root = this.contextRootError();
14608
14648
  if (!root.ok)
14609
14649
  return root;
@@ -14650,6 +14690,9 @@ class WorkitCore {
14650
14690
  return success(changed.data.revision, null, changed.data.policy);
14651
14691
  }
14652
14692
  evidence(request) {
14693
+ return this.retryOmittedRevisions(request, () => this.evidenceOnce(request));
14694
+ }
14695
+ evidenceOnce(request) {
14653
14696
  const root = this.contextRootError();
14654
14697
  if (!root.ok)
14655
14698
  return root;
@@ -14734,6 +14777,23 @@ class WorkitCore {
14734
14777
  observeStandingDecision(request) {
14735
14778
  return this.recordDecision(request, undefined, true, true);
14736
14779
  }
14780
+ commitTask(taskId, expected, filled, recheck, update) {
14781
+ let revision = expected;
14782
+ let first = true;
14783
+ return retryFilledRevisions({ task: filled, workspace: false }, () => {
14784
+ if (!first) {
14785
+ const fresh = this.store.readTask(taskId);
14786
+ if (!fresh.ok)
14787
+ return fresh;
14788
+ const checked = recheck(fresh.data);
14789
+ if (!checked.ok)
14790
+ return checked;
14791
+ revision = fresh.data.revision;
14792
+ }
14793
+ first = false;
14794
+ return this.store.mutateTask(taskId, revision, update, trustedNow(this.context));
14795
+ });
14796
+ }
14737
14797
  recordDecision(request, nativeObservation, nativeRequired = false, standing = false) {
14738
14798
  const root = this.contextRootError();
14739
14799
  if (!root.ok)
@@ -14758,6 +14818,7 @@ class WorkitCore {
14758
14818
  return task;
14759
14819
  if ((this.context.workerId ?? null) !== null)
14760
14820
  return failure2("permission_denied", "helpers cannot record or revoke decisions");
14821
+ const filled = input.expectedRevision === undefined;
14761
14822
  this.fillRevisions(input, task.data);
14762
14823
  if (input.action === "record") {
14763
14824
  if (task.data.status === "closed")
@@ -14806,7 +14867,20 @@ class WorkitCore {
14806
14867
  const nativeProvenance = native?.ok ? retireNativeAuthority(native.data) : null;
14807
14868
  if (native?.ok && !nativeProvenance)
14808
14869
  return failure2("permission_denied", "native decision authority was retired");
14809
- const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
14870
+ let standingProvenance = standingApproval?.ok === true ? standingApproval.data : null;
14871
+ const recheck = (fresh) => {
14872
+ if (fresh.status === "closed")
14873
+ return failure2("invalid_transition", "closed task cannot record a decision");
14874
+ if (!standingProvenance)
14875
+ return success(null, null, null);
14876
+ const again = verifyStandingApproval(this.store.root, fresh, this.context.caller, input.binding);
14877
+ if (again.ok)
14878
+ standingProvenance = again.data;
14879
+ return again;
14880
+ };
14881
+ const changed = this.commitTask(task.data.id, input.expectedRevision, filled, recheck, (current, mutation) => {
14882
+ if (current.status === "closed")
14883
+ return failure2("invalid_transition", "closed task cannot record a decision");
14810
14884
  const latestContent = verifyDecisionContent(this.store, input.binding);
14811
14885
  if (!latestContent.ok)
14812
14886
  return latestContent;
@@ -14818,14 +14892,14 @@ class WorkitCore {
14818
14892
  const entry = {
14819
14893
  id: newId(),
14820
14894
  recordedAt: mutation.now,
14821
- provenance: standingApproval?.ok === true ? standingApproval.data : nativeProvenance ?? provenance(this.context, "agent_reported"),
14895
+ provenance: standingProvenance ?? nativeProvenance ?? provenance(this.context, "agent_reported"),
14822
14896
  data
14823
14897
  };
14824
14898
  return success(mutation.revision, null, {
14825
14899
  ...current,
14826
14900
  decisions: [...current.decisions, entry]
14827
14901
  });
14828
- }, trustedNow(this.context));
14902
+ });
14829
14903
  if (!changed.ok)
14830
14904
  return changed;
14831
14905
  return success(changed.data.revision, null, changed.data.decisions.at(-1));
@@ -14839,7 +14913,7 @@ class WorkitCore {
14839
14913
  return failure2("invalid_transition", "decision is already revoked");
14840
14914
  if (decision.data.consumption)
14841
14915
  return failure2("permission_denied", "consumed decision cannot be revoked");
14842
- const changed = this.store.mutateTask(task.data.id, input.expectedRevision, (current, mutation) => {
14916
+ const changed = this.commitTask(task.data.id, input.expectedRevision, filled, () => success(null, null, null), (current, mutation) => {
14843
14917
  const entry = current.decisions.find((candidate) => candidate.id === input.decisionId);
14844
14918
  if (!entry)
14845
14919
  return failure2("not_found", "decision not found");
@@ -14856,13 +14930,16 @@ class WorkitCore {
14856
14930
  ...current,
14857
14931
  decisions: current.decisions.map((candidate) => candidate.id === input.decisionId ? updated : candidate)
14858
14932
  });
14859
- }, trustedNow(this.context));
14933
+ });
14860
14934
  if (!changed.ok)
14861
14935
  return changed;
14862
14936
  const entry = changed.data.decisions.find((candidate) => candidate.id === input.decisionId);
14863
14937
  return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "decision disappeared");
14864
14938
  }
14865
14939
  finding(request) {
14940
+ return this.retryOmittedRevisions(request, () => this.findingOnce(request));
14941
+ }
14942
+ findingOnce(request) {
14866
14943
  const root = this.contextRootError();
14867
14944
  if (!root.ok)
14868
14945
  return root;
@@ -14986,6 +15063,9 @@ class WorkitCore {
14986
15063
  return entry ? success(changed.data.revision, null, entry) : failure2("recovery_required", "finding disappeared");
14987
15064
  }
14988
15065
  worker(request) {
15066
+ return this.retryOmittedRevisions(request, () => this.workerOnce(request));
15067
+ }
15068
+ workerOnce(request) {
14989
15069
  const root = this.contextRootError();
14990
15070
  if (!root.ok)
14991
15071
  return root;
@@ -15333,6 +15413,9 @@ class WorkitCore {
15333
15413
  return applied;
15334
15414
  }
15335
15415
  writer(request) {
15416
+ return this.retryOmittedRevisions(request, () => this.writerOnce(request));
15417
+ }
15418
+ writerOnce(request) {
15336
15419
  const root = this.contextRootError();
15337
15420
  if (!root.ok)
15338
15421
  return root;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "2.2.0",
3
+ "version": "2.2.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": "^2.2.0"
42
+ "@brainervirus/workit-core": "^2.2.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@opencode-ai/plugin": "1.18.34",