cruo-agent 0.1.17 → 0.1.19

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/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.17
1
+ 0.1.19
package/dist/cli.js CHANGED
@@ -15300,6 +15300,13 @@ var init_order = __esm({
15300
15300
  }
15301
15301
  });
15302
15302
 
15303
+ // ../../packages/core/dist/csv.js
15304
+ var init_csv = __esm({
15305
+ "../../packages/core/dist/csv.js"() {
15306
+ "use strict";
15307
+ }
15308
+ });
15309
+
15303
15310
  // ../../packages/core/dist/index.js
15304
15311
  var init_dist = __esm({
15305
15312
  "../../packages/core/dist/index.js"() {
@@ -15315,6 +15322,7 @@ var init_dist = __esm({
15315
15322
  init_tree();
15316
15323
  init_place();
15317
15324
  init_order();
15325
+ init_csv();
15318
15326
  }
15319
15327
  });
15320
15328
 
@@ -47659,6 +47667,14 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47659
47667
  "--strict-mcp-config",
47660
47668
  "--allowedTools",
47661
47669
  options.allow,
47670
+ // CRA-131. The local database is shared by every agent and person on the
47671
+ // machine, so no agent resets it. `pnpm supabase:reset` refuses inside a
47672
+ // run on its own (CRUO_AGENT_RUN, below); these are the spellings that
47673
+ // bypass that script. A deny rule matches a prefix, so it is a fence and
47674
+ // not a wall — `bash -c "…"` still gets round it — but it stops the
47675
+ // ordinary command an agent reaches for, which is how 2026-09-19 happened.
47676
+ "--disallowedTools",
47677
+ DB_RESET_DENY.join(","),
47662
47678
  "--system-prompt",
47663
47679
  systemPromptText,
47664
47680
  ...options.model ? ["--model", options.model] : [],
@@ -47675,7 +47691,9 @@ async function invokeHarness(ctx, identity, hit, worktree) {
47675
47691
  // both are written straight through — this tees, it does not capture.
47676
47692
  stdio: ["ignore", "pipe", "pipe"],
47677
47693
  cwd,
47678
- env: harnessEnv()
47694
+ // CRA-131: marks everything the harness runs as inside an agent run,
47695
+ // which is what makes `pnpm supabase:reset` refuse with no override.
47696
+ env: { ...harnessEnv(), CRUO_AGENT_RUN: hit.ref }
47679
47697
  });
47680
47698
  const startedAtMs = Date.now();
47681
47699
  let stdoutBytes = 0;
@@ -47980,28 +47998,40 @@ async function recordRun(ctx, hit, run, outcome) {
47980
47998
  }
47981
47999
  }
