nolo-cli 0.1.23 → 0.1.26
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -3
- package/agent-runtime/hybridRecordStore.ts +30 -9
- package/agent-runtime/localLoop.ts +12 -4
- package/agent-runtime/localToolPolicy.ts +22 -10
- package/agent-runtime/localWorkspaceTools.ts +140 -374
- package/agent-runtime/runtimeToolPolicy.ts +43 -2
- package/agent-runtime/types.ts +5 -0
- package/agentAliases.ts +1 -1
- package/agentCommandSupport.ts +77 -0
- package/agentInternalCommandEntries.ts +34 -0
- package/agentListCommands.ts +123 -0
- package/agentListHelpers.ts +187 -0
- package/agentMachineCommands.ts +402 -0
- package/agentRecordCommands.ts +141 -0
- package/agentRecordHelpers.ts +395 -0
- package/agentRunCommand.ts +1 -2
- package/agentRuntimeCommands.ts +7 -701
- package/ai/agent/_executeModel.ts +29 -3
- package/ai/agent/buildSystemPrompt.ts +17 -0
- package/ai/agent/cliChatClient.ts +38 -4
- package/ai/agent/cliExecutor.ts +304 -25
- package/ai/agent/createAgentSchema.ts +2 -2
- package/ai/agent/guidedCreation/assistant.ts +82 -0
- package/ai/agent/guidedCreation/capabilities.ts +84 -0
- package/ai/agent/guidedCreation/draft.ts +62 -0
- package/ai/agent/guidedCreation/types.ts +38 -0
- package/ai/agent/machineRunPermissions.ts +21 -3
- package/ai/agent/runtimeGuidance.ts +1 -1
- package/ai/agent/server/fetchPublicAgents.ts +6 -1
- package/ai/agent/streamAgentChatTurn.ts +492 -19
- package/ai/agent/web/useGuidedAgentCreation.ts +217 -0
- package/ai/ai.locale.ts +14 -2
- package/ai/chat/sendOpenAICompletionsRequest.ts +28 -3
- package/ai/chat/sendOpenAIResponseRequest.ts +3 -2
- package/ai/llm/getPricing.ts +14 -0
- package/ai/llm/kimi.ts +3 -0
- package/ai/llm/modelAvatar.ts +1 -0
- package/ai/llm/providers.ts +1 -1
- package/ai/taskRun/taskRunProtocol.ts +24 -10
- package/ai/token/modelUsageQuery.ts +17 -0
- package/ai/tools/agent/agentTools.ts +21 -0
- package/ai/tools/agent/prepareAgentDraftTool.ts +159 -0
- package/ai/tools/checkEnvTool.ts +52 -20
- package/ai/tools/convertMarxistsBookTool.ts +103 -0
- package/ai/tools/{execBashTool.ts → execShellTool.ts} +44 -161
- package/ai/tools/index.ts +24 -14
- package/ai/tools/marxistsOfflineBook.ts +253 -0
- package/ai/tools/modelUsageTools.ts +4 -2
- package/ai/tools/readTool.ts +31 -0
- package/ai/tools/toolApiClient.ts +35 -1
- package/ai/tools/toolNameAliases.ts +13 -0
- package/cliCommandAdapters.ts +28 -0
- package/cliCommandDispatch.ts +66 -0
- package/cliCommandFactories.ts +34 -0
- package/cliCommandTypes.ts +49 -0
- package/cliEnvHelpers.ts +44 -0
- package/client/agentRun.test.ts +123 -19
- package/client/agentRun.ts +40 -1
- package/client/hybridRecordStore.test.ts +42 -0
- package/client/hybridRecordStore.ts +11 -0
- package/client/localRuntimeAdapter.test.ts +189 -21
- package/client/localRuntimeAdapter.ts +84 -8
- package/client/localRuntimeDryRun.test.ts +5 -2
- package/client/taskRunPrompt.ts +1 -0
- package/commandRegistry.ts +11 -61
- package/connector-experimental/capabilities.ts +1 -0
- package/database/server/coreDataProxy.ts +43 -26
- package/index.ts +54 -191
- package/internalCommandEntries.ts +12 -0
- package/machineCommands.ts +301 -32
- package/offlineMarxistsAgentCommand.ts +314 -0
- package/package.json +20 -2
- package/runtimeDoctorCommands.ts +118 -0
- package/scriptCommandEntries.ts +71 -0
- package/systemInternalCommandEntries.ts +71 -0
- package/workflowInternalCommandEntries.ts +11 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentRuntimeAgentConfig } from "./hostAdapter";
|
|
2
|
-
import type { AgentRuntimeToolPolicy } from "./types";
|
|
2
|
+
import type { AgentRuntimeHost, AgentRuntimeToolPolicy } from "./types";
|
|
3
3
|
|
|
4
4
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
5
5
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -39,6 +39,7 @@ export function normalizeAgentRuntimeToolPolicy(
|
|
|
39
39
|
: {}),
|
|
40
40
|
...(isRecord(value.workspace) ? { workspace: { ...value.workspace } } : {}),
|
|
41
41
|
...(isRecord(value.shell) ? { shell: { ...value.shell } } : {}),
|
|
42
|
+
...(isRecord(value.isolation) ? { isolation: { ...value.isolation } } : {}),
|
|
42
43
|
...(isRecord(value.git) ? { git: { ...value.git } } : {}),
|
|
43
44
|
...(isRecord(value.budget) ? { budget: { ...value.budget } } : {}),
|
|
44
45
|
...(isRecord(value.audit) ? { audit: { ...value.audit } } : {}),
|
|
@@ -69,15 +70,52 @@ export function mergeAgentRuntimeToolPolicies(
|
|
|
69
70
|
]),
|
|
70
71
|
},
|
|
71
72
|
shell: mergeRecords(base?.shell, override?.shell),
|
|
73
|
+
isolation: mergeRecords(base?.isolation, override?.isolation),
|
|
72
74
|
git: mergeRecords(base?.git, override?.git),
|
|
73
75
|
budget: mergeRecords(base?.budget, override?.budget),
|
|
74
76
|
audit: mergeRecords(base?.audit, override?.audit),
|
|
75
77
|
};
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
function policyRequestsHostedWorkspace(policy: AgentRuntimeToolPolicy | undefined) {
|
|
81
|
+
if (!policy) return false;
|
|
82
|
+
const workspaceMode = policy.workspace?.mode;
|
|
83
|
+
return (
|
|
84
|
+
policy.shell?.enabled === true ||
|
|
85
|
+
policy.runtimeTools?.some((tool) => tool === "execShell") ||
|
|
86
|
+
workspaceMode === "lease" ||
|
|
87
|
+
workspaceMode === "task-worktree"
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function applyWebRuntimeSafetyBaseline(
|
|
92
|
+
policy: AgentRuntimeToolPolicy | undefined,
|
|
93
|
+
): AgentRuntimeToolPolicy | undefined {
|
|
94
|
+
if (!policyRequestsHostedWorkspace(policy)) return policy;
|
|
95
|
+
return mergeAgentRuntimeToolPolicies(
|
|
96
|
+
policy,
|
|
97
|
+
{
|
|
98
|
+
version: 1,
|
|
99
|
+
shell: {
|
|
100
|
+
commandPolicy: "approval",
|
|
101
|
+
networkPolicy: "default-deny",
|
|
102
|
+
},
|
|
103
|
+
isolation: {
|
|
104
|
+
mode: "os-sandbox",
|
|
105
|
+
},
|
|
106
|
+
audit: {
|
|
107
|
+
logToolCalls: true,
|
|
108
|
+
logShellCommands: true,
|
|
109
|
+
writeToDialog: true,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
78
115
|
export function resolveEffectiveRuntimeToolPolicy(args: {
|
|
79
116
|
agentConfig: Pick<AgentRuntimeAgentConfig, "runtimeToolPolicy" | "runtimeBinding">;
|
|
80
117
|
taskRunControl?: { runtimeToolPolicy?: unknown } | null;
|
|
118
|
+
host?: AgentRuntimeHost;
|
|
81
119
|
}): AgentRuntimeToolPolicy | undefined {
|
|
82
120
|
const agentPolicy = normalizeAgentRuntimeToolPolicy(
|
|
83
121
|
args.agentConfig.runtimeToolPolicy ??
|
|
@@ -88,5 +126,8 @@ export function resolveEffectiveRuntimeToolPolicy(args: {
|
|
|
88
126
|
const taskPolicy = normalizeAgentRuntimeToolPolicy(
|
|
89
127
|
args.taskRunControl?.runtimeToolPolicy,
|
|
90
128
|
);
|
|
91
|
-
|
|
129
|
+
const effectivePolicy = mergeAgentRuntimeToolPolicies(agentPolicy, taskPolicy);
|
|
130
|
+
return args.host === "web"
|
|
131
|
+
? applyWebRuntimeSafetyBaseline(effectivePolicy)
|
|
132
|
+
: effectivePolicy;
|
|
92
133
|
}
|
package/agent-runtime/types.ts
CHANGED
|
@@ -105,6 +105,10 @@ export type AgentRuntimeAuditPolicy = {
|
|
|
105
105
|
writeToTask?: boolean;
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
export type AgentRuntimeIsolationPolicy = {
|
|
109
|
+
mode?: "none" | "os-sandbox" | "container" | "gvisor" | "microvm" | "dedicated-vm";
|
|
110
|
+
};
|
|
111
|
+
|
|
108
112
|
export type AgentRuntimeToolPolicy = {
|
|
109
113
|
version?: 1;
|
|
110
114
|
agentTools?: string[];
|
|
@@ -115,6 +119,7 @@ export type AgentRuntimeToolPolicy = {
|
|
|
115
119
|
cwd?: string;
|
|
116
120
|
};
|
|
117
121
|
shell?: AgentRuntimeShellPolicy;
|
|
122
|
+
isolation?: AgentRuntimeIsolationPolicy;
|
|
118
123
|
git?: AgentRuntimeGitPolicy;
|
|
119
124
|
budget?: {
|
|
120
125
|
dailyUsdLimit?: number;
|
package/agentAliases.ts
CHANGED
|
@@ -13,7 +13,7 @@ export const NOLO_FULLSTACK_AGENT_KEY =
|
|
|
13
13
|
export const NOLO_SENIOR_FULLSTACK_AGENT_ID = "01NOLOFULLSTK00000000T3XR4";
|
|
14
14
|
export const NOLO_SENIOR_FULLSTACK_AGENT_KEY =
|
|
15
15
|
`agent-${PLATFORM_DEMO_USER_ID}-${NOLO_SENIOR_FULLSTACK_AGENT_ID}`;
|
|
16
|
-
export const NOLO_REVIEW_AGENT_ID = "
|
|
16
|
+
export const NOLO_REVIEW_AGENT_ID = "01CODEXCLIMAIN00000001OK";
|
|
17
17
|
export const NOLO_REVIEW_AGENT_KEY =
|
|
18
18
|
`agent-${PLATFORM_DEMO_USER_ID}-${NOLO_REVIEW_AGENT_ID}`;
|
|
19
19
|
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { MachineHeartbeat } from "./connector-experimental/protocol";
|
|
2
|
+
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
3
|
+
import type { EnvLike } from "./cliEnvHelpers";
|
|
4
|
+
|
|
5
|
+
export type OutputLike = { write(chunk: string): unknown };
|
|
6
|
+
|
|
7
|
+
export type SmokeWebSocketOptions = {
|
|
8
|
+
headers: Record<string, string>;
|
|
9
|
+
onMessage: (message: string) => void | Promise<void>;
|
|
10
|
+
onOpen: () => void | Promise<void>;
|
|
11
|
+
sentMessages: string[];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type LocalCliExecutor = (
|
|
15
|
+
provider: string,
|
|
16
|
+
prompt: string,
|
|
17
|
+
options: { model?: string; timeout?: number; cwd?: string; yolo?: boolean }
|
|
18
|
+
) => Promise<{ text: string; raw?: string; elapsed?: number }>;
|
|
19
|
+
|
|
20
|
+
export type LocalRuntimeProbeResult = {
|
|
21
|
+
ok: boolean;
|
|
22
|
+
dbPath: string;
|
|
23
|
+
agentFound: boolean;
|
|
24
|
+
agentKey?: string;
|
|
25
|
+
error?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export type AgentCommandDeps = {
|
|
29
|
+
env?: EnvLike;
|
|
30
|
+
output?: OutputLike;
|
|
31
|
+
db?: CliKvDb;
|
|
32
|
+
fetchImpl?: typeof fetch;
|
|
33
|
+
fallbackFetchImpl?: typeof fetch;
|
|
34
|
+
machineInfo?: () => MachineHeartbeat;
|
|
35
|
+
connectWebSocket?: (url: string, options: SmokeWebSocketOptions) => Promise<void>;
|
|
36
|
+
executeCli?: LocalCliExecutor;
|
|
37
|
+
connectorRunMessageHandler?: (
|
|
38
|
+
machine: MachineHeartbeat,
|
|
39
|
+
message: string,
|
|
40
|
+
pushMessage: (response: string) => void,
|
|
41
|
+
executeCli: LocalCliExecutor,
|
|
42
|
+
env: EnvLike,
|
|
43
|
+
fetchImpl: typeof fetch
|
|
44
|
+
) => Promise<void>;
|
|
45
|
+
localRuntimeProbe?: (env: EnvLike) => Promise<LocalRuntimeProbeResult>;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
async function getDefaultCliDb() {
|
|
49
|
+
const { getDefaultCliLocalRuntimeDb } = await import("./localRuntimeDb");
|
|
50
|
+
return getDefaultCliLocalRuntimeDb();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function createNullCliDb(): CliKvDb {
|
|
54
|
+
return {
|
|
55
|
+
get: async () => {
|
|
56
|
+
throw new Error("local cache unavailable");
|
|
57
|
+
},
|
|
58
|
+
put: async () => undefined,
|
|
59
|
+
batch: async () => undefined,
|
|
60
|
+
iterator: () => {
|
|
61
|
+
throw new Error("local cache unavailable");
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function getReadableCliDb(output: OutputLike) {
|
|
67
|
+
try {
|
|
68
|
+
return await getDefaultCliDb();
|
|
69
|
+
} catch (error) {
|
|
70
|
+
output.write(
|
|
71
|
+
`[nolo] local agent cache unavailable; falling back to remote reads only: ${
|
|
72
|
+
error instanceof Error ? error.message : String(error)
|
|
73
|
+
}\n`
|
|
74
|
+
);
|
|
75
|
+
return createNullCliDb();
|
|
76
|
+
}
|
|
77
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { runAgentListCommand } from "./agentListCommands";
|
|
2
|
+
import {
|
|
3
|
+
runAgentBindCurrentCommand,
|
|
4
|
+
runAgentRuntimeDoctorCommand,
|
|
5
|
+
runAgentSmokeCurrentCommand,
|
|
6
|
+
} from "./agentMachineCommands";
|
|
7
|
+
import { runAgentPullCommand } from "./agentPullCommand";
|
|
8
|
+
import {
|
|
9
|
+
runAgentReadCommand,
|
|
10
|
+
runAgentUpdateCommand,
|
|
11
|
+
} from "./agentRecordCommands";
|
|
12
|
+
import { runAgentRunCommand } from "./agentRunCommand";
|
|
13
|
+
import {
|
|
14
|
+
createEnvCommand,
|
|
15
|
+
createEnvScriptDirCommand,
|
|
16
|
+
} from "./cliCommandFactories";
|
|
17
|
+
import type { CommandEntry } from "./cliCommandTypes";
|
|
18
|
+
import { runSetupOfflineMarxistsAgentCommand } from "./offlineMarxistsAgentCommand";
|
|
19
|
+
|
|
20
|
+
export function getAgentInternalCommandEntries(): CommandEntry[] {
|
|
21
|
+
return [
|
|
22
|
+
createEnvScriptDirCommand(["chat"], "Chat with an agent", runAgentRunCommand),
|
|
23
|
+
createEnvCommand(["agent", "list"], "List owned agents", runAgentListCommand),
|
|
24
|
+
createEnvCommand(["agent", "pull"], "Cache an agent for local runs", runAgentPullCommand),
|
|
25
|
+
createEnvCommand(["agent", "read"], "Read a single agent", runAgentReadCommand),
|
|
26
|
+
createEnvScriptDirCommand(["agent", "run"], "Run an agent", runAgentRunCommand),
|
|
27
|
+
createEnvCommand(["agent", "setup-offline-marxists"], "Create or update the Marxists.org offline book agent", runSetupOfflineMarxistsAgentCommand),
|
|
28
|
+
createEnvCommand(["agent", "update"], "Update agent fields", runAgentUpdateCommand),
|
|
29
|
+
createEnvCommand(["agent", "bind-current"], "Bind an agent to this machine", runAgentBindCurrentCommand),
|
|
30
|
+
createEnvCommand(["agent", "smoke-current"], "Smoke test a bound agent through this machine", runAgentSmokeCurrentCommand),
|
|
31
|
+
createEnvCommand(["agent", "runtime-doctor"], "Diagnose current machine runtime compatibility", runAgentRuntimeDoctorCommand),
|
|
32
|
+
createEnvScriptDirCommand(["agent", "chat"], "Chat with an agent", runAgentRunCommand),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
|
|
2
|
+
import {
|
|
3
|
+
listLocalCachedAgents,
|
|
4
|
+
listRemoteAgents,
|
|
5
|
+
parseAgentListArgs,
|
|
6
|
+
type ListedAgent,
|
|
7
|
+
} from "./agentListHelpers";
|
|
8
|
+
import {
|
|
9
|
+
queryUserRecords,
|
|
10
|
+
readDbRecord,
|
|
11
|
+
} from "./agentRecordHelpers";
|
|
12
|
+
import {
|
|
13
|
+
parseUserIdFromAuthToken,
|
|
14
|
+
resolveAuthToken,
|
|
15
|
+
resolveServerUrl,
|
|
16
|
+
} from "./cliEnvHelpers";
|
|
17
|
+
|
|
18
|
+
export async function runAgentListCommand(
|
|
19
|
+
args: string[],
|
|
20
|
+
deps: AgentCommandDeps = {}
|
|
21
|
+
) {
|
|
22
|
+
const env = deps.env ?? process.env;
|
|
23
|
+
const output = deps.output ?? process.stdout;
|
|
24
|
+
const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
|
|
25
|
+
|
|
26
|
+
const authToken = resolveAuthToken(env);
|
|
27
|
+
if (!authToken) {
|
|
28
|
+
output.write("[nolo] agent list requires an auth token. Run `nolo login` or set AUTH_TOKEN.\n");
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const userId = parseUserIdFromAuthToken(authToken);
|
|
33
|
+
if (!userId) {
|
|
34
|
+
output.write("[nolo] agent list could not read userId from AUTH_TOKEN.\n");
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const db = deps.db ?? await getReadableCliDb(output);
|
|
39
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
40
|
+
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
let agents: ListedAgent[];
|
|
44
|
+
let source: "local-cache" | "remote-cache";
|
|
45
|
+
try {
|
|
46
|
+
agents = await listLocalCachedAgents({ db, userId });
|
|
47
|
+
source = "local-cache";
|
|
48
|
+
} catch {
|
|
49
|
+
const serverUrl = resolveServerUrl(env);
|
|
50
|
+
agents = await listRemoteAgents({
|
|
51
|
+
authToken,
|
|
52
|
+
fallbackFetchImpl,
|
|
53
|
+
fetchImpl,
|
|
54
|
+
includeLegacy,
|
|
55
|
+
serverUrl,
|
|
56
|
+
userId,
|
|
57
|
+
queryUserRecords,
|
|
58
|
+
readDbRecord,
|
|
59
|
+
});
|
|
60
|
+
source = "remote-cache";
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!includeLegacy) {
|
|
64
|
+
agents = agents.filter((agent) => !agent.privateKey.startsWith("cybot-"));
|
|
65
|
+
}
|
|
66
|
+
if (publicOnly) {
|
|
67
|
+
agents = agents.filter((agent) => agent.publicRecordExists);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (idsOnly) {
|
|
71
|
+
output.write(`${agents.map((agent) => agent.id).join("\n")}\n`);
|
|
72
|
+
return 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (wantJson) {
|
|
76
|
+
output.write(JSON.stringify({
|
|
77
|
+
userId,
|
|
78
|
+
total: agents.length,
|
|
79
|
+
publicCount: agents.filter((agent) => agent.publicRecordExists).length,
|
|
80
|
+
source,
|
|
81
|
+
agents,
|
|
82
|
+
}, null, 2));
|
|
83
|
+
output.write("\n");
|
|
84
|
+
return 0;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
output.write(`userId: ${userId}\n`);
|
|
88
|
+
output.write(`total agents: ${agents.length}\n`);
|
|
89
|
+
output.write(`public agents: ${agents.filter((agent) => agent.publicRecordExists).length}\n`);
|
|
90
|
+
output.write(`source: ${source}\n`);
|
|
91
|
+
if (agents.length === 0) {
|
|
92
|
+
output.write("\n(no agents found)\n");
|
|
93
|
+
return 0;
|
|
94
|
+
}
|
|
95
|
+
for (const agent of agents) {
|
|
96
|
+
const status = agent.publicRecordExists ? "public" : "private";
|
|
97
|
+
const flagMismatch = agent.isPublicFlag !== agent.publicRecordExists
|
|
98
|
+
? ` flag=${agent.isPublicFlag}`
|
|
99
|
+
: "";
|
|
100
|
+
output.write(
|
|
101
|
+
[
|
|
102
|
+
`\n[${status}] ${agent.name}`,
|
|
103
|
+
`id=${agent.id}`,
|
|
104
|
+
`type=${agent.type ?? "-"}`,
|
|
105
|
+
`model=${agent.model}`,
|
|
106
|
+
`updatedAt=${agent.updatedAt ?? "-"}`,
|
|
107
|
+
`privateKey=${agent.privateKey}`,
|
|
108
|
+
`publicKey=${agent.publicKey}${flagMismatch}`,
|
|
109
|
+
`tools=${agent.tools.join(", ") || "-"}`,
|
|
110
|
+
].join("\n")
|
|
111
|
+
);
|
|
112
|
+
output.write("\n");
|
|
113
|
+
}
|
|
114
|
+
return 0;
|
|
115
|
+
} catch (error) {
|
|
116
|
+
output.write(
|
|
117
|
+
`[nolo] agent list failed: ${
|
|
118
|
+
error instanceof Error ? error.message : String(error)
|
|
119
|
+
}\n`
|
|
120
|
+
);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
2
|
+
|
|
3
|
+
export type ListedAgent = {
|
|
4
|
+
id: string;
|
|
5
|
+
privateKey: string;
|
|
6
|
+
publicKey: string;
|
|
7
|
+
name: string;
|
|
8
|
+
model: string;
|
|
9
|
+
updatedAt: string | number | null;
|
|
10
|
+
isPublicFlag: boolean;
|
|
11
|
+
publicRecordExists: boolean;
|
|
12
|
+
type: string | null;
|
|
13
|
+
tools: string[];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function parseAgentRecordId(privateKey: string, explicitId?: string) {
|
|
17
|
+
if (explicitId) return explicitId;
|
|
18
|
+
const agentMatch = privateKey.match(/^agent-[^-]+-(.+)$/i);
|
|
19
|
+
if (agentMatch?.[1]) return agentMatch[1];
|
|
20
|
+
const cybotMatch = privateKey.match(/^cybot-[^-]+-([0-9A-HJKMNP-TV-Z]{26})$/i);
|
|
21
|
+
return cybotMatch?.[1] ?? "";
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildCompanionKeys(privateKey: string, rawId: string, userId: string) {
|
|
25
|
+
if (privateKey.startsWith("cybot-")) {
|
|
26
|
+
return {
|
|
27
|
+
privateKey: `cybot-${userId}-${rawId}`,
|
|
28
|
+
publicKey: `cybot-pub-${rawId}`,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
privateKey: `agent-${userId}-${rawId}`,
|
|
33
|
+
publicKey: `agent-pub-${rawId}`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function normalizeListedAgent(record: any): ListedAgent | null {
|
|
38
|
+
const privateKey = typeof record?.dbKey === "string" ? record.dbKey : "";
|
|
39
|
+
const explicitId = typeof record?.id === "string" && record.id ? record.id : undefined;
|
|
40
|
+
const ownerUserId = typeof record?.userId === "string" ? record.userId : "";
|
|
41
|
+
const rawId = explicitId
|
|
42
|
+
|| (ownerUserId && privateKey.startsWith(`agent-${ownerUserId}-`)
|
|
43
|
+
? privateKey.slice(`agent-${ownerUserId}-`.length)
|
|
44
|
+
: ownerUserId && privateKey.startsWith(`cybot-${ownerUserId}-`)
|
|
45
|
+
? privateKey.slice(`cybot-${ownerUserId}-`.length)
|
|
46
|
+
: parseAgentRecordId(privateKey, explicitId));
|
|
47
|
+
if (!privateKey || !rawId || !ownerUserId) return null;
|
|
48
|
+
const keys = buildCompanionKeys(privateKey, rawId, ownerUserId);
|
|
49
|
+
return {
|
|
50
|
+
id: rawId,
|
|
51
|
+
privateKey,
|
|
52
|
+
publicKey: keys.publicKey,
|
|
53
|
+
name: typeof record?.name === "string" && record.name ? record.name : "(unnamed)",
|
|
54
|
+
model: typeof record?.model === "string" && record.model ? record.model : "-",
|
|
55
|
+
updatedAt:
|
|
56
|
+
typeof record?.updatedAt === "string" || typeof record?.updatedAt === "number"
|
|
57
|
+
? record.updatedAt
|
|
58
|
+
: typeof record?.createdAt === "string" || typeof record?.createdAt === "number"
|
|
59
|
+
? record.createdAt
|
|
60
|
+
: typeof record?.created === "string" || typeof record?.created === "number"
|
|
61
|
+
? record.created
|
|
62
|
+
: null,
|
|
63
|
+
isPublicFlag: !!record?.isPublic,
|
|
64
|
+
publicRecordExists: false,
|
|
65
|
+
type: typeof record?.type === "string" ? record.type : null,
|
|
66
|
+
tools: Array.isArray(record?.tools)
|
|
67
|
+
? record.tools.filter((tool: unknown): tool is string => typeof tool === "string")
|
|
68
|
+
: [],
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function sortListedAgents(agents: ListedAgent[]) {
|
|
73
|
+
agents.sort((a, b) => {
|
|
74
|
+
const ta = a.updatedAt == null ? 0 : new Date(a.updatedAt).getTime();
|
|
75
|
+
const tb = b.updatedAt == null ? 0 : new Date(b.updatedAt).getTime();
|
|
76
|
+
return tb - ta;
|
|
77
|
+
});
|
|
78
|
+
return agents;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function parseAgentListArgs(args: string[]) {
|
|
82
|
+
return {
|
|
83
|
+
wantJson: args.includes("--json"),
|
|
84
|
+
publicOnly: args.includes("--public-only"),
|
|
85
|
+
idsOnly: args.includes("--ids-only"),
|
|
86
|
+
includeLegacy: args.includes("--include-legacy"),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function listLocalCachedAgents(args: {
|
|
91
|
+
db: CliKvDb;
|
|
92
|
+
userId: string;
|
|
93
|
+
}) {
|
|
94
|
+
const privateRecords = new Map<string, any>();
|
|
95
|
+
const publicKeys = new Set<string>();
|
|
96
|
+
for await (const [key, value] of args.db.iterator()) {
|
|
97
|
+
if (typeof key !== "string" || !value || typeof value !== "object") continue;
|
|
98
|
+
if (key.startsWith(`agent-${args.userId}-`) || key.startsWith(`cybot-${args.userId}-`)) {
|
|
99
|
+
privateRecords.set(key, { ...(value as Record<string, unknown>), dbKey: key });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (key.startsWith("agent-pub-") || key.startsWith("cybot-pub-")) {
|
|
103
|
+
publicKeys.add(key);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const agents = sortListedAgents(
|
|
108
|
+
[...privateRecords.values()]
|
|
109
|
+
.map((record) => normalizeListedAgent(record))
|
|
110
|
+
.filter((agent): agent is ListedAgent => agent != null)
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
for (const agent of agents) {
|
|
114
|
+
agent.publicRecordExists = publicKeys.has(agent.publicKey);
|
|
115
|
+
}
|
|
116
|
+
return agents;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function hasReadableRecord(args: {
|
|
120
|
+
authToken: string;
|
|
121
|
+
dbKey: string;
|
|
122
|
+
fallbackFetchImpl?: typeof fetch;
|
|
123
|
+
fetchImpl: typeof fetch;
|
|
124
|
+
serverUrl: string;
|
|
125
|
+
readDbRecord: (args: {
|
|
126
|
+
authToken: string;
|
|
127
|
+
dbKey: string;
|
|
128
|
+
fallbackFetchImpl?: typeof fetch;
|
|
129
|
+
fetchImpl: typeof fetch;
|
|
130
|
+
serverUrl: string;
|
|
131
|
+
}) => Promise<any>;
|
|
132
|
+
}) {
|
|
133
|
+
try {
|
|
134
|
+
await args.readDbRecord(args);
|
|
135
|
+
return true;
|
|
136
|
+
} catch {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export async function listRemoteAgents(args: {
|
|
142
|
+
authToken: string;
|
|
143
|
+
fallbackFetchImpl?: typeof fetch;
|
|
144
|
+
fetchImpl: typeof fetch;
|
|
145
|
+
includeLegacy: boolean;
|
|
146
|
+
serverUrl: string;
|
|
147
|
+
userId: string;
|
|
148
|
+
queryUserRecords: (args: {
|
|
149
|
+
authToken: string;
|
|
150
|
+
fallbackFetchImpl?: typeof fetch;
|
|
151
|
+
fetchImpl: typeof fetch;
|
|
152
|
+
serverUrl: string;
|
|
153
|
+
userId: string;
|
|
154
|
+
type: "agent" | "cybot";
|
|
155
|
+
}) => Promise<any[]>;
|
|
156
|
+
readDbRecord: (args: {
|
|
157
|
+
authToken: string;
|
|
158
|
+
dbKey: string;
|
|
159
|
+
fallbackFetchImpl?: typeof fetch;
|
|
160
|
+
fetchImpl: typeof fetch;
|
|
161
|
+
serverUrl: string;
|
|
162
|
+
}) => Promise<any>;
|
|
163
|
+
}) {
|
|
164
|
+
const recordGroups = [await args.queryUserRecords({ ...args, type: "agent" as const })];
|
|
165
|
+
if (args.includeLegacy) {
|
|
166
|
+
recordGroups.push(await args.queryUserRecords({ ...args, type: "cybot" as const }));
|
|
167
|
+
}
|
|
168
|
+
const agents = sortListedAgents(
|
|
169
|
+
recordGroups
|
|
170
|
+
.flat()
|
|
171
|
+
.map((record) => normalizeListedAgent(record))
|
|
172
|
+
.filter((agent): agent is ListedAgent => agent != null)
|
|
173
|
+
);
|
|
174
|
+
await Promise.all(
|
|
175
|
+
agents.map(async (agent) => {
|
|
176
|
+
agent.publicRecordExists = await hasReadableRecord({
|
|
177
|
+
authToken: args.authToken,
|
|
178
|
+
dbKey: agent.publicKey,
|
|
179
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
180
|
+
fetchImpl: args.fetchImpl,
|
|
181
|
+
serverUrl: args.serverUrl,
|
|
182
|
+
readDbRecord: args.readDbRecord,
|
|
183
|
+
});
|
|
184
|
+
})
|
|
185
|
+
);
|
|
186
|
+
return agents;
|
|
187
|
+
}
|