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.
@@ -1,6 +1,6 @@
1
1
  import type { MachineHeartbeat } from "../connector-experimental/protocol";
2
2
  import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
3
- import { resolveCliAgentKeyInput } from "./agentAliases";
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 agentKey = args[0]?.trim();
177
- if (!agentKey || agentKey === "--help" || agentKey === "-h") {
178
- output.write("Usage: nolo agent bind-current <agentKey>\n");
179
- return agentKey ? 0 : 1;
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 ${agentKey} to this machine: ${machine.name} (${machine.machineId})\n`);
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: ${agentKey}`);
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
- const agentKey = cached?.agentKey ?? resolveCliAgentKeyInput(args.parsed.agentInput);
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;
@@ -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, MIMO_MONTH_AGENT_KEY, isLocalCliAgentKey, resolveCliAgentKeyInput } from "./agentAliases";
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
- const normalized = raw?.trim().toLowerCase();
97
- return (
98
- resolved === MIMO_MONTH_AGENT_KEY ||
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: parsed.agentKey,
508
+ agentName: runnerAgentName,
497
509
  agentKey: parsed.agentKey,
498
510
  serverUrl: resolveServerUrl(env),
499
511
  message: prependWorkflowReferencePrompt(
@@ -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 knownServerPlatformAgent;
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
- ...(exposePlatformTools ? buildServerPlatformOpenAiTools({ toolNames: args.toolNames }) : []),
467
- ...(exposePlatformTools ? buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }) : []),
460
+ ...buildServerPlatformOpenAiTools({ toolNames: args.toolNames }),
461
+ ...buildNoloWorkspaceOpenAiTools({ toolNames: args.toolNames }),
468
462
  ];
469
463
  }
470
464
 
@@ -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-implementer --msg "polish notifications"',
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-0e95801d90-NOLOTASKBOARD --columns \'["title","status","owner","priority","codeStatus"]\' --no-base-fields --output items',
77
- ' nolo table update-row --table meta-0e95801d90-NOLOTASKBOARD --row 01ROWID --changes \'{"status":"已完成"}\'',
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
 
@@ -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
- `Read task row: bun packages/cli/index.ts table query --table meta-0e95801d90-NOLOTASKBOARD --row ${JSON.stringify(rowDbKey)} --include-activity --output json`,
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.NOLO_MIMO_SOURCE_AGENT_KEY ??
79
- `agent-${userId}-${DEFAULT_SOURCE_AGENT_ID}`;
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