@vibe-lark/larkpal 0.1.77 → 0.1.78

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 (2) hide show
  1. package/dist/main.mjs +153 -105
  2. package/package.json +1 -1
package/dist/main.mjs CHANGED
@@ -251,88 +251,98 @@ function getWorkspaceChatHistoryRoot(workspaceRoot = resolveWorkspaceRoot()) {
251
251
  * 以及确保会话工作目录结构完整。
252
252
  */
253
253
  const logger$10 = larkLogger("config/defaults");
254
- const DEFAULT_SETTINGS = {
255
- permissions: {
256
- allow: [
257
- "Bash(*)",
258
- "Read(*)",
259
- "Write(*)",
260
- "Edit(*)",
261
- "MultiEdit(*)",
262
- "Glob(*)",
263
- "Grep(*)",
264
- "LS(*)",
265
- "TodoWrite(*)",
266
- "WebFetch(*)",
267
- "WebSearch(*)"
268
- ],
269
- deny: [
270
- "Write(//.claude/CLAUDE.md)",
271
- "Edit(//.claude/CLAUDE.md)",
272
- "Write(//.claude/settings.json)",
273
- "Edit(//.claude/settings.json)",
274
- "Read(//.lark-cli/**)",
275
- "Read(//.config/lark/**)",
276
- "Read(//.larkpal/credentials.json)",
277
- "Read(//.env)",
278
- "Read(//.env.*)",
279
- "Bash(cat ~/.lark-cli:*)",
280
- "Bash(cat ~/.config/lark:*)",
281
- "Bash(cat ~/.larkpal/credentials:*)",
282
- "Bash(head ~/.lark-cli:*)",
283
- "Bash(head ~/.config/lark:*)",
284
- "Bash(tail ~/.lark-cli:*)",
285
- "Bash(tail ~/.config/lark:*)",
286
- "Bash(less ~/.lark-cli:*)",
287
- "Bash(less ~/.config/lark:*)",
288
- "Bash(more ~/.lark-cli:*)",
289
- "Bash(more ~/.config/lark:*)",
290
- "Bash(env:*)",
291
- "Bash(printenv:*)",
292
- "Bash(export -p:*)",
293
- "Bash(echo $LARK_APP_SECRET:*)",
294
- "Bash(echo $ANTHROPIC_API_KEY:*)"
295
- ]
296
- },
297
- hooks: {
298
- SessionStart: [{ hooks: [{
299
- type: "http",
300
- url: "http://localhost:3000/hooks/session-start",
301
- timeout: 5
302
- }] }],
303
- Stop: [{ hooks: [{
304
- type: "http",
305
- url: "http://localhost:3000/hooks/stop",
306
- timeout: 5
307
- }] }],
308
- PreToolUse: [{
309
- matcher: "Read",
310
- hooks: [{
254
+ function createDefaultSettings() {
255
+ const hookBaseUrl = getClaudeHookBaseUrl();
256
+ return {
257
+ permissions: {
258
+ allow: [
259
+ "Bash(*)",
260
+ "Read(*)",
261
+ "Write(*)",
262
+ "Edit(*)",
263
+ "MultiEdit(*)",
264
+ "Glob(*)",
265
+ "Grep(*)",
266
+ "LS(*)",
267
+ "TodoWrite(*)",
268
+ "WebFetch(*)",
269
+ "WebSearch(*)"
270
+ ],
271
+ deny: [
272
+ "Write(//.claude/CLAUDE.md)",
273
+ "Edit(//.claude/CLAUDE.md)",
274
+ "Write(//.claude/settings.json)",
275
+ "Edit(//.claude/settings.json)",
276
+ "Read(//.lark-cli/**)",
277
+ "Read(//.config/lark/**)",
278
+ "Read(//.larkpal/credentials.json)",
279
+ "Read(//.env)",
280
+ "Read(//.env.*)",
281
+ "Bash(cat ~/.lark-cli:*)",
282
+ "Bash(cat ~/.config/lark:*)",
283
+ "Bash(cat ~/.larkpal/credentials:*)",
284
+ "Bash(head ~/.lark-cli:*)",
285
+ "Bash(head ~/.config/lark:*)",
286
+ "Bash(tail ~/.lark-cli:*)",
287
+ "Bash(tail ~/.config/lark:*)",
288
+ "Bash(less ~/.lark-cli:*)",
289
+ "Bash(less ~/.config/lark:*)",
290
+ "Bash(more ~/.lark-cli:*)",
291
+ "Bash(more ~/.config/lark:*)",
292
+ "Bash(env:*)",
293
+ "Bash(printenv:*)",
294
+ "Bash(export -p:*)",
295
+ "Bash(echo $LARK_APP_SECRET:*)",
296
+ "Bash(echo $ANTHROPIC_API_KEY:*)"
297
+ ]
298
+ },
299
+ hooks: {
300
+ SessionStart: [{ hooks: [{
311
301
  type: "http",
312
- url: "http://localhost:3000/hooks/pre-tool-use",
302
+ url: `${hookBaseUrl}/hooks/session-start`,
313
303
  timeout: 5
314
- }]
315
- }],
316
- PostToolUse: [{
317
- matcher: "Read",
318
- hooks: [{
304
+ }] }],
305
+ Stop: [{ hooks: [{
319
306
  type: "http",
320
- url: "http://localhost:3000/hooks/post-tool-use",
307
+ url: `${hookBaseUrl}/hooks/stop`,
321
308
  timeout: 5
322
- }]
323
- }],
324
- SessionEnd: [{ hooks: [{
325
- type: "http",
326
- url: "http://localhost:3000/hooks/session-end",
327
- timeout: 5
328
- }] }],
329
- Notification: [{ hooks: [{
330
- type: "http",
331
- url: "http://localhost:3000/hooks/notification",
332
- timeout: 5
333
- }] }]
334
- }
335
- };
309
+ }] }],
310
+ PreToolUse: [{
311
+ matcher: "Read",
312
+ hooks: [{
313
+ type: "http",
314
+ url: `${hookBaseUrl}/hooks/pre-tool-use`,
315
+ timeout: 5
316
+ }]
317
+ }],
318
+ PostToolUse: [{
319
+ matcher: "Read",
320
+ hooks: [{
321
+ type: "http",
322
+ url: `${hookBaseUrl}/hooks/post-tool-use`,
323
+ timeout: 5
324
+ }]
325
+ }],
326
+ SessionEnd: [{ hooks: [{
327
+ type: "http",
328
+ url: `${hookBaseUrl}/hooks/session-end`,
329
+ timeout: 5
330
+ }] }],
331
+ Notification: [{ hooks: [{
332
+ type: "http",
333
+ url: `${hookBaseUrl}/hooks/notification`,
334
+ timeout: 5
335
+ }] }]
336
+ }
337
+ };
338
+ }
339
+ function getClaudeHookBaseUrl() {
340
+ const explicit = process.env.LARKPAL_CC_HOOK_BASE_URL?.trim();
341
+ if (explicit) return explicit.replace(/\/+$/, "");
342
+ const port = process.env.LARKPAL_GATEWAY_PORT || "3000";
343
+ const rawHost = process.env.LARKPAL_GATEWAY_HOST || "localhost";
344
+ return `http://${rawHost === "0.0.0.0" || rawHost === "::" ? "127.0.0.1" : rawHost}:${port}`;
345
+ }
336
346
  const DEFAULT_CLAUDE_MD = `# LarkPal
337
347
 
338
348
  你是一个运行在飞书中的 AI 协作伙伴。这是你的初始状态。
@@ -430,7 +440,7 @@ async function ensureDefaults() {
430
440
  const claudeMdPath = join(claudeDir, "CLAUDE.md");
431
441
  await mkdir(claudeDir, { recursive: true });
432
442
  logger$10.info("确保 ~/.claude 目录存在", { path: claudeDir });
433
- await writeFile(settingsPath, JSON.stringify(DEFAULT_SETTINGS, null, 2), "utf-8");
443
+ await writeFile(settingsPath, JSON.stringify(createDefaultSettings(), null, 2), "utf-8");
434
444
  logger$10.info("settings.json 已同步(强制覆盖)", { path: settingsPath });
435
445
  if (await fileExists(claudeMdPath)) logger$10.info("CLAUDE.md 已存在,跳过生成", { path: claudeMdPath });
436
446
  else {
@@ -878,7 +888,7 @@ var CCStreamParser = class extends EventEmitter {
878
888
  toolUseId: block?.id
879
889
  });
880
890
  if (block?.type === "tool_use" && block.name) {
881
- this.recordToolUse(block.name, block.id);
891
+ if (!this.recordToolUse(block.name, block.id, block.input)) break;
882
892
  log$37.info("工具调用开始", {
883
893
  toolName: block.name,
884
894
  displayName: getToolDisplayName(block.name),
@@ -926,7 +936,7 @@ var CCStreamParser = class extends EventEmitter {
926
936
  });
927
937
  if (Array.isArray(content)) {
928
938
  for (const block of content) if (block.type === "tool_use" && block.name) {
929
- this.recordToolUse(block.name, block.id);
939
+ if (!this.recordToolUse(block.name, block.id, block.input)) continue;
930
940
  log$37.info("从 assistant 消息提取工具调用", {
931
941
  toolName: block.name,
932
942
  displayName: getToolDisplayName(block.name),
@@ -960,15 +970,19 @@ var CCStreamParser = class extends EventEmitter {
960
970
  if (msg.subtype !== "init" || !Array.isArray(msg.tools)) return;
961
971
  this.declaredTools = new Set(msg.tools);
962
972
  }
963
- recordToolUse(toolName, toolUseId) {
973
+ recordToolUse(toolName, toolUseId, toolInput) {
964
974
  if (toolUseId) this.toolNamesById.set(toolUseId, toolName);
965
975
  if (this.declaredTools && !this.declaredTools.has(toolName)) {
966
- log$37.warn("模型请求了未声明工具", {
976
+ const availableTools = [...this.declaredTools];
977
+ log$37.warn("模型请求了未声明工具,已抑制正常 toolUseStart", {
967
978
  toolName,
968
- toolUseId
979
+ toolUseId,
980
+ availableTools
969
981
  });
970
- this.emit("unknownToolUse", toolName, toolUseId);
982
+ this.emit("unknownToolUse", toolName, toolUseId, toolInput, availableTools);
983
+ return false;
971
984
  }
985
+ return true;
972
986
  }
973
987
  buildToolResultSummary(item, toolUseResult) {
974
988
  const serializedContent = serializeToolResultContent(item.content);
@@ -1133,30 +1147,64 @@ async function sanitizeTranscriptEntry(entry, artifactRoot) {
1133
1147
  let artifacts = 0;
1134
1148
  const message = asObject$1(entry.message);
1135
1149
  const content = message ? message.content : void 0;
1136
- if (!Array.isArray(content)) return {
1150
+ const toolUseResult = asObject$1(entry.toolUseResult);
1151
+ let firstArtifactUri;
1152
+ if (Array.isArray(content)) for (let i = 0; i < content.length; i++) {
1153
+ const block = asObject$1(content[i]);
1154
+ if (!block) continue;
1155
+ if (block.type === "tool_result") {
1156
+ const toolUseId = typeof block.tool_use_id === "string" ? block.tool_use_id : void 0;
1157
+ const replacement = await sanitizeToolResultContent(block.content, artifactRoot, toolUseId);
1158
+ if (!replacement) continue;
1159
+ block.content = replacement.content;
1160
+ sanitizedImageBlocks += replacement.imageBlocks;
1161
+ artifacts += replacement.artifacts;
1162
+ firstArtifactUri ??= replacement.metadata[0]?.artifactUri;
1163
+ continue;
1164
+ }
1165
+ if (isBase64ImageBlock(block)) {
1166
+ const artifact = await writeImageArtifact(artifactRoot, block, void 0, i);
1167
+ content[i] = {
1168
+ type: "text",
1169
+ text: buildReplacementText([artifact])
1170
+ };
1171
+ sanitizedImageBlocks++;
1172
+ artifacts++;
1173
+ firstArtifactUri ??= artifact.artifactUri;
1174
+ }
1175
+ }
1176
+ const toolUseResultReplacement = await sanitizeToolUseResult(toolUseResult, artifactRoot, firstArtifactUri);
1177
+ if (toolUseResultReplacement) {
1178
+ sanitizedImageBlocks += toolUseResultReplacement.sanitizedImageBlocks;
1179
+ artifacts += toolUseResultReplacement.artifacts;
1180
+ }
1181
+ return {
1137
1182
  sanitizedImageBlocks,
1138
1183
  artifacts
1139
1184
  };
1140
- for (const block of content) {
1141
- const toolResult = asObject$1(block);
1142
- if (!toolResult || toolResult.type !== "tool_result") continue;
1143
- const toolUseId = typeof toolResult.tool_use_id === "string" ? toolResult.tool_use_id : void 0;
1144
- const replacement = await sanitizeToolResultContent(toolResult.content, artifactRoot, toolUseId);
1145
- if (!replacement) continue;
1146
- toolResult.content = replacement.content;
1147
- sanitizedImageBlocks += replacement.imageBlocks;
1148
- artifacts += replacement.artifacts;
1149
- const toolUseResult = asObject$1(entry.toolUseResult);
1150
- if (toolUseResult?.type === "image") {
1151
- const file = asObject$1(toolUseResult.file);
1152
- if (file && typeof file.base64 === "string") {
1153
- file.artifactUri = replacement.metadata[0]?.artifactUri;
1154
- file.base64 = "[redacted by LarkPal: image artifact externalized]";
1155
- }
1156
- }
1185
+ }
1186
+ async function sanitizeToolUseResult(toolUseResult, artifactRoot, existingArtifactUri) {
1187
+ if (toolUseResult?.type !== "image") return null;
1188
+ const file = asObject$1(toolUseResult.file);
1189
+ if (!file || typeof file.base64 !== "string") return null;
1190
+ let artifactUri = existingArtifactUri;
1191
+ let artifacts = 0;
1192
+ if (!artifactUri) {
1193
+ artifactUri = (await writeImageArtifact(artifactRoot, {
1194
+ type: "image",
1195
+ source: {
1196
+ type: "base64",
1197
+ media_type: typeof file.type === "string" ? file.type : void 0,
1198
+ data: file.base64
1199
+ },
1200
+ filePath: typeof file.filePath === "string" ? file.filePath : void 0
1201
+ }, void 0, 0)).artifactUri;
1202
+ artifacts = 1;
1157
1203
  }
1204
+ file.artifactUri = artifactUri;
1205
+ file.base64 = "[redacted by LarkPal: image artifact externalized]";
1158
1206
  return {
1159
- sanitizedImageBlocks,
1207
+ sanitizedImageBlocks: 1,
1160
1208
  artifacts
1161
1209
  };
1162
1210
  }
@@ -1235,7 +1283,7 @@ function formatDimensions(block) {
1235
1283
  return `${width}x${height}`;
1236
1284
  }
1237
1285
  function mightContainImagePayload(line) {
1238
- return line.includes("\"tool_result\"") && line.includes("\"type\":\"image\"") && line.includes("\"base64\"");
1286
+ return line.includes("\"base64\"") && (line.includes("\"image\"") || line.includes("\"source\""));
1239
1287
  }
1240
1288
  function asObject$1(value) {
1241
1289
  return typeof value === "object" && value != null ? value : void 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-lark/larkpal",
3
- "version": "0.1.77",
3
+ "version": "0.1.78",
4
4
  "description": "LarkPal - Lark/Feishu bot service",
5
5
  "type": "module",
6
6
  "main": "./dist/main.mjs",