cruo-agent 0.1.17 → 0.1.18

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.18
package/dist/cli.js CHANGED
@@ -47980,28 +47980,40 @@ async function recordRun(ctx, hit, run, outcome) {
47980
47980
  }
47981
47981
  }
47982
47982
  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
- );
47983
+ const readQueue = async (exclude, quiet2) => {
47984
+ const mentions = await pollMentions(ctx, identity);
47985
+ const [assigned, stateWork] = await Promise.all([pollAssigned(ctx, identity), poll(ctx, identity)]);
47986
+ const seen = /* @__PURE__ */ new Set();
47987
+ const hits = [...mentions, ...assigned, ...stateWork].filter((h) => {
47988
+ if (seen.has(h.ref)) return false;
47989
+ seen.add(h.ref);
47990
+ return true;
47991
+ });
47992
+ const heldByOthers2 = await claimedElsewhere(ctx, hits);
47993
+ const free2 = hits.filter((h) => !heldByOthers2.has(h.issue.id));
47994
+ if (heldByOthers2.size > 0 && !quiet2) {
47995
+ log(`${heldByOthers2.size} issue(s) already being worked by someone else`);
47996
+ }
47997
+ const attemptsByHit = await loadAttempts(ctx, free2);
47998
+ const attemptsFor2 = (h) => attemptsByHit.get(`${h.reason}:${h.issue.id}`) ?? 0;
47999
+ const spendByHit = await loadSpend(ctx, free2);
48000
+ const spendFor = (h) => spendByHit.get(h.issue.id) ?? 0;
48001
+ const overBudget = options.maxCostUsd !== void 0 ? free2.filter(
48002
+ (h) => !exclude.has(h.issue.id) && attemptsFor2(h) < options.maxAttempts && spendFor(h) >= options.maxCostUsd
48003
+ ) : [];
48004
+ for (const hit of overBudget) {
48005
+ exclude.add(hit.issue.id);
48006
+ await handOffForCost(ctx, hit, spendFor(hit));
48007
+ }
48008
+ const actionable2 = free2.filter(
48009
+ (h) => !exclude.has(h.issue.id) && attemptsFor2(h) < options.maxAttempts && (options.maxCostUsd === void 0 || spendFor(h) < options.maxCostUsd)
48010
+ );
48011
+ return { free: free2, heldByOthers: heldByOthers2, actionable: actionable2, attemptsFor: attemptsFor2 };
48012
+ };
48013
+ const tried = /* @__PURE__ */ new Set();
48014
+ const queue = await readQueue(tried, false);
48015
+ const { free, heldByOthers } = queue;
48016
+ let { actionable, attemptsFor } = queue;
48005
48017
  if (actionable.length === 0) {
48006
48018
  if (free.length > 0) {
48007
48019
  log(`${free.length} issue(s) waiting, all set aside`);
@@ -48039,13 +48051,25 @@ async function tick(ctx, identity, deadTicks, allowance) {
48039
48051
  `${actionable.length} issue(s) for "${identity.jobFunction}"` + (mentionCount > 0 ? ` \u2014 ${mentionCount} from unread mentions` : "")
48040
48052
  );
48041
48053
  const cap = allowance === "probe" ? 1 : options.limit;
48042
- const taking = cap ? actionable.slice(0, cap) : actionable;
48043
- if (taking.length < actionable.length) {
48054
+ if (cap && cap < actionable.length) {
48044
48055
  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})`
48056
+ 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
48057
  );
48047
48058
  }
48048
- for (const queued of taking) {
48059
+ const firstRead = new Set(actionable.map((h) => h.issue.id));
48060
+ let worked = 0;
48061
+ for (let first = true; ; first = false) {
48062
+ if (cap && worked >= cap) break;
48063
+ if (!first) {
48064
+ if (stopping || paused) break;
48065
+ ({ actionable, attemptsFor } = await readQueue(tried, true));
48066
+ }
48067
+ const queued = actionable.find((h) => !tried.has(h.issue.id));
48068
+ if (!queued) break;
48069
+ tried.add(queued.issue.id);
48070
+ if (!firstRead.has(queued.issue.id)) {
48071
+ log(` ${queued.ref} arrived since this pass began \u2014 taking it next`);
48072
+ }
48049
48073
  const rechecked = await recheckEligibility(ctx, queued, identity);
48050
48074
  if (!rechecked.ok) {
48051
48075
  log(` ${queued.ref} ${rechecked.why} \u2014 skipping`);
@@ -48054,6 +48078,7 @@ async function tick(ctx, identity, deadTicks, allowance) {
48054
48078
  const hit = rechecked.hit;
48055
48079
  const why = hit.reason === "mention" ? "mentioned" : hit.reason === "assigned" ? `assigned \xB7 ${hit.state.name}` : hit.state.name;
48056
48080
  log(`\u2192 ${hit.ref} "${hit.issue.title}" (${why})`);
48081
+ worked += 1;
48057
48082
  if (options.dryRun) {
48058
48083
  log(` dry run \u2014 would invoke: ${options.harness} -p \u2026 --allowedTools ${options.allow}`);
48059
48084
  continue;
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.18",
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",