nolo-cli 0.1.22 → 0.1.26

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.
Files changed (128) hide show
  1. package/README.md +25 -3
  2. package/agent-runtime/agentRecordConfig.ts +4 -0
  3. package/agent-runtime/hostAdapter.ts +2 -0
  4. package/agent-runtime/hybridRecordStore.ts +30 -9
  5. package/agent-runtime/index.ts +7 -0
  6. package/agent-runtime/localLoop.ts +14 -4
  7. package/agent-runtime/localToolPolicy.ts +22 -10
  8. package/agent-runtime/localWorkspaceTools.ts +140 -374
  9. package/agent-runtime/platformChatProvider.ts +3 -0
  10. package/agent-runtime/runtimeToolPolicy.ts +133 -0
  11. package/agent-runtime/types.ts +47 -0
  12. package/agentAliases.ts +1 -1
  13. package/agentCommandSupport.ts +77 -0
  14. package/agentInternalCommandEntries.ts +34 -0
  15. package/agentListCommands.ts +123 -0
  16. package/agentListHelpers.ts +187 -0
  17. package/agentMachineCommands.ts +402 -0
  18. package/agentRecordCommands.ts +141 -0
  19. package/agentRecordHelpers.ts +395 -0
  20. package/agentRunCommand.ts +75 -3
  21. package/agentRuntimeCommands.ts +7 -773
  22. package/ai/agent/_executeModel.ts +29 -3
  23. package/ai/agent/buildSystemPrompt.ts +17 -0
  24. package/ai/agent/cliChatClient.ts +38 -4
  25. package/ai/agent/cliExecutor.ts +304 -25
  26. package/ai/agent/createAgentSchema.ts +2 -2
  27. package/ai/agent/guidedCreation/assistant.ts +82 -0
  28. package/ai/agent/guidedCreation/capabilities.ts +84 -0
  29. package/ai/agent/guidedCreation/draft.ts +62 -0
  30. package/ai/agent/guidedCreation/types.ts +38 -0
  31. package/ai/agent/machineRunPermissions.ts +21 -3
  32. package/ai/agent/runtimeGuidance.ts +1 -1
  33. package/ai/agent/server/fetchPublicAgents.ts +6 -1
  34. package/ai/agent/streamAgentChatTurn.ts +593 -36
  35. package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
  36. package/ai/ai.locale.ts +14 -2
  37. package/ai/chat/fetchUtils.native.ts +2 -0
  38. package/ai/chat/fetchUtils.ts +2 -0
  39. package/ai/chat/sendOpenAICompletionsRequest.ts +82 -1
  40. package/ai/chat/sendOpenAIResponseRequest.ts +65 -0
  41. package/ai/llm/getPricing.ts +14 -0
  42. package/ai/llm/kimi.ts +4 -1
  43. package/ai/llm/modelAvatar.ts +1 -0
  44. package/ai/llm/providers.ts +4 -1
  45. package/ai/llm/reasoningModels.ts +1 -0
  46. package/ai/taskRun/taskRunProtocol.ts +25 -10
  47. package/ai/token/modelUsageQuery.ts +17 -0
  48. package/ai/tools/agent/agentTools.ts +38 -0
  49. package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
  50. package/ai/tools/agent/startAgentDialogTool.ts +53 -0
  51. package/ai/tools/checkEnvTool.ts +52 -20
  52. package/ai/tools/convertMarxistsBookTool.ts +103 -0
  53. package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
  54. package/ai/tools/index.ts +24 -14
  55. package/ai/tools/marxistsOfflineBook.ts +253 -0
  56. package/ai/tools/modelUsageTools.ts +9 -2
  57. package/ai/tools/readTool.ts +31 -0
  58. package/ai/tools/toolApiClient.ts +35 -1
  59. package/ai/tools/toolNameAliases.ts +13 -0
  60. package/cliCommandAdapters.ts +28 -0
  61. package/cliCommandDispatch.ts +66 -0
  62. package/cliCommandFactories.ts +34 -0
  63. package/cliCommandTypes.ts +49 -0
  64. package/cliEnvHelpers.ts +44 -0
  65. package/client/agentRun.test.ts +330 -24
  66. package/client/agentRun.ts +117 -35
  67. package/client/hybridRecordStore.test.ts +42 -0
  68. package/client/hybridRecordStore.ts +11 -0
  69. package/client/localRuntimeAdapter.test.ts +191 -21
  70. package/client/localRuntimeAdapter.ts +99 -10
  71. package/client/localRuntimeDryRun.test.ts +5 -2
  72. package/client/taskRunPrompt.ts +1 -0
  73. package/commandRegistry.ts +11 -61
  74. package/connector-experimental/capabilities.ts +1 -0
  75. package/database/actions/common.ts +4 -3
  76. package/database/config.ts +19 -0
  77. package/database/server/coreDataProxy.ts +43 -26
  78. package/index.ts +54 -191
  79. package/internalCommandEntries.ts +12 -0
  80. package/machineCommands.ts +688 -64
  81. package/offlineMarxistsAgentCommand.ts +314 -0
  82. package/package.json +22 -2
  83. package/render/canvas/canvasEditContext.ts +127 -0
  84. package/render/canvas/canvasRuntime.ts +57 -0
  85. package/render/canvas/canvasSnapshotParser.ts +76 -0
  86. package/render/canvas/canvasTree.ts +308 -0
  87. package/render/canvas/types.ts +46 -0
  88. package/render/layout/deleteBehavior.ts +52 -0
  89. package/render/layout/mainLayoutSidebar.ts +17 -0
  90. package/render/layout/mainLayoutViewMode.ts +56 -0
  91. package/render/layout/topbarUtils.ts +87 -0
  92. package/render/layout/useDevReloadPending.ts +30 -0
  93. package/render/page/createPageAction.ts +183 -0
  94. package/render/page/docSlice.ts +468 -0
  95. package/render/page/server/createPage.ts +174 -0
  96. package/render/page/server/handleCreatePage.ts +91 -0
  97. package/render/page/server/index.ts +4 -0
  98. package/render/page/types.ts +17 -0
  99. package/render/page/useKeyboardSave.ts +48 -0
  100. package/render/styles/zIndex.ts +12 -0
  101. package/render/surf/WeatherIconStyles.ts +17 -0
  102. package/render/surf/color.ts +9 -0
  103. package/render/surf/config.ts +46 -0
  104. package/render/surf/screens/style.ts +1 -0
  105. package/render/surf/styles/ToggleButtonStyles.ts +8 -0
  106. package/render/surf/utils/groupedWeatherData.ts +32 -0
  107. package/render/surf/weatherUtils.ts +50 -0
  108. package/render/table/activityColumns.ts +6 -0
  109. package/render/table/createTableAction.ts +270 -0
  110. package/render/table/deleteTableAction.ts +129 -0
  111. package/render/table/fetchAndCacheTableRows.ts +174 -0
  112. package/render/table/tableSlice.ts +1106 -0
  113. package/render/table/tableView.ts +289 -0
  114. package/render/table/toolValueUtils.ts +363 -0
  115. package/render/table/types.ts +252 -0
  116. package/render/table/useCreateTable.ts +72 -0
  117. package/render/table/useTable.ts +61 -0
  118. package/render/table/utils/tableSerialization.ts +50 -0
  119. package/render/web/elements/artifactPreviewCode.ts +43 -0
  120. package/render/web/elements/artifactRuntimePreload.ts +52 -0
  121. package/render/web/elements/codeBlockAutoPreview.ts +10 -0
  122. package/render/web/elements/mermaidPreview.ts +21 -0
  123. package/render/web/ui/useInlineEdit.ts +135 -0
  124. package/runtimeDoctorCommands.ts +118 -0
  125. package/scriptCommandEntries.ts +71 -0
  126. package/systemInternalCommandEntries.ts +71 -0
  127. package/tableCommands.ts +42 -5
  128. package/workflowInternalCommandEntries.ts +11 -0
