larkway 0.3.10 → 0.3.12

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.
package/README.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  You @ the bot in a Feishu thread. It runs on your machine — reading your real codebase, executing commands, opening MRs — and posts the result back. You define what the agent knows and what it can do. Larkway just carries the messages.
10
10
 
11
- **Current release: v0.3.10**
11
+ **Current release: v0.3.12**
12
12
 
13
13
  ---
14
14
 
package/README.zh.md CHANGED
@@ -8,7 +8,7 @@
8
8
 
9
9
  你在飞书话题里 @ bot,它在你的机器上运行——读真实代码库、执行命令、开 MR——把结果贴回飞书。你定义 agent 知道什么、能做什么。Larkway 只负责传递消息。
10
10
 
11
- **当前版本:v0.3.10**
11
+ **当前版本:v0.3.12**
12
12
 
13
13
  ---
14
14
 
package/dist/cli/index.js CHANGED
@@ -107597,6 +107597,10 @@ function stringField(obj, key) {
107597
107597
  const value = obj[key];
107598
107598
  return typeof value === "string" ? value : void 0;
107599
107599
  }
107600
+ function nonEmptyStringField(obj, key) {
107601
+ const value = stringField(obj, key);
107602
+ return value && value.length > 0 ? value : void 0;
107603
+ }
107600
107604
  function parseLarkCliMessages(stdout2) {
107601
107605
  const parsed = JSON.parse(stdout2);
107602
107606
  if (Array.isArray(parsed)) return parsed;
@@ -107610,6 +107614,25 @@ function parseLarkCliMessages(stdout2) {
107610
107614
  }
107611
107615
  return null;
107612
107616
  }
