nolo-cli 0.1.38 → 0.1.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +4 -4
  2. package/agent-runtime/agentRecordConfig.ts +0 -14
  3. package/agent-runtime/dialogWritePlan.ts +39 -0
  4. package/agent-runtime/hostAdapter.ts +4 -1
  5. package/agent-runtime/hybridRecordStore.ts +1 -1
  6. package/agent-runtime/index.ts +39 -18
  7. package/agent-runtime/localDialogRead.ts +23 -0
  8. package/agent-runtime/localLoop.ts +87 -7
  9. package/agent-runtime/localToolPolicy.ts +1 -17
  10. package/agent-runtime/localWorkspaceTools.ts +51 -110
  11. package/agent-runtime/noloWorkspaceTools.ts +371 -0
  12. package/agent-runtime/runtimeToolPolicy.ts +3 -14
  13. package/agent-runtime/runtimeToolSurface.ts +198 -0
  14. package/agent-runtime/types.ts +2 -1
  15. package/agentAliases.ts +8 -1
  16. package/agentRunCommand.ts +65 -95
  17. package/agentRuntimeLocal.ts +1 -7
  18. package/ai/agent/cliExecutor.ts +249 -34
  19. package/chat/messages/fetchMessages.ts +84 -0
  20. package/chat/messages/types.ts +185 -0
  21. package/cli/agentAliases.ts +8 -1
  22. package/cli/agentRunCommand.ts +65 -95
  23. package/cli/agentRuntimeLocal.ts +1 -7
  24. package/cli/cliEnvHelpers.ts +11 -7
  25. package/cli/client/agentRun.ts +94 -93
  26. package/cli/client/localRuntimeAdapter.ts +250 -69
  27. package/cli/commandRegistry.ts +2 -2
  28. package/cli/connectorRunArtifact.ts +0 -1
  29. package/cli/dialogCommands.ts +572 -0
  30. package/cli/dialogInternalCommandEntries.ts +4 -0
  31. package/cli/machineWsRunDispatch.ts +76 -39
  32. package/cli/offlineMarxistsAgentCommand.ts +6 -4
  33. package/cli/scriptCommandEntries.ts +0 -2
  34. package/cli/tui/readlineWorkspace.ts +69 -0
  35. package/cli/tui/session.ts +176 -1
  36. package/cliEnvHelpers.ts +11 -7
  37. package/client/agentConfigResolver.test.ts +4 -5
  38. package/client/agentRun.test.ts +307 -57
  39. package/client/agentRun.ts +94 -93
  40. package/client/localRuntimeAdapter.test.ts +332 -53
  41. package/client/localRuntimeAdapter.ts +250 -69
  42. package/client/localRuntimeDryRun.test.ts +23 -9
  43. package/client/localToolPolicy.test.ts +4 -4
  44. package/commandRegistry.ts +2 -2
  45. package/connectorRunArtifact.ts +0 -1
  46. package/database/server/db.ts +99 -0
  47. package/database/server/ensureDbOpen.ts +12 -0
  48. package/database/server/levelAuthorityStore.ts +20 -1
  49. package/database/server/memoryAuthorityStore.ts +133 -0
  50. package/database/server/serverStoreFactory.ts +118 -0
  51. package/dialogCommands.ts +572 -0
  52. package/dialogInternalCommandEntries.ts +4 -0
  53. package/machineWsRunDispatch.ts +76 -39
  54. package/offlineMarxistsAgentCommand.ts +6 -4
  55. package/package.json +11 -9
  56. package/scriptCommandEntries.ts +0 -2
  57. package/tui/readlineWorkspace.ts +69 -0
  58. package/tui/session.ts +176 -1
  59. package/agent-runtime/taskWorkspace.ts +0 -193
  60. package/agent-runtime/workspaceSession.ts +0 -76
  61. package/cli/client/taskWorktree.ts +0 -8
  62. package/cli/client/workspaceSession.ts +0 -11
  63. package/client/taskWorktree.ts +0 -8
  64. package/client/workspaceSession.test.ts +0 -57
  65. package/client/workspaceSession.ts +0 -11
