pikiclaw 0.2.48 → 0.2.50

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -92,48 +92,48 @@ npx pikiclaw@latest --doctor
92
92
 
93
93
  ---
94
94
 
95
- ## What Exists Today
95
+ ## Current Capabilities
96
96
 
97
- ### Channels
97
+ ### Channels And Agents
98
98
 
99
- - Telegram 已可用
100
- - 飞书已可用
101
- - 两个渠道可以同时启动
102
-
103
- ### Agents
104
-
105
- - Claude Code
106
- - Codex CLI
107
- - Gemini CLI
108
-
109
- Agent 通过 driver registry 接入,模型列表、会话列表、usage 展示都走统一接口。
99
+ - Telegram、飞书都可用,也可以同时启动
100
+ - Claude Code、Codex CLI、Gemini CLI 都已接入
101
+ - agent 通过统一 driver registry 管理,模型列表、session 列表、usage 展示走同一套接口
110
102
 
111
103
  ### Runtime
112
104
 
113
105
  - 流式预览和持续消息更新
114
106
  - 会话切换、恢复和多轮续聊
115
107
  - 工作目录浏览与切换
116
- - 长任务防休眠
117
- - watchdog 守护和自动重启
118
- - 长文本自动拆分,文件和图片自动回传
108
+ - 文件附件自动进入 session workspace
109
+ - 长任务防休眠、watchdog 守护和自动重启
110
+ - 长文本自动拆分,图片和文件可直接回传到 IM
111
+ - Dashboard 可查看运行状态、sessions、usage、主机状态和 macOS 权限状态
119
112
 
120
113
  ### Skills
121
114
 
122
- - 支持项目级 `.pikiclaw/skills/*/SKILL.md`
115
+ - 项目级 skills 以 `.pikiclaw/skills/*/SKILL.md` 为 canonical 入口
123
116
  - 兼容 `.claude/commands/*.md`
117
+ - 兼容 legacy `.claude/skills` / `.agents/skills`,并可合并回 `.pikiclaw/skills`
124
118
  - IM 内可通过 `/skills` 和 `/sk_<name>` 触发
125
119
 
126
- ### MCP Session Bridge
120
+ ### Codex Human Loop
121
+
122
+ 当 Codex 在运行过程中请求额外用户输入时,pikiclaw 会把问题转成 Telegram / 飞书里的交互提示,用户回复后再继续当前任务。
127
123
 
128
- 每次 Agent stream 会启动一个会话级 MCP server,把 IM 能力暴露给 Agent。
124
+ ### MCP And GUI Automation
129
125
 
130
- 当前已接入的工具:
126
+ 每次 Agent stream 都会启动一个会话级 MCP bridge,把本地工具按本次任务注入给 Agent。
127
+
128
+ 当前内置工具:
131
129
 
132
130
  - `im_list_files`:列出 session workspace 文件
133
131
  - `im_send_file`:把文件实时发回 IM
134
- - `take_screenshot`:跨平台截图并返回路径
135
132
 
136
- 当前 `guiTools` 模块已经预留,但点击、输入、窗口控制等顶级 GUI 工具还没接上。
133
+ 可选 GUI 能力:
134
+
135
+ - 浏览器自动化:通过 `@playwright/mcp` 补充接入,默认支持 Chrome extension mode,也可切到 headless / isolated 模式
136
+ - macOS 桌面自动化:通过 Appium Mac2 提供 `desktop_open_app`、`desktop_snapshot`、`desktop_click`、`desktop_type`、`desktop_screenshot` 等工具
137
137
 
138
138
  ---
139
139
 
@@ -156,42 +156,37 @@ Agent 通过 driver registry 接入,模型列表、会话列表、usage 展示
156
156
 
157
157
  ---
158
158
 
159
- ## Skills And MCP
159
+ ## Config And Setup Notes
160
160
 
161
- 项目里现在有两条能力扩展线:
161
+ - 持久化配置在 `~/.pikiclaw/setting.json`
162
+ - Dashboard 是主配置入口,环境变量仍然可用
163
+ - 浏览器 GUI 相关常用变量:
164
+ - `PIKICLAW_BROWSER_GUI`
165
+ - `PIKICLAW_BROWSER_USE_EXTENSION`
166
+ - `PIKICLAW_BROWSER_HEADLESS`
167
+ - `PIKICLAW_BROWSER_ISOLATED`
168
+ - `PLAYWRIGHT_MCP_EXTENSION_TOKEN`
169
+ - 桌面 GUI 相关常用变量:
170
+ - `PIKICLAW_DESKTOP_GUI`
171
+ - `PIKICLAW_DESKTOP_APPIUM_URL`
162
172
 
