@oyasmi/pipiclaw 0.5.1 → 0.5.3

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 (193) hide show
  1. package/README.md +308 -209
  2. package/dist/agent/channel-runner.d.ts +47 -0
  3. package/dist/agent/channel-runner.js +441 -0
  4. package/dist/agent/index.d.ts +3 -0
  5. package/dist/agent/index.js +2 -0
  6. package/dist/agent/progress-formatter.d.ts +4 -0
  7. package/dist/agent/progress-formatter.js +52 -0
  8. package/dist/agent/run-queue.d.ts +7 -0
  9. package/dist/agent/run-queue.js +26 -0
  10. package/dist/agent/runner-factory.d.ts +3 -0
  11. package/dist/agent/runner-factory.js +10 -0
  12. package/dist/agent/session-events.d.ts +14 -0
  13. package/dist/agent/session-events.js +215 -0
  14. package/dist/agent/session-resource-gate.d.ts +10 -0
  15. package/dist/agent/session-resource-gate.js +44 -0
  16. package/dist/agent/type-guards.d.ts +22 -0
  17. package/dist/agent/type-guards.js +106 -0
  18. package/dist/agent/types.d.ts +160 -0
  19. package/dist/agent/types.js +22 -0
  20. package/dist/agent.d.ts +2 -16
  21. package/dist/agent.js +1 -782
  22. package/dist/command-extension.d.ts +0 -1
  23. package/dist/command-extension.js +0 -1
  24. package/dist/commands.d.ts +0 -1
  25. package/dist/commands.js +0 -1
  26. package/dist/config-loader.d.ts +0 -1
  27. package/dist/config-loader.js +1 -2
  28. package/dist/context.d.ts +58 -15
  29. package/dist/context.js +50 -8
  30. package/dist/index.d.ts +12 -13
  31. package/dist/index.js +12 -13
  32. package/dist/log.d.ts +0 -1
  33. package/dist/log.js +0 -1
  34. package/dist/main.d.ts +0 -1
  35. package/dist/main.js +5 -405
  36. package/dist/memory/bootstrap.d.ts +6 -0
  37. package/dist/memory/bootstrap.js +46 -0
  38. package/dist/{memory-candidates.d.ts → memory/candidates.d.ts} +1 -1
  39. package/dist/{memory-candidates.js → memory/candidates.js} +33 -21
  40. package/dist/memory/chinese-words.d.ts +1 -0
  41. package/dist/memory/chinese-words.js +273 -0
  42. package/dist/{memory-consolidation.d.ts → memory/consolidation.d.ts} +0 -1
  43. package/dist/{memory-consolidation.js → memory/consolidation.js} +26 -35
  44. package/dist/{memory-files.d.ts → memory/files.d.ts} +0 -6
  45. package/dist/{memory-files.js → memory/files.js} +11 -36
  46. package/dist/{memory-lifecycle.d.ts → memory/lifecycle.d.ts} +23 -6
  47. package/dist/memory/lifecycle.js +246 -0
  48. package/dist/{memory-recall.d.ts → memory/recall.d.ts} +2 -2
  49. package/dist/memory/recall.js +501 -0
  50. package/dist/{session-memory.d.ts → memory/session.d.ts} +1 -1
  51. package/dist/{session-memory.js → memory/session.js} +31 -62
  52. package/dist/model-utils.d.ts +0 -1
  53. package/dist/model-utils.js +0 -1
  54. package/dist/paths.d.ts +0 -1
  55. package/dist/paths.js +0 -1
  56. package/dist/prompt-builder.d.ts +0 -1
  57. package/dist/prompt-builder.js +0 -1
  58. package/dist/runtime/bootstrap.d.ts +47 -0
  59. package/dist/runtime/bootstrap.js +450 -0
  60. package/dist/{delivery.d.ts → runtime/delivery.d.ts} +0 -1
  61. package/dist/{delivery.js → runtime/delivery.js} +1 -2
  62. package/dist/{dingtalk.d.ts → runtime/dingtalk.d.ts} +10 -1
  63. package/dist/{dingtalk.js → runtime/dingtalk.js} +87 -28
  64. package/dist/{events.d.ts → runtime/events.d.ts} +0 -1
  65. package/dist/{events.js → runtime/events.js} +1 -2
  66. package/dist/{store.d.ts → runtime/store.d.ts} +5 -1
  67. package/dist/{store.js → runtime/store.js} +60 -20
  68. package/dist/sandbox.d.ts +0 -1
  69. package/dist/sandbox.js +1 -2
  70. package/dist/{llm-json.d.ts → shared/llm-json.d.ts} +0 -1
  71. package/dist/{llm-json.js → shared/llm-json.js} +0 -1
  72. package/dist/shared/markdown-sections.d.ts +6 -0
  73. package/dist/{markdown-sections.js → shared/markdown-sections.js} +10 -4
  74. package/dist/{shell-escape.d.ts → shared/shell-escape.d.ts} +0 -1
  75. package/dist/{shell-escape.js → shared/shell-escape.js} +0 -1
  76. package/dist/shared/text-utils.d.ts +9 -0
  77. package/dist/shared/text-utils.js +36 -0
  78. package/dist/shared/type-guards.d.ts +5 -0
  79. package/dist/shared/type-guards.js +12 -0
  80. package/dist/shared/types.d.ts +14 -0
  81. package/dist/shared/types.js +1 -0
  82. package/dist/sidecar-worker.d.ts +0 -1
  83. package/dist/sidecar-worker.js +1 -8
  84. package/dist/{sub-agents.d.ts → subagents/discovery.d.ts} +0 -1
  85. package/dist/{sub-agents.js → subagents/discovery.js} +2 -3
  86. package/dist/{tools/subagent.d.ts → subagents/tool.d.ts} +2 -16
  87. package/dist/{tools/subagent.js → subagents/tool.js} +16 -38
  88. package/dist/tools/attach.d.ts +0 -1
  89. package/dist/tools/attach.js +0 -1
  90. package/dist/tools/bash.d.ts +0 -1
  91. package/dist/tools/bash.js +0 -1
  92. package/dist/tools/edit.d.ts +0 -1
  93. package/dist/tools/edit.js +1 -2
  94. package/dist/tools/index.d.ts +1 -2
  95. package/dist/tools/index.js +1 -2
  96. package/dist/tools/read.d.ts +0 -1
  97. package/dist/tools/read.js +1 -2
  98. package/dist/tools/truncate.d.ts +0 -1
  99. package/dist/tools/truncate.js +0 -1
  100. package/dist/tools/write-content.d.ts +0 -1
  101. package/dist/tools/write-content.js +1 -2
  102. package/dist/tools/write.d.ts +0 -1
  103. package/dist/tools/write.js +0 -1
  104. package/package.json +9 -3
  105. package/CHANGELOG.md +0 -47
  106. package/dist/agent.d.ts.map +0 -1
  107. package/dist/agent.js.map +0 -1
  108. package/dist/command-extension.d.ts.map +0 -1
  109. package/dist/command-extension.js.map +0 -1
  110. package/dist/commands.d.ts.map +0 -1
  111. package/dist/commands.js.map +0 -1
  112. package/dist/config-loader.d.ts.map +0 -1
  113. package/dist/config-loader.js.map +0 -1
  114. package/dist/context.d.ts.map +0 -1
  115. package/dist/context.js.map +0 -1
  116. package/dist/delivery.d.ts.map +0 -1
  117. package/dist/delivery.js.map +0 -1
  118. package/dist/dingtalk.d.ts.map +0 -1
  119. package/dist/dingtalk.js.map +0 -1
  120. package/dist/events.d.ts.map +0 -1
  121. package/dist/events.js.map +0 -1
  122. package/dist/index.d.ts.map +0 -1
  123. package/dist/index.js.map +0 -1
  124. package/dist/llm-json.d.ts.map +0 -1
  125. package/dist/llm-json.js.map +0 -1
  126. package/dist/log.d.ts.map +0 -1
  127. package/dist/log.js.map +0 -1
  128. package/dist/main.d.ts.map +0 -1
  129. package/dist/main.js.map +0 -1
  130. package/dist/markdown-sections.d.ts +0 -6
  131. package/dist/markdown-sections.d.ts.map +0 -1
  132. package/dist/markdown-sections.js.map +0 -1
  133. package/dist/memory-candidates.d.ts.map +0 -1
  134. package/dist/memory-candidates.js.map +0 -1
  135. package/dist/memory-consolidation.d.ts.map +0 -1
  136. package/dist/memory-consolidation.js.map +0 -1
  137. package/dist/memory-files.d.ts.map +0 -1
  138. package/dist/memory-files.js.map +0 -1
  139. package/dist/memory-lifecycle.d.ts.map +0 -1
  140. package/dist/memory-lifecycle.js +0 -150
  141. package/dist/memory-lifecycle.js.map +0 -1
  142. package/dist/memory-recall.d.ts.map +0 -1
  143. package/dist/memory-recall.js +0 -218
  144. package/dist/memory-recall.js.map +0 -1
  145. package/dist/model-utils.d.ts.map +0 -1
  146. package/dist/model-utils.js.map +0 -1
  147. package/dist/paths.d.ts.map +0 -1
  148. package/dist/paths.js.map +0 -1
  149. package/dist/prompt-builder.d.ts.map +0 -1
  150. package/dist/prompt-builder.js.map +0 -1
  151. package/dist/sandbox.d.ts.map +0 -1
  152. package/dist/sandbox.js.map +0 -1
  153. package/dist/session-memory-files.d.ts +0 -2
  154. package/dist/session-memory-files.d.ts.map +0 -1
  155. package/dist/session-memory-files.js +0 -2
  156. package/dist/session-memory-files.js.map +0 -1
  157. package/dist/session-memory.d.ts.map +0 -1
  158. package/dist/session-memory.js.map +0 -1
  159. package/dist/shell-escape.d.ts.map +0 -1
  160. package/dist/shell-escape.js.map +0 -1
  161. package/dist/sidecar-worker.d.ts.map +0 -1
  162. package/dist/sidecar-worker.js.map +0 -1
  163. package/dist/store.d.ts.map +0 -1
  164. package/dist/store.js.map +0 -1
  165. package/dist/sub-agents.d.ts.map +0 -1
  166. package/dist/sub-agents.js.map +0 -1
  167. package/dist/tools/attach.d.ts.map +0 -1
  168. package/dist/tools/attach.js.map +0 -1
  169. package/dist/tools/bash.d.ts.map +0 -1
  170. package/dist/tools/bash.js.map +0 -1
  171. package/dist/tools/edit.d.ts.map +0 -1
  172. package/dist/tools/edit.js.map +0 -1
  173. package/dist/tools/index.d.ts.map +0 -1
  174. package/dist/tools/index.js.map +0 -1
  175. package/dist/tools/read.d.ts.map +0 -1
  176. package/dist/tools/read.js.map +0 -1
  177. package/dist/tools/subagent.d.ts.map +0 -1
  178. package/dist/tools/subagent.js.map +0 -1
  179. package/dist/tools/truncate.d.ts.map +0 -1
  180. package/dist/tools/truncate.js.map +0 -1
  181. package/dist/tools/write-content.d.ts.map +0 -1
  182. package/dist/tools/write-content.js.map +0 -1
  183. package/dist/tools/write.d.ts.map +0 -1
  184. package/dist/tools/write.js.map +0 -1
  185. package/docs/improve-memory/design.md +0 -537
  186. package/docs/improve-memory/interfaces-and-tests.md +0 -473
  187. package/docs/improve-memory/spec.md +0 -357
  188. package/docs/memory-rfc.md +0 -297
  189. package/docs/proj-review.md +0 -188
  190. package/docs/subagent/pi-subagent-analyse.txt +0 -190
  191. package/docs/subagent/pi-subagent-design.txt +0 -266
  192. package/docs/subagent/pi-subagent-phase1-plan.txt +0 -529
  193. package/docs/test-supplementation-plan.md +0 -553