@@ -0,0 +1,371 @@
1
+ export const NOLO_WORKSPACE_TOOL_NAMES = [
2
+ "listDialogs",
3
+ "readDialog",
4
+ "listAgents",
5
+ "readAgent",
6
+ "listSpaces",
7
+ "readSpace",
8
+ "readDoc",
9
+ "readSkillDoc",
10
+ "queryTableRows",
11
+ "cliWhoami",
12
+ "cliDoctor",
13
+ ] as const;
14
+
15
+ export type NoloWorkspaceToolName = typeof NOLO_WORKSPACE_TOOL_NAMES[number];
16
+
17
+ export const NOLO_WORKSPACE_TOOL_PROMPT =
18
+ "Nolo workspace tools are available for Nolo data: use listDialogs/readDialog, listAgents/readAgent, listSpaces/readSpace, readDoc/readSkillDoc, queryTableRows, cliWhoami, and cliDoctor when the user asks to inspect Nolo workspace data. Prefer tools over guessing, and combine tool results when the user asks for summaries or analysis.";
19
+
20
+ const NOLO_WORKSPACE_TOOL_NAME_SET = new Set<string>(NOLO_WORKSPACE_TOOL_NAMES);
21
+ const DIALOG_ID_RE = /^[0-9A-HJKMNP-TV-Z]{26}$/i;
22
+ const DIALOG_PATH_RE = /^\/(?:space\/([^/]+)\/)?dialog-(.+)-([0-9A-HJKMNP-TV-Z]{26})\/?$/i;
23
+
24
+ function stringToolParam(description: string) {
25
+ return { type: "string", description };
26
+ }
27
+
28
+ export function isNoloWorkspaceToolName(toolName: unknown): toolName is NoloWorkspaceToolName {
29
+ return typeof toolName === "string" && NOLO_WORKSPACE_TOOL_NAME_SET.has(toolName);
30
+ }
31
+
32
+ export function filterNoloWorkspaceToolNames(toolNames?: string[]) {
33
+ return (toolNames ?? []).filter(isNoloWorkspaceToolName);
34
+ }
35
+
36
+ export function buildNoloWorkspaceOpenAiTools(args: { toolNames?: string[] }) {
37
+ const toolNames = new Set(args.toolNames ?? []);
38
+ const tools: Array<Record<string, unknown>> = [];
39
+ function add(name: NoloWorkspaceToolName, description: string, properties: Record<string, unknown>, required: string[] = []) {
40
+ if (!toolNames.has(name)) return;
41
+ tools.push({
42
+ type: "function",
43
+ function: {
44
+ name,
45
+ description,
46
+ parameters: {
47
+ type: "object",
48
+ properties,
49
+ ...(required.length ? { required } : {}),
50
+ },
51
+ },
52
+ });
53
+ }
54
+
55
+ add("listDialogs", "List the current user's dialogs in the Nolo workspace.", {
56
+ limit: { type: "integer", description: "Maximum dialogs to return." },
57
+ space: stringToolParam("Optional space id or URL."),
58
+ });
59
+ add("readDialog", "Read one persisted dialog in the Nolo workspace.", {
60
+ dialog: stringToolParam("Dialog id, dialog db key, or dialog URL."),
61
+ limit: { type: "integer", description: "Optional message limit." },
62
+ }, ["dialog"]);
63
+ add("listAgents", "List the current user's agents in the Nolo workspace.", {
64
+ space: stringToolParam("Optional space id or URL."),
65
+ publicOnly: { type: "boolean", description: "Only show public agents." },
66
+ });
67
+ add("readAgent", "Read one agent config in the Nolo workspace.", {
68
+ agent: stringToolParam("Agent key, id, alias, or URL."),
69
+ }, ["agent"]);
70
+ add("listSpaces", "List joined spaces in the Nolo workspace.", {});
71
+ add("readSpace", "Read one space in the Nolo workspace.", {
72
+ space: stringToolParam("Space id or URL."),
73
+ contentKey: stringToolParam("Optional content key inside the space."),
74
+ brief: { type: "boolean", description: "Return brief output when supported." },
75
+ }, ["space"]);
76
+ add("readDoc", "Read one normal doc/page in the Nolo workspace.", {
77
+ doc: stringToolParam("Doc/page key."),
78
+ }, ["doc"]);
79
+ add("readSkillDoc", "Read one skill doc in the Nolo workspace.", {
80
+ doc: stringToolParam("Skill doc/page key."),
81
+ }, ["doc"]);
82
+ add("queryTableRows", "Query rows from a Nolo table.", {
83
+ table: stringToolParam("Table id or meta key."),
84
+ limit: { type: "integer", description: "Optional row limit." },
85
+ row: stringToolParam("Optional row id or row db key."),
86
+ output: stringToolParam("Optional output format such as json, jsonl, or items."),
87
+ }, ["table"]);
88
+ add("cliWhoami", "Show the current Nolo CLI login state.", {});
89
+ add("cliDoctor", "Show Nolo CLI doctor diagnostics.", {});
90
+ return tools;
91
+ }
92
+
93
+ export function noloPositiveIntegerString(value: unknown) {
94
+ const parsed = Number(value);
95
+ return Number.isInteger(parsed) && parsed > 0 ? String(parsed) : null;
96
+ }
97
+
98
+ export function noloStringArg(value: unknown) {
99
+ return typeof value === "string" && value.trim() ? value.trim() : null;
100
+ }
101
+
102
+ export function parseNoloWorkspaceToolArguments(raw: string) {
103
+ try {
104
+ const parsed = JSON.parse(raw || "{}");
105
+ return parsed && typeof parsed === "object" ? parsed as Record<string, unknown> : {};
106
+ } catch {
107
+ return {};
108
+ }
109
+ }
110
+
111
+ export function clampNoloPositiveInteger(value: unknown, fallback: number, max = 500) {
112
+ const parsed = Number(value);
113
+ if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
114
+ return Math.min(Math.floor(parsed), max);
115
+ }
116
+
117
+ export function getNoloDialogIdFromKey(dbKey: string) {
118
+ const index = dbKey.lastIndexOf("-");
119
+ return index >= 0 ? dbKey.slice(index + 1) : dbKey;
120
+ }
121
+
122
+ export function resolveNoloDialogInput(rawInput: string, userId: string) {
123
+ const raw = rawInput.trim();
124
+ if (raw.startsWith("http://") || raw.startsWith("https://")) {
125
+ const url = new URL(raw);
126
+ const match = url.pathname.match(DIALOG_PATH_RE);
127
+ if (!match) throw new Error(`Unsupported dialog URL path: ${url.pathname}`);
128
+ const [, spaceId, ownerId, dialogId] = match;
129
+ return {
130
+ dbKey: `dialog-${ownerId}-${dialogId}`,
131
+ dialogId,
132
+ ownerId,
133
+ spaceId: spaceId ? decodeURIComponent(spaceId) : null,
134
+ };
135
+ }
136
+ if (raw.startsWith("dialog-")) {
137
+ const dialogId = getNoloDialogIdFromKey(raw);
138
+ return {
139
+ dbKey: raw,
140
+ dialogId,
141
+ ownerId: raw.slice("dialog-".length, Math.max("dialog-".length, raw.length - dialogId.length - 1)),
142
+ spaceId: null,
143
+ };
144
+ }
145
+ if (!DIALOG_ID_RE.test(raw)) {
146
+ throw new Error(`Unsupported dialog id: ${raw}`);
147
+ }
148
+ return {
149
+ dbKey: `dialog-${userId}-${raw}`,
150
+ dialogId: raw,
151
+ ownerId: userId,
152
+ spaceId: null,
153
+ };
154
+ }
155
+
156
+ export function normalizeNoloSpaceInput(raw: string) {
157
+ const value = raw.trim();
158
+ if (!value) return "";
159
+ if (value.startsWith("http://") || value.startsWith("https://")) {
160
+ const match = new URL(value).pathname.match(/^\/space\/([^/]+)/);
161
+ return match?.[1] ? decodeURIComponent(match[1]).replace(/^space-/, "") : "";
162
+ }
163
+ return value.replace(/^space-/, "");
164
+ }
165
+
166
+ export function getNoloSpaceContentKeys(spaceRecord: any) {
167
+ const keys = new Set<string>();
168
+ const contents = spaceRecord?.contents;
169
+ if (!contents || typeof contents !== "object") return keys;
170
+ for (const [entryKey, value] of Object.entries(contents)) {
171
+ keys.add(entryKey);
172
+ if (value && typeof value === "object") {
173
+ const contentKey = (value as any).contentKey;
174
+ if (typeof contentKey === "string" && contentKey.trim()) {
175
+ keys.add(contentKey.trim());
176
+ }
177
+ }
178
+ }
179
+ return keys;
180
+ }
181
+
182
+ export function getNoloComparableUpdatedAt(record: any) {
183
+ const raw = record?.updatedAt ?? record?.updated_at ?? record?.createdAt ?? record?.created;
184
+ if (typeof raw === "number" && Number.isFinite(raw)) return raw;
185
+ if (typeof raw === "string") return Date.parse(raw) || 0;
186
+ return 0;
187
+ }
188
+
189
+ export function normalizeNoloDocReadArgs(args: Record<string, any>) {
190
+ const id = args.id ?? args.doc ?? args.docKey ?? args.pageKey ?? args.key;
191
+ return id == null ? args : { ...args, id };
192
+ }
193
+
194
+ export function buildNoloTableQueryRequest(args: Record<string, any>, currentUserId?: string | null) {
195
+ const tableInput = noloStringArg(args.table ?? args.metaKey);
196
+ if (!tableInput) return null;
197
+ const tableMetaParts = tableInput.startsWith("meta-")
198
+ ? tableInput.split("-")
199
+ : [];
200
+ const tenantId = args.tenantId
201
+ ?? (tableMetaParts.length >= 3 ? tableMetaParts[1] : currentUserId);
202
+ const tableId = args.tableId
203
+ ?? (tableMetaParts.length >= 3 ? tableMetaParts.slice(2).join("-") : tableInput);
204
+ const filters: Record<string, unknown> = {
205
+ ...(args.filters && typeof args.filters === "object" ? args.filters : {}),
206
+ };
207
+ const row = noloStringArg(args.row ?? args.rowId);
208
+ if (row) {
209
+ if (row.startsWith("row-")) filters.dbKey = row;
210
+ else filters.rowId = row;
211
+ }
212
+ return {
213
+ tenantId,
214
+ tableId,
215
+ filters,
216
+ columns: args.columns,
217
+ limit: args.limit ?? (row ? 1 : 20),
218
+ offset: args.offset,
219
+ includeBaseFields: args.includeBaseFields,
220
+ sortBy: args.sortBy ?? "updatedAt",
221
+ sortOrder: args.sortOrder === "asc" ? "asc" : "desc",
222
+ };
223
+ }
224
+
225
+ export function buildNoloWorkspaceCommandArgs(call: { name: string; arguments: string }) {
226
+ const args = parseNoloWorkspaceToolArguments(call.arguments);
227
+ switch (call.name) {
228
+ case "listDialogs": {
229
+ const cliArgs = ["dialog", "list"];
230
+ const limit = noloPositiveIntegerString(args.limit);
231
+ const space = noloStringArg(args.space);
232
+ if (limit) cliArgs.push("--limit", limit);
233
+ if (space) cliArgs.push("--space", space);
234
+ return cliArgs;
235
+ }
236
+ case "readDialog": {
237
+ const dialog = noloStringArg(args.dialog ?? args.dialogId ?? args.id);
238
+ if (!dialog) throw new Error("readDialog requires dialog.");
239
+ const cliArgs = ["dialog", "read", dialog];
240
+ const limit = noloPositiveIntegerString(args.limit);
241
+ if (limit) cliArgs.push(limit);
242
+ return cliArgs;
243
+ }
244
+ case "listAgents": {
245
+ const cliArgs = ["agent", "list"];
246
+ const space = noloStringArg(args.space);
247
+ if (space) cliArgs.push("--space", space);
248
+ if (args.publicOnly === true) cliArgs.push("--public-only");
249
+ return cliArgs;
250
+ }
251
+ case "readAgent": {
252
+ const agent = noloStringArg(args.agent ?? args.agentKey ?? args.id);
253
+ if (!agent) throw new Error("readAgent requires agent.");
254
+ return ["agent", "read", agent];
255
+ }
256
+ case "listSpaces":
257
+ return ["space", "list"];
258
+ case "readSpace": {
259
+ const space = noloStringArg(args.space ?? args.spaceId ?? args.id);
260
+ if (!space) throw new Error("readSpace requires space.");
261
+ const cliArgs = ["space", "read", space];
262
+ const contentKey = noloStringArg(args.contentKey);
263
+ if (contentKey) cliArgs.push("--content-key", contentKey);
264
+ if (args.brief === true) cliArgs.push("--brief");
265
+ return cliArgs;
266
+ }
267
+ case "readDoc": {
268
+ const doc = noloStringArg(args.doc ?? args.docKey ?? args.pageKey ?? args.key);
269
+ if (!doc) throw new Error("readDoc requires doc.");
270
+ return ["doc", "read", doc];
271
+ }
272
+ case "readSkillDoc": {
273
+ const doc = noloStringArg(args.doc ?? args.docKey ?? args.pageKey ?? args.key);
274
+ if (!doc) throw new Error("readSkillDoc requires doc.");
275
+ return ["skill-doc", "read", doc];
276
+ }
277
+ case "queryTableRows": {
278
+ const table = noloStringArg(args.table ?? args.tableId ?? args.metaKey);
279
+ if (!table) throw new Error("queryTableRows requires table.");
280
+ const cliArgs = ["table", "query", "--table", table];
281
+ const limit = noloPositiveIntegerString(args.limit);
282
+ const row = noloStringArg(args.row ?? args.rowId);
283
+ const output = noloStringArg(args.output);
284
+ if (limit) cliArgs.push("--limit", limit);
285
+ if (row) cliArgs.push("--row", row);
286
+ if (output) cliArgs.push("--output", output);
287
+ return cliArgs;
288
+ }
289
+ case "cliWhoami":
290
+ return ["whoami"];
291
+ case "cliDoctor":
292
+ return ["doctor"];
293
+ default:
294
+ throw new Error(`Unsupported Nolo workspace tool: ${call.name}`);
295
+ }
296
+ }
297
+
298
+ async function readNoloProcessStream(readable: ReadableStream<Uint8Array> | null) {
299
+ if (!readable) return "";
300
+ return new Response(readable).text();
301
+ }
302
+
303
+ type NoloSpawnProcess = {
304
+ stdout: ReadableStream<Uint8Array> | null;
305
+ stderr: ReadableStream<Uint8Array> | null;
306
+ exited: Promise<number>;
307
+ };
308
+
309
+ type NoloSpawn = (options: {
310
+ cmd: string[];
311
+ stdout: "pipe";
312
+ stderr: "pipe";
313
+ env?: Record<string, string | undefined>;
314
+ }) => NoloSpawnProcess;
315
+
316
+ export async function runNoloWorkspaceCliTool(call: {
317
+ name: string;
318
+ arguments: string;
319
+ }, args: {
320
+ cliEntrypoint: string;
321
+ env?: Record<string, string | undefined>;
322
+ metadataKind?: string;
323
+ processExecPath?: string;
324
+ spawn?: NoloSpawn;
325
+ }) {
326
+ const cliArgs = buildNoloWorkspaceCommandArgs(call);
327
+ const spawn = args.spawn ?? (globalThis as any).Bun?.spawn;
328
+ if (typeof spawn !== "function") {
329
+ throw new Error("Nolo workspace CLI tools require a Bun-compatible spawn runtime.");
330
+ }
331
+ const proc = spawn({
332
+ cmd: [args.processExecPath ?? process.execPath, args.cliEntrypoint, ...cliArgs],
333
+ stdout: "pipe",
334
+ stderr: "pipe",
335
+ env: args.env,
336
+ });
337
+ const [stdout, stderr, exitCode] = await Promise.all([
338
+ readNoloProcessStream(proc.stdout),
339
+ readNoloProcessStream(proc.stderr),
340
+ proc.exited,
341
+ ]);
342
+ const content = `${stdout}${stderr}`;
343
+ if (exitCode !== 0) {
344
+ throw new Error(content.trim() || `nolo ${cliArgs.join(" ")} exited ${exitCode}`);
345
+ }
346
+ return {
347
+ content,
348
+ metadata: {
349
+ [args.metadataKind ?? "noloWorkspaceTool"]: true,
350
+ command: ["nolo", ...cliArgs].join(" "),
351
+ exitCode,
352
+ },
353
+ };
354
+ }
355
+
356
+ export function buildNoloWorkspaceCliToolExecutors(args: {
357
+ cliEntrypoint: string;
358
+ env?: Record<string, string | undefined>;
359
+ metadataKind?: string;
360
+ processExecPath?: string;
361
+ spawn?: NoloSpawn;
362
+ }) {
363
+ const executors: Record<string, (call: { name: string; arguments: string }) => Promise<{
364
+ content: string;
365
+ metadata?: Record<string, unknown>;
366
+ }>> = {};
367
+ for (const toolName of NOLO_WORKSPACE_TOOL_NAMES) {
368
+ executors[toolName] = (call) => runNoloWorkspaceCliTool(call, args);
369
+ }
370
+ return executors;
371
+ }
@@ -132,18 +132,8 @@ export function resolveLocalRuntimeEnvFromPolicy(
132
132
  runtimeEnv: EnvLike,
133
133
  policy?: AgentRuntimeToolPolicy,
134
134
  ): EnvLike {
135
- const enablesWorktreeShell =
136
- Array.isArray(policy?.runtimeTools) &&
137
- policy.runtimeTools.includes("execShell") &&
138
- policy.shell?.enabled === true &&
139
- policy.shell?.mode === "worktree";
140
- return {
141
- ...runtimeEnv,
142
- ...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
143
- ...(enablesWorktreeShell && !runtimeEnv.NOLO_LOCAL_SHELL_MODE
144
- ? { NOLO_LOCAL_SHELL_MODE: "worktree" }
145
- : {}),
146
- };
135
+ void policy;
136
+ return { ...runtimeEnv };
147
137
  }
