im-agent 0.2.1 → 0.2.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "im-agent",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "description": "把飞书(以及以后的 Telegram 等)和 Web 页面桥接到本机 Claude Code / Codex:聊天驱动、卡片审批、会话与模型切换",
6
6
  "keywords": [
@@ -255,7 +255,7 @@ export async function handleApi(req: IncomingMessage, res: ServerResponse, url:
255
255
  capabilities: d?.capabilities ?? {},
256
256
  };
257
257
  }),
258
- bots: config().feishu.bots.map((b) => ({ id: b.id, name: b.name, agents: b.agents })),
258
+ bots: config().feishu.bots.map((b) => ({ id: b.id, name: b.name, agents: b.agents, enabled: b.enabled })),
259
259
  workspaceRoot: config().workspaceRoot,
260
260
  conversations: hub.list(),
261
261
  pending: interactions.listPending(),
package/web/app.js CHANGED
@@ -41,6 +41,8 @@ const ICON = {
41
41
  gear: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1Z"/></svg>',
42
42
  copy: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>',
43
43
  x: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><path d="M18 6 6 18M6 6l12 12"/></svg>',
44
+ feishu:
45
+ '<svg viewBox="2 4 44 40"><path fill="#00D6B9" d="M11.6 7H27.6C30.9 9.9 32.8 14.7 33.6 17C30.9 18.3 29 20.6 27 24C22.6 16.4 15.6 11 11.6 7Z"/><path fill="#133C9A" d="M17 29C21 29 25 26.9 28 23.4C36 14 41.4 16.8 44 18C38.5 21 40.5 29.6 33 36C27.5 34.8 21.8 32.2 17 29Z"/><path fill="#3370FF" d="M4 16.2C7.2 20.8 12.4 26 17.4 29.4C23 33.2 28.6 35.7 34 35.2C28.9 39.8 23.8 41 19 41C12.5 41 6.9 37.8 4 35.4Z"/></svg>',
44
46
  };
45
47
 
