nolo-cli 0.1.41 → 0.1.43

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 (136) hide show
  1. package/README.md +27 -12
  2. package/agent-runtime/agentThread.ts +222 -0
  3. package/agent-runtime/agentThreadAdmission.ts +74 -0
  4. package/agent-runtime/dialogWritePlan.ts +42 -0
  5. package/agent-runtime/hostAdapter.ts +1 -0
  6. package/agent-runtime/index.ts +43 -0
  7. package/agent-runtime/localLoop.ts +31 -2
  8. package/agent-runtime/localToolPolicy.ts +1 -0
  9. package/agent-runtime/localWorkspaceTools.ts +1258 -78
  10. package/agent-runtime/noloWorkspaceTools.ts +20 -1
  11. package/agent-runtime/runtimeToolPolicy.ts +1 -5
  12. package/agent-runtime/runtimeToolSurface.ts +1 -0
  13. package/agentAliases.ts +29 -9
  14. package/agentInternalCommandEntries.ts +11 -3
  15. package/agentMachineCommands.ts +1 -0
  16. package/agentRecordCommands.ts +2 -2
  17. package/agentRecordHelpers.ts +59 -6
  18. package/agentRunCommand.ts +84 -19
  19. package/ai/agent/buildSystemPrompt.ts +14 -0
  20. package/ai/agent/cliExecutor.ts +202 -43
  21. package/ai/agent/createAgentSchema.ts +2 -2
  22. package/ai/agent/liveAudioModel.ts +1 -1
  23. package/ai/agent/streamAgentChatTurn.ts +154 -20
  24. package/ai/agent/streamAgentChatTurnUtils.ts +63 -0
  25. package/ai/ai.locale.ts +70 -0
  26. package/ai/chat/inlineImageUrlsForCustomProvider.ts +7 -2
  27. package/ai/chat/sendOpenAICompletionsRequest.ts +25 -1
  28. package/ai/llm/kimi.ts +0 -1
  29. package/ai/llm/openrouterModels.ts +27 -2
  30. package/ai/memory/policy.ts +49 -0
  31. package/ai/memory/queryShared.ts +10 -4
  32. package/ai/memory/recentRelationshipRecap.ts +4 -0
  33. package/ai/memory/remember.ts +48 -104
  34. package/ai/memory/scope.ts +74 -0
  35. package/ai/memory/types.ts +6 -0
  36. package/ai/memory/understanding.ts +10 -18
  37. package/ai/policy/runtimePolicy.ts +95 -23
  38. package/ai/tools/agent/agentTools.ts +0 -21
  39. package/ai/tools/agent/startAgentDialogTool.ts +2 -1
  40. package/ai/tools/index.ts +2 -20
  41. package/ai/tools/prepareTools.ts +171 -1
  42. package/ai/tools/readXhsProfileTool.ts +80 -69
  43. package/ai/tools/table/createTableTool.ts +11 -0
  44. package/ai/tools/toolPacks.ts +19 -0
  45. package/ai/types.ts +3 -0
  46. package/app/favorite/deletedFavoriteProjection.ts +31 -0
  47. package/app/hooks/deleteDbKey.ts +24 -3
  48. package/app/i18n/translations/interface.locale.ts +48 -16
  49. package/app/types/appSummary.ts +4 -0
  50. package/app/utils/desktopAgentRuntimeTurnClient.ts +2 -0
  51. package/auth/adminPermissions.ts +64 -4
  52. package/auth/routes.ts +5 -0
  53. package/chat/chat.locale.ts +4 -0
  54. package/chat/dialog/actions/handleSendMessageAction.ts +7 -2
  55. package/chat/dialog/dialogAttachmentCleanup.ts +135 -0
  56. package/chat/dialog/dialogSlice.ts +117 -4
  57. package/chat/messages/activityCompletion.ts +97 -0
  58. package/chat/messages/imagePayloadPersistence.ts +22 -0
  59. package/chat/messages/messageContent.ts +20 -16
  60. package/chat/messages/messageSlice.ts +20 -1
  61. package/chat/messages/toolThunks.ts +32 -11
  62. package/chat/messages/types.ts +8 -1
  63. package/chat/messages/web/toolDisplayName.ts +686 -0
  64. package/cli/agentAliases.ts +29 -9
  65. package/cli/agentInternalCommandEntries.ts +11 -3
  66. package/cli/agentMachineCommands.ts +1 -0
  67. package/cli/agentRecordCommands.ts +2 -2
  68. package/cli/agentRecordHelpers.ts +59 -6
  69. package/cli/agentRunCommand.ts +84 -19
  70. package/cli/client/agentRun.ts +65 -253
  71. package/cli/client/localRuntimeAdapter.ts +179 -53
  72. package/cli/commandRegistry.ts +7 -4
  73. package/cli/dialogAttachmentCleanup.ts +151 -0
  74. package/cli/dialogCommands.ts +163 -61
  75. package/cli/dialogInternalCommandEntries.ts +1 -1
  76. package/cli/docCreateCommands.ts +345 -0
  77. package/cli/docInternalCommandEntries.ts +11 -0
  78. package/cli/docPageHelpers.ts +80 -0
  79. package/cli/docSkillHelpers.ts +67 -0
  80. package/cli/docSpaceHelpers.ts +46 -0
  81. package/cli/internalCommandEntries.ts +4 -0
  82. package/cli/machineCommands.ts +18 -34
  83. package/cli/memoryCommands.ts +253 -0
  84. package/cli/memoryInternalCommandEntries.ts +9 -0
  85. package/cli/qoderUsageProbe.ts +252 -0
  86. package/cli/scriptCommandEntries.ts +0 -3
  87. package/cli/secretScan.ts +31 -0
  88. package/cli/tableCommands.ts +248 -13
  89. package/cli/workflowInternalCommandEntries.ts +2 -3
  90. package/client/agentRun.test.ts +68 -84
  91. package/client/agentRun.ts +65 -253
  92. package/client/localRuntimeAdapter.test.ts +505 -74
  93. package/client/localRuntimeAdapter.ts +179 -53
  94. package/commandRegistry.ts +7 -4
  95. package/connector-experimental/capabilities.ts +10 -9
  96. package/create/space/sidebarVisibleTypes.ts +19 -0
  97. package/dialogAttachmentCleanup.ts +151 -0
  98. package/dialogCommands.ts +163 -61
  99. package/dialogInternalCommandEntries.ts +1 -1
  100. package/docCreateCommands.ts +345 -0
  101. package/docInternalCommandEntries.ts +11 -0
  102. package/docPageHelpers.ts +80 -0
  103. package/docSkillHelpers.ts +67 -0
  104. package/docSpaceHelpers.ts +46 -0
  105. package/integrations/google/models.ts +4 -4
  106. package/integrations/openai/generateOpenAIRequestBody.ts +0 -3
  107. package/integrations/xhs-reader/types.ts +36 -3
  108. package/integrations/xhs-reader/url.ts +24 -1
  109. package/internalCommandEntries.ts +4 -0
  110. package/machineCommands.ts +18 -34
  111. package/memoryCommands.ts +253 -0
  112. package/memoryInternalCommandEntries.ts +9 -0
  113. package/package.json +141 -114
  114. package/qoderUsageProbe.ts +252 -0
  115. package/render/contentIcon/types.ts +167 -0
  116. package/render/page/docSlice.ts +30 -3
  117. package/render/page/types.ts +2 -0
  118. package/render/table/createTableAction.ts +4 -0
  119. package/render/table/tableSlice.ts +61 -0
  120. package/render/table/types.ts +10 -0
  121. package/scriptCommandEntries.ts +0 -3
  122. package/secretScan.ts +31 -0
  123. package/server/handlers/agentRun/types.ts +24 -23
  124. package/tableCommands.ts +248 -13
  125. package/workflowInternalCommandEntries.ts +2 -3
  126. package/ai/tools/agent/taskRunTool.ts +0 -112
  127. package/cli/client/taskRunPrompt.ts +0 -27
  128. package/cli/taskRunCommand.ts +0 -265
  129. package/client/taskRunPrompt.ts +0 -27
  130. package/integrations/xhs-reader/analyze/profileAnalyzer.ts +0 -205
  131. package/integrations/xhs-reader/backends/playwrightProfileCollector.ts +0 -624
  132. package/integrations/xhs-reader/bridge/readXhsProfileWithBridge.ts +0 -227
  133. package/integrations/xhs-reader/client/xhsWebApiClient.ts +0 -229
  134. package/integrations/xhs-reader/normalize.ts +0 -390
  135. package/integrations/xhs-reader/orchestrator.ts +0 -178
  136. package/taskRunCommand.ts +0 -265
