@brainervirus/workit-opencode 8.30.0 → 8.31.0

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 +121 -22
  2. package/package.json +2 -2
package/dist/plugin.js CHANGED
@@ -25721,6 +25721,22 @@ var judgeVerdictText = (root, session, workerContext) => {
25721
25721
  });
25722
25722
  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
25723
  };
25724
+ var injectModelSwitch = (root, session, directChildren, system) => {
25725
+ if (!session || !sameWorkspace(root, session.directory))
25726
+ return;
25727
+ const contract = session.parentID ? workerContextFor(root, session.id, session.parentID, directChildren) : getWorkitBootstrap();
25728
+ const taskContext = compactContextFor(root, session.id);
25729
+ const parts = [
25730
+ contract,
25731
+ taskContext && `<workit-task-context>${taskContext}</workit-task-context>`
25732
+ ].filter(Boolean);
25733
+ if (parts.length === 0)
25734
+ return;
25735
+ system.push(textPart(`<workit-model-switch>
25736
+ ${parts.join(`
25737
+ `)}
25738
+ </workit-model-switch>`));
25739
+ };
25724
25740
  var injectHistoryOffer = (root, session, offeredSessions, system) => {
25725
25741
  if (!session || session.parentID !== undefined || !sameWorkspace(root, session.directory) || offeredSessions.has(session.id))
25726
25742
  return;
@@ -25804,6 +25820,60 @@ var childDenial = (identity) => `OpenCode child session has no validated Workit
25804
25820
 
25805
25821
  // packages/workit-opencode/src/v2/shell.ts
25806
25822
  import path44 from "node:path";
25823
+
25824
+ // packages/workit-core/src/hooks/repeat-failure.ts
25825
+ import { createHash as createHash18 } from "node:crypto";
25826
+ var SAME_CALL_FAILED = "Same call failed twice:";
25827
+ var MAX_SESSIONS = 256;
25828
+ 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";
25829
+ var hashOf = (call) => createHash18("sha256").update(call).digest("hex").slice(0, 32);
25830
+ var record3 = (memo, sessionId, call, failed, next) => {
25831
+ const previous = memo.get(sessionId);
25832
+ memo.delete(sessionId);
25833
+ if (!failed)
25834
+ return null;
25835
+ const hash = hashOf(call);
25836
+ memo.set(sessionId, hash);
25837
+ if (memo.size > MAX_SESSIONS)
25838
+ memo.delete(memo.keys().next().value);
25839
+ return previous === hash && next ? `${SAME_CALL_FAILED} ${next}` : null;
25840
+ };
25841
+ var isFailure = (result) => result !== null && typeof result === "object" && result.ok === false && typeof result.error === "string";
25842
+ var correctedPayload = (tool, result) => {
25843
+ const example = /Example: (\{.*\})\s*$/.exec(result.error)?.[1];
25844
+ if (example)
25845
+ return `${tool} ${example}`;
25846
+ const guidance = result.details?.guidance;
25847
+ return typeof guidance === "string" && guidance ? guidance : null;
25848
+ };
25849
+ var escalateToolFailure = (memo, sessionId, tool, input, result) => {
25850
+ const failed = isFailure(result);
25851
+ const line = record3(memo, sessionId, `tool\x00${tool}\x00${stable(input)}`, failed, failed ? correctedPayload(tool, result) : null);
25852
+ return line && failed ? { ...result, error: `${result.error}
25853
+ ${line}` } : result;
25854
+ };
25855
+ var WORKIT_COMMAND = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*workit\s/;
25856
+ var unblockOf = (output) => {
25857
+ const human = /^\s*unblock: (.+)$/m.exec(output)?.[1];
25858
+ if (human)
25859
+ return human.trim();
25860
+ const json = /"unblock":("(?:[^"\\]|\\.)*")/.exec(output)?.[1];
25861
+ if (!json)
25862
+ return null;
25863
+ try {
25864
+ return JSON.parse(json);
25865
+ } catch {
25866
+ return null;
25867
+ }
25868
+ };
25869
+ var repeatedCliFailure = (memo, sessionId, command, output) => {
25870
+ if (!WORKIT_COMMAND.test(command))
25871
+ return null;
25872
+ const failed = /^workit: /m.test(output) || /"ok":\s*false/.test(output);
25873
+ return record3(memo, sessionId, `cli\x00${command.trim()}`, failed, failed ? unblockOf(output) : null);
25874
+ };
25875
+
25876
+ // packages/workit-opencode/src/v2/shell.ts
25807
25877
  var commandOf = (input) => {
25808
25878
  if (input === null || typeof input !== "object")
25809
25879
  return null;
@@ -25855,7 +25925,12 @@ var observeShellBefore = (root, event, workdirs) => {
25855
25925
  noteRawCommit(input, shell.command, callKey(String(event.id), shell.command));
25856
25926
  } catch {}
25857
25927
  };
25858
- var observeShellResult = (root, event) => {
25928
+ var outputText = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.map((part) => typeof part.text === "string" ? part.text : "").join(`
25929
+ `) : "";
25930
+ var appendText = (result, text) => typeof result.content === "string" ? { ...result, content: `${result.content}
25931
+
25932
+ ${text}` } : Array.isArray(result.content) ? { ...result, content: [...result.content, { type: "text", text }] } : result;
25933
+ var observeShellResult = (root, event, failures = new Map) => {
25859
25934
  try {
25860
25935
  if (event.tool !== "shell" || event.status !== "completed")
25861
25936
  return;
@@ -25866,17 +25941,14 @@ var observeShellResult = (root, event) => {
25866
25941
  rawGitPost(input, shell.command, callKey(String(event.id), shell.command));
25867
25942
  const nudge = shellNudge(input, shell.command);
25868
25943
  const result = event.result;
25869
- if (nudge.kind !== "context" || !result)
25944
+ if (!result)
25870
25945
  return;
25871
- if (typeof result.content === "string")
25872
- event.result = { ...result, content: `${result.content}
25946
+ const repeated = repeatedCliFailure(failures, String(event.sessionID), shell.command, outputText(result.content));
25947
+ const extra = [nudge.kind === "context" ? nudge.text : null, repeated].filter((text) => text !== null);
25948
+ if (extra.length > 0)
25949
+ event.result = appendText(result, extra.join(`
25873
25950
 
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
- };
25951
+ `));
25880
25952
  } catch {}
