nolo-cli 0.1.43 → 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/LICENSE +21 -0
- package/README.md +118 -263
- 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/app/utils/myContentItems.ts +28 -0
- 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 +23 -18
- package/client/localRuntimeAdapter.ts +2 -8
- package/client/localRuntimeDryRun.test.ts +2 -0
- package/commandRegistry.ts +3 -3
- package/machineCommands.ts +6 -1
- package/offlineMarxistsAgentCommand.ts +4 -5
- package/package.json +28 -22
|
@@ -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
|
|
package/client/agentRun.test.ts
CHANGED
|
@@ -7,7 +7,9 @@ import {
|
|
|
7
7
|
runAgentTurn,
|
|
8
8
|
} from "./agentRun";
|
|
9
9
|
import { BUILTIN_NOLO_AGENT_KEY } from "./localRuntimeAdapter";
|
|
10
|
-
|
|
10
|
+
|
|
11
|
+
const TEST_MONTHLY_MIMO_AGENT_KEY = "agent-user-1-monthly-mimo";
|
|
12
|
+
const TEST_PROJECT_MANAGER_AGENT_KEY = "agent-user-1-project-manager";
|
|
11
13
|
|
|
12
14
|
class CaptureOutput extends Writable {
|
|
13
15
|
chunks: string[] = [];
|
|
@@ -929,8 +931,8 @@ describe("cli agent run client", () => {
|
|
|
929
931
|
let providerCalled = false;
|
|
930
932
|
|
|
931
933
|
const result = await runAgentTurn({
|
|
932
|
-
agentName:
|
|
933
|
-
agentKey:
|
|
934
|
+
agentName: TEST_MONTHLY_MIMO_AGENT_KEY,
|
|
935
|
+
agentKey: TEST_MONTHLY_MIMO_AGENT_KEY,
|
|
934
936
|
serverUrl: "https://us.nolo.chat",
|
|
935
937
|
message: "fix tests and commit",
|
|
936
938
|
scriptDir: "C:/missing/scripts",
|
|
@@ -972,7 +974,7 @@ describe("cli agent run client", () => {
|
|
|
972
974
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-mimo-local" });
|
|
973
975
|
expect(httpCalls).toEqual([]);
|
|
974
976
|
expect(output.text()).not.toContain("skipping local runtime");
|
|
975
|
-
expect(output.text()).toContain(`${
|
|
977
|
+
expect(output.text()).toContain(`${TEST_MONTHLY_MIMO_AGENT_KEY} -> working locally`);
|
|
976
978
|
});
|
|
977
979
|
|
|
978
980
|
test("auto mode skips local runtime for machine-bound localhost custom providers", async () => {
|
|
@@ -1128,13 +1130,13 @@ describe("cli agent run client", () => {
|
|
|
1128
1130
|
expect(output.text()).toContain("queryTableRows, streamParallelAgents");
|
|
1129
1131
|
});
|
|
1130
1132
|
|
|
1131
|
-
test("auto mode
|
|
1133
|
+
test("auto mode falls back to server when local config cannot be read", async () => {
|
|
1132
1134
|
const output = new CaptureOutput();
|
|
1133
1135
|
const httpCalls: Array<{ url: string; body: any }> = [];
|
|
1134
1136
|
|
|
1135
1137
|
const result = await runAgentTurn({
|
|
1136
|
-
agentName: "
|
|
1137
|
-
agentKey:
|
|
1138
|
+
agentName: "project-manager",
|
|
1139
|
+
agentKey: TEST_PROJECT_MANAGER_AGENT_KEY,
|
|
1138
1140
|
serverUrl: "https://us.nolo.chat",
|
|
1139
1141
|
message: "write task rows",
|
|
1140
1142
|
scriptDir: "C:/missing/scripts",
|
|
@@ -1168,10 +1170,10 @@ describe("cli agent run client", () => {
|
|
|
1168
1170
|
|
|
1169
1171
|
expect(result).toEqual({ exitCode: 0, dialogId: "dialog-server" });
|
|
1170
1172
|
expect(httpCalls).toHaveLength(1);
|
|
1171
|
-
expect(httpCalls[0]?.body.agentKey).toBe(
|
|
1172
|
-
expect(output.text()).toContain("known platform agent");
|
|
1173
|
-
expect(output.text()).toContain("
|
|
1174
|
-
expect(output.text()).toContain("
|
|
1173
|
+
expect(httpCalls[0]?.body.agentKey).toBe(TEST_PROJECT_MANAGER_AGENT_KEY);
|
|
1174
|
+
expect(output.text()).not.toContain("known platform agent");
|
|
1175
|
+
expect(output.text()).toContain("project-manager -> working");
|
|
1176
|
+
expect(output.text()).toContain("project-manager > server ok");
|
|
1175
1177
|
});
|
|
1176
1178
|
|
|
1177
1179
|
test("builds the default local adapter when env requests local mode", async () => {
|