@dev-anywhere/proxy 0.1.8 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/dist/{chunk-2SBGSJLQ.js → chunk-3ZUZ22V6.js} +2 -2
  2. package/dist/{chunk-PDX6QFJ7.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-R4S6OFIZ.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-KQPEQVP7.js → relay-token-RKAVVQHE.js} +6 -5
  19. package/dist/relay-token-RKAVVQHE.js.map +1 -0
  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-4MDRBCL4.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-2XO3KLWW.js +0 -868
  28. package/dist/chunk-2XO3KLWW.js.map +0 -1
  29. package/dist/chunk-BLWDLNT6.js +0 -346
  30. package/dist/chunk-BLWDLNT6.js.map +0 -1
  31. package/dist/chunk-GTTLWHIG.js +0 -84
  32. package/dist/chunk-GTTLWHIG.js.map +0 -1
  33. package/dist/chunk-ORZTFYXR.js +0 -123
  34. package/dist/chunk-ORZTFYXR.js.map +0 -1
  35. package/dist/chunk-PDX6QFJ7.js.map +0 -1
  36. package/dist/chunk-R4S6OFIZ.js.map +0 -1
  37. package/dist/relay-token-KQPEQVP7.js.map +0 -1
  38. package/dist/terminal-4MDRBCL4.js.map +0 -1
  39. /package/dist/{chunk-2SBGSJLQ.js.map → chunk-3ZUZ22V6.js.map} +0 -0
  40. /package/dist/{chunk-6O6JTF24.js.map → chunk-WUBRUO3G.js.map} +0 -0
