@team-agent/installer 0.3.25 → 0.3.27

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.
Files changed (29) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/emit.rs +1 -0
  4. package/crates/team-agent/src/cli/mod.rs +193 -7
  5. package/crates/team-agent/src/cli/send.rs +106 -0
  6. package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -0
  7. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +270 -13
  8. package/crates/team-agent/src/cli/tests/status_send.rs +1 -0
  9. package/crates/team-agent/src/cli/types.rs +7 -0
  10. package/crates/team-agent/src/coordinator/tests/energy.rs +1 -0
  11. package/crates/team-agent/src/coordinator/tests/health_sync.rs +1 -0
  12. package/crates/team-agent/src/coordinator/tests/spine.rs +1 -0
  13. package/crates/team-agent/src/coordinator/tests/takeover.rs +1 -0
  14. package/crates/team-agent/src/leader/lease.rs +57 -0
  15. package/crates/team-agent/src/leader/start.rs +1 -0
  16. package/crates/team-agent/src/lifecycle/launch.rs +9 -0
  17. package/crates/team-agent/src/lifecycle/restart/agent.rs +55 -0
  18. package/crates/team-agent/src/messaging/delivery.rs +83 -3
  19. package/crates/team-agent/src/messaging/leader_receiver.rs +61 -24
  20. package/crates/team-agent/src/messaging/tests/runtime.rs +2 -0
  21. package/crates/team-agent/src/messaging/tests/spine.rs +1 -0
  22. package/crates/team-agent/src/tmux_backend/tests.rs +227 -25
  23. package/crates/team-agent/src/tmux_backend.rs +409 -105
  24. package/crates/team-agent/src/transport/test_support.rs +1 -0
  25. package/crates/team-agent/src/transport/tests/mod.rs +1 -0
  26. package/crates/team-agent/src/transport/tests/wire.rs +2 -1
  27. package/crates/team-agent/src/transport.rs +77 -0
  28. package/npm/install.mjs +222 -14
  29. package/package.json +4 -4
