@integrity-labs/agt-cli 0.28.657 → 0.28.659

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-2W67OQUH.js");
103
+ const { resolveClaudeBinary } = await import("./persistent-session-QVLWWFK3.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-3CYAS6UC.js.map
376
+ //# sourceMappingURL=claude-pair-runtime-ODPRSH45.js.map
@@ -55,7 +55,7 @@ import {
55
55
  safeWriteJsonAtomic,
56
56
  setConfigHash,
57
57
  tripClass
58
- } from "../chunk-PHIFSHBS.js";
58
+ } from "../chunk-ACQZZIK6.js";
59
59
  import {
60
60
  getProjectDir as getProjectDir2,
61
61
  getReadyTasks,
@@ -111,7 +111,7 @@ import {
111
111
  takeZombieDetection,
112
112
  toOpencodeModel,
113
113
  writeEgressAllowlist
114
- } from "../chunk-OBIOE63G.js";
114
+ } from "../chunk-LUIGOCT4.js";
115
115
  import {
116
116
  ACCOUNT_ENFORCEMENT_MARKER_FILENAME,
117
117
  AnchorSessionClient,
@@ -192,7 +192,7 @@ import {
192
192
  subagentActivityAgeSeconds,
193
193
  sumTranscriptUsageInWindow,
194
194
  transcriptActivityAgeSeconds
195
- } from "../chunk-XUJIPPKF.js";
195
+ } from "../chunk-NLCHGFD5.js";
196
196
  import {
197
197
  reapOrphanChannelMcps
198
198
  } from "../chunk-XWVM4KPK.js";
@@ -11609,7 +11609,7 @@ var pendingDayRolloverReset = /* @__PURE__ */ new Set();
11609
11609
  var dayRolloverInboundHold = /* @__PURE__ */ new Map();
11610
11610
  var INBOUND_HOLD_EPISODE_GAP_MS = 12e4;
