@brainervirus/workit-opencode 8.30.0 → 8.31.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 +139 -27
  2. package/package.json +2 -2
package/dist/plugin.js CHANGED
@@ -14149,6 +14149,18 @@ function diffHash(cwd, base, head) {
14149
14149
  }
14150
14150
  return `sha256:${hash.digest("hex")}`;
14151
14151
  }
14152
+ function cleanRebase(cwd, range) {
14153
+ if (!Object.values(range).every(safeRef2))
14154
+ return false;
14155
+ const fromFork = git4(cwd, ["merge-base", range.fromBase, range.from]);
14156
+ const toFork = git4(cwd, ["merge-base", range.toBase, range.to]);
14157
+ if (!fromFork || !toFork || fromFork === toFork)
14158
+ return false;
14159
+ const merged = gitRun2(cwd, ["merge-tree", "--write-tree", "--no-messages", `--merge-base=${fromFork}`, toFork, range.from], GIT_TIMEOUTS.worktree);
14160
+ const tree = merged.ok ? merged.stdout.split(`
14161
+ `)[0]?.trim() : null;
14162
+ return Boolean(tree) && tree === git4(cwd, ["rev-parse", "--verify", "-q", `${range.to}^{tree}`]);
14163
+ }
14152
14164
  function storeRoot(cwd) {
14153
14165
  const location = resolveStore(path16.resolve(cwd));
14154
14166
  if (location instanceof Error)
@@ -14812,23 +14824,24 @@ function checkVerdicts(cwd, branch, ledgerRows) {
14812
14824
  return drift;
14813
14825
  };
14814
14826
  const exactCache = new Map;
14815
- const exactCarry = (row) => {
14816
- if (FAILING.has(String(row.result)))
14817
- return true;
14827
+ const exactMatch = (row) => {
14818
14828
  let exact = exactCache.get(row);
14819
14829
  if (exact === undefined) {
14820
14830
  const toBase = row.base ?? fallbackBase;
14821
14831
  const toBaseSha = toBase ? resolveCommit(cwd, toBase) : null;
14822
14832
  const fromBase = row.baseSha ?? toBaseSha;
14823
- exact = Boolean(row.head && head && toBaseSha && fromBase && sameOwnChange(cwd, { from: row.head, to: head, fromBase, toBase: toBaseSha }));
14833
+ const range = row.head && head && toBaseSha && fromBase ? { from: row.head, to: head, fromBase, toBase: toBaseSha } : null;
14834
+ exact = !range ? null : sameOwnChange(cwd, range) ? "same" : cleanRebase(cwd, range) ? "rebase" : null;
14824
14835
  exactCache.set(row, exact);
14825
14836
  }
14826
14837
  return exact;
14827
14838
  };
14839
+ const exactCarry = (row) => FAILING.has(String(row.result)) || exactMatch(row) !== null;
14828
14840
  const keyBasis = (row) => {
14829
14841
  const basis = verdictBasis(row, { head, ...keyFor(row.base ?? fallbackBase) });
14830
14842
  return basis === "carried" && !exactCarry(row) ? "stale" : basis;
14831
14843
  };
14844
+ const rebaseNote = (row) => row.head && head && row.head !== head && !FAILING.has(String(row.result)) && keyBasis(row) === "carried" && exactMatch(row) === "rebase" ? { rule: "rebase", since: row.head, files: [] } : null;
14832
14845
  const restacked = (row) => restackCarries(FAILING.has(String(row.result)) ? null : cwd, rows, branch, row.head, head);
14833
14846
  const carryOf = (row) => {
14834
14847
  if (keyBasis(row) !== "stale")
@@ -14906,7 +14919,7 @@ function checkVerdicts(cwd, branch, ledgerRows) {
14906
14919
  independent: independence.length === 0,
14907
14920
  accepted: reasons.length === 0,
14908
14921
  reasons,
14909
- carry: basis === "carried" ? carryOf(row) : null,
14922
+ carry: basis === "carried" ? rebaseNote(row) ?? carryOf(row) : null,
14910
14923
  verdict: row
14911
14924
  });
14912
14925
  if (reasons.length === 0) {
@@ -25721,6 +25734,22 @@ var judgeVerdictText = (root, session, workerContext) => {
25721
25734
  });
25722
25735
  return `If this session verifies or reviews a candidate, record its verdict with exactly this command (pick the result and kind, live when you drove the real surface, name the candidate branch if it differs, say what you ran): \`${line}\`. Your shell already acts as your own Workit session ${childSessionId(session.parentID ?? "", session.id)}: pass no --session or --token, and never set WORKIT_SESSION_ID by hand.`;
25723
25736
  };
25737
+ var injectModelSwitch = (root, session, directChildren, system) => {
25738
+ if (!session || !sameWorkspace(root, session.directory))
25739
+ return;
25740
+ const contract = session.parentID ? workerContextFor(root, session.id, session.parentID, directChildren) : getWorkitBootstrap();
25741
+ const taskContext = compactContextFor(root, session.id);
25742
+ const parts = [
25743
+ contract,
25744
+ taskContext && `<workit-task-context>${taskContext}</workit-task-context>`
25745
+ ].filter(Boolean);
25746
+ if (parts.length === 0)
25747
+ return;
25748
+ system.push(textPart(`<workit-model-switch>
25749
+ ${parts.join(`
25750
+ `)}
25751
+ </workit-model-switch>`));
25752
+ };
25724
25753
  var injectHistoryOffer = (root, session, offeredSessions, system) => {
25725
25754
  if (!session || session.parentID !== undefined || !sameWorkspace(root, session.directory) || offeredSessions.has(session.id))
25726
25755
  return;
@@ -25804,6 +25833,60 @@ var childDenial = (identity) => `OpenCode child session has no validated Workit
25804
25833
 
25805
25834
  // packages/workit-opencode/src/v2/shell.ts
25806
25835
  import path44 from "node:path";
25836
+
25837
+ // packages/workit-core/src/hooks/repeat-failure.ts
25838
+ import { createHash as createHash18 } from "node:crypto";
25839
+ var SAME_CALL_FAILED = "Same call failed twice:";
25840
+ var MAX_SESSIONS = 256;
25841
+ var stable = (value) => JSON.stringify(value, (_key, entry) => entry && typeof entry === "object" && !Array.isArray(entry) ? Object.fromEntries(Object.entries(entry).toSorted(([a], [b]) => a.localeCompare(b))) : entry) ?? "null";
25842
+ var hashOf = (call) => createHash18("sha256").update(call).digest("hex").slice(0, 32);
25843
+ var record3 = (memo, sessionId, call, failed, next) => {
25844
+ const previous = memo.get(sessionId);
25845
+ memo.delete(sessionId);
25846
+ if (!failed)
25847
+ return null;
25848
+ const hash = hashOf(call);
25849
+ memo.set(sessionId, hash);
25850
+ if (memo.size > MAX_SESSIONS)
25851
+ memo.delete(memo.keys().next().value);
25852
+ return previous === hash && next ? `${SAME_CALL_FAILED} ${next}` : null;
25853
+ };
25854
+ var isFailure = (result) => result !== null && typeof result === "object" && result.ok === false && typeof result.error === "string";
25855
+ var correctedPayload = (tool, result) => {
25856
+ const example = /Example: (\{.*\})\s*$/.exec(result.error)?.[1];
25857
+ if (example)
25858
+ return `${tool} ${example}`;
25859
+ const guidance = result.details?.guidance;
25860
+ return typeof guidance === "string" && guidance ? guidance : null;
25861
+ };
25862
+ var escalateToolFailure = (memo, sessionId, tool, input, result) => {
25863
+ const failed = isFailure(result);
25864
+ const line = record3(memo, sessionId, `tool\x00${tool}\x00${stable(input)}`, failed, failed ? correctedPayload(tool, result) : null);
25865
+ return line && failed ? { ...result, error: `${result.error}
25866
+ ${line}` } : result;
25867
+ };
25868
+ var WORKIT_COMMAND = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*workit\s/;
25869
+ var unblockOf = (output) => {
25870
+ const human = /^\s*unblock: (.+)$/m.exec(output)?.[1];
25871
+ if (human)
25872
+ return human.trim();
25873
+ const json = /"unblock":("(?:[^"\\]|\\.)*")/.exec(output)?.[1];
25874
+ if (!json)
25875
+ return null;
25876
+ try {
25877
+ return JSON.parse(json);
25878
+ } catch {
25879
+ return null;
25880
+ }
25881
+ };
25882
+ var repeatedCliFailure = (memo, sessionId, command, output) => {
25883
+ if (!WORKIT_COMMAND.test(command))
25884
+ return null;
25885
+ const failed = /^workit: /m.test(output) || /"ok":\s*false/.test(output);
25886
+ return record3(memo, sessionId, `cli\x00${command.trim()}`, failed, failed ? unblockOf(output) : null);
25887
+ };
25888
+
25889
+ // packages/workit-opencode/src/v2/shell.ts
25807
25890
  var commandOf = (input) => {
25808
25891
  if (input === null || typeof input !== "object")
25809
25892
  return null;
@@ -25855,7 +25938,12 @@ var observeShellBefore = (root, event, workdirs) => {
25855
25938
  noteRawCommit(input, shell.command, callKey(String(event.id), shell.command));
25856
25939
  } catch {}
25857
25940
  };
25858
- var observeShellResult = (root, event) => {
25941
+ var outputText = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part.text === "string" ? part.text : "").join(`
25942
+ `) : "";
25943
+ var appendText = (result, text) => typeof result.content === "string" ? { ...result, content: `${result.content}
25944
+
25945
+ ${text}` } : Array.isArray(result.content) ? { ...result, content: [...result.content, { type: "text", text }] } : result;
25946
+ var observeShellResult = (root, event, failures = new Map) => {
25859
25947
  try {
25860
25948
  if (event.tool !== "shell" || event.status !== "completed")
25861
25949
  return;
@@ -25866,17 +25954,14 @@ var observeShellResult = (root, event) => {
25866
25954
  rawGitPost(input, shell.command, callKey(String(event.id), shell.command));
25867
25955
  const nudge = shellNudge(input, shell.command);
25868
25956
  const result = event.result;
25869
- if (nudge.kind !== "context" || !result)
25957
+ if (!result)
25870
25958
  return;
25871
- if (typeof result.content === "string")
25872
- event.result = { ...result, content: `${result.content}
25959
+ const repeated = repeatedCliFailure(failures, String(event.sessionID), shell.command, outputText(result.content));
25960
+ const extra = [nudge.kind === "context" ? nudge.text : null, repeated].filter((text) => text !== null);
25961
+ if (extra.length > 0)
25962
+ event.result = appendText(result, extra.join(`
25873
25963
 
25874
- ${nudge.text}` };
25875
- else if (Array.isArray(result.content))
25876
- event.result = {
25877
- ...result,
25878
- content: [...result.content, { type: "text", text: nudge.text }]
25879
- };
25964
+ `));
25880
25965
  } catch {}
25881
25966
  };
25882
25967
 
@@ -25968,15 +26053,15 @@ var injectSkillNudge = (root, session, messages, system) => {
25968
26053
  };
25969
26054
 
25970
26055
  // packages/workit-opencode/src/v2/stop.ts
25971
- var record3 = (value) => typeof value === "object" && value !== null ? value : {};
26056
+ var record4 = (value) => typeof value === "object" && value !== null ? value : {};
25972
26057
  var lastAssistantText = (messages) => {
25973
26058
  for (const message of messages.toReversed()) {
25974
- if (record3(message).type !== "assistant")
26059
+ if (record4(message).type !== "assistant")
25975
26060
  continue;
25976
- const content = record3(message).content;
26061
+ const content = record4(message).content;
25977
26062
  if (!Array.isArray(content))
25978
26063
  return null;
25979
- const text = content.filter((part) => record3(part).type === "text" && typeof record3(part).text === "string").map((part) => record3(part).text).join(`
26064
+ const text = content.filter((part) => record4(part).type === "text" && typeof record4(part).text === "string").map((part) => record4(part).text).join(`
25980
26065
  `);
25981
26066
  return text || null;
25982
26067
  }
@@ -26147,6 +26232,28 @@ var pluginSourceFiles = [
26147
26232
  ])
26148
26233
  ].filter((file) => existsSync16(file)).toSorted();
