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.
@@ -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
 
@@ -7,7 +7,9 @@ import {
7
7
  runAgentTurn,
8
8
  } from "./agentRun";
9
9
  import { BUILTIN_NOLO_AGENT_KEY } from "./localRuntimeAdapter";
10
- import { MIMO_MONTH_AGENT_KEY, NOLO_PROJECT_MANAGER_AGENT_KEY } from "../agentAliases";
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: MIMO_MONTH_AGENT_KEY,
933
- agentKey: MIMO_MONTH_AGENT_KEY,
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(`${MIMO_MONTH_AGENT_KEY} -> working locally`);
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 skips known platform agents when local config cannot be read", async () => {
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: "nolo-project-manager",
1137
- agentKey: NOLO_PROJECT_MANAGER_AGENT_KEY,
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(NOLO_PROJECT_MANAGER_AGENT_KEY);
1172
- expect(output.text()).toContain("known platform agent");
1173
- expect(output.text()).toContain("nolo-project-manager -> working");
1174
- expect(output.text()).toContain("nolo-project-manager > server ok");
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 () => {