@@ -337,6 +337,75 @@ pub struct InjectReport {
337
337
  /// Gap42:仅 metadata,`not_yet_observed` 也算成功。
338
338
  pub turn_verification: TurnVerification,
339
339
  pub attempts: u32,
340
+ /// E50 PR-1 (0.3.24 P0, pasted-prompt 假阴诊断): per-attempt observations
341
+ /// emitted by the paste-prompt + Enter loop in `tmux_backend.rs::inject`.
342
+ /// `None` when the inject path did not exercise the diagnostic
343
+ /// instrumentation (peer payloads, empty payloads, non-bracketed Text,
344
+ /// or any path that bypasses `capture_has_pasted_content_prompt`). When
345
+ /// present, downstream `send.unverified` / `send.failed` events surface
346
+ /// it as `submit_attempts_detail[]` so operators see live pane state
347
+ /// per attempt (matched literal, where-in-tail offset, scrubbed pane
348
+ /// excerpt, elapsed ms) — the missing forensic data the user has been
349
+ /// asking for for many rounds.
350
+ ///
351
+ /// Tests / mocks should default this to `None` via `Default`. Wire
352
+ /// byte-lock (`transport::tests::wire`) is untouched: this field is NOT
353
+ /// serialised on the typed wire — it lives only in the in-process
354
+ /// `InjectReport` and is rendered into JSON manually by the delivery
355
+ /// layer when emitting forensic events.
356
+ pub submit_diagnostics: Option<SubmitDiagnostics>,
357
+ }
358
+
359
+ /// E50 PR-1 diagnostic payload — one entry per submit attempt in the
360
+ /// pasted-prompt branch + (informational) for the appear-gate poll.
361
+ #[derive(Debug, Clone, PartialEq, Eq, Default)]
362
+ pub struct SubmitDiagnostics {
363
+ /// Time spent in the appear-gate (poll for the pasted-content placeholder
364
+ /// before Enter). When `saw_pasted_prompt == false` this is the time we
365
+ /// spent polling before falling through to the E46 token path.
366
+ pub appear_gate_elapsed_ms: u64,
367
+ /// Did the appear-gate ever match the `pasted content` / `pasted text`
368
+ /// literal? When `false`, the inject took the E46 token path; the
369
+ /// `attempts_detail` may still capture observations the operator wants.
370
+ pub appear_gate_matched: bool,
371
+ /// Total elapsed across the whole submit gate (appear-gate + Enter
372
+ /// loop). Useful to triage real-machine slow-paste (codex large-paste
373
+ /// collapse can take >100 s while the framework's loop is sub-second).
374
+ pub total_elapsed_ms: u64,
375
+ /// Per-attempt observations of the post-Enter capture in the pasted-
376
+ /// prompt loop. Empty when `saw_pasted_prompt == false` and the inject
377
+ /// went through the E46 token path.
378
+ pub attempts_detail: Vec<SubmitAttemptObservation>,
379
+ }
380
+
381
+ /// E50 PR-1 single attempt observation (one Enter + one capture). All fields
382
+ /// are forensic — they exist to make `send.unverified` / `send.failed` events
383
+ /// self-describing rather than requiring the operator to guess.
384
+ #[derive(Debug, Clone, PartialEq, Eq, Default)]
385
+ pub struct SubmitAttemptObservation {
386
+ /// 1-based attempt index (mirrors the `attempts` counter that already
387
+ /// ships in `InjectReport.attempts`).
388
+ pub attempt_index: u32,
389
+ /// Did this attempt's post-Enter capture STILL match the pasted-prompt
390
+ /// literal? `true` means the inject saw the placeholder; `false` means
391
+ /// the placeholder cleared (= submit succeeded by the legacy criterion).
392
+ pub matched: bool,
393
+ /// The matched literal substring (`pasted content` / `pasted text`)
394
+ /// when `matched == true`. `None` otherwise.
395
+ pub matched_literal: Option<String>,
396
+ /// Distance from the bottom of the tail where the match occurred:
397
+ /// `Some(0)` = bottom-most line (composer), `Some(N)` = N lines above
398
+ /// bottom (likely scrollback), `None` = no match. Critical for the
399
+ /// false-negative root cause: a `pasted content` literal in scrollback
400
+ /// is NOT the live composer placeholder.
401
+ pub where_in_tail: Option<u32>,
402
+ /// Scrubbed last 20 / 80 lines of the captured tail, ANSI-stripped,
403
+ /// capped ~1200 bytes. Secrets scrubbed (sk-/ghp_/AKIA/Bearer/hex32+).
404
+ pub pane_tail_excerpt: String,
405
+ /// Number of non-empty lines in `pane_tail_excerpt`.
406
+ pub pane_tail_lines: u32,
407
+ /// Time elapsed for THIS attempt (Enter + capture + match).
408
+ pub elapsed_ms: u64,
340
409
  }
341
410
 
342
411
  // ─────────────────────────────────────────────────────────────────────────────
@@ -761,9 +830,17 @@ pub fn tmux_spawn_argv(
761
830
  command.to_string(),
762
831
  ]