25881
25953
  };
25882
25954
 
@@ -25968,15 +26040,15 @@ var injectSkillNudge = (root, session, messages, system) => {
25968
26040
  };
25969
26041
 
25970
26042
  // packages/workit-opencode/src/v2/stop.ts
25971
- var record3 = (value) => typeof value === "object" && value !== null ? value : {};
26043
+ var record4 = (value) => typeof value === "object" && value !== null ? value : {};
25972
26044
  var lastAssistantText = (messages) => {
25973
26045
  for (const message of messages.toReversed()) {
25974
- if (record3(message).type !== "assistant")
26046
+ if (record4(message).type !== "assistant")
25975
26047
  continue;
25976
- const content = record3(message).content;
26048
+ const content = record4(message).content;
25977
26049
  if (!Array.isArray(content))
25978
26050
  return null;
25979
- const text = content.filter((part) => record3(part).type === "text" && typeof record3(part).text === "string").map((part) => record3(part).text).join(`
26051
+ const text = content.filter((part) => record4(part).type === "text" && typeof record4(part).text === "string").map((part) => record4(part).text).join(`
25980
26052
  `);
25981
26053
  return text || null;
25982
26054
  }
@@ -26147,6 +26219,28 @@ var pluginSourceFiles = [
26147
26219
  ])
26148
26220
  ].filter((file) => existsSync16(file)).toSorted();
26149
26221
 
26222
+ // packages/workit-core/src/hooks/model-switch.ts
26223
+ var MAX_SESSIONS2 = 256;
26224
+ var modelSwitched = (models, sessionId, model) => {
26225
+ if (!model)
26226
+ return false;
26227
+ const previous = models.get(sessionId);
26228
+ models.delete(sessionId);
26229
+ models.set(sessionId, model);
26230
+ if (models.size > MAX_SESSIONS2)
26231
+ models.delete(models.keys().next().value);
26232
+ return previous !== undefined && previous !== model;
26233
+ };
26234
+ var modelKey = (model) => {
26235
+ if (model === null || typeof model !== "object")
26236
+ return null;
26237
+ const { id, providerID, provider } = model;
26238
+ if (typeof id !== "string" || !id)
26239
+ return null;
26240
+ const source = typeof providerID === "string" ? providerID : provider;
26241
+ return typeof source === "string" && source ? `${source}/${id}` : id;
26242
+ };
26243
+
26150
26244
  // packages/workit-opencode/src/v2/plugin.ts
26151
26245
  var sessionFacts = async (ctx, sessionID) => {
26152
26246
  try {
@@ -26185,6 +26279,8 @@ var setup = async (ctx) => {
26185
26279
  const sourceMarker = markSourcesLoaded(pluginSourceFiles);
26186
26280
  let staleSourcesWarned = false;
26187
26281
  const historyOfferSessions = new Set;
26282
+ const sessionModels = new Map;
26283
+ const failureMemo = new Map;
26188
26284
  const lifecycle = createV2Lifecycle({
26189
26285
  root,
26190
26286
  getSession: async (sessionID) => {
@@ -26223,9 +26319,10 @@ var setup = async (ctx) => {
26223
26319
  input: spec.input,
26224
26320
  options: { codemode: false },
26225
26321
  execute: async (input, tool) => {
26322
+ const respond = (value) => resultContent(escalateToolFailure(failureMemo, String(tool.sessionID), spec.name, input, value));
26226
26323
  const session = await sessionFacts(ctx, tool.sessionID);
26227
26324
  if (session === null || !sameWorkspace(root, session.directory))
26228
- return resultContent(failure2("permission_denied", "OpenCode session location does not match the plugin checkout"));
26325
+ return respond(failure2("permission_denied", "OpenCode session location does not match the plugin checkout"));
26229
26326
  if (spec.name === "workit_context")
26230
26327
  return {
26231
26328
  content: await contextTool.execute(input, { directory: root, sessionID: session.id })
@@ -26239,19 +26336,19 @@ var setup = async (ctx) => {
26239
26336
  const directChild = lead !== null && lead.parentID === undefined && sameWorkspace(root, lead.directory);
26240
26337
  const inspecting = childMayInspect(family, input);
26241
26338
  if (!directChild || family === null || !(inspecting || childMayRecord(family, input)))
26242
- return resultContent(failure2("permission_denied", childDenial(identity)));
26339
+ return respond(failure2("permission_denied", childDenial(identity)));
26243
26340
  if (inspecting)
26244
- return resultContent(runFamily(family, input, { ...session, id: identity }, store, null, true));
26341
+ return respond(runFamily(family, input, { ...session, id: identity }, store, null, true));
26245
26342
  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));
26343
+ 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"));
26344
+ return respond(runFamily(family, input, { ...session, id: identity }, store, null));
26248
26345
  }
26249
26346
  if (family !== null)
26250
- return resultContent(runFamily(family, input, session, store, workerId));
26347
+ return respond(runFamily(family, input, session, store, workerId));
26251
26348
  if (spec.name === "workit_init_apply") {
26252
26349
  return { content: executeInitApply(input, root, initApplyRuntime) };
26253
26350
  }
26254
- return resultContent(failure2("not_found", `unknown Workit tool ${spec.name}`));
26351
+ return respond(failure2("not_found", `unknown Workit tool ${spec.name}`));
26255
26352
  }
26256
26353
  });
26257
26354
  }
@@ -26286,7 +26383,7 @@ var setup = async (ctx) => {
26286
26383
  await ctx.tool.hook("execute.after", async (event) => {
26287
26384
  if (event.tool === "shell") {
26288
26385
  settleShellCall(pendingShells, { sessionID: String(event.sessionID), input: event.input });
26289
- return observeShellResult(root, event);
26386
+ return observeShellResult(root, event, failureMemo);
26290
26387
  }
26291
26388
  if (event.tool !== "subagent")
26292
26389
  return;
@@ -26307,6 +26404,8 @@ var setup = async (ctx) => {
26307
26404
  injectHistoryOffer(root, session, historyOfferSessions, event.system);
26308
26405
  if (session)
26309
26406
  injectSkillNudge(root, session, event.messages, event.system);
26407
+ if (modelSwitched(sessionModels, String(event.sessionID), modelKey(event.model)))
26408
+ injectModelSwitch(root, session, lifecycle.directChildren, event.system);
26310
26409
  if (!staleSourcesWarned) {
26311
26410
  const changed = changedSourcesSinceLoad(sourceMarker);
26312
26411
  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.0",
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.0",
43
43
  "@opencode/plugin": "2.0.18"
44
44
  },
45
45
  "engines": {