package/dist/main.js CHANGED
@@ -1,409 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
3
- import { join } from "path";
4
- import { getOrCreateRunner } from "./agent.js";
5
- import { parseBuiltInCommand } from "./commands.js";
6
- import { createDingTalkContext } from "./delivery.js";
7
- import { DingTalkBot, } from "./dingtalk.js";
8
- import { createEventsWatcher } from "./events.js";
9
- import * as log from "./log.js";
10
- import { ensureChannelMemoryFilesSync } from "./memory-files.js";
11
- import { APP_HOME_DIR, APP_NAME, AUTH_CONFIG_PATH, CHANNEL_CONFIG_PATH, MODELS_CONFIG_PATH, SETTINGS_CONFIG_PATH, WORKSPACE_DIR, } from "./paths.js";
12
- import { parseSandboxArg, validateSandbox } from "./sandbox.js";
13
- import { ChannelStore } from "./store.js";
14
- if (process.env.DINGTALK_FORCE_PROXY !== "true") {
15
- delete process.env.http_proxy;
16
- delete process.env.https_proxy;
17
- delete process.env.all_proxy;
18
- delete process.env.HTTP_PROXY;
19
- delete process.env.HTTPS_PROXY;
20
- delete process.env.ALL_PROXY;
21
- }
22
- const DEFAULT_SOUL = `# SOUL.md
23
-
24
- Configure Pipiclaw's identity, voice, and communication style here.
25
-
26
- Suggested sections:
27
-
28
- - Who the assistant is
29
- - Default language
30
- - Tone and personality
31
- - Reply style
32
- - Formatting preferences
33
-
34
- Example topics you may want to define:
35
-
36
- - "Answer in Chinese by default."
37
- - "Be concise and direct."
38
- - "Prefer Markdown."
39
- - "Act as an engineering assistant for our team."
40
-
41
- Replace this template with your actual identity prompt.
42
- `;
43
- const DEFAULT_AGENT = `# AGENTS.md
44
-
45
- Configure Pipiclaw's operating rules here.
46
-
47
- This file should define behavior and workflow. Identity, tone, and personality belong in \`SOUL.md\`.
48
-
49
- Suggested sections:
50
-
51
- - Tool usage policy
52
- - Security constraints
53
- - Scheduling/reminder policy
54
- - Project-specific workflows
55
- - Things the assistant must always or never do
56
-
57
- Replace this template with your actual operating instructions.
58
- `;
59
- const DEFAULT_MEMORY = `# Workspace Memory
60
-
61
- This file stores stable workspace-level memory.
62
-
63
- - It is intended to be managed by a human administrator.
64
- - It is not automatically rewritten by normal runtime consolidation.
65
- - Store durable shared background here when it should apply across channels.
66
- - Keep this file focused on stable facts, policies, and shared context, not transient conversation history.
67
-
68
- ## Shared Context
69
-
70
- <!-- Put team-wide or workspace-wide background here. -->
71
-
72
- ## Tooling And Environment
73
-
74
- <!-- Put durable tool usage rules, environment assumptions, or shared operational conventions here. -->
75
-
76
- ## Project Notes
77
-
78
- <!-- Put long-lived project facts here. -->
79
- `;
80
- const CHANNEL_CONFIG_TEMPLATE = {
81
- clientId: "your-dingtalk-client-id",
82
- clientSecret: "your-dingtalk-client-secret",
83
- robotCode: "your-robot-code",
84
- cardTemplateId: "your-card-template-id",
85
- cardTemplateKey: "content",
86
- allowFrom: ["your-staff-id"],
87
- };
88
- const MODELS_CONFIG_TEMPLATE = { providers: {} };
89
- function writeTextFileIfMissing(path, content, label, created) {
90
- if (existsSync(path)) {
91
- return false;
2
+ import { bootstrap, isBootstrapExitError } from "./runtime/bootstrap.js";
3
+ void bootstrap(process.argv).catch((error) => {
4
+ if (isBootstrapExitError(error)) {
5
+ process.exit(error.code);
92
6
  }
93
- writeFileSync(path, content, "utf-8");
94
- created.push(label);
95
- return true;
96
- }
97
- function writeJsonFileIfMissing(path, value, label, created) {
98
- return writeTextFileIfMissing(path, `${JSON.stringify(value, null, 2)}\n`, label, created);
99
- }
100
- function bootstrapAppHome() {
101
- const created = [];
102
- if (!existsSync(APP_HOME_DIR)) {
103
- mkdirSync(APP_HOME_DIR, { recursive: true });
104
- created.push("app home");
105
- }
106
- if (!existsSync(WORKSPACE_DIR)) {
107
- mkdirSync(WORKSPACE_DIR, { recursive: true });
108
- created.push("workspace/");
109
- }
110
- for (const dir of ["skills", "events", "sub-agents"]) {
111
- const dirPath = join(WORKSPACE_DIR, dir);
112
- if (!existsSync(dirPath)) {
113
- mkdirSync(dirPath, { recursive: true });
114
- created.push(`workspace/${dir}/`);
115
- }
116
- }
117
- writeTextFileIfMissing(join(WORKSPACE_DIR, "SOUL.md"), DEFAULT_SOUL, "workspace/SOUL.md", created);
118
- writeTextFileIfMissing(join(WORKSPACE_DIR, "AGENTS.md"), DEFAULT_AGENT, "workspace/AGENTS.md", created);
119
- writeTextFileIfMissing(join(WORKSPACE_DIR, "MEMORY.md"), DEFAULT_MEMORY, "workspace/MEMORY.md", created);
120
- const channelTemplateCreated = writeJsonFileIfMissing(CHANNEL_CONFIG_PATH, CHANNEL_CONFIG_TEMPLATE, "channel.json", created);
121
- writeJsonFileIfMissing(AUTH_CONFIG_PATH, {}, "auth.json", created);
122
- writeJsonFileIfMissing(MODELS_CONFIG_PATH, MODELS_CONFIG_TEMPLATE, "models.json", created);
123
- writeJsonFileIfMissing(SETTINGS_CONFIG_PATH, {}, "settings.json", created);
124
- return { created, channelTemplateCreated };
125
- }
126
- function isPlaceholderString(value) {
127
- return value.trim().startsWith("your-");
128
- }
129
- function listChannelConfigIssues(config) {
130
- const issues = [];
131
- if (!config.clientId) {
132
- issues.push("Missing required field `clientId`.");
133
- }
134
- else if (isPlaceholderString(config.clientId)) {
135
- issues.push("Replace placeholder value for `clientId`.");
136
- }
137
- if (!config.clientSecret) {
138
- issues.push("Missing required field `clientSecret`.");
139
- }
140
- else if (isPlaceholderString(config.clientSecret)) {
141
- issues.push("Replace placeholder value for `clientSecret`.");
142
- }
143
- if (config.robotCode && isPlaceholderString(config.robotCode)) {
144
- issues.push("Replace placeholder value for `robotCode`, or set it to an empty string to reuse `clientId`.");
145
- }
146
- if (config.cardTemplateId && isPlaceholderString(config.cardTemplateId)) {
147
- issues.push("Replace placeholder value for `cardTemplateId`, or set it to an empty string to disable AI Card streaming.");
148
- }
149
- if (Array.isArray(config.allowFrom) && config.allowFrom.some((value) => isPlaceholderString(value))) {
150
- issues.push("Replace placeholder values in `allowFrom`, or set it to an empty array to allow all users.");
151
- }
152
- return issues;
153
- }
154
- function printBootstrapSummary(result) {
155
- if (result.created.length === 0) {
156
- return;
157
- }
158
- console.log(`Initialized ${APP_NAME} under ${APP_HOME_DIR}:`);
159
- for (const item of result.created) {
160
- console.log(` - ${item}`);
161
- }
162
- console.log("");
163
- }
164
- function loadConfig() {
165
- let parsed;
166
- try {
167
- parsed = JSON.parse(readFileSync(CHANNEL_CONFIG_PATH, "utf-8"));
168
- }
169
- catch (err) {
170
- console.error(`Failed to parse configuration: ${CHANNEL_CONFIG_PATH}`);
171
- console.error(err instanceof Error ? err.message : String(err));
172
- process.exit(1);
173
- }
174
- const issues = listChannelConfigIssues(parsed);
175
- if (issues.length > 0) {
176
- console.error(`Configuration is not ready: ${CHANNEL_CONFIG_PATH}`);
177
- for (const issue of issues) {
178
- console.error(` - ${issue}`);
179
- }
180
- console.error("");
181
- console.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \`${APP_NAME}\` again.`);
182
- process.exit(1);
183
- }
184
- parsed.cardTemplateKey = parsed.cardTemplateKey || "content";
185
- parsed.robotCode = parsed.robotCode?.trim() ? parsed.robotCode : parsed.clientId;
186
- if (Array.isArray(parsed.allowFrom)) {
187
- parsed.allowFrom = parsed.allowFrom.filter((value) => value.trim().length > 0);
188
- }
189
- return parsed;
190
- }
191
- function parseArgs() {
192
- const args = process.argv.slice(2);
193
- let sandbox = { type: "host" };
194
- for (let i = 0; i < args.length; i++) {
195
- const arg = args[i];
196
- if (arg.startsWith("--sandbox=")) {
197
- sandbox = parseSandboxArg(arg.slice("--sandbox=".length));
198
- }
199
- else if (arg === "--sandbox") {
200
- sandbox = parseSandboxArg(args[++i] || "");
201
- }
202
- else if (arg === "--help" || arg === "-h") {
203
- console.log(`Usage: ${APP_NAME} [options]`);
204
- console.log("");
205
- console.log("Options:");
206
- console.log(" --sandbox=host Run tools on host (default)");
207
- console.log(" --sandbox=docker:<name> Run tools in Docker container");
208
- console.log("");
209
- console.log(`Config: ${CHANNEL_CONFIG_PATH}`);
210
- console.log(`Workspace: ${WORKSPACE_DIR}`);
211
- process.exit(0);
212
- }
213
- }
214
- return { sandbox };
215
- }
216
- const parsedArgs = parseArgs();
217
- const sandbox = parsedArgs.sandbox;
218
- const bootstrapResult = bootstrapAppHome();
219
- printBootstrapSummary(bootstrapResult);
220
- if (bootstrapResult.channelTemplateCreated) {
221
- console.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \`${APP_NAME}\` again.`);
7
+ console.error(error);
222
8
  process.exit(1);
223
- }
224
- const dingtalkConfig = loadConfig();
225
- dingtalkConfig.stateDir = WORKSPACE_DIR;
226
- await validateSandbox(sandbox);
227
- const channelStates = new Map();
228
- const activeTasks = new Set();
229
- const SHUTDOWN_WAIT_MS = 15000;
230
- const SHUTDOWN_ABORT_WAIT_MS = 5000;
231
- let shuttingDown = false;
232
- let shutdownPromise = null;
233
- function getState(channelId) {
234
- let state = channelStates.get(channelId);
235
- if (!state) {
236
- const channelDir = join(WORKSPACE_DIR, channelId);
237
- ensureChannelMemoryFilesSync(channelDir);
238
- state = {
239
- running: false,
240
- runner: getOrCreateRunner(sandbox, channelId, channelDir),
241
- store: new ChannelStore({ workingDir: WORKSPACE_DIR }),
242
- stopRequested: false,
243
- };
244
- channelStates.set(channelId, state);
245
- }
246
- return state;
247
- }
248
- const handler = {
249
- isRunning(channelId) {
250
- const state = channelStates.get(channelId);
251
- return state?.running ?? false;
252
- },
253
- async handleStop(channelId, _bot) {
254
- const state = channelStates.get(channelId);
255
- if (state?.running) {
256
- state.stopRequested = true;
257
- void state.runner.abort().catch((err) => {
258
- log.logWarning(`[${channelId}] Failed to abort run`, err instanceof Error ? err.message : String(err));
259
- });
260
- log.logInfo(`[${channelId}] Stop requested`);
261
- }
262
- },
263
- async handleBusyMessage(event, bot, mode, queueText) {
264
- if (shuttingDown) {
265
- return;
266
- }
267
- const state = getState(event.channelId);
268
- const trimmedQueueText = queueText.trim();
269
- await state.store.logMessage(event.channelId, {
270
- date: new Date().toISOString(),
271
- ts: event.ts,
272
- user: event.user,
273
- userName: event.userName,
274
- text: event.text,
275
- isBot: false,
276
- deliveryMode: mode,
277
- skipContextSync: true,
278
- });
279
- try {
280
- if (mode === "followUp") {
281
- await state.runner.queueFollowUp(trimmedQueueText, event.userName);
282
- }
283
- else {
284
- await state.runner.queueSteer(trimmedQueueText, event.userName);
285
- }
286
- const confirmation = mode === "followUp"
287
- ? "Queued as follow-up. I’ll handle it after the current task completes."
288
- : event.text.trim().startsWith("/")
289
- ? "Queued as steer. I’ll apply it after the current tool step finishes."
290
- : "Queued as steer. I’ll apply this after the current tool step finishes. Use `/followup <message>` to queue it after completion.";
291
- await bot.sendPlain(event.channelId, confirmation);
292
- log.logInfo(`[${event.channelId}] Queued ${mode}: ${trimmedQueueText.substring(0, 80)}`);
293
- }
294
- catch (err) {
295
- const errMsg = err instanceof Error ? err.message : String(err);
296
- log.logWarning(`[${event.channelId}] Failed to queue ${mode}`, errMsg);
297
- await bot.sendPlain(event.channelId, `Could not queue this message: ${errMsg}`);
298
- }
299
- },
300
- async handleEvent(event, bot, _isEvent) {
301
- if (shuttingDown) {
302
- log.logInfo(`[${event.channelId}] Ignoring event during shutdown`);
303
- return;
304
- }
305
- const state = getState(event.channelId);
306
- const task = (async () => {
307
- state.running = true;
308
- state.stopRequested = false;
309
- await state.store.logMessage(event.channelId, {
310
- date: new Date().toISOString(),
311
- ts: event.ts,
312
- user: event.user,
313
- userName: event.userName,
314
- text: event.text,
315
- isBot: false,
316
- });
317
- try {
318
- const ctx = createDingTalkContext(event, bot, state.store);
319
- const builtInCommand = parseBuiltInCommand(event.text);
320
- if (builtInCommand) {
321
- log.logInfo(`[${event.channelId}] Executing command: ${builtInCommand.rawText}`);
322
- await state.runner.handleBuiltinCommand(ctx, builtInCommand);
323
- return;
324
- }
325
- log.logInfo(`[${event.channelId}] Starting run: ${event.text.substring(0, 50)}`);
326
- const result = await state.runner.run(ctx, state.store);
327
- if (result.stopReason === "aborted" && state.stopRequested) {
328
- log.logInfo(`[${event.channelId}] Stopped`);
329
- }
330
- }
331
- catch (err) {
332
- log.logWarning(`[${event.channelId}] Run error`, err instanceof Error ? err.message : String(err));
333
- }
334
- finally {
335
- state.running = false;
336
- }
337
- })();
338
- activeTasks.add(task);
339
- try {
340
- await task;
341
- }
342
- finally {
343
- activeTasks.delete(task);
344
- }
345
- },
346
- };
347
- log.logStartup(WORKSPACE_DIR, sandbox.type === "host" ? "host" : `docker:${sandbox.container}`);
348
- const bot = new DingTalkBot(handler, dingtalkConfig);
349
- const eventsWatcher = createEventsWatcher(WORKSPACE_DIR, bot);
350
- eventsWatcher.start();
351
- function waitForTasks(tasks, timeoutMs) {
352
- if (tasks.length === 0) {
353
- return Promise.resolve(true);
354
- }
355
- return Promise.race([
356
- Promise.allSettled(tasks).then(() => true),
357
- new Promise((resolve) => {
358
- setTimeout(() => resolve(false), timeoutMs);
359
- }),
360
- ]);
361
- }
362
- async function shutdown(signal) {
363
- if (shutdownPromise) {
364
- return shutdownPromise;
365
- }
366
- shutdownPromise = (async () => {
367
- shuttingDown = true;
368
- log.logInfo(`Shutting down (${signal})...`);
369
- eventsWatcher.stop();
370
- await bot.stop();
371
- const runningTasks = Array.from(activeTasks);
372
- if (runningTasks.length > 0) {
373
- log.logInfo(`Waiting for ${runningTasks.length} active task(s) to finish`);
374
- const completed = await waitForTasks(runningTasks, SHUTDOWN_WAIT_MS);
375
- if (!completed) {
376
- log.logWarning(`Shutdown grace period exceeded ${SHUTDOWN_WAIT_MS}ms, aborting active runs`);
377
- const aborts = [];
378
- for (const [channelId, state] of channelStates) {
379
- if (!state.running)
380
- continue;
381
- state.stopRequested = true;
382
- log.logInfo(`[${channelId}] Aborting active run for shutdown`);
383
- aborts.push(state.runner.abort().catch((err) => {
384
- log.logWarning(`[${channelId}] Failed to abort run during shutdown`, err instanceof Error ? err.message : String(err));
385
- }));
386
- }
387
- await Promise.allSettled(aborts);
388
- const remainingTasks = Array.from(activeTasks);
389
- if (remainingTasks.length > 0) {
390
- const abortedCompleted = await waitForTasks(remainingTasks, SHUTDOWN_ABORT_WAIT_MS);
391
- if (!abortedCompleted) {
392
- log.logWarning(`Shutdown forced exit with ${remainingTasks.length} task(s) still active`);
393
- }
394
- }
395
- }
396
- }
397
- })().finally(() => {
398
- process.exit(0);
399
- });
400
- return shutdownPromise;
401
- }
402
- process.once("SIGINT", () => {
403
- void shutdown("SIGINT");
404
- });
405
- process.once("SIGTERM", () => {
406
- void shutdown("SIGTERM");
407
9
  });
408
- void bot.start();
409
- //# sourceMappingURL=main.js.map
@@ -0,0 +1,6 @@
1
+ export interface FirstTurnMemoryBootstrapOptions {
2
+ channelMemory: string;
3
+ workspaceMemory: string;
4
+ maxChars?: number;
5
+ }
6
+ export declare function buildFirstTurnMemoryBootstrap(options: FirstTurnMemoryBootstrapOptions): string;
@@ -0,0 +1,46 @@
1
+ import { clipText } from "../shared/text-utils.js";
2
+ const FIRST_TURN_MEMORY_SNAPSHOT_MAX_CHARS = 3_000;
3
+ const MIN_SECTION_BUDGET = 600;
4
+ const CHANNEL_MEMORY_WEIGHT = 0.6;
5
+ function normalizeContent(content) {
6
+ return content.replace(/\r/g, "").trim();
7
+ }
8
+ function allocateBudgets(channelMemory, workspaceMemory, maxChars) {
9
+ if (!channelMemory && !workspaceMemory) {
10
+ return [0, 0];
11
+ }
12
+ if (!channelMemory) {
13
+ return [0, maxChars];
14
+ }
15
+ if (!workspaceMemory) {
16
+ return [maxChars, 0];
17
+ }
18
+ const channelBudget = Math.max(MIN_SECTION_BUDGET, Math.floor(maxChars * CHANNEL_MEMORY_WEIGHT));
19
+ const workspaceBudget = Math.max(MIN_SECTION_BUDGET, maxChars - channelBudget);
20
+ const remainder = maxChars - channelBudget - workspaceBudget;
21
+ return [channelBudget + Math.max(0, remainder), workspaceBudget];
22
+ }
23
+ export function buildFirstTurnMemoryBootstrap(options) {
24
+ const maxChars = options.maxChars ?? FIRST_TURN_MEMORY_SNAPSHOT_MAX_CHARS;
25
+ const channelMemory = normalizeContent(options.channelMemory);
26
+ const workspaceMemory = normalizeContent(options.workspaceMemory);
27
+ if (!channelMemory && !workspaceMemory) {
28
+ return "";
29
+ }
30
+ const [channelBudget, workspaceBudget] = allocateBudgets(channelMemory, workspaceMemory, maxChars);
31
+ const sections = [
32
+ "<durable_memory_snapshot>",
33
+ "Durable memory bootstrap for the first user turn in this session.",
34
+ "Use it as background context together with any turn-specific recalled snippets.",
35
+ ];
36
+ if (channelMemory) {
37
+ sections.push("", "[Channel MEMORY.md]");
38
+ sections.push(channelBudget > 0 ? clipText(channelMemory, channelBudget, { headRatio: 1 }) : channelMemory);
39
+ }
40
+ if (workspaceMemory) {
41
+ sections.push("", "[Workspace MEMORY.md]");
42
+ sections.push(workspaceBudget > 0 ? clipText(workspaceMemory, workspaceBudget, { headRatio: 1 }) : workspaceMemory);
43
+ }
44
+ sections.push("</durable_memory_snapshot>");
45
+ return sections.join("\n");
46
+ }
@@ -4,6 +4,7 @@ export interface MemoryCandidate {
4
4
  path: string;
5
5
  title: string;
6
6
  content: string;
7
+ searchText?: string;
7
8
  timestamp?: string;
8
9
  sectionKind?: string;
9
10
  priority: number;
@@ -18,4 +19,3 @@ export interface MemoryCandidateCache {
18
19
  }
19
20
  export declare function createMemoryCandidateCache(): MemoryCandidateCache;
20
21
  export declare function buildMemoryCandidates(options: BuildMemoryCandidatesOptions): Promise<MemoryCandidate[]>;
21
- //# sourceMappingURL=memory-candidates.d.ts.map
@@ -1,7 +1,7 @@
1
1
  import { readFile } from "fs/promises";
2
2
  import { join } from "path";
3
- import { splitLevelOneSections } from "./markdown-sections.js";
4
- import { getChannelHistoryPath, getChannelMemoryPath, getChannelSessionPath, splitMarkdownSections, } from "./memory-files.js";
3
+ import { splitH1Sections, splitH2Sections } from "../shared/markdown-sections.js";
4
+ import { getChannelHistoryPath, getChannelMemoryPath, getChannelSessionPath } from "./files.js";
5
5
  export function createMemoryCandidateCache() {
6
6
  return {
7
7
  entries: new Map(),
@@ -28,38 +28,49 @@ function inferPriority(source, title) {
28
28
  const normalizedTitle = title.trim().toLowerCase();
29
29
  if (source === "channel-session") {
30
30
  if (normalizedTitle === "current state")
31
- return 120;
31
+ return 18;
32
32
  if (normalizedTitle === "next steps")
33
- return 115;
33
+ return 17;
34
34
  if (normalizedTitle === "errors & corrections")
35
- return 110;
35
+ return 16;
36
36
  if (normalizedTitle === "constraints")
37
- return 108;
37
+ return 15;
38
38
  if (normalizedTitle === "user intent")
39
- return 105;
40
- return 100;
39
+ return 15;
40
+ if (normalizedTitle === "active files")
41
+ return 14;
42
+ if (normalizedTitle === "decisions")
43
+ return 14;
44
+ if (normalizedTitle === "session title")
45
+ return 13;
46
+ return 12;
41
47
  }
42
48
  if (source === "channel-memory") {
43
49
  if (normalizedTitle.includes("constraints"))
44
- return 88;
50
+ return 11;
45
51
  if (normalizedTitle.includes("decisions"))
46
- return 86;
52
+ return 10;
47
53
  if (normalizedTitle.includes("open loops"))
48
- return 84;
49
- return 80;
54
+ return 10;
55
+ if (normalizedTitle.includes("preferences"))
56
+ return 9;
57
+ if (normalizedTitle.includes("ongoing work"))
58
+ return 9;
59
+ return 8;
50
60
  }
51
61
  if (source === "workspace-memory") {
52
- return 60;
62
+ return 6;
53
63
  }
54
- return 40;
64
+ return 4;
55
65
  }
56
- function buildCandidate(source, path, title, content, timestamp) {
66
+ function buildCandidate(source, path, title, content, timestamp, searchText) {
57
67
  return {
58
68
  id: `${source}:${slugify(title)}:${timestamp ?? ""}`,
59
69
  source,
60
70
  path,
61
71
  title,
62
72
  content,
73
+ searchText,
63
74
  timestamp,
64
75
  sectionKind: title.trim().toLowerCase(),
65
76
  priority: inferPriority(source, title),
@@ -69,7 +80,7 @@ function buildCacheKey(options) {
69
80
  return `${options.workspaceDir}\u0000${options.channelDir}`;
70
81
  }
71
82
  function buildWorkspaceOrChannelMemoryCandidates(source, path, content) {
72
- const sections = splitMarkdownSections(content);
83
+ const sections = splitH2Sections(content);
73
84
  if (sections.length === 0 && content) {
74
85
  return [
75
86
  buildCandidate(source, path, source === "workspace-memory" ? "Workspace Memory" : "Channel Memory", content),
@@ -80,12 +91,14 @@ function buildWorkspaceOrChannelMemoryCandidates(source, path, content) {
80
91
  .map((section) => buildCandidate(source, path, section.heading, section.content));
81
92
  }
82
93
  function buildSessionCandidates(path, content) {
83
- return splitLevelOneSections(content)
84
- .filter((section) => section.content.trim())
85
- .map((section) => buildCandidate("channel-session", path, section.heading, section.content));
94
+ const sections = splitH1Sections(content).filter((section) => section.content.trim());
95
+ const sessionTitle = sections.find((section) => section.heading.toLowerCase() === "session title")?.content ?? "";
96
+ return sections.map((section) => buildCandidate("channel-session", path, section.heading, section.content, undefined, section.heading.toLowerCase() === "session title" || !sessionTitle.trim()
97
+ ? section.content
98
+ : `${sessionTitle.trim()}\n${section.content}`));
86
99
  }
87
100
  function buildHistoryCandidates(path, content) {
88
- return splitMarkdownSections(content)
101
+ return splitH2Sections(content)
89
102
  .filter((section) => section.content.trim())
90
103
  .map((section) => buildCandidate("channel-history", path, section.heading, section.content, section.heading));
91
104
  }
@@ -123,4 +136,3 @@ export async function buildMemoryCandidates(options) {
123
136
  options.cache.entries.set(key, pending);
124
137
  return pending;
125
138
  }
126
- //# sourceMappingURL=memory-candidates.js.map
@@ -0,0 +1 @@
1
+ export declare const COMMON_CHINESE_WORDS: Set<string>;