@agent-team-foundation/first-tree-hub 0.8.6 → 0.9.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.
- package/dist/{bootstrap-99vUYmLs.mjs → bootstrap-CWcBzk6C.mjs} +124 -4
- package/dist/cli/index.mjs +26 -12
- package/dist/{core-e1-NPfEC.mjs → core-DzuW7b5v.mjs} +1168 -558
- package/dist/{feishu-n9Y2yGTT.mjs → feishu-GlaczcVf.mjs} +5 -0
- package/dist/index.mjs +4 -4
- package/dist/web/assets/{index-nMyXPMPC.js → index-CDv9Rfc_.js} +73 -73
- package/dist/web/assets/index-DlK6gHQF.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -1
- package/dist/web/assets/index-D9iKLIsB.css +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { d as __exportAll } from "./esm-CYu4tXXn.mjs";
|
|
2
2
|
import { a as logLevelSchema, i as logFormatSchema } from "./logger-core-2yeIU1fc-B-__AsQO.mjs";
|
|
3
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { chmodSync, cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { parse, stringify } from "yaml";
|
|
@@ -28,7 +28,7 @@ function defineConfig(shape) {
|
|
|
28
28
|
return shape;
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* Agent config layout on disk: `~/.first-tree
|
|
31
|
+
* Agent config layout on disk: `~/.first-tree/hub/config/agents/<name>/agent.yaml`.
|
|
32
32
|
*
|
|
33
33
|
* After the unified-user-token milestone the local config no longer stores an
|
|
34
34
|
* agent bearer; authentication comes from the user's member JWT in
|
|
@@ -45,10 +45,28 @@ const agentConfigSchema = defineConfig({
|
|
|
45
45
|
max_sessions: field(z.number().int().positive().default(10))
|
|
46
46
|
}
|
|
47
47
|
});
|
|
48
|
+
/**
|
|
49
|
+
* Phase-dependent defaults that flip with release milestones. Kept as a plain
|
|
50
|
+
* module-level constant so reviews of the beta→GA transition are a one-line
|
|
51
|
+
* diff, and so tests can mock this module to exercise both branches.
|
|
52
|
+
*/
|
|
53
|
+
const UPDATE_POLICIES = [
|
|
54
|
+
"auto",
|
|
55
|
+
"prompt",
|
|
56
|
+
"off"
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* Default value of `update.policy` on the Client config. During the beta this
|
|
60
|
+
* is `"auto"` — operators rarely know to `npm i -g` weekly and we chase the
|
|
61
|
+
* latest published Command by default. The GA PR flips it to `"prompt"` and
|
|
62
|
+
* bumps Command to `1.0.0`.
|
|
63
|
+
*/
|
|
64
|
+
const UPDATE_POLICY_DEFAULT = "auto";
|
|
48
65
|
/** Store the resolved config as a singleton. Called by initConfig(). */
|
|
49
66
|
function setConfig(config) {}
|
|
50
67
|
/** Reset the config singleton. For testing only. */
|
|
51
68
|
function resetConfig() {}
|
|
69
|
+
const updatePolicySchema = z.enum(UPDATE_POLICIES);
|
|
52
70
|
const clientConfigSchema = defineConfig({
|
|
53
71
|
server: { url: field(z.string(), {
|
|
54
72
|
env: "FIRST_TREE_HUB_SERVER_URL",
|
|
@@ -61,9 +79,15 @@ const clientConfigSchema = defineConfig({
|
|
|
61
79
|
auto: "client-id",
|
|
62
80
|
env: "FIRST_TREE_HUB_CLIENT_ID"
|
|
63
81
|
}) },
|
|
82
|
+
update: {
|
|
83
|
+
policy: field(updatePolicySchema.default(UPDATE_POLICY_DEFAULT), { env: "FIRST_TREE_HUB_UPDATE_POLICY" }),
|
|
84
|
+
restart_quiet_seconds: field(z.number().int().min(1).max(3600).default(30), { env: "FIRST_TREE_HUB_UPDATE_RESTART_QUIET_SECONDS" }),
|
|
85
|
+
restart_check_interval_seconds: field(z.number().int().min(5).max(300).default(10), { env: "FIRST_TREE_HUB_UPDATE_RESTART_CHECK_INTERVAL_SECONDS" }),
|
|
86
|
+
prompt_timeout_seconds: field(z.number().int().min(10).max(600).default(60), { env: "FIRST_TREE_HUB_UPDATE_PROMPT_TIMEOUT_SECONDS" })
|
|
87
|
+
},
|
|
64
88
|
logLevel: field(logLevelSchema.default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
|
|
65
89
|
});
|
|
66
|
-
const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree
|
|
90
|
+
const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree", "hub");
|
|
67
91
|
const DEFAULT_CONFIG_DIR = join(DEFAULT_HOME_DIR, "config");
|
|
68
92
|
const DEFAULT_DATA_DIR = join(DEFAULT_HOME_DIR, "data");
|
|
69
93
|
function isFieldDef(value) {
|
|
@@ -407,6 +431,102 @@ function loadAgents(options) {
|
|
|
407
431
|
}
|
|
408
432
|
return result;
|
|
409
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Pre-v0.9 path. The home directory was a flat `~/.first-tree-hub/` — this
|
|
436
|
+
* was renamed to `~/.first-tree/hub/` so the `.first-tree/` parent can be
|
|
437
|
+
* shared with sibling products (context-tree etc.) under the same brand.
|
|
438
|
+
*/
|
|
439
|
+
const LEGACY_HOME_DIR = join(homedir(), ".first-tree-hub");
|
|
440
|
+
/**
|
|
441
|
+
* Auto-migrate the legacy `~/.first-tree-hub/` home to the new
|
|
442
|
+
* `~/.first-tree/hub/` layout. Designed to be called once at CLI startup —
|
|
443
|
+
* idempotent, never throws, and skips any case that could merge state.
|
|
444
|
+
*
|
|
445
|
+
* **Copy-only semantics:** the legacy tree is preserved on disk as a safety
|
|
446
|
+
* net. Users can inspect or fall back to it, and can delete it manually
|
|
447
|
+
* once they've confirmed the new location is healthy. Idempotency is
|
|
448
|
+
* therefore keyed on whether the *target* already has content, not on
|
|
449
|
+
* whether the legacy path still exists.
|
|
450
|
+
*
|
|
451
|
+
* Skip rules (in order):
|
|
452
|
+
* 1. `FIRST_TREE_HUB_HOME` is set → user is driving the path explicitly.
|
|
453
|
+
* 2. Legacy path doesn't exist → nothing to migrate.
|
|
454
|
+
* 3. New path already has content → treat as already-migrated (or a
|
|
455
|
+
* conflict the user must resolve). Either way, never merge.
|
|
456
|
+
*
|
|
457
|
+
* Otherwise we recursively copy legacy → new with `cpSync`, preserving
|
|
458
|
+
* mtimes so log rotation and mtime heuristics keep working.
|
|
459
|
+
*/
|
|
460
|
+
/**
|
|
461
|
+
* Walk `src` depth-first and mirror every directory's mode onto the matching
|
|
462
|
+
* path in `dest`. Skips symlinks (we don't want to chmod whatever they
|
|
463
|
+
* resolve to — that can reach outside the tree). Files are left alone
|
|
464
|
+
* because `cpSync` already preserves file modes.
|
|
465
|
+
*/
|
|
466
|
+
function syncDirectoryModes(src, dest) {
|
|
467
|
+
chmodSync(dest, statSync(src).mode & 4095);
|
|
468
|
+
const stack = [""];
|
|
469
|
+
while (stack.length > 0) {
|
|
470
|
+
const rel = stack.pop();
|
|
471
|
+
if (rel === void 0) break;
|
|
472
|
+
const srcDir = join(src, rel);
|
|
473
|
+
for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
|
|
474
|
+
if (!entry.isDirectory()) continue;
|
|
475
|
+
const relChild = rel === "" ? entry.name : join(rel, entry.name);
|
|
476
|
+
const mode = statSync(join(src, relChild)).mode & 4095;
|
|
477
|
+
chmodSync(join(dest, relChild), mode);
|
|
478
|
+
stack.push(relChild);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
function migrateLegacyHome(opts) {
|
|
483
|
+
const { newHome, envOverride } = opts;
|
|
484
|
+
const legacyHome = opts.legacyHome ?? LEGACY_HOME_DIR;
|
|
485
|
+
if (envOverride) return {
|
|
486
|
+
migrated: false,
|
|
487
|
+
reason: "custom-home"
|
|
488
|
+
};
|
|
489
|
+
if (!existsSync(legacyHome)) return {
|
|
490
|
+
migrated: false,
|
|
491
|
+
reason: "no-legacy-dir"
|
|
492
|
+
};
|
|
493
|
+
if (existsSync(newHome)) try {
|
|
494
|
+
if (readdirSync(newHome).length > 0) return {
|
|
495
|
+
migrated: false,
|
|
496
|
+
reason: "new-dir-populated",
|
|
497
|
+
from: legacyHome,
|
|
498
|
+
to: newHome
|
|
499
|
+
};
|
|
500
|
+
} catch (err) {
|
|
501
|
+
return {
|
|
502
|
+
migrated: false,
|
|
503
|
+
reason: "failed",
|
|
504
|
+
from: legacyHome,
|
|
505
|
+
to: newHome,
|
|
506
|
+
error: err instanceof Error ? err.message : String(err)
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
try {
|
|
510
|
+
cpSync(legacyHome, newHome, {
|
|
511
|
+
recursive: true,
|
|
512
|
+
preserveTimestamps: true
|
|
513
|
+
});
|
|
514
|
+
syncDirectoryModes(legacyHome, newHome);
|
|
515
|
+
return {
|
|
516
|
+
migrated: true,
|
|
517
|
+
from: legacyHome,
|
|
518
|
+
to: newHome
|
|
519
|
+
};
|
|
520
|
+
} catch (err) {
|
|
521
|
+
return {
|
|
522
|
+
migrated: false,
|
|
523
|
+
reason: "failed",
|
|
524
|
+
from: legacyHome,
|
|
525
|
+
to: newHome,
|
|
526
|
+
error: err instanceof Error ? err.message : String(err)
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
}
|
|
410
530
|
const serverConfigSchema = defineConfig({
|
|
411
531
|
database: {
|
|
412
532
|
url: field(z.string(), {
|
|
@@ -622,4 +742,4 @@ function saveAgentConfig(agentName, agentId, runtime) {
|
|
|
622
742
|
return agentDir;
|
|
623
743
|
}
|
|
624
744
|
//#endregion
|
|
625
|
-
export {
|
|
745
|
+
export { serverConfigSchema as C, resolveConfigReadonly as S, loadAgents as _, resolveAccessToken as a, resetConfig as b, saveCredentials as c, DEFAULT_HOME_DIR as d, agentConfigSchema as f, initConfig as g, getConfigValue as h, loadCredentials as i, DEFAULT_CONFIG_DIR as l, collectMissingPrompts as m, ensureFreshAccessToken as n, resolveServerUrl as o, clientConfigSchema as p, ensureFreshAdminToken as r, saveAgentConfig as s, bootstrap_exports as t, DEFAULT_DATA_DIR as u, migrateLegacyHome as v, setConfigValue as w, resetConfigMeta as x, readConfigFile as y };
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "../logger-core-2yeIU1fc-B-__AsQO.mjs";
|
|
3
|
-
import { C as
|
|
3
|
+
import { C as serverConfigSchema, _ as loadAgents, b as resetConfig, c as saveCredentials, f as agentConfigSchema, g as initConfig, h as getConfigValue, i as loadCredentials, 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, w as setConfigValue, x as resetConfigMeta, y as readConfigFile } from "../bootstrap-CWcBzk6C.mjs";
|
|
4
4
|
import "../observability-CJzDFY_G-CmvgUuzc.mjs";
|
|
5
|
-
import { A as
|
|
6
|
-
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-
|
|
7
|
-
import { createRequire } from "node:module";
|
|
5
|
+
import { A as printResults, B as SdkError, C as checkDatabase, D as checkServerHealth, E as checkServerConfig, F as stopPostgres, H as cleanWorkspaces, I as ClientRuntime, L as createOwner, O as checkServerReachable, S as checkClientConfig, T as checkNodeVersion, U as applyClientLoggerConfig, V as SessionRegistry, _ as isServiceSupported, a as COMMAND_VERSION, b as runMigrations, c as promptMissingFields, d as onboardCheck, f as onboardCreate, g as installClientService, h as getClientServiceStatus, i as startServer, k as checkWebSocket, l as formatCheckReport, m as runHomeMigration, n as declineUpdate, o as isInteractive, p as saveOnboardState, r as promptUpdate, s as promptAddAgent, t as createExecuteUpdate, u as loadOnboardState, w as checkDocker, x as checkAgentConfigs, y as uninstallClientService, z as FirstTreeHubSDK } from "../core-DzuW7b5v.mjs";
|
|
6
|
+
import { n as bindFeishuUser, t as bindFeishuBot } from "../feishu-GlaczcVf.mjs";
|
|
8
7
|
import { Command } from "commander";
|
|
9
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
|
10
9
|
import { join } from "node:path";
|
|
@@ -781,7 +780,7 @@ function decodeJwtPayload(token) {
|
|
|
781
780
|
* and give the operator a chance to back out before we overwrite credentials.
|
|
782
781
|
*
|
|
783
782
|
* Why this gate exists: running `client connect` implicitly overwrites
|
|
784
|
-
* `~/.first-tree
|
|
783
|
+
* `~/.first-tree/hub/config/credentials.json`. Without this prompt, someone
|
|
785
784
|
* onboarding a second account on their own machine silently logs themselves
|
|
786
785
|
* out of the first account — they'd only notice when their "main" agents
|
|
787
786
|
* appeared offline later. We treat single-account-per-machine as the product
|
|
@@ -839,7 +838,7 @@ async function promptReplaceOrCancel(newMemberId, newServerUrl) {
|
|
|
839
838
|
function printIsolationGuide(newServerUrl) {
|
|
840
839
|
process.stderr.write("\n Cancelled. The existing account on this computer is untouched.\n\n");
|
|
841
840
|
process.stderr.write(" To run this new account alongside it (advanced — no background service):\n\n");
|
|
842
|
-
process.stderr.write(" export FIRST_TREE_HUB_HOME=\"$HOME/.first-tree
|
|
841
|
+
process.stderr.write(" export FIRST_TREE_HUB_HOME=\"$HOME/.first-tree/hub-<label>\"\n");
|
|
843
842
|
process.stderr.write(` first-tree-hub client connect ${newServerUrl} --token <token>\n`);
|
|
844
843
|
process.stderr.write(" first-tree-hub client start\n\n");
|
|
845
844
|
process.stderr.write(" Notes:\n");
|
|
@@ -930,7 +929,14 @@ function registerConnectCommand(parent) {
|
|
|
930
929
|
schema: agentConfigSchema,
|
|
931
930
|
agentsDir
|
|
932
931
|
});
|
|
933
|
-
const runtime = new ClientRuntime(config.server.url, config.client.id
|
|
932
|
+
const runtime = new ClientRuntime(config.server.url, config.client.id, {
|
|
933
|
+
currentVersion: COMMAND_VERSION,
|
|
934
|
+
update: {
|
|
935
|
+
updateConfig: config.update,
|
|
936
|
+
prompt: promptUpdate,
|
|
937
|
+
executeUpdate: createExecuteUpdate({ managed: false })
|
|
938
|
+
}
|
|
939
|
+
});
|
|
934
940
|
for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
|
|
935
941
|
await runtime.start();
|
|
936
942
|
runtime.watchAgentsDir(agentsDir);
|
|
@@ -980,7 +986,15 @@ function registerClientCommands(program) {
|
|
|
980
986
|
agentsDir
|
|
981
987
|
});
|
|
982
988
|
process.stderr.write(`\n Connecting to ${config.server.url} (client id: ${config.client.id})...\n`);
|
|
983
|
-
const
|
|
989
|
+
const managed = options.interactive === false;
|
|
990
|
+
const runtime = new ClientRuntime(config.server.url, config.client.id, {
|
|
991
|
+
currentVersion: COMMAND_VERSION,
|
|
992
|
+
update: {
|
|
993
|
+
updateConfig: config.update,
|
|
994
|
+
prompt: managed ? declineUpdate : promptUpdate,
|
|
995
|
+
executeUpdate: createExecuteUpdate({ managed })
|
|
996
|
+
}
|
|
997
|
+
});
|
|
984
998
|
for (const [name, agentConfig] of agents) runtime.addAgent(name, agentConfig);
|
|
985
999
|
await runtime.start();
|
|
986
1000
|
runtime.watchAgentsDir(agentsDir);
|
|
@@ -1226,13 +1240,13 @@ function isSecretField(schema, dotPath) {
|
|
|
1226
1240
|
//#region src/commands/onboard.ts
|
|
1227
1241
|
async function promptMissing(args) {
|
|
1228
1242
|
if (!args.server) try {
|
|
1229
|
-
const { resolveServerUrl } = await import("../bootstrap-
|
|
1243
|
+
const { resolveServerUrl } = await import("../bootstrap-CWcBzk6C.mjs").then((n) => n.t);
|
|
1230
1244
|
resolveServerUrl();
|
|
1231
1245
|
} catch {
|
|
1232
1246
|
args.server = await input({ message: "Hub server URL:" });
|
|
1233
1247
|
saveOnboardState(args);
|
|
1234
1248
|
}
|
|
1235
|
-
const { loadCredentials } = await import("../bootstrap-
|
|
1249
|
+
const { loadCredentials } = await import("../bootstrap-CWcBzk6C.mjs").then((n) => n.t);
|
|
1236
1250
|
if (!loadCredentials()) throw new Error("No saved credentials. Run `first-tree-hub client connect <server-url>` before onboarding.");
|
|
1237
1251
|
if (!args.id) {
|
|
1238
1252
|
args.id = await input({ message: "Agent ID:" });
|
|
@@ -1492,9 +1506,9 @@ function formatUptime(seconds) {
|
|
|
1492
1506
|
}
|
|
1493
1507
|
//#endregion
|
|
1494
1508
|
//#region src/cli/index.ts
|
|
1495
|
-
|
|
1509
|
+
runHomeMigration();
|
|
1496
1510
|
const program = new Command();
|
|
1497
|
-
program.name("first-tree-hub").description("First Tree Hub — centralized collaboration platform for agent teams").version(
|
|
1511
|
+
program.name("first-tree-hub").description("First Tree Hub — centralized collaboration platform for agent teams").version(COMMAND_VERSION);
|
|
1498
1512
|
registerServerCommands(program);
|
|
1499
1513
|
registerClientCommands(program);
|
|
1500
1514
|
registerAgentCommands(program);
|