@integrity-labs/agt-cli 0.28.921 → 0.28.923

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.
@@ -100,7 +100,7 @@ async function spawnPairSession(session) {
100
100
  return { ok: true };
101
101
  } catch {
102
102
  }
103
- const { resolveClaudeBinary } = await import("./persistent-session-OQRYZ3YP.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-UEAJJ72P.js");
104
104
  const claudeBin = resolveClaudeBinary();
105
105
  const pairEnv = {
106
106
  ...process.env,
@@ -373,4 +373,4 @@ export {
373
373
  startClaudePair,
374
374
  submitClaudePairCode
375
375
  };
376
- //# sourceMappingURL=claude-pair-runtime-NNHCDXLG.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-SY5VCTCE.js.map
@@ -63,7 +63,7 @@ import {
63
63
  safeWriteJsonAtomic,
64
64
  setConfigHash,
65
65
  tripClass
66
- } from "../chunk-GWP5VUJ4.js";
66
+ } from "../chunk-D3DVYP4X.js";
67
67
  import {
68
68
  getProjectDir as getProjectDir2,
69
69
  getReadyTasks,
@@ -136,7 +136,7 @@ import {
136
136
  takeZombieDetection,
137
137
  toOpencodeModel,
138
138
  writeEgressAllowlist
139
- } from "../chunk-NGJFE75J.js";
139
+ } from "../chunk-BLEV5PAO.js";
140
140
  import {
141
141
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
142
142
  AnchorSessionClient,
@@ -231,7 +231,7 @@ import {
231
231
  subagentActivityAgeSeconds,
232
232
  sumTranscriptUsageInWindow,
233
233
  transcriptActivityAgeSeconds
234
- } from "../chunk-ZOPP3ZC7.js";
234
+ } from "../chunk-VHO7KAB7.js";
235
235
  import {
236
236
  reapOrphanChannelMcps
237
237
  } from "../chunk-XWVM4KPK.js";
