@stelis/say-ur-intent 0.0.0 → 0.0.2
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/README.md +4 -39
- package/dist/adapters/adapterLifecycleValidators.js +7 -0
- package/dist/adapters/adapterPromptSurfaces.js +71 -0
- package/dist/adapters/deepbook/deepbookHumanReviewProducer.js +175 -0
- package/dist/adapters/deepbook/deepbookQuotePolicy.js +112 -0
- package/dist/adapters/deepbook/deepbookReviewEvidence.js +507 -0
- package/dist/adapters/deepbook/deepbookReviewLifecycle.js +85 -0
- package/dist/adapters/deepbook/deepbookSwapIntent.js +79 -0
- package/dist/adapters/deepbook/deepbookTransactionMaterialProducer.js +269 -0
- package/dist/adapters/flowx/flowxSwapHumanReviewProducer.js +176 -0
- package/dist/adapters/flowx/flowxSwapIntent.js +79 -0
- package/dist/adapters/flowx/flowxSwapQuotePolicy.js +104 -0
- package/dist/adapters/flowx/flowxSwapReviewEvidence.js +468 -0
- package/dist/adapters/flowx/flowxSwapReviewLifecycle.js +85 -0
- package/dist/adapters/flowx/flowxSwapTransactionMaterialProducer.js +362 -0
- package/dist/adapters/intentPlanFactories.js +59 -0
- package/dist/adapters/reviewAdapters.js +81 -0
- package/dist/core/action/adapterLifecycleValidation.js +12 -0
- package/dist/core/action/forbiddenFields.js +43 -0
- package/dist/core/action/humanReadableReviewEvidence.js +203 -0
- package/dist/core/action/humanReadableReviewProjectionVerifier.js +29 -0
- package/dist/core/action/ptbVisualizationProducer.js +66 -0
- package/dist/core/action/reviewCheckResults.js +6 -0
- package/dist/core/action/reviewStateValidation.js +11 -0
- package/dist/core/action/reviewTimeSimulationEvidence.js +471 -0
- package/dist/core/action/schemas.js +529 -0
- package/dist/core/action/signableAdapterContract.js +993 -0
- package/dist/core/action/swapHumanReadableReviewProjection.js +124 -0
- package/dist/core/action/swapQuotePolicyEvidence.js +278 -0
- package/dist/core/action/transactionObjectOwnershipEvidence.js +247 -0
- package/dist/core/action/transactionObjectOwnershipProducer.js +329 -0
- package/dist/core/action/types.js +35 -0
- package/dist/core/action/walletReviewContractAssembler.js +282 -0
- package/dist/core/activity/activityStore.js +15 -0
- package/dist/core/activity/localDataService.js +258 -0
- package/dist/core/activity/localDataTypes.js +11 -0
- package/dist/core/activity/localDataValidation.js +396 -0
- package/dist/core/activity/schemaVersion.js +1 -0
- package/dist/core/activity/sqliteActivityStore.js +820 -0
- package/dist/core/activity/sqliteActivityStoreRows.js +430 -0
- package/dist/core/activity/sqliteActivityStoreSchema.js +258 -0
- package/dist/core/activity/sqliteActivityStoreTypes.js +5 -0
- package/dist/core/activity/suiFunctionTarget.js +43 -0
- package/dist/core/activity/transactionActivityAccountEffects.js +189 -0
- package/dist/core/activity/transactionActivityAnalysis.js +295 -0
- package/dist/core/activity/transactionActivityClassifier.js +306 -0
- package/dist/core/activity/transactionActivityDetails.js +229 -0
- package/dist/core/activity/transactionActivityProtocolRules.js +218 -0
- package/dist/core/activity/transactionActivityScanPolicy.js +170 -0
- package/dist/core/activity/transactionActivityService.js +379 -0
- package/dist/core/activity/transactionActivityTypes.js +18 -0
- package/dist/core/eventlog/sink.js +35 -0
- package/dist/core/evidence/settlementFamilies.js +87 -0
- package/dist/core/evidence/userAnswerUse.js +1 -0
- package/dist/core/numeric/rawU64.js +63 -0
- package/dist/core/preferences/preferencesStore.js +26 -0
- package/dist/core/preferences/sqlitePreferencesRepository.js +136 -0
- package/dist/core/proposal/externalProposalReview.js +347 -0
- package/dist/core/proposal/schemas.js +208 -0
- package/dist/core/proposal/types.js +35 -0
- package/dist/core/read/amounts.js +14 -0
- package/dist/core/read/coinMetadata.js +60 -0
- package/dist/core/read/deepbookRawQuoteClient.js +86 -0
- package/dist/core/read/deepbookReadHelpers.js +265 -0
- package/dist/core/read/deepbookRegistry.js +133 -0
- package/dist/core/read/flowxQuoteClient.js +117 -0
- package/dist/core/read/flowxReadHelpers.js +145 -0
- package/dist/core/read/flowxRegistry.js +174 -0
- package/dist/core/read/intentEvidenceResponseFormatting.js +228 -0
- package/dist/core/read/readResponseGuidance.js +451 -0
- package/dist/core/read/readService.js +1164 -0
- package/dist/core/read/readServiceTypes.js +59 -0
- package/dist/core/read/settlementParityFormatting.js +82 -0
- package/dist/core/read/walletReadHelpers.js +99 -0
- package/dist/core/review/reviewChecks.js +54 -0
- package/dist/core/review/reviewComputation.js +38 -0
- package/dist/core/review/reviewComputationResult.js +87 -0
- package/dist/core/session/localSession.js +31 -0
- package/dist/core/session/privateReviewArtifacts.js +73 -0
- package/dist/core/session/sessionErrors.js +9 -0
- package/dist/core/session/sessionStore.js +821 -0
- package/dist/core/session/settingsSession.js +1 -0
- package/dist/core/session/settingsSessions.js +43 -0
- package/dist/core/session/status.js +86 -0
- package/dist/core/session/transactionMaterialStore.js +205 -0
- package/dist/core/session/wait.js +102 -0
- package/dist/core/session/walletIdentity.js +103 -0
- package/dist/core/session/walletIdentitySessions.js +189 -0
- package/dist/core/suiAddress.js +18 -0
- package/dist/core/suiEndpoint.js +72 -0
- package/dist/mcp/activeAccountResponse.js +24 -0
- package/dist/mcp/prompts.js +146 -0
- package/dist/mcp/registerTool.js +19 -0
- package/dist/mcp/resources.js +72 -0
- package/dist/mcp/responseGuidance.js +381 -0
- package/dist/mcp/result.js +17 -0
- package/dist/mcp/schemas.js +8 -0
- package/dist/mcp/server.js +30 -0
- package/dist/mcp/serverInfo.js +123 -0
- package/dist/mcp/toolErrors.js +105 -0
- package/dist/mcp/toolNames.js +50 -0
- package/dist/mcp/tools/account/index.js +44 -0
- package/dist/mcp/tools/action/prepareSuiActionReview.js +120 -0
- package/dist/mcp/tools/read/commonSchemas.js +43 -0
- package/dist/mcp/tools/read/deepbookReadTools.js +453 -0
- package/dist/mcp/tools/read/flowxReadTools.js +135 -0
- package/dist/mcp/tools/read/index.js +16 -0
- package/dist/mcp/tools/read/readToolHelpers.js +68 -0
- package/dist/mcp/tools/read/reviewActivityTools.js +176 -0
- package/dist/mcp/tools/read/serverStatusTools.js +103 -0
- package/dist/mcp/tools/read/transactionActivityOutput.js +300 -0
- package/dist/mcp/tools/read/transactionActivityTools.js +544 -0
- package/dist/mcp/tools/read/walletReadTools.js +733 -0
- package/dist/mcp/tools/session/executionResultTools.js +92 -0
- package/dist/mcp/tools/session/index.js +8 -0
- package/dist/mcp/tools/session/shared.js +79 -0
- package/dist/mcp/tools/session/statusTools.js +134 -0
- package/dist/mcp/tools/session/walletIdentityTools.js +119 -0
- package/dist/mcp/tools/settings/index.js +64 -0
- package/dist/review-app/analysis.css +1 -0
- package/dist/review-app/analysis.js +1 -0
- package/dist/review-app/arc-BjIacwQm.js +1 -0
- package/dist/review-app/architecture-U656AL7Q-aSB9x1OK.js +1 -0
- package/dist/review-app/architectureDiagram-VXUJARFQ-C5W6re2I.js +36 -0
- package/dist/review-app/array-BmXUUrU6.js +1 -0
- package/dist/review-app/blockDiagram-VD42YOAC-20MLNcUm.js +122 -0
- package/dist/review-app/c4Diagram-YG6GDRKO-BZXRrcck.js +10 -0
- package/dist/review-app/channel-lk2p_CUu.js +1 -0
- package/dist/review-app/chunk-4BX2VUAB-BPITOdjX.js +1 -0
- package/dist/review-app/chunk-55IACEB6-Dz-pyw5k.js +1 -0
- package/dist/review-app/chunk-76Q3JFCE-cK_X1P_l.js +1 -0
- package/dist/review-app/chunk-ABZYJK2D-Dt4W53JI.js +81 -0
- package/dist/review-app/chunk-ATLVNIR6-fZHLXURb.js +1 -0
- package/dist/review-app/chunk-B4BG7PRW-BbgcjusC.js +165 -0
- package/dist/review-app/chunk-BJD4TVEz.js +1 -0
- package/dist/review-app/chunk-CVBHYZKI-CViawAKX.js +1 -0
- package/dist/review-app/chunk-DI55MBZ5-C5aoul-d.js +220 -0
- package/dist/review-app/chunk-FMBD7UC4-Chxmw62A.js +15 -0
- package/dist/review-app/chunk-FPAJGGOC-DDHjQ09H.js +80 -0
- package/dist/review-app/chunk-FWNWRKHM-CVVQUptk.js +1 -0
- package/dist/review-app/chunk-HN2XXSSU-yzNpjaSZ.js +1 -0
- package/dist/review-app/chunk-JA3XYJ7Z-C5ZJdU01.js +70 -0
- package/dist/review-app/chunk-JZLCHNYA-BBST4Cnk.js +54 -0
- package/dist/review-app/chunk-LBM3YZW2-CdwAPuHr.js +1 -0
- package/dist/review-app/chunk-LHMN2FUI-BtB5uDcp.js +1 -0
- package/dist/review-app/chunk-O7ZBX7Z2-pxdK4Sa3.js +1 -0
- package/dist/review-app/chunk-QN33PNHL-CbVv3uGK.js +1 -0
- package/dist/review-app/chunk-QXUST7PY-DKM2-t2c.js +7 -0
- package/dist/review-app/chunk-QZHKN3VN-C5ni2pN_.js +1 -0
- package/dist/review-app/chunk-S3R3BYOJ-BWvOhDs0.js +2 -0
- package/dist/review-app/chunk-S6J4BHB3-D9Fk0YeD.js +1 -0
- package/dist/review-app/chunk-T53DSG4Q-C1qEyzyV.js +1 -0
- package/dist/review-app/chunk-TZMSLE5B-B--7eU69.js +1 -0
- package/dist/review-app/classDiagram-2ON5EDUG-DlL1m2bp.js +1 -0
- package/dist/review-app/classDiagram-v2-WZHVMYZB-FXRskT1j.js +1 -0
- package/dist/review-app/clone-BZZb7gpZ.js +1 -0
- package/dist/review-app/cose-bilkent-S5V4N54A-CRIb8XEO.js +1 -0
- package/dist/review-app/cytoscape.esm-C7jYqDP5.js +321 -0
- package/dist/review-app/dagre-6UL2VRFP-FNCAXbdE.js +4 -0
- package/dist/review-app/dagre-Be46QtUd.js +1 -0
- package/dist/review-app/defaultLocale-BaWNtAUL.js +1 -0
- package/dist/review-app/diagram-PSM6KHXK-ylLWjiNM.js +24 -0
- package/dist/review-app/diagram-QEK2KX5R-BCDcESxs.js +43 -0
- package/dist/review-app/diagram-S2PKOQOG-Vdrc-vrO.js +24 -0
- package/dist/review-app/dist-WPc74x_f.js +1 -0
- package/dist/review-app/erDiagram-Q2GNP2WA-E5ZsUbDF.js +60 -0
- package/dist/review-app/flatten-DHf9IeNI.js +1 -0
- package/dist/review-app/flowDiagram-NV44I4VS-DBSQuj6x.js +162 -0
- package/dist/review-app/ganttDiagram-LVOFAZNH-CKUOsqwl.js +267 -0
- package/dist/review-app/gitGraph-F6HP7TQM-DsAD6qK1.js +1 -0
- package/dist/review-app/gitGraphDiagram-NY62KEGX-BCeIMWdl.js +65 -0
- package/dist/review-app/graphlib-CiX5CXxR.js +1 -0
- package/dist/review-app/http-DMvwuuFk.js +1 -0
- package/dist/review-app/identity-DY8PXc6t.js +1 -0
- package/dist/review-app/info-NVLQJR56-Dlx1nZic.js +1 -0
- package/dist/review-app/infoDiagram-F6ZHWCRC-CAuANIrz.js +2 -0
- package/dist/review-app/init-BvqephKz.js +1 -0
- package/dist/review-app/journeyDiagram-XKPGCS4Q-C-Z9phnx.js +139 -0
- package/dist/review-app/kanban-definition-3W4ZIXB7-DufgZABq.js +89 -0
- package/dist/review-app/katex-B-Z-NXXN.js +257 -0
- package/dist/review-app/line-DiIv3Jgw.js +1 -0
- package/dist/review-app/linear-Cv-UPvo1.js +1 -0
- package/dist/review-app/math-kmyYrkHL.js +1 -0
- package/dist/review-app/mermaid-parser.core-DkwUYTPl.js +4 -0
- package/dist/review-app/mindmap-definition-VGOIOE7T-TM_CqdmV.js +68 -0
- package/dist/review-app/ordinal-BliTlkoG.js +1 -0
- package/dist/review-app/packet-BFZMPI3H-DqbnU92v.js +1 -0
- package/dist/review-app/path-AEo9W6mQ.js +1 -0
- package/dist/review-app/pie-7BOR55EZ-LJzaLkgr.js +1 -0
- package/dist/review-app/pieDiagram-ADFJNKIX-BAs8OfRS.js +30 -0
- package/dist/review-app/quadrantDiagram-AYHSOK5B-CyUDZP5S.js +7 -0
- package/dist/review-app/radar-NHE76QYJ-DBpHc8_Y.js +1 -0
- package/dist/review-app/reduce-B-HuPpdd.js +1 -0
- package/dist/review-app/requirementDiagram-UZGBJVZJ-BEHix78P.js +64 -0
- package/dist/review-app/review.css +1 -0
- package/dist/review-app/review.js +43 -0
- package/dist/review-app/sankeyDiagram-TZEHDZUN-B2bKbmsm.js +10 -0
- package/dist/review-app/sequenceDiagram-WL72ISMW-DVLOORFJ.js +145 -0
- package/dist/review-app/settings.css +1 -0
- package/dist/review-app/settings.js +1 -0
- package/dist/review-app/src-Buml7cM5.js +1 -0
- package/dist/review-app/stateDiagram-FKZM4ZOC-sFGGp2kV.js +1 -0
- package/dist/review-app/stateDiagram-v2-4FDKWEC3-BHfCF4dX.js +1 -0
- package/dist/review-app/timeline-definition-IT6M3QCI-BESnBijC.js +61 -0
- package/dist/review-app/treemap-KMMF4GRG-wnVLBDeQ.js +1 -0
- package/dist/review-app/walletStatus-CcojOdGy.js +7 -0
- package/dist/review-app/xychartDiagram-PRI3JC2R-BGWVfCx4.js +7 -0
- package/dist/review-server/assets.js +48 -0
- package/dist/review-server/html.js +66 -0
- package/dist/review-server/http.js +47 -0
- package/dist/review-server/middleware/hostOrigin.js +48 -0
- package/dist/review-server/middleware/reviewToken.js +7 -0
- package/dist/review-server/reviewServerPolicy.js +10 -0
- package/dist/review-server/server.js +568 -0
- package/dist/review-server/settingsApi.js +182 -0
- package/dist/review-server/walletIdentityResponse.js +13 -0
- package/dist/runtime/config.js +103 -0
- package/dist/runtime/localSettingsService.js +198 -0
- package/dist/runtime/logger.js +50 -0
- package/dist/runtime/reviewServerAcquire.js +128 -0
- package/dist/runtime/smokeMainnetRead.js +529 -0
- package/dist/runtime/smokeMainnetReadAssertions.js +308 -0
- package/dist/runtime/start.js +295 -0
- package/dist/runtime/suiEndpoint.js +97 -0
- package/dist/runtime/suiTransactionGraphqlMapping.js +200 -0
- package/dist/runtime/suiTransactionGraphqlQueries.js +231 -0
- package/dist/runtime/suiTransactionGraphqlSource.js +148 -0
- package/docs/AGENT_BEHAVIOR.md +1 -1
- package/docs/AGENT_DEVELOPMENT_POLICY.md +20 -0
- package/docs/FRONTEND_POLICY.md +4 -3
- package/docs/MCP_SETUP.md +59 -7
- package/docs/MCP_TOOLS.md +1 -1
- package/docs/SDK_API.md +5 -1
- package/package.json +3 -2
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { LocalDataError } from "../core/activity/localDataService.js";
|
|
2
|
+
import { LocalSettingsError } from "../core/preferences/preferencesStore.js";
|
|
3
|
+
import { validateHostOrigin } from "./middleware/hostOrigin.js";
|
|
4
|
+
import { readReviewToken } from "./middleware/reviewToken.js";
|
|
5
|
+
import { HttpError, MAX_JSON_BODY_BYTES, readJsonBody, sendJson } from "./http.js";
|
|
6
|
+
import { ALLOWED_HOSTNAMES } from "./reviewServerPolicy.js";
|
|
7
|
+
import { walletIdentitySessionResponse } from "./walletIdentityResponse.js";
|
|
8
|
+
const MAX_SETTINGS_BODY_BYTES = MAX_JSON_BODY_BYTES;
|
|
9
|
+
const MAX_IMPORT_BODY_BYTES = 16 * 1024 * 1024;
|
|
10
|
+
export async function routeSettingsApi(request, response, options, url, matches) {
|
|
11
|
+
const hostOrigin = validateHostOrigin(request, { allowedHostnames: ALLOWED_HOSTNAMES });
|
|
12
|
+
if (!hostOrigin.ok) {
|
|
13
|
+
sendJson(response, hostOrigin.status, { error: hostOrigin.reason });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (url.searchParams.has("token")) {
|
|
17
|
+
sendJson(response, 400, { error: "token_query_not_supported" });
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
const settings = requireSettingsDeps(options);
|
|
21
|
+
const sessionId = matches.status ??
|
|
22
|
+
matches.walletIdentity ??
|
|
23
|
+
matches.clearActiveAccount ??
|
|
24
|
+
matches.setSuiGrpcUrl ??
|
|
25
|
+
matches.restoreDefaultSuiGrpcUrl ??
|
|
26
|
+
matches.setSuiGraphqlUrl ??
|
|
27
|
+
matches.restoreDefaultSuiGraphqlUrl ??
|
|
28
|
+
matches.exportLocalData ??
|
|
29
|
+
matches.previewImport ??
|
|
30
|
+
matches.importLocalData ??
|
|
31
|
+
matches.resetLocalData;
|
|
32
|
+
if (!sessionId) {
|
|
33
|
+
throw new HttpError(404, "not_found");
|
|
34
|
+
}
|
|
35
|
+
const token = readReviewToken(request.headers);
|
|
36
|
+
if (!token || !(await options.store.validateSettingsToken(sessionId, token))) {
|
|
37
|
+
sendJson(response, 401, { error: "invalid_settings_token" });
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (request.method === "GET" && matches.status) {
|
|
41
|
+
const [localSettings, activeAccount, dataCounts] = await Promise.all([
|
|
42
|
+
mapLocalSettingsError(() => settings.localSettings.getLocalSettings()),
|
|
43
|
+
settings.activityStore.getActiveAccount(),
|
|
44
|
+
settings.localData.getDataCounts()
|
|
45
|
+
]);
|
|
46
|
+
sendJson(response, 200, {
|
|
47
|
+
settingsSessionId: sessionId,
|
|
48
|
+
server: settings.serverInfo,
|
|
49
|
+
localSettings,
|
|
50
|
+
activeAccount: activeAccount
|
|
51
|
+
? {
|
|
52
|
+
account: activeAccount.address,
|
|
53
|
+
source: activeAccount.source,
|
|
54
|
+
setAt: activeAccount.setAt
|
|
55
|
+
}
|
|
56
|
+
: undefined,
|
|
57
|
+
dataCounts,
|
|
58
|
+
restartRequired: localSettings.suiGrpcUrl.appliesAfter === "mcp_server_restart" ||
|
|
59
|
+
localSettings.suiGraphqlUrl.appliesAfter === "mcp_server_restart"
|
|
60
|
+
});
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (request.method === "POST" && matches.walletIdentity) {
|
|
64
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
65
|
+
const wallet = await options.store.createWalletIdentitySession();
|
|
66
|
+
const baseUrl = requestBaseUrl(request);
|
|
67
|
+
sendJson(response, 200, walletIdentitySessionResponse(wallet, baseUrl));
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (request.method === "POST" && matches.clearActiveAccount) {
|
|
71
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
72
|
+
await settings.activityStore.clearActiveAccount();
|
|
73
|
+
sendJson(response, 200, { status: "cleared" });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (request.method === "POST" && matches.setSuiGrpcUrl) {
|
|
77
|
+
const body = await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
78
|
+
const urlValue = typeof body.url === "string" ? body.url : undefined;
|
|
79
|
+
if (!urlValue) {
|
|
80
|
+
sendJson(response, 400, { error: "input_invalid" });
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.setSuiGrpcUrl(urlValue));
|
|
84
|
+
sendJson(response, 200, result);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (request.method === "POST" && matches.restoreDefaultSuiGrpcUrl) {
|
|
88
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
89
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.resetSuiGrpcUrl());
|
|
90
|
+
sendJson(response, 200, result);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (request.method === "POST" && matches.setSuiGraphqlUrl) {
|
|
94
|
+
const body = await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
95
|
+
const urlValue = typeof body.url === "string" ? body.url : undefined;
|
|
96
|
+
if (!urlValue) {
|
|
97
|
+
sendJson(response, 400, { error: "input_invalid" });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.setSuiGraphqlUrl(urlValue));
|
|
101
|
+
sendJson(response, 200, result);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
if (request.method === "POST" && matches.restoreDefaultSuiGraphqlUrl) {
|
|
105
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
106
|
+
const result = await mapLocalSettingsError(() => settings.localSettings.resetSuiGraphqlUrl());
|
|
107
|
+
sendJson(response, 200, result);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (request.method === "GET" && matches.exportLocalData) {
|
|
111
|
+
const envelope = await mapLocalDataError(() => settings.localData.exportLocalData());
|
|
112
|
+
sendJson(response, 200, envelope);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
if (request.method === "POST" && matches.previewImport) {
|
|
116
|
+
const body = await readJsonBody(request, MAX_IMPORT_BODY_BYTES);
|
|
117
|
+
const preview = await mapLocalDataError(() => settings.localData.previewImportLocalData(body));
|
|
118
|
+
sendJson(response, 200, preview);
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (request.method === "POST" && matches.importLocalData) {
|
|
122
|
+
const body = await readJsonBody(request, MAX_IMPORT_BODY_BYTES);
|
|
123
|
+
const result = await mapLocalDataError(() => settings.localData.importLocalDataReplace(body));
|
|
124
|
+
await options.store.invalidateAllLocalSessions("local_data_import");
|
|
125
|
+
sendJson(response, 200, result);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (request.method === "POST" && matches.resetLocalData) {
|
|
129
|
+
await readJsonBody(request, MAX_SETTINGS_BODY_BYTES);
|
|
130
|
+
const result = await mapLocalDataError(() => settings.localData.resetLocalData());
|
|
131
|
+
await options.store.invalidateAllLocalSessions("local_data_reset");
|
|
132
|
+
sendJson(response, 200, result);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
sendJson(response, 404, { error: "not_found" });
|
|
136
|
+
}
|
|
137
|
+
function requireSettingsDeps(options) {
|
|
138
|
+
if (!options.activityStore || !options.localSettings || !options.localData || !options.serverInfo) {
|
|
139
|
+
throw new HttpError(404, "not_found");
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
activityStore: options.activityStore,
|
|
143
|
+
localSettings: options.localSettings,
|
|
144
|
+
localData: options.localData,
|
|
145
|
+
serverInfo: options.serverInfo
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
function requestBaseUrl(request) {
|
|
149
|
+
const host = request.headers.host;
|
|
150
|
+
if (!host) {
|
|
151
|
+
throw new HttpError(400, "host_required");
|
|
152
|
+
}
|
|
153
|
+
return `http://${host}`;
|
|
154
|
+
}
|
|
155
|
+
async function mapLocalSettingsError(operation) {
|
|
156
|
+
try {
|
|
157
|
+
return await operation();
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (error instanceof LocalSettingsError) {
|
|
161
|
+
throw new HttpError(statusForLocalSettingsError(error), error.kind);
|
|
162
|
+
}
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
function statusForLocalSettingsError(error) {
|
|
167
|
+
return error.kind === "input_invalid" ? 400 : 500;
|
|
168
|
+
}
|
|
169
|
+
async function mapLocalDataError(operation) {
|
|
170
|
+
try {
|
|
171
|
+
return await operation();
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error instanceof LocalDataError) {
|
|
175
|
+
throw new HttpError(statusForLocalDataError(error), error.kind);
|
|
176
|
+
}
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function statusForLocalDataError(error) {
|
|
181
|
+
return error.kind === "input_invalid" ? 400 : 500;
|
|
182
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { walletIdentityPollingHint } from "../core/session/walletIdentity.js";
|
|
2
|
+
export function walletIdentitySessionResponse(wallet, baseUrl) {
|
|
3
|
+
return {
|
|
4
|
+
walletSessionId: wallet.session.id,
|
|
5
|
+
walletUrl: `${baseUrl}/analysis/${wallet.session.id}#${wallet.token}`,
|
|
6
|
+
status: wallet.session.status,
|
|
7
|
+
expiresAt: wallet.session.expiresAt,
|
|
8
|
+
lastActivityAt: wallet.session.lastActivityAt,
|
|
9
|
+
openTarget: "system_browser",
|
|
10
|
+
accessScope: "same_machine_loopback",
|
|
11
|
+
pollingHint: walletIdentityPollingHint()
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { resolveActivityDatabasePath } from "../core/activity/sqliteActivityStore.js";
|
|
2
|
+
import { DEFAULT_SUI_GRAPHQL_URL, DEFAULT_SUI_GRPC_URL, SUI_MAINNET_CHAIN_IDENTIFIER, parseGraphqlUrl, parseGrpcUrl } from "./suiEndpoint.js";
|
|
3
|
+
import { SETTINGS_APPLIES_AFTER_RESTART } from "../core/preferences/preferencesStore.js";
|
|
4
|
+
export function loadBootConfig(env = process.env) {
|
|
5
|
+
const network = env.SUI_NETWORK ?? "mainnet";
|
|
6
|
+
if (network !== "mainnet") {
|
|
7
|
+
throw new Error("Say Ur Intent product runtime only supports mainnet");
|
|
8
|
+
}
|
|
9
|
+
if (env.SUI_RPC_URL) {
|
|
10
|
+
throw new Error("Sui JSON-RPC endpoint configuration is not supported; use local settings, SUI_GRPC_URL for gRPC, or SUI_GRAPHQL_URL for GraphQL");
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
network: "mainnet",
|
|
14
|
+
expectedChainIdentifier: SUI_MAINNET_CHAIN_IDENTIFIER,
|
|
15
|
+
reviewHost: "127.0.0.1",
|
|
16
|
+
reviewPort: parseReviewPort(env.SAY_UR_INTENT_REVIEW_PORT),
|
|
17
|
+
activityDatabasePath: resolveActivityDatabasePath(env)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// Fixed default so the loopback review origin (scheme://127.0.0.1:port) stays
|
|
21
|
+
// stable across server restarts, which is what lets the browser wallet
|
|
22
|
+
// autoconnect silently instead of prompting again. Override per MCP
|
|
23
|
+
// registration with SAY_UR_INTENT_REVIEW_PORT. The port is never silently
|
|
24
|
+
// reassigned to a random port (that would break origin continuity); instead,
|
|
25
|
+
// when a newer instance finds the port held by a previous Say Ur Intent review
|
|
26
|
+
// server on the same machine, it takes the port over so the most recently
|
|
27
|
+
// started client owns the single review origin (see reviewServerAcquire.ts).
|
|
28
|
+
export const DEFAULT_REVIEW_PORT = 8765;
|
|
29
|
+
function parseReviewPort(value) {
|
|
30
|
+
if (value === undefined || value === "") {
|
|
31
|
+
return DEFAULT_REVIEW_PORT;
|
|
32
|
+
}
|
|
33
|
+
const port = Number(value);
|
|
34
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
35
|
+
throw new Error(`SAY_UR_INTENT_REVIEW_PORT must be an integer between 1 and 65535, got: ${value}`);
|
|
36
|
+
}
|
|
37
|
+
return port;
|
|
38
|
+
}
|
|
39
|
+
export function composeRuntimeConfig(input) {
|
|
40
|
+
const env = input.env ?? process.env;
|
|
41
|
+
const defaultSuiGrpcUrl = parseGrpcUrl(input.defaultSuiGrpcUrl ?? DEFAULT_SUI_GRPC_URL);
|
|
42
|
+
const defaultSuiGraphqlUrl = parseGraphqlUrl(input.defaultSuiGraphqlUrl ?? DEFAULT_SUI_GRAPHQL_URL);
|
|
43
|
+
const storedValue = parseGrpcUrl(input.storedSuiGrpcUrl ?? defaultSuiGrpcUrl);
|
|
44
|
+
const storedGraphqlValue = parseGraphqlUrl(input.storedSuiGraphqlUrl ?? defaultSuiGraphqlUrl);
|
|
45
|
+
const envValue = env.SUI_GRPC_URL === undefined ? undefined : parseGrpcUrl(env.SUI_GRPC_URL);
|
|
46
|
+
const envGraphqlValue = env.SUI_GRAPHQL_URL === undefined ? undefined : parseGraphqlUrl(env.SUI_GRAPHQL_URL);
|
|
47
|
+
const suiGrpcUrl = resolveSuiGrpcUrlView({
|
|
48
|
+
defaultSuiGrpcUrl,
|
|
49
|
+
storedValue,
|
|
50
|
+
envValue
|
|
51
|
+
});
|
|
52
|
+
const suiGraphqlUrl = resolveSuiGraphqlUrlView({
|
|
53
|
+
defaultSuiGraphqlUrl,
|
|
54
|
+
storedValue: storedGraphqlValue,
|
|
55
|
+
envValue: envGraphqlValue
|
|
56
|
+
});
|
|
57
|
+
return {
|
|
58
|
+
...input.bootConfig,
|
|
59
|
+
grpcUrl: suiGrpcUrl.effectiveValue,
|
|
60
|
+
graphqlUrl: suiGraphqlUrl.effectiveValue,
|
|
61
|
+
suiGrpcUrl,
|
|
62
|
+
suiGraphqlUrl
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function resolveSuiGrpcUrlView(input) {
|
|
66
|
+
const defaultSuiGrpcUrl = parseGrpcUrl(input.defaultSuiGrpcUrl ?? DEFAULT_SUI_GRPC_URL);
|
|
67
|
+
const storedValue = parseGrpcUrl(input.storedValue ?? defaultSuiGrpcUrl);
|
|
68
|
+
if (input.envValue !== undefined) {
|
|
69
|
+
const effectiveValue = parseGrpcUrl(input.envValue);
|
|
70
|
+
return {
|
|
71
|
+
storedValue,
|
|
72
|
+
effectiveValue,
|
|
73
|
+
source: "environment",
|
|
74
|
+
pendingStoredValue: effectiveValue === storedValue ? undefined : storedValue,
|
|
75
|
+
appliesAfter: effectiveValue === storedValue ? undefined : SETTINGS_APPLIES_AFTER_RESTART
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
storedValue,
|
|
80
|
+
effectiveValue: storedValue,
|
|
81
|
+
source: storedValue === defaultSuiGrpcUrl ? "builtin_default" : "local_db"
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
export function resolveSuiGraphqlUrlView(input) {
|
|
85
|
+
const defaultSuiGraphqlUrl = parseGraphqlUrl(input.defaultSuiGraphqlUrl ?? DEFAULT_SUI_GRAPHQL_URL);
|
|
86
|
+
const storedValue = parseGraphqlUrl(input.storedValue ?? defaultSuiGraphqlUrl);
|
|
87
|
+
if (input.envValue !== undefined) {
|
|
88
|
+
const effectiveValue = parseGraphqlUrl(input.envValue);
|
|
89
|
+
return {
|
|
90
|
+
storedValue,
|
|
91
|
+
effectiveValue,
|
|
92
|
+
source: "environment",
|
|
93
|
+
pendingStoredValue: effectiveValue === storedValue ? undefined : storedValue,
|
|
94
|
+
appliesAfter: effectiveValue === storedValue ? undefined : SETTINGS_APPLIES_AFTER_RESTART
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
storedValue,
|
|
99
|
+
effectiveValue: storedValue,
|
|
100
|
+
source: storedValue === defaultSuiGraphqlUrl ? "builtin_default" : "local_db"
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export { DEFAULT_SUI_GRAPHQL_URL, DEFAULT_SUI_GRPC_URL, SUI_MAINNET_CHAIN_IDENTIFIER };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { SETTINGS_APPLIES_AFTER_RESTART, LocalSettingsError } from "../core/preferences/preferencesStore.js";
|
|
2
|
+
import { DEFAULT_SUI_GRAPHQL_ENDPOINT_VERIFY_TIMEOUT_MS, DEFAULT_SUI_GRAPHQL_URL, DEFAULT_SUI_GRPC_ENDPOINT_VERIFY_TIMEOUT_MS, DEFAULT_SUI_GRPC_URL, SUI_MAINNET_CHAIN_IDENTIFIER, SuiEndpointError, parseGraphqlUrl, parseGrpcUrl, verifyMainnetGraphqlEndpoint, verifyMainnetGrpcEndpoint } from "./suiEndpoint.js";
|
|
3
|
+
import { resolveSuiGraphqlUrlView, resolveSuiGrpcUrlView } from "./config.js";
|
|
4
|
+
export class RuntimeLocalSettingsService {
|
|
5
|
+
options;
|
|
6
|
+
env;
|
|
7
|
+
defaultSuiGrpcUrl;
|
|
8
|
+
defaultSuiGraphqlUrl;
|
|
9
|
+
bootSuiGrpcUrl;
|
|
10
|
+
bootSuiGraphqlUrl;
|
|
11
|
+
verifyGrpcEndpoint;
|
|
12
|
+
verifyGraphqlEndpoint;
|
|
13
|
+
now;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.options = options;
|
|
16
|
+
this.env = options.env ?? process.env;
|
|
17
|
+
this.defaultSuiGrpcUrl = parseGrpcUrl(options.defaultSuiGrpcUrl ?? DEFAULT_SUI_GRPC_URL);
|
|
18
|
+
this.defaultSuiGraphqlUrl = parseGraphqlUrl(options.defaultSuiGraphqlUrl ?? DEFAULT_SUI_GRAPHQL_URL);
|
|
19
|
+
this.bootSuiGrpcUrl = normalizeBootSuiGrpcUrlView(options.bootSuiGrpcUrl ??
|
|
20
|
+
resolveSuiGrpcUrlView({
|
|
21
|
+
defaultSuiGrpcUrl: this.defaultSuiGrpcUrl,
|
|
22
|
+
envValue: this.env.SUI_GRPC_URL
|
|
23
|
+
}));
|
|
24
|
+
this.bootSuiGraphqlUrl = normalizeBootSuiGraphqlUrlView(options.bootSuiGraphqlUrl ??
|
|
25
|
+
resolveSuiGraphqlUrlView({
|
|
26
|
+
defaultSuiGraphqlUrl: this.defaultSuiGraphqlUrl,
|
|
27
|
+
envValue: this.env.SUI_GRAPHQL_URL
|
|
28
|
+
}));
|
|
29
|
+
this.verifyGrpcEndpoint = options.verifyGrpcEndpoint ?? defaultGrpcEndpointVerifier;
|
|
30
|
+
this.verifyGraphqlEndpoint = options.verifyGraphqlEndpoint ?? defaultGraphqlEndpointVerifier;
|
|
31
|
+
this.now = options.now ?? (() => new Date());
|
|
32
|
+
}
|
|
33
|
+
async getLocalSettings() {
|
|
34
|
+
const stored = await this.options.preferencesRepository.getSuiGrpcUrl();
|
|
35
|
+
const storedGraphql = await this.options.preferencesRepository.getSuiGraphqlUrl();
|
|
36
|
+
const currentStoredView = resolveSuiGrpcUrlView({
|
|
37
|
+
defaultSuiGrpcUrl: this.defaultSuiGrpcUrl,
|
|
38
|
+
storedValue: stored?.value,
|
|
39
|
+
envValue: this.env.SUI_GRPC_URL
|
|
40
|
+
});
|
|
41
|
+
const currentStoredGraphqlView = resolveSuiGraphqlUrlView({
|
|
42
|
+
defaultSuiGraphqlUrl: this.defaultSuiGraphqlUrl,
|
|
43
|
+
storedValue: storedGraphql?.value,
|
|
44
|
+
envValue: this.env.SUI_GRAPHQL_URL
|
|
45
|
+
});
|
|
46
|
+
return {
|
|
47
|
+
suiGrpcUrl: withBootEffectiveEndpoint(currentStoredView, this.bootSuiGrpcUrl),
|
|
48
|
+
suiGraphqlUrl: withBootEffectiveEndpoint(currentStoredGraphqlView, this.bootSuiGraphqlUrl)
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async setSuiGrpcUrl(url) {
|
|
52
|
+
const normalized = parseEndpointForTool(url);
|
|
53
|
+
await this.verifyEndpointForTool(normalized, "grpc");
|
|
54
|
+
const result = await this.options.preferencesRepository.setSuiGrpcUrl(normalized, this.now());
|
|
55
|
+
return {
|
|
56
|
+
status: "saved",
|
|
57
|
+
storedValue: result.storedValue,
|
|
58
|
+
previousStoredValue: result.previousStoredValue,
|
|
59
|
+
appliesAfter: SETTINGS_APPLIES_AFTER_RESTART
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
async setSuiGraphqlUrl(url) {
|
|
63
|
+
const normalized = parseGraphqlEndpointForTool(url);
|
|
64
|
+
await this.verifyEndpointForTool(normalized, "graphql");
|
|
65
|
+
const result = await this.options.preferencesRepository.setSuiGraphqlUrl(normalized, this.now());
|
|
66
|
+
return {
|
|
67
|
+
status: "saved",
|
|
68
|
+
storedValue: result.storedValue,
|
|
69
|
+
previousStoredValue: result.previousStoredValue,
|
|
70
|
+
appliesAfter: SETTINGS_APPLIES_AFTER_RESTART
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
async resetSuiGrpcUrl() {
|
|
74
|
+
const result = await this.options.preferencesRepository.resetSuiGrpcUrl(this.defaultSuiGrpcUrl, this.now());
|
|
75
|
+
return {
|
|
76
|
+
status: "reset",
|
|
77
|
+
storedValue: result.storedValue,
|
|
78
|
+
previousStoredValue: result.previousStoredValue,
|
|
79
|
+
appliesAfter: SETTINGS_APPLIES_AFTER_RESTART
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
async resetSuiGraphqlUrl() {
|
|
83
|
+
const result = await this.options.preferencesRepository.resetSuiGraphqlUrl(this.defaultSuiGraphqlUrl, this.now());
|
|
84
|
+
return {
|
|
85
|
+
status: "reset",
|
|
86
|
+
storedValue: result.storedValue,
|
|
87
|
+
previousStoredValue: result.previousStoredValue,
|
|
88
|
+
appliesAfter: SETTINGS_APPLIES_AFTER_RESTART
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
async verifyEndpointForTool(url, transport) {
|
|
92
|
+
try {
|
|
93
|
+
await (transport === "grpc" ? this.verifyGrpcEndpoint(url) : this.verifyGraphqlEndpoint(url));
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (error instanceof LocalSettingsError) {
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
if (error instanceof SuiEndpointError) {
|
|
100
|
+
throw endpointErrorForTool(error, transport);
|
|
101
|
+
}
|
|
102
|
+
const label = transport === "grpc" ? "gRPC" : "GraphQL";
|
|
103
|
+
throw new LocalSettingsError("internal_error", `Sui ${label} endpoint verification failed`, {
|
|
104
|
+
message: `Sui ${label} endpoint verification failed`
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function normalizeBootSuiGrpcUrlView(view) {
|
|
110
|
+
return {
|
|
111
|
+
...view,
|
|
112
|
+
storedValue: parseGrpcUrl(view.storedValue),
|
|
113
|
+
effectiveValue: parseGrpcUrl(view.effectiveValue)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function normalizeBootSuiGraphqlUrlView(view) {
|
|
117
|
+
return {
|
|
118
|
+
...view,
|
|
119
|
+
storedValue: parseGraphqlUrl(view.storedValue),
|
|
120
|
+
effectiveValue: parseGraphqlUrl(view.effectiveValue)
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
function withBootEffectiveEndpoint(storedView, bootView) {
|
|
124
|
+
if (storedView.effectiveValue === bootView.effectiveValue) {
|
|
125
|
+
return storedView;
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
storedValue: storedView.storedValue,
|
|
129
|
+
effectiveValue: bootView.effectiveValue,
|
|
130
|
+
source: bootView.source,
|
|
131
|
+
pendingStoredValue: storedView.storedValue === bootView.effectiveValue ? undefined : storedView.storedValue,
|
|
132
|
+
appliesAfter: SETTINGS_APPLIES_AFTER_RESTART
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
async function defaultGrpcEndpointVerifier(url) {
|
|
136
|
+
await verifyMainnetGrpcEndpoint({
|
|
137
|
+
url,
|
|
138
|
+
expectedChainIdentifier: SUI_MAINNET_CHAIN_IDENTIFIER,
|
|
139
|
+
timeoutMs: DEFAULT_SUI_GRPC_ENDPOINT_VERIFY_TIMEOUT_MS
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async function defaultGraphqlEndpointVerifier(url) {
|
|
143
|
+
await verifyMainnetGraphqlEndpoint({
|
|
144
|
+
url,
|
|
145
|
+
expectedChainIdentifier: SUI_MAINNET_CHAIN_IDENTIFIER,
|
|
146
|
+
timeoutMs: DEFAULT_SUI_GRAPHQL_ENDPOINT_VERIFY_TIMEOUT_MS
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
function parseEndpointForTool(url) {
|
|
150
|
+
try {
|
|
151
|
+
return parseGrpcUrl(url);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
if (error instanceof SuiEndpointError) {
|
|
155
|
+
throw endpointErrorForTool(error, "grpc");
|
|
156
|
+
}
|
|
157
|
+
throw error;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
function parseGraphqlEndpointForTool(url) {
|
|
161
|
+
try {
|
|
162
|
+
return parseGraphqlUrl(url);
|
|
163
|
+
}
|
|
164
|
+
catch (error) {
|
|
165
|
+
if (error instanceof SuiEndpointError) {
|
|
166
|
+
throw endpointErrorForTool(error, "graphql");
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
function endpointErrorForTool(error, transport) {
|
|
172
|
+
const label = transport === "grpc" ? "gRPC" : "GraphQL";
|
|
173
|
+
if (error.kind === "chain_identifier_mismatch") {
|
|
174
|
+
return new LocalSettingsError("input_invalid", `Sui ${label} endpoint did not report the mainnet chain identifier`, {
|
|
175
|
+
reason: error.kind,
|
|
176
|
+
...endpointErrorDetailsForTool(error, ["chainIdentifier", "expectedChainIdentifier"])
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
if (error.kind === "endpoint_timeout" || error.kind === "endpoint_unreachable") {
|
|
180
|
+
return new LocalSettingsError("input_invalid", `Sui ${label} endpoint could not be verified`, {
|
|
181
|
+
reason: error.kind,
|
|
182
|
+
...endpointErrorDetailsForTool(error, error.kind === "endpoint_timeout" ? ["timeoutMs"] : [])
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
return new LocalSettingsError("input_invalid", error.message, {
|
|
186
|
+
reason: error.kind
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
function endpointErrorDetailsForTool(error, allowedKeys) {
|
|
190
|
+
const details = {};
|
|
191
|
+
for (const key of allowedKeys) {
|
|
192
|
+
const value = error.details[key];
|
|
193
|
+
if (value !== undefined) {
|
|
194
|
+
details[key] = value;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return details;
|
|
198
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function createStderrLogger(scope) {
|
|
2
|
+
const write = (level, message, meta) => {
|
|
3
|
+
const record = {
|
|
4
|
+
level,
|
|
5
|
+
scope,
|
|
6
|
+
message,
|
|
7
|
+
meta: meta ? redactMeta(meta) : undefined,
|
|
8
|
+
at: new Date().toISOString()
|
|
9
|
+
};
|
|
10
|
+
process.stderr.write(`${JSON.stringify(record)}\n`);
|
|
11
|
+
};
|
|
12
|
+
return {
|
|
13
|
+
info: (message, meta) => write("info", message, meta),
|
|
14
|
+
warn: (message, meta) => write("warn", message, meta),
|
|
15
|
+
error: (message, meta) => write("error", message, meta)
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
const REDACTED = "[redacted]";
|
|
19
|
+
const CIRCULAR = "[circular]";
|
|
20
|
+
const SENSITIVE_META_KEY = /token|secret|private|signature|bytes/i;
|
|
21
|
+
export function redactMeta(meta) {
|
|
22
|
+
return redactRecord(meta, new WeakSet());
|
|
23
|
+
}
|
|
24
|
+
function redactRecord(record, seen) {
|
|
25
|
+
if (seen.has(record)) {
|
|
26
|
+
return { value: CIRCULAR };
|
|
27
|
+
}
|
|
28
|
+
seen.add(record);
|
|
29
|
+
const redacted = Object.fromEntries(Object.entries(record).map(([key, value]) => [
|
|
30
|
+
key,
|
|
31
|
+
SENSITIVE_META_KEY.test(key) ? REDACTED : redactValue(value, seen)
|
|
32
|
+
]));
|
|
33
|
+
seen.delete(record);
|
|
34
|
+
return redacted;
|
|
35
|
+
}
|
|
36
|
+
function redactValue(value, seen) {
|
|
37
|
+
if (value === null || typeof value !== "object") {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
if (Array.isArray(value)) {
|
|
41
|
+
if (seen.has(value)) {
|
|
42
|
+
return CIRCULAR;
|
|
43
|
+
}
|
|
44
|
+
seen.add(value);
|
|
45
|
+
const redacted = value.map((item) => redactValue(item, seen));
|
|
46
|
+
seen.delete(value);
|
|
47
|
+
return redacted;
|
|
48
|
+
}
|
|
49
|
+
return redactRecord(value, seen);
|
|
50
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
function errorCode(error) {
|
|
2
|
+
return typeof error === "object" && error !== null
|
|
3
|
+
? error.code
|
|
4
|
+
: undefined;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Bind the review server to its fixed port, taking the port over from a previous
|
|
8
|
+
* instance of our own review server if necessary.
|
|
9
|
+
*
|
|
10
|
+
* Safety: the holder is positively identified over loopback before any signal is
|
|
11
|
+
* sent. A process that is not our review server is never signalled; a same-name
|
|
12
|
+
* instance owned by another OS user surfaces a clear error rather than a kill.
|
|
13
|
+
* The port is never silently reassigned, so the wallet autoconnect origin stays
|
|
14
|
+
* stable.
|
|
15
|
+
*/
|
|
16
|
+
export async function startReviewServerWithTakeover(start, port, deps) {
|
|
17
|
+
try {
|
|
18
|
+
return await start(port);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (errorCode(error) !== "EADDRINUSE") {
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
const holder = await deps.probeIdentity(port);
|
|
25
|
+
if (!holder || holder.service !== deps.serviceName) {
|
|
26
|
+
throw new Error(`Review server port ${port} is already in use by a process that is not a ${deps.serviceName} review server. ` +
|
|
27
|
+
`Set SAY_UR_INTENT_REVIEW_PORT to a free port. The port is not reassigned automatically so the wallet autoconnect origin stays stable.`);
|
|
28
|
+
}
|
|
29
|
+
if (holder.pid === deps.currentPid) {
|
|
30
|
+
// We appear to hold the port ourselves yet cannot bind it. Never signal
|
|
31
|
+
// our own process; surface the original bind error.
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
deps.logger.info("review port held by a previous say-ur-intent instance; taking it over", {
|
|
35
|
+
port,
|
|
36
|
+
previousPid: holder.pid
|
|
37
|
+
});
|
|
38
|
+
const terminated = deps.terminate(holder.pid);
|
|
39
|
+
if (!terminated.ok) {
|
|
40
|
+
if (terminated.reason === "no_permission") {
|
|
41
|
+
throw new Error(`Review server port ${port} is held by a ${deps.serviceName} instance owned by another OS user; cannot take it over. ` +
|
|
42
|
+
`Set SAY_UR_INTENT_REVIEW_PORT to a free port for this client.`);
|
|
43
|
+
}
|
|
44
|
+
if (terminated.reason === "error") {
|
|
45
|
+
throw new Error(`Failed to stop the ${deps.serviceName} instance holding review port ${port}: ${terminated.message ?? "unknown error"}.`);
|
|
46
|
+
}
|
|
47
|
+
// not_found: the holder already exited between probe and signal; fall
|
|
48
|
+
// through and rebind.
|
|
49
|
+
}
|
|
50
|
+
const waitForReleaseMs = deps.waitForReleaseMs ?? 3000;
|
|
51
|
+
const pollIntervalMs = deps.pollIntervalMs ?? 100;
|
|
52
|
+
const attempts = Math.max(1, Math.ceil(waitForReleaseMs / pollIntervalMs));
|
|
53
|
+
for (let attempt = 0; attempt < attempts; attempt++) {
|
|
54
|
+
await deps.delay(pollIntervalMs);
|
|
55
|
+
try {
|
|
56
|
+
return await start(port);
|
|
57
|
+
}
|
|
58
|
+
catch (retryError) {
|
|
59
|
+
if (errorCode(retryError) !== "EADDRINUSE") {
|
|
60
|
+
throw retryError;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`Review server port ${port} did not become free after stopping the previous ${deps.serviceName} instance (pid ${holder.pid}). ` +
|
|
65
|
+
`Set SAY_UR_INTENT_REVIEW_PORT to a free port.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Ask whatever is listening on the loopback review port to identify itself. Only
|
|
70
|
+
* our own review server answers `/__identity`; any other process either returns
|
|
71
|
+
* something else or does not answer, and we report it as "not ours" so the
|
|
72
|
+
* caller never signals it.
|
|
73
|
+
*/
|
|
74
|
+
export async function probeReviewServerIdentity(port, host = "127.0.0.1", timeoutMs = 1000) {
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(`http://${host}:${port}/__identity`, {
|
|
77
|
+
method: "GET",
|
|
78
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
79
|
+
});
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
const body = await response.json();
|
|
84
|
+
if (typeof body === "object" &&
|
|
85
|
+
body !== null &&
|
|
86
|
+
typeof body.service === "string" &&
|
|
87
|
+
typeof body.role === "string" &&
|
|
88
|
+
typeof body.pid === "number") {
|
|
89
|
+
const typed = body;
|
|
90
|
+
return {
|
|
91
|
+
service: typed.service,
|
|
92
|
+
role: typed.role,
|
|
93
|
+
pid: typed.pid,
|
|
94
|
+
...(typeof typed.version === "string" ? { version: typed.version } : {})
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Gracefully signal a same-user process. SIGTERM lets the previous instance run
|
|
105
|
+
* its own shutdown handler (close the review server and database, then exit).
|
|
106
|
+
* Killing another OS user's process fails with EPERM, which we report instead of
|
|
107
|
+
* escalating.
|
|
108
|
+
*/
|
|
109
|
+
export function terminateProcessByPid(pid) {
|
|
110
|
+
try {
|
|
111
|
+
process.kill(pid, "SIGTERM");
|
|
112
|
+
return { ok: true };
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
const code = errorCode(error);
|
|
116
|
+
if (code === "ESRCH") {
|
|
117
|
+
return { ok: false, reason: "not_found" };
|
|
118
|
+
}
|
|
119
|
+
if (code === "EPERM") {
|
|
120
|
+
return { ok: false, reason: "no_permission" };
|
|
121
|
+
}
|
|
122
|
+
return {
|
|
123
|
+
ok: false,
|
|
124
|
+
reason: "error",
|
|
125
|
+
message: error instanceof Error ? error.message : String(error)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|