@agent-team-foundation/first-tree-hub 0.10.12 → 0.10.14
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.
- package/dist/{bootstrap-CDeXqhkQ.mjs → bootstrap-B2x4TTyJ.mjs} +33 -3
- package/dist/cli/index.mjs +43 -6
- package/dist/{dist-DwbhZyGi.mjs → dist-D6AOiyNg.mjs} +15 -0
- package/dist/{feishu-viiZmwcn.mjs → feishu-DQ1l18Ah.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{invitation-CBnQyB7o-DLQyW5ek.mjs → invitation-CBnQyB7o-Bulf3Sl7.mjs} +1 -1
- package/dist/{saas-connect-DLVGb8OH.mjs → saas-connect-DWcxHtjX.mjs} +296 -159
- package/dist/web/assets/{index-iNiy68VO.js → index-BQda2sqe.js} +1 -1
- package/dist/web/assets/index-C7yW7sWI.js +388 -0
- package/dist/web/assets/index-CKoTjI0J.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BR-pWR44.css +0 -1
- package/dist/web/assets/index-CJ9XbSvm.js +0 -388
|
@@ -563,6 +563,11 @@ const serverConfigSchema = defineConfig({
|
|
|
563
563
|
secret: true
|
|
564
564
|
})
|
|
565
565
|
},
|
|
566
|
+
auth: {
|
|
567
|
+
accessTokenExpiry: field(z.string().default("30m"), { env: "FIRST_TREE_HUB_AUTH_ACCESS_TOKEN_EXPIRY" }),
|
|
568
|
+
refreshTokenExpiry: field(z.string().default("30d"), { env: "FIRST_TREE_HUB_AUTH_REFRESH_TOKEN_EXPIRY" }),
|
|
569
|
+
connectTokenExpiry: field(z.string().default("10m"), { env: "FIRST_TREE_HUB_AUTH_CONNECT_TOKEN_EXPIRY" })
|
|
570
|
+
},
|
|
566
571
|
contextTree: optional({
|
|
567
572
|
repo: field(z.string(), {
|
|
568
573
|
env: "FIRST_TREE_HUB_CONTEXT_TREE_REPO",
|
|
@@ -644,6 +649,7 @@ const serverConfigSchema = defineConfig({
|
|
|
644
649
|
//#endregion
|
|
645
650
|
//#region src/core/bootstrap.ts
|
|
646
651
|
var bootstrap_exports = /* @__PURE__ */ __exportAll({
|
|
652
|
+
AuthRefreshFailedError: () => AuthRefreshFailedError,
|
|
647
653
|
ensureFreshAccessToken: () => ensureFreshAccessToken,
|
|
648
654
|
ensureFreshAdminToken: () => ensureFreshAdminToken,
|
|
649
655
|
loadCredentials: () => loadCredentials,
|
|
@@ -686,6 +692,20 @@ function resolveAccessToken() {
|
|
|
686
692
|
return creds.accessToken;
|
|
687
693
|
}
|
|
688
694
|
/**
|
|
695
|
+
* Thrown when `/auth/refresh` returns 401 — i.e. the persisted refresh
|
|
696
|
+
* token has expired or been revoked, so no amount of retrying will get
|
|
697
|
+
* us back online without operator action. Callers (the WS reconnect
|
|
698
|
+
* loop in particular) catch this distinctly from generic network/HTTP
|
|
699
|
+
* errors so they can stop the 1Hz reconnect-and-fail thrash and ask
|
|
700
|
+
* systemd/launchd to back off.
|
|
701
|
+
*/
|
|
702
|
+
var AuthRefreshFailedError = class extends Error {
|
|
703
|
+
constructor(message) {
|
|
704
|
+
super(message);
|
|
705
|
+
this.name = "AuthRefreshFailedError";
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
/**
|
|
689
709
|
* In-flight refresh promise. Multiple callers (WS handshake, proactive
|
|
690
710
|
* refresh timer, every SDK request) can see an expired token within the same
|
|
691
711
|
* millisecond — without dedupe each would fire an independent `/auth/refresh`
|
|
@@ -704,6 +724,14 @@ const DEFAULT_MIN_VALIDITY_MS = 3e4;
|
|
|
704
724
|
* token has less than that much life left. The WS proactive-refresh path
|
|
705
725
|
* passes a value that overlaps its lead window so it never receives a
|
|
706
726
|
* token already inside the "about to expire" zone.
|
|
727
|
+
*
|
|
728
|
+
* Sliding-window note: the server now rotates the refresh token on every
|
|
729
|
+
* successful `/auth/refresh`. We persist the rotated token alongside the
|
|
730
|
+
* new access token so an actively-used client never hits the absolute
|
|
731
|
+
* `refreshTokenExpiry` ceiling. If the response omits `refreshToken`
|
|
732
|
+
* (i.e. an older server) we keep the existing one — the cost is just
|
|
733
|
+
* losing the sliding behaviour against that backend, not a correctness
|
|
734
|
+
* regression.
|
|
707
735
|
*/
|
|
708
736
|
async function ensureFreshAccessToken(opts) {
|
|
709
737
|
const minValidityMs = opts?.minValidityMs ?? DEFAULT_MIN_VALIDITY_MS;
|
|
@@ -718,11 +746,13 @@ async function ensureFreshAccessToken(opts) {
|
|
|
718
746
|
body: JSON.stringify({ refreshToken: creds.refreshToken }),
|
|
719
747
|
signal: AbortSignal.timeout(1e4)
|
|
720
748
|
});
|
|
721
|
-
if (
|
|
749
|
+
if (res.status === 401) throw new AuthRefreshFailedError("Refresh token rejected by server. Re-run `first-tree-hub connect <token>` (get a fresh token from the Web Computers page → New Connection).");
|
|
750
|
+
if (!res.ok) throw new Error(`Refresh request failed with status ${res.status}.`);
|
|
722
751
|
const data = await res.json();
|
|
723
752
|
saveCredentials({
|
|
724
753
|
...creds,
|
|
725
|
-
accessToken: data.accessToken
|
|
754
|
+
accessToken: data.accessToken,
|
|
755
|
+
refreshToken: data.refreshToken ?? creds.refreshToken
|
|
726
756
|
});
|
|
727
757
|
return data.accessToken;
|
|
728
758
|
})();
|
|
@@ -778,4 +808,4 @@ function saveAgentConfig(agentName, agentId, runtime) {
|
|
|
778
808
|
return agentDir;
|
|
779
809
|
}
|
|
780
810
|
//#endregion
|
|
781
|
-
export {
|
|
811
|
+
export { resolveConfigReadonly as C, resetConfigMeta as S, setConfigValue as T, initConfig as _, loadCredentials as a, readConfigFile as b, saveAgentConfig as c, DEFAULT_DATA_DIR as d, DEFAULT_HOME_DIR as f, getConfigValue as g, collectMissingPrompts as h, ensureFreshAdminToken as i, saveCredentials as l, clientConfigSchema as m, bootstrap_exports as n, resolveAccessToken as o, agentConfigSchema as p, ensureFreshAccessToken as r, resolveServerUrl as s, AuthRefreshFailedError as t, DEFAULT_CONFIG_DIR as u, loadAgents as v, serverConfigSchema as w, resetConfig as x, migrateLegacyHome as y };
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "../observability-DPyf745N-BSc8QNcR.mjs";
|
|
3
|
-
import { $ as findStaleAliases, A as checkDatabase, B as installClientService, C as runHomeMigration, D as checkAgentConfigs, E as runMigrations, F as checkServerReachable, G as stopClientService, I as checkWebSocket, L as printResults, M as checkNodeVersion, N as checkServerConfig, O as checkBackgroundService, P as checkServerHealth, R as reconcileAgentConfigs, S as saveOnboardState, T as migrateLocalAgentDirs, U as restartClientService, V as isServiceSupported, W as startClientService, X as ClientRuntime, Y as stopPostgres, Z as handleClientOrgMismatch, _ as promptMissingFields, _t as probeCapabilities, a as declineUpdate, at as fail, b as onboardCheck, c as detectInstallMode, ct as print, d as startServer, dt as ClientOrgMismatchError, et as formatStaleReason, f as COMMAND_VERSION, ft as ClientUserMismatchError, g as promptAddAgent, gt as cleanWorkspaces, h as isInteractive, ht as SessionRegistry, i as createExecuteUpdate, it as resolveReplyToFromEnv, j as checkDocker, k as checkClientConfig, l as fetchLatestVersion, lt as setJsonMode, m as uploadClientCapabilities, mt as SdkError, nt as createOwner, o as promptUpdate, ot as success, p as reconcileLocalRuntimeProviders, pt as FirstTreeHubSDK, r as registerSaaSConnectCommand, s as PACKAGE_NAME, tt as removeLocalAgent, u as installGlobalLatest, v as formatCheckReport, vt as applyClientLoggerConfig, w as createApiNameResolver, x as onboardCreate, y as loadOnboardState, yt as configureClientLoggerForService, z as getClientServiceStatus } from "../saas-connect-
|
|
3
|
+
import { $ as findStaleAliases, A as checkDatabase, B as installClientService, C as runHomeMigration, D as checkAgentConfigs, E as runMigrations, F as checkServerReachable, G as stopClientService, I as checkWebSocket, L as printResults, M as checkNodeVersion, N as checkServerConfig, O as checkBackgroundService, P as checkServerHealth, R as reconcileAgentConfigs, S as saveOnboardState, T as migrateLocalAgentDirs, U as restartClientService, V as isServiceSupported, W as startClientService, X as ClientRuntime, Y as stopPostgres, Z as handleClientOrgMismatch, _ as promptMissingFields, _t as probeCapabilities, a as declineUpdate, at as fail, b as onboardCheck, c as detectInstallMode, ct as print, d as startServer, dt as ClientOrgMismatchError, et as formatStaleReason, f as COMMAND_VERSION, ft as ClientUserMismatchError, g as promptAddAgent, gt as cleanWorkspaces, h as isInteractive, ht as SessionRegistry, i as createExecuteUpdate, it as resolveReplyToFromEnv, j as checkDocker, k as checkClientConfig, l as fetchLatestVersion, lt as setJsonMode, m as uploadClientCapabilities, mt as SdkError, nt as createOwner, o as promptUpdate, ot as success, p as reconcileLocalRuntimeProviders, pt as FirstTreeHubSDK, r as registerSaaSConnectCommand, s as PACKAGE_NAME, tt as removeLocalAgent, u as installGlobalLatest, v as formatCheckReport, vt as applyClientLoggerConfig, w as createApiNameResolver, x as onboardCreate, y as loadOnboardState, yt as configureClientLoggerForService, z as getClientServiceStatus } from "../saas-connect-DWcxHtjX.mjs";
|
|
4
4
|
import "../logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
5
|
-
import { C as
|
|
6
|
-
import "../dist-
|
|
7
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-
|
|
5
|
+
import { C as resolveConfigReadonly, S as resetConfigMeta, T as setConfigValue, _ as initConfig, a as loadCredentials, b as readConfigFile, c as saveAgentConfig, d as DEFAULT_DATA_DIR, f as DEFAULT_HOME_DIR, g as getConfigValue, i as ensureFreshAdminToken, l as saveCredentials, m as clientConfigSchema, p as agentConfigSchema, r as ensureFreshAccessToken, s as resolveServerUrl, u as DEFAULT_CONFIG_DIR, v as loadAgents, w as serverConfigSchema, x as resetConfig } from "../bootstrap-B2x4TTyJ.mjs";
|
|
6
|
+
import "../dist-D6AOiyNg.mjs";
|
|
7
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-DQ1l18Ah.mjs";
|
|
8
8
|
import "../invitation-B1pjAyOz-BaCA9PII.mjs";
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { existsSync, mkdirSync, readFileSync, readdirSync } from "node:fs";
|
|
@@ -1308,6 +1308,24 @@ function registerClientCommands(program) {
|
|
|
1308
1308
|
print.line(` Hub: (could not read ${clientYaml}: ${msg.slice(0, 60)})\n`);
|
|
1309
1309
|
}
|
|
1310
1310
|
else print.line(" Hub: (not configured — run `first-tree-hub client connect <url>`)\n");
|
|
1311
|
+
const creds = loadCredentials();
|
|
1312
|
+
if (creds) {
|
|
1313
|
+
const exp = decodeJwtExpSeconds(creds.refreshToken);
|
|
1314
|
+
if (exp == null) print.line(" Auth: ⚠ could not parse refresh token (corrupt credentials)\n");
|
|
1315
|
+
else {
|
|
1316
|
+
const remainingSec = exp - Math.floor(Date.now() / 1e3);
|
|
1317
|
+
if (remainingSec <= 0) {
|
|
1318
|
+
print.line(" Auth: ✗ refresh token EXPIRED — re-run `first-tree-hub connect <token>`\n");
|
|
1319
|
+
print.line(" (get a fresh token from your Hub's Web admin → Computers → New Connection)\n");
|
|
1320
|
+
} else if (remainingSec < 2 * 86400) {
|
|
1321
|
+
const hours = Math.floor(remainingSec / 3600);
|
|
1322
|
+
print.line(` Auth: ⚠ refresh token expires in ~${hours}h — reclaim soon to stay online\n`);
|
|
1323
|
+
} else {
|
|
1324
|
+
const days = Math.floor(remainingSec / 86400);
|
|
1325
|
+
print.line(` Auth: ✓ refresh token valid for ~${days}d\n`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
} else print.line(" Auth: (no credentials — run `first-tree-hub client connect <url>`)\n");
|
|
1311
1329
|
const agentsDir = join(DEFAULT_CONFIG_DIR, "agents");
|
|
1312
1330
|
try {
|
|
1313
1331
|
const agents = loadAgents({
|
|
@@ -1474,6 +1492,25 @@ function getNested(obj, path) {
|
|
|
1474
1492
|
}
|
|
1475
1493
|
return typeof cur === "string" ? cur : null;
|
|
1476
1494
|
}
|
|
1495
|
+
/**
|
|
1496
|
+
* Pull the `exp` claim (in seconds since epoch) out of a JWT without
|
|
1497
|
+
* verifying the signature — the `client status` auth-health line just
|
|
1498
|
+
* needs the wall-clock countdown, not a trust decision. Returns null
|
|
1499
|
+
* for malformed tokens so the caller can render a friendly fallback
|
|
1500
|
+
* instead of crashing.
|
|
1501
|
+
*/
|
|
1502
|
+
function decodeJwtExpSeconds(token) {
|
|
1503
|
+
const parts = token.split(".");
|
|
1504
|
+
if (parts.length < 2 || !parts[1]) return null;
|
|
1505
|
+
try {
|
|
1506
|
+
const b64 = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
1507
|
+
const pad = b64.length % 4 === 0 ? "" : "=".repeat(4 - b64.length % 4);
|
|
1508
|
+
const payload = JSON.parse(Buffer.from(b64 + pad, "base64").toString("utf-8"));
|
|
1509
|
+
return typeof payload.exp === "number" ? payload.exp : null;
|
|
1510
|
+
} catch {
|
|
1511
|
+
return null;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1477
1514
|
//#endregion
|
|
1478
1515
|
//#region src/commands/config.ts
|
|
1479
1516
|
function resolveConfigPath(flags) {
|
|
@@ -1571,13 +1608,13 @@ function isSecretField(schema, dotPath) {
|
|
|
1571
1608
|
//#region src/commands/onboard.ts
|
|
1572
1609
|
async function promptMissing(args) {
|
|
1573
1610
|
if (!args.server) try {
|
|
1574
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1611
|
+
const { resolveServerUrl } = await import("../bootstrap-B2x4TTyJ.mjs").then((n) => n.n);
|
|
1575
1612
|
resolveServerUrl();
|
|
1576
1613
|
} catch {
|
|
1577
1614
|
args.server = await input({ message: "Hub server URL:" });
|
|
1578
1615
|
saveOnboardState(args);
|
|
1579
1616
|
}
|
|
1580
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1617
|
+
const { loadCredentials } = await import("../bootstrap-B2x4TTyJ.mjs").then((n) => n.n);
|
|
1581
1618
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1582
1619
|
if (!args.id) {
|
|
1583
1620
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -585,10 +585,25 @@ const addParticipantSchema = z.object({
|
|
|
585
585
|
});
|
|
586
586
|
z.object({ agentId: z.string().min(1) });
|
|
587
587
|
const clientStatusSchema = z.enum(["connected", "disconnected"]);
|
|
588
|
+
/**
|
|
589
|
+
* Auth health channel surfaced to the Web admin dashboard. Computed
|
|
590
|
+
* server-side per request from the row's offline duration vs the
|
|
591
|
+
* configured refresh-token TTL — there is no DB column. See
|
|
592
|
+
* `deriveAuthState` server-side.
|
|
593
|
+
*
|
|
594
|
+
* - `ok` — online, or recently offline (cached refresh token can
|
|
595
|
+
* plausibly still mint access tokens).
|
|
596
|
+
* - `expired` — offline longer than the refresh-token TTL; the client
|
|
597
|
+
* cannot recover on its own. The operator mints a fresh
|
|
598
|
+
* connect token via the Web "+ New Connection" button
|
|
599
|
+
* (or the inline Reconnect button on the row).
|
|
600
|
+
*/
|
|
601
|
+
const clientAuthStateSchema = z.enum(["ok", "expired"]);
|
|
588
602
|
z.object({
|
|
589
603
|
id: z.string(),
|
|
590
604
|
userId: z.string().nullable(),
|
|
591
605
|
status: clientStatusSchema,
|
|
606
|
+
authState: clientAuthStateSchema,
|
|
592
607
|
sdkVersion: z.string().max(50).nullable(),
|
|
593
608
|
hostname: z.string().max(100).nullable(),
|
|
594
609
|
os: z.string().max(50).nullable(),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { d as __exportAll } from "./esm-CYu4tXXn.mjs";
|
|
2
|
-
import { r as AGENT_SELECTOR_HEADER } from "./dist-
|
|
2
|
+
import { r as AGENT_SELECTOR_HEADER } from "./dist-D6AOiyNg.mjs";
|
|
3
3
|
//#region src/core/feishu.ts
|
|
4
4
|
var feishu_exports = /* @__PURE__ */ __exportAll({
|
|
5
5
|
bindFeishuBot: () => bindFeishuBot,
|
package/dist/index.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import "./observability-DPyf745N-BSc8QNcR.mjs";
|
|
2
|
-
import { A as checkDatabase, B as installClientService, C as runHomeMigration, D as checkAgentConfigs, E as runMigrations, F as checkServerReachable, G as stopClientService, H as resolveCliInvocation, I as checkWebSocket, J as isDockerAvailable, K as uninstallClientService, L as printResults, M as checkNodeVersion, N as checkServerConfig, P as checkServerHealth, Q as rotateClientIdWithBackup, U as restartClientService, V as isServiceSupported, W as startClientService, X as ClientRuntime, Y as stopPostgres, Z as handleClientOrgMismatch, _ as promptMissingFields, b as onboardCheck, d as startServer, g as promptAddAgent, h as isInteractive, j as checkDocker, k as checkClientConfig, mt as SdkError, n as deriveHubUrlFromToken, nt as createOwner, pt as FirstTreeHubSDK, q as ensurePostgres, rt as hasUser, st as blank, t as HubUrlDerivationError, ut as status, v as formatCheckReport, x as onboardCreate, z as getClientServiceStatus } from "./saas-connect-
|
|
2
|
+
import { A as checkDatabase, B as installClientService, C as runHomeMigration, D as checkAgentConfigs, E as runMigrations, F as checkServerReachable, G as stopClientService, H as resolveCliInvocation, I as checkWebSocket, J as isDockerAvailable, K as uninstallClientService, L as printResults, M as checkNodeVersion, N as checkServerConfig, P as checkServerHealth, Q as rotateClientIdWithBackup, U as restartClientService, V as isServiceSupported, W as startClientService, X as ClientRuntime, Y as stopPostgres, Z as handleClientOrgMismatch, _ as promptMissingFields, b as onboardCheck, d as startServer, g as promptAddAgent, h as isInteractive, j as checkDocker, k as checkClientConfig, mt as SdkError, n as deriveHubUrlFromToken, nt as createOwner, pt as FirstTreeHubSDK, q as ensurePostgres, rt as hasUser, st as blank, t as HubUrlDerivationError, ut as status, v as formatCheckReport, x as onboardCreate, z as getClientServiceStatus } from "./saas-connect-DWcxHtjX.mjs";
|
|
3
3
|
import "./logger-core-BTmvdflj-DjW8FM4T.mjs";
|
|
4
|
-
import {
|
|
5
|
-
import "./dist-
|
|
6
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-
|
|
4
|
+
import { i as ensureFreshAdminToken, o as resolveAccessToken, r as ensureFreshAccessToken, s as resolveServerUrl, t as AuthRefreshFailedError } from "./bootstrap-B2x4TTyJ.mjs";
|
|
5
|
+
import "./dist-D6AOiyNg.mjs";
|
|
6
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "./feishu-DQ1l18Ah.mjs";
|
|
7
7
|
import "./invitation-B1pjAyOz-BaCA9PII.mjs";
|
|
8
|
-
export { ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, restartClientService, rotateClientIdWithBackup, runHomeMigration, runMigrations, startClientService, startServer, status, stopClientService, stopPostgres, uninstallClientService };
|
|
8
|
+
export { AuthRefreshFailedError, ClientRuntime, FirstTreeHubSDK, HubUrlDerivationError, SdkError, bindFeishuBot, bindFeishuUser, blank, checkAgentConfigs, checkClientConfig, checkDatabase, checkDocker, checkNodeVersion, checkServerConfig, checkServerHealth, checkServerReachable, checkWebSocket, createOwner, deriveHubUrlFromToken, ensureFreshAccessToken, ensureFreshAdminToken, ensurePostgres, formatCheckReport, getClientServiceStatus, handleClientOrgMismatch, hasUser, installClientService, isDockerAvailable, isInteractive, isServiceSupported, onboardCheck, onboardCreate, printResults, promptAddAgent, promptMissingFields, resolveAccessToken, resolveCliInvocation, resolveServerUrl, restartClientService, rotateClientIdWithBackup, runHomeMigration, runMigrations, startClientService, startServer, status, stopClientService, stopPostgres, uninstallClientService };
|