46
48
  function icon(name) {
@@ -472,21 +474,57 @@ function agentLabel(id) {
472
474
  return S.agents.find((a) => a.id === id)?.label ?? id;
473
475
  }
474
476
 
477
+ function agentShort(id) {
478
+ return id === "claude" ? "Claude" : id === "codex" ? "Codex" : id;
479
+ }
480
+
475
481
  function renderSidebar() {
476
482
  if (!ui.convList) return;
477
483
  const groups = new Map();
478
484
  const askKeys = new Set([...S.pending.values()].filter((i) => !i.settled).map((i) => i.convKey));
479
- const list = [...S.convs.values()]
480
- .filter((c) => !c.hidden)
481
- .filter((c) => !S.search || convTitle(c).toLowerCase().includes(S.search))
482
- .sort((a, b) => b.activeAt - a.activeAt);
485
+ const visible = [...S.convs.values()].filter((c) => !c.hidden).sort((a, b) => b.activeAt - a.activeAt);
486
+ const nodes = [];
487
+
488
+ // 飞书:每个机器人只占一行,点开它最近活跃的那个聊天
489
+ const cur = S.current ? S.convs.get(S.current) : null;
490
+ const botRows = [];
491
+ for (const bot of S.bots) {
492
+ const convs = visible.filter((c) => c.channel === "feishu" && c.channelId === bot.id);
493
+ if (bot.enabled === false && convs.length === 0) continue;
494
+ if (S.search && !bot.name.toLowerCase().includes(S.search)) continue;
495
+ const latest = convs[0];
496
+ const ask = convs.some((c) => askKeys.has(c.key));
497
+ const busy = convs.some((c) => c.busy);
498
+ botRows.push(
499
+ h(
500
+ "button",
501
+ {
502
+ class: `conv-item${cur?.channel === "feishu" && cur.channelId === bot.id ? " active" : ""}`,
503
+ title: latest ? `${bot.name}\n${agentLabel(latest.agent)} · ${latest.cwd}` : `${bot.name}\n还没有会话`,
504
+ onclick: () => {
505
+ if (!latest) {
506
+ toast("这个机器人还没有会话,先在飞书里给它发条消息");
507
+ return;
508
+ }
509
+ closeSide();
510
+ if (latest.key !== S.current) void openConversation(latest.key);
511
+ },
512
+ },
513
+ ask ? h("span", { class: "dot ask", title: "等你处理" }) : busy ? h("span", { class: "dot busy" }) : null,
514
+ h("span", { class: "brand-icon", html: ICON.feishu }),
515
+ h("span", { class: "title" }, bot.name),
516
+ latest && agentShort(latest.agent).toLowerCase() !== bot.name.toLowerCase() ? h("span", { class: "badge" }, agentShort(latest.agent)) : null,
517
+ ),
518
+ );
519
+ }
520
+ if (botRows.length) nodes.push(h("div", { class: "conv-group" }, "飞书"), ...botRows);
521
+
522
+ const list = visible.filter((c) => c.channel !== "feishu").filter((c) => !S.search || convTitle(c).toLowerCase().includes(S.search));
483
523
  for (const c of list) {
484
- const group =
485
- c.channel === "web" ? "Web" : c.channel === "feishu" ? `飞书 · ${S.bots.find((b) => b.id === c.channelId)?.name ?? c.channelId}` : c.channel;
524
+ const group = c.channel === "web" ? "Web" : c.channel;
486
525
  if (!groups.has(group)) groups.set(group, []);
487
526
  groups.get(group).push(c);
488
527
  }
489
- const nodes = [];
490
528
  for (const [group, convs] of groups) {
491
529
  nodes.push(h("div", { class: "conv-group" }, group));
492
530
  for (const c of convs) {
@@ -503,7 +541,7 @@ function renderSidebar() {
503
541
  },
504
542
  askKeys.has(c.key) ? h("span", { class: "dot ask", title: "等你处理" }) : c.busy ? h("span", { class: "dot busy" }) : null,
505
543
  h("span", { class: "title" }, convTitle(c)),
506
- h("span", { class: "badge" }, c.agent === "claude" ? "Claude" : c.agent === "codex" ? "Codex" : c.agent),
544
+ h("span", { class: "badge" }, agentShort(c.agent)),
507
545
  ),
508
546
  );
509
547
  }
Binary file
@@ -0,0 +1,13 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <defs>
3
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#3b82f6"/>
5
+ <stop offset="1" stop-color="#06b6d4"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <rect width="512" height="512" fill="#fff"/>
9
+ <g transform="translate(256 262) scale(1.2) translate(-256 -262)">
10
+ <path d="M196 140H316A64 64 0 0 1 380 204V276A64 64 0 0 1 316 340H236L180 380C173 385 164 380 166 372L173 336A64 64 0 0 1 132 276V204A64 64 0 0 1 196 140Z" fill="none" stroke="url(#g)" stroke-width="26" stroke-linejoin="round"/>
11
+ <path d="M256 178C262 222 274 234 318 240C274 246 262 258 256 302C250 258 238 246 194 240C238 234 250 222 256 178Z" fill="url(#g)"/>
12
+ </g>
13
+ </svg>
package/web/icon.svg CHANGED
@@ -1,6 +1,12 @@
1
1
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
- <rect width="512" height="512" rx="112" fill="#10a37f"/>
3
- <path d="M136 168c0-26.5 21.5-48 48-48h144c26.5 0 48 21.5 48 48v112c0 26.5-21.5 48-48 48h-72l-64 56v-56h-8c-26.5 0-48-21.5-48-48z" fill="#fff"/>
4
- <circle cx="208" cy="224" r="20" fill="#10a37f"/>
5
- <circle cx="304" cy="224" r="20" fill="#10a37f"/>
2
+ <defs>
3
+ <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
4
+ <stop offset="0" stop-color="#3b82f6"/>
5
+ <stop offset="1" stop-color="#06b6d4"/>
6
+ </linearGradient>
7
+ </defs>
8
+ <g transform="translate(256 262) scale(1.4) translate(-256 -262)">
9
+ <path d="M196 140H316A64 64 0 0 1 380 204V276A64 64 0 0 1 316 340H236L180 380C173 385 164 380 166 372L173 336A64 64 0 0 1 132 276V204A64 64 0 0 1 196 140Z" fill="none" stroke="url(#g)" stroke-width="26" stroke-linejoin="round"/>
10
+ <path d="M256 178C262 222 274 234 318 240C274 246 262 258 256 302C250 258 238 246 194 240C238 234 250 222 256 178Z" fill="url(#g)"/>
11
+ </g>
6
12
  </svg>
package/web/index.html CHANGED
@@ -11,7 +11,7 @@
11
11
  <title>IM Agent</title>
12
12
  <link rel="manifest" href="/manifest.webmanifest" />
13
13
  <link rel="icon" href="/icon.svg" type="image/svg+xml" />
14
- <link rel="apple-touch-icon" href="/icon.svg" />
14
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png" />
15
15
  <link rel="stylesheet" href="/vendor/hljs-light.css" media="(prefers-color-scheme: light)" />
16
16
  <link rel="stylesheet" href="/vendor/hljs-dark.css" media="(prefers-color-scheme: dark)" />
17
17
  <link rel="stylesheet" href="/style.css" />
@@ -8,6 +8,8 @@
8
8
  "background_color": "#ffffff",
9
9
  "theme_color": "#ffffff",
10
10
  "icons": [
11
- { "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any maskable" }
11
+ { "src": "/icon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" },
12
+ { "src": "/icon-maskable.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "maskable" },
13
+ { "src": "/apple-touch-icon.png", "sizes": "180x180", "type": "image/png" }
12
14
  ]
13
15
  }
package/web/style.css CHANGED
@@ -8,8 +8,9 @@
8
8
  --fg-soft: #5d5d5d;
9
9
  --fg-faint: #8f8f8f;
10
10
  --border: #e5e5e5;
11
- --accent: #10a37f;
11
+ --accent: #2563eb;
12
12
  --accent-fg: #ffffff;
13
+ --link: #2563eb;
13
14
  --danger: #d93f3f;
14
15
  --warn-bg: #fff7e6;
15
16
  --warn-border: #f5c26b;
@@ -38,6 +39,7 @@
38
39
  --fg-soft: #b4b4b4;
39
40
  --fg-faint: #8a8a8a;
40
41
  --border: #3a3a3a;
42
+ --link: #60a5fa;
41
43
  --warn-bg: #3a2f1a;
42
44
  --warn-border: #8a6a2a;
43
45
  --ask-bg: #2c2640;
@@ -652,7 +654,7 @@ select {
652
654
  }
653
655
 
654
656
  .md a {
655
- color: var(--accent);
657
+ color: var(--link);
656
658
  }
657
659
 
658
660
  .md blockquote {
@@ -1412,3 +1414,15 @@ button.list-item:hover {
1412
1414
  .cmd-row input[type="checkbox"] {
1413
1415
  width: auto;
1414
1416
  }
1417
+
1418
+ .brand-icon {
1419
+ display: inline-grid;
1420
+ width: 18px;
1421
+ height: 18px;
1422
+ flex-shrink: 0;
1423
+ }
1424
+
1425
+ .brand-icon svg {
1426
+ width: 100%;
1427
+ height: 100%;
1428
+ }