@agent-team-foundation/first-tree-hub 0.8.5 → 0.9.0
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-DoIprl2f.mjs → core-DZDhomaN.mjs} +3980 -677
- package/dist/{feishu-n9Y2yGTT.mjs → feishu-GlaczcVf.mjs} +5 -0
- package/dist/index.mjs +4 -4
- package/dist/web/assets/geist-cyrillic-wght-normal-CHSlOQsW.woff2 +0 -0
- package/dist/web/assets/geist-latin-ext-wght-normal-DMtmJ5ZE.woff2 +0 -0
- package/dist/web/assets/geist-latin-wght-normal-Dm3htQBi.woff2 +0 -0
- package/dist/web/assets/geist-mono-cyrillic-wght-normal-BZdD_g9V.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-ext-wght-normal-b6lpi8_2.woff2 +0 -0
- package/dist/web/assets/geist-mono-latin-wght-normal-Cjtb1TV-.woff2 +0 -0
- package/dist/web/assets/index-CKtxY9Dp.js +361 -0
- package/dist/web/assets/index-D9iKLIsB.css +1 -0
- package/dist/web/favicon.svg +8 -20
- package/dist/web/index.html +5 -3
- package/package.json +3 -1
- package/dist/web/assets/index-CIVitOsR.css +0 -1
- package/dist/web/assets/index-CVfrxdFe.js +0 -361
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { m as __toESM } from "./esm-CYu4tXXn.mjs";
|
|
2
|
-
import { C as
|
|
1
|
+
import { f as __require, l as __commonJSMin, m as __toESM } from "./esm-CYu4tXXn.mjs";
|
|
2
|
+
import { C as serverConfigSchema, S as resolveConfigReadonly, _ 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, v as migrateLegacyHome, w as setConfigValue } from "./bootstrap-CWcBzk6C.mjs";
|
|
3
3
|
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, t as adapterAttrs, u as observabilityPlugin, v as withWsMessageSpan, y as FIRST_TREE_HUB_ATTR } from "./observability-CJzDFY_G-CmvgUuzc.mjs";
|
|
4
|
-
import { $ as updateAdapterConfigSchema, 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 taskListQuerySchema, R as loginSchema, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as runtimeStateMessageSchema, V as paginationQuerySchema, W as selfServiceFeishuBotSchema, X as sessionReconcileRequestSchema, Y as sessionEventSchema$1, Z as sessionStateMessageSchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as updateSystemConfigSchema, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateAgentRuntimeConfigSchema, 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 updateOrganizationSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateChatSchema, o as AGENT_SOURCES, ot as updateTaskStatusSchema, p as TASK_HEALTH_SIGNALS, q as sessionCompletionMessageSchema, rt as updateMemberSchema, s as AGENT_STATUSES, st as wsAuthFrameSchema, tt as updateAgentSchema, 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-
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { $ as updateAdapterConfigSchema, 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 taskListQuerySchema, R as loginSchema, S as agentRuntimeConfigPayloadSchema$1, T as connectTokenExchangeSchema, U as runtimeStateMessageSchema, V as paginationQuerySchema, W as selfServiceFeishuBotSchema, X as sessionReconcileRequestSchema, Y as sessionEventSchema$1, Z as sessionStateMessageSchema, _ as addParticipantSchema, a as AGENT_SELECTOR_HEADER$1, at as updateSystemConfigSchema, b as agentBindRequestSchema, c as AGENT_TYPES, d as SYSTEM_CONFIG_DEFAULTS, et as updateAgentRuntimeConfigSchema, 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 updateOrganizationSchema, j as createOrganizationSchema, k as createChatSchema, l as AGENT_VISIBILITY, m as TASK_STATUSES, nt as updateChatSchema, o as AGENT_SOURCES, ot as updateTaskStatusSchema, p as TASK_HEALTH_SIGNALS, q as sessionCompletionMessageSchema, rt as updateMemberSchema, s as AGENT_STATUSES, st as wsAuthFrameSchema, tt as updateAgentSchema, 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-GlaczcVf.mjs";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
import { copyFileSync, createReadStream, createWriteStream, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, watch, writeFileSync } from "node:fs";
|
|
7
|
+
import { dirname, extname, isAbsolute, join, resolve } from "node:path";
|
|
7
8
|
import { ZodError, z } from "zod";
|
|
8
9
|
import { Writable } from "node:stream";
|
|
9
10
|
import { stringify } from "yaml";
|
|
@@ -13,13 +14,14 @@ import { EventEmitter } from "node:events";
|
|
|
13
14
|
import WebSocket from "ws";
|
|
14
15
|
import { query } from "@anthropic-ai/claude-agent-sdk";
|
|
15
16
|
import { execFileSync, execSync, spawn, spawnSync } from "node:child_process";
|
|
17
|
+
import * as semver from "semver";
|
|
16
18
|
import bcrypt from "bcrypt";
|
|
17
19
|
import { and, asc, count, desc, eq, gt, inArray, isNotNull, isNull, lt, ne, or, sql } from "drizzle-orm";
|
|
18
20
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
19
21
|
import postgres from "postgres";
|
|
20
22
|
import { fileURLToPath } from "node:url";
|
|
21
23
|
import { migrate } from "drizzle-orm/postgres-js/migrator";
|
|
22
|
-
import { input, password, select } from "@inquirer/prompts";
|
|
24
|
+
import { confirm, input, password, select } from "@inquirer/prompts";
|
|
23
25
|
import cors from "@fastify/cors";
|
|
24
26
|
import rateLimit from "@fastify/rate-limit";
|
|
25
27
|
import fastifyStatic from "@fastify/static";
|
|
@@ -959,6 +961,18 @@ z.object({
|
|
|
959
961
|
type: z.literal("auth"),
|
|
960
962
|
token: z.string().min(1)
|
|
961
963
|
});
|
|
964
|
+
/**
|
|
965
|
+
* Advisory frame sent server → client immediately after `auth:ok`. It carries
|
|
966
|
+
* the Command-package version the server was bundled with, so the client can
|
|
967
|
+
* detect version drift on startup and on each reconnect. `.passthrough()` so
|
|
968
|
+
* future server versions may add fields without breaking older clients that
|
|
969
|
+
* validate this frame.
|
|
970
|
+
*/
|
|
971
|
+
const serverWelcomeFrameSchema = z.object({
|
|
972
|
+
type: z.literal("server:welcome"),
|
|
973
|
+
serverCommandVersion: z.string().min(1),
|
|
974
|
+
serverTimeMs: z.number().int().nonnegative()
|
|
975
|
+
}).passthrough();
|
|
962
976
|
const FETCH_TIMEOUT_MS = 15e3;
|
|
963
977
|
var FirstTreeHubSDK = class {
|
|
964
978
|
_baseUrl;
|
|
@@ -1132,6 +1146,8 @@ var ClientConnection = class extends EventEmitter {
|
|
|
1132
1146
|
reconnectAttempt = 0;
|
|
1133
1147
|
closing = false;
|
|
1134
1148
|
registered = false;
|
|
1149
|
+
/** Count of `server:welcome` frames received; drives `isReconnect` flag. */
|
|
1150
|
+
welcomeFramesReceived = 0;
|
|
1135
1151
|
boundAgents = /* @__PURE__ */ new Map();
|
|
1136
1152
|
/** Agents scheduled to rebind automatically on every reconnect. */
|
|
1137
1153
|
desiredBindings = /* @__PURE__ */ new Map();
|
|
@@ -1324,6 +1340,20 @@ var ClientConnection = class extends EventEmitter {
|
|
|
1324
1340
|
}));
|
|
1325
1341
|
return;
|
|
1326
1342
|
}
|
|
1343
|
+
if (type === "server:welcome") {
|
|
1344
|
+
const parsed = serverWelcomeFrameSchema.safeParse(msg);
|
|
1345
|
+
if (!parsed.success) {
|
|
1346
|
+
process.stderr.write(`[ClientConnection] Ignoring malformed server:welcome frame: ${parsed.error.issues.map((i) => i.message).join(", ")}\n`);
|
|
1347
|
+
return;
|
|
1348
|
+
}
|
|
1349
|
+
const isReconnect = this.welcomeFramesReceived > 0;
|
|
1350
|
+
this.welcomeFramesReceived++;
|
|
1351
|
+
this.emit("server:welcome", {
|
|
1352
|
+
frame: parsed.data,
|
|
1353
|
+
isReconnect
|
|
1354
|
+
});
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1327
1357
|
if (type === "auth:rejected" || type === "auth:expired") {
|
|
1328
1358
|
if (type === "auth:expired") this.emit("auth:expired");
|
|
1329
1359
|
this.ws?.close(4401, type);
|
|
@@ -1552,6 +1582,24 @@ defineConfig({
|
|
|
1552
1582
|
max_sessions: field(z.number().int().positive().default(10))
|
|
1553
1583
|
}
|
|
1554
1584
|
});
|
|
1585
|
+
/**
|
|
1586
|
+
* Phase-dependent defaults that flip with release milestones. Kept as a plain
|
|
1587
|
+
* module-level constant so reviews of the beta→GA transition are a one-line
|
|
1588
|
+
* diff, and so tests can mock this module to exercise both branches.
|
|
1589
|
+
*/
|
|
1590
|
+
const UPDATE_POLICIES = [
|
|
1591
|
+
"auto",
|
|
1592
|
+
"prompt",
|
|
1593
|
+
"off"
|
|
1594
|
+
];
|
|
1595
|
+
/**
|
|
1596
|
+
* Default value of `update.policy` on the Client config. During the beta this
|
|
1597
|
+
* is `"auto"` — operators rarely know to `npm i -g` weekly and we chase the
|
|
1598
|
+
* latest published Command by default. The GA PR flips it to `"prompt"` and
|
|
1599
|
+
* bumps Command to `1.0.0`.
|
|
1600
|
+
*/
|
|
1601
|
+
const UPDATE_POLICY_DEFAULT = "auto";
|
|
1602
|
+
const updatePolicySchema = z.enum(UPDATE_POLICIES);
|
|
1555
1603
|
defineConfig({
|
|
1556
1604
|
server: { url: field(z.string(), {
|
|
1557
1605
|
env: "FIRST_TREE_HUB_SERVER_URL",
|
|
@@ -1564,11 +1612,18 @@ defineConfig({
|
|
|
1564
1612
|
auto: "client-id",
|
|
1565
1613
|
env: "FIRST_TREE_HUB_CLIENT_ID"
|
|
1566
1614
|
}) },
|
|
1615
|
+
update: {
|
|
1616
|
+
policy: field(updatePolicySchema.default(UPDATE_POLICY_DEFAULT), { env: "FIRST_TREE_HUB_UPDATE_POLICY" }),
|
|
1617
|
+
restart_quiet_seconds: field(z.number().int().min(1).max(3600).default(30), { env: "FIRST_TREE_HUB_UPDATE_RESTART_QUIET_SECONDS" }),
|
|
1618
|
+
restart_check_interval_seconds: field(z.number().int().min(5).max(300).default(10), { env: "FIRST_TREE_HUB_UPDATE_RESTART_CHECK_INTERVAL_SECONDS" }),
|
|
1619
|
+
prompt_timeout_seconds: field(z.number().int().min(10).max(600).default(60), { env: "FIRST_TREE_HUB_UPDATE_PROMPT_TIMEOUT_SECONDS" })
|
|
1620
|
+
},
|
|
1567
1621
|
logLevel: field(logLevelSchema.default("info"), { env: "FIRST_TREE_HUB_LOG_LEVEL" })
|
|
1568
1622
|
});
|
|
1569
|
-
const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree
|
|
1623
|
+
const DEFAULT_HOME_DIR = process.env.FIRST_TREE_HUB_HOME ?? join(homedir(), ".first-tree", "hub");
|
|
1570
1624
|
join(DEFAULT_HOME_DIR, "config");
|
|
1571
1625
|
const DEFAULT_DATA_DIR = join(DEFAULT_HOME_DIR, "data");
|
|
1626
|
+
join(homedir(), ".first-tree-hub");
|
|
1572
1627
|
defineConfig({
|
|
1573
1628
|
database: {
|
|
1574
1629
|
url: field(z.string(), {
|
|
@@ -3235,6 +3290,20 @@ var SessionManager = class {
|
|
|
3235
3290
|
get totalCount() {
|
|
3236
3291
|
return this.sessions.size;
|
|
3237
3292
|
}
|
|
3293
|
+
/**
|
|
3294
|
+
* Snapshot used by the UpdateManager's quiet gate to decide whether it is
|
|
3295
|
+
* safe to exit the process for a self-update. `activeCount` is the number of
|
|
3296
|
+
* sessions currently handling a message; `lastActivityMs` is the most recent
|
|
3297
|
+
* activity timestamp across all tracked sessions (0 when there are none).
|
|
3298
|
+
*/
|
|
3299
|
+
getQuietGateSnapshot() {
|
|
3300
|
+
let lastActivityMs = 0;
|
|
3301
|
+
for (const entry of this.sessions.values()) if (entry.lastActivity > lastActivityMs) lastActivityMs = entry.lastActivity;
|
|
3302
|
+
return {
|
|
3303
|
+
activeCount: this._activeCount,
|
|
3304
|
+
lastActivityMs
|
|
3305
|
+
};
|
|
3306
|
+
}
|
|
3238
3307
|
/** Return the current aggregate runtime state, or null if no sessions have reported. */
|
|
3239
3308
|
getAggregateRuntimeState() {
|
|
3240
3309
|
return this.lastReportedRuntimeState;
|
|
@@ -3558,6 +3627,17 @@ var AgentSlot = class {
|
|
|
3558
3627
|
get clientConnection() {
|
|
3559
3628
|
return this.config.clientConnection;
|
|
3560
3629
|
}
|
|
3630
|
+
/**
|
|
3631
|
+
* Snapshot of this slot's busy/idle state used by the UpdateManager's
|
|
3632
|
+
* quiet gate. Returns zeros before `start()` has built the session manager,
|
|
3633
|
+
* which is the same semantics: idle.
|
|
3634
|
+
*/
|
|
3635
|
+
getQuietGateSnapshot() {
|
|
3636
|
+
return this.sessionManager?.getQuietGateSnapshot() ?? {
|
|
3637
|
+
activeCount: 0,
|
|
3638
|
+
lastActivityMs: 0
|
|
3639
|
+
};
|
|
3640
|
+
}
|
|
3561
3641
|
async start(contextTreePath) {
|
|
3562
3642
|
const sdk = (await this.clientConnection.bindAgent(this.config.agentId, this.config.runtimeType ?? this.config.type, this.config.runtimeVersion)).sdk;
|
|
3563
3643
|
this.sdk = sdk;
|
|
@@ -3741,6 +3821,147 @@ z.object({
|
|
|
3741
3821
|
server: z.url().default("http://localhost:8000"),
|
|
3742
3822
|
agents: z.record(z.string(), agentSlotConfigSchema).refine((agents) => Object.keys(agents).length > 0, "At least one agent must be defined")
|
|
3743
3823
|
});
|
|
3824
|
+
/**
|
|
3825
|
+
* Version-drift decision flow. Install, prompt, and exit are delegated to
|
|
3826
|
+
* command-layer callbacks so the Client package stays free of CLI /
|
|
3827
|
+
* filesystem knowledge.
|
|
3828
|
+
*/
|
|
3829
|
+
var UpdateManager = class UpdateManager {
|
|
3830
|
+
options;
|
|
3831
|
+
connection;
|
|
3832
|
+
welcomeListener;
|
|
3833
|
+
updateInFlight = false;
|
|
3834
|
+
quietGateTimer = null;
|
|
3835
|
+
disposed = false;
|
|
3836
|
+
/**
|
|
3837
|
+
* Set when a standalone (unmanaged) executeUpdate reports `installed: true`
|
|
3838
|
+
* without exiting. The new bits are on disk; subsequent welcome frames must
|
|
3839
|
+
* not re-invoke npm since a restart is the only way to pick them up.
|
|
3840
|
+
*/
|
|
3841
|
+
pendingRestart = false;
|
|
3842
|
+
constructor(connection, options) {
|
|
3843
|
+
this.connection = connection;
|
|
3844
|
+
this.options = options;
|
|
3845
|
+
this.welcomeListener = (welcome) => {
|
|
3846
|
+
this.onWelcome(welcome).catch((err) => {
|
|
3847
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3848
|
+
this.options.log("warn", `update decision failed: ${msg}`);
|
|
3849
|
+
});
|
|
3850
|
+
};
|
|
3851
|
+
}
|
|
3852
|
+
/** Attach a manager to a connection. Returns the instance so callers can dispose. */
|
|
3853
|
+
static attach(connection, options) {
|
|
3854
|
+
const mgr = new UpdateManager(connection, options);
|
|
3855
|
+
connection.on("server:welcome", mgr.welcomeListener);
|
|
3856
|
+
return mgr;
|
|
3857
|
+
}
|
|
3858
|
+
dispose() {
|
|
3859
|
+
if (this.disposed) return;
|
|
3860
|
+
this.disposed = true;
|
|
3861
|
+
this.connection.off("server:welcome", this.welcomeListener);
|
|
3862
|
+
if (this.quietGateTimer) {
|
|
3863
|
+
clearTimeout(this.quietGateTimer);
|
|
3864
|
+
this.quietGateTimer = null;
|
|
3865
|
+
}
|
|
3866
|
+
}
|
|
3867
|
+
async onWelcome(welcome) {
|
|
3868
|
+
if (this.disposed || this.updateInFlight || this.pendingRestart) return;
|
|
3869
|
+
this.updateInFlight = true;
|
|
3870
|
+
try {
|
|
3871
|
+
await this.decide(welcome);
|
|
3872
|
+
} finally {
|
|
3873
|
+
this.updateInFlight = false;
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3876
|
+
async decide(welcome) {
|
|
3877
|
+
const { serverCommandVersion: target } = welcome.frame;
|
|
3878
|
+
const current = this.options.currentVersion;
|
|
3879
|
+
if (!semver.valid(target)) {
|
|
3880
|
+
this.options.log("warn", `Server advertised invalid version "${target}"; skipping drift check`);
|
|
3881
|
+
return;
|
|
3882
|
+
}
|
|
3883
|
+
if (!semver.valid(current)) {
|
|
3884
|
+
this.options.log("warn", `Own version "${current}" is not valid SemVer; skipping drift check`);
|
|
3885
|
+
return;
|
|
3886
|
+
}
|
|
3887
|
+
if (semver.eq(target, current)) {
|
|
3888
|
+
this.options.log("debug", `Server advertises ${target}, matching running version`);
|
|
3889
|
+
return;
|
|
3890
|
+
}
|
|
3891
|
+
if (semver.lt(target, current)) {
|
|
3892
|
+
this.options.log("info", `Server advertises ${target}, running ${current} (ahead)`);
|
|
3893
|
+
return;
|
|
3894
|
+
}
|
|
3895
|
+
const policy = this.options.updateConfig.policy;
|
|
3896
|
+
if (policy === "off") {
|
|
3897
|
+
this.options.log("info", `Server advertises ${target}, running ${current}; self-update disabled (policy=off)`);
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3900
|
+
if (policy === "prompt") {
|
|
3901
|
+
if (!this.options.isTTY) {
|
|
3902
|
+
this.options.log("warn", `Update available (${current} → ${target}) but policy=prompt requires a terminal; operator action required`);
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
if (!await this.options.prompt({
|
|
3906
|
+
currentVersion: current,
|
|
3907
|
+
targetVersion: target,
|
|
3908
|
+
timeoutSeconds: this.options.updateConfig.prompt_timeout_seconds
|
|
3909
|
+
})) {
|
|
3910
|
+
this.options.log("info", `Update declined by operator (still running ${current})`);
|
|
3911
|
+
return;
|
|
3912
|
+
}
|
|
3913
|
+
await this.runUpdate(current, target);
|
|
3914
|
+
return;
|
|
3915
|
+
}
|
|
3916
|
+
this.options.log("info", `Server advertises ${target}, running ${current}; policy=auto`);
|
|
3917
|
+
if (this.options.isTTY) {
|
|
3918
|
+
this.options.log("info", "Auto-update starting in 5s");
|
|
3919
|
+
await sleep(5e3);
|
|
3920
|
+
if (this.disposed) return;
|
|
3921
|
+
}
|
|
3922
|
+
if (welcome.isReconnect) {
|
|
3923
|
+
await this.waitForQuietGate();
|
|
3924
|
+
if (this.disposed) return;
|
|
3925
|
+
}
|
|
3926
|
+
await this.runUpdate(current, target);
|
|
3927
|
+
}
|
|
3928
|
+
async waitForQuietGate() {
|
|
3929
|
+
const quietMs = this.options.updateConfig.restart_quiet_seconds * 1e3;
|
|
3930
|
+
const intervalMs = this.options.updateConfig.restart_check_interval_seconds * 1e3;
|
|
3931
|
+
while (!this.disposed) {
|
|
3932
|
+
const snapshot = this.options.getQuietGateSnapshot();
|
|
3933
|
+
const now = Date.now();
|
|
3934
|
+
const idleFor = snapshot.lastActivityMs === 0 ? Number.POSITIVE_INFINITY : now - snapshot.lastActivityMs;
|
|
3935
|
+
if (snapshot.activeCount === 0 && idleFor >= quietMs) return;
|
|
3936
|
+
this.options.log("debug", `Quiet gate: activeCount=${snapshot.activeCount}, idleFor=${Math.round(idleFor)}ms; re-checking in ${intervalMs}ms`);
|
|
3937
|
+
await new Promise((resolve) => {
|
|
3938
|
+
this.quietGateTimer = setTimeout(() => {
|
|
3939
|
+
this.quietGateTimer = null;
|
|
3940
|
+
resolve();
|
|
3941
|
+
}, intervalMs);
|
|
3942
|
+
});
|
|
3943
|
+
}
|
|
3944
|
+
}
|
|
3945
|
+
async runUpdate(current, target) {
|
|
3946
|
+
try {
|
|
3947
|
+
if ((await this.options.executeUpdate({
|
|
3948
|
+
currentVersion: current,
|
|
3949
|
+
targetVersion: target
|
|
3950
|
+
})).installed) {
|
|
3951
|
+
this.pendingRestart = true;
|
|
3952
|
+
this.options.log("info", `Update ${target} installed; restart required to pick it up (no further self-update attempts until restart)`);
|
|
3953
|
+
return;
|
|
3954
|
+
}
|
|
3955
|
+
this.options.log("warn", "Self-update did not complete; will retry on next welcome frame");
|
|
3956
|
+
} catch (err) {
|
|
3957
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
3958
|
+
this.options.log("warn", `Self-update threw: ${msg}`);
|
|
3959
|
+
}
|
|
3960
|
+
}
|
|
3961
|
+
};
|
|
3962
|
+
function sleep(ms) {
|
|
3963
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3964
|
+
}
|
|
3744
3965
|
//#endregion
|
|
3745
3966
|
//#region src/core/admin.ts
|
|
3746
3967
|
/**
|
|
@@ -3841,6 +4062,8 @@ var ClientRuntime = class {
|
|
|
3841
4062
|
agents = [];
|
|
3842
4063
|
agentNames = /* @__PURE__ */ new Set();
|
|
3843
4064
|
agentIds = /* @__PURE__ */ new Set();
|
|
4065
|
+
options;
|
|
4066
|
+
updateManager = null;
|
|
3844
4067
|
watcher = null;
|
|
3845
4068
|
debounceTimer = null;
|
|
3846
4069
|
/**
|
|
@@ -3849,11 +4072,13 @@ var ClientRuntime = class {
|
|
|
3849
4072
|
* `agent:pinned` handler knows where to materialise new configs.
|
|
3850
4073
|
*/
|
|
3851
4074
|
agentsDir = null;
|
|
3852
|
-
constructor(serverUrl, clientId) {
|
|
4075
|
+
constructor(serverUrl, clientId, options = {}) {
|
|
3853
4076
|
this.serverUrl = serverUrl;
|
|
4077
|
+
this.options = options;
|
|
3854
4078
|
this.connection = new ClientConnection({
|
|
3855
4079
|
serverUrl,
|
|
3856
4080
|
clientId,
|
|
4081
|
+
sdkVersion: options.currentVersion,
|
|
3857
4082
|
getAccessToken: () => ensureFreshAccessToken()
|
|
3858
4083
|
});
|
|
3859
4084
|
registerBuiltinHandlers();
|
|
@@ -3892,6 +4117,13 @@ var ClientRuntime = class {
|
|
|
3892
4117
|
this.agentIds.add(config.agentId);
|
|
3893
4118
|
}
|
|
3894
4119
|
async start() {
|
|
4120
|
+
if (this.options.currentVersion && this.options.update) this.updateManager = UpdateManager.attach(this.connection, {
|
|
4121
|
+
currentVersion: this.options.currentVersion,
|
|
4122
|
+
...this.options.update,
|
|
4123
|
+
isTTY: Boolean(process.stdout.isTTY),
|
|
4124
|
+
log: (level, msg) => process.stderr.write(` [update/${level}] ${msg}\n`),
|
|
4125
|
+
getQuietGateSnapshot: () => this.aggregateQuietGate()
|
|
4126
|
+
});
|
|
3895
4127
|
await this.connection.connect();
|
|
3896
4128
|
process.stderr.write(` \u2713 Client registered: ${this.connection.clientId}\n`);
|
|
3897
4129
|
if (this.agents.length === 0) {
|
|
@@ -3935,9 +4167,24 @@ var ClientRuntime = class {
|
|
|
3935
4167
|
}
|
|
3936
4168
|
async stop() {
|
|
3937
4169
|
this.unwatchAgentsDir();
|
|
4170
|
+
this.updateManager?.dispose();
|
|
4171
|
+
this.updateManager = null;
|
|
3938
4172
|
await Promise.allSettled(this.agents.map((a) => a.slot.stop()));
|
|
3939
4173
|
await this.connection.disconnect();
|
|
3940
4174
|
}
|
|
4175
|
+
aggregateQuietGate() {
|
|
4176
|
+
let activeCount = 0;
|
|
4177
|
+
let lastActivityMs = 0;
|
|
4178
|
+
for (const entry of this.agents) {
|
|
4179
|
+
const snap = entry.slot.getQuietGateSnapshot();
|
|
4180
|
+
activeCount += snap.activeCount;
|
|
4181
|
+
if (snap.lastActivityMs > lastActivityMs) lastActivityMs = snap.lastActivityMs;
|
|
4182
|
+
}
|
|
4183
|
+
return {
|
|
4184
|
+
activeCount,
|
|
4185
|
+
lastActivityMs
|
|
4186
|
+
};
|
|
4187
|
+
}
|
|
3941
4188
|
scanForNewAgents(agentsDir) {
|
|
3942
4189
|
try {
|
|
3943
4190
|
const all = loadAgents({
|
|
@@ -4506,136 +4753,570 @@ async function runMigrations(databaseUrl) {
|
|
|
4506
4753
|
}
|
|
4507
4754
|
}
|
|
4508
4755
|
//#endregion
|
|
4509
|
-
//#region src/core/
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4756
|
+
//#region src/core/service-install.ts
|
|
4757
|
+
/**
|
|
4758
|
+
* Run a subprocess capturing stderr so failures surface a meaningful error
|
|
4759
|
+
* instead of Node's opaque "Command failed". Used for launchctl/systemctl —
|
|
4760
|
+
* anywhere the stderr message is diagnostically crucial.
|
|
4761
|
+
*/
|
|
4762
|
+
function runCapture(program, args, timeoutMs) {
|
|
4763
|
+
const res = spawnSync(program, args, {
|
|
4764
|
+
encoding: "utf-8",
|
|
4765
|
+
timeout: timeoutMs,
|
|
4766
|
+
stdio: [
|
|
4767
|
+
"ignore",
|
|
4768
|
+
"pipe",
|
|
4769
|
+
"pipe"
|
|
4770
|
+
]
|
|
4771
|
+
});
|
|
4772
|
+
if (res.status === 0) return { ok: true };
|
|
4773
|
+
return {
|
|
4774
|
+
ok: false,
|
|
4775
|
+
stderr: (res.stderr ?? "").trim(),
|
|
4776
|
+
code: res.status
|
|
4777
|
+
};
|
|
4515
4778
|
}
|
|
4516
|
-
|
|
4517
|
-
|
|
4779
|
+
function sleepSync(ms) {
|
|
4780
|
+
const shared = new Int32Array(new SharedArrayBuffer(4));
|
|
4781
|
+
Atomics.wait(shared, 0, 0, ms);
|
|
4782
|
+
}
|
|
4783
|
+
const LAUNCHD_LABEL = "dev.first-tree-hub.client";
|
|
4784
|
+
const SYSTEMD_UNIT = "first-tree-hub-client.service";
|
|
4785
|
+
const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
|
|
4786
|
+
function whichBin(name) {
|
|
4518
4787
|
try {
|
|
4519
|
-
return
|
|
4788
|
+
return execFileSync(process.platform === "win32" ? "where" : "which", [name], {
|
|
4789
|
+
encoding: "utf-8",
|
|
4790
|
+
timeout: 3e3
|
|
4791
|
+
}).split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0] ?? null;
|
|
4520
4792
|
} catch {
|
|
4521
4793
|
return null;
|
|
4522
4794
|
}
|
|
4523
4795
|
}
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
try {
|
|
4540
|
-
const serverUrl = resolveServerUrl(args.server);
|
|
4541
|
-
items.push({
|
|
4542
|
-
key: "server",
|
|
4543
|
-
label: "Server URL",
|
|
4544
|
-
status: "ok",
|
|
4545
|
-
value: serverUrl
|
|
4546
|
-
});
|
|
4547
|
-
try {
|
|
4548
|
-
const res = await fetch(`${serverUrl}/api/v1/health`);
|
|
4549
|
-
items.push({
|
|
4550
|
-
key: "server_reachable",
|
|
4551
|
-
label: "Server reachable",
|
|
4552
|
-
status: res.ok ? "ok" : "error",
|
|
4553
|
-
value: res.ok ? "healthy" : `HTTP ${res.status}`
|
|
4554
|
-
});
|
|
4555
|
-
} catch {
|
|
4556
|
-
items.push({
|
|
4557
|
-
key: "server_reachable",
|
|
4558
|
-
label: "Server reachable",
|
|
4559
|
-
status: "error",
|
|
4560
|
-
hint: "Cannot connect to server"
|
|
4561
|
-
});
|
|
4562
|
-
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Resolve how the service should launch the CLI.
|
|
4798
|
+
*
|
|
4799
|
+
* Prefers the installed `first-tree-hub` bin on PATH (usually a shim under
|
|
4800
|
+
* /usr/local/bin or ~/.npm-global/bin). Falls back to invoking the current
|
|
4801
|
+
* Node interpreter against the running script (handles `pnpm dev`, tsx, and
|
|
4802
|
+
* dev-only global installs).
|
|
4803
|
+
*/
|
|
4804
|
+
function resolveCliInvocation() {
|
|
4805
|
+
const bin = whichBin("first-tree-hub");
|
|
4806
|
+
if (bin && isAbsolute(bin)) try {
|
|
4807
|
+
return {
|
|
4808
|
+
kind: "bin",
|
|
4809
|
+
program: realpathSync(bin)
|
|
4810
|
+
};
|
|
4563
4811
|
} catch {
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4568
|
-
hint: "Provide via --server, FIRST_TREE_HUB_SERVER_URL, or config"
|
|
4569
|
-
});
|
|
4570
|
-
}
|
|
4571
|
-
if (args.id) items.push({
|
|
4572
|
-
key: "id",
|
|
4573
|
-
label: "Agent ID",
|
|
4574
|
-
status: "ok",
|
|
4575
|
-
value: args.id
|
|
4576
|
-
});
|
|
4577
|
-
else items.push({
|
|
4578
|
-
key: "id",
|
|
4579
|
-
label: "Agent ID",
|
|
4580
|
-
status: "missing_required",
|
|
4581
|
-
hint: "Provide via --id"
|
|
4582
|
-
});
|
|
4583
|
-
if (args.type) items.push({
|
|
4584
|
-
key: "type",
|
|
4585
|
-
label: "Agent type",
|
|
4586
|
-
status: "ok",
|
|
4587
|
-
value: args.type
|
|
4588
|
-
});
|
|
4589
|
-
else items.push({
|
|
4590
|
-
key: "type",
|
|
4591
|
-
label: "Agent type",
|
|
4592
|
-
status: "missing_required",
|
|
4593
|
-
hint: "Provide via --type"
|
|
4594
|
-
});
|
|
4595
|
-
if (args.type && args.type !== "human") if (args.clientId) items.push({
|
|
4596
|
-
key: "client",
|
|
4597
|
-
label: "Target client",
|
|
4598
|
-
status: "ok",
|
|
4599
|
-
value: args.clientId
|
|
4600
|
-
});
|
|
4601
|
-
else items.push({
|
|
4602
|
-
key: "client",
|
|
4603
|
-
label: "Target client",
|
|
4604
|
-
status: "ok",
|
|
4605
|
-
value: "(unbound — claimed on first WS connect)"
|
|
4606
|
-
});
|
|
4607
|
-
return items;
|
|
4608
|
-
}
|
|
4609
|
-
function formatCheckReport(items) {
|
|
4610
|
-
const lines = [];
|
|
4611
|
-
for (const item of items) {
|
|
4612
|
-
const icon = item.status === "ok" ? "✅" : item.status === "missing_required" ? "❌" : item.status === "error" ? "❌" : item.status === "warning" ? "⚠️" : "⬜";
|
|
4613
|
-
const valueStr = item.value ? ` ${item.value}` : "";
|
|
4614
|
-
const hintStr = item.hint ? ` (${item.hint})` : "";
|
|
4615
|
-
lines.push(` ${icon} ${item.label.padEnd(20)}${valueStr}${hintStr}`);
|
|
4812
|
+
return {
|
|
4813
|
+
kind: "bin",
|
|
4814
|
+
program: bin
|
|
4815
|
+
};
|
|
4616
4816
|
}
|
|
4617
|
-
|
|
4817
|
+
const script = process.argv[1];
|
|
4818
|
+
if (!script) throw new Error("Cannot resolve CLI entry point (process.argv[1] is empty).");
|
|
4819
|
+
const scriptAbs = isAbsolute(script) ? script : join(process.cwd(), script);
|
|
4820
|
+
return {
|
|
4821
|
+
kind: "node",
|
|
4822
|
+
program: process.execPath,
|
|
4823
|
+
args: [scriptAbs]
|
|
4824
|
+
};
|
|
4618
4825
|
}
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
Authorization: `Bearer ${accessToken}`,
|
|
4624
|
-
"Content-Type": "application/json"
|
|
4625
|
-
},
|
|
4626
|
-
body: JSON.stringify(body),
|
|
4627
|
-
signal: AbortSignal.timeout(1e4)
|
|
4826
|
+
function ensureLogDir() {
|
|
4827
|
+
mkdirSync(LOG_DIR, {
|
|
4828
|
+
recursive: true,
|
|
4829
|
+
mode: 448
|
|
4628
4830
|
});
|
|
4629
|
-
if (!res.ok) {
|
|
4630
|
-
const errBody = await res.json().catch(() => ({}));
|
|
4631
|
-
throw new Error(errBody.error ?? `Failed to create agent (HTTP ${res.status})`);
|
|
4632
|
-
}
|
|
4633
|
-
return await res.json();
|
|
4634
4831
|
}
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4832
|
+
function launchdPlistPath() {
|
|
4833
|
+
return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
4834
|
+
}
|
|
4835
|
+
function renderPlist(invocation) {
|
|
4836
|
+
const argsXml = (invocation.kind === "bin" ? [
|
|
4837
|
+
invocation.program,
|
|
4838
|
+
"client",
|
|
4839
|
+
"start",
|
|
4840
|
+
"--no-interactive"
|
|
4841
|
+
] : [
|
|
4842
|
+
invocation.program,
|
|
4843
|
+
...invocation.args,
|
|
4844
|
+
"client",
|
|
4845
|
+
"start",
|
|
4846
|
+
"--no-interactive"
|
|
4847
|
+
]).map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
4848
|
+
const outLog = join(LOG_DIR, "client.out.log");
|
|
4849
|
+
const errLog = join(LOG_DIR, "client.err.log");
|
|
4850
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4851
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTD/PropertyList-1.0.dtd">
|
|
4852
|
+
<plist version="1.0">
|
|
4853
|
+
<dict>
|
|
4854
|
+
<key>Label</key>
|
|
4855
|
+
<string>${LAUNCHD_LABEL}</string>
|
|
4856
|
+
<key>ProgramArguments</key>
|
|
4857
|
+
<array>
|
|
4858
|
+
${argsXml}
|
|
4859
|
+
</array>
|
|
4860
|
+
<key>EnvironmentVariables</key>
|
|
4861
|
+
<dict>
|
|
4862
|
+
<key>PATH</key>
|
|
4863
|
+
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
4864
|
+
</dict>
|
|
4865
|
+
<key>RunAtLoad</key>
|
|
4866
|
+
<true/>
|
|
4867
|
+
<key>KeepAlive</key>
|
|
4868
|
+
<dict>
|
|
4869
|
+
<key>SuccessfulExit</key>
|
|
4870
|
+
<false/>
|
|
4871
|
+
</dict>
|
|
4872
|
+
<key>ThrottleInterval</key>
|
|
4873
|
+
<integer>10</integer>
|
|
4874
|
+
<key>StandardOutPath</key>
|
|
4875
|
+
<string>${escapeXml(outLog)}</string>
|
|
4876
|
+
<key>StandardErrorPath</key>
|
|
4877
|
+
<string>${escapeXml(errLog)}</string>
|
|
4878
|
+
</dict>
|
|
4879
|
+
</plist>
|
|
4880
|
+
`;
|
|
4881
|
+
}
|
|
4882
|
+
function escapeXml(value) {
|
|
4883
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
4884
|
+
}
|
|
4885
|
+
function launchctlDomainTarget() {
|
|
4886
|
+
return `gui/${userInfo().uid}`;
|
|
4887
|
+
}
|
|
4888
|
+
function launchdState() {
|
|
4889
|
+
if (!existsSync(launchdPlistPath())) return { state: "not-installed" };
|
|
4890
|
+
const res = spawnSync("launchctl", ["print", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], {
|
|
4891
|
+
encoding: "utf-8",
|
|
4892
|
+
timeout: 5e3,
|
|
4893
|
+
stdio: [
|
|
4894
|
+
"ignore",
|
|
4895
|
+
"pipe",
|
|
4896
|
+
"pipe"
|
|
4897
|
+
]
|
|
4898
|
+
});
|
|
4899
|
+
if (res.status !== 0) return {
|
|
4900
|
+
state: "inactive",
|
|
4901
|
+
detail: "plist present but not loaded"
|
|
4902
|
+
};
|
|
4903
|
+
const out = res.stdout ?? "";
|
|
4904
|
+
const stateLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("state ="));
|
|
4905
|
+
const pidLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("pid ="));
|
|
4906
|
+
if (stateLine?.includes("running")) {
|
|
4907
|
+
const pid = pidLine?.split("=")[1]?.trim();
|
|
4908
|
+
return {
|
|
4909
|
+
state: "active",
|
|
4910
|
+
detail: pid ? `pid ${pid}` : "running"
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4913
|
+
return {
|
|
4914
|
+
state: "inactive",
|
|
4915
|
+
detail: stateLine?.trim() ?? "loaded"
|
|
4916
|
+
};
|
|
4917
|
+
}
|
|
4918
|
+
/**
|
|
4919
|
+
* Poll `launchctl print` until the label disappears, confirming launchd has
|
|
4920
|
+
* finished the async eviction kicked off by `bootout`. Required because
|
|
4921
|
+
* `bootout` returns before the actual unload completes when the service has
|
|
4922
|
+
* active WebSocket connections — a follow-up `bootstrap` against a still-
|
|
4923
|
+
* registered label fails with `Bootstrap failed: 5: Input/output error`.
|
|
4924
|
+
*/
|
|
4925
|
+
function waitForLabelEvicted(target, label, timeoutMs) {
|
|
4926
|
+
const deadline = Date.now() + timeoutMs;
|
|
4927
|
+
while (Date.now() < deadline) {
|
|
4928
|
+
if (spawnSync("launchctl", ["print", `${target}/${label}`], {
|
|
4929
|
+
encoding: "utf-8",
|
|
4930
|
+
timeout: 2e3,
|
|
4931
|
+
stdio: [
|
|
4932
|
+
"ignore",
|
|
4933
|
+
"ignore",
|
|
4934
|
+
"pipe"
|
|
4935
|
+
]
|
|
4936
|
+
}).status !== 0) return true;
|
|
4937
|
+
sleepSync(200);
|
|
4938
|
+
}
|
|
4939
|
+
return false;
|
|
4940
|
+
}
|
|
4941
|
+
function installLaunchd() {
|
|
4942
|
+
const invocation = resolveCliInvocation();
|
|
4943
|
+
ensureLogDir();
|
|
4944
|
+
const plistPath = launchdPlistPath();
|
|
4945
|
+
mkdirSync(dirname(plistPath), { recursive: true });
|
|
4946
|
+
writeFileSync(plistPath, renderPlist(invocation), { mode: 420 });
|
|
4947
|
+
const target = launchctlDomainTarget();
|
|
4948
|
+
const bootoutRes = runCapture("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], 15e3);
|
|
4949
|
+
if (!bootoutRes.ok) {
|
|
4950
|
+
if (!/not find|no such|not loaded/i.test(bootoutRes.stderr)) process.stderr.write(` warning: launchctl bootout: ${bootoutRes.stderr || `exit ${bootoutRes.code ?? "unknown"}`}\n`);
|
|
4951
|
+
}
|
|
4952
|
+
waitForLabelEvicted(target, LAUNCHD_LABEL, 1e4);
|
|
4953
|
+
let lastBootstrapErr = null;
|
|
4954
|
+
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
4955
|
+
const res = runCapture("launchctl", [
|
|
4956
|
+
"bootstrap",
|
|
4957
|
+
target,
|
|
4958
|
+
plistPath
|
|
4959
|
+
], 1e4);
|
|
4960
|
+
if (res.ok) {
|
|
4961
|
+
lastBootstrapErr = null;
|
|
4962
|
+
break;
|
|
4963
|
+
}
|
|
4964
|
+
lastBootstrapErr = res;
|
|
4965
|
+
if (attempt < 2) sleepSync(1e3);
|
|
4966
|
+
}
|
|
4967
|
+
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 service install\`.`);
|
|
4968
|
+
const enableRes = runCapture("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], 5e3);
|
|
4969
|
+
if (!enableRes.ok) process.stderr.write(` warning: launchctl enable: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n`);
|
|
4970
|
+
const { state, detail } = launchdState();
|
|
4971
|
+
return {
|
|
4972
|
+
platform: "launchd",
|
|
4973
|
+
label: LAUNCHD_LABEL,
|
|
4974
|
+
unitPath: plistPath,
|
|
4975
|
+
logDir: LOG_DIR,
|
|
4976
|
+
state,
|
|
4977
|
+
detail
|
|
4978
|
+
};
|
|
4979
|
+
}
|
|
4980
|
+
function uninstallLaunchd() {
|
|
4981
|
+
const plistPath = launchdPlistPath();
|
|
4982
|
+
const res = runCapture("launchctl", ["bootout", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], 15e3);
|
|
4983
|
+
if (!res.ok && !/not find|no such|not loaded/i.test(res.stderr)) process.stderr.write(` warning: bootout during uninstall: ${res.stderr || `exit ${res.code ?? "unknown"}`}\n`);
|
|
4984
|
+
if (existsSync(plistPath)) rmSync(plistPath);
|
|
4985
|
+
return {
|
|
4986
|
+
platform: "launchd",
|
|
4987
|
+
label: LAUNCHD_LABEL,
|
|
4988
|
+
unitPath: plistPath,
|
|
4989
|
+
logDir: LOG_DIR,
|
|
4990
|
+
state: "not-installed"
|
|
4991
|
+
};
|
|
4992
|
+
}
|
|
4993
|
+
function systemdUnitPath() {
|
|
4994
|
+
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "systemd", "user", SYSTEMD_UNIT);
|
|
4995
|
+
}
|
|
4996
|
+
function renderSystemdUnit(invocation) {
|
|
4997
|
+
return `[Unit]
|
|
4998
|
+
Description=First Tree Hub Client
|
|
4999
|
+
After=network-online.target
|
|
5000
|
+
Wants=network-online.target
|
|
5001
|
+
|
|
5002
|
+
[Service]
|
|
5003
|
+
Type=simple
|
|
5004
|
+
ExecStart=${invocation.kind === "bin" ? `${shellQuote(invocation.program)} client start --no-interactive` : `${shellQuote(invocation.program)} ${invocation.args.map(shellQuote).join(" ")} client start --no-interactive`}
|
|
5005
|
+
Restart=always
|
|
5006
|
+
RestartSec=10
|
|
5007
|
+
StandardOutput=append:${join(LOG_DIR, "client.out.log")}
|
|
5008
|
+
StandardError=append:${join(LOG_DIR, "client.err.log")}
|
|
5009
|
+
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
5010
|
+
|
|
5011
|
+
[Install]
|
|
5012
|
+
WantedBy=default.target
|
|
5013
|
+
`;
|
|
5014
|
+
}
|
|
5015
|
+
function shellQuote(value) {
|
|
5016
|
+
if (/^[A-Za-z0-9_\-./:=]+$/.test(value)) return value;
|
|
5017
|
+
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
5018
|
+
}
|
|
5019
|
+
function systemdState() {
|
|
5020
|
+
if (!existsSync(systemdUnitPath())) return { state: "not-installed" };
|
|
5021
|
+
const res = spawnSync("systemctl", [
|
|
5022
|
+
"--user",
|
|
5023
|
+
"is-active",
|
|
5024
|
+
SYSTEMD_UNIT
|
|
5025
|
+
], {
|
|
5026
|
+
encoding: "utf-8",
|
|
5027
|
+
timeout: 5e3,
|
|
5028
|
+
stdio: [
|
|
5029
|
+
"ignore",
|
|
5030
|
+
"pipe",
|
|
5031
|
+
"pipe"
|
|
5032
|
+
]
|
|
5033
|
+
});
|
|
5034
|
+
const out = (res.stdout ?? "").trim();
|
|
5035
|
+
if (res.status === 0 && out === "active") return {
|
|
5036
|
+
state: "active",
|
|
5037
|
+
detail: "running"
|
|
5038
|
+
};
|
|
5039
|
+
return {
|
|
5040
|
+
state: "inactive",
|
|
5041
|
+
detail: out || "unit present but not active"
|
|
5042
|
+
};
|
|
5043
|
+
}
|
|
5044
|
+
function installSystemd() {
|
|
5045
|
+
const invocation = resolveCliInvocation();
|
|
5046
|
+
ensureLogDir();
|
|
5047
|
+
const unitPath = systemdUnitPath();
|
|
5048
|
+
mkdirSync(dirname(unitPath), { recursive: true });
|
|
5049
|
+
writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
|
|
5050
|
+
const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
|
|
5051
|
+
if (!reloadRes.ok) throw new Error(`systemctl --user daemon-reload failed: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}`);
|
|
5052
|
+
const enableRes = runCapture("systemctl", [
|
|
5053
|
+
"--user",
|
|
5054
|
+
"enable",
|
|
5055
|
+
"--now",
|
|
5056
|
+
SYSTEMD_UNIT
|
|
5057
|
+
], 1e4);
|
|
5058
|
+
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 service install\`.`);
|
|
5059
|
+
const { state, detail } = systemdState();
|
|
5060
|
+
return {
|
|
5061
|
+
platform: "systemd",
|
|
5062
|
+
label: SYSTEMD_UNIT,
|
|
5063
|
+
unitPath,
|
|
5064
|
+
logDir: LOG_DIR,
|
|
5065
|
+
state,
|
|
5066
|
+
detail
|
|
5067
|
+
};
|
|
5068
|
+
}
|
|
5069
|
+
function uninstallSystemd() {
|
|
5070
|
+
const unitPath = systemdUnitPath();
|
|
5071
|
+
const disableRes = runCapture("systemctl", [
|
|
5072
|
+
"--user",
|
|
5073
|
+
"disable",
|
|
5074
|
+
"--now",
|
|
5075
|
+
SYSTEMD_UNIT
|
|
5076
|
+
], 1e4);
|
|
5077
|
+
if (!disableRes.ok && !/not found|no such|not loaded/i.test(disableRes.stderr)) process.stderr.write(` warning: systemctl disable during uninstall: ${disableRes.stderr || `exit ${disableRes.code ?? "unknown"}`}\n`);
|
|
5078
|
+
if (existsSync(unitPath)) rmSync(unitPath);
|
|
5079
|
+
const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
|
|
5080
|
+
if (!reloadRes.ok) process.stderr.write(` warning: systemctl daemon-reload during uninstall: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}\n`);
|
|
5081
|
+
return {
|
|
5082
|
+
platform: "systemd",
|
|
5083
|
+
label: SYSTEMD_UNIT,
|
|
5084
|
+
unitPath,
|
|
5085
|
+
logDir: LOG_DIR,
|
|
5086
|
+
state: "not-installed"
|
|
5087
|
+
};
|
|
5088
|
+
}
|
|
5089
|
+
/** Is background-service install supported on the current platform? */
|
|
5090
|
+
function isServiceSupported() {
|
|
5091
|
+
return process.platform === "darwin" || process.platform === "linux";
|
|
5092
|
+
}
|
|
5093
|
+
/**
|
|
5094
|
+
* Install the background service for the current platform.
|
|
5095
|
+
*
|
|
5096
|
+
* @throws {Error} if the platform is not supported or the service manager fails.
|
|
5097
|
+
*/
|
|
5098
|
+
function installClientService() {
|
|
5099
|
+
if (process.platform === "darwin") return installLaunchd();
|
|
5100
|
+
if (process.platform === "linux") return installSystemd();
|
|
5101
|
+
throw new Error(`Background service install is not supported on ${process.platform}. Run \`first-tree-hub client start\` manually to keep the computer online.`);
|
|
5102
|
+
}
|
|
5103
|
+
/** Report the current service state without modifying anything. */
|
|
5104
|
+
function getClientServiceStatus() {
|
|
5105
|
+
if (process.platform === "darwin") {
|
|
5106
|
+
const { state, detail } = launchdState();
|
|
5107
|
+
return {
|
|
5108
|
+
platform: "launchd",
|
|
5109
|
+
label: LAUNCHD_LABEL,
|
|
5110
|
+
unitPath: launchdPlistPath(),
|
|
5111
|
+
logDir: LOG_DIR,
|
|
5112
|
+
state,
|
|
5113
|
+
detail
|
|
5114
|
+
};
|
|
5115
|
+
}
|
|
5116
|
+
if (process.platform === "linux") {
|
|
5117
|
+
const { state, detail } = systemdState();
|
|
5118
|
+
return {
|
|
5119
|
+
platform: "systemd",
|
|
5120
|
+
label: SYSTEMD_UNIT,
|
|
5121
|
+
unitPath: systemdUnitPath(),
|
|
5122
|
+
logDir: LOG_DIR,
|
|
5123
|
+
state,
|
|
5124
|
+
detail
|
|
5125
|
+
};
|
|
5126
|
+
}
|
|
5127
|
+
return {
|
|
5128
|
+
platform: "unsupported",
|
|
5129
|
+
label: "",
|
|
5130
|
+
unitPath: "",
|
|
5131
|
+
logDir: LOG_DIR,
|
|
5132
|
+
state: "not-installed",
|
|
5133
|
+
detail: `platform ${process.platform} not supported`
|
|
5134
|
+
};
|
|
5135
|
+
}
|
|
5136
|
+
/** Uninstall the background service. No-op if not installed. */
|
|
5137
|
+
function uninstallClientService() {
|
|
5138
|
+
if (process.platform === "darwin") return uninstallLaunchd();
|
|
5139
|
+
if (process.platform === "linux") return uninstallSystemd();
|
|
5140
|
+
return getClientServiceStatus();
|
|
5141
|
+
}
|
|
5142
|
+
//#endregion
|
|
5143
|
+
//#region src/core/migrate-home.ts
|
|
5144
|
+
/**
|
|
5145
|
+
* Run the one-shot legacy home migration at CLI startup and, if it succeeds,
|
|
5146
|
+
* re-register the background service so launchd/systemd pick up the new
|
|
5147
|
+
* `StandardOutPath` / `StandardErrorPath` / `ExecStart` log paths (those are
|
|
5148
|
+
* baked into the plist/unit file at install time — when we populate the new
|
|
5149
|
+
* home, those paths would otherwise still point at the old location).
|
|
5150
|
+
*
|
|
5151
|
+
* Copy-only semantics: the legacy `~/.first-tree-hub/` tree is preserved
|
|
5152
|
+
* as a safety net. The user can inspect/fall-back to it, and can delete it
|
|
5153
|
+
* manually once they've confirmed the new layout is healthy.
|
|
5154
|
+
*
|
|
5155
|
+
* Contract:
|
|
5156
|
+
* - Synchronous and cheap when there's nothing to do (most runs — the
|
|
5157
|
+
* steady state is "new dir populated", which short-circuits the copy).
|
|
5158
|
+
* - Never throws — migration failures and service re-register failures
|
|
5159
|
+
* both fall through to a stderr warning so the CLI command still runs.
|
|
5160
|
+
* - Idempotent — safe to call on every CLI invocation.
|
|
5161
|
+
* - Skips service re-register when we are already running AS the service
|
|
5162
|
+
* (launchd/systemd invoke the CLI with `--no-interactive`), because the
|
|
5163
|
+
* re-register would bootout our own process mid-execution.
|
|
5164
|
+
*/
|
|
5165
|
+
function runHomeMigration() {
|
|
5166
|
+
const result = migrateLegacyHome({
|
|
5167
|
+
newHome: DEFAULT_HOME_DIR$1,
|
|
5168
|
+
envOverride: process.env.FIRST_TREE_HUB_HOME ?? null
|
|
5169
|
+
});
|
|
5170
|
+
if (!result.migrated) {
|
|
5171
|
+
if (result.reason === "failed") process.stderr.write(`[first-tree-hub] WARNING: failed to auto-migrate legacy home ${result.from} → ${result.to}: ${result.error ?? "unknown error"}\n Resolve manually: cp -R "${result.from}" "${result.to}"\n`);
|
|
5172
|
+
return;
|
|
5173
|
+
}
|
|
5174
|
+
process.stderr.write(`[first-tree-hub] Copied client home to new layout: ${result.from} → ${result.to}\n (Legacy directory preserved as a backup — delete it manually once you've verified the new location works.)\n`);
|
|
5175
|
+
if (process.argv.includes("--no-interactive")) {
|
|
5176
|
+
process.stderr.write("[first-tree-hub] Note: running as background service — skipped auto re-register to avoid self-termination.\n Run `first-tree-hub client service install` from a terminal to refresh log paths.\n");
|
|
5177
|
+
return;
|
|
5178
|
+
}
|
|
5179
|
+
const status = getClientServiceStatus();
|
|
5180
|
+
if (status.platform === "unsupported" || status.state === "not-installed") return;
|
|
5181
|
+
try {
|
|
5182
|
+
installClientService();
|
|
5183
|
+
process.stderr.write(`[first-tree-hub] Re-registered background service with new home paths.\n`);
|
|
5184
|
+
} catch (err) {
|
|
5185
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5186
|
+
process.stderr.write(`[first-tree-hub] WARNING: home migration succeeded but re-registering the background service failed: ${msg}\n Run \`first-tree-hub client service install\` to refresh log paths.\n`);
|
|
5187
|
+
}
|
|
5188
|
+
}
|
|
5189
|
+
//#endregion
|
|
5190
|
+
//#region src/core/onboard.ts
|
|
5191
|
+
const STATE_FILE = join(DEFAULT_HOME_DIR$1, ".onboard-state.json");
|
|
5192
|
+
/** Save current onboard args to state file for resume. */
|
|
5193
|
+
function saveOnboardState(args) {
|
|
5194
|
+
mkdirSync(DEFAULT_HOME_DIR$1, { recursive: true });
|
|
5195
|
+
writeFileSync(STATE_FILE, JSON.stringify({ args }, null, 2));
|
|
5196
|
+
}
|
|
5197
|
+
/** Load saved onboard args from state file. */
|
|
5198
|
+
function loadOnboardState() {
|
|
5199
|
+
try {
|
|
5200
|
+
return JSON.parse(readFileSync(STATE_FILE, "utf-8")).args;
|
|
5201
|
+
} catch {
|
|
5202
|
+
return null;
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
async function onboardCheck(args) {
|
|
5206
|
+
const items = [];
|
|
5207
|
+
const creds = loadCredentials();
|
|
5208
|
+
if (creds) items.push({
|
|
5209
|
+
key: "connect",
|
|
5210
|
+
label: "Signed in",
|
|
5211
|
+
status: "ok",
|
|
5212
|
+
value: creds.serverUrl
|
|
5213
|
+
});
|
|
5214
|
+
else items.push({
|
|
5215
|
+
key: "connect",
|
|
5216
|
+
label: "Signed in",
|
|
5217
|
+
status: "missing_required",
|
|
5218
|
+
hint: "Run `first-tree-hub client connect <server-url>` first"
|
|
5219
|
+
});
|
|
5220
|
+
try {
|
|
5221
|
+
const serverUrl = resolveServerUrl(args.server);
|
|
5222
|
+
items.push({
|
|
5223
|
+
key: "server",
|
|
5224
|
+
label: "Server URL",
|
|
5225
|
+
status: "ok",
|
|
5226
|
+
value: serverUrl
|
|
5227
|
+
});
|
|
5228
|
+
try {
|
|
5229
|
+
const res = await fetch(`${serverUrl}/api/v1/health`);
|
|
5230
|
+
items.push({
|
|
5231
|
+
key: "server_reachable",
|
|
5232
|
+
label: "Server reachable",
|
|
5233
|
+
status: res.ok ? "ok" : "error",
|
|
5234
|
+
value: res.ok ? "healthy" : `HTTP ${res.status}`
|
|
5235
|
+
});
|
|
5236
|
+
} catch {
|
|
5237
|
+
items.push({
|
|
5238
|
+
key: "server_reachable",
|
|
5239
|
+
label: "Server reachable",
|
|
5240
|
+
status: "error",
|
|
5241
|
+
hint: "Cannot connect to server"
|
|
5242
|
+
});
|
|
5243
|
+
}
|
|
5244
|
+
} catch {
|
|
5245
|
+
items.push({
|
|
5246
|
+
key: "server",
|
|
5247
|
+
label: "Server URL",
|
|
5248
|
+
status: "missing_required",
|
|
5249
|
+
hint: "Provide via --server, FIRST_TREE_HUB_SERVER_URL, or config"
|
|
5250
|
+
});
|
|
5251
|
+
}
|
|
5252
|
+
if (args.id) items.push({
|
|
5253
|
+
key: "id",
|
|
5254
|
+
label: "Agent ID",
|
|
5255
|
+
status: "ok",
|
|
5256
|
+
value: args.id
|
|
5257
|
+
});
|
|
5258
|
+
else items.push({
|
|
5259
|
+
key: "id",
|
|
5260
|
+
label: "Agent ID",
|
|
5261
|
+
status: "missing_required",
|
|
5262
|
+
hint: "Provide via --id"
|
|
5263
|
+
});
|
|
5264
|
+
if (args.type) items.push({
|
|
5265
|
+
key: "type",
|
|
5266
|
+
label: "Agent type",
|
|
5267
|
+
status: "ok",
|
|
5268
|
+
value: args.type
|
|
5269
|
+
});
|
|
5270
|
+
else items.push({
|
|
5271
|
+
key: "type",
|
|
5272
|
+
label: "Agent type",
|
|
5273
|
+
status: "missing_required",
|
|
5274
|
+
hint: "Provide via --type"
|
|
5275
|
+
});
|
|
5276
|
+
if (args.type && args.type !== "human") if (args.clientId) items.push({
|
|
5277
|
+
key: "client",
|
|
5278
|
+
label: "Target client",
|
|
5279
|
+
status: "ok",
|
|
5280
|
+
value: args.clientId
|
|
5281
|
+
});
|
|
5282
|
+
else items.push({
|
|
5283
|
+
key: "client",
|
|
5284
|
+
label: "Target client",
|
|
5285
|
+
status: "ok",
|
|
5286
|
+
value: "(unbound — claimed on first WS connect)"
|
|
5287
|
+
});
|
|
5288
|
+
return items;
|
|
5289
|
+
}
|
|
5290
|
+
function formatCheckReport(items) {
|
|
5291
|
+
const lines = [];
|
|
5292
|
+
for (const item of items) {
|
|
5293
|
+
const icon = item.status === "ok" ? "✅" : item.status === "missing_required" ? "❌" : item.status === "error" ? "❌" : item.status === "warning" ? "⚠️" : "⬜";
|
|
5294
|
+
const valueStr = item.value ? ` ${item.value}` : "";
|
|
5295
|
+
const hintStr = item.hint ? ` (${item.hint})` : "";
|
|
5296
|
+
lines.push(` ${icon} ${item.label.padEnd(20)}${valueStr}${hintStr}`);
|
|
5297
|
+
}
|
|
5298
|
+
return lines.join("\n");
|
|
5299
|
+
}
|
|
5300
|
+
async function createAgentViaAdmin(serverUrl, accessToken, body) {
|
|
5301
|
+
const res = await fetch(`${serverUrl}/api/v1/admin/agents`, {
|
|
5302
|
+
method: "POST",
|
|
5303
|
+
headers: {
|
|
5304
|
+
Authorization: `Bearer ${accessToken}`,
|
|
5305
|
+
"Content-Type": "application/json"
|
|
5306
|
+
},
|
|
5307
|
+
body: JSON.stringify(body),
|
|
5308
|
+
signal: AbortSignal.timeout(1e4)
|
|
5309
|
+
});
|
|
5310
|
+
if (!res.ok) {
|
|
5311
|
+
const errBody = await res.json().catch(() => ({}));
|
|
5312
|
+
throw new Error(errBody.error ?? `Failed to create agent (HTTP ${res.status})`);
|
|
5313
|
+
}
|
|
5314
|
+
return await res.json();
|
|
5315
|
+
}
|
|
5316
|
+
async function onboardCreate(args) {
|
|
5317
|
+
const serverUrl = resolveServerUrl(args.server).replace(/\/+$/, "");
|
|
5318
|
+
const accessToken = await ensureFreshAccessToken();
|
|
5319
|
+
const metadata = {};
|
|
4639
5320
|
if (args.role) metadata.role = args.role;
|
|
4640
5321
|
if (args.domains) metadata.domains = args.domains.split(",").map((d) => d.trim());
|
|
4641
5322
|
process.stderr.write(`Creating agent "${args.id}"...\n`);
|
|
@@ -4647,174 +5328,2880 @@ async function onboardCreate(args) {
|
|
|
4647
5328
|
metadata: Object.keys(metadata).length > 0 ? metadata : void 0,
|
|
4648
5329
|
clientId: args.type === "human" ? void 0 : args.clientId
|
|
4649
5330
|
});
|
|
4650
|
-
process.stderr.write(`Agent "${args.id}" created (uuid ${primary.uuid}).\n`);
|
|
4651
|
-
if (args.type !== "human") saveAgentConfig(args.id, primary.uuid, "claude-code");
|
|
4652
|
-
let assistantUuid = null;
|
|
4653
|
-
if (args.assistant) {
|
|
4654
|
-
process.stderr.write(`Creating assistant "${args.assistant}"...\n`);
|
|
5331
|
+
process.stderr.write(`Agent "${args.id}" created (uuid ${primary.uuid}).\n`);
|
|
5332
|
+
if (args.type !== "human") saveAgentConfig(args.id, primary.uuid, "claude-code");
|
|
5333
|
+
let assistantUuid = null;
|
|
5334
|
+
if (args.assistant) {
|
|
5335
|
+
process.stderr.write(`Creating assistant "${args.assistant}"...\n`);
|
|
5336
|
+
try {
|
|
5337
|
+
const assistant = await createAgentViaAdmin(serverUrl, accessToken, {
|
|
5338
|
+
name: args.assistant,
|
|
5339
|
+
type: "personal_assistant",
|
|
5340
|
+
displayName: args.assistant,
|
|
5341
|
+
metadata: {
|
|
5342
|
+
role: `Personal Assistant to ${args.id}`,
|
|
5343
|
+
domains: ["message triage", "task coordination"]
|
|
5344
|
+
},
|
|
5345
|
+
clientId: args.clientId
|
|
5346
|
+
});
|
|
5347
|
+
assistantUuid = assistant.uuid;
|
|
5348
|
+
saveAgentConfig(args.assistant, assistant.uuid, "claude-code");
|
|
5349
|
+
process.stderr.write(`Assistant "${args.assistant}" ready.\n`);
|
|
5350
|
+
} catch (err) {
|
|
5351
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
5352
|
+
process.stderr.write(`Warning: Failed to create assistant "${args.assistant}": ${msg}\n`);
|
|
5353
|
+
}
|
|
5354
|
+
}
|
|
5355
|
+
const runtimeAgent = args.type === "human" ? args.assistant : args.id;
|
|
5356
|
+
if (args.feishuBotAppId && args.feishuBotAppSecret) {
|
|
5357
|
+
const { bindFeishuBot } = await import("./feishu-GlaczcVf.mjs").then((n) => n.r);
|
|
5358
|
+
const targetAgentUuid = args.type === "human" ? assistantUuid : primary.uuid;
|
|
5359
|
+
if (!targetAgentUuid) process.stderr.write(`Warning: Cannot bind Feishu bot — no runtime agent available for "${args.id}".\n`);
|
|
5360
|
+
else {
|
|
5361
|
+
process.stderr.write("Binding Feishu bot...\n");
|
|
5362
|
+
await bindFeishuBot(serverUrl, accessToken, targetAgentUuid, args.feishuBotAppId, args.feishuBotAppSecret);
|
|
5363
|
+
process.stderr.write("Feishu bot bound.\n");
|
|
5364
|
+
}
|
|
5365
|
+
}
|
|
5366
|
+
setConfigValue(join(DEFAULT_CONFIG_DIR, "client.yaml"), "server.url", serverUrl);
|
|
5367
|
+
try {
|
|
5368
|
+
const { unlinkSync } = await import("node:fs");
|
|
5369
|
+
unlinkSync(STATE_FILE);
|
|
5370
|
+
} catch {}
|
|
5371
|
+
const typeLabel = args.type === "human" ? "Human" : args.type === "autonomous_agent" ? "Agent" : "Assistant";
|
|
5372
|
+
process.stderr.write("\n✅ Onboard complete!\n\n");
|
|
5373
|
+
process.stderr.write(` ${typeLabel}:${" ".repeat(Math.max(1, 10 - typeLabel.length))}${args.id}\n`);
|
|
5374
|
+
if (args.assistant) process.stderr.write(` Assistant: ${args.assistant}\n`);
|
|
5375
|
+
if (runtimeAgent) process.stderr.write(` Config: ${DEFAULT_HOME_DIR$1}/config/agents/${runtimeAgent}/agent.yaml\n`);
|
|
5376
|
+
if (args.feishuBotAppId) process.stderr.write(` Feishu: bot bound (${args.feishuBotAppId})\n`);
|
|
5377
|
+
if (args.type === "human") {
|
|
5378
|
+
process.stderr.write("\n Next step — bind your Feishu account:\n");
|
|
5379
|
+
process.stderr.write(` Send this message to the bot in Feishu: /bind ${args.id}\n`);
|
|
5380
|
+
if (!args.feishuBotAppId) process.stderr.write(" (requires a Feishu bot to be configured in the system)\n");
|
|
5381
|
+
}
|
|
5382
|
+
if (runtimeAgent) {
|
|
5383
|
+
process.stderr.write("\n Start the agent:\n");
|
|
5384
|
+
process.stderr.write(" first-tree-hub client start\n");
|
|
5385
|
+
}
|
|
5386
|
+
process.stderr.write("\n");
|
|
5387
|
+
}
|
|
5388
|
+
//#endregion
|
|
5389
|
+
//#region src/core/prompt.ts
|
|
5390
|
+
/**
|
|
5391
|
+
* Check if interactive mode is available.
|
|
5392
|
+
* Returns false if --no-interactive flag is set or stdin is not a TTY.
|
|
5393
|
+
*/
|
|
5394
|
+
function isInteractive(noInteractiveFlag) {
|
|
5395
|
+
if (noInteractiveFlag) return false;
|
|
5396
|
+
return process.stdin.isTTY === true;
|
|
5397
|
+
}
|
|
5398
|
+
/**
|
|
5399
|
+
* Schema-driven interactive setup.
|
|
5400
|
+
* Scans the config schema for fields with `prompt` that are missing.
|
|
5401
|
+
*
|
|
5402
|
+
* In interactive mode: prompts the user and writes results to YAML.
|
|
5403
|
+
* In non-interactive mode: fails with a clear error listing missing fields.
|
|
5404
|
+
*/
|
|
5405
|
+
async function promptMissingFields(options) {
|
|
5406
|
+
const missing = collectMissingPrompts({
|
|
5407
|
+
schema: options.schema,
|
|
5408
|
+
role: options.role,
|
|
5409
|
+
configDir: options.configDir,
|
|
5410
|
+
cliArgs: options.cliArgs
|
|
5411
|
+
});
|
|
5412
|
+
if (missing.length === 0) return {};
|
|
5413
|
+
if (!isInteractive(options.noInteractive)) {
|
|
5414
|
+
const lines = missing.map((m) => {
|
|
5415
|
+
const envHint = findEnvVar(options.schema, m.dotPath);
|
|
5416
|
+
const envStr = envHint ? ` (env: ${envHint})` : "";
|
|
5417
|
+
return ` ${m.dotPath}${envStr}`;
|
|
5418
|
+
});
|
|
5419
|
+
throw new Error(`Missing required configuration:\n${lines.join("\n")}\n\nProvide values via environment variables, config file (${DEFAULT_HOME_DIR$1}/server.yaml),\nor run without --no-interactive to use the interactive setup wizard.`);
|
|
5420
|
+
}
|
|
5421
|
+
const configPath = join(options.configDir ?? DEFAULT_CONFIG_DIR, `${options.role}.yaml`);
|
|
5422
|
+
const results = {};
|
|
5423
|
+
for (const { dotPath, prompt } of missing) {
|
|
5424
|
+
const value = await askPrompt(dotPath, prompt);
|
|
5425
|
+
if (value !== void 0) {
|
|
5426
|
+
setConfigValue(configPath, dotPath, value);
|
|
5427
|
+
setNestedByDot(results, dotPath, value);
|
|
5428
|
+
}
|
|
5429
|
+
}
|
|
5430
|
+
return results;
|
|
5431
|
+
}
|
|
5432
|
+
/**
|
|
5433
|
+
* Interactive add agent — simple two-field prompt.
|
|
5434
|
+
*/
|
|
5435
|
+
async function promptAddAgent() {
|
|
5436
|
+
return {
|
|
5437
|
+
name: await input({
|
|
5438
|
+
message: "Local alias:",
|
|
5439
|
+
validate: (v) => /^[a-z0-9][a-z0-9-]*$/.test(v) ? true : "Lowercase alphanumeric and hyphens only"
|
|
5440
|
+
}),
|
|
5441
|
+
agentId: await input({
|
|
5442
|
+
message: "Agent UUID on the Hub:",
|
|
5443
|
+
validate: (v) => v.length > 0 ? true : "Agent UUID is required"
|
|
5444
|
+
})
|
|
5445
|
+
};
|
|
5446
|
+
}
|
|
5447
|
+
async function askPrompt(dotPath, prompt) {
|
|
5448
|
+
const type = prompt.type ?? "input";
|
|
5449
|
+
if (type === "select" && prompt.choices) {
|
|
5450
|
+
const value = await select({
|
|
5451
|
+
message: prompt.message,
|
|
5452
|
+
choices: prompt.choices.map((c) => ({
|
|
5453
|
+
name: c.name,
|
|
5454
|
+
value: c.value
|
|
5455
|
+
}))
|
|
5456
|
+
});
|
|
5457
|
+
if (value === "__auto__") return void 0;
|
|
5458
|
+
if (value === "__input__") return input({
|
|
5459
|
+
message: `${dotPath}:`,
|
|
5460
|
+
validate: (v) => v.length > 0 ? true : "Value is required"
|
|
5461
|
+
});
|
|
5462
|
+
return value;
|
|
5463
|
+
}
|
|
5464
|
+
if (type === "password") return password({ message: prompt.message });
|
|
5465
|
+
return input({
|
|
5466
|
+
message: prompt.message,
|
|
5467
|
+
default: prompt.default
|
|
5468
|
+
});
|
|
5469
|
+
}
|
|
5470
|
+
/** Walk schema to find the env var name for a given dot path. */
|
|
5471
|
+
function findEnvVar(schema, dotPath) {
|
|
5472
|
+
const parts = dotPath.split(".");
|
|
5473
|
+
let current = schema;
|
|
5474
|
+
for (const part of parts) {
|
|
5475
|
+
if (current === null || current === void 0 || typeof current !== "object") return void 0;
|
|
5476
|
+
const obj = current;
|
|
5477
|
+
if (obj._tag === "optional") current = obj.shape[part];
|
|
5478
|
+
else current = obj[part];
|
|
5479
|
+
}
|
|
5480
|
+
if (typeof current === "object" && current !== null && "_tag" in current) {
|
|
5481
|
+
const field = current;
|
|
5482
|
+
if (field._tag === "field") return field.options?.env;
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
function setNestedByDot(obj, dotPath, value) {
|
|
5486
|
+
const parts = dotPath.split(".");
|
|
5487
|
+
let current = obj;
|
|
5488
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
5489
|
+
const key = parts[i];
|
|
5490
|
+
if (key === void 0) continue;
|
|
5491
|
+
if (!(key in current) || typeof current[key] !== "object" || current[key] === null) current[key] = {};
|
|
5492
|
+
current = current[key];
|
|
5493
|
+
}
|
|
5494
|
+
const lastKey = parts.at(-1);
|
|
5495
|
+
if (lastKey !== void 0) current[lastKey] = value;
|
|
5496
|
+
}
|
|
5497
|
+
//#endregion
|
|
5498
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/deps/streamsearch/sbmh.js
|
|
5499
|
+
var require_sbmh = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5500
|
+
/**
|
|
5501
|
+
* Copyright Brian White. All rights reserved.
|
|
5502
|
+
*
|
|
5503
|
+
* @see https://github.com/mscdex/streamsearch
|
|
5504
|
+
*
|
|
5505
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
5506
|
+
* of this software and associated documentation files (the "Software"), to
|
|
5507
|
+
* deal in the Software without restriction, including without limitation the
|
|
5508
|
+
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
5509
|
+
* sell copies of the Software, and to permit persons to whom the Software is
|
|
5510
|
+
* furnished to do so, subject to the following conditions:
|
|
5511
|
+
*
|
|
5512
|
+
* The above copyright notice and this permission notice shall be included in
|
|
5513
|
+
* all copies or substantial portions of the Software.
|
|
5514
|
+
*
|
|
5515
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
5516
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
5517
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
5518
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
5519
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
5520
|
+
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
5521
|
+
* IN THE SOFTWARE.
|
|
5522
|
+
*
|
|
5523
|
+
* Based heavily on the Streaming Boyer-Moore-Horspool C++ implementation
|
|
5524
|
+
* by Hongli Lai at: https://github.com/FooBarWidget/boyer-moore-horspool
|
|
5525
|
+
*/
|
|
5526
|
+
const { EventEmitter: EventEmitter$2 } = __require("node:events");
|
|
5527
|
+
const { inherits: inherits$5 } = __require("node:util");
|
|
5528
|
+
function SBMH(needle) {
|
|
5529
|
+
if (typeof needle === "string") needle = Buffer.from(needle);
|
|
5530
|
+
if (!Buffer.isBuffer(needle)) throw new TypeError("The needle has to be a String or a Buffer.");
|
|
5531
|
+
const needleLength = needle.length;
|
|
5532
|
+
const needleLastCharIndex = needleLength - 1;
|
|
5533
|
+
if (needleLength === 0) throw new Error("The needle cannot be an empty String/Buffer.");
|
|
5534
|
+
if (needleLength > 256) throw new Error("The needle cannot have a length bigger than 256.");
|
|
5535
|
+
this.maxMatches = Infinity;
|
|
5536
|
+
this.matches = 0;
|
|
5537
|
+
this._occ = new Uint8Array(256).fill(needleLength);
|
|
5538
|
+
this._lookbehind_size = 0;
|
|
5539
|
+
this._needle = needle;
|
|
5540
|
+
this._bufpos = 0;
|
|
5541
|
+
this._lookbehind = Buffer.alloc(needleLastCharIndex);
|
|
5542
|
+
for (var i = 0; i < needleLastCharIndex; ++i) this._occ[needle[i]] = needleLastCharIndex - i;
|
|
5543
|
+
}
|
|
5544
|
+
inherits$5(SBMH, EventEmitter$2);
|
|
5545
|
+
SBMH.prototype.reset = function() {
|
|
5546
|
+
this._lookbehind_size = 0;
|
|
5547
|
+
this.matches = 0;
|
|
5548
|
+
this._bufpos = 0;
|
|
5549
|
+
};
|
|
5550
|
+
SBMH.prototype.push = function(chunk, pos) {
|
|
5551
|
+
if (!Buffer.isBuffer(chunk)) chunk = Buffer.from(chunk, "binary");
|
|
5552
|
+
const chlen = chunk.length;
|
|
5553
|
+
this._bufpos = pos || 0;
|
|
5554
|
+
let r;
|
|
5555
|
+
while (r !== chlen && this.matches < this.maxMatches) r = this._sbmh_feed(chunk);
|
|
5556
|
+
return r;
|
|
5557
|
+
};
|
|
5558
|
+
SBMH.prototype._sbmh_feed = function(data) {
|
|
5559
|
+
const len = data.length;
|
|
5560
|
+
const needle = this._needle;
|
|
5561
|
+
const needleLength = needle.length;
|
|
5562
|
+
const needleLastCharIndex = needleLength - 1;
|
|
5563
|
+
const needleLastChar = needle[needleLastCharIndex];
|
|
5564
|
+
let pos = -this._lookbehind_size;
|
|
5565
|
+
let ch;
|
|
5566
|
+
if (pos < 0) {
|
|
5567
|
+
while (pos < 0 && pos <= len - needleLength) {
|
|
5568
|
+
ch = data[pos + needleLastCharIndex];
|
|
5569
|
+
if (ch === needleLastChar && this._sbmh_memcmp(data, pos, needleLastCharIndex)) {
|
|
5570
|
+
this._lookbehind_size = 0;
|
|
5571
|
+
++this.matches;
|
|
5572
|
+
this.emit("info", true);
|
|
5573
|
+
return this._bufpos = pos + needleLength;
|
|
5574
|
+
}
|
|
5575
|
+
pos += this._occ[ch];
|
|
5576
|
+
}
|
|
5577
|
+
while (pos < 0 && !this._sbmh_memcmp(data, pos, len - pos)) ++pos;
|
|
5578
|
+
if (pos >= 0) {
|
|
5579
|
+
this.emit("info", false, this._lookbehind, 0, this._lookbehind_size);
|
|
5580
|
+
this._lookbehind_size = 0;
|
|
5581
|
+
} else {
|
|
5582
|
+
const bytesToCutOff = this._lookbehind_size + pos;
|
|
5583
|
+
if (bytesToCutOff > 0) this.emit("info", false, this._lookbehind, 0, bytesToCutOff);
|
|
5584
|
+
this._lookbehind_size -= bytesToCutOff;
|
|
5585
|
+
this._lookbehind.copy(this._lookbehind, 0, bytesToCutOff, this._lookbehind_size);
|
|
5586
|
+
data.copy(this._lookbehind, this._lookbehind_size);
|
|
5587
|
+
this._lookbehind_size += len;
|
|
5588
|
+
this._bufpos = len;
|
|
5589
|
+
return len;
|
|
5590
|
+
}
|
|
5591
|
+
}
|
|
5592
|
+
pos = data.indexOf(needle, pos + this._bufpos);
|
|
5593
|
+
if (pos !== -1) {
|
|
5594
|
+
++this.matches;
|
|
5595
|
+
if (pos === 0) this.emit("info", true);
|
|
5596
|
+
else this.emit("info", true, data, this._bufpos, pos);
|
|
5597
|
+
return this._bufpos = pos + needleLength;
|
|
5598
|
+
}
|
|
5599
|
+
pos = len - needleLastCharIndex;
|
|
5600
|
+
if (pos < 0) pos = 0;
|
|
5601
|
+
while (pos !== len && (data[pos] !== needle[0] || Buffer.compare(data.subarray(pos + 1, len), needle.subarray(1, len - pos)) !== 0)) ++pos;
|
|
5602
|
+
if (pos !== len) {
|
|
5603
|
+
data.copy(this._lookbehind, 0, pos, len);
|
|
5604
|
+
this._lookbehind_size = len - pos;
|
|
5605
|
+
}
|
|
5606
|
+
if (pos !== 0) this.emit("info", false, data, this._bufpos, pos);
|
|
5607
|
+
this._bufpos = len;
|
|
5608
|
+
return len;
|
|
5609
|
+
};
|
|
5610
|
+
SBMH.prototype._sbmh_lookup_char = function(data, pos) {
|
|
5611
|
+
return pos < 0 ? this._lookbehind[this._lookbehind_size + pos] : data[pos];
|
|
5612
|
+
};
|
|
5613
|
+
SBMH.prototype._sbmh_memcmp = function(data, pos, len) {
|
|
5614
|
+
for (var i = 0; i < len; ++i) if (this._sbmh_lookup_char(data, pos + i) !== this._needle[i]) return false;
|
|
5615
|
+
return true;
|
|
5616
|
+
};
|
|
5617
|
+
module.exports = SBMH;
|
|
5618
|
+
}));
|
|
5619
|
+
//#endregion
|
|
5620
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/deps/dicer/lib/PartStream.js
|
|
5621
|
+
var require_PartStream = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5622
|
+
const inherits$4 = __require("node:util").inherits;
|
|
5623
|
+
const ReadableStream = __require("node:stream").Readable;
|
|
5624
|
+
function PartStream(opts) {
|
|
5625
|
+
ReadableStream.call(this, opts);
|
|
5626
|
+
}
|
|
5627
|
+
inherits$4(PartStream, ReadableStream);
|
|
5628
|
+
PartStream.prototype._read = function(n) {};
|
|
5629
|
+
module.exports = PartStream;
|
|
5630
|
+
}));
|
|
5631
|
+
//#endregion
|
|
5632
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/getLimit.js
|
|
5633
|
+
var require_getLimit = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5634
|
+
module.exports = function getLimit(limits, name, defaultLimit) {
|
|
5635
|
+
if (!limits || limits[name] === void 0 || limits[name] === null) return defaultLimit;
|
|
5636
|
+
if (typeof limits[name] !== "number" || isNaN(limits[name])) throw new TypeError("Limit " + name + " is not a valid number");
|
|
5637
|
+
return limits[name];
|
|
5638
|
+
};
|
|
5639
|
+
}));
|
|
5640
|
+
//#endregion
|
|
5641
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/deps/dicer/lib/HeaderParser.js
|
|
5642
|
+
var require_HeaderParser = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5643
|
+
const EventEmitter$1 = __require("node:events").EventEmitter;
|
|
5644
|
+
const inherits$3 = __require("node:util").inherits;
|
|
5645
|
+
const getLimit = require_getLimit();
|
|
5646
|
+
const StreamSearch = require_sbmh();
|
|
5647
|
+
const B_DCRLF = Buffer.from("\r\n\r\n");
|
|
5648
|
+
const RE_CRLF = /\r\n/g;
|
|
5649
|
+
const RE_HDR = /^([^:]+):[ \t]?([\x00-\xFF]+)?$/;
|
|
5650
|
+
function HeaderParser(cfg) {
|
|
5651
|
+
EventEmitter$1.call(this);
|
|
5652
|
+
cfg = cfg || {};
|
|
5653
|
+
const self = this;
|
|
5654
|
+
this.nread = 0;
|
|
5655
|
+
this.maxed = false;
|
|
5656
|
+
this.npairs = 0;
|
|
5657
|
+
this.maxHeaderPairs = getLimit(cfg, "maxHeaderPairs", 2e3);
|
|
5658
|
+
this.maxHeaderSize = getLimit(cfg, "maxHeaderSize", 80 * 1024);
|
|
5659
|
+
this.buffer = "";
|
|
5660
|
+
this.header = {};
|
|
5661
|
+
this.finished = false;
|
|
5662
|
+
this.ss = new StreamSearch(B_DCRLF);
|
|
5663
|
+
this.ss.on("info", function(isMatch, data, start, end) {
|
|
5664
|
+
if (data && !self.maxed) {
|
|
5665
|
+
if (self.nread + end - start >= self.maxHeaderSize) {
|
|
5666
|
+
end = self.maxHeaderSize - self.nread + start;
|
|
5667
|
+
self.nread = self.maxHeaderSize;
|
|
5668
|
+
self.maxed = true;
|
|
5669
|
+
} else self.nread += end - start;
|
|
5670
|
+
self.buffer += data.toString("binary", start, end);
|
|
5671
|
+
}
|
|
5672
|
+
if (isMatch) self._finish();
|
|
5673
|
+
});
|
|
5674
|
+
}
|
|
5675
|
+
inherits$3(HeaderParser, EventEmitter$1);
|
|
5676
|
+
HeaderParser.prototype.push = function(data) {
|
|
5677
|
+
const r = this.ss.push(data);
|
|
5678
|
+
if (this.finished) return r;
|
|
5679
|
+
};
|
|
5680
|
+
HeaderParser.prototype.reset = function() {
|
|
5681
|
+
this.finished = false;
|
|
5682
|
+
this.buffer = "";
|
|
5683
|
+
this.header = {};
|
|
5684
|
+
this.ss.reset();
|
|
5685
|
+
};
|
|
5686
|
+
HeaderParser.prototype._finish = function() {
|
|
5687
|
+
if (this.buffer) this._parseHeader();
|
|
5688
|
+
this.ss.matches = this.ss.maxMatches;
|
|
5689
|
+
const header = this.header;
|
|
5690
|
+
this.header = {};
|
|
5691
|
+
this.buffer = "";
|
|
5692
|
+
this.finished = true;
|
|
5693
|
+
this.nread = this.npairs = 0;
|
|
5694
|
+
this.maxed = false;
|
|
5695
|
+
this.emit("header", header);
|
|
5696
|
+
};
|
|
5697
|
+
HeaderParser.prototype._parseHeader = function() {
|
|
5698
|
+
if (this.npairs === this.maxHeaderPairs) return;
|
|
5699
|
+
const lines = this.buffer.split(RE_CRLF);
|
|
5700
|
+
const len = lines.length;
|
|
5701
|
+
let m, h;
|
|
5702
|
+
for (var i = 0; i < len; ++i) {
|
|
5703
|
+
if (lines[i].length === 0) continue;
|
|
5704
|
+
if (lines[i][0] === " " || lines[i][0] === " ") {
|
|
5705
|
+
if (h) {
|
|
5706
|
+
this.header[h][this.header[h].length - 1] += lines[i];
|
|
5707
|
+
continue;
|
|
5708
|
+
}
|
|
5709
|
+
}
|
|
5710
|
+
const posColon = lines[i].indexOf(":");
|
|
5711
|
+
if (posColon === -1 || posColon === 0) return;
|
|
5712
|
+
m = RE_HDR.exec(lines[i]);
|
|
5713
|
+
h = m[1].toLowerCase();
|
|
5714
|
+
this.header[h] = this.header[h] || [];
|
|
5715
|
+
this.header[h].push(m[2] || "");
|
|
5716
|
+
if (++this.npairs === this.maxHeaderPairs) break;
|
|
5717
|
+
}
|
|
5718
|
+
};
|
|
5719
|
+
module.exports = HeaderParser;
|
|
5720
|
+
}));
|
|
5721
|
+
//#endregion
|
|
5722
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/deps/dicer/lib/Dicer.js
|
|
5723
|
+
var require_Dicer = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5724
|
+
const WritableStream$1 = __require("node:stream").Writable;
|
|
5725
|
+
const inherits$2 = __require("node:util").inherits;
|
|
5726
|
+
const StreamSearch = require_sbmh();
|
|
5727
|
+
const PartStream = require_PartStream();
|
|
5728
|
+
const HeaderParser = require_HeaderParser();
|
|
5729
|
+
const DASH = 45;
|
|
5730
|
+
const B_ONEDASH = Buffer.from("-");
|
|
5731
|
+
const B_CRLF = Buffer.from("\r\n");
|
|
5732
|
+
const EMPTY_FN = function() {};
|
|
5733
|
+
function Dicer(cfg) {
|
|
5734
|
+
if (!(this instanceof Dicer)) return new Dicer(cfg);
|
|
5735
|
+
WritableStream$1.call(this, cfg);
|
|
5736
|
+
if (!cfg || !cfg.headerFirst && typeof cfg.boundary !== "string") throw new TypeError("Boundary required");
|
|
5737
|
+
if (typeof cfg.boundary === "string") this.setBoundary(cfg.boundary);
|
|
5738
|
+
else this._bparser = void 0;
|
|
5739
|
+
this._headerFirst = cfg.headerFirst;
|
|
5740
|
+
this._dashes = 0;
|
|
5741
|
+
this._parts = 0;
|
|
5742
|
+
this._finished = false;
|
|
5743
|
+
this._realFinish = false;
|
|
5744
|
+
this._isPreamble = true;
|
|
5745
|
+
this._justMatched = false;
|
|
5746
|
+
this._firstWrite = true;
|
|
5747
|
+
this._inHeader = true;
|
|
5748
|
+
this._part = void 0;
|
|
5749
|
+
this._cb = void 0;
|
|
5750
|
+
this._ignoreData = false;
|
|
5751
|
+
this._partOpts = { highWaterMark: cfg.partHwm };
|
|
5752
|
+
this._pause = false;
|
|
5753
|
+
const self = this;
|
|
5754
|
+
this._hparser = new HeaderParser(cfg);
|
|
5755
|
+
this._hparser.on("header", function(header) {
|
|
5756
|
+
self._inHeader = false;
|
|
5757
|
+
self._part.emit("header", header);
|
|
5758
|
+
});
|
|
5759
|
+
}
|
|
5760
|
+
inherits$2(Dicer, WritableStream$1);
|
|
5761
|
+
Dicer.prototype.emit = function(ev) {
|
|
5762
|
+
if (ev === "finish" && !this._realFinish) {
|
|
5763
|
+
if (!this._finished) {
|
|
5764
|
+
const self = this;
|
|
5765
|
+
process.nextTick(function() {
|
|
5766
|
+
self.emit("error", /* @__PURE__ */ new Error("Unexpected end of multipart data"));
|
|
5767
|
+
if (self._part && !self._ignoreData) {
|
|
5768
|
+
const type = self._isPreamble ? "Preamble" : "Part";
|
|
5769
|
+
self._part.emit("error", /* @__PURE__ */ new Error(type + " terminated early due to unexpected end of multipart data"));
|
|
5770
|
+
self._part.push(null);
|
|
5771
|
+
process.nextTick(function() {
|
|
5772
|
+
self._realFinish = true;
|
|
5773
|
+
self.emit("finish");
|
|
5774
|
+
self._realFinish = false;
|
|
5775
|
+
});
|
|
5776
|
+
return;
|
|
5777
|
+
}
|
|
5778
|
+
self._realFinish = true;
|
|
5779
|
+
self.emit("finish");
|
|
5780
|
+
self._realFinish = false;
|
|
5781
|
+
});
|
|
5782
|
+
}
|
|
5783
|
+
} else WritableStream$1.prototype.emit.apply(this, arguments);
|
|
5784
|
+
};
|
|
5785
|
+
Dicer.prototype._write = function(data, encoding, cb) {
|
|
5786
|
+
if (!this._hparser && !this._bparser) return cb();
|
|
5787
|
+
if (this._headerFirst && this._isPreamble) {
|
|
5788
|
+
if (!this._part) {
|
|
5789
|
+
this._part = new PartStream(this._partOpts);
|
|
5790
|
+
if (this.listenerCount("preamble") !== 0) this.emit("preamble", this._part);
|
|
5791
|
+
else this._ignore();
|
|
5792
|
+
}
|
|
5793
|
+
const r = this._hparser.push(data);
|
|
5794
|
+
if (!this._inHeader && r !== void 0 && r < data.length) data = data.slice(r);
|
|
5795
|
+
else return cb();
|
|
5796
|
+
}
|
|
5797
|
+
if (this._firstWrite) {
|
|
5798
|
+
this._bparser.push(B_CRLF);
|
|
5799
|
+
this._firstWrite = false;
|
|
5800
|
+
}
|
|
5801
|
+
this._bparser.push(data);
|
|
5802
|
+
if (this._pause) this._cb = cb;
|
|
5803
|
+
else cb();
|
|
5804
|
+
};
|
|
5805
|
+
Dicer.prototype.reset = function() {
|
|
5806
|
+
this._part = void 0;
|
|
5807
|
+
this._bparser = void 0;
|
|
5808
|
+
this._hparser = void 0;
|
|
5809
|
+
};
|
|
5810
|
+
Dicer.prototype.setBoundary = function(boundary) {
|
|
5811
|
+
const self = this;
|
|
5812
|
+
this._bparser = new StreamSearch("\r\n--" + boundary);
|
|
5813
|
+
this._bparser.on("info", function(isMatch, data, start, end) {
|
|
5814
|
+
self._oninfo(isMatch, data, start, end);
|
|
5815
|
+
});
|
|
5816
|
+
};
|
|
5817
|
+
Dicer.prototype._ignore = function() {
|
|
5818
|
+
if (this._part && !this._ignoreData) {
|
|
5819
|
+
this._ignoreData = true;
|
|
5820
|
+
this._part.on("error", EMPTY_FN);
|
|
5821
|
+
this._part.resume();
|
|
5822
|
+
}
|
|
5823
|
+
};
|
|
5824
|
+
Dicer.prototype._oninfo = function(isMatch, data, start, end) {
|
|
5825
|
+
let buf;
|
|
5826
|
+
const self = this;
|
|
5827
|
+
let i = 0;
|
|
5828
|
+
let r;
|
|
5829
|
+
let shouldWriteMore = true;
|
|
5830
|
+
if (!this._part && this._justMatched && data) {
|
|
5831
|
+
while (this._dashes < 2 && start + i < end) if (data[start + i] === DASH) {
|
|
5832
|
+
++i;
|
|
5833
|
+
++this._dashes;
|
|
5834
|
+
} else {
|
|
5835
|
+
if (this._dashes) buf = B_ONEDASH;
|
|
5836
|
+
this._dashes = 0;
|
|
5837
|
+
break;
|
|
5838
|
+
}
|
|
5839
|
+
if (this._dashes === 2) {
|
|
5840
|
+
if (start + i < end && this.listenerCount("trailer") !== 0) this.emit("trailer", data.slice(start + i, end));
|
|
5841
|
+
this.reset();
|
|
5842
|
+
this._finished = true;
|
|
5843
|
+
if (self._parts === 0) {
|
|
5844
|
+
self._realFinish = true;
|
|
5845
|
+
self.emit("finish");
|
|
5846
|
+
self._realFinish = false;
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
if (this._dashes) return;
|
|
5850
|
+
}
|
|
5851
|
+
if (this._justMatched) this._justMatched = false;
|
|
5852
|
+
if (!this._part) {
|
|
5853
|
+
this._part = new PartStream(this._partOpts);
|
|
5854
|
+
this._part._read = function(n) {
|
|
5855
|
+
self._unpause();
|
|
5856
|
+
};
|
|
5857
|
+
if (this._isPreamble && this.listenerCount("preamble") !== 0) this.emit("preamble", this._part);
|
|
5858
|
+
else if (this._isPreamble !== true && this.listenerCount("part") !== 0) this.emit("part", this._part);
|
|
5859
|
+
else this._ignore();
|
|
5860
|
+
if (!this._isPreamble) this._inHeader = true;
|
|
5861
|
+
}
|
|
5862
|
+
if (data && start < end && !this._ignoreData) {
|
|
5863
|
+
if (this._isPreamble || !this._inHeader) {
|
|
5864
|
+
if (buf) shouldWriteMore = this._part.push(buf);
|
|
5865
|
+
shouldWriteMore = this._part.push(data.slice(start, end));
|
|
5866
|
+
if (!shouldWriteMore) this._pause = true;
|
|
5867
|
+
} else if (!this._isPreamble && this._inHeader) {
|
|
5868
|
+
if (buf) this._hparser.push(buf);
|
|
5869
|
+
r = this._hparser.push(data.slice(start, end));
|
|
5870
|
+
if (!this._inHeader && r !== void 0 && r < end) this._oninfo(false, data, start + r, end);
|
|
5871
|
+
}
|
|
5872
|
+
}
|
|
5873
|
+
if (isMatch) {
|
|
5874
|
+
this._hparser.reset();
|
|
5875
|
+
if (this._isPreamble) this._isPreamble = false;
|
|
5876
|
+
else if (start !== end) {
|
|
5877
|
+
++this._parts;
|
|
5878
|
+
this._part.on("end", function() {
|
|
5879
|
+
if (--self._parts === 0) if (self._finished) {
|
|
5880
|
+
self._realFinish = true;
|
|
5881
|
+
self.emit("finish");
|
|
5882
|
+
self._realFinish = false;
|
|
5883
|
+
} else self._unpause();
|
|
5884
|
+
});
|
|
5885
|
+
}
|
|
5886
|
+
this._part.push(null);
|
|
5887
|
+
this._part = void 0;
|
|
5888
|
+
this._ignoreData = false;
|
|
5889
|
+
this._justMatched = true;
|
|
5890
|
+
this._dashes = 0;
|
|
5891
|
+
}
|
|
5892
|
+
};
|
|
5893
|
+
Dicer.prototype._unpause = function() {
|
|
5894
|
+
if (!this._pause) return;
|
|
5895
|
+
this._pause = false;
|
|
5896
|
+
if (this._cb) {
|
|
5897
|
+
const cb = this._cb;
|
|
5898
|
+
this._cb = void 0;
|
|
5899
|
+
cb();
|
|
5900
|
+
}
|
|
5901
|
+
};
|
|
5902
|
+
module.exports = Dicer;
|
|
5903
|
+
}));
|
|
5904
|
+
//#endregion
|
|
5905
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/decodeText.js
|
|
5906
|
+
var require_decodeText = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5907
|
+
const utf8Decoder = new TextDecoder("utf-8");
|
|
5908
|
+
const textDecoders = new Map([["utf-8", utf8Decoder], ["utf8", utf8Decoder]]);
|
|
5909
|
+
function getDecoder(charset) {
|
|
5910
|
+
let lc;
|
|
5911
|
+
while (true) switch (charset) {
|
|
5912
|
+
case "utf-8":
|
|
5913
|
+
case "utf8": return decoders.utf8;
|
|
5914
|
+
case "latin1":
|
|
5915
|
+
case "ascii":
|
|
5916
|
+
case "us-ascii":
|
|
5917
|
+
case "iso-8859-1":
|
|
5918
|
+
case "iso8859-1":
|
|
5919
|
+
case "iso88591":
|
|
5920
|
+
case "iso_8859-1":
|
|
5921
|
+
case "windows-1252":
|
|
5922
|
+
case "iso_8859-1:1987":
|
|
5923
|
+
case "cp1252":
|
|
5924
|
+
case "x-cp1252": return decoders.latin1;
|
|
5925
|
+
case "utf16le":
|
|
5926
|
+
case "utf-16le":
|
|
5927
|
+
case "ucs2":
|
|
5928
|
+
case "ucs-2": return decoders.utf16le;
|
|
5929
|
+
case "base64": return decoders.base64;
|
|
5930
|
+
default:
|
|
5931
|
+
if (lc === void 0) {
|
|
5932
|
+
lc = true;
|
|
5933
|
+
charset = charset.toLowerCase();
|
|
5934
|
+
continue;
|
|
5935
|
+
}
|
|
5936
|
+
return decoders.other.bind(charset);
|
|
5937
|
+
}
|
|
5938
|
+
}
|
|
5939
|
+
const decoders = {
|
|
5940
|
+
utf8: (data, sourceEncoding) => {
|
|
5941
|
+
if (data.length === 0) return "";
|
|
5942
|
+
if (typeof data === "string") data = Buffer.from(data, sourceEncoding);
|
|
5943
|
+
return data.utf8Slice(0, data.length);
|
|
5944
|
+
},
|
|
5945
|
+
latin1: (data, sourceEncoding) => {
|
|
5946
|
+
if (data.length === 0) return "";
|
|
5947
|
+
if (typeof data === "string") return data;
|
|
5948
|
+
return data.latin1Slice(0, data.length);
|
|
5949
|
+
},
|
|
5950
|
+
utf16le: (data, sourceEncoding) => {
|
|
5951
|
+
if (data.length === 0) return "";
|
|
5952
|
+
if (typeof data === "string") data = Buffer.from(data, sourceEncoding);
|
|
5953
|
+
return data.ucs2Slice(0, data.length);
|
|
5954
|
+
},
|
|
5955
|
+
base64: (data, sourceEncoding) => {
|
|
5956
|
+
if (data.length === 0) return "";
|
|
5957
|
+
if (typeof data === "string") data = Buffer.from(data, sourceEncoding);
|
|
5958
|
+
return data.base64Slice(0, data.length);
|
|
5959
|
+
},
|
|
5960
|
+
other: (data, sourceEncoding) => {
|
|
5961
|
+
if (data.length === 0) return "";
|
|
5962
|
+
if (typeof data === "string") data = Buffer.from(data, sourceEncoding);
|
|
5963
|
+
if (textDecoders.has(exports.toString())) try {
|
|
5964
|
+
return textDecoders.get(exports).decode(data);
|
|
5965
|
+
} catch {}
|
|
5966
|
+
return typeof data === "string" ? data : data.toString();
|
|
5967
|
+
}
|
|
5968
|
+
};
|
|
5969
|
+
function decodeText(text, sourceEncoding, destEncoding) {
|
|
5970
|
+
if (text) return getDecoder(destEncoding)(text, sourceEncoding);
|
|
5971
|
+
return text;
|
|
5972
|
+
}
|
|
5973
|
+
module.exports = decodeText;
|
|
5974
|
+
}));
|
|
5975
|
+
//#endregion
|
|
5976
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/parseParams.js
|
|
5977
|
+
var require_parseParams = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
5978
|
+
const decodeText = require_decodeText();
|
|
5979
|
+
const RE_ENCODED = /%[a-fA-F0-9][a-fA-F0-9]/g;
|
|
5980
|
+
const EncodedLookup = {
|
|
5981
|
+
"%00": "\0",
|
|
5982
|
+
"%01": "",
|
|
5983
|
+
"%02": "",
|
|
5984
|
+
"%03": "",
|
|
5985
|
+
"%04": "",
|
|
5986
|
+
"%05": "",
|
|
5987
|
+
"%06": "",
|
|
5988
|
+
"%07": "\x07",
|
|
5989
|
+
"%08": "\b",
|
|
5990
|
+
"%09": " ",
|
|
5991
|
+
"%0a": "\n",
|
|
5992
|
+
"%0A": "\n",
|
|
5993
|
+
"%0b": "\v",
|
|
5994
|
+
"%0B": "\v",
|
|
5995
|
+
"%0c": "\f",
|
|
5996
|
+
"%0C": "\f",
|
|
5997
|
+
"%0d": "\r",
|
|
5998
|
+
"%0D": "\r",
|
|
5999
|
+
"%0e": "",
|
|
6000
|
+
"%0E": "",
|
|
6001
|
+
"%0f": "",
|
|
6002
|
+
"%0F": "",
|
|
6003
|
+
"%10": "",
|
|
6004
|
+
"%11": "",
|
|
6005
|
+
"%12": "",
|
|
6006
|
+
"%13": "",
|
|
6007
|
+
"%14": "",
|
|
6008
|
+
"%15": "",
|
|
6009
|
+
"%16": "",
|
|
6010
|
+
"%17": "",
|
|
6011
|
+
"%18": "",
|
|
6012
|
+
"%19": "",
|
|
6013
|
+
"%1a": "",
|
|
6014
|
+
"%1A": "",
|
|
6015
|
+
"%1b": "\x1B",
|
|
6016
|
+
"%1B": "\x1B",
|
|
6017
|
+
"%1c": "",
|
|
6018
|
+
"%1C": "",
|
|
6019
|
+
"%1d": "",
|
|
6020
|
+
"%1D": "",
|
|
6021
|
+
"%1e": "",
|
|
6022
|
+
"%1E": "",
|
|
6023
|
+
"%1f": "",
|
|
6024
|
+
"%1F": "",
|
|
6025
|
+
"%20": " ",
|
|
6026
|
+
"%21": "!",
|
|
6027
|
+
"%22": "\"",
|
|
6028
|
+
"%23": "#",
|
|
6029
|
+
"%24": "$",
|
|
6030
|
+
"%25": "%",
|
|
6031
|
+
"%26": "&",
|
|
6032
|
+
"%27": "'",
|
|
6033
|
+
"%28": "(",
|
|
6034
|
+
"%29": ")",
|
|
6035
|
+
"%2a": "*",
|
|
6036
|
+
"%2A": "*",
|
|
6037
|
+
"%2b": "+",
|
|
6038
|
+
"%2B": "+",
|
|
6039
|
+
"%2c": ",",
|
|
6040
|
+
"%2C": ",",
|
|
6041
|
+
"%2d": "-",
|
|
6042
|
+
"%2D": "-",
|
|
6043
|
+
"%2e": ".",
|
|
6044
|
+
"%2E": ".",
|
|
6045
|
+
"%2f": "/",
|
|
6046
|
+
"%2F": "/",
|
|
6047
|
+
"%30": "0",
|
|
6048
|
+
"%31": "1",
|
|
6049
|
+
"%32": "2",
|
|
6050
|
+
"%33": "3",
|
|
6051
|
+
"%34": "4",
|
|
6052
|
+
"%35": "5",
|
|
6053
|
+
"%36": "6",
|
|
6054
|
+
"%37": "7",
|
|
6055
|
+
"%38": "8",
|
|
6056
|
+
"%39": "9",
|
|
6057
|
+
"%3a": ":",
|
|
6058
|
+
"%3A": ":",
|
|
6059
|
+
"%3b": ";",
|
|
6060
|
+
"%3B": ";",
|
|
6061
|
+
"%3c": "<",
|
|
6062
|
+
"%3C": "<",
|
|
6063
|
+
"%3d": "=",
|
|
6064
|
+
"%3D": "=",
|
|
6065
|
+
"%3e": ">",
|
|
6066
|
+
"%3E": ">",
|
|
6067
|
+
"%3f": "?",
|
|
6068
|
+
"%3F": "?",
|
|
6069
|
+
"%40": "@",
|
|
6070
|
+
"%41": "A",
|
|
6071
|
+
"%42": "B",
|
|
6072
|
+
"%43": "C",
|
|
6073
|
+
"%44": "D",
|
|
6074
|
+
"%45": "E",
|
|
6075
|
+
"%46": "F",
|
|
6076
|
+
"%47": "G",
|
|
6077
|
+
"%48": "H",
|
|
6078
|
+
"%49": "I",
|
|
6079
|
+
"%4a": "J",
|
|
6080
|
+
"%4A": "J",
|
|
6081
|
+
"%4b": "K",
|
|
6082
|
+
"%4B": "K",
|
|
6083
|
+
"%4c": "L",
|
|
6084
|
+
"%4C": "L",
|
|
6085
|
+
"%4d": "M",
|
|
6086
|
+
"%4D": "M",
|
|
6087
|
+
"%4e": "N",
|
|
6088
|
+
"%4E": "N",
|
|
6089
|
+
"%4f": "O",
|
|
6090
|
+
"%4F": "O",
|
|
6091
|
+
"%50": "P",
|
|
6092
|
+
"%51": "Q",
|
|
6093
|
+
"%52": "R",
|
|
6094
|
+
"%53": "S",
|
|
6095
|
+
"%54": "T",
|
|
6096
|
+
"%55": "U",
|
|
6097
|
+
"%56": "V",
|
|
6098
|
+
"%57": "W",
|
|
6099
|
+
"%58": "X",
|
|
6100
|
+
"%59": "Y",
|
|
6101
|
+
"%5a": "Z",
|
|
6102
|
+
"%5A": "Z",
|
|
6103
|
+
"%5b": "[",
|
|
6104
|
+
"%5B": "[",
|
|
6105
|
+
"%5c": "\\",
|
|
6106
|
+
"%5C": "\\",
|
|
6107
|
+
"%5d": "]",
|
|
6108
|
+
"%5D": "]",
|
|
6109
|
+
"%5e": "^",
|
|
6110
|
+
"%5E": "^",
|
|
6111
|
+
"%5f": "_",
|
|
6112
|
+
"%5F": "_",
|
|
6113
|
+
"%60": "`",
|
|
6114
|
+
"%61": "a",
|
|
6115
|
+
"%62": "b",
|
|
6116
|
+
"%63": "c",
|
|
6117
|
+
"%64": "d",
|
|
6118
|
+
"%65": "e",
|
|
6119
|
+
"%66": "f",
|
|
6120
|
+
"%67": "g",
|
|
6121
|
+
"%68": "h",
|
|
6122
|
+
"%69": "i",
|
|
6123
|
+
"%6a": "j",
|
|
6124
|
+
"%6A": "j",
|
|
6125
|
+
"%6b": "k",
|
|
6126
|
+
"%6B": "k",
|
|
6127
|
+
"%6c": "l",
|
|
6128
|
+
"%6C": "l",
|
|
6129
|
+
"%6d": "m",
|
|
6130
|
+
"%6D": "m",
|
|
6131
|
+
"%6e": "n",
|
|
6132
|
+
"%6E": "n",
|
|
6133
|
+
"%6f": "o",
|
|
6134
|
+
"%6F": "o",
|
|
6135
|
+
"%70": "p",
|
|
6136
|
+
"%71": "q",
|
|
6137
|
+
"%72": "r",
|
|
6138
|
+
"%73": "s",
|
|
6139
|
+
"%74": "t",
|
|
6140
|
+
"%75": "u",
|
|
6141
|
+
"%76": "v",
|
|
6142
|
+
"%77": "w",
|
|
6143
|
+
"%78": "x",
|
|
6144
|
+
"%79": "y",
|
|
6145
|
+
"%7a": "z",
|
|
6146
|
+
"%7A": "z",
|
|
6147
|
+
"%7b": "{",
|
|
6148
|
+
"%7B": "{",
|
|
6149
|
+
"%7c": "|",
|
|
6150
|
+
"%7C": "|",
|
|
6151
|
+
"%7d": "}",
|
|
6152
|
+
"%7D": "}",
|
|
6153
|
+
"%7e": "~",
|
|
6154
|
+
"%7E": "~",
|
|
6155
|
+
"%7f": "",
|
|
6156
|
+
"%7F": "",
|
|
6157
|
+
"%80": "",
|
|
6158
|
+
"%81": "",
|
|
6159
|
+
"%82": "",
|
|
6160
|
+
"%83": "",
|
|
6161
|
+
"%84": "",
|
|
6162
|
+
"%85": "
",
|
|
6163
|
+
"%86": "",
|
|
6164
|
+
"%87": "",
|
|
6165
|
+
"%88": "",
|
|
6166
|
+
"%89": "",
|
|
6167
|
+
"%8a": "",
|
|
6168
|
+
"%8A": "",
|
|
6169
|
+
"%8b": "",
|
|
6170
|
+
"%8B": "",
|
|
6171
|
+
"%8c": "",
|
|
6172
|
+
"%8C": "",
|
|
6173
|
+
"%8d": "",
|
|
6174
|
+
"%8D": "",
|
|
6175
|
+
"%8e": "",
|
|
6176
|
+
"%8E": "",
|
|
6177
|
+
"%8f": "",
|
|
6178
|
+
"%8F": "",
|
|
6179
|
+
"%90": "",
|
|
6180
|
+
"%91": "",
|
|
6181
|
+
"%92": "",
|
|
6182
|
+
"%93": "",
|
|
6183
|
+
"%94": "",
|
|
6184
|
+
"%95": "",
|
|
6185
|
+
"%96": "",
|
|
6186
|
+
"%97": "",
|
|
6187
|
+
"%98": "",
|
|
6188
|
+
"%99": "",
|
|
6189
|
+
"%9a": "",
|
|
6190
|
+
"%9A": "",
|
|
6191
|
+
"%9b": "",
|
|
6192
|
+
"%9B": "",
|
|
6193
|
+
"%9c": "",
|
|
6194
|
+
"%9C": "",
|
|
6195
|
+
"%9d": "",
|
|
6196
|
+
"%9D": "",
|
|
6197
|
+
"%9e": "",
|
|
6198
|
+
"%9E": "",
|
|
6199
|
+
"%9f": "",
|
|
6200
|
+
"%9F": "",
|
|
6201
|
+
"%a0": "\xA0",
|
|
6202
|
+
"%A0": "\xA0",
|
|
6203
|
+
"%a1": "¡",
|
|
6204
|
+
"%A1": "¡",
|
|
6205
|
+
"%a2": "¢",
|
|
6206
|
+
"%A2": "¢",
|
|
6207
|
+
"%a3": "£",
|
|
6208
|
+
"%A3": "£",
|
|
6209
|
+
"%a4": "¤",
|
|
6210
|
+
"%A4": "¤",
|
|
6211
|
+
"%a5": "¥",
|
|
6212
|
+
"%A5": "¥",
|
|
6213
|
+
"%a6": "¦",
|
|
6214
|
+
"%A6": "¦",
|
|
6215
|
+
"%a7": "§",
|
|
6216
|
+
"%A7": "§",
|
|
6217
|
+
"%a8": "¨",
|
|
6218
|
+
"%A8": "¨",
|
|
6219
|
+
"%a9": "©",
|
|
6220
|
+
"%A9": "©",
|
|
6221
|
+
"%aa": "ª",
|
|
6222
|
+
"%Aa": "ª",
|
|
6223
|
+
"%aA": "ª",
|
|
6224
|
+
"%AA": "ª",
|
|
6225
|
+
"%ab": "«",
|
|
6226
|
+
"%Ab": "«",
|
|
6227
|
+
"%aB": "«",
|
|
6228
|
+
"%AB": "«",
|
|
6229
|
+
"%ac": "¬",
|
|
6230
|
+
"%Ac": "¬",
|
|
6231
|
+
"%aC": "¬",
|
|
6232
|
+
"%AC": "¬",
|
|
6233
|
+
"%ad": "",
|
|
6234
|
+
"%Ad": "",
|
|
6235
|
+
"%aD": "",
|
|
6236
|
+
"%AD": "",
|
|
6237
|
+
"%ae": "®",
|
|
6238
|
+
"%Ae": "®",
|
|
6239
|
+
"%aE": "®",
|
|
6240
|
+
"%AE": "®",
|
|
6241
|
+
"%af": "¯",
|
|
6242
|
+
"%Af": "¯",
|
|
6243
|
+
"%aF": "¯",
|
|
6244
|
+
"%AF": "¯",
|
|
6245
|
+
"%b0": "°",
|
|
6246
|
+
"%B0": "°",
|
|
6247
|
+
"%b1": "±",
|
|
6248
|
+
"%B1": "±",
|
|
6249
|
+
"%b2": "²",
|
|
6250
|
+
"%B2": "²",
|
|
6251
|
+
"%b3": "³",
|
|
6252
|
+
"%B3": "³",
|
|
6253
|
+
"%b4": "´",
|
|
6254
|
+
"%B4": "´",
|
|
6255
|
+
"%b5": "µ",
|
|
6256
|
+
"%B5": "µ",
|
|
6257
|
+
"%b6": "¶",
|
|
6258
|
+
"%B6": "¶",
|
|
6259
|
+
"%b7": "·",
|
|
6260
|
+
"%B7": "·",
|
|
6261
|
+
"%b8": "¸",
|
|
6262
|
+
"%B8": "¸",
|
|
6263
|
+
"%b9": "¹",
|
|
6264
|
+
"%B9": "¹",
|
|
6265
|
+
"%ba": "º",
|
|
6266
|
+
"%Ba": "º",
|
|
6267
|
+
"%bA": "º",
|
|
6268
|
+
"%BA": "º",
|
|
6269
|
+
"%bb": "»",
|
|
6270
|
+
"%Bb": "»",
|
|
6271
|
+
"%bB": "»",
|
|
6272
|
+
"%BB": "»",
|
|
6273
|
+
"%bc": "¼",
|
|
6274
|
+
"%Bc": "¼",
|
|
6275
|
+
"%bC": "¼",
|
|
6276
|
+
"%BC": "¼",
|
|
6277
|
+
"%bd": "½",
|
|
6278
|
+
"%Bd": "½",
|
|
6279
|
+
"%bD": "½",
|
|
6280
|
+
"%BD": "½",
|
|
6281
|
+
"%be": "¾",
|
|
6282
|
+
"%Be": "¾",
|
|
6283
|
+
"%bE": "¾",
|
|
6284
|
+
"%BE": "¾",
|
|
6285
|
+
"%bf": "¿",
|
|
6286
|
+
"%Bf": "¿",
|
|
6287
|
+
"%bF": "¿",
|
|
6288
|
+
"%BF": "¿",
|
|
6289
|
+
"%c0": "À",
|
|
6290
|
+
"%C0": "À",
|
|
6291
|
+
"%c1": "Á",
|
|
6292
|
+
"%C1": "Á",
|
|
6293
|
+
"%c2": "Â",
|
|
6294
|
+
"%C2": "Â",
|
|
6295
|
+
"%c3": "Ã",
|
|
6296
|
+
"%C3": "Ã",
|
|
6297
|
+
"%c4": "Ä",
|
|
6298
|
+
"%C4": "Ä",
|
|
6299
|
+
"%c5": "Å",
|
|
6300
|
+
"%C5": "Å",
|
|
6301
|
+
"%c6": "Æ",
|
|
6302
|
+
"%C6": "Æ",
|
|
6303
|
+
"%c7": "Ç",
|
|
6304
|
+
"%C7": "Ç",
|
|
6305
|
+
"%c8": "È",
|
|
6306
|
+
"%C8": "È",
|
|
6307
|
+
"%c9": "É",
|
|
6308
|
+
"%C9": "É",
|
|
6309
|
+
"%ca": "Ê",
|
|
6310
|
+
"%Ca": "Ê",
|
|
6311
|
+
"%cA": "Ê",
|
|
6312
|
+
"%CA": "Ê",
|
|
6313
|
+
"%cb": "Ë",
|
|
6314
|
+
"%Cb": "Ë",
|
|
6315
|
+
"%cB": "Ë",
|
|
6316
|
+
"%CB": "Ë",
|
|
6317
|
+
"%cc": "Ì",
|
|
6318
|
+
"%Cc": "Ì",
|
|
6319
|
+
"%cC": "Ì",
|
|
6320
|
+
"%CC": "Ì",
|
|
6321
|
+
"%cd": "Í",
|
|
6322
|
+
"%Cd": "Í",
|
|
6323
|
+
"%cD": "Í",
|
|
6324
|
+
"%CD": "Í",
|
|
6325
|
+
"%ce": "Î",
|
|
6326
|
+
"%Ce": "Î",
|
|
6327
|
+
"%cE": "Î",
|
|
6328
|
+
"%CE": "Î",
|
|
6329
|
+
"%cf": "Ï",
|
|
6330
|
+
"%Cf": "Ï",
|
|
6331
|
+
"%cF": "Ï",
|
|
6332
|
+
"%CF": "Ï",
|
|
6333
|
+
"%d0": "Ð",
|
|
6334
|
+
"%D0": "Ð",
|
|
6335
|
+
"%d1": "Ñ",
|
|
6336
|
+
"%D1": "Ñ",
|
|
6337
|
+
"%d2": "Ò",
|
|
6338
|
+
"%D2": "Ò",
|
|
6339
|
+
"%d3": "Ó",
|
|
6340
|
+
"%D3": "Ó",
|
|
6341
|
+
"%d4": "Ô",
|
|
6342
|
+
"%D4": "Ô",
|
|
6343
|
+
"%d5": "Õ",
|
|
6344
|
+
"%D5": "Õ",
|
|
6345
|
+
"%d6": "Ö",
|
|
6346
|
+
"%D6": "Ö",
|
|
6347
|
+
"%d7": "×",
|
|
6348
|
+
"%D7": "×",
|
|
6349
|
+
"%d8": "Ø",
|
|
6350
|
+
"%D8": "Ø",
|
|
6351
|
+
"%d9": "Ù",
|
|
6352
|
+
"%D9": "Ù",
|
|
6353
|
+
"%da": "Ú",
|
|
6354
|
+
"%Da": "Ú",
|
|
6355
|
+
"%dA": "Ú",
|
|
6356
|
+
"%DA": "Ú",
|
|
6357
|
+
"%db": "Û",
|
|
6358
|
+
"%Db": "Û",
|
|
6359
|
+
"%dB": "Û",
|
|
6360
|
+
"%DB": "Û",
|
|
6361
|
+
"%dc": "Ü",
|
|
6362
|
+
"%Dc": "Ü",
|
|
6363
|
+
"%dC": "Ü",
|
|
6364
|
+
"%DC": "Ü",
|
|
6365
|
+
"%dd": "Ý",
|
|
6366
|
+
"%Dd": "Ý",
|
|
6367
|
+
"%dD": "Ý",
|
|
6368
|
+
"%DD": "Ý",
|
|
6369
|
+
"%de": "Þ",
|
|
6370
|
+
"%De": "Þ",
|
|
6371
|
+
"%dE": "Þ",
|
|
6372
|
+
"%DE": "Þ",
|
|
6373
|
+
"%df": "ß",
|
|
6374
|
+
"%Df": "ß",
|
|
6375
|
+
"%dF": "ß",
|
|
6376
|
+
"%DF": "ß",
|
|
6377
|
+
"%e0": "à",
|
|
6378
|
+
"%E0": "à",
|
|
6379
|
+
"%e1": "á",
|
|
6380
|
+
"%E1": "á",
|
|
6381
|
+
"%e2": "â",
|
|
6382
|
+
"%E2": "â",
|
|
6383
|
+
"%e3": "ã",
|
|
6384
|
+
"%E3": "ã",
|
|
6385
|
+
"%e4": "ä",
|
|
6386
|
+
"%E4": "ä",
|
|
6387
|
+
"%e5": "å",
|
|
6388
|
+
"%E5": "å",
|
|
6389
|
+
"%e6": "æ",
|
|
6390
|
+
"%E6": "æ",
|
|
6391
|
+
"%e7": "ç",
|
|
6392
|
+
"%E7": "ç",
|
|
6393
|
+
"%e8": "è",
|
|
6394
|
+
"%E8": "è",
|
|
6395
|
+
"%e9": "é",
|
|
6396
|
+
"%E9": "é",
|
|
6397
|
+
"%ea": "ê",
|
|
6398
|
+
"%Ea": "ê",
|
|
6399
|
+
"%eA": "ê",
|
|
6400
|
+
"%EA": "ê",
|
|
6401
|
+
"%eb": "ë",
|
|
6402
|
+
"%Eb": "ë",
|
|
6403
|
+
"%eB": "ë",
|
|
6404
|
+
"%EB": "ë",
|
|
6405
|
+
"%ec": "ì",
|
|
6406
|
+
"%Ec": "ì",
|
|
6407
|
+
"%eC": "ì",
|
|
6408
|
+
"%EC": "ì",
|
|
6409
|
+
"%ed": "í",
|
|
6410
|
+
"%Ed": "í",
|
|
6411
|
+
"%eD": "í",
|
|
6412
|
+
"%ED": "í",
|
|
6413
|
+
"%ee": "î",
|
|
6414
|
+
"%Ee": "î",
|
|
6415
|
+
"%eE": "î",
|
|
6416
|
+
"%EE": "î",
|
|
6417
|
+
"%ef": "ï",
|
|
6418
|
+
"%Ef": "ï",
|
|
6419
|
+
"%eF": "ï",
|
|
6420
|
+
"%EF": "ï",
|
|
6421
|
+
"%f0": "ð",
|
|
6422
|
+
"%F0": "ð",
|
|
6423
|
+
"%f1": "ñ",
|
|
6424
|
+
"%F1": "ñ",
|
|
6425
|
+
"%f2": "ò",
|
|
6426
|
+
"%F2": "ò",
|
|
6427
|
+
"%f3": "ó",
|
|
6428
|
+
"%F3": "ó",
|
|
6429
|
+
"%f4": "ô",
|
|
6430
|
+
"%F4": "ô",
|
|
6431
|
+
"%f5": "õ",
|
|
6432
|
+
"%F5": "õ",
|
|
6433
|
+
"%f6": "ö",
|
|
6434
|
+
"%F6": "ö",
|
|
6435
|
+
"%f7": "÷",
|
|
6436
|
+
"%F7": "÷",
|
|
6437
|
+
"%f8": "ø",
|
|
6438
|
+
"%F8": "ø",
|
|
6439
|
+
"%f9": "ù",
|
|
6440
|
+
"%F9": "ù",
|
|
6441
|
+
"%fa": "ú",
|
|
6442
|
+
"%Fa": "ú",
|
|
6443
|
+
"%fA": "ú",
|
|
6444
|
+
"%FA": "ú",
|
|
6445
|
+
"%fb": "û",
|
|
6446
|
+
"%Fb": "û",
|
|
6447
|
+
"%fB": "û",
|
|
6448
|
+
"%FB": "û",
|
|
6449
|
+
"%fc": "ü",
|
|
6450
|
+
"%Fc": "ü",
|
|
6451
|
+
"%fC": "ü",
|
|
6452
|
+
"%FC": "ü",
|
|
6453
|
+
"%fd": "ý",
|
|
6454
|
+
"%Fd": "ý",
|
|
6455
|
+
"%fD": "ý",
|
|
6456
|
+
"%FD": "ý",
|
|
6457
|
+
"%fe": "þ",
|
|
6458
|
+
"%Fe": "þ",
|
|
6459
|
+
"%fE": "þ",
|
|
6460
|
+
"%FE": "þ",
|
|
6461
|
+
"%ff": "ÿ",
|
|
6462
|
+
"%Ff": "ÿ",
|
|
6463
|
+
"%fF": "ÿ",
|
|
6464
|
+
"%FF": "ÿ"
|
|
6465
|
+
};
|
|
6466
|
+
function encodedReplacer(match) {
|
|
6467
|
+
return EncodedLookup[match];
|
|
6468
|
+
}
|
|
6469
|
+
const STATE_KEY = 0;
|
|
6470
|
+
const STATE_VALUE = 1;
|
|
6471
|
+
const STATE_CHARSET = 2;
|
|
6472
|
+
const STATE_LANG = 3;
|
|
6473
|
+
function parseParams(str) {
|
|
6474
|
+
const res = [];
|
|
6475
|
+
let state = STATE_KEY;
|
|
6476
|
+
let charset = "";
|
|
6477
|
+
let inquote = false;
|
|
6478
|
+
let escaping = false;
|
|
6479
|
+
let p = 0;
|
|
6480
|
+
let tmp = "";
|
|
6481
|
+
const len = str.length;
|
|
6482
|
+
for (var i = 0; i < len; ++i) {
|
|
6483
|
+
const char = str[i];
|
|
6484
|
+
if (char === "\\" && inquote) if (escaping) escaping = false;
|
|
6485
|
+
else {
|
|
6486
|
+
escaping = true;
|
|
6487
|
+
continue;
|
|
6488
|
+
}
|
|
6489
|
+
else if (char === "\"") if (!escaping) {
|
|
6490
|
+
if (inquote) {
|
|
6491
|
+
inquote = false;
|
|
6492
|
+
state = STATE_KEY;
|
|
6493
|
+
while (i + 1 < len && str[i + 1] !== ";") ++i;
|
|
6494
|
+
} else inquote = true;
|
|
6495
|
+
continue;
|
|
6496
|
+
} else escaping = false;
|
|
6497
|
+
else {
|
|
6498
|
+
if (escaping && inquote) tmp += "\\";
|
|
6499
|
+
escaping = false;
|
|
6500
|
+
if ((state === STATE_CHARSET || state === STATE_LANG) && char === "'") {
|
|
6501
|
+
if (state === STATE_CHARSET) {
|
|
6502
|
+
state = STATE_LANG;
|
|
6503
|
+
charset = tmp.substring(1);
|
|
6504
|
+
} else state = STATE_VALUE;
|
|
6505
|
+
tmp = "";
|
|
6506
|
+
continue;
|
|
6507
|
+
} else if (state === STATE_KEY && (char === "*" || char === "=") && res.length) {
|
|
6508
|
+
state = char === "*" ? STATE_CHARSET : STATE_VALUE;
|
|
6509
|
+
res[p] = [tmp, void 0];
|
|
6510
|
+
tmp = "";
|
|
6511
|
+
continue;
|
|
6512
|
+
} else if (!inquote && char === ";") {
|
|
6513
|
+
state = STATE_KEY;
|
|
6514
|
+
if (charset) {
|
|
6515
|
+
if (tmp.length) tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), "binary", charset);
|
|
6516
|
+
charset = "";
|
|
6517
|
+
} else if (tmp.length) tmp = decodeText(tmp, "binary", "utf8");
|
|
6518
|
+
if (res[p] === void 0) res[p] = tmp;
|
|
6519
|
+
else res[p][1] = tmp;
|
|
6520
|
+
tmp = "";
|
|
6521
|
+
++p;
|
|
6522
|
+
continue;
|
|
6523
|
+
} else if (!inquote && (char === " " || char === " ")) continue;
|
|
6524
|
+
}
|
|
6525
|
+
tmp += char;
|
|
6526
|
+
}
|
|
6527
|
+
if (charset && tmp.length) tmp = decodeText(tmp.replace(RE_ENCODED, encodedReplacer), "binary", charset);
|
|
6528
|
+
else if (tmp) tmp = decodeText(tmp, "binary", "utf8");
|
|
6529
|
+
if (res[p] === void 0) {
|
|
6530
|
+
if (tmp) res[p] = tmp;
|
|
6531
|
+
} else res[p][1] = tmp;
|
|
6532
|
+
return res;
|
|
6533
|
+
}
|
|
6534
|
+
module.exports = parseParams;
|
|
6535
|
+
}));
|
|
6536
|
+
//#endregion
|
|
6537
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/basename.js
|
|
6538
|
+
var require_basename = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6539
|
+
module.exports = function basename(path) {
|
|
6540
|
+
if (typeof path !== "string") return "";
|
|
6541
|
+
for (var i = path.length - 1; i >= 0; --i) switch (path.charCodeAt(i)) {
|
|
6542
|
+
case 47:
|
|
6543
|
+
case 92:
|
|
6544
|
+
path = path.slice(i + 1);
|
|
6545
|
+
return path === ".." || path === "." ? "" : path;
|
|
6546
|
+
}
|
|
6547
|
+
return path === ".." || path === "." ? "" : path;
|
|
6548
|
+
};
|
|
6549
|
+
}));
|
|
6550
|
+
//#endregion
|
|
6551
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/types/multipart.js
|
|
6552
|
+
var require_multipart$1 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6553
|
+
const { Readable: Readable$1 } = __require("node:stream");
|
|
6554
|
+
const { inherits: inherits$1 } = __require("node:util");
|
|
6555
|
+
const Dicer = require_Dicer();
|
|
6556
|
+
const parseParams = require_parseParams();
|
|
6557
|
+
const decodeText = require_decodeText();
|
|
6558
|
+
const basename = require_basename();
|
|
6559
|
+
const getLimit = require_getLimit();
|
|
6560
|
+
const RE_BOUNDARY = /^boundary$/i;
|
|
6561
|
+
const RE_FIELD = /^form-data$/i;
|
|
6562
|
+
const RE_CHARSET = /^charset$/i;
|
|
6563
|
+
const RE_FILENAME = /^filename$/i;
|
|
6564
|
+
const RE_NAME = /^name$/i;
|
|
6565
|
+
Multipart.detect = /^multipart\/form-data/i;
|
|
6566
|
+
function Multipart(boy, cfg) {
|
|
6567
|
+
let i;
|
|
6568
|
+
let len;
|
|
6569
|
+
const self = this;
|
|
6570
|
+
let boundary;
|
|
6571
|
+
const limits = cfg.limits;
|
|
6572
|
+
const isPartAFile = cfg.isPartAFile || ((fieldName, contentType, fileName) => contentType === "application/octet-stream" || fileName !== void 0);
|
|
6573
|
+
const parsedConType = cfg.parsedConType || [];
|
|
6574
|
+
const defCharset = cfg.defCharset || "utf8";
|
|
6575
|
+
const preservePath = cfg.preservePath;
|
|
6576
|
+
const fileOpts = { highWaterMark: cfg.fileHwm };
|
|
6577
|
+
for (i = 0, len = parsedConType.length; i < len; ++i) if (Array.isArray(parsedConType[i]) && RE_BOUNDARY.test(parsedConType[i][0])) {
|
|
6578
|
+
boundary = parsedConType[i][1];
|
|
6579
|
+
break;
|
|
6580
|
+
}
|
|
6581
|
+
function checkFinished() {
|
|
6582
|
+
if (nends === 0 && finished && !boy._done) {
|
|
6583
|
+
finished = false;
|
|
6584
|
+
self.end();
|
|
6585
|
+
}
|
|
6586
|
+
}
|
|
6587
|
+
if (typeof boundary !== "string") throw new Error("Multipart: Boundary not found");
|
|
6588
|
+
const fieldSizeLimit = getLimit(limits, "fieldSize", 1 * 1024 * 1024);
|
|
6589
|
+
const fileSizeLimit = getLimit(limits, "fileSize", Infinity);
|
|
6590
|
+
const filesLimit = getLimit(limits, "files", Infinity);
|
|
6591
|
+
const fieldsLimit = getLimit(limits, "fields", Infinity);
|
|
6592
|
+
const partsLimit = getLimit(limits, "parts", Infinity);
|
|
6593
|
+
const headerPairsLimit = getLimit(limits, "headerPairs", 2e3);
|
|
6594
|
+
const headerSizeLimit = getLimit(limits, "headerSize", 80 * 1024);
|
|
6595
|
+
let nfiles = 0;
|
|
6596
|
+
let nfields = 0;
|
|
6597
|
+
let nends = 0;
|
|
6598
|
+
let curFile;
|
|
6599
|
+
let curField;
|
|
6600
|
+
let finished = false;
|
|
6601
|
+
this._needDrain = false;
|
|
6602
|
+
this._pause = false;
|
|
6603
|
+
this._cb = void 0;
|
|
6604
|
+
this._nparts = 0;
|
|
6605
|
+
this._boy = boy;
|
|
6606
|
+
this.parser = new Dicer({
|
|
6607
|
+
boundary,
|
|
6608
|
+
maxHeaderPairs: headerPairsLimit,
|
|
6609
|
+
maxHeaderSize: headerSizeLimit,
|
|
6610
|
+
partHwm: fileOpts.highWaterMark,
|
|
6611
|
+
highWaterMark: cfg.highWaterMark
|
|
6612
|
+
});
|
|
6613
|
+
this.parser.on("drain", function() {
|
|
6614
|
+
self._needDrain = false;
|
|
6615
|
+
if (self._cb && !self._pause) {
|
|
6616
|
+
const cb = self._cb;
|
|
6617
|
+
self._cb = void 0;
|
|
6618
|
+
cb();
|
|
6619
|
+
}
|
|
6620
|
+
}).on("part", function onPart(part) {
|
|
6621
|
+
if (++self._nparts > partsLimit) {
|
|
6622
|
+
self.parser.removeListener("part", onPart);
|
|
6623
|
+
self.parser.on("part", skipPart);
|
|
6624
|
+
boy.hitPartsLimit = true;
|
|
6625
|
+
boy.emit("partsLimit");
|
|
6626
|
+
return skipPart(part);
|
|
6627
|
+
}
|
|
6628
|
+
if (curField) {
|
|
6629
|
+
const field = curField;
|
|
6630
|
+
field.emit("end");
|
|
6631
|
+
field.removeAllListeners("end");
|
|
6632
|
+
}
|
|
6633
|
+
part.on("header", function(header) {
|
|
6634
|
+
let contype;
|
|
6635
|
+
let fieldname;
|
|
6636
|
+
let parsed;
|
|
6637
|
+
let charset;
|
|
6638
|
+
let encoding;
|
|
6639
|
+
let filename;
|
|
6640
|
+
let nsize = 0;
|
|
6641
|
+
if (header["content-type"]) {
|
|
6642
|
+
parsed = parseParams(header["content-type"][0]);
|
|
6643
|
+
if (parsed[0]) {
|
|
6644
|
+
contype = parsed[0].toLowerCase();
|
|
6645
|
+
for (i = 0, len = parsed.length; i < len; ++i) if (RE_CHARSET.test(parsed[i][0])) {
|
|
6646
|
+
charset = parsed[i][1].toLowerCase();
|
|
6647
|
+
break;
|
|
6648
|
+
}
|
|
6649
|
+
}
|
|
6650
|
+
}
|
|
6651
|
+
if (contype === void 0) contype = "text/plain";
|
|
6652
|
+
if (charset === void 0) charset = defCharset;
|
|
6653
|
+
if (header["content-disposition"]) {
|
|
6654
|
+
parsed = parseParams(header["content-disposition"][0]);
|
|
6655
|
+
if (!RE_FIELD.test(parsed[0])) return skipPart(part);
|
|
6656
|
+
for (i = 0, len = parsed.length; i < len; ++i) if (RE_NAME.test(parsed[i][0])) fieldname = parsed[i][1];
|
|
6657
|
+
else if (RE_FILENAME.test(parsed[i][0])) {
|
|
6658
|
+
filename = parsed[i][1];
|
|
6659
|
+
if (!preservePath) filename = basename(filename);
|
|
6660
|
+
}
|
|
6661
|
+
} else return skipPart(part);
|
|
6662
|
+
if (header["content-transfer-encoding"]) encoding = header["content-transfer-encoding"][0].toLowerCase();
|
|
6663
|
+
else encoding = "7bit";
|
|
6664
|
+
let onData, onEnd;
|
|
6665
|
+
if (isPartAFile(fieldname, contype, filename)) {
|
|
6666
|
+
if (nfiles === filesLimit) {
|
|
6667
|
+
if (!boy.hitFilesLimit) {
|
|
6668
|
+
boy.hitFilesLimit = true;
|
|
6669
|
+
boy.emit("filesLimit");
|
|
6670
|
+
}
|
|
6671
|
+
return skipPart(part);
|
|
6672
|
+
}
|
|
6673
|
+
++nfiles;
|
|
6674
|
+
if (boy.listenerCount("file") === 0) {
|
|
6675
|
+
self.parser._ignore();
|
|
6676
|
+
return;
|
|
6677
|
+
}
|
|
6678
|
+
++nends;
|
|
6679
|
+
const file = new FileStream(fileOpts);
|
|
6680
|
+
curFile = file;
|
|
6681
|
+
file.on("end", function() {
|
|
6682
|
+
--nends;
|
|
6683
|
+
self._pause = false;
|
|
6684
|
+
checkFinished();
|
|
6685
|
+
if (self._cb && !self._needDrain) {
|
|
6686
|
+
const cb = self._cb;
|
|
6687
|
+
self._cb = void 0;
|
|
6688
|
+
cb();
|
|
6689
|
+
}
|
|
6690
|
+
});
|
|
6691
|
+
file._read = function(n) {
|
|
6692
|
+
if (!self._pause) return;
|
|
6693
|
+
self._pause = false;
|
|
6694
|
+
if (self._cb && !self._needDrain) {
|
|
6695
|
+
const cb = self._cb;
|
|
6696
|
+
self._cb = void 0;
|
|
6697
|
+
cb();
|
|
6698
|
+
}
|
|
6699
|
+
};
|
|
6700
|
+
boy.emit("file", fieldname, file, filename, encoding, contype);
|
|
6701
|
+
onData = function(data) {
|
|
6702
|
+
if ((nsize += data.length) > fileSizeLimit) {
|
|
6703
|
+
const extralen = fileSizeLimit - nsize + data.length;
|
|
6704
|
+
if (extralen > 0) file.push(data.slice(0, extralen));
|
|
6705
|
+
file.truncated = true;
|
|
6706
|
+
file.bytesRead = fileSizeLimit;
|
|
6707
|
+
part.removeAllListeners("data");
|
|
6708
|
+
file.emit("limit");
|
|
6709
|
+
return;
|
|
6710
|
+
} else if (!file.push(data)) self._pause = true;
|
|
6711
|
+
file.bytesRead = nsize;
|
|
6712
|
+
};
|
|
6713
|
+
onEnd = function() {
|
|
6714
|
+
curFile = void 0;
|
|
6715
|
+
file.push(null);
|
|
6716
|
+
};
|
|
6717
|
+
} else {
|
|
6718
|
+
if (nfields === fieldsLimit) {
|
|
6719
|
+
if (!boy.hitFieldsLimit) {
|
|
6720
|
+
boy.hitFieldsLimit = true;
|
|
6721
|
+
boy.emit("fieldsLimit");
|
|
6722
|
+
}
|
|
6723
|
+
return skipPart(part);
|
|
6724
|
+
}
|
|
6725
|
+
++nfields;
|
|
6726
|
+
++nends;
|
|
6727
|
+
let buffer = "";
|
|
6728
|
+
let truncated = false;
|
|
6729
|
+
curField = part;
|
|
6730
|
+
onData = function(data) {
|
|
6731
|
+
if ((nsize += data.length) > fieldSizeLimit) {
|
|
6732
|
+
const extralen = fieldSizeLimit - (nsize - data.length);
|
|
6733
|
+
buffer += data.toString("binary", 0, extralen);
|
|
6734
|
+
truncated = true;
|
|
6735
|
+
part.removeAllListeners("data");
|
|
6736
|
+
} else buffer += data.toString("binary");
|
|
6737
|
+
};
|
|
6738
|
+
onEnd = function() {
|
|
6739
|
+
curField = void 0;
|
|
6740
|
+
if (buffer.length) buffer = decodeText(buffer, "binary", charset);
|
|
6741
|
+
boy.emit("field", fieldname, buffer, false, truncated, encoding, contype);
|
|
6742
|
+
--nends;
|
|
6743
|
+
checkFinished();
|
|
6744
|
+
};
|
|
6745
|
+
}
|
|
6746
|
+
part._readableState.sync = false;
|
|
6747
|
+
part.on("data", onData);
|
|
6748
|
+
part.on("end", onEnd);
|
|
6749
|
+
}).on("error", function(err) {
|
|
6750
|
+
if (curFile) curFile.emit("error", err);
|
|
6751
|
+
});
|
|
6752
|
+
}).on("error", function(err) {
|
|
6753
|
+
boy.emit("error", err);
|
|
6754
|
+
}).on("finish", function() {
|
|
6755
|
+
finished = true;
|
|
6756
|
+
checkFinished();
|
|
6757
|
+
});
|
|
6758
|
+
}
|
|
6759
|
+
Multipart.prototype.write = function(chunk, cb) {
|
|
6760
|
+
const r = this.parser.write(chunk);
|
|
6761
|
+
if (r && !this._pause) cb();
|
|
6762
|
+
else {
|
|
6763
|
+
this._needDrain = !r;
|
|
6764
|
+
this._cb = cb;
|
|
6765
|
+
}
|
|
6766
|
+
};
|
|
6767
|
+
Multipart.prototype.end = function() {
|
|
6768
|
+
const self = this;
|
|
6769
|
+
if (self.parser.writable) self.parser.end();
|
|
6770
|
+
else if (!self._boy._done) process.nextTick(function() {
|
|
6771
|
+
self._boy._done = true;
|
|
6772
|
+
self._boy.emit("finish");
|
|
6773
|
+
});
|
|
6774
|
+
};
|
|
6775
|
+
function skipPart(part) {
|
|
6776
|
+
part.resume();
|
|
6777
|
+
}
|
|
6778
|
+
function FileStream(opts) {
|
|
6779
|
+
Readable$1.call(this, opts);
|
|
6780
|
+
this.bytesRead = 0;
|
|
6781
|
+
this.truncated = false;
|
|
6782
|
+
}
|
|
6783
|
+
inherits$1(FileStream, Readable$1);
|
|
6784
|
+
FileStream.prototype._read = function(n) {};
|
|
6785
|
+
module.exports = Multipart;
|
|
6786
|
+
}));
|
|
6787
|
+
//#endregion
|
|
6788
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/utils/Decoder.js
|
|
6789
|
+
var require_Decoder = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6790
|
+
const RE_PLUS = /\+/g;
|
|
6791
|
+
const HEX = [
|
|
6792
|
+
0,
|
|
6793
|
+
0,
|
|
6794
|
+
0,
|
|
6795
|
+
0,
|
|
6796
|
+
0,
|
|
6797
|
+
0,
|
|
6798
|
+
0,
|
|
6799
|
+
0,
|
|
6800
|
+
0,
|
|
6801
|
+
0,
|
|
6802
|
+
0,
|
|
6803
|
+
0,
|
|
6804
|
+
0,
|
|
6805
|
+
0,
|
|
6806
|
+
0,
|
|
6807
|
+
0,
|
|
6808
|
+
0,
|
|
6809
|
+
0,
|
|
6810
|
+
0,
|
|
6811
|
+
0,
|
|
6812
|
+
0,
|
|
6813
|
+
0,
|
|
6814
|
+
0,
|
|
6815
|
+
0,
|
|
6816
|
+
0,
|
|
6817
|
+
0,
|
|
6818
|
+
0,
|
|
6819
|
+
0,
|
|
6820
|
+
0,
|
|
6821
|
+
0,
|
|
6822
|
+
0,
|
|
6823
|
+
0,
|
|
6824
|
+
0,
|
|
6825
|
+
0,
|
|
6826
|
+
0,
|
|
6827
|
+
0,
|
|
6828
|
+
0,
|
|
6829
|
+
0,
|
|
6830
|
+
0,
|
|
6831
|
+
0,
|
|
6832
|
+
0,
|
|
6833
|
+
0,
|
|
6834
|
+
0,
|
|
6835
|
+
0,
|
|
6836
|
+
0,
|
|
6837
|
+
0,
|
|
6838
|
+
0,
|
|
6839
|
+
0,
|
|
6840
|
+
1,
|
|
6841
|
+
1,
|
|
6842
|
+
1,
|
|
6843
|
+
1,
|
|
6844
|
+
1,
|
|
6845
|
+
1,
|
|
6846
|
+
1,
|
|
6847
|
+
1,
|
|
6848
|
+
1,
|
|
6849
|
+
1,
|
|
6850
|
+
0,
|
|
6851
|
+
0,
|
|
6852
|
+
0,
|
|
6853
|
+
0,
|
|
6854
|
+
0,
|
|
6855
|
+
0,
|
|
6856
|
+
0,
|
|
6857
|
+
1,
|
|
6858
|
+
1,
|
|
6859
|
+
1,
|
|
6860
|
+
1,
|
|
6861
|
+
1,
|
|
6862
|
+
1,
|
|
6863
|
+
0,
|
|
6864
|
+
0,
|
|
6865
|
+
0,
|
|
6866
|
+
0,
|
|
6867
|
+
0,
|
|
6868
|
+
0,
|
|
6869
|
+
0,
|
|
6870
|
+
0,
|
|
6871
|
+
0,
|
|
6872
|
+
0,
|
|
6873
|
+
0,
|
|
6874
|
+
0,
|
|
6875
|
+
0,
|
|
6876
|
+
0,
|
|
6877
|
+
0,
|
|
6878
|
+
0,
|
|
6879
|
+
0,
|
|
6880
|
+
0,
|
|
6881
|
+
0,
|
|
6882
|
+
0,
|
|
6883
|
+
0,
|
|
6884
|
+
0,
|
|
6885
|
+
0,
|
|
6886
|
+
0,
|
|
6887
|
+
0,
|
|
6888
|
+
0,
|
|
6889
|
+
1,
|
|
6890
|
+
1,
|
|
6891
|
+
1,
|
|
6892
|
+
1,
|
|
6893
|
+
1,
|
|
6894
|
+
1,
|
|
6895
|
+
0,
|
|
6896
|
+
0,
|
|
6897
|
+
0,
|
|
6898
|
+
0,
|
|
6899
|
+
0,
|
|
6900
|
+
0,
|
|
6901
|
+
0,
|
|
6902
|
+
0,
|
|
6903
|
+
0,
|
|
6904
|
+
0,
|
|
6905
|
+
0,
|
|
6906
|
+
0,
|
|
6907
|
+
0,
|
|
6908
|
+
0,
|
|
6909
|
+
0,
|
|
6910
|
+
0,
|
|
6911
|
+
0,
|
|
6912
|
+
0,
|
|
6913
|
+
0,
|
|
6914
|
+
0,
|
|
6915
|
+
0,
|
|
6916
|
+
0,
|
|
6917
|
+
0,
|
|
6918
|
+
0,
|
|
6919
|
+
0
|
|
6920
|
+
];
|
|
6921
|
+
function Decoder() {
|
|
6922
|
+
this.buffer = void 0;
|
|
6923
|
+
}
|
|
6924
|
+
Decoder.prototype.write = function(str) {
|
|
6925
|
+
str = str.replace(RE_PLUS, " ");
|
|
6926
|
+
let res = "";
|
|
6927
|
+
let i = 0;
|
|
6928
|
+
let p = 0;
|
|
6929
|
+
const len = str.length;
|
|
6930
|
+
for (; i < len; ++i) if (this.buffer !== void 0) if (!HEX[str.charCodeAt(i)]) {
|
|
6931
|
+
res += "%" + this.buffer;
|
|
6932
|
+
this.buffer = void 0;
|
|
6933
|
+
--i;
|
|
6934
|
+
} else {
|
|
6935
|
+
this.buffer += str[i];
|
|
6936
|
+
++p;
|
|
6937
|
+
if (this.buffer.length === 2) {
|
|
6938
|
+
res += String.fromCharCode(parseInt(this.buffer, 16));
|
|
6939
|
+
this.buffer = void 0;
|
|
6940
|
+
}
|
|
6941
|
+
}
|
|
6942
|
+
else if (str[i] === "%") {
|
|
6943
|
+
if (i > p) {
|
|
6944
|
+
res += str.substring(p, i);
|
|
6945
|
+
p = i;
|
|
6946
|
+
}
|
|
6947
|
+
this.buffer = "";
|
|
6948
|
+
++p;
|
|
6949
|
+
}
|
|
6950
|
+
if (p < len && this.buffer === void 0) res += str.substring(p);
|
|
6951
|
+
return res;
|
|
6952
|
+
};
|
|
6953
|
+
Decoder.prototype.reset = function() {
|
|
6954
|
+
this.buffer = void 0;
|
|
6955
|
+
};
|
|
6956
|
+
module.exports = Decoder;
|
|
6957
|
+
}));
|
|
6958
|
+
//#endregion
|
|
6959
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/types/urlencoded.js
|
|
6960
|
+
var require_urlencoded = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
6961
|
+
const Decoder = require_Decoder();
|
|
6962
|
+
const decodeText = require_decodeText();
|
|
6963
|
+
const getLimit = require_getLimit();
|
|
6964
|
+
const RE_CHARSET = /^charset$/i;
|
|
6965
|
+
UrlEncoded.detect = /^application\/x-www-form-urlencoded/i;
|
|
6966
|
+
function UrlEncoded(boy, cfg) {
|
|
6967
|
+
const limits = cfg.limits;
|
|
6968
|
+
const parsedConType = cfg.parsedConType;
|
|
6969
|
+
this.boy = boy;
|
|
6970
|
+
this.fieldSizeLimit = getLimit(limits, "fieldSize", 1 * 1024 * 1024);
|
|
6971
|
+
this.fieldNameSizeLimit = getLimit(limits, "fieldNameSize", 100);
|
|
6972
|
+
this.fieldsLimit = getLimit(limits, "fields", Infinity);
|
|
6973
|
+
let charset;
|
|
6974
|
+
for (var i = 0, len = parsedConType.length; i < len; ++i) if (Array.isArray(parsedConType[i]) && RE_CHARSET.test(parsedConType[i][0])) {
|
|
6975
|
+
charset = parsedConType[i][1].toLowerCase();
|
|
6976
|
+
break;
|
|
6977
|
+
}
|
|
6978
|
+
if (charset === void 0) charset = cfg.defCharset || "utf8";
|
|
6979
|
+
this.decoder = new Decoder();
|
|
6980
|
+
this.charset = charset;
|
|
6981
|
+
this._fields = 0;
|
|
6982
|
+
this._state = "key";
|
|
6983
|
+
this._checkingBytes = true;
|
|
6984
|
+
this._bytesKey = 0;
|
|
6985
|
+
this._bytesVal = 0;
|
|
6986
|
+
this._key = "";
|
|
6987
|
+
this._val = "";
|
|
6988
|
+
this._keyTrunc = false;
|
|
6989
|
+
this._valTrunc = false;
|
|
6990
|
+
this._hitLimit = false;
|
|
6991
|
+
}
|
|
6992
|
+
UrlEncoded.prototype.write = function(data, cb) {
|
|
6993
|
+
if (this._fields === this.fieldsLimit) {
|
|
6994
|
+
if (!this.boy.hitFieldsLimit) {
|
|
6995
|
+
this.boy.hitFieldsLimit = true;
|
|
6996
|
+
this.boy.emit("fieldsLimit");
|
|
6997
|
+
}
|
|
6998
|
+
return cb();
|
|
6999
|
+
}
|
|
7000
|
+
let idxeq;
|
|
7001
|
+
let idxamp;
|
|
7002
|
+
let i;
|
|
7003
|
+
let p = 0;
|
|
7004
|
+
const len = data.length;
|
|
7005
|
+
while (p < len) if (this._state === "key") {
|
|
7006
|
+
idxeq = idxamp = void 0;
|
|
7007
|
+
for (i = p; i < len; ++i) {
|
|
7008
|
+
if (!this._checkingBytes) ++p;
|
|
7009
|
+
if (data[i] === 61) {
|
|
7010
|
+
idxeq = i;
|
|
7011
|
+
break;
|
|
7012
|
+
} else if (data[i] === 38) {
|
|
7013
|
+
idxamp = i;
|
|
7014
|
+
break;
|
|
7015
|
+
}
|
|
7016
|
+
if (this._checkingBytes && this._bytesKey === this.fieldNameSizeLimit) {
|
|
7017
|
+
this._hitLimit = true;
|
|
7018
|
+
break;
|
|
7019
|
+
} else if (this._checkingBytes) ++this._bytesKey;
|
|
7020
|
+
}
|
|
7021
|
+
if (idxeq !== void 0) {
|
|
7022
|
+
if (idxeq > p) this._key += this.decoder.write(data.toString("binary", p, idxeq));
|
|
7023
|
+
this._state = "val";
|
|
7024
|
+
this._hitLimit = false;
|
|
7025
|
+
this._checkingBytes = true;
|
|
7026
|
+
this._val = "";
|
|
7027
|
+
this._bytesVal = 0;
|
|
7028
|
+
this._valTrunc = false;
|
|
7029
|
+
this.decoder.reset();
|
|
7030
|
+
p = idxeq + 1;
|
|
7031
|
+
} else if (idxamp !== void 0) {
|
|
7032
|
+
++this._fields;
|
|
7033
|
+
let key;
|
|
7034
|
+
const keyTrunc = this._keyTrunc;
|
|
7035
|
+
if (idxamp > p) key = this._key += this.decoder.write(data.toString("binary", p, idxamp));
|
|
7036
|
+
else key = this._key;
|
|
7037
|
+
this._hitLimit = false;
|
|
7038
|
+
this._checkingBytes = true;
|
|
7039
|
+
this._key = "";
|
|
7040
|
+
this._bytesKey = 0;
|
|
7041
|
+
this._keyTrunc = false;
|
|
7042
|
+
this.decoder.reset();
|
|
7043
|
+
if (key.length) this.boy.emit("field", decodeText(key, "binary", this.charset), "", keyTrunc, false);
|
|
7044
|
+
p = idxamp + 1;
|
|
7045
|
+
if (this._fields === this.fieldsLimit) return cb();
|
|
7046
|
+
} else if (this._hitLimit) {
|
|
7047
|
+
if (i > p) this._key += this.decoder.write(data.toString("binary", p, i));
|
|
7048
|
+
p = i;
|
|
7049
|
+
if ((this._bytesKey = this._key.length) === this.fieldNameSizeLimit) {
|
|
7050
|
+
this._checkingBytes = false;
|
|
7051
|
+
this._keyTrunc = true;
|
|
7052
|
+
}
|
|
7053
|
+
} else {
|
|
7054
|
+
if (p < len) this._key += this.decoder.write(data.toString("binary", p));
|
|
7055
|
+
p = len;
|
|
7056
|
+
}
|
|
7057
|
+
} else {
|
|
7058
|
+
idxamp = void 0;
|
|
7059
|
+
for (i = p; i < len; ++i) {
|
|
7060
|
+
if (!this._checkingBytes) ++p;
|
|
7061
|
+
if (data[i] === 38) {
|
|
7062
|
+
idxamp = i;
|
|
7063
|
+
break;
|
|
7064
|
+
}
|
|
7065
|
+
if (this._checkingBytes && this._bytesVal === this.fieldSizeLimit) {
|
|
7066
|
+
this._hitLimit = true;
|
|
7067
|
+
break;
|
|
7068
|
+
} else if (this._checkingBytes) ++this._bytesVal;
|
|
7069
|
+
}
|
|
7070
|
+
if (idxamp !== void 0) {
|
|
7071
|
+
++this._fields;
|
|
7072
|
+
if (idxamp > p) this._val += this.decoder.write(data.toString("binary", p, idxamp));
|
|
7073
|
+
this.boy.emit("field", decodeText(this._key, "binary", this.charset), decodeText(this._val, "binary", this.charset), this._keyTrunc, this._valTrunc);
|
|
7074
|
+
this._state = "key";
|
|
7075
|
+
this._hitLimit = false;
|
|
7076
|
+
this._checkingBytes = true;
|
|
7077
|
+
this._key = "";
|
|
7078
|
+
this._bytesKey = 0;
|
|
7079
|
+
this._keyTrunc = false;
|
|
7080
|
+
this.decoder.reset();
|
|
7081
|
+
p = idxamp + 1;
|
|
7082
|
+
if (this._fields === this.fieldsLimit) return cb();
|
|
7083
|
+
} else if (this._hitLimit) {
|
|
7084
|
+
if (i > p) this._val += this.decoder.write(data.toString("binary", p, i));
|
|
7085
|
+
p = i;
|
|
7086
|
+
if (this._val === "" && this.fieldSizeLimit === 0 || (this._bytesVal = this._val.length) === this.fieldSizeLimit) {
|
|
7087
|
+
this._checkingBytes = false;
|
|
7088
|
+
this._valTrunc = true;
|
|
7089
|
+
}
|
|
7090
|
+
} else {
|
|
7091
|
+
if (p < len) this._val += this.decoder.write(data.toString("binary", p));
|
|
7092
|
+
p = len;
|
|
7093
|
+
}
|
|
7094
|
+
}
|
|
7095
|
+
cb();
|
|
7096
|
+
};
|
|
7097
|
+
UrlEncoded.prototype.end = function() {
|
|
7098
|
+
if (this.boy._done) return;
|
|
7099
|
+
if (this._state === "key" && this._key.length > 0) this.boy.emit("field", decodeText(this._key, "binary", this.charset), "", this._keyTrunc, false);
|
|
7100
|
+
else if (this._state === "val") this.boy.emit("field", decodeText(this._key, "binary", this.charset), decodeText(this._val, "binary", this.charset), this._keyTrunc, this._valTrunc);
|
|
7101
|
+
this.boy._done = true;
|
|
7102
|
+
this.boy.emit("finish");
|
|
7103
|
+
};
|
|
7104
|
+
module.exports = UrlEncoded;
|
|
7105
|
+
}));
|
|
7106
|
+
//#endregion
|
|
7107
|
+
//#region ../../node_modules/.pnpm/@fastify+busboy@3.2.0/node_modules/@fastify/busboy/lib/main.js
|
|
7108
|
+
var require_main = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7109
|
+
const WritableStream = __require("node:stream").Writable;
|
|
7110
|
+
const { inherits } = __require("node:util");
|
|
7111
|
+
const Dicer = require_Dicer();
|
|
7112
|
+
const MultipartParser = require_multipart$1();
|
|
7113
|
+
const UrlencodedParser = require_urlencoded();
|
|
7114
|
+
const parseParams = require_parseParams();
|
|
7115
|
+
function Busboy(opts) {
|
|
7116
|
+
if (!(this instanceof Busboy)) return new Busboy(opts);
|
|
7117
|
+
if (typeof opts !== "object") throw new TypeError("Busboy expected an options-Object.");
|
|
7118
|
+
if (typeof opts.headers !== "object") throw new TypeError("Busboy expected an options-Object with headers-attribute.");
|
|
7119
|
+
if (typeof opts.headers["content-type"] !== "string") throw new TypeError("Missing Content-Type-header.");
|
|
7120
|
+
const { headers, ...streamOptions } = opts;
|
|
7121
|
+
this.opts = {
|
|
7122
|
+
autoDestroy: false,
|
|
7123
|
+
...streamOptions
|
|
7124
|
+
};
|
|
7125
|
+
WritableStream.call(this, this.opts);
|
|
7126
|
+
this._done = false;
|
|
7127
|
+
this._parser = this.getParserByHeaders(headers);
|
|
7128
|
+
this._finished = false;
|
|
7129
|
+
}
|
|
7130
|
+
inherits(Busboy, WritableStream);
|
|
7131
|
+
Busboy.prototype.emit = function(ev) {
|
|
7132
|
+
if (ev === "finish") {
|
|
7133
|
+
if (!this._done) {
|
|
7134
|
+
this._parser?.end();
|
|
7135
|
+
return;
|
|
7136
|
+
} else if (this._finished) return;
|
|
7137
|
+
this._finished = true;
|
|
7138
|
+
}
|
|
7139
|
+
WritableStream.prototype.emit.apply(this, arguments);
|
|
7140
|
+
};
|
|
7141
|
+
Busboy.prototype.getParserByHeaders = function(headers) {
|
|
7142
|
+
const parsed = parseParams(headers["content-type"]);
|
|
7143
|
+
const cfg = {
|
|
7144
|
+
defCharset: this.opts.defCharset,
|
|
7145
|
+
fileHwm: this.opts.fileHwm,
|
|
7146
|
+
headers,
|
|
7147
|
+
highWaterMark: this.opts.highWaterMark,
|
|
7148
|
+
isPartAFile: this.opts.isPartAFile,
|
|
7149
|
+
limits: this.opts.limits,
|
|
7150
|
+
parsedConType: parsed,
|
|
7151
|
+
preservePath: this.opts.preservePath
|
|
7152
|
+
};
|
|
7153
|
+
if (MultipartParser.detect.test(parsed[0])) return new MultipartParser(this, cfg);
|
|
7154
|
+
if (UrlencodedParser.detect.test(parsed[0])) return new UrlencodedParser(this, cfg);
|
|
7155
|
+
throw new Error("Unsupported Content-Type.");
|
|
7156
|
+
};
|
|
7157
|
+
Busboy.prototype._write = function(chunk, encoding, cb) {
|
|
7158
|
+
this._parser.write(chunk, cb);
|
|
7159
|
+
};
|
|
7160
|
+
module.exports = Busboy;
|
|
7161
|
+
module.exports.default = Busboy;
|
|
7162
|
+
module.exports.Busboy = Busboy;
|
|
7163
|
+
module.exports.Dicer = Dicer;
|
|
7164
|
+
}));
|
|
7165
|
+
//#endregion
|
|
7166
|
+
//#region ../../node_modules/.pnpm/fastify-plugin@5.1.0/node_modules/fastify-plugin/lib/getPluginName.js
|
|
7167
|
+
var require_getPluginName = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7168
|
+
const fpStackTracePattern = /at\s(?:.*\.)?plugin\s.*\n\s*(.*)/;
|
|
7169
|
+
const fileNamePattern = /(\w*(\.\w*)*)\..*/;
|
|
7170
|
+
module.exports = function getPluginName(fn) {
|
|
7171
|
+
if (fn.name.length > 0) return fn.name;
|
|
7172
|
+
const stackTraceLimit = Error.stackTraceLimit;
|
|
7173
|
+
Error.stackTraceLimit = 10;
|
|
4655
7174
|
try {
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
7175
|
+
throw new Error("anonymous function");
|
|
7176
|
+
} catch (e) {
|
|
7177
|
+
Error.stackTraceLimit = stackTraceLimit;
|
|
7178
|
+
return extractPluginName(e.stack);
|
|
7179
|
+
}
|
|
7180
|
+
};
|
|
7181
|
+
function extractPluginName(stack) {
|
|
7182
|
+
const m = stack.match(fpStackTracePattern);
|
|
7183
|
+
return m ? m[1].split(/[/\\]/).slice(-1)[0].match(fileNamePattern)[1] : "anonymous";
|
|
7184
|
+
}
|
|
7185
|
+
module.exports.extractPluginName = extractPluginName;
|
|
7186
|
+
}));
|
|
7187
|
+
//#endregion
|
|
7188
|
+
//#region ../../node_modules/.pnpm/fastify-plugin@5.1.0/node_modules/fastify-plugin/lib/toCamelCase.js
|
|
7189
|
+
var require_toCamelCase = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7190
|
+
module.exports = function toCamelCase(name) {
|
|
7191
|
+
if (name[0] === "@") name = name.slice(1).replace("/", "-");
|
|
7192
|
+
return name.replace(/-(.)/g, function(match, g1) {
|
|
7193
|
+
return g1.toUpperCase();
|
|
7194
|
+
});
|
|
7195
|
+
};
|
|
7196
|
+
}));
|
|
7197
|
+
//#endregion
|
|
7198
|
+
//#region ../../node_modules/.pnpm/fastify-plugin@5.1.0/node_modules/fastify-plugin/plugin.js
|
|
7199
|
+
var require_plugin = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7200
|
+
const getPluginName = require_getPluginName();
|
|
7201
|
+
const toCamelCase = require_toCamelCase();
|
|
7202
|
+
let count = 0;
|
|
7203
|
+
function plugin(fn, options = {}) {
|
|
7204
|
+
let autoName = false;
|
|
7205
|
+
if (fn.default !== void 0) fn = fn.default;
|
|
7206
|
+
if (typeof fn !== "function") throw new TypeError(`fastify-plugin expects a function, instead got a '${typeof fn}'`);
|
|
7207
|
+
if (typeof options === "string") options = { fastify: options };
|
|
7208
|
+
if (typeof options !== "object" || Array.isArray(options) || options === null) throw new TypeError("The options object should be an object");
|
|
7209
|
+
if (options.fastify !== void 0 && typeof options.fastify !== "string") throw new TypeError(`fastify-plugin expects a version string, instead got '${typeof options.fastify}'`);
|
|
7210
|
+
if (!options.name) {
|
|
7211
|
+
autoName = true;
|
|
7212
|
+
options.name = getPluginName(fn) + "-auto-" + count++;
|
|
7213
|
+
}
|
|
7214
|
+
fn[Symbol.for("skip-override")] = options.encapsulate !== true;
|
|
7215
|
+
fn[Symbol.for("fastify.display-name")] = options.name;
|
|
7216
|
+
fn[Symbol.for("plugin-meta")] = options;
|
|
7217
|
+
if (!fn.default) fn.default = fn;
|
|
7218
|
+
const camelCase = toCamelCase(options.name);
|
|
7219
|
+
if (!autoName && !fn[camelCase]) fn[camelCase] = fn;
|
|
7220
|
+
return fn;
|
|
7221
|
+
}
|
|
7222
|
+
module.exports = plugin;
|
|
7223
|
+
module.exports.default = plugin;
|
|
7224
|
+
module.exports.fastifyPlugin = plugin;
|
|
7225
|
+
}));
|
|
7226
|
+
//#endregion
|
|
7227
|
+
//#region ../../node_modules/.pnpm/@fastify+multipart@10.0.0/node_modules/@fastify/multipart/lib/generateId.js
|
|
7228
|
+
var require_generateId = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7229
|
+
const HEX = [
|
|
7230
|
+
"00",
|
|
7231
|
+
"01",
|
|
7232
|
+
"02",
|
|
7233
|
+
"03",
|
|
7234
|
+
"04",
|
|
7235
|
+
"05",
|
|
7236
|
+
"06",
|
|
7237
|
+
"07",
|
|
7238
|
+
"08",
|
|
7239
|
+
"09",
|
|
7240
|
+
"0a",
|
|
7241
|
+
"0b",
|
|
7242
|
+
"0c",
|
|
7243
|
+
"0d",
|
|
7244
|
+
"0e",
|
|
7245
|
+
"0f",
|
|
7246
|
+
"10",
|
|
7247
|
+
"11",
|
|
7248
|
+
"12",
|
|
7249
|
+
"13",
|
|
7250
|
+
"14",
|
|
7251
|
+
"15",
|
|
7252
|
+
"16",
|
|
7253
|
+
"17",
|
|
7254
|
+
"18",
|
|
7255
|
+
"19",
|
|
7256
|
+
"1a",
|
|
7257
|
+
"1b",
|
|
7258
|
+
"1c",
|
|
7259
|
+
"1d",
|
|
7260
|
+
"1e",
|
|
7261
|
+
"1f",
|
|
7262
|
+
"20",
|
|
7263
|
+
"21",
|
|
7264
|
+
"22",
|
|
7265
|
+
"23",
|
|
7266
|
+
"24",
|
|
7267
|
+
"25",
|
|
7268
|
+
"26",
|
|
7269
|
+
"27",
|
|
7270
|
+
"28",
|
|
7271
|
+
"29",
|
|
7272
|
+
"2a",
|
|
7273
|
+
"2b",
|
|
7274
|
+
"2c",
|
|
7275
|
+
"2d",
|
|
7276
|
+
"2e",
|
|
7277
|
+
"2f",
|
|
7278
|
+
"30",
|
|
7279
|
+
"31",
|
|
7280
|
+
"32",
|
|
7281
|
+
"33",
|
|
7282
|
+
"34",
|
|
7283
|
+
"35",
|
|
7284
|
+
"36",
|
|
7285
|
+
"37",
|
|
7286
|
+
"38",
|
|
7287
|
+
"39",
|
|
7288
|
+
"3a",
|
|
7289
|
+
"3b",
|
|
7290
|
+
"3c",
|
|
7291
|
+
"3d",
|
|
7292
|
+
"3e",
|
|
7293
|
+
"3f",
|
|
7294
|
+
"40",
|
|
7295
|
+
"41",
|
|
7296
|
+
"42",
|
|
7297
|
+
"43",
|
|
7298
|
+
"44",
|
|
7299
|
+
"45",
|
|
7300
|
+
"46",
|
|
7301
|
+
"47",
|
|
7302
|
+
"48",
|
|
7303
|
+
"49",
|
|
7304
|
+
"4a",
|
|
7305
|
+
"4b",
|
|
7306
|
+
"4c",
|
|
7307
|
+
"4d",
|
|
7308
|
+
"4e",
|
|
7309
|
+
"4f",
|
|
7310
|
+
"50",
|
|
7311
|
+
"51",
|
|
7312
|
+
"52",
|
|
7313
|
+
"53",
|
|
7314
|
+
"54",
|
|
7315
|
+
"55",
|
|
7316
|
+
"56",
|
|
7317
|
+
"57",
|
|
7318
|
+
"58",
|
|
7319
|
+
"59",
|
|
7320
|
+
"5a",
|
|
7321
|
+
"5b",
|
|
7322
|
+
"5c",
|
|
7323
|
+
"5d",
|
|
7324
|
+
"5e",
|
|
7325
|
+
"5f",
|
|
7326
|
+
"60",
|
|
7327
|
+
"61",
|
|
7328
|
+
"62",
|
|
7329
|
+
"63",
|
|
7330
|
+
"64",
|
|
7331
|
+
"65",
|
|
7332
|
+
"66",
|
|
7333
|
+
"67",
|
|
7334
|
+
"68",
|
|
7335
|
+
"69",
|
|
7336
|
+
"6a",
|
|
7337
|
+
"6b",
|
|
7338
|
+
"6c",
|
|
7339
|
+
"6d",
|
|
7340
|
+
"6e",
|
|
7341
|
+
"6f",
|
|
7342
|
+
"70",
|
|
7343
|
+
"71",
|
|
7344
|
+
"72",
|
|
7345
|
+
"73",
|
|
7346
|
+
"74",
|
|
7347
|
+
"75",
|
|
7348
|
+
"76",
|
|
7349
|
+
"77",
|
|
7350
|
+
"78",
|
|
7351
|
+
"79",
|
|
7352
|
+
"7a",
|
|
7353
|
+
"7b",
|
|
7354
|
+
"7c",
|
|
7355
|
+
"7d",
|
|
7356
|
+
"7e",
|
|
7357
|
+
"7f",
|
|
7358
|
+
"80",
|
|
7359
|
+
"81",
|
|
7360
|
+
"82",
|
|
7361
|
+
"83",
|
|
7362
|
+
"84",
|
|
7363
|
+
"85",
|
|
7364
|
+
"86",
|
|
7365
|
+
"87",
|
|
7366
|
+
"88",
|
|
7367
|
+
"89",
|
|
7368
|
+
"8a",
|
|
7369
|
+
"8b",
|
|
7370
|
+
"8c",
|
|
7371
|
+
"8d",
|
|
7372
|
+
"8e",
|
|
7373
|
+
"8f",
|
|
7374
|
+
"90",
|
|
7375
|
+
"91",
|
|
7376
|
+
"92",
|
|
7377
|
+
"93",
|
|
7378
|
+
"94",
|
|
7379
|
+
"95",
|
|
7380
|
+
"96",
|
|
7381
|
+
"97",
|
|
7382
|
+
"98",
|
|
7383
|
+
"99",
|
|
7384
|
+
"9a",
|
|
7385
|
+
"9b",
|
|
7386
|
+
"9c",
|
|
7387
|
+
"9d",
|
|
7388
|
+
"9e",
|
|
7389
|
+
"9f",
|
|
7390
|
+
"a0",
|
|
7391
|
+
"a1",
|
|
7392
|
+
"a2",
|
|
7393
|
+
"a3",
|
|
7394
|
+
"a4",
|
|
7395
|
+
"a5",
|
|
7396
|
+
"a6",
|
|
7397
|
+
"a7",
|
|
7398
|
+
"a8",
|
|
7399
|
+
"a9",
|
|
7400
|
+
"aa",
|
|
7401
|
+
"ab",
|
|
7402
|
+
"ac",
|
|
7403
|
+
"ad",
|
|
7404
|
+
"ae",
|
|
7405
|
+
"af",
|
|
7406
|
+
"b0",
|
|
7407
|
+
"b1",
|
|
7408
|
+
"b2",
|
|
7409
|
+
"b3",
|
|
7410
|
+
"b4",
|
|
7411
|
+
"b5",
|
|
7412
|
+
"b6",
|
|
7413
|
+
"b7",
|
|
7414
|
+
"b8",
|
|
7415
|
+
"b9",
|
|
7416
|
+
"ba",
|
|
7417
|
+
"bb",
|
|
7418
|
+
"bc",
|
|
7419
|
+
"bd",
|
|
7420
|
+
"be",
|
|
7421
|
+
"bf",
|
|
7422
|
+
"c0",
|
|
7423
|
+
"c1",
|
|
7424
|
+
"c2",
|
|
7425
|
+
"c3",
|
|
7426
|
+
"c4",
|
|
7427
|
+
"c5",
|
|
7428
|
+
"c6",
|
|
7429
|
+
"c7",
|
|
7430
|
+
"c8",
|
|
7431
|
+
"c9",
|
|
7432
|
+
"ca",
|
|
7433
|
+
"cb",
|
|
7434
|
+
"cc",
|
|
7435
|
+
"cd",
|
|
7436
|
+
"ce",
|
|
7437
|
+
"cf",
|
|
7438
|
+
"d0",
|
|
7439
|
+
"d1",
|
|
7440
|
+
"d2",
|
|
7441
|
+
"d3",
|
|
7442
|
+
"d4",
|
|
7443
|
+
"d5",
|
|
7444
|
+
"d6",
|
|
7445
|
+
"d7",
|
|
7446
|
+
"d8",
|
|
7447
|
+
"d9",
|
|
7448
|
+
"da",
|
|
7449
|
+
"db",
|
|
7450
|
+
"dc",
|
|
7451
|
+
"dd",
|
|
7452
|
+
"de",
|
|
7453
|
+
"df",
|
|
7454
|
+
"e0",
|
|
7455
|
+
"e1",
|
|
7456
|
+
"e2",
|
|
7457
|
+
"e3",
|
|
7458
|
+
"e4",
|
|
7459
|
+
"e5",
|
|
7460
|
+
"e6",
|
|
7461
|
+
"e7",
|
|
7462
|
+
"e8",
|
|
7463
|
+
"e9",
|
|
7464
|
+
"ea",
|
|
7465
|
+
"eb",
|
|
7466
|
+
"ec",
|
|
7467
|
+
"ed",
|
|
7468
|
+
"ee",
|
|
7469
|
+
"ef",
|
|
7470
|
+
"f0",
|
|
7471
|
+
"f1",
|
|
7472
|
+
"f2",
|
|
7473
|
+
"f3",
|
|
7474
|
+
"f4",
|
|
7475
|
+
"f5",
|
|
7476
|
+
"f6",
|
|
7477
|
+
"f7",
|
|
7478
|
+
"f8",
|
|
7479
|
+
"f9",
|
|
7480
|
+
"fa",
|
|
7481
|
+
"fb",
|
|
7482
|
+
"fc",
|
|
7483
|
+
"fd",
|
|
7484
|
+
"fe",
|
|
7485
|
+
"ff"
|
|
7486
|
+
];
|
|
7487
|
+
const random = Math.random;
|
|
7488
|
+
function seed() {
|
|
7489
|
+
return HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0] + HEX[255 * random() | 0];
|
|
7490
|
+
}
|
|
7491
|
+
module.exports.generateId = (function generateIdFn() {
|
|
7492
|
+
let num = 0;
|
|
7493
|
+
let str = seed();
|
|
7494
|
+
return function generateId() {
|
|
7495
|
+
return num === 255 ? (str = seed()) + HEX[num = 0] : str + HEX[++num];
|
|
7496
|
+
};
|
|
7497
|
+
})();
|
|
7498
|
+
}));
|
|
7499
|
+
//#endregion
|
|
7500
|
+
//#region ../../node_modules/.pnpm/@fastify+error@4.2.0/node_modules/@fastify/error/index.js
|
|
7501
|
+
var require_error = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7502
|
+
const { format } = __require("node:util");
|
|
7503
|
+
function toString() {
|
|
7504
|
+
return `${this.name} [${this.code}]: ${this.message}`;
|
|
7505
|
+
}
|
|
7506
|
+
const FastifyGenericErrorSymbol = Symbol.for("fastify-error-generic");
|
|
7507
|
+
function createError(code, message, statusCode = 500, Base = Error, captureStackTrace = createError.captureStackTrace) {
|
|
7508
|
+
const shouldCreateFastifyGenericError = code === FastifyGenericErrorSymbol;
|
|
7509
|
+
if (shouldCreateFastifyGenericError) code = "FST_ERR";
|
|
7510
|
+
if (!code) throw new Error("Fastify error code must not be empty");
|
|
7511
|
+
if (!message) throw new Error("Fastify error message must not be empty");
|
|
7512
|
+
code = code.toUpperCase();
|
|
7513
|
+
!statusCode && (statusCode = void 0);
|
|
7514
|
+
const FastifySpecificErrorSymbol = Symbol.for(`fastify-error ${code}`);
|
|
7515
|
+
function FastifyError(...args) {
|
|
7516
|
+
if (!new.target) return new FastifyError(...args);
|
|
7517
|
+
this.code = code;
|
|
7518
|
+
this.name = "FastifyError";
|
|
7519
|
+
this.statusCode = statusCode;
|
|
7520
|
+
const lastElement = args.length - 1;
|
|
7521
|
+
if (lastElement !== -1 && args[lastElement] && typeof args[lastElement] === "object" && "cause" in args[lastElement]) this.cause = args.pop().cause;
|
|
7522
|
+
this.message = format(message, ...args);
|
|
7523
|
+
Error.stackTraceLimit && captureStackTrace && Error.captureStackTrace(this, FastifyError);
|
|
7524
|
+
}
|
|
7525
|
+
FastifyError.prototype = Object.create(Base.prototype, {
|
|
7526
|
+
constructor: {
|
|
7527
|
+
value: FastifyError,
|
|
7528
|
+
enumerable: false,
|
|
7529
|
+
writable: true,
|
|
7530
|
+
configurable: true
|
|
7531
|
+
},
|
|
7532
|
+
[FastifyGenericErrorSymbol]: {
|
|
7533
|
+
value: true,
|
|
7534
|
+
enumerable: false,
|
|
7535
|
+
writable: false,
|
|
7536
|
+
configurable: false
|
|
7537
|
+
},
|
|
7538
|
+
[FastifySpecificErrorSymbol]: {
|
|
7539
|
+
value: true,
|
|
7540
|
+
enumerable: false,
|
|
7541
|
+
writable: false,
|
|
7542
|
+
configurable: false
|
|
7543
|
+
}
|
|
7544
|
+
});
|
|
7545
|
+
if (shouldCreateFastifyGenericError) Object.defineProperty(FastifyError, Symbol.hasInstance, {
|
|
7546
|
+
value(instance) {
|
|
7547
|
+
return instance && instance[FastifyGenericErrorSymbol];
|
|
7548
|
+
},
|
|
7549
|
+
configurable: false,
|
|
7550
|
+
writable: false,
|
|
7551
|
+
enumerable: false
|
|
7552
|
+
});
|
|
7553
|
+
else Object.defineProperty(FastifyError, Symbol.hasInstance, {
|
|
7554
|
+
value(instance) {
|
|
7555
|
+
return instance && instance[FastifySpecificErrorSymbol];
|
|
7556
|
+
},
|
|
7557
|
+
configurable: false,
|
|
7558
|
+
writable: false,
|
|
7559
|
+
enumerable: false
|
|
7560
|
+
});
|
|
7561
|
+
FastifyError.prototype[Symbol.toStringTag] = "Error";
|
|
7562
|
+
FastifyError.prototype.toString = toString;
|
|
7563
|
+
return FastifyError;
|
|
7564
|
+
}
|
|
7565
|
+
createError.captureStackTrace = true;
|
|
7566
|
+
const FastifyErrorConstructor = createError(FastifyGenericErrorSymbol, "Fastify Error", 500, Error);
|
|
7567
|
+
module.exports = createError;
|
|
7568
|
+
module.exports.FastifyError = FastifyErrorConstructor;
|
|
7569
|
+
module.exports.default = createError;
|
|
7570
|
+
module.exports.createError = createError;
|
|
7571
|
+
}));
|
|
7572
|
+
//#endregion
|
|
7573
|
+
//#region ../../node_modules/.pnpm/@fastify+multipart@10.0.0/node_modules/@fastify/multipart/lib/stream-consumer.js
|
|
7574
|
+
var require_stream_consumer = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7575
|
+
module.exports = function streamToNull(stream) {
|
|
7576
|
+
return new Promise((resolve, reject) => {
|
|
7577
|
+
stream.on("data", () => {});
|
|
7578
|
+
stream.on("close", () => {
|
|
7579
|
+
resolve();
|
|
4665
7580
|
});
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
7581
|
+
stream.on("end", () => {
|
|
7582
|
+
resolve();
|
|
7583
|
+
});
|
|
7584
|
+
stream.on("error", (error) => {
|
|
7585
|
+
reject(error);
|
|
7586
|
+
});
|
|
7587
|
+
});
|
|
7588
|
+
};
|
|
7589
|
+
}));
|
|
7590
|
+
//#endregion
|
|
7591
|
+
//#region ../../node_modules/.pnpm/@fastify+deepmerge@3.2.1/node_modules/@fastify/deepmerge/index.js
|
|
7592
|
+
var require_deepmerge = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7593
|
+
const JSON_PROTO = Object.getPrototypeOf({});
|
|
7594
|
+
function defaultIsMergeableObjectFactory() {
|
|
7595
|
+
return function defaultIsMergeableObject(value) {
|
|
7596
|
+
return typeof value === "object" && value !== null && !(value instanceof RegExp) && !(value instanceof Date);
|
|
7597
|
+
};
|
|
7598
|
+
}
|
|
7599
|
+
function deepmergeConstructor(options) {
|
|
7600
|
+
function isNotPrototypeKey(value) {
|
|
7601
|
+
return value !== "constructor" && value !== "prototype" && value !== "__proto__";
|
|
7602
|
+
}
|
|
7603
|
+
function cloneArray(value) {
|
|
7604
|
+
let i = 0;
|
|
7605
|
+
const il = value.length;
|
|
7606
|
+
const result = new Array(il);
|
|
7607
|
+
for (; i < il; ++i) result[i] = clone(value[i]);
|
|
7608
|
+
return result;
|
|
7609
|
+
}
|
|
7610
|
+
function cloneObject(target) {
|
|
7611
|
+
const result = {};
|
|
7612
|
+
if (cloneProtoObject && Object.getPrototypeOf(target) !== JSON_PROTO) return cloneProtoObject(target);
|
|
7613
|
+
const targetKeys = getKeys(target);
|
|
7614
|
+
let i, il, key;
|
|
7615
|
+
for (i = 0, il = targetKeys.length; i < il; ++i) isNotPrototypeKey(key = targetKeys[i]) && (result[key] = clone(target[key]));
|
|
7616
|
+
return result;
|
|
7617
|
+
}
|
|
7618
|
+
function concatArrays(target, source) {
|
|
7619
|
+
const tl = target.length;
|
|
7620
|
+
const sl = source.length;
|
|
7621
|
+
let i = 0;
|
|
7622
|
+
const result = new Array(tl + sl);
|
|
7623
|
+
for (; i < tl; ++i) result[i] = clone(target[i]);
|
|
7624
|
+
for (i = 0; i < sl; ++i) result[i + tl] = clone(source[i]);
|
|
7625
|
+
return result;
|
|
7626
|
+
}
|
|
7627
|
+
const propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
|
|
7628
|
+
function getSymbolsAndKeys(value) {
|
|
7629
|
+
const result = Object.keys(value);
|
|
7630
|
+
const keys = Object.getOwnPropertySymbols(value);
|
|
7631
|
+
for (let i = 0, il = keys.length; i < il; ++i) propertyIsEnumerable.call(value, keys[i]) && result.push(keys[i]);
|
|
7632
|
+
return result;
|
|
7633
|
+
}
|
|
7634
|
+
const getKeys = options?.symbols ? getSymbolsAndKeys : Object.keys;
|
|
7635
|
+
const cloneProtoObject = typeof options?.cloneProtoObject === "function" ? options.cloneProtoObject : void 0;
|
|
7636
|
+
const isMergeableObject = typeof options?.isMergeableObject === "function" ? options.isMergeableObject : defaultIsMergeableObjectFactory();
|
|
7637
|
+
const onlyDefinedProperties = options?.onlyDefinedProperties === true;
|
|
7638
|
+
function isPrimitive(value) {
|
|
7639
|
+
return typeof value !== "object" || value === null;
|
|
7640
|
+
}
|
|
7641
|
+
const mergeArray = options && typeof options.mergeArray === "function" ? options.mergeArray({
|
|
7642
|
+
clone,
|
|
7643
|
+
deepmerge: _deepmerge,
|
|
7644
|
+
getKeys,
|
|
7645
|
+
isMergeableObject
|
|
7646
|
+
}) : concatArrays;
|
|
7647
|
+
function clone(entry) {
|
|
7648
|
+
return isMergeableObject(entry) ? Array.isArray(entry) ? cloneArray(entry) : cloneObject(entry) : entry;
|
|
7649
|
+
}
|
|
7650
|
+
function mergeObject(target, source) {
|
|
7651
|
+
const result = {};
|
|
7652
|
+
const targetKeys = getKeys(target);
|
|
7653
|
+
const sourceKeys = getKeys(source);
|
|
7654
|
+
let i, il, key;
|
|
7655
|
+
for (i = 0, il = targetKeys.length; i < il; ++i) isNotPrototypeKey(key = targetKeys[i]) && sourceKeys.indexOf(key) === -1 && (result[key] = clone(target[key]));
|
|
7656
|
+
for (i = 0, il = sourceKeys.length; i < il; ++i) {
|
|
7657
|
+
if (!isNotPrototypeKey(key = sourceKeys[i])) continue;
|
|
7658
|
+
if (key in target) {
|
|
7659
|
+
if (targetKeys.indexOf(key) !== -1) if (cloneProtoObject && isMergeableObject(source[key]) && Object.getPrototypeOf(source[key]) !== JSON_PROTO) result[key] = cloneProtoObject(source[key]);
|
|
7660
|
+
else result[key] = _deepmerge(target[key], source[key]);
|
|
7661
|
+
} else {
|
|
7662
|
+
if (onlyDefinedProperties && typeof source[key] === "undefined") continue;
|
|
7663
|
+
result[key] = clone(source[key]);
|
|
7664
|
+
}
|
|
7665
|
+
}
|
|
7666
|
+
return result;
|
|
7667
|
+
}
|
|
7668
|
+
function _deepmerge(target, source) {
|
|
7669
|
+
if (onlyDefinedProperties && typeof source === "undefined") return clone(target);
|
|
7670
|
+
const sourceIsArray = Array.isArray(source);
|
|
7671
|
+
const targetIsArray = Array.isArray(target);
|
|
7672
|
+
if (isPrimitive(source)) return source;
|
|
7673
|
+
else if (!isMergeableObject(target)) return clone(source);
|
|
7674
|
+
else if (sourceIsArray && targetIsArray) return mergeArray(target, source);
|
|
7675
|
+
else if (sourceIsArray !== targetIsArray) return clone(source);
|
|
7676
|
+
else return mergeObject(target, source);
|
|
7677
|
+
}
|
|
7678
|
+
function _deepmergeAll() {
|
|
7679
|
+
switch (arguments.length) {
|
|
7680
|
+
case 0: return {};
|
|
7681
|
+
case 1: return clone(arguments[0]);
|
|
7682
|
+
case 2: return _deepmerge(arguments[0], arguments[1]);
|
|
7683
|
+
}
|
|
7684
|
+
let result;
|
|
7685
|
+
for (let i = 0, il = arguments.length; i < il; ++i) result = _deepmerge(result, arguments[i]);
|
|
7686
|
+
return result;
|
|
4672
7687
|
}
|
|
7688
|
+
return options?.all ? _deepmergeAll : _deepmerge;
|
|
4673
7689
|
}
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
7690
|
+
module.exports = deepmergeConstructor;
|
|
7691
|
+
module.exports.default = deepmergeConstructor;
|
|
7692
|
+
module.exports.deepmerge = deepmergeConstructor;
|
|
7693
|
+
Object.defineProperty(module.exports, "isMergeableObject", { get: defaultIsMergeableObjectFactory });
|
|
7694
|
+
}));
|
|
7695
|
+
//#endregion
|
|
7696
|
+
//#region ../../node_modules/.pnpm/secure-json-parse@4.1.0/node_modules/secure-json-parse/index.js
|
|
7697
|
+
var require_secure_json_parse = /* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7698
|
+
const hasBuffer = typeof Buffer !== "undefined";
|
|
7699
|
+
const suspectProtoRx = /"(?:_|\\u005[Ff])(?:_|\\u005[Ff])(?:p|\\u0070)(?:r|\\u0072)(?:o|\\u006[Ff])(?:t|\\u0074)(?:o|\\u006[Ff])(?:_|\\u005[Ff])(?:_|\\u005[Ff])"\s*:/;
|
|
7700
|
+
const suspectConstructorRx = /"(?:c|\\u0063)(?:o|\\u006[Ff])(?:n|\\u006[Ee])(?:s|\\u0073)(?:t|\\u0074)(?:r|\\u0072)(?:u|\\u0075)(?:c|\\u0063)(?:t|\\u0074)(?:o|\\u006[Ff])(?:r|\\u0072)"\s*:/;
|
|
7701
|
+
/**
|
|
7702
|
+
* @description Internal parse function that parses JSON text with security checks.
|
|
7703
|
+
* @private
|
|
7704
|
+
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
|
|
7705
|
+
* @param {Function} [reviver] - The JSON.parse() optional reviver argument.
|
|
7706
|
+
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
|
|
7707
|
+
* @returns {*} The parsed object.
|
|
7708
|
+
* @throws {SyntaxError} If a forbidden prototype property is found and `options.protoAction` or
|
|
7709
|
+
* `options.constructorAction` is `'error'`.
|
|
7710
|
+
*/
|
|
7711
|
+
function _parse(text, reviver, options) {
|
|
7712
|
+
if (options == null) {
|
|
7713
|
+
if (reviver !== null && typeof reviver === "object") {
|
|
7714
|
+
options = reviver;
|
|
7715
|
+
reviver = void 0;
|
|
7716
|
+
}
|
|
4683
7717
|
}
|
|
7718
|
+
if (hasBuffer && Buffer.isBuffer(text)) text = text.toString();
|
|
7719
|
+
if (text && text.charCodeAt(0) === 65279) text = text.slice(1);
|
|
7720
|
+
const obj = JSON.parse(text, reviver);
|
|
7721
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
7722
|
+
const protoAction = options && options.protoAction || "error";
|
|
7723
|
+
const constructorAction = options && options.constructorAction || "error";
|
|
7724
|
+
if (protoAction === "ignore" && constructorAction === "ignore") return obj;
|
|
7725
|
+
if (protoAction !== "ignore" && constructorAction !== "ignore") {
|
|
7726
|
+
if (suspectProtoRx.test(text) === false && suspectConstructorRx.test(text) === false) return obj;
|
|
7727
|
+
} else if (protoAction !== "ignore" && constructorAction === "ignore") {
|
|
7728
|
+
if (suspectProtoRx.test(text) === false) return obj;
|
|
7729
|
+
} else if (suspectConstructorRx.test(text) === false) return obj;
|
|
7730
|
+
return filter(obj, {
|
|
7731
|
+
protoAction,
|
|
7732
|
+
constructorAction,
|
|
7733
|
+
safe: options && options.safe
|
|
7734
|
+
});
|
|
4684
7735
|
}
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
7736
|
+
/**
|
|
7737
|
+
* @description Scans and filters an object for forbidden prototype properties.
|
|
7738
|
+
* @param {Object} obj - The object being scanned.
|
|
7739
|
+
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
|
|
7740
|
+
* @returns {Object|null} The filtered object, or `null` if safe mode is enabled and issues are found.
|
|
7741
|
+
* @throws {SyntaxError} If a forbidden prototype property is found and `options.protoAction` or
|
|
7742
|
+
* `options.constructorAction` is `'error'`.
|
|
7743
|
+
*/
|
|
7744
|
+
function filter(obj, { protoAction = "error", constructorAction = "error", safe } = {}) {
|
|
7745
|
+
let next = [obj];
|
|
7746
|
+
while (next.length) {
|
|
7747
|
+
const nodes = next;
|
|
7748
|
+
next = [];
|
|
7749
|
+
for (const node of nodes) {
|
|
7750
|
+
if (protoAction !== "ignore" && Object.prototype.hasOwnProperty.call(node, "__proto__")) {
|
|
7751
|
+
if (safe === true) return null;
|
|
7752
|
+
else if (protoAction === "error") throw new SyntaxError("Object contains forbidden prototype property");
|
|
7753
|
+
delete node.__proto__;
|
|
7754
|
+
}
|
|
7755
|
+
if (constructorAction !== "ignore" && Object.prototype.hasOwnProperty.call(node, "constructor") && node.constructor !== null && typeof node.constructor === "object" && Object.prototype.hasOwnProperty.call(node.constructor, "prototype")) {
|
|
7756
|
+
if (safe === true) return null;
|
|
7757
|
+
else if (constructorAction === "error") throw new SyntaxError("Object contains forbidden prototype property");
|
|
7758
|
+
delete node.constructor;
|
|
7759
|
+
}
|
|
7760
|
+
for (const key in node) {
|
|
7761
|
+
const value = node[key];
|
|
7762
|
+
if (value && typeof value === "object") next.push(value);
|
|
7763
|
+
}
|
|
7764
|
+
}
|
|
7765
|
+
}
|
|
7766
|
+
return obj;
|
|
4700
7767
|
}
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
7768
|
+
/**
|
|
7769
|
+
* @description Parses a given JSON-formatted text into an object.
|
|
7770
|
+
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
|
|
7771
|
+
* @param {Function} [reviver] - The `JSON.parse()` optional reviver argument, or options object.
|
|
7772
|
+
* @param {import('./types').ParseOptions} [options] - Optional configuration object.
|
|
7773
|
+
* @returns {*} The parsed object.
|
|
7774
|
+
* @throws {SyntaxError} If the JSON text is malformed or contains forbidden prototype properties
|
|
7775
|
+
* when `options.protoAction` or `options.constructorAction` is `'error'`.
|
|
7776
|
+
*/
|
|
7777
|
+
function parse(text, reviver, options) {
|
|
7778
|
+
const { stackTraceLimit } = Error;
|
|
7779
|
+
Error.stackTraceLimit = 0;
|
|
7780
|
+
try {
|
|
7781
|
+
return _parse(text, reviver, options);
|
|
7782
|
+
} finally {
|
|
7783
|
+
Error.stackTraceLimit = stackTraceLimit;
|
|
7784
|
+
}
|
|
4704
7785
|
}
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
}
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
* In interactive mode: prompts the user and writes results to YAML.
|
|
4722
|
-
* In non-interactive mode: fails with a clear error listing missing fields.
|
|
4723
|
-
*/
|
|
4724
|
-
async function promptMissingFields(options) {
|
|
4725
|
-
const missing = collectMissingPrompts({
|
|
4726
|
-
schema: options.schema,
|
|
4727
|
-
role: options.role,
|
|
4728
|
-
configDir: options.configDir,
|
|
4729
|
-
cliArgs: options.cliArgs
|
|
4730
|
-
});
|
|
4731
|
-
if (missing.length === 0) return {};
|
|
4732
|
-
if (!isInteractive(options.noInteractive)) {
|
|
4733
|
-
const lines = missing.map((m) => {
|
|
4734
|
-
const envHint = findEnvVar(options.schema, m.dotPath);
|
|
4735
|
-
const envStr = envHint ? ` (env: ${envHint})` : "";
|
|
4736
|
-
return ` ${m.dotPath}${envStr}`;
|
|
4737
|
-
});
|
|
4738
|
-
throw new Error(`Missing required configuration:\n${lines.join("\n")}\n\nProvide values via environment variables, config file (${DEFAULT_HOME_DIR$1}/server.yaml),\nor run without --no-interactive to use the interactive setup wizard.`);
|
|
7786
|
+
/**
|
|
7787
|
+
* @description Safely parses a given JSON-formatted text into an object.
|
|
7788
|
+
* @param {string|Buffer} text - The JSON text string or Buffer to parse.
|
|
7789
|
+
* @param {Function} [reviver] - The `JSON.parse()` optional reviver argument.
|
|
7790
|
+
* @returns {*|null|undefined} The parsed object, `null` if security issues found, or `undefined` on parse error.
|
|
7791
|
+
*/
|
|
7792
|
+
function safeParse(text, reviver) {
|
|
7793
|
+
const { stackTraceLimit } = Error;
|
|
7794
|
+
Error.stackTraceLimit = 0;
|
|
7795
|
+
try {
|
|
7796
|
+
return _parse(text, reviver, { safe: true });
|
|
7797
|
+
} catch {
|
|
7798
|
+
return;
|
|
7799
|
+
} finally {
|
|
7800
|
+
Error.stackTraceLimit = stackTraceLimit;
|
|
7801
|
+
}
|
|
4739
7802
|
}
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
7803
|
+
module.exports = parse;
|
|
7804
|
+
module.exports.default = parse;
|
|
7805
|
+
module.exports.parse = parse;
|
|
7806
|
+
module.exports.safeParse = safeParse;
|
|
7807
|
+
module.exports.scan = filter;
|
|
7808
|
+
}));
|
|
7809
|
+
//#endregion
|
|
7810
|
+
//#region ../server/dist/app-BDvasXc4.mjs
|
|
7811
|
+
var import_multipart = /* @__PURE__ */ __toESM((/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
7812
|
+
const Busboy = require_main();
|
|
7813
|
+
const os = __require("node:os");
|
|
7814
|
+
const fp = require_plugin();
|
|
7815
|
+
const { createWriteStream: createWriteStream$1 } = __require("node:fs");
|
|
7816
|
+
const { unlink } = __require("node:fs/promises");
|
|
7817
|
+
const path = __require("node:path");
|
|
7818
|
+
const { generateId } = require_generateId();
|
|
7819
|
+
const createError = require_error();
|
|
7820
|
+
const streamToNull = require_stream_consumer();
|
|
7821
|
+
const deepmergeAll = require_deepmerge()({ all: true });
|
|
7822
|
+
const { PassThrough, Readable } = __require("node:stream");
|
|
7823
|
+
const { pipeline: pump } = __require("node:stream/promises");
|
|
7824
|
+
const secureJSON = require_secure_json_parse();
|
|
7825
|
+
const kMultipart = Symbol("multipart");
|
|
7826
|
+
const kMultipartHandler = Symbol("multipartHandler");
|
|
7827
|
+
const kSavedRequestFilesResult = Symbol("savedRequestFilesResult");
|
|
7828
|
+
const PartsLimitError = createError("FST_PARTS_LIMIT", "reach parts limit", 413);
|
|
7829
|
+
const FilesLimitError = createError("FST_FILES_LIMIT", "reach files limit", 413);
|
|
7830
|
+
const FieldsLimitError = createError("FST_FIELDS_LIMIT", "reach fields limit", 413);
|
|
7831
|
+
const RequestFileTooLargeError = createError("FST_REQ_FILE_TOO_LARGE", "request file too large", 413);
|
|
7832
|
+
const PrototypeViolationError = createError("FST_PROTO_VIOLATION", "prototype property is not allowed as field name", 400);
|
|
7833
|
+
const InvalidMultipartContentTypeError = createError("FST_INVALID_MULTIPART_CONTENT_TYPE", "the request is not multipart", 406);
|
|
7834
|
+
const InvalidJSONFieldError = createError("FST_INVALID_JSON_FIELD_ERROR", "a request field is not a valid JSON as declared by its Content-Type", 406);
|
|
7835
|
+
const FileBufferNotFoundError = createError("FST_FILE_BUFFER_NOT_FOUND", "the file buffer was not found", 500);
|
|
7836
|
+
const NoFormData = createError("FST_NO_FORM_DATA", "FormData is not available", 500);
|
|
7837
|
+
function setMultipart(req, _payload, done) {
|
|
7838
|
+
req[kMultipart] = true;
|
|
7839
|
+
done();
|
|
7840
|
+
}
|
|
7841
|
+
function busboy(options) {
|
|
7842
|
+
try {
|
|
7843
|
+
return new Busboy(options);
|
|
7844
|
+
} catch (error) {
|
|
7845
|
+
const errorEmitter = new PassThrough();
|
|
7846
|
+
process.nextTick(function() {
|
|
7847
|
+
errorEmitter.emit("error", error);
|
|
7848
|
+
});
|
|
7849
|
+
return errorEmitter;
|
|
4747
7850
|
}
|
|
4748
7851
|
}
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
}
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
7852
|
+
function fastifyMultipart(fastify, options, done) {
|
|
7853
|
+
options.limits = {
|
|
7854
|
+
...options.limits,
|
|
7855
|
+
parts: options.limits?.parts || 1e3,
|
|
7856
|
+
fileSize: options.limits?.fileSize || fastify.initialConfig.bodyLimit
|
|
7857
|
+
};
|
|
7858
|
+
const attachFieldsToBody = options.attachFieldsToBody;
|
|
7859
|
+
if (attachFieldsToBody === true || attachFieldsToBody === "keyValues") {
|
|
7860
|
+
if (typeof options.sharedSchemaId === "string" && attachFieldsToBody === true) fastify.addSchema({
|
|
7861
|
+
$id: options.sharedSchemaId,
|
|
7862
|
+
type: "object",
|
|
7863
|
+
properties: {
|
|
7864
|
+
fieldname: { type: "string" },
|
|
7865
|
+
encoding: { type: "string" },
|
|
7866
|
+
filename: { type: "string" },
|
|
7867
|
+
mimetype: { type: "string" }
|
|
7868
|
+
}
|
|
7869
|
+
});
|
|
7870
|
+
fastify.addHook("preValidation", async function(req) {
|
|
7871
|
+
if (!req.isMultipart()) return;
|
|
7872
|
+
for await (const part of req.parts(req.routeOptions.config.multipartOptions)) {
|
|
7873
|
+
req.body = part.fields;
|
|
7874
|
+
if (part.file) if (options.onFile) await options.onFile.call(req, part);
|
|
7875
|
+
else await part.toBuffer();
|
|
7876
|
+
}
|
|
7877
|
+
if (attachFieldsToBody === "keyValues") {
|
|
7878
|
+
const body = {};
|
|
7879
|
+
if (req.body) {
|
|
7880
|
+
const reqBodyKeys = Object.keys(req.body);
|
|
7881
|
+
for (let i = 0; i < reqBodyKeys.length; ++i) {
|
|
7882
|
+
const key = reqBodyKeys[i];
|
|
7883
|
+
const field = req.body[key];
|
|
7884
|
+
if (field.value !== void 0) body[key] = field.value;
|
|
7885
|
+
else if (field._buf) body[key] = field._buf;
|
|
7886
|
+
else if (Array.isArray(field)) {
|
|
7887
|
+
const items = [];
|
|
7888
|
+
for (let i = 0; i < field.length; ++i) {
|
|
7889
|
+
const item = field[i];
|
|
7890
|
+
if (item.value !== void 0) items.push(item.value);
|
|
7891
|
+
else if (item._buf) items.push(item._buf);
|
|
7892
|
+
}
|
|
7893
|
+
if (items.length) body[key] = items;
|
|
7894
|
+
}
|
|
7895
|
+
}
|
|
7896
|
+
}
|
|
7897
|
+
req.body = body;
|
|
7898
|
+
}
|
|
7899
|
+
});
|
|
7900
|
+
/* istanbul ignore next */
|
|
7901
|
+
if (globalThis.FormData && !fastify.hasRequestDecorator("formData")) fastify.decorateRequest("formData", async function() {
|
|
7902
|
+
const formData = new FormData();
|
|
7903
|
+
for (const key in this.body) {
|
|
7904
|
+
const value = this.body[key];
|
|
7905
|
+
if (Array.isArray(value)) for (const item of value) await append(key, item);
|
|
7906
|
+
else await append(key, value);
|
|
7907
|
+
}
|
|
7908
|
+
async function append(key, entry) {
|
|
7909
|
+
/* c8 ignore next: Buffer.isBuffer is not covered and causing `npm test` to fail */
|
|
7910
|
+
if (entry.type === "file" || attachFieldsToBody === "keyValues" && Buffer.isBuffer(entry)) formData.append(key, new Blob([await entry.toBuffer()], { type: entry.mimetype }), entry.filename);
|
|
7911
|
+
else formData.append(key, entry.value);
|
|
7912
|
+
}
|
|
7913
|
+
return formData;
|
|
7914
|
+
});
|
|
7915
|
+
}
|
|
7916
|
+
/* istanbul ignore next */
|
|
7917
|
+
if (!fastify.hasRequestDecorator("formData")) fastify.decorateRequest("formData", async function() {
|
|
7918
|
+
/* c8 ignore next: Next line is not covered and causing `npm test` to fail */
|
|
7919
|
+
throw new NoFormData();
|
|
4775
7920
|
});
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
7921
|
+
const defaultThrowFileSizeLimit = typeof options.throwFileSizeLimit === "boolean" ? options.throwFileSizeLimit : true;
|
|
7922
|
+
fastify.decorate("multipartErrors", {
|
|
7923
|
+
PartsLimitError,
|
|
7924
|
+
FilesLimitError,
|
|
7925
|
+
FieldsLimitError,
|
|
7926
|
+
PrototypeViolationError,
|
|
7927
|
+
InvalidMultipartContentTypeError,
|
|
7928
|
+
RequestFileTooLargeError,
|
|
7929
|
+
FileBufferNotFoundError
|
|
4780
7930
|
});
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
7931
|
+
fastify.addContentTypeParser("multipart/form-data", setMultipart);
|
|
7932
|
+
fastify.decorateRequest(kMultipart, false);
|
|
7933
|
+
fastify.decorateRequest(kMultipartHandler, handleMultipart);
|
|
7934
|
+
fastify.decorateRequest(kSavedRequestFilesResult, null);
|
|
7935
|
+
fastify.decorateRequest("parts", getMultipartIterator);
|
|
7936
|
+
fastify.decorateRequest("isMultipart", isMultipart);
|
|
7937
|
+
fastify.decorateRequest("tmpUploads", null);
|
|
7938
|
+
fastify.decorateRequest("savedRequestFiles", null);
|
|
7939
|
+
fastify.decorateRequest("file", getMultipartFile);
|
|
7940
|
+
fastify.decorateRequest("files", getMultipartFiles);
|
|
7941
|
+
fastify.decorateRequest("saveRequestFiles", saveRequestFiles);
|
|
7942
|
+
fastify.decorateRequest("cleanRequestFiles", cleanRequestFiles);
|
|
7943
|
+
fastify.addHook("onResponse", async (request) => {
|
|
7944
|
+
await request.cleanRequestFiles();
|
|
7945
|
+
});
|
|
7946
|
+
function isMultipart() {
|
|
7947
|
+
return this[kMultipart];
|
|
7948
|
+
}
|
|
7949
|
+
function handleMultipart(opts = {}) {
|
|
7950
|
+
if (!this.isMultipart()) throw new InvalidMultipartContentTypeError();
|
|
7951
|
+
this.log.debug("starting multipart parsing");
|
|
7952
|
+
let values = [];
|
|
7953
|
+
let pendingHandler = null;
|
|
7954
|
+
const ch = (val) => {
|
|
7955
|
+
if (pendingHandler) {
|
|
7956
|
+
pendingHandler(val);
|
|
7957
|
+
pendingHandler = null;
|
|
7958
|
+
} else values.push(val);
|
|
7959
|
+
};
|
|
7960
|
+
const handle = (handler) => {
|
|
7961
|
+
if (values.length > 0) {
|
|
7962
|
+
const value = values[0];
|
|
7963
|
+
values = values.slice(1);
|
|
7964
|
+
handler(value);
|
|
7965
|
+
} else pendingHandler = handler;
|
|
7966
|
+
};
|
|
7967
|
+
const parts = () => {
|
|
7968
|
+
return new Promise((resolve, reject) => {
|
|
7969
|
+
handle((val) => {
|
|
7970
|
+
if (val instanceof Error) if (val.message === "Unexpected end of multipart data") resolve(null);
|
|
7971
|
+
else reject(val);
|
|
7972
|
+
else resolve(val);
|
|
7973
|
+
});
|
|
7974
|
+
});
|
|
7975
|
+
};
|
|
7976
|
+
const body = {};
|
|
7977
|
+
let lastError = null;
|
|
7978
|
+
let currentFile = null;
|
|
7979
|
+
const request = this.raw;
|
|
7980
|
+
const busboyOptions = deepmergeAll({ headers: request.headers }, options, opts);
|
|
7981
|
+
this.log.trace({ busboyOptions }, "Providing options to busboy");
|
|
7982
|
+
const bb = busboy(busboyOptions);
|
|
7983
|
+
request.on("close", cleanup);
|
|
7984
|
+
request.on("error", cleanup);
|
|
7985
|
+
bb.on("field", onField).on("file", onFile).on("end", cleanup).on("finish", cleanup).on("close", cleanup).on("error", cleanup);
|
|
7986
|
+
bb.on("partsLimit", function() {
|
|
7987
|
+
const err = new PartsLimitError();
|
|
7988
|
+
onError(err);
|
|
7989
|
+
process.nextTick(() => cleanup(err));
|
|
7990
|
+
});
|
|
7991
|
+
bb.on("filesLimit", function() {
|
|
7992
|
+
const err = new FilesLimitError();
|
|
7993
|
+
onError(err);
|
|
7994
|
+
process.nextTick(() => cleanup(err));
|
|
7995
|
+
});
|
|
7996
|
+
bb.on("fieldsLimit", function() {
|
|
7997
|
+
const err = new FieldsLimitError();
|
|
7998
|
+
onError(err);
|
|
7999
|
+
process.nextTick(() => cleanup(err));
|
|
8000
|
+
});
|
|
8001
|
+
request.pipe(bb);
|
|
8002
|
+
function onField(name, fieldValue, fieldnameTruncated, valueTruncated, encoding, contentType) {
|
|
8003
|
+
if (name in Object.prototype) {
|
|
8004
|
+
onError(new PrototypeViolationError());
|
|
8005
|
+
return;
|
|
8006
|
+
}
|
|
8007
|
+
if (contentType.startsWith("application/json")) {
|
|
8008
|
+
if (valueTruncated) {
|
|
8009
|
+
onError(new InvalidJSONFieldError());
|
|
8010
|
+
return;
|
|
8011
|
+
}
|
|
8012
|
+
try {
|
|
8013
|
+
fieldValue = secureJSON.parse(fieldValue);
|
|
8014
|
+
contentType = "application/json";
|
|
8015
|
+
} catch {
|
|
8016
|
+
onError(new InvalidJSONFieldError());
|
|
8017
|
+
return;
|
|
8018
|
+
}
|
|
8019
|
+
}
|
|
8020
|
+
const value = {
|
|
8021
|
+
type: "field",
|
|
8022
|
+
fieldname: name,
|
|
8023
|
+
mimetype: contentType,
|
|
8024
|
+
encoding,
|
|
8025
|
+
value: fieldValue,
|
|
8026
|
+
fieldnameTruncated,
|
|
8027
|
+
valueTruncated,
|
|
8028
|
+
fields: body
|
|
8029
|
+
};
|
|
8030
|
+
if (body[name] === void 0) body[name] = value;
|
|
8031
|
+
else if (Array.isArray(body[name])) body[name].push(value);
|
|
8032
|
+
else body[name] = [body[name], value];
|
|
8033
|
+
ch(value);
|
|
8034
|
+
}
|
|
8035
|
+
function onFile(name, file, filename, encoding, mimetype) {
|
|
8036
|
+
if (name in Object.prototype) {
|
|
8037
|
+
streamToNull(file).catch(() => {});
|
|
8038
|
+
onError(new PrototypeViolationError());
|
|
8039
|
+
return;
|
|
8040
|
+
}
|
|
8041
|
+
const throwFileSizeLimit = typeof opts.throwFileSizeLimit === "boolean" ? opts.throwFileSizeLimit : defaultThrowFileSizeLimit;
|
|
8042
|
+
const value = {
|
|
8043
|
+
type: "file",
|
|
8044
|
+
fieldname: name,
|
|
8045
|
+
filename,
|
|
8046
|
+
encoding,
|
|
8047
|
+
mimetype,
|
|
8048
|
+
file,
|
|
8049
|
+
fields: body,
|
|
8050
|
+
_buf: null,
|
|
8051
|
+
async toBuffer() {
|
|
8052
|
+
if (this._buf) return this._buf;
|
|
8053
|
+
const fileChunks = [];
|
|
8054
|
+
let err;
|
|
8055
|
+
for await (const chunk of this.file) {
|
|
8056
|
+
fileChunks.push(chunk);
|
|
8057
|
+
if (throwFileSizeLimit && this.file.truncated) {
|
|
8058
|
+
err = new RequestFileTooLargeError();
|
|
8059
|
+
err.part = this;
|
|
8060
|
+
onError(err);
|
|
8061
|
+
fileChunks.length = 0;
|
|
8062
|
+
}
|
|
8063
|
+
}
|
|
8064
|
+
if (err) throw err;
|
|
8065
|
+
this._buf = Buffer.concat(fileChunks);
|
|
8066
|
+
return this._buf;
|
|
8067
|
+
}
|
|
8068
|
+
};
|
|
8069
|
+
file.on("error", function(err) {
|
|
8070
|
+
if (err.message && err.message.includes("terminated early")) onError(err);
|
|
8071
|
+
});
|
|
8072
|
+
if (throwFileSizeLimit) file.on("limit", function() {
|
|
8073
|
+
const err = new RequestFileTooLargeError();
|
|
8074
|
+
err.part = value;
|
|
8075
|
+
onError(err);
|
|
8076
|
+
});
|
|
8077
|
+
if (body[name] === void 0) body[name] = value;
|
|
8078
|
+
else if (Array.isArray(body[name])) body[name].push(value);
|
|
8079
|
+
else body[name] = [body[name], value];
|
|
8080
|
+
currentFile = file;
|
|
8081
|
+
ch(value);
|
|
8082
|
+
}
|
|
8083
|
+
function onError(err) {
|
|
8084
|
+
lastError = err;
|
|
8085
|
+
currentFile = null;
|
|
8086
|
+
}
|
|
8087
|
+
function cleanup(err) {
|
|
8088
|
+
request.unpipe(bb);
|
|
8089
|
+
if ((err || request.aborted) && currentFile) {
|
|
8090
|
+
currentFile.destroy();
|
|
8091
|
+
currentFile = null;
|
|
8092
|
+
}
|
|
8093
|
+
ch(err || lastError || null);
|
|
8094
|
+
}
|
|
8095
|
+
return parts;
|
|
8096
|
+
}
|
|
8097
|
+
async function saveRequestFiles(options) {
|
|
8098
|
+
if (this[kSavedRequestFilesResult]) return this[kSavedRequestFilesResult];
|
|
8099
|
+
let parts;
|
|
8100
|
+
let values = {};
|
|
8101
|
+
if (attachFieldsToBody === true || attachFieldsToBody === "keyValues") {
|
|
8102
|
+
parts = this.body ? filesFromFields.call(this, this.body) : [];
|
|
8103
|
+
values = this.body || {};
|
|
8104
|
+
} else parts = this.parts(options);
|
|
8105
|
+
this.savedRequestFiles = [];
|
|
8106
|
+
const tmpdir = options?.tmpdir || os.tmpdir();
|
|
8107
|
+
this.tmpUploads = [];
|
|
8108
|
+
let i = 0;
|
|
8109
|
+
for await (const part of parts) {
|
|
8110
|
+
values = part.fields;
|
|
8111
|
+
if (!part.file) continue;
|
|
8112
|
+
const filepath = path.join(tmpdir, generateId() + path.extname(part.filename || "file" + i++));
|
|
8113
|
+
const target = createWriteStream$1(filepath);
|
|
8114
|
+
try {
|
|
8115
|
+
this.tmpUploads.push(filepath);
|
|
8116
|
+
await pump(part.file, target);
|
|
8117
|
+
this.savedRequestFiles.push({
|
|
8118
|
+
...part,
|
|
8119
|
+
filepath
|
|
8120
|
+
});
|
|
8121
|
+
} catch (err) {
|
|
8122
|
+
target.destroy();
|
|
8123
|
+
await this.cleanRequestFiles();
|
|
8124
|
+
this.log.error({ err }, "save request file");
|
|
8125
|
+
throw err;
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
this[kSavedRequestFilesResult] = {
|
|
8129
|
+
files: this.savedRequestFiles,
|
|
8130
|
+
values
|
|
8131
|
+
};
|
|
8132
|
+
return this[kSavedRequestFilesResult];
|
|
8133
|
+
}
|
|
8134
|
+
function* filesFromFields(container) {
|
|
8135
|
+
try {
|
|
8136
|
+
const fields = Array.isArray(container) ? container : Object.values(container);
|
|
8137
|
+
for (let i = 0; i < fields.length; ++i) {
|
|
8138
|
+
const field = fields[i];
|
|
8139
|
+
if (Array.isArray(field)) for (const subField of filesFromFields.call(this, field)) yield subField;
|
|
8140
|
+
if (!field.file) continue;
|
|
8141
|
+
if (!field._buf) throw new FileBufferNotFoundError();
|
|
8142
|
+
field.file = Readable.from(field._buf);
|
|
8143
|
+
yield field;
|
|
8144
|
+
}
|
|
8145
|
+
} catch (err) {
|
|
8146
|
+
this.log.error({ err }, "save request file failed");
|
|
8147
|
+
throw err;
|
|
8148
|
+
}
|
|
8149
|
+
}
|
|
8150
|
+
async function cleanRequestFiles() {
|
|
8151
|
+
if (!this.tmpUploads) return;
|
|
8152
|
+
for (let i = 0; i < this.tmpUploads.length; ++i) {
|
|
8153
|
+
const filepath = this.tmpUploads[i];
|
|
8154
|
+
try {
|
|
8155
|
+
await unlink(filepath);
|
|
8156
|
+
} /* c8 ignore start */catch (error) {
|
|
8157
|
+
this.log.error(error, "Could not delete file");
|
|
8158
|
+
}
|
|
8159
|
+
}
|
|
8160
|
+
}
|
|
8161
|
+
async function getMultipartFile(options) {
|
|
8162
|
+
const parts = this[kMultipartHandler](options);
|
|
8163
|
+
let part;
|
|
8164
|
+
while ((part = await parts()) != null) if (part.file) return part;
|
|
8165
|
+
}
|
|
8166
|
+
async function* getMultipartFiles(options) {
|
|
8167
|
+
const parts = this[kMultipartHandler](options);
|
|
8168
|
+
let part;
|
|
8169
|
+
while ((part = await parts()) != null) if (part.file) yield part;
|
|
8170
|
+
}
|
|
8171
|
+
async function* getMultipartIterator(options) {
|
|
8172
|
+
const parts = this[kMultipartHandler](options);
|
|
8173
|
+
let part;
|
|
8174
|
+
while ((part = await parts()) != null) yield part;
|
|
8175
|
+
}
|
|
8176
|
+
done();
|
|
4802
8177
|
}
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
|
|
4810
|
-
|
|
4811
|
-
|
|
8178
|
+
/**
|
|
8179
|
+
* Adds a new type `isFile` to help @fastify/swagger generate the correct schema.
|
|
8180
|
+
*/
|
|
8181
|
+
function ajvFilePlugin(ajv) {
|
|
8182
|
+
return ajv.addKeyword({
|
|
8183
|
+
keyword: "isFile",
|
|
8184
|
+
compile: (_schema, parent) => {
|
|
8185
|
+
parent.type = "string";
|
|
8186
|
+
parent.format = "binary";
|
|
8187
|
+
delete parent.isFile;
|
|
8188
|
+
return (field) => !!field.file;
|
|
8189
|
+
},
|
|
8190
|
+
error: { message: "should be a file" }
|
|
8191
|
+
});
|
|
4812
8192
|
}
|
|
4813
|
-
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
8193
|
+
/**
|
|
8194
|
+
* These export configurations enable JS and TS developers
|
|
8195
|
+
* to consumer fastify in whatever way best suits their needs.
|
|
8196
|
+
*/
|
|
8197
|
+
module.exports = fp(fastifyMultipart, {
|
|
8198
|
+
fastify: "5.x",
|
|
8199
|
+
name: "@fastify/multipart"
|
|
8200
|
+
});
|
|
8201
|
+
module.exports.default = fastifyMultipart;
|
|
8202
|
+
module.exports.fastifyMultipart = fastifyMultipart;
|
|
8203
|
+
module.exports.ajvFilePlugin = ajvFilePlugin;
|
|
8204
|
+
})))(), 1);
|
|
4818
8205
|
var __defProp = Object.defineProperty;
|
|
4819
8206
|
var __exportAll = (all, no_symbols) => {
|
|
4820
8207
|
let target = {};
|
|
@@ -8819,6 +12206,99 @@ async function adminTaskRoutes(app) {
|
|
|
8819
12206
|
return getTaskHealth(app.db, request.params.taskId);
|
|
8820
12207
|
});
|
|
8821
12208
|
}
|
|
12209
|
+
const UPLOADS_DIR = join(DEFAULT_DATA_DIR$1, "uploads");
|
|
12210
|
+
const MAX_FILE_SIZE = 10 * 1024 * 1024;
|
|
12211
|
+
const ALLOWED_MIME_TYPES = new Set([
|
|
12212
|
+
"image/png",
|
|
12213
|
+
"image/jpeg",
|
|
12214
|
+
"image/gif",
|
|
12215
|
+
"image/webp",
|
|
12216
|
+
"image/svg+xml"
|
|
12217
|
+
]);
|
|
12218
|
+
function ensureUploadsDir() {
|
|
12219
|
+
if (!existsSync(UPLOADS_DIR)) mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
12220
|
+
}
|
|
12221
|
+
async function adminUploadRoutes(app) {
|
|
12222
|
+
ensureUploadsDir();
|
|
12223
|
+
/** POST /admin/uploads — upload a file, returns URL */
|
|
12224
|
+
app.post("/", async (request, reply) => {
|
|
12225
|
+
requireMember(request);
|
|
12226
|
+
const data = await request.file();
|
|
12227
|
+
if (!data) return reply.status(400).send({ error: "No file provided" });
|
|
12228
|
+
const mimeType = data.mimetype;
|
|
12229
|
+
if (!ALLOWED_MIME_TYPES.has(mimeType)) return reply.status(400).send({ error: `Unsupported file type: ${mimeType}. Allowed: ${[...ALLOWED_MIME_TYPES].join(", ")}` });
|
|
12230
|
+
const ext = extname(data.filename) || mimeExtension(mimeType);
|
|
12231
|
+
const uniqueName = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[-:T]/g, "").slice(0, 14)}_${randomUUID().slice(0, 8)}${ext}`;
|
|
12232
|
+
const filePath = join(UPLOADS_DIR, uniqueName);
|
|
12233
|
+
let totalSize = 0;
|
|
12234
|
+
const writeStream = createWriteStream(filePath);
|
|
12235
|
+
try {
|
|
12236
|
+
const fileStream = data.file;
|
|
12237
|
+
for await (const chunk of fileStream) {
|
|
12238
|
+
totalSize += chunk.length;
|
|
12239
|
+
if (totalSize > MAX_FILE_SIZE) {
|
|
12240
|
+
writeStream.destroy();
|
|
12241
|
+
try {
|
|
12242
|
+
unlinkSync(filePath);
|
|
12243
|
+
} catch {}
|
|
12244
|
+
return reply.status(400).send({ error: `File too large. Maximum size: ${MAX_FILE_SIZE / 1024 / 1024}MB` });
|
|
12245
|
+
}
|
|
12246
|
+
writeStream.write(chunk);
|
|
12247
|
+
}
|
|
12248
|
+
writeStream.end();
|
|
12249
|
+
await new Promise((resolve, reject) => {
|
|
12250
|
+
writeStream.on("finish", resolve);
|
|
12251
|
+
writeStream.on("error", reject);
|
|
12252
|
+
});
|
|
12253
|
+
} catch (err) {
|
|
12254
|
+
writeStream.destroy();
|
|
12255
|
+
throw err;
|
|
12256
|
+
}
|
|
12257
|
+
const url = `/api/v1/uploads/${uniqueName}`;
|
|
12258
|
+
return reply.status(201).send({
|
|
12259
|
+
url,
|
|
12260
|
+
filename: data.filename,
|
|
12261
|
+
storedName: uniqueName,
|
|
12262
|
+
mimeType,
|
|
12263
|
+
size: totalSize
|
|
12264
|
+
});
|
|
12265
|
+
});
|
|
12266
|
+
}
|
|
12267
|
+
/** Public routes — GET uploaded files (URL contains random UUID, not guessable) */
|
|
12268
|
+
async function publicUploadRoutes(app) {
|
|
12269
|
+
ensureUploadsDir();
|
|
12270
|
+
/** GET /uploads/:filename — serve uploaded file without auth */
|
|
12271
|
+
app.get("/:filename", async (request, reply) => {
|
|
12272
|
+
const { filename } = request.params;
|
|
12273
|
+
if (filename.includes("/") || filename.includes("..")) return reply.status(400).send({ error: "Invalid filename" });
|
|
12274
|
+
const filePath = join(UPLOADS_DIR, filename);
|
|
12275
|
+
if (!existsSync(filePath)) return reply.status(404).send({ error: "File not found" });
|
|
12276
|
+
const contentType = extensionToMime(extname(filename).toLowerCase()) ?? "application/octet-stream";
|
|
12277
|
+
const stream = createReadStream(filePath);
|
|
12278
|
+
return reply.type(contentType).send(stream);
|
|
12279
|
+
});
|
|
12280
|
+
}
|
|
12281
|
+
function mimeExtension(mime) {
|
|
12282
|
+
switch (mime) {
|
|
12283
|
+
case "image/png": return ".png";
|
|
12284
|
+
case "image/jpeg": return ".jpg";
|
|
12285
|
+
case "image/gif": return ".gif";
|
|
12286
|
+
case "image/webp": return ".webp";
|
|
12287
|
+
case "image/svg+xml": return ".svg";
|
|
12288
|
+
default: return "";
|
|
12289
|
+
}
|
|
12290
|
+
}
|
|
12291
|
+
function extensionToMime(ext) {
|
|
12292
|
+
switch (ext) {
|
|
12293
|
+
case ".png": return "image/png";
|
|
12294
|
+
case ".jpg":
|
|
12295
|
+
case ".jpeg": return "image/jpeg";
|
|
12296
|
+
case ".gif": return "image/gif";
|
|
12297
|
+
case ".webp": return "image/webp";
|
|
12298
|
+
case ".svg": return "image/svg+xml";
|
|
12299
|
+
default: return null;
|
|
12300
|
+
}
|
|
12301
|
+
}
|
|
8822
12302
|
async function loadVisibleAgentIds(db, organizationId, memberId) {
|
|
8823
12303
|
const rows = await db.select({ id: agents.uuid }).from(agents).where(and(eq(agents.organizationId, organizationId), ne(agents.status, AGENT_STATUSES.DELETED), or(eq(agents.visibility, AGENT_VISIBILITY.ORGANIZATION), eq(agents.managerId, memberId))));
|
|
8824
12304
|
return new Set(rows.map((r) => r.id));
|
|
@@ -9541,6 +13021,11 @@ function clientWsRoutes(notifier, instanceId) {
|
|
|
9541
13021
|
clearTimeout(authTimeout);
|
|
9542
13022
|
scheduleAuthExpiry(claims.exp);
|
|
9543
13023
|
socket.send(JSON.stringify({ type: "auth:ok" }));
|
|
13024
|
+
socket.send(JSON.stringify({
|
|
13025
|
+
type: "server:welcome",
|
|
13026
|
+
serverCommandVersion: app.commandVersion,
|
|
13027
|
+
serverTimeMs: Date.now()
|
|
13028
|
+
}));
|
|
9544
13029
|
} catch (err) {
|
|
9545
13030
|
const message = err instanceof Error ? err.message : "auth failure";
|
|
9546
13031
|
socket.send(JSON.stringify({
|
|
@@ -11898,6 +15383,21 @@ function createPulseAggregator(options) {
|
|
|
11898
15383
|
ingest
|
|
11899
15384
|
};
|
|
11900
15385
|
}
|
|
15386
|
+
/**
|
|
15387
|
+
* Resolve the Command-package version advertised to clients. Prefers the
|
|
15388
|
+
* value the Command CLI explicitly injected; otherwise falls back to the
|
|
15389
|
+
* server workspace's own package.json (dev mode, `pnpm --filter … dev`).
|
|
15390
|
+
* Returning a string (rather than undefined) keeps the welcome frame well-
|
|
15391
|
+
* formed — the client treats the value advisorily.
|
|
15392
|
+
*/
|
|
15393
|
+
function resolveCommandVersion(injected) {
|
|
15394
|
+
if (injected && injected.trim().length > 0) return injected;
|
|
15395
|
+
try {
|
|
15396
|
+
const pkg = createRequire(import.meta.url)("../package.json");
|
|
15397
|
+
if (typeof pkg.version === "string" && pkg.version.length > 0) return pkg.version;
|
|
15398
|
+
} catch {}
|
|
15399
|
+
return "0.0.0";
|
|
15400
|
+
}
|
|
11901
15401
|
async function buildApp(config) {
|
|
11902
15402
|
applyLoggerConfig({
|
|
11903
15403
|
level: config.observability.logging.level,
|
|
@@ -11911,9 +15411,13 @@ async function buildApp(config) {
|
|
|
11911
15411
|
const db = connectDatabase(config.database.url);
|
|
11912
15412
|
app.decorate("db", db);
|
|
11913
15413
|
app.decorate("config", config);
|
|
15414
|
+
const commandVersion = resolveCommandVersion(config.commandVersion);
|
|
15415
|
+
app.decorate("commandVersion", commandVersion);
|
|
15416
|
+
app.log.info({ commandVersion }, "Hub server advertising command version");
|
|
11914
15417
|
const listenClient = postgres(config.database.url, { max: 1 });
|
|
11915
15418
|
const notifier = createNotifier(listenClient);
|
|
11916
15419
|
await app.register(websocket);
|
|
15420
|
+
await app.register(import_multipart.default, { limits: { fileSize: 10 * 1024 * 1024 } });
|
|
11917
15421
|
const corsOrigin = config.cors?.origin;
|
|
11918
15422
|
const isDev = process.env.NODE_ENV !== "production";
|
|
11919
15423
|
await app.register(cors, {
|
|
@@ -11952,6 +15456,7 @@ async function buildApp(config) {
|
|
|
11952
15456
|
await api.register(authRoutes, { prefix: "/auth" });
|
|
11953
15457
|
await api.register(contextTreeInfoRoutes, { prefix: "/context-tree" });
|
|
11954
15458
|
await api.register(bootstrapConfigRoutes, { prefix: "/bootstrap" });
|
|
15459
|
+
await api.register(publicUploadRoutes, { prefix: "/uploads" });
|
|
11955
15460
|
await api.register(async (adminApp) => {
|
|
11956
15461
|
adminApp.addHook("onRequest", memberAuth);
|
|
11957
15462
|
await adminApp.register(adminAgentRoutes);
|
|
@@ -11998,6 +15503,10 @@ async function buildApp(config) {
|
|
|
11998
15503
|
adminApp.addHook("onRequest", memberAuth);
|
|
11999
15504
|
await adminApp.register(adminChatRoutes);
|
|
12000
15505
|
}, { prefix: "/admin/chats" });
|
|
15506
|
+
await api.register(async (adminApp) => {
|
|
15507
|
+
adminApp.addHook("onRequest", memberAuth);
|
|
15508
|
+
await adminApp.register(adminUploadRoutes);
|
|
15509
|
+
}, { prefix: "/admin/uploads" });
|
|
12001
15510
|
await api.register(async (adminApp) => {
|
|
12002
15511
|
adminApp.addHook("onRequest", memberAuth);
|
|
12003
15512
|
await adminApp.register(adminClientRoutes);
|
|
@@ -12101,6 +15610,10 @@ async function buildApp(config) {
|
|
|
12101
15610
|
return app;
|
|
12102
15611
|
}
|
|
12103
15612
|
//#endregion
|
|
15613
|
+
//#region src/core/version.ts
|
|
15614
|
+
const pkg = createRequire(import.meta.url)("../../package.json");
|
|
15615
|
+
const COMMAND_VERSION = typeof pkg.version === "string" && pkg.version.length > 0 ? pkg.version : "0.0.0";
|
|
15616
|
+
//#endregion
|
|
12104
15617
|
//#region src/core/server.ts
|
|
12105
15618
|
/**
|
|
12106
15619
|
* Full server start orchestration:
|
|
@@ -12113,7 +15626,7 @@ async function buildApp(config) {
|
|
|
12113
15626
|
* 7. Start Fastify server
|
|
12114
15627
|
*/
|
|
12115
15628
|
async function startServer(options) {
|
|
12116
|
-
process.stderr.write(
|
|
15629
|
+
process.stderr.write(`\n First Tree Hub v${COMMAND_VERSION}\n\n`);
|
|
12117
15630
|
const cliArgs = {};
|
|
12118
15631
|
if (options.port !== void 0) cliArgs.server = { port: options.port };
|
|
12119
15632
|
if (options.host !== void 0) cliArgs.server = {
|
|
@@ -12152,7 +15665,8 @@ async function startServer(options) {
|
|
|
12152
15665
|
const config = {
|
|
12153
15666
|
...serverConfig,
|
|
12154
15667
|
webDistPath: webDistPath ?? void 0,
|
|
12155
|
-
instanceId: `srv_${randomUUID().slice(0, 8)}
|
|
15668
|
+
instanceId: `srv_${randomUUID().slice(0, 8)}`,
|
|
15669
|
+
commandVersion: COMMAND_VERSION
|
|
12156
15670
|
};
|
|
12157
15671
|
const { initTelemetry, shutdownTelemetry } = await import("./observability-Xi-sEZI7.mjs");
|
|
12158
15672
|
await initTelemetry(serverConfig.observability.tracing, config.instanceId);
|
|
@@ -12176,419 +15690,208 @@ async function startServer(options) {
|
|
|
12176
15690
|
status("Server", `running at http://${config.server.host}:${config.server.port}`);
|
|
12177
15691
|
blank();
|
|
12178
15692
|
process.stderr.write(" Open the URL above in your browser to get started.\n");
|
|
12179
|
-
process.stderr.write(" Press Ctrl+C to stop.\n\n");
|
|
12180
|
-
}
|
|
12181
|
-
/**
|
|
12182
|
-
* Resolve web dist path.
|
|
12183
|
-
* 1. npm install: embedded at dist/web/ (relative to the built CLI)
|
|
12184
|
-
* 2. Monorepo dev: resolved from @first-tree-hub/web package (builds if needed)
|
|
12185
|
-
*/
|
|
12186
|
-
function resolveWebDist() {
|
|
12187
|
-
const embeddedPath = join(dirname(fileURLToPath(import.meta.url)), "..", "web");
|
|
12188
|
-
if (existsSync(join(embeddedPath, "index.html"))) return embeddedPath;
|
|
12189
|
-
try {
|
|
12190
|
-
const webDir = dirname(fileURLToPath(import.meta.resolve("@first-tree-hub/web/package.json")));
|
|
12191
|
-
const distPath = join(webDir, "dist");
|
|
12192
|
-
const indexPath = join(distPath, "index.html");
|
|
12193
|
-
if (existsSync(indexPath)) return distPath;
|
|
12194
|
-
status("Web", "building...");
|
|
12195
|
-
execSync("pnpm --filter @first-tree-hub/web build", {
|
|
12196
|
-
stdio: [
|
|
12197
|
-
"ignore",
|
|
12198
|
-
"ignore",
|
|
12199
|
-
"pipe"
|
|
12200
|
-
],
|
|
12201
|
-
cwd: join(webDir, "../..")
|
|
12202
|
-
});
|
|
12203
|
-
if (existsSync(indexPath)) return distPath;
|
|
12204
|
-
} catch {}
|
|
12205
|
-
}
|
|
12206
|
-
//#endregion
|
|
12207
|
-
//#region src/core/service-install.ts
|
|
12208
|
-
/**
|
|
12209
|
-
* Run a subprocess capturing stderr so failures surface a meaningful error
|
|
12210
|
-
* instead of Node's opaque "Command failed". Used for launchctl/systemctl —
|
|
12211
|
-
* anywhere the stderr message is diagnostically crucial.
|
|
12212
|
-
*/
|
|
12213
|
-
function runCapture(program, args, timeoutMs) {
|
|
12214
|
-
const res = spawnSync(program, args, {
|
|
12215
|
-
encoding: "utf-8",
|
|
12216
|
-
timeout: timeoutMs,
|
|
12217
|
-
stdio: [
|
|
12218
|
-
"ignore",
|
|
12219
|
-
"pipe",
|
|
12220
|
-
"pipe"
|
|
12221
|
-
]
|
|
12222
|
-
});
|
|
12223
|
-
if (res.status === 0) return { ok: true };
|
|
12224
|
-
return {
|
|
12225
|
-
ok: false,
|
|
12226
|
-
stderr: (res.stderr ?? "").trim(),
|
|
12227
|
-
code: res.status
|
|
12228
|
-
};
|
|
12229
|
-
}
|
|
12230
|
-
function sleepSync(ms) {
|
|
12231
|
-
const shared = new Int32Array(new SharedArrayBuffer(4));
|
|
12232
|
-
Atomics.wait(shared, 0, 0, ms);
|
|
12233
|
-
}
|
|
12234
|
-
const LAUNCHD_LABEL = "dev.first-tree-hub.client";
|
|
12235
|
-
const SYSTEMD_UNIT = "first-tree-hub-client.service";
|
|
12236
|
-
const LOG_DIR = join(DEFAULT_HOME_DIR$1, "logs");
|
|
12237
|
-
function whichBin(name) {
|
|
12238
|
-
try {
|
|
12239
|
-
return execFileSync(process.platform === "win32" ? "where" : "which", [name], {
|
|
12240
|
-
encoding: "utf-8",
|
|
12241
|
-
timeout: 3e3
|
|
12242
|
-
}).split(/\r?\n/).map((s) => s.trim()).filter(Boolean)[0] ?? null;
|
|
12243
|
-
} catch {
|
|
12244
|
-
return null;
|
|
12245
|
-
}
|
|
12246
|
-
}
|
|
12247
|
-
/**
|
|
12248
|
-
* Resolve how the service should launch the CLI.
|
|
12249
|
-
*
|
|
12250
|
-
* Prefers the installed `first-tree-hub` bin on PATH (usually a shim under
|
|
12251
|
-
* /usr/local/bin or ~/.npm-global/bin). Falls back to invoking the current
|
|
12252
|
-
* Node interpreter against the running script (handles `pnpm dev`, tsx, and
|
|
12253
|
-
* dev-only global installs).
|
|
12254
|
-
*/
|
|
12255
|
-
function resolveCliInvocation() {
|
|
12256
|
-
const bin = whichBin("first-tree-hub");
|
|
12257
|
-
if (bin && isAbsolute(bin)) try {
|
|
12258
|
-
return {
|
|
12259
|
-
kind: "bin",
|
|
12260
|
-
program: realpathSync(bin)
|
|
12261
|
-
};
|
|
12262
|
-
} catch {
|
|
12263
|
-
return {
|
|
12264
|
-
kind: "bin",
|
|
12265
|
-
program: bin
|
|
12266
|
-
};
|
|
12267
|
-
}
|
|
12268
|
-
const script = process.argv[1];
|
|
12269
|
-
if (!script) throw new Error("Cannot resolve CLI entry point (process.argv[1] is empty).");
|
|
12270
|
-
const scriptAbs = isAbsolute(script) ? script : join(process.cwd(), script);
|
|
12271
|
-
return {
|
|
12272
|
-
kind: "node",
|
|
12273
|
-
program: process.execPath,
|
|
12274
|
-
args: [scriptAbs]
|
|
12275
|
-
};
|
|
12276
|
-
}
|
|
12277
|
-
function ensureLogDir() {
|
|
12278
|
-
mkdirSync(LOG_DIR, {
|
|
12279
|
-
recursive: true,
|
|
12280
|
-
mode: 448
|
|
12281
|
-
});
|
|
12282
|
-
}
|
|
12283
|
-
function launchdPlistPath() {
|
|
12284
|
-
return join(homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
12285
|
-
}
|
|
12286
|
-
function renderPlist(invocation) {
|
|
12287
|
-
const argsXml = (invocation.kind === "bin" ? [
|
|
12288
|
-
invocation.program,
|
|
12289
|
-
"client",
|
|
12290
|
-
"start",
|
|
12291
|
-
"--no-interactive"
|
|
12292
|
-
] : [
|
|
12293
|
-
invocation.program,
|
|
12294
|
-
...invocation.args,
|
|
12295
|
-
"client",
|
|
12296
|
-
"start",
|
|
12297
|
-
"--no-interactive"
|
|
12298
|
-
]).map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
12299
|
-
const outLog = join(LOG_DIR, "client.out.log");
|
|
12300
|
-
const errLog = join(LOG_DIR, "client.err.log");
|
|
12301
|
-
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
12302
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTD/PropertyList-1.0.dtd">
|
|
12303
|
-
<plist version="1.0">
|
|
12304
|
-
<dict>
|
|
12305
|
-
<key>Label</key>
|
|
12306
|
-
<string>${LAUNCHD_LABEL}</string>
|
|
12307
|
-
<key>ProgramArguments</key>
|
|
12308
|
-
<array>
|
|
12309
|
-
${argsXml}
|
|
12310
|
-
</array>
|
|
12311
|
-
<key>EnvironmentVariables</key>
|
|
12312
|
-
<dict>
|
|
12313
|
-
<key>PATH</key>
|
|
12314
|
-
<string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
|
|
12315
|
-
</dict>
|
|
12316
|
-
<key>RunAtLoad</key>
|
|
12317
|
-
<true/>
|
|
12318
|
-
<key>KeepAlive</key>
|
|
12319
|
-
<dict>
|
|
12320
|
-
<key>SuccessfulExit</key>
|
|
12321
|
-
<false/>
|
|
12322
|
-
</dict>
|
|
12323
|
-
<key>ThrottleInterval</key>
|
|
12324
|
-
<integer>10</integer>
|
|
12325
|
-
<key>StandardOutPath</key>
|
|
12326
|
-
<string>${escapeXml(outLog)}</string>
|
|
12327
|
-
<key>StandardErrorPath</key>
|
|
12328
|
-
<string>${escapeXml(errLog)}</string>
|
|
12329
|
-
</dict>
|
|
12330
|
-
</plist>
|
|
12331
|
-
`;
|
|
12332
|
-
}
|
|
12333
|
-
function escapeXml(value) {
|
|
12334
|
-
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
12335
|
-
}
|
|
12336
|
-
function launchctlDomainTarget() {
|
|
12337
|
-
return `gui/${userInfo().uid}`;
|
|
12338
|
-
}
|
|
12339
|
-
function launchdState() {
|
|
12340
|
-
if (!existsSync(launchdPlistPath())) return { state: "not-installed" };
|
|
12341
|
-
const res = spawnSync("launchctl", ["print", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], {
|
|
12342
|
-
encoding: "utf-8",
|
|
12343
|
-
timeout: 5e3,
|
|
12344
|
-
stdio: [
|
|
12345
|
-
"ignore",
|
|
12346
|
-
"pipe",
|
|
12347
|
-
"pipe"
|
|
12348
|
-
]
|
|
12349
|
-
});
|
|
12350
|
-
if (res.status !== 0) return {
|
|
12351
|
-
state: "inactive",
|
|
12352
|
-
detail: "plist present but not loaded"
|
|
12353
|
-
};
|
|
12354
|
-
const out = res.stdout ?? "";
|
|
12355
|
-
const stateLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("state ="));
|
|
12356
|
-
const pidLine = out.split(/\r?\n/).find((l) => l.trim().startsWith("pid ="));
|
|
12357
|
-
if (stateLine?.includes("running")) {
|
|
12358
|
-
const pid = pidLine?.split("=")[1]?.trim();
|
|
12359
|
-
return {
|
|
12360
|
-
state: "active",
|
|
12361
|
-
detail: pid ? `pid ${pid}` : "running"
|
|
12362
|
-
};
|
|
12363
|
-
}
|
|
12364
|
-
return {
|
|
12365
|
-
state: "inactive",
|
|
12366
|
-
detail: stateLine?.trim() ?? "loaded"
|
|
12367
|
-
};
|
|
15693
|
+
process.stderr.write(" Press Ctrl+C to stop.\n\n");
|
|
12368
15694
|
}
|
|
12369
15695
|
/**
|
|
12370
|
-
*
|
|
12371
|
-
*
|
|
12372
|
-
*
|
|
12373
|
-
* active WebSocket connections — a follow-up `bootstrap` against a still-
|
|
12374
|
-
* registered label fails with `Bootstrap failed: 5: Input/output error`.
|
|
15696
|
+
* Resolve web dist path.
|
|
15697
|
+
* 1. npm install: embedded at dist/web/ (relative to the built CLI)
|
|
15698
|
+
* 2. Monorepo dev: resolved from @first-tree-hub/web package (builds if needed)
|
|
12375
15699
|
*/
|
|
12376
|
-
function
|
|
12377
|
-
const
|
|
12378
|
-
|
|
12379
|
-
|
|
12380
|
-
|
|
12381
|
-
|
|
15700
|
+
function resolveWebDist() {
|
|
15701
|
+
const embeddedPath = join(dirname(fileURLToPath(import.meta.url)), "..", "web");
|
|
15702
|
+
if (existsSync(join(embeddedPath, "index.html"))) return embeddedPath;
|
|
15703
|
+
try {
|
|
15704
|
+
const webDir = dirname(fileURLToPath(import.meta.resolve("@first-tree-hub/web/package.json")));
|
|
15705
|
+
const distPath = join(webDir, "dist");
|
|
15706
|
+
const indexPath = join(distPath, "index.html");
|
|
15707
|
+
if (existsSync(indexPath)) return distPath;
|
|
15708
|
+
status("Web", "building...");
|
|
15709
|
+
execSync("pnpm --filter @first-tree-hub/web build", {
|
|
12382
15710
|
stdio: [
|
|
12383
15711
|
"ignore",
|
|
12384
15712
|
"ignore",
|
|
12385
15713
|
"pipe"
|
|
12386
|
-
]
|
|
12387
|
-
|
|
12388
|
-
|
|
12389
|
-
|
|
12390
|
-
|
|
12391
|
-
}
|
|
12392
|
-
function installLaunchd() {
|
|
12393
|
-
const invocation = resolveCliInvocation();
|
|
12394
|
-
ensureLogDir();
|
|
12395
|
-
const plistPath = launchdPlistPath();
|
|
12396
|
-
mkdirSync(dirname(plistPath), { recursive: true });
|
|
12397
|
-
writeFileSync(plistPath, renderPlist(invocation), { mode: 420 });
|
|
12398
|
-
const target = launchctlDomainTarget();
|
|
12399
|
-
const bootoutRes = runCapture("launchctl", ["bootout", `${target}/${LAUNCHD_LABEL}`], 15e3);
|
|
12400
|
-
if (!bootoutRes.ok) {
|
|
12401
|
-
if (!/not find|no such|not loaded/i.test(bootoutRes.stderr)) process.stderr.write(` warning: launchctl bootout: ${bootoutRes.stderr || `exit ${bootoutRes.code ?? "unknown"}`}\n`);
|
|
12402
|
-
}
|
|
12403
|
-
waitForLabelEvicted(target, LAUNCHD_LABEL, 1e4);
|
|
12404
|
-
let lastBootstrapErr = null;
|
|
12405
|
-
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
12406
|
-
const res = runCapture("launchctl", [
|
|
12407
|
-
"bootstrap",
|
|
12408
|
-
target,
|
|
12409
|
-
plistPath
|
|
12410
|
-
], 1e4);
|
|
12411
|
-
if (res.ok) {
|
|
12412
|
-
lastBootstrapErr = null;
|
|
12413
|
-
break;
|
|
12414
|
-
}
|
|
12415
|
-
lastBootstrapErr = res;
|
|
12416
|
-
if (attempt < 2) sleepSync(1e3);
|
|
12417
|
-
}
|
|
12418
|
-
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 service install\`.`);
|
|
12419
|
-
const enableRes = runCapture("launchctl", ["enable", `${target}/${LAUNCHD_LABEL}`], 5e3);
|
|
12420
|
-
if (!enableRes.ok) process.stderr.write(` warning: launchctl enable: ${enableRes.stderr || `exit ${enableRes.code ?? "unknown"}`}\n`);
|
|
12421
|
-
const { state, detail } = launchdState();
|
|
12422
|
-
return {
|
|
12423
|
-
platform: "launchd",
|
|
12424
|
-
label: LAUNCHD_LABEL,
|
|
12425
|
-
unitPath: plistPath,
|
|
12426
|
-
logDir: LOG_DIR,
|
|
12427
|
-
state,
|
|
12428
|
-
detail
|
|
12429
|
-
};
|
|
12430
|
-
}
|
|
12431
|
-
function uninstallLaunchd() {
|
|
12432
|
-
const plistPath = launchdPlistPath();
|
|
12433
|
-
const res = runCapture("launchctl", ["bootout", `${launchctlDomainTarget()}/${LAUNCHD_LABEL}`], 15e3);
|
|
12434
|
-
if (!res.ok && !/not find|no such|not loaded/i.test(res.stderr)) process.stderr.write(` warning: bootout during uninstall: ${res.stderr || `exit ${res.code ?? "unknown"}`}\n`);
|
|
12435
|
-
if (existsSync(plistPath)) rmSync(plistPath);
|
|
12436
|
-
return {
|
|
12437
|
-
platform: "launchd",
|
|
12438
|
-
label: LAUNCHD_LABEL,
|
|
12439
|
-
unitPath: plistPath,
|
|
12440
|
-
logDir: LOG_DIR,
|
|
12441
|
-
state: "not-installed"
|
|
12442
|
-
};
|
|
12443
|
-
}
|
|
12444
|
-
function systemdUnitPath() {
|
|
12445
|
-
return join(process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config"), "systemd", "user", SYSTEMD_UNIT);
|
|
15714
|
+
],
|
|
15715
|
+
cwd: join(webDir, "../..")
|
|
15716
|
+
});
|
|
15717
|
+
if (existsSync(indexPath)) return distPath;
|
|
15718
|
+
} catch {}
|
|
12446
15719
|
}
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
|
|
12451
|
-
|
|
12452
|
-
|
|
12453
|
-
|
|
12454
|
-
|
|
12455
|
-
|
|
12456
|
-
|
|
12457
|
-
|
|
12458
|
-
|
|
12459
|
-
|
|
12460
|
-
|
|
12461
|
-
|
|
12462
|
-
|
|
12463
|
-
|
|
12464
|
-
|
|
15720
|
+
//#endregion
|
|
15721
|
+
//#region src/core/update.ts
|
|
15722
|
+
const PACKAGE_NAME = "@agent-team-foundation/first-tree-hub";
|
|
15723
|
+
/**
|
|
15724
|
+
* Pick the `npm` binary to invoke for self-update. Background service units
|
|
15725
|
+
* hard-code a minimal PATH (/usr/local/bin, /opt/homebrew/bin, /usr/bin,
|
|
15726
|
+
* /bin) that misses nvm / asdf / Volta toolchain directories — the client
|
|
15727
|
+
* launches fine from an absolute path resolved at install time, but a plain
|
|
15728
|
+
* `spawn("npm")` then ENOENTs. Node and npm always ship side-by-side, so
|
|
15729
|
+
* `dirname(execPath)/npm` is the most reliable fallback across those
|
|
15730
|
+
* managers; if the sibling is missing (e.g. corporate custom layout) we
|
|
15731
|
+
* fall back to PATH lookup.
|
|
15732
|
+
*/
|
|
15733
|
+
function resolveNpmCommand() {
|
|
15734
|
+
const binName = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
15735
|
+
const sibling = join(dirname(process.execPath), binName);
|
|
15736
|
+
if (existsSync(sibling)) return sibling;
|
|
15737
|
+
return "npm";
|
|
12465
15738
|
}
|
|
12466
|
-
|
|
12467
|
-
|
|
12468
|
-
|
|
15739
|
+
/**
|
|
15740
|
+
* Detect how the CLI was launched. Used by the update path to decide whether
|
|
15741
|
+
* `npm install -g <pkg>@latest` makes sense.
|
|
15742
|
+
*
|
|
15743
|
+
* - `"global"`: launched from an `npm install -g` install. The self-update
|
|
15744
|
+
* reinstalls the same package at `@latest`.
|
|
15745
|
+
* - `"source"`: launched from inside a git checkout (dev / monorepo). Update
|
|
15746
|
+
* is a no-op; operator should `git pull`.
|
|
15747
|
+
* - `"npx"` (fallback): any other path (e.g. one-shot `npx`, pnpm dlx). Auto
|
|
15748
|
+
* update is not safe; log a hint and skip.
|
|
15749
|
+
*/
|
|
15750
|
+
function detectInstallMode(argv1 = process.argv[1] ?? "") {
|
|
15751
|
+
if (!argv1) return "npx";
|
|
15752
|
+
let dir = dirname(resolve(argv1));
|
|
15753
|
+
for (let i = 0; i < 10; i++) {
|
|
15754
|
+
if (existsSync(resolve(dir, ".git"))) return "source";
|
|
15755
|
+
const pkgPath = resolve(dir, "package.json");
|
|
15756
|
+
if (existsSync(pkgPath)) try {
|
|
15757
|
+
if (JSON.parse(readFileSync(pkgPath, "utf8")).name === PACKAGE_NAME) {
|
|
15758
|
+
if (/\/(?:_npx|\.npm\/_npx)\//.test(dir)) return "npx";
|
|
15759
|
+
return "global";
|
|
15760
|
+
}
|
|
15761
|
+
} catch {}
|
|
15762
|
+
const parent = dirname(dir);
|
|
15763
|
+
if (parent === dir) break;
|
|
15764
|
+
dir = parent;
|
|
15765
|
+
}
|
|
15766
|
+
return "npx";
|
|
12469
15767
|
}
|
|
12470
|
-
|
|
12471
|
-
|
|
12472
|
-
|
|
12473
|
-
|
|
12474
|
-
|
|
12475
|
-
|
|
12476
|
-
|
|
12477
|
-
|
|
12478
|
-
|
|
12479
|
-
|
|
15768
|
+
/**
|
|
15769
|
+
* Install `<pkg>@latest` globally. Returns after the child exits. Does not
|
|
15770
|
+
* exit the parent process — callers are expected to handle that (so the
|
|
15771
|
+
* UpdateManager can attempt the restart itself while this function remains
|
|
15772
|
+
* side-effect-scoped).
|
|
15773
|
+
*/
|
|
15774
|
+
async function installGlobalLatest() {
|
|
15775
|
+
return new Promise((resolvePromise) => {
|
|
15776
|
+
const child = spawn(resolveNpmCommand(), [
|
|
15777
|
+
"install",
|
|
15778
|
+
"-g",
|
|
15779
|
+
`${PACKAGE_NAME}@latest`
|
|
15780
|
+
], { stdio: [
|
|
12480
15781
|
"ignore",
|
|
12481
15782
|
"pipe",
|
|
12482
15783
|
"pipe"
|
|
12483
|
-
]
|
|
15784
|
+
] });
|
|
15785
|
+
const stdoutChunks = [];
|
|
15786
|
+
const stderrChunks = [];
|
|
15787
|
+
child.stdout.on("data", (chunk) => stdoutChunks.push(chunk));
|
|
15788
|
+
child.stderr.on("data", (chunk) => {
|
|
15789
|
+
stderrChunks.push(chunk);
|
|
15790
|
+
process.stderr.write(chunk);
|
|
15791
|
+
});
|
|
15792
|
+
child.on("error", (err) => {
|
|
15793
|
+
resolvePromise({
|
|
15794
|
+
ok: false,
|
|
15795
|
+
mode: "global",
|
|
15796
|
+
reason: err instanceof Error ? err.message : String(err)
|
|
15797
|
+
});
|
|
15798
|
+
});
|
|
15799
|
+
child.on("exit", (code) => {
|
|
15800
|
+
if (code === 0) resolvePromise({
|
|
15801
|
+
ok: true,
|
|
15802
|
+
mode: "global",
|
|
15803
|
+
installedVersion: parseInstalledVersion(Buffer.concat(stdoutChunks).toString("utf8"))
|
|
15804
|
+
});
|
|
15805
|
+
else {
|
|
15806
|
+
const stderr = Buffer.concat(stderrChunks).toString("utf8").trim();
|
|
15807
|
+
resolvePromise({
|
|
15808
|
+
ok: false,
|
|
15809
|
+
mode: "global",
|
|
15810
|
+
reason: `npm install -g exited with code ${code}${stderr ? `: ${stderr.split("\n").slice(-3).join(" | ")}` : ""}`
|
|
15811
|
+
});
|
|
15812
|
+
}
|
|
15813
|
+
});
|
|
12484
15814
|
});
|
|
12485
|
-
const out = (res.stdout ?? "").trim();
|
|
12486
|
-
if (res.status === 0 && out === "active") return {
|
|
12487
|
-
state: "active",
|
|
12488
|
-
detail: "running"
|
|
12489
|
-
};
|
|
12490
|
-
return {
|
|
12491
|
-
state: "inactive",
|
|
12492
|
-
detail: out || "unit present but not active"
|
|
12493
|
-
};
|
|
12494
|
-
}
|
|
12495
|
-
function installSystemd() {
|
|
12496
|
-
const invocation = resolveCliInvocation();
|
|
12497
|
-
ensureLogDir();
|
|
12498
|
-
const unitPath = systemdUnitPath();
|
|
12499
|
-
mkdirSync(dirname(unitPath), { recursive: true });
|
|
12500
|
-
writeFileSync(unitPath, renderSystemdUnit(invocation), { mode: 420 });
|
|
12501
|
-
const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
|
|
12502
|
-
if (!reloadRes.ok) throw new Error(`systemctl --user daemon-reload failed: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}`);
|
|
12503
|
-
const enableRes = runCapture("systemctl", [
|
|
12504
|
-
"--user",
|
|
12505
|
-
"enable",
|
|
12506
|
-
"--now",
|
|
12507
|
-
SYSTEMD_UNIT
|
|
12508
|
-
], 1e4);
|
|
12509
|
-
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 service install\`.`);
|
|
12510
|
-
const { state, detail } = systemdState();
|
|
12511
|
-
return {
|
|
12512
|
-
platform: "systemd",
|
|
12513
|
-
label: SYSTEMD_UNIT,
|
|
12514
|
-
unitPath,
|
|
12515
|
-
logDir: LOG_DIR,
|
|
12516
|
-
state,
|
|
12517
|
-
detail
|
|
12518
|
-
};
|
|
12519
|
-
}
|
|
12520
|
-
function uninstallSystemd() {
|
|
12521
|
-
const unitPath = systemdUnitPath();
|
|
12522
|
-
const disableRes = runCapture("systemctl", [
|
|
12523
|
-
"--user",
|
|
12524
|
-
"disable",
|
|
12525
|
-
"--now",
|
|
12526
|
-
SYSTEMD_UNIT
|
|
12527
|
-
], 1e4);
|
|
12528
|
-
if (!disableRes.ok && !/not found|no such|not loaded/i.test(disableRes.stderr)) process.stderr.write(` warning: systemctl disable during uninstall: ${disableRes.stderr || `exit ${disableRes.code ?? "unknown"}`}\n`);
|
|
12529
|
-
if (existsSync(unitPath)) rmSync(unitPath);
|
|
12530
|
-
const reloadRes = runCapture("systemctl", ["--user", "daemon-reload"], 5e3);
|
|
12531
|
-
if (!reloadRes.ok) process.stderr.write(` warning: systemctl daemon-reload during uninstall: ${reloadRes.stderr || `exit ${reloadRes.code ?? "unknown"}`}\n`);
|
|
12532
|
-
return {
|
|
12533
|
-
platform: "systemd",
|
|
12534
|
-
label: SYSTEMD_UNIT,
|
|
12535
|
-
unitPath,
|
|
12536
|
-
logDir: LOG_DIR,
|
|
12537
|
-
state: "not-installed"
|
|
12538
|
-
};
|
|
12539
|
-
}
|
|
12540
|
-
/** Is background-service install supported on the current platform? */
|
|
12541
|
-
function isServiceSupported() {
|
|
12542
|
-
return process.platform === "darwin" || process.platform === "linux";
|
|
12543
15815
|
}
|
|
12544
15816
|
/**
|
|
12545
|
-
*
|
|
12546
|
-
*
|
|
12547
|
-
*
|
|
15817
|
+
* Best-effort extraction of the version npm reported as installed. npm's
|
|
15818
|
+
* stdout lines look like `+ @agent-team-foundation/first-tree-hub@0.9.2`.
|
|
15819
|
+
* Returns null if nothing matches — callers treat null as "install succeeded
|
|
15820
|
+
* but version unknown".
|
|
12548
15821
|
*/
|
|
12549
|
-
function
|
|
12550
|
-
|
|
12551
|
-
if (
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
|
|
12555
|
-
function
|
|
12556
|
-
|
|
12557
|
-
|
|
12558
|
-
|
|
12559
|
-
|
|
12560
|
-
|
|
12561
|
-
|
|
12562
|
-
|
|
12563
|
-
|
|
12564
|
-
|
|
12565
|
-
|
|
12566
|
-
|
|
12567
|
-
|
|
12568
|
-
|
|
12569
|
-
|
|
12570
|
-
|
|
12571
|
-
|
|
12572
|
-
|
|
12573
|
-
|
|
12574
|
-
state,
|
|
12575
|
-
detail
|
|
12576
|
-
};
|
|
15822
|
+
function parseInstalledVersion(stdout) {
|
|
15823
|
+
const match = new RegExp(`${escapeForRegex(PACKAGE_NAME)}@(\\S+)`).exec(stdout);
|
|
15824
|
+
if (!match?.[1]) return null;
|
|
15825
|
+
const cleaned = match[1].replace(/[,\s)]+$/, "");
|
|
15826
|
+
return semver.valid(cleaned) ?? cleaned;
|
|
15827
|
+
}
|
|
15828
|
+
function escapeForRegex(s) {
|
|
15829
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
15830
|
+
}
|
|
15831
|
+
/** Interactive update prompt. Defaults to N on timeout. */
|
|
15832
|
+
const promptUpdate = async ({ currentVersion, targetVersion, timeoutSeconds }) => {
|
|
15833
|
+
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?`;
|
|
15834
|
+
try {
|
|
15835
|
+
const controller = new AbortController();
|
|
15836
|
+
const timer = setTimeout(() => controller.abort(), timeoutSeconds * 1e3);
|
|
15837
|
+
try {
|
|
15838
|
+
return await confirm({
|
|
15839
|
+
message,
|
|
15840
|
+
default: false
|
|
15841
|
+
}, { signal: controller.signal });
|
|
15842
|
+
} finally {
|
|
15843
|
+
clearTimeout(timer);
|
|
15844
|
+
}
|
|
15845
|
+
} catch {
|
|
15846
|
+
return false;
|
|
12577
15847
|
}
|
|
12578
|
-
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
12582
|
-
|
|
12583
|
-
|
|
12584
|
-
|
|
15848
|
+
};
|
|
15849
|
+
/**
|
|
15850
|
+
* Update prompt that always declines. Wired in when the operator passes
|
|
15851
|
+
* `--no-interactive` — the UpdateManager will log the drift and move on
|
|
15852
|
+
* instead of blocking on a TTY confirm.
|
|
15853
|
+
*/
|
|
15854
|
+
const declineUpdate = async () => false;
|
|
15855
|
+
/**
|
|
15856
|
+
* Build the command-layer `executeUpdate` callback.
|
|
15857
|
+
*
|
|
15858
|
+
* `managed=true` means a process supervisor (launchd / systemd / Docker
|
|
15859
|
+
* `restart`) is expected to relaunch us after `process.exit` — the callback
|
|
15860
|
+
* installs the new bits and exits with `SELF_RESTART_EXIT_CODE` so the
|
|
15861
|
+
* relaunch picks up the new binary.
|
|
15862
|
+
*
|
|
15863
|
+
* `managed=false` means the process is running standalone (e.g. manual
|
|
15864
|
+
* `client start`, `client connect --no-service`, CI without a supervisor).
|
|
15865
|
+
* Exiting in that mode would leave the client offline until an operator
|
|
15866
|
+
* noticed — so the callback instead prints a restart hint, returns
|
|
15867
|
+
* `{ installed: true }`, and the UpdateManager stops retrying until the
|
|
15868
|
+
* operator restarts manually.
|
|
15869
|
+
*/
|
|
15870
|
+
function createExecuteUpdate({ managed }) {
|
|
15871
|
+
return async () => {
|
|
15872
|
+
const mode = detectInstallMode();
|
|
15873
|
+
if (mode === "source") {
|
|
15874
|
+
process.stderr.write(" [update] Running from source checkout — self-update skipped. Use `git pull` instead.\n");
|
|
15875
|
+
return { installed: false };
|
|
15876
|
+
}
|
|
15877
|
+
if (mode === "npx") {
|
|
15878
|
+
process.stderr.write(" [update] Cannot self-update — not launched from a global npm install.\n Run `npm i -g @agent-team-foundation/first-tree-hub` manually.\n");
|
|
15879
|
+
return { installed: false };
|
|
15880
|
+
}
|
|
15881
|
+
process.stderr.write(" [update] Running `npm install -g @agent-team-foundation/first-tree-hub@latest`...\n");
|
|
15882
|
+
const result = await installGlobalLatest();
|
|
15883
|
+
if (!result.ok) {
|
|
15884
|
+
process.stderr.write(` [update] Install failed: ${result.reason}\n`);
|
|
15885
|
+
return { installed: false };
|
|
15886
|
+
}
|
|
15887
|
+
const installed = result.installedVersion ?? "latest";
|
|
15888
|
+
if (managed) {
|
|
15889
|
+
process.stderr.write(` [update] Installed ${installed}. Restarting (exit 75).\n`);
|
|
15890
|
+
process.exit(75);
|
|
15891
|
+
}
|
|
15892
|
+
process.stderr.write(` [update] Installed ${installed}. Restart the client manually (Ctrl+C then \`first-tree-hub client start\`) to pick up the new version.\n`);
|
|
15893
|
+
return { installed: true };
|
|
12585
15894
|
};
|
|
12586
15895
|
}
|
|
12587
|
-
/** Uninstall the background service. No-op if not installed. */
|
|
12588
|
-
function uninstallClientService() {
|
|
12589
|
-
if (process.platform === "darwin") return uninstallLaunchd();
|
|
12590
|
-
if (process.platform === "linux") return uninstallSystemd();
|
|
12591
|
-
return getClientServiceStatus();
|
|
12592
|
-
}
|
|
12593
15896
|
//#endregion
|
|
12594
|
-
export {
|
|
15897
|
+
export { printResults as A, SdkError as B, checkDatabase as C, checkServerHealth as D, checkServerConfig as E, stopPostgres as F, cleanWorkspaces as H, ClientRuntime as I, createOwner as L, status as M, ensurePostgres as N, checkServerReachable as O, isDockerAvailable as P, hasUser as R, checkClientConfig as S, checkNodeVersion as T, applyClientLoggerConfig as U, SessionRegistry as V, isServiceSupported as _, COMMAND_VERSION as a, runMigrations as b, promptMissingFields as c, onboardCheck as d, onboardCreate as f, installClientService as g, getClientServiceStatus as h, startServer as i, blank as j, checkWebSocket as k, formatCheckReport as l, runHomeMigration as m, declineUpdate as n, isInteractive as o, saveOnboardState as p, promptUpdate as r, promptAddAgent as s, createExecuteUpdate as t, loadOnboardState as u, resolveCliInvocation as v, checkDocker as w, checkAgentConfigs as x, uninstallClientService as y, FirstTreeHubSDK as z };
|