nolo-cli 0.1.34 → 0.1.36
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 +11 -1
- package/agent-runtime/index.ts +2 -0
- package/agent-runtime/localToolPolicy.ts +1 -1
- package/agent-runtime/localWorkspaceTools.ts +5 -2
- package/agent-runtime/platformChatProvider.ts +17 -9
- package/agent-runtime/providerResolution.ts +71 -13
- package/agentCommandSupport.ts +4 -0
- package/agentListCommands.ts +73 -11
- package/agentListHelpers.ts +56 -1
- package/agentRecordHelpers.ts +45 -5
- package/cli/agentCommandSupport.ts +4 -0
- package/cli/agentListCommands.ts +73 -11
- package/cli/agentListHelpers.ts +56 -1
- package/cli/agentRecordHelpers.ts +45 -5
- package/cli/cliSpaceHelpers.ts +39 -0
- package/cli/client/localRuntimeAdapter.ts +86 -2
- package/cli/connectorWebSocketTarget.ts +10 -3
- package/cli/dialogCommands.ts +446 -0
- package/cli/dialogInternalCommandEntries.ts +13 -0
- package/cli/globalRecordOperations.ts +258 -0
- package/cli/internalCommandEntries.ts +2 -0
- package/cli/localRuntimeAuthority.ts +182 -0
- package/cli/localRuntimeDb.ts +2 -4
- package/cli/machineWsRunDispatch.ts +42 -3
- package/cli/machineWsSession.ts +2 -0
- package/cli/runtimeDoctorCommands.ts +55 -5
- package/cliSpaceHelpers.ts +39 -0
- package/client/localRuntimeAdapter.ts +86 -2
- package/connectorWebSocketTarget.ts +10 -3
- package/database/server/cliAuthorityBrokerClient.ts +227 -0
- package/database/server/cliAuthorityBrokerServer.ts +283 -0
- package/database/server/cliAuthorityBrokerTypes.ts +43 -0
- package/database/server/cliAuthorityStoreDriver.ts +98 -0
- package/database/server/levelAuthorityStore.ts +46 -26
- package/database/tombstones.ts +52 -0
- package/database/userDataMerge.ts +95 -0
- package/dialogCommands.ts +446 -0
- package/dialogInternalCommandEntries.ts +13 -0
- package/globalRecordOperations.ts +258 -0
- package/internalCommandEntries.ts +2 -0
- package/localRuntimeAuthority.ts +182 -0
- package/localRuntimeDb.ts +2 -4
- package/machineWsRunDispatch.ts +42 -3
- package/machineWsSession.ts +2 -0
- package/package.json +18 -6
- package/runtimeDoctorCommands.ts +55 -5
- package/database/server/db.ts +0 -99
- package/database/server/ensureDbOpen.ts +0 -12
- package/database/server/memoryAuthorityStore.ts +0 -133
- package/database/server/serverStoreFactory.ts +0 -118
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getAgentInternalCommandEntries } from "./agentInternalCommandEntries";
|
|
2
|
+
import { getDialogInternalCommandEntries } from "./dialogInternalCommandEntries";
|
|
2
3
|
import type { CommandEntry } from "./cliCommandTypes";
|
|
3
4
|
import { getSystemInternalCommandEntries } from "./systemInternalCommandEntries";
|
|
4
5
|
import { getWorkflowInternalCommandEntries } from "./workflowInternalCommandEntries";
|
|
@@ -6,6 +7,7 @@ import { getWorkflowInternalCommandEntries } from "./workflowInternalCommandEntr
|
|
|
6
7
|
export function getInternalCommandEntries(renderHelpText: () => string): CommandEntry[] {
|
|
7
8
|
return [
|
|
8
9
|
...getAgentInternalCommandEntries(),
|
|
10
|
+
...getDialogInternalCommandEntries(),
|
|
9
11
|
...getWorkflowInternalCommandEntries(),
|
|
10
12
|
...getSystemInternalCommandEntries(renderHelpText),
|
|
11
13
|
];
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
createCliAuthorityBrokerClient,
|
|
6
|
+
createCliAuthorityBrokerSocketInvoker,
|
|
7
|
+
isCliAuthorityBrokerUnavailableError,
|
|
8
|
+
} from "../database/server/cliAuthorityBrokerClient";
|
|
9
|
+
import type { CliAuthorityBrokerClientOptions } from "../database/server/cliAuthorityBrokerClient";
|
|
10
|
+
import {
|
|
11
|
+
getOrCreateCliAuthorityBrokerServer,
|
|
12
|
+
} from "../database/server/cliAuthorityBrokerServer";
|
|
13
|
+
import type { CliAuthorityBrokerServerOptions } from "../database/server/cliAuthorityBrokerServer";
|
|
14
|
+
import {
|
|
15
|
+
resolveCliAuthorityBrokerEndpoint,
|
|
16
|
+
resolveCliAuthorityBrokerHealthPath,
|
|
17
|
+
resolveCliAuthorityBrokerMetadataPath,
|
|
18
|
+
} from "../database/server/cliAuthorityStoreDriver";
|
|
19
|
+
import { resolveNoloHome } from "../database/server/dbPath";
|
|
20
|
+
import type { AuthorityStore } from "../database/server/authorityStoreTypes";
|
|
21
|
+
import { createLevelAuthorityStore } from "../database/server/levelAuthorityStore";
|
|
22
|
+
import { createLegacyServerDb } from "../database/server/legacyServerDb";
|
|
23
|
+
|
|
24
|
+
type EnvLike = Record<string, string | undefined>;
|
|
25
|
+
|
|
26
|
+
type ResolveCliLocalRuntimeAuthorityOptions = {
|
|
27
|
+
env?: EnvLike;
|
|
28
|
+
homeDir?: string;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
function ensureDbParentDir(dbPath: string) {
|
|
32
|
+
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function isCliAuthorityLockError(error: unknown) {
|
|
36
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
37
|
+
return /LEVEL_LOCKED|Resource temporarily unavailable|\/LOCK|LOCK:|Database failed to open|EADDRINUSE|Failed to listen/i.test(message);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function sleep(ms: number) {
|
|
41
|
+
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveCliLocalRuntimeDbPath(options: ResolveCliLocalRuntimeAuthorityOptions = {}) {
|
|
45
|
+
return path.join(
|
|
46
|
+
resolveNoloHome({
|
|
47
|
+
env: options.env,
|
|
48
|
+
homeDir: options.homeDir,
|
|
49
|
+
}),
|
|
50
|
+
"data",
|
|
51
|
+
"leveldb"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type CliAuthorityBrokerConnectDeps = {
|
|
56
|
+
createClient: (options: CliAuthorityBrokerClientOptions) => AuthorityStore;
|
|
57
|
+
startBroker: (options: CliAuthorityBrokerServerOptions) => Promise<unknown>;
|
|
58
|
+
sleep: (ms: number) => Promise<void>;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const defaultCliAuthorityBrokerConnectDeps: CliAuthorityBrokerConnectDeps = {
|
|
62
|
+
createClient: (options) => createCliAuthorityBrokerClient(options),
|
|
63
|
+
startBroker: (options) => getOrCreateCliAuthorityBrokerServer(options),
|
|
64
|
+
sleep,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export async function connectCliAuthorityBroker(args: {
|
|
68
|
+
endpoint: string;
|
|
69
|
+
metadataPath: string;
|
|
70
|
+
healthPath: string;
|
|
71
|
+
dbPath: string;
|
|
72
|
+
deps?: Partial<CliAuthorityBrokerConnectDeps>;
|
|
73
|
+
}) {
|
|
74
|
+
const deps: CliAuthorityBrokerConnectDeps = {
|
|
75
|
+
...defaultCliAuthorityBrokerConnectDeps,
|
|
76
|
+
...args.deps,
|
|
77
|
+
};
|
|
78
|
+
const client = deps.createClient({
|
|
79
|
+
endpoint: args.endpoint,
|
|
80
|
+
invoke: createCliAuthorityBrokerSocketInvoker({ endpoint: args.endpoint }),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
async function attachToExistingBroker(attempts: number) {
|
|
84
|
+
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
|
85
|
+
await deps.sleep(100);
|
|
86
|
+
try {
|
|
87
|
+
await client.open();
|
|
88
|
+
return true;
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (!isCliAuthorityBrokerUnavailableError(error)) throw error;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
await deps.startBroker({
|
|
98
|
+
endpoint: args.endpoint,
|
|
99
|
+
metadataPath: args.metadataPath,
|
|
100
|
+
healthPath: args.healthPath,
|
|
101
|
+
createStore: () => createLevelAuthorityStore(args.dbPath),
|
|
102
|
+
});
|
|
103
|
+
await client.open();
|
|
104
|
+
return client;
|
|
105
|
+
} catch (error) {
|
|
106
|
+
if (!isCliAuthorityLockError(error)) throw error;
|
|
107
|
+
|
|
108
|
+
if (await attachToExistingBroker(5)) {
|
|
109
|
+
return client;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
let lastError: unknown = error;
|
|
113
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
114
|
+
await deps.sleep(100);
|
|
115
|
+
try {
|
|
116
|
+
await deps.startBroker({
|
|
117
|
+
endpoint: args.endpoint,
|
|
118
|
+
metadataPath: args.metadataPath,
|
|
119
|
+
healthPath: args.healthPath,
|
|
120
|
+
createStore: () => createLevelAuthorityStore(args.dbPath),
|
|
121
|
+
});
|
|
122
|
+
await client.open();
|
|
123
|
+
return client;
|
|
124
|
+
} catch (retryError) {
|
|
125
|
+
lastError = retryError;
|
|
126
|
+
if (isCliAuthorityLockError(retryError)) {
|
|
127
|
+
if (await attachToExistingBroker(2)) {
|
|
128
|
+
return client;
|
|
129
|
+
}
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
if (isCliAuthorityBrokerUnavailableError(retryError)) {
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
throw retryError;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
throw new Error(`CLI authority broker could not attach or take ownership for ${args.endpoint}`, {
|
|
139
|
+
cause: lastError,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function getDefaultCliLocalRuntimeAuthority(
|
|
146
|
+
options: ResolveCliLocalRuntimeAuthorityOptions = {}
|
|
147
|
+
) {
|
|
148
|
+
const env = options.env ?? process.env;
|
|
149
|
+
const dbPath = resolveCliLocalRuntimeDbPath({
|
|
150
|
+
env,
|
|
151
|
+
homeDir: options.homeDir,
|
|
152
|
+
});
|
|
153
|
+
ensureDbParentDir(dbPath);
|
|
154
|
+
const endpoint = resolveCliAuthorityBrokerEndpoint({
|
|
155
|
+
transport: "tcp",
|
|
156
|
+
env,
|
|
157
|
+
homeDir: options.homeDir,
|
|
158
|
+
});
|
|
159
|
+
const metadataPath = resolveCliAuthorityBrokerMetadataPath({
|
|
160
|
+
transport: "tcp",
|
|
161
|
+
env,
|
|
162
|
+
homeDir: options.homeDir,
|
|
163
|
+
});
|
|
164
|
+
const healthPath = resolveCliAuthorityBrokerHealthPath({
|
|
165
|
+
transport: "tcp",
|
|
166
|
+
env,
|
|
167
|
+
homeDir: options.homeDir,
|
|
168
|
+
});
|
|
169
|
+
return connectCliAuthorityBroker({
|
|
170
|
+
endpoint,
|
|
171
|
+
metadataPath,
|
|
172
|
+
healthPath,
|
|
173
|
+
dbPath,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export async function getDefaultCliLocalRuntimeDb(
|
|
178
|
+
options: ResolveCliLocalRuntimeAuthorityOptions = {}
|
|
179
|
+
) {
|
|
180
|
+
const authorityStore = await getDefaultCliLocalRuntimeAuthority(options);
|
|
181
|
+
return createLegacyServerDb(authorityStore);
|
|
182
|
+
}
|
package/cli/localRuntimeDb.ts
CHANGED
|
@@ -21,8 +21,6 @@ export function buildCliLocalRuntimeDbEnv(env: EnvLike = process.env) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export async function getDefaultCliLocalRuntimeDb(): Promise<CliLocalRuntimeDb> {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
await ensureServerDbOpen();
|
|
27
|
-
return serverDb as CliLocalRuntimeDb;
|
|
24
|
+
const { getDefaultCliLocalRuntimeDb: getAuthorityBackedDb } = await import("./localRuntimeAuthority");
|
|
25
|
+
return getAuthorityBackedDb();
|
|
28
26
|
}
|
|
@@ -96,6 +96,37 @@ function logConnectorRun(message: string, fields: Record<string, unknown> = {})
|
|
|
96
96
|
console.error(`[nolo-connector] ${message} ${JSON.stringify(fields)}`);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
function summarizeEndpoint(value: unknown) {
|
|
100
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
101
|
+
try {
|
|
102
|
+
const url = new URL(value);
|
|
103
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
104
|
+
} catch {
|
|
105
|
+
return "invalid-url";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function summarizeAgentConfigForLogs(agentConfig: any) {
|
|
110
|
+
const runtimeBinding = isRecord(agentConfig?.runtimeBinding) ? agentConfig.runtimeBinding : {};
|
|
111
|
+
return {
|
|
112
|
+
apiSource: agentConfig?.apiSource ?? null,
|
|
113
|
+
provider: agentConfig?.provider ?? null,
|
|
114
|
+
cliProvider: agentConfig?.cliProvider ?? null,
|
|
115
|
+
model: agentConfig?.model ?? null,
|
|
116
|
+
customProviderEndpoint: summarizeEndpoint(agentConfig?.customProviderUrl) ?? null,
|
|
117
|
+
useServerProxy: agentConfig?.useServerProxy ?? null,
|
|
118
|
+
skipLocalRuntimeEnsure: agentConfig?.skipLocalRuntimeEnsure ?? null,
|
|
119
|
+
hasApiKey: typeof agentConfig?.apiKey === "string" && agentConfig.apiKey.length > 0,
|
|
120
|
+
apiKeyHeader: agentConfig?.apiKeyHeader ?? null,
|
|
121
|
+
hasApiKeyFromAgentKey:
|
|
122
|
+
typeof agentConfig?.apiKeyFromAgentKey === "string" &&
|
|
123
|
+
agentConfig.apiKeyFromAgentKey.length > 0,
|
|
124
|
+
runtimeBindingKind: runtimeBinding.kind ?? null,
|
|
125
|
+
runtimeMachineId: runtimeBinding.machineId ?? null,
|
|
126
|
+
connectorSurface: runtimeBinding.connectorSurface ?? runtimeBinding.surface ?? null,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
99
130
|
function isRecord(value: unknown): value is Record<string, any> {
|
|
100
131
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
101
132
|
}
|
|
@@ -197,9 +228,6 @@ function mergeToolNames(...values: unknown[]) {
|
|
|
197
228
|
function localRuntimeEnvFromPolicy(runtimeEnv: EnvLike, policy?: ConnectorRuntimePolicy): EnvLike {
|
|
198
229
|
return {
|
|
199
230
|
...runtimeEnv,
|
|
200
|
-
...(policy?.shell?.enabled && policy.shell.mode === "worktree"
|
|
201
|
-
? { NOLO_LOCAL_SHELL_MODE: "worktree" }
|
|
202
|
-
: {}),
|
|
203
231
|
...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
|
|
204
232
|
};
|
|
205
233
|
}
|
|
@@ -572,6 +600,15 @@ export async function handleConnectorRunMessage(
|
|
|
572
600
|
if (!requestsLocalWorkspaceRuntime(runtimePolicy) && !isMachineBoundLocalCustomProvider(agentConfig)) {
|
|
573
601
|
throw new Error("Connector can only execute non-CLI agents when runtimeToolPolicySnapshot requests a local workspace runtime.");
|
|
574
602
|
}
|
|
603
|
+
logConnectorRun("agent.run.local-runtime.start", {
|
|
604
|
+
requestId: parsed.requestId,
|
|
605
|
+
agentKey: parsed.payload?.agentKey,
|
|
606
|
+
cwd,
|
|
607
|
+
timeout,
|
|
608
|
+
machineBoundLocalCustomProvider: isMachineBoundLocalCustomProvider(agentConfig),
|
|
609
|
+
runtimePolicy: Boolean(runtimePolicy),
|
|
610
|
+
...summarizeAgentConfigForLogs(agentConfig),
|
|
611
|
+
});
|
|
575
612
|
baseSha = await readConnectorGitHead(cwd);
|
|
576
613
|
const result = await runConnectorLocalRuntimeAgent({
|
|
577
614
|
parsed,
|
|
@@ -610,6 +647,8 @@ export async function handleConnectorRunMessage(
|
|
|
610
647
|
logConnectorRun("agent.run.error", {
|
|
611
648
|
requestId: parsed.requestId,
|
|
612
649
|
agentKey: parsed.payload?.agentKey,
|
|
650
|
+
...summarizeAgentConfigForLogs(agentConfig),
|
|
651
|
+
errorName: error instanceof Error ? error.name : null,
|
|
613
652
|
error: error instanceof Error ? error.message : String(error),
|
|
614
653
|
});
|
|
615
654
|
send(JSON.stringify({
|
package/cli/machineWsSession.ts
CHANGED
|
@@ -119,6 +119,7 @@ export async function runMachineWsSession(options: {
|
|
|
119
119
|
resolveConnectorWebSocketTarget: (args: {
|
|
120
120
|
serverUrl: string;
|
|
121
121
|
machineId: string;
|
|
122
|
+
connectorSurface?: string;
|
|
122
123
|
headers: Record<string, string>;
|
|
123
124
|
}) => Promise<string>;
|
|
124
125
|
connectWebSocket: (url: string, options: ConnectorWebSocketOptions) => Promise<void>;
|
|
@@ -149,6 +150,7 @@ export async function runMachineWsSession(options: {
|
|
|
149
150
|
const wsTarget = await options.resolveConnectorWebSocketTarget({
|
|
150
151
|
serverUrl: options.serverUrl,
|
|
151
152
|
machineId: options.machine.machineId,
|
|
153
|
+
connectorSurface: "cli",
|
|
152
154
|
headers: { Authorization: `Bearer ${options.authToken}` },
|
|
153
155
|
});
|
|
154
156
|
websocketPromise = options.connectWebSocket(wsTarget, {
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { resolveAgentRuntimeDecision } from "../agent-runtime/runtimeDecision";
|
|
2
|
+
import {
|
|
3
|
+
resolveCliAuthorityBrokerEndpoint,
|
|
4
|
+
resolveCliAuthorityBrokerHealthPath,
|
|
5
|
+
resolveCliAuthorityBrokerMetadataPath,
|
|
6
|
+
resolveCliAuthorityStoreDriver,
|
|
7
|
+
} from "../database/server/cliAuthorityStoreDriver";
|
|
2
8
|
import {
|
|
3
9
|
type AgentCommandDeps,
|
|
4
10
|
type LocalRuntimeProbeResult,
|
|
@@ -39,23 +45,57 @@ function detectProviderLabel(env: EnvLike) {
|
|
|
39
45
|
async function defaultLocalRuntimeProbe(env: EnvLike): Promise<LocalRuntimeProbeResult> {
|
|
40
46
|
const { resolveCliLocalRuntimeDbPath } = await import("./localRuntimeDb");
|
|
41
47
|
const dbPath = resolveCliLocalRuntimeDbPath({ env });
|
|
48
|
+
const authorityDriver = resolveCliAuthorityStoreDriver({ env });
|
|
49
|
+
const authorityEndpoint = resolveCliAuthorityBrokerEndpoint({ transport: "tcp", env });
|
|
50
|
+
const authorityMetadataPath = resolveCliAuthorityBrokerMetadataPath({ transport: "tcp", env });
|
|
51
|
+
const authorityHealthPath = resolveCliAuthorityBrokerHealthPath({ transport: "tcp", env });
|
|
42
52
|
const agentKey = readLocalAgentKey(env);
|
|
43
53
|
try {
|
|
44
54
|
const { getDefaultCliLocalRuntimeDb } = await import("./localRuntimeDb");
|
|
45
|
-
const db = await getDefaultCliLocalRuntimeDb();
|
|
55
|
+
const db = await getDefaultCliLocalRuntimeDb({ env });
|
|
46
56
|
if (!agentKey) {
|
|
47
|
-
return {
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
dbPath,
|
|
60
|
+
authorityDriver,
|
|
61
|
+
authorityEndpoint,
|
|
62
|
+
authorityMetadataPath,
|
|
63
|
+
authorityHealthPath,
|
|
64
|
+
agentFound: false,
|
|
65
|
+
};
|
|
48
66
|
}
|
|
49
67
|
try {
|
|
50
|
-
await db.get(agentKey);
|
|
51
|
-
return {
|
|
68
|
+
const record = await db.get(agentKey);
|
|
69
|
+
return {
|
|
70
|
+
ok: true,
|
|
71
|
+
dbPath,
|
|
72
|
+
authorityDriver,
|
|
73
|
+
authorityEndpoint,
|
|
74
|
+
authorityMetadataPath,
|
|
75
|
+
authorityHealthPath,
|
|
76
|
+
agentFound: record != null,
|
|
77
|
+
agentKey,
|
|
78
|
+
};
|
|
52
79
|
} catch {
|
|
53
|
-
return {
|
|
80
|
+
return {
|
|
81
|
+
ok: true,
|
|
82
|
+
dbPath,
|
|
83
|
+
authorityDriver,
|
|
84
|
+
authorityEndpoint,
|
|
85
|
+
authorityMetadataPath,
|
|
86
|
+
authorityHealthPath,
|
|
87
|
+
agentFound: false,
|
|
88
|
+
agentKey,
|
|
89
|
+
};
|
|
54
90
|
}
|
|
55
91
|
} catch (error) {
|
|
56
92
|
return {
|
|
57
93
|
ok: false,
|
|
58
94
|
dbPath,
|
|
95
|
+
authorityDriver,
|
|
96
|
+
authorityEndpoint,
|
|
97
|
+
authorityMetadataPath,
|
|
98
|
+
authorityHealthPath,
|
|
59
99
|
agentFound: false,
|
|
60
100
|
...(agentKey ? { agentKey } : {}),
|
|
61
101
|
error: error instanceof Error ? error.message : String(error),
|
|
@@ -101,6 +141,16 @@ export async function runDoctorRuntimeCommand(
|
|
|
101
141
|
output.write(`Reason: ${decision.reason}\n`);
|
|
102
142
|
output.write(`LevelDB: ${localProbe.ok ? "ok" : "failed"}\n`);
|
|
103
143
|
output.write(`DB path: ${localProbe.dbPath}\n`);
|
|
144
|
+
output.write(`Authority driver: ${localProbe.authorityDriver ?? "unknown"}\n`);
|
|
145
|
+
if (localProbe.authorityEndpoint) {
|
|
146
|
+
output.write(`Authority endpoint: ${localProbe.authorityEndpoint}\n`);
|
|
147
|
+
}
|
|
148
|
+
if (localProbe.authorityMetadataPath) {
|
|
149
|
+
output.write(`Authority metadata: ${localProbe.authorityMetadataPath}\n`);
|
|
150
|
+
}
|
|
151
|
+
if (localProbe.authorityHealthPath) {
|
|
152
|
+
output.write(`Authority health: ${localProbe.authorityHealthPath}\n`);
|
|
153
|
+
}
|
|
104
154
|
if (!localProbe.ok && localProbe.error) {
|
|
105
155
|
output.write(`DB error: ${localProbe.error}\n`);
|
|
106
156
|
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function normalizeSpaceIdInput(rawInput: string) {
|
|
2
|
+
const raw = rawInput.trim();
|
|
3
|
+
if (!raw) return "";
|
|
4
|
+
|
|
5
|
+
if (raw.startsWith("http://") || raw.startsWith("https://")) {
|
|
6
|
+
const url = new URL(raw);
|
|
7
|
+
const match = url.pathname.match(/^\/space\/([^/?#]+)/);
|
|
8
|
+
if (match?.[1]) return decodeURIComponent(match[1]);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return raw.startsWith("space-") ? raw.slice("space-".length) : raw;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function buildSpaceLookup(rawInput: string) {
|
|
15
|
+
const spaceId = normalizeSpaceIdInput(rawInput);
|
|
16
|
+
return {
|
|
17
|
+
spaceId,
|
|
18
|
+
spaceKey: spaceId.startsWith("space-") ? spaceId : `space-${spaceId}`,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function getSpaceContentKeys(spaceRecord: any): Set<string> {
|
|
23
|
+
const keys = new Set<string>();
|
|
24
|
+
const contents = spaceRecord?.contents;
|
|
25
|
+
if (!contents || typeof contents !== "object") return keys;
|
|
26
|
+
|
|
27
|
+
for (const [entryKey, value] of Object.entries(contents)) {
|
|
28
|
+
if (typeof entryKey === "string" && entryKey.trim()) {
|
|
29
|
+
keys.add(entryKey.trim());
|
|
30
|
+
}
|
|
31
|
+
if (value && typeof value === "object") {
|
|
32
|
+
const contentKey = (value as any).contentKey;
|
|
33
|
+
if (typeof contentKey === "string" && contentKey.trim()) {
|
|
34
|
+
keys.add(contentKey.trim());
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return keys;
|
|
39
|
+
}
|
|
@@ -221,6 +221,29 @@ function buildRequestTimeoutSignal(timeoutMs?: number) {
|
|
|
221
221
|
return controller.signal;
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
function summarizeEndpoint(value: unknown) {
|
|
225
|
+
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
226
|
+
try {
|
|
227
|
+
const url = new URL(value);
|
|
228
|
+
return `${url.protocol}//${url.host}${url.pathname}`;
|
|
229
|
+
} catch {
|
|
230
|
+
return "invalid-url";
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseJsonObject(raw: string) {
|
|
235
|
+
try {
|
|
236
|
+
const parsed = JSON.parse(raw);
|
|
237
|
+
return parsed && typeof parsed === "object" ? parsed : {};
|
|
238
|
+
} catch {
|
|
239
|
+
return {};
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function logLocalRuntimeDiagnostic(event: string, fields: Record<string, unknown>) {
|
|
244
|
+
console.error(`[nolo-local-runtime] ${event} ${JSON.stringify(fields)}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
224
247
|
function buildOpenAiTools(args: { toolNames?: string[]; env: EnvLike }) {
|
|
225
248
|
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
226
249
|
return [
|
|
@@ -476,6 +499,20 @@ export function createCliLocalRuntimeAdapter(
|
|
|
476
499
|
agentConfig,
|
|
477
500
|
env: deps.env,
|
|
478
501
|
});
|
|
502
|
+
logLocalRuntimeDiagnostic("provider.selected", {
|
|
503
|
+
agentKey: agentConfig.key,
|
|
504
|
+
transport: "platform-proxy",
|
|
505
|
+
apiSource: agentConfig.apiSource ?? null,
|
|
506
|
+
provider: providerConfig.provider,
|
|
507
|
+
model: providerConfig.model,
|
|
508
|
+
endpoint: summarizeEndpoint(providerConfig.endpoint) ?? null,
|
|
509
|
+
proxyServer: summarizeEndpoint(providerConfig.serverUrl) ?? null,
|
|
510
|
+
hasAuthToken: Boolean(providerConfig.authToken),
|
|
511
|
+
hasApiKey: Boolean(providerConfig.apiKey),
|
|
512
|
+
apiKeyHeader: providerConfig.apiKeyHeader ?? null,
|
|
513
|
+
useServerProxy: agentConfig.useServerProxy ?? null,
|
|
514
|
+
customProviderEndpoint: summarizeEndpoint(agentConfig.customProviderUrl) ?? null,
|
|
515
|
+
});
|
|
479
516
|
return {
|
|
480
517
|
model: providerConfig.model,
|
|
481
518
|
complete: async (messages, options) => {
|
|
@@ -492,6 +529,16 @@ export function createCliLocalRuntimeAdapter(
|
|
|
492
529
|
tools,
|
|
493
530
|
});
|
|
494
531
|
const signal = buildRequestTimeoutSignal(options?.timeoutMs);
|
|
532
|
+
logLocalRuntimeDiagnostic("provider.request.start", {
|
|
533
|
+
agentKey: agentConfig.key,
|
|
534
|
+
transport: "platform-proxy",
|
|
535
|
+
requestUrl: summarizeEndpoint(request.url) ?? null,
|
|
536
|
+
endpoint: summarizeEndpoint(providerConfig.endpoint) ?? null,
|
|
537
|
+
model: providerConfig.model,
|
|
538
|
+
messageCount: messages.length,
|
|
539
|
+
toolCount: tools.length,
|
|
540
|
+
timeoutMs: options?.timeoutMs ?? null,
|
|
541
|
+
});
|
|
495
542
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
496
543
|
...request.init,
|
|
497
544
|
...(signal ? { signal } : {}),
|
|
@@ -499,7 +546,15 @@ export function createCliLocalRuntimeAdapter(
|
|
|
499
546
|
sleep: deps.sleep,
|
|
500
547
|
loopbackRequest,
|
|
501
548
|
});
|
|
502
|
-
const
|
|
549
|
+
const raw = await res.text().catch(() => "");
|
|
550
|
+
logLocalRuntimeDiagnostic("provider.request.result", {
|
|
551
|
+
agentKey: agentConfig.key,
|
|
552
|
+
transport: "platform-proxy",
|
|
553
|
+
status: res.status,
|
|
554
|
+
ok: res.ok,
|
|
555
|
+
responseBytes: raw.length,
|
|
556
|
+
});
|
|
557
|
+
const data = parsePlatformChatCompletionData(raw);
|
|
503
558
|
if (!res.ok) {
|
|
504
559
|
throw new Error(`platform provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
505
560
|
}
|
|
@@ -516,6 +571,18 @@ export function createCliLocalRuntimeAdapter(
|
|
|
516
571
|
agentConfig,
|
|
517
572
|
env: deps.env,
|
|
518
573
|
});
|
|
574
|
+
logLocalRuntimeDiagnostic("provider.selected", {
|
|
575
|
+
agentKey: agentConfig.key,
|
|
576
|
+
transport: "direct-openai-compatible",
|
|
577
|
+
apiSource: agentConfig.apiSource ?? null,
|
|
578
|
+
provider: providerConfig.provider,
|
|
579
|
+
model: providerConfig.model,
|
|
580
|
+
endpoint: summarizeEndpoint(providerConfig.endpoint) ?? null,
|
|
581
|
+
hasApiKey: Boolean(providerConfig.apiKey),
|
|
582
|
+
apiKeyHeader: providerConfig.apiKeyHeader ?? null,
|
|
583
|
+
useServerProxy: agentConfig.useServerProxy ?? null,
|
|
584
|
+
customProviderEndpoint: summarizeEndpoint(agentConfig.customProviderUrl) ?? null,
|
|
585
|
+
});
|
|
519
586
|
return {
|
|
520
587
|
model: providerConfig.model,
|
|
521
588
|
complete: async (messages, options) => {
|
|
@@ -532,6 +599,15 @@ export function createCliLocalRuntimeAdapter(
|
|
|
532
599
|
tools,
|
|
533
600
|
});
|
|
534
601
|
const signal = buildRequestTimeoutSignal(options?.timeoutMs);
|
|
602
|
+
logLocalRuntimeDiagnostic("provider.request.start", {
|
|
603
|
+
agentKey: agentConfig.key,
|
|
604
|
+
transport: "direct-openai-compatible",
|
|
605
|
+
requestUrl: summarizeEndpoint(request.url) ?? null,
|
|
606
|
+
model: providerConfig.model,
|
|
607
|
+
messageCount: messages.length,
|
|
608
|
+
toolCount: tools.length,
|
|
609
|
+
timeoutMs: options?.timeoutMs ?? null,
|
|
610
|
+
});
|
|
535
611
|
const res = await fetchWithTransientRetry(fetchImpl, request.url, {
|
|
536
612
|
...request.init,
|
|
537
613
|
...(signal ? { signal } : {}),
|
|
@@ -539,7 +615,15 @@ export function createCliLocalRuntimeAdapter(
|
|
|
539
615
|
sleep: deps.sleep,
|
|
540
616
|
loopbackRequest,
|
|
541
617
|
});
|
|
542
|
-
const
|
|
618
|
+
const raw = await res.text().catch(() => "");
|
|
619
|
+
logLocalRuntimeDiagnostic("provider.request.result", {
|
|
620
|
+
agentKey: agentConfig.key,
|
|
621
|
+
transport: "direct-openai-compatible",
|
|
622
|
+
status: res.status,
|
|
623
|
+
ok: res.ok,
|
|
624
|
+
responseBytes: raw.length,
|
|
625
|
+
});
|
|
626
|
+
const data = parseJsonObject(raw);
|
|
543
627
|
if (!res.ok) {
|
|
544
628
|
throw new Error(`local provider failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
545
629
|
}
|
|
@@ -49,14 +49,21 @@ function parseRetryAfterMs(response: Response, bodyRetryAfterMs?: unknown) {
|
|
|
49
49
|
export async function resolveConnectorWebSocketTarget(input: {
|
|
50
50
|
serverUrl: string;
|
|
51
51
|
machineId: string;
|
|
52
|
+
connectorSurface?: string;
|
|
52
53
|
headers: Record<string, string>;
|
|
53
54
|
fetchImpl?: typeof fetch;
|
|
54
55
|
}) {
|
|
55
56
|
const directWsUrl = new URL(input.serverUrl);
|
|
56
57
|
directWsUrl.protocol = directWsUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
57
|
-
directWsUrl.pathname = "/api/connector/ws";
|
|
58
|
-
directWsUrl.search = "";
|
|
59
|
-
directWsUrl.searchParams.set("machineId", input.machineId);
|
|
58
|
+
directWsUrl.pathname = "/api/connector/ws";
|
|
59
|
+
directWsUrl.search = "";
|
|
60
|
+
directWsUrl.searchParams.set("machineId", input.machineId);
|
|
61
|
+
if (input.connectorSurface?.trim()) {
|
|
62
|
+
directWsUrl.searchParams.set(
|
|
63
|
+
"connectorSurface",
|
|
64
|
+
input.connectorSurface.trim().toLowerCase()
|
|
65
|
+
);
|
|
66
|
+
}
|
|
60
67
|
|
|
61
68
|
const probeUrl = new URL(input.serverUrl);
|
|
62
69
|
probeUrl.pathname = "/api/connector/ws";
|