package/README.md CHANGED
@@ -168,6 +168,7 @@ The preferred command model is Agent-first:
168
168
  nolo agent list
169
169
  nolo agent switch <agent>
170
170
  nolo agent read <agent>
171
+ nolo agent update <agent> --model gpt-5.4 --cli-provider codex --api-source cli
171
172
  nolo agent run <agent> "检查最近失败任务"
172
173
  nolo agent run frontend-implementer --msg "修一个小的通知弹窗 CSS 问题"
173
174
  nolo dialog list --agent <agent>
@@ -183,6 +184,19 @@ nolo table add-column --table meta-b2e06f801f-NOLOTASKBOARD --schema-write-ok --
183
184
  `--schema-write-ok` and should be run serially per table. Row-level table writes
184
185
  do not require this flag.
185
186
 
187
+ For agent config inspection and small config mutations, prefer the CLI-native
188
+ commands instead of ad hoc scripts:
189
+
190
+ - `nolo agent list` is CLI-native and prefers the local cache, then degrades to
191
+ remote query when the local LevelDB cache is unavailable or locked.
192
+ - `nolo agent read <agent>` reads local cached agent records first, then falls
193
+ back to remote fetch + local cache refresh across known server candidates.
194
+ - if the local LevelDB cache is unavailable or locked, `nolo agent read`
195
+ degrades to remote-only lookup instead of hard-failing on the local DB.
196
+ - `nolo agent update <agent> ...` is CLI-native and supports small targeted
197
+ updates such as `--model`, `--cli-provider`, `--api-source`, and repeated
198
+ `--field key=value`.
199
+
186
200
  Inside the current TUI, examples of supported slash commands include:
