first-tree-staging 0.5.2-staging.23.1 → 0.5.2-staging.24.1

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.
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { a as cliFetch, f as channelConfig, l as print, n as bindFeishuUser, o as CLI_USER_AGENT, r as documentContextSchema, s as COMMAND_VERSION, t as bindFeishuBot, u as setJsonMode } from "../feishu-DmpOdXci.mjs";
3
- import { $ as agentConfigSchema, A as installClientService, C as checkClientConfig, D as printResults, E as checkWebSocket, F as startClientService, G as loadCredentials, I as stopClientService, J as saveAgentConfig, M as isServiceUnitDriftDetected, O as reconcileAgentConfigs, P as restartClientService, Q as removeLocalAgent, R as ClientRuntime, S as checkBackgroundService, T as checkServerReachable, U as ensureFreshAccessToken, W as ensureFreshAdminToken, X as findStaleAliases, Y as saveCredentials, Z as formatStaleReason, _t as buildMessageDocumentSnapshots, a as declineUpdate, at as initConfig, b as migrateLocalAgentDirs, bt as applyClientLoggerConfig, c as detectInstallMode, ct as resetConfig, d as reconcileLocalRuntimeProviders, dt as setConfigValue, et as clientConfigSchema, f as uploadClientCapabilities, ft as ClientOrgMismatchError, gt as SessionRegistry, h as promptMissingFields, ht as SdkError, i as createExecuteUpdate, it as getConfigValue, j as isServiceSupported, k as getClientServiceStatus, l as fetchLatestVersion, lt as resetConfigMeta, m as promptAddAgent, mt as FirstTreeHubSDK, n as decodeJwtPayload, nt as defaultDataDir, o as promptUpdate, ot as loadAgents, pt as ClientUserMismatchError, q as resolveServerUrl, r as deriveHubUrlFromToken, rt as defaultHome, s as PACKAGE_NAME, st as readConfigFile, t as HubUrlDerivationError, tt as defaultConfigDir, u as installGlobalLatest, ut as resolveConfigReadonly, vt as cleanWorkspaces, w as checkNodeVersion, x as checkAgentConfigs, xt as configureClientLoggerForService, y as createApiNameResolver, yt as probeCapabilities, z as handleClientOrgMismatch } from "../connect-token-2AaEgnAr.mjs";
3
+ import { $ as agentConfigSchema, A as installClientService, C as checkClientConfig, D as printResults, E as checkWebSocket, F as startClientService, G as loadCredentials, I as stopClientService, J as saveAgentConfig, M as isServiceUnitDriftDetected, O as reconcileAgentConfigs, P as restartClientService, Q as removeLocalAgent, R as ClientRuntime, S as checkBackgroundService, T as checkServerReachable, U as ensureFreshAccessToken, W as ensureFreshAdminToken, X as findStaleAliases, Y as saveCredentials, Z as formatStaleReason, _t as buildMessageDocumentSnapshots, a as declineUpdate, at as initConfig, b as migrateLocalAgentDirs, bt as applyClientLoggerConfig, c as detectInstallMode, ct as resetConfig, d as reconcileLocalRuntimeProviders, dt as setConfigValue, et as clientConfigSchema, f as uploadClientCapabilities, ft as ClientOrgMismatchError, gt as SessionRegistry, h as promptMissingFields, ht as SdkError, i as createExecuteUpdate, it as getConfigValue, j as isServiceSupported, k as getClientServiceStatus, l as fetchLatestVersion, lt as resetConfigMeta, m as promptAddAgent, mt as FirstTreeHubSDK, n as decodeJwtPayload, nt as defaultDataDir, o as promptUpdate, ot as loadAgents, pt as ClientUserMismatchError, q as resolveServerUrl, r as deriveHubUrlFromToken, rt as defaultHome, s as PACKAGE_NAME, st as readConfigFile, t as HubUrlDerivationError, tt as defaultConfigDir, u as installGlobalLatest, ut as resolveConfigReadonly, vt as cleanWorkspaces, w as checkNodeVersion, x as checkAgentConfigs, xt as configureClientLoggerForService, y as createApiNameResolver, yt as probeCapabilities, z as handleClientOrgMismatch } from "../connect-token-CJozTiUf.mjs";
4
4
  import { basename, dirname, join, relative, resolve } from "node:path";
