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.
Files changed (46) hide show
  1. package/README.md +11 -1
  2. package/agent-runtime/localToolPolicy.ts +1 -1
  3. package/agent-runtime/localWorkspaceTools.ts +5 -2
  4. package/agent-runtime/providerResolution.ts +1 -3
  5. package/agentCommandSupport.ts +4 -0
  6. package/agentListCommands.ts +73 -11
  7. package/agentListHelpers.ts +56 -1
  8. package/agentRecordHelpers.ts +45 -5
  9. package/cli/agentCommandSupport.ts +4 -0
  10. package/cli/agentListCommands.ts +73 -11
  11. package/cli/agentListHelpers.ts +56 -1
  12. package/cli/agentRecordHelpers.ts +45 -5
  13. package/cli/cliSpaceHelpers.ts +39 -0
  14. package/cli/connectorWebSocketTarget.ts +10 -3
  15. package/cli/dialogCommands.ts +446 -0
  16. package/cli/dialogInternalCommandEntries.ts +13 -0
  17. package/cli/globalRecordOperations.ts +258 -0
  18. package/cli/internalCommandEntries.ts +2 -0
  19. package/cli/localRuntimeAuthority.ts +182 -0
  20. package/cli/localRuntimeDb.ts +2 -4
  21. package/cli/machineWsRunDispatch.ts +0 -3
  22. package/cli/machineWsSession.ts +2 -0
  23. package/cli/runtimeDoctorCommands.ts +55 -5
  24. package/cliSpaceHelpers.ts +39 -0
  25. package/connectorWebSocketTarget.ts +10 -3
  26. package/database/server/cliAuthorityBrokerClient.ts +227 -0
  27. package/database/server/cliAuthorityBrokerServer.ts +283 -0
  28. package/database/server/cliAuthorityBrokerTypes.ts +43 -0
  29. package/database/server/cliAuthorityStoreDriver.ts +98 -0
  30. package/database/server/levelAuthorityStore.ts +46 -26
  31. package/database/tombstones.ts +52 -0
  32. package/database/userDataMerge.ts +95 -0
  33. package/dialogCommands.ts +446 -0
  34. package/dialogInternalCommandEntries.ts +13 -0
  35. package/globalRecordOperations.ts +258 -0
  36. package/internalCommandEntries.ts +2 -0
  37. package/localRuntimeAuthority.ts +182 -0
  38. package/localRuntimeDb.ts +2 -4
  39. package/machineWsRunDispatch.ts +0 -3
  40. package/machineWsSession.ts +2 -0
  41. package/package.json +18 -6
  42. package/runtimeDoctorCommands.ts +55 -5
  43. package/database/server/db.ts +0 -99
  44. package/database/server/ensureDbOpen.ts +0 -12
  45. package/database/server/memoryAuthorityStore.ts +0 -133
  46. 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 dialog list --agent <agent>
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.
@@ -1,7 +1,7 @@
1
1
  import type {
2
2
  AgentRuntimeToolCallInput,
3
3
  AgentRuntimeToolResult,
4
- } from "./types";
4
+ } from "./hostAdapter";
5
5
  import { canonicalizeToolName } from "../ai/tools/toolNameAliases";
6
6
  import { evaluateShellCommandPolicy } from "./shellCommandPolicy";
7
7
 
@@ -4,7 +4,7 @@ import { dirname, relative, resolve, sep } from "node:path";
4
4
  import type {
5
5
  AgentRuntimeToolCallInput,
6
6
  AgentRuntimeToolResult,
7
- } from "./types";
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);
@@ -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;
@@ -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
- agents = await listLocalCachedAgents({ db, userId });
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
- serverUrl,
60
+ serverUrls,
56
61
  userId,
57
- queryUserRecords,
58
- readDbRecord,
59
62
  });
60
- source = "remote-cache";
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`);
@@ -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
+ }
@@ -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: "agent" | "cybot";
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({ type: args.type }),
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;
@@ -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
- agents = await listLocalCachedAgents({ db, userId });
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
- serverUrl,
60
+ serverUrls,
56
61
  userId,
57
- queryUserRecords,
58
- readDbRecord,
59
62
  });
60
- source = "remote-cache";
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`);
@@ -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
+ }