@dev-anywhere/proxy 0.1.9 → 0.2.1

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 (39) hide show
  1. package/dist/{chunk-BMVYMCKF.js → chunk-3ZUZ22V6.js} +2 -2
  2. package/dist/{chunk-7XMJMVIL.js → chunk-4YQ2JUM7.js} +41 -6
  3. package/dist/chunk-4YQ2JUM7.js.map +1 -0
  4. package/dist/chunk-7UOPAMX7.js +220 -0
  5. package/dist/chunk-7UOPAMX7.js.map +1 -0
  6. package/dist/chunk-NBRBO5GS.js +1032 -0
  7. package/dist/chunk-NBRBO5GS.js.map +1 -0
  8. package/dist/chunk-NQDJ6QAM.js +18 -0
  9. package/dist/chunk-NQDJ6QAM.js.map +1 -0
  10. package/dist/chunk-OBYEKZWC.js +104 -0
  11. package/dist/chunk-OBYEKZWC.js.map +1 -0
  12. package/dist/chunk-PWG6K5QB.js +204 -0
  13. package/dist/chunk-PWG6K5QB.js.map +1 -0
  14. package/dist/{chunk-DCDXAM76.js → chunk-RIQ6OL7X.js} +9 -6
  15. package/dist/chunk-RIQ6OL7X.js.map +1 -0
  16. package/dist/{chunk-6O6JTF24.js → chunk-WUBRUO3G.js} +1 -1
  17. package/dist/index.js +5 -5
  18. package/dist/{relay-token-Z4JZFPQ5.js → relay-token-RKAVVQHE.js} +5 -4
  19. package/dist/{relay-token-Z4JZFPQ5.js.map → relay-token-RKAVVQHE.js.map} +1 -1
  20. package/dist/serve.js +538 -431
  21. package/dist/serve.js.map +1 -1
  22. package/dist/session-worker.js +99 -32
  23. package/dist/session-worker.js.map +1 -1
  24. package/dist/{terminal-FJAIRC73.js → terminal-YO2D2OJU.js} +194 -151
  25. package/dist/terminal-YO2D2OJU.js.map +1 -0
  26. package/package.json +3 -3
  27. package/dist/chunk-2JUB4LDU.js +0 -84
  28. package/dist/chunk-2JUB4LDU.js.map +0 -1
  29. package/dist/chunk-7XMJMVIL.js.map +0 -1
  30. package/dist/chunk-DCDXAM76.js.map +0 -1
  31. package/dist/chunk-ORZTFYXR.js +0 -123
  32. package/dist/chunk-ORZTFYXR.js.map +0 -1
  33. package/dist/chunk-QFYI6AMN.js +0 -870
  34. package/dist/chunk-QFYI6AMN.js.map +0 -1
  35. package/dist/chunk-U5T7ZYXT.js +0 -346
  36. package/dist/chunk-U5T7ZYXT.js.map +0 -1
  37. package/dist/terminal-FJAIRC73.js.map +0 -1
  38. /package/dist/{chunk-BMVYMCKF.js.map → chunk-3ZUZ22V6.js.map} +0 -0
  39. /package/dist/{chunk-6O6JTF24.js.map → chunk-WUBRUO3G.js.map} +0 -0
@@ -1,41 +1,38 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  daemonRelayArgs
4
- } from "./chunk-BMVYMCKF.js";
4
+ } from "./chunk-3ZUZ22V6.js";
5
5
  import {
6
- createFSM,
6
+ decidePtySemanticTransition,
7
7
  extractOscSequences,
8
- extractOscSignals,
9
- shouldReleaseApprovalWait,
10
- stateAfterApprovalRelease
11
- } from "./chunk-ORZTFYXR.js";
8
+ extractOscSignals
9
+ } from "./chunk-OBYEKZWC.js";
12
10
  import {
13
11
  spawnScript
14
12
  } from "./chunk-ZUWAB67J.js";
15
13
  import {
16
14
  CLAUDE_PROVIDER,
17
15
  CODEX_PROVIDER
18
- } from "./chunk-6O6JTF24.js";
16
+ } from "./chunk-WUBRUO3G.js";
19
17
  import {
18
+ createFSM,
20
19
  createIpcReader,
21
20
  encodeBinaryIpcFrame,
22
21
  serializeIpc
23
- } from "./chunk-U5T7ZYXT.js";
22
+ } from "./chunk-NBRBO5GS.js";
24
23
  import {
25
24
  terminalLogger
26
- } from "./chunk-2JUB4LDU.js";
25
+ } from "./chunk-7UOPAMX7.js";
27
26
  import {
28
27
  PROFILE_NAME,
29
28
  SERVICE_LOG_PATH,
30
29
  SOCK_PATH,
31
30
  STOPPED_PATH,
32
31
  tildify
33
- } from "./chunk-QFYI6AMN.js";
32
+ } from "./chunk-PWG6K5QB.js";
34
33
 
35
34
  // src/terminal.ts
36
- import { connect } from "net";
37
- import { existsSync, unlinkSync } from "fs";
38
- import { setTimeout as sleep } from "timers/promises";
35
+ import { setTimeout as sleep2 } from "timers/promises";
39
36
 
40
37
  // src/terminal/tty.ts
