nolo-cli 0.1.44 → 0.1.45
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 +5 -3
- package/agentAliases.ts +4 -74
- package/agentMachineCommands.ts +33 -10
- package/agentNameResolver.ts +164 -0
- package/agentRecordCommands.ts +1 -3
- package/agentRecordHelpers.ts +23 -2
- package/agentRunCommand.ts +26 -14
- package/cli/agentAliases.ts +4 -74
- package/cli/agentMachineCommands.ts +33 -10
- package/cli/agentNameResolver.ts +164 -0
- package/cli/agentRecordCommands.ts +1 -3
- package/cli/agentRecordHelpers.ts +23 -2
- package/cli/agentRunCommand.ts +26 -14
- package/cli/client/agentRun.ts +1 -61
- package/cli/client/localRuntimeAdapter.ts +2 -8
- package/cli/commandRegistry.ts +3 -3
- package/cli/machineCommands.ts +6 -1
- package/cli/offlineMarxistsAgentCommand.ts +4 -5
- package/client/agentRun.test.ts +13 -11
- package/client/agentRun.ts +1 -61
- package/client/localRuntimeAdapter.test.ts +22 -17
- package/client/localRuntimeAdapter.ts +2 -8
- package/commandRegistry.ts +3 -3
- package/machineCommands.ts +6 -1
- package/offlineMarxistsAgentCommand.ts +4 -5
- package/package.json +4 -2
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { MachineHeartbeat } from "../connector-experimental/protocol";
|
|
2
2
|
import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
|
|
3
|
-
import {
|
|
3
|
+
import { resolveAgentInput } from "./agentNameResolver";
|
|
4
4
|
import {
|
|
5
5
|
type AgentCommandDeps,
|
|
6
6
|
type LocalCliExecutor,
|
|
@@ -173,10 +173,10 @@ export async function runAgentBindCurrentCommand(
|
|
|
173
173
|
) {
|
|
174
174
|
const env = deps.env ?? process.env;
|
|
175
175
|
const output = deps.output ?? process.stdout;
|
|
176
|
-
const
|
|
177
|
-
if (!
|
|
178
|
-
output.write("Usage: nolo agent bind-current <
|
|
179
|
-
return
|
|
176
|
+
const agentInput = args[0]?.trim();
|
|
177
|
+
if (!agentInput || agentInput === "--help" || agentInput === "-h") {
|
|
178
|
+
output.write("Usage: nolo agent bind-current <agent>\n");
|
|
179
|
+
return agentInput ? 0 : 1;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
const authToken = resolveAuthToken(env);
|
|
@@ -197,6 +197,15 @@ export async function runAgentBindCurrentCommand(
|
|
|
197
197
|
const machine = await (deps.machineInfo ?? detectLaunchableMachineInfo)();
|
|
198
198
|
|
|
199
199
|
try {
|
|
200
|
+
const { agentKey } = await resolveAgentInput({
|
|
201
|
+
agentInput,
|
|
202
|
+
authToken,
|
|
203
|
+
db: deps.db,
|
|
204
|
+
env,
|
|
205
|
+
fallbackFetchImpl,
|
|
206
|
+
fetchImpl,
|
|
207
|
+
output,
|
|
208
|
+
});
|
|
200
209
|
await heartbeatCurrentMachine({ authToken, fetchImpl, machine, serverUrl });
|
|
201
210
|
const existing = await readAgentRecord({ agentKey, authToken, fallbackFetchImpl, fetchImpl, serverUrl });
|
|
202
211
|
const updated = {
|
|
@@ -228,7 +237,7 @@ export async function runAgentBindCurrentCommand(
|
|
|
228
237
|
return 1;
|
|
229
238
|
}
|
|
230
239
|
|
|
231
|
-
output.write(`Bound agent ${
|
|
240
|
+
output.write(`Bound agent ${agentInput} to this machine: ${machine.name} (${machine.machineId})\n`);
|
|
232
241
|
return 0;
|
|
233
242
|
}
|
|
234
243
|
|
|
@@ -243,8 +252,6 @@ export async function runAgentSmokeCurrentCommand(
|
|
|
243
252
|
output.write("Usage: nolo agent smoke-current <agent> --msg \"hello\"\n");
|
|
244
253
|
return agentInput ? 0 : 1;
|
|
245
254
|
}
|
|
246
|
-
const agentKey = resolveCliAgentKeyInput(agentInput);
|
|
247
|
-
|
|
248
255
|
const authToken = resolveAuthToken(env);
|
|
249
256
|
if (!authToken) {
|
|
250
257
|
output.write("[nolo] agent smoke-current requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
@@ -264,6 +271,15 @@ export async function runAgentSmokeCurrentCommand(
|
|
|
264
271
|
const sentMessages: string[] = [];
|
|
265
272
|
|
|
266
273
|
try {
|
|
274
|
+
const { agentKey } = await resolveAgentInput({
|
|
275
|
+
agentInput,
|
|
276
|
+
authToken,
|
|
277
|
+
db: deps.db,
|
|
278
|
+
env,
|
|
279
|
+
fallbackFetchImpl,
|
|
280
|
+
fetchImpl,
|
|
281
|
+
output,
|
|
282
|
+
});
|
|
267
283
|
await heartbeatCurrentMachine({ authToken, fetchImpl, machine, serverUrl });
|
|
268
284
|
const existing = await readAgentRecord({ agentKey, authToken, fallbackFetchImpl, fetchImpl, serverUrl });
|
|
269
285
|
assertSmokeCompatible(existing, machine);
|
|
@@ -358,8 +374,6 @@ export async function runAgentRuntimeDoctorCommand(
|
|
|
358
374
|
output.write("Usage: nolo agent runtime-doctor <agentKey>\n");
|
|
359
375
|
return agentInput ? 0 : 1;
|
|
360
376
|
}
|
|
361
|
-
const agentKey = resolveCliAgentKeyInput(agentInput);
|
|
362
|
-
|
|
363
377
|
const authToken = resolveAuthToken(env);
|
|
364
378
|
if (!authToken) {
|
|
365
379
|
output.write("[nolo] agent runtime-doctor requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
@@ -372,6 +386,15 @@ export async function runAgentRuntimeDoctorCommand(
|
|
|
372
386
|
const machine = await (deps.machineInfo ?? detectLaunchableMachineInfo)();
|
|
373
387
|
|
|
374
388
|
try {
|
|
389
|
+
const { agentKey } = await resolveAgentInput({
|
|
390
|
+
agentInput,
|
|
391
|
+
authToken,
|
|
392
|
+
db: deps.db,
|
|
393
|
+
env,
|
|
394
|
+
fallbackFetchImpl,
|
|
395
|
+
fetchImpl,
|
|
396
|
+
output,
|
|
397
|
+
});
|
|
375
398
|
const agent = await readAgentRecord({ agentKey, authToken, fallbackFetchImpl, fetchImpl, serverUrl });
|
|
376
399
|
const requiredCapability = requiredCapabilityForAgent(agent);
|
|
377
400
|
const runtimeClass = classifyAgentRuntime(agent);
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { getReadableCliDb, type OutputLike } from "./agentCommandSupport";
|
|
2
|
+
import {
|
|
3
|
+
listLocalCachedAgents,
|
|
4
|
+
listRemoteAgentsAcrossServers,
|
|
5
|
+
type ListedAgent,
|
|
6
|
+
} from "./agentListHelpers";
|
|
7
|
+
import { resolveCliAgentKeyInput } from "./agentAliases";
|
|
8
|
+
import {
|
|
9
|
+
parseUserIdFromAuthToken,
|
|
10
|
+
resolveAuthToken,
|
|
11
|
+
resolveServerCandidates,
|
|
12
|
+
resolveServerUrl,
|
|
13
|
+
type EnvLike,
|
|
14
|
+
} from "./cliEnvHelpers";
|
|
15
|
+
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
16
|
+
|
|
17
|
+
export type ResolvedAgentInput = {
|
|
18
|
+
agentKey: string;
|
|
19
|
+
agentName: string;
|
|
20
|
+
source: "explicit" | "agent-list";
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function normalizeAgentName(value: string) {
|
|
24
|
+
return value.trim().toLowerCase();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isExplicitAgentKey(value: string) {
|
|
28
|
+
return /^(agent|cybot)-(pub-|[^-]+-).+/i.test(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function findAgentByName(input: string, agents: ListedAgent[]) {
|
|
32
|
+
const normalized = normalizeAgentName(input);
|
|
33
|
+
return agents.filter((agent) => normalizeAgentName(agent.name) === normalized);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function formatAmbiguousAgentName(input: string, matches: ListedAgent[]) {
|
|
37
|
+
return [
|
|
38
|
+
`ambiguous agent name: ${input}`,
|
|
39
|
+
...matches.map((agent) => `- ${agent.name}: ${agent.privateKey}`),
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function isAmbiguousAgentNameError(error: unknown) {
|
|
44
|
+
return error instanceof Error && error.message.startsWith("ambiguous agent name:");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function listLocalAgentsForResolution(args: {
|
|
48
|
+
authToken: string;
|
|
49
|
+
db?: CliKvDb;
|
|
50
|
+
output: OutputLike;
|
|
51
|
+
}) {
|
|
52
|
+
const userId = parseUserIdFromAuthToken(args.authToken);
|
|
53
|
+
if (!userId) {
|
|
54
|
+
throw new Error("could not read userId from AUTH_TOKEN; run `nolo login` first.");
|
|
55
|
+
}
|
|
56
|
+
const db = args.db ?? await getReadableCliDb(args.output);
|
|
57
|
+
return listLocalCachedAgents({ db, userId });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function listRemoteAgentsForResolution(args: {
|
|
61
|
+
authToken: string;
|
|
62
|
+
env: EnvLike;
|
|
63
|
+
fallbackFetchImpl?: typeof fetch;
|
|
64
|
+
fetchImpl: typeof fetch;
|
|
65
|
+
}) {
|
|
66
|
+
const userId = parseUserIdFromAuthToken(args.authToken);
|
|
67
|
+
if (!userId) {
|
|
68
|
+
throw new Error("could not read userId from AUTH_TOKEN; run `nolo login` first.");
|
|
69
|
+
}
|
|
70
|
+
const serverUrl = resolveServerUrl(args.env);
|
|
71
|
+
const serverUrls = resolveServerCandidates(args.env, serverUrl);
|
|
72
|
+
return (await listRemoteAgentsAcrossServers({
|
|
73
|
+
authToken: args.authToken,
|
|
74
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
75
|
+
fetchImpl: args.fetchImpl,
|
|
76
|
+
includeLegacy: false,
|
|
77
|
+
serverUrls,
|
|
78
|
+
userId,
|
|
79
|
+
})).agents;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function resolveUniqueAgentName(input: string, agents: ListedAgent[]) {
|
|
83
|
+
const matches = findAgentByName(input, agents);
|
|
84
|
+
if (matches.length === 1) {
|
|
85
|
+
return {
|
|
86
|
+
agentKey: matches[0].privateKey,
|
|
87
|
+
agentName: matches[0].name,
|
|
88
|
+
source: "agent-list" as const,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (matches.length > 1) {
|
|
92
|
+
throw new Error(formatAmbiguousAgentName(input, matches));
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function resolveAgentInput(args: {
|
|
98
|
+
agentInput: string;
|
|
99
|
+
authToken?: string;
|
|
100
|
+
db?: CliKvDb;
|
|
101
|
+
env: EnvLike;
|
|
102
|
+
fallbackFetchImpl?: typeof fetch;
|
|
103
|
+
fetchImpl: typeof fetch;
|
|
104
|
+
output: OutputLike;
|
|
105
|
+
}): Promise<ResolvedAgentInput> {
|
|
106
|
+
const parsed = resolveCliAgentKeyInput(args.agentInput);
|
|
107
|
+
if (isExplicitAgentKey(parsed)) {
|
|
108
|
+
return {
|
|
109
|
+
agentKey: parsed,
|
|
110
|
+
agentName: args.agentInput,
|
|
111
|
+
source: "explicit",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const authToken = args.authToken ?? resolveAuthToken(args.env);
|
|
116
|
+
if (!authToken) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`agent not found: ${args.agentInput}. Run \`nolo login\` and \`nolo agent list\`, or pass an explicit agent key.`
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (args.db) {
|
|
123
|
+
try {
|
|
124
|
+
const localAgents = await listLocalAgentsForResolution({
|
|
125
|
+
authToken,
|
|
126
|
+
db: args.db,
|
|
127
|
+
output: args.output,
|
|
128
|
+
});
|
|
129
|
+
const localMatch = resolveUniqueAgentName(parsed, localAgents);
|
|
130
|
+
if (localMatch) return localMatch;
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (isAmbiguousAgentNameError(error)) throw error;
|
|
133
|
+
// Local cache is an optimization; remote agent list remains authoritative enough to resolve names.
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const remoteAgents = await listRemoteAgentsForResolution({
|
|
139
|
+
authToken,
|
|
140
|
+
env: args.env,
|
|
141
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
142
|
+
fetchImpl: args.fetchImpl,
|
|
143
|
+
});
|
|
144
|
+
const remoteMatch = resolveUniqueAgentName(parsed, remoteAgents);
|
|
145
|
+
if (remoteMatch) return remoteMatch;
|
|
146
|
+
} catch (error) {
|
|
147
|
+
if (isAmbiguousAgentNameError(error)) throw error;
|
|
148
|
+
if (!args.db) {
|
|
149
|
+
try {
|
|
150
|
+
const localAgents = await listLocalAgentsForResolution({
|
|
151
|
+
authToken,
|
|
152
|
+
output: args.output,
|
|
153
|
+
});
|
|
154
|
+
const localMatch = resolveUniqueAgentName(parsed, localAgents);
|
|
155
|
+
if (localMatch) return localMatch;
|
|
156
|
+
} catch (localError) {
|
|
157
|
+
if (isAmbiguousAgentNameError(localError)) throw localError;
|
|
158
|
+
// Fall through to the user-facing not-found message below.
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
throw new Error(`agent not found by name: ${args.agentInput}. Run \`nolo agent list\` to see available agents.`);
|
|
164
|
+
}
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { resolveCliAgentKeyInput } from "./agentAliases";
|
|
2
1
|
import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
|
|
3
2
|
import {
|
|
4
3
|
buildUpdatedAgentRecord,
|
|
@@ -28,7 +27,6 @@ export async function runAgentReadCommand(
|
|
|
28
27
|
return 1;
|
|
29
28
|
}
|
|
30
29
|
|
|
31
|
-
const agentKey = resolveCliAgentKeyInput(agentInput);
|
|
32
30
|
const db = deps.db ?? await getReadableCliDb(output);
|
|
33
31
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
34
32
|
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
@@ -43,7 +41,7 @@ export async function runAgentReadCommand(
|
|
|
43
41
|
fallbackFetchImpl,
|
|
44
42
|
});
|
|
45
43
|
if (!result) {
|
|
46
|
-
throw new Error(`agent not found: ${
|
|
44
|
+
throw new Error(`agent not found: ${agentInput}`);
|
|
47
45
|
}
|
|
48
46
|
output.write(JSON.stringify({
|
|
49
47
|
...normalizeAgentRecordForOutput(result.agentKey, authToken, result.record),
|
|
@@ -2,6 +2,7 @@ import { readFileSync } from "node:fs";
|
|
|
2
2
|
import { DataType } from "../create/types";
|
|
3
3
|
import { createAgentKey } from "../database/keys";
|
|
4
4
|
import { resolveCliAgentKeyInput } from "./agentAliases";
|
|
5
|
+
import { resolveAgentInput } from "./agentNameResolver";
|
|
5
6
|
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
6
7
|
import { buildLocalAgentLookupKeys, shouldReadAgentKeyRemotely } from "./client/localAgentRecords";
|
|
7
8
|
import {
|
|
@@ -25,6 +26,8 @@ const PROVIDER_COPY_FIELDS = [
|
|
|
25
26
|
"outputPrice",
|
|
26
27
|
] as const;
|
|
27
28
|
|
|
29
|
+
const silentOutput = { write() {} };
|
|
30
|
+
|
|
28
31
|
function readRepeatedOption(args: string[], flag: string) {
|
|
29
32
|
const values: string[] = [];
|
|
30
33
|
for (let index = 0; index < args.length; index += 1) {
|
|
@@ -283,10 +286,25 @@ export async function resolveAgentRecordFromHybridStore(args: {
|
|
|
283
286
|
fetchImpl: typeof fetch;
|
|
284
287
|
fallbackFetchImpl?: typeof fetch;
|
|
285
288
|
}) {
|
|
286
|
-
const agentKey = resolveCliAgentKeyInput(args.agentInput);
|
|
287
289
|
const authToken = args.cliArgs
|
|
288
290
|
? resolveAuthToken(args.cliArgs, args.env)
|
|
289
291
|
: resolveAuthToken(args.env);
|
|
292
|
+
const resolvedAgent = await resolveAgentInput({
|
|
293
|
+
agentInput: args.agentInput,
|
|
294
|
+
authToken,
|
|
295
|
+
db: args.db,
|
|
296
|
+
env: args.env,
|
|
297
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
298
|
+
fetchImpl: args.fetchImpl,
|
|
299
|
+
output: silentOutput,
|
|
300
|
+
}).catch((error) => {
|
|
301
|
+
if (error instanceof Error && error.message.startsWith("ambiguous agent name:")) {
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
return null;
|
|
305
|
+
});
|
|
306
|
+
if (!resolvedAgent) return null;
|
|
307
|
+
const agentKey = resolvedAgent.agentKey;
|
|
290
308
|
const defaultServerUrl = args.cliArgs
|
|
291
309
|
? resolveServerUrl(args.cliArgs, args.env)
|
|
292
310
|
: resolveServerUrl(args.env);
|
|
@@ -505,7 +523,10 @@ export async function buildUpdatedAgentRecord(args: {
|
|
|
505
523
|
fetchImpl: args.fetchImpl,
|
|
506
524
|
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
507
525
|
});
|
|
508
|
-
|
|
526
|
+
if (!cached) {
|
|
527
|
+
throw new Error(`agent not found: ${args.parsed.agentInput}`);
|
|
528
|
+
}
|
|
529
|
+
const agentKey = cached.agentKey;
|
|
509
530
|
const explicitServerUrl = args.cliArgs
|
|
510
531
|
? readOption(args.cliArgs, "--server-url") || readOption(args.cliArgs, "--server")
|
|
511
532
|
: undefined;
|
package/cli/agentRunCommand.ts
CHANGED
|
@@ -4,7 +4,9 @@ import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "./agent
|
|
|
4
4
|
import { existsSync, readFileSync } from "node:fs";
|
|
5
5
|
import { extname, resolve } from "node:path";
|
|
6
6
|
import { parseSkillDocProtocol, type WorkflowReferenceConfig } from "../ai/skills/skillDocProtocol";
|
|
7
|
-
import { LOCAL_CODEX_AGENT_KEY,
|
|
7
|
+
import { LOCAL_CODEX_AGENT_KEY, isLocalCliAgentKey, resolveCliAgentKeyInput } from "./agentAliases";
|
|
8
|
+
import { resolveAgentInput } from "./agentNameResolver";
|
|
9
|
+
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
8
10
|
|
|
9
11
|
type EnvLike = Record<string, string | undefined>;
|
|
10
12
|
|
|
@@ -21,6 +23,9 @@ type AgentRunCommandDeps = {
|
|
|
21
23
|
localRuntimeAdapterFactory?: (env: EnvLike, options?: { cwd?: string }) => AgentRuntimeHostAdapter;
|
|
22
24
|
inspectLocalRunWorkspace?: typeof inspectLocalRunWorkspace;
|
|
23
25
|
resolveWorkflowReference?: typeof resolveWorkflowReference;
|
|
26
|
+
db?: CliKvDb;
|
|
27
|
+
fetchImpl?: typeof fetch;
|
|
28
|
+
fallbackFetchImpl?: typeof fetch;
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
type LocalRunWorkspaceInspection = {
|
|
@@ -93,18 +98,9 @@ function runtimeModeFromArgs(args: string[]): AgentRuntimeRequestedMode | undefi
|
|
|
93
98
|
}
|
|
94
99
|
|
|
95
100
|
function isMonthlyMimoAgentRef(raw: string | undefined, resolved: string) {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
normalized === "fullstack" ||
|
|
100
|
-
normalized === "full-stack" ||
|
|
101
|
-
normalized === "nolo-fullstack" ||
|
|
102
|
-
normalized === "包月mimo" ||
|
|
103
|
-
normalized === "包月mimo2.5" ||
|
|
104
|
-
normalized === "mimo-month" ||
|
|
105
|
-
normalized === "全栈" ||
|
|
106
|
-
normalized === "nolo 全栈工程师"
|
|
107
|
-
);
|
|
101
|
+
void raw;
|
|
102
|
+
void resolved;
|
|
103
|
+
return false;
|
|
108
104
|
}
|
|
109
105
|
|
|
110
106
|
function parsePositiveInteger(value: string | undefined) {
|
|
@@ -473,6 +469,22 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
|
|
|
473
469
|
}
|
|
474
470
|
|
|
475
471
|
const runner = deps.runner ?? runAgentTurn;
|
|
472
|
+
let runnerAgentName = parsed.agentKey;
|
|
473
|
+
try {
|
|
474
|
+
const resolvedAgent = await resolveAgentInput({
|
|
475
|
+
agentInput: parsed.agentKey,
|
|
476
|
+
env,
|
|
477
|
+
db: deps.db,
|
|
478
|
+
fetchImpl: deps.fetchImpl ?? fetch,
|
|
479
|
+
fallbackFetchImpl: deps.fallbackFetchImpl,
|
|
480
|
+
output,
|
|
481
|
+
});
|
|
482
|
+
parsed.agentKey = resolvedAgent.agentKey;
|
|
483
|
+
runnerAgentName = resolvedAgent.agentName;
|
|
484
|
+
} catch (error) {
|
|
485
|
+
output.write(`[nolo] ${error instanceof Error ? error.message : String(error)}\n`);
|
|
486
|
+
return 1;
|
|
487
|
+
}
|
|
476
488
|
let workflowReference: ResolvedWorkflowReference | undefined;
|
|
477
489
|
if (parsed.workflowRef) {
|
|
478
490
|
try {
|
|
@@ -493,7 +505,7 @@ export async function runAgentRunCommand(args: string[], deps: AgentRunCommandDe
|
|
|
493
505
|
allowShell: parsed.allowShell,
|
|
494
506
|
});
|
|
495
507
|
const result: RunAgentTurnResult = await runner({
|
|
496
|
-
agentName:
|
|
508
|
+
agentName: runnerAgentName,
|
|
497
509
|
agentKey: parsed.agentKey,
|
|
498
510
|
serverUrl: resolveServerUrl(env),
|
|
499
511
|
message: prependWorkflowReferencePrompt(
|
package/cli/client/agentRun.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import { runLocalAgentTurn } from "../agentRuntimeLocal";
|
|
2
2
|
import { LOCAL_AGENT_CONFIG_MISSING_CODE } from "../../agent-runtime/localLoop";
|
|
3
|
-
import {
|
|
4
|
-
MIMO_MONTH_AGENT_KEY,
|
|
5
|
-
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
6
|
-
WIN_CODEX_AGENT_KEY,
|
|
7
|
-
} from "../agentAliases";
|
|
8
3
|
import type { LocalAgentToolEvent } from "../../agent-runtime/localLoop";
|
|
9
4
|
import type { AgentRuntimeHostAdapter, AgentRuntimeRequestedMode } from "../agentRuntimeLocal";
|
|
10
5
|
import { createCliLocalRuntimeAdapter, isBuiltinNoloAgentRef } from "./localRuntimeAdapter";
|
|
@@ -115,36 +110,6 @@ const SERVER_PLATFORM_TOOL_NAMES = new Set([
|
|
|
115
110
|
"updateTableRows",
|
|
116
111
|
]);
|
|
117
112
|
|
|
118
|
-
const KNOWN_SERVER_PLATFORM_AGENT_KEYS = new Set([
|
|
119
|
-
MIMO_MONTH_AGENT_KEY,
|
|
120
|
-
NOLO_PROJECT_MANAGER_AGENT_KEY,
|
|
121
|
-
WIN_CODEX_AGENT_KEY,
|
|
122
|
-
]);
|
|
123
|
-
|
|
124
|
-
const KNOWN_SERVER_PLATFORM_AGENT_ALIASES = new Set([
|
|
125
|
-
"code-review",
|
|
126
|
-
"frontend",
|
|
127
|
-
"frontend-agent",
|
|
128
|
-
"frontend-implementer",
|
|
129
|
-
"full-stack",
|
|
130
|
-
"fullstack",
|
|
131
|
-
"nolo code review",
|
|
132
|
-
"nolo frontend",
|
|
133
|
-
"nolo fullstack",
|
|
134
|
-
"nolo project manager",
|
|
135
|
-
"nolo reviewer",
|
|
136
|
-
"nolo-code-review",
|
|
137
|
-
"nolo-frontend",
|
|
138
|
-
"nolo-fullstack",
|
|
139
|
-
"nolo-pm",
|
|
140
|
-
"nolo-project-manager",
|
|
141
|
-
"nolo-reviewer",
|
|
142
|
-
"pm",
|
|
143
|
-
"project-manager",
|
|
144
|
-
"review",
|
|
145
|
-
"reviewer",
|
|
146
|
-
]);
|
|
147
|
-
|
|
148
113
|
export function findServerPlatformTools(toolNames?: string[]) {
|
|
149
114
|
if (!Array.isArray(toolNames)) return [];
|
|
150
115
|
return toolNames.filter((toolName) => SERVER_PLATFORM_TOOL_NAMES.has(toolName));
|
|
@@ -159,16 +124,6 @@ function resolveServerPlatformToolNames(agentConfig: any) {
|
|
|
159
124
|
]);
|
|
160
125
|
}
|
|
161
126
|
|
|
162
|
-
function normalizeAgentRef(ref?: string) {
|
|
163
|
-
return ref?.trim().toLowerCase().replace(/\s+/g, " ");
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function isKnownServerPlatformAgent(options: RunAgentTurnOptions) {
|
|
167
|
-
if (KNOWN_SERVER_PLATFORM_AGENT_KEYS.has(options.agentKey)) return true;
|
|
168
|
-
const normalizedKey = normalizeAgentRef(options.agentKey);
|
|
169
|
-
return Boolean(normalizedKey && KNOWN_SERVER_PLATFORM_AGENT_ALIASES.has(normalizedKey));
|
|
170
|
-
}
|
|
171
|
-
|
|
172
127
|
function isMachineBoundLocalhostCustomProvider(agentConfig: any) {
|
|
173
128
|
const machineId =
|
|
174
129
|
agentConfig?.runtimeBinding && typeof agentConfig.runtimeBinding === "object"
|
|
@@ -233,30 +188,15 @@ async function shouldSkipAutoLocalForServerPlatformTools(options: RunAgentTurnOp
|
|
|
233
188
|
if (options.localRuntimeCwd) {
|
|
234
189
|
return false;
|
|
235
190
|
}
|
|
236
|
-
const knownServerPlatformAgent = isKnownServerPlatformAgent(options);
|
|
237
191
|
const adapter = resolveLocalRuntimeAdapter(options);
|
|
238
|
-
if (!adapter) return
|
|
192
|
+
if (!adapter) return false;
|
|
239
193
|
let agentConfig;
|
|
240
194
|
try {
|
|
241
195
|
agentConfig = await adapter.loadAgentConfig(options.agentKey);
|
|
242
196
|
} catch {
|
|
243
|
-
if (knownServerPlatformAgent) {
|
|
244
|
-
options.output.write(
|
|
245
|
-
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
|
|
246
|
-
"Use --local explicitly to force local workspace tools.\n"
|
|
247
|
-
);
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
197
|
return false;
|
|
251
198
|
}
|
|
252
199
|
if (isCliProviderAgentConfig(agentConfig)) return false;
|
|
253
|
-
if (knownServerPlatformAgent) {
|
|
254
|
-
options.output.write(
|
|
255
|
-
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a known platform agent. ` +
|
|
256
|
-
"Use --local explicitly to force local workspace tools.\n"
|
|
257
|
-
);
|
|
258
|
-
return true;
|
|
259
|
-
}
|
|
260
200
|
if (isMachineBoundLocalhostCustomProvider(agentConfig)) {
|
|
261
201
|
options.output.write(
|
|
262
202
|
`[nolo] auto runtime: skipping local runtime because ${options.agentKey} is a machine-bound localhost custom provider. ` +
|
|
@@ -50,7 +50,6 @@ import {
|
|
|
50
50
|
LOCAL_CODEX_AGENT_KEY,
|
|
51
51
|
LOCAL_QODER_AGENT_ID,
|
|
52
52
|
LOCAL_QODER_AGENT_KEY,
|
|
53
|
-
MIMO_MONTH_AGENT_KEY,
|
|
54
53
|
NOLO_DEFAULT_AGENT_ID,
|
|
55
54
|
NOLO_DEFAULT_AGENT_KEY,
|
|
56
55
|
} from "../agentAliases";
|
|
@@ -424,10 +423,6 @@ function summarizeOpenAiToolNames(tools: Array<Record<string, unknown>>) {
|
|
|
424
423
|
.filter((name): name is string => Boolean(name));
|
|
425
424
|
}
|
|
426
425
|
|
|
427
|
-
function shouldExposeLocalPlatformTools(agentKey?: string) {
|
|
428
|
-
return agentKey !== MIMO_MONTH_AGENT_KEY;
|
|
429
|
-
}
|
|
430
|
-
|
|
431
426
|
function addDefaultLightWebToolsForConfiguredAgents(
|
|
432
427
|
toolNames: string[],
|
|
433
428
|
agentConfig?: AgentRuntimeAgentConfig | null,
|
|
@@ -449,7 +444,6 @@ function addDefaultLightWebToolsForConfiguredAgents(
|
|
|
449
444
|
|
|
450
445
|
function buildOpenAiTools(args: { agentKey?: string; toolNames?: string[]; env: EnvLike }) {
|
|
451
446
|
const toolset = buildLocalWorkspaceToolsetForEnv(args);
|
|
452
|
-
const exposePlatformTools = shouldExposeLocalPlatformTools(args.agentKey);
|
|
453
447
|
return [
|
|
454
448
|
...buildLocalWorkspaceOpenAiTools({
|
|
455
449
|
toolNames: toolset.toolNames,
|
|
@@ -463,8 +457,8 @@ function buildOpenAiTools(args: { agentKey?: string; toolNames?: string[]; env:
|
|
|
463
457
|
searchFilesDescriptionVariant: resolveSearchFilesDescriptionVariant(args.env),
|
|
464
458
|
searchFilesParameterVariant: resolveSearchFilesParameterVariant(args.env),
|
|
465
459
|
}),
|
|
466
|
-
...
|
|
467
|
-
...
|
|
460
|
+
...buildServerPlatformOpenAiTools({ toolNames: args.toolNames }),
|
|
461
|
+
...buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }),
|
|
468
462
|
];
|
|
469
463
|
}
|
|
470
464
|
|
package/cli/commandRegistry.ts
CHANGED
|
@@ -64,7 +64,7 @@ export function renderHelpText() {
|
|
|
64
64
|
" nolo agent pull agent-pub-01APPBUILDER00000001YAII3I",
|
|
65
65
|
" nolo agent read agent-pub-01APPBUILDER00000001YAII3I",
|
|
66
66
|
" nolo agent usage qoder",
|
|
67
|
-
' nolo agent run frontend
|
|
67
|
+
' nolo agent run frontend --msg "polish notifications"',
|
|
68
68
|
" nolo agent bind-current agent-user-1-agent-1",
|
|
69
69
|
" nolo agent runtime-doctor agent-user-1-agent-1",
|
|
70
70
|
' nolo agent smoke-current agent-user-1-agent-1 --msg "ping"',
|
|
@@ -73,8 +73,8 @@ export function renderHelpText() {
|
|
|
73
73
|
" nolo space read 01KKY77TT0DA9NY7TNW3R7255N --content-key page-user-id --brief",
|
|
74
74
|
" nolo space delete --name-prefix rn_owner_verify_0504 --yes",
|
|
75
75
|
" nolo table query --table 01ABCXYZ",
|
|
76
|
-
' nolo table query --table meta-
|
|
77
|
-
' nolo table update-row --table meta-
|
|
76
|
+
' nolo table query --table meta-your-user-TASKBOARD --columns \'["title","status","owner","priority","codeStatus"]\' --no-base-fields --output items',
|
|
77
|
+
' nolo table update-row --table meta-your-user-TASKBOARD --row 01ROWID --changes \'{"status":"done"}\'',
|
|
78
78
|
" nolo llama status",
|
|
79
79
|
];
|
|
80
80
|
|
package/cli/machineCommands.ts
CHANGED
|
@@ -150,6 +150,9 @@ function buildTaskEvidencePrompt(args: {
|
|
|
150
150
|
}) {
|
|
151
151
|
const rowDbKey = findTaskRowSubjectRef(args.runtimeContext);
|
|
152
152
|
if (!rowDbKey) return "";
|
|
153
|
+
const taskBoardTableKey = typeof args.runtimeContext?.taskBoardTableKey === "string"
|
|
154
|
+
? args.runtimeContext.taskBoardTableKey.trim()
|
|
155
|
+
: "";
|
|
153
156
|
return [
|
|
154
157
|
"--- Nolo task evidence context ---",
|
|
155
158
|
"This CLI runtime does not receive server-side function tools directly.",
|
|
@@ -157,7 +160,9 @@ function buildTaskEvidencePrompt(args: {
|
|
|
157
160
|
"Run commands from the repository root. The runner already provides server URL and auth in the environment.",
|
|
158
161
|
"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.",
|
|
159
162
|
"Handoff context: inspect activityRefs/latestActivityRef, dialog checkpoints, artifacts, commits, and test evidence. Use dialog read/search for exact evidence; do not infer completion from handoff text alone.",
|
|
160
|
-
|
|
163
|
+
taskBoardTableKey
|
|
164
|
+
? `Read task row: bun packages/cli/index.ts table query --table ${JSON.stringify(taskBoardTableKey)} --row ${JSON.stringify(rowDbKey)} --include-activity --output json`
|
|
165
|
+
: `Task row subjectRef: ${JSON.stringify(rowDbKey)}. If a task board table key is provided in context, read it with: bun packages/cli/index.ts table query --table <tableKey> --row ${JSON.stringify(rowDbKey)} --include-activity --output json`,
|
|
161
166
|
"Then read linked activity dialog ids with: bun packages/cli/index.ts dialog read <dialogId>",
|
|
162
167
|
"Report progress, blockers, worktree, branch, commit/diff, tests, and unverified items in the dialog.",
|
|
163
168
|
].join("\n");
|
|
@@ -28,7 +28,6 @@ type ParsedArgs = {
|
|
|
28
28
|
|
|
29
29
|
const DEFAULT_USER_ID = "b2e06f801f";
|
|
30
30
|
const DEFAULT_SPACE_ID = "01KKY77TT0DA9NY7TNW3R7255N";
|
|
31
|
-
const DEFAULT_SOURCE_AGENT_ID = "01MIMO25MONTH0000000NEW001";
|
|
32
31
|
const DEFAULT_TARGET_AGENT_ID = "01OFFMARXBOOK000000010AHL1";
|
|
33
32
|
const DEFAULT_AGENT_NAME = "离线马克思主义文库书籍转换助手";
|
|
34
33
|
const TOOL_NAME = "convertMarxistsBookToOfflineHtml";
|
|
@@ -75,8 +74,8 @@ function parseArgs(args: string[], env: EnvLike): ParsedArgs | null {
|
|
|
75
74
|
DEFAULT_USER_ID;
|
|
76
75
|
const sourceAgentKey =
|
|
77
76
|
readFlagValue(args, "--source-agent") ??
|
|
78
|
-
env.
|
|
79
|
-
|
|
77
|
+
env.NOLO_OFFLINE_MARXISTS_SOURCE_AGENT_KEY ??
|
|
78
|
+
"";
|
|
80
79
|
const targetAgentId =
|
|
81
80
|
readFlagValue(args, "--target-agent-id") ??
|
|
82
81
|
env.NOLO_OFFLINE_MARXISTS_AGENT_ID ??
|
|
@@ -90,7 +89,7 @@ function parseArgs(args: string[], env: EnvLike): ParsedArgs | null {
|
|
|
90
89
|
env.NOLO_OFFLINE_MARXISTS_SPACE_ID ??
|
|
91
90
|
DEFAULT_SPACE_ID;
|
|
92
91
|
|
|
93
|
-
if (!authToken.trim()) return null;
|
|
92
|
+
if (!authToken.trim() || !sourceAgentKey.trim()) return null;
|
|
94
93
|
return {
|
|
95
94
|
serverUrl,
|
|
96
95
|
authToken,
|
|
@@ -227,7 +226,7 @@ async function attachAgentToSpace(args: {
|
|
|
227
226
|
function writeUsage(output: OutputLike) {
|
|
228
227
|
output.write(
|
|
229
228
|
"Usage: nolo agent setup-offline-marxists [--server https://nolo.chat] [--source-agent <key>] [--space <spaceId>] [--target-agent-id <id>] [--json]\n" +
|
|
230
|
-
"Requires AUTH_TOKEN from `nolo login` or --token.\n"
|
|
229
|
+
"Requires AUTH_TOKEN from `nolo login` or --token, plus --source-agent or NOLO_OFFLINE_MARXISTS_SOURCE_AGENT_KEY.\n"
|
|
231
230
|
);
|
|
232
231
|
}
|
|
233
232
|
|