nolo-cli 0.1.21 → 0.1.23
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/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +2 -0
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +92 -0
- package/agent-runtime/types.ts +42 -0
- package/agentRunCommand.ts +74 -1
- package/agentRuntimeCommands.ts +17 -89
- package/ai/agent/streamAgentChatTurn.ts +104 -20
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +56 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +64 -0
- package/ai/llm/kimi.ts +1 -1
- package/ai/llm/providers.ts +3 -0
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/skills/skillDocProtocol.ts +95 -3
- package/ai/taskRun/taskRunProtocol.ts +1 -0
- package/ai/tools/agent/agentTools.ts +17 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/modelUsageTools.ts +5 -0
- package/client/agentRun.test.ts +257 -7
- package/client/agentRun.ts +133 -34
- package/client/localRuntimeAdapter.test.ts +2 -0
- package/client/localRuntimeAdapter.ts +15 -2
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/machineCommands.ts +400 -45
- package/package.json +4 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/tableCommands.ts +42 -5
package/client/agentRun.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { runLocalAgentTurn } from "../agentRuntimeLocal";
|
|
4
|
+
import {
|
|
5
|
+
FRONTEND_IMPLEMENTER_AGENT_KEY,
|
|
6
|
+
NOLO_FULLSTACK_AGENT_KEY,
|
|
7
|
+
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
8
|
+
NOLO_REVIEW_AGENT_KEY,
|
|
9
|
+
NOLO_SENIOR_FULLSTACK_AGENT_KEY,
|
|
10
|
+
} from "../agentAliases";
|
|
4
11
|
import type { LocalAgentToolEvent } from "../agent-runtime/localLoop";
|
|
5
12
|
import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
|
|
6
13
|
import { createCliLocalRuntimeAdapter } from "./localRuntimeAdapter";
|
|
@@ -52,7 +59,7 @@ type RunAgentTurnOptions = {
|
|
|
52
59
|
taskRunContext?: TaskRunPromptContext;
|
|
53
60
|
taskRunRecorder?: (args: {
|
|
54
61
|
options: RunAgentTurnOptions;
|
|
55
|
-
status: "completed" | "failed";
|
|
62
|
+
status: "running" | "completed" | "failed";
|
|
56
63
|
dialogId?: string;
|
|
57
64
|
resultSummary?: string;
|
|
58
65
|
errorCode?: string;
|
|
@@ -62,10 +69,11 @@ type RunAgentTurnOptions = {
|
|
|
62
69
|
fetchImpl?: typeof fetch;
|
|
63
70
|
};
|
|
64
71
|
|
|
65
|
-
export type RunAgentTurnResult = {
|
|
66
|
-
exitCode: number;
|
|
67
|
-
dialogId?: string;
|
|
68
|
-
|
|
72
|
+
export type RunAgentTurnResult = {
|
|
73
|
+
exitCode: number;
|
|
74
|
+
dialogId?: string;
|
|
75
|
+
streamInterrupted?: boolean;
|
|
76
|
+
};
|
|
69
77
|
|
|
70
78
|
type ScriptBridgeDecision = {
|
|
71
79
|
hasAuthToken: boolean;
|
|
@@ -84,6 +92,38 @@ const SERVER_PLATFORM_TOOL_NAMES = new Set([
|
|
|
84
92
|
"updateTableRows",
|
|
85
93
|
]);
|
|
86
94
|
|
|
95
|
+
const KNOWN_SERVER_PLATFORM_AGENT_KEYS = new Set([
|
|
96
|
+
FRONTEND_IMPLEMENTER_AGENT_KEY,
|
|
97
|
+
NOLO_FULLSTACK_AGENT_KEY,
|
|
98
|
+
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
99
|
+
NOLO_REVIEW_AGENT_KEY,
|
|
100
|
+
NOLO_SENIOR_FULLSTACK_AGENT_KEY,
|
|
101
|
+
]);
|
|
102
|
+
|
|
103
|
+
const KNOWN_SERVER_PLATFORM_AGENT_ALIASES = new Set([
|
|
104
|
+
"code-review",
|
|
105
|
+
"frontend",
|
|
106
|
+
"frontend-agent",
|
|
107
|
+
"frontend-implementer",
|
|
108
|
+
"full-stack",
|
|
109
|
+
"fullstack",
|
|
110
|
+
"nolo code review",
|
|
111
|
+
"nolo frontend",
|
|
112
|
+
"nolo fullstack",
|
|
113
|
+
"nolo project manager",
|
|
114
|
+
"nolo reviewer",
|
|
115
|
+
"nolo-code-review",
|
|
116
|
+
"nolo-frontend",
|
|
117
|
+
"nolo-fullstack",
|
|
118
|
+
"nolo-pm",
|
|
119
|
+
"nolo-project-manager",
|
|
120
|
+
"nolo-reviewer",
|
|
121
|
+
"pm",
|
|
122
|
+
"project-manager",
|
|
123
|
+
"review",
|
|
124
|
+
"reviewer",
|
|
125
|
+
]);
|
|
126
|
+
|
|
87
127
|
export function shouldUseScriptBridge(decision: ScriptBridgeDecision) {
|
|
88
128
|
return !decision.hasAuthToken && decision.scriptPathExists;
|
|
89
129
|
}
|
|
@@ -92,6 +132,25 @@ export function findServerPlatformTools(toolNames?: string[]) {
|
|
|
92
132
|
if (!Array.isArray(toolNames)) return [];
|
|
93
133
|
return toolNames.filter((toolName) => SERVER_PLATFORM_TOOL_NAMES.has(toolName));
|
|
94
134
|
}
|
|
135
|
+
|
|
136
|
+
function resolveServerPlatformToolNames(agentConfig: any) {
|
|
137
|
+
return findServerPlatformTools([
|
|
138
|
+
...(Array.isArray(agentConfig?.toolNames) ? agentConfig.toolNames : []),
|
|
139
|
+
...(Array.isArray(agentConfig?.runtimeToolPolicy?.agentTools)
|
|
140
|
+
? agentConfig.runtimeToolPolicy.agentTools
|
|
141
|
+
: []),
|
|
142
|
+
]);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function normalizeAgentRef(ref?: string) {
|
|
146
|
+
return ref?.trim().toLowerCase().replace(/\s+/g, " ");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
|
|
150
|
+
if (KNOWN_SERVER_PLATFORM_AGENT_KEYS.has(options.agentKey)) return true;
|
|
151
|
+
const normalizedKey = normalizeAgentRef(options.agentKey);
|
|
152
|
+
return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
|
|
153
|
+
}
|
|
95
154
|
|
|
96
155
|
function resolveAuthToken(env: EnvLike) {
|
|
97
156
|
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
@@ -126,6 +185,13 @@ function resolveLocalRuntimeAdapter(options: RunAgentTurnOptions) {
|
|
|
126
185
|
}
|
|
127
186
|
|
|
128
187
|
async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOptions) {
|
|
188
|
+
if (isKnownServerPlatformAgent(options)) {
|
|
189
|
+
options.output.write(
|
|
190
|
+
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
|
|
191
|
+
"Use --local explicitly to force local workspace tools.\n"
|
|
192
|
+
);
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
129
195
|
const adapter = resolveLocalRuntimeAdapter(options);
|
|
130
196
|
if (!adapter) return false;
|
|
131
197
|
let agentConfig;
|
|
@@ -134,7 +200,7 @@ async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOp
|
|
|
134
200
|
} catch {
|
|
135
201
|
return false;
|
|
136
202
|
}
|
|
137
|
-
const serverTools =
|
|
203
|
+
const serverTools = resolveServerPlatformToolNames(agentConfig);
|
|
138
204
|
if (serverTools.length === 0) return false;
|
|
139
205
|
options.output.write(
|
|
140
206
|
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} declares server platform tools ` +
|
|
@@ -292,7 +358,7 @@ async function runTaskRunCli(args: {
|
|
|
292
358
|
|
|
293
359
|
async function defaultTaskRunRecorder(args: {
|
|
294
360
|
options: RunAgentTurnOptions;
|
|
295
|
-
status: "completed" | "failed";
|
|
361
|
+
status: "running" | "completed" | "failed";
|
|
296
362
|
dialogId?: string;
|
|
297
363
|
resultSummary?: string;
|
|
298
364
|
errorCode?: string;
|
|
@@ -301,7 +367,7 @@ async function defaultTaskRunRecorder(args: {
|
|
|
301
367
|
const context = args.options.taskRunContext;
|
|
302
368
|
if (!context?.rowDbKey) return;
|
|
303
369
|
const role = inferTaskRunAgentRole(args.options.agentName || args.options.agentKey);
|
|
304
|
-
if (role === "reviewer") {
|
|
370
|
+
if (role === "reviewer" && args.status !== "running") {
|
|
305
371
|
await runTaskRunCli({
|
|
306
372
|
options: args.options,
|
|
307
373
|
cliArgs: [
|
|
@@ -359,7 +425,7 @@ async function defaultTaskRunRecorder(args: {
|
|
|
359
425
|
],
|
|
360
426
|
});
|
|
361
427
|
}
|
|
362
|
-
if (role === "reviewer") {
|
|
428
|
+
if (role === "reviewer" && args.status !== "running") {
|
|
363
429
|
const reviewStatus =
|
|
364
430
|
args.status === "failed" ? "blocked" : classifyTaskRunReviewStatus(args.resultSummary);
|
|
365
431
|
if (reviewStatus) {
|
|
@@ -385,7 +451,7 @@ async function defaultTaskRunRecorder(args: {
|
|
|
385
451
|
|
|
386
452
|
async function recordTaskRunOutcome(args: {
|
|
387
453
|
options: RunAgentTurnOptions;
|
|
388
|
-
status: "completed" | "failed";
|
|
454
|
+
status: "running" | "completed" | "failed";
|
|
389
455
|
dialogId?: string;
|
|
390
456
|
resultSummary?: string;
|
|
391
457
|
errorCode?: string;
|
|
@@ -510,12 +576,28 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
510
576
|
options.imageUrls
|
|
511
577
|
),
|
|
512
578
|
runtimeContext: {
|
|
513
|
-
surface: "cli",
|
|
514
|
-
host: "terminal",
|
|
515
|
-
runtime: "bun",
|
|
516
|
-
entrypoint: "nolo-cli",
|
|
517
|
-
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
518
|
-
|
|
579
|
+
surface: "cli",
|
|
580
|
+
host: "terminal",
|
|
581
|
+
runtime: "bun",
|
|
582
|
+
entrypoint: "nolo-cli",
|
|
583
|
+
capabilities: ["text-io", "streaming", "slash-commands"],
|
|
584
|
+
...(options.taskRunContext
|
|
585
|
+
? {
|
|
586
|
+
taskRun: {
|
|
587
|
+
rowDbKey: options.taskRunContext.rowDbKey,
|
|
588
|
+
...(options.taskRunContext.taskRunId
|
|
589
|
+
? { taskRunId: options.taskRunContext.taskRunId }
|
|
590
|
+
: {}),
|
|
591
|
+
...(options.taskRunContext.workItemId
|
|
592
|
+
? { workItemId: options.taskRunContext.workItemId }
|
|
593
|
+
: {}),
|
|
594
|
+
...(options.taskRunContext.artifactIds?.length
|
|
595
|
+
? { artifactIds: options.taskRunContext.artifactIds }
|
|
596
|
+
: {}),
|
|
597
|
+
},
|
|
598
|
+
}
|
|
599
|
+
: {}),
|
|
600
|
+
},
|
|
519
601
|
...(options.continueDialogId
|
|
520
602
|
? { continueDialogId: options.continueDialogId }
|
|
521
603
|
: {}),
|
|
@@ -541,7 +623,11 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
541
623
|
const result = await readStreamingAgentRun(options, res);
|
|
542
624
|
await recordTaskRunOutcome({
|
|
543
625
|
options,
|
|
544
|
-
status: result.
|
|
626
|
+
status: result.streamInterrupted
|
|
627
|
+
? "running"
|
|
628
|
+
: result.exitCode === 0
|
|
629
|
+
? "completed"
|
|
630
|
+
: "failed",
|
|
545
631
|
dialogId: result.dialogId,
|
|
546
632
|
...(result.exitCode !== 0 ? { errorCode: "STREAM_FAILED" } : {}),
|
|
547
633
|
});
|
|
@@ -586,9 +672,9 @@ async function runHttpAgentTurn(options: RunAgentTurnOptions, authToken: string)
|
|
|
586
672
|
};
|
|
587
673
|
await recordTaskRunOutcome({
|
|
588
674
|
options,
|
|
589
|
-
status: "completed",
|
|
675
|
+
status: options.background ? "running" : "completed",
|
|
590
676
|
dialogId: result.dialogId,
|
|
591
|
-
resultSummary: content
|
|
677
|
+
...(!options.background && content ? { resultSummary: content } : {}),
|
|
592
678
|
});
|
|
593
679
|
return result;
|
|
594
680
|
}
|
|
@@ -691,9 +777,9 @@ async function runLocalAgentTurnForCli(
|
|
|
691
777
|
}
|
|
692
778
|
await recordTaskRunOutcome({
|
|
693
779
|
options,
|
|
694
|
-
status: "completed",
|
|
780
|
+
status: options.background ? "running" : "completed",
|
|
695
781
|
dialogId: result.dialogId,
|
|
696
|
-
resultSummary: content
|
|
782
|
+
...(!options.background && content ? { resultSummary: content } : {}),
|
|
697
783
|
toolEvidence: summarizeLocalToolEvidence(toolEvents),
|
|
698
784
|
});
|
|
699
785
|
return { exitCode: 0, dialogId: result.dialogId };
|
|
@@ -735,15 +821,20 @@ async function readStreamingAgentRun(
|
|
|
735
821
|
hasPrintedLabel = true;
|
|
736
822
|
};
|
|
737
823
|
|
|
738
|
-
const handlePayload = (payload: any) => {
|
|
739
|
-
if (
|
|
740
|
-
|
|
741
|
-
}
|
|
742
|
-
if (payload?.type === "
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
824
|
+
const handlePayload = (payload: any) => {
|
|
825
|
+
if (typeof payload?.dialogId === "string" && payload.dialogId.trim()) {
|
|
826
|
+
dialogId = payload.dialogId;
|
|
827
|
+
}
|
|
828
|
+
if (payload?.error || payload?.type === "error") {
|
|
829
|
+
throw new Error(String(payload.error || payload.message || "Agent stream failed"));
|
|
830
|
+
}
|
|
831
|
+
if (payload?.type === "done") {
|
|
832
|
+
usage = payload.usage;
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (payload?.type === "dialog" || payload?.type === "status") {
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
747
838
|
|
|
748
839
|
const chunk =
|
|
749
840
|
payload?.type === "text"
|
|
@@ -786,10 +877,18 @@ async function readStreamingAgentRun(
|
|
|
786
877
|
.trim();
|
|
787
878
|
if (raw) handlePayload(JSON.parse(raw));
|
|
788
879
|
}
|
|
789
|
-
} catch (error) {
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
880
|
+
} catch (error) {
|
|
881
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
882
|
+
if (dialogId) {
|
|
883
|
+
options.output.write(
|
|
884
|
+
`\n[nolo] Agent stream transport interrupted after dialog ${dialogId} was created: ${message}\n`
|
|
885
|
+
);
|
|
886
|
+
options.output.write("[nolo] The agent run may still finish on the server; read the dialog before retrying.\n");
|
|
887
|
+
return { exitCode: 0, dialogId, streamInterrupted: true };
|
|
888
|
+
}
|
|
889
|
+
options.output.write(`\n[nolo] Agent stream failed: ${message}\n`);
|
|
890
|
+
return { exitCode: 1 };
|
|
891
|
+
} finally {
|
|
793
892
|
writer.flushAll();
|
|
794
893
|
}
|
|
795
894
|
|
|
@@ -353,6 +353,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
353
353
|
tool_choice: "auto",
|
|
354
354
|
url: "https://api.fireworks.ai/inference/v1/chat/completions",
|
|
355
355
|
provider: "fireworks",
|
|
356
|
+
agentKey: "agent-user-1-frontend",
|
|
356
357
|
},
|
|
357
358
|
});
|
|
358
359
|
expect(toolNamesFromRequest(requests[0])).toEqual(DEFAULT_LOCAL_CODING_TOOL_NAMES);
|
|
@@ -419,6 +420,7 @@ describe("CLI local runtime adapter", () => {
|
|
|
419
420
|
body: {
|
|
420
421
|
provider: "fireworks",
|
|
421
422
|
apiSource: "platform",
|
|
423
|
+
agentKey: "agent-user-1-frontend",
|
|
422
424
|
},
|
|
423
425
|
});
|
|
424
426
|
});
|
|
@@ -58,6 +58,7 @@ export type CliLocalRuntimeAdapterDeps = {
|
|
|
58
58
|
createId?: () => string;
|
|
59
59
|
fetchImpl?: typeof fetch;
|
|
60
60
|
cwd?: string;
|
|
61
|
+
useCwdAsTaskWorkspaceBase?: boolean;
|
|
61
62
|
output?: { write(chunk: string): unknown };
|
|
62
63
|
prepareTaskWorktree?: typeof prepareTaskWorktree;
|
|
63
64
|
localToolExecutors?: Record<string, (call: any) => Promise<{ content: string; metadata?: Record<string, unknown> }>>;
|
|
@@ -254,7 +255,11 @@ export function createCliLocalRuntimeAdapter(
|
|
|
254
255
|
const localToolBudgets = parseLocalToolBudgets(deps.env);
|
|
255
256
|
const localToolUsage = new Map<string, number>();
|
|
256
257
|
let activeAgentToolNames: string[] = [];
|
|
257
|
-
let workspaceSession: WorkspaceSession = createWorkspaceSession(
|
|
258
|
+
let workspaceSession: WorkspaceSession = createWorkspaceSession(
|
|
259
|
+
deps.useCwdAsTaskWorkspaceBase
|
|
260
|
+
? { defaultCwd: deps.cwd }
|
|
261
|
+
: { cwd: deps.cwd }
|
|
262
|
+
);
|
|
258
263
|
let localToolExecutors = buildLocalToolExecutors({
|
|
259
264
|
workspaceRoot: workspaceSession.workspaceRoot,
|
|
260
265
|
localToolExecutors: deps.localToolExecutors,
|
|
@@ -378,12 +383,20 @@ export function createCliLocalRuntimeAdapter(
|
|
|
378
383
|
budgets: localToolBudgets,
|
|
379
384
|
usage: localToolUsage,
|
|
380
385
|
});
|
|
381
|
-
|
|
386
|
+
const result = await executeLocalToolWithPolicy({
|
|
382
387
|
env: deps.env,
|
|
383
388
|
agentToolNames: activeAgentToolNames,
|
|
384
389
|
call,
|
|
385
390
|
executors: localToolExecutors,
|
|
386
391
|
});
|
|
392
|
+
return {
|
|
393
|
+
...result,
|
|
394
|
+
metadata: {
|
|
395
|
+
...(result.metadata ?? {}),
|
|
396
|
+
workspaceRoot: workspaceSession.workspaceRoot,
|
|
397
|
+
workspaceKind: workspaceSession.kind,
|
|
398
|
+
},
|
|
399
|
+
};
|
|
387
400
|
},
|
|
388
401
|
};
|
|
389
402
|
}
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import pino from "pino";
|
|
4
4
|
import { getIsDesktopApp } from "app/utils/env";
|
|
5
|
-
import {
|
|
5
|
+
import { fetchWithTransientReadRetry } from "app/utils/retryFetch";
|
|
6
|
+
import { API_ENDPOINTS, NOLO_CLUSTER_SERVERS, normalizeKnownServerOrigin } from "../config";
|
|
6
7
|
|
|
7
8
|
// RN 下 pino 的 browser 写法可能有兼容性问题
|
|
8
9
|
// 使用简单的 console 封装作为 fallback
|
|
@@ -23,7 +24,7 @@ export const logger = isRN ? {
|
|
|
23
24
|
// },
|
|
24
25
|
});
|
|
25
26
|
const normalizeServer = (server: string): string =>
|
|
26
|
-
server.trim().replace(/\/+$/, "");
|
|
27
|
+
normalizeKnownServerOrigin(server) ?? server.trim().replace(/\/+$/, "");
|
|
27
28
|
const isNoloClusterServer = (server: string): boolean =>
|
|
28
29
|
/^https?:\/\/(?:us\.)?nolo\.chat$/i.test(normalizeServer(server));
|
|
29
30
|
|
|
@@ -202,7 +203,7 @@ export const fetchFromServer = async (
|
|
|
202
203
|
signal?.addEventListener("abort", onExternalAbort);
|
|
203
204
|
|
|
204
205
|
try {
|
|
205
|
-
const res = await
|
|
206
|
+
const res = await fetchWithTransientReadRetry(
|
|
206
207
|
`${server}${buildReadUrl(dbKey)}`,
|
|
207
208
|
{
|
|
208
209
|
signal: controller.signal as any,
|
package/database/config.ts
CHANGED
|
@@ -8,6 +8,25 @@ export const SERVERS = {
|
|
|
8
8
|
} as const;
|
|
9
9
|
export const NOLO_CLUSTER_SERVERS = Object.values(SERVERS);
|
|
10
10
|
|
|
11
|
+
const LEGACY_SERVER_ORIGIN_MAP: Record<string, string> = {
|
|
12
|
+
"https://nolotus.com": SERVERS.MAIN,
|
|
13
|
+
"https://www.nolotus.com": SERVERS.MAIN,
|
|
14
|
+
"https://us.nolotus.com": SERVERS.US,
|
|
15
|
+
"https://www.us.nolotus.com": SERVERS.US,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const normalizeKnownServerOrigin = (server: unknown): string | null => {
|
|
19
|
+
if (typeof server !== "string" || server.trim().length === 0) return null;
|
|
20
|
+
const trimmed = server.trim();
|
|
21
|
+
let origin: string;
|
|
22
|
+
try {
|
|
23
|
+
origin = new URL(trimmed).origin;
|
|
24
|
+
} catch {
|
|
25
|
+
origin = trimmed.replace(/\/+$/, "");
|
|
26
|
+
}
|
|
27
|
+
return LEGACY_SERVER_ORIGIN_MAP[origin.toLowerCase()] ?? origin;
|
|
28
|
+
};
|
|
29
|
+
|
|
11
30
|
export const API_ENDPOINTS = {
|
|
12
31
|
DATABASE: `${API_VERSION}/db`,
|
|
13
32
|
SHARE: `${API_VERSION}/share`,
|