187
201
  `/agent`, `/agents`, `/switch`, `/context` (alias `/ctx`), `/dialog`, `/doc`,
188
202
  `/help`, `/new`, `/customize`, `/login`, `/profile`, `/version`, `/quit`
@@ -216,13 +230,21 @@ This creates a `dist/` directory with:
216
230
  - Inlined workspace dependencies (copied as nested directories)
217
231
  - A modified package.json with workspace dependencies stripped
218
232
 
219
- The `dist/` directory can be published to npm:
233
+ The `dist/` directory is the publish-safe artifact consumed by the CI npm
234
+ publish workflow:
220
235
 
221
236
  ```bash
222
- cd dist
223
- npm publish
237
+ bun ./scripts/release/prepareCliPublishPackage.ts --out-dir .tmp/nolo-cli-publish
238
+ cd .tmp/nolo-cli-publish
239
+ npm pack
224
240
  ```
225
241
 
242
+ For this repository, the default release path for `nolo-cli` is the GitHub
243
+ Actions workflow [`.github/workflows/cli-npm-publish.yml`](../../.github/workflows/cli-npm-publish.yml).
244
+ Use local `npm pack` or tarball-based `npm install -g` as preflight verification.
245
+ Do not treat manual local `npm publish` as the normal release route unless you
246
+ are intentionally debugging the CI publish lane.
247
+
226
248
  Key differences between repo-local and published versions:
227
249
  - **Repo-local**: Runs from source (`packages/cli/index.ts`) with workspace
228
250
  dependencies resolved by the monorepo
