@syrkos/filaxis 0.1.0-alpha.10

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 (85) hide show
  1. package/AGENTS.md +46 -0
  2. package/LICENSE +202 -0
  3. package/NOTICE +10 -0
  4. package/README.md +61 -0
  5. package/bin/filaxis.ts +112 -0
  6. package/dist/agent-toolkit.d.ts +99 -0
  7. package/dist/dialect.d.ts +31 -0
  8. package/dist/driver.d.ts +130 -0
  9. package/dist/events.d.ts +26 -0
  10. package/dist/factory.d.ts +16 -0
  11. package/dist/file-edit.d.ts +43 -0
  12. package/dist/file-transfer.d.ts +44 -0
  13. package/dist/index.d.ts +38 -0
  14. package/dist/index.js +4200 -0
  15. package/dist/layer-discovery.d.ts +30 -0
  16. package/dist/layer-env.d.ts +95 -0
  17. package/dist/local-driver.d.ts +47 -0
  18. package/dist/local-session-manager.d.ts +98 -0
  19. package/dist/logger.d.ts +12 -0
  20. package/dist/remote-session-manager.d.ts +64 -0
  21. package/dist/runtime-stack.d.ts +52 -0
  22. package/dist/session-buffer.d.ts +73 -0
  23. package/dist/session-types.d.ts +74 -0
  24. package/dist/shell-discovery.d.ts +4 -0
  25. package/dist/ssh-helpers.d.ts +88 -0
  26. package/dist/ssh-pty-driver.d.ts +74 -0
  27. package/dist/stream-session-manager.d.ts +83 -0
  28. package/dist/stream-window.d.ts +124 -0
  29. package/dist/terminal/command-executer.d.ts +73 -0
  30. package/dist/terminal/output-buffer.d.ts +49 -0
  31. package/dist/terminal/session-launcher.d.ts +28 -0
  32. package/dist/terminal/session-registry.d.ts +29 -0
  33. package/dist/terminal/tmux-invoker.d.ts +8 -0
  34. package/dist/terminal/types.d.ts +56 -0
  35. package/dist/terminal-manager.d.ts +30 -0
  36. package/dist/tmux-driver.d.ts +49 -0
  37. package/dist/vt100-screen.d.ts +46 -0
  38. package/docs/DEVELOPMENT.md +38 -0
  39. package/docs/KNOWN-ISSUES.md +35 -0
  40. package/docs/implementation-notes.md +57 -0
  41. package/docs/skills/cross-host-diagnosis.md +21 -0
  42. package/docs/skills/file-sync.md +22 -0
  43. package/docs/skills/longrun-observe.md +20 -0
  44. package/docs/skills/stream-window.md +28 -0
  45. package/docs//345/256/214/346/225/264/345/267/241/346/243/200/346/212/245/345/221/212.md +141 -0
  46. package/docs//346/265/213/350/257/2252.md +112 -0
  47. package/docs//346/265/213/350/257/2253.md +73 -0
  48. package/docs//346/265/213/350/257/2254.md +138 -0
  49. package/docs//346/265/213/350/257/2255.md +124 -0
  50. package/docs//347/254/254/344/272/214/344/273/275/345/267/241/346/243/200/346/212/245/345/221/212.md +103 -0
  51. package/examples/agent-tools.ts +74 -0
  52. package/examples/cross-host-diagnosis.ts +49 -0
  53. package/examples/longrun-observe.ts +46 -0
  54. package/package.json +47 -0
  55. package/src/agent-toolkit.ts +247 -0
  56. package/src/dialect.ts +100 -0
  57. package/src/driver.ts +142 -0
  58. package/src/events.ts +96 -0
  59. package/src/factory.ts +51 -0
  60. package/src/file-edit.ts +195 -0
  61. package/src/file-transfer.ts +196 -0
  62. package/src/index.ts +38 -0
  63. package/src/layer-discovery.ts +79 -0
  64. package/src/layer-env.ts +331 -0
  65. package/src/local-driver.ts +325 -0
  66. package/src/local-session-manager.ts +397 -0
  67. package/src/logger.ts +19 -0
  68. package/src/remote-session-manager.ts +286 -0
  69. package/src/runtime-stack.ts +128 -0
  70. package/src/session-buffer.ts +183 -0
  71. package/src/session-types.ts +78 -0
  72. package/src/shell-discovery.ts +52 -0
  73. package/src/ssh-helpers.ts +225 -0
  74. package/src/ssh-pty-driver.ts +655 -0
  75. package/src/stream-session-manager.ts +296 -0
  76. package/src/stream-window.ts +309 -0
  77. package/src/terminal/command-executer.ts +374 -0
  78. package/src/terminal/output-buffer.ts +126 -0
  79. package/src/terminal/session-launcher.ts +146 -0
  80. package/src/terminal/session-registry.ts +98 -0
  81. package/src/terminal/tmux-invoker.ts +65 -0
  82. package/src/terminal/types.ts +85 -0
  83. package/src/terminal-manager.ts +83 -0
  84. package/src/tmux-driver.ts +167 -0
  85. package/src/vt100-screen.ts +166 -0