148
138
 
149
139
  export function resolveLocalWorkspaceExecutorOptionsFromPolicy(
@@ -161,8 +151,7 @@ function policyRequestsHostedWorkspace(policy: AgentRuntimeToolPolicy | undefine
161
151
  return (
162
152
  policy.shell?.enabled === true ||
163
153
  policy.runtimeTools?.some((tool) => tool === "execShell") ||
164
- workspaceMode === "lease" ||
165
- workspaceMode === "task-worktree"
154
+ workspaceMode === "lease"
166
155
  );
167
156
  }
168
157
 
@@ -0,0 +1,198 @@
1
+ export const DEFAULT_PRIVATE_NOLO_WORKSPACE_TOOLS = [
2
+ "listDialogs",
3
+ "readDialog",
4
+ "listAgents",
5
+ "readAgent",
6
+ "listSpaces",
7
+ "readSpace",
8
+ "readDoc",
9
+ "readSkillDoc",
10
+ "queryTableRows",
11
+ "cliWhoami",
12
+ "cliDoctor",
13
+ ] as const;
14
+
15
+ export type RuntimeToolSurfaceHost =
16
+ | "web"
17
+ | "cli"
18
+ | "desktop"
19
+ | "connector"
20
+ | "server";
21
+
22
+ export type RuntimeToolSurfaceVisibility =
23
+ | "private"
24
+ | "public"
25
+ | "anonymous"
26
+ | "shared";
27
+
28
+ export type RuntimeToolSurfaceInput = {
29
+ explicitToolNames?: string[] | null;
30
+ currentUserId?: string | null;
31
+ agentOwnerId?: string | null;
32
+ invocationVisibility?: RuntimeToolSurfaceVisibility;
33
+ runtimeHost: RuntimeToolSurfaceHost;
34
+ trustedPrivateInvocation?: boolean;
35
+ };
36
+
37
+ export type RuntimeToolSurfaceResult = {
38
+ explicitToolNames: string[];
39
+ injectedToolNames: string[];
40
+ finalToolNames: string[];
41
+ auditReason:
42
+ | "private-authenticated-defaults"
43
+ | "explicit-only-public"
44
+ | "explicit-only-anonymous"
45
+ | "explicit-only-shared"
46
+ | "explicit-only-missing-identity"
47
+ | "explicit-only-owner-mismatch";
48
+ };
49
+
50
+ export type RuntimeToolSurfaceForAgentInput = {
51
+ explicitToolNames?: string[] | null;
52
+ currentUserId?: string | null;
53
+ agentOwnerId?: string | null;
54
+ agentKey?: string | null;
55
+ isPublic?: boolean | null;
56
+ sharingLevel?: string | null;
57
+ runtimeHost: RuntimeToolSurfaceHost;
58
+ trustedPrivateInvocation?: boolean;
59
+ };
60
+
61
+ function uniqueToolNames(values: readonly string[]) {
62
+ return [
63
+ ...new Set(
64
+ values
65
+ .filter((value) => typeof value === "string")
66
+ .map((value) => value.trim())
67
+ .filter(Boolean)
68
+ ),
69
+ ];
70
+ }
71
+
72
+ function canInjectPrivateDefaults(args: RuntimeToolSurfaceInput) {
73
+ if (!args.currentUserId) return false;
74
+ if (args.invocationVisibility !== "private") return false;
75
+ if (args.agentOwnerId && args.agentOwnerId === args.currentUserId) return true;
76
+ if (args.trustedPrivateInvocation === true) return true;
77
+ return false;
78
+ }
79
+
80
+ function explicitOnlyReason(
81
+ args: RuntimeToolSurfaceInput
82
+ ): RuntimeToolSurfaceResult["auditReason"] {
83
+ if (!args.currentUserId || !args.agentOwnerId) {
84
+ return "explicit-only-missing-identity";
85
+ }
86
+ if (args.invocationVisibility === "public") return "explicit-only-public";
87
+ if (args.invocationVisibility === "shared") return "explicit-only-shared";
88
+ if (args.agentOwnerId !== args.currentUserId) {
89
+ return "explicit-only-owner-mismatch";
90
+ }
91
+ return "explicit-only-anonymous";
92
+ }
93
+
94
+ export function resolveRuntimeToolSurface(
95
+ args: RuntimeToolSurfaceInput
96
+ ): RuntimeToolSurfaceResult {
97
+ const explicitToolNames = uniqueToolNames(args.explicitToolNames ?? []);
98
+
99
+ if (canInjectPrivateDefaults(args)) {
100
+ const injectedToolNames = [...DEFAULT_PRIVATE_NOLO_WORKSPACE_TOOLS];
101
+ return {
102
+ explicitToolNames,
103
+ injectedToolNames,
104
+ finalToolNames: uniqueToolNames([
105
+ ...explicitToolNames,
106
+ ...injectedToolNames,
107
+ ]),
108
+ auditReason: "private-authenticated-defaults",
109
+ };
110
+ }
111
+
112
+ return {
113
+ explicitToolNames,
114
+ injectedToolNames: [],
115
+ finalToolNames: explicitToolNames,
116
+ auditReason: explicitOnlyReason(args),
117
+ };
118
+ }
119
+
120
+ export function isPublicRuntimeAgentRef(value: unknown) {
121
+ return (
122
+ typeof value === "string" &&
123
+ (value.startsWith("agent-pub-") || value.startsWith("cybot-pub-"))
124
+ );
125
+ }
126
+
127
+ export function inferOwnerIdFromRuntimeAgentKey(value: unknown) {
128
+ if (typeof value !== "string") return null;
129
+ const privateAgentMatch = value.match(/^agent-([^-]+)-(.+)$/);
130
+ if (privateAgentMatch && privateAgentMatch[1] !== "pub") {
131
+ return privateAgentMatch[1];
132
+ }
133
+ const privateCybotMatch = value.match(/^cybot-([^-]+)-(.+)$/);
134
+ if (privateCybotMatch && privateCybotMatch[1] !== "pub") {
135
+ return privateCybotMatch[1];
136
+ }
137
+ return null;
138
+ }
139
+
140
+ export function resolveRuntimeToolSurfaceForAgent(
141
+ args: RuntimeToolSurfaceForAgentInput
142
+ ) {
143
+ const agentKey = typeof args.agentKey === "string" ? args.agentKey : "";
144
+ const currentUserId = typeof args.currentUserId === "string" ? args.currentUserId : "";
145
+ const inferredOwnerId =
146
+ args.agentOwnerId ??
147
+ (currentUserId &&
148
+ (agentKey.startsWith(`agent-${currentUserId}-`) ||
149
+ agentKey.startsWith(`cybot-${currentUserId}-`))
150
+ ? currentUserId
151
+ : inferOwnerIdFromRuntimeAgentKey(agentKey));
152
+ const publicRef = isPublicRuntimeAgentRef(agentKey);
153
+ const visibility =
154
+ args.trustedPrivateInvocation === true
155
+ ? "private"
156
+ : publicRef || args.isPublic === true || args.sharingLevel === "public"
157
+ ? "public"
158
+ : "private";
159
+ return resolveRuntimeToolSurface({
160
+ explicitToolNames: args.explicitToolNames,
161
+ currentUserId: args.currentUserId,
162
+ agentOwnerId: inferredOwnerId,
163
+ invocationVisibility: visibility,
164
+ runtimeHost: args.runtimeHost,
165
+ trustedPrivateInvocation: args.trustedPrivateInvocation,
166
+ });
167
+ }
168
+
169
+ const SECRET_FIELD_NAMES = [
170
+ "apikey",
171
+ "api_key",
172
+ "secret",
173
+ "token",
174
+ "authorization",
175
+ "credential",
176
+ "password",
177
+ ];
178
+
179
+ function isSecretFieldName(key: string) {
180
+ const normalized = key.toLowerCase();
181
+ return SECRET_FIELD_NAMES.some((name) => normalized.includes(name));
182
+ }
183
+
184
+ export function redactAgentRecordForWorkspaceTool(record: unknown): unknown {
185
+ if (!record || typeof record !== "object") return record;
186
+ if (Array.isArray(record)) return record.map(redactAgentRecordForWorkspaceTool);
187
+ const redacted: Record<string, unknown> = {};
188
+ for (const [key, value] of Object.entries(record as Record<string, unknown>)) {
189
+ if (isSecretFieldName(key)) {
190
+ redacted[key] = "[redacted]";
191
+ continue;
192
+ }
193
+ redacted[key] = value && typeof value === "object"
194
+ ? redactAgentRecordForWorkspaceTool(value)
195
+ : value;
196
+ }
197
+ return redacted;
198
+ }
@@ -55,6 +55,7 @@ export interface AgentRuntimeResult {
55
55
  tool_calls?: AgentRuntimeToolCall[];
56
56
  reasoning_content?: string;
57
57
  runtimeToolNames?: string[];
58
+ runtimeToolSurface?: unknown;
58
59
  toolCallCount?: number;
59
60
  policyState?: unknown;
60
61
  latencyProfile?: {
@@ -81,7 +82,7 @@ export type AgentRuntimeDecisionInput = {
81
82
  serverFallbackAvailable: boolean;
82
83
  };
83
84
 
84
- export type AgentRuntimeWorkspaceMode = "none" | "current" | "task-worktree" | "lease";
85
+ export type AgentRuntimeWorkspaceMode = "none" | "current" | "lease";
85
86
 
86
87
  export type AgentRuntimeShellPolicy = {
87
88
  enabled?: boolean;
package/agentAliases.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export const PLATFORM_DEMO_USER_ID = "b2e06f801f";
2
+ export const CURRENT_OPERATOR_USER_ID = "0e95801d90";
3
+ export const NOLO_DEFAULT_AGENT_ID = "01NOLOAPPBLD000000019KCKT0";
4
+ export const NOLO_DEFAULT_AGENT_KEY = `agent-pub-${NOLO_DEFAULT_AGENT_ID}`;
2
5
  export const NOLO_PROJECT_MANAGER_AGENT_ID = "01NOLOPROJMGR00000000MSVGG";
3
6
  export const NOLO_PROJECT_MANAGER_AGENT_KEY =
4
7
  `agent-${PLATFORM_DEMO_USER_ID}-${NOLO_PROJECT_MANAGER_AGENT_ID}`;
@@ -7,7 +10,7 @@ export const NOLO_SENIOR_FULLSTACK_AGENT_KEY =
7
10
  `agent-${PLATFORM_DEMO_USER_ID}-${NOLO_SENIOR_FULLSTACK_AGENT_ID}`;
8
11
  export const NOLO_FRONTEND_AGENT_ID = "01FRONTENDAG0000000115N4E1";
9
12
  export const NOLO_FRONTEND_AGENT_KEY =
10
- `agent-${PLATFORM_DEMO_USER_ID}-${NOLO_FRONTEND_AGENT_ID}`;
13
+ `agent-${CURRENT_OPERATOR_USER_ID}-${NOLO_FRONTEND_AGENT_ID}`;
11
14
  export const WIN_CODEX_AGENT_ID = "WINCODEX00000000FQ0LK0";
12
15
  export const WIN_CODEX_AGENT_KEY =
13
16
  `agent-0e95801d90-${WIN_CODEX_AGENT_ID}`;
@@ -19,6 +22,10 @@ export const MIMO_MONTH_AGENT_KEY =
19
22
  `agent-0e95801d90-${MIMO_MONTH_AGENT_ID}`;
20
23
 
21
24
  const AGENT_ALIAS_TO_KEY: Record<string, string> = {
25
+ nolo: NOLO_DEFAULT_AGENT_KEY,
26
+ default: NOLO_DEFAULT_AGENT_KEY,
27
+ "default-nolo": NOLO_DEFAULT_AGENT_KEY,
28
+
22
29
  // Win Codex (高智力终审与架构把关)
23
30
  "win-codex": WIN_CODEX_AGENT_KEY,
24
31
  "wincodex": WIN_CODEX_AGENT_KEY,