5
5
  import { homedir, tmpdir } from "node:os";
6
6
  import { cpSync, existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, readlinkSync, rmSync, statSync, symlinkSync, unlinkSync, writeFileSync } from "node:fs";
@@ -7332,6 +7332,33 @@ const DEFAULT_CLONE_TIMEOUT_MS = 300 * 1e3;
7332
7332
  const FETCH_REFSPEC = "+refs/heads/*:refs/remotes/origin/*";
7333
7333
  const SESSION_BRANCH_PREFIX = "hub-session";
7334
7334
  /**
7335
+ * Backoff schedule for retrying a remote-talking git op (`fetch`,
7336
+ * `remote set-head --auto`) on a transient network-layer failure. Each entry
7337
+ * is the wait BEFORE the next attempt, so this lays out 4 attempts total
7338
+ * (1 initial + 3 retries) with a worst-case sleep budget of ~5s **per
7339
+ * protocol attempt**.
7340
+ *
7341
+ * Per-call totals depend on whether the helper chains a primary + fallback:
7342
+ * - `fetchOrigin` ≤ 5s sleep budget: the SSH fallback only fires when
7343
+ * the primary's terminal failure is credential-shaped, which a transient
7344
+ * stderr will never be. So a transient-only failure burns ~5s.
7345
+ * - `setHeadAuto` ≤ 10s sleep budget: the fallback fires on ANY terminal
7346
+ * primary failure, so a doubly-transient run burns ~5s + ~5s ≈ 10s.
7347
+ *
7348
+ * Tuned for the operator-visible case of session start/resume hitting a
7349
+ * proxy / VPN that flips a rule, restarts a TUN, or drops a TLS handshake
7350
+ * mid-flight. The user's own manual workaround in those cases is "@-mention
7351
+ * the agent again 2 seconds later" — this is that, automated.
7352
+ *
7353
+ * Per-step jitter (up to 25% of the delay) prevents thundering-herd retries
7354
+ * when several agents resume in lockstep against the same flaky proxy.
7355
+ */
7356
+ const NETWORK_RETRY_DELAYS_MS = [
7357
+ 500,
7358
+ 1500,
7359
+ 3e3
7360
+ ];
7361
+ /**
7335
7362
  * Hash a repo URL into the mirror directory name.
7336
7363
  *
7337
7364
  * The hash is computed over a *canonical* form (host + path, lowercased
@@ -7510,6 +7537,20 @@ function createGitMirrorManager(opts) {
7510
7537
  return false;
7511
7538
  }
7512
7539
  }
7540
+ async function gitWithNetworkRetry(args, cwd, timeoutMs, opLabel) {
7541
+ return retryOnTransientNetwork(() => git(args, cwd, timeoutMs), {
7542
+ delaysMs: NETWORK_RETRY_DELAYS_MS,
7543
+ isRetryable: isLikelyTransientNetworkError,
7544
+ onRetry: ({ attempt, nextDelayMs, message }) => {
7545
+ log?.warn({
7546
+ op: opLabel,
7547
+ attempt,
7548
+ nextDelayMs,
7549
+ stderr: message.slice(0, 512)
7550
+ }, "git remote op hit transient network error — retrying");
7551
+ }
7552
+ });
7553
+ }
7513
7554
  /**
7514
7555
  * `git fetch --prune origin` with one-shot bidirectional protocol fallback.
7515
7556
  *
@@ -7547,23 +7588,38 @@ function createGitMirrorManager(opts) {
7547
7588
  * here: callers that need origin/HEAD already get a clear
7548
7589
  * `GitMirrorError("Cannot resolve default branch …")` if both attempts fail.
7549
7590
  */
