@agent-team-foundation/first-tree-hub 0.8.0 → 0.8.2

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,4 +1,5 @@
1
- import { t as __exportAll } from "./rolldown-runtime-twds-ZHy.mjs";
1
+ import { d as __exportAll } from "./esm-CYu4tXXn.mjs";
2
+ import { a as logLevelSchema, i as logFormatSchema } from "./logger-core-2yeIU1fc-B-__AsQO.mjs";
2
3
  import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
3
4
  import { dirname, join } from "node:path";
4
5
  import { z } from "zod";
@@ -60,12 +61,7 @@ const clientConfigSchema = defineConfig({
60
61
  auto: "client-id",
61
62
  env: "FIRST_TREE_HUB_CLIENT_ID"
62
63
  }) },
63
- logLevel: field(z.enum([
64
- "debug",
65
- "info",
66
- "warn",
67
- "error"
68
- ]).default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
64
+ logLevel: field(logLevelSchema.default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
69
65
  });
70
66
  const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree-hub");
71
67
  const DEFAULT_CONFIG_DIR = join(DEFAULT_HOME_DIR, "config");
@@ -473,7 +469,29 @@ const serverConfigSchema = defineConfig({
473
469
  secret: true
474
470
  }),
475
471
  hubPublicUrl: field(z.string(), { env: "FIRST_TREE_HUB_PUBLIC_URL" })
476
- })
472
+ }),
473
+ observability: {
474
+ logging: {
475
+ level: field(logLevelSchema.default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" }),
476
+ format: field(logFormatSchema.default(process.env.NODE_ENV === "production" ? "json" : "pretty")),
477
+ bridgeToSpanLevel: field(z.enum([
478
+ "error",
479
+ "warn",
480
+ "off"
481
+ ]).default("error"))
482
+ },
483
+ tracing: optional({
484
+ endpoint: field(z.string(), { env: "FIRST_TREE_HUB_OTEL_ENDPOINT" }),
485
+ headers: field(z.string().default(""), {
486
+ env: "FIRST_TREE_HUB_OTEL_HEADERS",
487
+ secret: true
488
+ }),
489
+ exporter: field(z.enum(["otlp-http", "otlp-grpc"]).default("otlp-http")),
490
+ serviceName: field(z.string().default("first-tree-hub")),
491
+ environment: field(z.string().default("development"), { env: "FIRST_TREE_HUB_OTEL_ENVIRONMENT" }),
492
+ sampleRate: field(z.number().min(0).max(1).default(1))
493
+ })
494
+ }
477
495
  });
478
496
  //#endregion
479
497
  //#region src/core/bootstrap.ts
@@ -520,6 +538,14 @@ function resolveAccessToken() {
520
538
  return creds.accessToken;
521
539
  }
522
540
  /**
541
+ * In-flight refresh promise. Multiple callers (WS handshake, proactive
542
+ * refresh timer, every SDK request) can see an expired token within the same
543
+ * millisecond — without dedupe each would fire an independent `/auth/refresh`
544
+ * round-trip and race to write `credentials.json`. Share one in-flight
545
+ * promise so N concurrent callers resolve from a single HTTP call.
546
+ */
547
+ let inflightRefresh = null;
548
+ /**
523
549
  * Ensure the persisted access token is fresh. Call before any API request
524
550
  * when using persisted credentials. Returns the (possibly refreshed) access
525
551
  * token. Service-user API keys are out of scope for this milestone.
@@ -528,19 +554,27 @@ async function ensureFreshAccessToken() {
528
554
  const creds = loadCredentials();
529
555
  if (!creds) throw new Error("No credentials found. Run `first-tree-hub client connect <server-url>` to sign in.");
530
556
  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;
557
+ if (inflightRefresh) return inflightRefresh;
558
+ inflightRefresh = (async () => {
559
+ const res = await fetch(`${creds.serverUrl}/api/v1/auth/refresh`, {
560
+ method: "POST",
561
+ headers: { "Content-Type": "application/json" },
562
+ body: JSON.stringify({ refreshToken: creds.refreshToken }),
563
+ signal: AbortSignal.timeout(1e4)
564
+ });
565
+ if (!res.ok) throw new Error("Access token expired and refresh failed. Run `first-tree-hub client connect <server-url>`.");
566
+ const data = await res.json();
567
+ saveCredentials({
568
+ ...creds,
569
+ accessToken: data.accessToken
570
+ });
571
+ return data.accessToken;
572
+ })();
573
+ try {
574
+ return await inflightRefresh;
575
+ } finally {
576
+ inflightRefresh = null;
577
+ }
544
578
  }
545
579
  /** Back-compat alias retained so existing call sites keep compiling. */
546
580
  const ensureFreshAdminToken = ensureFreshAccessToken;
@@ -1,7 +1,9 @@
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";
4
- import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-D9JkMZnU.mjs";
2
+ import "../logger-core-2yeIU1fc-B-__AsQO.mjs";
3
+ 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-99vUYmLs.mjs";
4
+ import "../observability-CJzDFY_G-CmvgUuzc.mjs";
5
+ import { A as stopPostgres, C as checkServerReachable, F as SdkError, I as SessionRegistry, L as cleanWorkspaces, M as createOwner, P as FirstTreeHubSDK, R as applyClientLoggerConfig, 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-CNR-lUlr.mjs";
6
+ import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-BOISS0DK.mjs";
5
7
  import { createRequire } from "node:module";
6
8
  import { Command } from "commander";
7
9
  import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
@@ -874,6 +876,7 @@ function registerClientCommands(program) {
874
876
  schema: clientConfigSchema,
875
877
  role: "client"
876
878
  });
879
+ applyClientLoggerConfig({ level: config.logLevel });
877
880
  const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
878
881
  const agents = loadAgents({
879
882
  schema: agentConfigSchema,
@@ -1126,13 +1129,13 @@ function isSecretField(schema, dotPath) {
1126
1129
  //#region src/commands/onboard.ts
1127
1130
  async function promptMissing(args) {
1128
1131
  if (!args.server) try {
1129
- const { resolveServerUrl } = await import("../bootstrap-CRDR6NwE.mjs").then((n) => n.t);
1132
+ const { resolveServerUrl } = await import("../bootstrap-99vUYmLs.mjs").then((n) => n.t);
1130
1133
  resolveServerUrl();
1131
1134
  } catch {
1132
1135
  args.server = await input({ message: "Hub server URL:" });
1133
1136
  saveOnboardState(args);
1134
1137
  }
1135
- const { loadCredentials } = await import("../bootstrap-CRDR6NwE.mjs").then((n) => n.t);
1138
+ const { loadCredentials } = await import("../bootstrap-99vUYmLs.mjs").then((n) => n.t);
1136
1139
  if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
1137
1140
  if (!args.id) {
1138
1141
  args.id = await input({ message: "Agent ID:" });