pi-courier 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (86) hide show
  1. package/README.md +363 -0
  2. package/README.zh-CN.md +362 -0
  3. package/deploy/pi-msg-bridge.service +26 -0
  4. package/deploy/pi-msg-bridge.user.service +37 -0
  5. package/dist/auth/challenge-auth.d.ts +76 -0
  6. package/dist/auth/challenge-auth.d.ts.map +1 -0
  7. package/dist/auth/challenge-auth.js +337 -0
  8. package/dist/auth/challenge-auth.js.map +1 -0
  9. package/dist/cli.d.ts +3 -0
  10. package/dist/cli.d.ts.map +1 -0
  11. package/dist/cli.js +265 -0
  12. package/dist/cli.js.map +1 -0
  13. package/dist/config.d.ts +10 -0
  14. package/dist/config.d.ts.map +1 -0
  15. package/dist/config.js +49 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/formatting.d.ts +22 -0
  18. package/dist/formatting.d.ts.map +1 -0
  19. package/dist/formatting.js +74 -0
  20. package/dist/formatting.js.map +1 -0
  21. package/dist/lock.d.ts +3 -0
  22. package/dist/lock.d.ts.map +1 -0
  23. package/dist/lock.js +75 -0
  24. package/dist/lock.js.map +1 -0
  25. package/dist/rpc/command-map.d.ts +19 -0
  26. package/dist/rpc/command-map.d.ts.map +1 -0
  27. package/dist/rpc/command-map.js +191 -0
  28. package/dist/rpc/command-map.js.map +1 -0
  29. package/dist/rpc/message-router.d.ts +30 -0
  30. package/dist/rpc/message-router.d.ts.map +1 -0
  31. package/dist/rpc/message-router.js +110 -0
  32. package/dist/rpc/message-router.js.map +1 -0
  33. package/dist/rpc/pi-rpc.d.ts +79 -0
  34. package/dist/rpc/pi-rpc.d.ts.map +1 -0
  35. package/dist/rpc/pi-rpc.js +199 -0
  36. package/dist/rpc/pi-rpc.js.map +1 -0
  37. package/dist/setup.d.ts +10 -0
  38. package/dist/setup.d.ts.map +1 -0
  39. package/dist/setup.js +159 -0
  40. package/dist/setup.js.map +1 -0
  41. package/dist/standalone.d.ts +13 -0
  42. package/dist/standalone.d.ts.map +1 -0
  43. package/dist/standalone.js +184 -0
  44. package/dist/standalone.js.map +1 -0
  45. package/dist/transports/discord.d.ts +33 -0
  46. package/dist/transports/discord.d.ts.map +1 -0
  47. package/dist/transports/discord.js +216 -0
  48. package/dist/transports/discord.js.map +1 -0
  49. package/dist/transports/interface.d.ts +42 -0
  50. package/dist/transports/interface.d.ts.map +1 -0
  51. package/dist/transports/interface.js +2 -0
  52. package/dist/transports/interface.js.map +1 -0
  53. package/dist/transports/manager.d.ts +54 -0
  54. package/dist/transports/manager.d.ts.map +1 -0
  55. package/dist/transports/manager.js +98 -0
  56. package/dist/transports/manager.js.map +1 -0
  57. package/dist/transports/matrix-utils.d.ts +31 -0
  58. package/dist/transports/matrix-utils.d.ts.map +1 -0
  59. package/dist/transports/matrix-utils.js +99 -0
  60. package/dist/transports/matrix-utils.js.map +1 -0
  61. package/dist/transports/matrix-utils.test.d.ts +2 -0
  62. package/dist/transports/matrix-utils.test.d.ts.map +1 -0
  63. package/dist/transports/matrix-utils.test.js +246 -0
  64. package/dist/transports/matrix-utils.test.js.map +1 -0
  65. package/dist/transports/matrix.d.ts +34 -0
  66. package/dist/transports/matrix.d.ts.map +1 -0
  67. package/dist/transports/matrix.js +240 -0
  68. package/dist/transports/matrix.js.map +1 -0
  69. package/dist/transports/slack.d.ts +31 -0
  70. package/dist/transports/slack.d.ts.map +1 -0
  71. package/dist/transports/slack.js +203 -0
  72. package/dist/transports/slack.js.map +1 -0
  73. package/dist/transports/telegram.d.ts +38 -0
  74. package/dist/transports/telegram.d.ts.map +1 -0
  75. package/dist/transports/telegram.js +167 -0
  76. package/dist/transports/telegram.js.map +1 -0
  77. package/dist/transports/whatsapp.d.ts +31 -0
  78. package/dist/transports/whatsapp.d.ts.map +1 -0
  79. package/dist/transports/whatsapp.js +235 -0
  80. package/dist/transports/whatsapp.js.map +1 -0
  81. package/dist/types.d.ts +69 -0
  82. package/dist/types.d.ts.map +1 -0
  83. package/dist/types.js +2 -0
  84. package/dist/types.js.map +1 -0
  85. package/extensions/index.ts +42 -0
  86. package/package.json +71 -0