163
- - Skills:偏“高层工作流提示词”,来源于 `.pikiclaw/skills` `.claude/commands`
164
- - MCP tools:偏“可执行工具能力”,目前是会话级 bridge,由 pikiclaw 在每次 stream 时注入
173
+ 如果要启用 macOS 桌面自动化,需要先准备 Appium Mac2:
165
174
 
166
- 这两条线已经能工作,但都还是偏“session / project 内部接入”,还不是仓库级统一入口。
175
+ ```bash
176
+ npm install -g appium
177
+ appium driver install mac2
178
+ appium
179
+ ```
167
180
 
168
- ---
181
+ 然后给运行 `pikiclaw` 的终端应用授予 macOS 的辅助功能权限。
169
182
 
170
- ## Status
183
+ ---
171
184
 
172
- ### 已完成
185
+ ## Roadmap
173
186
 
174
- | 项目 | 状态 |
175
- |---|---|
176
- | Telegram 渠道 | ✅ |
177
- | 飞书渠道 | ✅ |
178
- | Claude Code driver | ✅ |
179
- | Codex CLI driver | ✅ |
180
- | Gemini CLI driver | ✅ |
181
- | Web Dashboard | ✅ |
182
- | 项目级 Skills | ✅ |
183
- | 会话级 MCP bridge | ✅ |
184
- | 文件回传 / 截图回传 | ✅ |
185
- | 守护重启 / 防休眠 | ✅ |
186
-
187
- ### 待办
188
-
189
- | 项目 | 说明 |
190
- |---|---|
191
- | 顶级 Skills 接入 | 把 skills 从当前项目级入口提升为更统一的顶级接入能力 |
192
- | 顶级 MCP 工具接入 | 把当前会话级 MCP bridge 扩展成更完整的顶级工具接入层 |
193
- | GUI 自动化工具补全 | 在 `src/tools/gui.ts` 上接入点击、输入、聚焦、窗口控制等工具 |
194
- | 更多渠道 | WhatsApp 仍在规划中 |
187
+ - 把当前会话级 MCP bridge 继续扩展成更完整的顶级工具接入层
188
+ - 继续完善 GUI 自动化能力,尤其是浏览器与桌面工具的协同链路
189
+ - 增加更多 IM 渠道,WhatsApp 仍在规划中
195
190
 
196
191
  ---
197
192
 
@@ -216,6 +211,9 @@ npx vitest run test/channel-feishu.unit.test.ts
216
211
  npx pikiclaw@latest --doctor
217
212
  ```
218
213
 
214
+ `npm run dev` 只跑本地源码链路,会固定使用 `--no-daemon`,避免跳转到生产/自举用的 `npx pikiclaw@latest`。
215
+ 同时会把本次启动的全部日志写到 `~/.pikiclaw/dev/dev.log`,并在每次启动时先清空旧日志。
216
+
219
217
  更多实现细节见:
220
218
 
221
219
  - [ARCHITECTURE.md](ARCHITECTURE.md)
@@ -12,17 +12,16 @@ import path from 'node:path';
12
12
  import { fmtTokens, fmtUptime, fmtBytes } from './bot.js';
13
13
  import { getProjectSkillPaths } from './code-agent.js';
14
14
  import { getDriver } from './agent-driver.js';
15
- import { buildWelcomeIntro, buildDefaultMenuCommands, buildSkillCommandName, indexSkillsByCommand, SKILL_CMD_PREFIX } from './bot-menu.js';
15
+ import { buildWelcomeIntro, buildSkillCommandName, indexSkillsByCommand, SKILL_CMD_PREFIX } from './bot-menu.js';
16
+ import { buildBotMenuState } from './bot-orchestration.js';
16
17
  import { summarizePromptForStatus } from './bot-streaming.js';
17
18
  import { getSessionStatusForChat } from './session-status.js';
18
19
  import { VERSION } from './version.js';
19
20
  export function getStartData(bot, chatId) {
20
21
  const cs = bot.chat(chatId);
21
22
  const intro = buildWelcomeIntro(VERSION);
23
+ const commands = buildBotMenuState(bot).commands;
22
24
  const res = bot.fetchAgents();
23
- const installedCount = res.agents.filter(a => a.installed).length;
24
- const skillRes = bot.fetchSkills();
25
- const commands = buildDefaultMenuCommands(installedCount, skillRes.skills);
26
25
  const agentDetails = res.agents
27
26
  .filter(a => a.installed)
28
27
  .map(a => ({
@@ -9,6 +9,7 @@ import { fmtUptime, fmtTokens, fmtBytes, formatThinkingForDisplay, thinkLabel }
9
9
  import { summarizePromptForStatus } from './bot-commands.js';
10
10
  import { formatProviderUsageLines } from './bot-telegram-render.js';
11
11
  import { formatActivityCommandSummary, parseActivitySummary, renderPlanForPreview, summarizeActivityForPreview } from './bot-streaming.js';
12
+ import { currentHumanLoopQuestion, humanLoopAnsweredCount, isHumanLoopAwaitingText, isHumanLoopQuestionAnswered, summarizeHumanLoopAnswer, } from './human-loop.js';
12
13
  import path from 'node:path';
13
14
  import { listSubdirs } from './bot.js';
14
15
  // ---------------------------------------------------------------------------
@@ -163,6 +164,38 @@ export function renderSessionTurnMarkdown(userText, assistantText) {
163
164
  parts.push('**Assistant**', assistant);
164
165
  return parts.join('\n\n');
165
166
  }
167
+ export function buildHumanLoopPromptMarkdown(prompt) {
168
+ const question = currentHumanLoopQuestion(prompt);
169
+ const lines = [`**${prompt.title}**`];
170
+ if (prompt.detail)
171
+ lines.push(`\`${prompt.detail}\``);
172
+ lines.push(`*${humanLoopAnsweredCount(prompt)}/${prompt.questions.length} answered*`);
173
+ if (!question)
174
+ return lines.join('\n\n');
175
+ if (question.header.trim())
176
+ lines.push(`**${question.header}**`);
177
+ lines.push(question.prompt);
178
+ const options = question.options || [];
179
+ if (options.length) {
180
+ lines.push(options.map((option, index) => {
181
+ const detail = option.description ? `\n ${option.description}` : '';
182
+ return `${index + 1}. ${option.label}${detail}`;
183
+ }).join('\n'));
184
+ }
185
+ if (isHumanLoopAwaitingText(prompt)) {
186
+ lines.push(`*${question.secret ? 'Reply in chat with the secret value.' : 'Reply in chat with text to answer.'}*`);
187
+ }
188
+ if (prompt.hint)
189
+ lines.push(`*${prompt.hint}*`);
190
+ if (prompt.questions.length > 1) {
191
+ lines.push(prompt.questions.map((item, index) => {
192
+ const summary = summarizeHumanLoopAnswer(prompt, item);
193
+ const answered = isHumanLoopQuestionAnswered(prompt, index);
194
+ return `${answered ? '●' : '○'} ${item.header || item.prompt}: ${summary.display}`;
195
+ }).join('\n'));
196
+ }
197
+ return lines.join('\n\n');
198
+ }
166
199
  // ---------------------------------------------------------------------------