7591
+ /**
7592
+ * Worst-case sleep budget: ~10s (primary ~5s + fallback ~5s). Unlike
7593
+ * `fetchOrigin`, which gates fallback on a credential-shaped terminal
7594
+ * error, `setHeadAuto` always falls through on any primary failure, so
7595
+ * a doubly-transient run pays both retry budgets. Per-call 30s timeout
7596
+ * still caps each individual git invocation.
7597
+ */
7550
7598
  async function setHeadAuto(mirrorPath, originUrl) {
7551
- if (await gitOk([
7552
- "remote",
7553
- "set-head",
7554
- "origin",
7555
- "--auto"
7556
- ], mirrorPath, 3e4)) return true;
7599
+ try {
7600
+ await gitWithNetworkRetry([
7601
+ "remote",
7602
+ "set-head",
7603
+ "origin",
7604
+ "--auto"
7605
+ ], mirrorPath, 3e4, "set-head:primary");
7606
+ return true;
7607
+ } catch {}
7557
7608
  const direction = pickFallbackDirection(originUrl);
7558
7609
  if (!direction) return false;
7559
- return await gitOk([
7560
- "-c",
7561
- `url.${direction.peerBase}.insteadOf=${direction.originBase}`,
7562
- "remote",
7563
- "set-head",
7564
- "origin",
7565
- "--auto"
7566
- ], mirrorPath, 3e4);
7610
+ try {
7611
+ await gitWithNetworkRetry([
7612
+ "-c",
7613
+ `url.${direction.peerBase}.insteadOf=${direction.originBase}`,
7614
+ "remote",
7615
+ "set-head",
7616
+ "origin",
7617
+ "--auto"
7618
+ ], mirrorPath, 3e4, "set-head:fallback");
7619
+ return true;
7620
+ } catch {
7621
+ return false;
7622
+ }
7567
7623
  }
