nolo-cli 0.1.32 → 0.1.34
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 -10
- package/agent-runtime/agentRecordConfig.ts +10 -0
- package/agent-runtime/hostAdapter.ts +10 -1
- package/agent-runtime/index.ts +9 -20
- package/agent-runtime/localLoop.ts +49 -2
- package/agent-runtime/localToolPolicy.ts +76 -34
- package/agent-runtime/localWorkspaceTools.ts +30 -333
- package/agent-runtime/openAiCompatibleProvider.ts +7 -3
- package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
- package/agent-runtime/platformChatProvider.ts +47 -73
- package/agent-runtime/providerResolution.ts +244 -0
- package/agent-runtime/runtimeToolPolicy.ts +77 -29
- package/agent-runtime/shellCommandPolicy.ts +120 -0
- package/agentAliases.ts +14 -83
- package/agentCommandSupport.ts +3 -6
- package/agentInternalCommandEntries.ts +4 -13
- package/agentListCommands.ts +18 -27
- package/agentMachineCommands.ts +40 -336
- package/agentPullCommand.ts +30 -55
- package/agentRecordCommands.ts +84 -1
- package/agentRecordHelpers.ts +102 -186
- package/agentRunCommand.ts +438 -107
- package/agentRuntimeLocal.ts +2 -9
- package/ai/agent/cliExecutor.ts +19 -323
- package/ai/tools/toolNameAliases.ts +70 -0
- package/authCommands.ts +2 -36
- package/cli/agentAliases.ts +14 -83
- package/cli/agentCommandSupport.ts +3 -6
- package/cli/agentInternalCommandEntries.ts +4 -13
- package/cli/agentListCommands.ts +18 -27
- package/cli/agentMachineCommands.ts +40 -336
- package/cli/agentPullCommand.ts +30 -55
- package/cli/agentRecordCommands.ts +84 -1
- package/cli/agentRecordHelpers.ts +102 -186
- package/cli/agentRunCommand.ts +438 -107
- package/cli/agentRuntimeLocal.ts +2 -9
- package/cli/authCommands.ts +2 -36
- package/cli/cliCommandDispatch.ts +2 -2
- package/cli/cliEnvHelpers.ts +34 -107
- package/cli/client/agentRun.ts +854 -57
- package/cli/client/localRuntimeAdapter.ts +59 -159
- package/cli/client/streamingOutput.ts +3 -1
- package/cli/client/taskRunPrompt.ts +24 -7
- package/cli/commandRegistry.ts +4 -5
- package/cli/connectorRunArtifact.ts +10 -34
- package/cli/connectorWebSocketTarget.ts +6 -10
- package/cli/internalCommandEntries.ts +0 -8
- package/cli/localRuntimeDb.ts +2 -39
- package/cli/machineCommands.ts +189 -190
- package/cli/machineDaemonCommands.ts +165 -9
- package/cli/machineHeartbeatCommands.ts +2 -1
- package/cli/machineStatusCommands.ts +13 -5
- package/cli/machineWatchCommands.ts +1 -1
- package/cli/machineWsRunDispatch.ts +30 -44
- package/cli/machineWsSession.ts +0 -2
- package/cli/offlineMarxistsAgentCommand.ts +314 -0
- package/cli/runtimeDoctorCommands.ts +72 -15
- package/cli/scriptCommandEntries.ts +45 -5
- package/cli/tableCommands.ts +133 -33
- package/cli/taskRunCommand.ts +211 -16
- package/cli/tui/session.ts +372 -43
- package/cli/workflowInternalCommandEntries.ts +0 -20
- package/cliCommandDispatch.ts +2 -2
- package/cliEnvHelpers.ts +34 -107
- package/client/agentConfigResolver.test.ts +2 -0
- package/client/agentRun.test.ts +12 -208
- package/client/agentRun.ts +854 -57
- package/client/localProviderResolver.test.ts +26 -2
- package/client/localRuntimeAdapter.test.ts +296 -171
- package/client/localRuntimeAdapter.ts +59 -159
- package/client/localToolPolicy.test.ts +4 -28
- package/client/streamingOutput.ts +3 -1
- package/client/taskRunPrompt.ts +24 -7
- package/commandRegistry.ts +4 -5
- package/connectorRunArtifact.ts +10 -34
- package/connectorWebSocketTarget.ts +6 -10
- package/database/server/serverStoreFactory.ts +2 -8
- package/internalCommandEntries.ts +0 -8
- package/localRuntimeDb.ts +2 -39
- package/machineCommands.ts +189 -190
- package/machineDaemonCommands.ts +165 -9
- package/machineHeartbeatCommands.ts +2 -1
- package/machineStatusCommands.ts +13 -5
- package/machineWatchCommands.ts +1 -1
- package/machineWsRunDispatch.ts +30 -44
- package/machineWsSession.ts +0 -2
- package/offlineMarxistsAgentCommand.ts +314 -0
- package/package.json +52 -188
- package/runtimeDoctorCommands.ts +72 -15
- package/scriptCommandEntries.ts +45 -5
- package/tableCommands.ts +133 -33
- package/taskRunCommand.ts +211 -16
- package/tui/session.ts +372 -43
- package/workflowInternalCommandEntries.ts +0 -20
- package/agent-runtime/dialogStatus.ts +0 -139
- package/agent-runtime/hostRuntimeConfig.ts +0 -86
- package/agentCreateCustomCommands.ts +0 -278
- package/agentDialogsCommands.ts +0 -227
- package/agentDoctorCommands.ts +0 -203
- package/agentRunLocalWorkspace.ts +0 -182
- package/agentRunParsing.ts +0 -298
- package/agentSuperviseCommands.ts +0 -8
- package/agentUnpublishCommands.ts +0 -113
- package/ai/agent/agentRunDialogLineage.ts +0 -56
- package/ai/policy/types.ts +0 -64
- package/ai/skills/skillSummaryMarker.ts +0 -26
- package/app/types.ts +0 -358
- package/build.ts +0 -16
- package/buildPublish.ts +0 -138
- package/buildPublishArtifactAssembly.ts +0 -724
- package/buildPublishImportAnalysis.ts +0 -242
- package/chat/messages/fetchMessages.ts +0 -84
- package/chat/messages/types.ts +0 -185
- package/cli/agentCreateCustomCommands.ts +0 -278
- package/cli/agentDialogsCommands.ts +0 -227
- package/cli/agentDoctorCommands.ts +0 -203
- package/cli/agentRunLocalWorkspace.ts +0 -182
- package/cli/agentRunParsing.ts +0 -298
- package/cli/agentSuperviseCommands.ts +0 -8
- package/cli/agentUnpublishCommands.ts +0 -113
- package/cli/buildPublish.ts +0 -138
- package/cli/buildPublishArtifactAssembly.ts +0 -724
- package/cli/buildPublishImportAnalysis.ts +0 -242
- package/cli/cliIoTypes.ts +0 -3
- package/cli/cliRuntimeDoctorReport.ts +0 -193
- package/cli/client/agentRunInput.ts +0 -10
- package/cli/client/agentRunLocalExecution.ts +0 -124
- package/cli/client/agentRunLocalExecutor.ts +0 -179
- package/cli/client/agentRunRuntimeSelection.ts +0 -145
- package/cli/client/agentRunServerTransport.ts +0 -397
- package/cli/client/agentRunTaskRunRecorder.ts +0 -176
- package/cli/customCodingAgentFlow.ts +0 -208
- package/cli/customCodingAgentRecord.ts +0 -179
- package/cli/dialogDataAccess.ts +0 -186
- package/cli/dialogInternalCommandEntries.ts +0 -11
- package/cli/dialogReadCommands.ts +0 -180
- package/cli/dialogReadHelpers.ts +0 -226
- package/cli/dialogStatusCommands.ts +0 -123
- package/cli/docInternalCommandEntries.ts +0 -20
- package/cli/hybridReadHelpers.ts +0 -109
- package/cli/machineCommandPrompts.ts +0 -79
- package/cli/machineCommandResolution.ts +0 -43
- package/cli/noloReadBridgePrompt.ts +0 -16
- package/cli/pageCreateCommands.ts +0 -219
- package/cli/pageDeleteCommands.ts +0 -167
- package/cli/pageReadCommands.ts +0 -151
- package/cli/pageUpdateCommands.ts +0 -249
- package/cli/pageWriteHelpers.ts +0 -209
- package/cli/publishPackage.ts +0 -36
- package/cli/runtimeInternalCommandEntries.ts +0 -15
- package/cli/runtimePassthroughCommands.ts +0 -22
- package/cli/scriptPassthroughHelpers.ts +0 -28
- package/cli/secretScanHelpers.ts +0 -31
- package/cli/spaceAttachCommands.ts +0 -134
- package/cli/spaceCategoryCommands.ts +0 -263
- package/cli/spaceCommands.ts +0 -295
- package/cli/spaceDeleteCommands.ts +0 -312
- package/cli/spaceInternalCommandEntries.ts +0 -21
- package/cli/spaceMutationHelpers.ts +0 -135
- package/cli/spaceReadCommands.ts +0 -323
- package/cli/tableDataOutput.ts +0 -41
- package/cli/tableDataSafety.ts +0 -26
- package/cli/tableMetaCommands.ts +0 -307
- package/cli/tableMutationCommands.ts +0 -266
- package/cli/tableQueryParsing.ts +0 -90
- package/cli/tableQueryRequest.ts +0 -53
- package/cli/taskRunActions.ts +0 -122
- package/cli/taskRunParsing.ts +0 -86
- package/cli/taskRunPromptShared.ts +0 -55
- package/cli/tui/tuiCatalog.ts +0 -22
- package/cli/tui/tuiCommandInterpreter.ts +0 -253
- package/cli/tui/tuiPresentation.ts +0 -146
- package/cliIoTypes.ts +0 -3
- package/cliRuntimeDoctorReport.ts +0 -193
- package/client/agentRunInput.ts +0 -10
- package/client/agentRunLocalExecution.test.ts +0 -123
- package/client/agentRunLocalExecution.ts +0 -124
- package/client/agentRunLocalExecutor.ts +0 -179
- package/client/agentRunRuntimeSelection.test.ts +0 -159
- package/client/agentRunRuntimeSelection.ts +0 -145
- package/client/agentRunServerTransport.ts +0 -397
- package/client/agentRunTaskRunRecorder.ts +0 -176
- package/connector-experimental/daemonLauncher.ts +0 -223
- package/create/space/spaceKeys.ts +0 -65
- package/create/types.ts +0 -21
- package/customCodingAgentFlow.ts +0 -208
- package/customCodingAgentRecord.ts +0 -179
- package/database/keys.ts +0 -765
- package/database/tombstones.ts +0 -52
- package/database/utils/ulid.ts +0 -1
- package/dialogDataAccess.ts +0 -186
- package/dialogInternalCommandEntries.ts +0 -11
- package/dialogReadCommands.ts +0 -180
- package/dialogReadHelpers.ts +0 -226
- package/dialogStatusCommands.ts +0 -123
- package/docInternalCommandEntries.ts +0 -20
- package/hybridReadHelpers.ts +0 -109
- package/machineCommandPrompts.ts +0 -79
- package/machineCommandResolution.ts +0 -43
- package/noloReadBridgePrompt.ts +0 -16
- package/pageCreateCommands.ts +0 -219
- package/pageDeleteCommands.ts +0 -167
- package/pageReadCommands.ts +0 -151
- package/pageUpdateCommands.ts +0 -249
- package/pageWriteHelpers.ts +0 -209
- package/publishPackage.ts +0 -36
- package/runtimeInternalCommandEntries.ts +0 -15
- package/runtimePassthroughCommands.ts +0 -22
- package/scriptPassthroughHelpers.ts +0 -28
- package/secretScanHelpers.ts +0 -31
- package/share/helpers.ts +0 -302
- package/share/keys.ts +0 -243
- package/share/types.ts +0 -100
- package/spaceAttachCommands.ts +0 -134
- package/spaceCategoryCommands.ts +0 -263
- package/spaceCommands.ts +0 -295
- package/spaceDeleteCommands.ts +0 -312
- package/spaceInternalCommandEntries.ts +0 -21
- package/spaceMutationHelpers.ts +0 -135
- package/spaceReadCommands.ts +0 -323
- package/tableDataOutput.ts +0 -41
- package/tableDataSafety.ts +0 -26
- package/tableMetaCommands.ts +0 -307
- package/tableMutationCommands.ts +0 -266
- package/tableQueryParsing.ts +0 -90
- package/tableQueryRequest.ts +0 -53
- package/taskRunActions.ts +0 -122
- package/taskRunParsing.ts +0 -86
- package/taskRunPromptShared.ts +0 -55
- package/tui/tuiCatalog.ts +0 -22
- package/tui/tuiCommandInterpreter.test.ts +0 -169
- package/tui/tuiCommandInterpreter.ts +0 -253
- package/tui/tuiPresentation.test.ts +0 -154
- package/tui/tuiPresentation.ts +0 -146
package/cli/machineCommands.ts
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
import type { MachineHeartbeat } from "../connector-experimental/protocol";
|
|
2
|
-
import { NOLO_CLUSTER_SERVERS, normalizeKnownServerOrigin } from "../database/config";
|
|
3
2
|
import { detectMachineInfo } from "../connector-experimental/machineInfo";
|
|
3
|
+
import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
|
|
4
4
|
import {
|
|
5
5
|
type HeartbeatLoopOptions,
|
|
6
6
|
runHeartbeatLoop as defaultRunHeartbeatLoop,
|
|
7
7
|
} from "../connector-experimental/heartbeatLoop";
|
|
8
|
+
import {
|
|
9
|
+
buildMachinePermissionPromptBlock,
|
|
10
|
+
resolveMachineRunPermissionPolicy,
|
|
11
|
+
} from "../ai/agent/machineRunPermissions";
|
|
8
12
|
import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
|
|
9
13
|
import {
|
|
10
14
|
checkConnectorWorkspaceLinks,
|
|
@@ -21,24 +25,14 @@ import {
|
|
|
21
25
|
runMachineWsSession,
|
|
22
26
|
type ConnectorWebSocketOptions,
|
|
23
27
|
} from "./machineWsSession";
|
|
24
|
-
import {
|
|
25
|
-
buildConnectorCliPrompt,
|
|
26
|
-
} from "./machineCommandPrompts";
|
|
27
|
-
import {
|
|
28
|
-
hasFlag,
|
|
29
|
-
readOption,
|
|
30
|
-
resolveConnectAuthToken,
|
|
31
|
-
resolveConnectServerUrl,
|
|
32
|
-
resolveConnectorReconnectDelayMs,
|
|
33
|
-
writeAuthMissing,
|
|
34
|
-
type EnvLike,
|
|
35
|
-
type OutputLike,
|
|
36
|
-
} from "./machineCommandResolution";
|
|
37
28
|
export {
|
|
38
29
|
formatMachineStatus,
|
|
39
30
|
runMachineStatusCommand,
|
|
40
31
|
type MachineSummary,
|
|
41
32
|
} from "./machineStatusCommands";
|
|
33
|
+
|
|
34
|
+
type EnvLike = Record<string, string | undefined>;
|
|
35
|
+
type OutputLike = { write(chunk: string): unknown };
|
|
42
36
|
|
|
43
37
|
type MachineCommandDeps = {
|
|
44
38
|
env?: EnvLike;
|
|
@@ -61,8 +55,50 @@ type MachineCommandDeps = {
|
|
|
61
55
|
}) => { pid?: number };
|
|
62
56
|
};
|
|
63
57
|
|
|
58
|
+
function resolveServerUrl(env: EnvLike) {
|
|
59
|
+
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function resolveAuthToken(env: EnvLike) {
|
|
63
|
+
return env.AUTH_TOKEN || env.AUTH || "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeAuthMissing(output: OutputLike) {
|
|
67
|
+
output.write(
|
|
68
|
+
"[nolo] Machine commands require an auth token. Run `nolo login` or set AUTH_TOKEN.\n"
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function hasFlag(args: string[], flag: string) {
|
|
73
|
+
return args.includes(flag);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readOption(args: string[], flag: string) {
|
|
77
|
+
for (let i = 0; i < args.length; i++) {
|
|
78
|
+
const arg = args[i];
|
|
79
|
+
if (arg === flag) {
|
|
80
|
+
const next = args[i + 1];
|
|
81
|
+
if (typeof next === "string" && !next.startsWith("-")) return next;
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
if (arg.startsWith(flag + "=")) {
|
|
85
|
+
return arg.slice(flag.length + 1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function resolveConnectServerUrl(args: string[], env: EnvLike) {
|
|
92
|
+
const fromFlag = readOption(args, "--server-url") || readOption(args, "--server");
|
|
93
|
+
const raw = fromFlag || env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL;
|
|
94
|
+
return String(raw).replace(/\/+$|\s+$/g, "").replace(/\/+$/g, "").replace(/\s+$/g, "");
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function resolveConnectAuthToken(args: string[], env: EnvLike) {
|
|
98
|
+
return readOption(args, "--machine-key") || readOption(args, "--token") || resolveAuthToken(env);
|
|
99
|
+
}
|
|
100
|
+
|
|
64
101
|
const defaultSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
|
|
65
|
-
const MACHINE_TOKEN_PREFIX = "sk_machine_";
|
|
66
102
|
|
|
67
103
|
async function sleepWithAbort(ms: number, sleep: (ms: number) => Promise<void>, signal?: AbortSignal) {
|
|
68
104
|
if (!signal) {
|
|
@@ -83,80 +119,6 @@ function resolveConnectorReconnectDelayMs(env: EnvLike) {
|
|
|
83
119
|
return Number.isFinite(raw) && raw >= 0 ? raw : 5_000;
|
|
84
120
|
}
|
|
85
121
|
|
|
86
|
-
function isMachineToken(token: string) {
|
|
87
|
-
return token.trim().startsWith(MACHINE_TOKEN_PREFIX);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function resolveClusterConnectServerUrls(serverUrl: string, authToken: string) {
|
|
91
|
-
const normalizedPrimary = normalizeKnownServerOrigin(serverUrl) ?? serverUrl.trim().replace(/\/+$/, "");
|
|
92
|
-
if (isMachineToken(authToken)) return [normalizedPrimary];
|
|
93
|
-
const normalizedCluster = NOLO_CLUSTER_SERVERS
|
|
94
|
-
.map((baseUrl) => normalizeKnownServerOrigin(baseUrl) ?? baseUrl)
|
|
95
|
-
.filter((value, index, list) => value && list.indexOf(value) === index);
|
|
96
|
-
if (!normalizedCluster.includes(normalizedPrimary)) return [normalizedPrimary];
|
|
97
|
-
return [normalizedPrimary, ...normalizedCluster.filter((baseUrl) => baseUrl !== normalizedPrimary)];
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function linkAbortSignal(signal: AbortSignal | undefined, controller: AbortController) {
|
|
101
|
-
if (!signal) return;
|
|
102
|
-
if (signal.aborted) {
|
|
103
|
-
controller.abort();
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
signal.addEventListener("abort", () => controller.abort(), { once: true });
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async function sendMachineHeartbeat(args: {
|
|
110
|
-
serverUrl: string;
|
|
111
|
-
authToken: string;
|
|
112
|
-
fetchImpl: typeof fetch;
|
|
113
|
-
machine: MachineHeartbeat;
|
|
114
|
-
}) {
|
|
115
|
-
const res = await args.fetchImpl(`${args.serverUrl}/api/machines/heartbeat`, {
|
|
116
|
-
method: "POST",
|
|
117
|
-
headers: {
|
|
118
|
-
Authorization: `Bearer ${args.authToken}`,
|
|
119
|
-
"Content-Type": "application/json",
|
|
120
|
-
},
|
|
121
|
-
body: JSON.stringify(args.machine),
|
|
122
|
-
});
|
|
123
|
-
|
|
124
|
-
if (!res.ok) {
|
|
125
|
-
const text = await res.text().catch(() => "");
|
|
126
|
-
throw new Error(`HTTP ${res.status}\n${text}`);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
async function sendHeartbeatAcrossServers(args: {
|
|
131
|
-
serverUrls: string[];
|
|
132
|
-
authToken: string;
|
|
133
|
-
fetchImpl: typeof fetch;
|
|
134
|
-
machine: MachineHeartbeat;
|
|
135
|
-
output: OutputLike;
|
|
136
|
-
}) {
|
|
137
|
-
let primaryError: Error | null = null;
|
|
138
|
-
for (const [index, serverUrl] of args.serverUrls.entries()) {
|
|
139
|
-
try {
|
|
140
|
-
await sendMachineHeartbeat({
|
|
141
|
-
serverUrl,
|
|
142
|
-
authToken: args.authToken,
|
|
143
|
-
fetchImpl: args.fetchImpl,
|
|
144
|
-
machine: args.machine,
|
|
145
|
-
});
|
|
146
|
-
} catch (error) {
|
|
147
|
-
const resolvedError = error instanceof Error ? error : new Error(String(error));
|
|
148
|
-
if (index === 0) {
|
|
149
|
-
primaryError = resolvedError;
|
|
150
|
-
} else {
|
|
151
|
-
args.output.write(
|
|
152
|
-
`[nolo] secondary connector registration failed for ${serverUrl}: ${resolvedError.message}\n`
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (primaryError) throw primaryError;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
122
|
async function defaultExecuteCli(
|
|
161
123
|
provider: string,
|
|
162
124
|
prompt: string,
|
|
@@ -169,11 +131,75 @@ async function defaultExecuteCli(
|
|
|
169
131
|
function detectLaunchableMachineInfo() {
|
|
170
132
|
return detectMachineInfo({ probeLaunchable: true });
|
|
171
133
|
}
|
|
134
|
+
|
|
135
|
+
function inferTaskRunRole(agentKey: string, agentConfig: any) {
|
|
136
|
+
const haystack = [
|
|
137
|
+
agentKey,
|
|
138
|
+
agentConfig?.name,
|
|
139
|
+
agentConfig?.prompt,
|
|
140
|
+
].filter(Boolean).join("\n").toLowerCase();
|
|
141
|
+
if (haystack.includes("frontend") || haystack.includes("前端")) return "frontend";
|
|
142
|
+
if (haystack.includes("fullstack") || haystack.includes("全栈")) return "fullstack";
|
|
143
|
+
if (haystack.includes("review") || haystack.includes("审查")) return "reviewer";
|
|
144
|
+
if (haystack.includes("project-manager") || haystack.includes("项目经理")) return "pm";
|
|
145
|
+
return "codex";
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function buildTaskRunBridgePrompt(args: {
|
|
149
|
+
agentKey: string;
|
|
150
|
+
agentConfig: any;
|
|
151
|
+
runtimeContext: any;
|
|
152
|
+
}) {
|
|
153
|
+
const taskRun = args.runtimeContext?.taskRun;
|
|
154
|
+
const rowDbKey = typeof taskRun?.rowDbKey === "string" ? taskRun.rowDbKey.trim() : "";
|
|
155
|
+
if (!rowDbKey) return "";
|
|
156
|
+
const workItemId = typeof taskRun?.workItemId === "string" ? taskRun.workItemId.trim() : "";
|
|
157
|
+
const role = inferTaskRunRole(args.agentKey, args.agentConfig);
|
|
158
|
+
return [
|
|
159
|
+
"--- Nolo task-run tool bridge ---",
|
|
160
|
+
"This CLI runtime does not receive server-side function tools directly.",
|
|
161
|
+
"Use the local task-run bridge command for durable task state writes. Do not edit meta.taskRun with table or database commands.",
|
|
162
|
+
"Run commands from the repository root. The runner already provides server URL and auth in the environment.",
|
|
163
|
+
"Release boundary: after review passes, AI/reviewer/Codex may advance alpha for verification. Do not merge, push, or release main/release unless the human owner explicitly authorizes it in the current task context.",
|
|
164
|
+
"Cost boundary: read taskRun.control from context. Prefer low-cost/free-window models for routine work, and reserve Codex for supervision, quality gates, complex unblocking, and main/release decisions.",
|
|
165
|
+
"Handoff context: start from read-context, then inspect existing refs such as workItems.dialogIds, workItems.outcome, review.dialogId, activityRefs, agentRuns.dialogId/resultSummary, and artifactRefs. Use dialog read/search for exact evidence; do not infer completion from handoff text alone.",
|
|
166
|
+
`Read context: bun packages/cli/index.ts task-run read-context --row-dbkey ${JSON.stringify(rowDbKey)}`,
|
|
167
|
+
...(workItemId
|
|
168
|
+
? [
|
|
169
|
+
`Claim work item: bun packages/cli/index.ts task-run claim-work-item --row-dbkey ${JSON.stringify(rowDbKey)} --work-item-id ${JSON.stringify(workItemId)} --agent-key ${JSON.stringify(args.agentKey)} --role ${role}`,
|
|
170
|
+
`Set blocker: bun packages/cli/index.ts task-run set-blocker --row-dbkey ${JSON.stringify(rowDbKey)} --work-item-id ${JSON.stringify(workItemId)} --layer tool --code <CODE> --message <MESSAGE>`,
|
|
171
|
+
`Clear resolved blocker: bun packages/cli/index.ts task-run clear-blocker --row-dbkey ${JSON.stringify(rowDbKey)} --work-item-id ${JSON.stringify(workItemId)} --blocker-id <BLOCKER_ID>`,
|
|
172
|
+
`Submit outcome: bun packages/cli/index.ts task-run submit-outcome --row-dbkey ${JSON.stringify(rowDbKey)} --work-item-id ${JSON.stringify(workItemId)} --summary <SUMMARY>`,
|
|
173
|
+
`Request review: bun packages/cli/index.ts task-run request-review --row-dbkey ${JSON.stringify(rowDbKey)} --work-item-id ${JSON.stringify(workItemId)} --reviewer-agent-key <AGENT_KEY> --message <MESSAGE>`,
|
|
174
|
+
`Record review: bun packages/cli/index.ts task-run record-review --row-dbkey ${JSON.stringify(rowDbKey)} --work-item-id ${JSON.stringify(workItemId)} --review-status <passed|needs_changes|blocked> --reviewer-agent-key ${JSON.stringify(args.agentKey)} --findings <FINDINGS>`,
|
|
175
|
+
`Close work item: bun packages/cli/index.ts task-run close-work-item --row-dbkey ${JSON.stringify(rowDbKey)} --work-item-id ${JSON.stringify(workItemId)} --summary <SUMMARY>`,
|
|
176
|
+
]
|
|
177
|
+
: []),
|
|
178
|
+
"If a bridge command fails, report the exact command and error.",
|
|
179
|
+
].join("\n");
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildConnectorCliPrompt(agentConfig: any, userInput: string, bridgeArgs?: {
|
|
183
|
+
agentKey: string;
|
|
184
|
+
runtimeContext: any;
|
|
185
|
+
}, permissionPolicy?: ReturnType<typeof resolveMachineRunPermissionPolicy>) {
|
|
186
|
+
const policy = permissionPolicy ?? resolveMachineRunPermissionPolicy(agentConfig);
|
|
187
|
+
return [
|
|
188
|
+
typeof agentConfig?.prompt === "string" ? agentConfig.prompt.trim() : "",
|
|
189
|
+
bridgeArgs ? buildTaskRunBridgePrompt({
|
|
190
|
+
agentKey: bridgeArgs.agentKey,
|
|
191
|
+
agentConfig,
|
|
192
|
+
runtimeContext: bridgeArgs.runtimeContext,
|
|
193
|
+
}) : "",
|
|
194
|
+
buildMachinePermissionPromptBlock(policy),
|
|
195
|
+
`--- User task ---\n${userInput}`,
|
|
196
|
+
].filter(Boolean).join("\n\n");
|
|
197
|
+
}
|
|
172
198
|
|
|
173
199
|
export async function runMachineConnectCommand(
|
|
174
|
-
args: string[],
|
|
175
|
-
deps: MachineCommandDeps = {}
|
|
176
|
-
) {
|
|
200
|
+
args: string[],
|
|
201
|
+
deps: MachineCommandDeps = {}
|
|
202
|
+
) {
|
|
177
203
|
const env = deps.env ?? process.env;
|
|
178
204
|
const output = deps.output ?? process.stdout;
|
|
179
205
|
const authToken = resolveConnectAuthToken(args, env);
|
|
@@ -181,31 +207,29 @@ export async function runMachineConnectCommand(
|
|
|
181
207
|
writeAuthMissing(output);
|
|
182
208
|
return 1;
|
|
183
209
|
}
|
|
184
|
-
|
|
210
|
+
|
|
185
211
|
const serverUrl = resolveConnectServerUrl(args, env);
|
|
186
|
-
const serverUrls = resolveClusterConnectServerUrls(serverUrl, authToken);
|
|
187
212
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
188
213
|
const machine = (deps.machineInfo ?? detectLaunchableMachineInfo)();
|
|
189
214
|
const sendHeartbeat = async () => {
|
|
190
|
-
await
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
215
|
+
const res = await fetchImpl(`${serverUrl}/api/machines/heartbeat`, {
|
|
216
|
+
method: "POST",
|
|
217
|
+
headers: {
|
|
218
|
+
Authorization: `Bearer ${authToken}`,
|
|
219
|
+
"Content-Type": "application/json",
|
|
220
|
+
},
|
|
221
|
+
body: JSON.stringify(machine),
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (!res.ok) {
|
|
225
|
+
const text = await res.text().catch(() => "");
|
|
226
|
+
throw new Error(`HTTP ${res.status}\n${text}`);
|
|
227
|
+
}
|
|
228
|
+
};
|
|
198
229
|
|
|
199
230
|
if (hasFlag(args, "--daemon") || hasFlag(args, "--background")) {
|
|
200
231
|
return runMachineDaemonCommand({
|
|
201
|
-
env
|
|
202
|
-
...env,
|
|
203
|
-
NOLO_SERVER: serverUrl,
|
|
204
|
-
NOLO_SERVER_URL: serverUrl,
|
|
205
|
-
BASE_URL: serverUrl,
|
|
206
|
-
AUTH_TOKEN: authToken,
|
|
207
|
-
NOLO_MACHINE_API_KEY: authToken,
|
|
208
|
-
},
|
|
232
|
+
env,
|
|
209
233
|
output,
|
|
210
234
|
cliEntrypointPath: deps.cliEntrypointPath,
|
|
211
235
|
validateWorkspaceLinks: deps.validateWorkspaceLinks,
|
|
@@ -221,88 +245,63 @@ export async function runMachineConnectCommand(
|
|
|
221
245
|
const maxAttempts = deps.maxConnectorAttempts ?? Infinity;
|
|
222
246
|
const sleep = deps.sleep ?? defaultSleep;
|
|
223
247
|
const reconnectDelayMs = resolveConnectorReconnectDelayMs(env);
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
return { serverUrl: targetServerUrl, exitCode: 0 };
|
|
228
|
-
}
|
|
229
|
-
const runtimeServerUrl =
|
|
230
|
-
env.NOLO_CONNECT_RUNTIME_SERVER_URL ||
|
|
231
|
-
env.NOLO_RUNTIME_SERVER_URL ||
|
|
232
|
-
targetServerUrl;
|
|
233
|
-
const sessionResult = await runMachineWsSession({
|
|
234
|
-
env,
|
|
235
|
-
output,
|
|
236
|
-
signal,
|
|
237
|
-
machine,
|
|
238
|
-
sendHeartbeat: () =>
|
|
239
|
-
sendMachineHeartbeat({
|
|
240
|
-
serverUrl: targetServerUrl,
|
|
241
|
-
authToken,
|
|
242
|
-
fetchImpl,
|
|
243
|
-
machine,
|
|
244
|
-
}),
|
|
245
|
-
runHeartbeatLoop: deps.runHeartbeatLoop ?? defaultRunHeartbeatLoop,
|
|
246
|
-
connectWebSocket: deps.connectWebSocket ?? defaultConnectWebSocket,
|
|
247
|
-
serverUrl: targetServerUrl,
|
|
248
|
-
authToken,
|
|
249
|
-
resolveConnectorWebSocketTarget: (options) =>
|
|
250
|
-
resolveConnectorWebSocketTarget({
|
|
251
|
-
...options,
|
|
252
|
-
fetchImpl,
|
|
253
|
-
}),
|
|
254
|
-
onMessage: (message, send) =>
|
|
255
|
-
handleConnectorRunMessage(
|
|
256
|
-
message,
|
|
257
|
-
send,
|
|
258
|
-
deps.executeCli ?? defaultExecuteCli,
|
|
259
|
-
{
|
|
260
|
-
...env,
|
|
261
|
-
NOLO_SERVER: runtimeServerUrl,
|
|
262
|
-
NOLO_SERVER_URL: runtimeServerUrl,
|
|
263
|
-
BASE_URL: runtimeServerUrl,
|
|
264
|
-
AUTH_TOKEN: authToken,
|
|
265
|
-
NOLO_MACHINE_API_KEY: authToken,
|
|
266
|
-
},
|
|
267
|
-
fetchImpl,
|
|
268
|
-
{
|
|
269
|
-
buildConnectorCliPrompt,
|
|
270
|
-
}
|
|
271
|
-
),
|
|
272
|
-
});
|
|
273
|
-
const exitCode = sessionResult.exitCode;
|
|
274
|
-
if (exitCode === 2) return { serverUrl: targetServerUrl, exitCode: 2 };
|
|
275
|
-
if (signal?.aborted) {
|
|
276
|
-
return { serverUrl: targetServerUrl, exitCode: 0 };
|
|
277
|
-
}
|
|
278
|
-
if (attempt >= maxAttempts) return { serverUrl: targetServerUrl, exitCode };
|
|
279
|
-
const retryDelayMs = sessionResult.retryAfterMs ?? reconnectDelayMs;
|
|
280
|
-
const reconnectContext =
|
|
281
|
-
sessionResult.reconnectReason === "core_draining" ? " during core draining" : "";
|
|
282
|
-
output.write(
|
|
283
|
-
`[nolo] Connector websocket disconnected${reconnectContext}. Reconnecting in ${retryDelayMs}ms.\n`
|
|
284
|
-
);
|
|
285
|
-
await sleepWithAbort(retryDelayMs, sleep, signal);
|
|
248
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
249
|
+
if (deps.signal?.aborted) {
|
|
250
|
+
return 0;
|
|
286
251
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
252
|
+
const runtimeServerUrl =
|
|
253
|
+
env.NOLO_CONNECT_RUNTIME_SERVER_URL ||
|
|
254
|
+
env.NOLO_RUNTIME_SERVER_URL ||
|
|
255
|
+
serverUrl;
|
|
256
|
+
const sessionResult = await runMachineWsSession({
|
|
257
|
+
env,
|
|
258
|
+
output,
|
|
259
|
+
signal: deps.signal,
|
|
260
|
+
machine,
|
|
261
|
+
sendHeartbeat,
|
|
262
|
+
runHeartbeatLoop: deps.runHeartbeatLoop ?? defaultRunHeartbeatLoop,
|
|
263
|
+
connectWebSocket: deps.connectWebSocket ?? defaultConnectWebSocket,
|
|
264
|
+
serverUrl,
|
|
265
|
+
authToken,
|
|
266
|
+
resolveConnectorWebSocketTarget: (options) =>
|
|
267
|
+
resolveConnectorWebSocketTarget({
|
|
268
|
+
...options,
|
|
269
|
+
fetchImpl,
|
|
270
|
+
}),
|
|
271
|
+
onMessage: (message, send) =>
|
|
272
|
+
handleConnectorRunMessage(
|
|
273
|
+
message,
|
|
274
|
+
send,
|
|
275
|
+
deps.executeCli ?? defaultExecuteCli,
|
|
276
|
+
{
|
|
277
|
+
...env,
|
|
278
|
+
NOLO_SERVER: runtimeServerUrl,
|
|
279
|
+
NOLO_SERVER_URL: runtimeServerUrl,
|
|
280
|
+
BASE_URL: runtimeServerUrl,
|
|
281
|
+
AUTH_TOKEN: authToken,
|
|
282
|
+
NOLO_MACHINE_API_KEY: authToken,
|
|
283
|
+
},
|
|
284
|
+
fetchImpl,
|
|
285
|
+
{
|
|
286
|
+
buildConnectorCliPrompt,
|
|
287
|
+
}
|
|
288
|
+
),
|
|
289
|
+
});
|
|
290
|
+
const exitCode = sessionResult.exitCode;
|
|
291
|
+
if (exitCode === 2) return 1;
|
|
292
|
+
if (deps.signal?.aborted) {
|
|
293
|
+
return 0;
|
|
299
294
|
}
|
|
300
|
-
return
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
295
|
+
if (attempt >= maxAttempts) return exitCode;
|
|
296
|
+
const retryDelayMs = sessionResult.retryAfterMs ?? reconnectDelayMs;
|
|
297
|
+
const reconnectContext =
|
|
298
|
+
sessionResult.reconnectReason === "core_draining" ? " during core draining" : "";
|
|
299
|
+
output.write(
|
|
300
|
+
`[nolo] Connector websocket disconnected${reconnectContext}. Reconnecting in ${retryDelayMs}ms.\n`
|
|
301
|
+
);
|
|
302
|
+
await sleepWithAbort(retryDelayMs, sleep, deps.signal);
|
|
303
|
+
}
|
|
304
|
+
return 0;
|
|
306
305
|
}
|
|
307
306
|
|
|
308
307
|
if (hasFlag(args, "--watch")) {
|
|
@@ -1,11 +1,153 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "../connector-experimental/daemonLauncher";
|
|
6
|
-
import type { OutputLike } from "./cliIoTypes";
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, openSync, readFileSync, realpathSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
7
5
|
|
|
8
6
|
type EnvLike = Record<string, string | undefined>;
|
|
7
|
+
type OutputLike = { write(chunk: string): unknown };
|
|
8
|
+
|
|
9
|
+
type PackageJson = {
|
|
10
|
+
name?: string;
|
|
11
|
+
workspaces?: string[] | { packages?: string[] };
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const CONNECTOR_RUNTIME_WORKSPACE_PACKAGES = new Set([
|
|
15
|
+
"connector-experimental",
|
|
16
|
+
"ai",
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
function readJsonFile<T>(path: string): T | null {
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
22
|
+
} catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getWorkspacePatterns(packageJson: PackageJson) {
|
|
28
|
+
if (Array.isArray(packageJson.workspaces)) return packageJson.workspaces;
|
|
29
|
+
return packageJson.workspaces?.packages ?? [];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function normalizePath(path: string) {
|
|
33
|
+
const resolved = resolve(path);
|
|
34
|
+
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function isInsideOrSame(path: string, parent: string) {
|
|
38
|
+
const relativePath = relative(parent, path);
|
|
39
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !isAbsolute(relativePath));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function findWorkspaceRoot(startPath: string) {
|
|
43
|
+
let current = resolve(startPath);
|
|
44
|
+
while (true) {
|
|
45
|
+
const packageJsonPath = join(current, "package.json");
|
|
46
|
+
if (existsSync(packageJsonPath)) {
|
|
47
|
+
const packageJson = readJsonFile<PackageJson>(packageJsonPath);
|
|
48
|
+
if (packageJson && getWorkspacePatterns(packageJson).length > 0) {
|
|
49
|
+
return current;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const parent = dirname(current);
|
|
53
|
+
if (parent === current) return null;
|
|
54
|
+
current = parent;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validateConnectorWorkspaceLinks(cwd: string) {
|
|
59
|
+
const repoRoot = findWorkspaceRoot(cwd);
|
|
60
|
+
if (!repoRoot) return [];
|
|
61
|
+
|
|
62
|
+
const rootPackageJson = readJsonFile<PackageJson>(join(repoRoot, "package.json"));
|
|
63
|
+
if (!rootPackageJson) return [];
|
|
64
|
+
|
|
65
|
+
const normalizedRepoRoot = normalizePath(realpathSync(repoRoot));
|
|
66
|
+
const errors: string[] = [];
|
|
67
|
+
|
|
68
|
+
for (const pattern of getWorkspacePatterns(rootPackageJson)) {
|
|
69
|
+
if (!pattern.endsWith("/*")) continue;
|
|
70
|
+
const workspaceParent = join(repoRoot, pattern.slice(0, -2));
|
|
71
|
+
if (!existsSync(workspaceParent)) continue;
|
|
72
|
+
|
|
73
|
+
for (const entry of Array.from(new Bun.Glob("*/package.json").scanSync({ cwd: workspaceParent }))) {
|
|
74
|
+
const packageDir = join(workspaceParent, dirname(entry));
|
|
75
|
+
const packageJson = readJsonFile<PackageJson>(join(workspaceParent, entry));
|
|
76
|
+
if (!packageJson?.name || !CONNECTOR_RUNTIME_WORKSPACE_PACKAGES.has(packageJson.name)) {
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const linkPath = join(repoRoot, "node_modules", ...packageJson.name.split("/"));
|
|
81
|
+
if (!existsSync(linkPath)) {
|
|
82
|
+
errors.push(`${linkPath} is missing. Run bun install in ${repoRoot}.`);
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const actualPath = normalizePath(realpathSync(linkPath));
|
|
87
|
+
const expectedPath = normalizePath(realpathSync(packageDir));
|
|
88
|
+
if (!isInsideOrSame(actualPath, normalizedRepoRoot)) {
|
|
89
|
+
errors.push(
|
|
90
|
+
`${linkPath} points outside this checkout: ${actualPath}. Run bun install in ${repoRoot}.`,
|
|
91
|
+
);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
if (actualPath !== expectedPath) {
|
|
95
|
+
errors.push(
|
|
96
|
+
`${linkPath} points at ${actualPath}, expected ${expectedPath}. Run bun install in ${repoRoot}.`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return errors;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export async function checkConnectorWorkspaceLinks(
|
|
106
|
+
cwd: string,
|
|
107
|
+
output: OutputLike,
|
|
108
|
+
validateWorkspaceLinks: (cwd: string) => string[] | Promise<string[]>,
|
|
109
|
+
) {
|
|
110
|
+
const errors = await validateWorkspaceLinks(cwd);
|
|
111
|
+
if (errors.length === 0) return true;
|
|
112
|
+
output.write(
|
|
113
|
+
[
|
|
114
|
+
"[nolo] Connector workspace package links are unsafe.",
|
|
115
|
+
"The connector would import workspace packages from a different checkout, so machine capabilities and runtime behavior may be stale.",
|
|
116
|
+
...errors,
|
|
117
|
+
"",
|
|
118
|
+
].join("\n"),
|
|
119
|
+
);
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function resolveDaemonLogPath(env: EnvLike) {
|
|
124
|
+
return env.NOLO_CONNECT_LOG || join(homedir(), ".nolo", "logs", "connector.log");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function buildDaemonCommand(cliEntrypointPath: string | undefined) {
|
|
128
|
+
return [process.execPath, cliEntrypointPath || import.meta.path, "connect", "--ws"];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function defaultSpawnDaemon(args: {
|
|
132
|
+
cmd: string[];
|
|
133
|
+
cwd: string;
|
|
134
|
+
env: EnvLike;
|
|
135
|
+
logPath: string;
|
|
136
|
+
}) {
|
|
137
|
+
mkdirSync(dirname(args.logPath), { recursive: true });
|
|
138
|
+
const out = openSync(args.logPath, "a");
|
|
139
|
+
const env = Object.fromEntries(
|
|
140
|
+
Object.entries(args.env).filter((entry): entry is [string, string] => typeof entry[1] === "string")
|
|
141
|
+
);
|
|
142
|
+
const proc = spawn(args.cmd[0], args.cmd.slice(1), {
|
|
143
|
+
cwd: args.cwd,
|
|
144
|
+
env,
|
|
145
|
+
detached: true,
|
|
146
|
+
stdio: ["ignore", out, out],
|
|
147
|
+
});
|
|
148
|
+
proc.unref();
|
|
149
|
+
return { pid: proc.pid };
|
|
150
|
+
}
|
|
9
151
|
|
|
10
152
|
export type MachineDaemonCommandDeps = {
|
|
11
153
|
env?: EnvLike;
|
|
@@ -23,7 +165,21 @@ export type MachineDaemonCommandDeps = {
|
|
|
23
165
|
export async function runMachineDaemonCommand(
|
|
24
166
|
deps: MachineDaemonCommandDeps = {}
|
|
25
167
|
) {
|
|
26
|
-
|
|
27
|
-
|
|
168
|
+
const env = deps.env ?? process.env;
|
|
169
|
+
const output = deps.output ?? process.stdout;
|
|
170
|
+
const validateWorkspaceLinks = deps.validateWorkspaceLinks ?? validateConnectorWorkspaceLinks;
|
|
28
171
|
|
|
29
|
-
|
|
172
|
+
if (!(await checkConnectorWorkspaceLinks(process.cwd(), output, validateWorkspaceLinks))) {
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const logPath = resolveDaemonLogPath(env);
|
|
177
|
+
const result = (deps.spawnDaemon ?? defaultSpawnDaemon)({
|
|
178
|
+
cmd: buildDaemonCommand(deps.cliEntrypointPath),
|
|
179
|
+
cwd: process.cwd(),
|
|
180
|
+
env,
|
|
181
|
+
logPath,
|
|
182
|
+
});
|
|
183
|
+
output.write(`Connector daemon started${result.pid ? ` pid=${result.pid}` : ""}. Log: ${logPath}\n`);
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { resolveAuthToken, resolveServerUrl } from "./cliEnvHelpers";
|
|
1
|
+
import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
|
|
3
2
|
|
|
4
3
|
type EnvLike = Record<string, string | undefined>;
|
|
4
|
+
type OutputLike = { write(chunk: string): unknown };
|
|
5
5
|
|
|
6
6
|
export type MachineSummary = {
|
|
7
7
|
machineId: string;
|
|
@@ -21,6 +21,14 @@ export type MachineStatusCommandDeps = {
|
|
|
21
21
|
fetchImpl?: typeof fetch;
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
function resolveServerUrl(env: EnvLike) {
|
|
25
|
+
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function resolveAuthToken(env: EnvLike) {
|
|
29
|
+
return env.AUTH_TOKEN || env.AUTH || "";
|
|
30
|
+
}
|
|
31
|
+
|
|
24
32
|
function writeAuthMissing(output: OutputLike) {
|
|
25
33
|
output.write(
|
|
26
34
|
"[nolo] Machine commands require an auth token. Run `nolo login` or set AUTH_TOKEN.\n"
|
|
@@ -44,18 +52,18 @@ export function formatMachineStatus(machines: MachineSummary[]) {
|
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
export async function runMachineStatusCommand(
|
|
47
|
-
|
|
55
|
+
_args: string[],
|
|
48
56
|
deps: MachineStatusCommandDeps = {}
|
|
49
57
|
) {
|
|
50
58
|
const env = deps.env ?? process.env;
|
|
51
59
|
const output = deps.output ?? process.stdout;
|
|
52
|
-
const authToken = resolveAuthToken(
|
|
60
|
+
const authToken = resolveAuthToken(env);
|
|
53
61
|
if (!authToken) {
|
|
54
62
|
writeAuthMissing(output);
|
|
55
63
|
return 1;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
|
-
const serverUrl = resolveServerUrl(
|
|
66
|
+
const serverUrl = resolveServerUrl(env);
|
|
59
67
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
60
68
|
let res: Response;
|
|
61
69
|
try {
|