@brainervirus/workit-pi 8.32.0 → 8.33.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/workit.js +78 -29
  2. package/package.json +1 -1
package/dist/workit.js CHANGED
@@ -7410,10 +7410,11 @@ function invalidOperation(family, input, error) {
7410
7410
  const request = isObject2(input) ? input : {};
7411
7411
  const action = typeof request.action === "string" ? request.action : "";
7412
7412
  const actions = OPERATION_ACTIONS[family];
7413
- if (!actions.includes(action))
7414
- return failure2("invalid_input", `${family}: action must be one of ${oneOf(actions)}`, {
7415
- fields: [{ path: "action", reason: `got ${JSON.stringify(request.action ?? null)}` }]
7416
- });
7413
+ if (!actions.includes(action)) {
7414
+ const common = actions.find((name) => READ_ONLY_ACTIONS.has(name)) ?? actions[0];
7415
+ const example = JSON.stringify(exampleOperation(family, common));
7416
+ return failure2("invalid_input", `${family}: action must be one of ${oneOf(actions)}. Example: ${example}`, { fields: [{ path: "action", reason: `got ${JSON.stringify(request.action ?? null)}` }] });
7417
+ }
7417
7418
  const own = actionKeys(family, action);
7418
7419
  const fields = [];
7419
7420
  const sentence = [];
@@ -16205,7 +16206,14 @@ var sessionMarkerFile = (input, session, store) => {
16205
16206
  return path19.join(dir, "hooks", `skills-${name}.json`);
16206
16207
  };
16207
16208
  var list2 = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
16208
- var EMPTY = { nudged: [], loaded: [], raw: [], stops: [], judges: [] };
16209
+ var EMPTY = {
16210
+ nudged: [],
16211
+ loaded: [],
16212
+ raw: [],
16213
+ stops: [],
16214
+ judges: [],
16215
+ model: null
16216
+ };
16209
16217
  var readRaw = (file) => {
16210
16218
  try {
16211
16219
  return fs15.readFileSync(file, "utf8");
@@ -16221,7 +16229,8 @@ var parseMarker = (text) => {
16221
16229
  loaded: list2(value.loaded),
16222
16230
  raw: list2(value.raw),
16223
16231
  stops: list2(value.stops),
16224
- judges: list2(value.judges)
16232
+ judges: list2(value.judges),
16233
+ model: typeof value.model === "string" ? value.model : null
16225
16234
  };
16226
16235
  } catch {
16227
16236
  return { ...EMPTY };
@@ -16266,6 +16275,18 @@ var trackJudge = (input, agentId, running) => {
16266
16275
  return { ...marker, judges: running ? [...rest, agentId] : rest };
16267
16276
  });
16268
16277
  };
16278
+ var modelSwitchedOnDisk = (input) => {
16279
+ const session = input.session.id;
16280
+ const model = input.model;
16281
+ const file = session && model ? sessionMarkerFile(input, session) : null;
16282
+ if (!file || !model)
16283
+ return false;
16284
+ const previous = readSessionMarker(file).model;
16285
+ if (previous === model)
16286
+ return false;
16287
+ updateSessionMarker(file, (marker) => ({ ...marker, model }));
16288
+ return previous !== null;
16289
+ };
16269
16290
 
16270
16291
  // packages/workit-core/src/hooks/shell-words.ts
16271
16292
  class ShellAnalysisError extends Error {
@@ -24561,7 +24582,9 @@ function decide(input, deps) {
24561
24582
  if (!usable2(descriptor.context.perTurn))
24562
24583
  return NONE6;
24563
24584
  const current = turnContextText(input, descriptor);
24564
- const text = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
24585
+ const turn = resendsOnChange(descriptor) ? changedTurnContext(input, current) : current;
24586
+ const text = input.model && modelSwitchedOnDisk(input) ? [invariantBootstrap(), turn].filter(Boolean).join(`
24587
+ `) : turn;
24565
24588
  return withContextLine(text ? { kind: "context", text } : NONE6, promptNudge(input, event.prompt));
24566
24589
  }
24567
24590
  case "shell.pre": {
@@ -24767,17 +24790,6 @@ var PI_DESCRIPTOR = {
24767
24790
  ]
24768
24791
  };
24769
24792
  // packages/workit-core/src/hooks/model-switch.ts
24770
- var MAX_SESSIONS = 256;
24771
- var modelSwitched = (models, sessionId, model) => {
24772
- if (!model)
24773
- return false;
24774
- const previous = models.get(sessionId);
24775
- models.delete(sessionId);
24776
- models.set(sessionId, model);
24777
- if (models.size > MAX_SESSIONS)
24778
- models.delete(models.keys().next().value);
24779
- return previous !== undefined && previous !== model;
24780
- };
24781
24793
  var modelKey = (model) => {
24782
24794
  if (model === null || typeof model !== "object")
24783
24795
  return null;
@@ -24813,13 +24825,15 @@ var taskTurnContext = (ctx) => {
24813
24825
  return null;
24814
24826
  }
24815
24827
  };
24816
- var sessionModels = new Map;
24817
- var turnContext = (ctx) => {
24818
- const task = taskTurnContext(ctx);
24819
- const switched = modelSwitched(sessionModels, ctx.sessionManager.getSessionId(), modelKey(ctx.model));
24820
- return switched && ctx.isProjectTrusted() ? `${invariantBootstrap()}${task ? `
24821
- ${task}` : ""}` : task;
24828
+ var switchedSessions = new Set;
24829
+ var noteModelSelect = (event, ctx) => {
24830
+ const next = modelKey(event.model);
24831
+ const previous = modelKey(event.previousModel);
24832
+ if (next && previous && next !== previous)
24833
+ switchedSessions.add(ctx.sessionManager.getSessionId());
24822
24834
  };
24835
+ var modelSwitchContract = (ctx) => switchedSessions.delete(ctx.sessionManager.getSessionId()) && ctx.isProjectTrusted() ? invariantBootstrap() : null;
24836
+ var turnContext = (ctx) => taskTurnContext(ctx);
24823
24837
  var turnInput = (ctx) => ({
24824
24838
  host: "pi",
24825
24839
  cwd: ctx.cwd,
@@ -24866,7 +24880,7 @@ var unfinishedTaskOffer2 = (ctx) => {
24866
24880
  // packages/workit-core/src/hooks/repeat-failure.ts
24867
24881
  import { createHash as createHash18 } from "node:crypto";
24868
24882
  var SAME_CALL_FAILED = "Same call failed twice:";
24869
- var MAX_SESSIONS2 = 256;
24883
+ var MAX_SESSIONS = 256;
24870
24884
  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";
24871
24885
  var hashOf = (call) => createHash18("sha256").update(call).digest("hex").slice(0, 32);
24872
24886
  var record2 = (memo, sessionId, call, failed, next) => {
@@ -24876,7 +24890,7 @@ var record2 = (memo, sessionId, call, failed, next) => {
24876
24890
  return null;
24877
24891
  const hash = hashOf(call);
24878
24892
  memo.set(sessionId, hash);
24879
- if (memo.size > MAX_SESSIONS2)
24893
+ if (memo.size > MAX_SESSIONS)
24880
24894
  memo.delete(memo.keys().next().value);
24881
24895
  return previous === hash && next ? `${SAME_CALL_FAILED} ${next}` : null;
24882
24896
  };
@@ -24894,6 +24908,26 @@ var escalateToolFailure = (memo, sessionId, tool, input, result) => {
24894
24908
  return line && failed ? { ...result, error: `${result.error}
24895
24909
  ${line}` } : result;
24896
24910
  };
24911
+ var WORKIT_COMMAND = /^\s*(?:[A-Za-z_][A-Za-z0-9_]*=\S*\s+)*workit\s/;
24912
+ var unblockOf = (output) => {
24913
+ const human = /^\s*unblock: (.+)$/m.exec(output)?.[1];
24914
+ if (human)
24915
+ return human.trim();
24916
+ const json = /"unblock":("(?:[^"\\]|\\.)*")/.exec(output)?.[1];
24917
+ if (!json)
24918
+ return null;
24919
+ try {
24920
+ return JSON.parse(json);
24921
+ } catch {
24922
+ return null;
24923
+ }
24924
+ };
24925
+ var repeatedCliFailure = (memo, sessionId, command, output) => {
24926
+ if (!WORKIT_COMMAND.test(command))
24927
+ return null;
24928
+ const failed = /^workit: /m.test(output) || /"ok":\s*false/.test(output);
24929
+ return record2(memo, sessionId, `cli\x00${command.trim()}`, failed, failed ? unblockOf(output) : null);
24930
+ };
24897
24931
 
24898
24932
  // packages/workit-pi/src/tools.ts
24899
24933
  var readOnlyActions = new Set(["list", "inspect", "preview", "explain", "export"]);
@@ -25013,11 +25047,17 @@ var enforceToolPolicy = (event, ctx) => {
25013
25047
  };
25014
25048
  var observeToolResult = (event, ctx) => {
25015
25049
  try {
25016
- if (event.toolName !== "bash" || event.isError)
25050
+ if (event.toolName !== "bash")
25017
25051
  return;
25018
25052
  const command = event.input?.command;
25019
25053
  if (typeof command !== "string")
25020
25054
  return;
25055
+ const shown = event.content.map((part) => part.type === "text" ? part.text : "").join(`
25056
+ `);
25057
+ const session = piContext(ctx).caller.actor;
25058
+ const repeated = repeatedCliFailure(failures, session, command, shown);
25059
+ if (event.isError)
25060
+ return repeated ? { content: [...event.content, { type: "text", text: repeated }] } : undefined;
25021
25061
  const post = hookInput(ctx, {
25022
25062
  kind: "shell.post",
25023
25063
  command,
@@ -25029,7 +25069,10 @@ var observeToolResult = (event, ctx) => {
25029
25069
  const named = process.env.WORKIT_PI_WORKER_ROLE;
25030
25070
  const role = named === "implementer" ? "implementer" : named ? "judge" : null;
25031
25071
  const nudge = process.env.WORKIT_PI_WORKER_SESSION ? rawGitPre(post, command, { nudge: roleSteering(role, false).nudges }) : shellNudge(post, command);
25032
- return nudge.kind === "context" ? { content: [...event.content, { type: "text", text: nudge.text }] } : undefined;
25072
+ const extra = [nudge.kind === "context" ? nudge.text : null, repeated].filter((text) => text !== null);
25073
+ return extra.length > 0 ? { content: [...event.content, { type: "text", text: extra.join(`
25074
+
25075
+ `) }] } : undefined;
25033
25076
  } catch {
25034
25077
  return;
25035
25078
  }
@@ -25953,6 +25996,7 @@ function extension(pi) {
25953
25996
  sessions.delete(ctx.sessionManager.getSessionId());
25954
25997
  reconcileLostWorkers(ctx);
25955
25998
  });
25999
+ pi.on("model_select", (event, ctx) => noteModelSelect(event, ctx));
25956
26000
  pi.on("before_agent_start", (event, ctx) => {
25957
26001
  if (event.messageType !== "user")
25958
26002
  return;
@@ -25961,7 +26005,8 @@ function extension(pi) {
25961
26005
  const trusted = ctx.isProjectTrusted();
25962
26006
  const task = turnContext(ctx);
25963
26007
  let content;
25964
- if (sessions.has(id)) {
26008
+ const firstTurn = !sessions.has(id);
26009
+ if (!firstTurn) {
25965
26010
  content = trusted ? changedTurnContext(turnInput(ctx), task) : null;
25966
26011
  } else {
25967
26012
  sessions.add(id);
@@ -25973,6 +26018,10 @@ function extension(pi) {
25973
26018
 
25974
26019
  ${offer}` : ""}`;
25975
26020
  }
26021
+ const contract = modelSwitchContract(ctx);
26022
+ if (contract && !firstTurn)
26023
+ content = content ? `${contract}
26024
+ ${content}` : contract;
25976
26025
  if (content)
25977
26026
  return { message: contextMessage(ctx, nudge ? `${content}
25978
26027
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-pi",
3
- "version": "8.32.0",
3
+ "version": "8.33.0",
4
4
  "description": "Workit native extension for Pi — tools, method skills, and session continuity",
5
5
  "keywords": [
6
6
  "agentic-coding",