107617
+ function expandMessagesWithThreadReplies(messages) {
107618
+ const expanded = [];
107619
+ for (const raw of messages) {
107620
+ expanded.push(raw);
107621
+ if (!raw || typeof raw !== "object") continue;
107622
+ const parent = raw;
107623
+ const parentRootId = nonEmptyStringField(parent, "root_id") ?? nonEmptyStringField(parent, "message_id");
107624
+ const replies = arrayField(parent, "thread_replies") ?? [];
107625
+ for (const replyRaw of replies) {
107626
+ if (!replyRaw || typeof replyRaw !== "object") continue;
107627
+ const reply = replyRaw;
107628
+ expanded.push({
107629
+ ...reply,
107630
+ root_id: nonEmptyStringField(reply, "root_id") ?? parentRootId
107631
+ });
107632
+ }
107633
+ }
107634
+ return expanded;
107635
+ }
107613
107636
  function cardActionChoice(value) {
107614
107637
  if (value && typeof value === "object") {
107615
107638
  const choice = value["larkway_choice"];
@@ -107942,8 +107965,9 @@ var init_channelClient = __esm({
107942
107965
  log(`gap-fill: failed to parse lark-cli output for chat ${chatId}`);
107943
107966
  continue;
107944
107967
  }
107945
- totalFetched += messages.length;
107946
- for (const raw of messages) {
107968
+ const messagesWithReplies = expandMessagesWithThreadReplies(messages);
107969
+ totalFetched += messagesWithReplies.length;
107970
+ for (const raw of messagesWithReplies) {
107947
107971
  if (this.closed) break;
107948
107972
  const m = raw;
107949
107973
  const messageId = m["message_id"];
@@ -120457,6 +120481,7 @@ function renderAgentsMd(input) {
120457
120481
  "",
120458
120482
  "## Workspace Contract",
120459
120483
  "",
120484
+ "- \u5F00\u573A\u4E0D\u53EF\u8DF3\u8FC7:\u56DE\u5E94 owner \u524D,\u5148 Read `memory/index.md`,\u5E76\u6309\u76F8\u5173\u6027 Read \u76F8\u5173 category \u6587\u4EF6,\u518D\u5F00\u59CB\u5E72\u6D3B(\u9632\u6B62\u65B0 session \u5931\u5FC6)\u3002",
120460
120485
  "- Larkway is a thin Feishu bridge. It passes scene/context pointers; you decide what to inspect and what work to do.",
120461
120486
  "- Each Feishu topic is one task session under `sessions/<thread_id>/`.",
120462
120487
  "- Keep durable notes, repo clones, session summaries, and permission decisions inside this workspace.",
@@ -120776,6 +120801,75 @@ async function ensureAgentWorkspace(input) {
120776
120801
  path7.join(input.workspacePath, "permissions-granted.md"),
120777
120802
  renderPermissionsGranted(input)
120778
120803
  );
120804
+ await ensureMemoryScaffold(input.workspacePath);
120805
+ }
120806
+ var MEMORY_CATEGORY_FILES = [
120807
+ {
120808
+ name: "preferences.md",
120809
+ title: "Owner Preferences",
120810
+ purpose: "owner \u6216\u56E2\u961F\u957F\u671F\u504F\u597D(\u6C47\u62A5\u683C\u5F0F\u3001\u9ED8\u8BA4\u8BED\u8A00\u3001\u9A8C\u8BC1\u504F\u597D\u7B49)\u3002"
120811
+ },
120812
+ {
120813
+ name: "reusable-knowledge.md",
120814
+ title: "Reusable Knowledge",
120815
+ purpose: "\u591A\u4E2A session \u540E\u6C89\u6DC0\u51FA\u7684\u53EF\u590D\u7528\u7ECF\u9A8C\u3001\u5E38\u89C1\u5751\u3001\u65B9\u6848\u5224\u65AD\u3002"
120816
+ },
120817
+ {
120818
+ name: "workflows.md",
120819
+ title: "Workflows",
120820
+ purpose: "\u8FD9\u4E2A Agent \u81EA\u5DF1\u7684\u957F\u671F\u5DE5\u4F5C\u65B9\u5F0F(\u9879\u76EE repo \u7684\u5DE5\u7A0B\u89C4\u8303\u4ECD\u5199\u8FDB\u9879\u76EE repo)\u3002"
120821
+ },
120822
+ {
120823
+ name: "decisions.md",
120824
+ title: "Decisions",
120825
+ purpose: "\u957F\u671F\u51B3\u7B56\u8BB0\u5F55,\u4F8B\u5982\u4E3A\u4EC0\u4E48\u67D0\u6743\u9650\u5FC5\u987B\u4EBA\u5DE5\u786E\u8BA4\u3002"
120826
+ },
120827
+ {
120828
+ name: "assets.md",
120829
+ title: "Assets",
120830
+ purpose: "\u957F\u671F\u56FE\u7247/\u622A\u56FE/\u9644\u4EF6\u7684\u7D22\u5F15,\u53EA\u8BB0\u5F15\u7528\u548C\u7528\u9014,\u4E0D\u5185\u8054\u5927\u6587\u4EF6(\u5B9E\u4F53\u653E assets/)\u3002"
120831
+ }
120832
+ ];
120833
+ function renderMemoryIndex() {
120834
+ return [
120835
+ "# Memory Index",
120836
+ "",
120837
+ "\u8FD9\u662F\u8DE8 session \u957F\u671F\u8BB0\u5FC6\u3002\u65B0 session \u8D77\u624B\u5148\u8BFB\u8FD9\u91CC,\u628A\u8FC7\u5F80\u79EF\u7D2F\u62C9\u8D77\u6765\u3002",
120838
+ "Larkway \u53EA\u521D\u59CB\u5316\u7A7A\u5BB9\u5668;\u63D0\u70BC\u3001\u5206\u7C7B\u3001\u5199\u5165\u6B63\u6587\u90FD\u7531\u4F60(Agent)\u5728 owner \u786E\u8BA4\u540E\u5B8C\u6210\u3002",
120839
+ "",
120840
+ "## \u5206\u7C7B\u6587\u4EF6",
120841
+ "",
120842
+ ...MEMORY_CATEGORY_FILES.map((f) => `- \`${f.name}\` \u2014 ${f.purpose}`),
120843
+ "",
120844
+ "## \u7EF4\u62A4\u89C4\u5219",
120845
+ "",
120846
+ "- \u70ED\u8DEF\u5F84(\u6BCF\u8F6E)\u53EA\u505A\u52A0\u6CD5:\u628A\u5019\u9009\u5199\u8FDB session \u7684 `memory-candidates.md`,\u6216\u5F80 category \u6587\u4EF6\u8FFD\u52A0\u65B0\u6761\u76EE;\u4E0D\u5728\u70ED\u8DEF\u5F84\u6539\u5199/\u5220\u9664\u5DF2\u6709\u6761\u76EE\u3002",
120847
+ "- \u6539\u5199\u3001\u5220\u9664\u3001\u89E3\u51B3\u51B2\u7A81 \u2192 \u63A8\u8FDF\u5230 owner \u663E\u5F0F\u8BF4\u300C\u6574\u7406\u8BB0\u5FC6\u300D\u65F6\u79BB\u7EBF\u505A\u3002UPDATE/DELETE \u7684\u65E7\u6761\u76EE\u79FB `archive/`(\u6CE8\u4E00\u53E5\u539F\u56E0+\u5BF9\u5E94 commit),\u4E0D\u624B\u5199 superseded \u6233,\u4E0D\u7269\u7406\u5220;archive/ \u7684\u957F\u671F\u6E05\u7406\u4EA4\u7ED9 git history\u3002",
120848
+ "- \u88C1\u51B3\u4EE5 source \u4F18\u5148:user \u4EB2\u53E3\u8BF4\u7684 >> agent \u63A8\u65AD;\u51B2\u7A81\u65F6\u4FDD\u7559\u65E7\u7684 user \u6761\u76EE,\u628A\u65B0\u63A8\u65AD\u964D\u7EA7\u4E3A candidate\u3002\u540C source \u5185 recency \u4EE5 git \u5386\u53F2\u4E3A\u51C6,\u4E0D\u624B\u5199\u65E5\u671F\u6233\u3002",
120849
+ "- \u53EA\u6709\u8DE8 session \u8FD8\u4F1A\u7528\u5230\u7684\u624D\u8FDB\u8FD9\u91CC(\u5355\u6B21\u4EFB\u52A1\u7559\u5728 session summary)\u3002\u672C\u6587\u4EF6\u4FDD\u6301\u7B80\u77ED(\u2264~50 \u884C),\u4E0D\u653E changelog/\u7EDF\u8BA1\u3002",
120850
+ ""
120851
+ ].join("\n");
120852
+ }
120853
+ function renderMemoryCategorySkeleton(title) {
120854
+ return [
120855
+ `# ${title}`,
120856
+ "",
120857
+ "\u5199\u524D\u5148\u8BFB\u672C\u6587\u4EF6;\u6709\u76F8\u540C/\u76F8\u5173\u6761\u76EE\u5C31 NOOP \u4E0D\u91CD\u590D\u5199(\u6539\u5199\u3001\u5220\u9664\u3001\u79FB archive \u7559\u5230 owner \u8BF4\u300C\u6574\u7406\u8BB0\u5FC6\u300D\u65F6\u7684\u79BB\u7EBF\u6B65\u9AA4);\u4FE1\u53F7\u4E0D\u8DE8 session \u590D\u7528\u5C31\u4E0D\u5199\u3002",
120858
+ ""
120859
+ ].join("\n");
120860
+ }
120861
+ async function ensureMemoryScaffold(workspacePath) {
120862
+ const memoryDir = path7.join(workspacePath, "memory");
120863
+ await fs.mkdir(memoryDir, { recursive: true });
120864
+ await fs.mkdir(path7.join(memoryDir, "assets"), { recursive: true });
120865
+ await fs.mkdir(path7.join(memoryDir, "archive"), { recursive: true });
120866
+ await writeIfMissing(path7.join(memoryDir, "index.md"), renderMemoryIndex());
120867
+ for (const file of MEMORY_CATEGORY_FILES) {
120868
+ await writeIfMissing(
120869
+ path7.join(memoryDir, file.name),
120870
+ renderMemoryCategorySkeleton(file.title)
120871
+ );
120872
+ }
120779
120873
  }
120780
120874
 
120781
120875
  // src/cli/commands/init.ts
package/dist/main.js CHANGED
@@ -111823,6 +111823,10 @@ function stringField(obj, key) {
111823
111823
  const value = obj[key];
111824
111824
  return typeof value === "string" ? value : void 0;
111825
111825
  }
111826
+ function nonEmptyStringField(obj, key) {
111827
+ const value = stringField(obj, key);
111828
+ return value && value.length > 0 ? value : void 0;
111829
+ }
111826
111830
  function parseLarkCliMessages(stdout) {
111827
111831
  const parsed = JSON.parse(stdout);
111828
111832
  if (Array.isArray(parsed)) return parsed;
@@ -111836,6 +111840,25 @@ function parseLarkCliMessages(stdout) {
111836
111840
  }
111837
111841
  return null;
111838
111842
  }
111843
+ function expandMessagesWithThreadReplies(messages) {
111844
+ const expanded = [];
111845
+ for (const raw of messages) {
111846
+ expanded.push(raw);
111847
+ if (!raw || typeof raw !== "object") continue;
111848
+ const parent = raw;
111849
+ const parentRootId = nonEmptyStringField(parent, "root_id") ?? nonEmptyStringField(parent, "message_id");
111850
+ const replies = arrayField(parent, "thread_replies") ?? [];
111851
+ for (const replyRaw of replies) {
111852
+ if (!replyRaw || typeof replyRaw !== "object") continue;
111853
+ const reply = replyRaw;
111854
+ expanded.push({
111855
+ ...reply,
111856
+ root_id: nonEmptyStringField(reply, "root_id") ?? parentRootId
111857
+ });
111858
+ }
111859
+ }
111860
+ return expanded;
111861
+ }
111839
111862
  function cardActionChoice(value) {
111840
111863
  if (value && typeof value === "object") {
111841
111864
  const choice = value["larkway_choice"];
@@ -112154,8 +112177,9 @@ var ChannelClient = class {
112154
112177
  log(`gap-fill: failed to parse lark-cli output for chat ${chatId}`);
112155
112178
  continue;
112156
112179
  }
112157
- totalFetched += messages.length;
112158
- for (const raw of messages) {
112180
+ const messagesWithReplies = expandMessagesWithThreadReplies(messages);
112181
+ totalFetched += messagesWithReplies.length;
112182
+ for (const raw of messagesWithReplies) {
112159
112183
  if (this.closed) break;
112160
112184
  const m = raw;
112161
112185
  const messageId = m["message_id"];
@@ -113246,6 +113270,9 @@ function senderOpenIdOf(senderId) {
113246
113270
  return "";
113247
113271
  }
113248
113272
 
113273
+ // src/claude/prompt.ts
113274
+ import { readFileSync as readFileSync2 } from "node:fs";
113275
+
113249
113276
  // src/agent/triggerFacts.ts
113250
113277
  function stringField2(value) {
113251
113278
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -113282,6 +113309,23 @@ function deriveTriggerFacts(parsed, isNewThread, larkCliProfile) {
113282
113309
  }
113283
113310
 
113284
113311
  // src/claude/prompt.ts
113312
+ var MEMORY_CATEGORY_FILE_NAMES = [
113313
+ "preferences.md",
113314
+ "reusable-knowledge.md",
113315
+ "workflows.md",
113316
+ "decisions.md",
113317
+ "assets.md"
113318
+ ];
113319
+ var MEMORY_FILE_LINE_LIMIT = 200;
113320
+ function statMemoryLines(filePath) {
113321
+ try {
113322
+ const text = readFileSync2(filePath, "utf8");
113323
+ if (text.length === 0) return 0;
113324
+ return (text.match(/\n/g)?.length ?? 0) + (text.endsWith("\n") ? 0 : 1);
113325
+ } catch {
113326
+ return 0;
113327
+ }
113328
+ }
113285
113329
  function csv(items) {
113286
113330
  return items.length > 0 ? items.join(",") : "(none)";
113287
113331
  }
@@ -113403,6 +113447,8 @@ function renderWorkspaceBlock(primarySlug, primaryCachePath, defaultBranch, extr
113403
113447
  }
113404
113448
  function renderAgentWorkspaceBlock(conventions, extraRepos) {
113405
113449
  const summaryFilePath = conventions.workspaceSessionPath ? `${conventions.workspaceSessionPath}/summary.md` : void 0;
113450
+ const memoryDir = conventions.agentWorkspacePath ? `${conventions.agentWorkspacePath}/memory` : void 0;
113451
+ const memoryIndex = memoryDir ? `${memoryDir}/index.md` : void 0;
113406
113452
  const lines = [
113407
113453
  "<agent-workspace>",
113408
113454
  "Larkway \u662F thin bridge:\u5B83\u53EA\u628A\u98DE\u4E66\u89E6\u53D1\u573A\u666F\u548C\u672C\u5730\u8DEF\u5F84\u6307\u9488\u4EA4\u7ED9\u4F60,\u4E0D\u66FF\u4F60\u7F16\u6392\u4EFB\u52A1\u3002",
@@ -113411,12 +113457,29 @@ function renderAgentWorkspaceBlock(conventions, extraRepos) {
113411
113457
  `- summary_file_path: ${summaryFilePath ?? "(topic_session_path)/summary.md"}`,
113412
113458
  `- state_file_path: ${conventions.stateFilePath}`,
113413
113459
  `- workspace_repos_dir: ${conventions.workspaceReposPath}`,
113460
+ `- memory_dir: ${memoryDir ?? "(agent_workspace_path)/memory"}`,
113461
+ `- memory_index: ${memoryIndex ?? "(memory_dir)/index.md"}`,
113414
113462
  "- \u4E00\u4E2A\u98DE\u4E66\u8BDD\u9898 = \u4E00\u4E2A task/session\u3002\u8BDD\u9898\u5185\u7EED\u63A5\u65F6,\u7EE7\u7EED\u4F7F\u7528\u540C\u4E00\u4E2A topic_session_path\u3002",
113415
113463
  "- \u7FA4\u91CC @ \u4F60\u65F6,bridge \u4F1A\u62C9\u8D77/\u5173\u8054\u4E00\u4E2A\u8BDD\u9898;\u662F\u5426\u8BFB\u53D6\u7FA4\u5386\u53F2\u3001\u8BDD\u9898\u5386\u53F2\u3001\u9644\u4EF6\u3001\u6587\u6863,\u7531\u4F60\u6839\u636E\u4EFB\u52A1\u81EA\u884C\u51B3\u5B9A\u3002",
113416
113464
  "- \u4E0D\u8981\u5047\u8BBE bridge \u5DF2\u7ECF clone/fetch/worktree/pnpm install;\u9700\u8981\u4EE3\u7801\u65F6,\u4F60\u5728 workspace \u91CC\u81EA\u5DF1 clone/branch/install/test\u3002",
113417
113465
  "- summary.md \u662F\u4F60\u7EF4\u62A4\u672C\u8BDD\u9898\u6458\u8981\u3001\u51B3\u7B56\u548C\u4E0B\u4E00\u6B65 notes \u7684\u5730\u65B9;bridge \u53EA\u521B\u5EFA\u5360\u4F4D,\u4E0D\u66FF\u4F60\u603B\u7ED3\u3002",
113418
- "- \u4F18\u5148\u8BFB\u53D6 workspace \u5185\u7684 AGENTS.md\u3001CLAUDE.md(\u5982\u5B58\u5728)\u3001permissions-request.md\u3001permissions-granted.md\u3002"
113466
+ "- \u8D77\u624B\u5148\u8BFB memory/index.md \u62C9\u8D77\u8DE8 session \u957F\u671F\u8BB0\u5FC6(preferences / reusable-knowledge / workflows / decisions / assets),\u518D\u8BFB workspace \u5185\u7684 AGENTS.md\u3001CLAUDE.md(\u5982\u5B58\u5728)\u3001permissions-request.md\u3001permissions-granted.md\u3002",
113467
+ "- \u672C session \u91CC\u8DE8 session \u53EF\u590D\u7528\u7684\u5185\u5BB9,\u5148\u8BB0\u5230 topic_session_path/memory-candidates.md;owner \u786E\u8BA4\u540E,\u7531\u4F60\u5199\u8FDB memory/<category>.md\u3002",
113468
+ "- \u70ED\u8DEF\u5F84(\u6BCF\u8F6E)\u53EA\u5141\u8BB8 ADD / NOOP:\u628A\u5019\u9009 append \u5230 memory-candidates.md,\u6216\u5F80 category \u6587\u4EF6\u8FFD\u52A0\u65B0\u6761\u76EE;\u4E0D\u5728\u70ED\u8DEF\u5F84\u505A UPDATE/DELETE/\u6539\u5199\u5DF2\u6709\u6761\u76EE\u3002",
113469
+ "- \u6539\u5199\u3001\u5220\u9664\u3001\u89E3\u51B3\u51B2\u7A81 \u2192 \u63A8\u8FDF\u5230 owner \u663E\u5F0F\u8BF4\u300C\u6574\u7406\u8BB0\u5FC6\u300D\u65F6\u7684\u79BB\u7EBF\u6B65\u9AA4\u505A\u3002\u5931\u6548/\u88AB\u63A8\u7FFB\u7684\u6761\u76EE\u79FB memory/archive/(\u6CE8\u4E00\u53E5\u539F\u56E0),\u4E0D\u7269\u7406\u5220\u3002",
113470
+ "- \u79BB\u7EBF\u6574\u7406/\u6539\u5199\u5DF2\u6709\u8BB0\u5FC6\u524D,\u5148\u7528 rg \u5728 sessions/*/transcript.md \u6838\u5230\u6765\u6E90\u884C;commit/\u7B14\u8BB0\u5F15\u7528\u8BE5\u884C;\u6838\u4E0D\u5230\u6765\u6E90\u7684\u7ED3\u8BBA\u964D\u7EA7\u4E3A candidate,\u4E0D\u5199\u8FDB\u6B63\u6587\u3002(\u5355 agent \u81EA\u5DF1\u505A,\u522B spawn \u522B\u7684 agent\u3002)",
113471
+ "- \u53EA\u6709\u8DE8 session \u8FD8\u4F1A\u518D\u7528\u5230\u7684\u624D\u8FDB memory/(\u5355\u6B21\u4EFB\u52A1\u7559\u5728 summary,\u968F session \u8FC7\u671F);\u65B0\u589E\u3001\u4EE5\u53CA\u4F1A\u6539\u53D8\u884C\u4E3A\u6216\u8FB9\u754C\u7684\u6539\u5199/\u5220\u9664\u90FD\u8981 owner \u786E\u8BA4\u3002"
113419
113472
  ];
113473
+ if (memoryDir) {
113474
+ for (const fileName of MEMORY_CATEGORY_FILE_NAMES) {
113475
+ const count = statMemoryLines(`${memoryDir}/${fileName}`);
113476
+ if (count > MEMORY_FILE_LINE_LIMIT) {
113477
+ lines.push(
113478
+ `- \u26A0\uFE0F ${fileName} \u5DF2 ${count} \u884C,\u8D85\u9650\u2014\u2014\u4E0B\u6B21\u300C\u6574\u7406\u8BB0\u5FC6\u300D\u65F6\u5148\u84B8\u998F\u538B\u7F29\u3002`
113479
+ );
113480
+ }
113481
+ }
113482
+ }
113420
113483
  if (conventions.defaultProjectSlug) {
113421
113484
  lines.push("");
113422
113485
  lines.push("Repo pointers(\u53EA\u662F\u6307\u9488,\u4E0D\u662F\u5DF2\u51C6\u5907\u597D\u7684 clone):");
@@ -113693,6 +113756,15 @@ function renderSummaryPlaceholder() {
113693
113756
  ""
113694
113757
  ].join("\n");
113695
113758
  }
113759
+ function renderMemoryCandidatesPlaceholder() {
113760
+ return [
113761
+ "# Memory Candidates",
113762
+ "",
113763
+ "\u672C session \u91CC\u503C\u5F97\u63D0\u5347\u4E3A\u8DE8 session \u957F\u671F\u8BB0\u5FC6\u7684\u5019\u9009,\u8BB0\u5728\u8FD9\u3002",
113764
+ "owner \u786E\u8BA4\u540E,\u7531\u4F60(Agent)\u5199\u8FDB ../../memory/<category>.md\u3002",
113765
+ ""
113766
+ ].join("\n");
113767
+ }
113696
113768
  function indentBlock(value) {
113697
113769
  const trimmed = value.trim();
113698
113770
  if (!trimmed) return " (empty)";
@@ -113748,6 +113820,10 @@ function renderTranscriptEntry(input) {
113748
113820
  async function ensureSessionArtifacts(input) {
113749
113821
  await fs.mkdir(input.sessionPath, { recursive: true });
113750
113822
  await writeIfMissing(path4.join(input.sessionPath, "summary.md"), renderSummaryPlaceholder());
113823
+ await writeIfMissing(
113824
+ path4.join(input.sessionPath, "memory-candidates.md"),
113825
+ renderMemoryCandidatesPlaceholder()
113826
+ );
113751
113827
  await fs.appendFile(
113752
113828
  path4.join(input.sessionPath, "transcript.md"),
113753
113829
  `${renderTranscriptEntry(input)}
@@ -113828,6 +113904,7 @@ function renderAgentsMd(input) {
113828
113904
  "",
113829
113905
  "## Workspace Contract",
113830
113906
  "",
113907
+ "- \u5F00\u573A\u4E0D\u53EF\u8DF3\u8FC7:\u56DE\u5E94 owner \u524D,\u5148 Read `memory/index.md`,\u5E76\u6309\u76F8\u5173\u6027 Read \u76F8\u5173 category \u6587\u4EF6,\u518D\u5F00\u59CB\u5E72\u6D3B(\u9632\u6B62\u65B0 session \u5931\u5FC6)\u3002",
113831
113908
  "- Larkway is a thin Feishu bridge. It passes scene/context pointers; you decide what to inspect and what work to do.",
113832
113909
  "- Each Feishu topic is one task session under `sessions/<thread_id>/`.",
113833
113910
  "- Keep durable notes, repo clones, session summaries, and permission decisions inside this workspace.",
@@ -113994,6 +114071,75 @@ async function ensureAgentWorkspace(input) {
113994
114071
  path5.join(input.workspacePath, "permissions-granted.md"),
113995
114072
  renderPermissionsGranted(input)
113996
114073
  );
114074
+ await ensureMemoryScaffold(input.workspacePath);
114075
+ }
114076
+ var MEMORY_CATEGORY_FILES = [
114077
+ {
114078
+ name: "preferences.md",
114079
+ title: "Owner Preferences",
114080
+ purpose: "owner \u6216\u56E2\u961F\u957F\u671F\u504F\u597D(\u6C47\u62A5\u683C\u5F0F\u3001\u9ED8\u8BA4\u8BED\u8A00\u3001\u9A8C\u8BC1\u504F\u597D\u7B49)\u3002"
114081
+ },
114082
+ {
114083
+ name: "reusable-knowledge.md",
114084
+ title: "Reusable Knowledge",
114085
+ purpose: "\u591A\u4E2A session \u540E\u6C89\u6DC0\u51FA\u7684\u53EF\u590D\u7528\u7ECF\u9A8C\u3001\u5E38\u89C1\u5751\u3001\u65B9\u6848\u5224\u65AD\u3002"
114086
+ },
114087
+ {
114088
+ name: "workflows.md",
114089
+ title: "Workflows",
114090
+ purpose: "\u8FD9\u4E2A Agent \u81EA\u5DF1\u7684\u957F\u671F\u5DE5\u4F5C\u65B9\u5F0F(\u9879\u76EE repo \u7684\u5DE5\u7A0B\u89C4\u8303\u4ECD\u5199\u8FDB\u9879\u76EE repo)\u3002"
114091
+ },
114092
+ {
114093
+ name: "decisions.md",
114094
+ title: "Decisions",
114095
+ purpose: "\u957F\u671F\u51B3\u7B56\u8BB0\u5F55,\u4F8B\u5982\u4E3A\u4EC0\u4E48\u67D0\u6743\u9650\u5FC5\u987B\u4EBA\u5DE5\u786E\u8BA4\u3002"
114096
+ },
114097
+ {
114098
+ name: "assets.md",
114099
+ title: "Assets",
114100
+ purpose: "\u957F\u671F\u56FE\u7247/\u622A\u56FE/\u9644\u4EF6\u7684\u7D22\u5F15,\u53EA\u8BB0\u5F15\u7528\u548C\u7528\u9014,\u4E0D\u5185\u8054\u5927\u6587\u4EF6(\u5B9E\u4F53\u653E assets/)\u3002"
114101
+ }
114102
+ ];
114103
+ function renderMemoryIndex() {
114104
+ return [
114105
+ "# Memory Index",
114106
+ "",
114107
+ "\u8FD9\u662F\u8DE8 session \u957F\u671F\u8BB0\u5FC6\u3002\u65B0 session \u8D77\u624B\u5148\u8BFB\u8FD9\u91CC,\u628A\u8FC7\u5F80\u79EF\u7D2F\u62C9\u8D77\u6765\u3002",
114108
+ "Larkway \u53EA\u521D\u59CB\u5316\u7A7A\u5BB9\u5668;\u63D0\u70BC\u3001\u5206\u7C7B\u3001\u5199\u5165\u6B63\u6587\u90FD\u7531\u4F60(Agent)\u5728 owner \u786E\u8BA4\u540E\u5B8C\u6210\u3002",
114109
+ "",
114110
+ "## \u5206\u7C7B\u6587\u4EF6",
114111
+ "",
114112
+ ...MEMORY_CATEGORY_FILES.map((f) => `- \`${f.name}\` \u2014 ${f.purpose}`),
114113
+ "",
114114
+ "## \u7EF4\u62A4\u89C4\u5219",
114115
+ "",
114116
+ "- \u70ED\u8DEF\u5F84(\u6BCF\u8F6E)\u53EA\u505A\u52A0\u6CD5:\u628A\u5019\u9009\u5199\u8FDB session \u7684 `memory-candidates.md`,\u6216\u5F80 category \u6587\u4EF6\u8FFD\u52A0\u65B0\u6761\u76EE;\u4E0D\u5728\u70ED\u8DEF\u5F84\u6539\u5199/\u5220\u9664\u5DF2\u6709\u6761\u76EE\u3002",
114117
+ "- \u6539\u5199\u3001\u5220\u9664\u3001\u89E3\u51B3\u51B2\u7A81 \u2192 \u63A8\u8FDF\u5230 owner \u663E\u5F0F\u8BF4\u300C\u6574\u7406\u8BB0\u5FC6\u300D\u65F6\u79BB\u7EBF\u505A\u3002UPDATE/DELETE \u7684\u65E7\u6761\u76EE\u79FB `archive/`(\u6CE8\u4E00\u53E5\u539F\u56E0+\u5BF9\u5E94 commit),\u4E0D\u624B\u5199 superseded \u6233,\u4E0D\u7269\u7406\u5220;archive/ \u7684\u957F\u671F\u6E05\u7406\u4EA4\u7ED9 git history\u3002",
114118
+ "- \u88C1\u51B3\u4EE5 source \u4F18\u5148:user \u4EB2\u53E3\u8BF4\u7684 >> agent \u63A8\u65AD;\u51B2\u7A81\u65F6\u4FDD\u7559\u65E7\u7684 user \u6761\u76EE,\u628A\u65B0\u63A8\u65AD\u964D\u7EA7\u4E3A candidate\u3002\u540C source \u5185 recency \u4EE5 git \u5386\u53F2\u4E3A\u51C6,\u4E0D\u624B\u5199\u65E5\u671F\u6233\u3002",
114119
+ "- \u53EA\u6709\u8DE8 session \u8FD8\u4F1A\u7528\u5230\u7684\u624D\u8FDB\u8FD9\u91CC(\u5355\u6B21\u4EFB\u52A1\u7559\u5728 session summary)\u3002\u672C\u6587\u4EF6\u4FDD\u6301\u7B80\u77ED(\u2264~50 \u884C),\u4E0D\u653E changelog/\u7EDF\u8BA1\u3002",
114120
+ ""
114121
+ ].join("\n");
114122
+ }
114123
+ function renderMemoryCategorySkeleton(title) {
114124
+ return [
114125
+ `# ${title}`,
114126
+ "",
114127
+ "\u5199\u524D\u5148\u8BFB\u672C\u6587\u4EF6;\u6709\u76F8\u540C/\u76F8\u5173\u6761\u76EE\u5C31 NOOP \u4E0D\u91CD\u590D\u5199(\u6539\u5199\u3001\u5220\u9664\u3001\u79FB archive \u7559\u5230 owner \u8BF4\u300C\u6574\u7406\u8BB0\u5FC6\u300D\u65F6\u7684\u79BB\u7EBF\u6B65\u9AA4);\u4FE1\u53F7\u4E0D\u8DE8 session \u590D\u7528\u5C31\u4E0D\u5199\u3002",
114128
+ ""
114129
+ ].join("\n");
114130
+ }
114131
+ async function ensureMemoryScaffold(workspacePath) {
114132
+ const memoryDir = path5.join(workspacePath, "memory");
114133
+ await fs2.mkdir(memoryDir, { recursive: true });
114134
+ await fs2.mkdir(path5.join(memoryDir, "assets"), { recursive: true });
114135
+ await fs2.mkdir(path5.join(memoryDir, "archive"), { recursive: true });
114136
+ await writeIfMissing2(path5.join(memoryDir, "index.md"), renderMemoryIndex());
114137
+ for (const file of MEMORY_CATEGORY_FILES) {
114138
+ await writeIfMissing2(
114139
+ path5.join(memoryDir, file.name),
114140
+ renderMemoryCategorySkeleton(file.title)
114141
+ );
114142
+ }
113997
114143
  }
113998
114144
 
113999
114145
  // src/bridge/stateFile.ts
@@ -119265,13 +119411,7 @@ async function runV2Mode({
119265
119411
  );
119266
119412
  }
119267
119413
  const larkCliProfile = deriveLarkCliProfile(bot.lark_cli_profile, bot.app_id);
119268
- if (healthyBots.length > 1) {
119269
- ensureLarkCliProfile(bot.id, larkCliProfile, bot.app_id, appSecret);
119270
- } else {
119271
- if (bot.lark_cli_profile) {
119272
- ensureLarkCliProfile(bot.id, larkCliProfile, bot.app_id, appSecret);
119273
- }
119274
- }
119414
+ ensureLarkCliProfile(bot.id, larkCliProfile, bot.app_id, appSecret);
119275
119415
  const botDir = resolveLarkwayDir(bot.id);
119276
119416
  const sharedReposDir = path13.join(larkwayHome(), "repos");
119277
119417
  const worktreesDir = path13.join(botDir, "worktrees");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "larkway",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
4
4
  "description": "Thin bridge: Feishu thread to local Claude Code CLI",
5
5
  "license": "MIT",
6
6
  "author": "Chuck Wu (chuckwu0)",
@@ -19,6 +19,18 @@
19
19
  "bin": {
20
20
  "larkway": "bin/larkway"
21
21
  },
22
+ "scripts": {
23
+ "start": "tsx src/main.ts",
24
+ "cli": "tsx src/cli/index.ts",
25
+ "start:supervised": "bin/start-bridge.sh",
26
+ "dev": "tsx watch src/main.ts",
27
+ "build": "node scripts/build.mjs",
28
+ "prepack": "node scripts/build.mjs",
29
+ "typecheck": "tsc --noEmit",
30
+ "check:links": "bash bin/check-doc-links.sh",
31
+ "test": "vitest run",
32
+ "test:v0.3": "bash bin/v0.3-local-acceptance.sh"
33
+ },
22
34
  "dependencies": {},
23
35
  "devDependencies": {
24
36
  "@types/js-yaml": "^4.0.9",
@@ -36,6 +48,7 @@
36
48
  "qrcode-terminal": "^0.12.0",
37
49
  "zod": "^3.22.0"
38
50
  },
51
+ "packageManager": "pnpm@9.0.0",
39
52
  "engines": {
40
53
  "node": ">=20"
41
54
  },
@@ -43,16 +56,5 @@
43
56
  "dist/",
44
57
  "bin/larkway",
45
58
  "README.md"
46
- ],
47
- "scripts": {
48
- "start": "tsx src/main.ts",
49
- "cli": "tsx src/cli/index.ts",
50
- "start:supervised": "bin/start-bridge.sh",
51
- "dev": "tsx watch src/main.ts",
52
- "build": "node scripts/build.mjs",
53
- "typecheck": "tsc --noEmit",
54
- "check:links": "bash bin/check-doc-links.sh",
55
- "test": "vitest run",
56
- "test:v0.3": "bash bin/v0.3-local-acceptance.sh"
57
- }
58
- }
59
+ ]
60
+ }