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.
Files changed (84) hide show
  1. package/README.md +15 -5
  2. package/agent-runtime/agentRecordConfig.ts +8 -0
  3. package/agent-runtime/hostAdapter.ts +9 -1
  4. package/agent-runtime/index.ts +10 -0
  5. package/agent-runtime/localLoop.ts +49 -2
  6. package/agent-runtime/localToolPolicy.ts +71 -22
  7. package/agent-runtime/localWorkspaceTools.ts +5 -2
  8. package/agent-runtime/openAiCompatibleProvider.ts +7 -3
  9. package/agent-runtime/openAiCompatibleProviderConfig.ts +15 -26
  10. package/agent-runtime/platformChatProvider.ts +48 -71
  11. package/agent-runtime/providerResolution.ts +242 -0
  12. package/agent-runtime/runtimeToolPolicy.ts +33 -0
  13. package/agent-runtime/shellCommandPolicy.ts +120 -0
  14. package/agentCommandSupport.ts +4 -0
  15. package/agentInternalCommandEntries.ts +2 -0
  16. package/agentListCommands.ts +74 -12
  17. package/agentListHelpers.ts +56 -1
  18. package/agentPullCommand.ts +6 -11
  19. package/agentRecordCommands.ts +87 -2
  20. package/agentRecordHelpers.ts +132 -9
  21. package/agentRuntimeLocal.ts +3 -0
  22. package/ai/tools/toolNameAliases.ts +70 -0
  23. package/cli/agentCommandSupport.ts +4 -0
  24. package/cli/agentInternalCommandEntries.ts +2 -0
  25. package/cli/agentListCommands.ts +74 -12
  26. package/cli/agentListHelpers.ts +56 -1
  27. package/cli/agentPullCommand.ts +6 -11
  28. package/cli/agentRecordCommands.ts +87 -2
  29. package/cli/agentRecordHelpers.ts +132 -9
  30. package/cli/agentRuntimeLocal.ts +3 -0
  31. package/cli/cliCommandDispatch.ts +2 -2
  32. package/cli/cliEnvHelpers.ts +80 -12
  33. package/cli/cliSpaceHelpers.ts +39 -0
  34. package/cli/client/agentRun.ts +1 -0
  35. package/cli/client/localRuntimeAdapter.ts +44 -7
  36. package/cli/commandRegistry.ts +2 -2
  37. package/cli/connectorWebSocketTarget.ts +10 -3
  38. package/cli/dialogCommands.ts +446 -0
  39. package/cli/dialogInternalCommandEntries.ts +13 -0
  40. package/cli/globalRecordOperations.ts +258 -0
  41. package/cli/internalCommandEntries.ts +2 -0
  42. package/cli/localRuntimeAuthority.ts +182 -0
  43. package/cli/localRuntimeDb.ts +2 -4
  44. package/cli/machineCommands.ts +1 -1
  45. package/cli/machineWsRunDispatch.ts +29 -12
  46. package/cli/machineWsSession.ts +2 -0
  47. package/cli/offlineMarxistsAgentCommand.ts +1 -1
  48. package/cli/runtimeDoctorCommands.ts +65 -9
  49. package/cli/tableCommands.ts +1 -1
  50. package/cli/taskRunCommand.ts +1 -1
  51. package/cliCommandDispatch.ts +2 -2
  52. package/cliEnvHelpers.ts +80 -12
  53. package/cliSpaceHelpers.ts +39 -0
  54. package/client/agentRun.ts +1 -0
  55. package/client/localProviderResolver.test.ts +26 -2
  56. package/client/localRuntimeAdapter.test.ts +287 -3
  57. package/client/localRuntimeAdapter.ts +44 -7
  58. package/commandRegistry.ts +2 -2
  59. package/connectorWebSocketTarget.ts +10 -3
  60. package/database/server/authorityStoreTypes.ts +31 -0
  61. package/database/server/cliAuthorityBrokerClient.ts +227 -0
  62. package/database/server/cliAuthorityBrokerServer.ts +283 -0
  63. package/database/server/cliAuthorityBrokerTypes.ts +43 -0
  64. package/database/server/cliAuthorityStoreDriver.ts +98 -0
  65. package/database/server/legacyServerDb.ts +58 -0
  66. package/database/server/levelAuthorityStore.ts +95 -0
  67. package/database/tombstones.ts +52 -0
  68. package/database/userDataMerge.ts +95 -0
  69. package/dialogCommands.ts +446 -0
  70. package/dialogInternalCommandEntries.ts +13 -0
  71. package/globalRecordOperations.ts +258 -0
  72. package/internalCommandEntries.ts +2 -0
  73. package/localRuntimeAuthority.ts +182 -0
  74. package/localRuntimeDb.ts +2 -4
  75. package/machineCommands.ts +1 -1
  76. package/machineWsRunDispatch.ts +29 -12
  77. package/machineWsSession.ts +2 -0
  78. package/offlineMarxistsAgentCommand.ts +1 -1
  79. package/package.json +24 -4
  80. package/runtimeDoctorCommands.ts +65 -9
  81. package/tableCommands.ts +1 -1
  82. package/taskRunCommand.ts +1 -1
  83. package/database/server/db.ts +0 -118
  84. package/database/server/ensureDbOpen.ts +0 -12
@@ -0,0 +1,258 @@
1
+ import {
2
+ deleteDbRecord,
3
+ queryUserRecords,
4
+ readDbRecord,
5
+ } from "./agentRecordHelpers";
6
+ import { isTombstoneRecord } from "../database/tombstones";
7
+ import { mergeAndDedupUserData } from "../database/userDataMerge";
8
+
9
+ export type GlobalRecordFailure = {
10
+ serverUrl: string;
11
+ error: string;
12
+ };
13
+
14
+ export type GlobalDeleteResult = {
15
+ serverUrl: string;
16
+ ok: boolean;
17
+ result?: any;
18
+ error?: string;
19
+ };
20
+
21
+ export type GlobalRecordTarget = {
22
+ serverUrl: string;
23
+ authToken?: string;
24
+ };
25
+
26
+ export type DbDeleteOptions = {
27
+ type?: "single" | "table" | "messages";
28
+ };
29
+
30
+ export async function listUserRecordsFromServers(args: {
31
+ authToken: string;
32
+ fallbackFetchImpl?: typeof fetch;
33
+ fetchImpl: typeof fetch;
34
+ limit?: number;
35
+ serverUrls: string[];
36
+ summary?: boolean;
37
+ type: string | string[];
38
+ userId: string;
39
+ label?: string;
40
+ }) {
41
+ const remoteResults: Array<{ data: { data: any[] } }> = [];
42
+ const failures: GlobalRecordFailure[] = [];
43
+
44
+ for (const serverUrl of args.serverUrls) {
45
+ try {
46
+ const records = await queryUserRecords({
47
+ authToken: args.authToken,
48
+ fallbackFetchImpl: args.fallbackFetchImpl,
49
+ fetchImpl: args.fetchImpl,
50
+ includeDeleted: true,
51
+ limit: args.limit,
52
+ serverUrl,
53
+ summary: args.summary,
54
+ type: args.type,
55
+ userId: args.userId,
56
+ });
57
+ remoteResults.push({
58
+ data: {
59
+ data: records.map((record: any) => ({
60
+ ...record,
61
+ serverOrigin:
62
+ typeof record?.serverOrigin === "string" && record.serverOrigin.trim()
63
+ ? record.serverOrigin
64
+ : serverUrl,
65
+ })),
66
+ },
67
+ });
68
+ } catch (error) {
69
+ failures.push({
70
+ serverUrl,
71
+ error: error instanceof Error ? error.message : String(error),
72
+ });
73
+ }
74
+ }
75
+
76
+ if (remoteResults.length === 0 && failures.length > 0) {
77
+ const label = args.label ?? "record query";
78
+ throw new Error(
79
+ `${label} failed on all candidate servers: ${failures
80
+ .map((failure) => `${failure.serverUrl}: ${failure.error}`)
81
+ .join("; ")}`
82
+ );
83
+ }
84
+
85
+ const merged = mergeAndDedupUserData([], remoteResults, { includeDeleted: true });
86
+ return {
87
+ records: merged.filter((record) => !isTombstoneRecord(record)),
88
+ failures,
89
+ };
90
+ }
91
+
92
+ export async function readDbRecordFromServers(args: {
93
+ authToken: string;
94
+ dbKey: string;
95
+ fallbackFetchImpl?: typeof fetch;
96
+ fetchImpl: typeof fetch;
97
+ includeDeleted?: boolean;
98
+ serverUrls: string[];
99
+ label?: string;
100
+ }) {
101
+ const failures: GlobalRecordFailure[] = [];
102
+ for (const serverUrl of args.serverUrls) {
103
+ try {
104
+ const record = await readDbRecord({
105
+ authToken: args.authToken,
106
+ dbKey: args.dbKey,
107
+ fallbackFetchImpl: args.fallbackFetchImpl,
108
+ fetchImpl: args.fetchImpl,
109
+ includeDeleted: args.includeDeleted,
110
+ serverUrl,
111
+ });
112
+ return { record, serverUrl, failures };
113
+ } catch (error) {
114
+ failures.push({
115
+ serverUrl,
116
+ error: error instanceof Error ? error.message : String(error),
117
+ });
118
+ }
119
+ }
120
+
121
+ const label = args.label ?? `record ${args.dbKey}`;
122
+ throw new Error(
123
+ `${label} is unreadable on all candidate servers: ${failures
124
+ .map((failure) => `${failure.serverUrl}: ${failure.error}`)
125
+ .join("; ")}`
126
+ );
127
+ }
128
+
129
+ export async function readDbRecordVersionsFromServers(args: {
130
+ authToken: string;
131
+ dbKey: string;
132
+ fallbackFetchImpl?: typeof fetch;
133
+ fetchImpl: typeof fetch;
134
+ serverUrls: string[];
135
+ }) {
136
+ const records: any[] = [];
137
+ const failures: GlobalRecordFailure[] = [];
138
+ for (const serverUrl of args.serverUrls) {
139
+ try {
140
+ const record = await readDbRecord({
141
+ authToken: args.authToken,
142
+ dbKey: args.dbKey,
143
+ fallbackFetchImpl: args.fallbackFetchImpl,
144
+ fetchImpl: args.fetchImpl,
145
+ includeDeleted: true,
146
+ serverUrl,
147
+ });
148
+ records.push({
149
+ ...record,
150
+ dbKey: typeof record?.dbKey === "string" && record.dbKey.trim()
151
+ ? record.dbKey
152
+ : args.dbKey,
153
+ serverOrigin:
154
+ typeof record?.serverOrigin === "string" && record.serverOrigin.trim()
155
+ ? record.serverOrigin
156
+ : serverUrl,
157
+ });
158
+ } catch (error) {
159
+ failures.push({
160
+ serverUrl,
161
+ error: error instanceof Error ? error.message : String(error),
162
+ });
163
+ }
164
+ }
165
+ return { records, failures };
166
+ }
167
+
168
+ export async function readLiveDbRecordAfterTombstoneMerge(args: {
169
+ authToken: string;
170
+ dbKey: string;
171
+ fallbackFetchImpl?: typeof fetch;
172
+ fetchImpl: typeof fetch;
173
+ serverUrls: string[];
174
+ label?: string;
175
+ }) {
176
+ const result = await readDbRecordVersionsFromServers(args);
177
+ const merged = mergeAndDedupUserData(
178
+ [],
179
+ [{ data: { data: result.records } }],
180
+ { includeDeleted: true }
181
+ );
182
+ const record = merged.find((item) => item?.dbKey === args.dbKey);
183
+ if (record && !isTombstoneRecord(record)) {
184
+ return {
185
+ record,
186
+ serverUrl: typeof record.serverOrigin === "string" ? record.serverOrigin : "",
187
+ failures: result.failures,
188
+ };
189
+ }
190
+
191
+ const label = args.label ?? `record ${args.dbKey}`;
192
+ const failureText = result.failures.length
193
+ ? ` Failures: ${result.failures
194
+ .map((failure) => `${failure.serverUrl}: ${failure.error}`)
195
+ .join("; ")}`
196
+ : "";
197
+ throw new Error(`${label} is deleted or unreadable after global tombstone merge.${failureText}`);
198
+ }
199
+
200
+ export async function recordExistsAfterTombstoneMerge(args: {
201
+ authToken: string;
202
+ dbKey: string;
203
+ fallbackFetchImpl?: typeof fetch;
204
+ fetchImpl: typeof fetch;
205
+ serverUrls: string[];
206
+ }) {
207
+ try {
208
+ await readLiveDbRecordAfterTombstoneMerge(args);
209
+ return true;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+
215
+ export async function deleteDbRecordOnServers(args: {
216
+ authToken: string;
217
+ dbKey: string;
218
+ deleteOptions?: DbDeleteOptions;
219
+ fallbackFetchImpl?: typeof fetch;
220
+ fetchImpl: typeof fetch;
221
+ serverUrls: string[];
222
+ }): Promise<GlobalDeleteResult[]> {
223
+ return deleteDbRecordOnTargets({
224
+ ...args,
225
+ targets: args.serverUrls.map((serverUrl) => ({ serverUrl })),
226
+ });
227
+ }
228
+
229
+ export async function deleteDbRecordOnTargets(args: {
230
+ authToken: string;
231
+ dbKey: string;
232
+ deleteOptions?: DbDeleteOptions;
233
+ fallbackFetchImpl?: typeof fetch;
234
+ fetchImpl: typeof fetch;
235
+ targets: GlobalRecordTarget[];
236
+ }): Promise<GlobalDeleteResult[]> {
237
+ const results: GlobalDeleteResult[] = [];
238
+ for (const target of args.targets) {
239
+ try {
240
+ const result = await deleteDbRecord({
241
+ authToken: target.authToken ?? args.authToken,
242
+ dbKey: args.dbKey,
243
+ deleteOptions: args.deleteOptions,
244
+ fallbackFetchImpl: args.fallbackFetchImpl,
245
+ fetchImpl: args.fetchImpl,
246
+ serverUrl: target.serverUrl,
247
+ });
248
+ results.push({ serverUrl: target.serverUrl, ok: true, result });
249
+ } catch (error) {
250
+ results.push({
251
+ serverUrl: target.serverUrl,
252
+ ok: false,
253
+ error: error instanceof Error ? error.message : String(error),
254
+ });
255
+ }
256
+ }
257
+ return results;
258
+ }
@@ -1,4 +1,5 @@
1
1
  import { getAgentInternalCommandEntries } from "./agentInternalCommandEntries";
2
+ import { getDialogInternalCommandEntries } from "./dialogInternalCommandEntries";
2
3
  import type { CommandEntry } from "./cliCommandTypes";
3
4
  import { getSystemInternalCommandEntries } from "./systemInternalCommandEntries";
4
5
  import { getWorkflowInternalCommandEntries } from "./workflowInternalCommandEntries";
@@ -6,6 +7,7 @@ import { getWorkflowInternalCommandEntries } from "./workflowInternalCommandEntr
6
7
  export function getInternalCommandEntries(renderHelpText: () => string): CommandEntry[] {
7
8
  return [
8
9
  ...getAgentInternalCommandEntries(),
10
+ ...getDialogInternalCommandEntries(),
9
11
  ...getWorkflowInternalCommandEntries(),
10
12
  ...getSystemInternalCommandEntries(renderHelpText),
11
13
  ];
@@ -0,0 +1,182 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ import {
5
+ createCliAuthorityBrokerClient,
6
+ createCliAuthorityBrokerSocketInvoker,
7
+ isCliAuthorityBrokerUnavailableError,
8
+ } from "../database/server/cliAuthorityBrokerClient";
9
+ import type { CliAuthorityBrokerClientOptions } from "../database/server/cliAuthorityBrokerClient";
10
+ import {
11
+ getOrCreateCliAuthorityBrokerServer,
12
+ } from "../database/server/cliAuthorityBrokerServer";
13
+ import type { CliAuthorityBrokerServerOptions } from "../database/server/cliAuthorityBrokerServer";
14
+ import {
15
+ resolveCliAuthorityBrokerEndpoint,
16
+ resolveCliAuthorityBrokerHealthPath,
17
+ resolveCliAuthorityBrokerMetadataPath,
18
+ } from "../database/server/cliAuthorityStoreDriver";
19
+ import { resolveNoloHome } from "../database/server/dbPath";
20
+ import type { AuthorityStore } from "../database/server/authorityStoreTypes";
21
+ import { createLevelAuthorityStore } from "../database/server/levelAuthorityStore";
22
+ import { createLegacyServerDb } from "../database/server/legacyServerDb";
23
+
24
+ type EnvLike = Record<string, string | undefined>;
25
+
26
+ type ResolveCliLocalRuntimeAuthorityOptions = {
27
+ env?: EnvLike;
28
+ homeDir?: string;
29
+ };
30
+
31
+ function ensureDbParentDir(dbPath: string) {
32
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
33
+ }
34
+
35
+ function isCliAuthorityLockError(error: unknown) {
36
+ const message = error instanceof Error ? error.message : String(error);
37
+ return /LEVEL_LOCKED|Resource temporarily unavailable|\/LOCK|LOCK:|Database failed to open|EADDRINUSE|Failed to listen/i.test(message);
38
+ }
39
+
40
+ async function sleep(ms: number) {
41
+ await new Promise((resolve) => setTimeout(resolve, ms));
42
+ }
43
+
44
+ function resolveCliLocalRuntimeDbPath(options: ResolveCliLocalRuntimeAuthorityOptions = {}) {
45
+ return path.join(
46
+ resolveNoloHome({
47
+ env: options.env,
48
+ homeDir: options.homeDir,
49
+ }),
50
+ "data",
51
+ "leveldb"
52
+ );
53
+ }
54
+
55
+ type CliAuthorityBrokerConnectDeps = {
56
+ createClient: (options: CliAuthorityBrokerClientOptions) => AuthorityStore;
57
+ startBroker: (options: CliAuthorityBrokerServerOptions) => Promise<unknown>;
58
+ sleep: (ms: number) => Promise<void>;
59
+ };
60
+
61
+ const defaultCliAuthorityBrokerConnectDeps: CliAuthorityBrokerConnectDeps = {
62
+ createClient: (options) => createCliAuthorityBrokerClient(options),
63
+ startBroker: (options) => getOrCreateCliAuthorityBrokerServer(options),
64
+ sleep,
65
+ };
66
+
67
+ export async function connectCliAuthorityBroker(args: {
68
+ endpoint: string;
69
+ metadataPath: string;
70
+ healthPath: string;
71
+ dbPath: string;
72
+ deps?: Partial<CliAuthorityBrokerConnectDeps>;
73
+ }) {
74
+ const deps: CliAuthorityBrokerConnectDeps = {
75
+ ...defaultCliAuthorityBrokerConnectDeps,
76
+ ...args.deps,
77
+ };
78
+ const client = deps.createClient({
79
+ endpoint: args.endpoint,
80
+ invoke: createCliAuthorityBrokerSocketInvoker({ endpoint: args.endpoint }),
81
+ });
82
+
83
+ async function attachToExistingBroker(attempts: number) {
84
+ for (let attempt = 0; attempt < attempts; attempt += 1) {
85
+ await deps.sleep(100);
86
+ try {
87
+ await client.open();
88
+ return true;
89
+ } catch (error) {
90
+ if (!isCliAuthorityBrokerUnavailableError(error)) throw error;
91
+ }
92
+ }
93
+ return false;
94
+ }
95
+
96
+ try {
97
+ await deps.startBroker({
98
+ endpoint: args.endpoint,
99
+ metadataPath: args.metadataPath,
100
+ healthPath: args.healthPath,
101
+ createStore: () => createLevelAuthorityStore(args.dbPath),
102
+ });
103
+ await client.open();
104
+ return client;
105
+ } catch (error) {
106
+ if (!isCliAuthorityLockError(error)) throw error;
107
+
108
+ if (await attachToExistingBroker(5)) {
109
+ return client;
110
+ }
111
+
112
+ let lastError: unknown = error;
113
+ for (let attempt = 0; attempt < 5; attempt += 1) {
114
+ await deps.sleep(100);
115
+ try {
116
+ await deps.startBroker({
117
+ endpoint: args.endpoint,
118
+ metadataPath: args.metadataPath,
119
+ healthPath: args.healthPath,
120
+ createStore: () => createLevelAuthorityStore(args.dbPath),
121
+ });
122
+ await client.open();
123
+ return client;
124
+ } catch (retryError) {
125
+ lastError = retryError;
126
+ if (isCliAuthorityLockError(retryError)) {
127
+ if (await attachToExistingBroker(2)) {
128
+ return client;
129
+ }
130
+ continue;
131
+ }
132
+ if (isCliAuthorityBrokerUnavailableError(retryError)) {
133
+ continue;
134
+ }
135
+ throw retryError;
136
+ }
137
+ }
138
+ throw new Error(`CLI authority broker could not attach or take ownership for ${args.endpoint}`, {
139
+ cause: lastError,
140
+ });
141
+ }
142
+
143
+ }
144
+
145
+ export async function getDefaultCliLocalRuntimeAuthority(
146
+ options: ResolveCliLocalRuntimeAuthorityOptions = {}
147
+ ) {
148
+ const env = options.env ?? process.env;
149
+ const dbPath = resolveCliLocalRuntimeDbPath({
150
+ env,
151
+ homeDir: options.homeDir,
152
+ });
153
+ ensureDbParentDir(dbPath);
154
+ const endpoint = resolveCliAuthorityBrokerEndpoint({
155
+ transport: "tcp",
156
+ env,
157
+ homeDir: options.homeDir,
158
+ });
159
+ const metadataPath = resolveCliAuthorityBrokerMetadataPath({
160
+ transport: "tcp",
161
+ env,
162
+ homeDir: options.homeDir,
163
+ });
164
+ const healthPath = resolveCliAuthorityBrokerHealthPath({
165
+ transport: "tcp",
166
+ env,
167
+ homeDir: options.homeDir,
168
+ });
169
+ return connectCliAuthorityBroker({
170
+ endpoint,
171
+ metadataPath,
172
+ healthPath,
173
+ dbPath,
174
+ });
175
+ }
176
+
177
+ export async function getDefaultCliLocalRuntimeDb(
178
+ options: ResolveCliLocalRuntimeAuthorityOptions = {}
179
+ ) {
180
+ const authorityStore = await getDefaultCliLocalRuntimeAuthority(options);
181
+ return createLegacyServerDb(authorityStore);
182
+ }
@@ -21,8 +21,6 @@ export function buildCliLocalRuntimeDbEnv(env: EnvLike = process.env) {
21
21
  }
22
22
 
23
23
  export async function getDefaultCliLocalRuntimeDb(): Promise<CliLocalRuntimeDb> {
24
- process.env.NOLO_SERVER_DB_PATH = buildCliLocalRuntimeDbEnv(process.env).NOLO_SERVER_DB_PATH;
25
- const { default: serverDb, ensureServerDbOpen } = await import("../database/server/db");
26
- await ensureServerDbOpen();
27
- return serverDb as CliLocalRuntimeDb;
24
+ const { getDefaultCliLocalRuntimeDb: getAuthorityBackedDb } = await import("./localRuntimeAuthority");
25
+ return getAuthorityBackedDb();
28
26
  }
@@ -95,7 +95,7 @@ function resolveConnectServerUrl(args: string[], env: EnvLike) {
95
95
  }
96
96
 
97
97
  function resolveConnectAuthToken(args: string[], env: EnvLike) {
98
- return readOption(args, "--api-key") || readOption(args, "--token") || resolveAuthToken(env);
98
+ return readOption(args, "--machine-key") || readOption(args, "--token") || resolveAuthToken(env);
99
99
  }
100
100
 
101
101
  const defaultSleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
@@ -148,6 +148,7 @@ function hasExplicitMachinePermissions(agentConfig: any) {
148
148
 
149
149
  function runtimeWorkspacePermissionPolicy(
150
150
  runtimePolicy: ConnectorRuntimePolicy | undefined,
151
+ runtimeEnv: EnvLike,
151
152
  cwd: string
152
153
  ): PermissionPolicy | null {
153
154
  if (!requestsTaskWorktree(runtimePolicy)) return null;
@@ -155,7 +156,10 @@ function runtimeWorkspacePermissionPolicy(
155
156
  mode: "full_access",
156
157
  allowFilesystemRead: true,
157
158
  allowFilesystemWrite: true,
158
- allowShell: runtimePolicy?.shell?.enabled !== false,
159
+ allowShell:
160
+ ["worktree", "dangerous", "1", "true"].includes(runtimeEnv.NOLO_LOCAL_SHELL_MODE || "") &&
161
+ Array.isArray(runtimePolicy?.runtimeTools) &&
162
+ runtimePolicy.runtimeTools.includes("execShell"),
159
163
  writableRoots: [cwd],
160
164
  };
161
165
  }
@@ -193,9 +197,6 @@ function mergeToolNames(...values: unknown[]) {
193
197
  function localRuntimeEnvFromPolicy(runtimeEnv: EnvLike, policy?: ConnectorRuntimePolicy): EnvLike {
194
198
  return {
195
199
  ...runtimeEnv,
196
- ...(policy?.shell?.enabled && policy.shell.mode === "worktree"
197
- ? { NOLO_LOCAL_SHELL_MODE: "worktree" }
198
- : {}),
199
200
  ...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
200
201
  };
201
202
  }
@@ -445,7 +446,7 @@ export async function handleConnectorRunMessage(
445
446
  artifactCwd = prepared.path;
446
447
  }
447
448
  const runtimePermissionPolicy = !hasExplicitMachinePermissions(agentConfig)
448
- ? runtimeWorkspacePermissionPolicy(runtimePolicy, cwd)
449
+ ? runtimeWorkspacePermissionPolicy(runtimePolicy, runtimeEnv, cwd)
449
450
  : null;
450
451
  const effectivePermissionPolicy = scopePermissionPolicyToRuntimeWorkspace(
451
452
  runtimePermissionPolicy ?? machinePermissionPolicy,
@@ -499,21 +500,37 @@ export async function handleConnectorRunMessage(
499
500
  const heartbeat = setInterval(() => {
500
501
  sendProgress({
501
502
  ...progressBase,
502
- message: "cli-running",
503
+ message: "cli-running",
503
504
  updatedAt: Date.now(),
504
505
  });
505
506
  }, 15_000);
507
+ const effectiveRuntimeEnv = withForwardedUserAuthToken(runtimeEnv, parsed);
506
508
  const result = await executeCli(provider, materializedPrompt.prompt, {
507
509
  model: agentConfig.model || undefined,
508
510
  timeout,
509
511
  cwd,
510
512
  yolo: true,
511
513
  env: {
512
- NOLO_SERVER: runtimeEnv.NOLO_SERVER || runtimeEnv.NOLO_SERVER_URL || runtimeEnv.BASE_URL,
513
- NOLO_SERVER_URL: runtimeEnv.NOLO_SERVER_URL || runtimeEnv.NOLO_SERVER || runtimeEnv.BASE_URL,
514
- BASE_URL: runtimeEnv.BASE_URL || runtimeEnv.NOLO_SERVER || runtimeEnv.NOLO_SERVER_URL,
515
- AUTH_TOKEN: runtimeEnv.AUTH_TOKEN || runtimeEnv.AUTH || runtimeEnv.NOLO_MACHINE_API_KEY,
516
- NOLO_MACHINE_API_KEY: runtimeEnv.NOLO_MACHINE_API_KEY || runtimeEnv.AUTH_TOKEN || runtimeEnv.AUTH,
514
+ NOLO_SERVER:
515
+ effectiveRuntimeEnv.NOLO_SERVER ||
516
+ effectiveRuntimeEnv.NOLO_SERVER_URL ||
517
+ effectiveRuntimeEnv.BASE_URL,
518
+ NOLO_SERVER_URL:
519
+ effectiveRuntimeEnv.NOLO_SERVER_URL ||
520
+ effectiveRuntimeEnv.NOLO_SERVER ||
521
+ effectiveRuntimeEnv.BASE_URL,
522
+ BASE_URL:
523
+ effectiveRuntimeEnv.BASE_URL ||
524
+ effectiveRuntimeEnv.NOLO_SERVER ||
525
+ effectiveRuntimeEnv.NOLO_SERVER_URL,
526
+ AUTH_TOKEN:
527
+ effectiveRuntimeEnv.AUTH_TOKEN ||
528
+ effectiveRuntimeEnv.AUTH ||
529
+ effectiveRuntimeEnv.NOLO_MACHINE_API_KEY,
530
+ NOLO_MACHINE_API_KEY:
531
+ effectiveRuntimeEnv.NOLO_MACHINE_API_KEY ||
532
+ effectiveRuntimeEnv.AUTH_TOKEN ||
533
+ effectiveRuntimeEnv.AUTH,
517
534
  },
518
535
  }).finally(() => clearInterval(heartbeat));
519
536
  logConnectorRun("agent.run.cli.result", {
@@ -555,7 +572,7 @@ export async function handleConnectorRunMessage(
555
572
  baseSha = await readConnectorGitHead(cwd);
556
573
  const result = await runConnectorLocalRuntimeAgent({
557
574
  parsed,
558
- runtimeEnv,
575
+ runtimeEnv: withForwardedUserAuthToken(runtimeEnv, parsed),
559
576
  cwd,
560
577
  fetchImpl,
561
578
  onProgress: sendProgress,
@@ -119,6 +119,7 @@ export async function runMachineWsSession(options: {
119
119
  resolveConnectorWebSocketTarget: (args: {
120
120
  serverUrl: string;
121
121
  machineId: string;
122
+ connectorSurface?: string;
122
123
  headers: Record<string, string>;
123
124
  }) => Promise<string>;
124
125
  connectWebSocket: (url: string, options: ConnectorWebSocketOptions) => Promise<void>;
@@ -149,6 +150,7 @@ export async function runMachineWsSession(options: {
149
150
  const wsTarget = await options.resolveConnectorWebSocketTarget({
150
151
  serverUrl: options.serverUrl,
151
152
  machineId: options.machine.machineId,
153
+ connectorSurface: "cli",
152
154
  headers: { Authorization: `Bearer ${options.authToken}` },
153
155
  });
154
156
  websocketPromise = options.connectWebSocket(wsTarget, {
@@ -28,7 +28,7 @@ type ParsedArgs = {
28
28
 
29
29
  const DEFAULT_USER_ID = "b2e06f801f";
30
30
  const DEFAULT_SPACE_ID = "01KKY77TT0DA9NY7TNW3R7255N";
31
- const DEFAULT_SOURCE_AGENT_ID = "01MIMO25MONTH00000001PGT4X";
31
+ const DEFAULT_SOURCE_AGENT_ID = "01MIMO25MONTH0000000NEW001";
32
32
  const DEFAULT_TARGET_AGENT_ID = "01OFFMARXBOOK000000010AHL1";
33
33
  const DEFAULT_AGENT_NAME = "离线马克思主义文库书籍转换助手";
34
34
  const TOOL_NAME = "convertMarxistsBookToOfflineHtml";