26149
26234
 
26235
+ // packages/workit-core/src/hooks/model-switch.ts
26236
+ var MAX_SESSIONS2 = 256;
26237
+ var modelSwitched = (models, sessionId, model) => {
26238
+ if (!model)
26239
+ return false;
26240
+ const previous = models.get(sessionId);
26241
+ models.delete(sessionId);
26242
+ models.set(sessionId, model);
26243
+ if (models.size > MAX_SESSIONS2)
26244
+ models.delete(models.keys().next().value);
26245
+ return previous !== undefined && previous !== model;
26246
+ };
26247
+ var modelKey = (model) => {
26248
+ if (model === null || typeof model !== "object")
26249
+ return null;
26250
+ const { id, providerID, provider } = model;
26251
+ if (typeof id !== "string" || !id)
26252
+ return null;
26253
+ const source = typeof providerID === "string" ? providerID : provider;
26254
+ return typeof source === "string" && source ? `${source}/${id}` : id;
26255
+ };
26256
+
26150
26257
  // packages/workit-opencode/src/v2/plugin.ts
26151
26258
  var sessionFacts = async (ctx, sessionID) => {
26152
26259
  try {
@@ -26185,6 +26292,8 @@ var setup = async (ctx) => {
26185
26292
  const sourceMarker = markSourcesLoaded(pluginSourceFiles);
26186
26293
  let staleSourcesWarned = false;
26187
26294
  const historyOfferSessions = new Set;
26295
+ const sessionModels = new Map;
26296
+ const failureMemo = new Map;
26188
26297
  const lifecycle = createV2Lifecycle({
26189
26298
  root,
26190
26299
  getSession: async (sessionID) => {
@@ -26223,9 +26332,10 @@ var setup = async (ctx) => {
26223
26332
  input: spec.input,
26224
26333
  options: { codemode: false },
26225
26334
  execute: async (input, tool) => {
26335
+ const respond = (value) => resultContent(escalateToolFailure(failureMemo, String(tool.sessionID), spec.name, input, value));
26226
26336
  const session = await sessionFacts(ctx, tool.sessionID);
26227
26337
  if (session === null || !sameWorkspace(root, session.directory))
26228
- return resultContent(failure2("permission_denied", "OpenCode session location does not match the plugin checkout"));
26338
+ return respond(failure2("permission_denied", "OpenCode session location does not match the plugin checkout"));
26229
26339
  if (spec.name === "workit_context")
26230
26340
  return {
26231
26341
  content: await contextTool.execute(input, { directory: root, sessionID: session.id })
@@ -26239,19 +26349,19 @@ var setup = async (ctx) => {
26239
26349
  const directChild = lead !== null && lead.parentID === undefined && sameWorkspace(root, lead.directory);
26240
26350
  const inspecting = childMayInspect(family, input);
26241
26351
  if (!directChild || family === null || !(inspecting || childMayRecord(family, input)))
26242
- return resultContent(failure2("permission_denied", childDenial(identity)));
26352
+ return respond(failure2("permission_denied", childDenial(identity)));
26243
26353
  if (inspecting)
26244
- return resultContent(runFamily(family, input, { ...session, id: identity }, store, null, true));
26354
+ return respond(runFamily(family, input, { ...session, id: identity }, store, null, true));
26245
26355
  if (childTaskMissing(store, input))
26246
- 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"));
26247
- return resultContent(runFamily(family, input, { ...session, id: identity }, store, null));
26356
+ return respond(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"));
26357
+ return respond(runFamily(family, input, { ...session, id: identity }, store, null));
26248
26358
  }
26249
26359
  if (family !== null)
26250
- return resultContent(runFamily(family, input, session, store, workerId));
26360
+ return respond(runFamily(family, input, session, store, workerId));
26251
26361
  if (spec.name === "workit_init_apply") {
26252
26362
  return { content: executeInitApply(input, root, initApplyRuntime) };
26253
26363
  }
26254
- return resultContent(failure2("not_found", `unknown Workit tool ${spec.name}`));
26364
+ return respond(failure2("not_found", `unknown Workit tool ${spec.name}`));
26255
26365
  }
26256
26366
  });
26257
26367
  }
@@ -26286,7 +26396,7 @@ var setup = async (ctx) => {
26286
26396
  await ctx.tool.hook("execute.after", async (event) => {
26287
26397
  if (event.tool === "shell") {
26288
26398
  settleShellCall(pendingShells, { sessionID: String(event.sessionID), input: event.input });
26289
- return observeShellResult(root, event);
26399
+ return observeShellResult(root, event, failureMemo);
26290
26400
  }
26291
26401
  if (event.tool !== "subagent")
26292
26402
  return;
@@ -26307,6 +26417,8 @@ var setup = async (ctx) => {
26307
26417
  injectHistoryOffer(root, session, historyOfferSessions, event.system);
26308
26418
  if (session)
26309
26419
  injectSkillNudge(root, session, event.messages, event.system);
26420
+ if (modelSwitched(sessionModels, String(event.sessionID), modelKey(event.model)))
26421
+ injectModelSwitch(root, session, lifecycle.directChildren, event.system);
26310
26422
  if (!staleSourcesWarned) {
26311
26423
  const changed = changedSourcesSinceLoad(sourceMarker);
26312
26424
  if (changed.length > 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-opencode",
3
- "version": "8.30.0",
3
+ "version": "8.31.1",
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
  "devDependencies": {
42
- "@brainervirus/workit-core": "^8.30.0",
42
+ "@brainervirus/workit-core": "^8.31.1",
43
43
  "@opencode/plugin": "2.0.18"
44
44
  },
45
45
  "engines": {