763
832
  } else {
833
+ // E53 (0.3.26, adaptive layout same-session tabs): `-d` makes the new
834
+ // window start without switching the client's active window to it. The
835
+ // leader stays on its own window; the worker opens as a background tab.
836
+ // Without `-d` every `new-window` call yanks the leader's terminal to
837
+ // the freshly spawned worker window, disrupting whatever the leader is
838
+ // doing. `-d` matches the Python golden: the managed leader and all
839
+ // workers share the same tmux session (= same terminal window tabs).
764
840
  vec![
765
841
  "tmux".to_string(),
766
842
  "new-window".to_string(),
843
+ "-d".to_string(),
767
844
  "-t".to_string(),
768
845
  session.as_str().to_string(),
769
846
  "-n".to_string(),
package/npm/install.mjs CHANGED
@@ -13,6 +13,10 @@ const require = createRequire(import.meta.url);
13
13
  const packageJson = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf8"));
14
14
  const DOCTOR_TIMEOUT_MS = 5000;
15
15
  const VERSION_SMOKE_TIMEOUT_MS = 5000;
16
+ const INSTALL_MANIFEST = "install-manifest.json";
17
+ const PATH_MARKER = "# team-agent PATH (E48)";
18
+ const WRAPPER_MARKER = "# team-agent installer wrapper";
19
+ const WRAPPER_NAMES = ["team-agent", "team_orchestrator", "team-agent-coordinator"];
16
20
 
17
21
  if (isCliEntrypoint()) {
18
22
  main();
@@ -115,7 +119,7 @@ Usage:
115
119
  npx @team-agent/installer@latest uninstall
116
120
 
117
121
  Options:
118
- --prefix <dir> wrapper install prefix, default ~/.local
122
+ --prefix <dir> fallback wrapper prefix when no writable PATH dir exists, default ~/.local
119
123
  --runtime-dir <dir> stable runtime root, default ~/.team-agent/runtime
120
124
  --purge-runtime uninstall also removes the runtime root
121
125
  `);
@@ -123,9 +127,9 @@ Options:
123
127
 
124
128
  function install(argv) {
125
129
  const opts = parseOptions(argv);
126
- const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
127
- const binDir = path.join(prefix, "bin");
128
130
  const runtimeRoot = path.resolve(expandHome(opts.runtimeDir || path.join(os.homedir(), ".team-agent", "runtime")));
131
+ const installTarget = resolveInstallBinDir({ env: process.env, home: os.homedir(), prefix: opts.prefix });
132
+ const binDir = installTarget.binDir;
129
133
  const version = packageJson.version || "dev";
130
134
  const dest = path.join(runtimeRoot, version);
131
135
  const tmp = path.join(runtimeRoot, `.${version}.${process.pid}.tmp`);
@@ -149,13 +153,29 @@ function install(argv) {
149
153
  writeExecWrapper(path.join(binDir, "team_orchestrator"), runtimeBinary, ["mcp-server"]);
150
154
  writeExecWrapper(path.join(binDir, "team-agent-coordinator"), runtimeBinary, ["coordinator"]);
151
155
  installSkills(runtimeBinary);
156
+ writeInstallManifest(runtimeRoot, {
157
+ version,
158
+ binDir,
159
+ runtimeRoot,
160
+ runtimeBinary,
161
+ installedAt: new Date().toISOString(),
162
+ installTargetKind: installTarget.kind,
163
+ });
152
164
 
153
165
  const teamAgent = path.join(binDir, "team-agent");
154
166
  console.log(`installed: ${teamAgent}`);
167
+ if (installTarget.readyNow) {
168
+ console.log(`installed to ${binDir} (on PATH, ready now)`);
169
+ } else if (installTarget.rc?.files?.length > 0) {
170
+ console.log(`installed to ${binDir}; added ${binDir} to ${installTarget.rc.files.join(", ")}; restart terminal or open a new shell to use team-agent`);
171
+ } else if (installTarget.rc?.skipped?.length > 0) {
172
+ console.log(`installed to ${binDir}; PATH entry already present in ${installTarget.rc.skipped.join(", ")}; restart terminal or open a new shell to use team-agent`);
173
+ } else {
174
+ console.log(`installed to ${binDir}; add it to PATH to use team-agent by name`);
175
+ }
155
176
  console.log(`runtime: ${dest}`);
156
177
  console.log(`binary: ${platformBinary.packageName}`);
157
178
  console.log("skill: installed for Codex, Claude and Copilot");
158
- console.log(`PATH: ensure ${binDir} is on PATH`);
159
179
 
160
180
  // 0.3.6 hotfix · C-5 cr verdict — post-install binary smoke 门(走 `--help`
161
181
  // 子命令,因为 0.3.x CLI 现阶段没有 --version)。真跑一次 binary 才能抓住
@@ -193,8 +213,8 @@ function install(argv) {
193
213
 
194
214
  function runDoctor(argv) {
195
215
  const opts = parseOptions(argv);
196
- const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
197
- const teamAgent = path.join(prefix, "bin", "team-agent");
216
+ const runtimeRoot = path.resolve(expandHome(opts.runtimeDir || path.join(os.homedir(), ".team-agent", "runtime")));
217
+ const teamAgent = path.join(installedBinDir(runtimeRoot, opts), "team-agent");
198
218
  if (!fs.existsSync(teamAgent)) {
199
219
  console.error(`team-agent wrapper not found: ${teamAgent}`);
200
220
  process.exit(1);
@@ -213,10 +233,11 @@ function runDoctor(argv) {
213
233
 
214
234
  function uninstall(argv) {
215
235
  const opts = parseOptions(argv);
216
- const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
236
+ const runtimeRoot = path.resolve(expandHome(opts.runtimeDir || path.join(os.homedir(), ".team-agent", "runtime")));
237
+ const binDir = installedBinDir(runtimeRoot, opts);
217
238
  // 卸载 skill 走二进制单源(同一 SkillTarget 表 codex/claude/copilot),在删 wrapper 前调
218
239
  // (删 wrapper 后 PATH 上的 team-agent 没了,但 runtime 二进制仍在;用 runtime 二进制直调)。
219
- const teamAgentBin = path.join(prefix, "bin", "team-agent");
240
+ const teamAgentBin = path.join(binDir, "team-agent");
220
241
  if (fs.existsSync(teamAgentBin)) {
221
242
  const res = spawnSync(teamAgentBin, ["install-skill", "--target", "all", "--uninstall", "--json"], {
222
243
  text: true,
@@ -227,13 +248,12 @@ function uninstall(argv) {
227
248
  console.error(`WARN: skill uninstall via binary failed (status=${res.status ?? "signal"}); skill dirs may remain under ~/.codex|.claude|.copilot/skills/team-agent`);
228
249
  }
229
250
  }
230
- for (const name of ["team-agent", "team_orchestrator", "team-agent-coordinator"]) {
231
- fs.rmSync(path.join(prefix, "bin", name), { force: true });
251
+ for (const name of WRAPPER_NAMES) {
252
+ fs.rmSync(path.join(binDir, name), { force: true });
232
253
  }
233
- console.log(`removed wrappers from ${path.join(prefix, "bin")}`);
254
+ console.log(`removed wrappers from ${binDir}`);
234
255
  console.log("removed skills from ~/.codex, ~/.claude and ~/.copilot skills/team-agent");
235
256
  if (opts.purgeRuntime) {
236
- const runtimeRoot = path.resolve(expandHome(opts.runtimeDir || path.join(os.homedir(), ".team-agent", "runtime")));
237
257
  fs.rmSync(runtimeRoot, { recursive: true, force: true });
238
258
  console.log(`removed runtime root ${runtimeRoot}`);
239
259
  } else {
@@ -262,6 +282,182 @@ function parseOptions(argv) {
262
282
  return opts;
263
283
  }
264
284
 
285
+ export function resolveInstallBinDir(options = {}) {
286
+ const env = options.env || process.env;
287
+ const home = options.home || os.homedir();
288
+ const entries = uniquePathEntries(env.PATH || "", home);
289
+ for (const entry of entries) {
290
+ if (isVersionManagedPath(entry) || !canWriteDir(entry) || hasForeignWrapper(entry)) {
291
+ continue;
292
+ }
293
+ return { binDir: entry, kind: "path", readyNow: true, rc: null };
294
+ }
295
+
296
+ for (const entry of entries) {
297
+ if (isVersionManagedPath(entry) || !isReasonableUserBinDir(entry, home) || !canWriteDir(entry) || hasForeignWrapper(entry)) {
298
+ continue;
299
+ }
300
+ return { binDir: entry, kind: "path_user", readyNow: true, rc: null };
301
+ }
302
+
303
+ const fallbackPrefix = options.prefix
304
+ ? path.resolve(expandHomeFor(options.prefix, home))
305
+ : path.join(home, ".local");
306
+ const binDir = path.join(fallbackPrefix, "bin");
307
+ fs.mkdirSync(binDir, { recursive: true });
308
+ const rc = ensureBinDirOnShellRc(binDir, { env, home });
309
+ return { binDir, kind: "shell_rc", readyNow: false, rc };
310
+ }
311
+
312
+ function uniquePathEntries(searchPath, home) {
313
+ const seen = new Set();
314
+ const entries = [];
315
+ for (const raw of searchPath.split(path.delimiter)) {
316
+ if (!raw) {
317
+ continue;
318
+ }
319
+ const resolved = path.resolve(expandHomeFor(raw, home));
320
+ if (seen.has(resolved)) {
321
+ continue;
322
+ }
323
+ seen.add(resolved);
324
+ entries.push(resolved);
325
+ }
326
+ return entries;
327
+ }
328
+
329
+ function isVersionManagedPath(dir) {
330
+ const value = dir.replace(/\\/g, "/");
331
+ return [
332
+ "/.nvm/versions/",
333
+ "/Cellar/",
334
+ "/volta/tools/image/",
335
+ "/fnm/node-versions/",
336
+ "/.asdf/installs/",
337
+ "/node_modules/.bin",
338
+ "/.npm/_npx/",
339
+ "/_npx/",
340
+ ].some((marker) => value.includes(marker));
341
+ }
342
+
343
+ function isReasonableUserBinDir(dir, home) {
344
+ const relative = path.relative(home, dir);
345
+ return Boolean(relative && !relative.startsWith("..") && !path.isAbsolute(relative));
346
+ }
347
+
348
+ function canWriteDir(dir) {
349
+ try {
350
+ const probe = path.join(dir, `.team-agent-write-test-${process.pid}-${Date.now()}`);
351
+ fs.writeFileSync(probe, "");
352
+ fs.rmSync(probe, { force: true });
353
+ return true;
354
+ } catch {
355
+ return false;
356
+ }
357
+ }
358
+
359
+ function hasForeignWrapper(binDir) {
360
+ return WRAPPER_NAMES.some((name) => {
361
+ const file = path.join(binDir, name);
362
+ return fs.existsSync(file) && !isInstallerManagedWrapper(file);
363
+ });
364
+ }
365
+
366
+ function isInstallerManagedWrapper(file) {
367
+ try {
368
+ const text = fs.readFileSync(file, "utf8");
369
+ if (text.includes(WRAPPER_MARKER)) {
370
+ return true;
371
+ }
372
+ return /^#!\/usr\/bin\/env sh\nexec '[^']+\/bin\/team-agent'(?: '[^']+')? "\$@"\n$/.test(text);
373
+ } catch {
374
+ return false;
375
+ }
376
+ }
377
+
378
+ function ensureBinDirOnShellRc(binDir, options = {}) {
379
+ const env = options.env || process.env;
380
+ const home = options.home || os.homedir();
381
+ const shell = path.basename(env.SHELL || "");
382
+ const rc = shellRcTargets(shell, home);
383
+ if (!rc) {
384
+ return { files: [], skipped: [], unsupported: true };
385
+ }
386
+ const block = rc.style === "fish"
387
+ ? `\n${PATH_MARKER}\nset -gx PATH ${shellQuote(binDir)} $PATH\n`
388
+ : `\n${PATH_MARKER}\nexport PATH=\"${escapeDoubleQuoted(binDir)}:$PATH\"\n`;
389
+ const files = [];
390
+ const skipped = [];
391
+ for (const file of rc.files) {
392
+ let existing = "";
393
+ try {
394
+ existing = fs.readFileSync(file, "utf8");
395
+ } catch {
396
+ existing = "";
397
+ }
398
+ if (existing.includes(PATH_MARKER)) {
399
+ skipped.push(file);
400
+ continue;
401
+ }
402
+ try {
403
+ fs.mkdirSync(path.dirname(file), { recursive: true });
404
+ fs.appendFileSync(file, block);
405
+ files.push(file);
406
+ } catch {
407
+ continue;
408
+ }
409
+ }
410
+ return { files, skipped, unsupported: false };
411
+ }
412
+
413
+ function shellRcTargets(shell, home) {
414
+ if (shell === "zsh") {
415
+ return { files: [path.join(home, ".zshrc")], style: "posix" };
416
+ }
417
+ if (shell === "bash") {
418
+ return { files: [path.join(home, ".bashrc"), path.join(home, ".bash_profile")], style: "posix" };
419
+ }
420
+ if (shell === "fish") {
421
+ return { files: [path.join(home, ".config", "fish", "config.fish")], style: "fish" };
422
+ }
423
+ if (!shell || shell === "sh") {
424
+ return {
425
+ files: [process.platform === "darwin" ? path.join(home, ".zshrc") : path.join(home, ".profile")],
426
+ style: "posix",
427
+ };
428
+ }
429
+ return null;
430
+ }
431
+
432
+ function installedBinDir(runtimeRoot, opts) {
433
+ const manifest = readInstallManifest(runtimeRoot);
434
+ if (typeof manifest?.binDir === "string" && manifest.binDir) {
435
+ return manifest.binDir;
436
+ }
437
+ const prefix = path.resolve(expandHome(opts.prefix || path.join(os.homedir(), ".local")));
438
+ return path.join(prefix, "bin");
439
+ }
440
+
441
+ function installManifestPath(runtimeRoot) {
442
+ return path.join(runtimeRoot, INSTALL_MANIFEST);
443
+ }
444
+
445
+ export function readInstallManifest(runtimeRoot) {
446
+ try {
447
+ return JSON.parse(fs.readFileSync(installManifestPath(runtimeRoot), "utf8"));
448
+ } catch {
449
+ return null;
450
+ }
451
+ }
452
+
453
+ export function writeInstallManifest(runtimeRoot, manifest) {
454
+ fs.mkdirSync(runtimeRoot, { recursive: true });
455
+ const file = installManifestPath(runtimeRoot);
456
+ const tmp = `${file}.${process.pid}.tmp`;
457
+ fs.writeFileSync(tmp, `${JSON.stringify(manifest, null, 2)}\n`);
458
+ fs.renameSync(tmp, file);
459
+ }
460
+
265
461
  function resolvePlatformBinary() {
266
462
  const packageName = platformPackageName();
267
463
  if (!packageName) {
@@ -304,9 +500,13 @@ function copyExecutable(src, dest) {
304
500
  }
305
501
 
306
502
  function writeExecWrapper(file, binary, fixedArgs) {
503
+ if (fs.existsSync(file) && !isInstallerManagedWrapper(file)) {
504
+ throw new Error(`refusing to overwrite non-Team Agent installer wrapper: ${file}`);
505
+ }
307
506
  const args = fixedArgs.map(shellQuote).join(" ");
308
507
  const argPrefix = args ? `${args} ` : "";
309
508
  const content = `#!/usr/bin/env sh
509
+ ${WRAPPER_MARKER}
310
510
  exec ${shellQuote(binary)} ${argPrefix}"$@"
311
511
  `;
312
512
  fs.writeFileSync(file, content, { mode: 0o755 });
@@ -340,15 +540,23 @@ function makeDoctorWorkspace() {
340
540
  }
341
541
 
342
542
  function expandHome(value) {
543
+ return expandHomeFor(value, os.homedir());
544
+ }
545
+
546
+ function expandHomeFor(value, home) {
343
547
  if (value === "~") {
344
- return os.homedir();
548
+ return home;
345
549
  }
346
550
  if (value.startsWith("~/")) {
347
- return path.join(os.homedir(), value.slice(2));
551
+ return path.join(home, value.slice(2));
348
552
  }
349
553
  return value;
350
554
  }
351
555
 
556
+ function escapeDoubleQuoted(value) {
557
+ return String(value).replace(/["\\`$]/g, "\\$&");
558
+ }
559
+
352
560
  function shellQuote(value) {
353
561
  return `'${String(value).replace(/'/g, "'\\''")}'`;
354
562
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.3.25",
3
+ "version": "0.3.27",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -20,9 +20,9 @@
20
20
  "team-agent-installer": "npm/install.mjs"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@team-agent/cli-darwin-arm64": "0.3.25",
24
- "@team-agent/cli-darwin-x64": "0.3.25",
25
- "@team-agent/cli-linux-x64": "0.3.25"
23
+ "@team-agent/cli-darwin-arm64": "0.3.27",
24
+ "@team-agent/cli-darwin-x64": "0.3.27",
25
+ "@team-agent/cli-linux-x64": "0.3.27"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",