nolo-cli 0.1.21 → 0.1.23
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/agent-runtime/agentRecordConfig.ts +4 -0
- package/agent-runtime/hostAdapter.ts +2 -0
- package/agent-runtime/index.ts +7 -0
- package/agent-runtime/localLoop.ts +2 -0
- package/agent-runtime/platformChatProvider.ts +3 -0
- package/agent-runtime/runtimeToolPolicy.ts +92 -0
- package/agent-runtime/types.ts +42 -0
- package/agentRunCommand.ts +74 -1
- package/agentRuntimeCommands.ts +17 -89
- package/ai/agent/streamAgentChatTurn.ts +104 -20
- package/ai/chat/fetchUtils.native.ts +2 -0
- package/ai/chat/fetchUtils.ts +2 -0
- package/ai/chat/sendOpenAICompletionsRequest.ts +56 -0
- package/ai/chat/sendOpenAIResponseRequest.ts +64 -0
- package/ai/llm/kimi.ts +1 -1
- package/ai/llm/providers.ts +3 -0
- package/ai/llm/reasoningModels.ts +1 -0
- package/ai/skills/skillDocProtocol.ts +95 -3
- package/ai/taskRun/taskRunProtocol.ts +1 -0
- package/ai/tools/agent/agentTools.ts +17 -0
- package/ai/tools/agent/startAgentDialogTool.ts +53 -0
- package/ai/tools/modelUsageTools.ts +5 -0
- package/client/agentRun.test.ts +257 -7
- package/client/agentRun.ts +133 -34
- package/client/localRuntimeAdapter.test.ts +2 -0
- package/client/localRuntimeAdapter.ts +15 -2
- package/database/actions/common.ts +4 -3
- package/database/config.ts +19 -0
- package/machineCommands.ts +400 -45
- package/package.json +4 -2
- package/render/canvas/canvasEditContext.ts +127 -0
- package/render/canvas/canvasRuntime.ts +57 -0
- package/render/canvas/canvasSnapshotParser.ts +76 -0
- package/render/canvas/canvasTree.ts +308 -0
- package/render/canvas/types.ts +46 -0
- package/render/layout/deleteBehavior.ts +52 -0
- package/render/layout/mainLayoutSidebar.ts +17 -0
- package/render/layout/mainLayoutViewMode.ts +56 -0
- package/render/layout/topbarUtils.ts +87 -0
- package/render/layout/useDevReloadPending.ts +30 -0
- package/render/page/createPageAction.ts +183 -0
- package/render/page/docSlice.ts +468 -0
- package/render/page/server/createPage.ts +174 -0
- package/render/page/server/handleCreatePage.ts +91 -0
- package/render/page/server/index.ts +4 -0
- package/render/page/types.ts +17 -0
- package/render/page/useKeyboardSave.ts +48 -0
- package/render/styles/zIndex.ts +12 -0
- package/render/surf/WeatherIconStyles.ts +17 -0
- package/render/surf/color.ts +9 -0
- package/render/surf/config.ts +46 -0
- package/render/surf/screens/style.ts +1 -0
- package/render/surf/styles/ToggleButtonStyles.ts +8 -0
- package/render/surf/utils/groupedWeatherData.ts +32 -0
- package/render/surf/weatherUtils.ts +50 -0
- package/render/table/activityColumns.ts +6 -0
- package/render/table/createTableAction.ts +270 -0
- package/render/table/deleteTableAction.ts +129 -0
- package/render/table/fetchAndCacheTableRows.ts +174 -0
- package/render/table/tableSlice.ts +1106 -0
- package/render/table/tableView.ts +289 -0
- package/render/table/toolValueUtils.ts +363 -0
- package/render/table/types.ts +252 -0
- package/render/table/useCreateTable.ts +72 -0
- package/render/table/useTable.ts +61 -0
- package/render/table/utils/tableSerialization.ts +50 -0
- package/render/web/elements/artifactPreviewCode.ts +43 -0
- package/render/web/elements/artifactRuntimePreload.ts +52 -0
- package/render/web/elements/codeBlockAutoPreview.ts +10 -0
- package/render/web/elements/mermaidPreview.ts +21 -0
- package/render/web/ui/useInlineEdit.ts +135 -0
- package/tableCommands.ts +42 -5
package/machineCommands.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import { mkdirSync, openSync } from "node:fs";
|
|
5
5
|
import { homedir } from "node:os";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
+
import { runLocalAgentTurn, type LocalAgentToolEvent } from "./agent-runtime/localLoop";
|
|
7
8
|
import { DEFAULT_NOLO_SERVER_URL } from "./defaultServer";
|
|
8
9
|
import {
|
|
9
10
|
collectConnectorRunArtifact,
|
|
@@ -19,7 +20,9 @@ import {
|
|
|
19
20
|
buildMachinePermissionPromptBlock,
|
|
20
21
|
resolveMachineRunPermissionPolicy,
|
|
21
22
|
} from "./ai/agent/machineRunPermissions";
|
|
22
|
-
import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
|
|
23
|
+
import { resolveConnectorWebSocketTarget } from "./connectorWebSocketTarget";
|
|
24
|
+
import { createCliLocalRuntimeAdapter } from "./client/localRuntimeAdapter";
|
|
25
|
+
import { prepareTaskWorktree } from "./client/taskWorktree";
|
|
23
26
|
|
|
24
27
|
type EnvLike = Record<string, string | undefined>;
|
|
25
28
|
type OutputLike = { write(chunk: string): unknown };
|
|
@@ -28,11 +31,35 @@ type ConnectorWebSocketOptions = {
|
|
|
28
31
|
onMessage: (message: string) => void | Promise<void>;
|
|
29
32
|
sentMessages: string[];
|
|
30
33
|
};
|
|
31
|
-
type LocalCliExecutor = (
|
|
34
|
+
export type LocalCliExecutor = (
|
|
32
35
|
provider: string,
|
|
33
36
|
prompt: string,
|
|
34
37
|
options: { model?: string; timeout?: number; cwd?: string; yolo?: boolean; env?: EnvLike }
|
|
35
38
|
) => Promise<{ text: string; raw?: string; elapsed?: number }>;
|
|
39
|
+
|
|
40
|
+
type ConnectorRuntimePolicy = {
|
|
41
|
+
runtimeTools?: string[];
|
|
42
|
+
agentTools?: string[];
|
|
43
|
+
workspace?: {
|
|
44
|
+
mode?: string;
|
|
45
|
+
cwd?: string;
|
|
46
|
+
};
|
|
47
|
+
shell?: {
|
|
48
|
+
enabled?: boolean;
|
|
49
|
+
mode?: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type ConnectorRunProgress = {
|
|
54
|
+
eventType?: LocalAgentToolEvent["type"];
|
|
55
|
+
toolCallCount?: number;
|
|
56
|
+
toolResultCount?: number;
|
|
57
|
+
lastToolNames?: string[];
|
|
58
|
+
workspaceRoot?: string;
|
|
59
|
+
workspaceKind?: string;
|
|
60
|
+
message?: string;
|
|
61
|
+
updatedAt?: number;
|
|
62
|
+
};
|
|
36
63
|
|
|
37
64
|
export type MachineSummary = {
|
|
38
65
|
machineId: string;
|
|
@@ -264,8 +291,8 @@ function buildTaskRunBridgePrompt(args: {
|
|
|
264
291
|
function buildConnectorCliPrompt(agentConfig: any, userInput: string, bridgeArgs?: {
|
|
265
292
|
agentKey: string;
|
|
266
293
|
runtimeContext: any;
|
|
267
|
-
}) {
|
|
268
|
-
const policy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
294
|
+
}, permissionPolicy?: ReturnType<typeof resolveMachineRunPermissionPolicy>) {
|
|
295
|
+
const policy = permissionPolicy ?? resolveMachineRunPermissionPolicy(agentConfig);
|
|
269
296
|
return [
|
|
270
297
|
typeof agentConfig?.prompt === "string" ? agentConfig.prompt.trim() : "",
|
|
271
298
|
bridgeArgs ? buildTaskRunBridgePrompt({
|
|
@@ -278,6 +305,250 @@ function buildConnectorCliPrompt(agentConfig: any, userInput: string, bridgeArgs
|
|
|
278
305
|
].filter(Boolean).join("\n\n");
|
|
279
306
|
}
|
|
280
307
|
|
|
308
|
+
function isRecord(value: unknown): value is Record<string, any> {
|
|
309
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function runtimePolicyFromConnectorPayload(parsed: any): ConnectorRuntimePolicy | undefined {
|
|
313
|
+
const fromMeta = parsed?.payload?.meta?.runtimeToolPolicySnapshot;
|
|
314
|
+
const fromAgent = parsed?.payload?.agentConfig?.runtimeToolPolicy;
|
|
315
|
+
const policy = isRecord(fromMeta) ? fromMeta : isRecord(fromAgent) ? fromAgent : null;
|
|
316
|
+
return policy ? policy as ConnectorRuntimePolicy : undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function requestsTaskWorktree(policy?: ConnectorRuntimePolicy) {
|
|
320
|
+
return policy?.workspace?.mode === "task-worktree" || policy?.workspace?.mode === "lease";
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function hasExplicitMachinePermissions(agentConfig: any) {
|
|
324
|
+
const runtimeBinding = isRecord(agentConfig?.runtimeBinding) ? agentConfig.runtimeBinding : {};
|
|
325
|
+
return Boolean(
|
|
326
|
+
isRecord(agentConfig?.machinePermissions) ||
|
|
327
|
+
isRecord(runtimeBinding.permissions) ||
|
|
328
|
+
isRecord(runtimeBinding.machinePermissions) ||
|
|
329
|
+
isRecord(agentConfig?.boundRuntimeMachine?.permissions)
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function runtimeWorkspacePermissionPolicy(
|
|
334
|
+
runtimePolicy: ConnectorRuntimePolicy | undefined,
|
|
335
|
+
cwd: string
|
|
336
|
+
): ReturnType<typeof resolveMachineRunPermissionPolicy> | null {
|
|
337
|
+
if (!requestsTaskWorktree(runtimePolicy)) return null;
|
|
338
|
+
return {
|
|
339
|
+
mode: "full_access",
|
|
340
|
+
allowFilesystemRead: true,
|
|
341
|
+
allowFilesystemWrite: true,
|
|
342
|
+
allowShell: runtimePolicy?.shell?.enabled !== false,
|
|
343
|
+
writableRoots: [cwd],
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function scopePermissionPolicyToRuntimeWorkspace(
|
|
348
|
+
policy: ReturnType<typeof resolveMachineRunPermissionPolicy>,
|
|
349
|
+
runtimePolicy: ConnectorRuntimePolicy | undefined,
|
|
350
|
+
cwd: string
|
|
351
|
+
): ReturnType<typeof resolveMachineRunPermissionPolicy> {
|
|
352
|
+
if (!requestsTaskWorktree(runtimePolicy)) return policy;
|
|
353
|
+
return {
|
|
354
|
+
...policy,
|
|
355
|
+
writableRoots: policy.allowFilesystemWrite ? [cwd] : [],
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function mergeToolNames(...values: unknown[]) {
|
|
360
|
+
const names: string[] = [];
|
|
361
|
+
for (const value of values) {
|
|
362
|
+
if (!Array.isArray(value)) continue;
|
|
363
|
+
for (const item of value) {
|
|
364
|
+
const name = typeof item === "string"
|
|
365
|
+
? item
|
|
366
|
+
: isRecord(item) && typeof item.name === "string"
|
|
367
|
+
? item.name
|
|
368
|
+
: isRecord(item) && typeof item.function?.name === "string"
|
|
369
|
+
? item.function.name
|
|
370
|
+
: "";
|
|
371
|
+
if (name && !names.includes(name)) names.push(name);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
return names;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function localRuntimeEnvFromPolicy(runtimeEnv: EnvLike, policy?: ConnectorRuntimePolicy): EnvLike {
|
|
378
|
+
return {
|
|
379
|
+
...runtimeEnv,
|
|
380
|
+
...(policy?.shell?.enabled && policy.shell.mode === "worktree"
|
|
381
|
+
? { NOLO_LOCAL_SHELL_MODE: "worktree" }
|
|
382
|
+
: {}),
|
|
383
|
+
...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function runtimeWorkspaceRootFromTrace(trace: unknown): string | undefined {
|
|
388
|
+
if (!Array.isArray(trace)) return undefined;
|
|
389
|
+
for (const message of trace) {
|
|
390
|
+
if (!isRecord(message)) continue;
|
|
391
|
+
const metadata = isRecord(message.tool_result_metadata)
|
|
392
|
+
? message.tool_result_metadata
|
|
393
|
+
: null;
|
|
394
|
+
const workspaceRoot = metadata && typeof metadata.workspaceRoot === "string"
|
|
395
|
+
? metadata.workspaceRoot.trim()
|
|
396
|
+
: "";
|
|
397
|
+
if (workspaceRoot) return workspaceRoot;
|
|
398
|
+
}
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function buildArtifactProgress(args: {
|
|
403
|
+
artifacts: unknown;
|
|
404
|
+
runtimePolicy?: ConnectorRuntimePolicy;
|
|
405
|
+
workspaceRoot: string;
|
|
406
|
+
workspaceKind?: string;
|
|
407
|
+
}): ConnectorRunProgress | null {
|
|
408
|
+
if (!isRecord(args.artifacts)) return null;
|
|
409
|
+
const changedFiles = Array.isArray(args.artifacts.changedFiles)
|
|
410
|
+
? args.artifacts.changedFiles
|
|
411
|
+
: [];
|
|
412
|
+
const statusShort = typeof args.artifacts.statusShort === "string"
|
|
413
|
+
? args.artifacts.statusShort.trim()
|
|
414
|
+
: "";
|
|
415
|
+
if (changedFiles.length === 0 && !statusShort) return null;
|
|
416
|
+
const runtimeTools = Array.isArray(args.runtimePolicy?.runtimeTools)
|
|
417
|
+
? args.runtimePolicy.runtimeTools
|
|
418
|
+
: [];
|
|
419
|
+
const shellTool = runtimeTools.includes("execBash")
|
|
420
|
+
? "execBash"
|
|
421
|
+
: runtimeTools.includes("execShell")
|
|
422
|
+
? "execShell"
|
|
423
|
+
: "workspaceArtifact";
|
|
424
|
+
return {
|
|
425
|
+
eventType: "tool-result",
|
|
426
|
+
toolCallCount: 1,
|
|
427
|
+
toolResultCount: 1,
|
|
428
|
+
lastToolNames: [shellTool],
|
|
429
|
+
workspaceRoot: args.workspaceRoot,
|
|
430
|
+
...(args.workspaceKind ? { workspaceKind: args.workspaceKind } : {}),
|
|
431
|
+
message: statusShort || `${changedFiles.length} changed file(s)`,
|
|
432
|
+
updatedAt: Date.now(),
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
async function runConnectorLocalRuntimeAgent(args: {
|
|
437
|
+
parsed: any;
|
|
438
|
+
runtimeEnv: EnvLike;
|
|
439
|
+
cwd: string;
|
|
440
|
+
fetchImpl?: typeof fetch;
|
|
441
|
+
onProgress?: (progress: ConnectorRunProgress) => void;
|
|
442
|
+
}): Promise<Awaited<ReturnType<typeof runLocalAgentTurn>> & { runtimeWorkspaceRoot?: string }> {
|
|
443
|
+
const agentKey = String(args.parsed.payload?.agentKey ?? "");
|
|
444
|
+
const payloadAgentConfig = isRecord(args.parsed.payload?.agentConfig)
|
|
445
|
+
? args.parsed.payload.agentConfig
|
|
446
|
+
: {};
|
|
447
|
+
const policy = runtimePolicyFromConnectorPayload(args.parsed);
|
|
448
|
+
const agentRecord = {
|
|
449
|
+
...payloadAgentConfig,
|
|
450
|
+
dbKey: agentKey,
|
|
451
|
+
id: agentKey,
|
|
452
|
+
key: agentKey,
|
|
453
|
+
apiSource: payloadAgentConfig.apiSource ?? "platform",
|
|
454
|
+
provider: payloadAgentConfig.provider ?? payloadAgentConfig.apiSource ?? "openai",
|
|
455
|
+
toolNames: mergeToolNames(
|
|
456
|
+
payloadAgentConfig.toolNames,
|
|
457
|
+
payloadAgentConfig.tools,
|
|
458
|
+
policy?.agentTools,
|
|
459
|
+
policy?.runtimeTools,
|
|
460
|
+
),
|
|
461
|
+
...(policy ? { runtimeToolPolicy: policy } : {}),
|
|
462
|
+
...(policy?.workspace?.mode === "task-worktree" || policy?.workspace?.mode === "lease"
|
|
463
|
+
? { localWorkspaceMode: "task-worktree" }
|
|
464
|
+
: {}),
|
|
465
|
+
};
|
|
466
|
+
const store = new Map<string, any>([
|
|
467
|
+
[agentKey, agentRecord],
|
|
468
|
+
[`agent-${args.runtimeEnv.NOLO_USER_ID || "local"}-${agentKey}`, agentRecord],
|
|
469
|
+
[`agent-${args.runtimeEnv.NOLO_LOCAL_USER_ID || args.runtimeEnv.NOLO_USER_ID || "local"}-${agentKey}`, agentRecord],
|
|
470
|
+
]);
|
|
471
|
+
const env = localRuntimeEnvFromPolicy(args.runtimeEnv, policy);
|
|
472
|
+
const wantsTaskWorktree = requestsTaskWorktree(policy);
|
|
473
|
+
const adapter = createCliLocalRuntimeAdapter({
|
|
474
|
+
env,
|
|
475
|
+
cwd: args.cwd,
|
|
476
|
+
useCwdAsTaskWorkspaceBase: wantsTaskWorktree,
|
|
477
|
+
...(wantsTaskWorktree
|
|
478
|
+
? {
|
|
479
|
+
prepareTaskWorktree: (input) => prepareTaskWorktree({
|
|
480
|
+
...input,
|
|
481
|
+
cwd: args.cwd,
|
|
482
|
+
}),
|
|
483
|
+
}
|
|
484
|
+
: {}),
|
|
485
|
+
fetchImpl: args.fetchImpl ?? fetch,
|
|
486
|
+
store: {
|
|
487
|
+
read: async (key) => store.get(key) ?? null,
|
|
488
|
+
batch: async (ops) => {
|
|
489
|
+
for (const op of ops) {
|
|
490
|
+
if (op.type === "put") store.set(op.key, op.value);
|
|
491
|
+
}
|
|
492
|
+
},
|
|
493
|
+
iterator: async function* (options) {
|
|
494
|
+
const keys = [...store.keys()].sort();
|
|
495
|
+
for (const key of keys) {
|
|
496
|
+
if (key < options.gte) continue;
|
|
497
|
+
if (options.lte && key > options.lte) continue;
|
|
498
|
+
if (options.lt && key >= options.lt) continue;
|
|
499
|
+
yield [key, store.get(key)];
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
let toolCallCount = 0;
|
|
505
|
+
let toolResultCount = 0;
|
|
506
|
+
const lastToolNames: string[] = [];
|
|
507
|
+
let runtimeWorkspaceRoot: string | undefined;
|
|
508
|
+
let runtimeWorkspaceKind: string | undefined;
|
|
509
|
+
const noteToolName = (toolName: string) => {
|
|
510
|
+
if (!toolName) return;
|
|
511
|
+
const existingIndex = lastToolNames.indexOf(toolName);
|
|
512
|
+
if (existingIndex >= 0) lastToolNames.splice(existingIndex, 1);
|
|
513
|
+
lastToolNames.push(toolName);
|
|
514
|
+
while (lastToolNames.length > 8) lastToolNames.shift();
|
|
515
|
+
};
|
|
516
|
+
const result = await runLocalAgentTurn({
|
|
517
|
+
adapter,
|
|
518
|
+
agentRef: agentKey,
|
|
519
|
+
input: String(args.parsed.payload?.userInput ?? ""),
|
|
520
|
+
continueDialogId: typeof args.parsed.payload?.continueDialogId === "string"
|
|
521
|
+
? args.parsed.payload.continueDialogId
|
|
522
|
+
: undefined,
|
|
523
|
+
onToolEvent: (event) => {
|
|
524
|
+
if (event.type === "tool-call") toolCallCount += 1;
|
|
525
|
+
if (event.type === "tool-result") toolResultCount += 1;
|
|
526
|
+
noteToolName(event.toolName);
|
|
527
|
+
const metadata = isRecord(event.metadata) ? event.metadata : {};
|
|
528
|
+
if (typeof metadata.workspaceRoot === "string" && metadata.workspaceRoot.trim()) {
|
|
529
|
+
runtimeWorkspaceRoot = metadata.workspaceRoot.trim();
|
|
530
|
+
}
|
|
531
|
+
if (typeof metadata.workspaceKind === "string" && metadata.workspaceKind.trim()) {
|
|
532
|
+
runtimeWorkspaceKind = metadata.workspaceKind.trim();
|
|
533
|
+
}
|
|
534
|
+
args.onProgress?.({
|
|
535
|
+
eventType: event.type,
|
|
536
|
+
toolCallCount,
|
|
537
|
+
toolResultCount,
|
|
538
|
+
lastToolNames: [...lastToolNames],
|
|
539
|
+
...(runtimeWorkspaceRoot ? { workspaceRoot: runtimeWorkspaceRoot } : {}),
|
|
540
|
+
...(runtimeWorkspaceKind ? { workspaceKind: runtimeWorkspaceKind } : {}),
|
|
541
|
+
...(event.message ? { message: event.message } : {}),
|
|
542
|
+
updatedAt: Date.now(),
|
|
543
|
+
});
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
return {
|
|
547
|
+
...result,
|
|
548
|
+
runtimeWorkspaceRoot: runtimeWorkspaceRootFromTrace(result.trace) ?? runtimeWorkspaceRoot,
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
281
552
|
function normalizeConnectorRunTimeoutMs(value: unknown): number | undefined {
|
|
282
553
|
if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
|
|
283
554
|
return undefined;
|
|
@@ -285,12 +556,13 @@ function normalizeConnectorRunTimeoutMs(value: unknown): number | undefined {
|
|
|
285
556
|
return Math.floor(value);
|
|
286
557
|
}
|
|
287
558
|
|
|
288
|
-
async function handleConnectorRunMessage(
|
|
559
|
+
export async function handleConnectorRunMessage(
|
|
289
560
|
machine: MachineHeartbeat,
|
|
290
561
|
message: string,
|
|
291
562
|
send: (message: string) => void,
|
|
292
563
|
executeCli: LocalCliExecutor,
|
|
293
|
-
runtimeEnv: EnvLike
|
|
564
|
+
runtimeEnv: EnvLike,
|
|
565
|
+
fetchImpl?: typeof fetch
|
|
294
566
|
) {
|
|
295
567
|
let parsed: any;
|
|
296
568
|
try {
|
|
@@ -298,51 +570,129 @@ async function handleConnectorRunMessage(
|
|
|
298
570
|
} catch {
|
|
299
571
|
return;
|
|
300
572
|
}
|
|
301
|
-
if (parsed?.type !== "agent.run" || typeof parsed.requestId !== "string") return;
|
|
302
|
-
const agentConfig = parsed.payload?.agentConfig ?? {};
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
573
|
+
if (parsed?.type !== "agent.run" || typeof parsed.requestId !== "string") return;
|
|
574
|
+
const agentConfig = parsed.payload?.agentConfig ?? {};
|
|
575
|
+
const sendProgress = (progress: ConnectorRunProgress) => {
|
|
576
|
+
send(JSON.stringify({
|
|
577
|
+
type: "agent.run.progress",
|
|
578
|
+
requestId: parsed.requestId,
|
|
579
|
+
progress,
|
|
580
|
+
}));
|
|
581
|
+
};
|
|
582
|
+
try {
|
|
583
|
+
const machinePermissionPolicy = resolveMachineRunPermissionPolicy(agentConfig);
|
|
584
|
+
const runtimePolicy = runtimePolicyFromConnectorPayload(parsed);
|
|
309
585
|
const userInput = String(parsed.payload?.userInput ?? "");
|
|
310
586
|
const timeout = normalizeConnectorRunTimeoutMs(parsed.payload?.timeoutMs);
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
587
|
+
let cwd = resolveConnectorRunCwd({ env: runtimeEnv, policy: machinePermissionPolicy });
|
|
588
|
+
let runContent = "";
|
|
589
|
+
let runModel = agentConfig.model;
|
|
590
|
+
let runTrace: unknown[] = [];
|
|
591
|
+
let artifactCwd = cwd;
|
|
592
|
+
let baseSha: string | null = null;
|
|
593
|
+
if (agentConfig.apiSource === "cli") {
|
|
594
|
+
if (requestsTaskWorktree(runtimePolicy)) {
|
|
595
|
+
const prepared = await prepareTaskWorktree({
|
|
596
|
+
agentKey: String(parsed.payload?.agentKey ?? "agent"),
|
|
597
|
+
cwd,
|
|
598
|
+
env: runtimeEnv,
|
|
599
|
+
});
|
|
600
|
+
cwd = prepared.path;
|
|
601
|
+
artifactCwd = prepared.path;
|
|
602
|
+
}
|
|
603
|
+
const runtimePermissionPolicy = !hasExplicitMachinePermissions(agentConfig)
|
|
604
|
+
? runtimeWorkspacePermissionPolicy(runtimePolicy, cwd)
|
|
605
|
+
: null;
|
|
606
|
+
const effectivePermissionPolicy = scopePermissionPolicyToRuntimeWorkspace(
|
|
607
|
+
runtimePermissionPolicy ?? machinePermissionPolicy,
|
|
608
|
+
runtimePolicy,
|
|
323
609
|
cwd,
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
610
|
+
);
|
|
611
|
+
assertMachineRunAllowed(userInput, effectivePermissionPolicy);
|
|
612
|
+
baseSha = await readConnectorGitHead(artifactCwd);
|
|
613
|
+
const provider = String(agentConfig.cliProvider || "copilot");
|
|
614
|
+
const result = await executeCli(
|
|
615
|
+
provider,
|
|
616
|
+
buildConnectorCliPrompt(agentConfig, userInput, {
|
|
617
|
+
agentKey: String(parsed.payload?.agentKey ?? ""),
|
|
618
|
+
runtimeContext: parsed.payload?.runtimeContext,
|
|
619
|
+
}, effectivePermissionPolicy),
|
|
620
|
+
{
|
|
621
|
+
model: agentConfig.model || undefined,
|
|
622
|
+
timeout,
|
|
623
|
+
cwd,
|
|
624
|
+
yolo: true,
|
|
625
|
+
env: {
|
|
626
|
+
NOLO_SERVER: runtimeEnv.NOLO_SERVER || runtimeEnv.NOLO_SERVER_URL || runtimeEnv.BASE_URL,
|
|
627
|
+
NOLO_SERVER_URL: runtimeEnv.NOLO_SERVER_URL || runtimeEnv.NOLO_SERVER || runtimeEnv.BASE_URL,
|
|
628
|
+
BASE_URL: runtimeEnv.BASE_URL || runtimeEnv.NOLO_SERVER || runtimeEnv.NOLO_SERVER_URL,
|
|
629
|
+
AUTH_TOKEN: runtimeEnv.AUTH_TOKEN || runtimeEnv.AUTH || runtimeEnv.NOLO_MACHINE_API_KEY,
|
|
630
|
+
NOLO_MACHINE_API_KEY: runtimeEnv.NOLO_MACHINE_API_KEY || runtimeEnv.AUTH_TOKEN || runtimeEnv.AUTH,
|
|
631
|
+
},
|
|
632
|
+
}
|
|
633
|
+
);
|
|
634
|
+
runContent = result.text;
|
|
635
|
+
runModel = agentConfig.model ?? provider;
|
|
636
|
+
runTrace = [{ role: "assistant", content: result.text }];
|
|
637
|
+
const artifacts = await collectConnectorRunArtifact({
|
|
638
|
+
cwd: artifactCwd,
|
|
639
|
+
baseSha,
|
|
640
|
+
exitStatus: "completed",
|
|
641
|
+
});
|
|
642
|
+
const artifactProgress = buildArtifactProgress({
|
|
643
|
+
artifacts,
|
|
644
|
+
runtimePolicy,
|
|
645
|
+
workspaceRoot: artifactCwd,
|
|
646
|
+
workspaceKind: requestsTaskWorktree(runtimePolicy) ? "task-worktree" : "current",
|
|
647
|
+
});
|
|
648
|
+
if (artifactProgress && requestsTaskWorktree(runtimePolicy)) sendProgress(artifactProgress);
|
|
649
|
+
send(JSON.stringify({
|
|
650
|
+
type: "agent.run.result",
|
|
651
|
+
requestId: parsed.requestId,
|
|
652
|
+
result: {
|
|
653
|
+
content: runContent,
|
|
654
|
+
model: runModel,
|
|
655
|
+
trace: runTrace,
|
|
656
|
+
artifacts,
|
|
331
657
|
},
|
|
658
|
+
}));
|
|
659
|
+
return;
|
|
660
|
+
} else {
|
|
661
|
+
if (!runtimePolicy) {
|
|
662
|
+
throw new Error("Connector can only execute non-CLI agents when runtimeToolPolicySnapshot requests a local workspace runtime.");
|
|
332
663
|
}
|
|
333
|
-
|
|
664
|
+
baseSha = await readConnectorGitHead(cwd);
|
|
665
|
+
const result = await runConnectorLocalRuntimeAgent({
|
|
666
|
+
parsed,
|
|
667
|
+
runtimeEnv,
|
|
668
|
+
cwd,
|
|
669
|
+
fetchImpl,
|
|
670
|
+
onProgress: sendProgress,
|
|
671
|
+
});
|
|
672
|
+
runContent = result.content;
|
|
673
|
+
runModel = result.model;
|
|
674
|
+
runTrace = result.trace ?? [];
|
|
675
|
+
artifactCwd = result.runtimeWorkspaceRoot ?? cwd;
|
|
676
|
+
}
|
|
334
677
|
const artifacts = await collectConnectorRunArtifact({
|
|
335
|
-
cwd,
|
|
678
|
+
cwd: artifactCwd,
|
|
336
679
|
baseSha,
|
|
337
680
|
exitStatus: "completed",
|
|
338
681
|
});
|
|
682
|
+
const artifactProgress = buildArtifactProgress({
|
|
683
|
+
artifacts,
|
|
684
|
+
runtimePolicy,
|
|
685
|
+
workspaceRoot: artifactCwd,
|
|
686
|
+
workspaceKind: requestsTaskWorktree(runtimePolicy) ? "task-worktree" : "current",
|
|
687
|
+
});
|
|
688
|
+
if (artifactProgress && requestsTaskWorktree(runtimePolicy)) sendProgress(artifactProgress);
|
|
339
689
|
send(JSON.stringify({
|
|
340
|
-
type: "agent.run.result",
|
|
341
|
-
requestId: parsed.requestId,
|
|
342
|
-
result: {
|
|
343
|
-
content:
|
|
344
|
-
model:
|
|
345
|
-
trace:
|
|
690
|
+
type: "agent.run.result",
|
|
691
|
+
requestId: parsed.requestId,
|
|
692
|
+
result: {
|
|
693
|
+
content: runContent,
|
|
694
|
+
model: runModel,
|
|
695
|
+
trace: runTrace,
|
|
346
696
|
artifacts,
|
|
347
697
|
},
|
|
348
698
|
}));
|
|
@@ -385,6 +735,10 @@ async function runConnectorWebSocketSession(options: {
|
|
|
385
735
|
headers: { Authorization: `Bearer ${options.authToken}` },
|
|
386
736
|
fetchImpl: options.fetchImpl,
|
|
387
737
|
});
|
|
738
|
+
const runtimeServerUrl =
|
|
739
|
+
options.env.NOLO_CONNECT_RUNTIME_SERVER_URL ||
|
|
740
|
+
options.env.NOLO_RUNTIME_SERVER_URL ||
|
|
741
|
+
options.serverUrl;
|
|
388
742
|
const websocketPromise = options.connectWebSocket(
|
|
389
743
|
wsTarget,
|
|
390
744
|
{
|
|
@@ -397,12 +751,13 @@ async function runConnectorWebSocketSession(options: {
|
|
|
397
751
|
options.executeCli,
|
|
398
752
|
{
|
|
399
753
|
...options.env,
|
|
400
|
-
NOLO_SERVER:
|
|
401
|
-
NOLO_SERVER_URL:
|
|
402
|
-
BASE_URL:
|
|
754
|
+
NOLO_SERVER: runtimeServerUrl,
|
|
755
|
+
NOLO_SERVER_URL: runtimeServerUrl,
|
|
756
|
+
BASE_URL: runtimeServerUrl,
|
|
403
757
|
AUTH_TOKEN: options.authToken,
|
|
404
758
|
NOLO_MACHINE_API_KEY: options.authToken,
|
|
405
|
-
}
|
|
759
|
+
},
|
|
760
|
+
options.fetchImpl
|
|
406
761
|
),
|
|
407
762
|
}
|
|
408
763
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nolo-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first terminal workspace for Nolo",
|
|
6
6
|
"bin": {
|
|
@@ -33,7 +33,8 @@
|
|
|
33
33
|
"agent-runtime/**/*.ts",
|
|
34
34
|
"database/**/*.ts",
|
|
35
35
|
"connector-experimental/**/*.ts",
|
|
36
|
-
"ai/**/*.ts"
|
|
36
|
+
"ai/**/*.ts",
|
|
37
|
+
"render/**/*.ts"
|
|
37
38
|
],
|
|
38
39
|
"publishConfig": {
|
|
39
40
|
"access": "public"
|
|
@@ -47,6 +48,7 @@
|
|
|
47
48
|
"dependencies": {
|
|
48
49
|
"date-fns-tz": "^2.0.0",
|
|
49
50
|
"level": "^10.0.0",
|
|
51
|
+
"js-yaml": "^4.1.0",
|
|
50
52
|
"ulid": "^2.3.0"
|
|
51
53
|
}
|
|
52
54
|
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { AgentRuntimeOptions } from "ai/agent/types";
|
|
3
|
+
import type { CanvasEvent } from "./types";
|
|
4
|
+
|
|
5
|
+
export type CanvasEditSelection = {
|
|
6
|
+
sourceMessageId?: string;
|
|
7
|
+
selectedNodeId: string;
|
|
8
|
+
part: string;
|
|
9
|
+
path: string[];
|
|
10
|
+
type: string;
|
|
11
|
+
props: Record<string, unknown>;
|
|
12
|
+
style: Record<string, unknown>;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const CANVAS_EDIT_SELECTION_EVENT = "nolo:canvas-edit-selection";
|
|
16
|
+
|
|
17
|
+
let currentSelection: CanvasEditSelection | null = null;
|
|
18
|
+
let pendingSelection: CanvasEditSelection | null = null;
|
|
19
|
+
const listeners = new Set<(selection: CanvasEditSelection | null) => void>();
|
|
20
|
+
const patchListeners = new Map<string, Set<(events: CanvasEvent[]) => void>>();
|
|
21
|
+
const queuedPatches = new Map<string, CanvasEvent[][]>();
|
|
22
|
+
|
|
23
|
+
export function publishCanvasEditSelection(
|
|
24
|
+
selection: CanvasEditSelection | null
|
|
25
|
+
) {
|
|
26
|
+
currentSelection = selection;
|
|
27
|
+
listeners.forEach((listener) => listener(selection));
|
|
28
|
+
|
|
29
|
+
if (typeof window !== "undefined") {
|
|
30
|
+
window.dispatchEvent(
|
|
31
|
+
new CustomEvent(CANVAS_EDIT_SELECTION_EVENT, { detail: selection })
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function subscribeCanvasEditSelection(
|
|
37
|
+
listener: (selection: CanvasEditSelection | null) => void
|
|
38
|
+
) {
|
|
39
|
+
listeners.add(listener);
|
|
40
|
+
listener(currentSelection);
|
|
41
|
+
return () => {
|
|
42
|
+
listeners.delete(listener);
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function useCanvasEditSelection() {
|
|
47
|
+
const [selection, setSelection] =
|
|
48
|
+
React.useState<CanvasEditSelection | null>(currentSelection);
|
|
49
|
+
|
|
50
|
+
React.useEffect(
|
|
51
|
+
() => subscribeCanvasEditSelection(setSelection),
|
|
52
|
+
[]
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
return selection;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function markPendingCanvasEditSelection(
|
|
59
|
+
selection: CanvasEditSelection | null
|
|
60
|
+
) {
|
|
61
|
+
pendingSelection = selection;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function consumePendingCanvasEditSelection() {
|
|
65
|
+
const selection = pendingSelection;
|
|
66
|
+
pendingSelection = null;
|
|
67
|
+
return selection;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function publishCanvasMessagePatch(
|
|
71
|
+
sourceMessageId: string,
|
|
72
|
+
events: CanvasEvent[]
|
|
73
|
+
) {
|
|
74
|
+
if (!events.length) return;
|
|
75
|
+
|
|
76
|
+
const queued = queuedPatches.get(sourceMessageId) ?? [];
|
|
77
|
+
queued.push(events);
|
|
78
|
+
queuedPatches.set(sourceMessageId, queued);
|
|
79
|
+
|
|
80
|
+
patchListeners
|
|
81
|
+
.get(sourceMessageId)
|
|
82
|
+
?.forEach((listener) => listener(events));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function subscribeCanvasMessagePatches(
|
|
86
|
+
sourceMessageId: string,
|
|
87
|
+
listener: (events: CanvasEvent[]) => void
|
|
88
|
+
) {
|
|
89
|
+
const listenersForMessage = patchListeners.get(sourceMessageId) ?? new Set();
|
|
90
|
+
listenersForMessage.add(listener);
|
|
91
|
+
patchListeners.set(sourceMessageId, listenersForMessage);
|
|
92
|
+
|
|
93
|
+
queuedPatches
|
|
94
|
+
.get(sourceMessageId)
|
|
95
|
+
?.forEach((events) => listener(events));
|
|
96
|
+
|
|
97
|
+
return () => {
|
|
98
|
+
listenersForMessage.delete(listener);
|
|
99
|
+
if (!listenersForMessage.size) {
|
|
100
|
+
patchListeners.delete(sourceMessageId);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function buildCanvasNodeEditingTarget(
|
|
106
|
+
selection: CanvasEditSelection
|
|
107
|
+
): NonNullable<AgentRuntimeOptions["editingTarget"]> {
|
|
108
|
+
return {
|
|
109
|
+
kind: "canvas_node",
|
|
110
|
+
key: selection.selectedNodeId,
|
|
111
|
+
title: selection.part,
|
|
112
|
+
summary: [
|
|
113
|
+
"用户正在编辑 Canvas Tree 画布中的一个已选中节点。",
|
|
114
|
+
"只输出针对选中节点的 canvas_snapshot updateNode 事件,除非用户明确要求新增内容。",
|
|
115
|
+
"不要重建整棵树,不要输出 Markdown、解释、React、HTML、CSS 或 JS 源码。",
|
|
116
|
+
].join("\n"),
|
|
117
|
+
metadata: {
|
|
118
|
+
sourceMessageId: selection.sourceMessageId,
|
|
119
|
+
selectedNodeId: selection.selectedNodeId,
|
|
120
|
+
part: selection.part,
|
|
121
|
+
path: selection.path,
|
|
122
|
+
type: selection.type,
|
|
123
|
+
props: selection.props,
|
|
124
|
+
style: selection.style,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|