package/dist/config.js ADDED
@@ -0,0 +1,49 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ const CONFIG_DIR = path.join(os.homedir(), ".pi");
5
+ const CONFIG_PATH = path.join(CONFIG_DIR, "msg-bridge.json");
6
+ /**
7
+ * Load config from file and env vars (env vars override file).
8
+ */
9
+ export function loadConfig() {
10
+ const config = {};
11
+ if (fs.existsSync(CONFIG_PATH)) {
12
+ try {
13
+ const stats = fs.statSync(CONFIG_PATH);
14
+ const mode = stats.mode & 0o777;
15
+ if ((mode & 0o077) !== 0) {
16
+ console.warn(`⚠️ Config file ${CONFIG_PATH} has insecure permissions (${mode.toString(8)}). Should be 0600.`);
17
+ }
18
+ const fileConfig = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
19
+ Object.assign(config, fileConfig);
20
+ }
21
+ catch (err) {
22
+ console.error("Failed to load config file:", err);
23
+ }
24
+ }
25
+ // Environment variables override file config (higher priority)
26
+ if (process.env.PI_MATRIX_HOMESERVER && process.env.PI_MATRIX_ACCESS_TOKEN) {
27
+ config.matrix = {
28
+ homeserverUrl: process.env.PI_MATRIX_HOMESERVER,
29
+ accessToken: process.env.PI_MATRIX_ACCESS_TOKEN,
30
+ };
31
+ }
32
+ return config;
33
+ }
34
+ /**
35
+ * Save config to file with secure permissions.
36
+ */
37
+ export function saveConfig(config) {
38
+ if (!fs.existsSync(CONFIG_DIR)) {
39
+ fs.mkdirSync(CONFIG_DIR, { recursive: true, mode: 0o700 });
40
+ }
41
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), { mode: 0o600 });
42
+ try {
43
+ fs.chmodSync(CONFIG_DIR, 0o700);
44
+ }
45
+ catch (err) {
46
+ console.warn("Failed to set directory permissions:", err);
47
+ }
48
+ }
49
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAC;AAClD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,iBAAiB,CAAC,CAAC;AAE7D;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,MAAM,MAAM,GAAoB,EAAE,CAAC;IAEnC,IAAI,EAAE,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;YACvC,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC;YAChC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;gBACzB,OAAO,CAAC,IAAI,CAAC,mBAAmB,WAAW,8BAA8B,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC;YACjH,CAAC;YAED,MAAM,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;YACrE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACpC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,6BAA6B,EAAE,GAAG,CAAC,CAAC;QACpD,CAAC;IACH,CAAC;IAED,+DAA+D;IAC/D,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,OAAO,CAAC,GAAG,CAAC,sBAAsB,EAAE,CAAC;QAC3E,MAAM,CAAC,MAAM,GAAG;YACd,aAAa,EAAE,OAAO,CAAC,GAAG,CAAC,oBAAoB;YAC/C,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,sBAAsB;SAChD,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAuB;IAChD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAChF,IAAI,CAAC;QACH,EAAE,CAAC,SAAS,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IAClC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC,sCAAsC,EAAE,GAAG,CAAC,CAAC;IAC5D,CAAC;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ import type { AssistantMessage } from "@earendil-works/pi-ai";
2
+ /**
3
+ * Extract text from assistant message.
4
+ */
5
+ export declare function extractTextFromMessage(message: AssistantMessage): string;
6
+ /**
7
+ * Check if assistant message contains tool calls (more turns will follow).
8
+ */
9
+ export declare function hasToolCalls(message: AssistantMessage): boolean;
10
+ /**
11
+ * Format tool call summaries for the remote user.
12
+ */
13
+ export declare function formatToolCalls(message: AssistantMessage): string;
14
+ /**
15
+ * Truncate string to max length with ellipsis.
16
+ */
17
+ export declare function truncate(str: string, maxLen: number): string;
18
+ /**
19
+ * Split long messages into chunks, breaking at newlines when possible.
20
+ */
21
+ export declare function splitMessage(text: string, maxLen: number): string[];
22
+ //# sourceMappingURL=formatting.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatting.d.ts","sourceRoot":"","sources":["../src/formatting.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAE9D;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAGxE;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAE/D;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAsBjE;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAI5D;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAyBnE"}
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Extract text from assistant message.
3
+ */
4
+ export function extractTextFromMessage(message) {
5
+ const textParts = message.content.filter((part) => part.type === "text");
6
+ return textParts.map((part) => part.text).join("\n");
7
+ }
8
+ /**
9
+ * Check if assistant message contains tool calls (more turns will follow).
10
+ */
11
+ export function hasToolCalls(message) {
12
+ return message.content.some((part) => part.type === "toolCall");
13
+ }
14
+ /**
15
+ * Format tool call summaries for the remote user.
16
+ */
17
+ export function formatToolCalls(message) {
18
+ const toolCalls = message.content.filter((part) => part.type === "toolCall");
19
+ if (toolCalls.length === 0)
20
+ return "";
21
+ return toolCalls
22
+ .map((tc) => {
23
+ const name = tc.name || "tool";
24
+ const args = tc.arguments || {};
25
+ const argPairs = Object.entries(args)
26
+ .map(([k, v]) => {
27
+ const valStr = typeof v === 'string' ? v : JSON.stringify(v);
28
+ return `${k}=${truncate(valStr, 50)}`;
29
+ })
30
+ .join(", ");
31
+ // Wrap the tool name in backticks so messengers render it as inline
32
+ // code — preserves snake_case readability across Telegram (which would
33
+ // otherwise have to backslash-escape underscores), Discord, Slack,
34
+ // Matrix, and WhatsApp uniformly.
35
+ return argPairs ? `🔧 \`${name}\` (${argPairs})` : `🔧 \`${name}\``;
36
+ })
37
+ .join("\n");
38
+ }
39
+ /**
40
+ * Truncate string to max length with ellipsis.
41
+ */
42
+ export function truncate(str, maxLen) {
43
+ if (!str)
44
+ return "";
45
+ if (str.length <= maxLen)
46
+ return str;
47
+ return str.substring(0, maxLen - 3) + "...";
48
+ }
49
+ /**
50
+ * Split long messages into chunks, breaking at newlines when possible.
51
+ */
52
+ export function splitMessage(text, maxLen) {
53
+ if (text.length <= maxLen)
54
+ return [text];
55
+ const chunks = [];
56
+ let remaining = text;
57
+ while (remaining.length > 0) {
58
+ if (remaining.length <= maxLen) {
59
+ chunks.push(remaining);
60
+ break;
61
+ }
62
+ let breakAt = remaining.lastIndexOf("\n", maxLen);
63
+ if (breakAt < maxLen * 0.5) {
64
+ breakAt = remaining.lastIndexOf(" ", maxLen);
65
+ }
66
+ if (breakAt < maxLen * 0.3) {
67
+ breakAt = maxLen;
68
+ }
69
+ chunks.push(remaining.substring(0, breakAt));
70
+ remaining = remaining.substring(breakAt).trimStart();
71
+ }
72
+ return chunks;
73
+ }
74
+ //# sourceMappingURL=formatting.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatting.js","sourceRoot":"","sources":["../src/formatting.ts"],"names":[],"mappings":"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"}
package/dist/lock.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function acquireLock(): boolean;
2
+ export declare function releaseLock(): void;
3
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"AAsBA,wBAAgB,WAAW,IAAI,OAAO,CAoCrC;AAED,wBAAgB,WAAW,IAAI,IAAI,CAgBlC"}
package/dist/lock.js ADDED
@@ -0,0 +1,75 @@
1
+ import * as fs from "fs";
2
+ import * as os from "os";
3
+ import * as path from "path";
4
+ /**
5
+ * Single-instance connection guard.
6
+ *
7
+ * Two layers:
8
+ * 1. global flag — catches same-process re-entrant calls (e.g. sub-agents
9
+ * spawned inside the same Node.js process, same PID).
10
+ * 2. PID lock file — catches separate-process duplicates (e.g. sub-agents
11
+ * launched as child processes with different PIDs).
12
+ */
13
+ const LOCK_PATH = path.join(os.homedir(), ".pi", "msg-bridge.lock");
14
+ const g = global;
15
+ if (!g.__msgBridgeInstanceId) {
16
+ g.__msgBridgeInstanceId = Math.random().toString(36).slice(2);
17
+ }
18
+ const instanceId = g.__msgBridgeInstanceId;
19
+ export function acquireLock() {
20
+ // Layer 1: same-process guard via a global flag
21
+ if (g.__msgBridgeConnected && g.__msgBridgeOwner !== instanceId) {
22
+ return false;
23
+ }
24
+ // Layer 2: cross-process guard via PID lock file
25
+ try {
26
+ if (fs.existsSync(LOCK_PATH)) {
27
+ const raw = fs.readFileSync(LOCK_PATH, "utf-8").trim().split(":");
28
+ const pid = parseInt(raw[0], 10);
29
+ const owner = raw[1] ?? "";
30
+ if (!Number.isNaN(pid) && pid === process.pid && owner !== instanceId) {
31
+ return false;
32
+ }
33
+ if (!Number.isNaN(pid) && pid !== process.pid) {
34
+ try {
35
+ process.kill(pid, 0); // throws if process does not exist
36
+ return false; // another live process holds the lock
37
+ }
38
+ catch {
39
+ // stale lock from a dead process — overwrite below
40
+ }
41
+ }
42
+ }
43
+ const configDir = path.join(os.homedir(), ".pi");
44
+ if (!fs.existsSync(configDir)) {
45
+ fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
46
+ }
47
+ fs.writeFileSync(LOCK_PATH, `${process.pid}:${instanceId}`, { mode: 0o600 });
48
+ }
49
+ catch {
50
+ // lock file mechanics failed — fall through, global flag is still set below
51
+ }
52
+ g.__msgBridgeConnected = true;
53
+ g.__msgBridgeOwner = instanceId;
54
+ return true;
55
+ }
56
+ export function releaseLock() {
57
+ if (g.__msgBridgeOwner !== instanceId)
58
+ return;
59
+ g.__msgBridgeConnected = false;
60
+ g.__msgBridgeOwner = undefined;
61
+ try {
62
+ if (fs.existsSync(LOCK_PATH)) {
63
+ const raw = fs.readFileSync(LOCK_PATH, "utf-8").trim().split(":");
64
+ const pid = parseInt(raw[0], 10);
65
+ const owner = raw[1] ?? "";
66
+ if (pid === process.pid && owner === instanceId) {
67
+ fs.unlinkSync(LOCK_PATH);
68
+ }
69
+ }
70
+ }
71
+ catch {
72
+ // ignore
73
+ }
74
+ }
75
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../src/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Slash command map — turns `/command args` messages from messengers into RPC calls.
3
+ *
4
+ * Layer ordering (matches previous extension behaviour, minus the conflicts):
5
+ * 1. Bridge admin commands (/help, /trusted, ...) are handled by ChallengeAuth before this module
6
+ * 2. Builtin pi commands that have a dedicated RPC command are mapped here
7
+ * 3. Everything else starting with "/" is forwarded via prompt: extension commands,
8
+ * skill commands (/skill:name) and prompt templates (/template) are expanded by pi itself
9
+ * 4. Unknown commands get a helpful error listing what is available
10
+ */
11
+ import type { PiRpc } from "./pi-rpc.js";
12
+ export interface SlashCommandContext {
13
+ rpc: PiRpc;
14
+ /** Send a reply back to the originating chat */
15
+ reply: (text: string) => Promise<void>;
16
+ }
17
+ /** Returns true if the command was handled (something was done / replied). */
18
+ export declare function handleSlashCommand(text: string, ctx: SlashCommandContext): Promise<boolean>;
19
+ //# sourceMappingURL=command-map.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-map.d.ts","sourceRoot":"","sources":["../../src/rpc/command-map.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,WAAW,mBAAmB;IAClC,GAAG,EAAE,KAAK,CAAC;IACX,gDAAgD;IAChD,KAAK,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC;AAED,8EAA8E;AAC9E,wBAAsB,kBAAkB,CACtC,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,mBAAmB,GACvB,OAAO,CAAC,OAAO,CAAC,CAoLlB"}
@@ -0,0 +1,191 @@
1
+ /** Returns true if the command was handled (something was done / replied). */
2
+ export async function handleSlashCommand(text, ctx) {
3
+ const { rpc, reply } = ctx;
4
+ const trimmed = text.trim();
5
+ if (!trimmed.startsWith("/"))
6
+ return false;
7
+ const spaceIndex = trimmed.indexOf(" ");
8
+ const name = spaceIndex === -1 ? trimmed : trimmed.slice(0, spaceIndex);
9
+ const args = (spaceIndex === -1 ? "" : trimmed.slice(spaceIndex + 1)).trim();
10
+ try {
11
+ switch (name) {
12
+ // --- Session lifecycle -------------------------------------------------
13
+ case "/new":
14
+ case "/clear": {
15
+ const { cancelled } = await rpc.newSession();
16
+ await reply(cancelled ? "⚠️ 新会话被扩展取消" : "✅ 已开始新会话");
17
+ return true;
18
+ }
19
+ case "/compact": {
20
+ const result = await rpc.compact(args || undefined);
21
+ const line = [
22
+ "✅ 已压缩",
23
+ ` tokens: ${result.tokensBefore} → 压缩后(见摘要)`,
24
+ `\n摘要: ${result.summary.slice(0, 500)}`,
25
+ ].join("");
26
+ await reply(line);
27
+ return true;
28
+ }
29
+ case "/abort": {
30
+ await rpc.abort();
31
+ await reply("🛑 已中止当前操作");
32
+ return true;
33
+ }
34
+ case "/reload": {
35
+ await reply("🔄 正在重启 pi 进程(扩展/技能/配置将重新加载)…");
36
+ await rpc.restart();
37
+ const state = await rpc.getState();
38
+ await reply(`✅ pi 已重启,模型: ${state.model?.id ?? "unknown"}`);
39
+ return true;
40
+ }
41
+ // --- Model / thinking --------------------------------------------------
42
+ case "/model": {
43
+ if (!args) {
44
+ const models = await rpc.getAvailableModels();
45
+ if (models.length === 0) {
46
+ await reply("没有可用模型(未配置 provider?)");
47
+ return true;
48
+ }
49
+ const current = await rpc.getState();
50
+ const list = models
51
+ .map((m) => `• ${m.provider}/${m.id}${m.id === current.model?.id ? " ← 当前" : ""}`)
52
+ .join("\n");
53
+ await reply(`可用模型:\n${list}\n\n用法: /model <provider/model-id>`);
54
+ return true;
55
+ }
56
+ const parsed = parseModelArg(args);
57
+ if (!parsed.provider) {
58
+ // Bare id — try to find a matching model and use its provider
59
+ const models = await rpc.getAvailableModels();
60
+ const match = models.find((m) => m.id.includes(parsed.modelId));
61
+ if (!match) {
62
+ await reply(`❌ 找不到模型 "${parsed.modelId}"。用 /models 查看可用列表。`);
63
+ return true;
64
+ }
65
+ parsed.provider = match.provider;
66
+ parsed.modelId = match.id;
67
+ }
68
+ const result = (await rpc.setModel(parsed.provider, parsed.modelId));
69
+ await reply(`✅ 已切换模型: ${result.id ?? `${parsed.provider}/${parsed.modelId}`}`);
70
+ return true;
71
+ }
72
+ case "/models": {
73
+ const models = await rpc.getAvailableModels();
74
+ if (models.length === 0) {
75
+ await reply("没有可用模型(未配置 provider?)");
76
+ return true;
77
+ }
78
+ const current = await rpc.getState();
79
+ await reply(models
80
+ .map((m) => `• ${m.provider}/${m.id}${m.id === current.model?.id ? " ← 当前" : ""}`)
81
+ .join("\n"));
82
+ return true;
83
+ }
84
+ case "/thinking": {
85
+ if (!args) {
86
+ const state = await rpc.getState();
87
+ await reply(`当前思考级别: ${state.thinkingLevel}\n可用级别: off, minimal, low, medium, high, xhigh, max\n用法: /thinking <level>`);
88
+ return true;
89
+ }
90
+ await rpc.setThinkingLevel(args);
91
+ await reply(`✅ 思考级别已设为: ${args}`);
92
+ return true;
93
+ }
94
+ // --- Session info / export ---------------------------------------------
95
+ case "/session":
96
+ case "/cost": {
97
+ const stats = await rpc.getSessionStats();
98
+ await reply([
99
+ `📊 会话: ${stats.sessionId}`,
100
+ `消息数: ${stats.totalMessages}`,
101
+ `tokens: ${stats.tokens.total}`,
102
+ `费用: $${stats.cost.toFixed(4)}`,
103
+ ].join("\n"));
104
+ return true;
105
+ }
106
+ case "/status": {
107
+ const state = await rpc.getState();
108
+ const modelName = state.model?.name || state.model?.id || "unknown";
109
+ await reply(`⚙️ 模型: ${modelName}\n流式中: ${state.isStreaming ? "是" : "否"}`);
110
+ return true;
111
+ }
112
+ case "/name": {
113
+ if (!args) {
114
+ await reply("用法: /name <会话名>");
115
+ return true;
116
+ }
117
+ await rpc.setSessionName(args);
118
+ await reply(`✅ 会话已命名: ${args}`);
119
+ return true;
120
+ }
121
+ case "/export": {
122
+ const result = await rpc.exportHtml(args || undefined);
123
+ await reply(`✅ 已导出: ${result.path}`);
124
+ return true;
125
+ }
126
+ // --- Bash ----------------------------------------------------------------
127
+ case "/bash": {
128
+ if (!args) {
129
+ await reply("用法: /bash <shell 命令> — 在 pi 的工作目录执行并写入上下文");
130
+ return true;
131
+ }
132
+ const result = await rpc.bash(args);
133
+ const output = result.output.length > 3000
134
+ ? result.output.slice(0, 3000) + "\n…(已截断)"
135
+ : result.output;
136
+ await reply(`$ ${args}\n\`\`\`\n${output || "(无输出)"}\n\`\`\`\n退出码: ${result.exitCode}`);
137
+ return true;
138
+ }
139
+ case "/help": {
140
+ await reply(helpText());
141
+ return true;
142
+ }
143
+ // --- Fallthrough: extension commands, skills, prompt templates -----------
144
+ default: {
145
+ // /skill:name, /template and extension commands are expanded by pi itself.
146
+ const commands = await rpc.getCommands();
147
+ const known = commands.some((c) => `/${c.name}` === name);
148
+ if (known) {
149
+ await rpc.prompt(trimmed);
150
+ return true;
151
+ }
152
+ await reply(`❌ 未知命令: ${name}\n${helpText()}`);
153
+ return true;
154
+ }
155
+ }
156
+ }
157
+ catch (err) {
158
+ await reply(`❌ 命令执行失败: ${err.message}`);
159
+ return true;
160
+ }
161
+ }
162
+ /** Parse "/model <provider>/<modelId>" or a bare id; bare ids keep the current provider. */
163
+ function parseModelArg(arg) {
164
+ const trimmed = arg.trim();
165
+ const slash = trimmed.indexOf("/");
166
+ if (slash > 0) {
167
+ return { provider: trimmed.slice(0, slash), modelId: trimmed.slice(slash + 1) };
168
+ }
169
+ return { provider: "", modelId: trimmed };
170
+ }
171
+ function helpText() {
172
+ return [
173
+ "**Pi 命令**(通过 RPC 执行):",
174
+ "• `/new` — 新会话",
175
+ "• `/compact [说明]` — 压缩上下文",
176
+ "• `/model` / `/model <provider/id>` — 查看/切换模型",
177
+ "• `/models` — 列出可用模型",
178
+ "• `/thinking [level]` — 查看/设置思考级别",
179
+ "• `/session` — 会话统计与费用",
180
+ "• `/status` — 当前模型与状态",
181
+ "• `/name <名字>` — 会话命名",
182
+ "• `/export [路径]` — 导出会话 HTML",
183
+ "• `/bash <命令>` — 执行 shell 命令(写入上下文)",
184
+ "• `/abort` — 中止当前操作",
185
+ "• `/reload` — 重启 pi 进程(装插件/改配置后使用)",
186
+ "",
187
+ "**透传**: `/skill:名称`、提示词模板、扩展命令会直接执行;普通文本发给模型。",
188
+ "**Bridge 管理命令**: `/help`(本帮助)、`/trusted`、`/revoke`、`/channels`、`/enable`、`/disable`、`/toggletools`",
189
+ ].join("\n");
190
+ }
191
+ //# sourceMappingURL=command-map.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command-map.js","sourceRoot":"","sources":["../../src/rpc/command-map.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Message router — the core wiring between messenger transports and pi RPC.
3
+ *
4
+ * Shared by the standalone entry (and testable in isolation with a mock transport):
5
+ * incoming messenger message ──> router ──> RPC command / prompt
6
+ * agent events ────────────────> router ──> reply back to the messenger
7
+ */
8
+ import type { ChallengeAuth } from "../auth/challenge-auth.js";
9
+ import type { TransportManager } from "../transports/manager.js";
10
+ import type { ExternalMessage, PendingRemoteChat } from "../types.js";
11
+ import type { PiRpc } from "./pi-rpc.js";
12
+ export interface MessageRouterDeps {
13
+ rpc: PiRpc;
14
+ auth: ChallengeAuth;
15
+ transportManager: TransportManager;
16
+ /** Send a text reply to a chat via a transport (errors swallowed by caller) */
17
+ sendReply: (chatId: string, transport: string, text: string) => Promise<void>;
18
+ log: (...args: unknown[]) => void;
19
+ debug: boolean;
20
+ }
21
+ export interface MessageRouter {
22
+ /** Handle an incoming messenger message */
23
+ handleIncoming(msg: ExternalMessage): Promise<void>;
24
+ /** Handle an agent event (from pi RPC) */
25
+ handleEvent(event: unknown): void;
26
+ /** Chat that the current/last turn belongs to */
27
+ pendingRemoteChat: PendingRemoteChat | null;
28
+ }
29
+ export declare function createMessageRouter(deps: MessageRouterDeps): MessageRouter;
30
+ //# sourceMappingURL=message-router.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-router.d.ts","sourceRoot":"","sources":["../../src/rpc/message-router.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAQ/D,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEtE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAEzC,MAAM,WAAW,iBAAiB;IAChC,GAAG,EAAE,KAAK,CAAC;IACX,IAAI,EAAE,aAAa,CAAC;IACpB,gBAAgB,EAAE,gBAAgB,CAAC;IACnC,+EAA+E;IAC/E,SAAS,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9E,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,CAAC;IAClC,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,2CAA2C;IAC3C,cAAc,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,0CAA0C;IAC1C,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI,CAAC;IAClC,iDAAiD;IACjD,iBAAiB,EAAE,iBAAiB,GAAG,IAAI,CAAC;CAC7C;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,iBAAiB,GAAG,aAAa,CAiI1E"}
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Message router — the core wiring between messenger transports and pi RPC.
3
+ *
4
+ * Shared by the standalone entry (and testable in isolation with a mock transport):
5
+ * incoming messenger message ──> router ──> RPC command / prompt
6
+ * agent events ────────────────> router ──> reply back to the messenger
7
+ */
8
+ import { loadConfig } from "../config.js";
9
+ import { extractTextFromMessage, formatToolCalls, hasToolCalls, splitMessage, } from "../formatting.js";
10
+ import { handleSlashCommand } from "./command-map.js";
11
+ export function createMessageRouter(deps) {
12
+ const { rpc, auth, transportManager, sendReply, log, debug } = deps;
13
+ let pendingRemoteChat = null;
14
+ return {
15
+ get pendingRemoteChat() {
16
+ return pendingRemoteChat;
17
+ },
18
+ async handleIncoming(msg) {
19
+ pendingRemoteChat = {
20
+ chatId: msg.chatId,
21
+ transport: msg.transport,
22
+ username: msg.username,
23
+ messageId: msg.messageId,
24
+ };
25
+ const text = msg.content.trim();
26
+ if (!text)
27
+ return;
28
+ if (debug)
29
+ log(`📥 [${msg.transport}] @${msg.username}: ${text.slice(0, 200)}`);
30
+ // Authorization (initiates 6-digit challenge for unknown users in DMs)
31
+ const isAuthorized = await auth.checkAuthorization(msg.userId, msg.chatId, msg.username, msg.isGroupChat, msg.wasMentioned ?? false, async (cId, replyText) => sendReply(cId, msg.transport, replyText), msg.transport);
32
+ // Bridge admin commands + challenge codes in DMs.
33
+ // /help is reserved for pi (the RPC command help also lists bridge commands).
34
+ if (!msg.isGroupChat && (text.startsWith("/") || /^\d{6}$/.test(text))) {
35
+ const cmdName = text.split(/\s+/)[0].toLowerCase();
36
+ if (!text.startsWith("/") || cmdName !== "/help") {
37
+ const handled = await auth.handleAdminCommand(text, msg.chatId, msg.userId, async (replyText) => sendReply(msg.chatId, msg.transport, replyText), msg.transport);
38
+ if (handled)
39
+ return;
40
+ }
41
+ }
42
+ if (!isAuthorized)
43
+ return;
44
+ // Slash commands → RPC mapping (builtin) or passthrough (extensions/skills/templates)
45
+ if (text.startsWith("/")) {
46
+ const handled = await handleSlashCommand(text, {
47
+ rpc,
48
+ reply: async (replyText) => sendReply(msg.chatId, msg.transport, replyText),
49
+ });
50
+ if (handled)
51
+ return;
52
+ }
53
+ // Plain message → prompt
54
+ try {
55
+ await rpc.prompt(text);
56
+ }
57
+ catch (err) {
58
+ await sendReply(msg.chatId, msg.transport, `❌ 无法发送给 pi: ${err.message}`);
59
+ }
60
+ },
61
+ handleEvent(rawEvent) {
62
+ // extension_error is emitted by the RPC layer but is not part of the
63
+ // typed AgentSessionEvent union — widen for runtime event checking.
64
+ const event = rawEvent;
65
+ if (event.type === "turn_start") {
66
+ if (pendingRemoteChat) {
67
+ transportManager
68
+ .sendTyping(pendingRemoteChat.chatId, pendingRemoteChat.transport)
69
+ .catch(() => { });
70
+ }
71
+ return;
72
+ }
73
+ if (event.type === "turn_end") {
74
+ if (!pendingRemoteChat)
75
+ return;
76
+ const message = event.message;
77
+ const responseText = extractTextFromMessage(message);
78
+ const toolCallsText = formatToolCalls(message);
79
+ const pendingTools = hasToolCalls(message);
80
+ const cfg = loadConfig();
81
+ const parts = [];
82
+ const trimmed = responseText.trim();
83
+ if (trimmed)
84
+ parts.push(trimmed);
85
+ if (toolCallsText && !cfg.hideToolCalls)
86
+ parts.push(toolCallsText);
87
+ if (parts.length === 0) {
88
+ // No content this turn — keep pendingRemoteChat for a follow-up turn
89
+ return;
90
+ }
91
+ const fullText = parts.join("\n\n");
92
+ const chunks = splitMessage(fullText, 4000);
93
+ for (const chunk of chunks) {
94
+ sendReply(pendingRemoteChat.chatId, pendingRemoteChat.transport, chunk).catch(() => { });
95
+ }
96
+ if (!pendingTools) {
97
+ pendingRemoteChat = null;
98
+ }
99
+ return;
100
+ }
101
+ if (event.type === "extension_error") {
102
+ if (pendingRemoteChat) {
103
+ sendReply(pendingRemoteChat.chatId, pendingRemoteChat.transport, `⚠️ 扩展错误 (${event.extensionPath ?? "unknown"}): ${event.error ?? "unknown"}`).catch(() => { });
104
+ }
105
+ log("⚠️ extension_error:", event.extensionPath, event.error);
106
+ }
107
+ },
108
+ };
109
+ }
110
+ //# sourceMappingURL=message-router.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-router.js","sourceRoot":"","sources":["../../src/rpc/message-router.ts"],"names":[],"mappings":"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"}