7568
7624
  async function readOriginUrl(mirrorPath) {
7569
7625
  try {
@@ -7580,11 +7636,11 @@ function createGitMirrorManager(opts) {
7580
7636
  async function fetchOrigin(mirrorPath, originUrl) {
7581
7637
  const direction = pickFallbackDirection(originUrl);
7582
7638
  try {
7583
- const { elapsedMs } = await git([
7639
+ const { elapsedMs } = await gitWithNetworkRetry([
7584
7640
  "fetch",
7585
7641
  "--prune",
7586
7642
  "origin"
7587
- ], mirrorPath, cloneTimeoutMs);
7643
+ ], mirrorPath, cloneTimeoutMs, "fetch:primary");
7588
7644
  return {
7589
7645
  elapsedMs,
7590
7646
  usedFallback: false
@@ -7599,13 +7655,13 @@ function createGitMirrorManager(opts) {
7599
7655
  peerBase: direction.peerBase
7600
7656
  }, "fetch failed with credential-shaped error; retrying with peer-protocol insteadOf rewrite");
7601
7657
  try {
7602
- const { elapsedMs } = await git([
7658
+ const { elapsedMs } = await gitWithNetworkRetry([
7603
7659
  "-c",
7604
7660
  `url.${direction.peerBase}.insteadOf=${direction.originBase}`,
7605
7661
  "fetch",
7606
7662
  "--prune",
7607
7663
  "origin"
7608
- ], mirrorPath, cloneTimeoutMs);
7664
+ ], mirrorPath, cloneTimeoutMs, "fetch:fallback");
7609
7665
  log?.info({
7610
7666
  gitUrl: originUrl,
7611
7667
  toProtocol: direction.toProtocol
@@ -8134,6 +8190,46 @@ function isLikelySshAuthFailure(message) {
8134
8190
  return /Permission denied\s*(?:\(|,)/i.test(message) || /Could not read from remote repository/i.test(message) || /Host key verification failed/i.test(message) || /no matching host key type/i.test(message) || /no mutual signature algorithm/i.test(message);
8135
8191
  }
8136
8192
  /**
8193
+ * Heuristic for transient network-layer failures emitted by `git` over
8194
+ * HTTPS or SSH. These are the failure modes a brief proxy/VPN hiccup, TLS
8195
+ * handshake blip, or peer connection reset produces mid-fetch — exactly
8196
+ * what `SSL_connect: SSL_ERROR_SYSCALL` looks like when Surge / Clash
8197
+ * swaps a rule mid-flight, what `early EOF` looks like when an HTTP/2
8198
+ * stream is reset, and what `Connection refused` looks like when a local
8199
+ * proxy listener restarts.
8200
+ *
8201
+ * Used by the `gitWithNetworkRetry` wrapper around `fetch` and
8202
+ * `remote set-head --auto` to absorb the kind of hiccup that today
8203
+ * surfaces as `Session start/resume failed (…)` in chat and only goes
8204
+ * away when the operator manually @-mentions the agent again two seconds
8205
+ * later.
8206
+ *
8207
+ * Negative space (intentionally NOT matched):
8208
+ * - credential failures — handled by the protocol-fallback path; retrying
8209
+ * in the same protocol won't help.
8210
+ * - `Repository not found`, `couldn't find remote ref` — deterministic
8211
+ * content errors; a 500ms retry won't fix them.
8212
+ * - `SSL certificate problem` — TLS trust failures; retrying won't help
8213
+ * and silently masking them would hide a real misconfiguration.
8214
+ * - `git … timed out after Xms` — our own per-call timeout. The op was
8215
+ * making progress (or wasn't); either way another full timeout window
8216
+ * is the wrong response.
8217
+ *
8218
+ * On localhost-proxy specifically (the common case for this codebase),
8219
+ * `ECONNREFUSED` IS a transient signal — when Surge / Clash bounces the
8220
+ * listener, the next attempt sees the same listener back up within
8221
+ * seconds. This is why we diverge from the SDK's `doFetch` policy (which
8222
+ * does NOT retry `ECONNREFUSED` because there the peer is the remote hub).
8223
+ *
8224
+ * Exported for unit testing.
8225
+ */
8226
+ function isLikelyTransientNetworkError(message) {
8227
+ if (!message) return false;
8228
+ if (isLikelyHttpsAuthFailure(message) || isLikelySshAuthFailure(message)) return false;
8229
+ if (/SSL certificate problem/i.test(message) || /server certificate verification failed/i.test(message) || /certificate verify failed/i.test(message) || /self.signed certificate/i.test(message) || /unable to get local issuer certificate/i.test(message) || /certificate has expired/i.test(message)) return false;
8230
+ return /SSL_ERROR_SYSCALL/i.test(message) || /unexpected eof while reading/i.test(message) || /TLS handshake|gnutls_handshake|gnutls\s+recv\s+error/i.test(message) || /\bConnection reset(?:\s+by\s+peer)?\b/i.test(message) || /\bConnection refused\b/i.test(message) || /\bConnection timed out\b/i.test(message) || /\bOperation timed out\b/i.test(message) || /\bNetwork is unreachable\b/i.test(message) || /Could not resolve host(?:name)?/i.test(message) || /Temporary failure in name resolution/i.test(message) || /\bRPC failed\b/i.test(message) || /\bearly EOF\b/i.test(message) || /the remote end hung up unexpectedly/i.test(message) || /transfer closed with outstanding read data remaining/i.test(message) || /HTTP\/2 stream\s+\d+\s+was\s+(?:not\s+)?(?:reset|closed)/i.test(message) || /HTTP\/2 stream was reset/i.test(message) || /unexpected disconnect while reading sideband packet/i.test(message) || /fetch-pack: unexpected disconnect/i.test(message) || /\bsend-pack:\s+unexpected\s+disconnect\b/i.test(message);
8231
+ }
8232
+ /**
8137
8233
  * Map an HTTPS git URL to the `insteadOf` rewrite needed to make git resolve
8138
8234
  * it through SSH. Returns *base* strings (suitable for
8139
8235
  * `git -c url.<sshBase>.insteadOf=<httpsBase>`) — git's `insteadOf` is a
@@ -8250,6 +8346,44 @@ function truncate(text, max = 512) {
8250
8346
  if (text.length <= max) return text;
8251
8347
  return `${text.slice(0, max)}…[truncated]`;
8252
8348
  }
8349
+ /**
8350
+ * Run `op` once, and on a `isRetryable`-classified failure replay it on the
8351
+ * given backoff schedule. Non-retryable failures propagate to the caller
8352
+ * immediately — those won't be cured by waiting and silently retrying would
8353
+ * mask real bugs and exhaust the retry budget.
8354
+ *
8355
+ * Per-attempt timeouts (when `op` enforces one of its own) are NOT reset
8356
+ * across attempts: each attempt gets its own full budget. Right policy for
8357
+ * `git fetch` where a slow but progressing transfer on attempt N+1 should
8358
+ * not be aborted because attempt N ate part of a shared budget.
8359
+ *
8360
+ * Exported so unit tests can drive the retry policy with a mock `op`
8361
+ * instead of standing up a real flaky network. Module-scope so the helper
8362
+ * stays pure and side-effect-free.
8363
+ */
8364
+ async function retryOnTransientNetwork(op, options) {
8365
+ const { delaysMs, isRetryable, onRetry } = options;
8366
+ const maxAttempts = delaysMs.length + 1;
8367
+ let lastErr;
8368
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) try {
8369
+ return await op(attempt);
8370
+ } catch (err) {
8371
+ lastErr = err;
8372
+ const message = err instanceof Error ? err.message : String(err);
8373
+ if (!isRetryable(message)) throw err;
8374
+ if (attempt === maxAttempts) throw err;
8375
+ const baseDelay = delaysMs[attempt - 1];
8376
+ if (baseDelay === void 0) throw err;
8377
+ const delayMs = baseDelay + Math.floor(Math.random() * Math.max(1, Math.floor(baseDelay / 4)));
8378
+ onRetry?.({
8379
+ attempt,
8380
+ nextDelayMs: delayMs,
8381
+ message
8382
+ });
8383
+ await new Promise((r) => setTimeout(r, delayMs));
8384
+ }
8385
+ throw lastErr;
8386
+ }
8253
8387
  function contextTreeReposDir() {
8254
8388
  return join(defaultDataDir$1(), "context-tree-repos");
8255
8389
  }
package/dist/index.mjs CHANGED
@@ -1,3 +1,3 @@
1
1
  import { c as blank, d as status, n as bindFeishuUser, t as bindFeishuBot } from "./feishu-DmpOdXci.mjs";
2
- import { A as installClientService, B as rotateClientIdWithBackup, C as checkClientConfig, D as printResults, E as checkWebSocket, F as startClientService, H as AuthRefreshRateLimitedError, I as stopClientService, K as resolveAccessToken, L as uninstallClientService, N as resolveCliInvocation, P as restartClientService, R as ClientRuntime, T as checkServerReachable, U as ensureFreshAccessToken, V as AuthRefreshFailedError, W as ensureFreshAdminToken, _ as onboardCheck, g as formatCheckReport, h as promptMissingFields, ht as SdkError, j as isServiceSupported, k as getClientServiceStatus, m as promptAddAgent, mt as FirstTreeHubSDK, p as isInteractive, q as resolveServerUrl, r as deriveHubUrlFromToken, t as HubUrlDerivationError, v as onboardCreate, w as checkNodeVersion, x as checkAgentConfigs, z as handleClientOrgMismatch } from "./connect-token-2AaEgnAr.mjs";
2
+ import { A as installClientService, B as rotateClientIdWithBackup, C as checkClientConfig, D as printResults, E as checkWebSocket, F as startClientService, H as AuthRefreshRateLimitedError, I as stopClientService, K as resolveAccessToken, L as uninstallClientService, N as resolveCliInvocation, P as restartClientService, R as ClientRuntime, T as checkServerReachable, U as ensureFreshAccessToken, V as AuthRefreshFailedError, W as ensureFreshAdminToken, _ as onboardCheck, g as formatCheckReport, h as promptMissingFields, ht as SdkError, j as isServiceSupported, k as getClientServiceStatus, m as promptAddAgent, mt as FirstTreeHubSDK, p as isInteractive, q as resolveServerUrl, r as deriveHubUrlFromToken, t as HubUrlDerivationError, v as onboardCreate, w as checkNodeVersion, x as checkAgentConfigs, z as handleClientOrgMismatch } from "./connect-token-CJozTiUf.mjs";
3
3
  export { AuthRefreshFailedError, AuthRefreshRateLimitedError, ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkNodeVersion, checkServerReachable, checkWebSocket, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, installClientService, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, restartClientService, rotateClientIdWithBackup, startClientService, status, stopClientService, uninstallClientService };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "first-tree-staging",
3
- "version": "0.5.2-staging.23.1",
3
+ "version": "0.5.2-staging.24.1",
4
4
  "type": "module",
5
5
  "description": "First Tree — unified CLI for Context Tree onboarding, GitHub Scan automation, agent management, and Hub messaging. (Source-tree dev build; CI rewrites `name` and `bin` for prod / staging publishes — see docs/local-dev-isolation.md.)",
6
6
  "keywords": [