@agent-team-foundation/first-tree-hub 0.8.0 → 0.8.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.
@@ -520,6 +520,14 @@ function resolveAccessToken() {
520
520
  return creds.accessToken;
521
521
  }
522
522
  /**
523
+ * In-flight refresh promise. Multiple callers (WS handshake, proactive
524
+ * refresh timer, every SDK request) can see an expired token within the same
525
+ * millisecond — without dedupe each would fire an independent `/auth/refresh`
526
+ * round-trip and race to write `credentials.json`. Share one in-flight
527
+ * promise so N concurrent callers resolve from a single HTTP call.
528
+ */
529
+ let inflightRefresh = null;
530
+ /**
523
531
  * Ensure the persisted access token is fresh. Call before any API request
524
532
  * when using persisted credentials. Returns the (possibly refreshed) access
525
533
  * token. Service-user API keys are out of scope for this milestone.
@@ -528,19 +536,27 @@ async function ensureFreshAccessToken() {
528
536
  const creds = loadCredentials();
529
537
  if (!creds) throw new Error("No credentials found. Run `first-tree-hub client connect <server-url>` to sign in.");
530
538
  if (!isTokenExpired(creds.accessToken)) return creds.accessToken;
531
- const res = await fetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
532
- method: "POST",
533
- headers: { "Content-Type": "application/json" },
534
- body: JSON.stringify({ refreshToken: creds.refreshToken }),
535
- signal: AbortSignal.timeout(1e4)
536
- });
537
- if (!res.ok) throw new Error("Access token expired and refresh failed. Run `first-tree-hub client connect <server-url>`.");
538
- const data = await res.json();
539
- saveCredentials({
540
- ...creds,
541
- accessToken: data.accessToken
542
- });
543
- return data.accessToken;
539
+ if (inflightRefresh) return inflightRefresh;
540
+ inflightRefresh = (async () => {
541
+ const res = await fetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
542
+ method: "POST",
543
+ headers: { "Content-Type": "application/json" },
544
+ body: JSON.stringify({ refreshToken: creds.refreshToken }),
545
+ signal: AbortSignal.timeout(1e4)
546
+ });
547
+ if (!res.ok) throw new Error("Access token expired and refresh failed. Run `first-tree-hub client connect <server-url>`.");
548
+ const data = await res.json();
549
+ saveCredentials({
550
+ ...creds,
551
+ accessToken: data.accessToken
552
+ });
553
+ return data.accessToken;
554
+ })();
555
+ try {
556
+ return await inflightRefresh;
557
+ } finally {
558
+ inflightRefresh = null;
559
+ }
544
560
  }
545
561
  /** Back-compat alias retained so existing call sites keep compiling. */