@@ -1,3 +1,4 @@
1
+ import i18n from "../../app/i18n";
1
2
  import {
2
3
  toolRegistry,
3
4
  toolDefinitionsByName,
@@ -7,6 +8,167 @@ import {
7
8
  import { canonicalizeToolNames } from "./toolNameAliases";
8
9
  import { sanitizeToolForProvider } from "./toolSchemaCompatibility";
9
10
 
11
+ function getTranslation(keys: string[], defaultVal: string): string {
12
+ for (const key of keys) {
13
+ if (i18n.exists(key)) {
14
+ return i18n.t(key);
15
+ }
16
+ const aiKey = key.startsWith("ai:") ? key : `ai:${key}`;
17
+ if (i18n.exists(aiKey)) {
18
+ return i18n.t(aiKey);
19
+ }
20
+ }
21
+ return defaultVal;
22
+ }
23
+
24
+ export function translateSchema(schema: any): any {
25
+ if (!schema || typeof schema !== "object") return schema;
26
+
27
+ const nextSchema = JSON.parse(JSON.stringify(schema));
28
+ const toolName = nextSchema.name;
29
+ if (!toolName) return nextSchema;
30
+
31
+ const walk = (node: any, path: string) => {
32
+ if (!node || typeof node !== "object") return;
33
+
34
+ if (typeof node.description === "string") {
35
+ const keysToTry = [
36
+ `${path}.description`,
37
+ `${path}.desc`,
38
+ path,
39
+ node.description,
40
+ ];
41
+ node.description = getTranslation(keysToTry, node.description);
42
+ }
43
+
44
+ if (node.parameters && typeof node.parameters === "object") {
45
+ walk(node.parameters, path);
46
+ }
47
+
48
+ if (node.properties && typeof node.properties === "object") {
49
+ for (const [key, child] of Object.entries(node.properties)) {
50
+ const nextPath = path.endsWith(".params") ? `${path}.${key}` : `${path}.params.${key}`;
51
+ walk(child, nextPath);
52
+ }
53
+ }
54
+
55
+ if (node.items && typeof node.items === "object") {
56
+ walk(node.items, path);
57
+ }
58
+
59
+ const compositions = ["anyOf", "oneOf", "allOf"] as const;
60
+ for (const comp of compositions) {
61
+ if (Array.isArray(node[comp])) {
62
+ node[comp].forEach((child: any, idx: number) => {
63
+ const suffix = child.type === "string" ? "stringDesc" : child.type === "object" ? "objectDesc" : `${idx}`;
64
+ walk(child, `${path}.${suffix}`);
65
+ });
66
+ }
67
+ }
68
+ };
69
+
70
+ walk(nextSchema, `tools.${toolName}`);
71
+ return nextSchema;
72
+ }
73
+
74
+ function buildActivitySchema() {
75
+ const refs = {
76
+ type: "array",
77
+ items: {
78
+ type: "object",
79
+ properties: {
80
+ type: { type: "string", enum: ["file", "terminal", "url"] },
81
+ path: { type: "string" },
82
+ id: { type: "string" },
83
+ label: { type: "string" },
84
+ url: { type: "string" },
85
+ },
86
+ },
87
+ };
88
+ const action = {
89
+ type: "object",
90
+ properties: {
91
+ title: { type: "string" },
92
+ kind: {
93
+ type: "string",
94
+ enum: ["read", "write", "edit", "search", "terminal", "version", "test", "build", "preview", "other"],
95
+ },
96
+ detail: { type: "string" },
97
+ refs,
98
+ },
99
+ required: ["title"],
100
+ };
101
+ const planPhase = {
102
+ type: "object",
103
+ properties: {
104
+ id: { type: "string" },
105
+ title: { type: "string" },
106
+ index: { type: "number" },
107
+ status: { type: "string", enum: ["pending", "running", "success", "failed"] },
108
+ },
109
+ required: ["id", "title"],
110
+ };
111
+ return {
112
+ type: "object",
113
+ description:
114
+ "Optional human-readable activity metadata for UI progress display only. Use plan for the visible task skeleton, phase for the current task step, and action for this specific tool call.",
115
+ properties: {
116
+ plan: {
117
+ type: "object",
118
+ properties: {
119
+ title: { type: "string" },
120
+ phases: {
121
+ type: "array",
122
+ items: planPhase,
123
+ },
124
+ },
125
+ required: ["phases"],
126
+ },
127
+ phase: {
128
+ type: "object",
129
+ properties: {
130
+ id: { type: "string" },
131
+ title: { type: "string" },
132
+ index: { type: "number" },
133
+ total: { type: "number" },
134
+ status: { type: "string", enum: ["pending", "running", "success", "failed"] },
135
+ },
136
+ required: ["id", "title"],
137
+ },
138
+ action,
139
+ title: { type: "string" },
140
+ kind: action.properties.kind,
141
+ detail: { type: "string" },
142
+ refs,
143
+ },
144
+ };
145
+ }
146
+
147
+ function addActivityMetadataToToolSchema(tool: any) {
148
+ if (!tool?.function?.parameters || typeof tool.function.parameters !== "object") {
149
+ return tool;
150
+ }
151
+ const parameters = tool.function.parameters;
152
+ if (parameters.type !== "object" || !parameters.properties || typeof parameters.properties !== "object") {
153
+ return tool;
154
+ }
155
+ if (parameters.properties._activity) return tool;
156
+
157
+ return {
158
+ ...tool,
159
+ function: {
160
+ ...tool.function,
161
+ parameters: {
162
+ ...parameters,
163
+ properties: {
164
+ ...parameters.properties,
165
+ _activity: buildActivitySchema(),
166
+ },
167
+ },
168
+ },
169
+ };
170
+ }
171
+
10
172
  export const prepareTools = (
11
173
  toolNames: string[],
12
174
  options?: { provider?: string; disabledToolNames?: string[] }
@@ -17,7 +179,15 @@ export const prepareTools = (
17
179
  );
18
180
  return canonicalizeToolNames(toolNames)
19
181
  .filter((toolName) => !disabledToolNames.has(toolName))
20
- .map((toolName: string) => toolRegistry[toolName])
182
+ .map((toolName: string) => {
183
+ const regTool = toolRegistry[toolName];
184
+ if (!regTool) return regTool;
185
+ return {
186
+ ...regTool,
187
+ function: translateSchema(regTool.function),
188
+ };
189
+ })
190
+ .map(addActivityMetadataToToolSchema)
21
191
  .map((tool) => sanitizeToolForProvider(tool, options?.provider))
22
192
  .filter(Boolean); // 过滤掉未找到的工具
23
193
  };
@@ -2,13 +2,14 @@
2
2
  * read_xhs_profile Tool
3
3
  *
4
4
  * Read-only tool for collecting XHS (Xiaohongshu) profile data:
5
- * profile info, public notes, note details, and top-level comments.
5
+ * anonymous public profile info and public notes visible without login.
6
6
  *
7
- * Desktop-first: uses Playwright persistent context for login state reuse.
7
+ * Anonymous-only: does not use XHS login state, cookies, or persistent profile.
8
8
  * No write actions (no like/comment/follow/collect/post/delete).
9
9
  * Cookies and xsecToken are never exposed in output.
10
10
  * Conservative by default: reads the initially loaded profile state only.
11
- * Extra scrolling, detail enrichment, and comments require explicit user intent.
11
+ * Extra scrolling requires explicit user intent. Direct detail/comment API
12
+ * enrichment is disabled so reads stay close to visible desktop browsing.
12
13
  * The tool clamps extended collection unless extendedCollectionConsent is true.
13
14
  */
14
15
 
@@ -17,21 +18,23 @@ import type {
17
18
  XhsReadResult,
18
19
  XhsProfileCollection,
19
20
  XhsCollectionStatus,
21
+ XhsParsedProfileUrl,
20
22
  } from "../../integrations/xhs-reader/types";
23
+ import { redactXhsSensitiveValue } from "../../integrations/xhs-reader/redaction";
21
24
  import { parseXhsProfileUrl } from "../../integrations/xhs-reader/url";
22
25
  import { getRequestConfig, ToolApiError } from "./toolApiClient";
23
26
 
24
27
  export const readXhsProfileFunctionSchema = {
25
28
  name: "read_xhs_profile",
26
29
  description:
27
- "读取小红书用户主页的公开信息。默认只读取页面初始加载出的资料和笔记列表,不自动翻页、不默认抓评论或批量详情。只有当用户明确要求更多笔记、评论或互动明细时,才应显式设置对应参数。默认通过桌面本地 Playwright 持久 profile 读取,不要求用户粘贴 cookie。",
30
+ "以未登录匿名访客身份读取小红书用户主页的公开可见信息。默认只读取页面初始加载出的资料和笔记列表,不自动翻页,不抓评论或批量详情。只有当用户明确要求更多笔记/翻页时,才可显式请求一次可见页面滚动。不会使用账号、cookie 或持久浏览器 profile。",
28
31
  parameters: {
29
32
  type: "object",
30
33
  properties: {
31
34
  url: {
32
35
  type: "string",
33
36
  description:
34
- "小红书用户主页 URL,例如 https://www.xiaohongshu.com/user/profile/5d2be8720000000010007556。",
37
+ "小红书用户主页 URL,例如 https://www.xiaohongshu.com/user/profile/5d2be8720000000010007556。用户从首页/feed 复制的 profile 链接可以带 xsec_token/xsec_source;token 只用于匿名桌面导航,输出会脱敏。",
35
38
  },
36
39
  maxScrollPages: {
37
40
  type: "number",
@@ -42,29 +45,29 @@ export const readXhsProfileFunctionSchema = {
42
45
  includeComments: {
43
46
  type: "boolean",
44
47
  description:
45
- "是否收集笔记的顶层评论。默认 false;只有用户明确要求评论且 extendedCollectionConsent=true 时才设为 true。",
48
+ "是否读取匿名公开笔记详情页首屏可见评论。只有 extendedCollectionConsent=true、collectionMode=\"assisted\"、assistedAction=\"read_visible_details\" 时才会生效;不会通过直接 API 收集评论。",
46
49
  default: false,
47
50
  },
48
51
  maxCommentPagesPerNote: {
49
52
  type: "number",
50
53
  description:
51
- "每篇笔记最多收集几页评论(仅当 includeComments=true 时生效)。默认 1。",
54
+ "保留兼容字段,当前固定为 1 且不会触发评论采集。",
52
55
  default: 1,
53
56
  },
54
57
  minLikesForDetail: {
55
58
  type: "number",
56
59
  description:
57
- "仅收集点赞数 >= 此值的笔记详情(可选)。只有 extendedCollectionConsent=true 时生效。",
60
+ "已停用:不再通过直接 API 收集笔记详情。",
58
61
  },
59
62
  minCommentsForCollect: {
60
63
  type: "number",
61
64
  description:
62
- "仅收集评论数 >= 此值的笔记的评论(可选,仅当 includeComments=true 且 extendedCollectionConsent=true 时生效)。",
65
+ "已停用:不再通过直接 API 收集评论。",
63
66
  },
64
67
  extendedCollectionConsent: {
65
68
  type: "boolean",
66
69
  description:
67
- "扩展采集确认开关。只有用户在本轮明确要求更多笔记、翻页、评论或互动明细时才可设为 true;否则必须省略或为 false。",
70
+ "扩展采集确认开关。只有用户在本轮明确要求更多笔记、翻页、公开笔记详情或首屏评论时才可设为 true;否则必须省略或为 false。匿名模式不会采集登录后内容。",
68
71
  default: false,
69
72
  },
70
73
  collectionMode: {
@@ -76,34 +79,29 @@ export const readXhsProfileFunctionSchema = {
76
79
  },
77
80
  assistedAction: {
78
81
  type: "string",
79
- enum: ["snapshot", "read_more_notes", "read_comments"],
82
+ enum: ["snapshot", "read_more_notes", "read_visible_details", "discover_indexed_notes"],
80
83
  description:
81
- "assisted 模式下的具体动作:snapshot 只读快照,read_more_notes 翻页读更多笔记,read_comments 读评论。",
84
+ "assisted 模式下的具体动作:snapshot 只读快照,read_more_notes 最多做一次可见页面滚动,read_visible_details 匿名打开最多 3 条公开笔记页读取页面可见详情和首屏评论,discover_indexed_notes 读取外部搜索索引发现的公开笔记 URL。",
82
85
  default: "snapshot",
83
86
  },
87
+ indexedNoteUrls: {
88
+ type: "array",
89
+ items: { type: "string" },
90
+ description:
91
+ "外部搜索索引发现的公开小红书笔记 URL(xiaohongshu.com/explore/...)。仅当 assistedAction=\"discover_indexed_notes\" 且 extendedCollectionConsent=true 时生效,最多读取 3 条;不要填搜索页 URL。",
92
+ },
84
93
  maxAssistedSteps: {
85
94
  type: "number",
86
95
  description:
87
- "assisted 模式最大步数(1..3)。默认 1,用于约束滚动页数或评论翻页数。",
96
+ "assisted 模式最大步数。当前固定为 1,只允许一次可见页面滚动。",
88
97
  default: 1,
89
98
  minimum: 1,
90
- maximum: 3,
91
- },
92
- profileDir: {
93
- type: "string",
94
- description:
95
- "桌面端本地 Playwright 持久 profile 目录,用于复用小红书登录态。不要传入用户日常 Chrome profile。默认使用专用缓存目录。",
99
+ maximum: 1,
96
100
  },
97
101
  headless: {
98
102
  type: "boolean",
99
103
  description:
100
- "是否用 headless 模式启动浏览器。首次登录小红书时需设为 false 并配合 keepOpen。",
101
- default: true,
102
- },
103
- keepOpen: {
104
- type: "boolean",
105
- description:
106
- "调试时是否保持浏览器上下文打开(方便手动登录)。默认 false。",
104
+ "是否用 headless 模式启动匿名浏览器。默认 false,优先打开可见桌面浏览器。",
107
105
  default: false,
108
106
  },
109
107
  },
@@ -125,12 +123,11 @@ export interface ReadXhsProfileArgs {
125
123
  minLikesForDetail?: number;
126
124
  minCommentsForCollect?: number;
127
125
  extendedCollectionConsent?: boolean;
128
- profileDir?: string;
129
126
  headless?: boolean;
130
- keepOpen?: boolean;
131
127
  collectionMode?: "conservative" | "assisted";
132
- assistedAction?: "snapshot" | "read_more_notes" | "read_comments";
128
+ assistedAction?: "snapshot" | "read_more_notes" | "read_visible_details" | "discover_indexed_notes";
133
129
  maxAssistedSteps?: number;
130
+ indexedNoteUrls?: string[];
134
131
  }
135
132
 
136
133
  export function normalizeXhsProfileReadArgs(
@@ -157,7 +154,7 @@ export function normalizeXhsProfileReadArgs(
157
154
  // Consent granted → determine mode
158
155
  const mode = args.collectionMode === "assisted" ? "assisted" : "conservative";
159
156
  const action = args.assistedAction ?? "snapshot";
160
- const steps = clamp(args.maxAssistedSteps ?? 1, 1, 3);
157
+ const steps = 1;
161
158
 
162
159
  if (mode === "conservative") {
163
160
  // Consent alone is not enough for extended collection. The model must
@@ -184,10 +181,14 @@ export function normalizeXhsProfileReadArgs(
184
181
  if (action === "read_more_notes") {
185
182
  maxScrollPages = clamp(maxScrollPages ?? steps, 1, steps);
186
183
  includeComments = false;
187
- } else if (action === "read_comments") {
184
+ } else if (action === "read_visible_details") {
185
+ maxScrollPages = 0;
188
186
  includeComments = true;
189
- maxCommentPagesPerNote = clamp(maxCommentPagesPerNote ?? steps, 1, steps);
187
+ maxCommentPagesPerNote = 3;
188
+ } else if (action === "discover_indexed_notes") {
190
189
  maxScrollPages = 0;
190
+ includeComments = true;
191
+ maxCommentPagesPerNote = 3;
191
192
  } else {
192
193
  // snapshot: no scrolling, no comments
193
194
  maxScrollPages = 0;
@@ -200,12 +201,15 @@ export function normalizeXhsProfileReadArgs(
200
201
  maxScrollPages,
201
202
  includeComments,
202
203
  maxCommentPagesPerNote,
203
- minLikesForDetail: args.minLikesForDetail,
204
- minCommentsForCollect: args.minCommentsForCollect,
204
+ minLikesForDetail: undefined,
205
+ minCommentsForCollect: undefined,
205
206
  extendedCollectionConsent: true,
206
207
  collectionMode: mode,
207
208
  assistedAction: action,
208
209
  maxAssistedSteps: steps,
210
+ indexedNoteUrls: action === "discover_indexed_notes"
211
+ ? (args.indexedNoteUrls ?? []).slice(0, 3)
212
+ : undefined,
209
213
  };
210
214
  }
211
215
 
@@ -213,10 +217,10 @@ function clamp(value: number, min: number, max: number): number {
213
217
  return Math.min(Math.max(value, min), max);
214
218
  }
215
219
 
216
- function assertXhsProfileUrl(url: string) {
220
+ function assertXhsProfileUrl(url: string): XhsParsedProfileUrl {
217
221
  // Reuse parseXhsProfileUrl for validation
218
222
  try {
219
- parseXhsProfileUrl(url);
223
+ return parseXhsProfileUrl(url);
220
224
  } catch {
221
225
  throw new Error(
222
226
  "read_xhs_profile 需要一个有效的小红书用户主页 URL(xiaohongshu.com/user/profile/<24位hexID>)。",
@@ -370,15 +374,15 @@ function formatDisplay(result: XhsReadResult<XhsProfileCollection>) {
370
374
  const hints: string[] = [];
371
375
  if (result.code === "login_required") {
372
376
  hints.push(
373
- "需要先登录小红书。请在桌面端用 headless=false + keepOpen=true 运行一次,手动完成登录后再次调用即可。",
377
+ "匿名公开访问遇到登录墙:当前用户主页对未登录访客不可见,read_xhs_profile 不会请求登录或复用账号。",
374
378
  );
375
379
  } else if (result.code === "blocked") {
376
380
  hints.push(
377
- "页面被验证码或安全检查拦截。请稍后重试或在桌面端手动处理验证后重试。",
381
+ "匿名公开访问被安全检查拦截。请稍后重试,或接受匿名模式下无法读取该页面。",
378
382
  );
379
383
  } else if (result.code === "empty_profile_state") {
380
384
  hints.push(
381
- "页面加载但未获取到笔记数据。建议:确认该用户有公开笔记;用本地持久 profile 重试;增加 timeoutMs。",
385
+ "页面加载但未获取到笔记数据。建议:确认该用户有公开笔记;如果页面需要登录,匿名模式会停止而不是切换账号。",
382
386
  );
383
387
  }
384
388
  return [
@@ -387,8 +391,8 @@ function formatDisplay(result: XhsReadResult<XhsProfileCollection>) {
387
391
  "",
388
392
  "可能的解决步骤:",
389
393
  "- 确认 URL 是有效的小红书用户主页链接",
390
- "- 如果是登录问题,请用 headless=false + keepOpen=true 手动登录一次",
391
- "- 如果是服务端(headless)环境,确保有有效的已登录 profile 目录",
394
+ "- 如果页面只对登录用户可见,接受匿名模式无法读取该内容",
395
+ "- 如果是临时网络或页面加载问题,可以稍后重试或增加 timeoutMs",
392
396
  "- 检查 Playwright chromium 是否已安装",
393
397
  ...hints,
394
398
  ]
@@ -424,6 +428,11 @@ function formatDisplay(result: XhsReadResult<XhsProfileCollection>) {
424
428
  if (noteDetails.length > 0) {
425
429
  parts.push(`已获取详情: ${noteDetails.length} 篇`);
426
430
  }
431
+ if (result.data.indexedDiscovery) {
432
+ parts.push(
433
+ `外部索引公开笔记: ${result.data.indexedDiscovery.verifiedNoteUrls.length}/${result.data.indexedDiscovery.acceptedNoteUrls.length} 已校验(${result.data.indexedDiscovery.requestedNoteUrls.length} 个候选)`,
434
+ );
435
+ }
427
436
 
428
437
  // Top notes
429
438
  if (analysis.highestLikedNote) {
@@ -477,7 +486,8 @@ function formatDisplay(result: XhsReadResult<XhsProfileCollection>) {
477
486
  const actionLabels: Record<string, string> = {
478
487
  snapshot: "快照",
479
488
  read_more_notes: "读取更多笔记",
480
- read_comments: "读取评论",
489
+ read_visible_details: "读取公开详情与首屏评论",
490
+ discover_indexed_notes: "读取外部索引公开笔记",
481
491
  };
482
492
  parts.push(`采集模式: ${modeLabel}`);
483
493
  parts.push(`采集动作: ${actionLabels[cs.action] ?? cs.action}`);
@@ -505,27 +515,27 @@ export async function readXhsProfileFunc(
505
515
  displayData: string;
506
516
  }> {
507
517
  const url = String(args?.url ?? "").trim();
508
- assertXhsProfileUrl(url);
518
+ const parsedUrl = assertXhsProfileUrl(url);
509
519
 
510
520
  const readerArgs: ReadXhsProfileArgs = normalizeXhsProfileReadArgs({
511
- url,
521
+ url: parsedUrl.navigationUrl,
512
522
  maxScrollPages: args.maxScrollPages,
513
523
  includeComments: args.includeComments,
514
524
  maxCommentPagesPerNote: args.maxCommentPagesPerNote,
515
525
  minLikesForDetail: args.minLikesForDetail,
516
526
  minCommentsForCollect: args.minCommentsForCollect,
517
527
  extendedCollectionConsent: args.extendedCollectionConsent,
518
- profileDir: args.profileDir?.trim() || undefined,
519
528
  headless: args.headless,
520
- keepOpen: args.keepOpen,
521
529
  collectionMode: args.collectionMode,
522
530
  assistedAction: args.assistedAction,
523
531
  maxAssistedSteps: args.maxAssistedSteps,
532
+ indexedNoteUrls: args.indexedNoteUrls,
524
533
  });
525
534
 
526
- const rawData =
535
+ const rawDataUnredacted =
527
536
  (await context.reader?.(readerArgs)) ??
528
537
  (await readWithDefaultBridge(readerArgs, thunkApi));
538
+ const rawData = redactXhsSensitiveValue(rawDataUnredacted) as XhsReadResult<XhsProfileCollection>;
529
539
 
530
540
  // Build and attach collectionStatus
531
541
  if (rawData.ok) {
@@ -573,15 +583,28 @@ function computeNextSuggestedAction(
573
583
  args: ReadXhsProfileArgs,
574
584
  data: XhsProfileCollection,
575
585
  ): XhsCollectionStatus["nextSuggestedAction"] {
576
- const { diagnostic, notes, analysis, commentsByNote } = data;
586
+ const { diagnostic, notes, analysis } = data;
577
587
  const action = args.assistedAction ?? "snapshot";
578
588
 
579
- // Login wall or empty state → suggest manual login
589
+ if (
590
+ (diagnostic?.loginDetected ||
591
+ diagnostic?.code === "login_required" ||
592
+ (diagnostic?.code === "empty_profile_state" && notes.length === 0)) &&
593
+ action !== "discover_indexed_notes"
594
+ ) {
595
+ return {
596
+ action: "discover_indexed_notes",
597
+ label: "外部索引找公开笔记",
598
+ reason: "站内匿名搜索不可用;可用 Google 等外部索引查找公开 note URL 后再读取可见页面",
599
+ };
600
+ }
601
+
602
+ // Login wall or empty state after indexed discovery → stop; anonymous mode must not request login.
580
603
  if (diagnostic?.loginDetected || diagnostic?.code === "login_required") {
581
604
  return {
582
- action: "manual_login",
583
- label: "手动登录",
584
- reason: "检测到登录提示,请先手动登录小红书",
605
+ action: "stop_anonymous_unavailable",
606
+ label: "匿名不可见",
607
+ reason: "检测到登录提示;匿名公开模式不会登录或复用账号",
585
608
  };
586
609
  }
587
610
 
@@ -594,28 +617,16 @@ function computeNextSuggestedAction(
594
617
  };
595
618
  }
596
619
 
597
- // Notes with high comment counts but no comments collected
598
- const hasHighCommentNotes = notes.some((n) => {
599
- // Check noteDetails for comment counts
600
- const detail = data.noteDetails.find((d) => d.noteId === n.noteId);
601
- return detail && detail.metrics.commentCount > 5;
602
- });
603
- const hasComments = Object.keys(commentsByNote).length > 0;
604
-
605
- if (
606
- (action === "snapshot" || action === "read_more_notes") &&
607
- hasHighCommentNotes &&
608
- !hasComments
609
- ) {
620
+ if (notes.length > 0 && action === "snapshot") {
610
621
  return {
611
- action: "read_comments",
612
- label: "读取评论",
613
- reason: "部分笔记评论较多,可以读取评论内容",
622
+ action: "read_visible_details",
623
+ label: "读取公开详情与首屏评论",
624
+ reason: "当前已有公开笔记列表,可匿名打开公开笔记页读取页面可见详情",
614
625
  };
615
626
  }
616
627
 
617
628
  // Analysis exists and has data → suggest saving to table
618
- if (analysis.totalNotes > 0 && (action === "read_comments" || action === "read_more_notes")) {
629
+ if (analysis.totalNotes > 0 && action === "read_more_notes") {
619
630
  return {
620
631
  action: "save_to_table",
621
632
  label: "保存到表格",
@@ -24,6 +24,7 @@ interface ToolTableColumnInput {
24
24
  type CreateTableToolArgs = {
25
25
  spaceId?: string;
26
26
  title?: string;
27
+ purpose?: string;
27
28
  description?: string;
28
29
  tags?: string[];
29
30
  categoryId?: string;
@@ -83,6 +84,11 @@ export const createTableFunctionSchema = {
83
84
  description:
84
85
  "表的显示名称,可以使用中文,例如「任务清单」「客户管理」。",
85
86
  },
87
+ purpose: {
88
+ type: "string",
89
+ description:
90
+ "可选:机器可读用途,用于之后在当前用户/Space 范围内查找这类表,例如 agent_eval_workbench。不要用它写自然语言说明。",
91
+ },
86
92
  description: {
87
93
  type: "string",
88
94
  description:
@@ -184,6 +190,10 @@ export async function createTableFunc(
184
190
  typeof args?.title === "string" && args.title.trim()
185
191
  ? args.title.trim()
186
192
  : undefined;
193
+ const purpose =
194
+ typeof args?.purpose === "string" && args.purpose.trim()
195
+ ? args.purpose.trim()
196
+ : undefined;
187
197
  const description =
188
198
  typeof args?.description === "string" && args.description.trim()
189
199
  ? args.description.trim()
@@ -254,6 +264,7 @@ export async function createTableFunc(
254
264
  const createArgs: any = {
255
265
  spaceId,
256
266
  title,
267
+ purpose,
257
268
  description,
258
269
  tags,
259
270
  categoryId,
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 能力分级定义 (Capability Tiers)
3
+ */
4
+ export const TOOL_PACKS = {
5
+ // L1 - 核心:交互 + 记忆读写 + 自我更新,所有 Agent 必有
6
+ CORE: ["ui_ask_choice", "rememberMemory", "read", "searchDialogMessages", "createDoc", "updateDoc", "search_workspace", "search_all_spaces", "updateSelf", "queryModelUsage", "createDialogGoal", "getDialogGoal", "completeDialogGoal", "createScheduledTask", "notifyUser"],
7
+ // L2 - 联网搜索:配置了 web-capable tools 的 Agent 默认加,纯 QA bot 不加
8
+ LIGHT_WEB: ["exa_search", "read_x_post", "read_xhs_profile"],
9
+ // L3 - 深度浏览器:全套复杂网页交互
10
+ FULL_BROWSER: [
11
+ "browser_openSession",
12
+ "browser_closeSession",
13
+ "browser_click",
14
+ "browser_typeText",
15
+ "browser_readContent",
16
+ "browser_selectOption",
17
+ "fetchWebpage",
18
+ ],
19
+ } as const;
package/ai/types.ts CHANGED
@@ -44,6 +44,9 @@ export interface Contexts {
44
44
  /** 用户偏好与自动化边界(tone / capture / read policy) */
45
45
  userPolicyContext?: string | null;
46
46
 
47
+ /** 长期记忆召回层,由服务端 memory API 基于当前用户、space、agent 和输入生成 */
48
+ memoryOverlay?: string | null;
49
+
47
50
  /** 对话增量摘要:已压缩的历史消息概要 */
48
51
  dialogSummary?: string | null;
49
52
 
@@ -0,0 +1,31 @@
1
+ import {
2
+ isAgentKey,
3
+ isFileKey,
4
+ isPageKey,
5
+ isTableMetaKey,
6
+ } from "../../database/keys";
7
+
8
+ type DeletedFavoriteProjectionRemoval = {
9
+ targetType: "agent" | "content";
10
+ id: string;
11
+ };
12
+
13
+ export const resolveDeletedFavoriteProjectionRemoval = (
14
+ contentKey: string
15
+ ): DeletedFavoriteProjectionRemoval | null => {
16
+ if (!contentKey) return null;
17
+
18
+ if (isAgentKey(contentKey)) {
19
+ return { targetType: "agent", id: contentKey };
20
+ }
21
+
22
+ if (
23
+ isPageKey(contentKey) ||
24
+ isTableMetaKey(contentKey) ||
25
+ isFileKey(contentKey)
26
+ ) {
27
+ return { targetType: "content", id: contentKey };
28
+ }
29
+
30
+ return null;
31
+ };