@integrity-labs/agt-cli 0.28.575 → 0.28.577

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.
@@ -53,7 +53,7 @@ import {
53
53
  safeWriteJsonAtomic,
54
54
  setConfigHash,
55
55
  tripClass
56
- } from "../chunk-Q76HILWC.js";
56
+ } from "../chunk-FOMHYTFV.js";
57
57
  import {
58
58
  getProjectDir as getProjectDir2,
59
59
  getReadyTasks,
@@ -11596,7 +11596,7 @@ var agentRestartTimezoneInputs = /* @__PURE__ */ new Map();
11596
11596
  var lastVersionCheckAt = 0;
11597
11597
  var VERSION_CHECK_INTERVAL_MS = 5 * 60 * 1e3;
11598
11598
  var lastResponsivenessProbeAt = 0;
11599
- var agtCliVersion = true ? "0.28.575" : "dev";
11599
+ var agtCliVersion = true ? "0.28.577" : "dev";
11600
11600
  function resolveBrewPath(execFileSync2) {
11601
11601
  try {
11602
11602
  const out = execFileSync2("which", ["brew"], { timeout: 5e3 }).toString().trim();
package/dist/mcp/index.js CHANGED
@@ -6802,6 +6802,15 @@ var require_dist = __commonJS({
6802
6802
 
6803
6803
  // src/index.ts
6804
6804
  import { mkdtemp, writeFile } from "fs/promises";
6805
+ import {
6806
+ closeSync,
6807
+ constants as fsConstants,
6808
+ fstatSync,
6809
+ lstatSync,
6810
+ openSync,
6811
+ readSync,
6812
+ realpathSync
6813
+ } from "fs";
6805
6814
 
6806
6815
  // src/kanban-add-result.ts
6807
6816
  function kanbanAddResultText(data, submittedTitle, status) {
@@ -6818,8 +6827,134 @@ ${first.duplicate_hint}` : added;
6818
6827
  }
6819
6828
 
6820
6829
  // src/index.ts
6821
- import { tmpdir } from "os";
6822
- import { basename, join as join3 } from "path";
6830
+ import { homedir as homedir3, tmpdir } from "os";
6831
+ import { basename, isAbsolute, join as join3, resolve as pathResolve, sep } from "path";
6832
+
6833
+ // src/skill-body-path.ts
6834
+ var SKILL_BODY_PATH_TOOLS = /* @__PURE__ */ new Set(["skill_create", "skill_update"]);
6835
+ var SKILL_BODY_PATH_MAX_BYTES = 512 * 1024;
6836
+ function allowedSkillBodyRoots(agentDir, path) {
6837
+ return [path.join(agentDir, "project"), path.join(agentDir, "scratch")];
6838
+ }
6839
+ function assertNoSymlinkComponents(target, roots, path, fs) {
6840
+ const root = roots.find((r) => isContained(target, r, path.sep));
6841
+ if (!root) return "resolved outside the allowed roots";
6842
+ const rest = target.slice(root.length).split(path.sep).filter(Boolean);
6843
+ let current = root;
6844
+ for (const segment of rest) {
6845
+ current = path.join(current, segment);
6846
+ try {
6847
+ if (fs.lstatSync(current).isSymbolicLink()) {
6848
+ return `'${current}' is a symlink, so the path changed after it was resolved`;
6849
+ }
6850
+ } catch {
6851
+ return `'${current}' could not be inspected`;
6852
+ }
6853
+ }
6854
+ return null;
6855
+ }
6856
+ function isContained(candidate, root, sep2) {
6857
+ if (candidate === root) return true;
6858
+ const rootWithSep = root.endsWith(sep2) ? root : root + sep2;
6859
+ return candidate.startsWith(rootWithSep);
6860
+ }
6861
+ function resolveSkillBodyPath(toolName, args, opts) {
6862
+ if (!SKILL_BODY_PATH_TOOLS.has(toolName)) return { ok: true, args };
6863
+ const raw = args["body_path"];
6864
+ if (raw === void 0 || raw === null) return { ok: true, args };
6865
+ if (typeof raw !== "string" || raw.trim() === "") {
6866
+ return { ok: false, error: "body_path must be a non-empty string." };
6867
+ }
6868
+ if (typeof args["body"] === "string") {
6869
+ return {
6870
+ ok: false,
6871
+ error: "Pass either body or body_path, not both \u2014 they are two ways to say the same thing and I will not guess which one you meant."
6872
+ };
6873
+ }
6874
+ const { agentDir, path, fs } = opts;
6875
+ const maxBytes = opts.maxBytes ?? SKILL_BODY_PATH_MAX_BYTES;
6876
+ const roots = allowedSkillBodyRoots(agentDir, path);
6877
+ const absolute = path.isAbsolute(raw) ? path.resolve(raw) : path.resolve(path.join(agentDir, "project"), raw);
6878
+ if (!roots.some((r) => isContained(absolute, r, path.sep))) {
6879
+ return {
6880
+ ok: false,
6881
+ error: `body_path must be inside your own project or scratch directory. Got '${raw}'. Allowed: ${roots.join(", ")}`
6882
+ };
6883
+ }
6884
+ let real;
6885
+ try {
6886
+ real = fs.realpathSync(absolute);
6887
+ } catch {
6888
+ return { ok: false, error: `body_path does not exist or is unreadable: '${raw}'` };
6889
+ }
6890
+ if (!roots.some((r) => isContained(real, r, path.sep))) {
6891
+ return {
6892
+ ok: false,
6893
+ error: `body_path resolves outside your own directories (symlink?). '${raw}' \u2192 '${real}'. Allowed: ${roots.join(", ")}`
6894
+ };
6895
+ }
6896
+ const walkError = assertNoSymlinkComponents(real, roots, path, fs);
6897
+ if (walkError) return { ok: false, error: `body_path: ${walkError}` };
6898
+ let vetted = null;
6899
+ try {
6900
+ vetted = fs.lstatSync(real);
6901
+ } catch {
6902
+ return { ok: false, error: `body_path vanished before it could be read: '${raw}'` };
6903
+ }
6904
+ let fd;
6905
+ try {
6906
+ fd = fs.openSync(real, opts.openFlags);
6907
+ } catch (err) {
6908
+ const msg = err.message;
6909
+ return {
6910
+ ok: false,
6911
+ error: `body_path could not be opened (it may have been replaced mid-check): '${raw}' \u2014 ${msg}`
6912
+ };
6913
+ }
6914
+ try {
6915
+ let stat;
6916
+ try {
6917
+ stat = fs.fstatSync(fd);
6918
+ } catch {
6919
+ return { ok: false, error: `body_path could not be inspected: '${raw}'` };
6920
+ }
6921
+ if (!stat.isFile()) {
6922
+ return { ok: false, error: `body_path is not a regular file: '${raw}'` };
6923
+ }
6924
+ if (stat.ino !== void 0 && vetted.ino !== void 0) {
6925
+ if (stat.ino !== vetted.ino || stat.dev !== vetted.dev) {
6926
+ return {
6927
+ ok: false,
6928
+ error: `body_path changed identity between the check and the open (it was replaced): '${raw}'`
6929
+ };
6930
+ }
6931
+ }
6932
+ if (stat.size > maxBytes) {
6933
+ return {
6934
+ ok: false,
6935
+ error: `body_path is ${stat.size} bytes, over the ${maxBytes}-byte limit for a skill body.`
6936
+ };
6937
+ }
6938
+ let bytes;
6939
+ try {
6940
+ bytes = fs.readFdSync(fd, maxBytes);
6941
+ } catch (err) {
6942
+ return { ok: false, error: `body_path could not be read: ${err.message}` };
6943
+ }
6944
+ if (bytes.byteLength > maxBytes) {
6945
+ return { ok: false, error: `body_path grew past the ${maxBytes}-byte limit while being read.` };
6946
+ }
6947
+ const body = fs.decodeUtf8(bytes);
6948
+ const next = { ...args, body };
6949
+ delete next["body_path"];
6950
+ return { ok: true, args: next };
6951
+ } finally {
6952
+ try {
6953
+ fs.closeSync(fd);
6954
+ } catch {
6955
+ }
6956
+ }
6957
+ }
6823
6958
 
6824
6959
  // ../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js
6825
6960
  var external_exports = {};
@@ -24345,16 +24480,72 @@ async function discoverApiTools() {
24345
24480
  return [];
24346
24481
  }
24347
24482
  }
24483
+ function canonicalAgentDir(dir) {
24484
+ try {
24485
+ return realpathSync(dir);
24486
+ } catch {
24487
+ return dir;
24488
+ }
24489
+ }
24348
24490
  async function forwardToolCall(toolName, args) {
24349
24491
  if (!AGT_AGENT_ID) {
24350
24492
  throw new Error("Cannot forward tool call: AGT_AGENT_ID is not set");
24351
24493
  }
24494
+ let outgoing = args;
24495
+ if (SKILL_BODY_PATH_TOOLS.has(toolName) && args["body_path"] !== void 0) {
24496
+ if (!AGT_AGENT_CODE_NAME) {
24497
+ throw new Error("Cannot resolve body_path: AGT_AGENT_CODE_NAME is not set");
24498
+ }
24499
+ const resolved = resolveSkillBodyPath(toolName, args, {
24500
+ // ADR-0049 / ENG-7891: the per-agent host dir is agent_id-keyed and the
24501
+ // codename path is only a compatibility SYMLINK. That is not a style
24502
+ // point here, it is a correctness one: the containment check below
24503
+ // realpath-resolves the caller's path, which resolves THROUGH the symlink
24504
+ // to the agent_id-keyed tree - so a codename-based root would fail
24505
+ // containment for every legitimate file and refuse them all. Resolve the
24506
+ // root to its canonical form so both sides of the comparison are real
24507
+ // paths. Falls back to the unresolved join if the dir is missing, which
24508
+ // then simply refuses (there is nothing to read anyway).
24509
+ agentDir: canonicalAgentDir(join3(homedir3(), ".augmented", AGT_AGENT_CODE_NAME)),
24510
+ // agent-dir-allow: immediately realpath-resolved to the ADR-0049 agent_id-keyed dir before use as a containment root
24511
+ path: { isAbsolute, resolve: pathResolve, join: join3, sep },
24512
+ // O_NOFOLLOW is the whole point of opening by flags: the path is already
24513
+ // symlink-resolved, so a symlink at the final component means it was
24514
+ // swapped after the check, and this turns that into an ELOOP.
24515
+ openFlags: fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW,
24516
+ fs: {
24517
+ realpathSync,
24518
+ openSync,
24519
+ fstatSync,
24520
+ closeSync,
24521
+ // Bounded read straight off the descriptor: at most maxBytes + 1 bytes,
24522
+ // so an oversize file is detected rather than buffered whole.
24523
+ lstatSync,
24524
+ // Raw bytes, decoded only after the byte-length check — see
24525
+ // readFdSync's docblock in skill-body-path.ts.
24526
+ readFdSync: (fd, maxBytes) => {
24527
+ const buf = Buffer.alloc(maxBytes + 1);
24528
+ let read = 0;
24529
+ for (; ; ) {
24530
+ const n = readSync(fd, buf, read, buf.length - read, null);
24531
+ if (n <= 0) break;
24532
+ read += n;
24533
+ if (read >= buf.length) break;
24534
+ }
24535
+ return buf.subarray(0, read);
24536
+ },
24537
+ decodeUtf8: (bytes) => Buffer.from(bytes).toString("utf8")
24538
+ }
24539
+ });
24540
+ if (!resolved.ok) throw new Error(resolved.error);
24541
+ outgoing = resolved.args;
24542
+ }
24352
24543
  return apiPost(
24353
24544
  "/host/mcp/tools/call",
24354
24545
  {
24355
24546
  agent_id: AGT_AGENT_ID,
24356
24547
  name: toolName,
24357
- arguments: args,
24548
+ arguments: outgoing,
24358
24549
  run_id: AGT_RUN_ID ?? null
24359
24550
  },
24360
24551
  false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@integrity-labs/agt-cli",
3
- "version": "0.28.575",
3
+ "version": "0.28.577",
4
4
  "description": "Augmented Team CLI — agent provisioning and management",
5
5
  "type": "module",
6
6
  "engines": {