@@ -13138,7 +13138,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
13138
13138
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
13139
13139
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
13140
13140
  async function channelInboundActivityAgeSecondsFor(codeName) {
13141
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-RBWP6HJE.js");
13141
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-VXJKA6RF.js");
13142
13142
  const newest = newestPendingInboundActivityMtimeMs(dirname11(paneLogPath(codeName)));
13143
13143
  if (newest === null) return null;
13144
13144
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -13823,7 +13823,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
13823
13823
  var lastVersionCheckAt = 0;
13824
13824
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
13825
13825
  var lastResponsivenessProbeAt = 0;
13826
- var agtCliVersion = true ? "0.28.921" : "dev";
13826
+ var agtCliVersion = true ? "0.28.923" : "dev";
13827
13827
  function resolveBrewPath(execFileSync2) {
13828
13828
  try {
13829
13829
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -15413,7 +15413,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
15413
15413
  if (codeNames.length === 0) return;
15414
15414
  void (async () => {
15415
15415
  try {
15416
- const { collectDiagnostics } = await import("../persistent-session-OQRYZ3YP.js");
15416
+ const { collectDiagnostics } = await import("../persistent-session-UEAJJ72P.js");
15417
15417
  await api.post("/host/heartbeat", {
15418
15418
  host_id: hostId,
15419
15419
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -15555,7 +15555,7 @@ async function pollCycleInner() {
15555
15555
  }
15556
15556
  try {
15557
15557
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
15558
- const { collectDiagnostics } = await import("../persistent-session-OQRYZ3YP.js");
15558
+ const { collectDiagnostics } = await import("../persistent-session-UEAJJ72P.js");
15559
15559
  const diagCodeNames = [...agentState.persistentSessionAgents];
15560
15560
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor) : void 0;
15561
15561
  let tailscaleHostname;
@@ -15730,7 +15730,7 @@ async function pollCycleInner() {
15730
15730
  collectPanelessActivityProbes,
15731
15731
  getResponsivenessIntervalMs,
15732
15732
  occupancyQualificationClassifications
15733
- } = await import("../responsiveness-probe-RBWP6HJE.js");
15733
+ } = await import("../responsiveness-probe-VXJKA6RF.js");
15734
15734
  const probeIntervalMs = getResponsivenessIntervalMs();
15735
15735
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
15736
15736
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -15847,7 +15847,7 @@ async function pollCycleInner() {
15847
15847
  collectResponsivenessProbes,
15848
15848
  livePendingInboundOldestAgeSeconds,
15849
15849
  parkPendingInbound
15850
- } = await import("../responsiveness-probe-RBWP6HJE.js");
15850
+ } = await import("../responsiveness-probe-VXJKA6RF.js");
15851
15851
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
15852
15852
  const wedgeNow = /* @__PURE__ */ new Date();
15853
15853
  const liveAgents = agentState.persistentSessionAgents;
@@ -16003,7 +16003,7 @@ async function pollCycleInner() {
16003
16003
  }
16004
16004
  try {
16005
16005
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
16006
- const { probeSessionAuth } = await import("../session-auth-dead-4KUPTD7M.js");
16006
+ const { probeSessionAuth } = await import("../session-auth-dead-XOP4XYBD.js");
16007
16007
  const observations = [];
16008
16008
  const pendingCacheCommits = [];
16009
16009
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -20267,7 +20267,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
20267
20267
  try {
20268
20268
  const freshId = rotateSessionForWedge(codeName);
20269
20269
  consecutiveWedgeCycles.delete(codeName);
20270
- const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-RBWP6HJE.js");
20270
+ const { parkPendingInbound: parkForRestart } = await import("../responsiveness-probe-VXJKA6RF.js");
20271
20271
  const { parked, deadLettered } = parkForRestart(codeName, /* @__PURE__ */ new Date());
20272
20272
  const parkNote = parked > 0 ? `, ${parked} inbound parked` : "";
20273
20273
  const deadNote = deadLettered > 0 ? `, ${deadLettered} undeliverable dead-lettered` : "";
@@ -20310,7 +20310,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
20310
20310
  void api.post("/host/restart-ack", { host_id: hostId, agent_id: agentId, restart_requested_at: requestedAt }).catch((err) => log(`[restart-lane] ack failed for '${codeName}': ${err.message}`));
20311
20311
  void (async () => {
20312
20312
  try {
20313
- const { collectDiagnostics } = await import("../persistent-session-OQRYZ3YP.js");
20313
+ const { collectDiagnostics } = await import("../persistent-session-UEAJJ72P.js");
20314
20314
  await api.post("/host/heartbeat", {
20315
20315
  host_id: hostId,
20316
20316
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -20361,7 +20361,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
20361
20361
  }
20362
20362
  try {
20363
20363
  const hostId = await getHostId();
20364
- const { collectDiagnostics } = await import("../persistent-session-OQRYZ3YP.js");
20364
+ const { collectDiagnostics } = await import("../persistent-session-UEAJJ72P.js");
20365
20365
  await api.post("/host/heartbeat", {
20366
20366
  host_id: hostId,
20367
20367
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor, forwardedToolsFor)
@@ -21003,7 +21003,7 @@ async function processClaudePairSessions(agents) {
21003
21003
  killPairSession,
21004
21004
  pairTmuxSession,
21005
21005
  finalizeClaudePairOnboarding
21006
- } = await import("../claude-pair-runtime-NNHCDXLG.js");
21006
+ } = await import("../claude-pair-runtime-SY5VCTCE.js");
21007
21007
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
21008
21008
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
21009
21009
  const killed = await killPairSession(pairTmuxSession(pairId));
@@ -35092,13 +35092,16 @@ function parseResetDateTime(humanDate, now) {
35092
35092
 
35093
35093
  // ../core/dist/claude-code-usage/banner-parser.js
35094
35094
  var SESSION_QUALIFIER = /^(?:session|\d{1,2}\s*-?\s*hours?|\d{1,2}h)$/i;
35095
- var SPEND_QUALIFIER = /^(?:individual\s+)?spend$/i;
35095
+ var SPEND_QUALIFIER = /^(?:individual\s+)?(?:(?:daily|weekly|monthly)\s+)?spend$/i;
35096
+ var ORG_SPEND_QUALIFIER = /^(?:your\s+)?(?:org|organi[sz]ation|team|workspace)['’]s\s+(?:(?:daily|weekly|monthly)\s+)?spend$/i;
35096
35097
  function classifyLimitScope(qualifier) {
35097
35098
  const q = (qualifier ?? "").trim();
35098
35099
  if (!q)
35099
35100
  return "weekly";
35100
35101
  if (SESSION_QUALIFIER.test(q))
35101
35102
  return "session";
35103
+ if (ORG_SPEND_QUALIFIER.test(q))
35104
+ return "org_spend";
35102
35105
  if (SPEND_QUALIFIER.test(q))
35103
35106
  return "spend";
35104
35107
  if (/^weekly$/i.test(q))
@@ -35135,7 +35138,13 @@ var BANNER_PATTERNS = [
35135
35138
  // "limit". This parser runs over agent-produced pane.log tails, so that is
35136
35139
  // reachable input. Requiring at least one separator per extra token, and
35137
35140
  // bounding the count, keeps it linear.
35138
- `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9]+(?:[\\s-]+[a-z0-9]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
35141
+ //
35142
+ // ENG-10474: the token class admits an apostrophe (both ' and ’). It was
35143
+ // `[a-z0-9]`, so ANY possessive qualifier ("org's monthly", "team's") made
35144
+ // the whole banner unmatchable, not merely unclassified: null, no
35145
+ // observation, no alert. The apostrophe cannot match empty, so the
35146
+ // at-least-one-separator shape above still keeps this linear.
35147
+ `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9'\u2019]+(?:[\\s-]+[a-z0-9'\u2019]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
35139
35148
  "i"
35140
35149
  ),
35141
35150
  // ENG-10365 — the Claude Team seat's INDIVIDUAL SPEND LIMIT.
@@ -35188,7 +35197,23 @@ var BANNER_PATTERNS = [
35188
35197
  // monitor's auto-close, and synthesising an instant we never read is the exact
35189
35198
  // confident-and-false reporting ENG-8901 exists to stop. Every spend banner
35190
35199
  // observed so far carries one.
35191
- new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:individual\\s+)?spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
35200
+ //
35201
+ // ENG-10474 — THE CAPTURE NO LONGER GATES THE CLASSIFIER. It admitted exactly
35202
+ // `spend` or `individual spend`, so "You've hit your org's monthly spend
35203
+ // limit" failed here as well as in both patterns above and alyve's agents went
35204
+ // dark with every platform signal green. The capture now takes up to three
35205
+ // qualifier tokens (apostrophes allowed) in front of `spend`, and
35206
+ // `classifyLimitScope` decides what they mean, which is how pattern 2 already
35207
+ // worked.
35208
+ //
35209
+ // `spend` STAYS THE ANCHOR, and that is the design decision. Making this
35210
+ // capture as open as pattern 2's would turn it into "any saturated banner with
35211
+ // up to 160 characters before `resets`", so "You've hit your weekly limit"
35212
+ // followed by pane prose and an unrelated "resets Nov 28" would start
35213
+ // producing observations — the false-pairing surface the 160-character bound
35214
+ // exists to fence. Pattern 2 keeps its adjacency rule for every non-spend
35215
+ // qualifier; this pattern only widens WHICH spend. Pinned by a test either way.
35216
+ new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:[a-z0-9'\u2019]+[\\s-]+){0,3}spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
35192
35217
  ];
35193
35218
  function parseUsageBanner(text, now = /* @__PURE__ */ new Date()) {
35194
35219
  let bestIndex = -1;
package/dist/mcp/index.js CHANGED
@@ -27761,7 +27761,13 @@ var BANNER_PATTERNS = [
27761
27761
  // "limit". This parser runs over agent-produced pane.log tails, so that is
27762
27762
  // reachable input. Requiring at least one separator per extra token, and
27763
27763
  // bounding the count, keeps it linear.
27764
- `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9]+(?:[\\s-]+[a-z0-9]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
27764
+ //
27765
+ // ENG-10474: the token class admits an apostrophe (both ' and ’). It was
27766
+ // `[a-z0-9]`, so ANY possessive qualifier ("org's monthly", "team's") made
27767
+ // the whole banner unmatchable, not merely unclassified: null, no
27768
+ // observation, no alert. The apostrophe cannot match empty, so the
27769
+ // at-least-one-separator shape above still keeps this linear.
27770
+ `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9'\u2019]+(?:[\\s-]+[a-z0-9'\u2019]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
27765
27771
  "i"
27766
27772
  ),
27767
27773
  // ENG-10365 — the Claude Team seat's INDIVIDUAL SPEND LIMIT.
@@ -27814,7 +27820,23 @@ var BANNER_PATTERNS = [
27814
27820
  // monitor's auto-close, and synthesising an instant we never read is the exact
27815
27821
  // confident-and-false reporting ENG-8901 exists to stop. Every spend banner
27816
27822
  // observed so far carries one.
27817
- new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:individual\\s+)?spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
27823
+ //
27824
+ // ENG-10474 — THE CAPTURE NO LONGER GATES THE CLASSIFIER. It admitted exactly
27825
+ // `spend` or `individual spend`, so "You've hit your org's monthly spend
27826
+ // limit" failed here as well as in both patterns above and alyve's agents went
27827
+ // dark with every platform signal green. The capture now takes up to three
27828
+ // qualifier tokens (apostrophes allowed) in front of `spend`, and
27829
+ // `classifyLimitScope` decides what they mean, which is how pattern 2 already
27830
+ // worked.
27831
+ //
27832
+ // `spend` STAYS THE ANCHOR, and that is the design decision. Making this
27833
+ // capture as open as pattern 2's would turn it into "any saturated banner with
27834
+ // up to 160 characters before `resets`", so "You've hit your weekly limit"
27835
+ // followed by pane prose and an unrelated "resets Nov 28" would start
27836
+ // producing observations — the false-pairing surface the 160-character bound
27837
+ // exists to fence. Pattern 2 keeps its adjacency rule for every non-spend
27838
+ // qualifier; this pattern only widens WHICH spend. Pinned by a test either way.
27839
+ new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:[a-z0-9'\u2019]+[\\s-]+){0,3}spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
27818
27840
  ];
27819
27841
 
27820
27842
  // ../core/dist/claude-code-usage/run-marker.js
@@ -48514,7 +48536,7 @@ server.tool(
48514
48536
  );
48515
48537
  server.tool(
48516
48538
  "routine_list",
48517
- "List the routines YOU own - the recurring responsibilities a human accepted on your behalf. Call this FIRST whenever you need a routine_id: routine_revise and submit_routine_output both require one, and this is the only tool that produces it. Each entry gives you the routine_id, its name and status, the schedule it runs on (id, cron, timezone), its version, whether it owes a submit_routine_output call, and its FULL current prompt. The prompt is complete and untruncated on purpose: routine_revise replaces a routine's substance wholesale, so anything missing from what you read is missing from what you write. Read it here before revising rather than reconstructing it from memory. Returns an empty list if you own none - that is an answer, not an error.",
48539
+ "List the routines YOU own - the recurring responsibilities a human accepted on your behalf. Call this FIRST whenever you need a routine_id: routine_revise and submit_routine_output both require one, and this is the only tool that produces it. Each entry gives you the routine_id, its name and status, the schedule it runs on (id, cron, timezone), its version, whether it owes a submit_routine_output call, whether a revision you proposed is still waiting on your manager (ENG-10494 - one is pending until they resolve it, and it blocks every further revision of that routine), and its FULL current prompt. The prompt is complete and untruncated on purpose: routine_revise replaces a routine's substance wholesale, so anything missing from what you read is missing from what you write. Read it here before revising rather than reconstructing it from memory. Returns an empty list if you own none - that is an answer, not an error.",
48518
48540
  {},
48519
48541
  async () => {
48520
48542
  try {
@@ -48537,9 +48559,13 @@ server.tool(
48537
48559
  const when = r.cron ? `cron ${r.cron}` : r.every ? `every ${r.every}` : "no schedule";
48538
48560
  const tz = r.timezone ? ` ${r.timezone}` : "";
48539
48561
  const gated = r.requires_output_approval ? "\n approval: REQUIRED \u2014 call submit_routine_output instead of delivering directly" : "";
48562
+ const pending = r.pending_revision ? `
48563
+ revision pending since ${r.pending_revision.proposed_at}` + (r.pending_revision.summary ? ` \u2014 "${r.pending_revision.summary}"` : "") + (r.pending_revision.snoozed_until ? `
48564
+ your manager chose "not now"; the card re-surfaces ${r.pending_revision.snoozed_until}` : "") + (r.pending_revision.review_url ? `
48565
+ review: ${r.pending_revision.review_url}` : "") + "\n You cannot propose another revision of this routine until they resolve it. Call routine_proposal_status to read what is waiting." : "";
48540
48566
  return `\u2022 ${r.name} [${r.status}] v${r.version}
48541
48567
  routine_id: ${r.routine_id}
48542
- schedule_id: ${r.schedule_id ?? "(none \u2014 this routine has no runtime row)"} (${when}${tz})${gated}
48568
+ schedule_id: ${r.schedule_id ?? "(none \u2014 this routine has no runtime row)"} (${when}${tz})${gated}${pending}
48543
48569
  prompt:
48544
48570
  ${r.prompt ?? "(none)"}`;
48545
48571
  }).join("\n\n");
@@ -48589,11 +48615,14 @@ server.tool(
48589
48615
  content: [{ type: "text", text: data.error ?? data.message ?? "Could not propose the revision." }]
48590
48616
  };
48591
48617
  }
48618
+ const link = data.review_url ? `
48619
+
48620
+ Give your manager this link so they can accept or decline it: ${data.review_url}` : "";
48592
48621
  return {
48593
48622
  content: [
48594
48623
  {
48595
48624
  type: "text",
48596
- text: data.message ?? "Revision proposed. The live routine is unchanged until your manager accepts it."
48625
+ text: (data.message ?? "Revision proposed. The live routine is unchanged until your manager accepts it.") + link + "\n\nCheck the outcome later with routine_proposal_status \u2014 until they resolve this one you cannot propose another revision of this routine."
48597
48626
  }
48598
48627
  ]
48599
48628
  };
@@ -48612,6 +48641,74 @@ server.tool(
48612
48641
  }
48613
48642
  }
48614
48643
  );
48644
+ server.tool(
48645
+ "routine_proposal_status",
48646
+ "Read the outcome of the routine revisions YOU proposed with routine_revise: pending (your manager has not resolved it yet), accepted, declined, or expired - plus the summary and the FULL replacement description you submitted, so you can see what is actually waiting on them. A pending proposal also returns a review_url to hand to your manager, and is flagged `stale` when the routine has changed since you proposed (accepting it would apply a rewrite of text the routine no longer says - propose again against the current prompt instead). Call this when routine_list shows a revision pending, when routine_revise refuses because one already is, or to close the loop on a proposal you filed in an earlier session. No decline reason is recorded anywhere on this path, so a declined proposal tells you THAT it was declined and not why - ask the person rather than reporting a reason you do not have.",
48647
+ {
48648
+ routine_id: external_exports.string().trim().min(1).optional().describe("Optional: only proposals targeting this routine (the id from routine_list).")
48649
+ },
48650
+ async (params) => {
48651
+ try {
48652
+ const data = await apiPost(
48653
+ "/host/routines/proposals",
48654
+ {
48655
+ agent_id: AGT_AGENT_ID,
48656
+ ...params.routine_id ? { routine_id: params.routine_id } : {}
48657
+ },
48658
+ false,
48659
+ 15e3,
48660
+ "routine_proposal_status",
48661
+ true
48662
+ );
48663
+ if (!data.ok) {
48664
+ return { content: [{ type: "text", text: data.error ?? "Could not read your revision proposals." }] };
48665
+ }
48666
+ const proposals = data.proposals ?? [];
48667
+ if (proposals.length === 0) {
48668
+ return {
48669
+ content: [
48670
+ {
48671
+ type: "text",
48672
+ text: params.routine_id ? `You have proposed no revisions of routine ${params.routine_id}.` : "You have proposed no routine revisions."
48673
+ }
48674
+ ]
48675
+ };
48676
+ }
48677
+ const body = proposals.map((p2) => {
48678
+ const stale = p2.stale ? `
48679
+ STALE \u2014 the routine is now v${p2.current_version ?? "?"} but you proposed against v${p2.from_version ?? "?"}. Read the current prompt with routine_list and propose again.` : "";
48680
+ const snoozed = p2.snoozed_until ? `
48681
+ your manager chose "not now"; the card re-surfaces ${p2.snoozed_until}` : "";
48682
+ const resolved = p2.resolved_at ? `
48683
+ resolved: ${p2.resolved_at}` : "";
48684
+ const link = p2.review_url ? `
48685
+ review: ${p2.review_url}` : "";
48686
+ return `\u2022 ${p2.routine_name ?? p2.routine_id} \u2014 ${p2.state.toUpperCase()}${stale}
48687
+ routine_id: ${p2.routine_id}
48688
+ proposed: ${p2.proposed_at}${resolved}${snoozed}${link}
48689
+ summary: ${p2.summary ?? "(none)"}
48690
+ proposed description:
48691
+ ${p2.proposed_description ?? "(none)"}`;
48692
+ }).join("\n\n");
48693
+ return {
48694
+ content: [
48695
+ {
48696
+ type: "text",
48697
+ text: `${proposals.length} routine revision${proposals.length === 1 ? "" : "s"} you proposed:
48698
+
48699
+ ${body}` + (data.truncated ? "\n\nTHIS IS A PAGE, NOT THE WHOLE HISTORY \u2014 the read hit its row cap, so older proposals exist that are not listed. Pass routine_id to narrow it." : "") + "\n\nNo decline reason is captured on this path \u2014 a declined proposal records that it was declined, not why."
48700
+ }
48701
+ ]
48702
+ };
48703
+ } catch (err) {
48704
+ const msg = err instanceof Error ? err.message : String(err);
48705
+ return {
48706
+ content: [{ type: "text", text: `Could not read your revision proposals: ${msg}` }],
48707
+ isError: true
48708
+ };
48709
+ }
48710
+ }
48711
+ );
48615
48712
  server.tool(
48616
48713
  "submit_routine_output",
48617
48714
  "Submit a gated routine's drafted output for human approval. Use ONLY when your routine prompt says the routine is in its approval phase - the platform delivers the draft after sign-off; do not deliver it yourself.",
@@ -49498,6 +49595,9 @@ var LOCAL_TOOL_NAMES = /* @__PURE__ */ new Set([
49498
49595
  "routine_revise",
49499
49596
  // ENG-9431: the discovery primitive both of the above depend on.
49500
49597
  "routine_list",
49598
+ // ENG-10494: the verdict loop for routine_revise. Without it a proposed
49599
+ // revision is write-only, and an unseen one blocks that routine for good.
49600
+ "routine_proposal_status",
49501
49601
  // ENG-10276: knowledge_get is the untruncated single-entry read. The list and
49502
49602
  // search tools serve a stored SUMMARY column, not the entry, so this is the
49503
49603
  // only one that can be trusted before a knowledge_update.
@@ -41683,7 +41683,13 @@ var BANNER_PATTERNS = [
41683
41683
  // "limit". This parser runs over agent-produced pane.log tails, so that is
41684
41684
  // reachable input. Requiring at least one separator per extra token, and
41685
41685
  // bounding the count, keeps it linear.
41686
- `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9]+(?:[\\s-]+[a-z0-9]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
41686
+ //
41687
+ // ENG-10474: the token class admits an apostrophe (both ' and ’). It was
41688
+ // `[a-z0-9]`, so ANY possessive qualifier ("org's monthly", "team's") made
41689
+ // the whole banner unmatchable, not merely unclassified: null, no
41690
+ // observation, no alert. The apostrophe cannot match empty, so the
41691
+ // at-least-one-separator shape above still keeps this linear.
41692
+ `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9'\u2019]+(?:[\\s-]+[a-z0-9'\u2019]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
41687
41693
  "i"
41688
41694
  ),
41689
41695
  // ENG-10365 — the Claude Team seat's INDIVIDUAL SPEND LIMIT.
@@ -41736,7 +41742,23 @@ var BANNER_PATTERNS = [
41736
41742
  // monitor's auto-close, and synthesising an instant we never read is the exact
41737
41743
  // confident-and-false reporting ENG-8901 exists to stop. Every spend banner
41738
41744
  // observed so far carries one.
41739
- new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:individual\\s+)?spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
41745
+ //
41746
+ // ENG-10474 — THE CAPTURE NO LONGER GATES THE CLASSIFIER. It admitted exactly
41747
+ // `spend` or `individual spend`, so "You've hit your org's monthly spend
41748
+ // limit" failed here as well as in both patterns above and alyve's agents went
41749
+ // dark with every platform signal green. The capture now takes up to three
41750
+ // qualifier tokens (apostrophes allowed) in front of `spend`, and
41751
+ // `classifyLimitScope` decides what they mean, which is how pattern 2 already
41752
+ // worked.
41753
+ //
41754
+ // `spend` STAYS THE ANCHOR, and that is the design decision. Making this
41755
+ // capture as open as pattern 2's would turn it into "any saturated banner with
41756
+ // up to 160 characters before `resets`", so "You've hit your weekly limit"
41757
+ // followed by pane prose and an unrelated "resets Nov 28" would start
41758
+ // producing observations — the false-pairing surface the 160-character bound
41759
+ // exists to fence. Pattern 2 keeps its adjacency rule for every non-spend
41760
+ // qualifier; this pattern only widens WHICH spend. Pinned by a test either way.
41761
+ new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:[a-z0-9'\u2019]+[\\s-]+){0,3}spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
41740
41762
  ];
41741
41763
 
41742
41764
  // ../core/dist/claude-code-usage/run-marker.js
@@ -35965,13 +35965,16 @@ function parseResetDateTime(humanDate, now) {
35965
35965
 
35966
35966
  // ../core/dist/claude-code-usage/banner-parser.js
35967
35967
  var SESSION_QUALIFIER = /^(?:session|\d{1,2}\s*-?\s*hours?|\d{1,2}h)$/i;
35968
- var SPEND_QUALIFIER = /^(?:individual\s+)?spend$/i;
35968
+ var SPEND_QUALIFIER = /^(?:individual\s+)?(?:(?:daily|weekly|monthly)\s+)?spend$/i;
35969
+ var ORG_SPEND_QUALIFIER = /^(?:your\s+)?(?:org|organi[sz]ation|team|workspace)['’]s\s+(?:(?:daily|weekly|monthly)\s+)?spend$/i;
35969
35970
  function classifyLimitScope(qualifier) {
35970
35971
  const q = (qualifier ?? "").trim();
35971
35972
  if (!q)
35972
35973
  return "weekly";
35973
35974
  if (SESSION_QUALIFIER.test(q))
35974
35975
  return "session";
35976
+ if (ORG_SPEND_QUALIFIER.test(q))
35977
+ return "org_spend";
35975
35978
  if (SPEND_QUALIFIER.test(q))
35976
35979
  return "spend";
35977
35980
  if (/^weekly$/i.test(q))
@@ -36008,7 +36011,13 @@ var BANNER_PATTERNS = [
36008
36011
  // "limit". This parser runs over agent-produced pane.log tails, so that is
36009
36012
  // reachable input. Requiring at least one separator per extra token, and
36010
36013
  // bounding the count, keeps it linear.
36011
- `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9]+(?:[\\s-]+[a-z0-9]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
36014
+ //
36015
+ // ENG-10474: the token class admits an apostrophe (both ' and ’). It was
36016
+ // `[a-z0-9]`, so ANY possessive qualifier ("org's monthly", "team's") made
36017
+ // the whole banner unmatchable, not merely unclassified: null, no
36018
+ // observation, no alert. The apostrophe cannot match empty, so the
36019
+ // at-least-one-separator shape above still keeps this linear.
36020
+ `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9'\u2019]+(?:[\\s-]+[a-z0-9'\u2019]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
36012
36021
  "i"
36013
36022
  ),
36014
36023
  // ENG-10365 — the Claude Team seat's INDIVIDUAL SPEND LIMIT.
@@ -36061,7 +36070,23 @@ var BANNER_PATTERNS = [
36061
36070
  // monitor's auto-close, and synthesising an instant we never read is the exact
36062
36071
  // confident-and-false reporting ENG-8901 exists to stop. Every spend banner
36063
36072
  // observed so far carries one.
36064
- new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:individual\\s+)?spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
36073
+ //
36074
+ // ENG-10474 — THE CAPTURE NO LONGER GATES THE CLASSIFIER. It admitted exactly
36075
+ // `spend` or `individual spend`, so "You've hit your org's monthly spend
36076
+ // limit" failed here as well as in both patterns above and alyve's agents went
36077
+ // dark with every platform signal green. The capture now takes up to three
36078
+ // qualifier tokens (apostrophes allowed) in front of `spend`, and
36079
+ // `classifyLimitScope` decides what they mean, which is how pattern 2 already
36080
+ // worked.
36081
+ //
36082
+ // `spend` STAYS THE ANCHOR, and that is the design decision. Making this
36083
+ // capture as open as pattern 2's would turn it into "any saturated banner with
36084
+ // up to 160 characters before `resets`", so "You've hit your weekly limit"
36085
+ // followed by pane prose and an unrelated "resets Nov 28" would start
36086
+ // producing observations — the false-pairing surface the 160-character bound
36087
+ // exists to fence. Pattern 2 keeps its adjacency rule for every non-spend
36088
+ // qualifier; this pattern only widens WHICH spend. Pinned by a test either way.
36089
+ new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:[a-z0-9'\u2019]+[\\s-]+){0,3}spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
36065
36090
  ];
36066
36091
  function parseUsageBanner(text, now = /* @__PURE__ */ new Date()) {
36067
36092
  let bestIndex = -1;
@@ -36001,13 +36001,16 @@ function parseResetDateTime(humanDate, now) {
36001
36001
 
36002
36002
  // ../core/dist/claude-code-usage/banner-parser.js
36003
36003
  var SESSION_QUALIFIER = /^(?:session|\d{1,2}\s*-?\s*hours?|\d{1,2}h)$/i;
36004
- var SPEND_QUALIFIER = /^(?:individual\s+)?spend$/i;
36004
+ var SPEND_QUALIFIER = /^(?:individual\s+)?(?:(?:daily|weekly|monthly)\s+)?spend$/i;
36005
+ var ORG_SPEND_QUALIFIER = /^(?:your\s+)?(?:org|organi[sz]ation|team|workspace)['’]s\s+(?:(?:daily|weekly|monthly)\s+)?spend$/i;
36005
36006
  function classifyLimitScope(qualifier) {
36006
36007
  const q = (qualifier ?? "").trim();
36007
36008
  if (!q)
36008
36009
  return "weekly";
36009
36010
  if (SESSION_QUALIFIER.test(q))
36010
36011
  return "session";
36012
+ if (ORG_SPEND_QUALIFIER.test(q))
36013
+ return "org_spend";
36011
36014
  if (SPEND_QUALIFIER.test(q))
36012
36015
  return "spend";
36013
36016
  if (/^weekly$/i.test(q))
@@ -36044,7 +36047,13 @@ var BANNER_PATTERNS = [
36044
36047
  // "limit". This parser runs over agent-produced pane.log tails, so that is
36045
36048
  // reachable input. Requiring at least one separator per extra token, and
36046
36049
  // bounding the count, keeps it linear.
36047
- `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9]+(?:[\\s-]+[a-z0-9]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
36050
+ //
36051
+ // ENG-10474: the token class admits an apostrophe (both ' and ’). It was
36052
+ // `[a-z0-9]`, so ANY possessive qualifier ("org's monthly", "team's") made
36053
+ // the whole banner unmatchable, not merely unclassified: null, no
36054
+ // observation, no alert. The apostrophe cannot match empty, so the
36055
+ // at-least-one-separator shape above still keeps this linear.
36056
+ `${SUBJECT}hit\\s+your\\s+(?<qualifier>[a-z0-9'\u2019]+(?:[\\s-]+[a-z0-9'\u2019]+){0,3}\\s+)?limit${SEP}resets\\s+(?<reset>${RESET_DATE})`,
36048
36057
  "i"
36049
36058
  ),
36050
36059
  // ENG-10365 — the Claude Team seat's INDIVIDUAL SPEND LIMIT.
@@ -36097,7 +36106,23 @@ var BANNER_PATTERNS = [
36097
36106
  // monitor's auto-close, and synthesising an instant we never read is the exact
36098
36107
  // confident-and-false reporting ENG-8901 exists to stop. Every spend banner
36099
36108
  // observed so far carries one.
36100
- new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:individual\\s+)?spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
36109
+ //
36110
+ // ENG-10474 — THE CAPTURE NO LONGER GATES THE CLASSIFIER. It admitted exactly
36111
+ // `spend` or `individual spend`, so "You've hit your org's monthly spend
36112
+ // limit" failed here as well as in both patterns above and alyve's agents went
36113
+ // dark with every platform signal green. The capture now takes up to three
36114
+ // qualifier tokens (apostrophes allowed) in front of `spend`, and
36115
+ // `classifyLimitScope` decides what they mean, which is how pattern 2 already
36116
+ // worked.
36117
+ //
36118
+ // `spend` STAYS THE ANCHOR, and that is the design decision. Making this
36119
+ // capture as open as pattern 2's would turn it into "any saturated banner with
36120
+ // up to 160 characters before `resets`", so "You've hit your weekly limit"
36121
+ // followed by pane prose and an unrelated "resets Nov 28" would start
36122
+ // producing observations — the false-pairing surface the 160-character bound
36123
+ // exists to fence. Pattern 2 keeps its adjacency rule for every non-spend
36124
+ // qualifier; this pattern only widens WHICH spend. Pinned by a test either way.
36125
+ new RegExp(`${SUBJECT}hit\\s+your\\s+(?<qualifier>(?:[a-z0-9'\u2019]+[\\s-]+){0,3}spend)\\s+limit[\\s\\S]{0,160}?resets\\s+(?<reset>${RESET_DATE})`, "i")
36101
36126
  ];
36102
36127
  function parseUsageBanner(text, now = /* @__PURE__ */ new Date()) {
36103
36128
  let bestIndex = -1;
@@ -62,8 +62,8 @@ import {
62
62
  writeDirectChatSessionState,
63
63
  writeEgressAllowlist,
64
64
  writePersistentClaudeWrapper
65
- } from "./chunk-NGJFE75J.js";
66
- import "./chunk-ZOPP3ZC7.js";
65
+ } from "./chunk-BLEV5PAO.js";
66
+ import "./chunk-VHO7KAB7.js";
67
67
  import "./chunk-XWVM4KPK.js";
68
68
  export {
69
69
  CLAUDE_HOST_WIDE,
@@ -130,4 +130,4 @@ export {
130
130
  writeEgressAllowlist,
131
131
  writePersistentClaudeWrapper
132
132
  };
133
- //# sourceMappingURL=persistent-session-OQRYZ3YP.js.map
133
+ //# sourceMappingURL=persistent-session-UEAJJ72P.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-NGJFE75J.js";
4
- import "./chunk-ZOPP3ZC7.js";
3
+ } from "./chunk-BLEV5PAO.js";
4
+ import "./chunk-VHO7KAB7.js";
5
5
  import "./chunk-XWVM4KPK.js";
6
6
 
7
7
  // src/lib/responsiveness-probe.ts
@@ -799,4 +799,4 @@ export {
799
799
  readAndResetSlackReplyBindingClassifications,
800
800
  readAndResetSlackReplyTargetClassifications
801
801
  };
802
- //# sourceMappingURL=responsiveness-probe-RBWP6HJE.js.map
802
+ //# sourceMappingURL=responsiveness-probe-VXJKA6RF.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionTranscriptDir
3
- } from "./chunk-ZOPP3ZC7.js";
3
+ } from "./chunk-VHO7KAB7.js";
4
4
 
5
5
  // src/lib/session-auth-dead.ts
6
6
  import { closeSync, openSync, readSync, readdirSync, statSync } from "fs";
@@ -203,4 +203,4 @@ export {
203
203
  decideSessionAuthState,
204
204
  probeSessionAuth
205
205
  };
206
- //# sourceMappingURL=session-auth-dead-4KUPTD7M.js.map
206
+ //# sourceMappingURL=session-auth-dead-XOP4XYBD.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.921",
3
+ "version": "0.28.923",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {