pi-maestro-mobile 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.
Files changed (110) hide show
  1. package/README.md +85 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +84 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/event-log.d.ts +18 -0
  7. package/dist/event-log.d.ts.map +1 -0
  8. package/dist/event-log.js +31 -0
  9. package/dist/event-log.js.map +1 -0
  10. package/dist/extension.d.ts +3 -0
  11. package/dist/extension.d.ts.map +1 -0
  12. package/dist/extension.js +118 -0
  13. package/dist/extension.js.map +1 -0
  14. package/dist/host-controller.d.ts +69 -0
  15. package/dist/host-controller.d.ts.map +1 -0
  16. package/dist/host-controller.js +195 -0
  17. package/dist/host-controller.js.map +1 -0
  18. package/dist/index.d.ts +11 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +12 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/jsonl-index.d.ts +19 -0
  23. package/dist/jsonl-index.d.ts.map +1 -0
  24. package/dist/jsonl-index.js +84 -0
  25. package/dist/jsonl-index.js.map +1 -0
  26. package/dist/jsonl-pager.d.ts +29 -0
  27. package/dist/jsonl-pager.d.ts.map +1 -0
  28. package/dist/jsonl-pager.js +237 -0
  29. package/dist/jsonl-pager.js.map +1 -0
  30. package/dist/jsonl-replay.d.ts +9 -0
  31. package/dist/jsonl-replay.d.ts.map +1 -0
  32. package/dist/jsonl-replay.js +140 -0
  33. package/dist/jsonl-replay.js.map +1 -0
  34. package/dist/live-sessions.d.ts +47 -0
  35. package/dist/live-sessions.d.ts.map +1 -0
  36. package/dist/live-sessions.js +201 -0
  37. package/dist/live-sessions.js.map +1 -0
  38. package/dist/maestro-settings.d.ts +40 -0
  39. package/dist/maestro-settings.d.ts.map +1 -0
  40. package/dist/maestro-settings.js +122 -0
  41. package/dist/maestro-settings.js.map +1 -0
  42. package/dist/maestro-state.d.ts +31 -0
  43. package/dist/maestro-state.d.ts.map +1 -0
  44. package/dist/maestro-state.js +219 -0
  45. package/dist/maestro-state.js.map +1 -0
  46. package/dist/mobile-agent.d.ts +86 -0
  47. package/dist/mobile-agent.d.ts.map +1 -0
  48. package/dist/mobile-agent.js +2 -0
  49. package/dist/mobile-agent.js.map +1 -0
  50. package/dist/mobile-ui-context.d.ts +26 -0
  51. package/dist/mobile-ui-context.d.ts.map +1 -0
  52. package/dist/mobile-ui-context.js +164 -0
  53. package/dist/mobile-ui-context.js.map +1 -0
  54. package/dist/monitor-projection.d.ts +19 -0
  55. package/dist/monitor-projection.d.ts.map +1 -0
  56. package/dist/monitor-projection.js +79 -0
  57. package/dist/monitor-projection.js.map +1 -0
  58. package/dist/pi/pi-sdk-runtime.d.ts +17 -0
  59. package/dist/pi/pi-sdk-runtime.d.ts.map +1 -0
  60. package/dist/pi/pi-sdk-runtime.js +57 -0
  61. package/dist/pi/pi-sdk-runtime.js.map +1 -0
  62. package/dist/server/helpers.d.ts +23 -0
  63. package/dist/server/helpers.d.ts.map +1 -0
  64. package/dist/server/helpers.js +179 -0
  65. package/dist/server/helpers.js.map +1 -0
  66. package/dist/server/mobile-host-server.d.ts +43 -0
  67. package/dist/server/mobile-host-server.d.ts.map +1 -0
  68. package/dist/server/mobile-host-server.js +715 -0
  69. package/dist/server/mobile-host-server.js.map +1 -0
  70. package/dist/server/ws-command-handlers.d.ts +18 -0
  71. package/dist/server/ws-command-handlers.d.ts.map +1 -0
  72. package/dist/server/ws-command-handlers.js +173 -0
  73. package/dist/server/ws-command-handlers.js.map +1 -0
  74. package/dist/session-runner.d.ts +130 -0
  75. package/dist/session-runner.d.ts.map +1 -0
  76. package/dist/session-runner.js +576 -0
  77. package/dist/session-runner.js.map +1 -0
  78. package/dist/types.d.ts +83 -0
  79. package/dist/types.d.ts.map +1 -0
  80. package/dist/types.js +2 -0
  81. package/dist/types.js.map +1 -0
  82. package/dist/usage-reader.d.ts +49 -0
  83. package/dist/usage-reader.d.ts.map +1 -0
  84. package/dist/usage-reader.js +189 -0
  85. package/dist/usage-reader.js.map +1 -0
  86. package/dist/vendor/shared/index.d.ts +4 -0
  87. package/dist/vendor/shared/index.d.ts.map +1 -0
  88. package/dist/vendor/shared/index.js +4 -0
  89. package/dist/vendor/shared/index.js.map +1 -0
  90. package/dist/vendor/shared/protocol.d.ts +471 -0
  91. package/dist/vendor/shared/protocol.d.ts.map +1 -0
  92. package/dist/vendor/shared/protocol.js +34 -0
  93. package/dist/vendor/shared/protocol.js.map +1 -0
  94. package/dist/vendor/shared/timeline.d.ts +15 -0
  95. package/dist/vendor/shared/timeline.d.ts.map +1 -0
  96. package/dist/vendor/shared/timeline.js +21 -0
  97. package/dist/vendor/shared/timeline.js.map +1 -0
  98. package/dist/vendor/shared/validation.d.ts +8 -0
  99. package/dist/vendor/shared/validation.d.ts.map +1 -0
  100. package/dist/vendor/shared/validation.js +25 -0
  101. package/dist/vendor/shared/validation.js.map +1 -0
  102. package/dist/version-detector.d.ts +36 -0
  103. package/dist/version-detector.d.ts.map +1 -0
  104. package/dist/version-detector.js +141 -0
  105. package/dist/version-detector.js.map +1 -0
  106. package/dist/workspace-telemetry.d.ts +10 -0
  107. package/dist/workspace-telemetry.d.ts.map +1 -0
  108. package/dist/workspace-telemetry.js +84 -0
  109. package/dist/workspace-telemetry.js.map +1 -0
  110. package/package.json +64 -0
@@ -0,0 +1,17 @@
1
+ import { type SessionInfo } from "@earendil-works/pi-coding-agent";
2
+ import type { MobileAgentRuntime } from "../mobile-agent.js";
3
+ import type { RuntimeFactory } from "../types.js";
4
+ export interface OpenSessionRequest {
5
+ cwd: string;
6
+ mode?: "create" | "continue";
7
+ sessionFile?: string;
8
+ }
9
+ /**
10
+ * PiSdkRuntimeFactory — 通过 Pi SDK 创建 AgentSessionRuntime
11
+ * 与 pi-mobile/apps/host 同源(参考实现),保证与桌面 TUI 共享同一份会话文件
12
+ */
13
+ export declare class PiSdkRuntimeFactory implements RuntimeFactory {
14
+ createRuntime(request: OpenSessionRequest): Promise<MobileAgentRuntime>;
15
+ listSessions(cwd?: string): Promise<SessionInfo[]>;
16
+ }
17
+ //# sourceMappingURL=pi-sdk-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi-sdk-runtime.d.ts","sourceRoot":"","sources":["../../src/pi/pi-sdk-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAOL,KAAK,WAAW,EACjB,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC7B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,qBAAa,mBAAoB,YAAW,cAAc;IAClD,aAAa,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAmCvE,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;CAIzD"}
@@ -0,0 +1,57 @@
1
+ import { createAgentSessionFromServices, createAgentSessionRuntime, createAgentSessionServices, getAgentDir, SessionManager, } from "@earendil-works/pi-coding-agent";
2
+ /**
3
+ * PiSdkRuntimeFactory — 通过 Pi SDK 创建 AgentSessionRuntime
4
+ * 与 pi-mobile/apps/host 同源(参考实现),保证与桌面 TUI 共享同一份会话文件
5
+ */
6
+ export class PiSdkRuntimeFactory {
7
+ async createRuntime(request) {
8
+ const sessionManager = createSessionManager(request);
9
+ const createRuntime = async ({ cwd, sessionManager: sm, sessionStartEvent }) => {
10
+ const services = await createAgentSessionServices({
11
+ cwd,
12
+ // 过滤 pi-maestro-teammate:host 进程与真实 Pi 会话共享工作区时
13
+ // 会触发 workspace owner claim 冲突(同一 cwd 只能有一个 live owner)。
14
+ // host 只做只读会话浏览/驱动,不需要 teammate 的 peer 运行时。
15
+ resourceLoaderOptions: {
16
+ extensionsOverride: (base) => ({
17
+ ...base,
18
+ extensions: base.extensions.filter((e) => !isTeammateExtension(e.path)),
19
+ }),
20
+ },
21
+ });
22
+ return {
23
+ ...(await createAgentSessionFromServices({
24
+ services,
25
+ sessionManager: sm,
26
+ ...(sessionStartEvent ? { sessionStartEvent } : {}),
27
+ })),
28
+ services,
29
+ diagnostics: services.diagnostics,
30
+ };
31
+ };
32
+ const runtime = await createAgentSessionRuntime(createRuntime, {
33
+ cwd: request.cwd,
34
+ agentDir: getAgentDir(),
35
+ sessionManager,
36
+ });
37
+ return runtime;
38
+ }
39
+ async listSessions(cwd) {
40
+ const sessions = cwd ? await SessionManager.list(cwd) : await SessionManager.listAll();
41
+ return sessions;
42
+ }
43
+ }
44
+ function createSessionManager(request) {
45
+ if (request.mode === "continue") {
46
+ return SessionManager.continueRecent(request.cwd);
47
+ }
48
+ if (request.sessionFile) {
49
+ return SessionManager.open(request.sessionFile, undefined, request.cwd);
50
+ }
51
+ return SessionManager.create(request.cwd);
52
+ }
53
+ /** 判断扩展路径是否属于 pi-maestro-teammate(其 workspace peer 会与真实 Pi 冲突) */
54
+ function isTeammateExtension(path) {
55
+ return path.includes("pi-maestro-teammate");
56
+ }
57
+ //# sourceMappingURL=pi-sdk-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pi-sdk-runtime.js","sourceRoot":"","sources":["../../src/pi/pi-sdk-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,8BAA8B,EAC9B,yBAAyB,EACzB,0BAA0B,EAC1B,WAAW,EACX,cAAc,GAGf,MAAM,iCAAiC,CAAC;AAUzC;;;GAGG;AACH,MAAM,OAAO,mBAAmB;IAC9B,KAAK,CAAC,aAAa,CAAC,OAA2B;QAC7C,MAAM,cAAc,GAAG,oBAAoB,CAAC,OAAO,CAAC,CAAC;QACrD,MAAM,aAAa,GAAqC,KAAK,EAAE,EAAE,GAAG,EAAE,cAAc,EAAE,EAAE,EAAE,iBAAiB,EAAE,EAAE,EAAE;YAC/G,MAAM,QAAQ,GAAG,MAAM,0BAA0B,CAAC;gBAChD,GAAG;gBACH,gDAAgD;gBAChD,yDAAyD;gBACzD,4CAA4C;gBAC5C,qBAAqB,EAAE;oBACrB,kBAAkB,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;wBAC7B,GAAG,IAAI;wBACP,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,mBAAmB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;qBACxE,CAAC;iBACH;aACF,CAAC,CAAC;YACH,OAAO;gBACL,GAAG,CAAC,MAAM,8BAA8B,CAAC;oBACvC,QAAQ;oBACR,cAAc,EAAE,EAAE;oBAClB,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC,EAAE,iBAAiB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACpD,CAAC,CAAC;gBACH,QAAQ;gBACR,WAAW,EAAE,QAAQ,CAAC,WAAW;aAClC,CAAC;QACJ,CAAC,CAAC;QAEF,MAAM,OAAO,GAAG,MAAM,yBAAyB,CAAC,aAAa,EAAE;YAC7D,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,QAAQ,EAAE,WAAW,EAAE;YACvB,cAAc;SACf,CAAC,CAAC;QAEH,OAAO,OAAwC,CAAC;IAClD,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,GAAY;QAC7B,MAAM,QAAQ,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,cAAc,CAAC,OAAO,EAAE,CAAC;QACvF,OAAO,QAAQ,CAAC;IAClB,CAAC;CACF;AAED,SAAS,oBAAoB,CAAC,OAA2B;IACvD,IAAI,OAAO,CAAC,IAAI,KAAK,UAAU,EAAE,CAAC;QAChC,OAAO,cAAc,CAAC,cAAc,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpD,CAAC;IACD,IAAI,OAAO,CAAC,WAAW,EAAE,CAAC;QACxB,OAAO,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,SAAS,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,cAAc,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;AAC5C,CAAC;AAED,kEAAkE;AAClE,SAAS,mBAAmB,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,QAAQ,CAAC,qBAAqB,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,23 @@
1
+ import type { HostSessionList } from "../vendor/shared/index.js";
2
+ /**
3
+ * 将 SessionManager 返回的完整 SessionInfo 裁剪为移动端友好的摘要。
4
+ * 关键:不携带 allMessagesText 等大字段,避免移动端流量/内存浪费。
5
+ */
6
+ export declare function toSessionSummaryList(records: unknown[]): Promise<HostSessionList>;
7
+ /** 协议 images 元素 → Pi SDK ImageContent(type/data/mimeType);非法返回 undefined */
8
+ export declare function toSdkImageContent(img: {
9
+ data: string;
10
+ mime: string;
11
+ }): {
12
+ type: "image";
13
+ data: string;
14
+ mimeType: string;
15
+ } | undefined;
16
+ /** 扫描可用的 skill 名录(agent 全局 + 项目本地) */
17
+ export declare function listSkills(cwd: string): Promise<string[]>;
18
+ /** 安全读取本地图片:绝对路径 + 图片扩展名 + 大小限制 + realpath 防 symlink 绕过 */
19
+ export declare function serveImageFile(filePath: string): Promise<{
20
+ data: Buffer;
21
+ mime: string;
22
+ } | undefined>;
23
+ //# sourceMappingURL=helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../src/server/helpers.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,eAAe,EAAsB,MAAM,2BAA2B,CAAC;AAIrF;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,eAAe,CAAC,CAuBvF;AAgDD,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAIpI;AAID,sCAAsC;AACtC,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAgB/D;AAqBD,2DAA2D;AAC3D,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAAC,CAsB1G"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * server/helpers — MobileHostServer 与 ws-command-handlers 共用的纯函数
3
+ * (H9 拆分:会话摘要、图片编解码、skill 扫描、安全文件读取)
4
+ */
5
+ import { readFile, open, stat, readdir } from "node:fs/promises";
6
+ import { realpath } from "node:fs/promises";
7
+ import { isAbsolute, normalize, join } from "node:path";
8
+ import { homedir, tmpdir } from "node:os";
9
+ // ── 会话摘要 ─────────────────────────────────────────────────────────────────
10
+ /**
11
+ * 将 SessionManager 返回的完整 SessionInfo 裁剪为移动端友好的摘要。
12
+ * 关键:不携带 allMessagesText 等大字段,避免移动端流量/内存浪费。
13
+ */
14
+ export async function toSessionSummaryList(records) {
15
+ const sessions = [];
16
+ for (const raw of records) {
17
+ const r = raw;
18
+ const cwd = String(r.cwd ?? "");
19
+ const title = String(r.firstMessage ?? r.title ?? "");
20
+ const id = String(r.id ?? "");
21
+ const path = String(r.path ?? r.sessionFile ?? "");
22
+ sessions.push({
23
+ id,
24
+ cwd,
25
+ cwdName: cwd.split("/").filter(Boolean).pop() ?? cwd,
26
+ path,
27
+ title: title.length > 80 ? `${title.slice(0, 80)}…` : title,
28
+ name: typeof r.name === "string" && r.name ? r.name : undefined,
29
+ model: await latestModelFromJsonl(path),
30
+ messageCount: typeof r.messageCount === "number" ? r.messageCount : 0,
31
+ // 规范化为 ISO 字符串:Hermes(iOS)解析不了 "Thu Sep 03 2026 ..." 这种本地化格式
32
+ updatedAt: normalizeIso(String(r.modified ?? r.updatedAt ?? "")),
33
+ ...(r.created ? { createdAt: normalizeIso(String(r.created)) } : {}),
34
+ });
35
+ }
36
+ return { sessions, observedAt: new Date().toISOString() };
37
+ }
38
+ /** 从 jsonl 里找最近的 model_change,返回 provider/modelId 精简名 */
39
+ async function latestModelFromJsonl(path) {
40
+ if (!path || !path.endsWith(".jsonl"))
41
+ return undefined;
42
+ try {
43
+ // 只读尾部 256KB(model_change 通常在会话活跃期靠后出现),避免整文件扫描
44
+ const handle = await open(path, "r");
45
+ try {
46
+ const { size } = await handle.stat();
47
+ const readLen = Math.min(TRAIL_READ_BYTES, size);
48
+ const buf = Buffer.alloc(readLen);
49
+ await handle.read(buf, 0, readLen, size - readLen);
50
+ const tail = buf.toString("utf8");
51
+ const lines = tail.split("\n");
52
+ for (let i = lines.length - 1; i >= 0; i--) {
53
+ const line = lines[i];
54
+ if (!line.includes("model_change"))
55
+ continue;
56
+ try {
57
+ const o = JSON.parse(line);
58
+ if (o.modelId) {
59
+ const provider = o.provider ? `${o.provider}/` : "";
60
+ return `${provider}${o.modelId}`;
61
+ }
62
+ }
63
+ catch {
64
+ // ignore malformed
65
+ }
66
+ }
67
+ return undefined;
68
+ }
69
+ finally {
70
+ await handle.close();
71
+ }
72
+ }
73
+ catch {
74
+ return undefined;
75
+ }
76
+ }
77
+ const TRAIL_READ_BYTES = 256 * 1024;
78
+ /** 尝试解析为 ISO;无法解析时保留原字符串(App 端需兜底) */
79
+ function normalizeIso(raw) {
80
+ if (!raw)
81
+ return "";
82
+ const t = Date.parse(raw);
83
+ return Number.isFinite(t) ? new Date(t).toISOString() : raw;
84
+ }
85
+ // ── 图片协议转换 ─────────────────────────────────────────────────────────────
86
+ /** 协议 images 元素 → Pi SDK ImageContent(type/data/mimeType);非法返回 undefined */
87
+ export function toSdkImageContent(img) {
88
+ if (typeof img?.data !== "string" || img.data.length === 0)
89
+ return undefined;
90
+ if (typeof img?.mime !== "string" || !img.mime.startsWith("image/"))
91
+ return undefined;
92
+ return { type: "image", data: img.data, mimeType: img.mime };
93
+ }
94
+ // ── skill 扫描 ───────────────────────────────────────────────────────────────
95
+ /** 扫描可用的 skill 名录(agent 全局 + 项目本地) */
96
+ export async function listSkills(cwd) {
97
+ const dirs = [];
98
+ try {
99
+ dirs.push(join(homedir(), ".pi", "agent", "skills"));
100
+ }
101
+ catch { /* skip */ }
102
+ try {
103
+ dirs.push(join(cwd, ".pi", "skills"));
104
+ }
105
+ catch { /* skip */ }
106
+ const names = new Set();
107
+ for (const dir of dirs) {
108
+ try {
109
+ const entries = await readdir(dir, { withFileTypes: true });
110
+ for (const e of entries) {
111
+ if (e.isDirectory() && !e.name.startsWith("."))
112
+ names.add(e.name);
113
+ }
114
+ }
115
+ catch {
116
+ // skip
117
+ }
118
+ }
119
+ return [...names].sort();
120
+ }
121
+ // ── 安全文件读取(图片预览)───────────────────────────────────────────────
122
+ const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".webp", ".bmp"]);
123
+ const MAX_IMAGE_BYTES = 20 * 1024 * 1024; // 20MB
124
+ const IMAGE_MIME = {
125
+ ".png": "image/png",
126
+ ".jpg": "image/jpeg",
127
+ ".jpeg": "image/jpeg",
128
+ ".gif": "image/gif",
129
+ ".webp": "image/webp",
130
+ ".bmp": "image/bmp",
131
+ };
132
+ /** stat 包装(抛错由调用方捕获) */
133
+ function fstat(p) {
134
+ return stat(p);
135
+ }
136
+ /** 安全读取本地图片:绝对路径 + 图片扩展名 + 大小限制 + realpath 防 symlink 绕过 */
137
+ export async function serveImageFile(filePath) {
138
+ const trimmed = filePath.trim();
139
+ if (!trimmed || !isAbsolute(trimmed))
140
+ return undefined;
141
+ // 防止路径穿越:normalize 后必须仍是绝对路径且不含 ..
142
+ const normalized = normalize(trimmed);
143
+ if (!isAbsolute(normalized) || normalized.includes(".."))
144
+ return undefined;
145
+ const ext = normalized.slice(normalized.lastIndexOf(".")).toLowerCase();
146
+ if (!IMAGE_EXTENSIONS.has(ext))
147
+ return undefined;
148
+ try {
149
+ // P3-3:先解析真实路径,防止指向任意位置的 symlink 绕过绝对路径约束
150
+ const resolved = await realpath(normalized);
151
+ if (!(await isUnderAllowedRoot(resolved)))
152
+ return undefined;
153
+ // 先 stat 校验大小再读取,避免超大文件先耗尽内存
154
+ const st = await fstat(resolved);
155
+ if (st.size === 0 || st.size > MAX_IMAGE_BYTES)
156
+ return undefined;
157
+ const data = await readFile(resolved);
158
+ return { data, mime: IMAGE_MIME[ext] ?? "application/octet-stream" };
159
+ }
160
+ catch {
161
+ return undefined;
162
+ }
163
+ }
164
+ /** 允许读取的根目录:用户目录、系统临时目录、进程工作目录(会话产物、图片预览所在)。取 realpath 以兼容 /tmp → /private/tmp */
165
+ async function isUnderAllowedRoot(resolved) {
166
+ const roots = [homedir(), tmpdir(), process.cwd()];
167
+ for (const root of roots) {
168
+ try {
169
+ const rp = await realpath(root);
170
+ if (resolved === rp || resolved.startsWith(rp + "/"))
171
+ return true;
172
+ }
173
+ catch {
174
+ // root 不存在时跳过
175
+ }
176
+ }
177
+ return false;
178
+ }
179
+ //# sourceMappingURL=helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/server/helpers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { HostController } from "../host-controller.js";
2
+ export interface MobileHostServerOptions {
3
+ token?: string;
4
+ corsOrigin?: string;
5
+ /** WS Origin 额外白名单(除 loopback 与绑定 host 外的受信来源,如显式 LAN IP) */
6
+ allowedOrigins?: string[];
7
+ }
8
+ /**
9
+ * MobileHostServer — 移动端直连服务器(HTTP REST + WebSocket)
10
+ *
11
+ * 直连模式:同一局域网/同机,无 relay。
12
+ * - WS: /ws 实时事件流 + 客户端命令
13
+ * - HTTP: /api/health, /api/status, /api/sessions, /api/maestro
14
+ *
15
+ * 安全:token 可选(Bearer header 或 ?token=)
16
+ */
17
+ export declare class MobileHostServer {
18
+ private readonly controller;
19
+ private readonly options;
20
+ private readonly server;
21
+ private readonly webSocketServer;
22
+ private readonly clients;
23
+ /** 单客户端并发命令上限:超过则拒绝,防命令洪泛(DoS) */
24
+ private static readonly MAX_CONNECTIONS;
25
+ private static readonly MAX_CONCURRENT_COMMANDS;
26
+ private unsubscribeController;
27
+ private boundHost;
28
+ constructor(controller: HostController, options?: MobileHostServerOptions);
29
+ listen(port: number, hostname?: string): Promise<void>;
30
+ address(): {
31
+ port: number;
32
+ };
33
+ close(): Promise<void>;
34
+ private handleHttp;
35
+ private authorized;
36
+ private handleUpgrade;
37
+ /** WS 握手 Origin 白名单:loopback 变体 + 绑定 host(非 0.0.0.0 时)+ 显式配置 */
38
+ private isOriginAllowed;
39
+ private handleClientMessage;
40
+ private sendAck;
41
+ private sendError;
42
+ }
43
+ //# sourceMappingURL=mobile-host-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mobile-host-server.d.ts","sourceRoot":"","sources":["../../src/server/mobile-host-server.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAO5D,MAAM,WAAW,uBAAuB;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AASD;;;;;;;;GAQG;AACH,qBAAa,gBAAgB;IAWzB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAX1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAwE;IACxG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA2B;IACnD,kCAAkC;IAClC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,eAAe,CAAM;IAC7C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAK;IACpD,OAAO,CAAC,qBAAqB,CAA2B;IACxD,OAAO,CAAC,SAAS,CAAa;gBAGX,UAAU,EAAE,cAAc,EAC1B,OAAO,GAAE,uBAA4B;IA4CxD,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAOzD,OAAO,IAAI;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE;IAQrB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAcd,UAAU;IAwFxB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,aAAa;IAuBrB,gEAAgE;IAChE,OAAO,CAAC,eAAe;YA4BT,mBAAmB;IAkOjC,OAAO,CAAC,OAAO;IASf,OAAO,CAAC,SAAS;CAQlB"}