47982
48000
  async function tick(ctx, identity, deadTicks, allowance) {
47983
- const mentions = await pollMentions(ctx, identity);
47984
- const [assigned, stateWork] = await Promise.all([pollAssigned(ctx, identity), poll(ctx, identity)]);
47985
- const seen = /* @__PURE__ */ new Set();
47986
- const hits = [...mentions, ...assigned, ...stateWork].filter((h) => {
47987
- if (seen.has(h.ref)) return false;
47988
- seen.add(h.ref);
47989
- return true;
47990
- });
47991
- const heldByOthers = await claimedElsewhere(ctx, hits);
47992
- const free = hits.filter((h) => !heldByOthers.has(h.issue.id));
47993
- if (heldByOthers.size > 0) {
47994
- log(`${heldByOthers.size} issue(s) already being worked by someone else`);
47995
- }
47996
- const attemptsByHit = await loadAttempts(ctx, free);
47997
- const attemptsFor = (h) => attemptsByHit.get(`${h.reason}:${h.issue.id}`) ?? 0;
47998
- const spendByHit = await loadSpend(ctx, free);
47999
- const spendFor = (h) => spendByHit.get(h.issue.id) ?? 0;
48000
- const overBudget = options.maxCostUsd !== void 0 ? free.filter((h) => attemptsFor(h) < options.maxAttempts && spendFor(h) >= options.maxCostUsd) : [];
48001
- for (const hit of overBudget) await handOffForCost(ctx, hit, spendFor(hit));
48002
- const actionable = free.filter(
48003
- (h) => attemptsFor(h) < options.maxAttempts && (options.maxCostUsd === void 0 || spendFor(h) < options.maxCostUsd)
48004
- );
48001
+ const readQueue = async (exclude, quiet2) => {
48002
+ const mentions = await pollMentions(ctx, identity);
48003
+ const [assigned, stateWork] = await Promise.all([pollAssigned(ctx, identity), poll(ctx, identity)]);
48004
+ const seen = /* @__PURE__ */ new Set();
48005
+ const hits = [...mentions, ...assigned, ...stateWork].filter((h) => {
48006
+ if (seen.has(h.ref)) return false;
48007
+ seen.add(h.ref);
48008
+ return true;
48009
+ });
48010
+ const heldByOthers2 = await claimedElsewhere(ctx, hits);
48011
+ const free2 = hits.filter((h) => !heldByOthers2.has(h.issue.id));
48012
+ if (heldByOthers2.size > 0 && !quiet2) {
48013
+ log(`${heldByOthers2.size} issue(s) already being worked by someone else`);
48014
+ }
48015
+ const attemptsByHit = await loadAttempts(ctx, free2);
48016
+ const attemptsFor2 = (h) => attemptsByHit.get(`${h.reason}:${h.issue.id}`) ?? 0;
48017
+ const spendByHit = await loadSpend(ctx, free2);
48018
+ const spendFor = (h) => spendByHit.get(h.issue.id) ?? 0;
48019
+ const overBudget = options.maxCostUsd !== void 0 ? free2.filter(
48020
+ (h) => !exclude.has(h.issue.id) && attemptsFor2(h) < options.maxAttempts && spendFor(h) >= options.maxCostUsd
48021
+ ) : [];
48022
+ for (const hit of overBudget) {
48023
+ exclude.add(hit.issue.id);
48024
+ await handOffForCost(ctx, hit, spendFor(hit));
48025
+ }
48026
+ const actionable2 = free2.filter(
48027
+ (h) => !exclude.has(h.issue.id) && attemptsFor2(h) < options.maxAttempts && (options.maxCostUsd === void 0 || spendFor(h) < options.maxCostUsd)
48028
+ );
48029
+ return { free: free2, heldByOthers: heldByOthers2, actionable: actionable2, attemptsFor: attemptsFor2 };
48030
+ };
48031
+ const tried = /* @__PURE__ */ new Set();
48032
+ const queue = await readQueue(tried, false);
48033
+ const { free, heldByOthers } = queue;
48034
+ let { actionable, attemptsFor } = queue;
48005
48035
  if (actionable.length === 0) {
48006
48036
  if (free.length > 0) {
48007
48037
  log(`${free.length} issue(s) waiting, all set aside`);
@@ -48039,13 +48069,25 @@ async function tick(ctx, identity, deadTicks, allowance) {
48039
48069
  `${actionable.length} issue(s) for "${identity.jobFunction}"` + (mentionCount > 0 ? ` \u2014 ${mentionCount} from unread mentions` : "")
48040
48070
  );
48041
48071
  const cap = allowance === "probe" ? 1 : options.limit;
48042
- const taking = cap ? actionable.slice(0, cap) : actionable;
48043
- if (taking.length < actionable.length) {
48072
+ if (cap && cap < actionable.length) {
48044
48073
  log(
48045
- allowance === "probe" ? ` probing with ${taking.length} of ${actionable.length} \u2014 one card is enough to learn whether the harness works again` : ` taking ${taking.length} this tick (--limit ${options.limit})`
48074
+ allowance === "probe" ? ` probing with ${cap} of ${actionable.length} \u2014 one card is enough to learn whether the harness works again` : ` taking ${cap} this tick (--limit ${options.limit})`
48046
48075
  );
48047
48076
  }
48048
- for (const queued of taking) {
48077
+ const firstRead = new Set(actionable.map((h) => h.issue.id));
48078
+ let worked = 0;
48079
+ for (let first = true; ; first = false) {
48080
+ if (cap && worked >= cap) break;
48081
+ if (!first) {
48082
+ if (stopping || paused) break;
48083
+ ({ actionable, attemptsFor } = await readQueue(tried, true));
48084
+ }
48085
+ const queued = actionable.find((h) => !tried.has(h.issue.id));
48086
+ if (!queued) break;
48087
+ tried.add(queued.issue.id);
48088
+ if (!firstRead.has(queued.issue.id)) {
48089
+ log(` ${queued.ref} arrived since this pass began \u2014 taking it next`);
48090
+ }
48049
48091
  const rechecked = await recheckEligibility(ctx, queued, identity);
48050
48092
  if (!rechecked.ok) {
48051
48093
  log(` ${queued.ref} ${rechecked.why} \u2014 skipping`);
@@ -48054,6 +48096,7 @@ async function tick(ctx, identity, deadTicks, allowance) {
48054
48096
  const hit = rechecked.hit;
48055
48097
  const why = hit.reason === "mention" ? "mentioned" : hit.reason === "assigned" ? `assigned \xB7 ${hit.state.name}` : hit.state.name;
48056
48098
  log(`\u2192 ${hit.ref} "${hit.issue.title}" (${why})`);
48099
+ worked += 1;
48057
48100
  if (options.dryRun) {
48058
48101
  log(` dry run \u2014 would invoke: ${options.harness} -p \u2026 --allowedTools ${options.allow}`);
48059
48102
  continue;
@@ -48375,7 +48418,7 @@ to catch: a second supervisor nothing could see or stop.
48375
48418
  if (stopping) return;
48376
48419
  }
48377
48420
  }
48378
- var argv, flag, opt, num2, ms, readOptions, options, log, sessionRenewal, worktreeConfig, lastRefusal, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV, HEAD_STORED, lastHolding, warnedAboutSpendTable, stopping, paused, stopRequestedAt, currentRun, pendingWait;
48421
+ var argv, flag, opt, num2, ms, readOptions, options, log, sessionRenewal, worktreeConfig, lastRefusal, CONFIG_DIR_PREFIX, CRUO_OWNED_ENV, DB_RESET_DENY, HEAD_STORED, lastHolding, warnedAboutSpendTable, stopping, paused, stopRequestedAt, currentRun, pendingWait;
48379
48422
  var init_supervisor = __esm({
48380
48423
  "src/supervisor.ts"() {
48381
48424
  "use strict";
@@ -48603,6 +48646,12 @@ cruo-supervisor: ${error51.message}
48603
48646
  // stands in for. Cheap to list, and wrong to discover the hard way.
48604
48647
  "CRUO_SESSION"
48605
48648
  ];
48649
+ DB_RESET_DENY = [
48650
+ "Bash(npx supabase db reset:*)",
48651
+ "Bash(supabase db reset:*)",
48652
+ "Bash(pnpm exec supabase db reset:*)",
48653
+ "Bash(pnpm dlx supabase db reset:*)"
48654
+ ];
48606
48655
  HEAD_STORED = 200;
48607
48656
  lastHolding = 0;
48608
48657
  warnedAboutSpendTable = false;
package/dist/index.js CHANGED
@@ -89353,7 +89353,7 @@ function registerTools(server, getContext2, options = {}) {
89353
89353
  "delete_issue",
89354
89354
  {
89355
89355
  title: "Delete issue",
89356
- description: "Delete an issue (its comments, labels and activity cascade).",
89356
+ description: "Move an issue to the trash. It disappears from the board and every tool, with its comments, labels and activity; a person can restore it from Trash in the app for 30 days, after which it is deleted for good.",
89357
89357
  inputSchema: { issue: external_exports.string().describe("Issue reference, e.g. CRU-14") }
89358
89358
  },
89359
89359
  async (args, ctx) => {
@@ -89361,7 +89361,7 @@ function registerTools(server, getContext2, options = {}) {
89361
89361
  const ref = issueRef(project, issue2);
89362
89362
  const { error: error52 } = await ctx.client.from("issues").delete().eq("id", issue2.id);
89363
89363
  if (error52) throw new ToolError(error52.message);
89364
- return `Deleted ${ref}.`;
89364
+ return `Moved ${ref} to the trash. It can be restored from Trash in the app for 30 days.`;
89365
89365
  }
89366
89366
  );
89367
89367
  register(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cruo-agent",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "Run a Cruo agent: it watches your board, picks up the cards you assign it, and works them.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://cruo.space/docs#agents",