nolo-cli 0.1.33 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -5
- package/agent-runtime/agentRecordConfig.ts +8 -0
- package/agent-runtime/hostAdapter.ts +9 -1
- package/agent-runtime/index.ts +10 -0
- package/agent-runtime/localLoop.ts +49 -2
- package/agent-runtime/localToolPolicy.ts +71 -22
- package/agent-runtime/localWorkspaceTools.ts +5 -2
- package/agent-runtime/openAiCompatibleProvider.ts +7 -3
- package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
- package/agent-runtime/platformChatProvider.ts +48 -71
- package/agent-runtime/providerResolution.ts +242 -0
- package/agent-runtime/runtimeToolPolicy.ts +33 -0
- package/agent-runtime/shellCommandPolicy.ts +120 -0
- package/agentCommandSupport.ts +4 -0
- package/agentInternalCommandEntries.ts +2 -0
- package/agentListCommands.ts +74 -12
- package/agentListHelpers.ts +56 -1
- package/agentPullCommand.ts +6 -11
- package/agentRecordCommands.ts +87 -2
- package/agentRecordHelpers.ts +132 -9
- package/agentRuntimeLocal.ts +3 -0
- package/ai/tools/toolNameAliases.ts +70 -0
- package/cli/agentCommandSupport.ts +4 -0
- package/cli/agentInternalCommandEntries.ts +2 -0
- package/cli/agentListCommands.ts +74 -12
- package/cli/agentListHelpers.ts +56 -1
- package/cli/agentPullCommand.ts +6 -11
- package/cli/agentRecordCommands.ts +87 -2
- package/cli/agentRecordHelpers.ts +132 -9
- package/cli/agentRuntimeLocal.ts +3 -0
- package/cli/cliCommandDispatch.ts +2 -2
- package/cli/cliEnvHelpers.ts +80 -12
- package/cli/cliSpaceHelpers.ts +39 -0
- package/cli/client/agentRun.ts +1 -0
- package/cli/client/localRuntimeAdapter.ts +44 -7
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorWebSocketTarget.ts +10 -3
- package/cli/dialogCommands.ts +446 -0
- package/cli/dialogInternalCommandEntries.ts +13 -0
- package/cli/globalRecordOperations.ts +258 -0
- package/cli/internalCommandEntries.ts +2 -0
- package/cli/localRuntimeAuthority.ts +182 -0
- package/cli/localRuntimeDb.ts +2 -4
- package/cli/machineCommands.ts +1 -1
- package/cli/machineWsRunDispatch.ts +29 -12
- package/cli/machineWsSession.ts +2 -0
- package/cli/offlineMarxistsAgentCommand.ts +1 -1
- package/cli/runtimeDoctorCommands.ts +65 -9
- package/cli/tableCommands.ts +1 -1
- package/cli/taskRunCommand.ts +1 -1
- package/cliCommandDispatch.ts +2 -2
- package/cliEnvHelpers.ts +80 -12
- package/cliSpaceHelpers.ts +39 -0
- package/client/agentRun.ts +1 -0
- package/client/localProviderResolver.test.ts +26 -2
- package/client/localRuntimeAdapter.test.ts +287 -3
- package/client/localRuntimeAdapter.ts +44 -7
- package/commandRegistry.ts +2 -2
- package/connectorWebSocketTarget.ts +10 -3
- package/database/server/authorityStoreTypes.ts +31 -0
- package/database/server/cliAuthorityBrokerClient.ts +227 -0
- package/database/server/cliAuthorityBrokerServer.ts +283 -0
- package/database/server/cliAuthorityBrokerTypes.ts +43 -0
- package/database/server/cliAuthorityStoreDriver.ts +98 -0
- package/database/server/legacyServerDb.ts +58 -0
- package/database/server/levelAuthorityStore.ts +95 -0
- package/database/tombstones.ts +52 -0
- package/database/userDataMerge.ts +95 -0
- package/dialogCommands.ts +446 -0
- package/dialogInternalCommandEntries.ts +13 -0
- package/globalRecordOperations.ts +258 -0
- package/internalCommandEntries.ts +2 -0
- package/localRuntimeAuthority.ts +182 -0
- package/localRuntimeDb.ts +2 -4
- package/machineCommands.ts +1 -1
- package/machineWsRunDispatch.ts +29 -12
- package/machineWsSession.ts +2 -0
- package/offlineMarxistsAgentCommand.ts +1 -1
- package/package.json +24 -4
- package/runtimeDoctorCommands.ts +65 -9
- package/tableCommands.ts +1 -1
- package/taskRunCommand.ts +1 -1
- package/database/server/db.ts +0 -118
- package/database/server/ensureDbOpen.ts +0 -12
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import type { AgentRuntimeAgentConfig } from "./hostAdapter";
|
|
2
|
+
import { pickAgentRuntimeInferenceOptions } from "./agentConfigOptions";
|
|
3
|
+
|
|
4
|
+
type EnvLike = Record<string, string | undefined>;
|
|
5
|
+
|
|
6
|
+
const PROVIDER_ENDPOINTS: Record<string, string> = {
|
|
7
|
+
deepinfra: "https://api.deepinfra.com/v1/openai/chat/completions",
|
|
8
|
+
deepseek: "https://api.deepseek.com/chat/completions",
|
|
9
|
+
fireworks: "https://api.fireworks.ai/inference/v1/chat/completions",
|
|
10
|
+
google: "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions",
|
|
11
|
+
mistral: "https://api.mistral.ai/v1/chat/completions",
|
|
12
|
+
mimo: "https://token-plan-cn.xiaomimimo.com/v1/chat/completions",
|
|
13
|
+
openai: "https://api.openai.com/v1/chat/completions",
|
|
14
|
+
openrouter: "https://openrouter.ai/api/v1/chat/completions",
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export type AgentProviderMode = "cli" | "platform" | "custom";
|
|
18
|
+
export type ProviderExecutionTransport = "direct" | "proxy";
|
|
19
|
+
|
|
20
|
+
type ProviderExecutionPlanBase = {
|
|
21
|
+
mode: AgentProviderMode;
|
|
22
|
+
transport: ProviderExecutionTransport;
|
|
23
|
+
model: string;
|
|
24
|
+
provider: string;
|
|
25
|
+
requestOptions: Record<string, number | string>;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type CliProviderExecutionPlan = ProviderExecutionPlanBase & {
|
|
29
|
+
mode: "cli";
|
|
30
|
+
transport: "direct";
|
|
31
|
+
cliProvider: string;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export type DirectProviderExecutionPlan = ProviderExecutionPlanBase & {
|
|
35
|
+
mode: "platform" | "custom";
|
|
36
|
+
transport: "direct";
|
|
37
|
+
endpoint: string;
|
|
38
|
+
apiKey: string;
|
|
39
|
+
apiKeyHeader?: string;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type ProxyProviderExecutionPlan = ProviderExecutionPlanBase & {
|
|
43
|
+
mode: "platform" | "custom";
|
|
44
|
+
transport: "proxy";
|
|
45
|
+
endpoint: string;
|
|
46
|
+
serverUrl: string;
|
|
47
|
+
authToken: string;
|
|
48
|
+
agentKey: string;
|
|
49
|
+
apiSource?: string;
|
|
50
|
+
apiKey?: string;
|
|
51
|
+
apiKeyHeader?: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export type ProviderExecutionPlan =
|
|
55
|
+
| CliProviderExecutionPlan
|
|
56
|
+
| DirectProviderExecutionPlan
|
|
57
|
+
| ProxyProviderExecutionPlan;
|
|
58
|
+
|
|
59
|
+
export function trimTrailingSlash(value: string) {
|
|
60
|
+
return value.trim().replace(/\/+$/, "");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function resolveChatCompletionsEndpoint(value: string) {
|
|
64
|
+
const trimmed = trimTrailingSlash(value);
|
|
65
|
+
return trimmed.endsWith("/chat/completions")
|
|
66
|
+
? trimmed
|
|
67
|
+
: `${trimmed}/chat/completions`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function resolveOpenAiCompatibleBaseUrl(env: EnvLike) {
|
|
71
|
+
return env.NOLO_LOCAL_OPENAI_BASE_URL || env.OPENAI_BASE_URL || "https://api.openai.com/v1";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function resolveOpenAiCompatibleApiKey(env: EnvLike) {
|
|
75
|
+
return env.OPENAI_API_KEY || env.NOLO_LOCAL_OPENAI_API_KEY || "";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function resolvePlatformServerUrl(env: EnvLike) {
|
|
79
|
+
return trimTrailingSlash(env.NOLO_SERVER || env.BASE_URL || "https://nolo.chat");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function resolvePlatformAuthToken(env: EnvLike) {
|
|
83
|
+
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function canUsePlatformChatProvider(env: EnvLike) {
|
|
87
|
+
return Boolean(resolvePlatformAuthToken(env));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function hasDirectOpenAiCompatibleProvider(env: EnvLike) {
|
|
91
|
+
return Boolean(
|
|
92
|
+
env.NOLO_LOCAL_OPENAI_API_KEY ||
|
|
93
|
+
env.OPENAI_API_KEY ||
|
|
94
|
+
env.NOLO_LOCAL_OPENAI_BASE_URL ||
|
|
95
|
+
env.OPENAI_BASE_URL ||
|
|
96
|
+
env.OLLAMA_BASE_URL
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function resolvePlatformProviderEndpoint(agentConfig: AgentRuntimeAgentConfig) {
|
|
101
|
+
const customProviderUrl = agentConfig.customProviderUrl?.trim();
|
|
102
|
+
if (customProviderUrl) return resolveChatCompletionsEndpoint(customProviderUrl);
|
|
103
|
+
|
|
104
|
+
const provider = agentConfig.provider?.trim().toLowerCase();
|
|
105
|
+
if (!provider) {
|
|
106
|
+
throw new Error("Platform chat provider requires agentConfig.provider.");
|
|
107
|
+
}
|
|
108
|
+
const endpoint = PROVIDER_ENDPOINTS[provider];
|
|
109
|
+
if (!endpoint) {
|
|
110
|
+
throw new Error(`Platform chat provider does not support provider "${provider}".`);
|
|
111
|
+
}
|
|
112
|
+
return endpoint;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function resolveAgentProviderMode(agentConfig: AgentRuntimeAgentConfig): AgentProviderMode {
|
|
116
|
+
if (agentConfig.apiSource === "cli" || agentConfig.provider === "cli" || agentConfig.cliProvider) {
|
|
117
|
+
return "cli";
|
|
118
|
+
}
|
|
119
|
+
if (agentConfig.apiSource === "custom" || agentConfig.customProviderUrl) {
|
|
120
|
+
return "custom";
|
|
121
|
+
}
|
|
122
|
+
return "platform";
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function shouldUsePlatformProxyTransport(
|
|
126
|
+
env: EnvLike,
|
|
127
|
+
agentConfig?: AgentRuntimeAgentConfig
|
|
128
|
+
) {
|
|
129
|
+
if (agentConfig?.apiSource === "platform" || agentConfig?.useServerProxy === true) return true;
|
|
130
|
+
if (env.NOLO_LOCAL_LLM === "platform") return true;
|
|
131
|
+
if (env.NOLO_LOCAL_LLM === "direct") return false;
|
|
132
|
+
return !hasDirectOpenAiCompatibleProvider(env) && canUsePlatformChatProvider(env);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export function resolveProviderAuthHeaderName(args: {
|
|
136
|
+
endpoint: string;
|
|
137
|
+
apiKeyHeader?: string;
|
|
138
|
+
}) {
|
|
139
|
+
const explicitHeader = args.apiKeyHeader?.trim();
|
|
140
|
+
if (explicitHeader) return explicitHeader;
|
|
141
|
+
if (/xiaomimimo\.com/i.test(args.endpoint)) return "api-key";
|
|
142
|
+
return "Authorization";
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function buildProviderAuthHeaders(args: {
|
|
146
|
+
endpoint: string;
|
|
147
|
+
apiKey: string;
|
|
148
|
+
apiKeyHeader?: string;
|
|
149
|
+
}) {
|
|
150
|
+
if (!args.apiKey) return {};
|
|
151
|
+
const headerName = resolveProviderAuthHeaderName(args);
|
|
152
|
+
return headerName.toLowerCase() === "authorization"
|
|
153
|
+
? { Authorization: `Bearer ${args.apiKey}` }
|
|
154
|
+
: { [headerName]: args.apiKey };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function buildProviderExecutionPlan(args: {
|
|
158
|
+
agentConfig: AgentRuntimeAgentConfig;
|
|
159
|
+
env: EnvLike;
|
|
160
|
+
runtimeKind: "local" | "desktop" | "server";
|
|
161
|
+
}): ProviderExecutionPlan {
|
|
162
|
+
const { agentConfig, env } = args;
|
|
163
|
+
const mode = resolveAgentProviderMode(agentConfig);
|
|
164
|
+
const model = agentConfig.model || "gpt-4.1-mini";
|
|
165
|
+
const requestOptions = pickAgentRuntimeInferenceOptions(agentConfig);
|
|
166
|
+
|
|
167
|
+
if (mode === "cli") {
|
|
168
|
+
return {
|
|
169
|
+
mode,
|
|
170
|
+
transport: "direct",
|
|
171
|
+
cliProvider: agentConfig.cliProvider || agentConfig.provider || "codex",
|
|
172
|
+
model,
|
|
173
|
+
provider: agentConfig.provider || "cli",
|
|
174
|
+
requestOptions,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (mode === "custom") {
|
|
179
|
+
const endpoint = resolveChatCompletionsEndpoint(agentConfig.customProviderUrl || resolveOpenAiCompatibleBaseUrl(env));
|
|
180
|
+
const apiKey = agentConfig.apiKey?.trim() || agentConfig.apiKeyFromAgentKey?.trim() || "";
|
|
181
|
+
const apiKeyHeader = resolveProviderAuthHeaderName({
|
|
182
|
+
endpoint,
|
|
183
|
+
apiKeyHeader: agentConfig.apiKeyHeader,
|
|
184
|
+
});
|
|
185
|
+
if (agentConfig.useServerProxy) {
|
|
186
|
+
return {
|
|
187
|
+
mode,
|
|
188
|
+
transport: "proxy",
|
|
189
|
+
model,
|
|
190
|
+
provider: agentConfig.provider || "custom",
|
|
191
|
+
endpoint,
|
|
192
|
+
requestOptions,
|
|
193
|
+
serverUrl: resolvePlatformServerUrl(env),
|
|
194
|
+
authToken: resolvePlatformAuthToken(env),
|
|
195
|
+
agentKey: agentConfig.key,
|
|
196
|
+
...(agentConfig.apiSource ? { apiSource: agentConfig.apiSource } : {}),
|
|
197
|
+
...(apiKey ? { apiKey } : {}),
|
|
198
|
+
...(apiKeyHeader ? { apiKeyHeader } : {}),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
mode,
|
|
203
|
+
transport: "direct",
|
|
204
|
+
model,
|
|
205
|
+
provider: agentConfig.provider || "custom",
|
|
206
|
+
endpoint,
|
|
207
|
+
requestOptions,
|
|
208
|
+
apiKey,
|
|
209
|
+
...(apiKeyHeader ? { apiKeyHeader } : {}),
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const provider = agentConfig.provider || agentConfig.apiSource || "openai";
|
|
214
|
+
const endpoint = shouldUsePlatformProxyTransport(env, agentConfig)
|
|
215
|
+
? resolvePlatformProviderEndpoint(agentConfig)
|
|
216
|
+
: resolveChatCompletionsEndpoint(resolveOpenAiCompatibleBaseUrl(env));
|
|
217
|
+
if (shouldUsePlatformProxyTransport(env, agentConfig)) {
|
|
218
|
+
return {
|
|
219
|
+
mode,
|
|
220
|
+
transport: "proxy",
|
|
221
|
+
model,
|
|
222
|
+
provider,
|
|
223
|
+
endpoint,
|
|
224
|
+
requestOptions,
|
|
225
|
+
serverUrl: resolvePlatformServerUrl(env),
|
|
226
|
+
authToken: resolvePlatformAuthToken(env),
|
|
227
|
+
agentKey: agentConfig.key,
|
|
228
|
+
...(agentConfig.apiSource ? { apiSource: agentConfig.apiSource } : {}),
|
|
229
|
+
...(agentConfig.apiKey?.trim() ? { apiKey: agentConfig.apiKey.trim() } : {}),
|
|
230
|
+
...(agentConfig.apiKeyHeader?.trim() ? { apiKeyHeader: agentConfig.apiKeyHeader.trim() } : {}),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
mode,
|
|
235
|
+
transport: "direct",
|
|
236
|
+
model,
|
|
237
|
+
provider: agentConfig.provider || "openai-compatible",
|
|
238
|
+
endpoint,
|
|
239
|
+
requestOptions,
|
|
240
|
+
apiKey: resolveOpenAiCompatibleApiKey(env),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
@@ -46,6 +46,16 @@ export function normalizeAgentRuntimeToolPolicy(
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
+
function runtimeBindingRecord(
|
|
50
|
+
runtimeBinding: unknown,
|
|
51
|
+
): Record<string, unknown> | undefined {
|
|
52
|
+
return isRecord(runtimeBinding) ? runtimeBinding : undefined;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function runtimeToolNamesFromPolicy(policy: AgentRuntimeToolPolicy | undefined) {
|
|
56
|
+
return stringArray(policy?.runtimeTools);
|
|
57
|
+
}
|
|
58
|
+
|
|
49
59
|
export function mergeAgentRuntimeToolPolicies(
|
|
50
60
|
base?: AgentRuntimeToolPolicy,
|
|
51
61
|
override?: AgentRuntimeToolPolicy,
|
|
@@ -77,6 +87,29 @@ export function mergeAgentRuntimeToolPolicies(
|
|
|
77
87
|
};
|
|
78
88
|
}
|
|
79
89
|
|
|
90
|
+
export function resolveRequestedRuntimeToolNames(args: {
|
|
91
|
+
agentConfig: Pick<
|
|
92
|
+
AgentRuntimeAgentConfig,
|
|
93
|
+
"toolNames" | "runtimeToolPolicy" | "runtimeBinding"
|
|
94
|
+
>;
|
|
95
|
+
}) {
|
|
96
|
+
const runtimeBinding = runtimeBindingRecord(args.agentConfig.runtimeBinding);
|
|
97
|
+
const bindingPolicy = normalizeAgentRuntimeToolPolicy(
|
|
98
|
+
runtimeBinding?.runtimeToolPolicy,
|
|
99
|
+
);
|
|
100
|
+
const bindingSnapshotPolicy = normalizeAgentRuntimeToolPolicy(
|
|
101
|
+
runtimeBinding?.runtimeToolPolicySnapshot,
|
|
102
|
+
);
|
|
103
|
+
return unique([
|
|
104
|
+
...stringArray(args.agentConfig.toolNames),
|
|
105
|
+
...runtimeToolNamesFromPolicy(
|
|
106
|
+
normalizeAgentRuntimeToolPolicy(args.agentConfig.runtimeToolPolicy),
|
|
107
|
+
),
|
|
108
|
+
...runtimeToolNamesFromPolicy(bindingPolicy),
|
|
109
|
+
...runtimeToolNamesFromPolicy(bindingSnapshotPolicy),
|
|
110
|
+
]);
|
|
111
|
+
}
|
|
112
|
+
|
|
80
113
|
function policyRequestsHostedWorkspace(policy: AgentRuntimeToolPolicy | undefined) {
|
|
81
114
|
if (!policy) return false;
|
|
82
115
|
const workspaceMode = policy.workspace?.mode;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
export type ShellCommandPolicyVerdict = "allowed" | "forbidden";
|
|
2
|
+
|
|
3
|
+
export type ShellCommandPolicyResult =
|
|
4
|
+
| {
|
|
5
|
+
verdict: "allowed";
|
|
6
|
+
}
|
|
7
|
+
| {
|
|
8
|
+
verdict: "forbidden";
|
|
9
|
+
code: "destructive_action_requires_confirmation";
|
|
10
|
+
reason: string;
|
|
11
|
+
policy: {
|
|
12
|
+
capability: "destructive_action";
|
|
13
|
+
target: "shell_command";
|
|
14
|
+
detail: string;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const DESTRUCTIVE_NEGATION_PATTERNS = [
|
|
19
|
+
"别删",
|
|
20
|
+
"别删除",
|
|
21
|
+
"不要删",
|
|
22
|
+
"不要删除",
|
|
23
|
+
"别乱删",
|
|
24
|
+
"别动",
|
|
25
|
+
"不要动",
|
|
26
|
+
"不要清理",
|
|
27
|
+
"不要清空",
|
|
28
|
+
"不要重置",
|
|
29
|
+
"don't delete",
|
|
30
|
+
"do not delete",
|
|
31
|
+
"don't remove",
|
|
32
|
+
"do not remove",
|
|
33
|
+
"without deleting",
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
const EXPLICIT_DESTRUCTIVE_REQUEST_PATTERNS = [
|
|
37
|
+
"删除",
|
|
38
|
+
"删掉",
|
|
39
|
+
"移除",
|
|
40
|
+
"清理",
|
|
41
|
+
"清空",
|
|
42
|
+
"重置",
|
|
43
|
+
"drop",
|
|
44
|
+
"delete",
|
|
45
|
+
"remove",
|
|
46
|
+
"erase",
|
|
47
|
+
"clean up",
|
|
48
|
+
"reset",
|
|
49
|
+
] as const;
|
|
50
|
+
|
|
51
|
+
const SHELL_DESTRUCTIVE_PATTERNS = [
|
|
52
|
+
/\brm\s+(-[^\n\r]*\s+)?/i,
|
|
53
|
+
/\bremove-item\b/i,
|
|
54
|
+
/\bdel\s+\/?[a-z]*\s+/i,
|
|
55
|
+
/\berase\s+/i,
|
|
56
|
+
/\brmdir\s+/i,
|
|
57
|
+
/\brd\s+\/s\b/i,
|
|
58
|
+
/\bgit\s+reset\s+(--hard\b|--merge\b|--keep\b)/i,
|
|
59
|
+
/\bgit\s+clean\s+-[^\n\r]*f/i,
|
|
60
|
+
/\bgit\s+checkout\s+--\b/i,
|
|
61
|
+
/\bgit\s+restore\s+--source=/i,
|
|
62
|
+
] as const;
|
|
63
|
+
|
|
64
|
+
const normalizeUserInput = (value: unknown): string =>
|
|
65
|
+
typeof value === "string" ? value.trim().toLowerCase() : "";
|
|
66
|
+
|
|
67
|
+
const containsAny = (text: string, patterns: readonly string[]): boolean =>
|
|
68
|
+
patterns.some((pattern) => text.includes(pattern));
|
|
69
|
+
|
|
70
|
+
const hasExplicitDestructivePermission = (userInput: unknown): boolean => {
|
|
71
|
+
const normalized = normalizeUserInput(userInput);
|
|
72
|
+
if (!normalized) return false;
|
|
73
|
+
if (containsAny(normalized, DESTRUCTIVE_NEGATION_PATTERNS)) return false;
|
|
74
|
+
return containsAny(normalized, EXPLICIT_DESTRUCTIVE_REQUEST_PATTERNS);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const buildCombinedShellInput = (args: {
|
|
78
|
+
command?: unknown;
|
|
79
|
+
input?: unknown;
|
|
80
|
+
}): string =>
|
|
81
|
+
[
|
|
82
|
+
typeof args.command === "string" ? args.command : "",
|
|
83
|
+
typeof args.input === "string" ? args.input : "",
|
|
84
|
+
]
|
|
85
|
+
.filter(Boolean)
|
|
86
|
+
.join("\n");
|
|
87
|
+
|
|
88
|
+
export function isDestructiveShellCommand(args: {
|
|
89
|
+
command?: unknown;
|
|
90
|
+
input?: unknown;
|
|
91
|
+
}): boolean {
|
|
92
|
+
const combined = buildCombinedShellInput(args);
|
|
93
|
+
if (!combined.trim()) return false;
|
|
94
|
+
return SHELL_DESTRUCTIVE_PATTERNS.some((pattern) => pattern.test(combined));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function evaluateShellCommandPolicy(args: {
|
|
98
|
+
command?: unknown;
|
|
99
|
+
input?: unknown;
|
|
100
|
+
userInput?: unknown;
|
|
101
|
+
}): ShellCommandPolicyResult {
|
|
102
|
+
if (
|
|
103
|
+
isDestructiveShellCommand(args) &&
|
|
104
|
+
!hasExplicitDestructivePermission(args.userInput)
|
|
105
|
+
) {
|
|
106
|
+
return {
|
|
107
|
+
verdict: "forbidden",
|
|
108
|
+
code: "destructive_action_requires_confirmation",
|
|
109
|
+
reason:
|
|
110
|
+
"当前运行默认禁止自动执行可能删除用户内容的 shell 命令。只有当用户在当前请求里明确要求删除/清理时,才能继续;否则请停止并先说明限制。",
|
|
111
|
+
policy: {
|
|
112
|
+
capability: "destructive_action",
|
|
113
|
+
target: "shell_command",
|
|
114
|
+
detail: "execShell destructive command",
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return { verdict: "allowed" };
|
|
120
|
+
}
|
package/agentCommandSupport.ts
CHANGED
|
@@ -20,6 +20,10 @@ export type LocalCliExecutor = (
|
|
|
20
20
|
export type LocalRuntimeProbeResult = {
|
|
21
21
|
ok: boolean;
|
|
22
22
|
dbPath: string;
|
|
23
|
+
authorityDriver?: string;
|
|
24
|
+
authorityEndpoint?: string;
|
|
25
|
+
authorityMetadataPath?: string;
|
|
26
|
+
authorityHealthPath?: string;
|
|
23
27
|
agentFound: boolean;
|
|
24
28
|
agentKey?: string;
|
|
25
29
|
error?: string;
|
|
@@ -8,6 +8,7 @@ import { runAgentPullCommand } from "./agentPullCommand";
|
|
|
8
8
|
import {
|
|
9
9
|
runAgentReadCommand,
|
|
10
10
|
runAgentUpdateCommand,
|
|
11
|
+
runAgentCreateCommand,
|
|
11
12
|
} from "./agentRecordCommands";
|
|
12
13
|
import { runAgentRunCommand } from "./agentRunCommand";
|
|
13
14
|
import {
|
|
@@ -21,6 +22,7 @@ export function getAgentInternalCommandEntries(): CommandEntry[] {
|
|
|
21
22
|
return [
|
|
22
23
|
createEnvScriptDirCommand(["chat"], "Chat with an agent", runAgentRunCommand),
|
|
23
24
|
createEnvCommand(["agent", "list"], "List owned agents", runAgentListCommand),
|
|
25
|
+
createEnvCommand(["agent", "create"], "Create a new agent", runAgentCreateCommand),
|
|
24
26
|
createEnvCommand(["agent", "pull"], "Cache an agent for local runs", runAgentPullCommand),
|
|
25
27
|
createEnvCommand(["agent", "read"], "Read a single agent", runAgentReadCommand),
|
|
26
28
|
createEnvScriptDirCommand(["agent", "run"], "Run an agent", runAgentRunCommand),
|
package/agentListCommands.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
|
|
2
2
|
import {
|
|
3
|
+
decorateAgentsWithPublicStatusAcrossServers,
|
|
3
4
|
listLocalCachedAgents,
|
|
5
|
+
listRemoteAgentsAcrossServers,
|
|
4
6
|
listRemoteAgents,
|
|
5
7
|
parseAgentListArgs,
|
|
6
8
|
type ListedAgent,
|
|
@@ -9,11 +11,15 @@ import {
|
|
|
9
11
|
queryUserRecords,
|
|
10
12
|
readDbRecord,
|
|
11
13
|
} from "./agentRecordHelpers";
|
|
14
|
+
import { buildSpaceLookup, getSpaceContentKeys } from "./cliSpaceHelpers";
|
|
12
15
|
import {
|
|
13
16
|
parseUserIdFromAuthToken,
|
|
17
|
+
readOption,
|
|
14
18
|
resolveAuthToken,
|
|
19
|
+
resolveServerCandidates,
|
|
15
20
|
resolveServerUrl,
|
|
16
21
|
} from "./cliEnvHelpers";
|
|
22
|
+
import { readLiveDbRecordAfterTombstoneMerge } from "./globalRecordOperations";
|
|
17
23
|
|
|
18
24
|
export async function runAgentListCommand(
|
|
19
25
|
args: string[],
|
|
@@ -22,8 +28,9 @@ export async function runAgentListCommand(
|
|
|
22
28
|
const env = deps.env ?? process.env;
|
|
23
29
|
const output = deps.output ?? process.stdout;
|
|
24
30
|
const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
|
|
31
|
+
const spaceInput = readOption(args, "--space") ?? readOption(args, "--space-id");
|
|
25
32
|
|
|
26
|
-
const authToken = resolveAuthToken(env);
|
|
33
|
+
const authToken = resolveAuthToken(args, env);
|
|
27
34
|
if (!authToken) {
|
|
28
35
|
output.write("[nolo] agent list requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
29
36
|
return 1;
|
|
@@ -35,34 +42,79 @@ export async function runAgentListCommand(
|
|
|
35
42
|
return 1;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
const db = deps.db ?? await getReadableCliDb(output);
|
|
39
45
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
40
46
|
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
47
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
48
|
+
const serverUrls = resolveServerCandidates(args, env, serverUrl);
|
|
41
49
|
|
|
42
50
|
try {
|
|
43
51
|
let agents: ListedAgent[];
|
|
44
|
-
let source: "local-cache" | "remote-cache";
|
|
52
|
+
let source: "local-cache" | "remote-cache" | "global-cache";
|
|
53
|
+
let serverFailures: Array<{ serverUrl: string; error: string }> = [];
|
|
45
54
|
try {
|
|
46
|
-
|
|
47
|
-
source = "local-cache";
|
|
48
|
-
} catch {
|
|
49
|
-
const serverUrl = resolveServerUrl(env);
|
|
50
|
-
agents = await listRemoteAgents({
|
|
55
|
+
const remoteResult = await listRemoteAgentsAcrossServers({
|
|
51
56
|
authToken,
|
|
52
57
|
fallbackFetchImpl,
|
|
53
58
|
fetchImpl,
|
|
54
59
|
includeLegacy,
|
|
55
|
-
|
|
60
|
+
serverUrls,
|
|
56
61
|
userId,
|
|
57
|
-
queryUserRecords,
|
|
58
|
-
readDbRecord,
|
|
59
62
|
});
|
|
60
|
-
|
|
63
|
+
agents = remoteResult.agents;
|
|
64
|
+
serverFailures = remoteResult.failures;
|
|
65
|
+
source = "global-cache";
|
|
66
|
+
} catch {
|
|
67
|
+
try {
|
|
68
|
+
const db = deps.db ?? await getReadableCliDb(output);
|
|
69
|
+
agents = await listLocalCachedAgents({ db, userId });
|
|
70
|
+
source = "local-cache";
|
|
71
|
+
} catch {
|
|
72
|
+
agents = await listRemoteAgents({
|
|
73
|
+
authToken,
|
|
74
|
+
fallbackFetchImpl,
|
|
75
|
+
fetchImpl,
|
|
76
|
+
includeLegacy,
|
|
77
|
+
serverUrl,
|
|
78
|
+
userId,
|
|
79
|
+
queryUserRecords,
|
|
80
|
+
readDbRecord,
|
|
81
|
+
});
|
|
82
|
+
source = "remote-cache";
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
if (!includeLegacy) {
|
|
64
87
|
agents = agents.filter((agent) => !agent.privateKey.startsWith("cybot-"));
|
|
65
88
|
}
|
|
89
|
+
let resolvedSpaceId: string | null = null;
|
|
90
|
+
if (spaceInput) {
|
|
91
|
+
const { spaceId, spaceKey } = buildSpaceLookup(spaceInput);
|
|
92
|
+
resolvedSpaceId = spaceId;
|
|
93
|
+
const spaceRead = await readLiveDbRecordAfterTombstoneMerge({
|
|
94
|
+
authToken,
|
|
95
|
+
dbKey: spaceKey,
|
|
96
|
+
fallbackFetchImpl,
|
|
97
|
+
fetchImpl,
|
|
98
|
+
serverUrls,
|
|
99
|
+
});
|
|
100
|
+
serverFailures = [...serverFailures, ...spaceRead.failures];
|
|
101
|
+
const spaceRecord = spaceRead.record;
|
|
102
|
+
const spaceContentKeys = getSpaceContentKeys(spaceRecord);
|
|
103
|
+
agents = agents.filter((agent) =>
|
|
104
|
+
spaceContentKeys.has(agent.privateKey) ||
|
|
105
|
+
spaceContentKeys.has(agent.publicKey) ||
|
|
106
|
+
spaceContentKeys.has(agent.id)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (source === "global-cache") {
|
|
110
|
+
await decorateAgentsWithPublicStatusAcrossServers({
|
|
111
|
+
agents,
|
|
112
|
+
authToken,
|
|
113
|
+
fallbackFetchImpl,
|
|
114
|
+
fetchImpl,
|
|
115
|
+
serverUrls,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
66
118
|
if (publicOnly) {
|
|
67
119
|
agents = agents.filter((agent) => agent.publicRecordExists);
|
|
68
120
|
}
|
|
@@ -75,6 +127,9 @@ export async function runAgentListCommand(
|
|
|
75
127
|
if (wantJson) {
|
|
76
128
|
output.write(JSON.stringify({
|
|
77
129
|
userId,
|
|
130
|
+
...(resolvedSpaceId ? { spaceId: resolvedSpaceId } : {}),
|
|
131
|
+
targetServers: serverUrls,
|
|
132
|
+
...(serverFailures.length ? { serverFailures } : {}),
|
|
78
133
|
total: agents.length,
|
|
79
134
|
publicCount: agents.filter((agent) => agent.publicRecordExists).length,
|
|
80
135
|
source,
|
|
@@ -85,6 +140,13 @@ export async function runAgentListCommand(
|
|
|
85
140
|
}
|
|
86
141
|
|
|
87
142
|
output.write(`userId: ${userId}\n`);
|
|
143
|
+
if (resolvedSpaceId) {
|
|
144
|
+
output.write(`spaceId: ${resolvedSpaceId}\n`);
|
|
145
|
+
}
|
|
146
|
+
output.write(`targetServers: ${serverUrls.join(", ")}\n`);
|
|
147
|
+
if (serverFailures.length) {
|
|
148
|
+
output.write(`serverFailures: ${serverFailures.length}\n`);
|
|
149
|
+
}
|
|
88
150
|
output.write(`total agents: ${agents.length}\n`);
|
|
89
151
|
output.write(`public agents: ${agents.filter((agent) => agent.publicRecordExists).length}\n`);
|
|
90
152
|
output.write(`source: ${source}\n`);
|
package/agentListHelpers.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
2
|
+
import {
|
|
3
|
+
listUserRecordsFromServers,
|
|
4
|
+
readLiveDbRecordAfterTombstoneMerge,
|
|
5
|
+
} from "./globalRecordOperations";
|
|
2
6
|
|
|
3
7
|
export type ListedAgent = {
|
|
4
8
|
id: string;
|
|
@@ -93,7 +97,7 @@ export async function listLocalCachedAgents(args: {
|
|
|
93
97
|
}) {
|
|
94
98
|
const privateRecords = new Map<string, any>();
|
|
95
99
|
const publicKeys = new Set<string>();
|
|
96
|
-
for await (const [key, value] of args.db.iterator()) {
|
|
100
|
+
for await (const [key, value] of args.db.iterator({ gte: "", lte: "\uffff" })) {
|
|
97
101
|
if (typeof key !== "string" || !value || typeof value !== "object") continue;
|
|
98
102
|
if (key.startsWith(`agent-${args.userId}-`) || key.startsWith(`cybot-${args.userId}-`)) {
|
|
99
103
|
privateRecords.set(key, { ...(value as Record<string, unknown>), dbKey: key });
|
|
@@ -185,3 +189,54 @@ export async function listRemoteAgents(args: {
|
|
|
185
189
|
);
|
|
186
190
|
return agents;
|
|
187
191
|
}
|
|
192
|
+
|
|
193
|
+
export async function listRemoteAgentsAcrossServers(args: {
|
|
194
|
+
authToken: string;
|
|
195
|
+
fallbackFetchImpl?: typeof fetch;
|
|
196
|
+
fetchImpl: typeof fetch;
|
|
197
|
+
includeLegacy: boolean;
|
|
198
|
+
serverUrls: string[];
|
|
199
|
+
userId: string;
|
|
200
|
+
}) {
|
|
201
|
+
const remoteResult = await listUserRecordsFromServers({
|
|
202
|
+
authToken: args.authToken,
|
|
203
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
204
|
+
fetchImpl: args.fetchImpl,
|
|
205
|
+
label: "agent query",
|
|
206
|
+
serverUrls: args.serverUrls,
|
|
207
|
+
type: args.includeLegacy ? ["agent", "cybot"] : "agent",
|
|
208
|
+
userId: args.userId,
|
|
209
|
+
});
|
|
210
|
+
const agents = sortListedAgents(
|
|
211
|
+
remoteResult.records
|
|
212
|
+
.map((record) => normalizeListedAgent(record))
|
|
213
|
+
.filter((agent): agent is ListedAgent => agent != null)
|
|
214
|
+
);
|
|
215
|
+
return { agents, failures: remoteResult.failures };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function decorateAgentsWithPublicStatusAcrossServers(args: {
|
|
219
|
+
agents: ListedAgent[];
|
|
220
|
+
authToken: string;
|
|
221
|
+
fallbackFetchImpl?: typeof fetch;
|
|
222
|
+
fetchImpl: typeof fetch;
|
|
223
|
+
serverUrls: string[];
|
|
224
|
+
}) {
|
|
225
|
+
await Promise.all(
|
|
226
|
+
args.agents.map(async (agent) => {
|
|
227
|
+
try {
|
|
228
|
+
const result = await readLiveDbRecordAfterTombstoneMerge({
|
|
229
|
+
authToken: args.authToken,
|
|
230
|
+
dbKey: agent.publicKey,
|
|
231
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
232
|
+
fetchImpl: args.fetchImpl,
|
|
233
|
+
serverUrls: args.serverUrls,
|
|
234
|
+
});
|
|
235
|
+
agent.publicRecordExists = result.record?.isPublic === true;
|
|
236
|
+
} catch {
|
|
237
|
+
agent.publicRecordExists = false;
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
return args.agents;
|
|
242
|
+
}
|
package/agentPullCommand.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
|
|
2
1
|
import { getDefaultCliLocalRuntimeDb } from "./localRuntimeDb";
|
|
3
2
|
import type { CliLocalRuntimeDb } from "./client/localRuntimeAdapter";
|
|
4
3
|
import { resolveCliAgentKeyInput } from "./agentAliases";
|
|
4
|
+
import { resolveAuthToken, resolveServerUrl } from "./cliEnvHelpers";
|
|
5
5
|
|
|
6
6
|
type EnvLike = Record<string, string | undefined>;
|
|
7
7
|
|
|
@@ -44,14 +44,6 @@ export function parseAgentPullArgs(args: string[]): ParsedAgentPullArgs | null {
|
|
|
44
44
|
return { agentKey: resolveCliAgentKeyInput(agentKey) };
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
function resolveServerUrl(env: EnvLike) {
|
|
48
|
-
return (env.NOLO_SERVER || env.BASE_URL || DEFAULT_NOLO_SERVER_URL).replace(/\/+$/, "");
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function resolveAuthToken(env: EnvLike) {
|
|
52
|
-
return env.AUTH_TOKEN || env.AUTH || env.BENCHMARK_AUTH_TOKEN || "";
|
|
53
|
-
}
|
|
54
|
-
|
|
55
47
|
function extractToolNames(record: any) {
|
|
56
48
|
if (Array.isArray(record?.toolNames)) return record.toolNames.filter((tool: unknown) => typeof tool === "string");
|
|
57
49
|
if (!Array.isArray(record?.tools)) return [];
|
|
@@ -113,8 +105,11 @@ export async function runAgentPullCommand(args: string[], deps: AgentPullCommand
|
|
|
113
105
|
return 1;
|
|
114
106
|
}
|
|
115
107
|
|
|
116
|
-
const serverUrl = resolveServerUrl(env);
|
|
117
|
-
const authToken =
|
|
108
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
109
|
+
const authToken =
|
|
110
|
+
readFlagValue(args, "--token") ||
|
|
111
|
+
readFlagValue(args, "--machine-key") ||
|
|
112
|
+
resolveAuthToken(env, ["BENCHMARK_AUTH_TOKEN"]);
|
|
118
113
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
119
114
|
const db = deps.db ?? await getDefaultCliLocalRuntimeDb();
|
|
120
115
|
|