@@ -1,123 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- // src/common/osc-extractor.ts
4
- var OSC_PATTERN = /\x1b\](\d+);([^\x07\x1b]*?)(?:\x07|\x1b\\)/g;
5
- function extractOscSequences(rawData) {
6
- const regex = new RegExp(OSC_PATTERN.source, OSC_PATTERN.flags);
7
- const matches = [];
8
- let match;
9
- while ((match = regex.exec(rawData)) !== null) {
10
- matches.push({ code: parseInt(match[1], 10), text: match[2] });
11
- }
12
- return matches;
13
- }
14
- function lastSequence(matches, code) {
15
- for (let i = matches.length - 1; i >= 0; i -= 1) {
16
- if (matches[i].code === code) return matches[i];
17
- }
18
- return void 0;
19
- }
20
- function isCodexActionRequiredTitle(title) {
21
- return /\bAction Required\b/i.test(title);
22
- }
23
- function extractOscSignals(rawData, provider) {
24
- const matches = extractOscSequences(rawData);
25
- if (matches.length === 0) return null;
26
- const osc0 = lastSequence(matches, 0);
27
- const osc9 = lastSequence(matches, 9);
28
- if (osc9) {
29
- if (osc9.text.includes("waiting for your input") || osc9.text.trim() === "4;0;") {
30
- return { state: "turn_complete", ...osc0 ? { title: osc0.text } : {} };
31
- }
32
- if (osc9.text.includes("needs your permission")) {
33
- const toolMatch = osc9.text.match(/permission.*?:\s*(\S+)/);
34
- return {
35
- state: "approval_wait",
36
- ...osc0 ? { title: osc0.text } : {},
37
- ...toolMatch?.[1] ? { tool: toolMatch[1] } : {}
38
- };
39
- }
40
- }
41
- if (provider === "codex" && osc0 && isCodexActionRequiredTitle(osc0.text)) {
42
- return { state: "approval_wait", title: osc0.text };
43
- }
44
- if (osc0 && !osc9) {
45
- return { state: "mid_pause", title: osc0.text };
46
- }
47
- return null;
48
- }
49
-
50
- // src/common/pty-approval-state.ts
51
- function shouldReleaseApprovalWait(options) {
52
- if (options.currentState !== "approval_wait") return false;
53
- return options.signalState !== void 0 && options.signalState !== "approval_wait";
54
- }
55
- function stateAfterApprovalRelease(signalState) {
56
- return signalState && signalState !== "approval_wait" ? signalState : "working";
57
- }
58
-
59
- // src/common/state-machine.ts
60
- function computeAbsorbingSet(transitions) {
61
- const absorbing = /* @__PURE__ */ new Set();
62
- const entries = Object.entries(transitions);
63
- for (const [s, outs] of entries) {
64
- if (outs.length === 0) absorbing.add(s);
65
- }
66
- let changed = true;
67
- while (changed) {
68
- changed = false;
69
- for (const [s, outs] of entries) {
70
- if (absorbing.has(s)) continue;
71
- if (outs.length > 0 && outs.every((t) => absorbing.has(t))) {
72
- absorbing.add(s);
73
- changed = true;
74
- }
75
- }
76
- }
77
- return absorbing;
78
- }
79
- function createFSM(def) {
80
- let state = def.initial;
81
- const absorbing = computeAbsorbingSet(def.transitions);
82
- const tryTransitionTo = (to) => {
83
- const allowed = def.transitions[state];
84
- if (!allowed?.includes(to)) {
85
- def.onRejected?.(state, to, absorbing.has(state));
86
- return false;
87
- }
88
- const from = state;
89
- state = to;
90
- def.onTransition?.(from, to);
91
- return true;
92
- };
93
- return {
94
- current: () => state,
95
- is: (s) => state === s,
96
- isIn: (ss) => ss.includes(state),
97
- canTransitionTo: (to) => def.transitions[state]?.includes(to) ?? false,
98
- transitionTo: (to) => {
99
- if (!tryTransitionTo(to)) {
100
- throw new Error(`Invalid FSM transition: ${state} -> ${to}`);
101
- }
102
- },
103
- tryTransitionTo,
104
- isInAbsorbingState: () => absorbing.has(state)
105
- };
106
- }
107
- function defineFSM(transitions) {
108
- const absorbing = computeAbsorbingSet(transitions);
109
- return {
110
- canTransition: (from, to) => transitions[from]?.includes(to) ?? false,
111
- isAbsorbing: (state) => absorbing.has(state)
112
- };
113
- }
114
-
115
- export {
116
- extractOscSequences,
117
- extractOscSignals,
118
- shouldReleaseApprovalWait,
119
- stateAfterApprovalRelease,
120
- createFSM,
121
- defineFSM
122
- };
123
- //# sourceMappingURL=chunk-ORZTFYXR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/common/osc-extractor.ts","../src/common/pty-approval-state.ts","../src/common/state-machine.ts"],"sourcesContent":["// OSC 0: 窗口标题 -- ESC ] 0 ; <title> BEL/ST\n// OSC 9: 通知 -- ESC ] 9 ; <text> BEL/ST\n// 每次调用创建新的 regex 实例避免 g flag 导致的 lastIndex 状态泄漏\n// eslint-disable-next-line no-control-regex\nconst OSC_PATTERN = /\\x1b\\](\\d+);([^\\x07\\x1b]*?)(?:\\x07|\\x1b\\\\)/g;\n\nexport type PtySemanticState = \"working\" | \"turn_complete\" | \"approval_wait\" | \"mid_pause\";\ntype PtySignalProvider = \"claude\" | \"codex\";\n\ninterface PtyStateEvent {\n state: PtySemanticState;\n title?: string;\n tool?: string;\n}\n\ninterface OscSequence {\n code: number;\n text: string;\n}\n\nexport function extractOscSequences(rawData: string): OscSequence[] {\n const regex = new RegExp(OSC_PATTERN.source, OSC_PATTERN.flags);\n const matches: OscSequence[] = [];\n\n let match: RegExpExecArray | null;\n while ((match = regex.exec(rawData)) !== null) {\n matches.push({ code: parseInt(match[1], 10), text: match[2] });\n }\n\n return matches;\n}\n\nfunction lastSequence(matches: OscSequence[], code: number): OscSequence | undefined {\n for (let i = matches.length - 1; i >= 0; i -= 1) {\n if (matches[i].code === code) return matches[i];\n }\n return undefined;\n}\n\nfunction isCodexActionRequiredTitle(title: string): boolean {\n return /\\bAction Required\\b/i.test(title);\n}\n\n// 从 PTY 原始数据中提取 OSC 语义信号。\n// OSC 9 优先级高于 OSC 0,无匹配时返回 null。\nexport function extractOscSignals(\n rawData: string,\n provider?: PtySignalProvider,\n): PtyStateEvent | null {\n const matches = extractOscSequences(rawData);\n\n if (matches.length === 0) return null;\n\n const osc0 = lastSequence(matches, 0);\n\n // OSC 9 优先级更高,包含具体的语义信号;同帧 OSC 0 仍保留 title 给 UI。\n const osc9 = lastSequence(matches, 9);\n if (osc9) {\n if (osc9.text.includes(\"waiting for your input\") || osc9.text.trim() === \"4;0;\") {\n return { state: \"turn_complete\", ...(osc0 ? { title: osc0.text } : {}) };\n }\n if (osc9.text.includes(\"needs your permission\")) {\n const toolMatch = osc9.text.match(/permission.*?:\\s*(\\S+)/);\n return {\n state: \"approval_wait\",\n ...(osc0 ? { title: osc0.text } : {}),\n ...(toolMatch?.[1] ? { tool: toolMatch[1] } : {}),\n };\n }\n }\n\n if (provider === \"codex\" && osc0 && isCodexActionRequiredTitle(osc0.text)) {\n return { state: \"approval_wait\", title: osc0.text };\n }\n\n // 仅有 OSC 0(标题/spinner 变化)时视为 MID_PAUSE\n if (osc0 && !osc9) {\n return { state: \"mid_pause\", title: osc0.text };\n }\n\n return null;\n}\n","import type { PtySemanticState } from \"./osc-extractor.js\";\n\nexport function shouldReleaseApprovalWait(options: {\n currentState: PtySemanticState;\n signalState?: PtySemanticState;\n}): boolean {\n if (options.currentState !== \"approval_wait\") return false;\n return options.signalState !== undefined && options.signalState !== \"approval_wait\";\n}\n\nexport function stateAfterApprovalRelease(signalState?: PtySemanticState): PtySemanticState {\n return signalState && signalState !== \"approval_wait\" ? signalState : \"working\";\n}\n","// 有限状态机 helper\n//\n// 提供显式转换表的小型 FSM,区分两类调用模式:\n// - transitionTo(throw):同步确定性流程里调用,非法转移代表 bug,立即暴露\n// - tryTransitionTo(bool):异步事件回调里调用,非法转移可能是吸收态残余事件,调用方按\n// isInAbsorbingState() 分级日志\n//\n// 吸收态采用传递闭包定义:终态(transitions=[])或所有出边都指向吸收态的状态都算吸收。\n// 这样 SessionState.ERROR (→[TERMINATED]) 和 RelayConnectionState.CLOSED ([]) 都被\n// 自动识别为吸收态,不用在 caller 里硬编码状态名。\n//\n// onTransition/onRejected 仅做日志/观测,不应 throw。\n\ninterface FSMDef<S extends string> {\n initial: S;\n // from-state → 允许转入的 to-state 列表;终态对应空数组\n transitions: Record<S, readonly S[]>;\n // 合法转换发生后触发,典型用法是结构化日志\n onTransition?: (from: S, to: S) => void;\n // tryTransitionTo 非法转移时触发;isAbsorbing 指示 from 是否吸收态(晚到残余 vs. 真非法)\n onRejected?: (from: S, to: S, isAbsorbing: boolean) => void;\n}\n\ninterface FSM<S extends string> {\n current(): S;\n is(state: S): boolean;\n isIn(states: readonly S[]): boolean;\n canTransitionTo(to: S): boolean;\n // 非法转换抛 Error;同步流程里当 assert 用\n transitionTo(to: S): void;\n // 非法转换返回 false,不抛;异步回调里用,配合 isInAbsorbingState 分级日志\n tryTransitionTo(to: S): boolean;\n // 当前是否在吸收态(传递闭包)\n isInAbsorbingState(): boolean;\n}\n\n// 计算吸收态集合:终态 + 所有出边都指向吸收态的状态,迭代至不动点\nfunction computeAbsorbingSet<S extends string>(transitions: Record<S, readonly S[]>): Set<S> {\n const absorbing = new Set<S>();\n const entries = Object.entries(transitions) as Array<[S, readonly S[]]>;\n for (const [s, outs] of entries) {\n if (outs.length === 0) absorbing.add(s);\n }\n let changed = true;\n while (changed) {\n changed = false;\n for (const [s, outs] of entries) {\n if (absorbing.has(s)) continue;\n if (outs.length > 0 && outs.every((t) => absorbing.has(t))) {\n absorbing.add(s);\n changed = true;\n }\n }\n }\n return absorbing;\n}\n\nexport function createFSM<S extends string>(def: FSMDef<S>): FSM<S> {\n let state = def.initial;\n const absorbing = computeAbsorbingSet(def.transitions);\n const tryTransitionTo = (to: S): boolean => {\n const allowed = def.transitions[state];\n if (!allowed?.includes(to)) {\n def.onRejected?.(state, to, absorbing.has(state));\n return false;\n }\n const from = state;\n state = to;\n def.onTransition?.(from, to);\n return true;\n };\n return {\n current: () => state,\n is: (s) => state === s,\n isIn: (ss) => ss.includes(state),\n canTransitionTo: (to) => def.transitions[state]?.includes(to) ?? false,\n transitionTo: (to) => {\n if (!tryTransitionTo(to)) {\n throw new Error(`Invalid FSM transition: ${state} -> ${to}`);\n }\n },\n tryTransitionTo,\n isInAbsorbingState: () => absorbing.has(state),\n };\n}\n\n// 无内部 state 的 FSM 视图,供 state 存在外部(如 SessionInfo、DB 行)的 per-instance 场景使用。\n// 调用方自行传入 from,canTransition 校验;吸收态判定通过 isAbsorbing(state) 提供。\ninterface StatelessFSM<S extends string> {\n canTransition(from: S, to: S): boolean;\n isAbsorbing(state: S): boolean;\n}\n\nexport function defineFSM<S extends string>(transitions: Record<S, readonly S[]>): StatelessFSM<S> {\n const absorbing = computeAbsorbingSet(transitions);\n return {\n canTransition: (from, to) => transitions[from]?.includes(to) ?? false,\n isAbsorbing: (state) => absorbing.has(state),\n };\n}\n"],"mappings":";;;AAIA,IAAM,cAAc;AAgBb,SAAS,oBAAoB,SAAgC;AAClE,QAAM,QAAQ,IAAI,OAAO,YAAY,QAAQ,YAAY,KAAK;AAC9D,QAAM,UAAyB,CAAC;AAEhC,MAAI;AACJ,UAAQ,QAAQ,MAAM,KAAK,OAAO,OAAO,MAAM;AAC7C,YAAQ,KAAK,EAAE,MAAM,SAAS,MAAM,CAAC,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,EAAE,CAAC;AAAA,EAC/D;AAEA,SAAO;AACT;AAEA,SAAS,aAAa,SAAwB,MAAuC;AACnF,WAAS,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK,GAAG;AAC/C,QAAI,QAAQ,CAAC,EAAE,SAAS,KAAM,QAAO,QAAQ,CAAC;AAAA,EAChD;AACA,SAAO;AACT;AAEA,SAAS,2BAA2B,OAAwB;AAC1D,SAAO,uBAAuB,KAAK,KAAK;AAC1C;AAIO,SAAS,kBACd,SACA,UACsB;AACtB,QAAM,UAAU,oBAAoB,OAAO;AAE3C,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAM,OAAO,aAAa,SAAS,CAAC;AAGpC,QAAM,OAAO,aAAa,SAAS,CAAC;AACpC,MAAI,MAAM;AACR,QAAI,KAAK,KAAK,SAAS,wBAAwB,KAAK,KAAK,KAAK,KAAK,MAAM,QAAQ;AAC/E,aAAO,EAAE,OAAO,iBAAiB,GAAI,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC,EAAG;AAAA,IACzE;AACA,QAAI,KAAK,KAAK,SAAS,uBAAuB,GAAG;AAC/C,YAAM,YAAY,KAAK,KAAK,MAAM,wBAAwB;AAC1D,aAAO;AAAA,QACL,OAAO;AAAA,QACP,GAAI,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC;AAAA,QACnC,GAAI,YAAY,CAAC,IAAI,EAAE,MAAM,UAAU,CAAC,EAAE,IAAI,CAAC;AAAA,MACjD;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,WAAW,QAAQ,2BAA2B,KAAK,IAAI,GAAG;AACzE,WAAO,EAAE,OAAO,iBAAiB,OAAO,KAAK,KAAK;AAAA,EACpD;AAGA,MAAI,QAAQ,CAAC,MAAM;AACjB,WAAO,EAAE,OAAO,aAAa,OAAO,KAAK,KAAK;AAAA,EAChD;AAEA,SAAO;AACT;;;AC/EO,SAAS,0BAA0B,SAG9B;AACV,MAAI,QAAQ,iBAAiB,gBAAiB,QAAO;AACrD,SAAO,QAAQ,gBAAgB,UAAa,QAAQ,gBAAgB;AACtE;AAEO,SAAS,0BAA0B,aAAkD;AAC1F,SAAO,eAAe,gBAAgB,kBAAkB,cAAc;AACxE;;;ACyBA,SAAS,oBAAsC,aAA8C;AAC3F,QAAM,YAAY,oBAAI,IAAO;AAC7B,QAAM,UAAU,OAAO,QAAQ,WAAW;AAC1C,aAAW,CAAC,GAAG,IAAI,KAAK,SAAS;AAC/B,QAAI,KAAK,WAAW,EAAG,WAAU,IAAI,CAAC;AAAA,EACxC;AACA,MAAI,UAAU;AACd,SAAO,SAAS;AACd,cAAU;AACV,eAAW,CAAC,GAAG,IAAI,KAAK,SAAS;AAC/B,UAAI,UAAU,IAAI,CAAC,EAAG;AACtB,UAAI,KAAK,SAAS,KAAK,KAAK,MAAM,CAAC,MAAM,UAAU,IAAI,CAAC,CAAC,GAAG;AAC1D,kBAAU,IAAI,CAAC;AACf,kBAAU;AAAA,MACZ;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAEO,SAAS,UAA4B,KAAwB;AAClE,MAAI,QAAQ,IAAI;AAChB,QAAM,YAAY,oBAAoB,IAAI,WAAW;AACrD,QAAM,kBAAkB,CAAC,OAAmB;AAC1C,UAAM,UAAU,IAAI,YAAY,KAAK;AACrC,QAAI,CAAC,SAAS,SAAS,EAAE,GAAG;AAC1B,UAAI,aAAa,OAAO,IAAI,UAAU,IAAI,KAAK,CAAC;AAChD,aAAO;AAAA,IACT;AACA,UAAM,OAAO;AACb,YAAQ;AACR,QAAI,eAAe,MAAM,EAAE;AAC3B,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,SAAS,MAAM;AAAA,IACf,IAAI,CAAC,MAAM,UAAU;AAAA,IACrB,MAAM,CAAC,OAAO,GAAG,SAAS,KAAK;AAAA,IAC/B,iBAAiB,CAAC,OAAO,IAAI,YAAY,KAAK,GAAG,SAAS,EAAE,KAAK;AAAA,IACjE,cAAc,CAAC,OAAO;AACpB,UAAI,CAAC,gBAAgB,EAAE,GAAG;AACxB,cAAM,IAAI,MAAM,2BAA2B,KAAK,OAAO,EAAE,EAAE;AAAA,MAC7D;AAAA,IACF;AAAA,IACA;AAAA,IACA,oBAAoB,MAAM,UAAU,IAAI,KAAK;AAAA,EAC/C;AACF;AASO,SAAS,UAA4B,aAAuD;AACjG,QAAM,YAAY,oBAAoB,WAAW;AACjD,SAAO;AAAA,IACL,eAAe,CAAC,MAAM,OAAO,YAAY,IAAI,GAAG,SAAS,EAAE,KAAK;AAAA,IAChE,aAAa,CAAC,UAAU,UAAU,IAAI,KAAK;AAAA,EAC7C;AACF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/common/seq-counter.ts","../src/common/stream-json-schema.ts"],"sourcesContent":["import { mkdirSync, readFileSync, writeFileSync, existsSync } from \"node:fs\";\nimport { sessionPaths } from \"./paths.js\";\n\n/**\n * 轻量级 per-session seq 计数器,持久化到文件\n *\n * 仅存储一个递增整数,proxy 重启后能接续。\n */\nexport class SeqCounter {\n private seq: number = 0;\n private readonly filePath: string;\n\n constructor(sessionId: string, baseDir?: string) {\n const dir = baseDir ?? sessionPaths(sessionId).dir;\n mkdirSync(dir, { recursive: true });\n this.filePath = `${dir}/seq`;\n this.load();\n }\n\n next(): number {\n this.seq++;\n this.save();\n return this.seq;\n }\n\n current(): number {\n return this.seq;\n }\n\n private load(): void {\n if (!existsSync(this.filePath)) return;\n const content = readFileSync(this.filePath, \"utf-8\").trim();\n const parsed = parseInt(content, 10);\n if (isNaN(parsed) || parsed < 0) {\n throw new Error(`Corrupt seq file: ${this.filePath} contains \"${content}\"`);\n }\n this.seq = parsed;\n }\n\n private save(): void {\n writeFileSync(this.filePath, String(this.seq));\n }\n}\n","import { z } from \"zod\";\n\n// Claude CLI stream-json 输出的事件结构定义。\n// 设计原则:\n// - 已知字段严格校验,未知字段 passthrough 保证前向兼容\n// - content block 单独 parse,一个未知 block type 不会导致整条 assistant 事件被丢弃\n// - 新 event/block 类型加到这里后,forwardEvent 添加对应分支即可\n// - 基于 ~/.claude-2.1.116 采样 fixture 验证\n\nconst TextBlockSchema = z\n .object({\n type: z.literal(\"text\"),\n text: z.string(),\n })\n .passthrough();\n\n// Opus extended thinking 的 thinking 字段可能是空字符串(明文被 Anthropic 服务端 redact),\n// 只在 signature 里保留加密内容。signature 始终存在于 thinking block。\nconst ThinkingBlockSchema = z\n .object({\n type: z.literal(\"thinking\"),\n thinking: z.string(),\n signature: z.string().optional(),\n })\n .passthrough();\n\nconst ToolUseBlockSchema = z\n .object({\n type: z.literal(\"tool_use\"),\n id: z.string(),\n name: z.string(),\n input: z.record(z.string(), z.unknown()),\n })\n .passthrough();\n\n// tool_result 的 content 形状多变(string / array of nested blocks),proxy 不解析内容本身\nconst ToolResultBlockSchema = z\n .object({\n type: z.literal(\"tool_result\"),\n tool_use_id: z.string(),\n content: z.unknown(),\n is_error: z.boolean().optional(),\n })\n .passthrough();\n\n// 已知 block 类型的 discriminated union;未知 type 会 safeParse 失败,调用方应跳过而非丢整个事件\nexport const KnownContentBlockSchema = z.discriminatedUnion(\"type\", [\n TextBlockSchema,\n ThinkingBlockSchema,\n ToolUseBlockSchema,\n ToolResultBlockSchema,\n]);\n\n// event 级别 schema。message.content 用 z.array(z.unknown()),不在此层 narrow content block 类型,\n// 让调用方逐 block 跑 KnownContentBlockSchema.safeParse 以宽容未知 block\nconst AssistantEventSchema = z\n .object({\n type: z.literal(\"assistant\"),\n message: z\n .object({\n content: z.array(z.unknown()),\n })\n .passthrough(),\n })\n .passthrough();\n\nconst UserEventSchema = z\n .object({\n type: z.literal(\"user\"),\n message: z\n .object({\n content: z.array(z.unknown()),\n })\n .passthrough(),\n })\n .passthrough();\n\nconst ResultEventSchema = z\n .object({\n type: z.literal(\"result\"),\n subtype: z.string(),\n is_error: z.boolean().optional(),\n })\n .passthrough();\n\n// --include-partial-messages 开启时出现的增量事件。我们消费 content_block_delta 中的\n// text_delta / thinking_delta,其余内层 event(message_start/message_delta/content_block_start 等)忽略。\n// signature_delta 存在但 proxy 不转发 —— signature 是 Anthropic replay 用的加密态,对 UI 无意义。\nconst TextDeltaSchema = z\n .object({\n type: z.literal(\"text_delta\"),\n text: z.string(),\n })\n .passthrough();\n\nconst ThinkingDeltaSchema = z\n .object({\n type: z.literal(\"thinking_delta\"),\n thinking: z.string(),\n })\n .passthrough();\n\nconst SignatureDeltaSchema = z\n .object({\n type: z.literal(\"signature_delta\"),\n signature: z.string(),\n })\n .passthrough();\n\nexport const ContentBlockDeltaSchema = z\n .object({\n type: z.literal(\"content_block_delta\"),\n index: z.number(),\n delta: z.discriminatedUnion(\"type\", [\n TextDeltaSchema,\n ThinkingDeltaSchema,\n SignatureDeltaSchema,\n ]),\n })\n .passthrough();\n\nconst StreamEventSchema = z\n .object({\n type: z.literal(\"stream_event\"),\n event: z.object({ type: z.string() }).passthrough(),\n })\n .passthrough();\n\n// permission-prompt-tool=stdio 模式下,claude 发 control_request 要求审批,\n// proxy handleControlRequest 需写回 control_response 解除工具阻塞。\n// subtype 目前只见过 \"can_use_tool\";request.display_name / permission_suggestions /\n// decision_reason / decision_reason_type / tool_use_id 是 CLI 辅助字段,审批决策只看 tool_name + input。\nconst CanUseToolRequestSchema = z\n .object({\n subtype: z.literal(\"can_use_tool\"),\n tool_name: z.string(),\n input: z.record(z.string(), z.unknown()),\n })\n .passthrough();\n\nexport const ControlRequestEventSchema = z\n .object({\n type: z.literal(\"control_request\"),\n request_id: z.string(),\n request: CanUseToolRequestSchema,\n })\n .passthrough();\n\n// 审批结果回写 shape。proxy 自己写入 stdin,主要用于单元测试断言 wire shape 合法。\nexport const ControlResponseEventSchema = z\n .object({\n type: z.literal(\"control_response\"),\n response: z\n .object({\n subtype: z.literal(\"success\"),\n request_id: z.string(),\n response: z.discriminatedUnion(\"behavior\", [\n z\n .object({\n behavior: z.literal(\"allow\"),\n updatedInput: z.record(z.string(), z.unknown()),\n })\n .passthrough(),\n z\n .object({\n behavior: z.literal(\"deny\"),\n message: z.string(),\n })\n .passthrough(),\n ]),\n })\n .passthrough(),\n })\n .passthrough();\n\n// control_response 是 proxy 写回 claude stdin 的方向,不会出现在 stdout fixture 里;\n// 但 control-request scenario 的采样脚本也把写回 shape 留在 fixture 里用于 round-trip 验证。\n// 并入 union 让通用 canary \"every event is known\" 测试能覆盖它。\nexport const StreamJsonEventSchema = z.discriminatedUnion(\"type\", [\n AssistantEventSchema,\n UserEventSchema,\n ResultEventSchema,\n StreamEventSchema,\n ControlRequestEventSchema,\n ControlResponseEventSchema,\n]);\n\n// schema discriminatedUnion 未覆盖但 proxy 明确静默忽略的 event type。\n// forwardEvent 遇到这些 type 不发 warn,测试也按这份名单判断\"safeParse 失败是否在意\"。\n// 新增一种忽略 type 时只改这里一处。\nexport const IGNORED_EVENT_TYPES: ReadonlySet<string> = new Set([\"system\", \"rate_limit_event\"]);\n"],"mappings":";;;;;;AAAA,SAAS,WAAW,cAAc,eAAe,kBAAkB;AAQ5D,IAAM,aAAN,MAAiB;AAAA,EACd,MAAc;AAAA,EACL;AAAA,EAEjB,YAAY,WAAmB,SAAkB;AAC/C,UAAM,MAAM,WAAW,aAAa,SAAS,EAAE;AAC/C,cAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAClC,SAAK,WAAW,GAAG,GAAG;AACtB,SAAK,KAAK;AAAA,EACZ;AAAA,EAEA,OAAe;AACb,SAAK;AACL,SAAK,KAAK;AACV,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,UAAkB;AAChB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,OAAa;AACnB,QAAI,CAAC,WAAW,KAAK,QAAQ,EAAG;AAChC,UAAM,UAAU,aAAa,KAAK,UAAU,OAAO,EAAE,KAAK;AAC1D,UAAM,SAAS,SAAS,SAAS,EAAE;AACnC,QAAI,MAAM,MAAM,KAAK,SAAS,GAAG;AAC/B,YAAM,IAAI,MAAM,qBAAqB,KAAK,QAAQ,cAAc,OAAO,GAAG;AAAA,IAC5E;AACA,SAAK,MAAM;AAAA,EACb;AAAA,EAEQ,OAAa;AACnB,kBAAc,KAAK,UAAU,OAAO,KAAK,GAAG,CAAC;AAAA,EAC/C;AACF;;;AC1CA,SAAS,SAAS;AASlB,IAAM,kBAAkB,EACrB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,MAAM,EAAE,OAAO;AACjB,CAAC,EACA,YAAY;AAIf,IAAM,sBAAsB,EACzB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,UAAU;AAAA,EAC1B,UAAU,EAAE,OAAO;AAAA,EACnB,WAAW,EAAE,OAAO,EAAE,SAAS;AACjC,CAAC,EACA,YAAY;AAEf,IAAM,qBAAqB,EACxB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,UAAU;AAAA,EAC1B,IAAI,EAAE,OAAO;AAAA,EACb,MAAM,EAAE,OAAO;AAAA,EACf,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AACzC,CAAC,EACA,YAAY;AAGf,IAAM,wBAAwB,EAC3B,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,aAAa;AAAA,EAC7B,aAAa,EAAE,OAAO;AAAA,EACtB,SAAS,EAAE,QAAQ;AAAA,EACnB,UAAU,EAAE,QAAQ,EAAE,SAAS;AACjC,CAAC,EACA,YAAY;AAGR,IAAM,0BAA0B,EAAE,mBAAmB,QAAQ;AAAA,EAClE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAID,IAAM,uBAAuB,EAC1B,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,WAAW;AAAA,EAC3B,SAAS,EACN,OAAO;AAAA,IACN,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC;AAAA,EAC9B,CAAC,EACA,YAAY;AACjB,CAAC,EACA,YAAY;AAEf,IAAM,kBAAkB,EACrB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,MAAM;AAAA,EACtB,SAAS,EACN,OAAO;AAAA,IACN,SAAS,EAAE,MAAM,EAAE,QAAQ,CAAC;AAAA,EAC9B,CAAC,EACA,YAAY;AACjB,CAAC,EACA,YAAY;AAEf,IAAM,oBAAoB,EACvB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,QAAQ;AAAA,EACxB,SAAS,EAAE,OAAO;AAAA,EAClB,UAAU,EAAE,QAAQ,EAAE,SAAS;AACjC,CAAC,EACA,YAAY;AAKf,IAAM,kBAAkB,EACrB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,YAAY;AAAA,EAC5B,MAAM,EAAE,OAAO;AACjB,CAAC,EACA,YAAY;AAEf,IAAM,sBAAsB,EACzB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,gBAAgB;AAAA,EAChC,UAAU,EAAE,OAAO;AACrB,CAAC,EACA,YAAY;AAEf,IAAM,uBAAuB,EAC1B,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,iBAAiB;AAAA,EACjC,WAAW,EAAE,OAAO;AACtB,CAAC,EACA,YAAY;AAER,IAAM,0BAA0B,EACpC,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,qBAAqB;AAAA,EACrC,OAAO,EAAE,OAAO;AAAA,EAChB,OAAO,EAAE,mBAAmB,QAAQ;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACH,CAAC,EACA,YAAY;AAEf,IAAM,oBAAoB,EACvB,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,cAAc;AAAA,EAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,EAAE,YAAY;AACpD,CAAC,EACA,YAAY;AAMf,IAAM,0BAA0B,EAC7B,OAAO;AAAA,EACN,SAAS,EAAE,QAAQ,cAAc;AAAA,EACjC,WAAW,EAAE,OAAO;AAAA,EACpB,OAAO,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AACzC,CAAC,EACA,YAAY;AAER,IAAM,4BAA4B,EACtC,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,iBAAiB;AAAA,EACjC,YAAY,EAAE,OAAO;AAAA,EACrB,SAAS;AACX,CAAC,EACA,YAAY;AAGR,IAAM,6BAA6B,EACvC,OAAO;AAAA,EACN,MAAM,EAAE,QAAQ,kBAAkB;AAAA,EAClC,UAAU,EACP,OAAO;AAAA,IACN,SAAS,EAAE,QAAQ,SAAS;AAAA,IAC5B,YAAY,EAAE,OAAO;AAAA,IACrB,UAAU,EAAE,mBAAmB,YAAY;AAAA,MACzC,EACG,OAAO;AAAA,QACN,UAAU,EAAE,QAAQ,OAAO;AAAA,QAC3B,cAAc,EAAE,OAAO,EAAE,OAAO,GAAG,EAAE,QAAQ,CAAC;AAAA,MAChD,CAAC,EACA,YAAY;AAAA,MACf,EACG,OAAO;AAAA,QACN,UAAU,EAAE,QAAQ,MAAM;AAAA,QAC1B,SAAS,EAAE,OAAO;AAAA,MACpB,CAAC,EACA,YAAY;AAAA,IACjB,CAAC;AAAA,EACH,CAAC,EACA,YAAY;AACjB,CAAC,EACA,YAAY;AAKR,IAAM,wBAAwB,EAAE,mBAAmB,QAAQ;AAAA,EAChE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAKM,IAAM,sBAA2C,oBAAI,IAAI,CAAC,UAAU,kBAAkB,CAAC;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/common/config.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { dirname, isAbsolute } from \"node:path\";\nimport { z } from \"zod\";\nimport { CONFIG_PATH, PROFILE_NAME, defaultHookPortForProfile } from \"./paths.js\";\nimport { serviceLogger } from \"./logger.js\";\nimport { VALID_LOG_LEVELS, loadProxyRuntimeEnv } from \"./runtime-env.js\";\nimport type { ProviderId } from \"../providers/types.js\";\n\nexport type { LogLevel } from \"./runtime-env.js\";\n\nexport interface ProxyConfig {\n profileName: string;\n relayName: string;\n relayUrl?: string;\n // /proxy 端点的预共享 token, 和 relay 侧 RELAY_PROXY_TOKEN 对应. 公网 relay 必须设置\n relayToken?: string;\n hookPort?: number;\n claudeBin?: string;\n codexBin?: string;\n previewRoots: string[];\n agentCliSuggestions: Record<ProviderId, string[]>;\n sources: {\n relayName: \"cli\" | \"profile\";\n relayUrl: \"env\" | \"file\" | \"none\";\n relayToken: \"env\" | \"file\" | \"none\";\n hookPort: \"env\" | \"default\";\n claudeBin: \"env\" | \"file\" | \"none\";\n codexBin: \"env\" | \"file\" | \"none\";\n };\n}\n\nconst LogLevelSchema = z.enum(VALID_LOG_LEVELS);\n// LogLevel 由 runtime-env.ts 定义并 re-export,schema 用同一组字面量保证两边对齐。\n\nconst RelayTargetSchema = z\n .object({\n url: z.string().optional(),\n proxyToken: z.string().optional(),\n })\n .strict();\n\nconst ProxyProfileSchema = z\n .object({\n relay: z.string().optional(),\n })\n .strict();\n\nconst AgentCliSchema = z\n .object({\n claudeBin: z.string().optional(),\n codexBin: z.string().optional(),\n claudeBinHistory: z.array(z.string()).optional(),\n codexBinHistory: z.array(z.string()).optional(),\n })\n .strict();\n\n// .strict() 在顶层捕获拼错的字段(\"relayss\" / \"profile\"),但 profiles/relays 内部\n// 是 record(用户定义键),不限制键名。\nconst ProxyConfigFileSchema = z\n .object({\n defaultProfile: z.string().optional(),\n profiles: z.record(z.string(), ProxyProfileSchema),\n relays: z.record(z.string(), RelayTargetSchema),\n agentCli: AgentCliSchema.optional(),\n previewRoots: z.array(z.string()).optional(),\n logLevel: LogLevelSchema.optional(),\n })\n .strict();\n\ntype ProxyConfigFile = z.infer<typeof ProxyConfigFileSchema>;\ntype RelayTargetConfig = z.infer<typeof RelayTargetSchema>;\ntype AgentCliConfig = z.infer<typeof AgentCliSchema>;\n\nfunction readConfigFile(): ProxyConfigFile {\n if (!existsSync(CONFIG_PATH)) {\n throw new Error(`Dev Anywhere config not found at ${CONFIG_PATH}. Run \"dev-anywhere init\".`);\n }\n let raw: unknown;\n try {\n raw = JSON.parse(readFileSync(CONFIG_PATH, \"utf-8\"));\n } catch (err) {\n throw new Error(\n `${CONFIG_PATH} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n { cause: err },\n );\n }\n const parsed = ProxyConfigFileSchema.safeParse(raw);\n if (!parsed.success) {\n const issues = parsed.error.issues\n .map(\n (issue) => ` ${issue.path.length > 0 ? issue.path.join(\".\") : \"(root)\"}: ${issue.message}`,\n )\n .join(\"\\n\");\n throw new Error(`Invalid config at ${CONFIG_PATH}:\\n${issues}`);\n }\n return parsed.data;\n}\n\nfunction agentCliField(provider: ProviderId): \"claudeBin\" | \"codexBin\" {\n return provider === \"claude\" ? \"claudeBin\" : \"codexBin\";\n}\n\nfunction agentCliHistoryField(provider: ProviderId): \"claudeBinHistory\" | \"codexBinHistory\" {\n return provider === \"claude\" ? \"claudeBinHistory\" : \"codexBinHistory\";\n}\n\nfunction validateAgentCliPath(path: string): string {\n const normalized = path.trim();\n if (!normalized) throw new Error(\"请输入 CLI 路径\");\n if (!isAbsolute(normalized)) throw new Error(\"CLI 路径必须是绝对路径\");\n return normalized;\n}\n\nfunction uniqueAbsolutePaths(paths: Array<string | undefined>): string[] {\n const seen = new Set<string>();\n const result: string[] = [];\n for (const path of paths) {\n const normalized = path?.trim();\n if (!normalized || !isAbsolute(normalized) || seen.has(normalized)) continue;\n seen.add(normalized);\n result.push(normalized);\n }\n return result;\n}\n\nfunction resolveRelayConfig(\n fromFile: ProxyConfigFile,\n requestedRelayName?: string,\n): {\n relayName: string;\n relayNameSource: ProxyConfig[\"sources\"][\"relayName\"];\n relay: RelayTargetConfig;\n} {\n const profile = fromFile.profiles[PROFILE_NAME];\n if (!profile) {\n const available = Object.keys(fromFile.profiles).sort();\n throw new Error(\n `Unknown profile \"${PROFILE_NAME}\". Available profiles: ${available.length > 0 ? available.join(\", \") : \"(none)\"}`,\n );\n }\n\n const relayName = requestedRelayName?.trim() || profile.relay?.trim();\n if (!relayName) {\n throw new Error(`Profile \"${PROFILE_NAME}\" must specify a relay.`);\n }\n\n const relay = fromFile.relays[relayName];\n if (!relay) {\n const available = Object.keys(fromFile.relays).sort();\n throw new Error(\n `Unknown relay \"${relayName}\". Available relays: ${available.length > 0 ? available.join(\", \") : \"(none)\"}`,\n );\n }\n\n return {\n relayName,\n relayNameSource: requestedRelayName?.trim() ? \"cli\" : \"profile\",\n relay,\n };\n}\n\nexport function loadConfig(options?: { relayName?: string }): ProxyConfig {\n const env = loadProxyRuntimeEnv();\n const fromFile = readConfigFile();\n const agentCli = fromFile.agentCli ?? {};\n const resolved = resolveRelayConfig(fromFile, options?.relayName);\n const claudeBin = env.claudeBin ?? agentCli.claudeBin;\n const codexBin = env.codexBin ?? agentCli.codexBin;\n const config: ProxyConfig = {\n profileName: PROFILE_NAME,\n relayName: resolved.relayName,\n relayUrl: env.relayUrl ?? resolved.relay.url,\n relayToken: env.relayProxyToken ?? resolved.relay.proxyToken,\n hookPort: env.hookPort ?? defaultHookPortForProfile(PROFILE_NAME),\n claudeBin,\n codexBin,\n previewRoots: uniqueAbsolutePaths(fromFile.previewRoots ?? []),\n agentCliSuggestions: {\n claude: uniqueAbsolutePaths([\n env.claudeBin,\n agentCli.claudeBin,\n ...(agentCli.claudeBinHistory ?? []),\n ]),\n codex: uniqueAbsolutePaths([\n env.codexBin,\n agentCli.codexBin,\n ...(agentCli.codexBinHistory ?? []),\n ]),\n },\n sources: {\n relayName: resolved.relayNameSource,\n relayUrl: env.relayUrl ? \"env\" : resolved.relay.url ? \"file\" : \"none\",\n relayToken: env.relayProxyToken ? \"env\" : resolved.relay.proxyToken ? \"file\" : \"none\",\n hookPort: env.hookPort !== undefined ? \"env\" : \"default\",\n claudeBin: env.claudeBin ? \"env\" : agentCli.claudeBin ? \"file\" : \"none\",\n codexBin: env.codexBin ? \"env\" : agentCli.codexBin ? \"file\" : \"none\",\n },\n };\n\n serviceLogger.info(\n {\n profile: config.profileName,\n relayName: config.relayName,\n relayNameSource: config.sources.relayName,\n relayUrl: config.relayUrl ?? \"(unset)\",\n relayUrlSource: config.sources.relayUrl,\n relayTokenSource: config.sources.relayToken,\n hookPort: config.hookPort,\n hookPortSource: config.sources.hookPort,\n claudeBinSource: config.sources.claudeBin,\n codexBinSource: config.sources.codexBin,\n },\n \"Config loaded\",\n );\n\n return config;\n}\n\nexport function buildProviderEnv(\n config: ProxyConfig,\n baseEnv: NodeJS.ProcessEnv = process.env,\n): NodeJS.ProcessEnv {\n return {\n ...baseEnv,\n ...(config.claudeBin ? { CLAUDE_BIN: config.claudeBin } : {}),\n ...(config.codexBin ? { CODEX_BIN: config.codexBin } : {}),\n };\n}\n\nfunction updateAgentCliConfig(\n config: AgentCliConfig,\n provider: ProviderId,\n path: string,\n): AgentCliConfig {\n const field = agentCliField(provider);\n const historyField = agentCliHistoryField(provider);\n const history = uniqueAbsolutePaths([path, ...(config[historyField] ?? [])]).slice(0, 8);\n return {\n ...config,\n [field]: path,\n [historyField]: history,\n };\n}\n\nexport function saveAgentCliPath(provider: ProviderId, path: string): void {\n const normalized = validateAgentCliPath(path);\n const fromFile = readConfigFile();\n fromFile.agentCli = updateAgentCliConfig(fromFile.agentCli ?? {}, provider, normalized);\n mkdirSync(dirname(CONFIG_PATH), { recursive: true });\n writeFileSync(CONFIG_PATH, `${JSON.stringify(fromFile, null, 2)}\\n`, \"utf-8\");\n}\n"],"mappings":";;;;;;;;;;;;;AAAA,SAAS,YAAY,WAAW,cAAc,qBAAqB;AACnE,SAAS,SAAS,kBAAkB;AACpC,SAAS,SAAS;AA6BlB,IAAM,iBAAiB,EAAE,KAAK,gBAAgB;AAG9C,IAAM,oBAAoB,EACvB,OAAO;AAAA,EACN,KAAK,EAAE,OAAO,EAAE,SAAS;AAAA,EACzB,YAAY,EAAE,OAAO,EAAE,SAAS;AAClC,CAAC,EACA,OAAO;AAEV,IAAM,qBAAqB,EACxB,OAAO;AAAA,EACN,OAAO,EAAE,OAAO,EAAE,SAAS;AAC7B,CAAC,EACA,OAAO;AAEV,IAAM,iBAAiB,EACpB,OAAO;AAAA,EACN,WAAW,EAAE,OAAO,EAAE,SAAS;AAAA,EAC/B,UAAU,EAAE,OAAO,EAAE,SAAS;AAAA,EAC9B,kBAAkB,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAC/C,iBAAiB,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAChD,CAAC,EACA,OAAO;AAIV,IAAM,wBAAwB,EAC3B,OAAO;AAAA,EACN,gBAAgB,EAAE,OAAO,EAAE,SAAS;AAAA,EACpC,UAAU,EAAE,OAAO,EAAE,OAAO,GAAG,kBAAkB;AAAA,EACjD,QAAQ,EAAE,OAAO,EAAE,OAAO,GAAG,iBAAiB;AAAA,EAC9C,UAAU,eAAe,SAAS;AAAA,EAClC,cAAc,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS;AAAA,EAC3C,UAAU,eAAe,SAAS;AACpC,CAAC,EACA,OAAO;AAMV,SAAS,iBAAkC;AACzC,MAAI,CAAC,WAAW,WAAW,GAAG;AAC5B,UAAM,IAAI,MAAM,oCAAoC,WAAW,4BAA4B;AAAA,EAC7F;AACA,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,aAAa,aAAa,OAAO,CAAC;AAAA,EACrD,SAAS,KAAK;AACZ,UAAM,IAAI;AAAA,MACR,GAAG,WAAW,uBAAuB,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAAA,MACrF,EAAE,OAAO,IAAI;AAAA,IACf;AAAA,EACF;AACA,QAAM,SAAS,sBAAsB,UAAU,GAAG;AAClD,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,SAAS,OAAO,MAAM,OACzB;AAAA,MACC,CAAC,UAAU,KAAK,MAAM,KAAK,SAAS,IAAI,MAAM,KAAK,KAAK,GAAG,IAAI,QAAQ,KAAK,MAAM,OAAO;AAAA,IAC3F,EACC,KAAK,IAAI;AACZ,UAAM,IAAI,MAAM,qBAAqB,WAAW;AAAA,EAAM,MAAM,EAAE;AAAA,EAChE;AACA,SAAO,OAAO;AAChB;AAEA,SAAS,cAAc,UAAgD;AACrE,SAAO,aAAa,WAAW,cAAc;AAC/C;AAEA,SAAS,qBAAqB,UAA8D;AAC1F,SAAO,aAAa,WAAW,qBAAqB;AACtD;AAEA,SAAS,qBAAqB,MAAsB;AAClD,QAAM,aAAa,KAAK,KAAK;AAC7B,MAAI,CAAC,WAAY,OAAM,IAAI,MAAM,qCAAY;AAC7C,MAAI,CAAC,WAAW,UAAU,EAAG,OAAM,IAAI,MAAM,4DAAe;AAC5D,SAAO;AACT;AAEA,SAAS,oBAAoB,OAA4C;AACvE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAmB,CAAC;AAC1B,aAAW,QAAQ,OAAO;AACxB,UAAM,aAAa,MAAM,KAAK;AAC9B,QAAI,CAAC,cAAc,CAAC,WAAW,UAAU,KAAK,KAAK,IAAI,UAAU,EAAG;AACpE,SAAK,IAAI,UAAU;AACnB,WAAO,KAAK,UAAU;AAAA,EACxB;AACA,SAAO;AACT;AAEA,SAAS,mBACP,UACA,oBAKA;AACA,QAAM,UAAU,SAAS,SAAS,YAAY;AAC9C,MAAI,CAAC,SAAS;AACZ,UAAM,YAAY,OAAO,KAAK,SAAS,QAAQ,EAAE,KAAK;AACtD,UAAM,IAAI;AAAA,MACR,oBAAoB,YAAY,0BAA0B,UAAU,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI,QAAQ;AAAA,IAClH;AAAA,EACF;AAEA,QAAM,YAAY,oBAAoB,KAAK,KAAK,QAAQ,OAAO,KAAK;AACpE,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,YAAY,YAAY,yBAAyB;AAAA,EACnE;AAEA,QAAM,QAAQ,SAAS,OAAO,SAAS;AACvC,MAAI,CAAC,OAAO;AACV,UAAM,YAAY,OAAO,KAAK,SAAS,MAAM,EAAE,KAAK;AACpD,UAAM,IAAI;AAAA,MACR,kBAAkB,SAAS,wBAAwB,UAAU,SAAS,IAAI,UAAU,KAAK,IAAI,IAAI,QAAQ;AAAA,IAC3G;AAAA,EACF;AAEA,SAAO;AAAA,IACL;AAAA,IACA,iBAAiB,oBAAoB,KAAK,IAAI,QAAQ;AAAA,IACtD;AAAA,EACF;AACF;AAEO,SAAS,WAAW,SAA+C;AACxE,QAAM,MAAM,oBAAoB;AAChC,QAAM,WAAW,eAAe;AAChC,QAAM,WAAW,SAAS,YAAY,CAAC;AACvC,QAAM,WAAW,mBAAmB,UAAU,SAAS,SAAS;AAChE,QAAM,YAAY,IAAI,aAAa,SAAS;AAC5C,QAAM,WAAW,IAAI,YAAY,SAAS;AAC1C,QAAM,SAAsB;AAAA,IAC1B,aAAa;AAAA,IACb,WAAW,SAAS;AAAA,IACpB,UAAU,IAAI,YAAY,SAAS,MAAM;AAAA,IACzC,YAAY,IAAI,mBAAmB,SAAS,MAAM;AAAA,IAClD,UAAU,IAAI,YAAY,0BAA0B,YAAY;AAAA,IAChE;AAAA,IACA;AAAA,IACA,cAAc,oBAAoB,SAAS,gBAAgB,CAAC,CAAC;AAAA,IAC7D,qBAAqB;AAAA,MACnB,QAAQ,oBAAoB;AAAA,QAC1B,IAAI;AAAA,QACJ,SAAS;AAAA,QACT,GAAI,SAAS,oBAAoB,CAAC;AAAA,MACpC,CAAC;AAAA,MACD,OAAO,oBAAoB;AAAA,QACzB,IAAI;AAAA,QACJ,SAAS;AAAA,QACT,GAAI,SAAS,mBAAmB,CAAC;AAAA,MACnC,CAAC;AAAA,IACH;AAAA,IACA,SAAS;AAAA,MACP,WAAW,SAAS;AAAA,MACpB,UAAU,IAAI,WAAW,QAAQ,SAAS,MAAM,MAAM,SAAS;AAAA,MAC/D,YAAY,IAAI,kBAAkB,QAAQ,SAAS,MAAM,aAAa,SAAS;AAAA,MAC/E,UAAU,IAAI,aAAa,SAAY,QAAQ;AAAA,MAC/C,WAAW,IAAI,YAAY,QAAQ,SAAS,YAAY,SAAS;AAAA,MACjE,UAAU,IAAI,WAAW,QAAQ,SAAS,WAAW,SAAS;AAAA,IAChE;AAAA,EACF;AAEA,gBAAc;AAAA,IACZ;AAAA,MACE,SAAS,OAAO;AAAA,MAChB,WAAW,OAAO;AAAA,MAClB,iBAAiB,OAAO,QAAQ;AAAA,MAChC,UAAU,OAAO,YAAY;AAAA,MAC7B,gBAAgB,OAAO,QAAQ;AAAA,MAC/B,kBAAkB,OAAO,QAAQ;AAAA,MACjC,UAAU,OAAO;AAAA,MACjB,gBAAgB,OAAO,QAAQ;AAAA,MAC/B,iBAAiB,OAAO,QAAQ;AAAA,MAChC,gBAAgB,OAAO,QAAQ;AAAA,IACjC;AAAA,IACA;AAAA,EACF;AAEA,SAAO;AACT;AAEO,SAAS,iBACd,QACA,UAA6B,QAAQ,KAClB;AACnB,SAAO;AAAA,IACL,GAAG;AAAA,IACH,GAAI,OAAO,YAAY,EAAE,YAAY,OAAO,UAAU,IAAI,CAAC;AAAA,IAC3D,GAAI,OAAO,WAAW,EAAE,WAAW,OAAO,SAAS,IAAI,CAAC;AAAA,EAC1D;AACF;AAEA,SAAS,qBACP,QACA,UACA,MACgB;AAChB,QAAM,QAAQ,cAAc,QAAQ;AACpC,QAAM,eAAe,qBAAqB,QAAQ;AAClD,QAAM,UAAU,oBAAoB,CAAC,MAAM,GAAI,OAAO,YAAY,KAAK,CAAC,CAAE,CAAC,EAAE,MAAM,GAAG,CAAC;AACvF,SAAO;AAAA,IACL,GAAG;AAAA,IACH,CAAC,KAAK,GAAG;AAAA,IACT,CAAC,YAAY,GAAG;AAAA,EAClB;AACF;AAEO,SAAS,iBAAiB,UAAsB,MAAoB;AACzE,QAAM,aAAa,qBAAqB,IAAI;AAC5C,QAAM,WAAW,eAAe;AAChC,WAAS,WAAW,qBAAqB,SAAS,YAAY,CAAC,GAAG,UAAU,UAAU;AACtF,YAAU,QAAQ,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,gBAAc,aAAa,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,GAAM,OAAO;AAC9E;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/relay-token.ts"],"sourcesContent":["// `dev-anywhere relay token` 实现:用本地 proxyToken 向已配置的 relay\n// 请求当前生效的 client token,避免运维者必须 ssh 到 VPS 读 .env。\nimport { loadConfig } from \"./common/config.js\";\n\ninterface FetchClientTokenResult {\n status: \"ok\" | \"no_client_token\";\n clientToken?: string;\n}\n\nfunction toHttpUrl(relayUrl: string): string {\n return relayUrl.replace(/^ws:/i, \"http:\").replace(/^wss:/i, \"https:\").replace(/\\/$/, \"\");\n}\n\nexport async function runRelayTokenCommand(options: { relayName?: string }): Promise<void> {\n let config: ReturnType<typeof loadConfig>;\n try {\n config = loadConfig({ relayName: options.relayName });\n } catch (err) {\n console.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n\n const { relayName, relayUrl, relayToken } = config;\n if (!relayUrl) {\n console.error(\n `Relay \"${relayName}\" has no URL configured. Edit ~/.dev-anywhere/config.json or set RELAY_URL.`,\n );\n process.exit(1);\n }\n if (!relayToken) {\n console.error(\n `Relay \"${relayName}\" has no proxy token configured. The admin endpoint requires one.`,\n );\n process.exit(1);\n }\n\n const adminUrl = `${toHttpUrl(relayUrl)}/admin/client-token`;\n let res: Response;\n try {\n res = await fetch(adminUrl, {\n headers: { authorization: `Bearer ${relayToken}` },\n cache: \"no-store\",\n });\n } catch (err) {\n console.error(`Request to ${adminUrl} failed: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n\n if (res.status === 401) {\n console.error(\n `Relay rejected the proxy token (HTTP 401). Verify ~/.dev-anywhere/config.json matches the relay's RELAY_PROXY_TOKEN.`,\n );\n process.exit(1);\n }\n if (res.status === 204) {\n const result: FetchClientTokenResult = { status: \"no_client_token\" };\n printResult(relayName, relayUrl, result);\n return;\n }\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n console.error(`Unexpected response from relay (HTTP ${res.status}): ${body}`);\n process.exit(1);\n }\n\n const json = (await res.json()) as { clientToken?: unknown };\n if (typeof json.clientToken !== \"string\" || json.clientToken.length === 0) {\n console.error(`Relay returned a malformed payload: ${JSON.stringify(json)}`);\n process.exit(1);\n }\n printResult(relayName, relayUrl, { status: \"ok\", clientToken: json.clientToken });\n}\n\nfunction printResult(relayName: string, relayUrl: string, result: FetchClientTokenResult): void {\n const httpBase = toHttpUrl(relayUrl);\n console.log(`Relay: ${relayName} (${relayUrl})`);\n if (result.status === \"no_client_token\") {\n console.log(`Token: (not configured — /client endpoint is open)`);\n console.log(`URL: ${httpBase}/`);\n return;\n }\n console.log(`Token: ${result.clientToken}`);\n console.log(`URL: ${httpBase}/?relayToken=${encodeURIComponent(result.clientToken!)}`);\n}\n"],"mappings":";;;;;;;;AASA,SAAS,UAAU,UAA0B;AAC3C,SAAO,SAAS,QAAQ,SAAS,OAAO,EAAE,QAAQ,UAAU,QAAQ,EAAE,QAAQ,OAAO,EAAE;AACzF;AAEA,eAAsB,qBAAqB,SAAgD;AACzF,MAAI;AACJ,MAAI;AACF,aAAS,WAAW,EAAE,WAAW,QAAQ,UAAU,CAAC;AAAA,EACtD,SAAS,KAAK;AACZ,YAAQ,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC;AAC9D,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,WAAW,UAAU,WAAW,IAAI;AAC5C,MAAI,CAAC,UAAU;AACb,YAAQ;AAAA,MACN,UAAU,SAAS;AAAA,IACrB;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,CAAC,YAAY;AACf,YAAQ;AAAA,MACN,UAAU,SAAS;AAAA,IACrB;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,WAAW,GAAG,UAAU,QAAQ,CAAC;AACvC,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,UAAU;AAAA,MAC1B,SAAS,EAAE,eAAe,UAAU,UAAU,GAAG;AAAA,MACjD,OAAO;AAAA,IACT,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,YAAQ,MAAM,cAAc,QAAQ,YAAY,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAC1F,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,IAAI,WAAW,KAAK;AACtB,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,IAAI,WAAW,KAAK;AACtB,UAAM,SAAiC,EAAE,QAAQ,kBAAkB;AACnE,gBAAY,WAAW,UAAU,MAAM;AACvC;AAAA,EACF;AACA,MAAI,CAAC,IAAI,IAAI;AACX,UAAM,OAAO,MAAM,IAAI,KAAK,EAAE,MAAM,MAAM,EAAE;AAC5C,YAAQ,MAAM,wCAAwC,IAAI,MAAM,MAAM,IAAI,EAAE;AAC5E,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,MAAI,OAAO,KAAK,gBAAgB,YAAY,KAAK,YAAY,WAAW,GAAG;AACzE,YAAQ,MAAM,uCAAuC,KAAK,UAAU,IAAI,CAAC,EAAE;AAC3E,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,cAAY,WAAW,UAAU,EAAE,QAAQ,MAAM,aAAa,KAAK,YAAY,CAAC;AAClF;AAEA,SAAS,YAAY,WAAmB,UAAkB,QAAsC;AAC9F,QAAM,WAAW,UAAU,QAAQ;AACnC,UAAQ,IAAI,UAAU,SAAS,KAAK,QAAQ,GAAG;AAC/C,MAAI,OAAO,WAAW,mBAAmB;AACvC,YAAQ,IAAI,yDAAoD;AAChE,YAAQ,IAAI,UAAU,QAAQ,GAAG;AACjC;AAAA,EACF;AACA,UAAQ,IAAI,UAAU,OAAO,WAAW,EAAE;AAC1C,UAAQ,IAAI,UAAU,QAAQ,gBAAgB,mBAAmB,OAAO,WAAY,CAAC,EAAE;AACzF;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/terminal.ts","../src/terminal/tty.ts","../src/terminal/pty-manager.ts","../src/terminal/cwd.ts","../src/terminal/state.ts"],"sourcesContent":["import { connect, type Socket } from \"node:net\";\nimport { existsSync, unlinkSync } from \"node:fs\";\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 pkg from \"@xterm/headless\";\nconst { Terminal: HeadlessTerminal } = pkg;\nimport { SerializeAddon } from \"@xterm/addon-serialize\";\nimport { UnicodeGraphemesAddon } from \"@xterm/addon-unicode-graphemes\";\nimport {\n extractOscSequences,\n extractOscSignals,\n type PtySemanticState,\n} from \"./common/osc-extractor.js\";\nimport {\n shouldReleaseApprovalWait,\n stateAfterApprovalRelease,\n} from \"./common/pty-approval-state.js\";\nimport { TerminalState, TERMINAL_TRANSITIONS, createExitHandler } from \"./terminal/state.js\";\nimport {\n SOCK_PATH,\n STOPPED_PATH,\n SERVICE_LOG_PATH,\n PROFILE_NAME,\n tildify,\n} from \"./common/paths.js\";\nimport { spawnScript } from \"./common/env.js\";\nimport { daemonRelayArgs } from \"./common/daemon-env.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 { createFSM } from \"./common/state-machine.js\";\nimport {\n CLAUDE_PROVIDER,\n CODEX_PROVIDER,\n type ProviderAdapter,\n type ProviderHookContext,\n type ProviderId,\n} from \"./providers/index.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// 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\nfunction 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\nasync 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\nfunction 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\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 idleCheckTimer: NodeJS.Timeout | 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 连刷;初值 null 让首次状态(无论真假)都打,启动时提示 remote viewing 是否就绪\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 getIdleCheckTimer: () => this.idleCheckTimer,\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 if (signal?.state === \"approval_wait\") {\n this.currentPtyState = \"approval_wait\";\n this.sendPtyState(\"approval_wait\", { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (\n shouldReleaseApprovalWait({\n currentState: this.currentPtyState,\n signalState: signal?.state,\n })\n ) {\n const nextState = stateAfterApprovalRelease(signal?.state);\n this.currentPtyState = nextState;\n this.sendPtyState(nextState, { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (this.currentPtyState === \"approval_wait\" && signal?.state !== \"turn_complete\") {\n this.sendPtyState(\"approval_wait\", { title: signal?.title, tool: signal?.tool });\n return;\n }\n if (signal && signal.state !== \"working\") {\n this.currentPtyState = signal.state;\n this.sendPtyState(signal.state, { title: signal.title, tool: signal.tool });\n return;\n }\n if (this.currentPtyState !== \"working\") {\n this.currentPtyState = \"working\";\n this.sendPtyState(\"working\");\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(this.socket, (msg: IpcMessage) => {\n if (msg.type === \"pty_input\" && msg.sessionId === this.sessionId) {\n log.debug({ sessionId: this.sessionId, bytes: msg.data.length }, \"Remote input received\");\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\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 if (this.idleCheckTimer) clearInterval(this.idleCheckTimer);\n this.idleCheckTimer = setInterval(() => {\n if (this.lastOutputTime > 0 && Date.now() - this.lastOutputTime > IDLE_THRESHOLD_MS) {\n this.lastOutputTime = 0;\n if (this.currentPtyState === \"working\") {\n this.currentPtyState = \"turn_complete\";\n this.sendPtyState(\"turn_complete\");\n }\n }\n }, IDLE_CHECK_INTERVAL_MS);\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 = 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 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 窗口合并快速连续的尺寸变化\n let resizeTimer: ReturnType<typeof setTimeout> | null = null;\n this.stdout.on(\"resize\", () => {\n if (resizeTimer) clearTimeout(resizeTimer);\n resizeTimer = setTimeout(() => {\n const { cols: newCols, rows: newRows } = readTtySize(this.stdout);\n child.resize(newCols, newRows);\n this.onResize?.(newCols, newRows);\n }, 50);\n });\n\n // 子进程退出,按 Unix 惯例处理信号退出码,通过回调通知调用方\n child.onExit(({ exitCode, signal }) => {\n if (isInteractive) {\n try {\n this.stdin.setRawMode(false);\n } catch {\n // stdin 可能已关闭\n }\n }\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 cleanup(exitCode: number): void {\n if (this.stdin.isTTY) {\n try {\n this.stdin.setRawMode(false);\n } catch {\n // stdin 可能已关闭\n }\n }\n restoreHostTerminalModes(this.stdout);\n if (this.child) {\n try {\n this.child.kill();\n } catch {\n // 子进程可能已退出\n }\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 type { Socket } from \"node:net\";\nimport { createFSM } from \"../common/state-machine.js\";\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// idleCheckTimer 在 setupIdleCheck 跑完才赋值。直接传值只会记录 handler 构造那一刻的旧值。\ninterface ExitHandlerDeps {\n fsm: ReturnType<typeof createFSM<TerminalState>>;\n getSocket: () => Socket;\n getSessionId: () => string | null;\n getIdleCheckTimer: () => NodeJS.Timeout | null;\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 const timer = deps.getIdleCheckTimer();\n if (timer) clearInterval(timer);\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":[]}