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
package/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # pi-maestro-mobile
2
+
3
+ PC 端常驻服务:把 Pi agent 会话、Monitor 窗口 telemetry、maestro 调度状态投影给手机 App(Maestro Mobile)。
4
+
5
+ ```
6
+ 手机 App ←—— 局域网 WS/HTTP ——→ pi-maestro-mobile ←→ Pi SDK AgentSession / ~/.pi 文件状态
7
+ ```
8
+
9
+ ## 安装
10
+
11
+ ### 方式一:npm 全局安装(推荐,launchd/systemd 常驻)
12
+
13
+ ```bash
14
+ npm install -g pi-maestro-mobile
15
+ pi-maestro-mobile --port 4739
16
+ ```
17
+
18
+ 开机常驻见下方 [守护进程](#守护进程)。
19
+
20
+ ### 方式二:pi 扩展安装
21
+
22
+ ```bash
23
+ pi install npm:pi-maestro-mobile
24
+ ```
25
+
26
+ (薄扩展入口:`/maestro-host` 命令管理守护进程,规划中;当前版本请用方式一。)
27
+
28
+ ### 方式三:Docker(看板模式)
29
+
30
+ ```bash
31
+ docker run -d --name maestro-mobile \
32
+ -p 4739:4739 \
33
+ -v ~/.pi/agent/sessions:/home/node/.pi/agent/sessions:ro \
34
+ -v ~/.pi/teammate:/home/node/.pi/teammate:ro \
35
+ -e MAESTRO_MOBILE_TOKEN=your-secret \
36
+ maestro-mobile
37
+ ```
38
+
39
+ 容器模式能力边界:Dashboard / Monitor 看板、会话历史浏览、usage 统计**可用**;
40
+ `open_session` / `steer_window` 的会话接管**不可用**(需要容器内安装 pi 及认证上下文)。
41
+ 详细说明见仓库 `docs/deploy.md`。
42
+
43
+ ## 使用
44
+
45
+ ```bash
46
+ pi-maestro-mobile [--port 4739] [--host 0.0.0.0] [--token <secret>] [--project-root <dir>] [--poll-ms 5000]
47
+ ```
48
+
49
+ | 参数 | 环境变量 | 默认 | 说明 |
50
+ |---|---|---|---|
51
+ | `--port` | `MAESTRO_MOBILE_PORT` | 4739 | HTTP+WS 端口 |
52
+ | `--host` | `MAESTRO_MOBILE_HOST` | 0.0.0.0 | 监听地址 |
53
+ | `--token` | `MAESTRO_MOBILE_TOKEN` | 随机(启动时打印) | Bearer/`?token=` 鉴权 |
54
+ | `--project-root` | `MAESTRO_MOBILE_PROJECT_ROOT` | cwd | maestro flow-schedule 读取根 |
55
+ | `--poll-ms` | `MAESTRO_MOBILE_POLL_MS` | 5000 | telemetry/调度轮询间隔 |
56
+
57
+ 手机 App 里填 `ws://<PC 局域网 IP>:4739/ws`(有 token 时加 `?token=`)。
58
+
59
+ ## 守护进程
60
+
61
+ **macOS(launchd)**:
62
+
63
+ ```bash
64
+ cp deploy/com.maestro-mobile.host.plist ~/Library/LaunchAgents/
65
+ # 编辑 plist 里的安装路径、WorkingDirectory、token
66
+ launchctl load ~/Library/LaunchAgents/com.maestro-mobile.host.plist
67
+ ```
68
+
69
+ **Linux(systemd)**:
70
+
71
+ ```bash
72
+ sudo cp deploy/maestro-mobile.service /etc/systemd/system/
73
+ sudo systemctl enable --now maestro-mobile
74
+ ```
75
+
76
+ ## HTTP 接口速览
77
+
78
+ | 路由 | 说明 |
79
+ |---|---|
80
+ | `GET /api/health` | 存活检查 |
81
+ | `GET /api/status` | 版本/uptime/sessions(含 pi/flow/CLI 版本探测) |
82
+ | `GET /api/workspace-telemetry` | Monitor 窗口 owners(pid/sessionId/agents/contextPressure) |
83
+ | `GET /api/maestro` | flow-schedule 调度状态 |
84
+ | `GET /api/live-sessions` | 活跃 Pi 会话扫描 |
85
+ | `WS /ws` | 实时事件流 + 客户端命令(协议见 `@maestro-mobile/shared`) |
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,84 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * maestro-mobile — Host 守护进程入口
4
+ *
5
+ * 用法:
6
+ * maestro-mobile [--port 4739] [--host 0.0.0.0] [--token <secret>] [--project-root <dir>] [--poll-ms 5000]
7
+ *
8
+ * 环境变量(与 CLI 参数等价):
9
+ * MAESTRO_MOBILE_PORT / MAESTRO_MOBILE_HOST / MAESTRO_MOBILE_TOKEN / MAESTRO_MOBILE_PROJECT_ROOT
10
+ *
11
+ * 安全:token 强制启用 —— 未提供 --token/环境变量时自动生成随机 token 并打印到 stdout。
12
+ * WS 握手校验 Origin(loopback 与绑定 host 白名单)。
13
+ */
14
+ import { HostController } from "./host-controller.js";
15
+ import { MobileHostServer } from "./server/mobile-host-server.js";
16
+ import { MaestroStateReader } from "./maestro-state.js";
17
+ import { PiSdkRuntimeFactory } from "./pi/pi-sdk-runtime.js";
18
+ import { randomBytes } from "node:crypto";
19
+ function parseArgs(argv) {
20
+ const args = new Map();
21
+ for (let i = 0; i < argv.length; i++) {
22
+ const arg = argv[i];
23
+ if (arg.startsWith("--")) {
24
+ const key = arg.slice(2);
25
+ const value = argv[i + 1] && !argv[i + 1].startsWith("--") ? argv[i + 1] : "true";
26
+ args.set(key, value);
27
+ if (value !== "true")
28
+ i++;
29
+ }
30
+ }
31
+ const port = Number(args.get("port") ?? process.env.MAESTRO_MOBILE_PORT ?? "4739");
32
+ // 安全默认:仅监听 loopback;显式 --host 0.0.0.0 才对 LAN 开放(README 安全节提醒配 token)
33
+ const host = args.get("host") ?? process.env.MAESTRO_MOBILE_HOST ?? "127.0.0.1";
34
+ const token = args.get("token") ?? process.env.MAESTRO_MOBILE_TOKEN ?? undefined;
35
+ const projectRoot = args.get("project-root") ?? process.env.MAESTRO_MOBILE_PROJECT_ROOT ?? process.cwd();
36
+ const pollMs = Number(args.get("poll-ms") ?? process.env.MAESTRO_MOBILE_POLL_MS ?? "5000");
37
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) {
38
+ throw new Error(`Invalid port: ${port}`);
39
+ }
40
+ return { port, host, token, projectRoot, pollMs };
41
+ }
42
+ async function main() {
43
+ const cli = parseArgs(process.argv.slice(2));
44
+ // P0-1:无 token 拒绝裸奔 —— 自动生成随机会话 token 并打印,避免 LAN 内无鉴权全权暴露
45
+ const token = cli.token ?? randomBytes(24).toString("hex");
46
+ if (!cli.token) {
47
+ console.log("[maestro-mobile] no --token provided; generated an ephemeral token for this run:");
48
+ console.log(`[maestro-mobile] token: ${token}`);
49
+ console.log("[maestro-mobile] (set MAESTRO_MOBILE_TOKEN or pass --token to reuse a stable token)");
50
+ }
51
+ console.log(`[maestro-mobile] starting on ${cli.host}:${cli.port} (project: ${cli.projectRoot})`);
52
+ const runtimeFactory = new PiSdkRuntimeFactory();
53
+ const maestroReader = new MaestroStateReader({ projectRoot: cli.projectRoot });
54
+ const controller = new HostController(runtimeFactory, maestroReader);
55
+ const server = new MobileHostServer(controller, { token });
56
+ await server.listen(cli.port, cli.host);
57
+ await controller.startMaestroPoll(cli.pollMs);
58
+ console.log(`[maestro-mobile] listening on http://${cli.host}:${server.address().port}`);
59
+ console.log(`[maestro-mobile] token auth enabled (use ?token= or Bearer header)`);
60
+ // 优雅关闭
61
+ let shuttingDown = false;
62
+ async function shutdown(signal) {
63
+ if (shuttingDown)
64
+ return;
65
+ shuttingDown = true;
66
+ console.log(`[maestro-mobile] received ${signal}, shutting down...`);
67
+ await controller.dispose();
68
+ await server.close();
69
+ console.log("[maestro-mobile] shutdown complete");
70
+ process.exit(0);
71
+ }
72
+ process.on("SIGINT", () => void shutdown("SIGINT"));
73
+ process.on("SIGTERM", () => void shutdown("SIGTERM"));
74
+ process.on("uncaughtException", (error) => {
75
+ // P3-6:未捕获异常后进程状态不可信,记录后带清理退出,避免带伤继续服务
76
+ console.error("[maestro-mobile] uncaught exception, shutting down:", error);
77
+ void shutdown("uncaughtException").finally(() => process.exit(1));
78
+ });
79
+ }
80
+ main().catch((error) => {
81
+ console.error("[maestro-mobile] fatal:", error);
82
+ process.exit(1);
83
+ });
84
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { HostEvent } from "./vendor/shared/index.js";
2
+ /** 分布式 Omit:对联合类型的每个成员分别 Omit(TS 默认 Omit 不是分布式的) */
3
+ export type DistributiveOmit<T, K extends PropertyKey> = T extends unknown ? Omit<T, K> : never;
4
+ /**
5
+ * EventLog — 事件日志(顺序序列号 + 持久化支持)
6
+ * 用于断线重连后的增量同步
7
+ */
8
+ export declare class EventLog {
9
+ private readonly events;
10
+ private nextSeq;
11
+ private readonly maxEntries;
12
+ constructor(maxEntries?: number);
13
+ get nextSequence(): number;
14
+ record(event: DistributiveOmit<HostEvent, "seq">): HostEvent;
15
+ eventsSince(seq: number): HostEvent[];
16
+ get all(): HostEvent[];
17
+ }
18
+ //# sourceMappingURL=event-log.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-log.d.ts","sourceRoot":"","sources":["../src/event-log.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,0BAA0B,CAAC;AAE1D,oDAAoD;AACpD,MAAM,MAAM,gBAAgB,CAAC,CAAC,EAAE,CAAC,SAAS,WAAW,IAAI,CAAC,SAAS,OAAO,GAAG,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,KAAK,CAAC;AAEhG;;;GAGG;AACH,qBAAa,QAAQ;IACnB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAmB;IAC1C,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,UAAU,SAAS;IAI/B,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED,MAAM,CAAC,KAAK,EAAE,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,GAAG,SAAS;IAU5D,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE;IAIrC,IAAI,GAAG,IAAI,SAAS,EAAE,CAErB;CACF"}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * EventLog — 事件日志(顺序序列号 + 持久化支持)
3
+ * 用于断线重连后的增量同步
4
+ */
5
+ export class EventLog {
6
+ events = [];
7
+ nextSeq = 1;
8
+ maxEntries;
9
+ constructor(maxEntries = 10_000) {
10
+ this.maxEntries = maxEntries;
11
+ }
12
+ get nextSequence() {
13
+ return this.nextSeq;
14
+ }
15
+ record(event) {
16
+ const seq = this.nextSeq++;
17
+ const full = { ...event, seq };
18
+ this.events.push(full);
19
+ if (this.events.length > this.maxEntries) {
20
+ this.events.splice(0, this.events.length - this.maxEntries);
21
+ }
22
+ return full;
23
+ }
24
+ eventsSince(seq) {
25
+ return this.events.filter((e) => e.seq > seq);
26
+ }
27
+ get all() {
28
+ return [...this.events];
29
+ }
30
+ }
31
+ //# sourceMappingURL=event-log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"event-log.js","sourceRoot":"","sources":["../src/event-log.ts"],"names":[],"mappings":"AAKA;;;GAGG;AACH,MAAM,OAAO,QAAQ;IACF,MAAM,GAAgB,EAAE,CAAC;IAClC,OAAO,GAAG,CAAC,CAAC;IACH,UAAU,CAAS;IAEpC,YAAY,UAAU,GAAG,MAAM;QAC7B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,OAAO,CAAC;IACtB,CAAC;IAED,MAAM,CAAC,KAAyC;QAC9C,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;QAC3B,MAAM,IAAI,GAAc,EAAE,GAAG,KAAK,EAAE,GAAG,EAAe,CAAC;QACvD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACvB,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC;YACzC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,WAAW,CAAC,GAAW;QACrB,OAAO,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;IAChD,CAAC;IAED,IAAI,GAAG;QACL,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC;IAC1B,CAAC;CACF"}
@@ -0,0 +1,3 @@
1
+ import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
+ export default function maestroHostExtension(pi: ExtensionAPI): void;
3
+ //# sourceMappingURL=extension.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.ts","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iCAAiC,CAAC;AA6CpE,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CAiEnE"}
@@ -0,0 +1,118 @@
1
+ /**
2
+ * maestro-mobile extension — 薄遥控器(用户提案:pi 扩展自动拉起 npm 包的 host)
3
+ *
4
+ * 职责边界(刻意保持薄):
5
+ * - start: 幂等启动 host 子进程(detached;已在监听则跳过)
6
+ * - status: 探测 /api/health + /api/status
7
+ * - stop: 杀掉由本扩展启动的 host(PID 文件校验,防误杀外部 launchd/systemd 实例)
8
+ *
9
+ * host 仍是独立常驻进程(不随 Pi 会话生灭);本扩展只做生命周期遥控。
10
+ * 无 npm 包的 host 时(pi install 场景),spawn 同包 dist/cli.js。
11
+ */
12
+ import { spawn } from "node:child_process";
13
+ import { readFile, writeFile, unlink } from "node:fs/promises";
14
+ import { join } from "node:path";
15
+ import { homedir } from "node:os";
16
+ import { fileURLToPath } from "node:url";
17
+ const PID_FILE = join(homedir(), ".pi", "maestro-mobile.pid");
18
+ const DEFAULT_PORT = 4739;
19
+ function hostPort() {
20
+ const raw = process.env.MAESTRO_MOBILE_PORT;
21
+ const n = raw ? Number(raw) : NaN;
22
+ return Number.isInteger(n) && n > 0 && n < 65536 ? n : DEFAULT_PORT;
23
+ }
24
+ /** 探测 host 是否在监听:/api/health 200 或 401 都算 alive(401 = 服务在,仅缺 token) */
25
+ function probeHealth(port, timeoutMs = 800) {
26
+ return new Promise((resolve) => {
27
+ fetch(`http://127.0.0.1:${port}/api/health`, { signal: AbortSignal.timeout(timeoutMs) })
28
+ .then((r) => resolve(r.status === 200 || r.status === 401))
29
+ .catch(() => resolve(false));
30
+ });
31
+ }
32
+ function hostStatus(port, timeoutMs = 800) {
33
+ return new Promise((resolve) => {
34
+ fetch(`http://127.0.0.1:${port}/api/status`, { signal: AbortSignal.timeout(timeoutMs) })
35
+ .then(async (r) => (r.ok ? (await r.json()) : null))
36
+ .then((d) => resolve(d ?? null))
37
+ .catch(() => resolve(null));
38
+ });
39
+ }
40
+ /** host cli.js 入口:同包 dist(pi install npm:pi-maestro-mobile 时随包分发) */
41
+ function hostCliPath() {
42
+ // dist/extension.js 与 dist/cli.js 同目录
43
+ return join(fileURLToPath(new URL(".", import.meta.url)), "cli.js");
44
+ }
45
+ async function readPid() {
46
+ try {
47
+ const raw = await readFile(PID_FILE, "utf8");
48
+ const pid = Number(raw.trim());
49
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
50
+ }
51
+ catch {
52
+ return null;
53
+ }
54
+ }
55
+ export default function maestroHostExtension(pi) {
56
+ pi.registerCommand("maestro-mobile", {
57
+ description: "Maestro Mobile Host 遥控(status / start / stop)",
58
+ handler: async (args, ctx) => {
59
+ const sub = (args ?? "").trim().split(/\s+/)[0] || "status";
60
+ const port = hostPort();
61
+ const alive = await probeHealth(port);
62
+ if (sub === "status") {
63
+ if (!alive) {
64
+ ctx.ui.notify(`maestro-mobile: 未运行(端口 ${port} 无响应)`, "info");
65
+ return;
66
+ }
67
+ const s = await hostStatus(port);
68
+ const ver = s
69
+ ? `${s.version} · pi ${s.piVersion ?? "?"} · flow ${s.flowVersion ?? "?"} · cli ${s.maestroCliVersion ?? "?"}`
70
+ : "运行中(未配 token,版本详情需在 host 侧设置 MAESTRO_MOBILE_TOKEN 后由手机 App 查看)";
71
+ ctx.ui.notify(`maestro-mobile: 运行中 :${port} — ${ver}`, "info");
72
+ return;
73
+ }
74
+ if (sub === "start") {
75
+ if (alive) {
76
+ ctx.ui.notify(`maestro-mobile: 已在运行(:${port}),跳过启动`, "info");
77
+ return;
78
+ }
79
+ const child = spawn(process.execPath, [hostCliPath(), "--port", String(port)], {
80
+ detached: true,
81
+ stdio: "ignore",
82
+ env: { ...process.env, MAESTRO_MOBILE_PORT: String(port) },
83
+ });
84
+ child.unref();
85
+ await writeFile(PID_FILE, String(child.pid ?? ""), "utf8");
86
+ // 等待端口就绪(最多 3s)
87
+ for (let i = 0; i < 10; i++) {
88
+ await new Promise((r) => setTimeout(r, 300));
89
+ if (await probeHealth(port)) {
90
+ ctx.ui.notify(`maestro-mobile: 已启动 :${port} pid=${child.pid}`, "info");
91
+ return;
92
+ }
93
+ }
94
+ ctx.ui.notify("maestro-mobile: 启动后 3s 内未见 health 通过,请查日志", "warning");
95
+ return;
96
+ }
97
+ if (sub === "stop") {
98
+ const pid = await readPid();
99
+ if (!pid) {
100
+ ctx.ui.notify("maestro-mobile: 无本扩展启动的实例(PID 文件不存在);launchd/systemd 管理的实例请用对应服务命令停", "warning");
101
+ return;
102
+ }
103
+ try {
104
+ process.kill(pid, "SIGTERM");
105
+ await unlink(PID_FILE).catch(() => { });
106
+ ctx.ui.notify(`maestro-mobile: 已发送 SIGTERM 到 pid=${pid}`, "info");
107
+ }
108
+ catch {
109
+ await unlink(PID_FILE).catch(() => { });
110
+ ctx.ui.notify(`maestro-mobile: pid=${pid} 已不存在,清理 PID 文件`, "info");
111
+ }
112
+ return;
113
+ }
114
+ ctx.ui.notify(`maestro-mobile: 未知子命令 "${sub}"(可用:status / start / stop)`, "warning");
115
+ },
116
+ });
117
+ }
118
+ //# sourceMappingURL=extension.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.js","sourceRoot":"","sources":["../src/extension.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,EAAY,MAAM,oBAAoB,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAGzC,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC;AAC9D,MAAM,YAAY,GAAG,IAAI,CAAC;AAE1B,SAAS,QAAQ;IACf,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,mBAAmB,CAAC;IAC5C,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;IAClC,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC;AACtE,CAAC;AAED,uEAAuE;AACvE,SAAS,WAAW,CAAC,IAAY,EAAE,SAAS,GAAG,GAAG;IAChD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,KAAK,CAAC,oBAAoB,IAAI,aAAa,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;aACrF,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC;aAC1D,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;IACjC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,SAAS,GAAG,GAAG;IAC/C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,KAAK,CAAC,oBAAoB,IAAI,aAAa,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;aACrF,IAAI,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAA4B,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aAC9E,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC;aAC/B,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,CAAC,CAAC,CAAC;AACL,CAAC;AAED,qEAAqE;AACrE,SAAS,WAAW;IAClB,sCAAsC;IACtC,OAAO,IAAI,CAAC,aAAa,CAAC,IAAI,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AACtE,CAAC;AAED,KAAK,UAAU,OAAO;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC7C,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC,CAAC;QAC/B,OAAO,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;IACvD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EAAgB;IAC3D,EAAE,CAAC,eAAe,CAAC,gBAAgB,EAAE;QACnC,WAAW,EAAE,+CAA+C;QAC5D,OAAO,EAAE,KAAK,EAAE,IAAY,EAAE,GAAG,EAAE,EAAE;YACnC,MAAM,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC;YAC5D,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAC;YACxB,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;YAEtC,IAAI,GAAG,KAAK,QAAQ,EAAE,CAAC;gBACrB,IAAI,CAAC,KAAK,EAAE,CAAC;oBACX,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,0BAA0B,IAAI,OAAO,EAAE,MAAM,CAAC,CAAC;oBAC7D,OAAO;gBACT,CAAC;gBACD,MAAM,CAAC,GAAG,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;gBACjC,MAAM,GAAG,GAAG,CAAC;oBACX,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,SAAS,CAAC,CAAC,SAAS,IAAI,GAAG,WAAW,CAAC,CAAC,WAAW,IAAI,GAAG,UAAU,CAAC,CAAC,iBAAiB,IAAI,GAAG,EAAE;oBAC9G,CAAC,CAAC,gEAAgE,CAAC;gBACrE,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,wBAAwB,IAAI,MAAM,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;gBAC/D,OAAO;YACT,CAAC;YAED,IAAI,GAAG,KAAK,OAAO,EAAE,CAAC;gBACpB,IAAI,KAAK,EAAE,CAAC;oBACV,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,yBAAyB,IAAI,QAAQ,EAAE,MAAM,CAAC,CAAC;oBAC7D,OAAO;gBACT,CAAC;gBACD,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE;oBAC7E,QAAQ,EAAE,IAAI;oBACd,KAAK,EAAE,QAAQ;oBACf,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE;iBAC3D,CAAC,CAAC;gBACH,KAAK,CAAC,KAAK,EAAE,CAAC;gBACd,MAAM,SAAS,CAAC,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,GAAG,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;gBAC3D,gBAAgB;gBAChB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC;oBAC5B,MAAM,IAAI,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;oBAC7C,IAAI,MAAM,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;wBAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,wBAAwB,IAAI,QAAQ,KAAK,CAAC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;wBACvE,OAAO;oBACT,CAAC;gBACH,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,2CAA2C,EAAE,SAAS,CAAC,CAAC;gBACtE,OAAO;YACT,CAAC;YAED,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;gBACnB,MAAM,GAAG,GAAG,MAAM,OAAO,EAAE,CAAC;gBAC5B,IAAI,CAAC,GAAG,EAAE,CAAC;oBACT,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,qEAAqE,EAAE,SAAS,CAAC,CAAC;oBAChG,OAAO;gBACT,CAAC;gBACD,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;oBAC7B,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBACvC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,qCAAqC,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;gBACpE,CAAC;gBAAC,MAAM,CAAC;oBACP,MAAM,MAAM,CAAC,QAAQ,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;oBACvC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,uBAAuB,GAAG,iBAAiB,EAAE,MAAM,CAAC,CAAC;gBACrE,CAAC;gBACD,OAAO;YACT,CAAC;YAED,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,0BAA0B,GAAG,6BAA6B,EAAE,SAAS,CAAC,CAAC;QACvF,CAAC;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,69 @@
1
+ import type { ExtensionUiResponse } from "./vendor/shared/index.js";
2
+ import type { RuntimeFactory, SessionRunner, OpenSessionRequest, HostEventListener } from "./types.js";
3
+ import { MaestroStateReader } from "./maestro-state.js";
4
+ import { type ComponentVersions } from "./version-detector.js";
5
+ /**
6
+ * HostController — 集中管理所有会话 + Maestro 状态 + 事件分发
7
+ *
8
+ * 职责:
9
+ * - 管理 SessionRunner 实例生命周期
10
+ * - 注册 HostEventListener 事件监听(给 WebSocket 广播用)
11
+ * - 调度 MaestroStateReader 定期读取
12
+ * - 处理 client commands
13
+ */
14
+ export declare class HostController {
15
+ private readonly runtimeFactory;
16
+ private readonly sessions;
17
+ private readonly eventLog;
18
+ private readonly listeners;
19
+ private readonly maestroReader;
20
+ private readonly liveSessions;
21
+ private readonly telemetryReader;
22
+ private telemetryCache;
23
+ private telemetryInFlight;
24
+ private readonly emitToListeners;
25
+ private maestroPollTimer;
26
+ private maestroDetected;
27
+ private readonly versionDetector;
28
+ private componentVersions;
29
+ private _startedAt;
30
+ constructor(runtimeFactory: RuntimeFactory, maestroReader?: MaestroStateReader);
31
+ get startedAt(): number;
32
+ get activeSessionIds(): string[];
33
+ /** 读取活跃会话列表(只读,不 claim owner) */
34
+ listLiveSessions(): Promise<import("./live-sessions.js").LiveSessionList>;
35
+ /** 读取 workspace telemetry(owner 状态,Monitor/Teammate 合同) */
36
+ readTelemetry(): Promise<import("./vendor/shared/protocol.js").WorkspaceTelemetryState>;
37
+ /** 轮询 telemetry,状态变化时推送 monitor_state 事件(single-flight + 稳定键变更检测) */
38
+ pollTelemetry(): Promise<void>;
39
+ /** 注册事件监听(WebSocket 层订阅) */
40
+ onEvent(listener: HostEventListener): () => void;
41
+ /** 获取状态信息 */
42
+ getStatus(): {
43
+ ok: boolean;
44
+ version: string;
45
+ maestroDetected: boolean;
46
+ sessions: number;
47
+ uptimeMs: number;
48
+ } & ComponentVersions;
49
+ /** 启动 Maestro 状态轮询 */
50
+ startMaestroPoll(intervalMs?: number): Promise<void>;
51
+ /** 立即读取一次 Maestro 状态(HTTP 路由用) */
52
+ readMaestroStateNow(): Promise<import("./vendor/shared/protocol.js").MaestroState>;
53
+ stopMaestroPoll(): void;
54
+ /** 列出所有会话(透传 runtimeFactory) */
55
+ listSessions(cwd?: string): Promise<unknown[]>;
56
+ /** 打开会话 */
57
+ openSession(request: OpenSessionRequest): Promise<SessionRunner>;
58
+ /** 获取会话 */
59
+ getSession(sessionId: string): SessionRunner | undefined;
60
+ /** 关闭会话 */
61
+ closeSession(sessionId: string): Promise<boolean>;
62
+ /** 处理 extension_ui_response */
63
+ respondToExtensionUi(sessionId: string, requestId: string, response: ExtensionUiResponse): boolean;
64
+ /** 刷新 Maestro 状态(仅状态变化时推送) */
65
+ private refreshMaestroState;
66
+ /** 释放所有资源(关闭时调用) */
67
+ dispose(): Promise<void>;
68
+ }
69
+ //# sourceMappingURL=host-controller.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"host-controller.d.ts","sourceRoot":"","sources":["../src/host-controller.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAA8B,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAChG,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEvG,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,EAAmB,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAGhF;;;;;;;;GAQG;AACH,qBAAa,cAAc;IAiBvB,OAAO,CAAC,QAAQ,CAAC,cAAc;IAhBjC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoC;IAC7D,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAkB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAgC;IAC1D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IACnD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAsB;IACnD,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA2B;IAC3D,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,iBAAiB,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAC7D,OAAO,CAAC,gBAAgB,CAA+C;IACvE,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAyB;IACzD,OAAO,CAAC,iBAAiB,CAAyB;IAClD,OAAO,CAAC,UAAU,CAAc;gBAGb,cAAc,EAAE,cAAc,EAC/C,aAAa,CAAC,EAAE,kBAAkB;IAYpC,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,IAAI,gBAAgB,IAAI,MAAM,EAAE,CAE/B;IAED,iCAAiC;IAC3B,gBAAgB;IAItB,2DAA2D;IACrD,aAAa;IAInB,qEAAqE;IAC/D,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBpC,4BAA4B;IAC5B,OAAO,CAAC,QAAQ,EAAE,iBAAiB,GAAG,MAAM,IAAI;IAKhD,aAAa;IACb,SAAS,IAAI;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,eAAe,EAAE,OAAO,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,GAAG,iBAAiB;IAY/H,sBAAsB;IAChB,gBAAgB,CAAC,UAAU,SAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAYxD,kCAAkC;IAC5B,mBAAmB;IAIzB,eAAe,IAAI,IAAI;IAOvB,gCAAgC;IAC1B,YAAY,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAIpD,WAAW;IACL,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,aAAa,CAAC;IAkBtE,WAAW;IACX,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAIxD,WAAW;IACL,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQvD,+BAA+B;IAC/B,oBAAoB,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,mBAAmB,GAAG,OAAO;IAMlG,8BAA8B;YAChB,mBAAmB;IAmBjC,oBAAoB;IACd,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;CAQ/B"}
@@ -0,0 +1,195 @@
1
+ import { SdkSessionRunner } from "./session-runner.js";
2
+ import { MaestroStateReader } from "./maestro-state.js";
3
+ import { LiveSessionsService } from "./live-sessions.js";
4
+ import { WorkspaceTelemetryReader } from "./workspace-telemetry.js";
5
+ import { projectMonitorState, telemetryStableKey, monitorStateEvent } from "./monitor-projection.js";
6
+ import { VersionDetector } from "./version-detector.js";
7
+ import { EventLog } from "./event-log.js";
8
+ /**
9
+ * HostController — 集中管理所有会话 + Maestro 状态 + 事件分发
10
+ *
11
+ * 职责:
12
+ * - 管理 SessionRunner 实例生命周期
13
+ * - 注册 HostEventListener 事件监听(给 WebSocket 广播用)
14
+ * - 调度 MaestroStateReader 定期读取
15
+ * - 处理 client commands
16
+ */
17
+ export class HostController {
18
+ runtimeFactory;
19
+ sessions = new Map();
20
+ eventLog = new EventLog();
21
+ listeners = new Set();
22
+ maestroReader;
23
+ liveSessions;
24
+ telemetryReader;
25
+ telemetryCache = null;
26
+ telemetryInFlight = false;
27
+ emitToListeners;
28
+ maestroPollTimer = null;
29
+ maestroDetected = false;
30
+ versionDetector = new VersionDetector();
31
+ componentVersions = {};
32
+ _startedAt = Date.now();
33
+ constructor(runtimeFactory, maestroReader) {
34
+ this.runtimeFactory = runtimeFactory;
35
+ this.maestroReader = maestroReader ?? new MaestroStateReader();
36
+ this.liveSessions = new LiveSessionsService();
37
+ this.telemetryReader = new WorkspaceTelemetryReader();
38
+ this.emitToListeners = (event) => {
39
+ for (const listener of this.listeners) {
40
+ try {
41
+ listener(event);
42
+ }
43
+ catch { /* ignore */ }
44
+ }
45
+ };
46
+ }
47
+ get startedAt() {
48
+ return this._startedAt;
49
+ }
50
+ get activeSessionIds() {
51
+ return [...this.sessions.keys()];
52
+ }
53
+ /** 读取活跃会话列表(只读,不 claim owner) */
54
+ async listLiveSessions() {
55
+ return this.liveSessions.list();
56
+ }
57
+ /** 读取 workspace telemetry(owner 状态,Monitor/Teammate 合同) */
58
+ async readTelemetry() {
59
+ return this.telemetryReader.read();
60
+ }
61
+ /** 轮询 telemetry,状态变化时推送 monitor_state 事件(single-flight + 稳定键变更检测) */
62
+ async pollTelemetry() {
63
+ if (this.telemetryInFlight)
64
+ return;
65
+ this.telemetryInFlight = true;
66
+ try {
67
+ const t = await this.telemetryReader.read();
68
+ const key = telemetryStableKey(t);
69
+ if (key === this.telemetryCache)
70
+ return;
71
+ this.telemetryCache = key;
72
+ this.emitToListeners(this.eventLog.record(monitorStateEvent(projectMonitorState(t))));
73
+ }
74
+ catch {
75
+ // 读取失败保留上次快照,不广播空窗口
76
+ }
77
+ finally {
78
+ this.telemetryInFlight = false;
79
+ }
80
+ }
81
+ /** 注册事件监听(WebSocket 层订阅) */
82
+ onEvent(listener) {
83
+ this.listeners.add(listener);
84
+ return () => this.listeners.delete(listener);
85
+ }
86
+ /** 获取状态信息 */
87
+ getStatus() {
88
+ const uptimeMs = Date.now() - this._startedAt;
89
+ return {
90
+ ok: true,
91
+ version: "0.1.0",
92
+ maestroDetected: this.maestroDetected,
93
+ ...this.componentVersions,
94
+ sessions: this.sessions.size,
95
+ uptimeMs,
96
+ };
97
+ }
98
+ /** 启动 Maestro 状态轮询 */
99
+ async startMaestroPoll(intervalMs = 5000) {
100
+ if (this.maestroPollTimer)
101
+ return; // 重复启动防护
102
+ // 启动时探测一次组件版本(内部带缓存,失败字段留空由 UI 显示待接入)
103
+ this.componentVersions = await this.versionDetector.detect();
104
+ await this.refreshMaestroState();
105
+ await this.pollTelemetry();
106
+ this.maestroPollTimer = setInterval(() => {
107
+ void this.refreshMaestroState();
108
+ void this.pollTelemetry();
109
+ }, intervalMs);
110
+ }
111
+ /** 立即读取一次 Maestro 状态(HTTP 路由用) */
112
+ async readMaestroStateNow() {
113
+ return this.maestroReader.readState();
114
+ }
115
+ stopMaestroPoll() {
116
+ if (this.maestroPollTimer) {
117
+ clearInterval(this.maestroPollTimer);
118
+ this.maestroPollTimer = null;
119
+ }
120
+ }
121
+ /** 列出所有会话(透传 runtimeFactory) */
122
+ async listSessions(cwd) {
123
+ return this.runtimeFactory.listSessions(cwd);
124
+ }
125
+ /** 打开会话 */
126
+ async openSession(request) {
127
+ const runner = await SdkSessionRunner.open(this.runtimeFactory, request, (event) => {
128
+ this.emitToListeners(this.eventLog.record(event));
129
+ });
130
+ // P2-4:同一会话(continue 同一 sessionFile 时 id 相同)重复 open 时,
131
+ // 先释放旧 runner(否则旧实例仍在订阅 SDK 事件并广播,且 runtime 常驻内存),再登记新实例。
132
+ const existing = this.sessions.get(runner.id);
133
+ if (existing && existing !== runner) {
134
+ await existing.dispose();
135
+ }
136
+ this.sessions.set(runner.id, runner);
137
+ this.emitToListeners(this.eventLog.record({
138
+ type: "host_status",
139
+ status: `session ${runner.id} opened`,
140
+ }));
141
+ return runner;
142
+ }
143
+ /** 获取会话 */
144
+ getSession(sessionId) {
145
+ return this.sessions.get(sessionId);
146
+ }
147
+ /** 关闭会话 */
148
+ async closeSession(sessionId) {
149
+ const runner = this.sessions.get(sessionId);
150
+ if (!runner)
151
+ return false;
152
+ this.sessions.delete(sessionId);
153
+ await runner.dispose();
154
+ return true;
155
+ }
156
+ /** 处理 extension_ui_response */
157
+ respondToExtensionUi(sessionId, requestId, response) {
158
+ const runner = this.sessions.get(sessionId);
159
+ if (!runner)
160
+ return false;
161
+ return runner.respondToExtensionUi(requestId, response);
162
+ }
163
+ /** 刷新 Maestro 状态(仅状态变化时推送) */
164
+ async refreshMaestroState() {
165
+ try {
166
+ // P3-1:接线 detectMaestro,使 getStatus().maestroDetected 反映真实检测结果
167
+ this.maestroDetected = await this.maestroReader.detectMaestro();
168
+ }
169
+ catch {
170
+ // ignore
171
+ }
172
+ try {
173
+ const state = await this.maestroReader.readStateChanged();
174
+ if (state === null)
175
+ return;
176
+ this.emitToListeners(this.eventLog.record({
177
+ type: "maestro_state",
178
+ state,
179
+ }));
180
+ }
181
+ catch {
182
+ // ignore
183
+ }
184
+ }
185
+ /** 释放所有资源(关闭时调用) */
186
+ async dispose() {
187
+ this.stopMaestroPoll();
188
+ for (const runner of this.sessions.values()) {
189
+ await runner.dispose();
190
+ }
191
+ this.sessions.clear();
192
+ this.listeners.clear();
193
+ }
194
+ }
195
+ //# sourceMappingURL=host-controller.js.map