@agent-team-foundation/first-tree-hub 0.10.9 → 0.10.11
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-jx5nN1qZ.mjs → bootstrap-BTKiVIYk.mjs} +4 -1
- package/dist/cli/index.mjs +198 -68
- package/dist/{dist-DSr_I5Ia.mjs → dist-DwbhZyGi.mjs} +1 -1
- package/dist/{feishu-eynC54km.mjs → feishu-viiZmwcn.mjs} +1 -1
- package/dist/index.mjs +5 -5
- package/dist/{invitation-CBnQyB7o-DVOpS_Ts.mjs → invitation-CBnQyB7o-DLQyW5ek.mjs} +1 -1
- package/dist/{saas-connect-CKQ15VLz.mjs → saas-connect-D-7x9KRd.mjs} +428 -56
- package/dist/web/assets/{index-B33n1w2k.js → index-CJ9XbSvm.js} +81 -81
- package/dist/web/assets/{index-DIgiOalZ.js → index-iNiy68VO.js} +1 -1
- package/dist/web/index.html +1 -1
- package/package.json +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { m as __toESM } from "./esm-CYu4tXXn.mjs";
|
|
2
2
|
import { _ as withSpan, a as endWsConnectionSpan, b as require_pino, c as messageAttrs, d as rootLogger$1, g as startWsConnectionSpan, i as currentTraceId, n as applyLoggerConfig, o as getFastifyOtelPlugin, p as setWsConnectionAttrs, r as createLogger$1, t as adapterAttrs, u as observabilityPlugin, v as withWsMessageSpan, y as FIRST_TREE_HUB_ATTR } from "./observability-DPyf745N-BSc8QNcR.mjs";
|
|
3
|
-
import { C as serverConfigSchema, S as resolveConfigReadonly, _ as loadAgents, b as resetConfig, c as saveCredentials, 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, v as migrateLegacyHome, w as setConfigValue, x as resetConfigMeta } from "./bootstrap-
|
|
4
|
-
import { $ as refreshTokenSchema, A as createOrgFromMeSchema, B as imageInlineContentSchema, C as clientRegisterSchema, D as createAgentSchema, E as createAdapterMappingSchema, F as dryRunAgentRuntimeConfigSchema, G as isReservedAgentName$1, H as inboxDeliverFrameSchema$1, I as extractMentions, J as loginSchema, K as joinByInvitationSchema, L as githubCallbackQuerySchema, M as createTaskSchema, N as defaultRuntimeConfigPayload, O as createChatSchema, P as delegateFeishuUserSchema, Q as rebindAgentSchema, R as githubDevCallbackQuerySchema, S as clientCapabilitiesSchema$1, St as wsAuthFrameSchema, T as createAdapterConfigSchema, U as inboxPollQuerySchema, V as inboxAckFrameSchema, W as isRedactedEnvValue, X as notificationQuerySchema, Y as messageSourceSchema$1, Z as paginationQuerySchema, _ as adminUpdateTaskSchema, _t as updateClientCapabilitiesSchema, a as AGENT_STATUSES, at as sendToAgentSchema, b as agentRuntimeConfigPayloadSchema$1, bt as updateSystemConfigSchema, ct as sessionEventSchema$1, d as TASK_HEALTH_SIGNALS, dt as switchOrgSchema, et as runtimeStateMessageSchema, f as TASK_STATUSES, ft as taskListQuerySchema, g as adminCreateTaskSchema, gt as updateChatSchema, h as addParticipantSchema, ht as updateAgentSchema, i as AGENT_SOURCES, it as sendMessageSchema, j as createOrganizationSchema, k as createMemberSchema, l as SYSTEM_CONFIG_DEFAULTS, lt as sessionReconcileRequestSchema, m as WS_AUTH_FRAME_TIMEOUT_MS, mt as updateAgentRuntimeConfigSchema, n as AGENT_NAME_REGEX$1, nt as scanMentionTokens, o as AGENT_TYPES, ot as sessionCompletionMessageSchema, p as TASK_TERMINAL_STATUSES, pt as updateAdapterConfigSchema, q as linkTaskChatSchema, r as AGENT_SELECTOR_HEADER$1, rt as selfServiceFeishuBotSchema, s as AGENT_VISIBILITY, st as sessionEventMessageSchema, t as AGENT_BIND_REJECT_REASONS, tt as safeRedirectPath, u as TASK_CREATOR_TYPES, ut as sessionStateMessageSchema, v as agentBindRequestSchema, vt as updateMemberSchema, w as connectTokenExchangeSchema, x as agentTypeSchema$1, xt as updateTaskStatusSchema, y as agentPinnedMessageSchema$1, yt as updateOrganizationSchema, z as githubStartQuerySchema } from "./dist-
|
|
3
|
+
import { C as serverConfigSchema, S as resolveConfigReadonly, _ as loadAgents, b as resetConfig, c as saveCredentials, 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, v as migrateLegacyHome, w as setConfigValue, x as resetConfigMeta } from "./bootstrap-BTKiVIYk.mjs";
|
|
4
|
+
import { $ as refreshTokenSchema, A as createOrgFromMeSchema, B as imageInlineContentSchema, C as clientRegisterSchema, D as createAgentSchema, E as createAdapterMappingSchema, F as dryRunAgentRuntimeConfigSchema, G as isReservedAgentName$1, H as inboxDeliverFrameSchema$1, I as extractMentions, J as loginSchema, K as joinByInvitationSchema, L as githubCallbackQuerySchema, M as createTaskSchema, N as defaultRuntimeConfigPayload, O as createChatSchema, P as delegateFeishuUserSchema, Q as rebindAgentSchema, R as githubDevCallbackQuerySchema, S as clientCapabilitiesSchema$1, St as wsAuthFrameSchema, T as createAdapterConfigSchema, U as inboxPollQuerySchema, V as inboxAckFrameSchema, W as isRedactedEnvValue, X as notificationQuerySchema, Y as messageSourceSchema$1, Z as paginationQuerySchema, _ as adminUpdateTaskSchema, _t as updateClientCapabilitiesSchema, a as AGENT_STATUSES, at as sendToAgentSchema, b as agentRuntimeConfigPayloadSchema$1, bt as updateSystemConfigSchema, ct as sessionEventSchema$1, d as TASK_HEALTH_SIGNALS, dt as switchOrgSchema, et as runtimeStateMessageSchema, f as TASK_STATUSES, ft as taskListQuerySchema, g as adminCreateTaskSchema, gt as updateChatSchema, h as addParticipantSchema, ht as updateAgentSchema, i as AGENT_SOURCES, it as sendMessageSchema, j as createOrganizationSchema, k as createMemberSchema, l as SYSTEM_CONFIG_DEFAULTS, lt as sessionReconcileRequestSchema, m as WS_AUTH_FRAME_TIMEOUT_MS, mt as updateAgentRuntimeConfigSchema, n as AGENT_NAME_REGEX$1, nt as scanMentionTokens, o as AGENT_TYPES, ot as sessionCompletionMessageSchema, p as TASK_TERMINAL_STATUSES, pt as updateAdapterConfigSchema, q as linkTaskChatSchema, r as AGENT_SELECTOR_HEADER$1, rt as selfServiceFeishuBotSchema, s as AGENT_VISIBILITY, st as sessionEventMessageSchema, t as AGENT_BIND_REJECT_REASONS, tt as safeRedirectPath, u as TASK_CREATOR_TYPES, ut as sessionStateMessageSchema, v as agentBindRequestSchema, vt as updateMemberSchema, w as connectTokenExchangeSchema, x as agentTypeSchema$1, xt as updateTaskStatusSchema, y as agentPinnedMessageSchema$1, yt as updateOrganizationSchema, z as githubStartQuerySchema } from "./dist-DwbhZyGi.mjs";
|
|
5
5
|
import { _ as recordRedemption, a as ConflictError, b as uuidv7, c as UnauthorizedError, d as findActiveByToken, f as getActiveInvitation, h as organizations, i as ClientUserMismatchError$1, l as buildInviteUrl, m as invitations, n as BadRequestError, o as ForbiddenError, p as invitationRedemptions, r as ClientOrgMismatchError$1, s as NotFoundError, t as AppError, u as ensureActiveInvitation, y as users } from "./invitation-B1pjAyOz-BaCA9PII.mjs";
|
|
6
6
|
import { createRequire } from "node:module";
|
|
7
7
|
import { ZodError, z } from "zod";
|
|
8
|
-
import { delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
8
|
+
import { basename, delimiter, dirname, isAbsolute, join, resolve } from "node:path";
|
|
9
9
|
import { Writable } from "node:stream";
|
|
10
10
|
import { homedir, hostname, platform, tmpdir, userInfo } from "node:os";
|
|
11
11
|
import { EventEmitter } from "node:events";
|
|
@@ -460,7 +460,7 @@ z.object({
|
|
|
460
460
|
type: agentTypeSchema,
|
|
461
461
|
displayName: z.string().min(1).max(200).optional(),
|
|
462
462
|
delegateMention: z.string().max(100).optional(),
|
|
463
|
-
organizationId: z.string().max(100).optional(),
|
|
463
|
+
organizationId: z.string().min(1).max(100).optional(),
|
|
464
464
|
source: agentSourceSchema.optional(),
|
|
465
465
|
visibility: agentVisibilitySchema.optional(),
|
|
466
466
|
metadata: z.record(z.string(), z.unknown()).optional(),
|
|
@@ -1539,11 +1539,14 @@ defineConfig({
|
|
|
1539
1539
|
})
|
|
1540
1540
|
}) }),
|
|
1541
1541
|
cors: optional({ origin: field(z.string(), { env: "FIRST_TREE_HUB_CORS_ORIGIN" }) }),
|
|
1542
|
+
trustProxy: field(z.boolean().default(false), { env: "FIRST_TREE_HUB_TRUST_PROXY" }),
|
|
1542
1543
|
rateLimit: optional({
|
|
1543
1544
|
max: field(z.number().default(100), { env: "FIRST_TREE_HUB_RATE_LIMIT_MAX" }),
|
|
1544
1545
|
loginMax: field(z.number().default(5), { env: "FIRST_TREE_HUB_RATE_LIMIT_LOGIN_MAX" }),
|
|
1545
|
-
webhookMax: field(z.number().default(60), { env: "FIRST_TREE_HUB_RATE_LIMIT_WEBHOOK_MAX" })
|
|
1546
|
+
webhookMax: field(z.number().default(60), { env: "FIRST_TREE_HUB_RATE_LIMIT_WEBHOOK_MAX" }),
|
|
1547
|
+
agentMessageMax: field(z.number().default(30), { env: "FIRST_TREE_HUB_RATE_LIMIT_AGENT_MESSAGE_MAX" })
|
|
1546
1548
|
}),
|
|
1549
|
+
ws: optional({ maxPayload: field(z.number().int().min(1024).default(262144), { env: "FIRST_TREE_HUB_WS_MAX_PAYLOAD" }) }),
|
|
1547
1550
|
inbox: optional({ maxInFlightPerAgent: field(z.number().int().min(1).max(1024).default(32), { env: "FIRST_TREE_HUB_INBOX_MAX_IN_FLIGHT_PER_AGENT" }) }),
|
|
1548
1551
|
kael: optional({
|
|
1549
1552
|
endpoint: field(z.string(), { env: "KAEL_ENDPOINT" }),
|
|
@@ -6637,6 +6640,37 @@ function runCapture(program, args, timeoutMs) {
|
|
|
6637
6640
|
]
|
|
6638
6641
|
});
|
|
6639
6642
|
if (res.status === 0) return { ok: true };
|
|
6643
|
+
if (res.signal) return {
|
|
6644
|
+
ok: false,
|
|
6645
|
+
stderr: `${program} timed out after ${timeoutMs}ms (signal=${res.signal})`,
|
|
6646
|
+
code: null
|
|
6647
|
+
};
|
|
6648
|
+
return {
|
|
6649
|
+
ok: false,
|
|
6650
|
+
stderr: (res.stderr ?? "").trim(),
|
|
6651
|
+
code: res.status
|
|
6652
|
+
};
|
|
6653
|
+
}
|
|
6654
|
+
/** Same as runCapture but also returns stdout — for queries (loginctl show-user, etc.). */
|
|
6655
|
+
function runCaptureOut(program, args, timeoutMs) {
|
|
6656
|
+
const res = spawnSync(program, args, {
|
|
6657
|
+
encoding: "utf-8",
|
|
6658
|
+
timeout: timeoutMs,
|
|
6659
|
+
stdio: [
|
|
6660
|
+
"ignore",
|
|
6661
|
+
"pipe",
|
|
6662
|
+
"pipe"
|
|
6663
|
+
]
|
|
6664
|
+
});
|
|
6665
|
+
if (res.status === 0) return {
|
|
6666
|
+
ok: true,
|
|
6667
|
+
stdout: (res.stdout ?? "").trim()
|
|
6668
|
+
};
|
|
6669
|
+
if (res.signal) return {
|
|
6670
|
+
ok: false,
|
|
6671
|
+
stderr: `${program} timed out after ${timeoutMs}ms (signal=${res.signal})`,
|
|
6672
|
+
code: null
|
|
6673
|
+
};
|
|
6640
6674
|
return {
|
|
6641
6675
|
ok: false,
|
|
6642
6676
|
stderr: (res.stderr ?? "").trim(),
|
|
@@ -6647,8 +6681,40 @@ function sleepSync(ms) {
|
|
|
6647
6681
|
const shared = new Int32Array(new SharedArrayBuffer(4));
|
|
6648
6682
|
Atomics.wait(shared, 0, 0, ms);
|
|
6649
6683
|
}
|
|
6650
|
-
|
|
6651
|
-
|
|
6684
|
+
/**
|
|
6685
|
+
* Map a `FIRST_TREE_HUB_HOME` basename to the suffix appended to the
|
|
6686
|
+
* service manager's unit name / label.
|
|
6687
|
+
*
|
|
6688
|
+
* Why this exists: `FIRST_TREE_HUB_HOME` already isolates config /
|
|
6689
|
+
* credentials / workspace under a separate home dir, but until now the
|
|
6690
|
+
* systemd unit name and launchd label were hard-coded — so a developer
|
|
6691
|
+
* running with an isolated home would still rewrite the same
|
|
6692
|
+
* `first-tree-hub-client.service` unit file as the prod install. This
|
|
6693
|
+
* derivation closes that loop: dev homes get their own unit name and
|
|
6694
|
+
* coexist with prod.
|
|
6695
|
+
*
|
|
6696
|
+
* Rule:
|
|
6697
|
+
* - "hub" → "" (default home; preserves the existing prod
|
|
6698
|
+
* unit name `first-tree-hub-client.service` for
|
|
6699
|
+
* every machine already in the field)
|
|
6700
|
+
* - "hub-<x>" → "<x>" ("hub-test" → "test", giving
|
|
6701
|
+
* `first-tree-hub-client-test.service`)
|
|
6702
|
+
* - anything else → the basename verbatim (a custom home like
|
|
6703
|
+
* "~/.first-tree/foo" yields suffix "foo")
|
|
6704
|
+
*
|
|
6705
|
+
* Empty / falsy basenames defensively fall back to the default — we
|
|
6706
|
+
* never want to silently drop a user's intent into prod's unit name.
|
|
6707
|
+
*/
|
|
6708
|
+
function deriveServiceSuffix(homeBasename) {
|
|
6709
|
+
if (!homeBasename) return "";
|
|
6710
|
+
if (homeBasename === "hub") return "";
|
|
6711
|
+
if (homeBasename.startsWith("hub-")) return homeBasename.slice(4) || homeBasename;
|
|
6712
|
+
return homeBasename;
|
|
6713
|
+
}
|
|
6714
|
+
const SERVICE_SUFFIX = deriveServiceSuffix(basename(DEFAULT_HOME_DIR$1));
|
|
6715
|
+
const LAUNCHD_LABEL = SERVICE_SUFFIX ? `dev.first-tree-hub.client.${SERVICE_SUFFIX}` : "dev.first-tree-hub.client";
|
|
6716
|
+
const SYSTEMD_UNIT = SERVICE_SUFFIX ? `first-tree-hub-client-${SERVICE_SUFFIX}.service` : "first-tree-hub-client.service";
|
|
6717
|
+
const SYSLOG_IDENT = SERVICE_SUFFIX ? `first-tree-hub-client-${SERVICE_SUFFIX}` : "first-tree-hub-client";
|
|
6652
6718
|
const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
|
|
6653
6719
|
function whichBin(name) {
|
|
6654
6720
|
try {
|
|
@@ -6663,23 +6729,36 @@ function whichBin(name) {
|
|
|
6663
6729
|
/**
|
|
6664
6730
|
* Resolve how the service should launch the CLI.
|
|
6665
6731
|
*
|
|
6666
|
-
*
|
|
6667
|
-
*
|
|
6668
|
-
*
|
|
6669
|
-
*
|
|
6732
|
+
* Two regimes:
|
|
6733
|
+
*
|
|
6734
|
+
* ① Prod (default home, empty service suffix) — prefer the installed
|
|
6735
|
+
* `first-tree-hub` bin on PATH (usually a shim under /usr/local/bin
|
|
6736
|
+
* or ~/.npm-global/bin). Using the shim means an `npm i -g … @latest`
|
|
6737
|
+
* atomically swaps the binary the unit launches, no unit rewrite
|
|
6738
|
+
* needed.
|
|
6739
|
+
*
|
|
6740
|
+
* ② Dev / isolated (non-empty suffix from a custom FIRST_TREE_HUB_HOME)
|
|
6741
|
+
* — pin to the running interpreter + script path. This skips the
|
|
6742
|
+
* PATH lookup, which would otherwise resolve `first-tree-hub` to
|
|
6743
|
+
* the operator's prod global install — making the dev unit silently
|
|
6744
|
+
* run prod code against a dev home (i.e., the whole isolation story
|
|
6745
|
+
* collapses with no error message). Pinning execPath+argv[1] forces
|
|
6746
|
+
* the dev unit to launch the dev build that just installed it.
|
|
6670
6747
|
*/
|
|
6671
|
-
function resolveCliInvocation() {
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
|
|
6676
|
-
|
|
6677
|
-
|
|
6678
|
-
|
|
6679
|
-
|
|
6680
|
-
|
|
6681
|
-
|
|
6682
|
-
|
|
6748
|
+
function resolveCliInvocation(serviceSuffix = SERVICE_SUFFIX) {
|
|
6749
|
+
if (serviceSuffix === "") {
|
|
6750
|
+
const bin = whichBin("first-tree-hub");
|
|
6751
|
+
if (bin && isAbsolute(bin)) try {
|
|
6752
|
+
return {
|
|
6753
|
+
kind: "bin",
|
|
6754
|
+
program: realpathSync(bin)
|
|
6755
|
+
};
|
|
6756
|
+
} catch {
|
|
6757
|
+
return {
|
|
6758
|
+
kind: "bin",
|
|
6759
|
+
program: bin
|
|
6760
|
+
};
|
|
6761
|
+
}
|
|
6683
6762
|
}
|
|
6684
6763
|
const script = process.argv[1];
|
|
6685
6764
|
if (!script) throw new Error("Cannot resolve CLI entry point (process.argv[1] is empty).");
|
|
@@ -6729,7 +6808,7 @@ ${argsXml}
|
|
|
6729
6808
|
<key>PATH</key>
|
|
6730
6809
|
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
6731
6810
|
<key>FIRST_TREE_HUB_SERVICE_MODE</key>
|
|
6732
|
-
<string>1</string>
|
|
6811
|
+
<string>1</string>${SERVICE_SUFFIX ? `\n <key>FIRST_TREE_HUB_HOME</key>\n <string>${escapeXml(DEFAULT_HOME_DIR$1)}</string>` : ""}
|
|
6733
6812
|
</dict>
|
|
6734
6813
|
<key>RunAtLoad</key>
|
|
6735
6814
|
<true/>
|
|
@@ -6773,9 +6852,12 @@ function launchdState() {
|
|
|
6773
6852
|
const stateLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("state ="));
|
|
6774
6853
|
const pidLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("pid ="));
|
|
6775
6854
|
if (stateLine?.includes("running")) {
|
|
6776
|
-
const
|
|
6855
|
+
const pidStr = pidLine?.split("=")[1]?.trim();
|
|
6856
|
+
const pidNum = pidStr ? Number(pidStr) : NaN;
|
|
6857
|
+
const pid = Number.isFinite(pidNum) && pidNum > 0 ? pidNum : void 0;
|
|
6777
6858
|
return {
|
|
6778
6859
|
state: "active",
|
|
6860
|
+
pid,
|
|
6779
6861
|
detail: pid ? `pid ${pid}` : "running"
|
|
6780
6862
|
};
|
|
6781
6863
|
}
|
|
@@ -6818,7 +6900,7 @@ function installLaunchd() {
|
|
|
6818
6900
|
if (!bootoutRes.ok) {
|
|
6819
6901
|
if (!/not find|no such|not loaded/i.test(bootoutRes.stderr)) print.line(` warning: launchctl bootout: ${bootoutRes.stderr || `exit ${bootoutRes.code ?? "unknown"}`}\n`);
|
|
6820
6902
|
}
|
|
6821
|
-
waitForLabelEvicted(target, LAUNCHD_LABEL, 1e4);
|
|
6903
|
+
if (!waitForLabelEvicted(target, LAUNCHD_LABEL, 1e4)) print.line(" warning: launchctl bootout still settling after 10s; bootstrap may need a retry\n");
|
|
6822
6904
|
let lastBootstrapErr = null;
|
|
6823
6905
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
6824
6906
|
const res = runCapture("launchctl", [
|
|
@@ -6836,13 +6918,14 @@ function installLaunchd() {
|
|
|
6836
6918
|
if (lastBootstrapErr) throw new Error(`launchctl bootstrap failed: ${lastBootstrapErr.stderr || `exit ${lastBootstrapErr.code ?? "unknown"}`}\n Command: launchctl bootstrap ${target} ${plistPath}\n Recovery: \`launchctl bootout ${target}/${LAUNCHD_LABEL}\` then \`first-tree-hub client connect <server-url>\`.`);
|
|
6837
6919
|
const enableRes = runCapture("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], 5e3);
|
|
6838
6920
|
if (!enableRes.ok) print.line(` warning: launchctl enable: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n`);
|
|
6839
|
-
const { state, detail } = launchdState();
|
|
6921
|
+
const { state, pid, detail } = launchdState();
|
|
6840
6922
|
return {
|
|
6841
6923
|
platform: "launchd",
|
|
6842
6924
|
label: LAUNCHD_LABEL,
|
|
6843
6925
|
unitPath: plistPath,
|
|
6844
6926
|
logDir: LOG_DIR,
|
|
6845
6927
|
state,
|
|
6928
|
+
pid,
|
|
6846
6929
|
detail
|
|
6847
6930
|
};
|
|
6848
6931
|
}
|
|
@@ -6865,20 +6948,25 @@ function systemdUnitPath() {
|
|
|
6865
6948
|
function renderSystemdUnit(invocation) {
|
|
6866
6949
|
return `[Unit]
|
|
6867
6950
|
Description=First Tree Hub Client
|
|
6868
|
-
|
|
6869
|
-
|
|
6951
|
+
StartLimitIntervalSec=300
|
|
6952
|
+
StartLimitBurst=10
|
|
6870
6953
|
|
|
6871
6954
|
[Service]
|
|
6872
6955
|
Type=simple
|
|
6873
6956
|
ExecStart=${invocation.kind === "bin" ? `${shellQuote(invocation.program)} client start --no-interactive` : `${shellQuote(invocation.program)} ${invocation.args.map(shellQuote).join(" ")} client start --no-interactive`}
|
|
6874
|
-
Restart=
|
|
6957
|
+
Restart=on-failure
|
|
6875
6958
|
RestartSec=10
|
|
6876
|
-
|
|
6877
|
-
|
|
6959
|
+
SuccessExitStatus=0
|
|
6960
|
+
RestartForceExitStatus=75
|
|
6961
|
+
KillSignal=SIGTERM
|
|
6962
|
+
KillMode=mixed
|
|
6963
|
+
TimeoutStopSec=30
|
|
6964
|
+
StandardOutput=journal
|
|
6965
|
+
StandardError=journal
|
|
6966
|
+
SyslogIdentifier=${SYSLOG_IDENT}
|
|
6878
6967
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
6879
6968
|
Environment=FIRST_TREE_HUB_SERVICE_MODE=1
|
|
6880
|
-
|
|
6881
|
-
[Install]
|
|
6969
|
+
${SERVICE_SUFFIX ? `Environment=FIRST_TREE_HUB_HOME=${shellQuote(DEFAULT_HOME_DIR$1)}\n` : ""}[Install]
|
|
6882
6970
|
WantedBy=default.target
|
|
6883
6971
|
`;
|
|
6884
6972
|
}
|
|
@@ -6902,15 +6990,75 @@ function systemdState() {
|
|
|
6902
6990
|
]
|
|
6903
6991
|
});
|
|
6904
6992
|
const out = (res.stdout ?? "").trim();
|
|
6905
|
-
if (res.status === 0 && out === "active")
|
|
6906
|
-
|
|
6907
|
-
|
|
6908
|
-
|
|
6993
|
+
if (res.status === 0 && out === "active") {
|
|
6994
|
+
const pid = readSystemdMainPid();
|
|
6995
|
+
return {
|
|
6996
|
+
state: "active",
|
|
6997
|
+
pid,
|
|
6998
|
+
detail: pid ? `pid ${pid}` : "running"
|
|
6999
|
+
};
|
|
7000
|
+
}
|
|
6909
7001
|
return {
|
|
6910
7002
|
state: "inactive",
|
|
6911
7003
|
detail: out || "unit present but not active"
|
|
6912
7004
|
};
|
|
6913
7005
|
}
|
|
7006
|
+
function readSystemdMainPid() {
|
|
7007
|
+
const res = runCaptureOut("systemctl", [
|
|
7008
|
+
"--user",
|
|
7009
|
+
"show",
|
|
7010
|
+
SYSTEMD_UNIT,
|
|
7011
|
+
"-p",
|
|
7012
|
+
"MainPID",
|
|
7013
|
+
"--value"
|
|
7014
|
+
], 5e3);
|
|
7015
|
+
if (!res.ok) return void 0;
|
|
7016
|
+
const n = Number(res.stdout);
|
|
7017
|
+
return Number.isFinite(n) && n > 0 ? n : void 0;
|
|
7018
|
+
}
|
|
7019
|
+
/**
|
|
7020
|
+
* Best-effort `loginctl enable-linger` for the current user.
|
|
7021
|
+
*
|
|
7022
|
+
* Why this matters: a `--user` systemd service is tied to the user's session.
|
|
7023
|
+
* Without linger, when the user logs out (closes their last SSH session,
|
|
7024
|
+
* graphical session ends, etc.) the user's systemd manager exits and stops
|
|
7025
|
+
* every service it owns — including ours. The next login restarts everything,
|
|
7026
|
+
* which is silently wrong: agents go offline for hours and the operator has
|
|
7027
|
+
* no obvious cause.
|
|
7028
|
+
*
|
|
7029
|
+
* `enable-linger <self>` is allowed without sudo on systemd ≥ 240 thanks to
|
|
7030
|
+
* polkit's `org.freedesktop.login1.set-self-linger` rule. On older distros
|
|
7031
|
+
* or hardened setups it requires polkit auth — we don't try to escalate;
|
|
7032
|
+
* the warning printed by the caller is the operator's signal to run it
|
|
7033
|
+
* manually.
|
|
7034
|
+
*/
|
|
7035
|
+
function tryEnableLinger() {
|
|
7036
|
+
const username = userInfo().username;
|
|
7037
|
+
if (!username) return {
|
|
7038
|
+
ok: false,
|
|
7039
|
+
reason: "could not determine username"
|
|
7040
|
+
};
|
|
7041
|
+
const showRes = runCaptureOut("loginctl", [
|
|
7042
|
+
"show-user",
|
|
7043
|
+
username,
|
|
7044
|
+
"-p",
|
|
7045
|
+
"Linger",
|
|
7046
|
+
"--value"
|
|
7047
|
+
], 5e3);
|
|
7048
|
+
if (showRes.ok && showRes.stdout === "yes") return {
|
|
7049
|
+
ok: true,
|
|
7050
|
+
alreadyOn: true
|
|
7051
|
+
};
|
|
7052
|
+
const res = runCapture("loginctl", ["enable-linger", username], 5e3);
|
|
7053
|
+
if (res.ok) return {
|
|
7054
|
+
ok: true,
|
|
7055
|
+
alreadyOn: false
|
|
7056
|
+
};
|
|
7057
|
+
return {
|
|
7058
|
+
ok: false,
|
|
7059
|
+
reason: res.stderr || `exit ${res.code ?? "unknown"}`
|
|
7060
|
+
};
|
|
7061
|
+
}
|
|
6914
7062
|
function installSystemd() {
|
|
6915
7063
|
const invocation = resolveCliInvocation();
|
|
6916
7064
|
ensureLogDir();
|
|
@@ -6919,6 +7067,8 @@ function installSystemd() {
|
|
|
6919
7067
|
writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
|
|
6920
7068
|
const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
|
|
6921
7069
|
if (!reloadRes.ok) throw new Error(`systemctl --user daemon-reload failed: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}`);
|
|
7070
|
+
const lingerRes = tryEnableLinger();
|
|
7071
|
+
if (!lingerRes.ok) print.line(` warning: loginctl enable-linger failed: ${lingerRes.reason}\n The service will stop when you log out. Run manually: sudo loginctl enable-linger ${userInfo().username}\n`);
|
|
6922
7072
|
const enableRes = runCapture("systemctl", [
|
|
6923
7073
|
"--user",
|
|
6924
7074
|
"enable",
|
|
@@ -6926,13 +7076,14 @@ function installSystemd() {
|
|
|
6926
7076
|
SYSTEMD_UNIT
|
|
6927
7077
|
], 1e4);
|
|
6928
7078
|
if (!enableRes.ok) throw new Error(`systemctl --user enable --now ${SYSTEMD_UNIT} failed: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n Recovery: \`systemctl --user stop ${SYSTEMD_UNIT}\` then \`first-tree-hub client connect <server-url>\`.`);
|
|
6929
|
-
const { state, detail } = systemdState();
|
|
7079
|
+
const { state, pid, detail } = systemdState();
|
|
6930
7080
|
return {
|
|
6931
7081
|
platform: "systemd",
|
|
6932
7082
|
label: SYSTEMD_UNIT,
|
|
6933
7083
|
unitPath,
|
|
6934
7084
|
logDir: LOG_DIR,
|
|
6935
7085
|
state,
|
|
7086
|
+
pid,
|
|
6936
7087
|
detail
|
|
6937
7088
|
};
|
|
6938
7089
|
}
|
|
@@ -6973,24 +7124,26 @@ function installClientService() {
|
|
|
6973
7124
|
/** Report the current service state without modifying anything. */
|
|
6974
7125
|
function getClientServiceStatus() {
|
|
6975
7126
|
if (process.platform === "darwin") {
|
|
6976
|
-
const { state, detail } = launchdState();
|
|
7127
|
+
const { state, pid, detail } = launchdState();
|
|
6977
7128
|
return {
|
|
6978
7129
|
platform: "launchd",
|
|
6979
7130
|
label: LAUNCHD_LABEL,
|
|
6980
7131
|
unitPath: launchdPlistPath(),
|
|
6981
7132
|
logDir: LOG_DIR,
|
|
6982
7133
|
state,
|
|
7134
|
+
pid,
|
|
6983
7135
|
detail
|
|
6984
7136
|
};
|
|
6985
7137
|
}
|
|
6986
7138
|
if (process.platform === "linux") {
|
|
6987
|
-
const { state, detail } = systemdState();
|
|
7139
|
+
const { state, pid, detail } = systemdState();
|
|
6988
7140
|
return {
|
|
6989
7141
|
platform: "systemd",
|
|
6990
7142
|
label: SYSTEMD_UNIT,
|
|
6991
7143
|
unitPath: systemdUnitPath(),
|
|
6992
7144
|
logDir: LOG_DIR,
|
|
6993
7145
|
state,
|
|
7146
|
+
pid,
|
|
6994
7147
|
detail
|
|
6995
7148
|
};
|
|
6996
7149
|
}
|
|
@@ -7003,6 +7156,137 @@ function getClientServiceStatus() {
|
|
|
7003
7156
|
detail: `platform ${process.platform} not supported`
|
|
7004
7157
|
};
|
|
7005
7158
|
}
|
|
7159
|
+
/** Start the service. No-op + ok if already running. */
|
|
7160
|
+
function startClientService() {
|
|
7161
|
+
if (process.platform === "linux") {
|
|
7162
|
+
const res = runCapture("systemctl", [
|
|
7163
|
+
"--user",
|
|
7164
|
+
"start",
|
|
7165
|
+
SYSTEMD_UNIT
|
|
7166
|
+
], 15e3);
|
|
7167
|
+
if (!res.ok) return {
|
|
7168
|
+
ok: false,
|
|
7169
|
+
reason: res.stderr || `exit ${res.code ?? "unknown"}`
|
|
7170
|
+
};
|
|
7171
|
+
return { ok: true };
|
|
7172
|
+
}
|
|
7173
|
+
if (process.platform === "darwin") {
|
|
7174
|
+
const target = launchctlDomainTarget();
|
|
7175
|
+
const plistPath = launchdPlistPath();
|
|
7176
|
+
if (!existsSync(plistPath)) return {
|
|
7177
|
+
ok: false,
|
|
7178
|
+
reason: "service not installed"
|
|
7179
|
+
};
|
|
7180
|
+
if (runCaptureOut("launchctl", ["print", `${target}/${LAUNCHD_LABEL}`], 5e3).ok) {
|
|
7181
|
+
const res = runCapture("launchctl", ["kickstart", `${target}/${LAUNCHD_LABEL}`], 1e4);
|
|
7182
|
+
if (!res.ok) return {
|
|
7183
|
+
ok: false,
|
|
7184
|
+
reason: res.stderr || `exit ${res.code ?? "unknown"}`
|
|
7185
|
+
};
|
|
7186
|
+
return { ok: true };
|
|
7187
|
+
}
|
|
7188
|
+
const res = runCapture("launchctl", [
|
|
7189
|
+
"bootstrap",
|
|
7190
|
+
target,
|
|
7191
|
+
plistPath
|
|
7192
|
+
], 1e4);
|
|
7193
|
+
if (!res.ok) return {
|
|
7194
|
+
ok: false,
|
|
7195
|
+
reason: res.stderr || `exit ${res.code ?? "unknown"}`
|
|
7196
|
+
};
|
|
7197
|
+
return { ok: true };
|
|
7198
|
+
}
|
|
7199
|
+
return {
|
|
7200
|
+
ok: false,
|
|
7201
|
+
reason: `service control not supported on ${process.platform}`
|
|
7202
|
+
};
|
|
7203
|
+
}
|
|
7204
|
+
/**
|
|
7205
|
+
* Stop the service without disabling auto-start on next boot/login.
|
|
7206
|
+
*
|
|
7207
|
+
* systemd: `systemctl --user stop` — unit stays enabled, so a reboot or
|
|
7208
|
+
* `client start` brings it back. Combined with `Restart=on-failure +
|
|
7209
|
+
* SuccessExitStatus=0` in the unit, the SIGTERM path actually terminates
|
|
7210
|
+
* (the bug `Restart=always` had: stop would be immediately undone).
|
|
7211
|
+
*
|
|
7212
|
+
* launchd: `launchctl bootout` — unloads the running registration but
|
|
7213
|
+
* leaves the plist in `~/Library/LaunchAgents/`, so the next user login
|
|
7214
|
+
* (or `client start`) reloads it.
|
|
7215
|
+
*/
|
|
7216
|
+
function stopClientService() {
|
|
7217
|
+
if (process.platform === "linux") {
|
|
7218
|
+
const res = runCapture("systemctl", [
|
|
7219
|
+
"--user",
|
|
7220
|
+
"stop",
|
|
7221
|
+
SYSTEMD_UNIT
|
|
7222
|
+
], 35e3);
|
|
7223
|
+
if (!res.ok) return {
|
|
7224
|
+
ok: false,
|
|
7225
|
+
reason: res.stderr || `exit ${res.code ?? "unknown"}`
|
|
7226
|
+
};
|
|
7227
|
+
return { ok: true };
|
|
7228
|
+
}
|
|
7229
|
+
if (process.platform === "darwin") {
|
|
7230
|
+
const res = runCapture("launchctl", ["bootout", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], 3e4);
|
|
7231
|
+
if (!res.ok) {
|
|
7232
|
+
if (/not find|no such|not loaded/i.test(res.stderr)) return {
|
|
7233
|
+
ok: true,
|
|
7234
|
+
detail: "not running"
|
|
7235
|
+
};
|
|
7236
|
+
return {
|
|
7237
|
+
ok: false,
|
|
7238
|
+
reason: res.stderr || `exit ${res.code ?? "unknown"}`
|
|
7239
|
+
};
|
|
7240
|
+
}
|
|
7241
|
+
return { ok: true };
|
|
7242
|
+
}
|
|
7243
|
+
return {
|
|
7244
|
+
ok: false,
|
|
7245
|
+
reason: `service control not supported on ${process.platform}`
|
|
7246
|
+
};
|
|
7247
|
+
}
|
|
7248
|
+
/** Restart the service. Equivalent to stop + start, but uses the manager's atomic primitive. */
|
|
7249
|
+
function restartClientService() {
|
|
7250
|
+
if (process.platform === "linux") {
|
|
7251
|
+
const res = runCapture("systemctl", [
|
|
7252
|
+
"--user",
|
|
7253
|
+
"restart",
|
|
7254
|
+
SYSTEMD_UNIT
|
|
7255
|
+
], 45e3);
|
|
7256
|
+
if (!res.ok) return {
|
|
7257
|
+
ok: false,
|
|
7258
|
+
reason: res.stderr || `exit ${res.code ?? "unknown"}`
|
|
7259
|
+
};
|
|
7260
|
+
return { ok: true };
|
|
7261
|
+
}
|
|
7262
|
+
if (process.platform === "darwin") {
|
|
7263
|
+
const target = launchctlDomainTarget();
|
|
7264
|
+
const plistPath = launchdPlistPath();
|
|
7265
|
+
if (!existsSync(plistPath)) return {
|
|
7266
|
+
ok: false,
|
|
7267
|
+
reason: "service not installed"
|
|
7268
|
+
};
|
|
7269
|
+
if (runCapture("launchctl", [
|
|
7270
|
+
"kickstart",
|
|
7271
|
+
"-k",
|
|
7272
|
+
`${target}/${LAUNCHD_LABEL}`
|
|
7273
|
+
], 3e4).ok) return { ok: true };
|
|
7274
|
+
const bootstrapRes = runCapture("launchctl", [
|
|
7275
|
+
"bootstrap",
|
|
7276
|
+
target,
|
|
7277
|
+
plistPath
|
|
7278
|
+
], 1e4);
|
|
7279
|
+
if (!bootstrapRes.ok) return {
|
|
7280
|
+
ok: false,
|
|
7281
|
+
reason: bootstrapRes.stderr || `exit ${bootstrapRes.code ?? "unknown"}`
|
|
7282
|
+
};
|
|
7283
|
+
return { ok: true };
|
|
7284
|
+
}
|
|
7285
|
+
return {
|
|
7286
|
+
ok: false,
|
|
7287
|
+
reason: `service control not supported on ${process.platform}`
|
|
7288
|
+
};
|
|
7289
|
+
}
|
|
7006
7290
|
/** Uninstall the background service. No-op if not installed. */
|
|
7007
7291
|
function uninstallClientService() {
|
|
7008
7292
|
if (process.platform === "darwin") return uninstallLaunchd();
|
|
@@ -7776,7 +8060,7 @@ async function onboardCreate(args) {
|
|
|
7776
8060
|
}
|
|
7777
8061
|
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
7778
8062
|
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
7779
|
-
const { bindFeishuBot } = await import("./feishu-
|
|
8063
|
+
const { bindFeishuBot } = await import("./feishu-viiZmwcn.mjs").then((n) => n.r);
|
|
7780
8064
|
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
7781
8065
|
if (!targetAgentUuid) print.line(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
7782
8066
|
else {
|
|
@@ -8756,7 +9040,7 @@ function createFeedbackHandler(config) {
|
|
|
8756
9040
|
return { handle };
|
|
8757
9041
|
}
|
|
8758
9042
|
//#endregion
|
|
8759
|
-
//#region ../server/dist/app-
|
|
9043
|
+
//#region ../server/dist/app-O9kCTpaF.mjs
|
|
8760
9044
|
var __defProp = Object.defineProperty;
|
|
8761
9045
|
var __exportAll = (all, no_symbols) => {
|
|
8762
9046
|
let target = {};
|
|
@@ -11561,7 +11845,8 @@ async function adminAgentRoutes(app) {
|
|
|
11561
11845
|
app.post("/", async (request, reply) => {
|
|
11562
11846
|
const scope = memberScope(request);
|
|
11563
11847
|
const body = createAgentSchema.parse(request.body);
|
|
11564
|
-
const
|
|
11848
|
+
const { organizationId: queryOrganizationId } = listAgentsFilterSchema.parse(request.query);
|
|
11849
|
+
const targetOrgId = body.organizationId ?? queryOrganizationId ?? scope.organizationId;
|
|
11565
11850
|
const probe = await requireMemberInOrg(app.db, request, targetOrgId);
|
|
11566
11851
|
const managerId = probe.role === "admin" ? body.managerId ?? probe.memberId : probe.memberId;
|
|
11567
11852
|
const agent = await createAgent(app.db, {
|
|
@@ -14194,8 +14479,40 @@ const editMessageSchema = z.object({
|
|
|
14194
14479
|
format: z.string().optional(),
|
|
14195
14480
|
content: z.unknown()
|
|
14196
14481
|
});
|
|
14482
|
+
/**
|
|
14483
|
+
* Per-agent rate limit on outbound message writes. Keyed by `agent.uuid`
|
|
14484
|
+
* (populated by `agentSelectorHook`, which runs as an onRequest hook before
|
|
14485
|
+
* the global limiter — registered with `hook: "preHandler"` — fires).
|
|
14486
|
+
*
|
|
14487
|
+
* Rationale: agent ↔ agent reply loops are the documented failure mode
|
|
14488
|
+
* (`mention_only` is the semantic guard; this is the hard ceiling).
|
|
14489
|
+
*
|
|
14490
|
+
* The IP fallback is **defensive scaffolding, not a real code path**. These
|
|
14491
|
+
* routes mount under `/agent` which forces `memberAuth + agentSelector`
|
|
14492
|
+
* onRequest hooks (see app.ts) — a missing `req.agent` would have already
|
|
14493
|
+
* 403'd before this preHandler runs. The fallback exists so that if a future
|
|
14494
|
+
* refactor reorders hooks (or detaches one of these routes from the agent
|
|
14495
|
+
* scope), the limiter degrades to per-IP keying with a logged warning rather
|
|
14496
|
+
* than silently keying everyone to the same `undefined` bucket.
|
|
14497
|
+
*/
|
|
14498
|
+
function agentMessageWriteRateLimit(max) {
|
|
14499
|
+
return { rateLimit: {
|
|
14500
|
+
max,
|
|
14501
|
+
timeWindow: "1 minute",
|
|
14502
|
+
keyGenerator: (req) => {
|
|
14503
|
+
const agentId = req.agent?.uuid;
|
|
14504
|
+
if (agentId) return `agent:${agentId}`;
|
|
14505
|
+
log$2.warn({
|
|
14506
|
+
ip: req.ip,
|
|
14507
|
+
route: req.routeOptions?.url ?? req.url
|
|
14508
|
+
}, "rate-limit keyGenerator fell back to IP — req.agent missing on a route under /agent (hook order regression?)");
|
|
14509
|
+
return `ip:${req.ip}`;
|
|
14510
|
+
}
|
|
14511
|
+
} };
|
|
14512
|
+
}
|
|
14197
14513
|
async function agentMessageRoutes(app) {
|
|
14198
|
-
app.
|
|
14514
|
+
const writeRateLimit = agentMessageWriteRateLimit(app.config.rateLimit?.agentMessageMax ?? 30);
|
|
14515
|
+
app.post("/:chatId/messages", { config: writeRateLimit }, async (request, reply) => {
|
|
14199
14516
|
const identity = requireAgent(request);
|
|
14200
14517
|
await assertParticipant(app.db, request.params.chatId, identity.uuid);
|
|
14201
14518
|
const body = sendMessageSchema.parse(request.body);
|
|
@@ -14239,7 +14556,8 @@ async function agentMessageRoutes(app) {
|
|
|
14239
14556
|
});
|
|
14240
14557
|
}
|
|
14241
14558
|
async function agentSendToAgentRoutes(app) {
|
|
14242
|
-
app.
|
|
14559
|
+
const writeRateLimit = agentMessageWriteRateLimit(app.config.rateLimit?.agentMessageMax ?? 30);
|
|
14560
|
+
app.post("/:name/messages", { config: writeRateLimit }, async (request, reply) => {
|
|
14243
14561
|
const identity = requireAgent(request);
|
|
14244
14562
|
const body = sendToAgentSchema.parse(request.body);
|
|
14245
14563
|
const { message: msg, recipients } = await sendToAgent(app.db, identity.uuid, request.params.name, body);
|
|
@@ -15041,7 +15359,7 @@ async function login(db, username, password, jwtSecretKey) {
|
|
|
15041
15359
|
const [user] = await db.select().from(users).where(eq(users.username, username)).limit(1);
|
|
15042
15360
|
if (!user || user.status !== "active") throw new UnauthorizedError("Invalid username or password");
|
|
15043
15361
|
if (!await bcrypt.compare(password, user.passwordHash)) throw new UnauthorizedError("Invalid username or password");
|
|
15044
|
-
const [member] = await db.select().from(members).where(and(eq(members.userId, user.id), eq(members.status, "active"))).limit(1);
|
|
15362
|
+
const [member] = await db.select().from(members).where(and(eq(members.userId, user.id), eq(members.status, "active"))).orderBy(desc(members.createdAt), desc(members.id)).limit(1);
|
|
15045
15363
|
if (!member) throw new UnauthorizedError("No organization membership found");
|
|
15046
15364
|
const tokens = await signTokensForMember(jwtSecretKey, {
|
|
15047
15365
|
userId: user.id,
|
|
@@ -16104,7 +16422,7 @@ async function inferWizardStep(app, m) {
|
|
|
16104
16422
|
* landing page.
|
|
16105
16423
|
*/
|
|
16106
16424
|
async function publicInvitePreviewRoute(app) {
|
|
16107
|
-
const { previewInvitation } = await import("./invitation-CBnQyB7o-
|
|
16425
|
+
const { previewInvitation } = await import("./invitation-CBnQyB7o-DLQyW5ek.mjs");
|
|
16108
16426
|
app.get("/:token/preview", async (request, reply) => {
|
|
16109
16427
|
if (!request.params.token) throw new UnauthorizedError("Token required");
|
|
16110
16428
|
const preview = await previewInvitation(app.db, request.params.token);
|
|
@@ -16134,7 +16452,7 @@ async function adminInvitationRoutes(app) {
|
|
|
16134
16452
|
const m = requireMember(request);
|
|
16135
16453
|
if (m.role !== "admin") throw new ForbiddenError("Admin role required");
|
|
16136
16454
|
if (request.params.id !== m.organizationId) throw new ForbiddenError("Cannot rotate invitations for another organization");
|
|
16137
|
-
const { rotateInvitation } = await import("./invitation-CBnQyB7o-
|
|
16455
|
+
const { rotateInvitation } = await import("./invitation-CBnQyB7o-DLQyW5ek.mjs");
|
|
16138
16456
|
const inv = await rotateInvitation(app.db, m.organizationId, m.userId);
|
|
16139
16457
|
return {
|
|
16140
16458
|
id: inv.id,
|
|
@@ -18014,7 +18332,11 @@ async function buildApp(config) {
|
|
|
18014
18332
|
format: config.observability.logging.format,
|
|
18015
18333
|
bridgeToSpanLevel: config.observability.logging.bridgeToSpanLevel
|
|
18016
18334
|
});
|
|
18017
|
-
const app = Fastify({
|
|
18335
|
+
const app = Fastify({
|
|
18336
|
+
loggerInstance: rootLogger$1,
|
|
18337
|
+
trustProxy: config.trustProxy
|
|
18338
|
+
});
|
|
18339
|
+
if (config.trustProxy) app.log.warn("trustProxy=true — Fastify trusts ANY upstream's x-forwarded-for. Ensure Cloudflare / CapRover is the only ingress; do NOT expose this container's port to the public internet directly.");
|
|
18018
18340
|
const otelPlugin = getFastifyOtelPlugin();
|
|
18019
18341
|
if (otelPlugin) await app.register(otelPlugin);
|
|
18020
18342
|
await app.register(observabilityPlugin);
|
|
@@ -18026,7 +18348,7 @@ async function buildApp(config) {
|
|
|
18026
18348
|
app.log.info({ commandVersion }, "Hub server advertising command version");
|
|
18027
18349
|
const listenClient = postgres(config.database.url, { max: 1 });
|
|
18028
18350
|
const notifier = createNotifier(listenClient);
|
|
18029
|
-
await app.register(websocket);
|
|
18351
|
+
await app.register(websocket, { options: { maxPayload: config.ws?.maxPayload ?? 65536 } });
|
|
18030
18352
|
const corsOrigin = config.cors?.origin;
|
|
18031
18353
|
const isDev = process.env.NODE_ENV !== "production";
|
|
18032
18354
|
await app.register(cors, {
|
|
@@ -18035,7 +18357,8 @@ async function buildApp(config) {
|
|
|
18035
18357
|
});
|
|
18036
18358
|
await app.register(rateLimit, {
|
|
18037
18359
|
max: config.rateLimit?.max ?? 100,
|
|
18038
|
-
timeWindow: "1 minute"
|
|
18360
|
+
timeWindow: "1 minute",
|
|
18361
|
+
hook: "preHandler"
|
|
18039
18362
|
});
|
|
18040
18363
|
const memberAuth = memberAuthHook(db, config.secrets.jwtSecret);
|
|
18041
18364
|
const adminOnly = requireAdminRoleHook();
|
|
@@ -18052,6 +18375,10 @@ async function buildApp(config) {
|
|
|
18052
18375
|
details: error.issues,
|
|
18053
18376
|
...traceField
|
|
18054
18377
|
});
|
|
18378
|
+
if (error instanceof Error && "statusCode" in error && typeof error.statusCode === "number" && error.statusCode >= 400 && error.statusCode < 500) return reply.status(error.statusCode).send({
|
|
18379
|
+
error: error.message,
|
|
18380
|
+
...traceField
|
|
18381
|
+
});
|
|
18055
18382
|
request.log.error({ err: error }, "unhandled request error");
|
|
18056
18383
|
return reply.status(500).send({
|
|
18057
18384
|
error: "Internal server error",
|
|
@@ -18425,12 +18752,21 @@ function resolveNpmCommand() {
|
|
|
18425
18752
|
*/
|
|
18426
18753
|
function detectInstallMode(argv1 = process.argv[1] ?? "") {
|
|
18427
18754
|
if (!argv1) return "npx";
|
|
18428
|
-
|
|
18755
|
+
const start = dirname(resolve(argv1));
|
|
18756
|
+
{
|
|
18757
|
+
let dir = start;
|
|
18758
|
+
for (let i = 0; i < 10; i++) {
|
|
18759
|
+
if (existsSync(resolve(dir, ".git"))) return "source";
|
|
18760
|
+
const parent = dirname(dir);
|
|
18761
|
+
if (parent === dir) break;
|
|
18762
|
+
dir = parent;
|
|
18763
|
+
}
|
|
18764
|
+
}
|
|
18765
|
+
let dir = start;
|
|
18429
18766
|
for (let i = 0; i < 10; i++) {
|
|
18430
|
-
if (existsSync(resolve(dir, ".git"))) return "source";
|
|
18431
18767
|
const pkgPath = resolve(dir, "package.json");
|
|
18432
18768
|
if (existsSync(pkgPath)) try {
|
|
18433
|
-
if (JSON.parse(readFileSync(pkgPath, "utf8")).name ===
|
|
18769
|
+
if (JSON.parse(readFileSync(pkgPath, "utf8")).name === "@agent-team-foundation/first-tree-hub") {
|
|
18434
18770
|
if (/\/(?:_npx|\.npm\/_npx)\//.test(dir)) return "npx";
|
|
18435
18771
|
return "global";
|
|
18436
18772
|
}
|
|
@@ -18504,6 +18840,42 @@ function parseInstalledVersion(stdout) {
|
|
|
18504
18840
|
function escapeForRegex(s) {
|
|
18505
18841
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
18506
18842
|
}
|
|
18843
|
+
/**
|
|
18844
|
+
* Look up the latest published version of the CLI package.
|
|
18845
|
+
*
|
|
18846
|
+
* Uses `npm view <pkg> version` (rather than fetch'ing registry.npmjs.org
|
|
18847
|
+
* directly) so the user's `.npmrc` registry, proxy, and auth settings are
|
|
18848
|
+
* honored — important for corporate users routed through Verdaccio /
|
|
18849
|
+
* Artifactory mirrors.
|
|
18850
|
+
*/
|
|
18851
|
+
function fetchLatestVersion(timeoutMs = 1e4) {
|
|
18852
|
+
const res = spawnSync(resolveNpmCommand(), [
|
|
18853
|
+
"view",
|
|
18854
|
+
PACKAGE_NAME,
|
|
18855
|
+
"version"
|
|
18856
|
+
], {
|
|
18857
|
+
encoding: "utf-8",
|
|
18858
|
+
timeout: timeoutMs,
|
|
18859
|
+
stdio: [
|
|
18860
|
+
"ignore",
|
|
18861
|
+
"pipe",
|
|
18862
|
+
"pipe"
|
|
18863
|
+
]
|
|
18864
|
+
});
|
|
18865
|
+
if (res.status !== 0) return {
|
|
18866
|
+
ok: false,
|
|
18867
|
+
reason: (res.stderr ?? "").trim() || `npm view exited with code ${res.status}`
|
|
18868
|
+
};
|
|
18869
|
+
const version = (res.stdout ?? "").trim();
|
|
18870
|
+
if (!semver.valid(version)) return {
|
|
18871
|
+
ok: false,
|
|
18872
|
+
reason: `npm view returned non-semver value: ${version.slice(0, 80)}`
|
|
18873
|
+
};
|
|
18874
|
+
return {
|
|
18875
|
+
ok: true,
|
|
18876
|
+
version
|
|
18877
|
+
};
|
|
18878
|
+
}
|
|
18507
18879
|
/** Interactive update prompt. Defaults to N on timeout. */
|
|
18508
18880
|
const promptUpdate = async ({ currentVersion, targetVersion, timeoutSeconds }) => {
|
|
18509
18881
|
const message = `A newer First Tree Hub client is available.\n You: ${currentVersion}\n Server bundled with: ${targetVersion}\n Will install: latest on npm (>= ${targetVersion})\n Updating will restart the client and briefly interrupt any active sessions.\n Update now?`;
|
|
@@ -18753,4 +19125,4 @@ function registerSaaSConnectCommand(program) {
|
|
|
18753
19125
|
});
|
|
18754
19126
|
}
|
|
18755
19127
|
//#endregion
|
|
18756
|
-
export {
|
|
19128
|
+
export { findStaleAliases as $, checkDatabase as A, installClientService as B, runHomeMigration as C, checkAgentConfigs as D, runMigrations as E, checkServerReachable as F, stopClientService as G, resolveCliInvocation as H, checkWebSocket as I, isDockerAvailable as J, uninstallClientService as K, printResults as L, checkNodeVersion as M, checkServerConfig as N, checkBackgroundService as O, checkServerHealth as P, rotateClientIdWithBackup as Q, reconcileAgentConfigs as R, saveOnboardState as S, migrateLocalAgentDirs as T, restartClientService as U, isServiceSupported as V, startClientService as W, ClientRuntime as X, stopPostgres as Y, handleClientOrgMismatch as Z, promptMissingFields as _, probeCapabilities as _t, declineUpdate as a, fail as at, onboardCheck as b, detectInstallMode as c, print as ct, startServer as d, ClientOrgMismatchError as dt, formatStaleReason as et, COMMAND_VERSION as f, ClientUserMismatchError as ft, promptAddAgent as g, cleanWorkspaces as gt, isInteractive as h, SessionRegistry as ht, createExecuteUpdate as i, resolveReplyToFromEnv as it, checkDocker as j, checkClientConfig as k, fetchLatestVersion as l, setJsonMode as lt, uploadClientCapabilities as m, SdkError as mt, deriveHubUrlFromToken as n, createOwner as nt, promptUpdate as o, success as ot, reconcileLocalRuntimeProviders as p, FirstTreeHubSDK as pt, ensurePostgres as q, registerSaaSConnectCommand as r, hasUser as rt, PACKAGE_NAME as s, blank as st, HubUrlDerivationError as t, removeLocalAgent as tt, installGlobalLatest as u, status as ut, formatCheckReport as v, applyClientLoggerConfig as vt, createApiNameResolver as w, onboardCreate as x, loadOnboardState as y, configureClientLoggerForService as yt, getClientServiceStatus as z };
|