@oyasmi/pipiclaw 0.1.1 → 0.1.2
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/CHANGELOG.md +1 -1
- package/README.md +45 -10
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +12 -12
- package/dist/agent.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +2 -2
- package/dist/main.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -27,6 +27,6 @@
|
|
|
27
27
|
- Conversation metadata is persisted per channel so scheduled events and proactive sends continue to work after process restarts
|
|
28
28
|
- Package, CLI, and data directory renamed to `pipiclaw`, `@oyasmi/pipiclaw`, and `~/.pi/pipiclaw/`
|
|
29
29
|
- Pipiclaw now bootstraps `channel.json`, `auth.json`, `models.json`, `settings.json`, and the workspace skeleton automatically on first start
|
|
30
|
-
- Auto-generated `models.json` now starts as an empty valid config, and `SOUL.md` / `
|
|
30
|
+
- Auto-generated `models.json` now starts as an empty valid config, and `SOUL.md` / `AGENTS.md` are guidance templates instead of prefilled behavior
|
|
31
31
|
- Global pipiclaw settings now live in `~/.pi/pipiclaw/settings.json`, and saved default models are restored on restart
|
|
32
32
|
- DingTalk channel configuration is now read from `~/.pi/pipiclaw/channel.json`
|
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Pipiclaw 是一个接入钉钉的 AI Card 机器人,把 [pi-coding-agent](../c
|
|
|
8
8
|
- 过程性思考和执行信息通过 AI Card 展示,最终答复独立快速返回
|
|
9
9
|
- 内置 Slash 命令:`/help`、`/new`、`/compact`、`/session`、`/model`
|
|
10
10
|
- 每个 DM / 群聊独立工作空间
|
|
11
|
-
- 支持全局和频道级 `SOUL.md`、`
|
|
11
|
+
- 支持全局和频道级 `SOUL.md`、`AGENTS.md`、`MEMORY.md`
|
|
12
12
|
- 支持全局和频道级技能目录
|
|
13
13
|
- 支持 immediate / one-shot / periodic 定时事件
|
|
14
14
|
- 支持自定义模型配置和模型切换
|
|
@@ -35,7 +35,7 @@ pipiclaw
|
|
|
35
35
|
- `workspace/events/`
|
|
36
36
|
- `workspace/skills/`
|
|
37
37
|
- `workspace/SOUL.md`
|
|
38
|
-
- `workspace/
|
|
38
|
+
- `workspace/AGENTS.md`
|
|
39
39
|
- `workspace/MEMORY.md`
|
|
40
40
|
|
|
41
41
|
如果 `channel.json` 还是示例占位符,程序会提示你先填写真实配置,然后退出。
|
|
@@ -158,15 +158,50 @@ pipiclaw --sandbox=docker:your-container
|
|
|
158
158
|
- `/model <ref>` 只支持精确匹配
|
|
159
159
|
- 未被 Pipiclaw 拦截的其他 slash 输入,仍会按 `AgentSession.prompt()` 的原有逻辑处理
|
|
160
160
|
|
|
161
|
-
|
|
161
|
+
## Workspace Files
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
Pipiclaw 只会自动识别并使用下面这些 workspace 文件或目录:
|
|
164
164
|
|
|
165
|
-
- `SOUL.md`
|
|
166
|
-
- `
|
|
167
|
-
- `MEMORY.md`
|
|
165
|
+
- `SOUL.md`
|
|
166
|
+
- `AGENTS.md`
|
|
167
|
+
- `MEMORY.md`
|
|
168
|
+
- `skills/`
|
|
169
|
+
- `events/`
|
|
168
170
|
|
|
169
|
-
|
|
171
|
+
`TOOLS.md` 当前不受支持。即使你手工创建了它,也不会被自动加载或生效。
|
|
172
|
+
|
|
173
|
+
### Global And Channel Scope
|
|
174
|
+
|
|
175
|
+
Pipiclaw 同时支持:
|
|
176
|
+
|
|
177
|
+
- 全局 workspace 文件:`~/.pi/pipiclaw/workspace/`
|
|
178
|
+
- 渠道级文件:`~/.pi/pipiclaw/workspace/dm_xxxx/` 或 `group_xxxx/`
|
|
179
|
+
|
|
180
|
+
它们的关系如下:
|
|
181
|
+
|
|
182
|
+
| 名称 | 全局位置 | 渠道级位置 | 生效方式 |
|
|
183
|
+
|------|----------|------------|----------|
|
|
184
|
+
| `SOUL.md` | `workspace/SOUL.md` | 不支持 | 仅使用全局文件。渠道级 `SOUL.md` 不会被读取。 |
|
|
185
|
+
| `AGENTS.md` | `workspace/AGENTS.md` | `<channel>/AGENTS.md` | 叠加。先读取全局,再追加渠道级,不是替换。 |
|
|
186
|
+
| `MEMORY.md` | `workspace/MEMORY.md` | `<channel>/MEMORY.md` | 合并。全局记忆和渠道记忆都会一起进入上下文。 |
|
|
187
|
+
| `skills/` | `workspace/skills/` | `<channel>/skills/` | 合并。两边的技能都会加载;如果同名,渠道级覆盖全局。 |
|
|
188
|
+
| `events/` | `workspace/events/` | 不支持 | 仅支持全局事件目录。 |
|
|
189
|
+
| `.channel-meta.json` | 不支持 | `<channel>/.channel-meta.json` | 运行时自动维护,用于主动发送和重启恢复,不建议手工编辑。 |
|
|
190
|
+
| `context.jsonl` | 不支持 | `<channel>/context.jsonl` | 运行时自动维护,保存结构化上下文。 |
|
|
191
|
+
| `log.jsonl` | 不支持 | `<channel>/log.jsonl` | 运行时自动维护,保存消息历史。 |
|
|
192
|
+
|
|
193
|
+
### File Intent
|
|
194
|
+
|
|
195
|
+
- `SOUL.md`
|
|
196
|
+
定义 Pipiclaw 的身份、语气、默认语言和回复风格。首次运行生成的只是说明模板,你需要替换成真实内容。
|
|
197
|
+
- `AGENTS.md`
|
|
198
|
+
定义行为规则、工具使用策略、安全约束和项目工作流。全局文件定义通用规则,渠道级文件补充该渠道的特定规则。
|
|
199
|
+
- `MEMORY.md`
|
|
200
|
+
定义持久记忆。适合存长期偏好、项目背景、联系人信息、长期约束等。
|
|
201
|
+
- `skills/`
|
|
202
|
+
存放自定义技能。适合放可复用的 CLI 工具、脚本和 skill 说明。
|
|
203
|
+
- `events/`
|
|
204
|
+
存放定时事件定义。只支持全局目录,不支持放到单个 channel 目录里。
|
|
170
205
|
|
|
171
206
|
## 工作空间布局
|
|
172
207
|
|
|
@@ -178,12 +213,12 @@ pipiclaw --sandbox=docker:your-container
|
|
|
178
213
|
├── settings.json
|
|
179
214
|
└── workspace/
|
|
180
215
|
├── SOUL.md
|
|
181
|
-
├──
|
|
216
|
+
├── AGENTS.md
|
|
182
217
|
├── MEMORY.md
|
|
183
218
|
├── skills/
|
|
184
219
|
├── events/
|
|
185
220
|
└── dm_{userId}/
|
|
186
|
-
├──
|
|
221
|
+
├── AGENTS.md
|
|
187
222
|
├── MEMORY.md
|
|
188
223
|
├── .channel-meta.json
|
|
189
224
|
├── context.jsonl
|
package/dist/agent.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,cAAc,EAAqB,MAAM,eAAe,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM/C,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,KAAK,IAAI,IAAI,CAAC;CACd;AAqfD,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAOlH;AA2kBD;;GAEG;AACH,wBAAgB,mBAAmB,CAClC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GACf,MAAM,CAWR","sourcesContent":["import { Agent } from \"@mariozechner/pi-agent-core\";\nimport { type Api, getModel, type Model } from \"@mariozechner/pi-ai\";\nimport {\n\tAgentSession,\n\tAuthStorage,\n\tconvertToLlm,\n\tDefaultResourceLoader,\n\tformatSkillsForPrompt,\n\tloadSkillsFromDir,\n\tModelRegistry,\n\tSessionManager,\n\ttype Skill,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { basename, join } from \"path\";\nimport { type BuiltInCommand, renderBuiltInHelp } from \"./commands.js\";\nimport { PipiclawSettingsManager, syncLogToSessionManager } from \"./context.js\";\nimport type { DingTalkContext } from \"./dingtalk.js\";\nimport * as log from \"./log.js\";\nimport { APP_HOME_DIR, AUTH_CONFIG_PATH, MODELS_CONFIG_PATH } from \"./paths.js\";\nimport { createExecutor, type SandboxConfig } from \"./sandbox.js\";\nimport type { ChannelStore } from \"./store.js\";\nimport { createPipiclawTools } from \"./tools/index.js\";\n\n// Default model - will be overridden by ModelRegistry if custom models are configured\nconst defaultModel = getModel(\"anthropic\", \"claude-sonnet-4-5\");\n\nexport interface AgentRunner {\n\trun(ctx: DingTalkContext, store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }>;\n\thandleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void>;\n\tabort(): void;\n}\n\ntype FinalOutcome = { kind: \"none\" } | { kind: \"silent\" } | { kind: \"final\"; text: string };\n\nfunction isSilentOutcome(outcome: FinalOutcome): outcome is { kind: \"silent\" } {\n\treturn outcome.kind === \"silent\";\n}\n\nfunction isFinalOutcome(outcome: FinalOutcome): outcome is { kind: \"final\"; text: string } {\n\treturn outcome.kind === \"final\";\n}\n\nfunction getFinalOutcomeText(outcome: FinalOutcome): string | null {\n\treturn isFinalOutcome(outcome) ? outcome.text : null;\n}\n\nfunction formatModelReference(model: Model<Api>): string {\n\treturn `${model.provider}/${model.id}`;\n}\n\nfunction findExactModelReferenceMatch(\n\tmodelReference: string,\n\tavailableModels: Model<Api>[],\n): { match?: Model<Api>; ambiguous: boolean } {\n\tconst trimmedReference = modelReference.trim();\n\tif (!trimmedReference) {\n\t\treturn { ambiguous: false };\n\t}\n\n\tconst normalizedReference = trimmedReference.toLowerCase();\n\n\tconst canonicalMatches = availableModels.filter(\n\t\t(model) => `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n\t);\n\tif (canonicalMatches.length === 1) {\n\t\treturn { match: canonicalMatches[0], ambiguous: false };\n\t}\n\tif (canonicalMatches.length > 1) {\n\t\treturn { ambiguous: true };\n\t}\n\n\tconst slashIndex = trimmedReference.indexOf(\"/\");\n\tif (slashIndex !== -1) {\n\t\tconst provider = trimmedReference.substring(0, slashIndex).trim();\n\t\tconst modelId = trimmedReference.substring(slashIndex + 1).trim();\n\t\tif (provider && modelId) {\n\t\t\tconst providerMatches = availableModels.filter(\n\t\t\t\t(model) =>\n\t\t\t\t\tmodel.provider.toLowerCase() === provider.toLowerCase() &&\n\t\t\t\t\tmodel.id.toLowerCase() === modelId.toLowerCase(),\n\t\t\t);\n\t\t\tif (providerMatches.length === 1) {\n\t\t\t\treturn { match: providerMatches[0], ambiguous: false };\n\t\t\t}\n\t\t\tif (providerMatches.length > 1) {\n\t\t\t\treturn { ambiguous: true };\n\t\t\t}\n\t\t}\n\t}\n\n\tconst idMatches = availableModels.filter((model) => model.id.toLowerCase() === normalizedReference);\n\tif (idMatches.length === 1) {\n\t\treturn { match: idMatches[0], ambiguous: false };\n\t}\n\n\treturn { ambiguous: idMatches.length > 1 };\n}\n\nfunction formatModelList(models: Model<Api>[], currentModel: Model<Api> | undefined, limit: number = 20): string {\n\tconst refs = models\n\t\t.slice()\n\t\t.sort((a, b) => formatModelReference(a).localeCompare(formatModelReference(b)))\n\t\t.map((model) => {\n\t\t\tconst ref = formatModelReference(model);\n\t\t\tconst marker =\n\t\t\t\tcurrentModel && currentModel.provider === model.provider && currentModel.id === model.id\n\t\t\t\t\t? \" (current)\"\n\t\t\t\t\t: \"\";\n\t\t\treturn `- \\`${ref}\\`${marker}`;\n\t\t});\n\n\tif (refs.length <= limit) {\n\t\treturn refs.join(\"\\n\");\n\t}\n\n\treturn `${refs.slice(0, limit).join(\"\\n\")}\\n- ... and ${refs.length - limit} more`;\n}\n\nfunction resolveInitialModel(modelRegistry: ModelRegistry, settingsManager: PipiclawSettingsManager): Model<Api> {\n\tconst savedProvider = settingsManager.getDefaultProvider();\n\tconst savedModelId = settingsManager.getDefaultModel();\n\tconst availableModels = modelRegistry.getAvailable();\n\tif (savedProvider && savedModelId) {\n\t\tconst savedModel = modelRegistry.find(savedProvider, savedModelId);\n\t\tif (\n\t\t\tsavedModel &&\n\t\t\tavailableModels.some((model) => model.provider === savedModel.provider && model.id === savedModel.id)\n\t\t) {\n\t\t\treturn savedModel;\n\t\t}\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn defaultModel;\n}\n\nasync function getApiKeyForModel(modelRegistry: ModelRegistry, model: any): Promise<string> {\n\tconst key = await modelRegistry.getApiKeyForProvider(model.provider);\n\tif (key) return key;\n\t// Fallback: try anthropic env var\n\tconst envKey = process.env.ANTHROPIC_API_KEY;\n\tif (envKey) return envKey;\n\tthrow new Error(\n\t\t`No API key found for provider: ${model.provider}.\\n\\n` +\n\t\t\t\"Configure API key in ~/.pi/agent/models.json or set ANTHROPIC_API_KEY environment variable.\",\n\t);\n}\n\n// ============================================================================\n// Configuration file loaders: SOUL.md, AGENT.md, MEMORY.md\n// ============================================================================\n\n/**\n * Load SOUL.md — defines the agent's identity, personality, and communication style.\n * Only loaded from workspace root (global).\n */\nfunction getSoul(workspaceDir: string): string {\n\tconst soulPath = join(workspaceDir, \"SOUL.md\");\n\tif (existsSync(soulPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(soulPath, \"utf-8\").trim();\n\t\t\tif (content) return content;\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read SOUL.md\", `${soulPath}: ${error}`);\n\t\t}\n\t}\n\treturn \"\";\n}\n\n/**\n * Load AGENT.md — defines the agent's behavior instructions, capabilities, and constraints.\n * Supports both global (workspace root) and channel-level override.\n */\nfunction getAgentConfig(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level AGENT.md (global)\n\tconst workspaceAgentPath = join(channelDir, \"..\", \"AGENT.md\");\n\tif (existsSync(workspaceAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace AGENT.md\", `${workspaceAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific AGENT.md (overrides/extends global)\n\tconst channelAgentPath = join(channelDir, \"AGENT.md\");\n\tif (existsSync(channelAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel AGENT.md\", `${channelAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction getMemory(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level memory (shared across all channels)\n\tconst workspaceMemoryPath = join(channelDir, \"..\", \"MEMORY.md\");\n\tif (existsSync(workspaceMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Global Workspace Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific memory\n\tconst channelMemoryPath = join(channelDir, \"MEMORY.md\");\n\tif (existsSync(channelMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Channel-Specific Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel memory\", `${channelMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\tif (parts.length === 0) {\n\t\treturn \"(no working memory yet)\";\n\t}\n\n\tconst combined = parts.join(\"\\n\\n\");\n\n\t// Warn if memory is getting too large (consumes system prompt token budget)\n\tif (combined.length > 5000) {\n\t\treturn `\\u26a0\\ufe0f Memory is large (${combined.length} chars). Consolidate: remove outdated entries, merge duplicates, tighten descriptions.\\n\\n${combined}`;\n\t}\n\n\treturn combined;\n}\n\nfunction loadPipiclawSkills(channelDir: string, workspacePath: string): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\tconst hostWorkspacePath = join(channelDir, \"..\");\n\n\tconst translatePath = (hostPath: string): string => {\n\t\tif (hostPath.startsWith(hostWorkspacePath)) {\n\t\t\treturn workspacePath + hostPath.slice(hostWorkspacePath.length);\n\t\t}\n\t\treturn hostPath;\n\t};\n\n\t// Load workspace-level skills (global)\n\tconst workspaceSkillsDir = join(hostWorkspacePath, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: \"workspace\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Load channel-specific skills\n\tconst channelSkillsDir = join(channelDir, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: channelSkillsDir, source: \"channel\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n\n// ============================================================================\n// System Prompt Builder\n// ============================================================================\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\tsoul: string,\n\tagentConfig: string,\n\tmemory: string,\n\tsandboxConfig: SandboxConfig,\n\tskills: Skill[],\n): string {\n\tconst channelPath = `${workspacePath}/${channelId}`;\n\tconst isDocker = sandboxConfig.type === \"docker\";\n\n\tconst envDescription = isDocker\n\t\t? `You are running inside a Docker container (Alpine Linux).\n- Bash working directory: / (use cd or absolute paths)\n- Install tools with: apk add <package>\n- Your changes persist across sessions`\n\t\t: `You are running directly on the host machine.\n- Bash working directory: ${process.cwd()}\n- Be careful with system modifications`;\n\n\t// Build system prompt with configuration file layering:\n\t// 1. SOUL.md (identity/personality)\n\t// 2. Core instructions\n\t// 3. AGENT.md (behavior instructions)\n\t// 4. Skills, Events, Memory\n\n\tconst sections: string[] = [];\n\n\t// 1. SOUL.md — Agent identity\n\tif (soul) {\n\t\tsections.push(soul);\n\t} else {\n\t\tsections.push(\"You are a DingTalk bot assistant. Be concise and helpful.\");\n\t}\n\n\t// 2. Core instructions\n\tsections.push(`## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\n\n## Formatting\nUse Markdown for formatting. DingTalk AI Card supports basic Markdown:\nBold: **text**, Italic: *text*, Code: \\`code\\`, Block: \\`\\`\\`code\\`\\`\\`, Links: [text](url)\n\n## Environment\n${envDescription}\n\n## Workspace Layout\n${workspacePath}/\n├── SOUL.md # Your identity/personality (read-only)\n├── AGENT.md # Custom behavior instructions (read-only)\n├── MEMORY.md # Global memory (all channels, you can read/write)\n├── skills/ # Global CLI tools you create\n├── events/ # Scheduled events\n└── ${channelId}/ # This channel\n ├── AGENT.md # Channel-specific instructions (read-only)\n ├── MEMORY.md # Channel-specific memory (you can read/write)\n ├── log.jsonl # Message history (no tool results)\n ├── scratch/ # Your working directory\n └── skills/ # Channel-specific tools`);\n\n\t// 3. AGENT.md — User-defined instructions\n\tif (agentConfig) {\n\t\tsections.push(`## Agent Instructions\\n${agentConfig}`);\n\t}\n\n\t// 4. Skills\n\tsections.push(`## Skills (Custom CLI Tools)\nYou can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).\n\n### Creating Skills\nStore in \\`${workspacePath}/skills/<name>/\\` (global) or \\`${channelPath}/skills/<name>/\\` (channel-specific).\nEach skill directory needs a \\`SKILL.md\\` with YAML frontmatter:\n\n\\`\\`\\`markdown\n---\nname: skill-name\ndescription: Short description of what this skill does\n---\n\n# Skill Name\n\nUsage instructions, examples, etc.\nScripts are in: {baseDir}/\n\\`\\`\\`\n\n\\`name\\` and \\`description\\` are required. Use \\`{baseDir}\\` as placeholder for the skill's directory path.\n\n### Available Skills\n${skills.length > 0 ? formatSkillsForPrompt(skills) : \"(no skills installed yet)\"}`);\n\n\t// 5. Events\n\tsections.push(`## Events\nYou can schedule events that wake you up at specific times or when external things happen. Events are JSON files in \\`${workspacePath}/events/\\`.\n\n### Event Types\n\n**Immediate** - Triggers as soon as harness sees the file.\n\\`\\`\\`json\n{\"type\": \"immediate\", \"channelId\": \"${channelId}\", \"text\": \"New event occurred\"}\n\\`\\`\\`\n\n**One-shot** - Triggers once at a specific time.\n\\`\\`\\`json\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder\", \"at\": \"2025-12-15T09:00:00+08:00\"}\n\\`\\`\\`\n\n**Periodic** - Triggers on a cron schedule.\n\\`\\`\\`json\n{\"type\": \"periodic\", \"channelId\": \"${channelId}\", \"text\": \"Check inbox\", \"schedule\": \"0 9 * * 1-5\", \"timezone\": \"${Intl.DateTimeFormat().resolvedOptions().timeZone}\"}\n\\`\\`\\`\n\n### Cron Format\n\\`minute hour day-of-month month day-of-week\\`\n\n### Creating Events\n\\`\\`\\`bash\ncat > ${workspacePath}/events/reminder-$(date +%s).json << 'EOF'\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder text\", \"at\": \"2025-12-14T09:00:00+08:00\"}\nEOF\n\\`\\`\\`\n\n### Silent Completion\nFor periodic events where there's nothing to report, respond with just \\`[SILENT]\\`. This deletes the status message. Use this to avoid spam when periodic checks find nothing.\n\n### Limits\nMaximum 5 events can be queued.`);\n\n\t// 6. Memory\n\tsections.push(`## Memory\nWrite to MEMORY.md files to persist context across conversations.\n- Global (${workspacePath}/MEMORY.md): skills, preferences, project info\n- Channel (${channelPath}/MEMORY.md): channel-specific decisions, ongoing work\n\n### Guidelines\n- Keep each MEMORY.md concise (target: under 50 lines)\n- Use clear headers to organize entries (## Preferences, ## Projects, etc.)\n- Remove outdated entries when they are no longer relevant\n- Merge duplicate or redundant items\n- Prefer structured formats (lists, key-value pairs) over prose\n- Update when you learn something important or when asked to remember something\n\n### Current Memory\n${memory}`);\n\n\t// 7. System Configuration Log\n\tsections.push(`## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment.`);\n\n\t// 8. Tools\n\tsections.push(`## Tools\n- bash: Run shell commands (primary tool). Install packages as needed.\n- read: Read files\n- write: Create/overwrite files\n- edit: Surgical file edits\n- attach: Share files (note: DingTalk file sharing is limited, output as text when possible)\n\nEach tool requires a \"label\" parameter (shown to user).`);\n\n\t// 9. Log Queries\n\tsections.push(`## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\\`\\`\\``);\n\n\treturn sections.join(\"\\n\\n\");\n}\n\n// ============================================================================\n// Agent Runner\n// ============================================================================\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn `${text.substring(0, maxLen - 3)}...`;\n}\n\nfunction sanitizeProgressText(text: string): string {\n\treturn text\n\t\t.replace(/\\uFFFC/g, \"\")\n\t\t.replace(/\\r/g, \"\")\n\t\t.trim();\n}\n\nfunction formatProgressEntry(kind: \"tool\" | \"thinking\" | \"error\" | \"assistant\", text: string): string {\n\tconst cleaned = sanitizeProgressText(text);\n\tif (!cleaned) return \"\";\n\n\tconst normalized = cleaned.replace(/\\n+/g, \" \").trim();\n\tswitch (kind) {\n\t\tcase \"tool\":\n\t\t\treturn `Running: ${normalized}`;\n\t\tcase \"thinking\":\n\t\t\treturn `Thinking: ${normalized}`;\n\t\tcase \"error\":\n\t\t\treturn `Error: ${normalized}`;\n\t\tcase \"assistant\":\n\t\t\treturn normalized;\n\t}\n}\n\nfunction extractToolResultText(result: unknown): string {\n\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\n\tif (\n\t\tresult &&\n\t\ttypeof result === \"object\" &&\n\t\t\"content\" in result &&\n\t\tArray.isArray((result as { content: unknown }).content)\n\t) {\n\t\tconst content = (result as { content: Array<{ type: string; text?: string }> }).content;\n\t\tconst textParts: string[] = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\" && part.text) {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t}\n\t\t}\n\t\tif (textParts.length > 0) {\n\t\t\treturn textParts.join(\"\\n\");\n\t\t}\n\t}\n\n\treturn JSON.stringify(result);\n}\n\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\tconst workspaceDir = join(channelDir, \"..\");\n\n\t// Create tools\n\tconst tools = createPipiclawTools(executor);\n\n\t// Initial system prompt\n\tconst soul = getSoul(workspaceDir);\n\tconst agentConfig = getAgentConfig(channelDir);\n\tconst memory = getMemory(channelDir);\n\tconst initialSkills = loadPipiclawSkills(channelDir, workspacePath);\n\tlet currentSkills = initialSkills;\n\tconst systemPrompt = buildSystemPrompt(\n\t\tworkspacePath,\n\t\tchannelId,\n\t\tsoul,\n\t\tagentConfig,\n\t\tmemory,\n\t\tsandboxConfig,\n\t\tinitialSkills,\n\t);\n\n\t// Create session manager\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\tconst sessionManager = SessionManager.open(contextFile, channelDir);\n\tconst settingsManager = new PipiclawSettingsManager(APP_HOME_DIR);\n\n\t// Create AuthStorage and ModelRegistry\n\tconst authStorage = AuthStorage.create(AUTH_CONFIG_PATH);\n\tconst modelRegistry = new ModelRegistry(authStorage, MODELS_CONFIG_PATH);\n\n\t// Resolve model: prefer saved global default, fall back to first available model\n\tlet activeModel = resolveInitialModel(modelRegistry, settingsManager);\n\tlog.logInfo(`Using model: ${activeModel.provider}/${activeModel.id} (${activeModel.name})`);\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: activeModel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tconvertToLlm,\n\t\tgetApiKey: async () => getApiKeyForModel(modelRegistry, activeModel),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.buildSessionContext();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\tcwd: process.cwd(),\n\t\tagentDir: APP_HOME_DIR,\n\t\tsettingsManager: settingsManager as any,\n\t\tskillsOverride: (base) => ({\n\t\t\tskills: [...base.skills, ...currentSkills],\n\t\t\tdiagnostics: base.diagnostics,\n\t\t}),\n\t});\n\n\tconst baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));\n\n\t// Create AgentSession\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager: settingsManager as any,\n\t\tcwd: process.cwd(),\n\t\tmodelRegistry,\n\t\tresourceLoader,\n\t\tbaseToolsOverride,\n\t});\n\n\t// Mutable per-run state\n\tconst runState: {\n\t\tctx: DingTalkContext | null;\n\t\tlogCtx: { channelId: string; userName?: string; channelName?: string } | null;\n\t\tqueue: {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null;\n\t\tpendingTools: Map<string, { toolName: string; args: unknown; startTime: number }>;\n\t\ttotalUsage: {\n\t\t\tinput: number;\n\t\t\toutput: number;\n\t\t\tcacheRead: number;\n\t\t\tcacheWrite: number;\n\t\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t\t};\n\t\tstopReason: string;\n\t\terrorMessage: string | undefined;\n\t\tfinalOutcome: FinalOutcome;\n\t\tfinalResponseDelivered: boolean;\n\t} = {\n\t\tctx: null as DingTalkContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\terrorMessage: undefined as string | undefined,\n\t\tfinalOutcome: { kind: \"none\" },\n\t\tfinalResponseDelivered: false,\n\t};\n\n\tconst sendCommandReply = async (ctx: DingTalkContext, text: string): Promise<void> => {\n\t\tconst delivered = await ctx.respondPlain(text);\n\t\tif (!delivered) {\n\t\t\tawait ctx.replaceMessage(text);\n\t\t\tawait ctx.flush();\n\t\t}\n\t};\n\n\tconst handleModelBuiltinCommand = async (ctx: DingTalkContext, args: string): Promise<void> => {\n\t\tmodelRegistry.refresh();\n\t\tconst availableModels = await modelRegistry.getAvailable();\n\t\tconst currentModel = session.model;\n\n\t\tif (!args.trim()) {\n\t\t\tconst current = currentModel ? `\\`${formatModelReference(currentModel)}\\`` : \"(none)\";\n\t\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel) : \"- (none)\";\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`# Model\n\nCurrent model: ${current}\n\nUse \\`/model <provider/modelId>\\` or \\`/model <modelId>\\` to switch. Bare model IDs must resolve uniquely.\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst match = findExactModelReferenceMatch(args, availableModels);\n\t\tif (match.match) {\n\t\t\tawait session.setModel(match.match);\n\t\t\tactiveModel = match.match;\n\t\t\tawait sendCommandReply(ctx, `已切换模型到 \\`${formatModelReference(match.match)}\\`。`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel, 10) : \"- (none)\";\n\t\tif (match.ambiguous) {\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`未切换模型:\\`${args.trim()}\\` 匹配到多个模型。请改用精确的 \\`provider/modelId\\` 形式。\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tawait sendCommandReply(\n\t\t\tctx,\n\t\t\t`未找到模型 \\`${args.trim()}\\`。请使用精确的 \\`provider/modelId\\` 或唯一的 \\`modelId\\`。\n\nAvailable models:\n${available}`,\n\t\t);\n\t};\n\n\tconst handleBuiltInCommand = async (ctx: DingTalkContext, command: BuiltInCommand): Promise<void> => {\n\t\ttry {\n\t\t\tswitch (command.name) {\n\t\t\t\tcase \"help\":\n\t\t\t\t\tawait sendCommandReply(ctx, renderBuiltInHelp());\n\t\t\t\t\treturn;\n\t\t\t\tcase \"new\": {\n\t\t\t\t\tconst completed = await session.newSession();\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tcompleted\n\t\t\t\t\t\t\t? `已开启新会话。\n\nSession ID: \\`${session.sessionId}\\``\n\t\t\t\t\t\t\t: \"新会话已取消。\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"compact\": {\n\t\t\t\t\tconst result = await session.compact(command.args || undefined);\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`已压缩当前会话上下文。\n\n- Tokens before compaction: \\`${result.tokensBefore}\\`\n- Summary:\n\n\\`\\`\\`text\n${result.summary}\n\\`\\`\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session\": {\n\t\t\t\t\tconst stats = session.getSessionStats();\n\t\t\t\t\tconst currentModel = session.model ? `\\`${formatModelReference(session.model)}\\`` : \"(none)\";\n\t\t\t\t\tconst sessionFile = stats.sessionFile ? `\\`${basename(stats.sessionFile)}\\`` : \"(none)\";\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`# Session\n\n- Session ID: \\`${stats.sessionId}\\`\n- Session file: ${sessionFile}\n- Model: ${currentModel}\n- Thinking level: \\`${session.thinkingLevel}\\`\n- User messages: \\`${stats.userMessages}\\`\n- Assistant messages: \\`${stats.assistantMessages}\\`\n- Tool calls: \\`${stats.toolCalls}\\`\n- Tool results: \\`${stats.toolResults}\\`\n- Total messages: \\`${stats.totalMessages}\\`\n- Tokens: \\`${stats.tokens.total}\\` (input \\`${stats.tokens.input}\\`, output \\`${stats.tokens.output}\\`, cache read \\`${stats.tokens.cacheRead}\\`, cache write \\`${stats.tokens.cacheWrite}\\`)\n- Cost: \\`$${stats.cost.toFixed(4)}\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"model\":\n\t\t\t\t\tawait handleModelBuiltinCommand(ctx, command.args);\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\tlog.logWarning(`[${channelId}] Built-in command failed`, errMsg);\n\t\t\tawait sendCommandReply(ctx, `命令执行失败:${errMsg}`);\n\t\t}\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event: any) => {\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"tool\", label), false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(\n\t\t\t\t\t() => ctx.respond(formatProgressEntry(\"error\", truncate(resultStr, 200)), false),\n\t\t\t\t\t\"tool error\",\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\t\t\t\tif (assistantMsg.errorMessage) {\n\t\t\t\t\trunState.errorMessage = assistantMsg.errorMessage;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tlet hasToolCalls = false;\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t} else if (part.type === \"toolCall\") {\n\t\t\t\t\t\thasToolCalls = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"thinking\", thinking), false), \"thinking\");\n\t\t\t\t}\n\n\t\t\t\tif (hasToolCalls && text.trim()) {\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"assistant\", text), false), \"assistant progress\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst turnEvent = event as any & {\n\t\t\t\ttype: \"turn_end\";\n\t\t\t\tmessage: { role: string; stopReason?: string; content: Array<{ type: string; text?: string }> };\n\t\t\t\ttoolResults: unknown[];\n\t\t\t};\n\t\t\tif (turnEvent.message.role === \"assistant\" && turnEvent.toolResults.length === 0) {\n\t\t\t\tif (turnEvent.message.stopReason === \"error\" || turnEvent.message.stopReason === \"aborted\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst finalContent = turnEvent.message.content as Array<{ type: string; text?: string }>;\n\t\t\t\tconst finalText = finalContent\n\t\t\t\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && !!part.text)\n\t\t\t\t\t.map((part) => part.text)\n\t\t\t\t\t.join(\"\\n\");\n\n\t\t\t\tconst trimmedFinalText = finalText.trim();\n\t\t\t\tif (!trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (trimmedFinalText === \"[SILENT]\" || trimmedFinalText.startsWith(\"[SILENT]\")) {\n\t\t\t\t\trunState.finalOutcome = { kind: \"silent\" };\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (runState.finalOutcome.kind === \"final\" && runState.finalOutcome.text.trim() === trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\trunState.finalOutcome = { kind: \"final\", text: finalText };\n\t\t\t\tlog.logResponse(logCtx, finalText);\n\t\t\t\tqueue.enqueue(async () => {\n\t\t\t\t\tconst delivered = await ctx.respondPlain(finalText);\n\t\t\t\t\tif (delivered) {\n\t\t\t\t\t\trunState.finalResponseDelivered = true;\n\t\t\t\t\t}\n\t\t\t\t}, \"final response\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(formatProgressEntry(\"assistant\", \"Compacting context...\"), false),\n\t\t\t\t\"compaction start\",\n\t\t\t);\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() =>\n\t\t\t\t\tctx.respond(\n\t\t\t\t\t\tformatProgressEntry(\"assistant\", `Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})...`),\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\treturn {\n\t\tasync handleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void> {\n\t\t\tawait handleBuiltInCommand(ctx, command);\n\t\t},\n\n\t\tasync run(ctx: DingTalkContext, _store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }> {\n\t\t\t// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.logCtx = {\n\t\t\t\tchannelId: ctx.message.channel,\n\t\t\t\tuserName: ctx.message.userName,\n\t\t\t\tchannelName: ctx.channelName,\n\t\t\t};\n\t\t\trunState.pendingTools.clear();\n\t\t\trunState.totalUsage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\t\t\trunState.errorMessage = undefined;\n\t\t\trunState.finalOutcome = { kind: \"none\" };\n\t\t\trunState.finalResponseDelivered = false;\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.then(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait fn();\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(`DingTalk API error (${errorContext})`, errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = true): void {\n\t\t\t\t\tthis.enqueue(\n\t\t\t\t\t\t() => (target === \"main\" ? ctx.respond(text, doLog) : ctx.respondInThread(text)),\n\t\t\t\t\t\terrorContext,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\t// Ensure channel directory exists\n\t\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t\t// Update system prompt and runtime resources with fresh config\n\t\t\t\tconst soul = getSoul(workspaceDir);\n\t\t\t\tconst agentConfig = getAgentConfig(channelDir);\n\t\t\t\tconst memory = getMemory(channelDir);\n\t\t\t\tconst skills = loadPipiclawSkills(channelDir, workspacePath);\n\t\t\t\tcurrentSkills = skills;\n\t\t\t\tconst systemPrompt = buildSystemPrompt(\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\tchannelId,\n\t\t\t\t\tsoul,\n\t\t\t\t\tagentConfig,\n\t\t\t\t\tmemory,\n\t\t\t\t\tsandboxConfig,\n\t\t\t\t\tskills,\n\t\t\t\t);\n\t\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\t\t\t\tawait session.reload();\n\n\t\t\t\t// Sync messages from log.jsonl\n\t\t\t\tconst syncedCount = syncLogToSessionManager(sessionManager, channelDir, ctx.message.ts);\n\t\t\t\tif (syncedCount > 0) {\n\t\t\t\t\tlog.logInfo(`[${channelId}] Synced ${syncedCount} messages from log.jsonl`);\n\t\t\t\t}\n\n\t\t\t\t// Reload messages from context.jsonl\n\t\t\t\tconst reloadedSession = sessionManager.buildSessionContext();\n\t\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t\t}\n\n\t\t\t\t// Log context info\n\t\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\n\t\t\t\t// Build user message with timestamp and username prefix\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst pad = (n: number) => n.toString().padStart(2, \"0\");\n\t\t\t\tconst offset = -now.getTimezoneOffset();\n\t\t\t\tconst offsetSign = offset >= 0 ? \"+\" : \"-\";\n\t\t\t\tconst offsetHours = pad(Math.floor(Math.abs(offset) / 60));\n\t\t\t\tconst offsetMins = pad(Math.abs(offset) % 60);\n\t\t\t\tconst timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`;\n\t\t\t\tconst userMessage = `[${timestamp}] [${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t\t// Debug: write context to last_prompt.json (only with PIPICLAW_DEBUG=1)\n\t\t\t\tif (process.env.PIPICLAW_DEBUG) {\n\t\t\t\t\tconst debugContext = {\n\t\t\t\t\t\tsystemPrompt,\n\t\t\t\t\t\tmessages: session.messages,\n\t\t\t\t\t\tnewUserMessage: userMessage,\n\t\t\t\t\t};\n\t\t\t\t\tawait writeFile(join(channelDir, \"last_prompt.json\"), JSON.stringify(debugContext, null, 2));\n\t\t\t\t}\n\n\t\t\t\tawait session.prompt(userMessage);\n\t\t\t} catch (err) {\n\t\t\t\trunState.stopReason = \"error\";\n\t\t\t\trunState.errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\t\tlog.logWarning(`[${channelId}] Runner failed`, runState.errorMessage);\n\t\t\t} finally {\n\t\t\t\tawait queueChain;\n\t\t\t\tconst finalOutcome = runState.finalOutcome;\n\t\t\t\tconst finalOutcomeText = getFinalOutcomeText(finalOutcome);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (runState.stopReason === \"error\" && runState.errorMessage && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(\"_Sorry, something went wrong_\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to post error message\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (isSilentOutcome(finalOutcome)) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.deleteMessage();\n\t\t\t\t\t\t\tlog.logInfo(\"Silent response - deleted message\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to delete message for silent response\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (finalOutcomeText && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(finalOutcomeText);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait ctx.flush();\n\t\t\t\t} finally {\n\t\t\t\t\tawait ctx.close();\n\t\t\t\t}\n\n\t\t\t\t// Log usage summary\n\t\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t\tconst messages = session.messages;\n\t\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t\t.slice()\n\t\t\t\t\t\t.reverse()\n\t\t\t\t\t\t.find((m: any) => m.role === \"assistant\" && m.stopReason !== \"aborted\") as any;\n\n\t\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst currentRunModel = session.model ?? activeModel;\n\t\t\t\t\tconst contextWindow = currentRunModel.contextWindow || 200000;\n\n\t\t\t\t\tlog.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\t}\n\n\t\t\t\t// Clear run state\n\t\t\t\trunState.ctx = null;\n\t\t\t\trunState.logCtx = null;\n\t\t\t\trunState.queue = null;\n\t\t\t}\n\n\t\t\treturn { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.abort();\n\t\t},\n\t};\n}\n\n/**\n * Translate container path back to host path for file operations\n */\nexport function translateToHostPath(\n\tcontainerPath: string,\n\tchannelDir: string,\n\tworkspacePath: string,\n\tchannelId: string,\n): string {\n\tif (workspacePath === \"/workspace\") {\n\t\tconst prefix = `/workspace/${channelId}/`;\n\t\tif (containerPath.startsWith(prefix)) {\n\t\t\treturn join(channelDir, containerPath.slice(prefix.length));\n\t\t}\n\t\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.d.ts","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,cAAc,EAAqB,MAAM,eAAe,CAAC;AAEvE,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAGrD,OAAO,EAAkB,KAAK,aAAa,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAM/C,MAAM,WAAW,WAAW;IAC3B,GAAG,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC;QAAE,UAAU,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvG,oBAAoB,CAAC,GAAG,EAAE,eAAe,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnF,KAAK,IAAI,IAAI,CAAC;CACd;AAqfD,wBAAgB,iBAAiB,CAAC,aAAa,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,WAAW,CAOlH;AA2kBD;;GAEG;AACH,wBAAgB,mBAAmB,CAClC,aAAa,EAAE,MAAM,EACrB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,GACf,MAAM,CAWR","sourcesContent":["import { Agent } from \"@mariozechner/pi-agent-core\";\nimport { type Api, getModel, type Model } from \"@mariozechner/pi-ai\";\nimport {\n\tAgentSession,\n\tAuthStorage,\n\tconvertToLlm,\n\tDefaultResourceLoader,\n\tformatSkillsForPrompt,\n\tloadSkillsFromDir,\n\tModelRegistry,\n\tSessionManager,\n\ttype Skill,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { basename, join } from \"path\";\nimport { type BuiltInCommand, renderBuiltInHelp } from \"./commands.js\";\nimport { PipiclawSettingsManager, syncLogToSessionManager } from \"./context.js\";\nimport type { DingTalkContext } from \"./dingtalk.js\";\nimport * as log from \"./log.js\";\nimport { APP_HOME_DIR, AUTH_CONFIG_PATH, MODELS_CONFIG_PATH } from \"./paths.js\";\nimport { createExecutor, type SandboxConfig } from \"./sandbox.js\";\nimport type { ChannelStore } from \"./store.js\";\nimport { createPipiclawTools } from \"./tools/index.js\";\n\n// Default model - will be overridden by ModelRegistry if custom models are configured\nconst defaultModel = getModel(\"anthropic\", \"claude-sonnet-4-5\");\n\nexport interface AgentRunner {\n\trun(ctx: DingTalkContext, store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }>;\n\thandleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void>;\n\tabort(): void;\n}\n\ntype FinalOutcome = { kind: \"none\" } | { kind: \"silent\" } | { kind: \"final\"; text: string };\n\nfunction isSilentOutcome(outcome: FinalOutcome): outcome is { kind: \"silent\" } {\n\treturn outcome.kind === \"silent\";\n}\n\nfunction isFinalOutcome(outcome: FinalOutcome): outcome is { kind: \"final\"; text: string } {\n\treturn outcome.kind === \"final\";\n}\n\nfunction getFinalOutcomeText(outcome: FinalOutcome): string | null {\n\treturn isFinalOutcome(outcome) ? outcome.text : null;\n}\n\nfunction formatModelReference(model: Model<Api>): string {\n\treturn `${model.provider}/${model.id}`;\n}\n\nfunction findExactModelReferenceMatch(\n\tmodelReference: string,\n\tavailableModels: Model<Api>[],\n): { match?: Model<Api>; ambiguous: boolean } {\n\tconst trimmedReference = modelReference.trim();\n\tif (!trimmedReference) {\n\t\treturn { ambiguous: false };\n\t}\n\n\tconst normalizedReference = trimmedReference.toLowerCase();\n\n\tconst canonicalMatches = availableModels.filter(\n\t\t(model) => `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n\t);\n\tif (canonicalMatches.length === 1) {\n\t\treturn { match: canonicalMatches[0], ambiguous: false };\n\t}\n\tif (canonicalMatches.length > 1) {\n\t\treturn { ambiguous: true };\n\t}\n\n\tconst slashIndex = trimmedReference.indexOf(\"/\");\n\tif (slashIndex !== -1) {\n\t\tconst provider = trimmedReference.substring(0, slashIndex).trim();\n\t\tconst modelId = trimmedReference.substring(slashIndex + 1).trim();\n\t\tif (provider && modelId) {\n\t\t\tconst providerMatches = availableModels.filter(\n\t\t\t\t(model) =>\n\t\t\t\t\tmodel.provider.toLowerCase() === provider.toLowerCase() &&\n\t\t\t\t\tmodel.id.toLowerCase() === modelId.toLowerCase(),\n\t\t\t);\n\t\t\tif (providerMatches.length === 1) {\n\t\t\t\treturn { match: providerMatches[0], ambiguous: false };\n\t\t\t}\n\t\t\tif (providerMatches.length > 1) {\n\t\t\t\treturn { ambiguous: true };\n\t\t\t}\n\t\t}\n\t}\n\n\tconst idMatches = availableModels.filter((model) => model.id.toLowerCase() === normalizedReference);\n\tif (idMatches.length === 1) {\n\t\treturn { match: idMatches[0], ambiguous: false };\n\t}\n\n\treturn { ambiguous: idMatches.length > 1 };\n}\n\nfunction formatModelList(models: Model<Api>[], currentModel: Model<Api> | undefined, limit: number = 20): string {\n\tconst refs = models\n\t\t.slice()\n\t\t.sort((a, b) => formatModelReference(a).localeCompare(formatModelReference(b)))\n\t\t.map((model) => {\n\t\t\tconst ref = formatModelReference(model);\n\t\t\tconst marker =\n\t\t\t\tcurrentModel && currentModel.provider === model.provider && currentModel.id === model.id\n\t\t\t\t\t? \" (current)\"\n\t\t\t\t\t: \"\";\n\t\t\treturn `- \\`${ref}\\`${marker}`;\n\t\t});\n\n\tif (refs.length <= limit) {\n\t\treturn refs.join(\"\\n\");\n\t}\n\n\treturn `${refs.slice(0, limit).join(\"\\n\")}\\n- ... and ${refs.length - limit} more`;\n}\n\nfunction resolveInitialModel(modelRegistry: ModelRegistry, settingsManager: PipiclawSettingsManager): Model<Api> {\n\tconst savedProvider = settingsManager.getDefaultProvider();\n\tconst savedModelId = settingsManager.getDefaultModel();\n\tconst availableModels = modelRegistry.getAvailable();\n\tif (savedProvider && savedModelId) {\n\t\tconst savedModel = modelRegistry.find(savedProvider, savedModelId);\n\t\tif (\n\t\t\tsavedModel &&\n\t\t\tavailableModels.some((model) => model.provider === savedModel.provider && model.id === savedModel.id)\n\t\t) {\n\t\t\treturn savedModel;\n\t\t}\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn defaultModel;\n}\n\nasync function getApiKeyForModel(modelRegistry: ModelRegistry, model: any): Promise<string> {\n\tconst key = await modelRegistry.getApiKeyForProvider(model.provider);\n\tif (key) return key;\n\t// Fallback: try anthropic env var\n\tconst envKey = process.env.ANTHROPIC_API_KEY;\n\tif (envKey) return envKey;\n\tthrow new Error(\n\t\t`No API key found for provider: ${model.provider}.\\n\\n` +\n\t\t\t\"Configure API key in ~/.pi/agent/models.json or set ANTHROPIC_API_KEY environment variable.\",\n\t);\n}\n\n// ============================================================================\n// Configuration file loaders: SOUL.md, AGENTS.md, MEMORY.md\n// ============================================================================\n\n/**\n * Load SOUL.md — defines the agent's identity, personality, and communication style.\n * Only loaded from workspace root (global).\n */\nfunction getSoul(workspaceDir: string): string {\n\tconst soulPath = join(workspaceDir, \"SOUL.md\");\n\tif (existsSync(soulPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(soulPath, \"utf-8\").trim();\n\t\t\tif (content) return content;\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read SOUL.md\", `${soulPath}: ${error}`);\n\t\t}\n\t}\n\treturn \"\";\n}\n\n/**\n * Load AGENTS.md — defines the agent's behavior instructions, capabilities, and constraints.\n * Supports both global (workspace root) and channel-level override.\n */\nfunction getAgentConfig(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level AGENTS.md (global)\n\tconst workspaceAgentPath = join(channelDir, \"..\", \"AGENTS.md\");\n\tif (existsSync(workspaceAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace AGENTS.md\", `${workspaceAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific AGENTS.md (overrides/extends global)\n\tconst channelAgentPath = join(channelDir, \"AGENTS.md\");\n\tif (existsSync(channelAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel AGENTS.md\", `${channelAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction getMemory(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level memory (shared across all channels)\n\tconst workspaceMemoryPath = join(channelDir, \"..\", \"MEMORY.md\");\n\tif (existsSync(workspaceMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Global Workspace Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific memory\n\tconst channelMemoryPath = join(channelDir, \"MEMORY.md\");\n\tif (existsSync(channelMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Channel-Specific Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel memory\", `${channelMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\tif (parts.length === 0) {\n\t\treturn \"(no working memory yet)\";\n\t}\n\n\tconst combined = parts.join(\"\\n\\n\");\n\n\t// Warn if memory is getting too large (consumes system prompt token budget)\n\tif (combined.length > 5000) {\n\t\treturn `\\u26a0\\ufe0f Memory is large (${combined.length} chars). Consolidate: remove outdated entries, merge duplicates, tighten descriptions.\\n\\n${combined}`;\n\t}\n\n\treturn combined;\n}\n\nfunction loadPipiclawSkills(channelDir: string, workspacePath: string): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\tconst hostWorkspacePath = join(channelDir, \"..\");\n\n\tconst translatePath = (hostPath: string): string => {\n\t\tif (hostPath.startsWith(hostWorkspacePath)) {\n\t\t\treturn workspacePath + hostPath.slice(hostWorkspacePath.length);\n\t\t}\n\t\treturn hostPath;\n\t};\n\n\t// Load workspace-level skills (global)\n\tconst workspaceSkillsDir = join(hostWorkspacePath, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: \"workspace\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Load channel-specific skills\n\tconst channelSkillsDir = join(channelDir, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: channelSkillsDir, source: \"channel\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n\n// ============================================================================\n// System Prompt Builder\n// ============================================================================\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\tsoul: string,\n\tagentConfig: string,\n\tmemory: string,\n\tsandboxConfig: SandboxConfig,\n\tskills: Skill[],\n): string {\n\tconst channelPath = `${workspacePath}/${channelId}`;\n\tconst isDocker = sandboxConfig.type === \"docker\";\n\n\tconst envDescription = isDocker\n\t\t? `You are running inside a Docker container (Alpine Linux).\n- Bash working directory: / (use cd or absolute paths)\n- Install tools with: apk add <package>\n- Your changes persist across sessions`\n\t\t: `You are running directly on the host machine.\n- Bash working directory: ${process.cwd()}\n- Be careful with system modifications`;\n\n\t// Build system prompt with configuration file layering:\n\t// 1. SOUL.md (identity/personality)\n\t// 2. Core instructions\n\t// 3. AGENTS.md (behavior instructions)\n\t// 4. Skills, Events, Memory\n\n\tconst sections: string[] = [];\n\n\t// 1. SOUL.md — Agent identity\n\tif (soul) {\n\t\tsections.push(soul);\n\t} else {\n\t\tsections.push(\"You are a DingTalk bot assistant. Be concise and helpful.\");\n\t}\n\n\t// 2. Core instructions\n\tsections.push(`## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\n\n## Formatting\nUse Markdown for formatting. DingTalk AI Card supports basic Markdown:\nBold: **text**, Italic: *text*, Code: \\`code\\`, Block: \\`\\`\\`code\\`\\`\\`, Links: [text](url)\n\n## Environment\n${envDescription}\n\n## Workspace Layout\n${workspacePath}/\n├── SOUL.md # Your identity/personality (read-only)\n├── AGENTS.md # Custom behavior instructions (read-only)\n├── MEMORY.md # Global memory (all channels, you can read/write)\n├── skills/ # Global CLI tools you create\n├── events/ # Scheduled events\n└── ${channelId}/ # This channel\n ├── AGENTS.md # Channel-specific instructions (read-only)\n ├── MEMORY.md # Channel-specific memory (you can read/write)\n ├── log.jsonl # Message history (no tool results)\n ├── scratch/ # Your working directory\n └── skills/ # Channel-specific tools`);\n\n\t// 3. AGENTS.md — User-defined instructions\n\tif (agentConfig) {\n\t\tsections.push(`## Agent Instructions\\n${agentConfig}`);\n\t}\n\n\t// 4. Skills\n\tsections.push(`## Skills (Custom CLI Tools)\nYou can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).\n\n### Creating Skills\nStore in \\`${workspacePath}/skills/<name>/\\` (global) or \\`${channelPath}/skills/<name>/\\` (channel-specific).\nEach skill directory needs a \\`SKILL.md\\` with YAML frontmatter:\n\n\\`\\`\\`markdown\n---\nname: skill-name\ndescription: Short description of what this skill does\n---\n\n# Skill Name\n\nUsage instructions, examples, etc.\nScripts are in: {baseDir}/\n\\`\\`\\`\n\n\\`name\\` and \\`description\\` are required. Use \\`{baseDir}\\` as placeholder for the skill's directory path.\n\n### Available Skills\n${skills.length > 0 ? formatSkillsForPrompt(skills) : \"(no skills installed yet)\"}`);\n\n\t// 5. Events\n\tsections.push(`## Events\nYou can schedule events that wake you up at specific times or when external things happen. Events are JSON files in \\`${workspacePath}/events/\\`.\n\n### Event Types\n\n**Immediate** - Triggers as soon as harness sees the file.\n\\`\\`\\`json\n{\"type\": \"immediate\", \"channelId\": \"${channelId}\", \"text\": \"New event occurred\"}\n\\`\\`\\`\n\n**One-shot** - Triggers once at a specific time.\n\\`\\`\\`json\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder\", \"at\": \"2025-12-15T09:00:00+08:00\"}\n\\`\\`\\`\n\n**Periodic** - Triggers on a cron schedule.\n\\`\\`\\`json\n{\"type\": \"periodic\", \"channelId\": \"${channelId}\", \"text\": \"Check inbox\", \"schedule\": \"0 9 * * 1-5\", \"timezone\": \"${Intl.DateTimeFormat().resolvedOptions().timeZone}\"}\n\\`\\`\\`\n\n### Cron Format\n\\`minute hour day-of-month month day-of-week\\`\n\n### Creating Events\n\\`\\`\\`bash\ncat > ${workspacePath}/events/reminder-$(date +%s).json << 'EOF'\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder text\", \"at\": \"2025-12-14T09:00:00+08:00\"}\nEOF\n\\`\\`\\`\n\n### Silent Completion\nFor periodic events where there's nothing to report, respond with just \\`[SILENT]\\`. This deletes the status message. Use this to avoid spam when periodic checks find nothing.\n\n### Limits\nMaximum 5 events can be queued.`);\n\n\t// 6. Memory\n\tsections.push(`## Memory\nWrite to MEMORY.md files to persist context across conversations.\n- Global (${workspacePath}/MEMORY.md): skills, preferences, project info\n- Channel (${channelPath}/MEMORY.md): channel-specific decisions, ongoing work\n\n### Guidelines\n- Keep each MEMORY.md concise (target: under 50 lines)\n- Use clear headers to organize entries (## Preferences, ## Projects, etc.)\n- Remove outdated entries when they are no longer relevant\n- Merge duplicate or redundant items\n- Prefer structured formats (lists, key-value pairs) over prose\n- Update when you learn something important or when asked to remember something\n\n### Current Memory\n${memory}`);\n\n\t// 7. System Configuration Log\n\tsections.push(`## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment.`);\n\n\t// 8. Tools\n\tsections.push(`## Tools\n- bash: Run shell commands (primary tool). Install packages as needed.\n- read: Read files\n- write: Create/overwrite files\n- edit: Surgical file edits\n- attach: Share files (note: DingTalk file sharing is limited, output as text when possible)\n\nEach tool requires a \"label\" parameter (shown to user).`);\n\n\t// 9. Log Queries\n\tsections.push(`## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\\`\\`\\``);\n\n\treturn sections.join(\"\\n\\n\");\n}\n\n// ============================================================================\n// Agent Runner\n// ============================================================================\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn `${text.substring(0, maxLen - 3)}...`;\n}\n\nfunction sanitizeProgressText(text: string): string {\n\treturn text\n\t\t.replace(/\\uFFFC/g, \"\")\n\t\t.replace(/\\r/g, \"\")\n\t\t.trim();\n}\n\nfunction formatProgressEntry(kind: \"tool\" | \"thinking\" | \"error\" | \"assistant\", text: string): string {\n\tconst cleaned = sanitizeProgressText(text);\n\tif (!cleaned) return \"\";\n\n\tconst normalized = cleaned.replace(/\\n+/g, \" \").trim();\n\tswitch (kind) {\n\t\tcase \"tool\":\n\t\t\treturn `Running: ${normalized}`;\n\t\tcase \"thinking\":\n\t\t\treturn `Thinking: ${normalized}`;\n\t\tcase \"error\":\n\t\t\treturn `Error: ${normalized}`;\n\t\tcase \"assistant\":\n\t\t\treturn normalized;\n\t}\n}\n\nfunction extractToolResultText(result: unknown): string {\n\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\n\tif (\n\t\tresult &&\n\t\ttypeof result === \"object\" &&\n\t\t\"content\" in result &&\n\t\tArray.isArray((result as { content: unknown }).content)\n\t) {\n\t\tconst content = (result as { content: Array<{ type: string; text?: string }> }).content;\n\t\tconst textParts: string[] = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\" && part.text) {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t}\n\t\t}\n\t\tif (textParts.length > 0) {\n\t\t\treturn textParts.join(\"\\n\");\n\t\t}\n\t}\n\n\treturn JSON.stringify(result);\n}\n\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\tconst workspaceDir = join(channelDir, \"..\");\n\n\t// Create tools\n\tconst tools = createPipiclawTools(executor);\n\n\t// Initial system prompt\n\tconst soul = getSoul(workspaceDir);\n\tconst agentConfig = getAgentConfig(channelDir);\n\tconst memory = getMemory(channelDir);\n\tconst initialSkills = loadPipiclawSkills(channelDir, workspacePath);\n\tlet currentSkills = initialSkills;\n\tconst systemPrompt = buildSystemPrompt(\n\t\tworkspacePath,\n\t\tchannelId,\n\t\tsoul,\n\t\tagentConfig,\n\t\tmemory,\n\t\tsandboxConfig,\n\t\tinitialSkills,\n\t);\n\n\t// Create session manager\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\tconst sessionManager = SessionManager.open(contextFile, channelDir);\n\tconst settingsManager = new PipiclawSettingsManager(APP_HOME_DIR);\n\n\t// Create AuthStorage and ModelRegistry\n\tconst authStorage = AuthStorage.create(AUTH_CONFIG_PATH);\n\tconst modelRegistry = new ModelRegistry(authStorage, MODELS_CONFIG_PATH);\n\n\t// Resolve model: prefer saved global default, fall back to first available model\n\tlet activeModel = resolveInitialModel(modelRegistry, settingsManager);\n\tlog.logInfo(`Using model: ${activeModel.provider}/${activeModel.id} (${activeModel.name})`);\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: activeModel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tconvertToLlm,\n\t\tgetApiKey: async () => getApiKeyForModel(modelRegistry, activeModel),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.buildSessionContext();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\tcwd: process.cwd(),\n\t\tagentDir: APP_HOME_DIR,\n\t\tsettingsManager: settingsManager as any,\n\t\tskillsOverride: (base) => ({\n\t\t\tskills: [...base.skills, ...currentSkills],\n\t\t\tdiagnostics: base.diagnostics,\n\t\t}),\n\t});\n\n\tconst baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));\n\n\t// Create AgentSession\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager: settingsManager as any,\n\t\tcwd: process.cwd(),\n\t\tmodelRegistry,\n\t\tresourceLoader,\n\t\tbaseToolsOverride,\n\t});\n\n\t// Mutable per-run state\n\tconst runState: {\n\t\tctx: DingTalkContext | null;\n\t\tlogCtx: { channelId: string; userName?: string; channelName?: string } | null;\n\t\tqueue: {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null;\n\t\tpendingTools: Map<string, { toolName: string; args: unknown; startTime: number }>;\n\t\ttotalUsage: {\n\t\t\tinput: number;\n\t\t\toutput: number;\n\t\t\tcacheRead: number;\n\t\t\tcacheWrite: number;\n\t\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t\t};\n\t\tstopReason: string;\n\t\terrorMessage: string | undefined;\n\t\tfinalOutcome: FinalOutcome;\n\t\tfinalResponseDelivered: boolean;\n\t} = {\n\t\tctx: null as DingTalkContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\terrorMessage: undefined as string | undefined,\n\t\tfinalOutcome: { kind: \"none\" },\n\t\tfinalResponseDelivered: false,\n\t};\n\n\tconst sendCommandReply = async (ctx: DingTalkContext, text: string): Promise<void> => {\n\t\tconst delivered = await ctx.respondPlain(text);\n\t\tif (!delivered) {\n\t\t\tawait ctx.replaceMessage(text);\n\t\t\tawait ctx.flush();\n\t\t}\n\t};\n\n\tconst handleModelBuiltinCommand = async (ctx: DingTalkContext, args: string): Promise<void> => {\n\t\tmodelRegistry.refresh();\n\t\tconst availableModels = await modelRegistry.getAvailable();\n\t\tconst currentModel = session.model;\n\n\t\tif (!args.trim()) {\n\t\t\tconst current = currentModel ? `\\`${formatModelReference(currentModel)}\\`` : \"(none)\";\n\t\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel) : \"- (none)\";\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`# Model\n\nCurrent model: ${current}\n\nUse \\`/model <provider/modelId>\\` or \\`/model <modelId>\\` to switch. Bare model IDs must resolve uniquely.\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst match = findExactModelReferenceMatch(args, availableModels);\n\t\tif (match.match) {\n\t\t\tawait session.setModel(match.match);\n\t\t\tactiveModel = match.match;\n\t\t\tawait sendCommandReply(ctx, `已切换模型到 \\`${formatModelReference(match.match)}\\`。`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel, 10) : \"- (none)\";\n\t\tif (match.ambiguous) {\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`未切换模型:\\`${args.trim()}\\` 匹配到多个模型。请改用精确的 \\`provider/modelId\\` 形式。\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tawait sendCommandReply(\n\t\t\tctx,\n\t\t\t`未找到模型 \\`${args.trim()}\\`。请使用精确的 \\`provider/modelId\\` 或唯一的 \\`modelId\\`。\n\nAvailable models:\n${available}`,\n\t\t);\n\t};\n\n\tconst handleBuiltInCommand = async (ctx: DingTalkContext, command: BuiltInCommand): Promise<void> => {\n\t\ttry {\n\t\t\tswitch (command.name) {\n\t\t\t\tcase \"help\":\n\t\t\t\t\tawait sendCommandReply(ctx, renderBuiltInHelp());\n\t\t\t\t\treturn;\n\t\t\t\tcase \"new\": {\n\t\t\t\t\tconst completed = await session.newSession();\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tcompleted\n\t\t\t\t\t\t\t? `已开启新会话。\n\nSession ID: \\`${session.sessionId}\\``\n\t\t\t\t\t\t\t: \"新会话已取消。\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"compact\": {\n\t\t\t\t\tconst result = await session.compact(command.args || undefined);\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`已压缩当前会话上下文。\n\n- Tokens before compaction: \\`${result.tokensBefore}\\`\n- Summary:\n\n\\`\\`\\`text\n${result.summary}\n\\`\\`\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session\": {\n\t\t\t\t\tconst stats = session.getSessionStats();\n\t\t\t\t\tconst currentModel = session.model ? `\\`${formatModelReference(session.model)}\\`` : \"(none)\";\n\t\t\t\t\tconst sessionFile = stats.sessionFile ? `\\`${basename(stats.sessionFile)}\\`` : \"(none)\";\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`# Session\n\n- Session ID: \\`${stats.sessionId}\\`\n- Session file: ${sessionFile}\n- Model: ${currentModel}\n- Thinking level: \\`${session.thinkingLevel}\\`\n- User messages: \\`${stats.userMessages}\\`\n- Assistant messages: \\`${stats.assistantMessages}\\`\n- Tool calls: \\`${stats.toolCalls}\\`\n- Tool results: \\`${stats.toolResults}\\`\n- Total messages: \\`${stats.totalMessages}\\`\n- Tokens: \\`${stats.tokens.total}\\` (input \\`${stats.tokens.input}\\`, output \\`${stats.tokens.output}\\`, cache read \\`${stats.tokens.cacheRead}\\`, cache write \\`${stats.tokens.cacheWrite}\\`)\n- Cost: \\`$${stats.cost.toFixed(4)}\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"model\":\n\t\t\t\t\tawait handleModelBuiltinCommand(ctx, command.args);\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\tlog.logWarning(`[${channelId}] Built-in command failed`, errMsg);\n\t\t\tawait sendCommandReply(ctx, `命令执行失败:${errMsg}`);\n\t\t}\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event: any) => {\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"tool\", label), false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(\n\t\t\t\t\t() => ctx.respond(formatProgressEntry(\"error\", truncate(resultStr, 200)), false),\n\t\t\t\t\t\"tool error\",\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\t\t\t\tif (assistantMsg.errorMessage) {\n\t\t\t\t\trunState.errorMessage = assistantMsg.errorMessage;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tlet hasToolCalls = false;\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t} else if (part.type === \"toolCall\") {\n\t\t\t\t\t\thasToolCalls = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"thinking\", thinking), false), \"thinking\");\n\t\t\t\t}\n\n\t\t\t\tif (hasToolCalls && text.trim()) {\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"assistant\", text), false), \"assistant progress\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst turnEvent = event as any & {\n\t\t\t\ttype: \"turn_end\";\n\t\t\t\tmessage: { role: string; stopReason?: string; content: Array<{ type: string; text?: string }> };\n\t\t\t\ttoolResults: unknown[];\n\t\t\t};\n\t\t\tif (turnEvent.message.role === \"assistant\" && turnEvent.toolResults.length === 0) {\n\t\t\t\tif (turnEvent.message.stopReason === \"error\" || turnEvent.message.stopReason === \"aborted\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst finalContent = turnEvent.message.content as Array<{ type: string; text?: string }>;\n\t\t\t\tconst finalText = finalContent\n\t\t\t\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && !!part.text)\n\t\t\t\t\t.map((part) => part.text)\n\t\t\t\t\t.join(\"\\n\");\n\n\t\t\t\tconst trimmedFinalText = finalText.trim();\n\t\t\t\tif (!trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (trimmedFinalText === \"[SILENT]\" || trimmedFinalText.startsWith(\"[SILENT]\")) {\n\t\t\t\t\trunState.finalOutcome = { kind: \"silent\" };\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (runState.finalOutcome.kind === \"final\" && runState.finalOutcome.text.trim() === trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\trunState.finalOutcome = { kind: \"final\", text: finalText };\n\t\t\t\tlog.logResponse(logCtx, finalText);\n\t\t\t\tqueue.enqueue(async () => {\n\t\t\t\t\tconst delivered = await ctx.respondPlain(finalText);\n\t\t\t\t\tif (delivered) {\n\t\t\t\t\t\trunState.finalResponseDelivered = true;\n\t\t\t\t\t}\n\t\t\t\t}, \"final response\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(formatProgressEntry(\"assistant\", \"Compacting context...\"), false),\n\t\t\t\t\"compaction start\",\n\t\t\t);\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() =>\n\t\t\t\t\tctx.respond(\n\t\t\t\t\t\tformatProgressEntry(\"assistant\", `Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})...`),\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\treturn {\n\t\tasync handleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void> {\n\t\t\tawait handleBuiltInCommand(ctx, command);\n\t\t},\n\n\t\tasync run(ctx: DingTalkContext, _store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }> {\n\t\t\t// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.logCtx = {\n\t\t\t\tchannelId: ctx.message.channel,\n\t\t\t\tuserName: ctx.message.userName,\n\t\t\t\tchannelName: ctx.channelName,\n\t\t\t};\n\t\t\trunState.pendingTools.clear();\n\t\t\trunState.totalUsage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\t\t\trunState.errorMessage = undefined;\n\t\t\trunState.finalOutcome = { kind: \"none\" };\n\t\t\trunState.finalResponseDelivered = false;\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.then(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait fn();\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(`DingTalk API error (${errorContext})`, errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = true): void {\n\t\t\t\t\tthis.enqueue(\n\t\t\t\t\t\t() => (target === \"main\" ? ctx.respond(text, doLog) : ctx.respondInThread(text)),\n\t\t\t\t\t\terrorContext,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\t// Ensure channel directory exists\n\t\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t\t// Update system prompt and runtime resources with fresh config\n\t\t\t\tconst soul = getSoul(workspaceDir);\n\t\t\t\tconst agentConfig = getAgentConfig(channelDir);\n\t\t\t\tconst memory = getMemory(channelDir);\n\t\t\t\tconst skills = loadPipiclawSkills(channelDir, workspacePath);\n\t\t\t\tcurrentSkills = skills;\n\t\t\t\tconst systemPrompt = buildSystemPrompt(\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\tchannelId,\n\t\t\t\t\tsoul,\n\t\t\t\t\tagentConfig,\n\t\t\t\t\tmemory,\n\t\t\t\t\tsandboxConfig,\n\t\t\t\t\tskills,\n\t\t\t\t);\n\t\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\t\t\t\tawait session.reload();\n\n\t\t\t\t// Sync messages from log.jsonl\n\t\t\t\tconst syncedCount = syncLogToSessionManager(sessionManager, channelDir, ctx.message.ts);\n\t\t\t\tif (syncedCount > 0) {\n\t\t\t\t\tlog.logInfo(`[${channelId}] Synced ${syncedCount} messages from log.jsonl`);\n\t\t\t\t}\n\n\t\t\t\t// Reload messages from context.jsonl\n\t\t\t\tconst reloadedSession = sessionManager.buildSessionContext();\n\t\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t\t}\n\n\t\t\t\t// Log context info\n\t\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\n\t\t\t\t// Build user message with timestamp and username prefix\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst pad = (n: number) => n.toString().padStart(2, \"0\");\n\t\t\t\tconst offset = -now.getTimezoneOffset();\n\t\t\t\tconst offsetSign = offset >= 0 ? \"+\" : \"-\";\n\t\t\t\tconst offsetHours = pad(Math.floor(Math.abs(offset) / 60));\n\t\t\t\tconst offsetMins = pad(Math.abs(offset) % 60);\n\t\t\t\tconst timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`;\n\t\t\t\tconst userMessage = `[${timestamp}] [${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t\t// Debug: write context to last_prompt.json (only with PIPICLAW_DEBUG=1)\n\t\t\t\tif (process.env.PIPICLAW_DEBUG) {\n\t\t\t\t\tconst debugContext = {\n\t\t\t\t\t\tsystemPrompt,\n\t\t\t\t\t\tmessages: session.messages,\n\t\t\t\t\t\tnewUserMessage: userMessage,\n\t\t\t\t\t};\n\t\t\t\t\tawait writeFile(join(channelDir, \"last_prompt.json\"), JSON.stringify(debugContext, null, 2));\n\t\t\t\t}\n\n\t\t\t\tawait session.prompt(userMessage);\n\t\t\t} catch (err) {\n\t\t\t\trunState.stopReason = \"error\";\n\t\t\t\trunState.errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\t\tlog.logWarning(`[${channelId}] Runner failed`, runState.errorMessage);\n\t\t\t} finally {\n\t\t\t\tawait queueChain;\n\t\t\t\tconst finalOutcome = runState.finalOutcome;\n\t\t\t\tconst finalOutcomeText = getFinalOutcomeText(finalOutcome);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (runState.stopReason === \"error\" && runState.errorMessage && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(\"_Sorry, something went wrong_\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to post error message\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (isSilentOutcome(finalOutcome)) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.deleteMessage();\n\t\t\t\t\t\t\tlog.logInfo(\"Silent response - deleted message\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to delete message for silent response\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (finalOutcomeText && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(finalOutcomeText);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait ctx.flush();\n\t\t\t\t} finally {\n\t\t\t\t\tawait ctx.close();\n\t\t\t\t}\n\n\t\t\t\t// Log usage summary\n\t\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t\tconst messages = session.messages;\n\t\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t\t.slice()\n\t\t\t\t\t\t.reverse()\n\t\t\t\t\t\t.find((m: any) => m.role === \"assistant\" && m.stopReason !== \"aborted\") as any;\n\n\t\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst currentRunModel = session.model ?? activeModel;\n\t\t\t\t\tconst contextWindow = currentRunModel.contextWindow || 200000;\n\n\t\t\t\t\tlog.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\t}\n\n\t\t\t\t// Clear run state\n\t\t\t\trunState.ctx = null;\n\t\t\t\trunState.logCtx = null;\n\t\t\t\trunState.queue = null;\n\t\t\t}\n\n\t\t\treturn { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.abort();\n\t\t},\n\t};\n}\n\n/**\n * Translate container path back to host path for file operations\n */\nexport function translateToHostPath(\n\tcontainerPath: string,\n\tchannelDir: string,\n\tworkspacePath: string,\n\tchannelId: string,\n): string {\n\tif (workspacePath === \"/workspace\") {\n\t\tconst prefix = `/workspace/${channelId}/`;\n\t\tif (containerPath.startsWith(prefix)) {\n\t\t\treturn join(channelDir, containerPath.slice(prefix.length));\n\t\t}\n\t\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
package/dist/agent.js
CHANGED
|
@@ -102,7 +102,7 @@ async function getApiKeyForModel(modelRegistry, model) {
|
|
|
102
102
|
"Configure API key in ~/.pi/agent/models.json or set ANTHROPIC_API_KEY environment variable.");
|
|
103
103
|
}
|
|
104
104
|
// ============================================================================
|
|
105
|
-
// Configuration file loaders: SOUL.md,
|
|
105
|
+
// Configuration file loaders: SOUL.md, AGENTS.md, MEMORY.md
|
|
106
106
|
// ============================================================================
|
|
107
107
|
/**
|
|
108
108
|
* Load SOUL.md — defines the agent's identity, personality, and communication style.
|
|
@@ -123,13 +123,13 @@ function getSoul(workspaceDir) {
|
|
|
123
123
|
return "";
|
|
124
124
|
}
|
|
125
125
|
/**
|
|
126
|
-
* Load
|
|
126
|
+
* Load AGENTS.md — defines the agent's behavior instructions, capabilities, and constraints.
|
|
127
127
|
* Supports both global (workspace root) and channel-level override.
|
|
128
128
|
*/
|
|
129
129
|
function getAgentConfig(channelDir) {
|
|
130
130
|
const parts = [];
|
|
131
|
-
// Read workspace-level
|
|
132
|
-
const workspaceAgentPath = join(channelDir, "..", "
|
|
131
|
+
// Read workspace-level AGENTS.md (global)
|
|
132
|
+
const workspaceAgentPath = join(channelDir, "..", "AGENTS.md");
|
|
133
133
|
if (existsSync(workspaceAgentPath)) {
|
|
134
134
|
try {
|
|
135
135
|
const content = readFileSync(workspaceAgentPath, "utf-8").trim();
|
|
@@ -138,11 +138,11 @@ function getAgentConfig(channelDir) {
|
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
catch (error) {
|
|
141
|
-
log.logWarning("Failed to read workspace
|
|
141
|
+
log.logWarning("Failed to read workspace AGENTS.md", `${workspaceAgentPath}: ${error}`);
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
-
// Read channel-specific
|
|
145
|
-
const channelAgentPath = join(channelDir, "
|
|
144
|
+
// Read channel-specific AGENTS.md (overrides/extends global)
|
|
145
|
+
const channelAgentPath = join(channelDir, "AGENTS.md");
|
|
146
146
|
if (existsSync(channelAgentPath)) {
|
|
147
147
|
try {
|
|
148
148
|
const content = readFileSync(channelAgentPath, "utf-8").trim();
|
|
@@ -151,7 +151,7 @@ function getAgentConfig(channelDir) {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
catch (error) {
|
|
154
|
-
log.logWarning("Failed to read channel
|
|
154
|
+
log.logWarning("Failed to read channel AGENTS.md", `${channelAgentPath}: ${error}`);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
return parts.join("\n\n");
|
|
@@ -236,7 +236,7 @@ function buildSystemPrompt(workspacePath, channelId, soul, agentConfig, memory,
|
|
|
236
236
|
// Build system prompt with configuration file layering:
|
|
237
237
|
// 1. SOUL.md (identity/personality)
|
|
238
238
|
// 2. Core instructions
|
|
239
|
-
// 3.
|
|
239
|
+
// 3. AGENTS.md (behavior instructions)
|
|
240
240
|
// 4. Skills, Events, Memory
|
|
241
241
|
const sections = [];
|
|
242
242
|
// 1. SOUL.md — Agent identity
|
|
@@ -262,17 +262,17 @@ ${envDescription}
|
|
|
262
262
|
## Workspace Layout
|
|
263
263
|
${workspacePath}/
|
|
264
264
|
├── SOUL.md # Your identity/personality (read-only)
|
|
265
|
-
├──
|
|
265
|
+
├── AGENTS.md # Custom behavior instructions (read-only)
|
|
266
266
|
├── MEMORY.md # Global memory (all channels, you can read/write)
|
|
267
267
|
├── skills/ # Global CLI tools you create
|
|
268
268
|
├── events/ # Scheduled events
|
|
269
269
|
└── ${channelId}/ # This channel
|
|
270
|
-
├──
|
|
270
|
+
├── AGENTS.md # Channel-specific instructions (read-only)
|
|
271
271
|
├── MEMORY.md # Channel-specific memory (you can read/write)
|
|
272
272
|
├── log.jsonl # Message history (no tool results)
|
|
273
273
|
├── scratch/ # Your working directory
|
|
274
274
|
└── skills/ # Channel-specific tools`);
|
|
275
|
-
// 3.
|
|
275
|
+
// 3. AGENTS.md — User-defined instructions
|
|
276
276
|
if (agentConfig) {
|
|
277
277
|
sections.push(`## Agent Instructions\n${agentConfig}`);
|
|
278
278
|
}
|
package/dist/agent.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { Agent } from \"@mariozechner/pi-agent-core\";\nimport { type Api, getModel, type Model } from \"@mariozechner/pi-ai\";\nimport {\n\tAgentSession,\n\tAuthStorage,\n\tconvertToLlm,\n\tDefaultResourceLoader,\n\tformatSkillsForPrompt,\n\tloadSkillsFromDir,\n\tModelRegistry,\n\tSessionManager,\n\ttype Skill,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { basename, join } from \"path\";\nimport { type BuiltInCommand, renderBuiltInHelp } from \"./commands.js\";\nimport { PipiclawSettingsManager, syncLogToSessionManager } from \"./context.js\";\nimport type { DingTalkContext } from \"./dingtalk.js\";\nimport * as log from \"./log.js\";\nimport { APP_HOME_DIR, AUTH_CONFIG_PATH, MODELS_CONFIG_PATH } from \"./paths.js\";\nimport { createExecutor, type SandboxConfig } from \"./sandbox.js\";\nimport type { ChannelStore } from \"./store.js\";\nimport { createPipiclawTools } from \"./tools/index.js\";\n\n// Default model - will be overridden by ModelRegistry if custom models are configured\nconst defaultModel = getModel(\"anthropic\", \"claude-sonnet-4-5\");\n\nexport interface AgentRunner {\n\trun(ctx: DingTalkContext, store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }>;\n\thandleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void>;\n\tabort(): void;\n}\n\ntype FinalOutcome = { kind: \"none\" } | { kind: \"silent\" } | { kind: \"final\"; text: string };\n\nfunction isSilentOutcome(outcome: FinalOutcome): outcome is { kind: \"silent\" } {\n\treturn outcome.kind === \"silent\";\n}\n\nfunction isFinalOutcome(outcome: FinalOutcome): outcome is { kind: \"final\"; text: string } {\n\treturn outcome.kind === \"final\";\n}\n\nfunction getFinalOutcomeText(outcome: FinalOutcome): string | null {\n\treturn isFinalOutcome(outcome) ? outcome.text : null;\n}\n\nfunction formatModelReference(model: Model<Api>): string {\n\treturn `${model.provider}/${model.id}`;\n}\n\nfunction findExactModelReferenceMatch(\n\tmodelReference: string,\n\tavailableModels: Model<Api>[],\n): { match?: Model<Api>; ambiguous: boolean } {\n\tconst trimmedReference = modelReference.trim();\n\tif (!trimmedReference) {\n\t\treturn { ambiguous: false };\n\t}\n\n\tconst normalizedReference = trimmedReference.toLowerCase();\n\n\tconst canonicalMatches = availableModels.filter(\n\t\t(model) => `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n\t);\n\tif (canonicalMatches.length === 1) {\n\t\treturn { match: canonicalMatches[0], ambiguous: false };\n\t}\n\tif (canonicalMatches.length > 1) {\n\t\treturn { ambiguous: true };\n\t}\n\n\tconst slashIndex = trimmedReference.indexOf(\"/\");\n\tif (slashIndex !== -1) {\n\t\tconst provider = trimmedReference.substring(0, slashIndex).trim();\n\t\tconst modelId = trimmedReference.substring(slashIndex + 1).trim();\n\t\tif (provider && modelId) {\n\t\t\tconst providerMatches = availableModels.filter(\n\t\t\t\t(model) =>\n\t\t\t\t\tmodel.provider.toLowerCase() === provider.toLowerCase() &&\n\t\t\t\t\tmodel.id.toLowerCase() === modelId.toLowerCase(),\n\t\t\t);\n\t\t\tif (providerMatches.length === 1) {\n\t\t\t\treturn { match: providerMatches[0], ambiguous: false };\n\t\t\t}\n\t\t\tif (providerMatches.length > 1) {\n\t\t\t\treturn { ambiguous: true };\n\t\t\t}\n\t\t}\n\t}\n\n\tconst idMatches = availableModels.filter((model) => model.id.toLowerCase() === normalizedReference);\n\tif (idMatches.length === 1) {\n\t\treturn { match: idMatches[0], ambiguous: false };\n\t}\n\n\treturn { ambiguous: idMatches.length > 1 };\n}\n\nfunction formatModelList(models: Model<Api>[], currentModel: Model<Api> | undefined, limit: number = 20): string {\n\tconst refs = models\n\t\t.slice()\n\t\t.sort((a, b) => formatModelReference(a).localeCompare(formatModelReference(b)))\n\t\t.map((model) => {\n\t\t\tconst ref = formatModelReference(model);\n\t\t\tconst marker =\n\t\t\t\tcurrentModel && currentModel.provider === model.provider && currentModel.id === model.id\n\t\t\t\t\t? \" (current)\"\n\t\t\t\t\t: \"\";\n\t\t\treturn `- \\`${ref}\\`${marker}`;\n\t\t});\n\n\tif (refs.length <= limit) {\n\t\treturn refs.join(\"\\n\");\n\t}\n\n\treturn `${refs.slice(0, limit).join(\"\\n\")}\\n- ... and ${refs.length - limit} more`;\n}\n\nfunction resolveInitialModel(modelRegistry: ModelRegistry, settingsManager: PipiclawSettingsManager): Model<Api> {\n\tconst savedProvider = settingsManager.getDefaultProvider();\n\tconst savedModelId = settingsManager.getDefaultModel();\n\tconst availableModels = modelRegistry.getAvailable();\n\tif (savedProvider && savedModelId) {\n\t\tconst savedModel = modelRegistry.find(savedProvider, savedModelId);\n\t\tif (\n\t\t\tsavedModel &&\n\t\t\tavailableModels.some((model) => model.provider === savedModel.provider && model.id === savedModel.id)\n\t\t) {\n\t\t\treturn savedModel;\n\t\t}\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn defaultModel;\n}\n\nasync function getApiKeyForModel(modelRegistry: ModelRegistry, model: any): Promise<string> {\n\tconst key = await modelRegistry.getApiKeyForProvider(model.provider);\n\tif (key) return key;\n\t// Fallback: try anthropic env var\n\tconst envKey = process.env.ANTHROPIC_API_KEY;\n\tif (envKey) return envKey;\n\tthrow new Error(\n\t\t`No API key found for provider: ${model.provider}.\\n\\n` +\n\t\t\t\"Configure API key in ~/.pi/agent/models.json or set ANTHROPIC_API_KEY environment variable.\",\n\t);\n}\n\n// ============================================================================\n// Configuration file loaders: SOUL.md, AGENT.md, MEMORY.md\n// ============================================================================\n\n/**\n * Load SOUL.md — defines the agent's identity, personality, and communication style.\n * Only loaded from workspace root (global).\n */\nfunction getSoul(workspaceDir: string): string {\n\tconst soulPath = join(workspaceDir, \"SOUL.md\");\n\tif (existsSync(soulPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(soulPath, \"utf-8\").trim();\n\t\t\tif (content) return content;\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read SOUL.md\", `${soulPath}: ${error}`);\n\t\t}\n\t}\n\treturn \"\";\n}\n\n/**\n * Load AGENT.md — defines the agent's behavior instructions, capabilities, and constraints.\n * Supports both global (workspace root) and channel-level override.\n */\nfunction getAgentConfig(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level AGENT.md (global)\n\tconst workspaceAgentPath = join(channelDir, \"..\", \"AGENT.md\");\n\tif (existsSync(workspaceAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace AGENT.md\", `${workspaceAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific AGENT.md (overrides/extends global)\n\tconst channelAgentPath = join(channelDir, \"AGENT.md\");\n\tif (existsSync(channelAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel AGENT.md\", `${channelAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction getMemory(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level memory (shared across all channels)\n\tconst workspaceMemoryPath = join(channelDir, \"..\", \"MEMORY.md\");\n\tif (existsSync(workspaceMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Global Workspace Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific memory\n\tconst channelMemoryPath = join(channelDir, \"MEMORY.md\");\n\tif (existsSync(channelMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Channel-Specific Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel memory\", `${channelMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\tif (parts.length === 0) {\n\t\treturn \"(no working memory yet)\";\n\t}\n\n\tconst combined = parts.join(\"\\n\\n\");\n\n\t// Warn if memory is getting too large (consumes system prompt token budget)\n\tif (combined.length > 5000) {\n\t\treturn `\\u26a0\\ufe0f Memory is large (${combined.length} chars). Consolidate: remove outdated entries, merge duplicates, tighten descriptions.\\n\\n${combined}`;\n\t}\n\n\treturn combined;\n}\n\nfunction loadPipiclawSkills(channelDir: string, workspacePath: string): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\tconst hostWorkspacePath = join(channelDir, \"..\");\n\n\tconst translatePath = (hostPath: string): string => {\n\t\tif (hostPath.startsWith(hostWorkspacePath)) {\n\t\t\treturn workspacePath + hostPath.slice(hostWorkspacePath.length);\n\t\t}\n\t\treturn hostPath;\n\t};\n\n\t// Load workspace-level skills (global)\n\tconst workspaceSkillsDir = join(hostWorkspacePath, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: \"workspace\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Load channel-specific skills\n\tconst channelSkillsDir = join(channelDir, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: channelSkillsDir, source: \"channel\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n\n// ============================================================================\n// System Prompt Builder\n// ============================================================================\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\tsoul: string,\n\tagentConfig: string,\n\tmemory: string,\n\tsandboxConfig: SandboxConfig,\n\tskills: Skill[],\n): string {\n\tconst channelPath = `${workspacePath}/${channelId}`;\n\tconst isDocker = sandboxConfig.type === \"docker\";\n\n\tconst envDescription = isDocker\n\t\t? `You are running inside a Docker container (Alpine Linux).\n- Bash working directory: / (use cd or absolute paths)\n- Install tools with: apk add <package>\n- Your changes persist across sessions`\n\t\t: `You are running directly on the host machine.\n- Bash working directory: ${process.cwd()}\n- Be careful with system modifications`;\n\n\t// Build system prompt with configuration file layering:\n\t// 1. SOUL.md (identity/personality)\n\t// 2. Core instructions\n\t// 3. AGENT.md (behavior instructions)\n\t// 4. Skills, Events, Memory\n\n\tconst sections: string[] = [];\n\n\t// 1. SOUL.md — Agent identity\n\tif (soul) {\n\t\tsections.push(soul);\n\t} else {\n\t\tsections.push(\"You are a DingTalk bot assistant. Be concise and helpful.\");\n\t}\n\n\t// 2. Core instructions\n\tsections.push(`## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\n\n## Formatting\nUse Markdown for formatting. DingTalk AI Card supports basic Markdown:\nBold: **text**, Italic: *text*, Code: \\`code\\`, Block: \\`\\`\\`code\\`\\`\\`, Links: [text](url)\n\n## Environment\n${envDescription}\n\n## Workspace Layout\n${workspacePath}/\n├── SOUL.md # Your identity/personality (read-only)\n├── AGENT.md # Custom behavior instructions (read-only)\n├── MEMORY.md # Global memory (all channels, you can read/write)\n├── skills/ # Global CLI tools you create\n├── events/ # Scheduled events\n└── ${channelId}/ # This channel\n ├── AGENT.md # Channel-specific instructions (read-only)\n ├── MEMORY.md # Channel-specific memory (you can read/write)\n ├── log.jsonl # Message history (no tool results)\n ├── scratch/ # Your working directory\n └── skills/ # Channel-specific tools`);\n\n\t// 3. AGENT.md — User-defined instructions\n\tif (agentConfig) {\n\t\tsections.push(`## Agent Instructions\\n${agentConfig}`);\n\t}\n\n\t// 4. Skills\n\tsections.push(`## Skills (Custom CLI Tools)\nYou can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).\n\n### Creating Skills\nStore in \\`${workspacePath}/skills/<name>/\\` (global) or \\`${channelPath}/skills/<name>/\\` (channel-specific).\nEach skill directory needs a \\`SKILL.md\\` with YAML frontmatter:\n\n\\`\\`\\`markdown\n---\nname: skill-name\ndescription: Short description of what this skill does\n---\n\n# Skill Name\n\nUsage instructions, examples, etc.\nScripts are in: {baseDir}/\n\\`\\`\\`\n\n\\`name\\` and \\`description\\` are required. Use \\`{baseDir}\\` as placeholder for the skill's directory path.\n\n### Available Skills\n${skills.length > 0 ? formatSkillsForPrompt(skills) : \"(no skills installed yet)\"}`);\n\n\t// 5. Events\n\tsections.push(`## Events\nYou can schedule events that wake you up at specific times or when external things happen. Events are JSON files in \\`${workspacePath}/events/\\`.\n\n### Event Types\n\n**Immediate** - Triggers as soon as harness sees the file.\n\\`\\`\\`json\n{\"type\": \"immediate\", \"channelId\": \"${channelId}\", \"text\": \"New event occurred\"}\n\\`\\`\\`\n\n**One-shot** - Triggers once at a specific time.\n\\`\\`\\`json\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder\", \"at\": \"2025-12-15T09:00:00+08:00\"}\n\\`\\`\\`\n\n**Periodic** - Triggers on a cron schedule.\n\\`\\`\\`json\n{\"type\": \"periodic\", \"channelId\": \"${channelId}\", \"text\": \"Check inbox\", \"schedule\": \"0 9 * * 1-5\", \"timezone\": \"${Intl.DateTimeFormat().resolvedOptions().timeZone}\"}\n\\`\\`\\`\n\n### Cron Format\n\\`minute hour day-of-month month day-of-week\\`\n\n### Creating Events\n\\`\\`\\`bash\ncat > ${workspacePath}/events/reminder-$(date +%s).json << 'EOF'\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder text\", \"at\": \"2025-12-14T09:00:00+08:00\"}\nEOF\n\\`\\`\\`\n\n### Silent Completion\nFor periodic events where there's nothing to report, respond with just \\`[SILENT]\\`. This deletes the status message. Use this to avoid spam when periodic checks find nothing.\n\n### Limits\nMaximum 5 events can be queued.`);\n\n\t// 6. Memory\n\tsections.push(`## Memory\nWrite to MEMORY.md files to persist context across conversations.\n- Global (${workspacePath}/MEMORY.md): skills, preferences, project info\n- Channel (${channelPath}/MEMORY.md): channel-specific decisions, ongoing work\n\n### Guidelines\n- Keep each MEMORY.md concise (target: under 50 lines)\n- Use clear headers to organize entries (## Preferences, ## Projects, etc.)\n- Remove outdated entries when they are no longer relevant\n- Merge duplicate or redundant items\n- Prefer structured formats (lists, key-value pairs) over prose\n- Update when you learn something important or when asked to remember something\n\n### Current Memory\n${memory}`);\n\n\t// 7. System Configuration Log\n\tsections.push(`## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment.`);\n\n\t// 8. Tools\n\tsections.push(`## Tools\n- bash: Run shell commands (primary tool). Install packages as needed.\n- read: Read files\n- write: Create/overwrite files\n- edit: Surgical file edits\n- attach: Share files (note: DingTalk file sharing is limited, output as text when possible)\n\nEach tool requires a \"label\" parameter (shown to user).`);\n\n\t// 9. Log Queries\n\tsections.push(`## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\\`\\`\\``);\n\n\treturn sections.join(\"\\n\\n\");\n}\n\n// ============================================================================\n// Agent Runner\n// ============================================================================\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn `${text.substring(0, maxLen - 3)}...`;\n}\n\nfunction sanitizeProgressText(text: string): string {\n\treturn text\n\t\t.replace(/\\uFFFC/g, \"\")\n\t\t.replace(/\\r/g, \"\")\n\t\t.trim();\n}\n\nfunction formatProgressEntry(kind: \"tool\" | \"thinking\" | \"error\" | \"assistant\", text: string): string {\n\tconst cleaned = sanitizeProgressText(text);\n\tif (!cleaned) return \"\";\n\n\tconst normalized = cleaned.replace(/\\n+/g, \" \").trim();\n\tswitch (kind) {\n\t\tcase \"tool\":\n\t\t\treturn `Running: ${normalized}`;\n\t\tcase \"thinking\":\n\t\t\treturn `Thinking: ${normalized}`;\n\t\tcase \"error\":\n\t\t\treturn `Error: ${normalized}`;\n\t\tcase \"assistant\":\n\t\t\treturn normalized;\n\t}\n}\n\nfunction extractToolResultText(result: unknown): string {\n\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\n\tif (\n\t\tresult &&\n\t\ttypeof result === \"object\" &&\n\t\t\"content\" in result &&\n\t\tArray.isArray((result as { content: unknown }).content)\n\t) {\n\t\tconst content = (result as { content: Array<{ type: string; text?: string }> }).content;\n\t\tconst textParts: string[] = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\" && part.text) {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t}\n\t\t}\n\t\tif (textParts.length > 0) {\n\t\t\treturn textParts.join(\"\\n\");\n\t\t}\n\t}\n\n\treturn JSON.stringify(result);\n}\n\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\tconst workspaceDir = join(channelDir, \"..\");\n\n\t// Create tools\n\tconst tools = createPipiclawTools(executor);\n\n\t// Initial system prompt\n\tconst soul = getSoul(workspaceDir);\n\tconst agentConfig = getAgentConfig(channelDir);\n\tconst memory = getMemory(channelDir);\n\tconst initialSkills = loadPipiclawSkills(channelDir, workspacePath);\n\tlet currentSkills = initialSkills;\n\tconst systemPrompt = buildSystemPrompt(\n\t\tworkspacePath,\n\t\tchannelId,\n\t\tsoul,\n\t\tagentConfig,\n\t\tmemory,\n\t\tsandboxConfig,\n\t\tinitialSkills,\n\t);\n\n\t// Create session manager\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\tconst sessionManager = SessionManager.open(contextFile, channelDir);\n\tconst settingsManager = new PipiclawSettingsManager(APP_HOME_DIR);\n\n\t// Create AuthStorage and ModelRegistry\n\tconst authStorage = AuthStorage.create(AUTH_CONFIG_PATH);\n\tconst modelRegistry = new ModelRegistry(authStorage, MODELS_CONFIG_PATH);\n\n\t// Resolve model: prefer saved global default, fall back to first available model\n\tlet activeModel = resolveInitialModel(modelRegistry, settingsManager);\n\tlog.logInfo(`Using model: ${activeModel.provider}/${activeModel.id} (${activeModel.name})`);\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: activeModel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tconvertToLlm,\n\t\tgetApiKey: async () => getApiKeyForModel(modelRegistry, activeModel),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.buildSessionContext();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\tcwd: process.cwd(),\n\t\tagentDir: APP_HOME_DIR,\n\t\tsettingsManager: settingsManager as any,\n\t\tskillsOverride: (base) => ({\n\t\t\tskills: [...base.skills, ...currentSkills],\n\t\t\tdiagnostics: base.diagnostics,\n\t\t}),\n\t});\n\n\tconst baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));\n\n\t// Create AgentSession\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager: settingsManager as any,\n\t\tcwd: process.cwd(),\n\t\tmodelRegistry,\n\t\tresourceLoader,\n\t\tbaseToolsOverride,\n\t});\n\n\t// Mutable per-run state\n\tconst runState: {\n\t\tctx: DingTalkContext | null;\n\t\tlogCtx: { channelId: string; userName?: string; channelName?: string } | null;\n\t\tqueue: {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null;\n\t\tpendingTools: Map<string, { toolName: string; args: unknown; startTime: number }>;\n\t\ttotalUsage: {\n\t\t\tinput: number;\n\t\t\toutput: number;\n\t\t\tcacheRead: number;\n\t\t\tcacheWrite: number;\n\t\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t\t};\n\t\tstopReason: string;\n\t\terrorMessage: string | undefined;\n\t\tfinalOutcome: FinalOutcome;\n\t\tfinalResponseDelivered: boolean;\n\t} = {\n\t\tctx: null as DingTalkContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\terrorMessage: undefined as string | undefined,\n\t\tfinalOutcome: { kind: \"none\" },\n\t\tfinalResponseDelivered: false,\n\t};\n\n\tconst sendCommandReply = async (ctx: DingTalkContext, text: string): Promise<void> => {\n\t\tconst delivered = await ctx.respondPlain(text);\n\t\tif (!delivered) {\n\t\t\tawait ctx.replaceMessage(text);\n\t\t\tawait ctx.flush();\n\t\t}\n\t};\n\n\tconst handleModelBuiltinCommand = async (ctx: DingTalkContext, args: string): Promise<void> => {\n\t\tmodelRegistry.refresh();\n\t\tconst availableModels = await modelRegistry.getAvailable();\n\t\tconst currentModel = session.model;\n\n\t\tif (!args.trim()) {\n\t\t\tconst current = currentModel ? `\\`${formatModelReference(currentModel)}\\`` : \"(none)\";\n\t\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel) : \"- (none)\";\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`# Model\n\nCurrent model: ${current}\n\nUse \\`/model <provider/modelId>\\` or \\`/model <modelId>\\` to switch. Bare model IDs must resolve uniquely.\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst match = findExactModelReferenceMatch(args, availableModels);\n\t\tif (match.match) {\n\t\t\tawait session.setModel(match.match);\n\t\t\tactiveModel = match.match;\n\t\t\tawait sendCommandReply(ctx, `已切换模型到 \\`${formatModelReference(match.match)}\\`。`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel, 10) : \"- (none)\";\n\t\tif (match.ambiguous) {\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`未切换模型:\\`${args.trim()}\\` 匹配到多个模型。请改用精确的 \\`provider/modelId\\` 形式。\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tawait sendCommandReply(\n\t\t\tctx,\n\t\t\t`未找到模型 \\`${args.trim()}\\`。请使用精确的 \\`provider/modelId\\` 或唯一的 \\`modelId\\`。\n\nAvailable models:\n${available}`,\n\t\t);\n\t};\n\n\tconst handleBuiltInCommand = async (ctx: DingTalkContext, command: BuiltInCommand): Promise<void> => {\n\t\ttry {\n\t\t\tswitch (command.name) {\n\t\t\t\tcase \"help\":\n\t\t\t\t\tawait sendCommandReply(ctx, renderBuiltInHelp());\n\t\t\t\t\treturn;\n\t\t\t\tcase \"new\": {\n\t\t\t\t\tconst completed = await session.newSession();\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tcompleted\n\t\t\t\t\t\t\t? `已开启新会话。\n\nSession ID: \\`${session.sessionId}\\``\n\t\t\t\t\t\t\t: \"新会话已取消。\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"compact\": {\n\t\t\t\t\tconst result = await session.compact(command.args || undefined);\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`已压缩当前会话上下文。\n\n- Tokens before compaction: \\`${result.tokensBefore}\\`\n- Summary:\n\n\\`\\`\\`text\n${result.summary}\n\\`\\`\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session\": {\n\t\t\t\t\tconst stats = session.getSessionStats();\n\t\t\t\t\tconst currentModel = session.model ? `\\`${formatModelReference(session.model)}\\`` : \"(none)\";\n\t\t\t\t\tconst sessionFile = stats.sessionFile ? `\\`${basename(stats.sessionFile)}\\`` : \"(none)\";\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`# Session\n\n- Session ID: \\`${stats.sessionId}\\`\n- Session file: ${sessionFile}\n- Model: ${currentModel}\n- Thinking level: \\`${session.thinkingLevel}\\`\n- User messages: \\`${stats.userMessages}\\`\n- Assistant messages: \\`${stats.assistantMessages}\\`\n- Tool calls: \\`${stats.toolCalls}\\`\n- Tool results: \\`${stats.toolResults}\\`\n- Total messages: \\`${stats.totalMessages}\\`\n- Tokens: \\`${stats.tokens.total}\\` (input \\`${stats.tokens.input}\\`, output \\`${stats.tokens.output}\\`, cache read \\`${stats.tokens.cacheRead}\\`, cache write \\`${stats.tokens.cacheWrite}\\`)\n- Cost: \\`$${stats.cost.toFixed(4)}\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"model\":\n\t\t\t\t\tawait handleModelBuiltinCommand(ctx, command.args);\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\tlog.logWarning(`[${channelId}] Built-in command failed`, errMsg);\n\t\t\tawait sendCommandReply(ctx, `命令执行失败:${errMsg}`);\n\t\t}\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event: any) => {\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"tool\", label), false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(\n\t\t\t\t\t() => ctx.respond(formatProgressEntry(\"error\", truncate(resultStr, 200)), false),\n\t\t\t\t\t\"tool error\",\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\t\t\t\tif (assistantMsg.errorMessage) {\n\t\t\t\t\trunState.errorMessage = assistantMsg.errorMessage;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tlet hasToolCalls = false;\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t} else if (part.type === \"toolCall\") {\n\t\t\t\t\t\thasToolCalls = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"thinking\", thinking), false), \"thinking\");\n\t\t\t\t}\n\n\t\t\t\tif (hasToolCalls && text.trim()) {\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"assistant\", text), false), \"assistant progress\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst turnEvent = event as any & {\n\t\t\t\ttype: \"turn_end\";\n\t\t\t\tmessage: { role: string; stopReason?: string; content: Array<{ type: string; text?: string }> };\n\t\t\t\ttoolResults: unknown[];\n\t\t\t};\n\t\t\tif (turnEvent.message.role === \"assistant\" && turnEvent.toolResults.length === 0) {\n\t\t\t\tif (turnEvent.message.stopReason === \"error\" || turnEvent.message.stopReason === \"aborted\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst finalContent = turnEvent.message.content as Array<{ type: string; text?: string }>;\n\t\t\t\tconst finalText = finalContent\n\t\t\t\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && !!part.text)\n\t\t\t\t\t.map((part) => part.text)\n\t\t\t\t\t.join(\"\\n\");\n\n\t\t\t\tconst trimmedFinalText = finalText.trim();\n\t\t\t\tif (!trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (trimmedFinalText === \"[SILENT]\" || trimmedFinalText.startsWith(\"[SILENT]\")) {\n\t\t\t\t\trunState.finalOutcome = { kind: \"silent\" };\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (runState.finalOutcome.kind === \"final\" && runState.finalOutcome.text.trim() === trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\trunState.finalOutcome = { kind: \"final\", text: finalText };\n\t\t\t\tlog.logResponse(logCtx, finalText);\n\t\t\t\tqueue.enqueue(async () => {\n\t\t\t\t\tconst delivered = await ctx.respondPlain(finalText);\n\t\t\t\t\tif (delivered) {\n\t\t\t\t\t\trunState.finalResponseDelivered = true;\n\t\t\t\t\t}\n\t\t\t\t}, \"final response\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(formatProgressEntry(\"assistant\", \"Compacting context...\"), false),\n\t\t\t\t\"compaction start\",\n\t\t\t);\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() =>\n\t\t\t\t\tctx.respond(\n\t\t\t\t\t\tformatProgressEntry(\"assistant\", `Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})...`),\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\treturn {\n\t\tasync handleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void> {\n\t\t\tawait handleBuiltInCommand(ctx, command);\n\t\t},\n\n\t\tasync run(ctx: DingTalkContext, _store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }> {\n\t\t\t// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.logCtx = {\n\t\t\t\tchannelId: ctx.message.channel,\n\t\t\t\tuserName: ctx.message.userName,\n\t\t\t\tchannelName: ctx.channelName,\n\t\t\t};\n\t\t\trunState.pendingTools.clear();\n\t\t\trunState.totalUsage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\t\t\trunState.errorMessage = undefined;\n\t\t\trunState.finalOutcome = { kind: \"none\" };\n\t\t\trunState.finalResponseDelivered = false;\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.then(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait fn();\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(`DingTalk API error (${errorContext})`, errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = true): void {\n\t\t\t\t\tthis.enqueue(\n\t\t\t\t\t\t() => (target === \"main\" ? ctx.respond(text, doLog) : ctx.respondInThread(text)),\n\t\t\t\t\t\terrorContext,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\t// Ensure channel directory exists\n\t\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t\t// Update system prompt and runtime resources with fresh config\n\t\t\t\tconst soul = getSoul(workspaceDir);\n\t\t\t\tconst agentConfig = getAgentConfig(channelDir);\n\t\t\t\tconst memory = getMemory(channelDir);\n\t\t\t\tconst skills = loadPipiclawSkills(channelDir, workspacePath);\n\t\t\t\tcurrentSkills = skills;\n\t\t\t\tconst systemPrompt = buildSystemPrompt(\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\tchannelId,\n\t\t\t\t\tsoul,\n\t\t\t\t\tagentConfig,\n\t\t\t\t\tmemory,\n\t\t\t\t\tsandboxConfig,\n\t\t\t\t\tskills,\n\t\t\t\t);\n\t\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\t\t\t\tawait session.reload();\n\n\t\t\t\t// Sync messages from log.jsonl\n\t\t\t\tconst syncedCount = syncLogToSessionManager(sessionManager, channelDir, ctx.message.ts);\n\t\t\t\tif (syncedCount > 0) {\n\t\t\t\t\tlog.logInfo(`[${channelId}] Synced ${syncedCount} messages from log.jsonl`);\n\t\t\t\t}\n\n\t\t\t\t// Reload messages from context.jsonl\n\t\t\t\tconst reloadedSession = sessionManager.buildSessionContext();\n\t\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t\t}\n\n\t\t\t\t// Log context info\n\t\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\n\t\t\t\t// Build user message with timestamp and username prefix\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst pad = (n: number) => n.toString().padStart(2, \"0\");\n\t\t\t\tconst offset = -now.getTimezoneOffset();\n\t\t\t\tconst offsetSign = offset >= 0 ? \"+\" : \"-\";\n\t\t\t\tconst offsetHours = pad(Math.floor(Math.abs(offset) / 60));\n\t\t\t\tconst offsetMins = pad(Math.abs(offset) % 60);\n\t\t\t\tconst timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`;\n\t\t\t\tconst userMessage = `[${timestamp}] [${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t\t// Debug: write context to last_prompt.json (only with PIPICLAW_DEBUG=1)\n\t\t\t\tif (process.env.PIPICLAW_DEBUG) {\n\t\t\t\t\tconst debugContext = {\n\t\t\t\t\t\tsystemPrompt,\n\t\t\t\t\t\tmessages: session.messages,\n\t\t\t\t\t\tnewUserMessage: userMessage,\n\t\t\t\t\t};\n\t\t\t\t\tawait writeFile(join(channelDir, \"last_prompt.json\"), JSON.stringify(debugContext, null, 2));\n\t\t\t\t}\n\n\t\t\t\tawait session.prompt(userMessage);\n\t\t\t} catch (err) {\n\t\t\t\trunState.stopReason = \"error\";\n\t\t\t\trunState.errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\t\tlog.logWarning(`[${channelId}] Runner failed`, runState.errorMessage);\n\t\t\t} finally {\n\t\t\t\tawait queueChain;\n\t\t\t\tconst finalOutcome = runState.finalOutcome;\n\t\t\t\tconst finalOutcomeText = getFinalOutcomeText(finalOutcome);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (runState.stopReason === \"error\" && runState.errorMessage && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(\"_Sorry, something went wrong_\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to post error message\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (isSilentOutcome(finalOutcome)) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.deleteMessage();\n\t\t\t\t\t\t\tlog.logInfo(\"Silent response - deleted message\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to delete message for silent response\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (finalOutcomeText && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(finalOutcomeText);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait ctx.flush();\n\t\t\t\t} finally {\n\t\t\t\t\tawait ctx.close();\n\t\t\t\t}\n\n\t\t\t\t// Log usage summary\n\t\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t\tconst messages = session.messages;\n\t\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t\t.slice()\n\t\t\t\t\t\t.reverse()\n\t\t\t\t\t\t.find((m: any) => m.role === \"assistant\" && m.stopReason !== \"aborted\") as any;\n\n\t\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst currentRunModel = session.model ?? activeModel;\n\t\t\t\t\tconst contextWindow = currentRunModel.contextWindow || 200000;\n\n\t\t\t\t\tlog.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\t}\n\n\t\t\t\t// Clear run state\n\t\t\t\trunState.ctx = null;\n\t\t\t\trunState.logCtx = null;\n\t\t\t\trunState.queue = null;\n\t\t\t}\n\n\t\t\treturn { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.abort();\n\t\t},\n\t};\n}\n\n/**\n * Translate container path back to host path for file operations\n */\nexport function translateToHostPath(\n\tcontainerPath: string,\n\tchannelDir: string,\n\tworkspacePath: string,\n\tchannelId: string,\n): string {\n\tif (workspacePath === \"/workspace\") {\n\t\tconst prefix = `/workspace/${channelId}/`;\n\t\tif (containerPath.startsWith(prefix)) {\n\t\t\treturn join(channelDir, containerPath.slice(prefix.length));\n\t\t}\n\t\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"agent.js","sourceRoot":"","sources":["../src/agent.ts"],"names":[],"mappings":"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","sourcesContent":["import { Agent } from \"@mariozechner/pi-agent-core\";\nimport { type Api, getModel, type Model } from \"@mariozechner/pi-ai\";\nimport {\n\tAgentSession,\n\tAuthStorage,\n\tconvertToLlm,\n\tDefaultResourceLoader,\n\tformatSkillsForPrompt,\n\tloadSkillsFromDir,\n\tModelRegistry,\n\tSessionManager,\n\ttype Skill,\n} from \"@mariozechner/pi-coding-agent\";\nimport { existsSync, readFileSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { basename, join } from \"path\";\nimport { type BuiltInCommand, renderBuiltInHelp } from \"./commands.js\";\nimport { PipiclawSettingsManager, syncLogToSessionManager } from \"./context.js\";\nimport type { DingTalkContext } from \"./dingtalk.js\";\nimport * as log from \"./log.js\";\nimport { APP_HOME_DIR, AUTH_CONFIG_PATH, MODELS_CONFIG_PATH } from \"./paths.js\";\nimport { createExecutor, type SandboxConfig } from \"./sandbox.js\";\nimport type { ChannelStore } from \"./store.js\";\nimport { createPipiclawTools } from \"./tools/index.js\";\n\n// Default model - will be overridden by ModelRegistry if custom models are configured\nconst defaultModel = getModel(\"anthropic\", \"claude-sonnet-4-5\");\n\nexport interface AgentRunner {\n\trun(ctx: DingTalkContext, store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }>;\n\thandleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void>;\n\tabort(): void;\n}\n\ntype FinalOutcome = { kind: \"none\" } | { kind: \"silent\" } | { kind: \"final\"; text: string };\n\nfunction isSilentOutcome(outcome: FinalOutcome): outcome is { kind: \"silent\" } {\n\treturn outcome.kind === \"silent\";\n}\n\nfunction isFinalOutcome(outcome: FinalOutcome): outcome is { kind: \"final\"; text: string } {\n\treturn outcome.kind === \"final\";\n}\n\nfunction getFinalOutcomeText(outcome: FinalOutcome): string | null {\n\treturn isFinalOutcome(outcome) ? outcome.text : null;\n}\n\nfunction formatModelReference(model: Model<Api>): string {\n\treturn `${model.provider}/${model.id}`;\n}\n\nfunction findExactModelReferenceMatch(\n\tmodelReference: string,\n\tavailableModels: Model<Api>[],\n): { match?: Model<Api>; ambiguous: boolean } {\n\tconst trimmedReference = modelReference.trim();\n\tif (!trimmedReference) {\n\t\treturn { ambiguous: false };\n\t}\n\n\tconst normalizedReference = trimmedReference.toLowerCase();\n\n\tconst canonicalMatches = availableModels.filter(\n\t\t(model) => `${model.provider}/${model.id}`.toLowerCase() === normalizedReference,\n\t);\n\tif (canonicalMatches.length === 1) {\n\t\treturn { match: canonicalMatches[0], ambiguous: false };\n\t}\n\tif (canonicalMatches.length > 1) {\n\t\treturn { ambiguous: true };\n\t}\n\n\tconst slashIndex = trimmedReference.indexOf(\"/\");\n\tif (slashIndex !== -1) {\n\t\tconst provider = trimmedReference.substring(0, slashIndex).trim();\n\t\tconst modelId = trimmedReference.substring(slashIndex + 1).trim();\n\t\tif (provider && modelId) {\n\t\t\tconst providerMatches = availableModels.filter(\n\t\t\t\t(model) =>\n\t\t\t\t\tmodel.provider.toLowerCase() === provider.toLowerCase() &&\n\t\t\t\t\tmodel.id.toLowerCase() === modelId.toLowerCase(),\n\t\t\t);\n\t\t\tif (providerMatches.length === 1) {\n\t\t\t\treturn { match: providerMatches[0], ambiguous: false };\n\t\t\t}\n\t\t\tif (providerMatches.length > 1) {\n\t\t\t\treturn { ambiguous: true };\n\t\t\t}\n\t\t}\n\t}\n\n\tconst idMatches = availableModels.filter((model) => model.id.toLowerCase() === normalizedReference);\n\tif (idMatches.length === 1) {\n\t\treturn { match: idMatches[0], ambiguous: false };\n\t}\n\n\treturn { ambiguous: idMatches.length > 1 };\n}\n\nfunction formatModelList(models: Model<Api>[], currentModel: Model<Api> | undefined, limit: number = 20): string {\n\tconst refs = models\n\t\t.slice()\n\t\t.sort((a, b) => formatModelReference(a).localeCompare(formatModelReference(b)))\n\t\t.map((model) => {\n\t\t\tconst ref = formatModelReference(model);\n\t\t\tconst marker =\n\t\t\t\tcurrentModel && currentModel.provider === model.provider && currentModel.id === model.id\n\t\t\t\t\t? \" (current)\"\n\t\t\t\t\t: \"\";\n\t\t\treturn `- \\`${ref}\\`${marker}`;\n\t\t});\n\n\tif (refs.length <= limit) {\n\t\treturn refs.join(\"\\n\");\n\t}\n\n\treturn `${refs.slice(0, limit).join(\"\\n\")}\\n- ... and ${refs.length - limit} more`;\n}\n\nfunction resolveInitialModel(modelRegistry: ModelRegistry, settingsManager: PipiclawSettingsManager): Model<Api> {\n\tconst savedProvider = settingsManager.getDefaultProvider();\n\tconst savedModelId = settingsManager.getDefaultModel();\n\tconst availableModels = modelRegistry.getAvailable();\n\tif (savedProvider && savedModelId) {\n\t\tconst savedModel = modelRegistry.find(savedProvider, savedModelId);\n\t\tif (\n\t\t\tsavedModel &&\n\t\t\tavailableModels.some((model) => model.provider === savedModel.provider && model.id === savedModel.id)\n\t\t) {\n\t\t\treturn savedModel;\n\t\t}\n\t}\n\n\tif (availableModels.length > 0) {\n\t\treturn availableModels[0];\n\t}\n\n\treturn defaultModel;\n}\n\nasync function getApiKeyForModel(modelRegistry: ModelRegistry, model: any): Promise<string> {\n\tconst key = await modelRegistry.getApiKeyForProvider(model.provider);\n\tif (key) return key;\n\t// Fallback: try anthropic env var\n\tconst envKey = process.env.ANTHROPIC_API_KEY;\n\tif (envKey) return envKey;\n\tthrow new Error(\n\t\t`No API key found for provider: ${model.provider}.\\n\\n` +\n\t\t\t\"Configure API key in ~/.pi/agent/models.json or set ANTHROPIC_API_KEY environment variable.\",\n\t);\n}\n\n// ============================================================================\n// Configuration file loaders: SOUL.md, AGENTS.md, MEMORY.md\n// ============================================================================\n\n/**\n * Load SOUL.md — defines the agent's identity, personality, and communication style.\n * Only loaded from workspace root (global).\n */\nfunction getSoul(workspaceDir: string): string {\n\tconst soulPath = join(workspaceDir, \"SOUL.md\");\n\tif (existsSync(soulPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(soulPath, \"utf-8\").trim();\n\t\t\tif (content) return content;\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read SOUL.md\", `${soulPath}: ${error}`);\n\t\t}\n\t}\n\treturn \"\";\n}\n\n/**\n * Load AGENTS.md — defines the agent's behavior instructions, capabilities, and constraints.\n * Supports both global (workspace root) and channel-level override.\n */\nfunction getAgentConfig(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level AGENTS.md (global)\n\tconst workspaceAgentPath = join(channelDir, \"..\", \"AGENTS.md\");\n\tif (existsSync(workspaceAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace AGENTS.md\", `${workspaceAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific AGENTS.md (overrides/extends global)\n\tconst channelAgentPath = join(channelDir, \"AGENTS.md\");\n\tif (existsSync(channelAgentPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelAgentPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(content);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel AGENTS.md\", `${channelAgentPath}: ${error}`);\n\t\t}\n\t}\n\n\treturn parts.join(\"\\n\\n\");\n}\n\nfunction getMemory(channelDir: string): string {\n\tconst parts: string[] = [];\n\n\t// Read workspace-level memory (shared across all channels)\n\tconst workspaceMemoryPath = join(channelDir, \"..\", \"MEMORY.md\");\n\tif (existsSync(workspaceMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(workspaceMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Global Workspace Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read workspace memory\", `${workspaceMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\t// Read channel-specific memory\n\tconst channelMemoryPath = join(channelDir, \"MEMORY.md\");\n\tif (existsSync(channelMemoryPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(channelMemoryPath, \"utf-8\").trim();\n\t\t\tif (content) {\n\t\t\t\tparts.push(`### Channel-Specific Memory\\n${content}`);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tlog.logWarning(\"Failed to read channel memory\", `${channelMemoryPath}: ${error}`);\n\t\t}\n\t}\n\n\tif (parts.length === 0) {\n\t\treturn \"(no working memory yet)\";\n\t}\n\n\tconst combined = parts.join(\"\\n\\n\");\n\n\t// Warn if memory is getting too large (consumes system prompt token budget)\n\tif (combined.length > 5000) {\n\t\treturn `\\u26a0\\ufe0f Memory is large (${combined.length} chars). Consolidate: remove outdated entries, merge duplicates, tighten descriptions.\\n\\n${combined}`;\n\t}\n\n\treturn combined;\n}\n\nfunction loadPipiclawSkills(channelDir: string, workspacePath: string): Skill[] {\n\tconst skillMap = new Map<string, Skill>();\n\tconst hostWorkspacePath = join(channelDir, \"..\");\n\n\tconst translatePath = (hostPath: string): string => {\n\t\tif (hostPath.startsWith(hostWorkspacePath)) {\n\t\t\treturn workspacePath + hostPath.slice(hostWorkspacePath.length);\n\t\t}\n\t\treturn hostPath;\n\t};\n\n\t// Load workspace-level skills (global)\n\tconst workspaceSkillsDir = join(hostWorkspacePath, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: workspaceSkillsDir, source: \"workspace\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\t// Load channel-specific skills\n\tconst channelSkillsDir = join(channelDir, \"skills\");\n\tfor (const skill of loadSkillsFromDir({ dir: channelSkillsDir, source: \"channel\" }).skills) {\n\t\tskill.filePath = translatePath(skill.filePath);\n\t\tskill.baseDir = translatePath(skill.baseDir);\n\t\tskillMap.set(skill.name, skill);\n\t}\n\n\treturn Array.from(skillMap.values());\n}\n\n// ============================================================================\n// System Prompt Builder\n// ============================================================================\n\nfunction buildSystemPrompt(\n\tworkspacePath: string,\n\tchannelId: string,\n\tsoul: string,\n\tagentConfig: string,\n\tmemory: string,\n\tsandboxConfig: SandboxConfig,\n\tskills: Skill[],\n): string {\n\tconst channelPath = `${workspacePath}/${channelId}`;\n\tconst isDocker = sandboxConfig.type === \"docker\";\n\n\tconst envDescription = isDocker\n\t\t? `You are running inside a Docker container (Alpine Linux).\n- Bash working directory: / (use cd or absolute paths)\n- Install tools with: apk add <package>\n- Your changes persist across sessions`\n\t\t: `You are running directly on the host machine.\n- Bash working directory: ${process.cwd()}\n- Be careful with system modifications`;\n\n\t// Build system prompt with configuration file layering:\n\t// 1. SOUL.md (identity/personality)\n\t// 2. Core instructions\n\t// 3. AGENTS.md (behavior instructions)\n\t// 4. Skills, Events, Memory\n\n\tconst sections: string[] = [];\n\n\t// 1. SOUL.md — Agent identity\n\tif (soul) {\n\t\tsections.push(soul);\n\t} else {\n\t\tsections.push(\"You are a DingTalk bot assistant. Be concise and helpful.\");\n\t}\n\n\t// 2. Core instructions\n\tsections.push(`## Context\n- For current date/time, use: date\n- You have access to previous conversation context including tool results from prior turns.\n- For older history beyond your context, search log.jsonl (contains user messages and your final responses, but not tool results).\n\n## Formatting\nUse Markdown for formatting. DingTalk AI Card supports basic Markdown:\nBold: **text**, Italic: *text*, Code: \\`code\\`, Block: \\`\\`\\`code\\`\\`\\`, Links: [text](url)\n\n## Environment\n${envDescription}\n\n## Workspace Layout\n${workspacePath}/\n├── SOUL.md # Your identity/personality (read-only)\n├── AGENTS.md # Custom behavior instructions (read-only)\n├── MEMORY.md # Global memory (all channels, you can read/write)\n├── skills/ # Global CLI tools you create\n├── events/ # Scheduled events\n└── ${channelId}/ # This channel\n ├── AGENTS.md # Channel-specific instructions (read-only)\n ├── MEMORY.md # Channel-specific memory (you can read/write)\n ├── log.jsonl # Message history (no tool results)\n ├── scratch/ # Your working directory\n └── skills/ # Channel-specific tools`);\n\n\t// 3. AGENTS.md — User-defined instructions\n\tif (agentConfig) {\n\t\tsections.push(`## Agent Instructions\\n${agentConfig}`);\n\t}\n\n\t// 4. Skills\n\tsections.push(`## Skills (Custom CLI Tools)\nYou can create reusable CLI tools for recurring tasks (email, APIs, data processing, etc.).\n\n### Creating Skills\nStore in \\`${workspacePath}/skills/<name>/\\` (global) or \\`${channelPath}/skills/<name>/\\` (channel-specific).\nEach skill directory needs a \\`SKILL.md\\` with YAML frontmatter:\n\n\\`\\`\\`markdown\n---\nname: skill-name\ndescription: Short description of what this skill does\n---\n\n# Skill Name\n\nUsage instructions, examples, etc.\nScripts are in: {baseDir}/\n\\`\\`\\`\n\n\\`name\\` and \\`description\\` are required. Use \\`{baseDir}\\` as placeholder for the skill's directory path.\n\n### Available Skills\n${skills.length > 0 ? formatSkillsForPrompt(skills) : \"(no skills installed yet)\"}`);\n\n\t// 5. Events\n\tsections.push(`## Events\nYou can schedule events that wake you up at specific times or when external things happen. Events are JSON files in \\`${workspacePath}/events/\\`.\n\n### Event Types\n\n**Immediate** - Triggers as soon as harness sees the file.\n\\`\\`\\`json\n{\"type\": \"immediate\", \"channelId\": \"${channelId}\", \"text\": \"New event occurred\"}\n\\`\\`\\`\n\n**One-shot** - Triggers once at a specific time.\n\\`\\`\\`json\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder\", \"at\": \"2025-12-15T09:00:00+08:00\"}\n\\`\\`\\`\n\n**Periodic** - Triggers on a cron schedule.\n\\`\\`\\`json\n{\"type\": \"periodic\", \"channelId\": \"${channelId}\", \"text\": \"Check inbox\", \"schedule\": \"0 9 * * 1-5\", \"timezone\": \"${Intl.DateTimeFormat().resolvedOptions().timeZone}\"}\n\\`\\`\\`\n\n### Cron Format\n\\`minute hour day-of-month month day-of-week\\`\n\n### Creating Events\n\\`\\`\\`bash\ncat > ${workspacePath}/events/reminder-$(date +%s).json << 'EOF'\n{\"type\": \"one-shot\", \"channelId\": \"${channelId}\", \"text\": \"Reminder text\", \"at\": \"2025-12-14T09:00:00+08:00\"}\nEOF\n\\`\\`\\`\n\n### Silent Completion\nFor periodic events where there's nothing to report, respond with just \\`[SILENT]\\`. This deletes the status message. Use this to avoid spam when periodic checks find nothing.\n\n### Limits\nMaximum 5 events can be queued.`);\n\n\t// 6. Memory\n\tsections.push(`## Memory\nWrite to MEMORY.md files to persist context across conversations.\n- Global (${workspacePath}/MEMORY.md): skills, preferences, project info\n- Channel (${channelPath}/MEMORY.md): channel-specific decisions, ongoing work\n\n### Guidelines\n- Keep each MEMORY.md concise (target: under 50 lines)\n- Use clear headers to organize entries (## Preferences, ## Projects, etc.)\n- Remove outdated entries when they are no longer relevant\n- Merge duplicate or redundant items\n- Prefer structured formats (lists, key-value pairs) over prose\n- Update when you learn something important or when asked to remember something\n\n### Current Memory\n${memory}`);\n\n\t// 7. System Configuration Log\n\tsections.push(`## System Configuration Log\nMaintain ${workspacePath}/SYSTEM.md to log all environment modifications:\n- Installed packages (apk add, npm install, pip install)\n- Environment variables set\n- Config files modified\n- Skill dependencies installed\n\nUpdate this file whenever you modify the environment.`);\n\n\t// 8. Tools\n\tsections.push(`## Tools\n- bash: Run shell commands (primary tool). Install packages as needed.\n- read: Read files\n- write: Create/overwrite files\n- edit: Surgical file edits\n- attach: Share files (note: DingTalk file sharing is limited, output as text when possible)\n\nEach tool requires a \"label\" parameter (shown to user).`);\n\n\t// 9. Log Queries\n\tsections.push(`## Log Queries (for older history)\nFormat: \\`{\"date\":\"...\",\"ts\":\"...\",\"user\":\"...\",\"userName\":\"...\",\"text\":\"...\",\"isBot\":false}\\`\nThe log contains user messages and your final responses (not tool calls/results).\n${isDocker ? \"Install jq: apk add jq\" : \"\"}\n\n\\`\\`\\`bash\n# Recent messages\ntail -30 log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\n# Search for specific topic\ngrep -i \"topic\" log.jsonl | jq -c '{date: .date[0:19], user: (.userName // .user), text}'\n\\`\\`\\``);\n\n\treturn sections.join(\"\\n\\n\");\n}\n\n// ============================================================================\n// Agent Runner\n// ============================================================================\n\nfunction truncate(text: string, maxLen: number): string {\n\tif (text.length <= maxLen) return text;\n\treturn `${text.substring(0, maxLen - 3)}...`;\n}\n\nfunction sanitizeProgressText(text: string): string {\n\treturn text\n\t\t.replace(/\\uFFFC/g, \"\")\n\t\t.replace(/\\r/g, \"\")\n\t\t.trim();\n}\n\nfunction formatProgressEntry(kind: \"tool\" | \"thinking\" | \"error\" | \"assistant\", text: string): string {\n\tconst cleaned = sanitizeProgressText(text);\n\tif (!cleaned) return \"\";\n\n\tconst normalized = cleaned.replace(/\\n+/g, \" \").trim();\n\tswitch (kind) {\n\t\tcase \"tool\":\n\t\t\treturn `Running: ${normalized}`;\n\t\tcase \"thinking\":\n\t\t\treturn `Thinking: ${normalized}`;\n\t\tcase \"error\":\n\t\t\treturn `Error: ${normalized}`;\n\t\tcase \"assistant\":\n\t\t\treturn normalized;\n\t}\n}\n\nfunction extractToolResultText(result: unknown): string {\n\tif (typeof result === \"string\") {\n\t\treturn result;\n\t}\n\n\tif (\n\t\tresult &&\n\t\ttypeof result === \"object\" &&\n\t\t\"content\" in result &&\n\t\tArray.isArray((result as { content: unknown }).content)\n\t) {\n\t\tconst content = (result as { content: Array<{ type: string; text?: string }> }).content;\n\t\tconst textParts: string[] = [];\n\t\tfor (const part of content) {\n\t\t\tif (part.type === \"text\" && part.text) {\n\t\t\t\ttextParts.push(part.text);\n\t\t\t}\n\t\t}\n\t\tif (textParts.length > 0) {\n\t\t\treturn textParts.join(\"\\n\");\n\t\t}\n\t}\n\n\treturn JSON.stringify(result);\n}\n\n// Cache runners per channel\nconst channelRunners = new Map<string, AgentRunner>();\n\nexport function getOrCreateRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst existing = channelRunners.get(channelId);\n\tif (existing) return existing;\n\n\tconst runner = createRunner(sandboxConfig, channelId, channelDir);\n\tchannelRunners.set(channelId, runner);\n\treturn runner;\n}\n\nfunction createRunner(sandboxConfig: SandboxConfig, channelId: string, channelDir: string): AgentRunner {\n\tconst executor = createExecutor(sandboxConfig);\n\tconst workspacePath = executor.getWorkspacePath(channelDir.replace(`/${channelId}`, \"\"));\n\tconst workspaceDir = join(channelDir, \"..\");\n\n\t// Create tools\n\tconst tools = createPipiclawTools(executor);\n\n\t// Initial system prompt\n\tconst soul = getSoul(workspaceDir);\n\tconst agentConfig = getAgentConfig(channelDir);\n\tconst memory = getMemory(channelDir);\n\tconst initialSkills = loadPipiclawSkills(channelDir, workspacePath);\n\tlet currentSkills = initialSkills;\n\tconst systemPrompt = buildSystemPrompt(\n\t\tworkspacePath,\n\t\tchannelId,\n\t\tsoul,\n\t\tagentConfig,\n\t\tmemory,\n\t\tsandboxConfig,\n\t\tinitialSkills,\n\t);\n\n\t// Create session manager\n\tconst contextFile = join(channelDir, \"context.jsonl\");\n\tconst sessionManager = SessionManager.open(contextFile, channelDir);\n\tconst settingsManager = new PipiclawSettingsManager(APP_HOME_DIR);\n\n\t// Create AuthStorage and ModelRegistry\n\tconst authStorage = AuthStorage.create(AUTH_CONFIG_PATH);\n\tconst modelRegistry = new ModelRegistry(authStorage, MODELS_CONFIG_PATH);\n\n\t// Resolve model: prefer saved global default, fall back to first available model\n\tlet activeModel = resolveInitialModel(modelRegistry, settingsManager);\n\tlog.logInfo(`Using model: ${activeModel.provider}/${activeModel.id} (${activeModel.name})`);\n\n\t// Create agent\n\tconst agent = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel: activeModel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools,\n\t\t},\n\t\tconvertToLlm,\n\t\tgetApiKey: async () => getApiKeyForModel(modelRegistry, activeModel),\n\t});\n\n\t// Load existing messages\n\tconst loadedSession = sessionManager.buildSessionContext();\n\tif (loadedSession.messages.length > 0) {\n\t\tagent.replaceMessages(loadedSession.messages);\n\t\tlog.logInfo(`[${channelId}] Loaded ${loadedSession.messages.length} messages from context.jsonl`);\n\t}\n\n\tconst resourceLoader = new DefaultResourceLoader({\n\t\tcwd: process.cwd(),\n\t\tagentDir: APP_HOME_DIR,\n\t\tsettingsManager: settingsManager as any,\n\t\tskillsOverride: (base) => ({\n\t\t\tskills: [...base.skills, ...currentSkills],\n\t\t\tdiagnostics: base.diagnostics,\n\t\t}),\n\t});\n\n\tconst baseToolsOverride = Object.fromEntries(tools.map((tool) => [tool.name, tool]));\n\n\t// Create AgentSession\n\tconst session = new AgentSession({\n\t\tagent,\n\t\tsessionManager,\n\t\tsettingsManager: settingsManager as any,\n\t\tcwd: process.cwd(),\n\t\tmodelRegistry,\n\t\tresourceLoader,\n\t\tbaseToolsOverride,\n\t});\n\n\t// Mutable per-run state\n\tconst runState: {\n\t\tctx: DingTalkContext | null;\n\t\tlogCtx: { channelId: string; userName?: string; channelName?: string } | null;\n\t\tqueue: {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null;\n\t\tpendingTools: Map<string, { toolName: string; args: unknown; startTime: number }>;\n\t\ttotalUsage: {\n\t\t\tinput: number;\n\t\t\toutput: number;\n\t\t\tcacheRead: number;\n\t\t\tcacheWrite: number;\n\t\t\tcost: { input: number; output: number; cacheRead: number; cacheWrite: number; total: number };\n\t\t};\n\t\tstopReason: string;\n\t\terrorMessage: string | undefined;\n\t\tfinalOutcome: FinalOutcome;\n\t\tfinalResponseDelivered: boolean;\n\t} = {\n\t\tctx: null as DingTalkContext | null,\n\t\tlogCtx: null as { channelId: string; userName?: string; channelName?: string } | null,\n\t\tqueue: null as {\n\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void;\n\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog?: boolean): void;\n\t\t} | null,\n\t\tpendingTools: new Map<string, { toolName: string; args: unknown; startTime: number }>(),\n\t\ttotalUsage: {\n\t\t\tinput: 0,\n\t\t\toutput: 0,\n\t\t\tcacheRead: 0,\n\t\t\tcacheWrite: 0,\n\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t},\n\t\tstopReason: \"stop\",\n\t\terrorMessage: undefined as string | undefined,\n\t\tfinalOutcome: { kind: \"none\" },\n\t\tfinalResponseDelivered: false,\n\t};\n\n\tconst sendCommandReply = async (ctx: DingTalkContext, text: string): Promise<void> => {\n\t\tconst delivered = await ctx.respondPlain(text);\n\t\tif (!delivered) {\n\t\t\tawait ctx.replaceMessage(text);\n\t\t\tawait ctx.flush();\n\t\t}\n\t};\n\n\tconst handleModelBuiltinCommand = async (ctx: DingTalkContext, args: string): Promise<void> => {\n\t\tmodelRegistry.refresh();\n\t\tconst availableModels = await modelRegistry.getAvailable();\n\t\tconst currentModel = session.model;\n\n\t\tif (!args.trim()) {\n\t\t\tconst current = currentModel ? `\\`${formatModelReference(currentModel)}\\`` : \"(none)\";\n\t\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel) : \"- (none)\";\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`# Model\n\nCurrent model: ${current}\n\nUse \\`/model <provider/modelId>\\` or \\`/model <modelId>\\` to switch. Bare model IDs must resolve uniquely.\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tconst match = findExactModelReferenceMatch(args, availableModels);\n\t\tif (match.match) {\n\t\t\tawait session.setModel(match.match);\n\t\t\tactiveModel = match.match;\n\t\t\tawait sendCommandReply(ctx, `已切换模型到 \\`${formatModelReference(match.match)}\\`。`);\n\t\t\treturn;\n\t\t}\n\n\t\tconst available = availableModels.length > 0 ? formatModelList(availableModels, currentModel, 10) : \"- (none)\";\n\t\tif (match.ambiguous) {\n\t\t\tawait sendCommandReply(\n\t\t\t\tctx,\n\t\t\t\t`未切换模型:\\`${args.trim()}\\` 匹配到多个模型。请改用精确的 \\`provider/modelId\\` 形式。\n\nAvailable models:\n${available}`,\n\t\t\t);\n\t\t\treturn;\n\t\t}\n\n\t\tawait sendCommandReply(\n\t\t\tctx,\n\t\t\t`未找到模型 \\`${args.trim()}\\`。请使用精确的 \\`provider/modelId\\` 或唯一的 \\`modelId\\`。\n\nAvailable models:\n${available}`,\n\t\t);\n\t};\n\n\tconst handleBuiltInCommand = async (ctx: DingTalkContext, command: BuiltInCommand): Promise<void> => {\n\t\ttry {\n\t\t\tswitch (command.name) {\n\t\t\t\tcase \"help\":\n\t\t\t\t\tawait sendCommandReply(ctx, renderBuiltInHelp());\n\t\t\t\t\treturn;\n\t\t\t\tcase \"new\": {\n\t\t\t\t\tconst completed = await session.newSession();\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\tcompleted\n\t\t\t\t\t\t\t? `已开启新会话。\n\nSession ID: \\`${session.sessionId}\\``\n\t\t\t\t\t\t\t: \"新会话已取消。\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"compact\": {\n\t\t\t\t\tconst result = await session.compact(command.args || undefined);\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`已压缩当前会话上下文。\n\n- Tokens before compaction: \\`${result.tokensBefore}\\`\n- Summary:\n\n\\`\\`\\`text\n${result.summary}\n\\`\\`\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"session\": {\n\t\t\t\t\tconst stats = session.getSessionStats();\n\t\t\t\t\tconst currentModel = session.model ? `\\`${formatModelReference(session.model)}\\`` : \"(none)\";\n\t\t\t\t\tconst sessionFile = stats.sessionFile ? `\\`${basename(stats.sessionFile)}\\`` : \"(none)\";\n\t\t\t\t\tawait sendCommandReply(\n\t\t\t\t\t\tctx,\n\t\t\t\t\t\t`# Session\n\n- Session ID: \\`${stats.sessionId}\\`\n- Session file: ${sessionFile}\n- Model: ${currentModel}\n- Thinking level: \\`${session.thinkingLevel}\\`\n- User messages: \\`${stats.userMessages}\\`\n- Assistant messages: \\`${stats.assistantMessages}\\`\n- Tool calls: \\`${stats.toolCalls}\\`\n- Tool results: \\`${stats.toolResults}\\`\n- Total messages: \\`${stats.totalMessages}\\`\n- Tokens: \\`${stats.tokens.total}\\` (input \\`${stats.tokens.input}\\`, output \\`${stats.tokens.output}\\`, cache read \\`${stats.tokens.cacheRead}\\`, cache write \\`${stats.tokens.cacheWrite}\\`)\n- Cost: \\`$${stats.cost.toFixed(4)}\\``,\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tcase \"model\":\n\t\t\t\t\tawait handleModelBuiltinCommand(ctx, command.args);\n\t\t\t\t\treturn;\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\tlog.logWarning(`[${channelId}] Built-in command failed`, errMsg);\n\t\t\tawait sendCommandReply(ctx, `命令执行失败:${errMsg}`);\n\t\t}\n\t};\n\n\t// Subscribe to events ONCE\n\tsession.subscribe(async (event: any) => {\n\t\tif (!runState.ctx || !runState.logCtx || !runState.queue) return;\n\n\t\tconst { ctx, logCtx, queue, pendingTools } = runState;\n\n\t\tif (event.type === \"tool_execution_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_start\" };\n\t\t\tconst args = agentEvent.args as { label?: string };\n\t\t\tconst label = args.label || agentEvent.toolName;\n\n\t\t\tpendingTools.set(agentEvent.toolCallId, {\n\t\t\t\ttoolName: agentEvent.toolName,\n\t\t\t\targs: agentEvent.args,\n\t\t\t\tstartTime: Date.now(),\n\t\t\t});\n\n\t\t\tlog.logToolStart(logCtx, agentEvent.toolName, label, agentEvent.args as Record<string, unknown>);\n\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"tool\", label), false), \"tool label\");\n\t\t} else if (event.type === \"tool_execution_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"tool_execution_end\" };\n\t\t\tconst resultStr = extractToolResultText(agentEvent.result);\n\t\t\tconst pending = pendingTools.get(agentEvent.toolCallId);\n\t\t\tpendingTools.delete(agentEvent.toolCallId);\n\n\t\t\tconst durationMs = pending ? Date.now() - pending.startTime : 0;\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tlog.logToolError(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t} else {\n\t\t\t\tlog.logToolSuccess(logCtx, agentEvent.toolName, durationMs, resultStr);\n\t\t\t}\n\n\t\t\tif (agentEvent.isError) {\n\t\t\t\tqueue.enqueue(\n\t\t\t\t\t() => ctx.respond(formatProgressEntry(\"error\", truncate(resultStr, 200)), false),\n\t\t\t\t\t\"tool error\",\n\t\t\t\t);\n\t\t\t}\n\t\t} else if (event.type === \"message_start\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_start\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tlog.logResponseStart(logCtx);\n\t\t\t}\n\t\t} else if (event.type === \"message_end\") {\n\t\t\tconst agentEvent = event as any & { type: \"message_end\" };\n\t\t\tif (agentEvent.message.role === \"assistant\") {\n\t\t\t\tconst assistantMsg = agentEvent.message as any;\n\n\t\t\t\tif (assistantMsg.stopReason) {\n\t\t\t\t\trunState.stopReason = assistantMsg.stopReason;\n\t\t\t\t}\n\t\t\t\tif (assistantMsg.errorMessage) {\n\t\t\t\t\trunState.errorMessage = assistantMsg.errorMessage;\n\t\t\t\t}\n\n\t\t\t\tif (assistantMsg.usage) {\n\t\t\t\t\trunState.totalUsage.input += assistantMsg.usage.input;\n\t\t\t\t\trunState.totalUsage.output += assistantMsg.usage.output;\n\t\t\t\t\trunState.totalUsage.cacheRead += assistantMsg.usage.cacheRead;\n\t\t\t\t\trunState.totalUsage.cacheWrite += assistantMsg.usage.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.input += assistantMsg.usage.cost.input;\n\t\t\t\t\trunState.totalUsage.cost.output += assistantMsg.usage.cost.output;\n\t\t\t\t\trunState.totalUsage.cost.cacheRead += assistantMsg.usage.cost.cacheRead;\n\t\t\t\t\trunState.totalUsage.cost.cacheWrite += assistantMsg.usage.cost.cacheWrite;\n\t\t\t\t\trunState.totalUsage.cost.total += assistantMsg.usage.cost.total;\n\t\t\t\t}\n\n\t\t\t\tconst content = agentEvent.message.content;\n\t\t\t\tconst thinkingParts: string[] = [];\n\t\t\t\tconst textParts: string[] = [];\n\t\t\t\tlet hasToolCalls = false;\n\t\t\t\tfor (const part of content) {\n\t\t\t\t\tif (part.type === \"thinking\") {\n\t\t\t\t\t\tthinkingParts.push((part as any).thinking);\n\t\t\t\t\t} else if (part.type === \"text\") {\n\t\t\t\t\t\ttextParts.push((part as any).text);\n\t\t\t\t\t} else if (part.type === \"toolCall\") {\n\t\t\t\t\t\thasToolCalls = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tconst text = textParts.join(\"\\n\");\n\n\t\t\t\tfor (const thinking of thinkingParts) {\n\t\t\t\t\tlog.logThinking(logCtx, thinking);\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"thinking\", thinking), false), \"thinking\");\n\t\t\t\t}\n\n\t\t\t\tif (hasToolCalls && text.trim()) {\n\t\t\t\t\tqueue.enqueue(() => ctx.respond(formatProgressEntry(\"assistant\", text), false), \"assistant progress\");\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === \"turn_end\") {\n\t\t\tconst turnEvent = event as any & {\n\t\t\t\ttype: \"turn_end\";\n\t\t\t\tmessage: { role: string; stopReason?: string; content: Array<{ type: string; text?: string }> };\n\t\t\t\ttoolResults: unknown[];\n\t\t\t};\n\t\t\tif (turnEvent.message.role === \"assistant\" && turnEvent.toolResults.length === 0) {\n\t\t\t\tif (turnEvent.message.stopReason === \"error\" || turnEvent.message.stopReason === \"aborted\") {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst finalContent = turnEvent.message.content as Array<{ type: string; text?: string }>;\n\t\t\t\tconst finalText = finalContent\n\t\t\t\t\t.filter((part): part is { type: \"text\"; text: string } => part.type === \"text\" && !!part.text)\n\t\t\t\t\t.map((part) => part.text)\n\t\t\t\t\t.join(\"\\n\");\n\n\t\t\t\tconst trimmedFinalText = finalText.trim();\n\t\t\t\tif (!trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (trimmedFinalText === \"[SILENT]\" || trimmedFinalText.startsWith(\"[SILENT]\")) {\n\t\t\t\t\trunState.finalOutcome = { kind: \"silent\" };\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (runState.finalOutcome.kind === \"final\" && runState.finalOutcome.text.trim() === trimmedFinalText) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\trunState.finalOutcome = { kind: \"final\", text: finalText };\n\t\t\t\tlog.logResponse(logCtx, finalText);\n\t\t\t\tqueue.enqueue(async () => {\n\t\t\t\t\tconst delivered = await ctx.respondPlain(finalText);\n\t\t\t\t\tif (delivered) {\n\t\t\t\t\t\trunState.finalResponseDelivered = true;\n\t\t\t\t\t}\n\t\t\t\t}, \"final response\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_compaction_start\") {\n\t\t\tlog.logInfo(`Auto-compaction started (reason: ${(event as any).reason})`);\n\t\t\tqueue.enqueue(\n\t\t\t\t() => ctx.respond(formatProgressEntry(\"assistant\", \"Compacting context...\"), false),\n\t\t\t\t\"compaction start\",\n\t\t\t);\n\t\t} else if (event.type === \"auto_compaction_end\") {\n\t\t\tconst compEvent = event as any;\n\t\t\tif (compEvent.result) {\n\t\t\t\tlog.logInfo(`Auto-compaction complete: ${compEvent.result.tokensBefore} tokens compacted`);\n\t\t\t} else if (compEvent.aborted) {\n\t\t\t\tlog.logInfo(\"Auto-compaction aborted\");\n\t\t\t}\n\t\t} else if (event.type === \"auto_retry_start\") {\n\t\t\tconst retryEvent = event as any;\n\t\t\tlog.logWarning(`Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})`, retryEvent.errorMessage);\n\t\t\tqueue.enqueue(\n\t\t\t\t() =>\n\t\t\t\t\tctx.respond(\n\t\t\t\t\t\tformatProgressEntry(\"assistant\", `Retrying (${retryEvent.attempt}/${retryEvent.maxAttempts})...`),\n\t\t\t\t\t\tfalse,\n\t\t\t\t\t),\n\t\t\t\t\"retry\",\n\t\t\t);\n\t\t}\n\t});\n\n\treturn {\n\t\tasync handleBuiltinCommand(ctx: DingTalkContext, command: BuiltInCommand): Promise<void> {\n\t\t\tawait handleBuiltInCommand(ctx, command);\n\t\t},\n\n\t\tasync run(ctx: DingTalkContext, _store: ChannelStore): Promise<{ stopReason: string; errorMessage?: string }> {\n\t\t\t// Reset per-run state\n\t\t\trunState.ctx = ctx;\n\t\t\trunState.logCtx = {\n\t\t\t\tchannelId: ctx.message.channel,\n\t\t\t\tuserName: ctx.message.userName,\n\t\t\t\tchannelName: ctx.channelName,\n\t\t\t};\n\t\t\trunState.pendingTools.clear();\n\t\t\trunState.totalUsage = {\n\t\t\t\tinput: 0,\n\t\t\t\toutput: 0,\n\t\t\t\tcacheRead: 0,\n\t\t\t\tcacheWrite: 0,\n\t\t\t\tcost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n\t\t\t};\n\t\t\trunState.stopReason = \"stop\";\n\t\t\trunState.errorMessage = undefined;\n\t\t\trunState.finalOutcome = { kind: \"none\" };\n\t\t\trunState.finalResponseDelivered = false;\n\n\t\t\t// Create queue for this run\n\t\t\tlet queueChain = Promise.resolve();\n\t\t\trunState.queue = {\n\t\t\t\tenqueue(fn: () => Promise<void>, errorContext: string): void {\n\t\t\t\t\tqueueChain = queueChain.then(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait fn();\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(`DingTalk API error (${errorContext})`, errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\tenqueueMessage(text: string, target: \"main\" | \"thread\", errorContext: string, doLog = true): void {\n\t\t\t\t\tthis.enqueue(\n\t\t\t\t\t\t() => (target === \"main\" ? ctx.respond(text, doLog) : ctx.respondInThread(text)),\n\t\t\t\t\t\terrorContext,\n\t\t\t\t\t);\n\t\t\t\t},\n\t\t\t};\n\n\t\t\ttry {\n\t\t\t\t// Ensure channel directory exists\n\t\t\t\tawait mkdir(channelDir, { recursive: true });\n\n\t\t\t\t// Update system prompt and runtime resources with fresh config\n\t\t\t\tconst soul = getSoul(workspaceDir);\n\t\t\t\tconst agentConfig = getAgentConfig(channelDir);\n\t\t\t\tconst memory = getMemory(channelDir);\n\t\t\t\tconst skills = loadPipiclawSkills(channelDir, workspacePath);\n\t\t\t\tcurrentSkills = skills;\n\t\t\t\tconst systemPrompt = buildSystemPrompt(\n\t\t\t\t\tworkspacePath,\n\t\t\t\t\tchannelId,\n\t\t\t\t\tsoul,\n\t\t\t\t\tagentConfig,\n\t\t\t\t\tmemory,\n\t\t\t\t\tsandboxConfig,\n\t\t\t\t\tskills,\n\t\t\t\t);\n\t\t\t\tsession.agent.setSystemPrompt(systemPrompt);\n\t\t\t\tawait session.reload();\n\n\t\t\t\t// Sync messages from log.jsonl\n\t\t\t\tconst syncedCount = syncLogToSessionManager(sessionManager, channelDir, ctx.message.ts);\n\t\t\t\tif (syncedCount > 0) {\n\t\t\t\t\tlog.logInfo(`[${channelId}] Synced ${syncedCount} messages from log.jsonl`);\n\t\t\t\t}\n\n\t\t\t\t// Reload messages from context.jsonl\n\t\t\t\tconst reloadedSession = sessionManager.buildSessionContext();\n\t\t\t\tif (reloadedSession.messages.length > 0) {\n\t\t\t\t\tagent.replaceMessages(reloadedSession.messages);\n\t\t\t\t\tlog.logInfo(`[${channelId}] Reloaded ${reloadedSession.messages.length} messages from context`);\n\t\t\t\t}\n\n\t\t\t\t// Log context info\n\t\t\t\tlog.logInfo(`Context sizes - system: ${systemPrompt.length} chars, memory: ${memory.length} chars`);\n\n\t\t\t\t// Build user message with timestamp and username prefix\n\t\t\t\tconst now = new Date();\n\t\t\t\tconst pad = (n: number) => n.toString().padStart(2, \"0\");\n\t\t\t\tconst offset = -now.getTimezoneOffset();\n\t\t\t\tconst offsetSign = offset >= 0 ? \"+\" : \"-\";\n\t\t\t\tconst offsetHours = pad(Math.floor(Math.abs(offset) / 60));\n\t\t\t\tconst offsetMins = pad(Math.abs(offset) % 60);\n\t\t\t\tconst timestamp = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}${offsetSign}${offsetHours}:${offsetMins}`;\n\t\t\t\tconst userMessage = `[${timestamp}] [${ctx.message.userName || \"unknown\"}]: ${ctx.message.text}`;\n\n\t\t\t\t// Debug: write context to last_prompt.json (only with PIPICLAW_DEBUG=1)\n\t\t\t\tif (process.env.PIPICLAW_DEBUG) {\n\t\t\t\t\tconst debugContext = {\n\t\t\t\t\t\tsystemPrompt,\n\t\t\t\t\t\tmessages: session.messages,\n\t\t\t\t\t\tnewUserMessage: userMessage,\n\t\t\t\t\t};\n\t\t\t\t\tawait writeFile(join(channelDir, \"last_prompt.json\"), JSON.stringify(debugContext, null, 2));\n\t\t\t\t}\n\n\t\t\t\tawait session.prompt(userMessage);\n\t\t\t} catch (err) {\n\t\t\t\trunState.stopReason = \"error\";\n\t\t\t\trunState.errorMessage = err instanceof Error ? err.message : String(err);\n\t\t\t\tlog.logWarning(`[${channelId}] Runner failed`, runState.errorMessage);\n\t\t\t} finally {\n\t\t\t\tawait queueChain;\n\t\t\t\tconst finalOutcome = runState.finalOutcome;\n\t\t\t\tconst finalOutcomeText = getFinalOutcomeText(finalOutcome);\n\n\t\t\t\ttry {\n\t\t\t\t\tif (runState.stopReason === \"error\" && runState.errorMessage && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(\"_Sorry, something went wrong_\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to post error message\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (isSilentOutcome(finalOutcome)) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.deleteMessage();\n\t\t\t\t\t\t\tlog.logInfo(\"Silent response - deleted message\");\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to delete message for silent response\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (finalOutcomeText && !runState.finalResponseDelivered) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait ctx.replaceMessage(finalOutcomeText);\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconst errMsg = err instanceof Error ? err.message : String(err);\n\t\t\t\t\t\t\tlog.logWarning(\"Failed to replace message with final text\", errMsg);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tawait ctx.flush();\n\t\t\t\t} finally {\n\t\t\t\t\tawait ctx.close();\n\t\t\t\t}\n\n\t\t\t\t// Log usage summary\n\t\t\t\tif (runState.totalUsage.cost.total > 0) {\n\t\t\t\t\tconst messages = session.messages;\n\t\t\t\t\tconst lastAssistantMessage = messages\n\t\t\t\t\t\t.slice()\n\t\t\t\t\t\t.reverse()\n\t\t\t\t\t\t.find((m: any) => m.role === \"assistant\" && m.stopReason !== \"aborted\") as any;\n\n\t\t\t\t\tconst contextTokens = lastAssistantMessage\n\t\t\t\t\t\t? lastAssistantMessage.usage.input +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.output +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheRead +\n\t\t\t\t\t\t\tlastAssistantMessage.usage.cacheWrite\n\t\t\t\t\t\t: 0;\n\t\t\t\t\tconst currentRunModel = session.model ?? activeModel;\n\t\t\t\t\tconst contextWindow = currentRunModel.contextWindow || 200000;\n\n\t\t\t\t\tlog.logUsageSummary(runState.logCtx!, runState.totalUsage, contextTokens, contextWindow);\n\t\t\t\t}\n\n\t\t\t\t// Clear run state\n\t\t\t\trunState.ctx = null;\n\t\t\t\trunState.logCtx = null;\n\t\t\t\trunState.queue = null;\n\t\t\t}\n\n\t\t\treturn { stopReason: runState.stopReason, errorMessage: runState.errorMessage };\n\t\t},\n\n\t\tabort(): void {\n\t\t\tsession.abort();\n\t\t},\n\t};\n}\n\n/**\n * Translate container path back to host path for file operations\n */\nexport function translateToHostPath(\n\tcontainerPath: string,\n\tchannelDir: string,\n\tworkspacePath: string,\n\tchannelId: string,\n): string {\n\tif (workspacePath === \"/workspace\") {\n\t\tconst prefix = `/workspace/${channelId}/`;\n\t\tif (containerPath.startsWith(prefix)) {\n\t\t\treturn join(channelDir, containerPath.slice(prefix.length));\n\t\t}\n\t\tif (containerPath.startsWith(\"/workspace/\")) {\n\t\t\treturn join(channelDir, \"..\", containerPath.slice(\"/workspace/\".length));\n\t\t}\n\t}\n\treturn containerPath;\n}\n"]}
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { parseBuiltInCommand } from \"./commands.js\";\nimport { createDingTalkContext } from \"./delivery.js\";\nimport { DingTalkBot, type DingTalkConfig, type DingTalkEvent, type DingTalkHandler } from \"./dingtalk.js\";\nimport { createEventsWatcher } from \"./events.js\";\nimport * as log from \"./log.js\";\nimport {\n\tAPP_HOME_DIR,\n\tAPP_NAME,\n\tAUTH_CONFIG_PATH,\n\tCHANNEL_CONFIG_PATH,\n\tMODELS_CONFIG_PATH,\n\tSETTINGS_CONFIG_PATH,\n\tWORKSPACE_DIR,\n} from \"./paths.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { ChannelStore } from \"./store.js\";\n\nif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\tdelete process.env.http_proxy;\n\tdelete process.env.https_proxy;\n\tdelete process.env.all_proxy;\n\tdelete process.env.HTTP_PROXY;\n\tdelete process.env.HTTPS_PROXY;\n\tdelete process.env.ALL_PROXY;\n}\n\ninterface DingTalkAppConfig extends DingTalkConfig {}\n\ninterface ParsedArgs {\n\tsandbox: SandboxConfig;\n}\n\ninterface BootstrapResult {\n\tcreated: string[];\n\tchannelTemplateCreated: boolean;\n}\n\nconst DEFAULT_SOUL = `# SOUL.md\n\nConfigure Pipiclaw's identity, voice, and communication style here.\n\nSuggested sections:\n\n- Who the assistant is\n- Default language\n- Tone and personality\n- Reply style\n- Formatting preferences\n\nExample topics you may want to define:\n\n- \"Answer in Chinese by default.\"\n- \"Be concise and direct.\"\n- \"Prefer Markdown.\"\n- \"Act as an engineering assistant for our team.\"\n\nReplace this template with your actual identity prompt.\n`;\n\nconst DEFAULT_AGENT = `# AGENT.md\n\nConfigure Pipiclaw's behavioral rules and operating constraints here.\n\nSuggested sections:\n\n- Tool usage policy\n- File access rules\n- Security constraints\n- Approval rules\n- Project-specific workflows\n- Things the assistant must always or never do\n\nExample topics you may want to define:\n\n- Which tools should be preferred first\n- Whether installation commands are allowed\n- How to handle sensitive data\n- Coding conventions for your team\n- Deployment or release restrictions\n\nReplace this template with your actual operating instructions.\n`;\n\nconst DEFAULT_MEMORY = `# 工作记忆\n\n(尚无记录)\n`;\n\nconst CHANNEL_CONFIG_TEMPLATE = {\n\tclientId: \"your-dingtalk-client-id\",\n\tclientSecret: \"your-dingtalk-client-secret\",\n\trobotCode: \"your-robot-code\",\n\tcardTemplateId: \"your-card-template-id\",\n\tcardTemplateKey: \"content\",\n\tallowFrom: [\"your-staff-id\"],\n} satisfies DingTalkAppConfig;\n\nconst MODELS_CONFIG_TEMPLATE = { providers: {} };\n\nfunction writeTextFileIfMissing(path: string, content: string, label: string, created: string[]): boolean {\n\tif (existsSync(path)) {\n\t\treturn false;\n\t}\n\twriteFileSync(path, content, \"utf-8\");\n\tcreated.push(label);\n\treturn true;\n}\n\nfunction writeJsonFileIfMissing(path: string, value: unknown, label: string, created: string[]): boolean {\n\treturn writeTextFileIfMissing(path, `${JSON.stringify(value, null, 2)}\\n`, label, created);\n}\n\nfunction bootstrapAppHome(): BootstrapResult {\n\tconst created: string[] = [];\n\n\tif (!existsSync(APP_HOME_DIR)) {\n\t\tmkdirSync(APP_HOME_DIR, { recursive: true });\n\t\tcreated.push(\"app home\");\n\t}\n\tif (!existsSync(WORKSPACE_DIR)) {\n\t\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n\t\tcreated.push(\"workspace/\");\n\t}\n\n\tfor (const dir of [\"skills\", \"events\"]) {\n\t\tconst dirPath = join(WORKSPACE_DIR, dir);\n\t\tif (!existsSync(dirPath)) {\n\t\t\tmkdirSync(dirPath, { recursive: true });\n\t\t\tcreated.push(`workspace/${dir}/`);\n\t\t}\n\t}\n\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"SOUL.md\"), DEFAULT_SOUL, \"workspace/SOUL.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"AGENT.md\"), DEFAULT_AGENT, \"workspace/AGENT.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"MEMORY.md\"), DEFAULT_MEMORY, \"workspace/MEMORY.md\", created);\n\n\tconst channelTemplateCreated = writeJsonFileIfMissing(\n\t\tCHANNEL_CONFIG_PATH,\n\t\tCHANNEL_CONFIG_TEMPLATE,\n\t\t\"channel.json\",\n\t\tcreated,\n\t);\n\twriteJsonFileIfMissing(AUTH_CONFIG_PATH, {}, \"auth.json\", created);\n\twriteJsonFileIfMissing(MODELS_CONFIG_PATH, MODELS_CONFIG_TEMPLATE, \"models.json\", created);\n\twriteJsonFileIfMissing(SETTINGS_CONFIG_PATH, {}, \"settings.json\", created);\n\n\treturn { created, channelTemplateCreated };\n}\n\nfunction isPlaceholderString(value: string): boolean {\n\treturn value.trim().startsWith(\"your-\");\n}\n\nfunction listChannelConfigIssues(config: Partial<DingTalkAppConfig>): string[] {\n\tconst issues: string[] = [];\n\n\tif (!config.clientId) {\n\t\tissues.push(\"Missing required field `clientId`.\");\n\t} else if (isPlaceholderString(config.clientId)) {\n\t\tissues.push(\"Replace placeholder value for `clientId`.\");\n\t}\n\n\tif (!config.clientSecret) {\n\t\tissues.push(\"Missing required field `clientSecret`.\");\n\t} else if (isPlaceholderString(config.clientSecret)) {\n\t\tissues.push(\"Replace placeholder value for `clientSecret`.\");\n\t}\n\n\tif (config.robotCode && isPlaceholderString(config.robotCode)) {\n\t\tissues.push(\"Replace placeholder value for `robotCode`, or set it to an empty string to reuse `clientId`.\");\n\t}\n\n\tif (config.cardTemplateId && isPlaceholderString(config.cardTemplateId)) {\n\t\tissues.push(\n\t\t\t\"Replace placeholder value for `cardTemplateId`, or set it to an empty string to disable AI Card streaming.\",\n\t\t);\n\t}\n\n\tif (Array.isArray(config.allowFrom) && config.allowFrom.some((value) => isPlaceholderString(value))) {\n\t\tissues.push(\"Replace placeholder values in `allowFrom`, or set it to an empty array to allow all users.\");\n\t}\n\n\treturn issues;\n}\n\nfunction printBootstrapSummary(result: BootstrapResult): void {\n\tif (result.created.length === 0) {\n\t\treturn;\n\t}\n\n\tconsole.log(`Initialized ${APP_NAME} under ${APP_HOME_DIR}:`);\n\tfor (const item of result.created) {\n\t\tconsole.log(` - ${item}`);\n\t}\n\tconsole.log(\"\");\n}\n\nfunction loadConfig(): DingTalkAppConfig {\n\tlet parsed: DingTalkAppConfig;\n\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(CHANNEL_CONFIG_PATH, \"utf-8\")) as DingTalkAppConfig;\n\t} catch (err) {\n\t\tconsole.error(`Failed to parse configuration: ${CHANNEL_CONFIG_PATH}`);\n\t\tconsole.error(err instanceof Error ? err.message : String(err));\n\t\tprocess.exit(1);\n\t}\n\n\tconst issues = listChannelConfigIssues(parsed);\n\tif (issues.length > 0) {\n\t\tconsole.error(`Configuration is not ready: ${CHANNEL_CONFIG_PATH}`);\n\t\tfor (const issue of issues) {\n\t\t\tconsole.error(` - ${issue}`);\n\t\t}\n\t\tconsole.error(\"\");\n\t\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\t\tprocess.exit(1);\n\t}\n\n\tparsed.cardTemplateKey = parsed.cardTemplateKey || \"content\";\n\tparsed.robotCode = parsed.robotCode?.trim() ? parsed.robotCode : parsed.clientId;\n\tif (Array.isArray(parsed.allowFrom)) {\n\t\tparsed.allowFrom = parsed.allowFrom.filter((value) => value.trim().length > 0);\n\t}\n\n\treturn parsed;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\tconsole.log(`Usage: ${APP_NAME} [options]`);\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(\"Options:\");\n\t\t\tconsole.log(\" --sandbox=host Run tools on host (default)\");\n\t\t\tconsole.log(\" --sandbox=docker:<name> Run tools in Docker container\");\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(`Config: ${CHANNEL_CONFIG_PATH}`);\n\t\t\tconsole.log(`Workspace: ${WORKSPACE_DIR}`);\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\treturn { sandbox };\n}\n\nconst parsedArgs = parseArgs();\nconst sandbox = parsedArgs.sandbox;\nconst bootstrapResult = bootstrapAppHome();\nprintBootstrapSummary(bootstrapResult);\n\nif (bootstrapResult.channelTemplateCreated) {\n\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\tprocess.exit(1);\n}\n\nconst dingtalkConfig = loadConfig();\ndingtalkConfig.stateDir = WORKSPACE_DIR;\n\nawait validateSandbox(sandbox);\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(WORKSPACE_DIR, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir: WORKSPACE_DIR }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\nconst handler: DingTalkHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, _bot: DingTalkBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tlog.logInfo(`[${channelId}] Stop requested`);\n\t\t}\n\t},\n\n\tasync handleEvent(event: DingTalkEvent, bot: DingTalkBot, _isEvent?: boolean): Promise<void> {\n\t\tconst state = getState(event.channelId);\n\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tstate.store.logMessage(event.channelId, {\n\t\t\tdate: new Date().toISOString(),\n\t\t\tts: event.ts,\n\t\t\tuser: event.user,\n\t\t\tuserName: event.userName,\n\t\t\ttext: event.text,\n\t\t\tisBot: false,\n\t\t});\n\n\t\ttry {\n\t\t\tconst ctx = createDingTalkContext(event, bot, state.store);\n\t\t\tconst builtInCommand = parseBuiltInCommand(event.text);\n\n\t\t\tif (builtInCommand) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Executing command: ${builtInCommand.rawText}`);\n\t\t\t\tawait state.runner.handleBuiltinCommand(ctx, builtInCommand);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlog.logInfo(`[${event.channelId}] Starting run: ${event.text.substring(0, 50)}`);\n\t\t\tconst result = await state.runner.run(ctx, state.store);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Stopped`);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channelId}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\nlog.logStartup(WORKSPACE_DIR, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\nif (!existsSync(WORKSPACE_DIR)) {\n\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n}\n\nconst bot = new DingTalkBot(handler, dingtalkConfig);\nconst eventsWatcher = createEventsWatcher(WORKSPACE_DIR, bot);\neventsWatcher.start();\n\nprocess.on(\"SIGINT\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nbot.start();\n"]}
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"","sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { parseBuiltInCommand } from \"./commands.js\";\nimport { createDingTalkContext } from \"./delivery.js\";\nimport { DingTalkBot, type DingTalkConfig, type DingTalkEvent, type DingTalkHandler } from \"./dingtalk.js\";\nimport { createEventsWatcher } from \"./events.js\";\nimport * as log from \"./log.js\";\nimport {\n\tAPP_HOME_DIR,\n\tAPP_NAME,\n\tAUTH_CONFIG_PATH,\n\tCHANNEL_CONFIG_PATH,\n\tMODELS_CONFIG_PATH,\n\tSETTINGS_CONFIG_PATH,\n\tWORKSPACE_DIR,\n} from \"./paths.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { ChannelStore } from \"./store.js\";\n\nif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\tdelete process.env.http_proxy;\n\tdelete process.env.https_proxy;\n\tdelete process.env.all_proxy;\n\tdelete process.env.HTTP_PROXY;\n\tdelete process.env.HTTPS_PROXY;\n\tdelete process.env.ALL_PROXY;\n}\n\ninterface DingTalkAppConfig extends DingTalkConfig {}\n\ninterface ParsedArgs {\n\tsandbox: SandboxConfig;\n}\n\ninterface BootstrapResult {\n\tcreated: string[];\n\tchannelTemplateCreated: boolean;\n}\n\nconst DEFAULT_SOUL = `# SOUL.md\n\nConfigure Pipiclaw's identity, voice, and communication style here.\n\nSuggested sections:\n\n- Who the assistant is\n- Default language\n- Tone and personality\n- Reply style\n- Formatting preferences\n\nExample topics you may want to define:\n\n- \"Answer in Chinese by default.\"\n- \"Be concise and direct.\"\n- \"Prefer Markdown.\"\n- \"Act as an engineering assistant for our team.\"\n\nReplace this template with your actual identity prompt.\n`;\n\nconst DEFAULT_AGENT = `# AGENTS.md\n\nConfigure Pipiclaw's behavioral rules and operating constraints here.\n\nSuggested sections:\n\n- Tool usage policy\n- File access rules\n- Security constraints\n- Approval rules\n- Project-specific workflows\n- Things the assistant must always or never do\n\nExample topics you may want to define:\n\n- Which tools should be preferred first\n- Whether installation commands are allowed\n- How to handle sensitive data\n- Coding conventions for your team\n- Deployment or release restrictions\n\nReplace this template with your actual operating instructions.\n`;\n\nconst DEFAULT_MEMORY = `# 工作记忆\n\n(尚无记录)\n`;\n\nconst CHANNEL_CONFIG_TEMPLATE = {\n\tclientId: \"your-dingtalk-client-id\",\n\tclientSecret: \"your-dingtalk-client-secret\",\n\trobotCode: \"your-robot-code\",\n\tcardTemplateId: \"your-card-template-id\",\n\tcardTemplateKey: \"content\",\n\tallowFrom: [\"your-staff-id\"],\n} satisfies DingTalkAppConfig;\n\nconst MODELS_CONFIG_TEMPLATE = { providers: {} };\n\nfunction writeTextFileIfMissing(path: string, content: string, label: string, created: string[]): boolean {\n\tif (existsSync(path)) {\n\t\treturn false;\n\t}\n\twriteFileSync(path, content, \"utf-8\");\n\tcreated.push(label);\n\treturn true;\n}\n\nfunction writeJsonFileIfMissing(path: string, value: unknown, label: string, created: string[]): boolean {\n\treturn writeTextFileIfMissing(path, `${JSON.stringify(value, null, 2)}\\n`, label, created);\n}\n\nfunction bootstrapAppHome(): BootstrapResult {\n\tconst created: string[] = [];\n\n\tif (!existsSync(APP_HOME_DIR)) {\n\t\tmkdirSync(APP_HOME_DIR, { recursive: true });\n\t\tcreated.push(\"app home\");\n\t}\n\tif (!existsSync(WORKSPACE_DIR)) {\n\t\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n\t\tcreated.push(\"workspace/\");\n\t}\n\n\tfor (const dir of [\"skills\", \"events\"]) {\n\t\tconst dirPath = join(WORKSPACE_DIR, dir);\n\t\tif (!existsSync(dirPath)) {\n\t\t\tmkdirSync(dirPath, { recursive: true });\n\t\t\tcreated.push(`workspace/${dir}/`);\n\t\t}\n\t}\n\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"SOUL.md\"), DEFAULT_SOUL, \"workspace/SOUL.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"AGENTS.md\"), DEFAULT_AGENT, \"workspace/AGENTS.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"MEMORY.md\"), DEFAULT_MEMORY, \"workspace/MEMORY.md\", created);\n\n\tconst channelTemplateCreated = writeJsonFileIfMissing(\n\t\tCHANNEL_CONFIG_PATH,\n\t\tCHANNEL_CONFIG_TEMPLATE,\n\t\t\"channel.json\",\n\t\tcreated,\n\t);\n\twriteJsonFileIfMissing(AUTH_CONFIG_PATH, {}, \"auth.json\", created);\n\twriteJsonFileIfMissing(MODELS_CONFIG_PATH, MODELS_CONFIG_TEMPLATE, \"models.json\", created);\n\twriteJsonFileIfMissing(SETTINGS_CONFIG_PATH, {}, \"settings.json\", created);\n\n\treturn { created, channelTemplateCreated };\n}\n\nfunction isPlaceholderString(value: string): boolean {\n\treturn value.trim().startsWith(\"your-\");\n}\n\nfunction listChannelConfigIssues(config: Partial<DingTalkAppConfig>): string[] {\n\tconst issues: string[] = [];\n\n\tif (!config.clientId) {\n\t\tissues.push(\"Missing required field `clientId`.\");\n\t} else if (isPlaceholderString(config.clientId)) {\n\t\tissues.push(\"Replace placeholder value for `clientId`.\");\n\t}\n\n\tif (!config.clientSecret) {\n\t\tissues.push(\"Missing required field `clientSecret`.\");\n\t} else if (isPlaceholderString(config.clientSecret)) {\n\t\tissues.push(\"Replace placeholder value for `clientSecret`.\");\n\t}\n\n\tif (config.robotCode && isPlaceholderString(config.robotCode)) {\n\t\tissues.push(\"Replace placeholder value for `robotCode`, or set it to an empty string to reuse `clientId`.\");\n\t}\n\n\tif (config.cardTemplateId && isPlaceholderString(config.cardTemplateId)) {\n\t\tissues.push(\n\t\t\t\"Replace placeholder value for `cardTemplateId`, or set it to an empty string to disable AI Card streaming.\",\n\t\t);\n\t}\n\n\tif (Array.isArray(config.allowFrom) && config.allowFrom.some((value) => isPlaceholderString(value))) {\n\t\tissues.push(\"Replace placeholder values in `allowFrom`, or set it to an empty array to allow all users.\");\n\t}\n\n\treturn issues;\n}\n\nfunction printBootstrapSummary(result: BootstrapResult): void {\n\tif (result.created.length === 0) {\n\t\treturn;\n\t}\n\n\tconsole.log(`Initialized ${APP_NAME} under ${APP_HOME_DIR}:`);\n\tfor (const item of result.created) {\n\t\tconsole.log(` - ${item}`);\n\t}\n\tconsole.log(\"\");\n}\n\nfunction loadConfig(): DingTalkAppConfig {\n\tlet parsed: DingTalkAppConfig;\n\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(CHANNEL_CONFIG_PATH, \"utf-8\")) as DingTalkAppConfig;\n\t} catch (err) {\n\t\tconsole.error(`Failed to parse configuration: ${CHANNEL_CONFIG_PATH}`);\n\t\tconsole.error(err instanceof Error ? err.message : String(err));\n\t\tprocess.exit(1);\n\t}\n\n\tconst issues = listChannelConfigIssues(parsed);\n\tif (issues.length > 0) {\n\t\tconsole.error(`Configuration is not ready: ${CHANNEL_CONFIG_PATH}`);\n\t\tfor (const issue of issues) {\n\t\t\tconsole.error(` - ${issue}`);\n\t\t}\n\t\tconsole.error(\"\");\n\t\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\t\tprocess.exit(1);\n\t}\n\n\tparsed.cardTemplateKey = parsed.cardTemplateKey || \"content\";\n\tparsed.robotCode = parsed.robotCode?.trim() ? parsed.robotCode : parsed.clientId;\n\tif (Array.isArray(parsed.allowFrom)) {\n\t\tparsed.allowFrom = parsed.allowFrom.filter((value) => value.trim().length > 0);\n\t}\n\n\treturn parsed;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\tconsole.log(`Usage: ${APP_NAME} [options]`);\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(\"Options:\");\n\t\t\tconsole.log(\" --sandbox=host Run tools on host (default)\");\n\t\t\tconsole.log(\" --sandbox=docker:<name> Run tools in Docker container\");\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(`Config: ${CHANNEL_CONFIG_PATH}`);\n\t\t\tconsole.log(`Workspace: ${WORKSPACE_DIR}`);\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\treturn { sandbox };\n}\n\nconst parsedArgs = parseArgs();\nconst sandbox = parsedArgs.sandbox;\nconst bootstrapResult = bootstrapAppHome();\nprintBootstrapSummary(bootstrapResult);\n\nif (bootstrapResult.channelTemplateCreated) {\n\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\tprocess.exit(1);\n}\n\nconst dingtalkConfig = loadConfig();\ndingtalkConfig.stateDir = WORKSPACE_DIR;\n\nawait validateSandbox(sandbox);\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(WORKSPACE_DIR, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir: WORKSPACE_DIR }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\nconst handler: DingTalkHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, _bot: DingTalkBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tlog.logInfo(`[${channelId}] Stop requested`);\n\t\t}\n\t},\n\n\tasync handleEvent(event: DingTalkEvent, bot: DingTalkBot, _isEvent?: boolean): Promise<void> {\n\t\tconst state = getState(event.channelId);\n\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tstate.store.logMessage(event.channelId, {\n\t\t\tdate: new Date().toISOString(),\n\t\t\tts: event.ts,\n\t\t\tuser: event.user,\n\t\t\tuserName: event.userName,\n\t\t\ttext: event.text,\n\t\t\tisBot: false,\n\t\t});\n\n\t\ttry {\n\t\t\tconst ctx = createDingTalkContext(event, bot, state.store);\n\t\t\tconst builtInCommand = parseBuiltInCommand(event.text);\n\n\t\t\tif (builtInCommand) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Executing command: ${builtInCommand.rawText}`);\n\t\t\t\tawait state.runner.handleBuiltinCommand(ctx, builtInCommand);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlog.logInfo(`[${event.channelId}] Starting run: ${event.text.substring(0, 50)}`);\n\t\t\tconst result = await state.runner.run(ctx, state.store);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Stopped`);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channelId}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\nlog.logStartup(WORKSPACE_DIR, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\nif (!existsSync(WORKSPACE_DIR)) {\n\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n}\n\nconst bot = new DingTalkBot(handler, dingtalkConfig);\nconst eventsWatcher = createEventsWatcher(WORKSPACE_DIR, bot);\neventsWatcher.start();\n\nprocess.on(\"SIGINT\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nbot.start();\n"]}
|
package/dist/main.js
CHANGED
|
@@ -39,7 +39,7 @@ Example topics you may want to define:
|
|
|
39
39
|
|
|
40
40
|
Replace this template with your actual identity prompt.
|
|
41
41
|
`;
|
|
42
|
-
const DEFAULT_AGENT = `#
|
|
42
|
+
const DEFAULT_AGENT = `# AGENTS.md
|
|
43
43
|
|
|
44
44
|
Configure Pipiclaw's behavioral rules and operating constraints here.
|
|
45
45
|
|
|
@@ -104,7 +104,7 @@ function bootstrapAppHome() {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
writeTextFileIfMissing(join(WORKSPACE_DIR, "SOUL.md"), DEFAULT_SOUL, "workspace/SOUL.md", created);
|
|
107
|
-
writeTextFileIfMissing(join(WORKSPACE_DIR, "
|
|
107
|
+
writeTextFileIfMissing(join(WORKSPACE_DIR, "AGENTS.md"), DEFAULT_AGENT, "workspace/AGENTS.md", created);
|
|
108
108
|
writeTextFileIfMissing(join(WORKSPACE_DIR, "MEMORY.md"), DEFAULT_MEMORY, "workspace/MEMORY.md", created);
|
|
109
109
|
const channelTemplateCreated = writeJsonFileIfMissing(CHANNEL_CONFIG_PATH, CHANNEL_CONFIG_TEMPLATE, "channel.json", created);
|
|
110
110
|
writeJsonFileIfMissing(AUTH_CONFIG_PATH, {}, "auth.json", created);
|
package/dist/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { parseBuiltInCommand } from \"./commands.js\";\nimport { createDingTalkContext } from \"./delivery.js\";\nimport { DingTalkBot, type DingTalkConfig, type DingTalkEvent, type DingTalkHandler } from \"./dingtalk.js\";\nimport { createEventsWatcher } from \"./events.js\";\nimport * as log from \"./log.js\";\nimport {\n\tAPP_HOME_DIR,\n\tAPP_NAME,\n\tAUTH_CONFIG_PATH,\n\tCHANNEL_CONFIG_PATH,\n\tMODELS_CONFIG_PATH,\n\tSETTINGS_CONFIG_PATH,\n\tWORKSPACE_DIR,\n} from \"./paths.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { ChannelStore } from \"./store.js\";\n\nif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\tdelete process.env.http_proxy;\n\tdelete process.env.https_proxy;\n\tdelete process.env.all_proxy;\n\tdelete process.env.HTTP_PROXY;\n\tdelete process.env.HTTPS_PROXY;\n\tdelete process.env.ALL_PROXY;\n}\n\ninterface DingTalkAppConfig extends DingTalkConfig {}\n\ninterface ParsedArgs {\n\tsandbox: SandboxConfig;\n}\n\ninterface BootstrapResult {\n\tcreated: string[];\n\tchannelTemplateCreated: boolean;\n}\n\nconst DEFAULT_SOUL = `# SOUL.md\n\nConfigure Pipiclaw's identity, voice, and communication style here.\n\nSuggested sections:\n\n- Who the assistant is\n- Default language\n- Tone and personality\n- Reply style\n- Formatting preferences\n\nExample topics you may want to define:\n\n- \"Answer in Chinese by default.\"\n- \"Be concise and direct.\"\n- \"Prefer Markdown.\"\n- \"Act as an engineering assistant for our team.\"\n\nReplace this template with your actual identity prompt.\n`;\n\nconst DEFAULT_AGENT = `# AGENT.md\n\nConfigure Pipiclaw's behavioral rules and operating constraints here.\n\nSuggested sections:\n\n- Tool usage policy\n- File access rules\n- Security constraints\n- Approval rules\n- Project-specific workflows\n- Things the assistant must always or never do\n\nExample topics you may want to define:\n\n- Which tools should be preferred first\n- Whether installation commands are allowed\n- How to handle sensitive data\n- Coding conventions for your team\n- Deployment or release restrictions\n\nReplace this template with your actual operating instructions.\n`;\n\nconst DEFAULT_MEMORY = `# 工作记忆\n\n(尚无记录)\n`;\n\nconst CHANNEL_CONFIG_TEMPLATE = {\n\tclientId: \"your-dingtalk-client-id\",\n\tclientSecret: \"your-dingtalk-client-secret\",\n\trobotCode: \"your-robot-code\",\n\tcardTemplateId: \"your-card-template-id\",\n\tcardTemplateKey: \"content\",\n\tallowFrom: [\"your-staff-id\"],\n} satisfies DingTalkAppConfig;\n\nconst MODELS_CONFIG_TEMPLATE = { providers: {} };\n\nfunction writeTextFileIfMissing(path: string, content: string, label: string, created: string[]): boolean {\n\tif (existsSync(path)) {\n\t\treturn false;\n\t}\n\twriteFileSync(path, content, \"utf-8\");\n\tcreated.push(label);\n\treturn true;\n}\n\nfunction writeJsonFileIfMissing(path: string, value: unknown, label: string, created: string[]): boolean {\n\treturn writeTextFileIfMissing(path, `${JSON.stringify(value, null, 2)}\\n`, label, created);\n}\n\nfunction bootstrapAppHome(): BootstrapResult {\n\tconst created: string[] = [];\n\n\tif (!existsSync(APP_HOME_DIR)) {\n\t\tmkdirSync(APP_HOME_DIR, { recursive: true });\n\t\tcreated.push(\"app home\");\n\t}\n\tif (!existsSync(WORKSPACE_DIR)) {\n\t\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n\t\tcreated.push(\"workspace/\");\n\t}\n\n\tfor (const dir of [\"skills\", \"events\"]) {\n\t\tconst dirPath = join(WORKSPACE_DIR, dir);\n\t\tif (!existsSync(dirPath)) {\n\t\t\tmkdirSync(dirPath, { recursive: true });\n\t\t\tcreated.push(`workspace/${dir}/`);\n\t\t}\n\t}\n\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"SOUL.md\"), DEFAULT_SOUL, \"workspace/SOUL.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"AGENT.md\"), DEFAULT_AGENT, \"workspace/AGENT.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"MEMORY.md\"), DEFAULT_MEMORY, \"workspace/MEMORY.md\", created);\n\n\tconst channelTemplateCreated = writeJsonFileIfMissing(\n\t\tCHANNEL_CONFIG_PATH,\n\t\tCHANNEL_CONFIG_TEMPLATE,\n\t\t\"channel.json\",\n\t\tcreated,\n\t);\n\twriteJsonFileIfMissing(AUTH_CONFIG_PATH, {}, \"auth.json\", created);\n\twriteJsonFileIfMissing(MODELS_CONFIG_PATH, MODELS_CONFIG_TEMPLATE, \"models.json\", created);\n\twriteJsonFileIfMissing(SETTINGS_CONFIG_PATH, {}, \"settings.json\", created);\n\n\treturn { created, channelTemplateCreated };\n}\n\nfunction isPlaceholderString(value: string): boolean {\n\treturn value.trim().startsWith(\"your-\");\n}\n\nfunction listChannelConfigIssues(config: Partial<DingTalkAppConfig>): string[] {\n\tconst issues: string[] = [];\n\n\tif (!config.clientId) {\n\t\tissues.push(\"Missing required field `clientId`.\");\n\t} else if (isPlaceholderString(config.clientId)) {\n\t\tissues.push(\"Replace placeholder value for `clientId`.\");\n\t}\n\n\tif (!config.clientSecret) {\n\t\tissues.push(\"Missing required field `clientSecret`.\");\n\t} else if (isPlaceholderString(config.clientSecret)) {\n\t\tissues.push(\"Replace placeholder value for `clientSecret`.\");\n\t}\n\n\tif (config.robotCode && isPlaceholderString(config.robotCode)) {\n\t\tissues.push(\"Replace placeholder value for `robotCode`, or set it to an empty string to reuse `clientId`.\");\n\t}\n\n\tif (config.cardTemplateId && isPlaceholderString(config.cardTemplateId)) {\n\t\tissues.push(\n\t\t\t\"Replace placeholder value for `cardTemplateId`, or set it to an empty string to disable AI Card streaming.\",\n\t\t);\n\t}\n\n\tif (Array.isArray(config.allowFrom) && config.allowFrom.some((value) => isPlaceholderString(value))) {\n\t\tissues.push(\"Replace placeholder values in `allowFrom`, or set it to an empty array to allow all users.\");\n\t}\n\n\treturn issues;\n}\n\nfunction printBootstrapSummary(result: BootstrapResult): void {\n\tif (result.created.length === 0) {\n\t\treturn;\n\t}\n\n\tconsole.log(`Initialized ${APP_NAME} under ${APP_HOME_DIR}:`);\n\tfor (const item of result.created) {\n\t\tconsole.log(` - ${item}`);\n\t}\n\tconsole.log(\"\");\n}\n\nfunction loadConfig(): DingTalkAppConfig {\n\tlet parsed: DingTalkAppConfig;\n\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(CHANNEL_CONFIG_PATH, \"utf-8\")) as DingTalkAppConfig;\n\t} catch (err) {\n\t\tconsole.error(`Failed to parse configuration: ${CHANNEL_CONFIG_PATH}`);\n\t\tconsole.error(err instanceof Error ? err.message : String(err));\n\t\tprocess.exit(1);\n\t}\n\n\tconst issues = listChannelConfigIssues(parsed);\n\tif (issues.length > 0) {\n\t\tconsole.error(`Configuration is not ready: ${CHANNEL_CONFIG_PATH}`);\n\t\tfor (const issue of issues) {\n\t\t\tconsole.error(` - ${issue}`);\n\t\t}\n\t\tconsole.error(\"\");\n\t\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\t\tprocess.exit(1);\n\t}\n\n\tparsed.cardTemplateKey = parsed.cardTemplateKey || \"content\";\n\tparsed.robotCode = parsed.robotCode?.trim() ? parsed.robotCode : parsed.clientId;\n\tif (Array.isArray(parsed.allowFrom)) {\n\t\tparsed.allowFrom = parsed.allowFrom.filter((value) => value.trim().length > 0);\n\t}\n\n\treturn parsed;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\tconsole.log(`Usage: ${APP_NAME} [options]`);\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(\"Options:\");\n\t\t\tconsole.log(\" --sandbox=host Run tools on host (default)\");\n\t\t\tconsole.log(\" --sandbox=docker:<name> Run tools in Docker container\");\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(`Config: ${CHANNEL_CONFIG_PATH}`);\n\t\t\tconsole.log(`Workspace: ${WORKSPACE_DIR}`);\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\treturn { sandbox };\n}\n\nconst parsedArgs = parseArgs();\nconst sandbox = parsedArgs.sandbox;\nconst bootstrapResult = bootstrapAppHome();\nprintBootstrapSummary(bootstrapResult);\n\nif (bootstrapResult.channelTemplateCreated) {\n\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\tprocess.exit(1);\n}\n\nconst dingtalkConfig = loadConfig();\ndingtalkConfig.stateDir = WORKSPACE_DIR;\n\nawait validateSandbox(sandbox);\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(WORKSPACE_DIR, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir: WORKSPACE_DIR }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\nconst handler: DingTalkHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, _bot: DingTalkBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tlog.logInfo(`[${channelId}] Stop requested`);\n\t\t}\n\t},\n\n\tasync handleEvent(event: DingTalkEvent, bot: DingTalkBot, _isEvent?: boolean): Promise<void> {\n\t\tconst state = getState(event.channelId);\n\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tstate.store.logMessage(event.channelId, {\n\t\t\tdate: new Date().toISOString(),\n\t\t\tts: event.ts,\n\t\t\tuser: event.user,\n\t\t\tuserName: event.userName,\n\t\t\ttext: event.text,\n\t\t\tisBot: false,\n\t\t});\n\n\t\ttry {\n\t\t\tconst ctx = createDingTalkContext(event, bot, state.store);\n\t\t\tconst builtInCommand = parseBuiltInCommand(event.text);\n\n\t\t\tif (builtInCommand) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Executing command: ${builtInCommand.rawText}`);\n\t\t\t\tawait state.runner.handleBuiltinCommand(ctx, builtInCommand);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlog.logInfo(`[${event.channelId}] Starting run: ${event.text.substring(0, 50)}`);\n\t\t\tconst result = await state.runner.run(ctx, state.store);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Stopped`);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channelId}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\nlog.logStartup(WORKSPACE_DIR, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\nif (!existsSync(WORKSPACE_DIR)) {\n\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n}\n\nconst bot = new DingTalkBot(handler, dingtalkConfig);\nconst eventsWatcher = createEventsWatcher(WORKSPACE_DIR, bot);\neventsWatcher.start();\n\nprocess.on(\"SIGINT\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nbot.start();\n"]}
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\";\nimport { join } from \"path\";\nimport { type AgentRunner, getOrCreateRunner } from \"./agent.js\";\nimport { parseBuiltInCommand } from \"./commands.js\";\nimport { createDingTalkContext } from \"./delivery.js\";\nimport { DingTalkBot, type DingTalkConfig, type DingTalkEvent, type DingTalkHandler } from \"./dingtalk.js\";\nimport { createEventsWatcher } from \"./events.js\";\nimport * as log from \"./log.js\";\nimport {\n\tAPP_HOME_DIR,\n\tAPP_NAME,\n\tAUTH_CONFIG_PATH,\n\tCHANNEL_CONFIG_PATH,\n\tMODELS_CONFIG_PATH,\n\tSETTINGS_CONFIG_PATH,\n\tWORKSPACE_DIR,\n} from \"./paths.js\";\nimport { parseSandboxArg, type SandboxConfig, validateSandbox } from \"./sandbox.js\";\nimport { ChannelStore } from \"./store.js\";\n\nif (process.env.DINGTALK_FORCE_PROXY !== \"true\") {\n\tdelete process.env.http_proxy;\n\tdelete process.env.https_proxy;\n\tdelete process.env.all_proxy;\n\tdelete process.env.HTTP_PROXY;\n\tdelete process.env.HTTPS_PROXY;\n\tdelete process.env.ALL_PROXY;\n}\n\ninterface DingTalkAppConfig extends DingTalkConfig {}\n\ninterface ParsedArgs {\n\tsandbox: SandboxConfig;\n}\n\ninterface BootstrapResult {\n\tcreated: string[];\n\tchannelTemplateCreated: boolean;\n}\n\nconst DEFAULT_SOUL = `# SOUL.md\n\nConfigure Pipiclaw's identity, voice, and communication style here.\n\nSuggested sections:\n\n- Who the assistant is\n- Default language\n- Tone and personality\n- Reply style\n- Formatting preferences\n\nExample topics you may want to define:\n\n- \"Answer in Chinese by default.\"\n- \"Be concise and direct.\"\n- \"Prefer Markdown.\"\n- \"Act as an engineering assistant for our team.\"\n\nReplace this template with your actual identity prompt.\n`;\n\nconst DEFAULT_AGENT = `# AGENTS.md\n\nConfigure Pipiclaw's behavioral rules and operating constraints here.\n\nSuggested sections:\n\n- Tool usage policy\n- File access rules\n- Security constraints\n- Approval rules\n- Project-specific workflows\n- Things the assistant must always or never do\n\nExample topics you may want to define:\n\n- Which tools should be preferred first\n- Whether installation commands are allowed\n- How to handle sensitive data\n- Coding conventions for your team\n- Deployment or release restrictions\n\nReplace this template with your actual operating instructions.\n`;\n\nconst DEFAULT_MEMORY = `# 工作记忆\n\n(尚无记录)\n`;\n\nconst CHANNEL_CONFIG_TEMPLATE = {\n\tclientId: \"your-dingtalk-client-id\",\n\tclientSecret: \"your-dingtalk-client-secret\",\n\trobotCode: \"your-robot-code\",\n\tcardTemplateId: \"your-card-template-id\",\n\tcardTemplateKey: \"content\",\n\tallowFrom: [\"your-staff-id\"],\n} satisfies DingTalkAppConfig;\n\nconst MODELS_CONFIG_TEMPLATE = { providers: {} };\n\nfunction writeTextFileIfMissing(path: string, content: string, label: string, created: string[]): boolean {\n\tif (existsSync(path)) {\n\t\treturn false;\n\t}\n\twriteFileSync(path, content, \"utf-8\");\n\tcreated.push(label);\n\treturn true;\n}\n\nfunction writeJsonFileIfMissing(path: string, value: unknown, label: string, created: string[]): boolean {\n\treturn writeTextFileIfMissing(path, `${JSON.stringify(value, null, 2)}\\n`, label, created);\n}\n\nfunction bootstrapAppHome(): BootstrapResult {\n\tconst created: string[] = [];\n\n\tif (!existsSync(APP_HOME_DIR)) {\n\t\tmkdirSync(APP_HOME_DIR, { recursive: true });\n\t\tcreated.push(\"app home\");\n\t}\n\tif (!existsSync(WORKSPACE_DIR)) {\n\t\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n\t\tcreated.push(\"workspace/\");\n\t}\n\n\tfor (const dir of [\"skills\", \"events\"]) {\n\t\tconst dirPath = join(WORKSPACE_DIR, dir);\n\t\tif (!existsSync(dirPath)) {\n\t\t\tmkdirSync(dirPath, { recursive: true });\n\t\t\tcreated.push(`workspace/${dir}/`);\n\t\t}\n\t}\n\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"SOUL.md\"), DEFAULT_SOUL, \"workspace/SOUL.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"AGENTS.md\"), DEFAULT_AGENT, \"workspace/AGENTS.md\", created);\n\twriteTextFileIfMissing(join(WORKSPACE_DIR, \"MEMORY.md\"), DEFAULT_MEMORY, \"workspace/MEMORY.md\", created);\n\n\tconst channelTemplateCreated = writeJsonFileIfMissing(\n\t\tCHANNEL_CONFIG_PATH,\n\t\tCHANNEL_CONFIG_TEMPLATE,\n\t\t\"channel.json\",\n\t\tcreated,\n\t);\n\twriteJsonFileIfMissing(AUTH_CONFIG_PATH, {}, \"auth.json\", created);\n\twriteJsonFileIfMissing(MODELS_CONFIG_PATH, MODELS_CONFIG_TEMPLATE, \"models.json\", created);\n\twriteJsonFileIfMissing(SETTINGS_CONFIG_PATH, {}, \"settings.json\", created);\n\n\treturn { created, channelTemplateCreated };\n}\n\nfunction isPlaceholderString(value: string): boolean {\n\treturn value.trim().startsWith(\"your-\");\n}\n\nfunction listChannelConfigIssues(config: Partial<DingTalkAppConfig>): string[] {\n\tconst issues: string[] = [];\n\n\tif (!config.clientId) {\n\t\tissues.push(\"Missing required field `clientId`.\");\n\t} else if (isPlaceholderString(config.clientId)) {\n\t\tissues.push(\"Replace placeholder value for `clientId`.\");\n\t}\n\n\tif (!config.clientSecret) {\n\t\tissues.push(\"Missing required field `clientSecret`.\");\n\t} else if (isPlaceholderString(config.clientSecret)) {\n\t\tissues.push(\"Replace placeholder value for `clientSecret`.\");\n\t}\n\n\tif (config.robotCode && isPlaceholderString(config.robotCode)) {\n\t\tissues.push(\"Replace placeholder value for `robotCode`, or set it to an empty string to reuse `clientId`.\");\n\t}\n\n\tif (config.cardTemplateId && isPlaceholderString(config.cardTemplateId)) {\n\t\tissues.push(\n\t\t\t\"Replace placeholder value for `cardTemplateId`, or set it to an empty string to disable AI Card streaming.\",\n\t\t);\n\t}\n\n\tif (Array.isArray(config.allowFrom) && config.allowFrom.some((value) => isPlaceholderString(value))) {\n\t\tissues.push(\"Replace placeholder values in `allowFrom`, or set it to an empty array to allow all users.\");\n\t}\n\n\treturn issues;\n}\n\nfunction printBootstrapSummary(result: BootstrapResult): void {\n\tif (result.created.length === 0) {\n\t\treturn;\n\t}\n\n\tconsole.log(`Initialized ${APP_NAME} under ${APP_HOME_DIR}:`);\n\tfor (const item of result.created) {\n\t\tconsole.log(` - ${item}`);\n\t}\n\tconsole.log(\"\");\n}\n\nfunction loadConfig(): DingTalkAppConfig {\n\tlet parsed: DingTalkAppConfig;\n\n\ttry {\n\t\tparsed = JSON.parse(readFileSync(CHANNEL_CONFIG_PATH, \"utf-8\")) as DingTalkAppConfig;\n\t} catch (err) {\n\t\tconsole.error(`Failed to parse configuration: ${CHANNEL_CONFIG_PATH}`);\n\t\tconsole.error(err instanceof Error ? err.message : String(err));\n\t\tprocess.exit(1);\n\t}\n\n\tconst issues = listChannelConfigIssues(parsed);\n\tif (issues.length > 0) {\n\t\tconsole.error(`Configuration is not ready: ${CHANNEL_CONFIG_PATH}`);\n\t\tfor (const issue of issues) {\n\t\t\tconsole.error(` - ${issue}`);\n\t\t}\n\t\tconsole.error(\"\");\n\t\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\t\tprocess.exit(1);\n\t}\n\n\tparsed.cardTemplateKey = parsed.cardTemplateKey || \"content\";\n\tparsed.robotCode = parsed.robotCode?.trim() ? parsed.robotCode : parsed.clientId;\n\tif (Array.isArray(parsed.allowFrom)) {\n\t\tparsed.allowFrom = parsed.allowFrom.filter((value) => value.trim().length > 0);\n\t}\n\n\treturn parsed;\n}\n\nfunction parseArgs(): ParsedArgs {\n\tconst args = process.argv.slice(2);\n\tlet sandbox: SandboxConfig = { type: \"host\" };\n\n\tfor (let i = 0; i < args.length; i++) {\n\t\tconst arg = args[i];\n\t\tif (arg.startsWith(\"--sandbox=\")) {\n\t\t\tsandbox = parseSandboxArg(arg.slice(\"--sandbox=\".length));\n\t\t} else if (arg === \"--sandbox\") {\n\t\t\tsandbox = parseSandboxArg(args[++i] || \"\");\n\t\t} else if (arg === \"--help\" || arg === \"-h\") {\n\t\t\tconsole.log(`Usage: ${APP_NAME} [options]`);\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(\"Options:\");\n\t\t\tconsole.log(\" --sandbox=host Run tools on host (default)\");\n\t\t\tconsole.log(\" --sandbox=docker:<name> Run tools in Docker container\");\n\t\t\tconsole.log(\"\");\n\t\t\tconsole.log(`Config: ${CHANNEL_CONFIG_PATH}`);\n\t\t\tconsole.log(`Workspace: ${WORKSPACE_DIR}`);\n\t\t\tprocess.exit(0);\n\t\t}\n\t}\n\n\treturn { sandbox };\n}\n\nconst parsedArgs = parseArgs();\nconst sandbox = parsedArgs.sandbox;\nconst bootstrapResult = bootstrapAppHome();\nprintBootstrapSummary(bootstrapResult);\n\nif (bootstrapResult.channelTemplateCreated) {\n\tconsole.error(`Fill in ${CHANNEL_CONFIG_PATH} and run \\`${APP_NAME}\\` again.`);\n\tprocess.exit(1);\n}\n\nconst dingtalkConfig = loadConfig();\ndingtalkConfig.stateDir = WORKSPACE_DIR;\n\nawait validateSandbox(sandbox);\n\ninterface ChannelState {\n\trunning: boolean;\n\trunner: AgentRunner;\n\tstore: ChannelStore;\n\tstopRequested: boolean;\n}\n\nconst channelStates = new Map<string, ChannelState>();\n\nfunction getState(channelId: string): ChannelState {\n\tlet state = channelStates.get(channelId);\n\tif (!state) {\n\t\tconst channelDir = join(WORKSPACE_DIR, channelId);\n\t\tstate = {\n\t\t\trunning: false,\n\t\t\trunner: getOrCreateRunner(sandbox, channelId, channelDir),\n\t\t\tstore: new ChannelStore({ workingDir: WORKSPACE_DIR }),\n\t\t\tstopRequested: false,\n\t\t};\n\t\tchannelStates.set(channelId, state);\n\t}\n\treturn state;\n}\n\nconst handler: DingTalkHandler = {\n\tisRunning(channelId: string): boolean {\n\t\tconst state = channelStates.get(channelId);\n\t\treturn state?.running ?? false;\n\t},\n\n\tasync handleStop(channelId: string, _bot: DingTalkBot): Promise<void> {\n\t\tconst state = channelStates.get(channelId);\n\t\tif (state?.running) {\n\t\t\tstate.stopRequested = true;\n\t\t\tstate.runner.abort();\n\t\t\tlog.logInfo(`[${channelId}] Stop requested`);\n\t\t}\n\t},\n\n\tasync handleEvent(event: DingTalkEvent, bot: DingTalkBot, _isEvent?: boolean): Promise<void> {\n\t\tconst state = getState(event.channelId);\n\n\t\tstate.running = true;\n\t\tstate.stopRequested = false;\n\n\t\tstate.store.logMessage(event.channelId, {\n\t\t\tdate: new Date().toISOString(),\n\t\t\tts: event.ts,\n\t\t\tuser: event.user,\n\t\t\tuserName: event.userName,\n\t\t\ttext: event.text,\n\t\t\tisBot: false,\n\t\t});\n\n\t\ttry {\n\t\t\tconst ctx = createDingTalkContext(event, bot, state.store);\n\t\t\tconst builtInCommand = parseBuiltInCommand(event.text);\n\n\t\t\tif (builtInCommand) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Executing command: ${builtInCommand.rawText}`);\n\t\t\t\tawait state.runner.handleBuiltinCommand(ctx, builtInCommand);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlog.logInfo(`[${event.channelId}] Starting run: ${event.text.substring(0, 50)}`);\n\t\t\tconst result = await state.runner.run(ctx, state.store);\n\n\t\t\tif (result.stopReason === \"aborted\" && state.stopRequested) {\n\t\t\t\tlog.logInfo(`[${event.channelId}] Stopped`);\n\t\t\t}\n\t\t} catch (err) {\n\t\t\tlog.logWarning(`[${event.channelId}] Run error`, err instanceof Error ? err.message : String(err));\n\t\t} finally {\n\t\t\tstate.running = false;\n\t\t}\n\t},\n};\n\nlog.logStartup(WORKSPACE_DIR, sandbox.type === \"host\" ? \"host\" : `docker:${sandbox.container}`);\n\nif (!existsSync(WORKSPACE_DIR)) {\n\tmkdirSync(WORKSPACE_DIR, { recursive: true });\n}\n\nconst bot = new DingTalkBot(handler, dingtalkConfig);\nconst eventsWatcher = createEventsWatcher(WORKSPACE_DIR, bot);\neventsWatcher.start();\n\nprocess.on(\"SIGINT\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nprocess.on(\"SIGTERM\", () => {\n\tlog.logInfo(\"Shutting down...\");\n\teventsWatcher.stop();\n\tprocess.exit(0);\n});\n\nbot.start();\n"]}
|