41
38
  function readTtySize(stream) {
@@ -77,6 +74,7 @@ function restoreHostTerminalModes(stream) {
77
74
  import * as pty from "node-pty";
78
75
  var PtyManager = class {
79
76
  child = null;
77
+ resizeTimer = null;
80
78
  provider;
81
79
  providerArgs;
82
80
  cwd;
@@ -120,22 +118,18 @@ var PtyManager = class {
120
118
  child.write(data.toString());
121
119
  });
122
120
  child.onData((data) => this.handleData(data));
123
- let resizeTimer = null;
124
121
  this.stdout.on("resize", () => {
125
- if (resizeTimer) clearTimeout(resizeTimer);
126
- resizeTimer = setTimeout(() => {
122
+ if (this.resizeTimer) clearTimeout(this.resizeTimer);
123
+ this.resizeTimer = setTimeout(() => {
124
+ this.resizeTimer = null;
125
+ if (!this.child) return;
127
126
  const { cols: newCols, rows: newRows } = readTtySize(this.stdout);
128
- child.resize(newCols, newRows);
127
+ this.child.resize(newCols, newRows);
129
128
  this.onResize?.(newCols, newRows);
130
129
  }, 50);
131
130
  });
132
131
  child.onExit(({ exitCode, signal }) => {
133
- if (isInteractive) {
134
- try {
135
- this.stdin.setRawMode(false);
136
- } catch {
137
- }
138
- }
132
+ if (isInteractive) this.disableRawModeQuiet();
139
133
  restoreHostTerminalModes(this.stdout);
140
134
  const code = signal ? 128 + signal : exitCode;
141
135
  this.onSessionExit?.(code);
@@ -160,19 +154,26 @@ var PtyManager = class {
160
154
  write(data) {
161
155
  this.child?.write(data);
162
156
  }
157
+ // 关闭 raw mode 并吞掉 stdin 已关闭场景下的异常。退出 / cleanup 两条路径共用。
158
+ disableRawModeQuiet() {
159
+ try {
160
+ this.stdin.setRawMode(false);
161
+ } catch {
162
+ }
163
+ }
163
164
  cleanup(exitCode) {
164
- if (this.stdin.isTTY) {
165
- try {
166
- this.stdin.setRawMode(false);
167
- } catch {
168
- }
165
+ if (this.resizeTimer) {
166
+ clearTimeout(this.resizeTimer);
167
+ this.resizeTimer = null;
169
168
  }
169
+ if (this.stdin.isTTY) this.disableRawModeQuiet();
170
170
  restoreHostTerminalModes(this.stdout);
171
171
  if (this.child) {
172
172
  try {
173
173
  this.child.kill();
174
174
  } catch {
175
175
  }
176
+ this.child = null;
176
177
  }
177
178
  this.onSessionExit?.(exitCode);
178
179
  }
@@ -193,60 +194,14 @@ function resolveTerminalCwd(env = process.env) {
193
194
  return candidates.find(isDirectory) ?? process.cwd();
194
195
  }
195
196
 
196
- // src/terminal.ts
197
- import pkg from "@xterm/headless";
198
- import { SerializeAddon } from "@xterm/addon-serialize";
199
- import { UnicodeGraphemesAddon } from "@xterm/addon-unicode-graphemes";
200
-
201
- // src/terminal/state.ts
202
- var TerminalState = {
203
- INIT: "init",
204
- CONNECTING_SERVICE: "connecting_service",
205
- CREATING_SESSION: "creating_session",
206
- RUNNING: "running",
207
- RECONNECTING: "reconnecting",
208
- EXITED: "exited"
209
- };
210
- var TERMINAL_TRANSITIONS = {
211
- init: ["connecting_service"],
212
- connecting_service: ["creating_session", "exited"],
213
- creating_session: ["running", "reconnecting", "exited"],
214
- running: ["reconnecting", "exited"],
215
- reconnecting: ["creating_session", "running", "exited"],
216
- exited: []
217
- };
218
- function createExitHandler(deps) {
219
- const exit = deps.exit ?? ((code) => process.exit(code));
220
- return (code) => {
221
- if (deps.fsm.is(TerminalState.EXITED)) return;
222
- deps.fsm.transitionTo(TerminalState.EXITED);
223
- const timer = deps.getIdleCheckTimer();
224
- if (timer) clearInterval(timer);
225
- const socket = deps.getSocket();
226
- const sessionId = deps.getSessionId();
227
- if (socket.writable && sessionId) {
228
- socket.end(serializeIpc({ type: "pty_deregister", sessionId }), () => exit(code));
229
- } else {
230
- exit(code);
231
- }
232
- };
233
- }
234
-
235
- // src/terminal.ts
236
- var { Terminal: HeadlessTerminal } = pkg;
197
+ // src/terminal/serve-bootstrap.ts
198
+ import { connect } from "net";
199
+ import { existsSync, unlinkSync } from "fs";
200
+ import { setTimeout as sleep } from "timers/promises";
237
201
  var ENSURE_SERVICE_MAX_RETRIES = 20;
238
202
  var ENSURE_SERVICE_INITIAL_DELAY_MS = 100;
239
203
  var ENSURE_SERVICE_MAX_DELAY_MS = 2e3;
240
204
  var WAIT_FOR_MESSAGE_TIMEOUT_MS = 1e4;
241
- var IDLE_CHECK_INTERVAL_MS = 3e3;
242
- var IDLE_THRESHOLD_MS = 3e3;
243
- var RECONNECT_INITIAL_DELAY_MS = 1e3;
244
- var RECONNECT_MAX_DELAY_MS = 5e3;
245
- var SPAWN_FAILURE_THRESHOLD = 3;
246
- var PROVIDERS = {
247
- claude: CLAUDE_PROVIDER,
248
- codex: CODEX_PROVIDER
249
- };
250
205
  function tryConnect(sockPath) {
251
206
  return new Promise((resolve) => {
252
207
  const s = connect(sockPath);
@@ -264,7 +219,7 @@ async function ensureService(autoStart = true) {
264
219
  if (existsSync(STOPPED_PATH)) unlinkSync(STOPPED_PATH);
265
220
  terminalLogger.info("Auto-starting serve daemon");
266
221
  const child = spawnScript(
267
- new URL("./serve", import.meta.url),
222
+ new URL("../serve", import.meta.url),
268
223
  ["--profile", PROFILE_NAME, ...daemonRelayArgs()],
269
224
  {
270
225
  env: { ...process.env },
@@ -324,6 +279,89 @@ function waitForMessage(socket, messageType) {
324
279
  }, WAIT_FOR_MESSAGE_TIMEOUT_MS);
325
280
  });
326
281
  }
282
+
283
+ // src/terminal/idle-checker.ts
284
+ function createIdleChecker(options) {
285
+ let timer = null;
286
+ const tick = () => {
287
+ const last = options.getLastOutputTime();
288
+ if (last <= 0) return;
289
+ if (Date.now() - last <= options.thresholdMs) return;
290
+ options.setLastOutputTime(0);
291
+ if (options.getCurrentState() !== "working") return;
292
+ options.onIdle();
293
+ };
294
+ return {
295
+ start() {
296
+ if (timer) clearInterval(timer);
297
+ timer = setInterval(tick, options.intervalMs);
298
+ },
299
+ stop() {
300
+ if (timer) {
301
+ clearInterval(timer);
302
+ timer = null;
303
+ }
304
+ }
305
+ };
306
+ }
307
+
308
+ // src/terminal/serve-socket-swap.ts
309
+ function swapServeSocket(prev, next) {
310
+ prev.removeAllListeners();
311
+ return next;
312
+ }
313
+
314
+ // src/terminal.ts
315
+ import pkg from "@xterm/headless";
316
+ import { SerializeAddon } from "@xterm/addon-serialize";
317
+ import { UnicodeGraphemesAddon } from "@xterm/addon-unicode-graphemes";
318
+
319
+ // src/terminal/state.ts
320
+ var TerminalState = {
321
+ INIT: "init",
322
+ CONNECTING_SERVICE: "connecting_service",
323
+ CREATING_SESSION: "creating_session",
324
+ RUNNING: "running",
325
+ RECONNECTING: "reconnecting",
326
+ EXITED: "exited"
327
+ };
328
+ var TERMINAL_TRANSITIONS = {
329
+ init: ["connecting_service"],
330
+ connecting_service: ["creating_session", "exited"],
331
+ creating_session: ["running", "reconnecting", "exited"],
332
+ running: ["reconnecting", "exited"],
333
+ reconnecting: ["creating_session", "running", "exited"],
334
+ exited: []
335
+ };
336
+ function createExitHandler(deps) {
337
+ const exit = deps.exit ?? ((code) => process.exit(code));
338
+ return (code) => {
339
+ if (deps.fsm.is(TerminalState.EXITED)) return;
340
+ deps.fsm.transitionTo(TerminalState.EXITED);
341
+ deps.stopIdleChecker();
342
+ deps.disposeRenderResources?.();
343
+ const socket = deps.getSocket();
344
+ const sessionId = deps.getSessionId();
345
+ if (socket.writable && sessionId) {
346
+ socket.end(serializeIpc({ type: "pty_deregister", sessionId }), () => exit(code));
347
+ } else {
348
+ exit(code);
349
+ }
350
+ };
351
+ }
352
+
353
+ // src/terminal.ts
354
+ import { existsSync as existsSync2 } from "fs";
355
+ var { Terminal: HeadlessTerminal } = pkg;
356
+ var IDLE_CHECK_INTERVAL_MS = 3e3;
357
+ var IDLE_THRESHOLD_MS = 3e3;
358
+ var RECONNECT_INITIAL_DELAY_MS = 1e3;
359
+ var RECONNECT_MAX_DELAY_MS = 5e3;
360
+ var SPAWN_FAILURE_THRESHOLD = 3;
361
+ var PROVIDERS = {
362
+ claude: CLAUDE_PROVIDER,
363
+ codex: CODEX_PROVIDER
364
+ };
327
365
  var TerminalSession = class {
328
366
  constructor(provider, providerArgs) {
329
367
  this.provider = provider;
@@ -343,14 +381,15 @@ var TerminalSession = class {
343
381
  hookContext = null;
344
382
  ptyManager = null;
345
383
  lastOutputTime = 0;
346
- idleCheckTimer = null;
384
+ idleChecker = null;
347
385
  currentPtyState = "turn_complete";
348
386
  // headless terminal 在本进程维护,用于按需 serialize() 给远程 client
349
387
  headlessTerminal = null;
350
388
  serializeAddon = null;
351
389
  outputSeq = 0;
352
390
  remoteDetached = false;
353
- // 记录上次 bridge 状态避免重连抖动导致 banner 连刷;初值 null 让首次状态(无论真假)都打,启动时提示 remote viewing 是否就绪
391
+ // 记录上次 bridge 连接状态,避免重连抖动重复打印 banner
392
+ // 初值 null 确保首次状态变更(无论 true/false)都触发一次输出
354
393
  lastBridgeConnected = null;
355
394
  // 收尾函数在 run() 里创建一次,PTY 退出与 SIGTERM 共用;内部通过 fsm EXITED 检查短路
356
395
  cleanupAndExit;
@@ -364,7 +403,12 @@ var TerminalSession = class {
364
403
  fsm: this.fsm,
365
404
  getSocket: () => this.socket,
366
405
  getSessionId: () => this.sessionId,
367
- getIdleCheckTimer: () => this.idleCheckTimer
406
+ stopIdleChecker: () => this.idleChecker?.stop(),
407
+ disposeRenderResources: () => {
408
+ this.headlessTerminal?.dispose();
409
+ this.headlessTerminal = null;
410
+ this.serializeAddon = null;
411
+ }
368
412
  });
369
413
  this.setupSocketHandlers();
370
414
  this.startPtyManager();
@@ -470,32 +514,13 @@ var TerminalSession = class {
470
514
  if (signal?.title) {
471
515
  this.sendTerminalTitle(signal.title);
472
516
  }
473
- if (signal?.state === "approval_wait") {
474
- this.currentPtyState = "approval_wait";
475
- this.sendPtyState("approval_wait", { title: signal?.title, tool: signal?.tool });
476
- return;
477
- }
478
- if (shouldReleaseApprovalWait({
517
+ const decision = decidePtySemanticTransition({
479
518
  currentState: this.currentPtyState,
480
- signalState: signal?.state
481
- })) {
482
- const nextState = stateAfterApprovalRelease(signal?.state);
483
- this.currentPtyState = nextState;
484
- this.sendPtyState(nextState, { title: signal?.title, tool: signal?.tool });
485
- return;
486
- }
487
- if (this.currentPtyState === "approval_wait" && signal?.state !== "turn_complete") {
488
- this.sendPtyState("approval_wait", { title: signal?.title, tool: signal?.tool });
489
- return;
490
- }
491
- if (signal && signal.state !== "working") {
492
- this.currentPtyState = signal.state;
493
- this.sendPtyState(signal.state, { title: signal.title, tool: signal.tool });
494
- return;
495
- }
496
- if (this.currentPtyState !== "working") {
497
- this.currentPtyState = "working";
498
- this.sendPtyState("working");
519
+ signal: signal ?? null
520
+ });
521
+ this.currentPtyState = decision.nextState;
522
+ if (decision.emit) {
523
+ this.sendPtyState(decision.nextState, decision.meta);
499
524
  }
500
525
  }
501
526
  sendTerminalTitle(title) {
@@ -536,40 +561,53 @@ var TerminalSession = class {
536
561
  notifyUser(connected ? "relay online" : "relay offline \u2014 remote viewing unavailable");
537
562
  }
538
563
  setupSocketHandlers() {
539
- createIpcReader(this.socket, (msg) => {
540
- if (msg.type === "pty_input" && msg.sessionId === this.sessionId) {
541
- terminalLogger.debug({ sessionId: this.sessionId, bytes: msg.data.length }, "Remote input received");
542
- this.ptyManager?.write(msg.data);
543
- } else if (msg.type === "pty_detach" && msg.sessionId === this.sessionId) {
544
- this.detachRemoteView();
545
- } else if (msg.type === "bridge_status") {
546
- this.handleBridgeStatus(msg.connected);
547
- } else if (msg.type === "pty_subscribe" && msg.sessionId === this.sessionId) {
548
- if (this.serializeAddon && this.headlessTerminal) {
549
- const data = this.serializeAddon.serialize();
550
- this.socket.write(
551
- serializeIpc({
552
- type: "pty_snapshot",
553
- sessionId: msg.sessionId,
554
- cols: this.headlessTerminal.cols,
555
- rows: this.headlessTerminal.rows,
556
- data,
557
- outputSeq: this.outputSeq,
558
- requestId: msg.requestId
559
- })
560
- );
561
- terminalLogger.info(
562
- {
563
- sessionId: this.sessionId,
564
- cols: this.headlessTerminal.cols,
565
- rows: this.headlessTerminal.rows,
566
- bytes: data.length
567
- },
568
- "Snapshot sent via IPC"
564
+ createIpcReader(
565
+ this.socket,
566
+ (msg) => {
567
+ if (msg.type === "pty_input" && msg.sessionId === this.sessionId) {
568
+ terminalLogger.debug(
569
+ { sessionId: this.sessionId, bytes: msg.data.length },
570
+ "Remote input received"
569
571
  );
572
+ this.ptyManager?.write(msg.data);
573
+ } else if (msg.type === "pty_detach" && msg.sessionId === this.sessionId) {
574
+ this.detachRemoteView();
575
+ } else if (msg.type === "bridge_status") {
576
+ this.handleBridgeStatus(msg.connected);
577
+ } else if (msg.type === "pty_subscribe" && msg.sessionId === this.sessionId) {
578
+ if (this.serializeAddon && this.headlessTerminal) {
579
+ const data = this.serializeAddon.serialize();
580
+ this.socket.write(
581
+ serializeIpc({
582
+ type: "pty_snapshot",
583
+ sessionId: msg.sessionId,
584
+ cols: this.headlessTerminal.cols,
585
+ rows: this.headlessTerminal.rows,
586
+ data,
587
+ outputSeq: this.outputSeq,
588
+ requestId: msg.requestId
589
+ })
590
+ );
591
+ terminalLogger.info(
592
+ {
593
+ sessionId: this.sessionId,
594
+ cols: this.headlessTerminal.cols,
595
+ rows: this.headlessTerminal.rows,
596
+ bytes: data.length
597
+ },
598
+ "Snapshot sent via IPC"
599
+ );
600
+ }
570
601
  }
602
+ },
603
+ void 0,
604
+ (err, line) => {
605
+ terminalLogger.warn(
606
+ { err: err.message, lineLen: line.length },
607
+ "Serve IPC message dropped (parse/schema error)"
608
+ );
571
609
  }
572
- });
610
+ );
573
611
  this.socket.on("close", () => {
574
612
  terminalLogger.info("Serve socket closed");
575
613
  if (this.remoteDetached) {
@@ -587,24 +625,29 @@ var TerminalSession = class {
587
625
  }
588
626
  // 超过 IDLE_THRESHOLD_MS 无 PTY 输出则从 working 翻回 turn_complete
589
627
  setupIdleCheck() {
590
- if (this.idleCheckTimer) clearInterval(this.idleCheckTimer);
591
- this.idleCheckTimer = setInterval(() => {
592
- if (this.lastOutputTime > 0 && Date.now() - this.lastOutputTime > IDLE_THRESHOLD_MS) {
593
- this.lastOutputTime = 0;
594
- if (this.currentPtyState === "working") {
595
- this.currentPtyState = "turn_complete";
596
- this.sendPtyState("turn_complete");
597
- }
628
+ this.idleChecker?.stop();
629
+ this.idleChecker = createIdleChecker({
630
+ intervalMs: IDLE_CHECK_INTERVAL_MS,
631
+ thresholdMs: IDLE_THRESHOLD_MS,
632
+ getLastOutputTime: () => this.lastOutputTime,
633
+ setLastOutputTime: (value) => {
634
+ this.lastOutputTime = value;
635
+ },
636
+ getCurrentState: () => this.currentPtyState,
637
+ onIdle: () => {
638
+ this.currentPtyState = "turn_complete";
639
+ this.sendPtyState("turn_complete");
598
640
  }
599
- }, IDLE_CHECK_INTERVAL_MS);
641
+ });
642
+ this.idleChecker.start();
600
643
  }
601
644
  async reconnectToServe() {
602
645
  terminalLogger.info("Serve connection lost, starting reconnection");
603
646
  let consecutiveSpawnFailures = 0;
604
647
  for (let i = 0; ; i++) {
605
648
  if (this.remoteDetached) return;
606
- await sleep(Math.min(RECONNECT_INITIAL_DELAY_MS * (i + 1), RECONNECT_MAX_DELAY_MS));
607
- const stopped = existsSync(STOPPED_PATH);
649
+ await sleep2(Math.min(RECONNECT_INITIAL_DELAY_MS * (i + 1), RECONNECT_MAX_DELAY_MS));
650
+ const stopped = existsSync2(STOPPED_PATH);
608
651
  const degraded = consecutiveSpawnFailures >= SPAWN_FAILURE_THRESHOLD;
609
652
  const passive = stopped || degraded;
610
653
  try {
@@ -613,7 +656,7 @@ var TerminalSession = class {
613
656
  if (!newSocket) continue;
614
657
  if (degraded) notifyUser("serve daemon reachable, reconnected");
615
658
  consecutiveSpawnFailures = 0;
616
- this.socket = newSocket;
659
+ this.socket = swapServeSocket(this.socket, newSocket);
617
660
  terminalLogger.info({ attempt: i + 1, sessionId: this.sessionId }, "Reconnected to serve");
618
661
  this.setupSocketHandlers();
619
662
  if (this.sessionId) {
@@ -680,4 +723,4 @@ async function startTerminal(providerArgs, providerId = providerFromEnv()) {
680
723
  export {
681
724
  startTerminal
682
725
  };
683
- //# sourceMappingURL=terminal-FJAIRC73.js.map
726
+ //# sourceMappingURL=terminal-YO2D2OJU.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/terminal.ts","../src/terminal/tty.ts","../src/terminal/pty-manager.ts","../src/terminal/cwd.ts","../src/terminal/serve-bootstrap.ts","../src/terminal/idle-checker.ts","../src/terminal/serve-socket-swap.ts","../src/terminal/state.ts"],"sourcesContent":["import type { Socket } from \"node:net\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { readTtySize, notifyUser } from \"./terminal/tty.js\";\nimport { PtyManager } from \"./terminal/pty-manager.js\";\nimport { resolveTerminalCwd } from \"./terminal/cwd.js\";\nimport { ensureService, tryConnect, waitForMessage } from \"./terminal/serve-bootstrap.js\";\nimport { createIdleChecker, type IdleChecker } from \"./terminal/idle-checker.js\";\nimport { swapServeSocket } from \"./terminal/serve-socket-swap.js\";\nimport pkg from \"@xterm/headless\";\nconst { Terminal: HeadlessTerminal } = pkg;\nimport { SerializeAddon } from \"@xterm/addon-serialize\";\nimport { UnicodeGraphemesAddon } from \"@xterm/addon-unicode-graphemes\";\nimport { extractOscSequences, extractOscSignals } from \"./common/osc-extractor.js\";\nimport { createFSM, type PtySemanticState } from \"@dev-anywhere/shared\";\nimport { decidePtySemanticTransition } from \"./common/pty-semantic-machine.js\";\nimport { TerminalState, TERMINAL_TRANSITIONS, createExitHandler } from \"./terminal/state.js\";\nimport { existsSync } from \"node:fs\";\nimport { SOCK_PATH, STOPPED_PATH, tildify } from \"./common/paths.js\";\nimport {\n createIpcReader,\n serializeIpc,\n encodeBinaryIpcFrame,\n type IpcMessage,\n} from \"./ipc/ipc-protocol.js\";\nimport { terminalLogger as log } from \"./common/logger.js\";\nimport {\n CLAUDE_PROVIDER,\n CODEX_PROVIDER,\n type ProviderAdapter,\n type ProviderHookContext,\n type ProviderId,\n} from \"./providers/index.js\";\n\n// idle 检测:超过 IDLE_THRESHOLD_MS 无输出则翻转 working -> turn_complete\nconst IDLE_CHECK_INTERVAL_MS = 3_000;\nconst IDLE_THRESHOLD_MS = 3_000;\n\n// serve 连接断开后的重连重试参数\nconst RECONNECT_INITIAL_DELAY_MS = 1_000;\nconst RECONNECT_MAX_DELAY_MS = 5_000;\n// 连续 spawn 失败到达阈值后停止自动 spawn,降为被动 tryConnect 轮询。\n// 作用:环境异常(端口占用、依赖缺失、权限不足)时避免反复拉起短命子进程把日志刷爆。\nconst SPAWN_FAILURE_THRESHOLD = 3;\n\nconst PROVIDERS: Record<ProviderId, ProviderAdapter> = {\n claude: CLAUDE_PROVIDER,\n codex: CODEX_PROVIDER,\n};\n\nclass TerminalSession {\n private readonly fsm = createFSM<TerminalState>({\n initial: TerminalState.INIT,\n transitions: TERMINAL_TRANSITIONS,\n onTransition: (from, to) => log.info({ from, to }, \"Terminal state transition\"),\n });\n private readonly sessionCwd = resolveTerminalCwd();\n // socket 在 run() 中连上 serve 后首次赋值;reconnect 会重新赋值为新实例\n private socket!: Socket;\n private sessionId: string | null = null;\n private hookContext: ProviderHookContext | null = null;\n private ptyManager: PtyManager | null = null;\n private lastOutputTime = 0;\n private idleChecker: IdleChecker | null = null;\n private currentPtyState: PtySemanticState = \"turn_complete\";\n // headless terminal 在本进程维护,用于按需 serialize() 给远程 client\n private headlessTerminal: InstanceType<typeof HeadlessTerminal> | null = null;\n private serializeAddon: SerializeAddon | null = null;\n private outputSeq = 0;\n private remoteDetached = false;\n // 记录上次 bridge 连接状态,避免重连抖动重复打印 banner;\n // 初值 null 确保首次状态变更(无论 true/false)都触发一次输出\n private lastBridgeConnected: boolean | null = null;\n // 收尾函数在 run() 里创建一次,PTY 退出与 SIGTERM 共用;内部通过 fsm EXITED 检查短路\n private cleanupAndExit!: (code: number) => void;\n\n constructor(\n private readonly provider: ProviderAdapter,\n private readonly providerArgs: string[],\n ) {}\n\n async run(): Promise<void> {\n log.info(\"Terminal starting\");\n this.fsm.transitionTo(TerminalState.CONNECTING_SERVICE);\n this.socket = await ensureService();\n\n await this.createSession();\n this.initHeadlessTerminal();\n this.cleanupAndExit = createExitHandler({\n fsm: this.fsm,\n getSocket: () => this.socket,\n getSessionId: () => this.sessionId,\n stopIdleChecker: () => this.idleChecker?.stop(),\n disposeRenderResources: () => {\n this.headlessTerminal?.dispose();\n this.headlessTerminal = null;\n this.serializeAddon = null;\n },\n });\n\n this.setupSocketHandlers();\n this.startPtyManager();\n\n this.socket.write(\n serializeIpc({ type: \"pty_register\", sessionId: this.sessionId!, pid: process.pid }),\n );\n this.replayCurrentPtyState();\n this.fsm.transitionTo(TerminalState.RUNNING);\n this.setupIdleCheck();\n\n process.on(\"SIGTERM\", () => {\n log.info({ sessionId: this.sessionId }, \"SIGTERM received, shutting down\");\n this.cleanupAndExit(143);\n });\n }\n\n private async createSession(): Promise<void> {\n this.fsm.transitionTo(TerminalState.CREATING_SESSION);\n const responsePromise = waitForMessage(this.socket, \"session_create_response\");\n this.socket.write(\n serializeIpc({\n type: \"session_create_request\",\n mode: \"pty\",\n provider: this.provider.id,\n cwd: this.sessionCwd,\n name: tildify(this.sessionCwd),\n pid: process.pid,\n }),\n );\n const response = await responsePromise;\n if (response.error) {\n throw new Error(`Failed to create session: ${response.error}`);\n }\n this.sessionId = response.sessionId;\n this.hookContext = response.hook ?? null;\n }\n\n private initHeadlessTerminal(): void {\n const { cols, rows } = readTtySize(process.stdout);\n log.info(\n { sessionId: this.sessionId, cols, rows },\n \"Session created, initializing headless terminal\",\n );\n this.headlessTerminal = new HeadlessTerminal({\n cols,\n rows,\n scrollback: 5000,\n allowProposedApi: true,\n });\n this.serializeAddon = new SerializeAddon();\n // UnicodeGraphemesAddon activate() 里会设置 activeVersion = '15-graphemes'\n this.headlessTerminal.loadAddon(this.serializeAddon);\n this.headlessTerminal.loadAddon(new UnicodeGraphemesAddon());\n }\n\n private startPtyManager(): void {\n this.ptyManager = new PtyManager({\n provider: this.provider,\n providerArgs: this.providerArgs,\n cwd: this.sessionCwd,\n hook: this.hookContext ?? undefined,\n tap: (data) => this.handlePtyData(data),\n stdin: process.stdin,\n stdout: process.stdout,\n onResize: (newCols, newRows) => {\n if (this.headlessTerminal) this.headlessTerminal.resize(newCols, newRows);\n if (this.socket.writable && this.sessionId) {\n this.socket.write(\n serializeIpc({\n type: \"pty_resize\",\n sessionId: this.sessionId,\n cols: newCols,\n rows: newRows,\n }),\n );\n }\n },\n onSessionExit: (code: number) => {\n log.info({ sessionId: this.sessionId, exitCode: code }, \"PTY exited, cleaning up\");\n this.cleanupAndExit(code);\n },\n });\n this.ptyManager.start();\n log.info({ sessionId: this.sessionId }, \"PTY started with headless terminal\");\n }\n\n // PTY 的每一帧输出都要:追到 headless terminal 状态、推 binary IPC、提取 provider 语义事件\n private handlePtyData(data: string): void {\n this.lastOutputTime = Date.now();\n this.outputSeq += 1;\n\n if (this.headlessTerminal) this.headlessTerminal.write(data);\n\n if (!this.remoteDetached && this.socket.writable && this.sessionId) {\n this.socket.write(\n encodeBinaryIpcFrame(this.sessionId, Buffer.from(data, \"utf-8\"), this.outputSeq),\n );\n }\n\n const oscSequences = extractOscSequences(data);\n const signal = extractOscSignals(data, this.provider.id);\n if (oscSequences.length > 0) {\n log.debug(\n {\n sessionId: this.sessionId,\n oscSequences,\n signal,\n },\n \"PTY OSC sequences parsed\",\n );\n }\n if (signal?.title) {\n this.sendTerminalTitle(signal.title);\n }\n\n // 语义状态机决策(六条规则)抽到 common/pty-semantic-machine:terminal 进程仅 emit 事件,\n // session FSM 副作用由 serve 端在收到 pty_state IPC 后驱动。\n const decision = decidePtySemanticTransition({\n currentState: this.currentPtyState,\n signal: signal ?? null,\n });\n this.currentPtyState = decision.nextState;\n if (decision.emit) {\n this.sendPtyState(decision.nextState, decision.meta);\n }\n }\n\n private sendTerminalTitle(title: string): void {\n if (this.remoteDetached || !this.socket.writable || !this.sessionId) return;\n this.socket.write(\n serializeIpc({\n type: \"pty_title_change\",\n sessionId: this.sessionId,\n title,\n }),\n );\n }\n\n private sendPtyState(state: PtySemanticState, meta?: { title?: string; tool?: string }): void {\n if (this.remoteDetached || !this.socket.writable || !this.sessionId) return;\n this.socket.write(\n serializeIpc({\n type: \"pty_semantic_event\",\n sessionId: this.sessionId,\n state,\n ...(meta?.title !== undefined ? { title: meta.title } : {}),\n ...(meta?.tool !== undefined ? { tool: meta.tool } : {}),\n }),\n );\n log.info(\n { sessionId: this.sessionId, state, title: meta?.title, tool: meta?.tool },\n \"PTY semantic event pushed\",\n );\n }\n\n private replayCurrentPtyState(): void {\n if (this.currentPtyState === \"turn_complete\") return;\n this.sendPtyState(this.currentPtyState);\n }\n\n private handleBridgeStatus(connected: boolean): void {\n if (this.remoteDetached) return;\n if (this.lastBridgeConnected === connected) return;\n this.lastBridgeConnected = connected;\n log.info({ connected }, \"Bridge status changed, notifying user\");\n notifyUser(connected ? \"relay online\" : \"relay offline — remote viewing unavailable\");\n }\n\n private setupSocketHandlers(): void {\n createIpcReader(\n this.socket,\n (msg: IpcMessage) => {\n if (msg.type === \"pty_input\" && msg.sessionId === this.sessionId) {\n log.debug(\n { sessionId: this.sessionId, bytes: msg.data.length },\n \"Remote input received\",\n );\n this.ptyManager?.write(msg.data);\n } else if (msg.type === \"pty_detach\" && msg.sessionId === this.sessionId) {\n this.detachRemoteView();\n } else if (msg.type === \"bridge_status\") {\n this.handleBridgeStatus(msg.connected);\n } else if (msg.type === \"pty_subscribe\" && msg.sessionId === this.sessionId) {\n if (this.serializeAddon && this.headlessTerminal) {\n const data = this.serializeAddon.serialize();\n this.socket.write(\n serializeIpc({\n type: \"pty_snapshot\",\n sessionId: msg.sessionId,\n cols: this.headlessTerminal.cols,\n rows: this.headlessTerminal.rows,\n data,\n outputSeq: this.outputSeq,\n requestId: msg.requestId,\n }),\n );\n log.info(\n {\n sessionId: this.sessionId,\n cols: this.headlessTerminal.cols,\n rows: this.headlessTerminal.rows,\n bytes: data.length,\n },\n \"Snapshot sent via IPC\",\n );\n }\n }\n },\n undefined,\n (err, line) => {\n log.warn(\n { err: err.message, lineLen: line.length },\n \"Serve IPC message dropped (parse/schema error)\",\n );\n },\n );\n\n this.socket.on(\"close\", () => {\n log.info(\"Serve socket closed\");\n if (this.remoteDetached) {\n log.info(\"Remote view detached, skipping serve reconnect\");\n return;\n }\n if (!this.fsm.isIn([TerminalState.RECONNECTING, TerminalState.EXITED])) {\n this.fsm.transitionTo(TerminalState.RECONNECTING);\n this.reconnectToServe();\n }\n });\n\n // socket error 通常和 close 成对出现;这里只记 warn 避免静默吞错,重连仍由 close handler 触发\n this.socket.on(\"error\", (err) => {\n log.warn({ err: err.message }, \"Serve socket error\");\n });\n }\n\n // 超过 IDLE_THRESHOLD_MS 无 PTY 输出则从 working 翻回 turn_complete\n private setupIdleCheck(): void {\n this.idleChecker?.stop();\n this.idleChecker = createIdleChecker({\n intervalMs: IDLE_CHECK_INTERVAL_MS,\n thresholdMs: IDLE_THRESHOLD_MS,\n getLastOutputTime: () => this.lastOutputTime,\n setLastOutputTime: (value) => {\n this.lastOutputTime = value;\n },\n getCurrentState: () => this.currentPtyState,\n onIdle: () => {\n this.currentPtyState = \"turn_complete\";\n this.sendPtyState(\"turn_complete\");\n },\n });\n this.idleChecker.start();\n }\n\n private async reconnectToServe(): Promise<void> {\n log.info(\"Serve connection lost, starting reconnection\");\n\n // 两条路径都不该再继续 spawn daemon:\n // - STOPPED=true:用户主动 dev-anywhere stop,不要对抗用户意图。\n // - consecutiveSpawnFailures 跨过阈值:说明环境有持续性问题,spawn 再多也白搭。\n // 进入 passive 后仅做 tryConnect 等待,daemon 起来或用户 dev-anywhere start 后自动恢复。\n let consecutiveSpawnFailures = 0;\n\n for (let i = 0; ; i++) {\n if (this.remoteDetached) return;\n await sleep(Math.min(RECONNECT_INITIAL_DELAY_MS * (i + 1), RECONNECT_MAX_DELAY_MS));\n\n const stopped = existsSync(STOPPED_PATH);\n const degraded = consecutiveSpawnFailures >= SPAWN_FAILURE_THRESHOLD;\n const passive = stopped || degraded;\n\n try {\n log.debug({ attempt: i + 1, stopped, degraded }, \"Reconnect attempt\");\n const newSocket = passive ? await tryConnect(SOCK_PATH) : await ensureService();\n if (!newSocket) continue;\n\n if (degraded) notifyUser(\"serve daemon reachable, reconnected\");\n consecutiveSpawnFailures = 0;\n\n this.socket = swapServeSocket(this.socket, newSocket);\n log.info({ attempt: i + 1, sessionId: this.sessionId }, \"Reconnected to serve\");\n\n this.setupSocketHandlers();\n\n if (this.sessionId) {\n this.fsm.transitionTo(TerminalState.CREATING_SESSION);\n this.socket.write(\n serializeIpc({\n type: \"session_create_request\",\n mode: \"pty\",\n provider: this.provider.id,\n cwd: this.sessionCwd,\n name: tildify(this.sessionCwd),\n pid: process.pid,\n sessionId: this.sessionId,\n }),\n );\n const resp = await waitForMessage(this.socket, \"session_create_response\");\n if (!resp.error) {\n this.sessionId = resp.sessionId;\n this.socket.write(\n serializeIpc({ type: \"pty_register\", sessionId: this.sessionId, pid: process.pid }),\n );\n this.replayCurrentPtyState();\n this.fsm.transitionTo(TerminalState.RUNNING);\n log.info({ sessionId: this.sessionId }, \"Session re-registered after reconnect\");\n }\n } else {\n this.fsm.transitionTo(TerminalState.RUNNING);\n }\n\n return;\n } catch (err) {\n // passive 模式走 tryConnect,失败返回 null 不抛;这里只可能是 ensureService spawn 失败\n if (!passive) {\n consecutiveSpawnFailures++;\n if (consecutiveSpawnFailures === SPAWN_FAILURE_THRESHOLD) {\n notifyUser(\n `serve daemon spawn failed ${SPAWN_FAILURE_THRESHOLD}x — auto-spawn disabled; check environment or run 'dev-anywhere start'`,\n );\n }\n }\n log.debug(\n { err: err instanceof Error ? err.message : err, attempt: i + 1, degraded },\n \"Reconnect attempt failed\",\n );\n }\n }\n }\n\n private detachRemoteView(): void {\n const sessionId = this.sessionId;\n if (!sessionId) return;\n this.remoteDetached = true;\n this.sessionId = null;\n this.hookContext = null;\n this.currentPtyState = \"turn_complete\";\n log.info({ sessionId }, \"Remote view detached; local PTY keeps running\");\n notifyUser(\"remote viewing detached\");\n if (this.socket.writable) this.socket.end();\n }\n}\n\nfunction providerFromEnv(): ProviderId {\n return process.env.DEV_ANYWHERE_PROVIDER === \"codex\" ? \"codex\" : \"claude\";\n}\n\nexport async function startTerminal(\n providerArgs: string[],\n providerId: ProviderId = providerFromEnv(),\n): Promise<void> {\n await new TerminalSession(PROVIDERS[providerId], providerArgs).run();\n}\n","// 读 stdout cols/rows,非 TTY 抛错。\nexport function readTtySize(stream: NodeJS.WriteStream): { cols: number; rows: number } {\n const { columns, rows } = stream;\n if (columns === undefined || rows === undefined) {\n throw new Error(\n \"stdout is not an interactive TTY (columns/rows undefined); dev-anywhere requires running in a real terminal\",\n );\n }\n return { cols: columns, rows };\n}\n\n// 发一条 OSC 9 iTerm2-style 系统通知 + 响铃。iTerm2 / kitty / wezterm 等会弹出带 message\n// 的系统通知;不认 OSC 9 的终端会忽略转义序列只剩下 BEL 响铃。\n// 用此而非 stderr banner 的原因:dev-anywhere 对 Claude PTY 画面保持透明是硬约束,\n// banner 会挤掉 Claude 的渲染行,OSC 9 不占画面,BEL 是纯听觉信号。\nexport function notifyUser(message: string): void {\n process.stderr.write(`\\x1b]9;${message}\\x07`);\n}\n\n// Provider TUI 可能开启 bracketed paste、application cursor/keypad、mouse tracking、\n// xterm modifyOtherKeys 或 kitty keyboard protocol。若 provider 被远程终止或异常退出,\n// 这些模式可能来不及自行恢复,外层 shell 会把 Ctrl-C 显示成 \";5;99~\" 一类残留序列。\nexport function restoreHostTerminalModes(stream: NodeJS.WriteStream): void {\n if (!stream.isTTY) return;\n const restoreSequences = [\n \"\\x1b[?1l\", // application cursor keys off\n \"\\x1b>\", // application keypad off\n \"\\x1b[?1000l\",\n \"\\x1b[?1002l\",\n \"\\x1b[?1003l\",\n \"\\x1b[?1004l\",\n \"\\x1b[?1006l\",\n \"\\x1b[?1015l\",\n \"\\x1b[?2004l\", // bracketed paste off\n \"\\x1b[>4;0m\", // xterm modifyOtherKeys off\n \"\\x1b[<u\", // kitty keyboard protocol off\n ].join(\"\");\n stream.write(restoreSequences);\n}\n","import * as pty from \"node-pty\";\nimport type { IPty } from \"node-pty\";\nimport type { ProviderAdapter, ProviderHookContext } from \"../providers/index.js\";\nimport { readTtySize, restoreHostTerminalModes } from \"./tty.js\";\n\ninterface PtyManagerOptions {\n provider: ProviderAdapter;\n providerArgs: string[];\n cwd: string;\n hook?: ProviderHookContext;\n tap: (data: string) => void;\n stdin: NodeJS.ReadStream;\n stdout: NodeJS.WriteStream;\n onSessionExit?: (code: number) => void | Promise<void>;\n onResize?: (cols: number, rows: number) => void;\n}\n\nexport class PtyManager {\n private child: IPty | null = null;\n private resizeTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly provider: ProviderAdapter;\n private readonly providerArgs: string[];\n private readonly cwd: string;\n private readonly hook?: ProviderHookContext;\n private readonly tap: (data: string) => void;\n private readonly stdin: NodeJS.ReadStream;\n private readonly stdout: NodeJS.WriteStream;\n private readonly onSessionExit?: (code: number) => void;\n private readonly onResize?: (cols: number, rows: number) => void;\n\n constructor(options: PtyManagerOptions) {\n this.provider = options.provider;\n this.providerArgs = options.providerArgs;\n this.cwd = options.cwd;\n this.hook = options.hook;\n this.tap = options.tap;\n this.stdin = options.stdin;\n this.stdout = options.stdout;\n this.onSessionExit = options.onSessionExit;\n this.onResize = options.onResize;\n }\n\n start(): void {\n const { cols, rows } = readTtySize(this.stdout);\n\n const command = this.provider.buildTerminalCommand(\n { args: this.providerArgs, hook: this.hook },\n process.env,\n );\n const child = pty.spawn(command.command, command.args, {\n name: process.env.TERM ?? \"xterm-256color\",\n cols,\n rows,\n cwd: this.cwd,\n env: command.env as Record<string, string>,\n });\n this.child = child;\n\n // raw mode 仅在 stdin 为 TTY 时开启\n const isInteractive = this.stdin.isTTY === true;\n if (isInteractive) {\n this.stdin.setRawMode(true);\n }\n this.stdin.resume();\n\n // stdin -> PTY\n this.stdin.on(\"data\", (data: Buffer) => {\n child.write(data.toString());\n });\n\n // PTY -> stdout + tap\n child.onData((data: string) => this.handleData(data));\n\n // resize 防抖,50ms 窗口合并快速连续的尺寸变化。timer 持有在实例字段上,cleanup 时\n // clearTimeout 防止 PTY 退出后 callback 还在等待,导致 child.resize 调用已 kill 的子进程。\n this.stdout.on(\"resize\", () => {\n if (this.resizeTimer) clearTimeout(this.resizeTimer);\n this.resizeTimer = setTimeout(() => {\n this.resizeTimer = null;\n if (!this.child) return;\n const { cols: newCols, rows: newRows } = readTtySize(this.stdout);\n this.child.resize(newCols, newRows);\n this.onResize?.(newCols, newRows);\n }, 50);\n });\n\n // 子进程退出,按 Unix 惯例处理信号退出码,通过回调通知调用方\n child.onExit(({ exitCode, signal }) => {\n if (isInteractive) this.disableRawModeQuiet();\n restoreHostTerminalModes(this.stdout);\n const code = signal ? 128 + signal : exitCode;\n this.onSessionExit?.(code);\n });\n\n // stdin 结束时写入 EOF 控制字符到 PTY\n this.stdin.on(\"end\", () => {\n child.write(\"\\x04\");\n });\n }\n\n /**\n * PTY 数据到达时的统一处理:OSC 9 修复 + 输出到终端 + 传给 tap\n */\n private handleData(data: string): void {\n // PTY 的 onlcr 会把 OSC 序列里的 \\n 转成 \\r\\n,还原为 \\n\n const fixed = data.replace(\n // eslint-disable-next-line no-control-regex\n /\\x1b\\]9;([\\s\\S]*?)\\x07/g,\n (_, content: string) => `\\x1b]9;${content.replace(/\\r\\n/g, \"\\n\")}\\x07`,\n );\n this.stdout.write(fixed);\n this.tap(data);\n }\n\n // 向 PTY 子进程写入数据,用于远程输入注入\n write(data: string): void {\n this.child?.write(data);\n }\n\n // 关闭 raw mode 并吞掉 stdin 已关闭场景下的异常。退出 / cleanup 两条路径共用。\n private disableRawModeQuiet(): void {\n try {\n this.stdin.setRawMode(false);\n } catch {\n // stdin 可能已关闭\n }\n }\n\n cleanup(exitCode: number): void {\n if (this.resizeTimer) {\n clearTimeout(this.resizeTimer);\n this.resizeTimer = null;\n }\n if (this.stdin.isTTY) this.disableRawModeQuiet();\n restoreHostTerminalModes(this.stdout);\n if (this.child) {\n try {\n this.child.kill();\n } catch {\n // 子进程可能已退出\n }\n this.child = null;\n }\n this.onSessionExit?.(exitCode);\n }\n}\n","import { statSync } from \"node:fs\";\n\nfunction isDirectory(path: string | undefined): path is string {\n if (!path) return false;\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\nexport function resolveTerminalCwd(env: NodeJS.ProcessEnv = process.env): string {\n const candidates = [env.DEV_ANYWHERE_CWD, env.INIT_CWD, env.PWD, process.cwd()];\n return candidates.find(isDirectory) ?? process.cwd();\n}\n","import { connect, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { setTimeout as sleep } from \"node:timers/promises\";\nimport { SOCK_PATH, STOPPED_PATH, SERVICE_LOG_PATH, PROFILE_NAME } from \"../common/paths.js\";\nimport { spawnScript } from \"../common/env.js\";\nimport { daemonRelayArgs } from \"../common/daemon-env.js\";\nimport { createIpcReader, type IpcMessage } from \"../ipc/ipc-protocol.js\";\nimport { terminalLogger as log } from \"../common/logger.js\";\n\n// serve daemon 自动拉起的连接重试参数\nconst ENSURE_SERVICE_MAX_RETRIES = 20;\nconst ENSURE_SERVICE_INITIAL_DELAY_MS = 100;\nconst ENSURE_SERVICE_MAX_DELAY_MS = 2_000;\n\n// 等待特定类型 IPC 消息的默认超时\nconst WAIT_FOR_MESSAGE_TIMEOUT_MS = 10_000;\n\n// 单次 socket 连接尝试:连上 resolve socket,连不上 resolve null(不抛异常)。\nexport function tryConnect(sockPath: string): Promise<Socket | null> {\n return new Promise((resolve) => {\n const s = connect(sockPath);\n s.on(\"connect\", () => resolve(s));\n s.on(\"error\", () => resolve(null));\n });\n}\n\n// 接到 serve daemon 的 unix socket 上:先尝试连接已有进程,连不上则拉起一个新 daemon\n// 子进程并轮询直到 socket 就绪。autoStart=false 用于命令式 status 查询,禁止 spawn。\nexport async function ensureService(autoStart = true): Promise<Socket> {\n const existing = await tryConnect(SOCK_PATH);\n if (existing) {\n log.info(\"Connected to existing service\");\n return existing;\n }\n\n if (!autoStart) throw new Error(\"Service is not running\");\n\n if (existsSync(STOPPED_PATH)) unlinkSync(STOPPED_PATH);\n\n log.info(\"Auto-starting serve daemon\");\n const child = spawnScript(\n new URL(\"../serve\", import.meta.url),\n [\"--profile\", PROFILE_NAME, ...daemonRelayArgs()],\n {\n env: { ...process.env },\n logger: log,\n },\n );\n\n // 监听 daemon 失败信号,让下面的 tryConnect 轮询能在 daemon 启动时就崩的场景下立刻抛诊断。\n // - 'exit':进程启动成功后又退出(配置错误、端口占用、内部崩溃),带 code/signal。\n // - 'error':spawn 本身失败(ENOENT 找不到 tsx/node 等),Node 文档说此时 'exit' may or may not 跟着 fire,\n // 所以显式监听补完备性。spawnScript 内部另装了一对只管日志的监听器,跟这里互不影响。\n let childFailed = false;\n let exitCode: number | null = null;\n let exitSignal: NodeJS.Signals | null = null;\n let spawnError: Error | null = null;\n child.once(\"exit\", (code, signal) => {\n childFailed = true;\n exitCode = code;\n exitSignal = signal;\n });\n child.once(\"error\", (err) => {\n childFailed = true;\n spawnError = err;\n });\n\n for (let i = 0; i < ENSURE_SERVICE_MAX_RETRIES; i++) {\n const delay = Math.min(ENSURE_SERVICE_INITIAL_DELAY_MS * (i + 1), ENSURE_SERVICE_MAX_DELAY_MS);\n await sleep(delay);\n\n if (childFailed) {\n log.error(\n { code: exitCode, signal: exitSignal, err: spawnError && String(spawnError) },\n \"Serve daemon failed to start\",\n );\n const detail = spawnError\n ? `spawn error=${String(spawnError)}`\n : `code=${exitCode}, signal=${exitSignal}`;\n throw new Error(\n `Serve daemon failed to start (${detail}). Check ${SERVICE_LOG_PATH} for details.`,\n );\n }\n\n const socket = await tryConnect(SOCK_PATH);\n if (socket) {\n log.info({ attempt: i + 1 }, \"Connected to service after retry\");\n return socket;\n }\n }\n\n log.error({ maxRetries: ENSURE_SERVICE_MAX_RETRIES }, \"Failed to connect to service\");\n throw new Error(\n `Failed to connect to dev-anywhere service after ${ENSURE_SERVICE_MAX_RETRIES} retries. Check ${SERVICE_LOG_PATH} for details.`,\n );\n}\n\n// 等待指定类型的 IPC 消息一次。`createIpcReader` 注册临时 listener,匹配后立即清理。\n// 超时返回 reject;调用方需自己保证 socket 不会同时被另一个 listener 持有否则消息可能被吃掉。\nexport function waitForMessage<T extends IpcMessage[\"type\"]>(\n socket: Socket,\n messageType: T,\n): Promise<Extract<IpcMessage, { type: T }>> {\n return new Promise((resolve, reject) => {\n let timeout: NodeJS.Timeout | null = null;\n const dispose = createIpcReader(socket, (msg: IpcMessage) => {\n if (msg.type === messageType) {\n if (timeout) clearTimeout(timeout);\n dispose();\n resolve(msg as Extract<IpcMessage, { type: T }>);\n }\n });\n timeout = setTimeout(() => {\n dispose();\n reject(new Error(`Timeout waiting for ${messageType}`));\n }, WAIT_FOR_MESSAGE_TIMEOUT_MS);\n });\n}\n","// PTY 闲置检测:每 IDLE_CHECK_INTERVAL_MS 抽一次,发现自上次输出已超过 IDLE_THRESHOLD_MS\n// 且当前局部状态仍是 working 时,把状态翻到 turn_complete 并触发 emit。\n//\n// 抽出来主要好处:terminal.ts / hosted-pty-registry.ts 都跑同样的 working→turn_complete\n// 退化逻辑,未来若把 hosted 也接入这个 checker,就只剩一份实现。\n\ninterface IdleCheckerOptions {\n // 检测周期\n intervalMs: number;\n // 超过这个时长无新输出即触发 turn_complete\n thresholdMs: number;\n // 读取最近一次 PTY 输出时间戳;返回 0 表示已经走过 turn_complete 不要重复触发\n getLastOutputTime: () => number;\n // 用于\"重置最近输出时间到 0\",保证下次再触发前必须有真实新输出\n setLastOutputTime: (value: number) => void;\n // 读当前局部 PTY 状态\n getCurrentState: () => \"working\" | \"turn_complete\" | \"approval_wait\";\n // 仅在 currentState === \"working\" 时触发;onIdle 内部决定具体怎么落地(emit IPC / 改 state)\n onIdle: () => void;\n}\n\nexport interface IdleChecker {\n start(): void;\n stop(): void;\n}\n\nexport function createIdleChecker(options: IdleCheckerOptions): IdleChecker {\n let timer: NodeJS.Timeout | null = null;\n\n const tick = (): void => {\n const last = options.getLastOutputTime();\n if (last <= 0) return;\n if (Date.now() - last <= options.thresholdMs) return;\n options.setLastOutputTime(0);\n if (options.getCurrentState() !== \"working\") return;\n options.onIdle();\n };\n\n return {\n start(): void {\n if (timer) clearInterval(timer);\n timer = setInterval(tick, options.intervalMs);\n },\n stop(): void {\n if (timer) {\n clearInterval(timer);\n timer = null;\n }\n },\n };\n}\n","import type { Socket } from \"node:net\";\n\n// terminal 进程在 reconnectToServe 成功后用新 socket 替换旧的。旧 socket 已被 close 事件\n// 触发过,但 createIpcReader 内部 pipe 的 LineBuffer + 在原 socket 上注册的 close/error/data\n// listener 仍持有引用;不显式 removeAllListeners 会让它们随着每次 reconnect 单调累积,\n// 长跑(夜间 / 网络抖动频繁)下成为内存泄漏。\nexport function swapServeSocket(prev: Socket, next: Socket): Socket {\n prev.removeAllListeners();\n return next;\n}\n","import type { Socket } from \"node:net\";\nimport { createFSM } from \"@dev-anywhere/shared\";\nimport { serializeIpc } from \"../ipc/ipc-protocol.js\";\n\n// terminal 进程生命周期状态\nexport const TerminalState = {\n INIT: \"init\",\n CONNECTING_SERVICE: \"connecting_service\",\n CREATING_SESSION: \"creating_session\",\n RUNNING: \"running\",\n RECONNECTING: \"reconnecting\",\n EXITED: \"exited\",\n} as const;\nexport type TerminalState = (typeof TerminalState)[keyof typeof TerminalState];\n\n// 允许的状态转换。CREATING_SESSION/RUNNING 下可被 socket close 打断进入 RECONNECTING;\n// 任意非终态都可能被 PTY 退出或 SIGTERM 打断进入 EXITED。\nexport const TERMINAL_TRANSITIONS: Record<TerminalState, readonly TerminalState[]> = {\n init: [\"connecting_service\"],\n connecting_service: [\"creating_session\", \"exited\"],\n creating_session: [\"running\", \"reconnecting\", \"exited\"],\n running: [\"reconnecting\", \"exited\"],\n reconnecting: [\"creating_session\", \"running\", \"exited\"],\n exited: [],\n};\n\n// 下面几个依赖是 getter 而非值:因为它们在 terminal.ts 里是 let 变量,在 handler 创建之后还会变——\n// socket 在 reconnect 时被重新赋值为新实例,sessionId 在 session_create 成功后才有值,\n// idleChecker 在 setupIdleCheck 跑完才赋值。直接传值只会记录 handler 构造那一刻的旧值。\ninterface ExitHandlerDeps {\n fsm: ReturnType<typeof createFSM<TerminalState>>;\n getSocket: () => Socket;\n getSessionId: () => string | null;\n // 退出时要停掉的 idle checker;handler 创建时尚未实例化,故传 getter\n stopIdleChecker: () => void;\n // 释放 xterm headless 渲染资源(HeadlessTerminal、SerializeAddon、UnicodeGraphemesAddon\n // 等)。process 退出后 OS 会回收文件描述符等系统资源,但 wasm 模块状态、addon listener\n // 留到 process 末期会让多次 spawn-exit 循环(如测试场景)累积内存。\n disposeRenderResources?: () => void;\n // 测试注入点,production 默认 process.exit\n exit?: (code: number) => void;\n}\n\n// 构造统一的收尾函数:转 EXITED → 停 idle 定时器 → 给 serve 发 pty_deregister → 退进程。\n// onSessionExit 与 SIGTERM handler 共享同一实例;Ctrl+C 两连击或 PTY 退出与 SIGTERM 竞争时,\n// 第二次调用通过 fsm EXITED 检查直接短路。\nexport function createExitHandler(deps: ExitHandlerDeps): (code: number) => void {\n const exit = deps.exit ?? ((code: number) => process.exit(code));\n return (code: number) => {\n if (deps.fsm.is(TerminalState.EXITED)) return;\n deps.fsm.transitionTo(TerminalState.EXITED);\n deps.stopIdleChecker();\n deps.disposeRenderResources?.();\n const socket = deps.getSocket();\n const sessionId = deps.getSessionId();\n if (socket.writable && sessionId) {\n socket.end(serializeIpc({ type: \"pty_deregister\", sessionId }), () => exit(code));\n } else {\n exit(code);\n }\n };\n}\n"],"mappings":"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","names":["sleep","existsSync","sleep","existsSync"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev-anywhere/proxy",
3
- "version": "0.1.9",
3
+ "version": "0.2.1",
4
4
  "description": "Transparent local proxy for AI coding CLIs that bridges local sessions to a web/PWA client via a relay server.",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -52,8 +52,8 @@
52
52
  "@types/node": "^25.5.2",
53
53
  "@types/ws": "^8.18.1",
54
54
  "vitest": "^4.1.2",
55
- "@dev-anywhere/shared": "0.1.9",
56
- "@dev-anywhere/relay": "0.1.9"
55
+ "@dev-anywhere/shared": "0.2.1",
56
+ "@dev-anywhere/relay": "0.2.1"
57
57
  },
58
58
  "scripts": {
59
59
  "build": "tsup",