@@ -0,0 +1,138 @@
1
+ Analyzed stream-session-manager.ts:L1-279
2
+ Analyzed remote-session-manager.ts:L1-266
3
+ Created test_observation_window.ts
4
+ Ran run Filaxis observation window test · Exited with code 1
5
+ Edited test_observation_window.ts
6
+ Ran run Filaxis observation window test · Exited with code 1
7
+ Edited test_observation_window.ts
8
+ Ran run Filaxis observation window test · Exited with code 1
9
+ Analyzed index.ts:L1-31
10
+ Analyzed ssh-pty-driver.ts:L1-60
11
+ Edited test_observation_window.ts
12
+ Edited test_observation_window.ts
13
+ Ran run Filaxis observation window test · Exited with code 1
14
+ Analyzed benchmark_raw_vs_filaxis.ts:L1-90
15
+ Edited test_observation_window.ts
16
+ Edited test_observation_window.ts
17
+ Edited test_observation_window.ts
18
+ Ran run Filaxis observation window test · Exited with code 1
19
+
20
+ 已在 `vm-ai-server` (`10.0.20.2`) 上启动了远端常驻任务 `watch -n 1 nvidia-smi`(会话 ID:`gpu-watch-3c529dd6`)并挂载了牵丝(Filaxis)的本地流式观察窗(`observe-gpu-3e0fcd`),正在按时间线采集 **`20~25s`** 与 **`35~40s`** 两个观察窗口的实时快照,预计约 40 秒后完成并输出完整结果。
21
+
22
+ Analyzed longrun-observe.md:L1-20
23
+ Analyzed tmux-driver.ts:L1-158
24
+ Analyzed terminal-manager.ts:L1-83
25
+ Analyzed runtime-stack.ts:L1-128
26
+ Edited test_observation_window.ts
27
+ Ran run Filaxis VT100 observation window test
28
+
29
+ 已针对 `watch -n 1` 的 `ncurses` 增量光标重绘机制(每秒仅输出 `\x1b[row;colH` 变更单元格)补上了 2D VT100 屏幕状态机还原与 `driver.destroy()` 收尾,当前任务正在运行中,等待 40 秒采集完成后为您呈现完整的 **`20~25s`** 与 **`35~40s`** 逐秒轨迹与终端画面快照。
30
+
31
+ Analyzed output.txt:L1-133
32
+
33
+ 这次「**观察窗(手眼分离)**」专项测试已经完整跑通(测试脚本见 [test_observation_window.ts](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/test_observation_window.ts))。
34
+
35
+ 用原汁原味的 `watch -n 1 nvidia-smi` 跑这个测试,不仅完美验证了牵丝(`@syrkos/filaxis`)的**手眼分离**架构,还顺带炸出了一个非常硬核的 **`ncurses` 增量光标重绘流** 细节!
36
+
37
+ ---
38
+
39
+ ### 一、 架构执行时间线(远端常开 vs 本地随看随走)
40
+
41
+ 1. **`T = 0.00s`(手:启动远端常驻会话)**
42
+ - 通过 [`SshPtyDriver.execSession`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/ssh-pty-driver.ts#L502-L504)(底层委托 [`RemoteSessionManager`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/remote-session-manager.ts#L75-L108))在 `vm-ai-server` (`10.0.20.2`) 启动常驻任务:
43
+ - `sessionId`: `gpu-watch-d2f06ad3`(远端 `PID = 72163`)
44
+ - 落盘三件套:`/tmp/filaxis-sessions/gpu-watch-d2f06ad3.{out,err,code}`
45
+ 2. **`T = 0.17s`(眼:挂载本地独立观察流)**
46
+ - 通过 [`StreamSessionManager.start`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/stream-session-manager.ts#L103-L153) 建立独立流式通道 `observe-gpu-ba0b88`,挂载 `onLine` 实时入窗钩子,同时配合 [`driver.readSessionLog`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/ssh-pty-driver.ts#L508-L510) 做完整终端画面还原。
47
+ 3. **`T = 20.00s ~ 25.19s`(抽取第一观察窗)** → 远端进程完全不受干扰。
48
+ 4. **`T = 25.19s ~ 35.00s`(中间 10s 静默期)** → 远端 `watch -n 1 nvidia-smi` 持续常开运行,本地无需重连或重启命令。
49
+ 5. **`T = 35.00s ~ 40.18s`(抽取第二观察窗)** → 再次精确抽取 5 秒窗口。
50
+ 6. **`T = 40.35s`(存活验证与收摊)**
51
+ - 观察结束后核实远端进程树,`PID 72163 -> 72165 -> 72166 (watch -n 1 nvidia-smi)` 依然 100% 零中断存活;
52
+ - 先调用 `streamMgr.stop("observe-gpu-ba0b88")` 关掉本地观察流(“闭眼”),远端任务毫发无损;
53
+ - 最后调用 `driver.sessions.stop("gpu-watch-d2f06ad3")` 完成终态三态核实(返回 `status: "stopped"`)并清理现场。
54
+
55
+ ---
56
+
57
+ ### 二、 【第一观察窗:`20s ~ 25s`】抽取情况
58
+
59
+ #### 1. 逐秒状态轨迹(`01:57:12` → `01:57:17`)
60
+ | 采样时刻 | 实测相对时间 | 远端 `.out` 累计字节 | `watch` 帧头时钟 | GPU 0 (`Tesla P100-PCIE-16GB`) | GPU 1 (`GTX 1080 Ti`) |
61
+ | :--- | :--- | :--- | :--- | :--- | :--- |
62
+ | **`T=20s`** | `20.18s` | `2,668 B` | `Sun Nov 8 01:57:12 2026` | `48C` \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` \| `0%` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` \| `0%` |
63
+ | **`T=21s`** | `21.18s` | `2,708 B` *(+40B)* | `Sun Nov 8 01:57:13 2026` | `49C` *(+1°C)* \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` |
64
+ | **`T=22s`** | `22.17s` | `2,748 B` *(+40B)* | `Sun Nov 8 01:57:14 2026` | `48C` *(-1°C)* \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` |
65
+ | **`T=23s`** | `23.18s` | `2,788 B` *(+40B)* | `Sun Nov 8 01:57:15 2026` | `49C` *(+1°C)* \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` |
66
+ | **`T=24s`** | `24.19s` | `2,828 B` *(+40B)* | `Sun Nov 8 01:57:16 2026` | `48C` *(-1°C)* \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` |
67
+ | **`T=25s`** | `25.19s` | `2,859 B` *(+31B)* | `Sun Nov 8 01:57:17 2026` | `48C` *(不变)* \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` |
68
+
69
+ #### 2. `StreamSessionManager.onLine` 实时捕获的 5 次 `ncurses` 增量刷新包
70
+ 在 `20s ~ 25s` 期间,`StreamSessionManager` 精准收到了 5 次推流事件(每秒 1 次):
71
+ ```text
72
+ • [T=20.86s | stream=stdout] diff="339B" <-- 01:57:1[3] (两处时钟个位变 3) + P100 温度 48C->4[9]C
73
+ • [T=21.89s | stream=stdout] diff="448B" <-- 01:57:1[4] (两处时钟个位变 4) + P100 温度 49C->4[8]C
74
+ • [T=22.92s | stream=stdout] diff="559B" <-- 01:57:1[5] (两处时钟个位变 5) + P100 温度 48C->4[9]C
75
+ • [T=23.95s | stream=stdout] diff="668B" <-- 01:57:1[6] (两处时钟个位变 6) + P100 温度 49C->4[8]C
76
+ • [T=24.99s | stream=stdout] diff="77B" <-- 01:57:1[7] (两处时钟个位变 7,温度未变,仅 31 字节!)
77
+ ```
78
+ > 💡 **细节亮点**:为什么 `watch -n 1 nvidia-smi` 每秒只发 31~40 个字节?因为 `watch` 是 `ncurses` 全屏程序,只在第 `0s` 画了一次完整表格(约 2KB),之后每秒**只通过 `\x1b[row;colH` 光标定位改写屏幕上发生变化的字符**(右上角时钟秒数、表头秒数、以及 P100 在 `48C` ↔ `49C` 之间跳动的那一个数字)!
79
+
80
+ #### 3. 第一窗口末(`T=25s`)完整还原画面
81
+ ```text
82
+ Every 1.0s: nvidia-smi vm-ai: Sun Nov 8 01:57:17 2026
83
+ | NVIDIA-SMI 580.178.04 Driver Version: 580.178.04 CUDA Version: 13.0 |
84
+ |-----------------------------------------+------------------------+----------------------|
85
+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC |
86
+ | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. |
87
+ |=========================================+========================+======================|
88
+ | 0 Tesla P100-PCIE-16GB On | 00000000:02:00.0 Off | 0 |
89
+ | N/A 48C P0 35W / 175W | 13157MiB / 16384MiB | 0% Default |
90
+ +-----------------------------------------+------------------------+----------------------+
91
+ | 1 NVIDIA GeForce GTX 1080 Ti On | 00000000:03:00.0 Off | N/A |
92
+ | 0% 45C P8 8W / 140W | 5MiB / 11264MiB | 0% Default |
93
+ +-----------------------------------------+------------------------+----------------------+
94
+ | Processes: |
95
+ | GPU GI CI PID Type Process name GPU Memory |
96
+ |=========================================================================================|
97
+ | 0 N/A N/A 640 C /usr/local/bin/chordia-server 272MiB |
98
+ | 0 N/A N/A 1313 C /opt/venv/bin/python 12882MiB |
99
+ +-----------------------------------------------------------------------------------------+
100
+ ```
101
+
102
+ ---
103
+
104
+ ### 三、 【第二观察窗:`35s ~ 40s`】抽取情况
105
+
106
+ 在 `25s ~ 35s` 期间远端持续常开,`.out` 字节流从 `2,859 B` 自然增长到 `3,178 B`。`T = 35.00s` 准时切入第二观察窗:
107
+
108
+ #### 1. 逐秒状态轨迹(`01:57:26` → `01:57:31`)
109
+ | 采样时刻 | 实测相对时间 | 远端 `.out` 累计字节 | `watch` 帧头时钟 | GPU 0 (`Tesla P100-PCIE-16GB`) | GPU 1 (`GTX 1080 Ti`) |
110
+ | :--- | :--- | :--- | :--- | :--- | :--- |
111
+ | **`T=35s`** | `35.19s` | `3,178 B` | `Sun Nov 8 01:57:26 2026` | `48C` \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` \| `0%` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` \| `0%` |
112
+ | **`T=36s`** | `36.17s` | `3,218 B` *(+40B)* | `Sun Nov 8 01:57:27 2026` | `48C` \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` \| `0%` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` \| `0%` |
113
+ | **`T=37s`** | `37.18s` | `3,249 B` *(+31B)* | `Sun Nov 8 01:57:28 2026` | `48C` \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` \| `0%` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` \| `0%` |
114
+ | **`T=38s`** | `38.18s` | `3,280 B` *(+31B)* | `Sun Nov 8 01:57:29 2026` | `48C` \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` \| `0%` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` \| `0%` |
115
+ | **`T=39s`** | `39.18s` | `3,313 B` *(+33B)* | `Sun Nov 8 01:57:30 2026` | `48C` \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` \| `0%` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` \| `0%` |
116
+ | **`T=40s`** | `40.18s` | `3,344 B` *(+31B)* | `Sun Nov 8 01:57:31 2026` | `48C` \| `P0` \| `35W / 175W` \| `13157MiB / 16384MiB` \| `0%` | `45C` \| `P8` \| `8W / 140W` \| `5MiB / 11264MiB` \| `0%` |
117
+
118
+ #### 2. `StreamSessionManager.onLine` 实时捕获的 5 次 `ncurses` 增量刷新包
119
+ ```text
120
+ • [T=35.32s | stream=stdout] diff="778B" <-- 01:57:2[7] (两处时钟个位变 7) + P100 温度回落 4[8]C
121
+ • [T=36.35s | stream=stdout] diff="88B" <-- 01:57:2[8] (两处时钟个位变 8,GPU 指标完全稳定)
122
+ • [T=37.38s | stream=stdout] diff="99B" <-- 01:57:2[9] (两处时钟个位变 9,GPU 指标完全稳定)
123
+ • [T=38.42s | stream=stdout] diff="3030B" <-- 01:57:[30] (跨十秒进位!两处时钟同时改写十位+个位 "30")
124
+ • [T=39.45s | stream=stdout] diff="11B" <-- 01:57:3[1] (两处时钟个位变 1)
125
+ ```
126
+
127
+ ---
128
+
129
+ ### 四、 总结与架构启发
130
+
131
+ 1. **为什么说这个场景拿裸 SSH 比是“欺负人”**:
132
+ - 裸 SSH 下,**执行通道与观察通道是绑死的**:你开一个 `ssh vm-ai-server "watch -n 1 nvidia-smi"`,它就占死你的 stdio 流;你想在 `25s` 抽完后去干别的事、`35s` 再回来抽,要么不断流一直自己写状态机攒内存,要么断流重开(丢失中间状态且重新 fork 远端进程)。
133
+ - 而在 **Filaxis(牵丝)** 里:
134
+ - **远端执行生命周期**归 [`RemoteSessionManager`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/remote-session-manager.ts)(远端落盘保底,退出码可核实);
135
+ - **本地观察生命周期**归 [`StreamSessionManager`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/stream-session-manager.ts)(随挂随看,有 `OutputRingBuffer` 热窗兜底,不用时 `stop` 掉观察流或靠 `reapIdle` 注意力衰减自动回收,完全不影响远端长跑任务)。
136
+ 2. **顺带给 Filaxis 未来迭代的一个小灵感**:
137
+ - 目前 [`OutputRingBuffer`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/session-buffer.ts) 对普通按行输出的日志(如训练日志、`tail -f`、构建输出)体验是满分的;
138
+ - 而像 `watch`、`htop`、`nvtop` 这类基于 **`ncurses` 光标定位增量重绘(不输出 `\n`)** 的全屏 TUI 程序,如果未来在 `session-buffer` 或 `terminal` 模块里内置一个可选的轻量级 `Vt100Screen` 虚拟屏幕模式(就像本次测试脚本里加的 50 行小状态机一样),Agent 甚至可以直接对任意全屏 TUI 随时拍出“字符级高清截屏”!
@@ -0,0 +1,124 @@
1
+ Ran download and extract @syrkos/filaxis 0.1.0-alpha.8
2
+ Analyzed filaxis.ts:L1-112
3
+ Analyzed index.ts:L1-38
4
+ Analyzed file-edit.ts:L1-93
5
+ Analyzed stream-window.md:L1-28
6
+ Analyzed agent-toolkit.ts:L1-247
7
+ Analyzed remote-session-manager.ts:L90-150
8
+ Ran reset identical fault labs for alpha.8 benchmark
9
+
10
+ 完全赞同您的严谨修正:上一轮裸 SSH 组写坏文件,本质上是 **Agent 在默认倾向(Default Affordance)下选择了高风险的 `ssh "... sed -i ..."` 单行拼接**,而不是裸 SSH 协议本身的上限(如果 Agent 主动选用 `ssh host "cat > file" < local`、SFTP 或远端 Python/临时脚本,同样可以做到零转义安全修改)。这也正是评测工具层的意义:**看工具层能否通过原语设计,把 Agent 的“默认最省事路径”直接变成“最安全路径”**。
11
+
12
+ 我已拉取并解压 **`@syrkos/filaxis@0.1.0-alpha.8`**(看到了新增的 `bin/filaxis.ts` 原生 CLI、`editFile` 一级导出、`Vt100Screen`、`StreamWindowRegistry` 以及扩充到 6 个原语的 `createAgentToolkit`),目前正在重置 `vm-agent-aethel` 上的双沙盒环境,马上启动第二轮同模型盲测对决。
13
+
14
+ Used tool: invoke_subagent
15
+ Used tool: invoke_subagent
16
+
17
+ 第二轮同模型盲测(基于 **`@syrkos/filaxis@0.1.0-alpha.8`**)已并发启动:
18
+
19
+ - **对照组 A2(裸 SSH 组,显式允许 stdin/heredoc/sftp/远端脚本等安全写文件手段)**:[3139e1c8-5326-460b-b14a-065c7c949745](conversation://3139e1c8-5326-460b-b14a-065c7c949745) → 排障沙盒 `/opt/fault-lab-a`
20
+ - **实验组 B2(Filaxis `0.1.0-alpha.8` 组,使用原生 `bin/filaxis.ts` 与 `editFile`)**:[cd798916-b84b-4e00-b550-7382c3034f91](conversation://cd798916-b84b-4e00-b550-7382c3034f91) → 排障沙盒 `/opt/fault-lab-b`
21
+
22
+ 等待两组完成后立即为您提取并对比第二轮的 `transcript_full.jsonl` 硬件账单。
23
+
24
+ Found 4 subagents
25
+
26
+ 第二轮对照组 A2(裸 SSH 组)已完成排障,实验组 B2(Filaxis `0.1.0-alpha.8` 组)正在执行收尾命令,待其回报后立即为您输出两轮完整对比数据。
27
+
28
+ Created analyze_r2.ts
29
+ Ran analyze Round 2 subagent transcripts and token usage
30
+ Created dump_r2_details.ts
31
+ Ran dump R2 details and clean up fault labs
32
+ Analyzed output.txt:L1-46
33
+
34
+ 第二轮同模型盲测(基于 **`@syrkos/filaxis@0.1.0-alpha.8`**)的硬件账单已经完整跑出!
35
+
36
+ 把两轮共 4 个独立子 Agent 的底层 `transcript_full.jsonl` 放在一起对比,数据变化极其惊艳,同时也验证了您刚才关于“裸 SSH 能力 vs Agent 默认行为倾向”的判断,并顺带在 `0.1.0-alpha.8` 的新 CLI 上抓到了 **3 个可以直接进 `alpha.9` 的实战细节**。
37
+
38
+ ---
39
+
40
+ ### 一、 两轮四组 Agent 硬件级真实账单总表
41
+
42
+ | 评测维度 | 第一轮 A1:裸 SSH ([日志](conversation://f35fa9a1-931f-4959-a59f-582137fdf92e)) | 第二轮 A2:裸 SSH(显式提示可用 stdin/sftp/脚本)([日志](conversation://3139e1c8-5326-460b-b14a-065c7c949745)) | 第一轮 B1:Filaxis `alpha.4`(手拼 `bun -e`)([日志](conversation://8e972fbd-08a1-4369-989a-44cc5282ffdf)) | **第二轮 B2:Filaxis `alpha.8`(原生 `bin/filaxis.ts`)** ([日志](conversation://cd798916-b84b-4e00-b550-7382c3034f91)) | **`alpha.8` 相比 `alpha.4` 降幅** |
43
+ | :--- | :--- | :--- | :--- | :--- | :--- |
44
+ | **模型推理轮次 (Turns)** | 8 轮 | **5 轮** | 19 轮 | **7 轮** | **↓ 63.2%** |
45
+ | **工具调用次数 (Tool Calls)** | 7 次 | **4 次** *(单壳拼 `&&`)* | 16 次 | **12 次** *(含并发)* | **↓ 25.0%** |
46
+ | **新增计费输入 (New Input Tokens)** | 32,975 | **24,688** | 80,471 | **32,346** | **↓ 59.8%** *(已低于 A1)* |
47
+ | **缓存读取输入 (Cache Read Tokens)** | 109,519 | **58,566** | 639,403 | **108,662** | **↓ 83.0%** *(已低于 A1)* |
48
+ | **总上下文消耗 (Total Input)** | 142,494 | **83,254** | 719,874 | **141,008** | **↓ 80.4%** *(与 A1 持平)* |
49
+ | **总输出 Token (Output Tokens)** | 3,314 | **2,077** | 7,894 | **3,494** | **↓ 55.7%** |
50
+ | **首次定位全部根因耗时** | `+48.0s` | **`+8.0s`** | `+65.0s` | **`+15.0s`** *(第 1 轮并发即定位)* | **↓ 76.9%** |
51
+ | **全程总耗时 (Wall Time)** | `130.0s` | **`53.0s`** | `288.0s` | **`156.0s`** | **↓ 45.8%** |
52
+ | **文件修改安全性 / 错误自检** | ❌ `sed -i` 写坏远端文件(重试 3 次) | ⚠️ `sed -i` 静默替换 0 处但返回 `exit 0`(重试 2 次) | ✅ `drv.fs` 一次写对 | ✅ **`filaxis edit` 拦截空转义并报错提示 `oldStr 长度 1`** | **100% 零文件损坏** |
53
+
54
+ ---
55
+
56
+ ### 二、 核心洞察:关于“裸 SSH 能不能安全改文件”的实证回答
57
+
58
+ 您在上一条提到的观点非常精准:**裸 SSH 协议本身完全可以通过 `stdin`、Heredoc、SFTP 或临时脚本安全改文件**。
59
+
60
+ 于是在第二轮给 **对照组 A2(裸 SSH)** 的系统提示词里,我专门加上了显式引导:
61
+ > *“你只能通过 `run_command` 调用标准 `ssh` / `scp` / `sftp`(**可使用任意原生 SSH 技巧,如 stdin 重定向、heredoc、远端脚本等**)完成所有远程排查、日志观察、文件修改与验证。”*
62
+
63
+ 但看看 **对照组 A2 在 Step 3 (`+10.0s`) 的真实选择**:
64
+ - **模型的肌肉记忆(Prior Bias)依然压倒了提示词**:尽管提示词明写了可以用 `stdin` / `heredoc` / `sftp`,模型为了省事(想在一条 `ssh` 命令里同时改两个文件并跑校验),**依然本能地写出了 `sed -i`**:
65
+ ```powershell
66
+ ssh vm-agent-aethel "sed -i 's/^ Up-stream/# Up-stream/; s|certs/ca-empty.crt|certs/ca-prod-2026.crt|' /opt/fault-lab-a/conf/upstream.env && sed -i 's/\"max_batch_size\": -1/\"max_batch_size\": 256/' /opt/fault-lab-a/conf/worker.json && /opt/fault-lab-a/bin/verify-worker.sh"
67
+ ```
68
+ - **发生了什么?—— `sed -i` 的「静默假成功」**:
69
+ - 在 Windows PowerShell 中,双引号字符串内部不能用 `\"` 转义双引号(要用反引号 `` `" ``)。因此 `\"max_batch_size\"` 的引号被吞,远端 `sed` 实际去匹配了不带引号的 `max_batch_size: -1`。
70
+ - 更要命的是:**`sed -i` 在匹配 0 处时,退出码依然是 `0`(静默成功)**!直到后面的 `verify-worker.sh` 报错 `VERIFY_FAIL: worker.json max_batch_size is -1`,A2 才在 Step 5 发了第二次 `sed -i 's/: -1/: 256/'` 补救。
71
+
72
+ **再对比实验组 B2(Filaxis `0.1.0-alpha.8`)在完全相同的 PowerShell `\"` 陷阱下的表现(Step 8 → Result 11)**:
73
+ - B2 在 Step 8 同样犯了 PowerShell `\"` 转义的错:
74
+ ```powershell
75
+ bun.exe .../bin/filaxis.ts edit --host vm-agent-aethel /opt/fault-lab-b/conf/worker.json "\"max_batch_size\": -1" "\"max_batch_size\": 256"
76
+ ```
77
+ - 但因为走的是你在 `0.1.0-alpha.8` 新增的 [`editFile`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/file-edit.ts#L41-L93),**Filaxis 当场以 `exitCode: 1` 拒绝写入,并给出了精确的诊断证据**:
78
+ ```text
79
+ [filaxis] 未命中待替换片段(0 处): /opt/fault-lab-b/conf/worker.json(oldStr 长度 1,文件 114 字符)
80
+ ```
81
+ - Agent B2 一眼看到 `oldStr 长度 1`,立刻明白是外层 Shell 把引号截断了,下一轮直接改成 `": -1,"` → `": 256,"`,精准返回:
82
+ - `edited: /opt/fault-lab-b/conf/upstream.env 替换 40 处,2509B → 2549B`
83
+ - `edited: /opt/fault-lab-b/conf/upstream.env 替换 1 处,2549B → 2553B`
84
+ - `edited: /opt/fault-lab-b/conf/worker.json 替换 1 处,114B → 115B`
85
+
86
+ > **结论**:裸 SSH 不是“不能”安全改文件,而是**裸 SSH 给 Agent 提供的最顺手路径(`sed -i`)是危险且静默吞错的**;而 Filaxis `0.1.0-alpha.8` 的 `filaxis edit` 把“唯一命中校验 + TOCTOU 锁 + Base64 免转义写回 + 字节数变化回执”做成了**最顺手的默认原语**,哪怕 Agent 写错引号,也会被 `oldStr 长度 1` 当场拦住,绝不静默放行或污染文件。
87
+
88
+ ---
89
+
90
+ ### 三、 为什么 `alpha.8` 比裸 SSH 还是慢了约 100 秒?—— 抓出 3 个可进 `0.1.0-alpha.9` 的关键优化点
91
+
92
+ 在 `0.1.0-alpha.8` 中,Agent B2 **0 次阅读源码**,推理轮次已经压到了 **7 轮**(第 1 轮并发 5 条命令就在 `+15s` 拿到了全部根因!)。但为什么总耗时是 `156s`(比 A2 的 `53s` 多了约 100s)?我逐秒拆解了 B2 的日志,定位到了 **3 个非常具体的底层原因**:
93
+
94
+ #### 1. 耗时大头:`editFile` 单次调用触发了 5 次串行 SSH 建连(Windows 无 ControlMaster 下放大)
95
+ 看 [`src/file-edit.ts`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/file-edit.ts#L51-L85) 的实现,执行一次 `filaxis edit` 内部依次调用了:
96
+ 1. `await drv.fs.stat(path)`(第 1 次 `ssh` 进程)
97
+ 2. `await drv.fs.readFile(path)`(第 2 次 `ssh` 进程)
98
+ 3. `await drv.fs.stat(path)`(第 3 次 `ssh` 进程,TOCTOU 锁校验)
99
+ 4. `await drv.fs.writeFile(path, edited)`(第 4 次 `ssh` 进程)
100
+ 5. `await drv.fs.stat(path)`(第 5 次 `ssh` 进程,写后核验)
101
+
102
+ 当 Agent B2 在同一轮并发调用 2~3 个 `filaxis edit` 时,Windows 瞬间向 `vm-agent-aethel` 发起了 **10~15 次串行/并发 SSH 密钥握手**,导致单条 `filaxis edit` 耗时超过了 `run_command` 的 `WaitMsBeforeAsync: 8000`(8 秒阈值),被转入后台任务(`task-10`、`task-14`、`task-15`),光等待后台任务通知就多花了 ~70 秒!
103
+ - **优化建议(`alpha.9`)**:
104
+ - `readFile` 完全可以在一次 `exec` 里顺带拿回 `stat`(或者 `editFile` 在检测到 `drv.exec` 可用时,把“读前 `stat` + `base64` 读取”合成 1 次 SSH 往返,“TOCTOU `stat` 校验 + `base64 -d` 原子写 + 写后 `stat`”合成 1 次 SSH 往返)。
105
+ - 这样每次 `filaxis edit` 从 **5 次 SSH 握手降为 2 次**,耗时直接从 `15s` 降到 `1.5s` 以内!
106
+
107
+ #### 2. `bin/filaxis.ts observe` 在远端 SSH 上漏读了 `stderr`
108
+ 看 [`bin/filaxis.ts`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/bin/filaxis.ts#L76-L86) 和 [`src/agent-toolkit.ts`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/agent-toolkit.ts#L224-L230):
109
+ ```ts
110
+ const lines = drv.sessions?.tailLines?.(h.sessionId, 500)
111
+ ?? (await drv.tailSession(h.sessionId, 500)).map((t) => ({ stream: "stdout", text: t }));
112
+ ```
113
+ 因为 `SshPtyDriver.sessions`(即 [`RemoteSessionManager`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/remote-session-manager.ts))目前只有 `tail()` 和 `readLog(id, { stream: "stderr" })`,**没有实现 `tailLines()` 方法**,所以 `filaxis observe --host ...` 总是走 `??` 分支回退到 `drv.tailSession()`,**只读了 `.out`(stdout),把 `.err`(stderr)丢了**!
114
+ - **优化建议(`alpha.9`)**:在 `RemoteSessionManager` 上补一个 `async tailLines(sessionId, n)`(或者在 `observe` 里同时调 `readSessionLog(id, { stream: "stdout" })` 和 `readSessionLog(id, { stream: "stderr" })`),远端 `filaxis observe --stream stderr` 就能一击必杀。
115
+
116
+ #### 3. `RemoteSessionManager.stop` 杀外层 `sh -c` 时未连带杀子进程树
117
+ 在 Step 1 中,B2 运行了 `filaxis.ts observe --host vm-agent-aethel --duration 2500 -- "/opt/fault-lab-b/bin/live-daemon.sh"`。
118
+ 2.5 秒后 `observe` 调用了 `drv.stopSession(h.sessionId)`,执行了 `kill -TERM ${e.pid}`。
119
+ 但在 Linux 下,`e.pid` 是外层包装 `sh -c "{ /opt/fault-lab-b/bin/live-daemon.sh; }; echo $? > ..."` 的 PID;对 `sh -c` 发 `SIGTERM` 只杀死了 `sh`,而它派生出的子进程 `bash /opt/fault-lab-b/bin/live-daemon.sh`(PID `15910`)被过继给了 `PID 1`,直到 B2 在最后一轮检查进程时手动清理掉。
120
+ - **优化建议(`alpha.9`)**:在 [`RemoteSessionManager.stop`](file:///C:/Users/dream/.gemini/antigravity/brain/f7c26020-3bdd-4477-b804-f050d731bc85/scratch/filaxis_alpha/package/src/remote-session-manager.ts#L148-L154) 中,将 `kill -TERM ${e.pid}` 升级为连同子进程一起终止:
121
+ ```sh
122
+ pkill -TERM -P ${e.pid} 2>/dev/null; kill -TERM ${e.pid} 2>/dev/null
123
+ ```
124
+ (SIGKILL 升级同理:`pkill -KILL -P ${e.pid}; kill -KILL ${e.pid}`),即可彻底杜绝包装脚本派生的孙进程残留!
@@ -0,0 +1,103 @@
1
+ # 第二份巡检报告:作者侧深度回应与工程自检
2
+
3
+ > 报告人:ZCode(牵丝 Filaxis 作者侧)
4
+ > 阈值版本:`0.1.0-alpha.1`(第一份报告对象)→ `0.1.0-alpha.2`(四项打磨中 #1 已落)→ **`0.1.0-alpha.3`(本报告随发,第一份报告全部建议闭环)**
5
+ > 第一份报告:Gemini Argon《完整巡检报告》(两轮全网 15 节点巡检 + 三技能剧本实战——`docs/完整巡检报告.md`)
6
+
7
+ ---
8
+
9
+ ## 一、 第一份报告逐条回应(8 项全闭环)
10
+
11
+ ### 1.1 四个打磨建议的修复实证
12
+
13
+ | # | 建议(第一份报告原文摘要) | 状态 | 修复版本 | 验证 |
14
+ |---|---|---|---|---|
15
+ | 1 | npm tarball 缺 `docs/skills`/`examples` | ✅ 已修 | alpha.2 | `files` 增 `docs`、`examples`、`dist`;alpha.2 tarball 实装 |
16
+ | 2 | `SshPtyDriver` 握手超时后残留 `ssh -tt` 子进程挂事件循环(OPNsense 交互菜单场景) | ✅ 已修 | **alpha.3** | `init()` 全链(root 建链/每跳 hop/每进入步)的握手失败路径均补 `link.kill()` 回收——三处 try/catch 收口 |
17
+ | 3 | 非 bash 环境(FreeBSD/OPNsense/Alpine)`bash -s`/`nohup bash -c` 报 not found | ✅ 已修 | **alpha.3** | `renderExecChannel` 终层 `bash -s`→**`sh -s`**;远端会话 `nohup bash -c`→**`nohup sh -c`**(包装脚本本就纯 POSIX:`( cmd ); ec=$?; printf` ——零成本通吃);测试断言与 mock 同步 |
18
+ | 4 | 群晖 DSM 无 `/etc/os-release`,版本探测缺席 | ✅ 已修 | **alpha.3** | `buildProbeStage1` 增 `fx_os_dsm` 探测行(`/etc.defaults/VERSION` 的 `productversion` → `Synology DSM v7.2.2` 形态),`parseProbeOutput` 的 `os_dsm` 分支覆盖空 `os` |
19
+
20
+ ### 1.2 第一份报告"惊艳点"的机制溯源(作者侧补注)
21
+
22
+ 第一份报告列的四个惊艳点,补上它们的确切机制来源(供后续维护者不迷路):
23
+
24
+ | 惊艳点 | 机制锚点(代码/需求) |
25
+ |---|---|
26
+ | 退出码是数据 | `REQ-0001 §3.2 细则 1`——设计动机:Agent 思考链不被 `grep`(exit 1)类噪音打断;实现:`ExecResult.exitCode` 如实返回,仅「无法执行」(超时/abort/连接死)reject |
27
+ | 终态三态 | `REQ-0001 §3.2 细则 6`「终止即请求、死亡须核实」——`stop()` 发 kill 后经 `kill -0`/`.code` 核实;核实不可达(断线/分区)落 `unknown` **而非静默当成功** |
28
+ | `ready()` 回溯热窗口 | `REQ-0001 FEAT-11`——`evictedCount===0` 时窗口历史完整(标记若出现过必在窗内);>0 显式报「可能已滚出」而非死等 |
29
+ | 方言探测屏蔽 | `REQ-0001 FEAT-14` 方言适配层——Aethelum 生产实锤 `base64 -w 0` 在 OPNsense 必错(`tmux-driver.ts:139` 现行 bug 的根治件);第一份报告实测 FreeBSD 15.1 已原生兼容 `-w 0`(新版本 BSD 收编了 GNU 参数)恰证明「探测而非假定」是对的做法 |
30
+
31
+ ---
32
+
33
+ ## 二、 第一份报告之外的作者侧深度自检(本报告增量)
34
+
35
+ ### 2.1 语义契约边界全表(超第一份的详细度)
36
+
37
+ **终态三态的完整状态机**(何时落哪个态):
38
+
39
+ ```
40
+ stop(sessionId)
41
+ ├─ 会话已终态 ──────────────→ exited(带历史 exitCode)
42
+ ├─ pid 缺失(start 未拿到 $!)→ unknown
43
+ ├─ exec 通道可达:
44
+ │ kill -TERM → kill -0 核实
45
+ │ ├─ DEAD → stopped(可能经 SIGKILL 升级)
46
+ │ └─ 核实链抛错 → unknown
47
+ └─ 自然退出(poll 轮发现 .code)→ exited(带 .code 退出码)
48
+ ```
49
+
50
+ **双通道矩阵**(何时选哪个通道):
51
+
52
+ | 场景 | 通道 | 理由 |
53
+ |---|---|---|
54
+ | 一次性命令(缺省) | `exec`(非 PTY) | stdout/stderr 物理分离;层上下文经 enterSteps stdin 管道重放 |
55
+ | 需要层内交互状态(`su`/`cd`/`export` 后续可见) | `pty` | 继承层内 shell 状态;stderr 混流是终端物理语义 |
56
+ | 多跳 PTY 登录链 / 交互式二次跳转(非 `… shell` 形态 enter) | **显式拒绝 exec 通道** | 单条非 PTY 进程无法复现——诚实报错指引用 pty 或收敛 `-J` |
57
+ | 远端探测命令(hostname/容器 ID) | `pty` | 走层内链路(alpha.3 教训:exec 通道会独立 spawn ssh,mock 与生产语义裂开) |
58
+
59
+ **跨流顺序契约**:双文件 `.out`/`.err` 落盘后的聚合序是**本地到达序**(两路观察的交错近似),不承诺进程真实写入序——需严格写入序的场景不适用双文件(`REQ-0001 §3.2 细则 5`)。
60
+
61
+ ### 2.2 alpha.1 → alpha.3 完整修复账(18 项)
62
+
63
+ | 版本 | 修复/增强 | 触发源 |
64
+ |---|---|---|
65
+ | alpha.2 | npm files 补 docs/examples/dist | 第一份报告 #1 |
66
+ | alpha.2 | `createAgentToolkit()`(tools+handlers 双层,三工具) | 第一份报告建议 3 |
67
+ | alpha.2 | dist 构建链 + 条件 exports(bun→src / node→dist) | 第一份报告建议 4 |
68
+ | alpha.2 | **watch 回溯匹配**(spawn→注册间隙输出永漏的真缺陷) | 作者侧全量测试实证 |
69
+ | alpha.2 | shell 发现序(PATH 优先 / WSL 入口防护 / Defender 并发坑规避) | 第一份报告 #1 的深挖 |
70
+ | alpha.3 | init 全链握手失败回收 `link.kill()` | 第一份报告 #2 |
71
+ | alpha.3 | exec 通道/远端会话 POSIX `sh` 通吃 | 第一份报告 #3 |
72
+ | alpha.3 | DSM 版本探测 fallback | 第一份报告 #4 |
73
+ | —— | 另有 10 项 CI/发布链修复(provision 顺序/dist-tag/heredoc 转义/YAML 块标量/npmrc token 写入/bun→npm 客户端/tmux pane 80 列折行/stage publish 适配/Node 24/printf 单行化) | Release 五连修 + alpha.2 发布链 |
74
+
75
+ ### 2.3 已知边界与待查(KNOWN-ISSUES 状态快照)
76
+
77
+ | 项 | 状态 | 影响面 |
78
+ |---|---|---|
79
+ | `reapIdle` interval 竞态 | 双侧 skip + TODO | 仅快速 mock 时钟下的测试;生产(真实网络延迟)低风险;stream 版 reapIdle 真机绿 |
80
+ | Aethelum 哨兵测试首用例 flaky | skip + TODO | Windows 冷启动环境性;CI Linux 预计无 |
81
+ | `bun file:` 依赖快照 | registry 化后自然消解 | Aethelum 回迁期工作方式 |
82
+
83
+ ### 2.4 测试矩阵与质量门禁快照(alpha.3 时点)
84
+
85
+ - **单元/集成测试**:99 用例(含 5 个 FILAXIS_IT 真机门控集成)全绿
86
+ - **类型**:`tsc --noEmit` 零错
87
+ - **边界门禁**:`check:boundaries` 三断言(零 Aethelum 概念 import / 零 `Bun.*` 裸调 / 零第三方运行时依赖)通过,负例拦截实证
88
+ - **CI(GitHub Actions)**:bun job(typecheck+门禁+tmux/sshd 自环+真机集成)与 node 24 job(tsc+门禁+dist 冒烟)双绿
89
+ - **发布**:tag `v*` 触发 Release——全量测试后 `npm stage publish`(暂存态,人工 2FA 审核公开)
90
+
91
+ ### 2.5 第一份巡检的运营结论转正(供 Aethelum 侧跟进)
92
+
93
+ 第一份报告第二节的运营发现(与本 SDK 无关但值得留档跟进):
94
+
95
+ - `proxmox-host` 内存 95% / 磁盘 90% ——水位告警级
96
+ - 非核心 failed 单元清单(`nvidia-gridd`/`glances`/`openipmi`/`rc-local` 等)——清理候选
97
+ - `lite-4c4g-2` 持续离线(ConnectTimeout 精准捕获)——下线或修复决策
98
+
99
+ ---
100
+
101
+ ## 三、 给第一份报告作者的致谢与回归邀请
102
+
103
+ 第一份报告的质量极高——尤其 #2(OPNsense 交互菜单握手残留)这种**只有真机异构集群才暴露得出来**的缺陷,以及它对 `egressError` 直交原文的正面验证。alpha.3 全部吸收。欢迎在 alpha.3 发布后做第三轮回归巡检(重点回归项:OPNsense 建链失败后的进程回收、`sh -s` 通道在 FreeBSD/Alpine 的实测、DSM 版本落盘形态)。
@@ -0,0 +1,74 @@
1
+ /**
2
+ * 示例 3:Agent 工具面最小封装(把 Filaxis 包成 LLM 可调用的工具集)
3
+ *
4
+ * 形态:本地执行 + 就绪握手 + 终态三态——SSH/容器层换 descriptor 即同一封装。
5
+ * 真实场景见 docs/skills/*.md。
6
+ *
7
+ * 运行:bun examples/agent-tools.ts
8
+ */
9
+ import { RuntimeStack } from "../src/index.js";
10
+ import type { ExecResult } from "../src/index.js";
11
+
12
+ /** 工具描述(喂给 LLM 的 schema 由你的框架生成——这里演示语义边界) */
13
+ const tools = {
14
+ run_command: {
15
+ description: "执行一次性命令。返回 stdout/stderr/exitCode(非零不抛错——退出码是数据)",
16
+ handler: async (cmd: string): Promise<ExecResult> => {
17
+ const layer = await stack.connect({ driver: "local" });
18
+ return layer.exec(cmd, { timeoutMs: 30_000 });
19
+ },
20
+ },
21
+ spawn_and_observe: {
22
+ description: "启动长跑任务并注册观察(ready 标记 + 错误模式)。返回 sessionId 与首窗口",
23
+ handler: async (cmd: string, readyMarker: string, errorPattern?: string) => {
24
+ const layer = await stack.connect({ driver: "local" });
25
+ const h = await layer.execSession(cmd, { name: "agent-task" });
26
+ // 就绪握手(回溯+增量——快输出进程不漏)
27
+ await new Promise<void>((resolve, reject) => {
28
+ const t0 = Date.now();
29
+ const poll = setInterval(async () => {
30
+ const lines = await layer.tailSession(h.sessionId, 50);
31
+ if (lines.some((l) => l.includes(readyMarker))) {
32
+ clearInterval(poll); resolve();
33
+ } else if (Date.now() - t0 > 15_000) {
34
+ clearInterval(poll);
35
+ await layer.stopSession(h.sessionId).catch(() => undefined); // 超时自动清理
36
+ reject(new Error("ready 超时(已自动停止)"));
37
+ }
38
+ }, 100);
39
+ });
40
+ if (errorPattern) {
41
+ layer.watchSession(h.sessionId, { pattern: errorPattern, once: false, thenStop: true });
42
+ }
43
+ return { sessionId: h.sessionId, tail: await layer.tailSession(h.sessionId, 5) };
44
+ },
45
+ },
46
+ stop_and_verify: {
47
+ description: "停止任务并核实终态(unknown=核实不可达,非成功非失败——需重试或上报)",
48
+ handler: async (sessionId: string) => {
49
+ const layer = await stack.connect({ driver: "local" });
50
+ const r = await layer.stopSession(sessionId) as { status: string; exitCode?: number | null };
51
+ return r.status === "exited"
52
+ ? { status: "exited", exitCode: r.exitCode }
53
+ : r.status === "stopped"
54
+ ? { status: "stopped", hint: "终止已核实路径(SIGKILL 升级在场)" }
55
+ : { status: "unknown", hint: "重连后核实进程是否真死" };
56
+ },
57
+ },
58
+ };
59
+
60
+ const stack = new RuntimeStack();
61
+
62
+ // 演示三工具联动
63
+ const r = await tools.run_command.handler("echo tool-ok; exit 3");
64
+ console.log("run_command:", r.stdout.trim(), "exitCode:", r.exitCode, "user:", r.effectiveUser);
65
+
66
+ const obs = await tools.spawn_and_observe.handler(
67
+ "echo READY; for i in 1 2 3; do echo tick-$i; sleep 0.1; done; sleep 20",
68
+ "READY",
69
+ "NEVER_MATCH",
70
+ );
71
+ console.log("spawn_and_observe:", obs.sessionId, "首窗口:", obs.tail.join(","));
72
+
73
+ const stop = await tools.stop_and_verify.handler(obs.sessionId);
74
+ console.log("stop_and_verify:", stop.status);
@@ -0,0 +1,49 @@
1
+ /**
2
+ * 示例 1:跨主机网络诊断(本机形态——零外部依赖可跑)
3
+ *
4
+ * 真实场景见 docs/skills/cross-host-diagnosis.md:把 local 换成
5
+ * `driver:"ssh", target:"防火墙"` 即同一剧本(环境能力先探测再选层)。
6
+ *
7
+ * 运行:bun examples/cross-host-diagnosis.ts
8
+ */
9
+ import { RuntimeStack } from "../src/index.js";
10
+
11
+ const stack = new RuntimeStack();
12
+
13
+ // 单机版:本机既是「防火墙」又是「源主机」——演示就绪握手竞态的正确处理
14
+ const fw = await stack.connect({ driver: "local" });
15
+
16
+ // ① 抓包任务(此处用慢速计数模拟 tcpdump):spawn 即返
17
+ const cap = await fw.execSession(
18
+ "for i in $(seq 1 20); do echo packet-$i; sleep 0.05; done; echo CAPTURE_DONE",
19
+ { name: "capture" },
20
+ );
21
+
22
+ // ② 就绪握手:等「开始监听」标记——exec 返回 ≠ 已监听,跳过即竞态
23
+ await fw.sessions /* ready 在会话句柄上 */;
24
+ const ready = await waitForReady(fw, cap.sessionId, "packet-1");
25
+ console.log("就绪标记:", ready);
26
+
27
+ // ③ 探测(退出码是数据)
28
+ const probe = await fw.exec("echo probe-ok; exit 0", { timeoutMs: 5000 });
29
+ console.log("探测:", probe.stdout.trim(), "exitCode:", probe.exitCode);
30
+
31
+ // ④ 收尾:等终态 → 读日志 → 拿退出码
32
+ await sleep(1500);
33
+ const log = await fw.readSessionLog(cap.sessionId);
34
+ console.log("抓包日志尾行:", log.split("\n").pop());
35
+
36
+ /** 最小 ready 轮询(SDK 会话句柄的 ready() 等价演示——回溯窗口+增量) */
37
+ async function waitForReady(drv: typeof fw, sessionId: string, marker: string): Promise<string> {
38
+ for (let i = 0; i < 100; i++) {
39
+ const lines = await drv.tailSession(sessionId, 50);
40
+ const hit = lines.find((l) => l.includes(marker));
41
+ if (hit) return hit;
42
+ await sleep(50);
43
+ }
44
+ throw new Error("就绪标记超时");
45
+ }
46
+
47
+ function sleep(ms: number): Promise<void> {
48
+ return new Promise((r) => setTimeout(r, ms));
49
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * 示例 2:长跑任务观察(本机形态可跑——watcher/thenStop/终态三态)
3
+ *
4
+ * 真实场景见 docs/skills/longrun-observe.md。
5
+ *
6
+ * 运行:bun examples/longrun-observe.ts
7
+ */
8
+ import { LocalRuntimeDriver } from "../src/index.js";
9
+ import type { SessionEvent } from "../src/index.js";
10
+
11
+ const drv = new LocalRuntimeDriver();
12
+
13
+ // 事件订阅(零队列依赖——你的看板/IM 桥从这里接线)
14
+ const events: SessionEvent[] = [];
15
+ drv.sessions.onEvent = (ev) => events.push(ev);
16
+
17
+ // ① 长跑任务(模拟训练):正常进度走 stdout,告警走 stderr
18
+ const h = await drv.execSession(
19
+ "for i in 1 2 3; do echo epoch=$i; sleep 0.2; done; echo 'OOM: cuda memory exhausted' 1>&2; sleep 30",
20
+ { name: "train" },
21
+ );
22
+ console.log("会话启动:", h.sessionId, "logBase:", h.logBase);
23
+
24
+ // ② watcher:等进度标记(once);错误模式命中即收摊(thenStop)
25
+ drv.watchSession(h.sessionId, { pattern: "epoch=3", once: true });
26
+ drv.watchSession(h.sessionId, { pattern: "OOM", once: false, thenStop: true });
27
+
28
+ // ③ 观察:双流行对象(按流过滤——告警只看 stderr)
29
+ await sleep(1200);
30
+ const stderrLines = drv.sessions.tailLines(h.sessionId).filter((l) => l.stream === "stderr");
31
+ console.log("stderr 告警:", stderrLines.map((l) => l.text));
32
+
33
+ // ④ 终态:等 exit 事件,拿可信退出码
34
+ await new Promise<void>((resolve) => {
35
+ const t = setInterval(() => {
36
+ if (events.some((e) => e.type === "exit")) { clearInterval(t); resolve(); }
37
+ }, 100);
38
+ setTimeout(() => { clearInterval(t); resolve(); }, 6000).unref?.();
39
+ });
40
+ const exit = events.find((e) => e.type === "exit") as Extract<SessionEvent, { type: "exit" }>;
41
+ console.log("终态: exitCode =", exit?.exitCode, "(thenStop 命中 OOM 收摊)");
42
+ console.log("落盘日志(stdout):", (await drv.readSessionLog(h.sessionId)).split("\n").slice(0, 3).join(" | "));
43
+
44
+ function sleep(ms: number): Promise<void> {
45
+ return new Promise((r) => setTimeout(r, ms));
46
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@syrkos/filaxis",
3
+ "version": "0.1.0-alpha.10",
4
+ "description": "牵丝 Filaxis — 面向自动化与 Agent 的远程执行运行时 SDK(Syrkos · 漩核)。千里一线,执行有迹。",
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "module": "src/index.ts",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "bun": "./src/index.ts",
12
+ "node": "./dist/index.js",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "scripts": {
17
+ "test": "bun test",
18
+ "typecheck": "tsc --noEmit",
19
+ "check:boundaries": "bun scripts/check-boundaries.ts",
20
+ "build": "bun build src/index.ts --target node --outdir dist && bunx tsc --emitDeclarationOnly --declaration --outDir dist --target es2022 --module esnext --moduleResolution bundler --skipLibCheck src/index.ts"
21
+ },
22
+ "devDependencies": {
23
+ "@types/bun": "latest",
24
+ "typescript": "^5"
25
+ },
26
+ "files": [
27
+ "src",
28
+ "dist",
29
+ "docs",
30
+ "examples",
31
+ "README.md",
32
+ "LICENSE",
33
+ "NOTICE",
34
+ "AGENTS.md"
35
+ ],
36
+ "publishConfig": {
37
+ "access": "public",
38
+ "registry": "https://registry.npmjs.org/"
39
+ },
40
+ "repository": {
41
+ "type": "git",
42
+ "url": "https://github.com/corolin/filaxis"
43
+ },
44
+ "bin": {
45
+ "filaxis": "bin/filaxis.ts"
46
+ }
47
+ }