11611
11611
  async function channelInboundActivityAgeSecondsFor(codeName) {
11612
- const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-44G7U6LR.js");
11612
+ const { newestPendingInboundActivityMtimeMs } = await import("../responsiveness-probe-NN4BNX2V.js");
11613
11613
  const newest = newestPendingInboundActivityMtimeMs(dirname10(paneLogPath(codeName)));
11614
11614
  if (newest === null) return null;
11615
11615
  return Math.max(0, Math.floor((Date.now() - newest) / 1e3));
@@ -12252,7 +12252,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
12252
12252
  var lastVersionCheckAt = 0;
12253
12253
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
12254
12254
  var lastResponsivenessProbeAt = 0;
12255
- var agtCliVersion = true ? "0.28.657" : "dev";
12255
+ var agtCliVersion = true ? "0.28.659" : "dev";
12256
12256
  function resolveBrewPath(execFileSync3) {
12257
12257
  try {
12258
12258
  const out = execFileSync3("which", ["brew"], { timeout: 5e3 }).toString().trim();
@@ -13562,7 +13562,7 @@ function flushRestartedAgentDiagnostics(hostId, codeNames) {
13562
13562
  if (codeNames.length === 0) return;
13563
13563
  void (async () => {
13564
13564
  try {
13565
- const { collectDiagnostics } = await import("../persistent-session-2W67OQUH.js");
13565
+ const { collectDiagnostics } = await import("../persistent-session-QVLWWFK3.js");
13566
13566
  await api.post("/host/heartbeat", {
13567
13567
  host_id: hostId,
13568
13568
  agent_diagnostics: collectDiagnostics(codeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -13678,7 +13678,7 @@ async function pollCycleInner() {
13678
13678
  }
13679
13679
  try {
13680
13680
  const { detectHostSecurity } = await import("../host-security-6PDFG7F5.js");
13681
- const { collectDiagnostics } = await import("../persistent-session-2W67OQUH.js");
13681
+ const { collectDiagnostics } = await import("../persistent-session-QVLWWFK3.js");
13682
13682
  const diagCodeNames = [...agentState.persistentSessionAgents];
13683
13683
  const agentDiagnostics = diagCodeNames.length > 0 ? collectDiagnostics(diagCodeNames, quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor) : void 0;
13684
13684
  let tailscaleHostname;
@@ -13801,7 +13801,7 @@ async function pollCycleInner() {
13801
13801
  collectPanelessActivityProbes,
13802
13802
  getResponsivenessIntervalMs,
13803
13803
  occupancyQualificationClassifications
13804
- } = await import("../responsiveness-probe-44G7U6LR.js");
13804
+ } = await import("../responsiveness-probe-NN4BNX2V.js");
13805
13805
  const probeIntervalMs = getResponsivenessIntervalMs();
13806
13806
  if (now - lastResponsivenessProbeAt > probeIntervalMs) {
13807
13807
  const probeCodeNames = [...agentState.persistentSessionAgents];
@@ -13911,7 +13911,7 @@ async function pollCycleInner() {
13911
13911
  collectResponsivenessProbes,
13912
13912
  livePendingInboundOldestAgeSeconds,
13913
13913
  parkPendingInbound
13914
- } = await import("../responsiveness-probe-44G7U6LR.js");
13914
+ } = await import("../responsiveness-probe-NN4BNX2V.js");
13915
13915
  const { getProjectDir: wedgeProjectDir } = await import("../scheduler-engine-NDP36U7O.js");
13916
13916
  const wedgeNow = /* @__PURE__ */ new Date();
13917
13917
  const liveAgents = agentState.persistentSessionAgents;
@@ -14067,7 +14067,7 @@ async function pollCycleInner() {
14067
14067
  }
14068
14068
  try {
14069
14069
  const { scrapeMcpFailedBannerCount } = await import("../pane-mcp-banner-scraper-JA437JIB.js");
14070
- const { probeSessionAuth } = await import("../session-auth-dead-TNWKYZIA.js");
14070
+ const { probeSessionAuth } = await import("../session-auth-dead-KD66YN7O.js");
14071
14071
  const observations = [];
14072
14072
  const pendingCacheCommits = [];
14073
14073
  const modelApiErrorReportingOn = hostFlagStore().getBoolean("model-api-error-reporting");
@@ -17700,7 +17700,7 @@ async function handleRestartDoorbell(agentId, requestedAt, restartReason) {
17700
17700
  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}`));
17701
17701
  void (async () => {
17702
17702
  try {
17703
- const { collectDiagnostics } = await import("../persistent-session-2W67OQUH.js");
17703
+ const { collectDiagnostics } = await import("../persistent-session-QVLWWFK3.js");
17704
17704
  await api.post("/host/heartbeat", {
17705
17705
  host_id: hostId,
17706
17706
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -17751,7 +17751,7 @@ async function respawnAgentAfterMcpStop(codeName, reason) {
17751
17751
  }
17752
17752
  try {
17753
17753
  const hostId = await getHostId();
17754
- const { collectDiagnostics } = await import("../persistent-session-2W67OQUH.js");
17754
+ const { collectDiagnostics } = await import("../persistent-session-QVLWWFK3.js");
17755
17755
  await api.post("/host/heartbeat", {
17756
17756
  host_id: hostId,
17757
17757
  agent_diagnostics: collectDiagnostics([codeName], quarantineEntriesFor, claudeMdSizeFor, spawnOutcomeForDiagnostics, pidPressureFor)
@@ -18348,7 +18348,7 @@ async function processClaudePairSessions(agents) {
18348
18348
  killPairSession,
18349
18349
  pairTmuxSession,
18350
18350
  finalizeClaudePairOnboarding
18351
- } = await import("../claude-pair-runtime-3CYAS6UC.js");
18351
+ } = await import("../claude-pair-runtime-ODPRSH45.js");
18352
18352
  for (const pairId of pendingResp.cancelled_pair_ids ?? []) {
18353
18353
  log(`[claude-pair] sweeping orphan tmux session for pair ${pairId.slice(0, 8)}`);
18354
18354
  const killed = await killPairSession(pairTmuxSession(pairId));
package/dist/mcp/index.js CHANGED
@@ -22373,7 +22373,6 @@ import {
22373
22373
  closeSync,
22374
22374
  constants as fsConstants,
22375
22375
  fstatSync,
22376
- lstatSync,
22377
22376
  openSync,
22378
22377
  readSync,
22379
22378
  realpathSync
@@ -22425,22 +22424,66 @@ function hostFilePathArg(toolName) {
22425
22424
  function allowedHostFileRoots(agentDir, path) {
22426
22425
  return [path.join(agentDir, "project"), path.join(agentDir, "scratch")];
22427
22426
  }
22428
- function assertNoSymlinkComponents(target, roots, path, fs) {
22427
+ function openContained(target, roots, opts) {
22428
+ const { path, fs, procFdBase } = opts;
22429
+ if (!procFdBase) {
22430
+ return {
22431
+ ok: false,
22432
+ error: "reading a host path needs descriptor-relative traversal (procfs), which this platform does not provide. Pass the content inline instead."
22433
+ };
22434
+ }
22429
22435
  const root = roots.find((r) => isContained(target, r, path.sep));
22430
- if (!root) return "resolved outside the allowed roots";
22431
- const rest = target.slice(root.length).split(path.sep).filter(Boolean);
22432
- let current = root;
22433
- for (const segment of rest) {
22434
- current = path.join(current, segment);
22435
- try {
22436
- if (fs.lstatSync(current).isSymbolicLink()) {
22437
- return `'${current}' is a symlink, so the path changed after it was resolved`;
22436
+ if (!root) return { ok: false, error: "resolved outside the allowed roots" };
22437
+ const segments = target.slice(root.length).split(path.sep).filter(Boolean);
22438
+ const fileName = segments[segments.length - 1];
22439
+ if (fileName === void 0) {
22440
+ return { ok: false, error: `'${target}' is a directory, not a file` };
22441
+ }
22442
+ for (const segment of segments) {
22443
+ if (segment === "." || segment === "..") {
22444
+ return { ok: false, error: `'${target}' contains a '${segment}' component` };
22445
+ }
22446
+ }
22447
+ const dirNames = segments.slice(0, -1);
22448
+ let dirFd;
22449
+ try {
22450
+ dirFd = fs.openSync(root, opts.dirOpenFlags);
22451
+ } catch (err) {
22452
+ return { ok: false, error: `the allowed root '${root}' could not be opened \u2014 ${err.message}` };
22453
+ }
22454
+ try {
22455
+ for (const name of dirNames) {
22456
+ let nextFd;
22457
+ try {
22458
+ nextFd = fs.openSync(path.join(procFdBase, String(dirFd), name), opts.dirOpenFlags);
22459
+ } catch (err) {
22460
+ return {
22461
+ ok: false,
22462
+ error: `'${name}' could not be entered \u2014 it is a symlink, is not a directory, or was replaced mid-traversal (${err.message})`
22463
+ };
22464
+ }
22465
+ try {
22466
+ fs.closeSync(dirFd);
22467
+ } catch {
22438
22468
  }
22469
+ dirFd = nextFd;
22470
+ }
22471
+ let fd;
22472
+ try {
22473
+ fd = fs.openSync(path.join(procFdBase, String(dirFd), fileName), opts.openFlags);
22474
+ } catch (err) {
22475
+ return {
22476
+ ok: false,
22477
+ error: `'${fileName}' could not be opened (a symlink, or replaced mid-check) \u2014 ${err.message}`
22478
+ };
22479
+ }
22480
+ return { ok: true, fd };
22481
+ } finally {
22482
+ try {
22483
+ fs.closeSync(dirFd);
22439
22484
  } catch {
22440
- return `'${current}' could not be inspected`;
22441
22485
  }
22442
22486
  }
22443
- return null;
22444
22487
  }
22445
22488
  function isContained(candidate, root, sep2) {
22446
22489
  if (candidate === root) return true;
@@ -22483,24 +22526,9 @@ function resolveHostFilePath(toolName, args, opts) {
22483
22526
  error: `${spec.pathArg} resolves outside your own directories (symlink?). '${raw}' \u2192 '${real}'. Allowed: ${roots.join(", ")}`
22484
22527
  };
22485
22528
  }
22486
- const walkError = assertNoSymlinkComponents(real, roots, path, fs);
22487
- if (walkError) return { ok: false, error: `${spec.pathArg}: ${walkError}` };
22488
- let vetted = null;
22489
- try {
22490
- vetted = fs.lstatSync(real);
22491
- } catch {
22492
- return { ok: false, error: `${spec.pathArg} vanished before it could be read: '${raw}'` };
22493
- }
22494
- let fd;
22495
- try {
22496
- fd = fs.openSync(real, opts.openFlags);
22497
- } catch (err) {
22498
- const msg = err.message;
22499
- return {
22500
- ok: false,
22501
- error: `${spec.pathArg} could not be opened (it may have been replaced mid-check): '${raw}' \u2014 ${msg}`
22502
- };
22503
- }
22529
+ const opened = openContained(real, roots, opts);
22530
+ if (!opened.ok) return { ok: false, error: `${spec.pathArg}: ${opened.error} ('${raw}')` };
22531
+ const fd = opened.fd;
22504
22532
  try {
22505
22533
  let stat;
22506
22534
  try {
@@ -22511,14 +22539,6 @@ function resolveHostFilePath(toolName, args, opts) {
22511
22539
  if (!stat.isFile()) {
22512
22540
  return { ok: false, error: `${spec.pathArg} is not a regular file: '${raw}'` };
22513
22541
  }
22514
- if (stat.ino !== void 0 && vetted.ino !== void 0) {
22515
- if (stat.ino !== vetted.ino || stat.dev !== vetted.dev) {
22516
- return {
22517
- ok: false,
22518
- error: `${spec.pathArg} changed identity between the check and the open (it was replaced): '${raw}'`
22519
- };
22520
- }
22521
- }
22522
22542
  if (stat.size > maxBytes) {
22523
22543
  return {
22524
22544
  ok: false,
@@ -44409,6 +44429,48 @@ server.tool(
44409
44429
  }
44410
44430
  }
44411
44431
  );
44432
+ server.tool(
44433
+ "kanban_reopen",
44434
+ "Send a card you assigned back to the agent that did the work, WITH your review feedback. Use this when you delegated a task with kanban_assign, the agent completed it, and you reviewed the delivery and found something wrong. The card reopens on THEIR board in todo carrying your feedback, and they are notified on the card itself. This is the only way your review reaches them: their card is not on your board, so notes you write on your own card are invisible to them, and opening a NEW card reframes a correction as fresh work and loses the link to what it corrected. You may only reopen a card YOU assigned, to somebody else, on your own team, that is finished (done or failed). You cannot reopen your own card this way - that would be a way around the rule that closed work stays closed; open a new card instead. BOUNDED: two reopens per card. A third means the two of you have not converged, and the answer to that is a human, not another round - raise it with yours.",
44435
+ {
44436
+ item_id: external_exports.string().describe("The card id, as returned by kanban_assign or shown on the completion notice."),
44437
+ feedback: external_exports.string().trim().min(1, "feedback is required - a reopen with nothing attached just deletes a result nobody replaced").max(2e3, "feedback is too long - send the correction (the criterion, the file, the behaviour), not the whole review").describe(
44438
+ 'What is wrong and what you want changed. REQUIRED and it must say something - a reopen with nothing attached just deletes a result nobody replaced. Be specific enough to act on: name the acceptance criterion, the file, or the behaviour, not "needs work".'
44439
+ )
44440
+ },
44441
+ async (params) => {
44442
+ try {
44443
+ const data = await apiPost("/host/kanban/reopen", {
44444
+ agent_id: AGT_AGENT_ID,
44445
+ item_id: params.item_id,
44446
+ feedback: params.feedback
44447
+ });
44448
+ if (!data?.ok) {
44449
+ const detail = data?.error ?? `Could not reopen that card (${data?.reason ?? "unknown"}).`;
44450
+ return { content: [{ type: "text", text: detail }] };
44451
+ }
44452
+ const turns = data.is_last_turn ? " That was the last reopen this card allows - if it comes back wrong again, raise it with a human rather than sending it back." : "";
44453
+ const delivery = data.notified ? " They have been notified on the card." : ` NOTE: the card reopened but the notification did NOT go out${data.notify_error ? ` (${data.notify_error})` : ""}, so they may not know yet - tell them directly.`;
44454
+ return {
44455
+ content: [
44456
+ {
44457
+ type: "text",
44458
+ text: `Reopened "${data.title ?? params.item_id}" on the assignee's board with your feedback (reopen ${data.reopen_count ?? 1} of 2).${delivery}${turns}`
44459
+ }
44460
+ ]
44461
+ };
44462
+ } catch (err) {
44463
+ return {
44464
+ content: [
44465
+ {
44466
+ type: "text",
44467
+ text: `Could not reopen that card: ${err instanceof Error ? err.message : String(err)}`
44468
+ }
44469
+ ]
44470
+ };
44471
+ }
44472
+ }
44473
+ );
44412
44474
  server.tool(
44413
44475
  "kanban_recall",
44414
44476
  'Pull a card you assigned to a HUMAN teammate back onto your own board. Use this when a card shows under "Assigned to a person" in kanban_list and you need to act on it - typically because the person finished the work (or it is no longer needed) and the card has to be closed. While a card is assigned to a person, every kanban write to it is refused; recall makes it yours again so kanban_move / kanban_done / kanban_log work. Recall does NOT complete the card - after recalling, close it yourself with kanban_done and a result. You can only recall a card you assigned.',
@@ -45984,16 +46046,37 @@ async function forwardToolCall(toolName, args) {
45984
46046
  // symlink-resolved, so a symlink at the final component means it was
45985
46047
  // swapped after the check, and this turns that into an ELOOP.
45986
46048
  openFlags: fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW,
46049
+ // ENG-9230. Each intermediate DIRECTORY is opened too, so the walk can be
46050
+ // anchored to descriptors rather than pathnames. O_DIRECTORY alongside
46051
+ // O_NOFOLLOW so a component that stopped being a directory is refused by
46052
+ // the kernel rather than noticed later.
46053
+ dirOpenFlags: fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_DIRECTORY,
46054
+ // ENG-9230. Supplied ONLY on Linux, and its absence is load-bearing:
46055
+ // `openContained` refuses outright rather than falling back to the
46056
+ // pathname walk, because that walk is the vulnerability. On a platform
46057
+ // without procfs the host-path shortcut is simply unavailable and the
46058
+ // agent passes the content inline — slower, and safe.
46059
+ //
46060
+ // `process.platform` rather than probing for /proc: a probe would have to
46061
+ // decide what a read failure means, and "cannot tell" would have to fail
46062
+ // closed anyway. Agent hosts are Linux EC2; this is about local dev on
46063
+ // macOS getting an honest refusal instead of a silent downgrade.
46064
+ ...process.platform === "linux" ? { procFdBase: "/proc/self/fd" } : {},
45987
46065
  fs: {
45988
46066
  realpathSync,
45989
46067
  openSync,
45990
46068
  fstatSync,
45991
46069
  closeSync,
45992
46070
  // Bounded read straight off the descriptor: at most maxBytes + 1 bytes,
45993
- // so an oversize file is detected rather than buffered whole.
45994
- lstatSync,
45995
- // Raw bytes, decoded only after the byte-length check — see
45996
- // readFdSync's docblock in host-file-path.ts.
46071
+ // so an oversize file is detected rather than buffered whole. Raw bytes,
46072
+ // decoded only after the byte-length check — see readFdSync's docblock
46073
+ // in host-file-path.ts.
46074
+ //
46075
+ // ENG-9230: `lstatSync` used to be injected here and is deliberately
46076
+ // gone. Every remaining member of this seam takes a DESCRIPTOR except
46077
+ // the two that cannot (realpath, and the single open of the allowed
46078
+ // root); handing the resolver a pathname-based stat back would put the
46079
+ // exploitable primitive within reach of the next change.
45997
46080
  readFdSync: (fd, maxBytes) => {
45998
46081
  const buf = Buffer.alloc(maxBytes + 1);
45999
46082
  let read = 0;
@@ -46043,6 +46126,8 @@ var LOCAL_TOOL_NAMES = /* @__PURE__ */ new Set([
46043
46126
  // ENG-8281: the inverse of assign_kanban_to_human — take a human-assigned
46044
46127
  // card back so the agent can finally close it.
46045
46128
  "kanban_recall",
46129
+ // ENG-9050: the return path for delegated work.
46130
+ "kanban_reopen",
46046
46131
  // ENG-6953: agent + people directory lookup (always registered locally).
46047
46132
  "directory_lookup",
46048
46133
  "status_standup",
@@ -43,8 +43,8 @@ import {
43
43
  writeDirectChatSessionState,
44
44
  writeEgressAllowlist,
45
45
  writePersistentClaudeWrapper
46
- } from "./chunk-OBIOE63G.js";
47
- import "./chunk-XUJIPPKF.js";
46
+ } from "./chunk-LUIGOCT4.js";
47
+ import "./chunk-NLCHGFD5.js";
48
48
  import "./chunk-XWVM4KPK.js";
49
49
  export {
50
50
  EGRESS_BASELINE_DOMAINS,
@@ -92,4 +92,4 @@ export {
92
92
  writeEgressAllowlist,
93
93
  writePersistentClaudeWrapper
94
94
  };
95
- //# sourceMappingURL=persistent-session-2W67OQUH.js.map
95
+ //# sourceMappingURL=persistent-session-QVLWWFK3.js.map
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  paneLogPath
3
- } from "./chunk-OBIOE63G.js";
4
- import "./chunk-XUJIPPKF.js";
3
+ } from "./chunk-LUIGOCT4.js";
4
+ import "./chunk-NLCHGFD5.js";
5
5
  import "./chunk-XWVM4KPK.js";
6
6
 
7
7
  // src/lib/responsiveness-probe.ts
@@ -727,4 +727,4 @@ export {
727
727
  readAndResetSlackReplyBindingClassifications,
728
728
  readAndResetSlackReplyTargetClassifications
729
729
  };
730
- //# sourceMappingURL=responsiveness-probe-44G7U6LR.js.map
730
+ //# sourceMappingURL=responsiveness-probe-NN4BNX2V.js.map
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  sessionTranscriptDir
3
- } from "./chunk-XUJIPPKF.js";
3
+ } from "./chunk-NLCHGFD5.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-TNWKYZIA.js.map
206
+ //# sourceMappingURL=session-auth-dead-KD66YN7O.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.657",
3
+ "version": "0.28.659",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {