nolo-cli 0.1.38 → 0.1.40
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 +4 -4
- package/agent-runtime/agentRecordConfig.ts +0 -14
- package/agent-runtime/dialogWritePlan.ts +39 -0
- package/agent-runtime/hostAdapter.ts +4 -1
- package/agent-runtime/hybridRecordStore.ts +1 -1
- package/agent-runtime/index.ts +39 -18
- package/agent-runtime/localDialogRead.ts +23 -0
- package/agent-runtime/localLoop.ts +87 -7
- package/agent-runtime/localToolPolicy.ts +1 -17
- package/agent-runtime/localWorkspaceTools.ts +51 -110
- package/agent-runtime/noloWorkspaceTools.ts +371 -0
- package/agent-runtime/runtimeToolPolicy.ts +3 -14
- package/agent-runtime/runtimeToolSurface.ts +198 -0
- package/agent-runtime/types.ts +2 -1
- package/agentAliases.ts +8 -1
- package/agentRunCommand.ts +65 -95
- package/agentRuntimeLocal.ts +1 -7
- package/ai/agent/cliExecutor.ts +249 -34
- package/chat/messages/fetchMessages.ts +84 -0
- package/chat/messages/types.ts +185 -0
- package/cli/agentAliases.ts +8 -1
- package/cli/agentRunCommand.ts +65 -95
- package/cli/agentRuntimeLocal.ts +1 -7
- package/cli/cliEnvHelpers.ts +11 -7
- package/cli/client/agentRun.ts +94 -93
- package/cli/client/localRuntimeAdapter.ts +250 -69
- package/cli/commandRegistry.ts +2 -2
- package/cli/connectorRunArtifact.ts +0 -1
- package/cli/dialogCommands.ts +572 -0
- package/cli/dialogInternalCommandEntries.ts +4 -0
- package/cli/machineWsRunDispatch.ts +76 -39
- package/cli/offlineMarxistsAgentCommand.ts +6 -4
- package/cli/scriptCommandEntries.ts +0 -2
- package/cli/tui/readlineWorkspace.ts +69 -0
- package/cli/tui/session.ts +176 -1
- package/cliEnvHelpers.ts +11 -7
- package/client/agentConfigResolver.test.ts +4 -5
- package/client/agentRun.test.ts +307 -57
- package/client/agentRun.ts +94 -93
- package/client/localRuntimeAdapter.test.ts +332 -53
- package/client/localRuntimeAdapter.ts +250 -69
- package/client/localRuntimeDryRun.test.ts +23 -9
- package/client/localToolPolicy.test.ts +4 -4
- package/commandRegistry.ts +2 -2
- package/connectorRunArtifact.ts +0 -1
- package/database/server/db.ts +99 -0
- package/database/server/ensureDbOpen.ts +12 -0
- package/database/server/levelAuthorityStore.ts +20 -1
- package/database/server/memoryAuthorityStore.ts +133 -0
- package/database/server/serverStoreFactory.ts +118 -0
- package/dialogCommands.ts +572 -0
- package/dialogInternalCommandEntries.ts +4 -0
- package/machineWsRunDispatch.ts +76 -39
- package/offlineMarxistsAgentCommand.ts +6 -4
- package/package.json +11 -9
- package/scriptCommandEntries.ts +0 -2
- package/tui/readlineWorkspace.ts +69 -0
- package/tui/session.ts +176 -1
- package/agent-runtime/taskWorkspace.ts +0 -193
- package/agent-runtime/workspaceSession.ts +0 -76
- package/cli/client/taskWorktree.ts +0 -8
- package/cli/client/workspaceSession.ts +0 -11
- package/client/taskWorktree.ts +0 -8
- package/client/workspaceSession.test.ts +0 -57
- package/client/workspaceSession.ts +0 -11
package/machineWsRunDispatch.ts
CHANGED
|
@@ -10,7 +10,6 @@ import {
|
|
|
10
10
|
resolveConnectorRunCwd as defaultResolveConnectorRunCwd,
|
|
11
11
|
} from "./connectorRunArtifact";
|
|
12
12
|
import { createCliLocalRuntimeAdapter } from "./client/localRuntimeAdapter";
|
|
13
|
-
import { prepareTaskWorktree } from "./client/taskWorktree";
|
|
14
13
|
import {
|
|
15
14
|
materializeLargeConnectorPrompt as defaultMaterializeLargeConnectorPrompt,
|
|
16
15
|
readRuntimePromptPageMeta as defaultReadRuntimePromptPageMeta,
|
|
@@ -21,9 +20,52 @@ type EnvLike = Record<string, string | undefined>;
|
|
|
21
20
|
export type LocalCliExecutor = (
|
|
22
21
|
provider: string,
|
|
23
22
|
prompt: string,
|
|
24
|
-
options: {
|
|
23
|
+
options: {
|
|
24
|
+
model?: string;
|
|
25
|
+
timeout?: number;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
yolo?: boolean;
|
|
28
|
+
env?: EnvLike;
|
|
29
|
+
imageInputs?: Array<{ source: string; materializedPath?: string }>;
|
|
30
|
+
}
|
|
25
31
|
) => Promise<{ text: string; raw?: string; elapsed?: number }>;
|
|
26
32
|
|
|
33
|
+
/**
|
|
34
|
+
* 从 connector payload 的 userInput 中提取 multimodal 内容。
|
|
35
|
+
* 如果 userInput 是字符串,直接返回 { text, imageUrls: [] }。
|
|
36
|
+
* 如果 userInput 是 multimodal content array(`[{ type: "text" }, { type: "image_url" }]`),
|
|
37
|
+
* 提取 text 和 image_url parts。
|
|
38
|
+
*/
|
|
39
|
+
function extractMultimodalUserInput(userInput: unknown): {
|
|
40
|
+
text: string;
|
|
41
|
+
imageUrls: string[];
|
|
42
|
+
} {
|
|
43
|
+
if (typeof userInput === "string") {
|
|
44
|
+
return { text: userInput, imageUrls: [] };
|
|
45
|
+
}
|
|
46
|
+
if (!Array.isArray(userInput)) {
|
|
47
|
+
return { text: String(userInput ?? ""), imageUrls: [] };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const textParts: string[] = [];
|
|
51
|
+
const imageUrls: string[] = [];
|
|
52
|
+
|
|
53
|
+
for (const part of userInput) {
|
|
54
|
+
if (!part || typeof part !== "object") continue;
|
|
55
|
+
if ((part as any).type === "text" && typeof (part as any).text === "string") {
|
|
56
|
+
textParts.push((part as any).text);
|
|
57
|
+
} else if (
|
|
58
|
+
(part as any).type === "image_url" &&
|
|
59
|
+
typeof (part as any).image_url?.url === "string" &&
|
|
60
|
+
(part as any).image_url.url.trim()
|
|
61
|
+
) {
|
|
62
|
+
imageUrls.push((part as any).image_url.url.trim());
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return { text: textParts.join("\n"), imageUrls };
|
|
67
|
+
}
|
|
68
|
+
|
|
27
69
|
type ConnectorRuntimePolicy = {
|
|
28
70
|
runtimeTools?: string[];
|
|
29
71
|
agentTools?: string[];
|
|
@@ -65,7 +107,6 @@ type MachineWsRunDispatchDeps = {
|
|
|
65
107
|
collectConnectorRunArtifact?: typeof defaultCollectConnectorRunArtifact;
|
|
66
108
|
materializeLargeConnectorPrompt?: typeof defaultMaterializeLargeConnectorPrompt;
|
|
67
109
|
readRuntimePromptPageMeta?: typeof defaultReadRuntimePromptPageMeta;
|
|
68
|
-
prepareTaskWorktree?: typeof prepareTaskWorktree;
|
|
69
110
|
buildConnectorCliPrompt?: (
|
|
70
111
|
agentConfig: any,
|
|
71
112
|
userInput: string,
|
|
@@ -97,6 +138,17 @@ function logConnectorRun(message: string, fields: Record<string, unknown> = {})
|
|
|
97
138
|
console.error(`[nolo-connector] ${message} ${JSON.stringify(fields)}`);
|
|
98
139
|
}
|
|
99
140
|
|
|
141
|
+
function summarizeRuntimePolicyForLogs(policy?: ConnectorRuntimePolicy) {
|
|
142
|
+
return {
|
|
143
|
+
hasPolicy: Boolean(policy),
|
|
144
|
+
runtimeTools: policy?.runtimeTools ?? [],
|
|
145
|
+
agentTools: policy?.agentTools ?? [],
|
|
146
|
+
workspaceMode: policy?.workspace?.mode ?? null,
|
|
147
|
+
workspaceCwd: policy?.workspace?.cwd ?? null,
|
|
148
|
+
shellEnabled: policy?.shell?.enabled ?? null,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
100
152
|
function summarizeEndpoint(value: unknown) {
|
|
101
153
|
if (typeof value !== "string" || !value.trim()) return undefined;
|
|
102
154
|
try {
|
|
@@ -139,14 +191,9 @@ function runtimePolicyFromConnectorPayload(parsed: any): ConnectorRuntimePolicy
|
|
|
139
191
|
return policy ? policy as ConnectorRuntimePolicy : undefined;
|
|
140
192
|
}
|
|
141
193
|
|
|
142
|
-
function requestsTaskWorktree(policy?: ConnectorRuntimePolicy) {
|
|
143
|
-
return policy?.workspace?.mode === "task-worktree" || policy?.workspace?.mode === "lease";
|
|
144
|
-
}
|
|
145
|
-
|
|
146
194
|
function requestsLocalWorkspaceRuntime(policy?: ConnectorRuntimePolicy) {
|
|
147
195
|
return Boolean(
|
|
148
|
-
Array.isArray(policy?.runtimeTools) && policy.runtimeTools.length > 0
|
|
149
|
-
requestsTaskWorktree(policy),
|
|
196
|
+
Array.isArray(policy?.runtimeTools) && policy.runtimeTools.length > 0,
|
|
150
197
|
);
|
|
151
198
|
}
|
|
152
199
|
|
|
@@ -183,13 +230,13 @@ function runtimeWorkspacePermissionPolicy(
|
|
|
183
230
|
runtimeEnv: EnvLike,
|
|
184
231
|
cwd: string
|
|
185
232
|
): PermissionPolicy | null {
|
|
186
|
-
|
|
233
|
+
void runtimeEnv;
|
|
234
|
+
if (!requestsLocalWorkspaceRuntime(runtimePolicy)) return null;
|
|
187
235
|
return {
|
|
188
236
|
mode: "full_access",
|
|
189
237
|
allowFilesystemRead: true,
|
|
190
238
|
allowFilesystemWrite: true,
|
|
191
239
|
allowShell:
|
|
192
|
-
["worktree", "dangerous", "1", "true"].includes(runtimeEnv.NOLO_LOCAL_SHELL_MODE || "") &&
|
|
193
240
|
Array.isArray(runtimePolicy?.runtimeTools) &&
|
|
194
241
|
runtimePolicy.runtimeTools.includes("execShell"),
|
|
195
242
|
writableRoots: [cwd],
|
|
@@ -201,7 +248,7 @@ function scopePermissionPolicyToRuntimeWorkspace(
|
|
|
201
248
|
runtimePolicy: ConnectorRuntimePolicy | undefined,
|
|
202
249
|
cwd: string
|
|
203
250
|
): PermissionPolicy {
|
|
204
|
-
if (!
|
|
251
|
+
if (!requestsLocalWorkspaceRuntime(runtimePolicy)) return policy;
|
|
205
252
|
return {
|
|
206
253
|
...policy,
|
|
207
254
|
writableRoots: policy.allowFilesystemWrite ? [cwd] : [],
|
|
@@ -313,7 +360,6 @@ async function defaultRunConnectorLocalRuntimeAgent(args: {
|
|
|
313
360
|
policy?.runtimeTools,
|
|
314
361
|
),
|
|
315
362
|
...(policy ? { runtimeToolPolicy: policy } : {}),
|
|
316
|
-
...(requestsTaskWorktree(policy) ? { localWorkspaceMode: "task-worktree" } : {}),
|
|
317
363
|
};
|
|
318
364
|
const store = new Map<string, any>([
|
|
319
365
|
[agentKey, agentRecord],
|
|
@@ -324,19 +370,15 @@ async function defaultRunConnectorLocalRuntimeAgent(args: {
|
|
|
324
370
|
withForwardedUserAuthToken(args.runtimeEnv, args.parsed),
|
|
325
371
|
policy
|
|
326
372
|
);
|
|
327
|
-
|
|
373
|
+
logConnectorRun("agent.run.local-runtime.env", {
|
|
374
|
+
requestId: args.parsed.requestId,
|
|
375
|
+
agentKey,
|
|
376
|
+
cwd: args.cwd,
|
|
377
|
+
...summarizeRuntimePolicyForLogs(policy),
|
|
378
|
+
});
|
|
328
379
|
const adapter = createCliLocalRuntimeAdapter({
|
|
329
380
|
env,
|
|
330
381
|
cwd: args.cwd,
|
|
331
|
-
useCwdAsTaskWorkspaceBase: wantsTaskWorktree,
|
|
332
|
-
...(wantsTaskWorktree
|
|
333
|
-
? {
|
|
334
|
-
prepareTaskWorktree: (input) => prepareTaskWorktree({
|
|
335
|
-
...input,
|
|
336
|
-
cwd: args.cwd,
|
|
337
|
-
}),
|
|
338
|
-
}
|
|
339
|
-
: {}),
|
|
340
382
|
fetchImpl: args.fetchImpl ?? fetch,
|
|
341
383
|
store: {
|
|
342
384
|
read: async (key) => store.get(key) ?? null,
|
|
@@ -447,13 +489,14 @@ export async function handleConnectorRunMessage(
|
|
|
447
489
|
deps.materializeLargeConnectorPrompt ?? defaultMaterializeLargeConnectorPrompt;
|
|
448
490
|
const readRuntimePromptPageMeta =
|
|
449
491
|
deps.readRuntimePromptPageMeta ?? defaultReadRuntimePromptPageMeta;
|
|
450
|
-
const prepareTaskWorktreeImpl = deps.prepareTaskWorktree ?? prepareTaskWorktree;
|
|
451
492
|
const runConnectorLocalRuntimeAgent =
|
|
452
493
|
deps.runConnectorLocalRuntimeAgent ?? defaultRunConnectorLocalRuntimeAgent;
|
|
453
494
|
try {
|
|
454
495
|
const machinePermissionPolicy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
455
496
|
const runtimePolicy = runtimePolicyFromConnectorPayload(parsed);
|
|
456
|
-
const
|
|
497
|
+
const extractedInput = extractMultimodalUserInput(parsed.payload?.userInput);
|
|
498
|
+
const userInput = extractedInput.text;
|
|
499
|
+
const connectorImageUrls = extractedInput.imageUrls;
|
|
457
500
|
const timeout = normalizeConnectorRunTimeoutMs(parsed.payload?.timeoutMs);
|
|
458
501
|
let cwd = resolveConnectorRunCwd({ env: runtimeEnv, policy: machinePermissionPolicy });
|
|
459
502
|
let runContent = "";
|
|
@@ -465,15 +508,6 @@ export async function handleConnectorRunMessage(
|
|
|
465
508
|
if (!buildConnectorCliPrompt) {
|
|
466
509
|
throw new Error("buildConnectorCliPrompt is required for CLI connector runs.");
|
|
467
510
|
}
|
|
468
|
-
if (requestsTaskWorktree(runtimePolicy)) {
|
|
469
|
-
const prepared = await prepareTaskWorktreeImpl({
|
|
470
|
-
agentKey: String(parsed.payload?.agentKey ?? "agent"),
|
|
471
|
-
cwd,
|
|
472
|
-
env: runtimeEnv,
|
|
473
|
-
});
|
|
474
|
-
cwd = prepared.path;
|
|
475
|
-
artifactCwd = prepared.path;
|
|
476
|
-
}
|
|
477
511
|
const runtimePermissionPolicy = !hasExplicitMachinePermissions(agentConfig)
|
|
478
512
|
? runtimeWorkspacePermissionPolicy(runtimePolicy, runtimeEnv, cwd)
|
|
479
513
|
: null;
|
|
@@ -507,7 +541,7 @@ export async function handleConnectorRunMessage(
|
|
|
507
541
|
...(runtimePromptPage?.dbKey ? { promptPageKey: runtimePromptPage.dbKey } : {}),
|
|
508
542
|
...(runtimePromptPage?.promptHash ? { promptPageHash: runtimePromptPage.promptHash } : {}),
|
|
509
543
|
workspaceRoot: artifactCwd,
|
|
510
|
-
workspaceKind:
|
|
544
|
+
workspaceKind: "current",
|
|
511
545
|
};
|
|
512
546
|
logConnectorRun("agent.run.cli.start", {
|
|
513
547
|
requestId: parsed.requestId,
|
|
@@ -561,6 +595,9 @@ export async function handleConnectorRunMessage(
|
|
|
561
595
|
effectiveRuntimeEnv.AUTH_TOKEN ||
|
|
562
596
|
effectiveRuntimeEnv.AUTH,
|
|
563
597
|
},
|
|
598
|
+
...(connectorImageUrls.length > 0
|
|
599
|
+
? { imageInputs: connectorImageUrls.map((url) => ({ source: url })) }
|
|
600
|
+
: {}),
|
|
564
601
|
}).finally(() => clearInterval(heartbeat));
|
|
565
602
|
logConnectorRun("agent.run.cli.result", {
|
|
566
603
|
requestId: parsed.requestId,
|
|
@@ -580,9 +617,9 @@ export async function handleConnectorRunMessage(
|
|
|
580
617
|
artifacts,
|
|
581
618
|
runtimePolicy,
|
|
582
619
|
workspaceRoot: artifactCwd,
|
|
583
|
-
workspaceKind:
|
|
620
|
+
workspaceKind: "current",
|
|
584
621
|
});
|
|
585
|
-
if (artifactProgress
|
|
622
|
+
if (artifactProgress) sendProgress(artifactProgress);
|
|
586
623
|
send(JSON.stringify({
|
|
587
624
|
type: "agent.run.result",
|
|
588
625
|
requestId: parsed.requestId,
|
|
@@ -628,9 +665,9 @@ export async function handleConnectorRunMessage(
|
|
|
628
665
|
artifacts,
|
|
629
666
|
runtimePolicy,
|
|
630
667
|
workspaceRoot: artifactCwd,
|
|
631
|
-
workspaceKind:
|
|
668
|
+
workspaceKind: "current",
|
|
632
669
|
});
|
|
633
|
-
if (artifactProgress
|
|
670
|
+
if (artifactProgress) sendProgress(artifactProgress);
|
|
634
671
|
send(JSON.stringify({
|
|
635
672
|
type: "agent.run.result",
|
|
636
673
|
requestId: parsed.requestId,
|
|
@@ -40,15 +40,17 @@ function readFlagValue(args: string[], flag: string) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
function parseUserIdFromToken(token: string) {
|
|
43
|
-
const payload = token.split(".")[
|
|
44
|
-
if (!payload) return
|
|
43
|
+
const payload = token.split(".")[1];
|
|
44
|
+
if (!payload) return undefined;
|
|
45
45
|
try {
|
|
46
46
|
const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
|
|
47
47
|
const padded = normalized + "=".repeat((4 - (normalized.length % 4)) % 4);
|
|
48
48
|
const parsed = JSON.parse(Buffer.from(padded, "base64").toString("utf8"));
|
|
49
|
-
return typeof parsed?.userId === "string"
|
|
49
|
+
return typeof parsed?.userId === "string" && parsed.userId.trim()
|
|
50
|
+
? parsed.userId.trim()
|
|
51
|
+
: undefined;
|
|
50
52
|
} catch {
|
|
51
|
-
return
|
|
53
|
+
return undefined;
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nolo-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.40",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first terminal workspace for Nolo",
|
|
6
6
|
"bin": {
|
|
@@ -65,12 +65,9 @@
|
|
|
65
65
|
"client/agentConfigResolver.ts",
|
|
66
66
|
"client/localToolPolicy.ts",
|
|
67
67
|
"client/localDialogRecords.test.ts",
|
|
68
|
-
"client/workspaceSession.ts",
|
|
69
68
|
"client/agentRun.test.ts",
|
|
70
69
|
"client/streamingOutput.test.ts",
|
|
71
70
|
"client/hybridRecordStore.test.ts",
|
|
72
|
-
"client/taskWorktree.ts",
|
|
73
|
-
"client/workspaceSession.test.ts",
|
|
74
71
|
"client/profileConfig.ts",
|
|
75
72
|
"client/profileConfig.test.ts",
|
|
76
73
|
"client/localRuntimeAdapter.test.ts",
|
|
@@ -116,7 +113,6 @@
|
|
|
116
113
|
"cli/defaultServer.ts",
|
|
117
114
|
"cli/client/agentRun.ts",
|
|
118
115
|
"cli/agentRuntimeLocal.ts",
|
|
119
|
-
"cli/client/taskWorktree.ts",
|
|
120
116
|
"cli/client/taskRunPrompt.ts",
|
|
121
117
|
"cli/cliCommandTypes.ts",
|
|
122
118
|
"cli/cliCommandAdapters.ts",
|
|
@@ -147,7 +143,6 @@
|
|
|
147
143
|
"cli/client/localProviderResolver.ts",
|
|
148
144
|
"cli/client/localDialogRecords.ts",
|
|
149
145
|
"cli/client/localToolPolicy.ts",
|
|
150
|
-
"cli/client/workspaceSession.ts",
|
|
151
146
|
"cli/client/compactDialog.ts",
|
|
152
147
|
"cli/tui/session.ts",
|
|
153
148
|
"cli/connectorRunArtifact.ts",
|
|
@@ -155,9 +150,9 @@
|
|
|
155
150
|
"agent-runtime/index.ts",
|
|
156
151
|
"agent-runtime/localLoop.ts",
|
|
157
152
|
"agent-runtime/localWorkspaceTools.ts",
|
|
153
|
+
"agent-runtime/localDialogRead.ts",
|
|
158
154
|
"agent-runtime/runtimeDecision.ts",
|
|
159
|
-
"agent-runtime/
|
|
160
|
-
"agent-runtime/taskWorkspace.ts",
|
|
155
|
+
"agent-runtime/noloWorkspaceTools.ts",
|
|
161
156
|
"agent-runtime/runtimeToolPolicy.ts",
|
|
162
157
|
"agent-runtime/hostAdapter.ts",
|
|
163
158
|
"agent-runtime/types.ts",
|
|
@@ -174,6 +169,7 @@
|
|
|
174
169
|
"agent-runtime/hybridRecordStore.ts",
|
|
175
170
|
"agent-runtime/localToolPolicy.ts",
|
|
176
171
|
"agent-runtime/shellCommandPolicy.ts",
|
|
172
|
+
"agent-runtime/runtimeToolSurface.ts",
|
|
177
173
|
"connector-experimental/heartbeatLoop.ts",
|
|
178
174
|
"connector-experimental/capabilities.ts",
|
|
179
175
|
"connector-experimental/codexBinary.ts",
|
|
@@ -188,12 +184,18 @@
|
|
|
188
184
|
"database/server/levelAuthorityStore.ts",
|
|
189
185
|
"database/server/legacyServerDb.ts",
|
|
190
186
|
"database/server/cliAuthorityBrokerTypes.ts",
|
|
187
|
+
"database/server/db.ts",
|
|
188
|
+
"database/server/ensureDbOpen.ts",
|
|
189
|
+
"database/server/serverStoreFactory.ts",
|
|
190
|
+
"database/server/memoryAuthorityStore.ts",
|
|
191
191
|
"ai/agent/cliExecutor.ts",
|
|
192
192
|
"ai/skills/skillDocProtocol.ts",
|
|
193
193
|
"ai/tools/agent/taskRunTool.ts",
|
|
194
194
|
"ai/agent/cliPrompt.ts",
|
|
195
195
|
"ai/tools/toolNameAliases.ts",
|
|
196
|
-
"render/table/activityColumns.ts"
|
|
196
|
+
"render/table/activityColumns.ts",
|
|
197
|
+
"chat/messages/fetchMessages.ts",
|
|
198
|
+
"chat/messages/types.ts"
|
|
197
199
|
],
|
|
198
200
|
"publishConfig": {
|
|
199
201
|
"access": "public"
|
package/scriptCommandEntries.ts
CHANGED
|
@@ -16,8 +16,6 @@ function getDocScriptCommands(): CommandEntry[] {
|
|
|
16
16
|
|
|
17
17
|
function getSpaceScriptCommands(): CommandEntry[] {
|
|
18
18
|
return [
|
|
19
|
-
createScriptCommand(["dialog", "read"], "readDialog.ts", "Read a dialog"),
|
|
20
|
-
createScriptCommand(["dialog", "status"], "dialogStatus.ts", "Read compact dialog status"),
|
|
21
19
|
createScriptCommand(["space", "list"], "listSpaces.ts", "List joined spaces"),
|
|
22
20
|
createScriptCommand(["space", "read"], "readSpace.ts", "Read a space"),
|
|
23
21
|
createScriptCommand(["space", "delete"], "deleteSpaces.ts", "Delete spaces by safe filters"),
|
package/tui/readlineWorkspace.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createInterface } from "node:readline";
|
|
2
2
|
import { stdin as defaultInput, stdout as defaultOutput } from "node:process";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
import { runAgentTurn, type RunAgentTurnResult } from "../client/agentRun";
|
|
4
5
|
import { compactDialog, type CompactDialogResult } from "../client/compactDialog";
|
|
5
6
|
import { runSelfUpdate } from "../updateCommands";
|
|
@@ -21,7 +22,9 @@ type WorkspaceOptions = {
|
|
|
21
22
|
env?: NodeJS.ProcessEnv;
|
|
22
23
|
input?: NodeJS.ReadableStream;
|
|
23
24
|
output?: NodeJS.WritableStream;
|
|
25
|
+
cliEntrypointPath?: string;
|
|
24
26
|
agentRunner?: typeof runAgentTurn;
|
|
27
|
+
cliCommandRunner?: CliCommandRunner;
|
|
25
28
|
compactRunner?: (options: {
|
|
26
29
|
serverUrl: string;
|
|
27
30
|
authToken: string;
|
|
@@ -30,6 +33,16 @@ type WorkspaceOptions = {
|
|
|
30
33
|
selfUpdater?: SelfUpdater;
|
|
31
34
|
};
|
|
32
35
|
|
|
36
|
+
type CliCommandRunner = (
|
|
37
|
+
args: string[],
|
|
38
|
+
context: {
|
|
39
|
+
env: NodeJS.ProcessEnv;
|
|
40
|
+
output: NodeJS.WritableStream;
|
|
41
|
+
scriptDir: string;
|
|
42
|
+
cliEntrypointPath: string;
|
|
43
|
+
}
|
|
44
|
+
) => Promise<number>;
|
|
45
|
+
|
|
33
46
|
async function runAgentChat(
|
|
34
47
|
scriptDir: string,
|
|
35
48
|
state: TuiState,
|
|
@@ -52,10 +65,48 @@ async function runAgentChat(
|
|
|
52
65
|
return result;
|
|
53
66
|
}
|
|
54
67
|
|
|
68
|
+
async function pipeReadableToOutput(
|
|
69
|
+
readable: ReadableStream<Uint8Array> | null,
|
|
70
|
+
output: NodeJS.WritableStream
|
|
71
|
+
) {
|
|
72
|
+
if (!readable) return;
|
|
73
|
+
const text = await new Response(readable).text();
|
|
74
|
+
if (text) output.write(text);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function resolveDefaultCliEntrypoint(scriptDir: string) {
|
|
78
|
+
if (process.argv[1]) return process.argv[1];
|
|
79
|
+
return join(scriptDir, "..", "packages", "cli", "index.ts");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function runCliCommandInChildProcess(
|
|
83
|
+
args: string[],
|
|
84
|
+
context: {
|
|
85
|
+
env: NodeJS.ProcessEnv;
|
|
86
|
+
output: NodeJS.WritableStream;
|
|
87
|
+
cliEntrypointPath: string;
|
|
88
|
+
}
|
|
89
|
+
) {
|
|
90
|
+
const proc = Bun.spawn({
|
|
91
|
+
cmd: [process.execPath, context.cliEntrypointPath, ...args],
|
|
92
|
+
stdout: "pipe",
|
|
93
|
+
stderr: "pipe",
|
|
94
|
+
env: context.env,
|
|
95
|
+
});
|
|
96
|
+
await Promise.all([
|
|
97
|
+
pipeReadableToOutput(proc.stdout, context.output),
|
|
98
|
+
pipeReadableToOutput(proc.stderr, context.output),
|
|
99
|
+
]);
|
|
100
|
+
return proc.exited;
|
|
101
|
+
}
|
|
102
|
+
|
|
55
103
|
export async function startTuiWorkspace(options: WorkspaceOptions) {
|
|
56
104
|
let state = createInitialTuiState(options.env ?? process.env);
|
|
57
105
|
const input = options.input ?? defaultInput;
|
|
58
106
|
const output = options.output ?? defaultOutput;
|
|
107
|
+
const cliEntrypointPath =
|
|
108
|
+
options.cliEntrypointPath ?? resolveDefaultCliEntrypoint(options.scriptDir);
|
|
109
|
+
const cliCommandRunner = options.cliCommandRunner ?? runCliCommandInChildProcess;
|
|
59
110
|
const selfUpdater: SelfUpdater =
|
|
60
111
|
options.selfUpdater ?? ((target) => runSelfUpdate({ output: target }));
|
|
61
112
|
const rl = createInterface({ input, output });
|
|
@@ -113,6 +164,24 @@ export async function startTuiWorkspace(options: WorkspaceOptions) {
|
|
|
113
164
|
}
|
|
114
165
|
}
|
|
115
166
|
|
|
167
|
+
if (result.action?.type === "cli-command") {
|
|
168
|
+
try {
|
|
169
|
+
const exitCode = await cliCommandRunner(result.action.args, {
|
|
170
|
+
env: options.env ?? process.env,
|
|
171
|
+
output,
|
|
172
|
+
scriptDir: options.scriptDir,
|
|
173
|
+
cliEntrypointPath,
|
|
174
|
+
});
|
|
175
|
+
if (exitCode !== 0) {
|
|
176
|
+
output.write(`[nolo] CLI command exited with code ${exitCode}.\n`);
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
output.write(
|
|
180
|
+
`[nolo] CLI command failed: ${error instanceof Error ? error.message : String(error)}\n`
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
116
185
|
if (result.action?.type === "chat") {
|
|
117
186
|
const runResult = await runAgentChat(
|
|
118
187
|
options.scriptDir,
|
package/tui/session.ts
CHANGED
|
@@ -63,6 +63,10 @@ export type TuiAction =
|
|
|
63
63
|
| {
|
|
64
64
|
type: "self-update";
|
|
65
65
|
}
|
|
66
|
+
| {
|
|
67
|
+
type: "cli-command";
|
|
68
|
+
args: string[];
|
|
69
|
+
}
|
|
66
70
|
| {
|
|
67
71
|
type: "exit";
|
|
68
72
|
};
|
|
@@ -152,7 +156,7 @@ export function renderTuiHelp() {
|
|
|
152
156
|
" /version Show version/update hint",
|
|
153
157
|
" /exit Leave the workspace",
|
|
154
158
|
"",
|
|
155
|
-
"You can also type normally. nolo
|
|
159
|
+
"You can also type normally. nolo routes simple read/status requests to CLI commands and sends the rest to the current agent.",
|
|
156
160
|
].join("\n");
|
|
157
161
|
}
|
|
158
162
|
|
|
@@ -203,6 +207,165 @@ function resolveSwitchTarget(rawTarget: string) {
|
|
|
203
207
|
return null;
|
|
204
208
|
}
|
|
205
209
|
|
|
210
|
+
const DIALOG_ID_PATTERN = /[0-9A-HJKMNP-TV-Z]{26}/i;
|
|
211
|
+
const DIALOG_KEY_PATTERN = /dialog-[^\s"'<>]+-[0-9A-HJKMNP-TV-Z]{26}/i;
|
|
212
|
+
const DIALOG_URL_PATTERN = /https?:\/\/[^\s"'<>]+\/(?:space\/[^/\s"'<>]+\/)?dialog-[^\s"'<>]+-[0-9A-HJKMNP-TV-Z]{26}\/?/i;
|
|
213
|
+
const GENERIC_TOKEN_PATTERN = /(?:agent-pub-|agent-|space-|page-|meta-)?[A-Za-z0-9][A-Za-z0-9._:/-]{2,}/;
|
|
214
|
+
|
|
215
|
+
function parseChineseSmallNumber(value: string) {
|
|
216
|
+
const normalized = value.trim();
|
|
217
|
+
const direct: Record<string, number> = {
|
|
218
|
+
一: 1,
|
|
219
|
+
二: 2,
|
|
220
|
+
两: 2,
|
|
221
|
+
三: 3,
|
|
222
|
+
四: 4,
|
|
223
|
+
五: 5,
|
|
224
|
+
六: 6,
|
|
225
|
+
七: 7,
|
|
226
|
+
八: 8,
|
|
227
|
+
九: 9,
|
|
228
|
+
十: 10,
|
|
229
|
+
};
|
|
230
|
+
if (direct[normalized]) return direct[normalized];
|
|
231
|
+
const teen = normalized.match(/^十([一二两三四五六七八九])$/);
|
|
232
|
+
if (teen) return 10 + direct[teen[1]];
|
|
233
|
+
const tens = normalized.match(/^([一二两三四五六七八九])十([一二两三四五六七八九])?$/);
|
|
234
|
+
if (tens) return direct[tens[1]] * 10 + (tens[2] ? direct[tens[2]] : 0);
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function resolveDialogListLimit(input: string) {
|
|
239
|
+
const digitMatch =
|
|
240
|
+
input.match(/(?:最近|前|latest|last)?\s*(\d{1,3})\s*(?:个|条|篇)?\s*(?:对话|dialogs?)/i) ??
|
|
241
|
+
input.match(/(?:对话|dialogs?).*?(?:最近|前|latest|last)?\s*(\d{1,3})\s*(?:个|条|篇)?/i);
|
|
242
|
+
if (digitMatch) {
|
|
243
|
+
const parsed = Number(digitMatch[1]);
|
|
244
|
+
if (Number.isInteger(parsed) && parsed > 0) return String(parsed);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const chineseMatch =
|
|
248
|
+
input.match(/(?:最近|前)?\s*([一二两三四五六七八九十]{1,3})\s*(?:个|条|篇)?\s*对话/) ??
|
|
249
|
+
input.match(/对话.*?(?:最近|前)?\s*([一二两三四五六七八九十]{1,3})\s*(?:个|条|篇)?/);
|
|
250
|
+
if (chineseMatch) {
|
|
251
|
+
const parsed = parseChineseSmallNumber(chineseMatch[1]);
|
|
252
|
+
if (parsed && parsed > 0) return String(parsed);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return undefined;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function extractDialogRef(input: string) {
|
|
259
|
+
return (
|
|
260
|
+
input.match(DIALOG_URL_PATTERN)?.[0] ??
|
|
261
|
+
input.match(DIALOG_KEY_PATTERN)?.[0] ??
|
|
262
|
+
input.match(DIALOG_ID_PATTERN)?.[0] ??
|
|
263
|
+
null
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function extractTokenAfterKeywords(input: string, keywords: string[]) {
|
|
268
|
+
for (const keyword of keywords) {
|
|
269
|
+
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
270
|
+
const match = input.match(new RegExp(`${escaped}\\s+(${GENERIC_TOKEN_PATTERN.source})`, "i"));
|
|
271
|
+
if (match?.[1]) return match[1];
|
|
272
|
+
}
|
|
273
|
+
return null;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
function formatCliCommand(args: string[]) {
|
|
277
|
+
return `nolo ${args.join(" ")}`;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
function wantsList(input: string) {
|
|
281
|
+
return /(列出|列表|有哪些|所有|我的|list|show all)/i.test(input);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function wantsRead(input: string) {
|
|
285
|
+
return /(读|读取|查看|打开|查询|read|show|open)/i.test(input);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function wantsAnalysisOrComposition(input: string) {
|
|
289
|
+
return /(总结|汇总|分析|归纳|对比|解释|整理|summary|summari[sz]e|analy[sz]e|compare)/i.test(input);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function resolveSystemCliCommand(input: string) {
|
|
293
|
+
if (/(我.*登录.*谁|当前.*登录|whoami|who am i)/i.test(input)) {
|
|
294
|
+
return ["whoami"];
|
|
295
|
+
}
|
|
296
|
+
if (/(版本|version)/i.test(input) && /(?:nolo|cli|版本|version)/i.test(input)) {
|
|
297
|
+
return ["version"];
|
|
298
|
+
}
|
|
299
|
+
if (/(诊断|doctor|健康检查|health)/i.test(input) && /(?:nolo|cli|诊断|doctor)/i.test(input)) {
|
|
300
|
+
return ["doctor"];
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function resolveResourceCliCommand(input: string) {
|
|
306
|
+
if (/(agent|代理|智能体)/i.test(input)) {
|
|
307
|
+
if (wantsList(input)) return ["agent", "list"];
|
|
308
|
+
if (wantsRead(input)) {
|
|
309
|
+
const agentRef = extractTokenAfterKeywords(input, ["agent", "代理", "智能体"]);
|
|
310
|
+
if (agentRef) return ["agent", "read", agentRef];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (/(space|空间)/i.test(input)) {
|
|
315
|
+
if (wantsList(input)) return ["space", "list"];
|
|
316
|
+
if (wantsRead(input)) {
|
|
317
|
+
const spaceRef = extractTokenAfterKeywords(input, ["space", "空间"]);
|
|
318
|
+
if (spaceRef) return ["space", "read", spaceRef];
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (/(skill-doc|skill doc|技能文档|技能)/i.test(input)) {
|
|
323
|
+
if (wantsRead(input)) {
|
|
324
|
+
const docRef = extractTokenAfterKeywords(input, ["skill-doc", "skill doc", "技能文档", "技能"]);
|
|
325
|
+
if (docRef) return ["skill-doc", "read", docRef];
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (/(doc|文档|page)/i.test(input)) {
|
|
330
|
+
if (wantsRead(input)) {
|
|
331
|
+
const docRef = extractTokenAfterKeywords(input, ["doc", "文档", "page"]);
|
|
332
|
+
if (docRef) return ["doc", "read", docRef];
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (/(table|表格|表|任务表)/i.test(input) && /(查询|查看|读取|query|read|show)/i.test(input)) {
|
|
337
|
+
const tableRef = extractTokenAfterKeywords(input, ["table", "表格", "表", "任务表"]);
|
|
338
|
+
if (tableRef) return ["table", "query", "--table", tableRef];
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export function resolveNaturalLanguageCliCommand(input: string): string[] | null {
|
|
345
|
+
const normalized = input.trim();
|
|
346
|
+
if (!normalized) return null;
|
|
347
|
+
|
|
348
|
+
const systemCommand = resolveSystemCliCommand(normalized);
|
|
349
|
+
if (systemCommand) return systemCommand;
|
|
350
|
+
|
|
351
|
+
if (/(对话|dialog)/i.test(normalized)) {
|
|
352
|
+
const dialogRef = extractDialogRef(normalized);
|
|
353
|
+
if (dialogRef && wantsRead(normalized)) {
|
|
354
|
+
return ["dialog", "read", dialogRef];
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const shouldList = /(查|查询|查看|列出|找|最近|list|show|latest|recent)/i.test(normalized);
|
|
358
|
+
if (shouldList && !dialogRef && !wantsAnalysisOrComposition(normalized)) {
|
|
359
|
+
const args = ["dialog", "list"];
|
|
360
|
+
const limit = resolveDialogListLimit(normalized);
|
|
361
|
+
if (limit) args.push("--limit", limit);
|
|
362
|
+
return args;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return resolveResourceCliCommand(normalized);
|
|
367
|
+
}
|
|
368
|
+
|
|
206
369
|
export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
207
370
|
const trimmed = input.trim();
|
|
208
371
|
if (!trimmed) {
|
|
@@ -210,6 +373,18 @@ export function handleTuiInput(input: string, state: TuiState): TuiInputResult {
|
|
|
210
373
|
}
|
|
211
374
|
|
|
212
375
|
if (!trimmed.startsWith("/")) {
|
|
376
|
+
const cliArgs = resolveNaturalLanguageCliCommand(trimmed);
|
|
377
|
+
if (cliArgs) {
|
|
378
|
+
return {
|
|
379
|
+
nextState: state,
|
|
380
|
+
output: `[nolo] ${formatCliCommand(cliArgs)}`,
|
|
381
|
+
action: {
|
|
382
|
+
type: "cli-command",
|
|
383
|
+
args: cliArgs,
|
|
384
|
+
},
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
213
388
|
return {
|
|
214
389
|
nextState: state,
|
|
215
390
|
output: "",
|