@@ -73,6 +73,9 @@ export function resolveAgentRuntimeConfigFromRecord(
73
73
  const maxTokens = numberField(record, "max_tokens");
74
74
  const reasoningEffort = stringField(record, "reasoning_effort");
75
75
  const runtimeBinding = objectField(record, "runtimeBinding");
76
+ const runtimeToolPolicy =
77
+ objectField(record, "runtimeToolPolicy") ??
78
+ objectField(runtimeBinding ?? {}, "runtimeToolPolicy");
76
79
  const localWorkspaceMode = localWorkspaceModeField(record);
77
80
  const delegation = objectField(record, "delegation");
78
81
  return {
@@ -92,6 +95,7 @@ export function resolveAgentRuntimeConfigFromRecord(
92
95
  ...(maxTokens !== undefined ? { max_tokens: maxTokens } : {}),
93
96
  ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
94
97
  ...(runtimeBinding ? { runtimeBinding } : {}),
98
+ ...(runtimeToolPolicy ? { runtimeToolPolicy } : {}),
95
99
  ...(localWorkspaceMode ? { localWorkspaceMode } : {}),
96
100
  ...(delegation ? { delegation } : {}),
97
101
  rawRecord: record,
@@ -2,6 +2,7 @@ import type {
2
2
  AgentRuntimeChatMessage,
3
3
  AgentRuntimeHost,
4
4
  AgentRuntimeResult,
5
+ AgentRuntimeToolPolicy,
5
6
  } from "./types";
6
7
 
7
8
  export type AgentRuntimeAgentConfig = {
@@ -21,6 +22,7 @@ export type AgentRuntimeAgentConfig = {
21
22
  max_tokens?: number;
22
23
  reasoning_effort?: string;
23
24
  runtimeBinding?: Record<string, unknown>;
25
+ runtimeToolPolicy?: AgentRuntimeToolPolicy;
24
26
  localWorkspaceMode?: "current" | "task-worktree";
25
27
  delegation?: Record<string, unknown>;
26
28
  rawRecord?: Record<string, unknown>;
@@ -15,6 +15,7 @@ export type HybridRecordStore = {
15
15
  type HybridRecordStoreDeps = {
16
16
  db: HybridRecordKvDb;
17
17
  defaultServer: string;
18
+ fallbackServers?: string[];
18
19
  authToken?: string;
19
20
  fetchImpl?: typeof fetch;
20
21
  };
@@ -23,10 +24,15 @@ function normalizeHybridServer(value: string) {
23
24
  return value.trim().replace(/\/+$/, "");
24
25
  }
25
26
 
26
- function resolveHybridServers(defaultServer: string, preferredServerOrigin?: string | null) {
27
+ function resolveHybridServers(
28
+ defaultServer: string,
29
+ preferredServerOrigin?: string | null,
30
+ fallbackServers: string[] = []
31
+ ) {
27
32
  const raw = [
28
33
  preferredServerOrigin || "",
29
34
  defaultServer,
35
+ ...fallbackServers,
30
36
  ].filter((value) => value.trim().length > 0);
31
37
  return [...new Set(raw.map(normalizeHybridServer))];
32
38
  }
@@ -72,10 +78,16 @@ function shouldReplaceWithHybridRecord(nextRecord: any, currentRecord: any): boo
72
78
  return isHybridTombstoneRecord(nextRecord) && !isHybridTombstoneRecord(currentRecord);
73
79
  }
74
80
 
81
+ function isHybridNotFoundError(error: unknown) {
82
+ const message = error instanceof Error ? error.message : String(error);
83
+ return /not found|notfound|leveldb: not found|key not found/i.test(message);
84
+ }
85
+
75
86
  async function readHybridLocalRecord(db: HybridRecordKvDb, dbKey: string) {
76
87
  try {
77
88
  return await db.get(dbKey);
78
- } catch {
89
+ } catch (error) {
90
+ if (!isHybridNotFoundError(error)) throw error;
79
91
  return null;
80
92
  }
81
93
  }
@@ -117,13 +129,22 @@ export function createHybridRecordStore(
117
129
  if (localRecord) return { ...localRecord, dbKey };
118
130
  if (options?.remote === false) return null;
119
131
 
120
- for (const server of resolveHybridServers(deps.defaultServer, options?.preferredServerOrigin)) {
121
- const remoteRecord = await fetchHybridRemoteRecord({
122
- dbKey,
123
- server,
124
- authToken: deps.authToken,
125
- fetchImpl,
126
- });
132
+ for (const server of resolveHybridServers(
133
+ deps.defaultServer,
134
+ options?.preferredServerOrigin,
135
+ deps.fallbackServers
136
+ )) {
137
+ let remoteRecord = null;
138
+ try {
139
+ remoteRecord = await fetchHybridRemoteRecord({
140
+ dbKey,
141
+ server,
142
+ authToken: deps.authToken,
143
+ fetchImpl,
144
+ });
145
+ } catch {
146
+ continue;
147
+ }
127
148
  if (!remoteRecord) continue;
128
149
  if (shouldCacheHybridRemoteRecord(remoteRecord, localRecord)) {
129
150
  await deps.db.put(dbKey, remoteRecord);
@@ -45,6 +45,11 @@ export {
45
45
  executeLocalToolWithPolicy,
46
46
  resolveLocalToolPolicy,
47
47
  } from "./localToolPolicy";
48
+ export {
49
+ mergeAgentRuntimeToolPolicies,
50
+ normalizeAgentRuntimeToolPolicy,
51
+ resolveEffectiveRuntimeToolPolicy,
52
+ } from "./runtimeToolPolicy";
48
53
  export {
49
54
  buildLocalWorkspaceOpenAiTools,
50
55
  buildLocalWorkspacePolicyToolNames,
@@ -94,5 +99,7 @@ export type {
94
99
  AgentRuntimeMode,
95
100
  AgentRuntimeRequestedMode,
96
101
  AgentRuntimeResult,
102
+ AgentRuntimeToolPolicy,
97
103
  AgentRuntimeToolCall,
104
+ AgentRuntimeWorkspaceMode,
98
105
  } from "./types";
@@ -26,9 +26,11 @@ export type LocalAgentToolEvent = {
26
26
  toolCallId: string;
27
27
  toolName: string;
28
28
  message?: string;
29
+ metadata?: Record<string, unknown>;
29
30
  };
30
31
 
31
- const DEFAULT_LOCAL_AGENT_MAX_TOOL_ROUNDS = 16;
32
+ export const DEFAULT_LOCAL_AGENT_MAX_TOOL_ROUNDS = 128;
33
+ export const LOCAL_AGENT_CONFIG_MISSING_CODE = "LOCAL_AGENT_CONFIG_MISSING";
32
34
 
33
35
  function formatToolExecutionError(args: {
34
36
  toolName: string;
@@ -54,9 +56,10 @@ function buildMessages(args: {
54
56
  history: AgentRuntimeChatMessage[];
55
57
  input: AgentRuntimeMessageContent;
56
58
  }): AgentRuntimeChatMessage[] {
59
+ const systemContent = args.prompt?.trim();
57
60
  return [
58
- ...(args.prompt?.trim()
59
- ? [{ role: "system" as const, content: args.prompt.trim() }]
61
+ ...(systemContent
62
+ ? [{ role: "system" as const, content: systemContent }]
60
63
  : []),
61
64
  ...args.history,
62
65
  { role: "user" as const, content: args.input },
@@ -68,7 +71,13 @@ export async function runLocalAgentTurn(
68
71
  ): Promise<LocalAgentTurnResult> {
69
72
  const agentConfig = await input.adapter.loadAgentConfig(input.agentRef);
70
73
  if (!agentConfig) {
71
- throw new Error(`Local agent config not found: ${input.agentRef}`);
74
+ const error = new Error(`Local agent config not found: ${input.agentRef}`) as Error & {
75
+ code?: string;
76
+ agentRef?: string;
77
+ };
78
+ error.code = LOCAL_AGENT_CONFIG_MISSING_CODE;
79
+ error.agentRef = input.agentRef;
80
+ throw error;
72
81
  }
73
82
 
74
83
  const history = input.continueDialogId
@@ -119,6 +128,7 @@ export async function runLocalAgentTurn(
119
128
  round,
120
129
  toolCallId: toolCall.id,
121
130
  toolName,
131
+ metadata: toolResult.metadata,
122
132
  });
123
133
  } catch (error) {
124
134
  if (!shouldReturnToolExecutionErrors(input.adapter)) throw error;
@@ -16,6 +16,15 @@ const NEVER_LOCAL_TOOLS = new Set([
16
16
  "createAgent",
17
17
  ]);
18
18
 
19
+ const REMOVED_LOCAL_TOOLS = new Set([
20
+ "gitStatus",
21
+ "gitDiff",
22
+ "gitCreateBranch",
23
+ "gitAdd",
24
+ "gitCommit",
25
+ "commitWorkspace",
26
+ ]);
27
+
19
28
  const DEFAULT_LOCAL_TOOLS = new Set([
20
29
  "listWorkspaceFiles",
21
30
  "readWorkspaceFile",
@@ -24,16 +33,12 @@ const DEFAULT_LOCAL_TOOLS = new Set([
24
33
  "listFiles",
25
34
  "readFile",
26
35
  "writeFile",
36
+ "editFile",
37
+ "searchFiles",
27
38
  "searchWorkspace",
28
39
  "applyPatch",
29
- "gitStatus",
30
- "gitDiff",
31
- "gitCreateBranch",
32
- "gitAdd",
33
- "gitCommit",
34
- "commitWorkspace",
35
40
  "runPackageScript",
36
- "execBash",
41
+ "execShell",
37
42
  ]);
38
43
 
39
44
  function parseToolAllowlist(value: string | undefined) {
@@ -68,15 +73,14 @@ export function resolveLocalToolPolicy(args: {
68
73
  toolName: string;
69
74
  }): LocalToolPolicyDecision {
70
75
  const restrictedMode = isRestrictedLocalToolMode(args.env);
71
- const isShellTool = args.toolName === "execShell" || args.toolName === "execBash";
72
- if (isShellTool) {
76
+ if (args.toolName === "execShell") {
73
77
  if (isLocalShellModeEnabled(args.env)) {
74
78
  return { allowed: true, toolName: args.toolName };
75
79
  }
76
80
  return {
77
81
  allowed: false,
78
82
  toolName: args.toolName,
79
- reason: `${args.toolName} requires NOLO_LOCAL_SHELL_MODE=worktree.`,
83
+ reason: "execShell requires NOLO_LOCAL_SHELL_MODE=worktree.",
80
84
  };
81
85
  }
82
86
 
@@ -88,6 +92,14 @@ export function resolveLocalToolPolicy(args: {
88
92
  };
89
93
  }
90
94
 
95
+ if (REMOVED_LOCAL_TOOLS.has(args.toolName)) {
96
+ return {
97
+ allowed: false,
98
+ toolName: args.toolName,
99
+ reason: `${args.toolName} has been removed from the local runtime tool surface. Use execShell in shell-enabled workspace runs when shell is needed.`,
100
+ };
101
+ }
102
+
91
103
  const allowedByEnv = new Set(parseToolAllowlist(args.env.NOLO_LOCAL_ALLOWED_TOOLS));
92
104
  const agentTools = new Set(args.agentToolNames ?? []);
93
105
  if (!restrictedMode && agentTools.has(args.toolName)) {