nolo-cli 0.1.38 → 0.1.39

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 (52) hide show
  1. package/agent-runtime/agentRecordConfig.ts +0 -14
  2. package/agent-runtime/hostAdapter.ts +0 -1
  3. package/agent-runtime/index.ts +24 -18
  4. package/agent-runtime/localDialogRead.ts +23 -0
  5. package/agent-runtime/localToolPolicy.ts +2 -7
  6. package/agent-runtime/localWorkspaceTools.ts +9 -0
  7. package/agent-runtime/noloWorkspaceTools.ts +371 -0
  8. package/agent-runtime/runtimeToolPolicy.ts +1 -3
  9. package/agent-runtime/types.ts +1 -1
  10. package/agentAliases.ts +6 -0
  11. package/agentRunCommand.ts +57 -65
  12. package/agentRuntimeLocal.ts +0 -7
  13. package/chat/messages/fetchMessages.ts +84 -0
  14. package/chat/messages/types.ts +185 -0
  15. package/cli/agentAliases.ts +6 -0
  16. package/cli/agentRunCommand.ts +57 -65
  17. package/cli/agentRuntimeLocal.ts +0 -7
  18. package/cli/cliEnvHelpers.ts +11 -7
  19. package/cli/client/agentRun.ts +55 -7
  20. package/cli/client/localRuntimeAdapter.ts +87 -59
  21. package/cli/connectorRunArtifact.ts +0 -1
  22. package/cli/dialogCommands.ts +572 -0
  23. package/cli/dialogInternalCommandEntries.ts +4 -0
  24. package/cli/machineWsRunDispatch.ts +29 -37
  25. package/cli/scriptCommandEntries.ts +0 -2
  26. package/cli/tui/readlineWorkspace.ts +69 -0
  27. package/cli/tui/session.ts +176 -1
  28. package/cliEnvHelpers.ts +11 -7
  29. package/client/agentConfigResolver.test.ts +2 -3
  30. package/client/agentRun.test.ts +185 -1
  31. package/client/agentRun.ts +55 -7
  32. package/client/localRuntimeAdapter.test.ts +90 -25
  33. package/client/localRuntimeAdapter.ts +87 -59
  34. package/connectorRunArtifact.ts +0 -1
  35. package/database/server/db.ts +99 -0
  36. package/database/server/ensureDbOpen.ts +12 -0
  37. package/database/server/memoryAuthorityStore.ts +133 -0
  38. package/database/server/serverStoreFactory.ts +118 -0
  39. package/dialogCommands.ts +572 -0
  40. package/dialogInternalCommandEntries.ts +4 -0
  41. package/machineWsRunDispatch.ts +29 -37
  42. package/package.json +10 -9
  43. package/scriptCommandEntries.ts +0 -2
  44. package/tui/readlineWorkspace.ts +69 -0
  45. package/tui/session.ts +176 -1
  46. package/agent-runtime/taskWorkspace.ts +0 -193
  47. package/agent-runtime/workspaceSession.ts +0 -76
  48. package/cli/client/taskWorktree.ts +0 -8
  49. package/cli/client/workspaceSession.ts +0 -11
  50. package/client/taskWorktree.ts +0 -8
  51. package/client/workspaceSession.test.ts +0 -57
  52. package/client/workspaceSession.ts +0 -11
@@ -19,18 +19,6 @@ function objectField(record: AgentRecord, key: string): Record<string, unknown>
19
19
  : undefined;
20
20
  }
21
21
 
