nolo-cli 0.1.34 → 0.1.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -1
- package/agent-runtime/localToolPolicy.ts +1 -1
- package/agent-runtime/localWorkspaceTools.ts +5 -2
- package/agent-runtime/providerResolution.ts +1 -3
- package/agentCommandSupport.ts +4 -0
- package/agentListCommands.ts +73 -11
- package/agentListHelpers.ts +56 -1
- package/agentRecordHelpers.ts +45 -5
- package/cli/agentCommandSupport.ts +4 -0
- package/cli/agentListCommands.ts +73 -11
- package/cli/agentListHelpers.ts +56 -1
- package/cli/agentRecordHelpers.ts +45 -5
- package/cli/cliSpaceHelpers.ts +39 -0
- package/cli/connectorWebSocketTarget.ts +10 -3
- package/cli/dialogCommands.ts +446 -0
- package/cli/dialogInternalCommandEntries.ts +13 -0
- package/cli/globalRecordOperations.ts +258 -0
- package/cli/internalCommandEntries.ts +2 -0
- package/cli/localRuntimeAuthority.ts +182 -0
- package/cli/localRuntimeDb.ts +2 -4
- package/cli/machineWsRunDispatch.ts +0 -3
- package/cli/machineWsSession.ts +2 -0
- package/cli/runtimeDoctorCommands.ts +55 -5
- package/cliSpaceHelpers.ts +39 -0
- package/connectorWebSocketTarget.ts +10 -3
- package/database/server/cliAuthorityBrokerClient.ts +227 -0
- package/database/server/cliAuthorityBrokerServer.ts +283 -0
- package/database/server/cliAuthorityBrokerTypes.ts +43 -0
- package/database/server/cliAuthorityStoreDriver.ts +98 -0
- package/database/server/levelAuthorityStore.ts +46 -26
- package/database/tombstones.ts +52 -0
- package/database/userDataMerge.ts +95 -0
- package/dialogCommands.ts +446 -0
- package/dialogInternalCommandEntries.ts +13 -0
- package/globalRecordOperations.ts +258 -0
- package/internalCommandEntries.ts +2 -0
- package/localRuntimeAuthority.ts +182 -0
- package/localRuntimeDb.ts +2 -4
- package/machineWsRunDispatch.ts +0 -3
- package/machineWsSession.ts +2 -0
- package/package.json +18 -6
- package/runtimeDoctorCommands.ts +55 -5
- package/database/server/db.ts +0 -99
- package/database/server/ensureDbOpen.ts +0 -12
- package/database/server/memoryAuthorityStore.ts +0 -133
- package/database/server/serverStoreFactory.ts +0 -118
package/README.md
CHANGED
|
@@ -171,8 +171,11 @@ nolo agent read <agent>
|
|
|
171
171
|
nolo agent update <agent> --model gpt-5.4 --cli-provider codex --api-source cli
|
|
172
172
|
nolo agent run <agent> "检查最近失败任务"
|
|
173
173
|
nolo agent run frontend-implementer --msg "修一个小的通知弹窗 CSS 问题"
|
|
174
|
-
nolo
|
|
174
|
+
nolo agent list --space <space>
|
|
175
|
+
nolo dialog list
|
|
176
|
+
nolo dialog list --space <space>
|
|
175
177
|
nolo dialog read <dialog>
|
|
178
|
+
nolo dialog delete <dialog> --yes
|
|
176
179
|
nolo doc list --agent <agent>
|
|
177
180
|
nolo table query --table meta-b2e06f801f-NOLOTASKBOARD --limit 20
|
|
178
181
|
nolo table query --table meta-b2e06f801f-NOLOTASKBOARD --columns '["title","status","owner","priority","codeStatus"]' --no-base-fields --output items
|
|
@@ -180,6 +183,13 @@ nolo table update-row --table meta-b2e06f801f-NOLOTASKBOARD --row 01ROWID --chan
|
|
|
180
183
|
nolo table add-column --table meta-b2e06f801f-NOLOTASKBOARD --schema-write-ok --name "blockedBy" --label "Blocked By"
|
|
181
184
|
```
|
|
182
185
|
|
|
186
|
+
`agent list`, `dialog list`, and record delete commands use global server
|
|
187
|
+
candidates for the current CLI profile: the selected `--server` / `NOLO_SERVER`
|
|
188
|
+
plus known Nolo cluster peers. List commands merge tombstones so a newer delete
|
|
189
|
+
on one server hides older live records from another. Dialog, doc, skill-doc,
|
|
190
|
+
space, and agent public-record deletes send the delete to all target servers;
|
|
191
|
+
each server applies the normal tombstone/cascade behavior for that record type.
|
|
192
|
+
|
|
183
193
|
`nolo table add-column` mutates table schema metadata, so it requires
|
|
184
194
|
`--schema-write-ok` and should be run serially per table. Row-level table writes
|
|
185
195
|
do not require this flag.
|
|
@@ -4,7 +4,7 @@ import { dirname, relative, resolve, sep } from "node:path";
|
|
|
4
4
|
import type {
|
|
5
5
|
AgentRuntimeToolCallInput,
|
|
6
6
|
AgentRuntimeToolResult,
|
|
7
|
-
} from "./
|
|
7
|
+
} from "./hostAdapter";
|
|
8
8
|
|
|
9
9
|
type LocalWorkspaceToolArgs = {
|
|
10
10
|
workspaceRoot: string;
|
|
@@ -646,7 +646,10 @@ function readVisualStateCaptureArgs(args: WorkspaceFileArgs) {
|
|
|
646
646
|
};
|
|
647
647
|
}
|
|
648
648
|
|
|
649
|
-
async function readWorkspacePackageScripts(workspaceRoot: string) {
|
|
649
|
+
async function readWorkspacePackageScripts(workspaceRoot: string): Promise<{
|
|
650
|
+
scripts: string[];
|
|
651
|
+
error?: string;
|
|
652
|
+
}> {
|
|
650
653
|
try {
|
|
651
654
|
const raw = await readFile(resolve(workspaceRoot, "package.json"), "utf8");
|
|
652
655
|
const parsed = JSON.parse(raw);
|
|
@@ -126,9 +126,7 @@ export function shouldUsePlatformProxyTransport(
|
|
|
126
126
|
env: EnvLike,
|
|
127
127
|
agentConfig?: AgentRuntimeAgentConfig
|
|
128
128
|
) {
|
|
129
|
-
if (agentConfig?.apiSource === "platform") return true;
|
|
130
|
-
if (agentConfig?.apiSource === "custom" || agentConfig?.customProviderUrl?.trim()) return false;
|
|
131
|
-
if (agentConfig?.useServerProxy === true) return true;
|
|
129
|
+
if (agentConfig?.apiSource === "platform" || agentConfig?.useServerProxy === true) return true;
|
|
132
130
|
if (env.NOLO_LOCAL_LLM === "platform") return true;
|
|
133
131
|
if (env.NOLO_LOCAL_LLM === "direct") return false;
|
|
134
132
|
return !hasDirectOpenAiCompatibleProvider(env) && canUsePlatformChatProvider(env);
|
package/agentCommandSupport.ts
CHANGED
|
@@ -20,6 +20,10 @@ export type LocalCliExecutor = (
|
|
|
20
20
|
export type LocalRuntimeProbeResult = {
|
|
21
21
|
ok: boolean;
|
|
22
22
|
dbPath: string;
|
|
23
|
+
authorityDriver?: string;
|
|
24
|
+
authorityEndpoint?: string;
|
|
25
|
+
authorityMetadataPath?: string;
|
|
26
|
+
authorityHealthPath?: string;
|
|
23
27
|
agentFound: boolean;
|
|
24
28
|
agentKey?: string;
|
|
25
29
|
error?: string;
|
package/agentListCommands.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
|
|
2
2
|
import {
|
|
3
|
+
decorateAgentsWithPublicStatusAcrossServers,
|
|
3
4
|
listLocalCachedAgents,
|
|
5
|
+
listRemoteAgentsAcrossServers,
|
|
4
6
|
listRemoteAgents,
|
|
5
7
|
parseAgentListArgs,
|
|
6
8
|
type ListedAgent,
|
|
@@ -9,11 +11,15 @@ import {
|
|
|
9
11
|
queryUserRecords,
|
|
10
12
|
readDbRecord,
|
|
11
13
|
} from "./agentRecordHelpers";
|
|
14
|
+
import { buildSpaceLookup, getSpaceContentKeys } from "./cliSpaceHelpers";
|
|
12
15
|
import {
|
|
13
16
|
parseUserIdFromAuthToken,
|
|
17
|
+
readOption,
|
|
14
18
|
resolveAuthToken,
|
|
19
|
+
resolveServerCandidates,
|
|
15
20
|
resolveServerUrl,
|
|
16
21
|
} from "./cliEnvHelpers";
|
|
22
|
+
import { readLiveDbRecordAfterTombstoneMerge } from "./globalRecordOperations";
|
|
17
23
|
|
|
18
24
|
export async function runAgentListCommand(
|
|
19
25
|
args: string[],
|
|
@@ -22,6 +28,7 @@ export async function runAgentListCommand(
|
|
|
22
28
|
const env = deps.env ?? process.env;
|
|
23
29
|
const output = deps.output ?? process.stdout;
|
|
24
30
|
const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
|
|
31
|
+
const spaceInput = readOption(args, "--space") ?? readOption(args, "--space-id");
|
|
25
32
|
|
|
26
33
|
const authToken = resolveAuthToken(args, env);
|
|
27
34
|
if (!authToken) {
|
|
@@ -35,34 +42,79 @@ export async function runAgentListCommand(
|
|
|
35
42
|
return 1;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
const db = deps.db ?? await getReadableCliDb(output);
|
|
39
45
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
40
46
|
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
47
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
48
|
+
const serverUrls = resolveServerCandidates(args, env, serverUrl);
|
|
41
49
|
|
|
42
50
|
try {
|
|
43
51
|
let agents: ListedAgent[];
|
|
44
|
-
let source: "local-cache" | "remote-cache";
|
|
52
|
+
let source: "local-cache" | "remote-cache" | "global-cache";
|
|
53
|
+
let serverFailures: Array<{ serverUrl: string; error: string }> = [];
|
|
45
54
|
try {
|
|
46
|
-
|
|
47
|
-
source = "local-cache";
|
|
48
|
-
} catch {
|
|
49
|
-
const serverUrl = resolveServerUrl(args, env);
|
|
50
|
-
agents = await listRemoteAgents({
|
|
55
|
+
const remoteResult = await listRemoteAgentsAcrossServers({
|
|
51
56
|
authToken,
|
|
52
57
|
fallbackFetchImpl,
|
|
53
58
|
fetchImpl,
|
|
54
59
|
includeLegacy,
|
|
55
|
-
|
|
60
|
+
serverUrls,
|
|
56
61
|
userId,
|
|
57
|
-
queryUserRecords,
|
|
58
|
-
readDbRecord,
|
|
59
62
|
});
|
|
60
|
-
|
|
63
|
+
agents = remoteResult.agents;
|
|
64
|
+
serverFailures = remoteResult.failures;
|
|
65
|
+
source = "global-cache";
|
|
66
|
+
} catch {
|
|
67
|
+
try {
|
|
68
|
+
const db = deps.db ?? await getReadableCliDb(output);
|
|
69
|
+
agents = await listLocalCachedAgents({ db, userId });
|
|
70
|
+
source = "local-cache";
|
|
71
|
+
} catch {
|
|
72
|
+
agents = await listRemoteAgents({
|
|
73
|
+
authToken,
|
|
74
|
+
fallbackFetchImpl,
|
|
75
|
+
fetchImpl,
|
|
76
|
+
includeLegacy,
|
|
77
|
+
serverUrl,
|
|
78
|
+
userId,
|
|
79
|
+
queryUserRecords,
|
|
80
|
+
readDbRecord,
|
|
81
|
+
});
|
|
82
|
+
source = "remote-cache";
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
if (!includeLegacy) {
|
|
64
87
|
agents = agents.filter((agent) => !agent.privateKey.startsWith("cybot-"));
|
|
65
88
|
}
|
|
89
|
+
let resolvedSpaceId: string | null = null;
|
|
90
|
+
if (spaceInput) {
|
|
91
|
+
const { spaceId, spaceKey } = buildSpaceLookup(spaceInput);
|
|
92
|
+
resolvedSpaceId = spaceId;
|
|
93
|
+
const spaceRead = await readLiveDbRecordAfterTombstoneMerge({
|
|
94
|
+
authToken,
|
|
95
|
+
dbKey: spaceKey,
|
|
96
|
+
fallbackFetchImpl,
|
|
97
|
+
fetchImpl,
|
|
98
|
+
serverUrls,
|
|
99
|
+
});
|
|
100
|
+
serverFailures = [...serverFailures, ...spaceRead.failures];
|
|
101
|
+
const spaceRecord = spaceRead.record;
|
|
102
|
+
const spaceContentKeys = getSpaceContentKeys(spaceRecord);
|
|
103
|
+
agents = agents.filter((agent) =>
|
|
104
|
+
spaceContentKeys.has(agent.privateKey) ||
|
|
105
|
+
spaceContentKeys.has(agent.publicKey) ||
|
|
106
|
+
spaceContentKeys.has(agent.id)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (source === "global-cache") {
|
|
110
|
+
await decorateAgentsWithPublicStatusAcrossServers({
|
|
111
|
+
agents,
|
|
112
|
+
authToken,
|
|
113
|
+
fallbackFetchImpl,
|
|
114
|
+
fetchImpl,
|
|
115
|
+
serverUrls,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
66
118
|
if (publicOnly) {
|
|
67
119
|
agents = agents.filter((agent) => agent.publicRecordExists);
|
|
68
120
|
}
|
|
@@ -75,6 +127,9 @@ export async function runAgentListCommand(
|
|
|
75
127
|
if (wantJson) {
|
|
76
128
|
output.write(JSON.stringify({
|
|
77
129
|
userId,
|
|
130
|
+
...(resolvedSpaceId ? { spaceId: resolvedSpaceId } : {}),
|
|
131
|
+
targetServers: serverUrls,
|
|
132
|
+
...(serverFailures.length ? { serverFailures } : {}),
|
|
78
133
|
total: agents.length,
|
|
79
134
|
publicCount: agents.filter((agent) => agent.publicRecordExists).length,
|
|
80
135
|
source,
|
|
@@ -85,6 +140,13 @@ export async function runAgentListCommand(
|
|
|
85
140
|
}
|
|
86
141
|
|
|
87
142
|
output.write(`userId: ${userId}\n`);
|
|
143
|
+
if (resolvedSpaceId) {
|
|
144
|
+
output.write(`spaceId: ${resolvedSpaceId}\n`);
|
|
145
|
+
}
|
|
146
|
+
output.write(`targetServers: ${serverUrls.join(", ")}\n`);
|
|
147
|
+
if (serverFailures.length) {
|
|
148
|
+
output.write(`serverFailures: ${serverFailures.length}\n`);
|
|
149
|
+
}
|
|
88
150
|
output.write(`total agents: ${agents.length}\n`);
|
|
89
151
|
output.write(`public agents: ${agents.filter((agent) => agent.publicRecordExists).length}\n`);
|
|
90
152
|
output.write(`source: ${source}\n`);
|
package/agentListHelpers.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
2
|
+
import {
|
|
3
|
+
listUserRecordsFromServers,
|
|
4
|
+
readLiveDbRecordAfterTombstoneMerge,
|
|
5
|
+
} from "./globalRecordOperations";
|
|
2
6
|
|
|
3
7
|
export type ListedAgent = {
|
|
4
8
|
id: string;
|
|
@@ -93,7 +97,7 @@ export async function listLocalCachedAgents(args: {
|
|
|
93
97
|
}) {
|
|
94
98
|
const privateRecords = new Map<string, any>();
|
|
95
99
|
const publicKeys = new Set<string>();
|
|
96
|
-
for await (const [key, value] of args.db.iterator()) {
|
|
100
|
+
for await (const [key, value] of args.db.iterator({ gte: "", lte: "\uffff" })) {
|
|
97
101
|
if (typeof key !== "string" || !value || typeof value !== "object") continue;
|
|
98
102
|
if (key.startsWith(`agent-${args.userId}-`) || key.startsWith(`cybot-${args.userId}-`)) {
|
|
99
103
|
privateRecords.set(key, { ...(value as Record<string, unknown>), dbKey: key });
|
|
@@ -185,3 +189,54 @@ export async function listRemoteAgents(args: {
|
|
|
185
189
|
);
|
|
186
190
|
return agents;
|
|
187
191
|
}
|
|
192
|
+
|
|
193
|
+
export async function listRemoteAgentsAcrossServers(args: {
|
|
194
|
+
authToken: string;
|
|
195
|
+
fallbackFetchImpl?: typeof fetch;
|
|
196
|
+
fetchImpl: typeof fetch;
|
|
197
|
+
includeLegacy: boolean;
|
|
198
|
+
serverUrls: string[];
|
|
199
|
+
userId: string;
|
|
200
|
+
}) {
|
|
201
|
+
const remoteResult = await listUserRecordsFromServers({
|
|
202
|
+
authToken: args.authToken,
|
|
203
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
204
|
+
fetchImpl: args.fetchImpl,
|
|
205
|
+
label: "agent query",
|
|
206
|
+
serverUrls: args.serverUrls,
|
|
207
|
+
type: args.includeLegacy ? ["agent", "cybot"] : "agent",
|
|
208
|
+
userId: args.userId,
|
|
209
|
+
});
|
|
210
|
+
const agents = sortListedAgents(
|
|
211
|
+
remoteResult.records
|
|
212
|
+
.map((record) => normalizeListedAgent(record))
|
|
213
|
+
.filter((agent): agent is ListedAgent => agent != null)
|
|
214
|
+
);
|
|
215
|
+
return { agents, failures: remoteResult.failures };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function decorateAgentsWithPublicStatusAcrossServers(args: {
|
|
219
|
+
agents: ListedAgent[];
|
|
220
|
+
authToken: string;
|
|
221
|
+
fallbackFetchImpl?: typeof fetch;
|
|
222
|
+
fetchImpl: typeof fetch;
|
|
223
|
+
serverUrls: string[];
|
|
224
|
+
}) {
|
|
225
|
+
await Promise.all(
|
|
226
|
+
args.agents.map(async (agent) => {
|
|
227
|
+
try {
|
|
228
|
+
const result = await readLiveDbRecordAfterTombstoneMerge({
|
|
229
|
+
authToken: args.authToken,
|
|
230
|
+
dbKey: agent.publicKey,
|
|
231
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
232
|
+
fetchImpl: args.fetchImpl,
|
|
233
|
+
serverUrls: args.serverUrls,
|
|
234
|
+
});
|
|
235
|
+
agent.publicRecordExists = result.record?.isPublic === true;
|
|
236
|
+
} catch {
|
|
237
|
+
agent.publicRecordExists = false;
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
return args.agents;
|
|
242
|
+
}
|
package/agentRecordHelpers.ts
CHANGED
|
@@ -125,10 +125,12 @@ export async function readDbRecord(args: {
|
|
|
125
125
|
authToken: string;
|
|
126
126
|
fallbackFetchImpl?: typeof fetch;
|
|
127
127
|
fetchImpl: typeof fetch;
|
|
128
|
+
includeDeleted?: boolean;
|
|
128
129
|
serverUrl: string;
|
|
129
130
|
}) {
|
|
131
|
+
const query = args.includeDeleted ? "?includeDeleted=true" : "";
|
|
130
132
|
const res = await fetchWithTransportFallback(
|
|
131
|
-
`${args.serverUrl}/api/v1/db/read/${encodeURIComponent(args.dbKey)}`,
|
|
133
|
+
`${args.serverUrl}/api/v1/db/read/${encodeURIComponent(args.dbKey)}${query}`,
|
|
132
134
|
{
|
|
133
135
|
method: "GET",
|
|
134
136
|
headers: { Authorization: `Bearer ${args.authToken}` },
|
|
@@ -146,19 +148,29 @@ export async function queryUserRecords(args: {
|
|
|
146
148
|
authToken: string;
|
|
147
149
|
fallbackFetchImpl?: typeof fetch;
|
|
148
150
|
fetchImpl: typeof fetch;
|
|
151
|
+
limit?: number;
|
|
149
152
|
serverUrl: string;
|
|
153
|
+
summary?: boolean;
|
|
154
|
+
includeDeleted?: boolean;
|
|
150
155
|
userId: string;
|
|
151
|
-
type:
|
|
152
|
-
}) {
|
|
156
|
+
type: string | string[];
|
|
157
|
+
}): Promise<any[]> {
|
|
158
|
+
const query = typeof args.limit === "number" && args.limit > 0
|
|
159
|
+
? `?limit=${Math.floor(args.limit)}`
|
|
160
|
+
: "";
|
|
153
161
|
const res = await fetchWithTransportFallback(
|
|
154
|
-
`${args.serverUrl}/api/v1/db/query/${encodeURIComponent(args.userId)}`,
|
|
162
|
+
`${args.serverUrl}/api/v1/db/query/${encodeURIComponent(args.userId)}${query}`,
|
|
155
163
|
{
|
|
156
164
|
method: "POST",
|
|
157
165
|
headers: {
|
|
158
166
|
Authorization: `Bearer ${args.authToken}`,
|
|
159
167
|
"Content-Type": "application/json",
|
|
160
168
|
},
|
|
161
|
-
body: JSON.stringify({
|
|
169
|
+
body: JSON.stringify({
|
|
170
|
+
type: args.type,
|
|
171
|
+
...(args.includeDeleted ? { includeDeleted: true } : {}),
|
|
172
|
+
...(args.summary ? { summary: true } : {}),
|
|
173
|
+
}),
|
|
162
174
|
},
|
|
163
175
|
args
|
|
164
176
|
);
|
|
@@ -173,6 +185,34 @@ export async function queryUserRecords(args: {
|
|
|
173
185
|
: [];
|
|
174
186
|
}
|
|
175
187
|
|
|
188
|
+
export async function deleteDbRecord(args: {
|
|
189
|
+
dbKey: string;
|
|
190
|
+
deleteOptions?: { type?: "single" | "table" | "messages" };
|
|
191
|
+
authToken: string;
|
|
192
|
+
fallbackFetchImpl?: typeof fetch;
|
|
193
|
+
fetchImpl: typeof fetch;
|
|
194
|
+
serverUrl: string;
|
|
195
|
+
}) {
|
|
196
|
+
const type = args.deleteOptions?.type;
|
|
197
|
+
const query =
|
|
198
|
+
type && type !== "single"
|
|
199
|
+
? `?type=${encodeURIComponent(type)}`
|
|
200
|
+
: "";
|
|
201
|
+
const res = await fetchWithTransportFallback(
|
|
202
|
+
`${args.serverUrl}/api/v1/db/delete/${encodeURIComponent(args.dbKey)}${query}`,
|
|
203
|
+
{
|
|
204
|
+
method: "DELETE",
|
|
205
|
+
headers: { Authorization: `Bearer ${args.authToken}` },
|
|
206
|
+
},
|
|
207
|
+
args
|
|
208
|
+
);
|
|
209
|
+
const data = await res.json().catch(() => ({}));
|
|
210
|
+
if (!res.ok) {
|
|
211
|
+
throw new Error(`delete failed: HTTP ${res.status} ${JSON.stringify(data)}`);
|
|
212
|
+
}
|
|
213
|
+
return data;
|
|
214
|
+
}
|
|
215
|
+
|
|
176
216
|
export async function writeAgentRecord(args: {
|
|
177
217
|
agentKey: string;
|
|
178
218
|
authToken: string;
|
|
@@ -20,6 +20,10 @@ export type LocalCliExecutor = (
|
|
|
20
20
|
export type LocalRuntimeProbeResult = {
|
|
21
21
|
ok: boolean;
|
|
22
22
|
dbPath: string;
|
|
23
|
+
authorityDriver?: string;
|
|
24
|
+
authorityEndpoint?: string;
|
|
25
|
+
authorityMetadataPath?: string;
|
|
26
|
+
authorityHealthPath?: string;
|
|
23
27
|
agentFound: boolean;
|
|
24
28
|
agentKey?: string;
|
|
25
29
|
error?: string;
|
package/cli/agentListCommands.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { getReadableCliDb, type AgentCommandDeps } from "./agentCommandSupport";
|
|
2
2
|
import {
|
|
3
|
+
decorateAgentsWithPublicStatusAcrossServers,
|
|
3
4
|
listLocalCachedAgents,
|
|
5
|
+
listRemoteAgentsAcrossServers,
|
|
4
6
|
listRemoteAgents,
|
|
5
7
|
parseAgentListArgs,
|
|
6
8
|
type ListedAgent,
|
|
@@ -9,11 +11,15 @@ import {
|
|
|
9
11
|
queryUserRecords,
|
|
10
12
|
readDbRecord,
|
|
11
13
|
} from "./agentRecordHelpers";
|
|
14
|
+
import { buildSpaceLookup, getSpaceContentKeys } from "./cliSpaceHelpers";
|
|
12
15
|
import {
|
|
13
16
|
parseUserIdFromAuthToken,
|
|
17
|
+
readOption,
|
|
14
18
|
resolveAuthToken,
|
|
19
|
+
resolveServerCandidates,
|
|
15
20
|
resolveServerUrl,
|
|
16
21
|
} from "./cliEnvHelpers";
|
|
22
|
+
import { readLiveDbRecordAfterTombstoneMerge } from "./globalRecordOperations";
|
|
17
23
|
|
|
18
24
|
export async function runAgentListCommand(
|
|
19
25
|
args: string[],
|
|
@@ -22,6 +28,7 @@ export async function runAgentListCommand(
|
|
|
22
28
|
const env = deps.env ?? process.env;
|
|
23
29
|
const output = deps.output ?? process.stdout;
|
|
24
30
|
const { wantJson, publicOnly, idsOnly, includeLegacy } = parseAgentListArgs(args);
|
|
31
|
+
const spaceInput = readOption(args, "--space") ?? readOption(args, "--space-id");
|
|
25
32
|
|
|
26
33
|
const authToken = resolveAuthToken(args, env);
|
|
27
34
|
if (!authToken) {
|
|
@@ -35,34 +42,79 @@ export async function runAgentListCommand(
|
|
|
35
42
|
return 1;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
|
-
const db = deps.db ?? await getReadableCliDb(output);
|
|
39
45
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
40
46
|
const fallbackFetchImpl = deps.fallbackFetchImpl;
|
|
47
|
+
const serverUrl = resolveServerUrl(args, env);
|
|
48
|
+
const serverUrls = resolveServerCandidates(args, env, serverUrl);
|
|
41
49
|
|
|
42
50
|
try {
|
|
43
51
|
let agents: ListedAgent[];
|
|
44
|
-
let source: "local-cache" | "remote-cache";
|
|
52
|
+
let source: "local-cache" | "remote-cache" | "global-cache";
|
|
53
|
+
let serverFailures: Array<{ serverUrl: string; error: string }> = [];
|
|
45
54
|
try {
|
|
46
|
-
|
|
47
|
-
source = "local-cache";
|
|
48
|
-
} catch {
|
|
49
|
-
const serverUrl = resolveServerUrl(args, env);
|
|
50
|
-
agents = await listRemoteAgents({
|
|
55
|
+
const remoteResult = await listRemoteAgentsAcrossServers({
|
|
51
56
|
authToken,
|
|
52
57
|
fallbackFetchImpl,
|
|
53
58
|
fetchImpl,
|
|
54
59
|
includeLegacy,
|
|
55
|
-
|
|
60
|
+
serverUrls,
|
|
56
61
|
userId,
|
|
57
|
-
queryUserRecords,
|
|
58
|
-
readDbRecord,
|
|
59
62
|
});
|
|
60
|
-
|
|
63
|
+
agents = remoteResult.agents;
|
|
64
|
+
serverFailures = remoteResult.failures;
|
|
65
|
+
source = "global-cache";
|
|
66
|
+
} catch {
|
|
67
|
+
try {
|
|
68
|
+
const db = deps.db ?? await getReadableCliDb(output);
|
|
69
|
+
agents = await listLocalCachedAgents({ db, userId });
|
|
70
|
+
source = "local-cache";
|
|
71
|
+
} catch {
|
|
72
|
+
agents = await listRemoteAgents({
|
|
73
|
+
authToken,
|
|
74
|
+
fallbackFetchImpl,
|
|
75
|
+
fetchImpl,
|
|
76
|
+
includeLegacy,
|
|
77
|
+
serverUrl,
|
|
78
|
+
userId,
|
|
79
|
+
queryUserRecords,
|
|
80
|
+
readDbRecord,
|
|
81
|
+
});
|
|
82
|
+
source = "remote-cache";
|
|
83
|
+
}
|
|
61
84
|
}
|
|
62
85
|
|
|
63
86
|
if (!includeLegacy) {
|
|
64
87
|
agents = agents.filter((agent) => !agent.privateKey.startsWith("cybot-"));
|
|
65
88
|
}
|
|
89
|
+
let resolvedSpaceId: string | null = null;
|
|
90
|
+
if (spaceInput) {
|
|
91
|
+
const { spaceId, spaceKey } = buildSpaceLookup(spaceInput);
|
|
92
|
+
resolvedSpaceId = spaceId;
|
|
93
|
+
const spaceRead = await readLiveDbRecordAfterTombstoneMerge({
|
|
94
|
+
authToken,
|
|
95
|
+
dbKey: spaceKey,
|
|
96
|
+
fallbackFetchImpl,
|
|
97
|
+
fetchImpl,
|
|
98
|
+
serverUrls,
|
|
99
|
+
});
|
|
100
|
+
serverFailures = [...serverFailures, ...spaceRead.failures];
|
|
101
|
+
const spaceRecord = spaceRead.record;
|
|
102
|
+
const spaceContentKeys = getSpaceContentKeys(spaceRecord);
|
|
103
|
+
agents = agents.filter((agent) =>
|
|
104
|
+
spaceContentKeys.has(agent.privateKey) ||
|
|
105
|
+
spaceContentKeys.has(agent.publicKey) ||
|
|
106
|
+
spaceContentKeys.has(agent.id)
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (source === "global-cache") {
|
|
110
|
+
await decorateAgentsWithPublicStatusAcrossServers({
|
|
111
|
+
agents,
|
|
112
|
+
authToken,
|
|
113
|
+
fallbackFetchImpl,
|
|
114
|
+
fetchImpl,
|
|
115
|
+
serverUrls,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
66
118
|
if (publicOnly) {
|
|
67
119
|
agents = agents.filter((agent) => agent.publicRecordExists);
|
|
68
120
|
}
|
|
@@ -75,6 +127,9 @@ export async function runAgentListCommand(
|
|
|
75
127
|
if (wantJson) {
|
|
76
128
|
output.write(JSON.stringify({
|
|
77
129
|
userId,
|
|
130
|
+
...(resolvedSpaceId ? { spaceId: resolvedSpaceId } : {}),
|
|
131
|
+
targetServers: serverUrls,
|
|
132
|
+
...(serverFailures.length ? { serverFailures } : {}),
|
|
78
133
|
total: agents.length,
|
|
79
134
|
publicCount: agents.filter((agent) => agent.publicRecordExists).length,
|
|
80
135
|
source,
|
|
@@ -85,6 +140,13 @@ export async function runAgentListCommand(
|
|
|
85
140
|
}
|
|
86
141
|
|
|
87
142
|
output.write(`userId: ${userId}\n`);
|
|
143
|
+
if (resolvedSpaceId) {
|
|
144
|
+
output.write(`spaceId: ${resolvedSpaceId}\n`);
|
|
145
|
+
}
|
|
146
|
+
output.write(`targetServers: ${serverUrls.join(", ")}\n`);
|
|
147
|
+
if (serverFailures.length) {
|
|
148
|
+
output.write(`serverFailures: ${serverFailures.length}\n`);
|
|
149
|
+
}
|
|
88
150
|
output.write(`total agents: ${agents.length}\n`);
|
|
89
151
|
output.write(`public agents: ${agents.filter((agent) => agent.publicRecordExists).length}\n`);
|
|
90
152
|
output.write(`source: ${source}\n`);
|
package/cli/agentListHelpers.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { CliKvDb } from "./client/hybridRecordStore";
|
|
2
|
+
import {
|
|
3
|
+
listUserRecordsFromServers,
|
|
4
|
+
readLiveDbRecordAfterTombstoneMerge,
|
|
5
|
+
} from "./globalRecordOperations";
|
|
2
6
|
|
|
3
7
|
export type ListedAgent = {
|
|
4
8
|
id: string;
|
|
@@ -93,7 +97,7 @@ export async function listLocalCachedAgents(args: {
|
|
|
93
97
|
}) {
|
|
94
98
|
const privateRecords = new Map<string, any>();
|
|
95
99
|
const publicKeys = new Set<string>();
|
|
96
|
-
for await (const [key, value] of args.db.iterator()) {
|
|
100
|
+
for await (const [key, value] of args.db.iterator({ gte: "", lte: "\uffff" })) {
|
|
97
101
|
if (typeof key !== "string" || !value || typeof value !== "object") continue;
|
|
98
102
|
if (key.startsWith(`agent-${args.userId}-`) || key.startsWith(`cybot-${args.userId}-`)) {
|
|
99
103
|
privateRecords.set(key, { ...(value as Record<string, unknown>), dbKey: key });
|
|
@@ -185,3 +189,54 @@ export async function listRemoteAgents(args: {
|
|
|
185
189
|
);
|
|
186
190
|
return agents;
|
|
187
191
|
}
|
|
192
|
+
|
|
193
|
+
export async function listRemoteAgentsAcrossServers(args: {
|
|
194
|
+
authToken: string;
|
|
195
|
+
fallbackFetchImpl?: typeof fetch;
|
|
196
|
+
fetchImpl: typeof fetch;
|
|
197
|
+
includeLegacy: boolean;
|
|
198
|
+
serverUrls: string[];
|
|
199
|
+
userId: string;
|
|
200
|
+
}) {
|
|
201
|
+
const remoteResult = await listUserRecordsFromServers({
|
|
202
|
+
authToken: args.authToken,
|
|
203
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
204
|
+
fetchImpl: args.fetchImpl,
|
|
205
|
+
label: "agent query",
|
|
206
|
+
serverUrls: args.serverUrls,
|
|
207
|
+
type: args.includeLegacy ? ["agent", "cybot"] : "agent",
|
|
208
|
+
userId: args.userId,
|
|
209
|
+
});
|
|
210
|
+
const agents = sortListedAgents(
|
|
211
|
+
remoteResult.records
|
|
212
|
+
.map((record) => normalizeListedAgent(record))
|
|
213
|
+
.filter((agent): agent is ListedAgent => agent != null)
|
|
214
|
+
);
|
|
215
|
+
return { agents, failures: remoteResult.failures };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export async function decorateAgentsWithPublicStatusAcrossServers(args: {
|
|
219
|
+
agents: ListedAgent[];
|
|
220
|
+
authToken: string;
|
|
221
|
+
fallbackFetchImpl?: typeof fetch;
|
|
222
|
+
fetchImpl: typeof fetch;
|
|
223
|
+
serverUrls: string[];
|
|
224
|
+
}) {
|
|
225
|
+
await Promise.all(
|
|
226
|
+
args.agents.map(async (agent) => {
|
|
227
|
+
try {
|
|
228
|
+
const result = await readLiveDbRecordAfterTombstoneMerge({
|
|
229
|
+
authToken: args.authToken,
|
|
230
|
+
dbKey: agent.publicKey,
|
|
231
|
+
fallbackFetchImpl: args.fallbackFetchImpl,
|
|
232
|
+
fetchImpl: args.fetchImpl,
|
|
233
|
+
serverUrls: args.serverUrls,
|
|
234
|
+
});
|
|
235
|
+
agent.publicRecordExists = result.record?.isPublic === true;
|
|
236
|
+
} catch {
|
|
237
|
+
agent.publicRecordExists = false;
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
);
|
|
241
|
+
return args.agents;
|
|
242
|
+
}
|