546
562
  const ensureFreshAdminToken = ensureFreshAccessToken;
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, b as resetConfigMeta, c as saveCredentials, f as agentConfigSchema, g as initConfig, h as getConfigValue, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, v as readConfigFile, y as resetConfig } from "../bootstrap-CRDR6NwE.mjs";
3
- import { A as stopPostgres, C as checkServerReachable, F as SdkError, I as SessionRegistry, L as cleanWorkspaces, M as createOwner, P as FirstTreeHubSDK, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, d as loadOnboardState, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, j as ClientRuntime, l as promptMissingFields, m as saveOnboardState, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "../core-BXS5ppsG.mjs";
2
+ import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, b as resetConfigMeta, c as saveCredentials, f as agentConfigSchema, g as initConfig, h as getConfigValue, l as DEFAULT_CONFIG_DIR, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, r as ensureFreshAdminToken, s as saveAgentConfig, u as DEFAULT_DATA_DIR, v as readConfigFile, y as resetConfig } from "../bootstrap-8nCntTrK.mjs";
3
+ import { A as stopPostgres, C as checkServerReachable, F as SdkError, I as SessionRegistry, L as cleanWorkspaces, M as createOwner, P as FirstTreeHubSDK, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, d as loadOnboardState, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, j as ClientRuntime, l as promptMissingFields, m as saveOnboardState, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "../core-BA5U1v9L.mjs";
4
4
  import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-D9JkMZnU.mjs";
5
5
  import { createRequire } from "node:module";
6
6
  import { Command } from "commander";
@@ -1126,13 +1126,13 @@ function isSecretField(schema, dotPath) {
1126
1126
  //#region src/commands/onboard.ts
1127
1127
  async function promptMissing(args) {
1128
1128
  if (!args.server) try {
1129
- const { resolveServerUrl } = await import("../bootstrap-CRDR6NwE.mjs").then((n) => n.t);
1129
+ const { resolveServerUrl } = await import("../bootstrap-8nCntTrK.mjs").then((n) => n.t);
1130
1130
  resolveServerUrl();
1131
1131
  } catch {
1132
1132
  args.server = await input({ message: "Hub server URL:" });
1133
1133
  saveOnboardState(args);
1134
1134
  }
1135
- const { loadCredentials } = await import("../bootstrap-CRDR6NwE.mjs").then((n) => n.t);
1135
+ const { loadCredentials } = await import("../bootstrap-8nCntTrK.mjs").then((n) => n.t);
1136
1136
  if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
1137
1137
  if (!args.id) {
1138
1138
  args.id = await input({ message: "Agent ID:" });
@@ -1,4 +1,4 @@
1
- import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, x as resolveConfigReadonly } from "./bootstrap-CRDR6NwE.mjs";
1
+ import { C as setConfigValue, S as serverConfigSchema, _ as loadAgents, d as DEFAULT_HOME_DIR$1, f as agentConfigSchema, g as initConfig, i as loadCredentials, l as DEFAULT_CONFIG_DIR, m as collectMissingPrompts, n as ensureFreshAccessToken, o as resolveServerUrl, p as clientConfigSchema, s as saveAgentConfig, u as DEFAULT_DATA_DIR$1, x as resolveConfigReadonly } from "./bootstrap-8nCntTrK.mjs";
2
2
  import { $ as updateAgentRuntimeConfigSchema, A as createMemberSchema, B as notificationQuerySchema, C as agentTypeSchema$1, D as createAdapterMappingSchema, E as createAdapterConfigSchema, F as inboxPollQuerySchema, G as sendMessageSchema, H as refreshTokenSchema, I as isRedactedEnvValue, J as sessionEventMessageSchema, K as sendToAgentSchema, L as linkTaskChatSchema, M as createTaskSchema, N as delegateFeishuUserSchema, O as createAgentSchema, P as dryRunAgentRuntimeConfigSchema, Q as updateAdapterConfigSchema, R as loginSchema, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as runtimeStateMessageSchema, V as paginationQuerySchema, W as selfServiceFeishuBotSchema, X as sessionStateMessageSchema, Y as sessionEventSchema$1, Z as taskListQuerySchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as wsAuthFrameSchema, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateAgentSchema, f as TASK_CREATOR_TYPES, g as WS_AUTH_FRAME_TIMEOUT_MS, h as TASK_TERMINAL_STATUSES, i as AGENT_BIND_REJECT_REASONS, it as updateTaskStatusSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateOrganizationSchema, o as AGENT_SOURCES, p as TASK_HEALTH_SIGNALS, q as sessionCompletionMessageSchema, rt as updateSystemConfigSchema, s as AGENT_STATUSES, tt as updateMemberSchema, u as DEFAULT_AGENT_RUNTIME_CONFIG_PAYLOAD, v as adminCreateTaskSchema, w as clientRegisterSchema, x as agentPinnedMessageSchema$1, y as adminUpdateTaskSchema, z as messageSourceSchema$1 } from "./feishu-D9JkMZnU.mjs";
3
3
  import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
4
4
  import { dirname, isAbsolute, join, resolve } from "node:path";
@@ -946,6 +946,7 @@ var ClientConnection = class extends EventEmitter {
946
946
  this.serverUrl = config.serverUrl.replace(/\/+$/, "");
947
947
  this.sdkVersion = config.sdkVersion;
948
948
  this.getAccessToken = config.getAccessToken;
949
+ this.on("error", () => {});
949
950
  }
950
951
  get isConnected() {
951
952
  return this.ws !== null && this.ws.readyState === WebSocket.OPEN && this.registered;
@@ -1458,6 +1459,75 @@ function bootstrapWorkspace(options) {
1458
1459
  }
1459
1460
  writeFileSync(join(agentDir, "tools.md"), generateToolsDoc(), "utf-8");
1460
1461
  }
1462
+ function defaultInstallExec(command, args, options) {
1463
+ execFileSync(command, args, {
1464
+ cwd: options.cwd,
1465
+ stdio: "pipe",
1466
+ timeout: options.timeout,
1467
+ encoding: "utf-8"
1468
+ });
1469
+ }
1470
+ /**
1471
+ * Install the first-tree skill and FIRST-TREE-SOURCE-INTEGRATION block into
1472
+ * the workspace by shelling out to `first-tree tree integrate`.
1473
+ *
1474
+ * Resolution order for the CLI binary:
1475
+ * 1. `first-tree` on PATH — preferred for runtime images that pre-install it.
1476
+ * 2. `npx -y first-tree@latest` — fallback that downloads on first run.
1477
+ *
1478
+ * Graceful degradation: returns false on failure and logs. The session still
1479
+ * starts; the agent just doesn't have the first-tree skill wired up.
1480
+ */
1481
+ function installFirstTreeIntegration(options) {
1482
+ const { workspacePath, contextTreePath, workspaceId, treeRepoUrl, log } = options;
1483
+ const exec = options.exec ?? defaultInstallExec;
1484
+ const integrateArgs = [
1485
+ "tree",
1486
+ "integrate",
1487
+ "--source-path",
1488
+ workspacePath,
1489
+ "--tree-path",
1490
+ contextTreePath,
1491
+ "--mode",
1492
+ "workspace-root",
1493
+ "--workspace-id",
1494
+ workspaceId,
1495
+ ...treeRepoUrl ? ["--tree-url", treeRepoUrl] : []
1496
+ ];
1497
+ const attempts = [{
1498
+ command: "first-tree",
1499
+ args: integrateArgs,
1500
+ label: "first-tree (PATH)"
1501
+ }, {
1502
+ command: "npx",
1503
+ args: [
1504
+ "-y",
1505
+ "first-tree@latest",
1506
+ ...integrateArgs
1507
+ ],
1508
+ label: "npx first-tree@latest"
1509
+ }];
1510
+ for (let index = 0; index < attempts.length; index += 1) {
1511
+ const attempt = attempts[index];
1512
+ if (!attempt) continue;
1513
+ try {
1514
+ exec(attempt.command, attempt.args, {
1515
+ cwd: workspacePath,
1516
+ timeout: 12e4
1517
+ });
1518
+ log(`First-tree integration installed via ${attempt.label}`);
1519
+ return true;
1520
+ } catch (err) {
1521
+ const msg = err instanceof Error ? err.message : String(err);
1522
+ const binaryMissing = /ENOENT|not found|command not found/i.test(msg);
1523
+ const isLastAttempt = index === attempts.length - 1;
1524
+ if (binaryMissing && !isLastAttempt) continue;
1525
+ log(`First-tree integration skipped (${attempt.label}): ${msg.slice(0, 200)}`);
1526
+ return false;
1527
+ }
1528
+ }
1529
+ return false;
1530
+ }
1461
1531
  function generateToolsDoc() {
1462
1532
  return `# Agent Hub SDK
1463
1533
 
@@ -2472,6 +2542,12 @@ const createClaudeCodeHandler = (config) => {
2472
2542
  chatId: sessionCtx.chatId
2473
2543
  });
2474
2544
  generateClaudeMd(workspace, sessionCtx.agent, contextTreePath);
2545
+ if (contextTreePath) installFirstTreeIntegration({
2546
+ workspacePath: workspace,
2547
+ contextTreePath,
2548
+ workspaceId: sessionCtx.chatId,
2549
+ log: (msg) => sessionCtx.log(msg)
2550
+ });
2475
2551
  }
2476
2552
  const handler = {
2477
2553
  async start(message, sessionCtx) {
@@ -3483,6 +3559,9 @@ var ClientRuntime = class {
3483
3559
  this.connection.on("auth:expired", () => {
3484
3560
  process.stderr.write(" ⚠️ Access token expired — reconnecting after refresh...\n");
3485
3561
  });
3562
+ this.connection.on("error", (err) => {
3563
+ process.stderr.write(` \u26A0\uFE0F Client connection error: ${err.message}\n`);
3564
+ });
3486
3565
  this.connection.on("agent:pinned", (message) => {
3487
3566
  this.handleAgentPinned(message);
3488
3567
  });
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-CRDR6NwE.mjs";
2
- import { A as stopPostgres, C as checkServerReachable, D as status, E as blank, F as SdkError, M as createOwner, N as hasUser, O as ensurePostgres, P as FirstTreeHubSDK, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, i as resolveCliInvocation, j as ClientRuntime, k as isDockerAvailable, l as promptMissingFields, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "./core-BXS5ppsG.mjs";
1
+ import { a as resolveAccessToken, n as ensureFreshAccessToken, o as resolveServerUrl, r as ensureFreshAdminToken } from "./bootstrap-8nCntTrK.mjs";
2
+ import { A as stopPostgres, C as checkServerReachable, D as status, E as blank, F as SdkError, M as createOwner, N as hasUser, O as ensurePostgres, P as FirstTreeHubSDK, S as checkServerHealth, T as printResults, _ as checkClientConfig, a as uninstallClientService, b as checkNodeVersion, c as promptAddAgent, f as onboardCheck, g as checkAgentConfigs, h as runMigrations, i as resolveCliInvocation, j as ClientRuntime, k as isDockerAvailable, l as promptMissingFields, n as installClientService, o as startServer, p as onboardCreate, r as isServiceSupported, s as isInteractive, t as getClientServiceStatus, u as formatCheckReport, v as checkDatabase, w as checkWebSocket, x as checkServerConfig, y as checkDocker } from "./core-BA5U1v9L.mjs";
3
3
  import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-D9JkMZnU.mjs";
4
4
  export { ClientRuntime, FirstTreeHubSDK, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, runMigrations, startServer, status, stopPostgres, uninstallClientService };