@tool-bridge/server 0.5.0 → 0.10.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/{airtable-7TFNLFIF.js → airtable-5W6SJMSM.js} +4 -4
- package/dist/{alt_text_generator_ai-VJSAHBJM.js → alt_text_generator_ai-YYVJM3RG.js} +4 -4
- package/dist/{amap-KZMUDGK6.js → amap-EREMQZHQ.js} +4 -4
- package/dist/{anthropic-C6FCUSY7.js → anthropic-53H7WGSE.js} +4 -4
- package/dist/{apify-MUCWNIWU.js → apify-CMRNU7VZ.js} +4 -4
- package/dist/{brave_search-NRVZ4ZNY.js → brave_search-AFZVZ3QK.js} +4 -4
- package/dist/{chunk-FHYFPPBZ.js → chunk-2DA2UI37.js} +2 -2
- package/dist/{chunk-B4ZA7EGO.js → chunk-Q44CYUSI.js} +1 -1
- package/dist/{chunk-3C3J36GT.js → chunk-Q7YHZ2SD.js} +683 -54
- package/dist/{chunk-7GP7DRES.js → chunk-SBJMMSPA.js} +1 -1
- package/dist/{chunk-7PM4FD5K.js → chunk-TPGTA43F.js} +291 -55
- package/dist/{circleci-QY36SZIM.js → circleci-JG7RV2RU.js} +4 -4
- package/dist/{clerk-ECH23XL5.js → clerk-U7OS5ALG.js} +4 -4
- package/dist/{cloudflare_dns-F6DN2F3X.js → cloudflare_dns-ADCTFMMI.js} +4 -4
- package/dist/{cohere-HD4XZOPJ.js → cohere-Z7ZEC2YV.js} +4 -4
- package/dist/{confluence-J4AZ2KYL.js → confluence-FT22JGYQ.js} +4 -4
- package/dist/{convertapi-BXBII5Z6.js → convertapi-UQQCWVWP.js} +4 -4
- package/dist/{currencyapi-HEEQ7EQJ.js → currencyapi-Y63O7JTP.js} +4 -4
- package/dist/{deepseek-RKRGIFPM.js → deepseek-7TSAMZJ6.js} +4 -4
- package/dist/{docker_hub-YFSTEZVP.js → docker_hub-AJYTVI25.js} +4 -4
- package/dist/{dropbox-VGK634PO.js → dropbox-CNGGNR5J.js} +4 -4
- package/dist/{dub-GEXD3XK6.js → dub-WTADKHA7.js} +4 -4
- package/dist/{e2b-LPTZQEDV.js → e2b-75EKXMN5.js} +4 -4
- package/dist/{exa-VJENLTKN.js → exa-OFLA5XND.js} +4 -4
- package/dist/{fal_ai-75O63LLP.js → fal_ai-5542HB7N.js} +4 -4
- package/dist/{feishu-FATBHGEJ.js → feishu-QQIZ7G24.js} +3 -3
- package/dist/{feishu_custom_bot-QFSYPOLI.js → feishu_custom_bot-EVDKIZCR.js} +4 -4
- package/dist/{figma-JED3Q56H.js → figma-ZEBDUMWI.js} +4 -4
- package/dist/{firehydrant-7JDKFJTB.js → firehydrant-CTBNY6FL.js} +4 -4
- package/dist/{fixer-ZI2JS5GU.js → fixer-ITDYMRBW.js} +4 -4
- package/dist/{fly-3RA7ETFP.js → fly-IBFMSAAO.js} +4 -4
- package/dist/{front-X2CJXBYS.js → front-AZQLLUJQ.js} +4 -4
- package/dist/{geocodio-C6NHG5UJ.js → geocodio-XHZHEWQW.js} +4 -4
- package/dist/{ghost-ZBFOMWQF.js → ghost-RTIKXD3T.js} +4 -4
- package/dist/{github-6QM2NIP4.js → github-WRJBJ5N7.js} +4 -4
- package/dist/{gmail-ST3MS32S.js → gmail-527RVDXV.js} +4 -4
- package/dist/{googlecalendar-SAUTUJ3Q.js → googlecalendar-WZJSK2MR.js} +4 -4
- package/dist/{googledocs-2SJKS2SC.js → googledocs-LFBVIHIK.js} +4 -4
- package/dist/{grafana-RP7DOS3A.js → grafana-3JEKO7NT.js} +4 -4
- package/dist/{graphhopper-XSDTLJTE.js → graphhopper-TOJN6K2Q.js} +4 -4
- package/dist/index.d.ts +400 -26
- package/dist/index.js +14 -2
- package/dist/{ip2proxy-IKV5IWUI.js → ip2proxy-KOA2JUNV.js} +4 -4
- package/dist/{ipgeolocation_io-NSUWUF5X.js → ipgeolocation_io-KNOQD7R7.js} +4 -4
- package/dist/{ipqualityscore-ZQKL3WLC.js → ipqualityscore-GF2MQKXI.js} +4 -4
- package/dist/{jina_ai-F4Y7V5AS.js → jina_ai-GB3CQPJ4.js} +4 -4
- package/dist/{jira-CYDY44ZO.js → jira-35XYV6G4.js} +4 -4
- package/dist/{langsmith-74JSSJNY.js → langsmith-W5234OE7.js} +4 -4
- package/dist/{lemlist-C3LKIPVN.js → lemlist-QB6W2G7N.js} +4 -4
- package/dist/{linear-YYGRHKJC.js → linear-MHBD6A7X.js} +4 -4
- package/dist/{logsnag-YBMDJ6DO.js → logsnag-RN57XIJS.js} +4 -4
- package/dist/main.js +113 -106
- package/dist/{mattermost-NDID4TAL.js → mattermost-QRXSZMZA.js} +4 -4
- package/dist/{mem0-ASBAEBWR.js → mem0-K63NN3LA.js} +4 -4
- package/dist/{memos-76I27HCW.js → memos-4IOUOFKH.js} +4 -4
- package/dist/{metabase-LCPIP2DL.js → metabase-IOE44EKL.js} +4 -4
- package/dist/{mistral_ai-YVBQ6QKO.js → mistral_ai-X7ASGV5I.js} +4 -4
- package/dist/{mother_duck-J2MPBIFO.js → mother_duck-CNN3QWQH.js} +4 -4
- package/dist/{ngrok-UJC2ORDH.js → ngrok-QNE4AHEH.js} +4 -4
- package/dist/{notes-6VWWD6RW.js → notes-Q6OLBS2L.js} +2 -2
- package/dist/{notion-6LBO2KHR.js → notion-ADDRQTCE.js} +4 -4
- package/dist/{open_exchange_rates-OGDH4ZBX.js → open_exchange_rates-WR5PJQTG.js} +4 -4
- package/dist/{openai-IKGD5M66.js → openai-UZBNNAAH.js} +4 -4
- package/dist/{opengraph_io-CIILQ7VK.js → opengraph_io-2JNEXCPJ.js} +4 -4
- package/dist/{openrouter-IYG5CPG5.js → openrouter-EF62BDIH.js} +4 -4
- package/dist/{outline-R2UMBY4M.js → outline-O4TIRWJT.js} +4 -4
- package/dist/{paddle-IYWA7DOV.js → paddle-SEFKNENH.js} +4 -4
- package/dist/{perplexity-3TGYQWHT.js → perplexity-FZYOIPDS.js} +4 -4
- package/dist/{pinecone-TEBOW4LJ.js → pinecone-EXDPYDY7.js} +4 -4
- package/dist/{pipedrive-ZXDY753U.js → pipedrive-EDO4C3AM.js} +4 -4
- package/dist/{polar-BVZV5A4O.js → polar-QJXHVFIP.js} +4 -4
- package/dist/{posthog-DXKAS4PJ.js → posthog-U6QEXJE2.js} +4 -4
- package/dist/{postmark-6RWFC3TD.js → postmark-PFZIWXMP.js} +4 -4
- package/dist/{prerender-YIXOTM3K.js → prerender-SK2AXNJY.js} +4 -4
- package/dist/{pubmed-2RHSZB4E.js → pubmed-CB7OJNBH.js} +4 -4
- package/dist/{railway-IF64BUE2.js → railway-BMMNQ6XH.js} +4 -4
- package/dist/{readwise-5XKQZNOM.js → readwise-YQY7O5QR.js} +4 -4
- package/dist/{render-Q2Z66LTA.js → render-7CRNEEJ3.js} +4 -4
- package/dist/{replicate-SOI65IJO.js → replicate-V6AX7WFC.js} +4 -4
- package/dist/{resend-O2BGONOO.js → resend-ZBH56UAQ.js} +4 -4
- package/dist/{rootly-WPQKTVU3.js → rootly-C4FIXIF7.js} +4 -4
- package/dist/{scrapfly-GLMBVNPG.js → scrapfly-UE434KD7.js} +4 -4
- package/dist/{scrapingbee-TUER54CY.js → scrapingbee-PQCNKALD.js} +4 -4
- package/dist/{screenshot_fyi-4J4W5EKY.js → screenshot_fyi-U6QGI6ZH.js} +4 -4
- package/dist/{semantic_scholar-4PITOANV.js → semantic_scholar-PSMUOG2W.js} +4 -4
- package/dist/{sentry-4WURPLIE.js → sentry-F5CNVQA2.js} +4 -4
- package/dist/{shodan-G3M3LFEW.js → shodan-NOWG7NV5.js} +4 -4
- package/dist/{shopify-72AIELMS.js → shopify-GGPLJIGC.js} +4 -4
- package/dist/{stripe-7C7XVPMU.js → stripe-72K2UMK3.js} +4 -4
- package/dist/{supabase-N4Y3YC23.js → supabase-H7NRCSAO.js} +4 -4
- package/dist/{tavily-GGLSWERP.js → tavily-ISP24QUX.js} +4 -4
- package/dist/{telegram-4RH36WYP.js → telegram-POII6NKE.js} +4 -4
- package/dist/{telnyx-I2KUITFV.js → telnyx-3D7TOVNA.js} +4 -4
- package/dist/{todoist-A3NX6X2Y.js → todoist-LKYPKRKB.js} +4 -4
- package/dist/{trello-2GJYN7GR.js → trello-WW74UYIQ.js} +4 -4
- package/dist/{turso-WKUXMTZ5.js → turso-SUFRHBHW.js} +4 -4
- package/dist/{twilio-SSXDBOK4.js → twilio-QB7PBL2B.js} +4 -4
- package/dist/{umami-SBDB4GXK.js → umami-CAHHP3SV.js} +4 -4
- package/dist/{unsplash-4AVC4ZGP.js → unsplash-ZCGEPHGQ.js} +4 -4
- package/dist/{upstash_redis-7XY3UHBO.js → upstash_redis-DZ4AKZWT.js} +4 -4
- package/dist/{uptimerobot-QVQP3QYG.js → uptimerobot-KVEQJGTZ.js} +4 -4
- package/dist/{v2ex-ASVTIKO7.js → v2ex-BCYYBPH7.js} +4 -4
- package/dist/{vercel-ZQXZCGWY.js → vercel-GH4G7EUS.js} +4 -4
- package/dist/{wolfram_alpha_api-RVBWQSEF.js → wolfram_alpha_api-XFG2MQBL.js} +4 -4
- package/dist/{wordpress-SOWKVNCX.js → wordpress-DZ2TVNAA.js} +4 -4
- package/dist/{workos-F45INANB.js → workos-EHUAGTGN.js} +4 -4
- package/dist/{zhihu-5VODAGAP.js → zhihu-OJJW3ZIH.js} +4 -4
- package/package.json +6 -4
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
AnnotationStore,
|
|
3
3
|
CONTEXT_CAPABILITIES,
|
|
4
4
|
DEFAULT_MAX_NODES,
|
|
5
|
+
DEVICE_CALL_TIMEOUT_MS,
|
|
5
6
|
DeviceGatewaySession,
|
|
6
7
|
FEEDBACK_HIDE_SCORE,
|
|
7
8
|
FeedbackStore,
|
|
@@ -17,6 +18,12 @@ import {
|
|
|
17
18
|
NEVER,
|
|
18
19
|
NodeRegistryStore,
|
|
19
20
|
OAUTH_CLIENT_FIELDS,
|
|
21
|
+
PG_SEARCH_INSERT_ROWS_MAX,
|
|
22
|
+
PG_SEARCH_SNAPSHOTS_COLUMNS,
|
|
23
|
+
PG_SEARCH_SNAPSHOTS_TABLE,
|
|
24
|
+
PG_SEARCH_TOOLS_COLUMNS,
|
|
25
|
+
PG_SEARCH_TOOLS_TABLE,
|
|
26
|
+
PG_SEARCH_WRITE_LOCK_KEY,
|
|
20
27
|
PRESIGN_TTL_SEC_DEFAULT,
|
|
21
28
|
RemoteAllowlistStore,
|
|
22
29
|
SKILLHUB_CAPABILITIES,
|
|
@@ -68,6 +75,7 @@ import {
|
|
|
68
75
|
createObjectContextProvider,
|
|
69
76
|
createSkillhubProvider,
|
|
70
77
|
decodeDeviceFrame,
|
|
78
|
+
derivePresence,
|
|
71
79
|
deviceDirectoryHelpModel,
|
|
72
80
|
deviceFsHelpModel,
|
|
73
81
|
deviceShellHelpModel,
|
|
@@ -104,6 +112,8 @@ import {
|
|
|
104
112
|
parseNodeInput,
|
|
105
113
|
parseTokenResponse,
|
|
106
114
|
parseVia,
|
|
115
|
+
pgBulkInsertSql,
|
|
116
|
+
pgSearchDialect,
|
|
107
117
|
pluginTokenSecretName,
|
|
108
118
|
preprocess,
|
|
109
119
|
record,
|
|
@@ -130,7 +140,7 @@ import {
|
|
|
130
140
|
url,
|
|
131
141
|
validatePath,
|
|
132
142
|
virtualizeTools
|
|
133
|
-
} from "./chunk-
|
|
143
|
+
} from "./chunk-TPGTA43F.js";
|
|
134
144
|
|
|
135
145
|
// src/config.ts
|
|
136
146
|
var DEFAULT_PORT = 8787;
|
|
@@ -149,7 +159,9 @@ function configFromEnv(env = process.env) {
|
|
|
149
159
|
const allowInsecure = env.TB_ALLOW_INSECURE_HTTP === "true";
|
|
150
160
|
const allowlist = (env.TB_REMOTE_ALLOWLIST ?? "").split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
151
161
|
const config = {
|
|
152
|
-
|
|
162
|
+
// TB_PORT 优先,PORT 兜底:Railway / Fly / Cloud Run / CF Container 等 PaaS 只注入
|
|
163
|
+
// PORT,不认识 TB_PORT —— 不兜底的话容器会监听 8787 而平台探活另一个端口,部署直接失败。
|
|
164
|
+
port: portEnv(env.TB_PORT) ?? portEnv(env.PORT) ?? DEFAULT_PORT,
|
|
153
165
|
host: env.TB_HOST !== void 0 && env.TB_HOST.length > 0 ? env.TB_HOST : "0.0.0.0",
|
|
154
166
|
dataDir: env.TB_DATA_DIR !== void 0 && env.TB_DATA_DIR.length > 0 ? env.TB_DATA_DIR : "./data",
|
|
155
167
|
allowInsecureHttp: allowInsecure,
|
|
@@ -160,9 +172,45 @@ function configFromEnv(env = process.env) {
|
|
|
160
172
|
...env.TB_INSTANCE_ID !== void 0 && env.TB_INSTANCE_ID.length > 0 ? { instanceId: env.TB_INSTANCE_ID } : {},
|
|
161
173
|
allowInsecure
|
|
162
174
|
},
|
|
163
|
-
deviceReclaimSec: positiveIntEnv(env.TB_DEVICE_RECLAIM_SEC) ?? DEFAULT_DEVICE_RECLAIM_SEC
|
|
175
|
+
deviceReclaimSec: positiveIntEnv(env.TB_DEVICE_RECLAIM_SEC) ?? DEFAULT_DEVICE_RECLAIM_SEC,
|
|
176
|
+
// 0 合法(立即关停,本地/单机默认);positiveIntEnv 拒 0,故单独解析。
|
|
177
|
+
shutdownDrainSec: (() => {
|
|
178
|
+
const n = Number(env.TB_SHUTDOWN_DRAIN_SEC);
|
|
179
|
+
return Number.isInteger(n) && n >= 0 ? n : 0;
|
|
180
|
+
})()
|
|
164
181
|
};
|
|
165
182
|
if (env.TB_UI_DIR !== void 0 && env.TB_UI_DIR.length > 0) config.uiDir = env.TB_UI_DIR;
|
|
183
|
+
if (env.TB_DATABASE_URL !== void 0 && env.TB_DATABASE_URL.length > 0) {
|
|
184
|
+
config.databaseUrl = env.TB_DATABASE_URL;
|
|
185
|
+
}
|
|
186
|
+
if (env.TB_REDIS_URL !== void 0 && env.TB_REDIS_URL.length > 0) {
|
|
187
|
+
config.redisUrl = env.TB_REDIS_URL;
|
|
188
|
+
}
|
|
189
|
+
if (env.TB_REPLICA_ID !== void 0 && env.TB_REPLICA_ID.length > 0) {
|
|
190
|
+
config.replicaId = env.TB_REPLICA_ID;
|
|
191
|
+
}
|
|
192
|
+
const s3Vars = [
|
|
193
|
+
["TB_OBJECT_STORE_ENDPOINT", env.TB_OBJECT_STORE_ENDPOINT],
|
|
194
|
+
["TB_OBJECT_STORE_BUCKET", env.TB_OBJECT_STORE_BUCKET],
|
|
195
|
+
["TB_OBJECT_STORE_ACCESS_KEY_ID", env.TB_OBJECT_STORE_ACCESS_KEY_ID],
|
|
196
|
+
["TB_OBJECT_STORE_SECRET_ACCESS_KEY", env.TB_OBJECT_STORE_SECRET_ACCESS_KEY]
|
|
197
|
+
];
|
|
198
|
+
const present = s3Vars.filter(([, value]) => value !== void 0 && value.length > 0);
|
|
199
|
+
if (present.length > 0) {
|
|
200
|
+
if (present.length < s3Vars.length) {
|
|
201
|
+
const missing = s3Vars.filter(([, value]) => value === void 0 || value.length === 0).map(([name]) => name);
|
|
202
|
+
throw new Error(
|
|
203
|
+
`\u5E73\u53F0\u5BF9\u8C61\u5B58\u50A8\u914D\u7F6E\u4E0D\u5B8C\u6574(\u7ED9\u4E86 ${present.length}/${s3Vars.length} \u9879),\u7F3A\u5C11:${missing.join(", ")}`
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
config.objectStore = {
|
|
207
|
+
accessKeyId: env.TB_OBJECT_STORE_ACCESS_KEY_ID,
|
|
208
|
+
bucket: env.TB_OBJECT_STORE_BUCKET,
|
|
209
|
+
endpoint: env.TB_OBJECT_STORE_ENDPOINT,
|
|
210
|
+
secretAccessKey: env.TB_OBJECT_STORE_SECRET_ACCESS_KEY,
|
|
211
|
+
...env.TB_OBJECT_STORE_REGION !== void 0 && env.TB_OBJECT_STORE_REGION.length > 0 ? { region: env.TB_OBJECT_STORE_REGION } : {}
|
|
212
|
+
};
|
|
213
|
+
}
|
|
166
214
|
if (env.TB_BOOTSTRAP_ADMIN_SK !== void 0 && env.TB_BOOTSTRAP_ADMIN_SK.length > 0) {
|
|
167
215
|
config.adminSk = env.TB_BOOTSTRAP_ADMIN_SK;
|
|
168
216
|
}
|
|
@@ -394,8 +442,15 @@ var BUILTIN_DESCRIPTIONS = {
|
|
|
394
442
|
async function mintAdminWithPlaintext(store, plaintext, now) {
|
|
395
443
|
const { key } = await mintKey(adminBootstrapInput(), now);
|
|
396
444
|
const adminKey = { ...key, hash: await sha256Hex(plaintext) };
|
|
397
|
-
|
|
398
|
-
|
|
445
|
+
const hashKey = KEY_SK_HASH + adminKey.hash;
|
|
446
|
+
let won;
|
|
447
|
+
if (store.putIfAbsent !== void 0) {
|
|
448
|
+
won = await store.putIfAbsent(hashKey, adminKey);
|
|
449
|
+
} else {
|
|
450
|
+
won = await store.get(hashKey) === null;
|
|
451
|
+
if (won) await store.put(hashKey, adminKey);
|
|
452
|
+
}
|
|
453
|
+
if (won) await store.put(KEY_SK_ID + adminKey.id, adminKey.hash);
|
|
399
454
|
}
|
|
400
455
|
async function ensureBuiltinNodes(registry, now) {
|
|
401
456
|
const ensure = async (node) => {
|
|
@@ -963,6 +1018,15 @@ async function invokeDevice(deps, deviceId, req) {
|
|
|
963
1018
|
if (body.ok) return body.value;
|
|
964
1019
|
throw tbErrorFromBody(body.error);
|
|
965
1020
|
}
|
|
1021
|
+
function deviceCallContextFrom(ctx) {
|
|
1022
|
+
const now = Date.now();
|
|
1023
|
+
return {
|
|
1024
|
+
caller: { keyId: ctx.keyId, owner: ctx.owner },
|
|
1025
|
+
traceId: ctx.traceId,
|
|
1026
|
+
createdAt: new Date(now).toISOString(),
|
|
1027
|
+
expiresAt: new Date(now + DEVICE_CALL_TIMEOUT_MS).toISOString()
|
|
1028
|
+
};
|
|
1029
|
+
}
|
|
966
1030
|
function deviceMarkerOf(pc) {
|
|
967
1031
|
if (pc === void 0 || typeof pc.deviceId !== "string" || typeof pc.mountPath !== "string") {
|
|
968
1032
|
return null;
|
|
@@ -14197,8 +14261,8 @@ function applyPostAuth(clientId, clientSecret, params) {
|
|
|
14197
14261
|
function applyPublicAuth(clientId, params) {
|
|
14198
14262
|
params.set("client_id", clientId);
|
|
14199
14263
|
}
|
|
14200
|
-
function isLoopbackHost(
|
|
14201
|
-
return
|
|
14264
|
+
function isLoopbackHost(hostname2) {
|
|
14265
|
+
return hostname2 === "localhost" || hostname2 === "127.0.0.1" || hostname2 === "[::1]" || hostname2 === "::1";
|
|
14202
14266
|
}
|
|
14203
14267
|
function assertSecureTokenEndpoint(tokenEndpoint) {
|
|
14204
14268
|
const url2 = new URL(String(tokenEndpoint));
|
|
@@ -19922,7 +19986,7 @@ async function processDeviceHello(opts) {
|
|
|
19922
19986
|
input,
|
|
19923
19987
|
authCtx.keyId,
|
|
19924
19988
|
now,
|
|
19925
|
-
input.path === mountPath ? { online: true } : {}
|
|
19989
|
+
input.path === mountPath ? { online: true, lastSeenAt: now } : {}
|
|
19926
19990
|
);
|
|
19927
19991
|
} catch (error) {
|
|
19928
19992
|
await searchSync?.abort(marker);
|
|
@@ -19932,8 +19996,8 @@ async function processDeviceHello(opts) {
|
|
|
19932
19996
|
throw error;
|
|
19933
19997
|
}
|
|
19934
19998
|
}
|
|
19935
|
-
await searchSync
|
|
19936
|
-
return { mountPath, keyId: authCtx.keyId };
|
|
19999
|
+
const searchIndexed = searchSync === void 0 ? true : await searchSync.rebuildAll(searchMarkers);
|
|
20000
|
+
return { mountPath, keyId: authCtx.keyId, searchIndexed };
|
|
19937
20001
|
}
|
|
19938
20002
|
|
|
19939
20003
|
// ../../node_modules/.pnpm/@modelcontextprotocol+server@2.0.0/node_modules/@modelcontextprotocol/server/dist/chunk-Br0eD_fh.mjs
|
|
@@ -35898,9 +35962,15 @@ async function assertRegisterPath2(registry, ctx, targetPath, action, deps) {
|
|
|
35898
35962
|
});
|
|
35899
35963
|
if (!res.allow) throw res.error;
|
|
35900
35964
|
}
|
|
35901
|
-
function toEntry(n) {
|
|
35965
|
+
function toEntry(n, now) {
|
|
35902
35966
|
const e = { path: n.path, kind: n.kind, description: n.description };
|
|
35903
|
-
if (n.online !== void 0)
|
|
35967
|
+
if (n.online !== void 0) {
|
|
35968
|
+
e.presence = derivePresence({
|
|
35969
|
+
online: n.online,
|
|
35970
|
+
...n.lastSeenAt !== void 0 ? { lastSeenAt: n.lastSeenAt } : {},
|
|
35971
|
+
now
|
|
35972
|
+
});
|
|
35973
|
+
}
|
|
35904
35974
|
return e;
|
|
35905
35975
|
}
|
|
35906
35976
|
function filterListVisible(nodes, scopes) {
|
|
@@ -36156,7 +36226,7 @@ function localizeRemoteEntry(mountPath, remoteParentPath, entry) {
|
|
|
36156
36226
|
kind: entry.kind,
|
|
36157
36227
|
description: entry.description
|
|
36158
36228
|
};
|
|
36159
|
-
if (entry.
|
|
36229
|
+
if (entry.presence !== void 0) out.presence = entry.presence;
|
|
36160
36230
|
return out;
|
|
36161
36231
|
}
|
|
36162
36232
|
async function resolveRemoteSettings(state, base) {
|
|
@@ -36460,6 +36530,17 @@ function registerPublicRoutes(app, env) {
|
|
|
36460
36530
|
...catalog !== void 0 && digest !== void 0 ? { catalog: { count: Object.keys(catalog).length, digest } } : {}
|
|
36461
36531
|
});
|
|
36462
36532
|
});
|
|
36533
|
+
app.get("/livez", (c) => c.json({ live: true }));
|
|
36534
|
+
app.get("/readyz", async (c) => {
|
|
36535
|
+
const probe = deps.readiness;
|
|
36536
|
+
if (probe === void 0) return c.json({ checks: {}, ready: true });
|
|
36537
|
+
try {
|
|
36538
|
+
const report = await probe();
|
|
36539
|
+
return c.json(report, report.ready ? 200 : 503);
|
|
36540
|
+
} catch {
|
|
36541
|
+
return c.json({ checks: {}, ready: false }, 503);
|
|
36542
|
+
}
|
|
36543
|
+
});
|
|
36463
36544
|
app.get(
|
|
36464
36545
|
"/~ref/:token",
|
|
36465
36546
|
(c) => runHandler(async () => {
|
|
@@ -36699,6 +36780,7 @@ function registerSearchRoute(app, env) {
|
|
|
36699
36780
|
|
|
36700
36781
|
// ../app/src/helpModel.ts
|
|
36701
36782
|
async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
36783
|
+
const index = opts.schemas !== true;
|
|
36702
36784
|
if (node.kind === "builtin" && node.config?.kind === "builtin") {
|
|
36703
36785
|
const mod = builtins.get(node.config.module);
|
|
36704
36786
|
if (mod) return mod.help(node.path);
|
|
@@ -36710,8 +36792,13 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
|
36710
36792
|
ctx.scopes
|
|
36711
36793
|
);
|
|
36712
36794
|
if (node.online !== void 0) {
|
|
36795
|
+
const presence = derivePresence({
|
|
36796
|
+
online: node.online,
|
|
36797
|
+
...node.lastSeenAt !== void 0 ? { lastSeenAt: node.lastSeenAt } : {},
|
|
36798
|
+
now: opts.now
|
|
36799
|
+
});
|
|
36713
36800
|
return deviceDirectoryHelpModel(
|
|
36714
|
-
{ path: node.path, description: node.description,
|
|
36801
|
+
{ path: node.path, description: node.description, presence },
|
|
36715
36802
|
children.map((n) => ({ path: n.path, kind: n.kind, description: n.description }))
|
|
36716
36803
|
);
|
|
36717
36804
|
}
|
|
@@ -36728,7 +36815,7 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
|
36728
36815
|
node.path,
|
|
36729
36816
|
{ kind: node.kind, description: node.description },
|
|
36730
36817
|
toolMarker.cmds ?? [],
|
|
36731
|
-
{ index
|
|
36818
|
+
{ index }
|
|
36732
36819
|
);
|
|
36733
36820
|
}
|
|
36734
36821
|
if (node.kind === "mcp" || node.kind === "http" || node.kind === "tool") {
|
|
@@ -36739,9 +36826,7 @@ async function helpModelFor(node, registry, ctx, builtins, deps, opts) {
|
|
|
36739
36826
|
node.path,
|
|
36740
36827
|
{ kind: node.kind, description: node.description },
|
|
36741
36828
|
exposed,
|
|
36742
|
-
{
|
|
36743
|
-
index: true
|
|
36744
|
-
}
|
|
36829
|
+
{ index }
|
|
36745
36830
|
);
|
|
36746
36831
|
}
|
|
36747
36832
|
if (node.kind === "device" && node.config?.kind === "device") {
|
|
@@ -37207,7 +37292,8 @@ async function handleInvoke(c, env) {
|
|
|
37207
37292
|
const result2 = await invokeDevice(deps, toolMarker.deviceId, {
|
|
37208
37293
|
path: relativeDevicePath(node.path, toolMarker.mountPath),
|
|
37209
37294
|
tool,
|
|
37210
|
-
arguments: args2
|
|
37295
|
+
arguments: args2,
|
|
37296
|
+
context: deviceCallContextFrom(ctx)
|
|
37211
37297
|
});
|
|
37212
37298
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
37213
37299
|
}
|
|
@@ -37236,7 +37322,8 @@ async function handleInvoke(c, env) {
|
|
|
37236
37322
|
const result2 = await invokeDevice(deps, node.config.deviceId, {
|
|
37237
37323
|
path: "shell",
|
|
37238
37324
|
tool: body2.tool,
|
|
37239
|
-
arguments: body2.arguments ?? {}
|
|
37325
|
+
arguments: body2.arguments ?? {},
|
|
37326
|
+
context: deviceCallContextFrom(ctx)
|
|
37240
37327
|
});
|
|
37241
37328
|
return renderResult(result2, negotiate(c.req.header("accept")));
|
|
37242
37329
|
}
|
|
@@ -37262,7 +37349,8 @@ async function handleInvoke(c, env) {
|
|
|
37262
37349
|
const result3 = await invokeDevice(deps, deviceIdForDeviceFs(cfg), {
|
|
37263
37350
|
path: "fs",
|
|
37264
37351
|
tool: body2.tool,
|
|
37265
|
-
arguments: args2
|
|
37352
|
+
arguments: args2,
|
|
37353
|
+
context: deviceCallContextFrom(ctx)
|
|
37266
37354
|
});
|
|
37267
37355
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
37268
37356
|
}
|
|
@@ -37271,7 +37359,8 @@ async function handleInvoke(c, env) {
|
|
|
37271
37359
|
const result3 = await invokeDevice(deps, contextMarker.deviceId, {
|
|
37272
37360
|
path: relativeDevicePath(node.path, contextMarker.mountPath),
|
|
37273
37361
|
tool: body2.tool,
|
|
37274
|
-
arguments: args2
|
|
37362
|
+
arguments: args2,
|
|
37363
|
+
context: deviceCallContextFrom(ctx)
|
|
37275
37364
|
});
|
|
37276
37365
|
return renderResult(result3, negotiate(c.req.header("accept")));
|
|
37277
37366
|
}
|
|
@@ -37497,8 +37586,10 @@ async function handleHelp(c, env) {
|
|
|
37497
37586
|
}
|
|
37498
37587
|
const builtins = builtinsOf(store);
|
|
37499
37588
|
const refresh = c.req.query("refresh") === "1";
|
|
37589
|
+
const schemas = c.req.query("schemas") === "1";
|
|
37500
37590
|
const model = await helpModelFor(node, registry, ctx, builtins, deps, {
|
|
37501
37591
|
refresh,
|
|
37592
|
+
schemas,
|
|
37502
37593
|
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
37503
37594
|
});
|
|
37504
37595
|
return renderHelp(await enrichHelp(model, path, store), rep);
|
|
@@ -37514,6 +37605,7 @@ async function handleTree(c, env) {
|
|
|
37514
37605
|
const store = c.get("store");
|
|
37515
37606
|
if (path !== "" && !check(ctx, path, "read").allow) throw TBError.notFound("not found");
|
|
37516
37607
|
const registry = new NodeRegistryStore(store);
|
|
37608
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
37517
37609
|
if (path !== "") {
|
|
37518
37610
|
const remote = await remotePassthroughIfMatch(c, ctx, registry, path, "~tree", deps);
|
|
37519
37611
|
if (remote) return remote;
|
|
@@ -37529,14 +37621,14 @@ async function handleTree(c, env) {
|
|
|
37529
37621
|
if (rootNode.kind === "context" && rootNode.config?.kind === "context") {
|
|
37530
37622
|
await assertContextAlive(rootNode, rootNode.config, registry);
|
|
37531
37623
|
}
|
|
37532
|
-
rootEntry = toEntry(rootNode);
|
|
37624
|
+
rootEntry = toEntry(rootNode, now);
|
|
37533
37625
|
}
|
|
37534
37626
|
const nodes = await pruneExpiredContext(await registry.subtree(path), registry);
|
|
37535
37627
|
const byParent = indexByParent(nodes);
|
|
37536
37628
|
const getChildren = async (p) => {
|
|
37537
37629
|
const localKids = filterListVisible(byParent.get(p) ?? [], ctx.scopes);
|
|
37538
37630
|
const remoteKids = await remoteTreeChildren(c, ctx, registry, p, deps);
|
|
37539
|
-
return [...localKids.map((n) => toEntry(n)), ...remoteKids];
|
|
37631
|
+
return [...localKids.map((n) => toEntry(n, now)), ...remoteKids];
|
|
37540
37632
|
};
|
|
37541
37633
|
const depth = clampDepth(Number(c.req.query("depth")));
|
|
37542
37634
|
const tree = await buildTree({
|
|
@@ -37683,14 +37775,26 @@ var DeviceHub = class {
|
|
|
37683
37775
|
connections = /* @__PURE__ */ new Set();
|
|
37684
37776
|
reclaimTimers = /* @__PURE__ */ new Map();
|
|
37685
37777
|
heartbeat;
|
|
37778
|
+
/** 多副本路由;缺省(单副本)时所有跨副本逻辑短路,行为与之前完全一致。 */
|
|
37779
|
+
router;
|
|
37686
37780
|
constructor(opts) {
|
|
37687
37781
|
this.store = opts.store;
|
|
37688
37782
|
this.search = opts.search;
|
|
37689
37783
|
this.reclaimSec = opts.reclaimSec;
|
|
37784
|
+
this.router = opts.router?.(
|
|
37785
|
+
async (deviceId, req) => await this.invokeLocal(deviceId, req)
|
|
37786
|
+
);
|
|
37690
37787
|
const heartbeatMs = opts.heartbeatMs ?? DEFAULT_HEARTBEAT_MS;
|
|
37691
37788
|
this.heartbeat = setInterval(() => this.pingConnections(), heartbeatMs);
|
|
37692
37789
|
this.heartbeat.unref?.();
|
|
37693
37790
|
}
|
|
37791
|
+
/**
|
|
37792
|
+
* 启动多副本路由(订阅本副本频道 + 路由续期)。单副本时是 no-op。
|
|
37793
|
+
* 必须在开始服务前完成:订阅没建好时转发进来的调用会丢。
|
|
37794
|
+
*/
|
|
37795
|
+
async startRouter() {
|
|
37796
|
+
await this.router?.start();
|
|
37797
|
+
}
|
|
37694
37798
|
/** 挂到 http.Server 的 'upgrade' 事件(仅处理 DEVICE_WS_PATH,其余 404)。 */
|
|
37695
37799
|
attach(server) {
|
|
37696
37800
|
server.on("upgrade", (req, socket, head) => {
|
|
@@ -37702,8 +37806,26 @@ var DeviceHub = class {
|
|
|
37702
37806
|
});
|
|
37703
37807
|
});
|
|
37704
37808
|
}
|
|
37705
|
-
/**
|
|
37809
|
+
/**
|
|
37810
|
+
* DeviceChannel.invoke:HTTP→WS 调用转发。
|
|
37811
|
+
*
|
|
37812
|
+
* 本副本持有该设备 → 直接走本地 socket(单副本部署恒走这条,零额外开销)。
|
|
37813
|
+
* 本地没有但配了 router → 问路由表并转发给持有者副本(socket 不可序列化,
|
|
37814
|
+
* 只能把调用送到 socket 所在进程)。都没有 → deviceOffline。
|
|
37815
|
+
*/
|
|
37706
37816
|
async invoke(deviceId, req) {
|
|
37817
|
+
if (this.activeByDevice.has(deviceId)) return await this.invokeLocal(deviceId, req);
|
|
37818
|
+
const offline = { ok: false, error: TBError.deviceOffline().toJSON() };
|
|
37819
|
+
if (this.router === void 0) return offline;
|
|
37820
|
+
return await this.router.forward(deviceId, req) ?? offline;
|
|
37821
|
+
}
|
|
37822
|
+
/**
|
|
37823
|
+
* 只走本副本 socket 的调用执行。
|
|
37824
|
+
*
|
|
37825
|
+
* router 收到转发来的调用时回调这里 —— **不能**回调 `invoke`:本地连接刚断时
|
|
37826
|
+
* 那会再次查路由并转发,在两个副本间弹来弹去。这里没有连接就直接 offline。
|
|
37827
|
+
*/
|
|
37828
|
+
async invokeLocal(deviceId, req) {
|
|
37707
37829
|
const offline = { ok: false, error: TBError.deviceOffline().toJSON() };
|
|
37708
37830
|
const conn = this.activeByDevice.get(deviceId);
|
|
37709
37831
|
if (conn === void 0) return offline;
|
|
@@ -37741,8 +37863,15 @@ var DeviceHub = class {
|
|
|
37741
37863
|
if (!isDeviceMeta(item.value)) continue;
|
|
37742
37864
|
const meta = item.value;
|
|
37743
37865
|
if (this.activeByDevice.has(meta.deviceId)) continue;
|
|
37866
|
+
if (this.router !== void 0 && await this.router.isOnlineAnywhere(meta.deviceId)) {
|
|
37867
|
+
continue;
|
|
37868
|
+
}
|
|
37744
37869
|
if (meta.disconnectedAt === void 0) {
|
|
37745
37870
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
37871
|
+
try {
|
|
37872
|
+
await new NodeRegistryStore(this.store).setOnline(meta.mountPath, false, now);
|
|
37873
|
+
} catch {
|
|
37874
|
+
}
|
|
37746
37875
|
await this.store.put(KEY_DEVICE_META + meta.deviceId, { ...meta, disconnectedAt: now });
|
|
37747
37876
|
this.scheduleReclaim(meta.deviceId, this.reclaimSec * 1e3);
|
|
37748
37877
|
} else {
|
|
@@ -37762,6 +37891,10 @@ var DeviceHub = class {
|
|
|
37762
37891
|
for (const conn of this.connections) conn.ws.terminate();
|
|
37763
37892
|
this.connections.clear();
|
|
37764
37893
|
this.activeByDevice.clear();
|
|
37894
|
+
const router = this.router;
|
|
37895
|
+
this.router = void 0;
|
|
37896
|
+
await router?.close().catch(() => {
|
|
37897
|
+
});
|
|
37765
37898
|
await new Promise((resolve3) => this.wss.close(() => resolve3()));
|
|
37766
37899
|
}
|
|
37767
37900
|
async handleUpgrade(req, socket, head) {
|
|
@@ -37807,6 +37940,10 @@ var DeviceHub = class {
|
|
|
37807
37940
|
this.connections.add(conn);
|
|
37808
37941
|
ws.on("pong", () => {
|
|
37809
37942
|
conn.isAlive = true;
|
|
37943
|
+
if (conn.mountPath !== void 0) {
|
|
37944
|
+
void new NodeRegistryStore(this.store).touchSeen(conn.mountPath, (/* @__PURE__ */ new Date()).toISOString()).catch(() => {
|
|
37945
|
+
});
|
|
37946
|
+
}
|
|
37810
37947
|
});
|
|
37811
37948
|
ws.on("message", (data) => {
|
|
37812
37949
|
try {
|
|
@@ -37823,7 +37960,7 @@ var DeviceHub = class {
|
|
|
37823
37960
|
}
|
|
37824
37961
|
async acceptHello(conn, hello) {
|
|
37825
37962
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
37826
|
-
const { mountPath, keyId } = await processDeviceHello({
|
|
37963
|
+
const { mountPath, keyId, searchIndexed } = await processDeviceHello({
|
|
37827
37964
|
store: this.store,
|
|
37828
37965
|
authorization: conn.authorization,
|
|
37829
37966
|
deviceIdHint: conn.deviceId,
|
|
@@ -37831,6 +37968,11 @@ var DeviceHub = class {
|
|
|
37831
37968
|
now,
|
|
37832
37969
|
...this.search === void 0 ? {} : { search: this.search }
|
|
37833
37970
|
});
|
|
37971
|
+
if (!searchIndexed) {
|
|
37972
|
+
console.warn(
|
|
37973
|
+
`[tool-bridge] device '${conn.deviceId}' mounted at ${mountPath} but its tools are NOT in the search index: registry exceeds the tool-search capacity (TOOL_SEARCH_AUDIT_NODE_LIMIT). The device is callable but won't appear in \`tb search\` until capacity frees up.`
|
|
37974
|
+
);
|
|
37975
|
+
}
|
|
37834
37976
|
const meta = { deviceId: conn.deviceId, mountPath, keyId, connectedAt: now };
|
|
37835
37977
|
await this.store.put(KEY_DEVICE_META + conn.deviceId, meta);
|
|
37836
37978
|
conn.keyId = keyId;
|
|
@@ -37839,6 +37981,8 @@ var DeviceHub = class {
|
|
|
37839
37981
|
const prev = this.activeByDevice.get(conn.deviceId);
|
|
37840
37982
|
if (prev !== void 0 && prev !== conn) prev.ws.close(1e3, "replaced");
|
|
37841
37983
|
this.activeByDevice.set(conn.deviceId, conn);
|
|
37984
|
+
await this.router?.claim(conn.deviceId).catch(() => {
|
|
37985
|
+
});
|
|
37842
37986
|
conn.session.accept(mountPath);
|
|
37843
37987
|
}
|
|
37844
37988
|
/** 连接失效收尾(对位 DO markDisconnected):registry 下线 + meta 记断线 + 排回收。 */
|
|
@@ -37847,6 +37991,8 @@ var DeviceHub = class {
|
|
|
37847
37991
|
this.connections.delete(conn);
|
|
37848
37992
|
if (this.activeByDevice.get(conn.deviceId) !== conn) return;
|
|
37849
37993
|
this.activeByDevice.delete(conn.deviceId);
|
|
37994
|
+
await this.router?.release(conn.deviceId).catch(() => {
|
|
37995
|
+
});
|
|
37850
37996
|
const raw = await this.store.get(KEY_DEVICE_META + conn.deviceId);
|
|
37851
37997
|
if (!isDeviceMeta(raw)) return;
|
|
37852
37998
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -37877,6 +38023,13 @@ var DeviceHub = class {
|
|
|
37877
38023
|
async reclaim(deviceId) {
|
|
37878
38024
|
this.reclaimTimers.delete(deviceId);
|
|
37879
38025
|
if (this.activeByDevice.has(deviceId)) return;
|
|
38026
|
+
if (this.router !== void 0) {
|
|
38027
|
+
try {
|
|
38028
|
+
if (await this.router.isOnlineAnywhere(deviceId)) return;
|
|
38029
|
+
} catch {
|
|
38030
|
+
return;
|
|
38031
|
+
}
|
|
38032
|
+
}
|
|
37880
38033
|
const raw = await this.store.get(KEY_DEVICE_META + deviceId);
|
|
37881
38034
|
if (!isDeviceMeta(raw)) return;
|
|
37882
38035
|
const searchSync = this.search === void 0 ? void 0 : new SearchSynchronizer(this.store, this.search);
|
|
@@ -37901,6 +38054,146 @@ var DeviceHub = class {
|
|
|
37901
38054
|
}
|
|
37902
38055
|
};
|
|
37903
38056
|
|
|
38057
|
+
// src/deviceRouter.ts
|
|
38058
|
+
var DEVICE_ROUTE_TTL_SEC = 30;
|
|
38059
|
+
var DEVICE_FORWARD_TIMEOUT_MS = 65e3;
|
|
38060
|
+
var deviceRouteKey = (deviceId) => `tb:device:route:${deviceId}`;
|
|
38061
|
+
var deviceCallChannel = (replicaId) => `tb:device:call:${replicaId}`;
|
|
38062
|
+
var deviceReplyChannel = (replicaId) => `tb:device:reply:${replicaId}`;
|
|
38063
|
+
var DeviceRouter = class _DeviceRouter {
|
|
38064
|
+
constructor(replicaId, backend, opts) {
|
|
38065
|
+
this.replicaId = replicaId;
|
|
38066
|
+
this.backend = backend;
|
|
38067
|
+
this.opts = opts;
|
|
38068
|
+
}
|
|
38069
|
+
replicaId;
|
|
38070
|
+
backend;
|
|
38071
|
+
opts;
|
|
38072
|
+
pending = /* @__PURE__ */ new Map();
|
|
38073
|
+
renewTimer;
|
|
38074
|
+
/** 本副本当前持有的设备(需周期续期路由条目)。 */
|
|
38075
|
+
owned = /* @__PURE__ */ new Set();
|
|
38076
|
+
get ttlSec() {
|
|
38077
|
+
return this.opts.ttlSec ?? DEVICE_ROUTE_TTL_SEC;
|
|
38078
|
+
}
|
|
38079
|
+
/** 订阅本副本的两个频道并启动路由续期。 */
|
|
38080
|
+
async start() {
|
|
38081
|
+
await this.backend.subscribe(deviceCallChannel(this.replicaId), (payload) => {
|
|
38082
|
+
void this.handleForwardedCall(payload);
|
|
38083
|
+
});
|
|
38084
|
+
await this.backend.subscribe(deviceReplyChannel(this.replicaId), (payload) => {
|
|
38085
|
+
this.handleReply(payload);
|
|
38086
|
+
});
|
|
38087
|
+
const renewMs = Math.max(1e3, Math.floor(this.ttlSec * 1e3 / 3));
|
|
38088
|
+
this.renewTimer = setInterval(() => {
|
|
38089
|
+
void this.renewOwned();
|
|
38090
|
+
}, renewMs);
|
|
38091
|
+
this.renewTimer.unref?.();
|
|
38092
|
+
}
|
|
38093
|
+
/** 本副本接受了某设备的连接:声明所有权。 */
|
|
38094
|
+
async claim(deviceId) {
|
|
38095
|
+
this.owned.add(deviceId);
|
|
38096
|
+
await this.backend.setRoute(deviceId, this.replicaId, this.ttlSec);
|
|
38097
|
+
}
|
|
38098
|
+
/** 本副本失去某设备的连接:释放所有权。 */
|
|
38099
|
+
async release(deviceId) {
|
|
38100
|
+
this.owned.delete(deviceId);
|
|
38101
|
+
await this.backend.clearRoute(deviceId, this.replicaId);
|
|
38102
|
+
}
|
|
38103
|
+
/** 某设备当前是否在**任一**副本上在线(reclaim 判定用,不能只看本副本)。 */
|
|
38104
|
+
async isOnlineAnywhere(deviceId) {
|
|
38105
|
+
return await this.backend.lookupRoute(deviceId) !== null;
|
|
38106
|
+
}
|
|
38107
|
+
/**
|
|
38108
|
+
* 把调用转发给持有该设备的副本并等结果。
|
|
38109
|
+
* 无人持有 → null(调用方按 deviceOffline 处理)。
|
|
38110
|
+
*/
|
|
38111
|
+
async forward(deviceId, req) {
|
|
38112
|
+
const owner = await this.backend.lookupRoute(deviceId);
|
|
38113
|
+
if (owner === null) return null;
|
|
38114
|
+
if (owner === this.replicaId) return null;
|
|
38115
|
+
const correlationId = `${this.replicaId}:${deviceId}:${++_DeviceRouter.seq}`;
|
|
38116
|
+
const call = {
|
|
38117
|
+
correlationId,
|
|
38118
|
+
deviceId,
|
|
38119
|
+
replyTo: this.replicaId,
|
|
38120
|
+
req
|
|
38121
|
+
};
|
|
38122
|
+
return await new Promise((resolve3) => {
|
|
38123
|
+
const timer = setTimeout(() => {
|
|
38124
|
+
this.pending.delete(correlationId);
|
|
38125
|
+
resolve3({ ok: false, error: new TBError("unavailable", "\u8BBE\u5907\u8C03\u7528\u8F6C\u53D1\u8D85\u65F6").toJSON() });
|
|
38126
|
+
}, this.opts.forwardTimeoutMs ?? DEVICE_FORWARD_TIMEOUT_MS);
|
|
38127
|
+
timer.unref?.();
|
|
38128
|
+
this.pending.set(correlationId, { resolve: resolve3, timer });
|
|
38129
|
+
void this.backend.publish(deviceCallChannel(owner), JSON.stringify(call)).catch(() => {
|
|
38130
|
+
const waiter = this.pending.get(correlationId);
|
|
38131
|
+
if (waiter === void 0) return;
|
|
38132
|
+
this.pending.delete(correlationId);
|
|
38133
|
+
clearTimeout(waiter.timer);
|
|
38134
|
+
waiter.resolve({
|
|
38135
|
+
ok: false,
|
|
38136
|
+
error: new TBError("unavailable", "\u8BBE\u5907\u8C03\u7528\u8F6C\u53D1\u5931\u8D25").toJSON()
|
|
38137
|
+
});
|
|
38138
|
+
});
|
|
38139
|
+
});
|
|
38140
|
+
}
|
|
38141
|
+
async close() {
|
|
38142
|
+
if (this.renewTimer !== void 0) clearInterval(this.renewTimer);
|
|
38143
|
+
this.renewTimer = void 0;
|
|
38144
|
+
for (const [, waiter] of this.pending) {
|
|
38145
|
+
clearTimeout(waiter.timer);
|
|
38146
|
+
waiter.resolve({ ok: false, error: new TBError("unavailable", "\u7F51\u5173\u6B63\u5728\u5173\u95ED").toJSON() });
|
|
38147
|
+
}
|
|
38148
|
+
this.pending.clear();
|
|
38149
|
+
await Promise.all([...this.owned].map(async (id) => await this.release(id)));
|
|
38150
|
+
await this.backend.close();
|
|
38151
|
+
}
|
|
38152
|
+
/** correlationId 的进程内序号(与 replicaId 组合后全局唯一)。 */
|
|
38153
|
+
static seq = 0;
|
|
38154
|
+
async renewOwned() {
|
|
38155
|
+
for (const deviceId of this.owned) {
|
|
38156
|
+
try {
|
|
38157
|
+
await this.backend.setRoute(deviceId, this.replicaId, this.ttlSec);
|
|
38158
|
+
} catch {
|
|
38159
|
+
}
|
|
38160
|
+
}
|
|
38161
|
+
}
|
|
38162
|
+
/** 作为持有者:执行本地调用并回执。 */
|
|
38163
|
+
async handleForwardedCall(payload) {
|
|
38164
|
+
let call;
|
|
38165
|
+
try {
|
|
38166
|
+
call = JSON.parse(payload);
|
|
38167
|
+
} catch {
|
|
38168
|
+
return;
|
|
38169
|
+
}
|
|
38170
|
+
if (typeof call?.correlationId !== "string" || typeof call?.replyTo !== "string") return;
|
|
38171
|
+
let result;
|
|
38172
|
+
try {
|
|
38173
|
+
result = await this.opts.onLocalCall(call.deviceId, call.req);
|
|
38174
|
+
} catch {
|
|
38175
|
+
result = { ok: false, error: new TBError("internal", "\u8BBE\u5907\u8C03\u7528\u6267\u884C\u5931\u8D25").toJSON() };
|
|
38176
|
+
}
|
|
38177
|
+
const reply = { correlationId: call.correlationId, result };
|
|
38178
|
+
await this.backend.publish(deviceReplyChannel(call.replyTo), JSON.stringify(reply)).catch(() => {
|
|
38179
|
+
});
|
|
38180
|
+
}
|
|
38181
|
+
/** 作为发起方:收到回执,唤醒等待者。 */
|
|
38182
|
+
handleReply(payload) {
|
|
38183
|
+
let reply;
|
|
38184
|
+
try {
|
|
38185
|
+
reply = JSON.parse(payload);
|
|
38186
|
+
} catch {
|
|
38187
|
+
return;
|
|
38188
|
+
}
|
|
38189
|
+
const waiter = this.pending.get(reply?.correlationId);
|
|
38190
|
+
if (waiter === void 0) return;
|
|
38191
|
+
this.pending.delete(reply.correlationId);
|
|
38192
|
+
clearTimeout(waiter.timer);
|
|
38193
|
+
waiter.resolve(reply.result);
|
|
38194
|
+
}
|
|
38195
|
+
};
|
|
38196
|
+
|
|
37904
38197
|
// ../core/src/node/fsObjectStore.ts
|
|
37905
38198
|
import { mkdir, readdir, readFile, realpath, rm, stat, writeFile } from "fs/promises";
|
|
37906
38199
|
import { basename, dirname, join, resolve, sep } from "path";
|
|
@@ -38185,6 +38478,219 @@ function createDataObjectStore(dataDir) {
|
|
|
38185
38478
|
};
|
|
38186
38479
|
}
|
|
38187
38480
|
|
|
38481
|
+
// src/pgSearchIndex.ts
|
|
38482
|
+
var PgSearchDriver = class {
|
|
38483
|
+
constructor(sql) {
|
|
38484
|
+
this.sql = sql;
|
|
38485
|
+
}
|
|
38486
|
+
sql;
|
|
38487
|
+
schemaReady;
|
|
38488
|
+
async all(statement) {
|
|
38489
|
+
return await this.sql.unsafe(statement.sql, statement.params);
|
|
38490
|
+
}
|
|
38491
|
+
async initializeSchema() {
|
|
38492
|
+
try {
|
|
38493
|
+
await this.sql.begin(async (tx) => {
|
|
38494
|
+
for (const ddl of pgSearchDialect.schemaStatements) {
|
|
38495
|
+
await tx.unsafe(ddl);
|
|
38496
|
+
}
|
|
38497
|
+
});
|
|
38498
|
+
} catch (error) {
|
|
38499
|
+
this.schemaReady = void 0;
|
|
38500
|
+
throw error;
|
|
38501
|
+
}
|
|
38502
|
+
}
|
|
38503
|
+
ensureSchema() {
|
|
38504
|
+
this.schemaReady ??= this.initializeSchema();
|
|
38505
|
+
return this.schemaReady;
|
|
38506
|
+
}
|
|
38507
|
+
async first(statement) {
|
|
38508
|
+
const rows = await this.sql.unsafe(statement.sql, statement.params);
|
|
38509
|
+
return rows[0] ?? null;
|
|
38510
|
+
}
|
|
38511
|
+
/**
|
|
38512
|
+
* 攒成多行 VALUES 批量插入,而非逐条。
|
|
38513
|
+
* 逐条对 4000 行需 ~920ms(每行一次事务内往返),批量后 ~12ms。
|
|
38514
|
+
*/
|
|
38515
|
+
bulk(items, table, columns, toParams) {
|
|
38516
|
+
const statements = [];
|
|
38517
|
+
for (let i = 0; i < items.length; i += PG_SEARCH_INSERT_ROWS_MAX) {
|
|
38518
|
+
const chunk = items.slice(i, i + PG_SEARCH_INSERT_ROWS_MAX);
|
|
38519
|
+
statements.push({
|
|
38520
|
+
params: chunk.flatMap(toParams),
|
|
38521
|
+
sql: pgBulkInsertSql(table, columns, chunk.length)
|
|
38522
|
+
});
|
|
38523
|
+
}
|
|
38524
|
+
return statements;
|
|
38525
|
+
}
|
|
38526
|
+
insertRecords(records) {
|
|
38527
|
+
return this.bulk(
|
|
38528
|
+
records,
|
|
38529
|
+
PG_SEARCH_TOOLS_TABLE,
|
|
38530
|
+
PG_SEARCH_TOOLS_COLUMNS,
|
|
38531
|
+
(record2) => [record2.path, record2.name, record2.description, record2.feedback]
|
|
38532
|
+
);
|
|
38533
|
+
}
|
|
38534
|
+
insertSnapshots(digests) {
|
|
38535
|
+
return this.bulk(
|
|
38536
|
+
[...digests],
|
|
38537
|
+
PG_SEARCH_SNAPSHOTS_TABLE,
|
|
38538
|
+
PG_SEARCH_SNAPSHOTS_COLUMNS,
|
|
38539
|
+
([path, digest]) => [path, digest]
|
|
38540
|
+
);
|
|
38541
|
+
}
|
|
38542
|
+
async write(statements) {
|
|
38543
|
+
await this.sql.begin(async (tx) => {
|
|
38544
|
+
await tx`SELECT pg_advisory_xact_lock(${PG_SEARCH_WRITE_LOCK_KEY})`;
|
|
38545
|
+
for (const statement of statements) {
|
|
38546
|
+
await tx.unsafe(statement.sql, statement.params);
|
|
38547
|
+
}
|
|
38548
|
+
});
|
|
38549
|
+
}
|
|
38550
|
+
};
|
|
38551
|
+
var PgSearchIndex = class extends SqlSearchIndex {
|
|
38552
|
+
constructor(sql) {
|
|
38553
|
+
super(new PgSearchDriver(sql), pgSearchDialect);
|
|
38554
|
+
}
|
|
38555
|
+
};
|
|
38556
|
+
|
|
38557
|
+
// src/pgStateStore.ts
|
|
38558
|
+
var DEFAULT_LIST_LIMIT = 1e3;
|
|
38559
|
+
var MAX_CODE_POINT = 1114111;
|
|
38560
|
+
var SURROGATE_START = 55296;
|
|
38561
|
+
var SURROGATE_END = 57343;
|
|
38562
|
+
function prefixUpperBound(prefix) {
|
|
38563
|
+
const points = [...prefix];
|
|
38564
|
+
for (let i = points.length - 1; i >= 0; i--) {
|
|
38565
|
+
const code = points[i]?.codePointAt(0);
|
|
38566
|
+
if (code === void 0 || code >= MAX_CODE_POINT) continue;
|
|
38567
|
+
let next = code + 1;
|
|
38568
|
+
if (next >= SURROGATE_START && next <= SURROGATE_END) next = SURROGATE_END + 1;
|
|
38569
|
+
return points.slice(0, i).join("") + String.fromCodePoint(next);
|
|
38570
|
+
}
|
|
38571
|
+
return void 0;
|
|
38572
|
+
}
|
|
38573
|
+
var PgStateStore = class {
|
|
38574
|
+
constructor(sql) {
|
|
38575
|
+
this.sql = sql;
|
|
38576
|
+
}
|
|
38577
|
+
sql;
|
|
38578
|
+
/** 建表(幂等)。key COLLATE "C" 是字节序正确性的前提,不能省。 */
|
|
38579
|
+
async ensureSchema() {
|
|
38580
|
+
await this.sql`
|
|
38581
|
+
CREATE TABLE IF NOT EXISTS tb_kv (
|
|
38582
|
+
key text COLLATE "C" PRIMARY KEY,
|
|
38583
|
+
value jsonb NOT NULL
|
|
38584
|
+
)
|
|
38585
|
+
`;
|
|
38586
|
+
}
|
|
38587
|
+
async get(key) {
|
|
38588
|
+
const rows = await this.sql`
|
|
38589
|
+
SELECT value FROM tb_kv WHERE key = ${key}
|
|
38590
|
+
`;
|
|
38591
|
+
const row = rows[0];
|
|
38592
|
+
return row === void 0 ? null : row.value;
|
|
38593
|
+
}
|
|
38594
|
+
async getMany(keys) {
|
|
38595
|
+
const unique = [...new Set(keys)];
|
|
38596
|
+
if (unique.length === 0) return /* @__PURE__ */ new Map();
|
|
38597
|
+
const rows = await this.sql`
|
|
38598
|
+
SELECT key, value FROM tb_kv WHERE key = ANY(${this.sql.array(unique)})
|
|
38599
|
+
`;
|
|
38600
|
+
const byKey = new Map(rows.map((row) => [row.key, row.value]));
|
|
38601
|
+
const out = /* @__PURE__ */ new Map();
|
|
38602
|
+
for (const key of unique) {
|
|
38603
|
+
if (byKey.has(key)) out.set(key, byKey.get(key));
|
|
38604
|
+
}
|
|
38605
|
+
return out;
|
|
38606
|
+
}
|
|
38607
|
+
async put(key, value) {
|
|
38608
|
+
await this.sql`
|
|
38609
|
+
INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
|
|
38610
|
+
ON CONFLICT (key) DO UPDATE SET value = excluded.value
|
|
38611
|
+
`;
|
|
38612
|
+
}
|
|
38613
|
+
async putIfAbsent(key, value) {
|
|
38614
|
+
const result = await this.sql`
|
|
38615
|
+
INSERT INTO tb_kv (key, value) VALUES (${key}, ${this.sql.json(value)})
|
|
38616
|
+
ON CONFLICT (key) DO NOTHING
|
|
38617
|
+
`;
|
|
38618
|
+
return result.count > 0;
|
|
38619
|
+
}
|
|
38620
|
+
async delete(key) {
|
|
38621
|
+
await this.sql`DELETE FROM tb_kv WHERE key = ${key}`;
|
|
38622
|
+
}
|
|
38623
|
+
async list(prefix, opts) {
|
|
38624
|
+
const limit = opts?.limit ?? DEFAULT_LIST_LIMIT;
|
|
38625
|
+
const lowerByCursor = opts?.cursor !== void 0 && opts.cursor >= prefix;
|
|
38626
|
+
const upper = prefix === "" ? void 0 : prefixUpperBound(prefix);
|
|
38627
|
+
const rows = await this.sql`
|
|
38628
|
+
SELECT key, value FROM tb_kv
|
|
38629
|
+
WHERE ${lowerByCursor ? this.sql`key > ${opts?.cursor ?? ""}` : this.sql`key >= ${prefix}`}
|
|
38630
|
+
${upper !== void 0 ? this.sql`AND key < ${upper}` : this.sql``}
|
|
38631
|
+
ORDER BY key
|
|
38632
|
+
LIMIT ${limit + 1}
|
|
38633
|
+
`;
|
|
38634
|
+
const matched = rows.filter((r) => r.key.startsWith(prefix));
|
|
38635
|
+
const hasMore = matched.length > limit;
|
|
38636
|
+
const page = hasMore ? matched.slice(0, limit) : matched;
|
|
38637
|
+
const items = page.map((r) => ({ key: r.key, value: r.value }));
|
|
38638
|
+
const last = page[page.length - 1];
|
|
38639
|
+
return hasMore && last !== void 0 ? { items, cursor: last.key } : { items };
|
|
38640
|
+
}
|
|
38641
|
+
};
|
|
38642
|
+
|
|
38643
|
+
// src/redisDeviceRouter.ts
|
|
38644
|
+
import { Redis } from "ioredis";
|
|
38645
|
+
var CLEAR_IF_OWNER = `
|
|
38646
|
+
if redis.call('get', KEYS[1]) == ARGV[1] then
|
|
38647
|
+
return redis.call('del', KEYS[1])
|
|
38648
|
+
end
|
|
38649
|
+
return 0
|
|
38650
|
+
`;
|
|
38651
|
+
var RedisDeviceRouterBackend = class {
|
|
38652
|
+
/** 命令连接(GET/SET/DEL/PUBLISH)。 */
|
|
38653
|
+
commands;
|
|
38654
|
+
/** 订阅连接(进入 subscribe 模式后不能发普通命令)。 */
|
|
38655
|
+
subscriber;
|
|
38656
|
+
handlers = /* @__PURE__ */ new Map();
|
|
38657
|
+
constructor(url2) {
|
|
38658
|
+
this.commands = new Redis(url2, { maxRetriesPerRequest: 2 });
|
|
38659
|
+
this.subscriber = new Redis(url2, { maxRetriesPerRequest: 2 });
|
|
38660
|
+
this.subscriber.on("message", (channel, payload) => {
|
|
38661
|
+
this.handlers.get(channel)?.(payload);
|
|
38662
|
+
});
|
|
38663
|
+
this.commands.on("error", () => {
|
|
38664
|
+
});
|
|
38665
|
+
this.subscriber.on("error", () => {
|
|
38666
|
+
});
|
|
38667
|
+
}
|
|
38668
|
+
async setRoute(deviceId, replicaId, ttlSec) {
|
|
38669
|
+
await this.commands.set(deviceRouteKey(deviceId), replicaId, "EX", ttlSec);
|
|
38670
|
+
}
|
|
38671
|
+
async lookupRoute(deviceId) {
|
|
38672
|
+
return await this.commands.get(deviceRouteKey(deviceId));
|
|
38673
|
+
}
|
|
38674
|
+
async clearRoute(deviceId, replicaId) {
|
|
38675
|
+
await this.commands.eval(CLEAR_IF_OWNER, 1, deviceRouteKey(deviceId), replicaId);
|
|
38676
|
+
}
|
|
38677
|
+
async publish(channel, payload) {
|
|
38678
|
+
await this.commands.publish(channel, payload);
|
|
38679
|
+
}
|
|
38680
|
+
/** 就绪探测(/readyz):命令连接 PING 一次。断连时 ioredis 会抛/挂起,由调用方限时。 */
|
|
38681
|
+
async ping() {
|
|
38682
|
+
await this.commands.ping();
|
|
38683
|
+
}
|
|
38684
|
+
async subscribe(channel, handler) {
|
|
38685
|
+
this.handlers.set(channel, handler);
|
|
38686
|
+
await this.subscriber.subscribe(channel);
|
|
38687
|
+
}
|
|
38688
|
+
async close() {
|
|
38689
|
+
this.handlers.clear();
|
|
38690
|
+
await Promise.allSettled([this.subscriber.quit(), this.commands.quit()]);
|
|
38691
|
+
}
|
|
38692
|
+
};
|
|
38693
|
+
|
|
38188
38694
|
// src/sqliteSearchIndex.ts
|
|
38189
38695
|
import Database from "better-sqlite3";
|
|
38190
38696
|
var SCHEMA_SQL = TOOL_SEARCH_SCHEMA_STATEMENTS.map((sql) => `${sql};`).join("\n");
|
|
@@ -38246,13 +38752,18 @@ var SqliteSearchIndex = class extends SqlSearchIndex {
|
|
|
38246
38752
|
|
|
38247
38753
|
// src/sqliteStateStore.ts
|
|
38248
38754
|
import Database2 from "better-sqlite3";
|
|
38249
|
-
var
|
|
38250
|
-
|
|
38251
|
-
|
|
38252
|
-
|
|
38253
|
-
|
|
38254
|
-
|
|
38255
|
-
|
|
38755
|
+
var DEFAULT_LIST_LIMIT2 = 1e3;
|
|
38756
|
+
var MAX_CODE_POINT2 = 1114111;
|
|
38757
|
+
var SURROGATE_START2 = 55296;
|
|
38758
|
+
var SURROGATE_END2 = 57343;
|
|
38759
|
+
function prefixUpperBound2(prefix) {
|
|
38760
|
+
const points = [...prefix];
|
|
38761
|
+
for (let i = points.length - 1; i >= 0; i--) {
|
|
38762
|
+
const code = points[i]?.codePointAt(0);
|
|
38763
|
+
if (code === void 0 || code >= MAX_CODE_POINT2) continue;
|
|
38764
|
+
let next = code + 1;
|
|
38765
|
+
if (next >= SURROGATE_START2 && next <= SURROGATE_END2) next = SURROGATE_END2 + 1;
|
|
38766
|
+
return points.slice(0, i).join("") + String.fromCodePoint(next);
|
|
38256
38767
|
}
|
|
38257
38768
|
return void 0;
|
|
38258
38769
|
}
|
|
@@ -38295,13 +38806,17 @@ var SqliteStateStore = class {
|
|
|
38295
38806
|
async put(key, value) {
|
|
38296
38807
|
this.stmtPut.run(key, JSON.stringify(value));
|
|
38297
38808
|
}
|
|
38809
|
+
async putIfAbsent(key, value) {
|
|
38810
|
+
const info = this.db.prepare("INSERT OR IGNORE INTO kv (key, value) VALUES (?, ?)").run(key, JSON.stringify(value));
|
|
38811
|
+
return info.changes > 0;
|
|
38812
|
+
}
|
|
38298
38813
|
async delete(key) {
|
|
38299
38814
|
this.stmtDelete.run(key);
|
|
38300
38815
|
}
|
|
38301
38816
|
async list(prefix, opts) {
|
|
38302
|
-
const limit = opts?.limit ??
|
|
38817
|
+
const limit = opts?.limit ?? DEFAULT_LIST_LIMIT2;
|
|
38303
38818
|
const lowerByCursor = opts?.cursor !== void 0 && opts.cursor >= prefix;
|
|
38304
|
-
const upper = prefix === "" ? void 0 :
|
|
38819
|
+
const upper = prefix === "" ? void 0 : prefixUpperBound2(prefix);
|
|
38305
38820
|
const conditions = [];
|
|
38306
38821
|
const params = { n: limit + 1 };
|
|
38307
38822
|
if (lowerByCursor) {
|
|
@@ -38330,14 +38845,16 @@ var SqliteStateStore = class {
|
|
|
38330
38845
|
|
|
38331
38846
|
// src/server.ts
|
|
38332
38847
|
import { serve } from "@hono/node-server";
|
|
38848
|
+
import postgres from "postgres";
|
|
38333
38849
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
38850
|
+
import { hostname } from "os";
|
|
38334
38851
|
import { join as join4 } from "path";
|
|
38335
38852
|
|
|
38336
38853
|
// package.json
|
|
38337
38854
|
var package_default = {
|
|
38338
38855
|
name: "@tool-bridge/server",
|
|
38339
|
-
version: "0.
|
|
38340
|
-
description: "tool-bridge self-hosted server: run the HTBP gateway on Node with SQLite persistence, filesystem object storage and WebSocket device channel \u2014 single container via docker run",
|
|
38856
|
+
version: "0.10.0",
|
|
38857
|
+
description: "tool-bridge self-hosted server: run the HTBP gateway on Node with SQLite or PostgreSQL persistence, filesystem object storage and WebSocket device channel \u2014 single container via docker run",
|
|
38341
38858
|
type: "module",
|
|
38342
38859
|
license: "MIT",
|
|
38343
38860
|
main: "src/index.ts",
|
|
@@ -38385,6 +38902,8 @@ var package_default = {
|
|
|
38385
38902
|
aws4fetch: "catalog:",
|
|
38386
38903
|
"better-sqlite3": "^12.6.0",
|
|
38387
38904
|
hono: "catalog:",
|
|
38905
|
+
ioredis: "^6.0.0",
|
|
38906
|
+
postgres: "^3.4.9",
|
|
38388
38907
|
ws: "catalog:"
|
|
38389
38908
|
},
|
|
38390
38909
|
devDependencies: {
|
|
@@ -38449,24 +38968,116 @@ function resolveUiAssets(uiDirOverride) {
|
|
|
38449
38968
|
}
|
|
38450
38969
|
|
|
38451
38970
|
// src/server.ts
|
|
38452
|
-
function
|
|
38453
|
-
|
|
38454
|
-
|
|
38455
|
-
const state = new
|
|
38456
|
-
|
|
38971
|
+
function pgBackends(databaseUrl) {
|
|
38972
|
+
const sql = postgres(databaseUrl, { onnotice: () => {
|
|
38973
|
+
} });
|
|
38974
|
+
const state = new PgStateStore(sql);
|
|
38975
|
+
const search = new PgSearchIndex(sql);
|
|
38976
|
+
return {
|
|
38977
|
+
state: {
|
|
38978
|
+
value: state,
|
|
38979
|
+
ensureReady: async () => await state.ensureSchema(),
|
|
38980
|
+
ping: async () => {
|
|
38981
|
+
await sql`SELECT 1`;
|
|
38982
|
+
},
|
|
38983
|
+
close: async () => await sql.end({ timeout: 5 })
|
|
38984
|
+
},
|
|
38985
|
+
search: {
|
|
38986
|
+
value: search,
|
|
38987
|
+
ensureReady: async () => {
|
|
38988
|
+
await search.initialized();
|
|
38989
|
+
},
|
|
38990
|
+
close: async () => {
|
|
38991
|
+
}
|
|
38992
|
+
}
|
|
38993
|
+
};
|
|
38994
|
+
}
|
|
38995
|
+
function sqliteStateBackend(dataDir) {
|
|
38996
|
+
const store = new SqliteStateStore(join4(dataDir, "state.sqlite3"));
|
|
38997
|
+
return {
|
|
38998
|
+
value: store,
|
|
38999
|
+
ensureReady: async () => {
|
|
39000
|
+
},
|
|
39001
|
+
close: async () => store.close()
|
|
39002
|
+
};
|
|
39003
|
+
}
|
|
39004
|
+
function sqliteSearchBackend(dataDir) {
|
|
39005
|
+
const index = new SqliteSearchIndex(join4(dataDir, "state.sqlite3"));
|
|
39006
|
+
return {
|
|
39007
|
+
value: index,
|
|
39008
|
+
ensureReady: async () => {
|
|
39009
|
+
},
|
|
39010
|
+
close: async () => index.close()
|
|
39011
|
+
};
|
|
39012
|
+
}
|
|
39013
|
+
function resolveBackends(config) {
|
|
39014
|
+
if (config.databaseUrl !== void 0) return pgBackends(config.databaseUrl);
|
|
39015
|
+
const state = sqliteStateBackend(config.dataDir);
|
|
38457
39016
|
try {
|
|
38458
|
-
|
|
39017
|
+
return { search: sqliteSearchBackend(config.dataDir), state };
|
|
38459
39018
|
} catch (error) {
|
|
38460
|
-
state.close()
|
|
39019
|
+
state.close().catch(() => {
|
|
39020
|
+
});
|
|
38461
39021
|
throw error;
|
|
38462
39022
|
}
|
|
39023
|
+
}
|
|
39024
|
+
function createTbServer(config) {
|
|
39025
|
+
mkdirSync2(config.dataDir, { recursive: true });
|
|
39026
|
+
const backends = resolveBackends(config);
|
|
39027
|
+
const state = backends.state.value;
|
|
39028
|
+
const search = backends.search.value;
|
|
38463
39029
|
const secrets = new SecretStoreImpl(state, config.encryptionKey);
|
|
38464
|
-
const objects = createDataObjectStore(config.dataDir);
|
|
39030
|
+
const objects = config.objectStore === void 0 ? createDataObjectStore(config.dataDir) : createS3ObjectStore(config.objectStore, { allowInsecure: config.allowInsecureHttp });
|
|
39031
|
+
const redisBackend = config.redisUrl === void 0 ? void 0 : new RedisDeviceRouterBackend(config.redisUrl);
|
|
38465
39032
|
const hub = new DeviceHub({
|
|
38466
39033
|
store: state,
|
|
38467
39034
|
search,
|
|
38468
|
-
reclaimSec: config.deviceReclaimSec
|
|
38469
|
-
|
|
39035
|
+
reclaimSec: config.deviceReclaimSec,
|
|
39036
|
+
// 配了 Redis 才启用多副本路由;缺省单副本,设备调用恒走本地 socket。
|
|
39037
|
+
...redisBackend === void 0 ? {} : {
|
|
39038
|
+
router: (onLocalCall) => new DeviceRouter(
|
|
39039
|
+
config.replicaId ?? hostname(),
|
|
39040
|
+
redisBackend,
|
|
39041
|
+
{ onLocalCall }
|
|
39042
|
+
)
|
|
39043
|
+
}
|
|
39044
|
+
});
|
|
39045
|
+
let draining = false;
|
|
39046
|
+
const probeOne = async (fn) => {
|
|
39047
|
+
let timer;
|
|
39048
|
+
try {
|
|
39049
|
+
await Promise.race([
|
|
39050
|
+
fn(),
|
|
39051
|
+
new Promise((_, reject) => {
|
|
39052
|
+
timer = setTimeout(() => reject(new Error("timeout after 1000ms")), 1e3);
|
|
39053
|
+
timer.unref?.();
|
|
39054
|
+
})
|
|
39055
|
+
]);
|
|
39056
|
+
return { ok: true };
|
|
39057
|
+
} catch (err) {
|
|
39058
|
+
return { ok: false, detail: err instanceof Error ? err.message : "probe failed" };
|
|
39059
|
+
} finally {
|
|
39060
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
39061
|
+
}
|
|
39062
|
+
};
|
|
39063
|
+
const readiness = async () => {
|
|
39064
|
+
const checks = {};
|
|
39065
|
+
const probes = [];
|
|
39066
|
+
const statePing = backends.state.ping;
|
|
39067
|
+
if (statePing !== void 0) {
|
|
39068
|
+
probes.push(probeOne(statePing).then((r) => {
|
|
39069
|
+
checks.state = r;
|
|
39070
|
+
}));
|
|
39071
|
+
}
|
|
39072
|
+
if (redisBackend !== void 0) {
|
|
39073
|
+
probes.push(probeOne(async () => await redisBackend.ping()).then((r) => {
|
|
39074
|
+
checks.redis = r;
|
|
39075
|
+
}));
|
|
39076
|
+
}
|
|
39077
|
+
await Promise.all(probes);
|
|
39078
|
+
if (draining) checks.draining = { ok: false, detail: "shutting down" };
|
|
39079
|
+
return { checks, ready: !draining && Object.values(checks).every((c) => c.ok) };
|
|
39080
|
+
};
|
|
38470
39081
|
const deps = {
|
|
38471
39082
|
state,
|
|
38472
39083
|
secrets,
|
|
@@ -38475,7 +39086,8 @@ function createTbServer(config) {
|
|
|
38475
39086
|
search,
|
|
38476
39087
|
allowInsecureHttp: config.allowInsecureHttp,
|
|
38477
39088
|
objects: () => objects,
|
|
38478
|
-
device: hub
|
|
39089
|
+
device: hub,
|
|
39090
|
+
readiness
|
|
38479
39091
|
};
|
|
38480
39092
|
if (config.encryptionKey !== void 0) deps.encryptionKey = config.encryptionKey;
|
|
38481
39093
|
if (config.pluginBindings !== void 0) deps.pluginBindings = config.pluginBindings;
|
|
@@ -38494,6 +39106,9 @@ function createTbServer(config) {
|
|
|
38494
39106
|
state,
|
|
38495
39107
|
deviceHub: hub,
|
|
38496
39108
|
async start() {
|
|
39109
|
+
await backends.state.ensureReady();
|
|
39110
|
+
await backends.search.ensureReady();
|
|
39111
|
+
await hub.startRouter();
|
|
38497
39112
|
await runBootstrap(state, {
|
|
38498
39113
|
...config.adminSk !== void 0 ? { adminSk: config.adminSk } : {},
|
|
38499
39114
|
requireAdminSk: !config.allowInsecureBootstrap
|
|
@@ -38506,16 +39121,24 @@ function createTbServer(config) {
|
|
|
38506
39121
|
hub.attach(server);
|
|
38507
39122
|
});
|
|
38508
39123
|
},
|
|
39124
|
+
startDraining() {
|
|
39125
|
+
draining = true;
|
|
39126
|
+
},
|
|
38509
39127
|
async close() {
|
|
38510
|
-
|
|
39128
|
+
draining = true;
|
|
39129
|
+
let closed;
|
|
38511
39130
|
if (server !== void 0) {
|
|
38512
|
-
|
|
38513
|
-
|
|
39131
|
+
const s = server;
|
|
39132
|
+
closed = new Promise((resolve3, reject) => {
|
|
39133
|
+
s.close((err) => err ? reject(err) : resolve3());
|
|
38514
39134
|
});
|
|
38515
|
-
|
|
39135
|
+
s.closeIdleConnections?.();
|
|
38516
39136
|
}
|
|
38517
|
-
|
|
38518
|
-
|
|
39137
|
+
await hub.close();
|
|
39138
|
+
if (closed !== void 0) await closed;
|
|
39139
|
+
server = void 0;
|
|
39140
|
+
await backends.search.close();
|
|
39141
|
+
await backends.state.close();
|
|
38519
39142
|
}
|
|
38520
39143
|
};
|
|
38521
39144
|
}
|
|
@@ -38524,7 +39147,13 @@ export {
|
|
|
38524
39147
|
configFromEnv,
|
|
38525
39148
|
DEVICE_WS_PATH,
|
|
38526
39149
|
DeviceHub,
|
|
39150
|
+
DEVICE_ROUTE_TTL_SEC,
|
|
39151
|
+
DEVICE_FORWARD_TIMEOUT_MS,
|
|
39152
|
+
DeviceRouter,
|
|
38527
39153
|
createDataObjectStore,
|
|
39154
|
+
PgSearchIndex,
|
|
39155
|
+
PgStateStore,
|
|
39156
|
+
RedisDeviceRouterBackend,
|
|
38528
39157
|
SqliteSearchIndex,
|
|
38529
39158
|
SqliteStateStore,
|
|
38530
39159
|
createTbServer
|