@brainervirus/workit-pi 8.36.0 → 8.37.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.
package/dist/worker.js CHANGED
@@ -8209,21 +8209,60 @@ var uniformImplementerModel = (cwd, host, dir) => {
8209
8209
  };
8210
8210
  var ROLE_SELECTORS = MODEL_ROLES.flatMap((role) => role === "implementer" ? MODEL_TIERS.map((tier) => ({ role, tier })) : [{ role }]);
8211
8211
 
8212
- // packages/workit-core/src/hooks/standing.ts
8212
+ // packages/workit-core/src/hooks/budget-signal.ts
8213
+ import { createHash as createHash4 } from "node:crypto";
8213
8214
  import fs4 from "node:fs";
8214
8215
  import path7 from "node:path";
8216
+ var hash = (text) => createHash4("sha256").update(text).digest("hex").slice(0, 32);
8217
+ var budgetDir = (store) => path7.join(store, "budget");
8218
+ var agentBase = (store, session, agentId) => path7.join(budgetDir(store), hash(`${session}\x00${agentId ?? ""}`));
8219
+ var spawnBudget = (prompt) => {
8220
+ const found = /^TIMEBOX:[^\n]*?\b(\d+)\s+tool calls\b/mu.exec(prompt);
8221
+ const n = found ? Number(found[1]) : NaN;
8222
+ return Number.isInteger(n) && n > 0 ? n : null;
8223
+ };
8224
+ var startBudget = (store, session, agentId, n) => {
8225
+ const dir = budgetDir(store);
8226
+ fs4.mkdirSync(dir, { recursive: true });
8227
+ const cutoff = Date.now() - 604800000;
8228
+ for (const name of fs4.readdirSync(dir))
8229
+ try {
8230
+ if (fs4.statSync(path7.join(dir, name)).mtimeMs < cutoff)
8231
+ fs4.rmSync(path7.join(dir, name));
8232
+ } catch {}
8233
+ const base = agentBase(store, session, agentId);
8234
+ fs4.writeFileSync(`${base}.json`, JSON.stringify({ limit: n, told: 0 }));
8235
+ fs4.rmSync(`${base}.calls`, { force: true });
8236
+ };
8237
+ var bindBudget = (cwd, session, agentId, prompt) => {
8238
+ try {
8239
+ const n = spawnBudget(prompt);
8240
+ const store = n && session ? gitStoreDir(cwd) : null;
8241
+ if (!n || !store)
8242
+ return null;
8243
+ startBudget(store, session, agentId, n);
8244
+ return n;
8245
+ } catch {
8246
+ return null;
8247
+ }
8248
+ };
8249
+ var CODEX_GATED = new Set(["bash", "unifiedexec", "shell", "applypatch", "edit", "write"]);
8250
+
8251
+ // packages/workit-core/src/hooks/standing.ts
8252
+ import fs5 from "node:fs";
8253
+ import path8 from "node:path";
8215
8254
  var readPlans = (store) => {
8216
- const dir = path7.join(store, "fanouts");
8255
+ const dir = path8.join(store, "fanouts");
8217
8256
  let names;
8218
8257
  try {
8219
- names = fs4.readdirSync(dir).filter((name) => name.endsWith(".json"));
8258
+ names = fs5.readdirSync(dir).filter((name) => name.endsWith(".json"));
8220
8259
  } catch {
8221
8260
  return [];
8222
8261
  }
8223
8262
  const plans = [];
8224
8263
  for (const name of names) {
8225
8264
  try {
8226
- const raw = JSON.parse(fs4.readFileSync(path7.join(dir, name), "utf8"));
8265
+ const raw = JSON.parse(fs5.readFileSync(path8.join(dir, name), "utf8"));
8227
8266
  if (typeof raw.name !== "string" || !Array.isArray(raw.slices))
8228
8267
  continue;
8229
8268
  const texts = (value) => Array.isArray(value) ? value.filter((item) => typeof item === "string" && item !== "") : [];
@@ -8454,6 +8493,8 @@ function launchWorker(assignment, options) {
8454
8493
  transcriptPath: null,
8455
8494
  event: { kind: "session.start", source: "startup" }
8456
8495
  });
8496
+ bindBudget(spec.cwd, env.WORKIT_SESSION_ID, null, `${options.pendingPrompt ?? ""}
8497
+ ${assignment.objective}`);
8457
8498
  try {
8458
8499
  const spawn = options.spawn ?? ((command, args, spawnOptions) => nodeSpawn(command, args, spawnOptions));
8459
8500
  handle.spec = { ...spec, env };
package/dist/workit.js CHANGED
@@ -24609,7 +24609,8 @@ function handleHook(input, deps) {
24609
24609
  const decision = decide(input, deps);
24610
24610
  if (deps.descriptor.transport !== "hook-process" || !PRE_TOOL.has(input.event.kind) || decision.kind === "deny" || decision.kind === "ask")
24611
24611
  return decision;
24612
- const delta = standingDelta(input);
24612
+ const delta = [standingDelta(input), deps.budget?.line(input) ?? null].filter(Boolean).join(`
24613
+ `) || null;
24613
24614
  if (!delta)
24614
24615
  return decision;
24615
24616
  return decision.kind === "rewrite" || decision.kind === "update-input" ? { ...decision, text: [decision.text, delta].filter(Boolean).join(`
@@ -24719,7 +24720,11 @@ function decide(input, deps) {
24719
24720
  case "subagent.start":
24720
24721
  if (workitRole(descriptor, event.agentType) === "judge")
24721
24722
  trackJudge(input, event.agentId, true);
24722
- seedStanding({ ...input, session: { ...input.session, agentId: event.agentId } });
24723
+ seedStanding({
24724
+ ...input,
24725
+ session: { ...input.session, agentId: event.agentId }
24726
+ });
24727
+ deps.budget?.start(input, event.agentId, event.agentType);
24723
24728
  return {
24724
24729
  kind: "context",
24725
24730
  text: subagentStartText(input, descriptor, event)
@@ -24732,13 +24737,19 @@ function decide(input, deps) {
24732
24737
  case "tool.pre":
24733
24738
  if (event.skill)
24734
24739
  recordSkillLoad(input, event.skill, "tool");
24735
- if (event.agentInput && input.host === "claude_code")
24736
- return spawnDecision(descriptor, input.cwd, event.agentInput);
24740
+ if (event.agentInput && input.host === "claude_code") {
24741
+ const spawn = spawnDecision(descriptor, input.cwd, event.agentInput);
24742
+ if (spawn.kind !== "deny")
24743
+ deps.budget?.spawn(input, event.agentInput);
24744
+ return spawn;
24745
+ }
24737
24746
  return NONE6;
24738
24747
  case "stop":
24739
24748
  return usable2(descriptor.events.stop.support) ? stopDecision(input, descriptor) : NONE6;
24740
24749
  case "subagent.stop":
24741
24750
  trackJudge(input, event.agentId, false);
24751
+ if (event.agentId)
24752
+ deps.budget?.stop(input, event.agentId);
24742
24753
  return NONE6;
24743
24754
  case "prompt.submit":
24744
24755
  return NONE6;
@@ -24856,6 +24867,88 @@ var modelKey = (model) => {
24856
24867
  return typeof source === "string" && source ? `${source}/${id}` : id;
24857
24868
  };
24858
24869
 
24870
+ // packages/workit-core/src/hooks/budget-signal.ts
24871
+ import { createHash as createHash18 } from "node:crypto";
24872
+ import fs26 from "node:fs";
24873
+ import path35 from "node:path";
24874
+ var hash2 = (text) => createHash18("sha256").update(text).digest("hex").slice(0, 32);
24875
+ var budgetDir = (store) => path35.join(store, "budget");
24876
+ var agentBase = (store, session, agentId) => path35.join(budgetDir(store), hash2(`${session}\x00${agentId ?? ""}`));
24877
+ var spawnBudget = (prompt) => {
24878
+ const found = /^TIMEBOX:[^\n]*?\b(\d+)\s+tool calls\b/mu.exec(prompt);
24879
+ const n = found ? Number(found[1]) : NaN;
24880
+ return Number.isInteger(n) && n > 0 ? n : null;
24881
+ };
24882
+ var readBudget = (file) => {
24883
+ try {
24884
+ const { limit, told } = JSON.parse(fs26.readFileSync(file, "utf8"));
24885
+ return typeof limit === "number" && limit > 0 ? { limit, told: Number(told) || 0 } : null;
24886
+ } catch {
24887
+ return null;
24888
+ }
24889
+ };
24890
+ var startBudget = (store, session, agentId, n) => {
24891
+ const dir = budgetDir(store);
24892
+ fs26.mkdirSync(dir, { recursive: true });
24893
+ const cutoff = Date.now() - 604800000;
24894
+ for (const name of fs26.readdirSync(dir))
24895
+ try {
24896
+ if (fs26.statSync(path35.join(dir, name)).mtimeMs < cutoff)
24897
+ fs26.rmSync(path35.join(dir, name));
24898
+ } catch {}
24899
+ const base = agentBase(store, session, agentId);
24900
+ fs26.writeFileSync(`${base}.json`, JSON.stringify({ limit: n, told: 0 }));
24901
+ fs26.rmSync(`${base}.calls`, { force: true });
24902
+ };
24903
+ var bindBudget = (cwd, session, agentId, prompt) => {
24904
+ try {
24905
+ const n = spawnBudget(prompt);
24906
+ const store = n && session ? gitStoreDir(cwd) : null;
24907
+ if (!n || !store)
24908
+ return null;
24909
+ startBudget(store, session, agentId, n);
24910
+ return n;
24911
+ } catch {
24912
+ return null;
24913
+ }
24914
+ };
24915
+ var tickBudget = (cwd, session, agentId) => {
24916
+ try {
24917
+ const store = session ? gitStoreDir(cwd) : null;
24918
+ if (!store)
24919
+ return false;
24920
+ const base = agentBase(store, session, agentId);
24921
+ const budget = readBudget(`${base}.json`);
24922
+ if (!budget)
24923
+ return false;
24924
+ fs26.appendFileSync(`${base}.calls`, ".");
24925
+ return fs26.statSync(`${base}.calls`).size >= budget.limit * (budget.told + 1);
24926
+ } catch {
24927
+ return false;
24928
+ }
24929
+ };
24930
+ var lineFor = (calls) => `Tool-call budget reached (${calls}): commit your work, write your report, stop.`;
24931
+ var reachedOf = (cwd, session, agentId) => {
24932
+ const store = session ? gitStoreDir(cwd) : null;
24933
+ const base = store && agentBase(store, session, agentId);
24934
+ const budget = base && readBudget(`${base}.json`);
24935
+ if (!budget)
24936
+ return null;
24937
+ return { base, budget, reached: Math.floor(fs26.statSync(`${base}.calls`).size / budget.limit) };
24938
+ };
24939
+ var budgetLine = (cwd, session, agentId) => {
24940
+ try {
24941
+ const at = reachedOf(cwd, session, agentId);
24942
+ if (!at || at.reached <= at.budget.told)
24943
+ return null;
24944
+ fs26.writeFileSync(`${at.base}.json`, JSON.stringify({ ...at.budget, told: at.reached }));
24945
+ return lineFor(at.budget.limit * at.reached);
24946
+ } catch {
24947
+ return null;
24948
+ }
24949
+ };
24950
+ var CODEX_GATED = new Set(["bash", "unifiedexec", "shell", "applypatch", "edit", "write"]);
24951
+
24859
24952
  // packages/workit-pi/src/context.ts
24860
24953
  var piCapabilities = (ctx) => capabilitiesFor(PI_DESCRIPTOR, { ui: ctx?.hasUI === true });
24861
24954
  var piContext = (ctx) => ({
@@ -24910,6 +25003,12 @@ var standingNotice = (ctx) => {
24910
25003
  const input = turnInput(ctx);
24911
25004
  return standingDelta(worker ? { ...input, session: { ...input.session, id: worker } } : input);
24912
25005
  };
25006
+ var budgetNotice = (ctx) => {
25007
+ if (!ctx.isProjectTrusted())
25008
+ return null;
25009
+ const session = process.env.WORKIT_PI_WORKER_SESSION && process.env.WORKIT_SESSION_ID?.trim() || ctx.sessionManager.getSessionId();
25010
+ return tickBudget(ctx.cwd, session, null) ? budgetLine(ctx.cwd, session, null) : null;
25011
+ };
24913
25012
  var workitContext = (ctx, taskContext = taskTurnContext(ctx)) => {
24914
25013
  const session = ctx.sessionManager.getSessionId();
24915
25014
  if (!ctx.isProjectTrusted())
@@ -24934,11 +25033,11 @@ var unfinishedTaskOffer2 = (ctx) => {
24934
25033
  };
24935
25034
 
24936
25035
  // packages/workit-core/src/hooks/repeat-failure.ts
24937
- import { createHash as createHash18 } from "node:crypto";
25036
+ import { createHash as createHash19 } from "node:crypto";
24938
25037
  var SAME_CALL_FAILED = "Same call failed twice:";
24939
25038
  var MAX_SESSIONS = 256;
24940
25039
  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";
24941
- var hashOf = (call) => createHash18("sha256").update(call).digest("hex").slice(0, 32);
25040
+ var hashOf = (call) => createHash19("sha256").update(call).digest("hex").slice(0, 32);
24942
25041
  var record2 = (memo, sessionId, call, failed, next) => {
24943
25042
  const previous = memo.get(sessionId);
24944
25043
  memo.delete(sessionId);
@@ -25138,12 +25237,12 @@ var skillPromptNudge = (prompt, ctx) => process.env.WORKIT_PI_WORKER_SESSION ? n
25138
25237
  // packages/workit-pi/src/launches.ts
25139
25238
  import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, readdirSync as readdirSync5, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "node:fs";
25140
25239
  import os11 from "node:os";
25141
- import path35 from "node:path";
25240
+ import path36 from "node:path";
25142
25241
  var launchDir = (root) => {
25143
25242
  const location = resolveStore(root);
25144
- return location instanceof Error ? null : path35.join(location.dir, "pi-launches");
25243
+ return location instanceof Error ? null : path36.join(location.dir, "pi-launches");
25145
25244
  };
25146
- var fileFor = (dir, workerId) => path35.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
25245
+ var fileFor = (dir, workerId) => path36.join(dir, `${workerId.replace(/[^A-Za-z0-9-]/g, "_")}.json`);
25147
25246
  var recordLaunch = (root, attempt) => {
25148
25247
  const dir = launchDir(root);
25149
25248
  if (!dir)
@@ -25189,7 +25288,7 @@ var abandonedLaunches = (root) => {
25189
25288
  const found = [];
25190
25289
  for (const name of names) {
25191
25290
  try {
25192
- const attempt = JSON.parse(readFileSync7(path35.join(dir, name), "utf8"));
25291
+ const attempt = JSON.parse(readFileSync7(path36.join(dir, name), "utf8"));
25193
25292
  if (typeof attempt.workerId === "string" && typeof attempt.session === "string") {
25194
25293
  if (!alive(attempt))
25195
25294
  found.push(attempt);
@@ -25379,6 +25478,8 @@ function launchWorker(assignment, options) {
25379
25478
  transcriptPath: null,
25380
25479
  event: { kind: "session.start", source: "startup" }
25381
25480
  });
25481
+ bindBudget(spec.cwd, env.WORKIT_SESSION_ID, null, `${options.pendingPrompt ?? ""}
25482
+ ${assignment.objective}`);
25382
25483
  try {
25383
25484
  const spawn = options.spawn ?? ((command, args, spawnOptions) => nodeSpawn(command, args, spawnOptions));
25384
25485
  handle.spec = { ...spec, env };
@@ -26129,7 +26230,8 @@ ${nudge}` : content) };
26129
26230
  const observed = observeResult(event, ctx);
26130
26231
  let standing = null;
26131
26232
  try {
26132
- standing = standingNotice(ctx);
26233
+ standing = [standingNotice(ctx), budgetNotice(ctx)].filter(Boolean).join(`
26234
+ `) || null;
26133
26235
  } catch {}
26134
26236
  if (!standing)
26135
26237
  return observed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@brainervirus/workit-pi",
3
- "version": "8.36.0",
3
+ "version": "8.37.0",
4
4
  "description": "Workit native extension for Pi — tools, method skills, and session continuity",
5
5
  "keywords": [
6
6
  "agentic-coding",