167
200
  // LivePreview renderer — produces Markdown for Feishu card elements
168
201
  // ---------------------------------------------------------------------------
@@ -584,7 +617,44 @@ function stripBoldMarkers(text) {
584
617
  }
585
618
  /** Strip anchor links [text](#id) → text (anchors don't work in Feishu cards). */
586
619
  function adaptLine(line) {
587
- return line.replace(/\[([^\]]+)\]\(#[^)]*\)/g, '$1');
620
+ const withoutAnchors = line.replace(/\[([^\]]+)\]\(#[^)]*\)/g, '$1');
621
+ const heading = withoutAnchors.match(/^(\s*)#{1,6}\s+(.+?)\s*#*\s*$/);
622
+ if (!heading)
623
+ return withoutAnchors;
624
+ const indent = heading[1] || '';
625
+ const content = heading[2].trim();
626
+ return content ? `${indent}**${content}**` : '';
627
+ }
628
+ function normalizeFeishuMarkdown(lines) {
629
+ const out = [];
630
+ let inCodeBlock = false;
631
+ let pendingBlankLine = false;
632
+ for (const rawLine of lines) {
633
+ const line = rawLine.replace(/\s+$/g, '');
634
+ const trimmed = line.trimStart();
635
+ if (trimmed.startsWith('```') || trimmed.startsWith('~~~')) {
636
+ if (pendingBlankLine && out.length && !inCodeBlock)
637
+ out.push('');
638
+ pendingBlankLine = false;
639
+ out.push(line);
640
+ inCodeBlock = !inCodeBlock;
641
+ continue;
642
+ }
643
+ if (inCodeBlock) {
644
+ out.push(rawLine);
645
+ continue;
646
+ }
647
+ if (!line.trim()) {
648
+ if (out.length)
649
+ pendingBlankLine = true;
650
+ continue;
651
+ }
652
+ if (pendingBlankLine && out.length)
653
+ out.push('');
654
+ pendingBlankLine = false;
655
+ out.push(line);
656
+ }
657
+ return out.join('\n');
588
658
  }
589
659
  export function adaptMarkdownForFeishu(markdown) {
590
660
  const lines = markdown.split('\n');
@@ -643,5 +713,5 @@ export function adaptMarkdownForFeishu(markdown) {
643
713
  i++;
644
714
  }
645
715
  }
646
- return out.join('\n');
716
+ return normalizeFeishuMarkdown(out);
647
717
  }