22
- function localWorkspaceModeField(record: AgentRecord): "current" | "task-worktree" | undefined {
23
- const directValue = stringField(record, "localWorkspaceMode");
24
- const runtimeBinding = objectField(record, "runtimeBinding");
25
- const runtimeValue = typeof runtimeBinding?.localWorkspaceMode === "string"
26
- ? runtimeBinding.localWorkspaceMode
27
- : typeof runtimeBinding?.workspaceMode === "string"
28
- ? runtimeBinding.workspaceMode
29
- : undefined;
30
- const value = directValue ?? runtimeValue;
31
- return value === "task-worktree" || value === "current" ? value : undefined;
32
- }
33
-
34
22
  function appendUniqueStrings(values: string[], next: unknown) {
35
23
  if (!Array.isArray(next)) return values;
36
24
  const seen = new Set(values);
@@ -84,7 +72,6 @@ export function resolveAgentRuntimeConfigFromRecord(
84
72
  const runtimeToolPolicy =
85
73
  objectField(record, "runtimeToolPolicy") ??
86
74
  objectField(runtimeBinding ?? {}, "runtimeToolPolicy");
87
- const localWorkspaceMode = localWorkspaceModeField(record);
88
75
  const delegation = objectField(record, "delegation");
89
76
  return {
90
77
  key,
@@ -108,7 +95,6 @@ export function resolveAgentRuntimeConfigFromRecord(
108
95
  ...(reasoningEffort ? { reasoning_effort: reasoningEffort } : {}),
109
96
  ...(runtimeBinding ? { runtimeBinding } : {}),
110
97
  ...(runtimeToolPolicy ? { runtimeToolPolicy } : {}),
111
- ...(localWorkspaceMode ? { localWorkspaceMode } : {}),
112
98
  ...(delegation ? { delegation } : {}),
113
99
  rawRecord: record,
114
100
  };
@@ -27,7 +27,6 @@ export type AgentRuntimeAgentConfig = {
27
27
  reasoning_effort?: string;
28
28
  runtimeBinding?: Record<string, unknown>;
29
29
  runtimeToolPolicy?: AgentRuntimeToolPolicy;
30
- localWorkspaceMode?: "current" | "task-worktree";
31
30
  delegation?: Record<string, unknown>;
32
31
  rawRecord?: Record<string, unknown>;
33
32
  };
@@ -39,15 +39,6 @@ export {
39
39
  createHybridRecordStore,
40
40
  shouldCacheHybridRemoteRecord,
41
41
  } from "./hybridRecordStore";
42
- export {
43
- activateWorkspaceSession,
44
- createWorkspaceSession,
45
- formatWorkspaceSessionActivation,
46
- } from "./workspaceSession";
47
- export {
48
- formatPreparedGitTaskWorkspace,
49
- prepareGitTaskWorkspace,
50
- } from "./taskWorkspace";
51
42
  export {
52
43
  executeLocalToolWithPolicy,
53
44
  resolveLocalToolPolicy,
@@ -71,6 +62,30 @@ export {
71
62
  buildLocalWorkspaceToolset,
72
63
  createLocalWorkspaceToolExecutors,
73
64
  } from "./localWorkspaceTools";
65
+ export {
66
+ buildNoloWorkspaceCommandArgs,
67
+ buildNoloWorkspaceCliToolExecutors,
68
+ buildNoloWorkspaceOpenAiTools,
69
+ buildNoloTableQueryRequest,
70
+ clampNoloPositiveInteger,
71
+ filterNoloWorkspaceToolNames,
72
+ getNoloComparableUpdatedAt,
73
+ getNoloDialogIdFromKey,
74
+ getNoloSpaceContentKeys,
75
+ isNoloWorkspaceToolName,
76
+ noloPositiveIntegerString,
77
+ noloStringArg,
78
+ normalizeNoloDocReadArgs,
79
+ normalizeNoloSpaceInput,
80
+ NOLO_WORKSPACE_TOOL_NAMES,
81
+ NOLO_WORKSPACE_TOOL_PROMPT,
82
+ parseNoloWorkspaceToolArguments,
83
+ resolveNoloDialogInput,
84
+ runNoloWorkspaceCliTool,
85
+ } from "./noloWorkspaceTools";
86
+ export type {
87
+ NoloWorkspaceToolName,
88
+ } from "./noloWorkspaceTools";
74
89
  export type {
75
90
  AgentRuntimeAgentConfig,
76
91
  AgentRuntimeHostAdapter,
@@ -87,15 +102,6 @@ export type {
87
102
  HybridRecordKvDb,
88
103
  HybridRecordStore,
89
104
  } from "./hybridRecordStore";
90
- export type {
91
- PreparedTaskWorkspace,
92
- PrepareTaskWorkspace,
93
- WorkspaceSession,
94
- WorkspaceSessionMode,
95
- } from "./workspaceSession";
96
- export type {
97
- PreparedGitTaskWorkspace,
98
- } from "./taskWorkspace";
99
105
  export type {
100
106
  LocalToolPolicyDecision,
101
107
  } from "./localToolPolicy";
@@ -0,0 +1,23 @@
1
+ export type LocalDialogReadResult = {
2
+ meta: any;
3
+ msgs: any[];
4
+ };
5
+
6
+ export async function readDialogFromLocalDb(args: {
7
+ dialogKey: string;
8
+ dialogId: string;
9
+ limit: number;
10
+ }): Promise<LocalDialogReadResult> {
11
+ const [{ default: serverDb, ensureServerDbOpen }, { fetchMessages }] = await Promise.all([
12
+ import("../database/server/db"),
13
+ import("../chat/messages/fetchMessages"),
14
+ ]);
15
+ await ensureServerDbOpen();
16
+ return {
17
+ meta: await serverDb.get(args.dialogKey),
18
+ msgs: await fetchMessages(serverDb, args.dialogId, {
19
+ limit: args.limit,
20
+ throwOnError: true,
21
+ }),
22
+ };
23
+ }
@@ -38,7 +38,6 @@ const DEFAULT_LOCAL_TOOLS = new Set([
38
38
  "editFile",
39
39
  "searchFiles",
40
40
  "searchWorkspace",
41
- "execShell",
42
41
  ]);
43
42
 
44
43
  function parseToolAllowlist(value: string | undefined) {
@@ -66,10 +65,6 @@ function parseShellCommandPayload(rawArguments: string) {
66
65
  }
67
66
  }
68
67
 
69
- function isLocalShellModeEnabled(env: EnvLike) {
70
- return ["worktree", "dangerous", "1", "true"].includes(env.NOLO_LOCAL_SHELL_MODE || "");
71
- }
72
-
73
68
  function isRestrictedLocalToolMode(env: EnvLike) {
74
69
  return env.NOLO_LOCAL_TOOL_MODE === "restricted";
75
70
  }
@@ -101,13 +96,13 @@ export function resolveLocalToolPolicy(args: {
101
96
  }
102
97
 
103
98
  if (toolName === "execShell") {
104
- if (isLocalShellModeEnabled(args.env)) {
99
+ if (args.env.NOLO_LOCAL_SHELL_MODE === "worktree") {
105
100
  return { allowed: true, toolName };
106
101
  }
107
102
  return {
108
103
  allowed: false,
109
104
  toolName,
110
- reason: "execShell requires NOLO_LOCAL_SHELL_MODE=worktree.",
105
+ reason: "execShell requires NOLO_LOCAL_SHELL_MODE=worktree for local runtime runs.",
111
106
  };
112
107
  }
113
108
 
@@ -20,6 +20,7 @@ type WorkspaceFileArgs = {
20
20
  newText?: unknown;
21
21
  expectedReplacements?: unknown;
22
22
  query?: unknown;
23
+ includeIgnored?: unknown;
23
24
  command?: unknown;
24
25
  cmd?: unknown;
25
26
  branch?: unknown;
@@ -188,6 +189,11 @@ function buildSearchWorkspaceTool(): OpenAiCompatibleTool {
188
189
  description: "Search query or regular expression.",
189
190
  },
190
191
  path: buildWorkspacePathProperty(),
192
+ includeIgnored: {
193
+ type: "boolean",
194
+ description:
195
+ "When true, search files ignored by .gitignore such as .tmp. Defaults to false; .git and node_modules remain excluded.",
196
+ },
191
197
  },
192
198
  required: ["query"],
193
199
  },
@@ -913,6 +919,7 @@ async function searchWorkspaceTool(args: {
913
919
  const requestedPath = typeof parsed.path === "string" && parsed.path.trim()
914
920
  ? parsed.path.trim()
915
921
  : ".";
922
+ const includeIgnored = parsed.includeIgnored === true;
916
923
  const searchPath = resolveLocalWorkspaceToolPath({
917
924
  workspaceRoot: args.workspaceRoot,
918
925
  requestedPath,
@@ -930,6 +937,7 @@ async function searchWorkspaceTool(args: {
930
937
  "--line-number",
931
938
  "--no-heading",
932
939
  "--hidden",
940
+ ...(includeIgnored ? ["--no-ignore"] : []),
933
941
  "--glob",
934
942
  "!node_modules",
935
943
  "--glob",
@@ -959,6 +967,7 @@ async function searchWorkspaceTool(args: {
959
967
  metadata: {
960
968
  query,
961
969
  path: requestedPath,
970
+ includeIgnored,
962
971
  exitCode: result.exitCode,
963
972
  },
964
973
  };
@@ -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
+ }
@@ -139,7 +139,6 @@ export function resolveLocalRuntimeEnvFromPolicy(
139
139
  policy.shell?.mode === "worktree";
140
140
  return {
141
141
  ...runtimeEnv,
142
- ...(policy?.workspace?.cwd ? { NOLO_LOCAL_WORKTREE: policy.workspace.cwd } : {}),
143
142
  ...(enablesWorktreeShell && !runtimeEnv.NOLO_LOCAL_SHELL_MODE
144
143
  ? { NOLO_LOCAL_SHELL_MODE: "worktree" }
145
144
  : {}),
@@ -161,8 +160,7 @@ function policyRequestsHostedWorkspace(policy: AgentRuntimeToolPolicy | undefine
161
160
  return (
162
161
  policy.shell?.enabled === true ||
163
162
  policy.runtimeTools?.some((tool) => tool === "execShell") ||
164
- workspaceMode === "lease" ||
165
- workspaceMode === "task-worktree"
163
+ workspaceMode === "lease"
166
164
  );
167
165
  }
168
166
 
@@ -81,7 +81,7 @@ export type AgentRuntimeDecisionInput = {
81
81
  serverFallbackAvailable: boolean;
82
82
  };
83
83
 
84
- export type AgentRuntimeWorkspaceMode = "none" | "current" | "task-worktree" | "lease";
84
+ export type AgentRuntimeWorkspaceMode = "none" | "current" | "lease";
85
85
 
86
86
  export type AgentRuntimeShellPolicy = {
87
87
  enabled?: boolean;
package/agentAliases.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export const PLATFORM_DEMO_USER_ID = "b2e06f801f";
2
+ export const NOLO_DEFAULT_AGENT_ID = "01NOLOAPPBLD000000019KCKT0";
3
+ export const NOLO_DEFAULT_AGENT_KEY = `agent-pub-${NOLO_DEFAULT_AGENT_ID}`;
2
4
  export const NOLO_PROJECT_MANAGER_AGENT_ID = "01NOLOPROJMGR00000000MSVGG";
3
5
  export const NOLO_PROJECT_MANAGER_AGENT_KEY =
4
6
  `agent-${PLATFORM_DEMO_USER_ID}-${NOLO_PROJECT_MANAGER_AGENT_ID}`;
@@ -19,6 +21,10 @@ export const MIMO_MONTH_AGENT_KEY =
19
21
  `agent-0e95801d90-${MIMO_MONTH_AGENT_ID}`;
20
22
 
21
23
  const AGENT_ALIAS_TO_KEY: Record<string, string> = {
24
+ nolo: NOLO_DEFAULT_AGENT_KEY,
25
+ default: NOLO_DEFAULT_AGENT_KEY,
26
+ "default-nolo": NOLO_DEFAULT_AGENT_KEY,
27
+
22
28
  // Win Codex (高智力终审与架构把关)
23
29
  "win-codex": WIN_CODEX_AGENT_KEY,
24
30
  "wincodex": WIN_CODEX_AGENT_KEY,