@oratis/lisa 0.9.0 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (296) hide show
  1. package/README.md +123 -16
  2. package/README.zh-CN.md +101 -15
  3. package/completions/_lisa +56 -0
  4. package/completions/lisa.bash +35 -1
  5. package/completions/lisa.fish +18 -1
  6. package/dist/advisor/engine.d.ts +7 -0
  7. package/dist/advisor/engine.d.ts.map +1 -1
  8. package/dist/advisor/engine.js +12 -0
  9. package/dist/advisor/engine.js.map +1 -1
  10. package/dist/agent.d.ts +14 -0
  11. package/dist/agent.d.ts.map +1 -1
  12. package/dist/agent.js +58 -7
  13. package/dist/agent.js.map +1 -1
  14. package/dist/agents/managed.d.ts +108 -0
  15. package/dist/agents/managed.d.ts.map +1 -0
  16. package/dist/agents/managed.js +241 -0
  17. package/dist/agents/managed.js.map +1 -0
  18. package/dist/agents/pty.d.ts +155 -0
  19. package/dist/agents/pty.d.ts.map +1 -0
  20. package/dist/agents/pty.js +299 -0
  21. package/dist/agents/pty.js.map +1 -0
  22. package/dist/approval.d.ts +13 -0
  23. package/dist/approval.d.ts.map +1 -1
  24. package/dist/approval.js +25 -2
  25. package/dist/approval.js.map +1 -1
  26. package/dist/autonomy/runs.d.ts +33 -0
  27. package/dist/autonomy/runs.d.ts.map +1 -0
  28. package/dist/autonomy/runs.js +106 -0
  29. package/dist/autonomy/runs.js.map +1 -0
  30. package/dist/channels/feishu.d.ts.map +1 -1
  31. package/dist/channels/feishu.js +65 -0
  32. package/dist/channels/feishu.js.map +1 -1
  33. package/dist/channels/router.d.ts +7 -0
  34. package/dist/channels/router.d.ts.map +1 -1
  35. package/dist/channels/router.js +1 -1
  36. package/dist/channels/router.js.map +1 -1
  37. package/dist/cli/agents-pty.d.ts +18 -0
  38. package/dist/cli/agents-pty.d.ts.map +1 -0
  39. package/dist/cli/agents-pty.js +182 -0
  40. package/dist/cli/agents-pty.js.map +1 -0
  41. package/dist/cli/agents.d.ts +2 -0
  42. package/dist/cli/agents.d.ts.map +1 -0
  43. package/dist/cli/agents.js +65 -0
  44. package/dist/cli/agents.js.map +1 -0
  45. package/dist/cli/consent.d.ts +2 -0
  46. package/dist/cli/consent.d.ts.map +1 -0
  47. package/dist/cli/consent.js +57 -0
  48. package/dist/cli/consent.js.map +1 -0
  49. package/dist/cli/model.d.ts +2 -0
  50. package/dist/cli/model.d.ts.map +1 -0
  51. package/dist/cli/model.js +121 -0
  52. package/dist/cli/model.js.map +1 -0
  53. package/dist/cli/sense.d.ts +2 -0
  54. package/dist/cli/sense.d.ts.map +1 -0
  55. package/dist/cli/sense.js +36 -0
  56. package/dist/cli/sense.js.map +1 -0
  57. package/dist/cli.js +117 -12
  58. package/dist/cli.js.map +1 -1
  59. package/dist/consent/blacklist.d.ts +28 -0
  60. package/dist/consent/blacklist.d.ts.map +1 -0
  61. package/dist/consent/blacklist.js +78 -0
  62. package/dist/consent/blacklist.js.map +1 -0
  63. package/dist/consent/store.d.ts +45 -0
  64. package/dist/consent/store.d.ts.map +1 -0
  65. package/dist/consent/store.js +126 -0
  66. package/dist/consent/store.js.map +1 -0
  67. package/dist/control/policy.d.ts +13 -0
  68. package/dist/control/policy.d.ts.map +1 -0
  69. package/dist/control/policy.js +57 -0
  70. package/dist/control/policy.js.map +1 -0
  71. package/dist/heartbeat/runner.d.ts.map +1 -1
  72. package/dist/heartbeat/runner.js +70 -14
  73. package/dist/heartbeat/runner.js.map +1 -1
  74. package/dist/idle/runner.d.ts.map +1 -1
  75. package/dist/idle/runner.js +87 -17
  76. package/dist/idle/runner.js.map +1 -1
  77. package/dist/integrations/aider/observer.d.ts +21 -1
  78. package/dist/integrations/aider/observer.d.ts.map +1 -1
  79. package/dist/integrations/aider/observer.js +110 -1
  80. package/dist/integrations/aider/observer.js.map +1 -1
  81. package/dist/integrations/claude-code/liveness.d.ts +10 -0
  82. package/dist/integrations/claude-code/liveness.d.ts.map +1 -0
  83. package/dist/integrations/claude-code/liveness.js +62 -0
  84. package/dist/integrations/claude-code/liveness.js.map +1 -0
  85. package/dist/integrations/claude-code/watcher.d.ts +39 -4
  86. package/dist/integrations/claude-code/watcher.d.ts.map +1 -1
  87. package/dist/integrations/claude-code/watcher.js +94 -57
  88. package/dist/integrations/claude-code/watcher.js.map +1 -1
  89. package/dist/integrations/codex/observer.d.ts +19 -6
  90. package/dist/integrations/codex/observer.d.ts.map +1 -1
  91. package/dist/integrations/codex/observer.js +228 -4
  92. package/dist/integrations/codex/observer.js.map +1 -1
  93. package/dist/integrations/dispatch-ledger.d.ts +30 -1
  94. package/dist/integrations/dispatch-ledger.d.ts.map +1 -1
  95. package/dist/integrations/dispatch-ledger.js +75 -9
  96. package/dist/integrations/dispatch-ledger.js.map +1 -1
  97. package/dist/integrations/git/branch.d.ts +11 -0
  98. package/dist/integrations/git/branch.d.ts.map +1 -0
  99. package/dist/integrations/git/branch.js +49 -0
  100. package/dist/integrations/git/branch.js.map +1 -0
  101. package/dist/integrations/git/observer.d.ts +39 -0
  102. package/dist/integrations/git/observer.d.ts.map +1 -0
  103. package/dist/integrations/git/observer.js +252 -0
  104. package/dist/integrations/git/observer.js.map +1 -0
  105. package/dist/integrations/github-pr/observer.d.ts.map +1 -1
  106. package/dist/integrations/github-pr/observer.js +7 -2
  107. package/dist/integrations/github-pr/observer.js.map +1 -1
  108. package/dist/integrations/hub.d.ts +8 -0
  109. package/dist/integrations/hub.d.ts.map +1 -1
  110. package/dist/integrations/hub.js +39 -1
  111. package/dist/integrations/hub.js.map +1 -1
  112. package/dist/integrations/managed/observer.d.ts +17 -0
  113. package/dist/integrations/managed/observer.d.ts.map +1 -0
  114. package/dist/integrations/managed/observer.js +46 -0
  115. package/dist/integrations/managed/observer.js.map +1 -0
  116. package/dist/integrations/opencode/observer.d.ts +53 -5
  117. package/dist/integrations/opencode/observer.d.ts.map +1 -1
  118. package/dist/integrations/opencode/observer.js +242 -20
  119. package/dist/integrations/opencode/observer.js.map +1 -1
  120. package/dist/integrations/pty/observer.d.ts +25 -0
  121. package/dist/integrations/pty/observer.d.ts.map +1 -0
  122. package/dist/integrations/pty/observer.js +48 -0
  123. package/dist/integrations/pty/observer.js.map +1 -0
  124. package/dist/integrations/registry.d.ts.map +1 -1
  125. package/dist/integrations/registry.js +5 -0
  126. package/dist/integrations/registry.js.map +1 -1
  127. package/dist/integrations/shell/observer.d.ts +33 -0
  128. package/dist/integrations/shell/observer.d.ts.map +1 -0
  129. package/dist/integrations/shell/observer.js +223 -0
  130. package/dist/integrations/shell/observer.js.map +1 -0
  131. package/dist/integrations/takoapi/a2a.d.ts +38 -0
  132. package/dist/integrations/takoapi/a2a.d.ts.map +1 -0
  133. package/dist/integrations/takoapi/a2a.js +70 -0
  134. package/dist/integrations/takoapi/a2a.js.map +1 -0
  135. package/dist/integrations/takoapi/ledger.d.ts +32 -0
  136. package/dist/integrations/takoapi/ledger.d.ts.map +1 -0
  137. package/dist/integrations/takoapi/ledger.js +96 -0
  138. package/dist/integrations/takoapi/ledger.js.map +1 -0
  139. package/dist/integrations/takoapi/observer.d.ts +39 -0
  140. package/dist/integrations/takoapi/observer.d.ts.map +1 -0
  141. package/dist/integrations/takoapi/observer.js +102 -0
  142. package/dist/integrations/takoapi/observer.js.map +1 -0
  143. package/dist/integrations/types.d.ts +17 -0
  144. package/dist/integrations/types.d.ts.map +1 -1
  145. package/dist/mcp/client.d.ts +5 -0
  146. package/dist/mcp/client.d.ts.map +1 -1
  147. package/dist/mcp/client.js +1 -1
  148. package/dist/mcp/client.js.map +1 -1
  149. package/dist/memory/embedding.d.ts +45 -0
  150. package/dist/memory/embedding.d.ts.map +1 -0
  151. package/dist/memory/embedding.js +139 -0
  152. package/dist/memory/embedding.js.map +1 -0
  153. package/dist/memory/search_tool.d.ts.map +1 -1
  154. package/dist/memory/search_tool.js +6 -2
  155. package/dist/memory/search_tool.js.map +1 -1
  156. package/dist/memory/vector.d.ts +14 -3
  157. package/dist/memory/vector.d.ts.map +1 -1
  158. package/dist/memory/vector.js +50 -0
  159. package/dist/memory/vector.js.map +1 -1
  160. package/dist/model/local.d.ts +43 -0
  161. package/dist/model/local.d.ts.map +1 -0
  162. package/dist/model/local.js +118 -0
  163. package/dist/model/local.js.map +1 -0
  164. package/dist/model/plan-usage.d.ts +41 -0
  165. package/dist/model/plan-usage.d.ts.map +1 -0
  166. package/dist/model/plan-usage.js +168 -0
  167. package/dist/model/plan-usage.js.map +1 -0
  168. package/dist/model/plans.d.ts +73 -0
  169. package/dist/model/plans.d.ts.map +1 -0
  170. package/dist/model/plans.js +246 -0
  171. package/dist/model/plans.js.map +1 -0
  172. package/dist/orchestrator/recent-recap.d.ts +2 -0
  173. package/dist/orchestrator/recent-recap.d.ts.map +1 -0
  174. package/dist/orchestrator/recent-recap.js +20 -0
  175. package/dist/orchestrator/recent-recap.js.map +1 -0
  176. package/dist/providers/anthropic.d.ts +1 -0
  177. package/dist/providers/anthropic.d.ts.map +1 -1
  178. package/dist/providers/anthropic.js +10 -2
  179. package/dist/providers/anthropic.js.map +1 -1
  180. package/dist/providers/fallback.d.ts +23 -0
  181. package/dist/providers/fallback.d.ts.map +1 -0
  182. package/dist/providers/fallback.js +29 -0
  183. package/dist/providers/fallback.js.map +1 -0
  184. package/dist/providers/gemini.d.ts +2 -0
  185. package/dist/providers/gemini.d.ts.map +1 -1
  186. package/dist/providers/gemini.js +24 -10
  187. package/dist/providers/gemini.js.map +1 -1
  188. package/dist/providers/openai.d.ts.map +1 -1
  189. package/dist/providers/openai.js +4 -1
  190. package/dist/providers/openai.js.map +1 -1
  191. package/dist/providers/registry.d.ts +18 -2
  192. package/dist/providers/registry.d.ts.map +1 -1
  193. package/dist/providers/registry.js +56 -9
  194. package/dist/providers/registry.js.map +1 -1
  195. package/dist/reflect.d.ts +55 -0
  196. package/dist/reflect.d.ts.map +1 -1
  197. package/dist/reflect.js +148 -54
  198. package/dist/reflect.js.map +1 -1
  199. package/dist/sense/log.d.ts +9 -0
  200. package/dist/sense/log.d.ts.map +1 -0
  201. package/dist/sense/log.js +63 -0
  202. package/dist/sense/log.js.map +1 -0
  203. package/dist/sense/screen.d.ts +43 -0
  204. package/dist/sense/screen.d.ts.map +1 -0
  205. package/dist/sense/screen.js +107 -0
  206. package/dist/sense/screen.js.map +1 -0
  207. package/dist/sense/service.d.ts +16 -0
  208. package/dist/sense/service.d.ts.map +1 -0
  209. package/dist/sense/service.js +33 -0
  210. package/dist/sense/service.js.map +1 -0
  211. package/dist/sense/types.d.ts +35 -0
  212. package/dist/sense/types.d.ts.map +1 -0
  213. package/dist/sense/types.js +11 -0
  214. package/dist/sense/types.js.map +1 -0
  215. package/dist/sense/voice.d.ts +28 -0
  216. package/dist/sense/voice.d.ts.map +1 -0
  217. package/dist/sense/voice.js +64 -0
  218. package/dist/sense/voice.js.map +1 -0
  219. package/dist/soul/git.d.ts.map +1 -1
  220. package/dist/soul/git.js +35 -16
  221. package/dist/soul/git.js.map +1 -1
  222. package/dist/soul/store.d.ts +22 -0
  223. package/dist/soul/store.d.ts.map +1 -1
  224. package/dist/soul/store.js +69 -14
  225. package/dist/soul/store.js.map +1 -1
  226. package/dist/soul/summary.d.ts +10 -0
  227. package/dist/soul/summary.d.ts.map +1 -0
  228. package/dist/soul/summary.js +57 -0
  229. package/dist/soul/summary.js.map +1 -0
  230. package/dist/soul/tools.d.ts.map +1 -1
  231. package/dist/soul/tools.js +13 -24
  232. package/dist/soul/tools.js.map +1 -1
  233. package/dist/soul/types.d.ts +9 -0
  234. package/dist/soul/types.d.ts.map +1 -1
  235. package/dist/soul/types.js.map +1 -1
  236. package/dist/subagent.d.ts +7 -0
  237. package/dist/subagent.d.ts.map +1 -1
  238. package/dist/subagent.js +3 -1
  239. package/dist/subagent.js.map +1 -1
  240. package/dist/tools/dispatch_agent.d.ts +13 -1
  241. package/dist/tools/dispatch_agent.d.ts.map +1 -1
  242. package/dist/tools/dispatch_agent.js +56 -12
  243. package/dist/tools/dispatch_agent.js.map +1 -1
  244. package/dist/tools/dispatch_status.d.ts +22 -0
  245. package/dist/tools/dispatch_status.d.ts.map +1 -0
  246. package/dist/tools/dispatch_status.js +57 -0
  247. package/dist/tools/dispatch_status.js.map +1 -0
  248. package/dist/tools/registry.d.ts +29 -0
  249. package/dist/tools/registry.d.ts.map +1 -1
  250. package/dist/tools/registry.js +67 -0
  251. package/dist/tools/registry.js.map +1 -1
  252. package/dist/tools/run_on_plan.d.ts +46 -0
  253. package/dist/tools/run_on_plan.d.ts.map +1 -0
  254. package/dist/tools/run_on_plan.js +89 -0
  255. package/dist/tools/run_on_plan.js.map +1 -0
  256. package/dist/tools/takoapi.d.ts +60 -0
  257. package/dist/tools/takoapi.d.ts.map +1 -0
  258. package/dist/tools/takoapi.js +181 -0
  259. package/dist/tools/takoapi.js.map +1 -0
  260. package/dist/tools/validate.d.ts +22 -0
  261. package/dist/tools/validate.d.ts.map +1 -0
  262. package/dist/tools/validate.js +50 -0
  263. package/dist/tools/validate.js.map +1 -0
  264. package/dist/web/agent-roster.d.ts +56 -0
  265. package/dist/web/agent-roster.d.ts.map +1 -0
  266. package/dist/web/agent-roster.js +109 -0
  267. package/dist/web/agent-roster.js.map +1 -0
  268. package/dist/web/devices.d.ts +27 -0
  269. package/dist/web/devices.d.ts.map +1 -0
  270. package/dist/web/devices.js +113 -0
  271. package/dist/web/devices.js.map +1 -0
  272. package/dist/web/island.d.ts +5 -1
  273. package/dist/web/island.d.ts.map +1 -1
  274. package/dist/web/island.js +410 -63
  275. package/dist/web/island.js.map +1 -1
  276. package/dist/web/lisa-client.d.ts +20 -0
  277. package/dist/web/lisa-client.d.ts.map +1 -0
  278. package/dist/web/lisa-client.js +1358 -0
  279. package/dist/web/lisa-client.js.map +1 -0
  280. package/dist/web/lisa-css.d.ts +13 -0
  281. package/dist/web/lisa-css.d.ts.map +1 -0
  282. package/dist/web/lisa-css.js +1163 -0
  283. package/dist/web/lisa-css.js.map +1 -0
  284. package/dist/web/lisa-html.d.ts +1 -1
  285. package/dist/web/lisa-html.d.ts.map +1 -1
  286. package/dist/web/lisa-html.js +15 -2136
  287. package/dist/web/lisa-html.js.map +1 -1
  288. package/dist/web/push.d.ts +76 -0
  289. package/dist/web/push.d.ts.map +1 -0
  290. package/dist/web/push.js +163 -0
  291. package/dist/web/push.js.map +1 -0
  292. package/dist/web/server.d.ts +19 -0
  293. package/dist/web/server.d.ts.map +1 -1
  294. package/dist/web/server.js +878 -64
  295. package/dist/web/server.js.map +1 -1
  296. package/package.json +5 -2
@@ -0,0 +1,299 @@
1
+ /**
2
+ * PTY-backed agents — EXPERIMENTAL spike (Stage C), OFF BY DEFAULT.
3
+ *
4
+ * Phase-3 managed agents run LISA's OWN agent loop (its tools, its provider).
5
+ * A PTY agent instead spawns the REAL `claude` / `codex` CLI inside a
6
+ * pseudo-terminal, so you get that tool's full configuration — its skills, MCP
7
+ * servers, hooks, model — while LISA owns stdin/stdout: it types your task and
8
+ * follow-ups, can answer prompts, and reads the stream for a coarse live status.
9
+ *
10
+ * HONEST LIMITS (why this is a flagged spike, not a shipped feature):
11
+ * - Native dep: needs `node-pty` (an *optional* dependency). If it isn't built,
12
+ * PTY agents are simply unavailable — nothing else in LISA changes.
13
+ * - Off by default: set `LISA_PTY_AGENTS=1` to enable.
14
+ * - Controls only CLIs LISA SPAWNS — NOT sessions you already opened in your
15
+ * own terminal (those have no control channel; they stay observe-only).
16
+ * - Output parsing is best-effort: the CLI's TUI is ANSI / box-drawn and
17
+ * version-sensitive, so "state" is inferred from output quiescence, not from
18
+ * parsed intent. The captured tail is shown to you (your own terminal), never
19
+ * folded into the structural cross-agent roster.
20
+ */
21
+ import { EventEmitter } from "node:events";
22
+ import { readdirSync, existsSync } from "node:fs";
23
+ import { homedir } from "node:os";
24
+ import { join } from "node:path";
25
+ /** Is the PTY-agent spike enabled? Off unless LISA_PTY_AGENTS=1. */
26
+ export function ptyEnabled() {
27
+ return process.env.LISA_PTY_AGENTS === "1";
28
+ }
29
+ /** Map a roster agent-kind to its CLI binary (env-overridable). */
30
+ export function resolveCli(agent) {
31
+ const a = agent.toLowerCase();
32
+ if (a === "codex")
33
+ return process.env.LISA_PTY_CODEX_CMD || "codex";
34
+ // default to claude (covers "claude" / "claude-code")
35
+ return process.env.LISA_PTY_CLAUDE_CMD || "claude";
36
+ }
37
+ /** Normalize a loose agent label to a roster AgentKind. */
38
+ export function normalizeAgentKind(agent) {
39
+ const a = agent.toLowerCase();
40
+ if (a === "codex")
41
+ return "codex";
42
+ if (a === "claude" || a === "claude-code")
43
+ return "claude-code";
44
+ return agent;
45
+ }
46
+ /**
47
+ * Best binary to drive a claude session: env override → the newest app-bundled
48
+ * claude (matches the version that wrote the sessions, best for --resume) → the
49
+ * PATH `claude`. The app-bundle probe is macOS-specific and silently no-ops
50
+ * elsewhere. Falls back to resolveCli for non-claude agents.
51
+ */
52
+ export function detectClaudeBinary() {
53
+ if (process.env.LISA_PTY_CLAUDE_CMD)
54
+ return process.env.LISA_PTY_CLAUDE_CMD;
55
+ try {
56
+ const root = join(homedir(), "Library", "Application Support", "Claude", "claude-code");
57
+ const versions = readdirSync(root)
58
+ .filter((v) => /^\d+\.\d+\.\d+/.test(v))
59
+ .sort((a, b) => cmpVersion(b, a)); // newest first
60
+ for (const v of versions) {
61
+ const bin = join(root, v, "claude.app", "Contents", "MacOS", "claude");
62
+ if (existsSync(bin))
63
+ return bin;
64
+ }
65
+ }
66
+ catch {
67
+ /* not macOS / not installed → fall through */
68
+ }
69
+ return "claude";
70
+ }
71
+ function cmpVersion(a, b) {
72
+ const pa = a.split(".").map((n) => parseInt(n, 10) || 0);
73
+ const pb = b.split(".").map((n) => parseInt(n, 10) || 0);
74
+ for (let i = 0; i < 3; i++) {
75
+ if ((pa[i] || 0) !== (pb[i] || 0))
76
+ return (pa[i] || 0) - (pb[i] || 0);
77
+ }
78
+ return 0;
79
+ }
80
+ // Strip ANSI / VT100 escape sequences so the captured tail is plain-ish text.
81
+ // Control anchors come via String.fromCharCode so the SOURCE carries no raw
82
+ // control bytes (which trip greps, diffs, and some editors).
83
+ const ESC = String.fromCharCode(27); // U+001B
84
+ const CSI = String.fromCharCode(155); // U+009B
85
+ const BEL = String.fromCharCode(7); // U+0007
86
+ const ANSI = new RegExp("[" + ESC + CSI + "][[\\]()#;?]*" +
87
+ "(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?" +
88
+ BEL +
89
+ ")|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))", "g");
90
+ /** Strip ANSI escape sequences + common bare control bytes (CR/backspace). Pure. */
91
+ export function stripAnsi(s) {
92
+ // eslint-disable-next-line no-control-regex
93
+ return s.replace(ANSI, "").replace(/[\r\b]/g, "");
94
+ }
95
+ /**
96
+ * Coarse live state from output quiescence: actively streaming ⇒ "working";
97
+ * quiet longer than idleMs ⇒ "waiting" (likely awaiting input or a prompt).
98
+ * `done`/`error` are decided by lifecycle flags, not here. Pure.
99
+ */
100
+ export function derivePtyState(lastChunkAtMs, nowMs, idleMs) {
101
+ return nowMs - lastChunkAtMs < idleMs ? "working" : "waiting";
102
+ }
103
+ const RING_MAX = 4000; // chars of ANSI-stripped tail kept
104
+ const IDLE_MS = 4000; // quiet longer than this ⇒ "waiting"
105
+ let counter = 0;
106
+ async function loadPty() {
107
+ try {
108
+ // Non-literal specifier so `tsc` treats this as `any` and never hard-requires
109
+ // node-pty (it's an optionalDependency that may be absent). Resolved at runtime.
110
+ const spec = "node-pty";
111
+ const mod = await import(spec);
112
+ const resolved = mod && typeof mod.spawn === "function" ? mod : ((mod && mod.default) ?? mod);
113
+ return resolved;
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ /** One real CLI running under a pseudo-terminal that LISA drives. */
120
+ export class PtyAgent {
121
+ id;
122
+ agent;
123
+ cli;
124
+ cwd;
125
+ project;
126
+ /** Real claude session id, when this agent is a resume-adopt (claude only). */
127
+ adoptedSessionId;
128
+ onChange = () => { };
129
+ /** Called with each ANSI-stripped output chunk — drives the live attach stream
130
+ * (GET /api/agents/pty/<id>/stream). Distinct from onChange (state snapshots). */
131
+ onOutput = () => { };
132
+ proc;
133
+ now;
134
+ ring = "";
135
+ bytesOut = 0;
136
+ aborted = false;
137
+ exited = false;
138
+ exitReason = "";
139
+ lastChunkAt;
140
+ lastMtime;
141
+ constructor(id, opts, cli, proc, now) {
142
+ this.id = id;
143
+ this.agent = normalizeAgentKind(opts.agent);
144
+ this.cli = cli;
145
+ this.cwd = opts.cwd;
146
+ this.project = opts.cwd.split("/").filter(Boolean).pop() || opts.cwd;
147
+ this.adoptedSessionId =
148
+ opts.resumeSessionId && this.agent === "claude-code" ? opts.resumeSessionId : undefined;
149
+ this.proc = proc;
150
+ this.now = now;
151
+ this.lastChunkAt = now();
152
+ this.lastMtime = now();
153
+ proc.onData((d) => this.onData(d));
154
+ proc.onExit((e) => this.onExit(e));
155
+ }
156
+ /** Spawn a real CLI under a PTY and type the initial task. */
157
+ static async start(opts) {
158
+ if (!ptyEnabled()) {
159
+ throw new Error("PTY agents are disabled — set LISA_PTY_AGENTS=1 to enable this spike");
160
+ }
161
+ const pty = opts.ptyModule ?? (await loadPty());
162
+ if (!pty) {
163
+ throw new Error("node-pty is not installed — run `npm i node-pty` to enable PTY agents");
164
+ }
165
+ const kind = normalizeAgentKind(opts.agent);
166
+ const cli = opts.cli ?? (kind === "claude-code" ? detectClaudeBinary() : resolveCli(opts.agent));
167
+ // Adopt an existing session by id (claude only): `claude --resume <id>`.
168
+ const resumeArgs = opts.resumeSessionId && kind === "claude-code" ? ["--resume", opts.resumeSessionId] : [];
169
+ const args = [...resumeArgs, ...(opts.args ?? [])];
170
+ const now = opts.now ?? Date.now;
171
+ const proc = pty.spawn(cli, args, {
172
+ name: "xterm-256color",
173
+ cols: opts.cols ?? 120,
174
+ rows: opts.rows ?? 32,
175
+ cwd: opts.cwd,
176
+ env: process.env,
177
+ });
178
+ const id = "p" + (++counter).toString(36) + "-" + Date.now().toString(36).slice(-4);
179
+ const agent = new PtyAgent(id, opts, cli, proc, now);
180
+ if (opts.task)
181
+ agent.send(opts.task);
182
+ return agent;
183
+ }
184
+ /** Type a line into the CLI (initial task or a follow-up). */
185
+ send(text) {
186
+ if (this.aborted || this.exited)
187
+ return;
188
+ this.proc.write(text + "\r");
189
+ this.touch();
190
+ }
191
+ /** Kill the CLI. Idempotent. */
192
+ cancel() {
193
+ if (this.exited)
194
+ return;
195
+ this.aborted = true;
196
+ try {
197
+ this.proc.kill();
198
+ }
199
+ catch {
200
+ /* already gone */
201
+ }
202
+ this.exitReason = "cancelled";
203
+ this.touch();
204
+ }
205
+ /**
206
+ * ANSI-stripped tail of the terminal — the user's window into THEIR agent.
207
+ * Exposed only via an explicit endpoint, never folded into the structural
208
+ * cross-agent roster (which stays metadata-only).
209
+ */
210
+ output() {
211
+ return this.ring;
212
+ }
213
+ view() {
214
+ let state;
215
+ let reason;
216
+ if (this.aborted) {
217
+ state = "done";
218
+ reason = "cancelled";
219
+ }
220
+ else if (this.exited) {
221
+ state = "done";
222
+ reason = this.exitReason || "exited";
223
+ }
224
+ else {
225
+ state = derivePtyState(this.lastChunkAt, this.now(), IDLE_MS);
226
+ reason = state === "working" ? "streaming" : "idle";
227
+ }
228
+ return {
229
+ id: this.id,
230
+ agent: this.agent,
231
+ cli: this.cli,
232
+ project: this.project,
233
+ cwd: this.cwd,
234
+ state,
235
+ stateReason: reason,
236
+ lastMtime: this.lastMtime,
237
+ bytesOut: this.bytesOut,
238
+ ...(this.adoptedSessionId ? { adoptedSessionId: this.adoptedSessionId } : {}),
239
+ };
240
+ }
241
+ // ── internals ──
242
+ onData(d) {
243
+ this.bytesOut += d.length;
244
+ const clean = stripAnsi(d);
245
+ this.ring = (this.ring + clean).slice(-RING_MAX);
246
+ this.lastChunkAt = this.now();
247
+ if (clean)
248
+ this.onOutput(clean);
249
+ this.touch();
250
+ }
251
+ onExit(e) {
252
+ this.exited = true;
253
+ if (!this.exitReason)
254
+ this.exitReason = "exit " + e.exitCode;
255
+ this.touch();
256
+ }
257
+ touch() {
258
+ this.lastMtime = this.now();
259
+ this.onChange();
260
+ }
261
+ }
262
+ /** Process-wide registry of PTY agents; emits "update" with a PtyView. */
263
+ export class PtyRegistry extends EventEmitter {
264
+ agents = new Map();
265
+ async start(opts) {
266
+ const a = await PtyAgent.start(opts);
267
+ a.onChange = () => this.emit("update", a.view());
268
+ a.onOutput = (chunk) => this.emit("output", { id: a.id, chunk });
269
+ this.agents.set(a.id, a);
270
+ this.emit("update", a.view());
271
+ return a.view();
272
+ }
273
+ send(id, text) {
274
+ const a = this.agents.get(id);
275
+ if (!a)
276
+ return false;
277
+ a.send(text);
278
+ return true;
279
+ }
280
+ cancel(id) {
281
+ const a = this.agents.get(id);
282
+ if (!a)
283
+ return false;
284
+ a.cancel();
285
+ return true;
286
+ }
287
+ /** ANSI-stripped tail of a PTY agent's terminal, or null if unknown. */
288
+ output(id) {
289
+ return this.agents.get(id)?.output() ?? null;
290
+ }
291
+ get(id) {
292
+ return this.agents.get(id);
293
+ }
294
+ list() {
295
+ return [...this.agents.values()].map((a) => a.view());
296
+ }
297
+ }
298
+ export const ptyRegistry = new PtyRegistry();
299
+ //# sourceMappingURL=pty.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pty.js","sourceRoot":"","sources":["../../src/agents/pty.ts"],"names":[],"mappings":"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"}
@@ -3,7 +3,20 @@ export type ApprovalMode = "auto" | "ask" | "ask-mutating";
3
3
  export interface ApprovalConfig {
4
4
  mode: ApprovalMode;
5
5
  mutatingTools: Set<string>;
6
+ /** Per-tool action-level mutating gate, for action-dispatched tools whose
7
+ * reads are safe but writes aren't (e.g. github). tool → mutating actions. */
8
+ mutatingActions?: Record<string, string[]>;
9
+ /** Injectable prompt reader (tests); defaults to reading one stdin line. */
10
+ readLine?: () => Promise<string>;
6
11
  }
7
12
  export declare const DEFAULT_MUTATING_TOOLS: Set<string>;
13
+ /**
14
+ * Action-dispatched tools where only SOME actions mutate. Reads (issue_view,
15
+ * pr_view, …) stay un-gated under ask-mutating; only these write actions prompt.
16
+ * Mirrors github.ts's own "reads are safe; create/comment/merge are writes".
17
+ */
18
+ export declare const DEFAULT_MUTATING_ACTIONS: Record<string, string[]>;
19
+ /** Does this tool call change state (so ask-mutating should prompt)? */
20
+ export declare function isMutatingCall(cfg: ApprovalConfig, toolName: string, input: unknown): boolean;
8
21
  export declare function buildApprovalCallback(cfg: ApprovalConfig): ApprovalCallback | undefined;
9
22
  //# sourceMappingURL=approval.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,YAAY,CAAC;AAErE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,cAAc,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED,eAAO,MAAM,sBAAsB,aAKjC,CAAC;AAEH,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,cAAc,GAClB,gBAAgB,GAAG,SAAS,CAgB9B"}
1
+ {"version":3,"file":"approval.d.ts","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAoB,MAAM,YAAY,CAAC;AAErE,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,cAAc,CAAC;AAE3D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B;mFAC+E;IAC/E,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC3C,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,MAAM,CAAC,CAAC;CAClC;AAED,eAAO,MAAM,sBAAsB,aAQjC,CAAC;AAEH;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAE7D,CAAC;AAEF,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,GAAG,EAAE,cAAc,EACnB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,OAAO,GACb,OAAO,CAQT;AAED,wBAAgB,qBAAqB,CACnC,GAAG,EAAE,cAAc,GAClB,gBAAgB,GAAG,SAAS,CAgB9B"}
package/dist/approval.js CHANGED
@@ -4,17 +4,40 @@ export const DEFAULT_MUTATING_TOOLS = new Set([
4
4
  "edit",
5
5
  "apply_patch",
6
6
  "bash",
7
+ // Local execution / process control — always state-changing.
8
+ "dispatch_agent",
9
+ "signal_agent",
7
10
  ]);
11
+ /**
12
+ * Action-dispatched tools where only SOME actions mutate. Reads (issue_view,
13
+ * pr_view, …) stay un-gated under ask-mutating; only these write actions prompt.
14
+ * Mirrors github.ts's own "reads are safe; create/comment/merge are writes".
15
+ */
16
+ export const DEFAULT_MUTATING_ACTIONS = {
17
+ github: ["issue_create", "issue_comment", "pr_create", "pr_comment", "pr_merge"],
18
+ };
19
+ /** Does this tool call change state (so ask-mutating should prompt)? */
20
+ export function isMutatingCall(cfg, toolName, input) {
21
+ if (cfg.mutatingTools.has(toolName))
22
+ return true;
23
+ const actions = cfg.mutatingActions?.[toolName];
24
+ if (actions && input && typeof input === "object") {
25
+ const action = input.action;
26
+ if (typeof action === "string" && actions.includes(action))
27
+ return true;
28
+ }
29
+ return false;
30
+ }
8
31
  export function buildApprovalCallback(cfg) {
9
32
  if (cfg.mode === "auto")
10
33
  return undefined;
11
34
  return async (toolName, toolInput) => {
12
- if (cfg.mode === "ask-mutating" && !cfg.mutatingTools.has(toolName)) {
35
+ if (cfg.mode === "ask-mutating" && !isMutatingCall(cfg, toolName, toolInput)) {
13
36
  return { allow: true };
14
37
  }
15
38
  const preview = previewInput(toolInput);
16
39
  process.stderr.write(`\n[approval] ${toolName}(${preview})\n [y]es / [n]o (default n) > `);
17
- const answer = await readSingleLine();
40
+ const answer = await (cfg.readLine ?? readSingleLine)();
18
41
  if (/^y(es)?$/i.test(answer.trim())) {
19
42
  return { allow: true };
20
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,eAAe,CAAC;AAUrC,MAAM,CAAC,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IAC5C,OAAO;IACP,MAAM;IACN,aAAa;IACb,MAAM;CACP,CAAC,CAAC;AAEH,MAAM,UAAU,qBAAqB,CACnC,GAAmB;IAEnB,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,SAAS,CAAC;IAC1C,OAAO,KAAK,EAAE,QAAgB,EAAE,SAAkB,EAA6B,EAAE;QAC/E,IAAI,GAAG,CAAC,IAAI,KAAK,cAAc,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;YACpE,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzB,CAAC;QACD,MAAM,OAAO,GAAG,YAAY,CAAC,SAAS,CAAC,CAAC;QACxC,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,gBAAgB,QAAQ,IAAI,OAAO,kCAAkC,CACtE,CAAC;QACF,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;QACtC,IAAI,WAAW,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACpC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;QACzB,CAAC;QACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,aAAa,EAAE,CAAC;IAClE,CAAC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,IAAI,CAAC,MAAM,IAAI,GAAG;YAAE,OAAO,IAAI,CAAC;QACpC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,GAAG,KAAK,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED,SAAS,cAAc;IACrB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC;YAClC,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;SACvB,CAAC,CAAC;QACH,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,EAAE,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"approval.js","sourceRoot":"","sources":["../src/approval.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ export declare const AUTONOMY_DIR: string;
2
+ /** Which self-driven mechanism produced the run. */
3
+ export type AutonomyKind = "idle" | "heartbeat" | "examen" | "desire" | "reflect";
4
+ /**
5
+ * What actually happened — replaces the old silent/non-silent binary.
6
+ * - "done" — produced a user-facing result / made real progress
7
+ * - "no-update" — ran fine, nothing worth surfacing (internal-only)
8
+ * - "blocked" — stopped early by a guard (e.g. token budget breaker)
9
+ * - "error" — threw / produced unusable output
10
+ */
11
+ export type AutonomyOutcome = "done" | "no-update" | "blocked" | "error";
12
+ export interface AutonomyRun {
13
+ kind: AutonomyKind;
14
+ /** Task name for heartbeat/desire/examen runs (e.g. "desire:learn-rust"). */
15
+ task?: string;
16
+ /** ISO 8601 start time. */
17
+ startedAt: string;
18
+ durationMs: number;
19
+ inputTokens: number;
20
+ outputTokens: number;
21
+ /** Tool calls observed during the run, when known. */
22
+ toolCalls?: number;
23
+ outcome: AutonomyOutcome;
24
+ /** Short human note — failure reason, stop reason, etc. */
25
+ note?: string;
26
+ }
27
+ /** Append one run record; trim the ledger opportunistically. Never throws. */
28
+ export declare function recordAutonomyRun(run: AutonomyRun): Promise<void>;
29
+ /** Read run records, optionally only those within the last `sinceMs`. */
30
+ export declare function readAutonomyRuns(sinceMs?: number): Promise<AutonomyRun[]>;
31
+ /** Human-readable digest for `lisa autonomy`. */
32
+ export declare function summarizeAutonomyRuns(runs: AutonomyRun[]): string;
33
+ //# sourceMappingURL=runs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runs.d.ts","sourceRoot":"","sources":["../../src/autonomy/runs.ts"],"names":[],"mappings":"AAoBA,eAAO,MAAM,YAAY,QAAmC,CAAC;AAK7D,oDAAoD;AACpD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAElF;;;;;;GAMG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAC;AAEzE,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,YAAY,CAAC;IACnB,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2BAA2B;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,eAAe,CAAC;IACzB,2DAA2D;IAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,8EAA8E;AAC9E,wBAAsB,iBAAiB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBvE;AAED,yEAAyE;AACzE,wBAAsB,gBAAgB,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAgB/E;AAED,iDAAiD;AACjD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,MAAM,CAoCjE"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Autonomy run ledger (PLAN_REVE_v1.0 R2).
3
+ *
4
+ * Every self-driven run — idle reflection (Reve), heartbeat tasks, desire pursuits, the
5
+ * weekly examen, end-of-session reflection — appends one structured record
6
+ * here. Before this, autonomy was a black box: idle/heartbeat logged their
7
+ * final text and a `silent` boolean, so you couldn't tell "did real work" from
8
+ * "said (no update) but actually failed to run". This ledger makes outcome,
9
+ * cost, and cadence observable (see `lisa autonomy`).
10
+ *
11
+ * Storage: append-only JSONL at ~/.lisa/autonomy/runs.jsonl, bounded to the
12
+ * last MAX_RUNS lines so it can't grow without limit. Append of a single small
13
+ * line is atomic on POSIX (O_APPEND); the trim runs opportunistically under a
14
+ * cross-process lock.
15
+ */
16
+ import path from "node:path";
17
+ import { LISA_HOME } from "../paths.js";
18
+ import { appendLine, atomicWrite, readTextOrEmpty } from "../fs-utils.js";
19
+ import { withFileLock } from "../soul/lock.js";
20
+ export const AUTONOMY_DIR = path.join(LISA_HOME, "autonomy");
21
+ const RUNS_FILE = path.join(AUTONOMY_DIR, "runs.jsonl");
22
+ const RUNS_LOCK = path.join(AUTONOMY_DIR, "runs.lock");
23
+ const MAX_RUNS = 2000;
24
+ /** Append one run record; trim the ledger opportunistically. Never throws. */
25
+ export async function recordAutonomyRun(run) {
26
+ try {
27
+ await appendLine(RUNS_FILE, JSON.stringify(run));
28
+ }
29
+ catch {
30
+ return; // recording is best-effort; never break the autonomy run on it
31
+ }
32
+ try {
33
+ const lineCount = (await readTextOrEmpty(RUNS_FILE)).split("\n").filter((l) => l.trim()).length;
34
+ if (lineCount > MAX_RUNS) {
35
+ await withFileLock(RUNS_LOCK, async () => {
36
+ const lines = (await readTextOrEmpty(RUNS_FILE)).split("\n").filter((l) => l.trim());
37
+ if (lines.length > MAX_RUNS) {
38
+ await atomicWrite(RUNS_FILE, lines.slice(lines.length - MAX_RUNS).join("\n") + "\n");
39
+ }
40
+ }, { timeoutMs: 2_000, staleMs: 60_000 });
41
+ }
42
+ }
43
+ catch {
44
+ // trimming is best-effort
45
+ }
46
+ }
47
+ /** Read run records, optionally only those within the last `sinceMs`. */
48
+ export async function readAutonomyRuns(sinceMs) {
49
+ const raw = await readTextOrEmpty(RUNS_FILE);
50
+ const cutoff = sinceMs != null ? Date.now() - sinceMs : null;
51
+ const out = [];
52
+ for (const line of raw.split("\n")) {
53
+ const t = line.trim();
54
+ if (!t)
55
+ continue;
56
+ try {
57
+ const run = JSON.parse(t);
58
+ if (cutoff != null && new Date(run.startedAt).getTime() < cutoff)
59
+ continue;
60
+ out.push(run);
61
+ }
62
+ catch {
63
+ // skip a malformed line rather than failing the whole read
64
+ }
65
+ }
66
+ return out;
67
+ }
68
+ /** Human-readable digest for `lisa autonomy`. */
69
+ export function summarizeAutonomyRuns(runs) {
70
+ if (runs.length === 0)
71
+ return "No autonomy runs recorded yet.";
72
+ const byKind = new Map();
73
+ const byOutcome = new Map();
74
+ let inTok = 0;
75
+ let outTok = 0;
76
+ for (const r of runs) {
77
+ byKind.set(r.kind, (byKind.get(r.kind) ?? 0) + 1);
78
+ byOutcome.set(r.outcome, (byOutcome.get(r.outcome) ?? 0) + 1);
79
+ inTok += r.inputTokens || 0;
80
+ outTok += r.outputTokens || 0;
81
+ }
82
+ const fmtMap = (m) => Array.from(m.entries())
83
+ .sort((a, b) => b[1] - a[1])
84
+ .map(([k, n]) => `${k} ${n}`)
85
+ .join(" · ");
86
+ const k = (n) => (n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n));
87
+ const recent = runs
88
+ .slice(-10)
89
+ .reverse()
90
+ .map((r) => {
91
+ const when = r.startedAt.replace("T", " ").slice(0, 16);
92
+ const label = (r.task ?? r.kind).padEnd(22).slice(0, 22);
93
+ const tok = k((r.inputTokens || 0) + (r.outputTokens || 0));
94
+ return ` ${when} ${label} ${r.outcome.padEnd(10)} ${tok} tok`;
95
+ })
96
+ .join("\n");
97
+ return [
98
+ `Autonomy runs: ${runs.length} total`,
99
+ ` by kind: ${fmtMap(byKind)}`,
100
+ ` by outcome: ${fmtMap(byOutcome)}`,
101
+ ` tokens: in ${k(inTok)} · out ${k(outTok)}`,
102
+ ` recent:`,
103
+ recent,
104
+ ].join("\n");
105
+ }
106
+ //# sourceMappingURL=runs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runs.js","sourceRoot":"","sources":["../../src/autonomy/runs.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"feishu.d.ts","sourceRoot":"","sources":["../../src/channels/feishu.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,UAAU,aAAa;IACrB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAOD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,MAAM,CAAC,CAAc;IAC7B,OAAO,CAAC,OAAO,CAAC,CAA0C;IAC1D,OAAO,CAAC,UAAU,CAAC,CAAyB;IAC5C,OAAO,CAAC,cAAc,CAAqB;gBAE/B,IAAI,EAAE,aAAa;IASzB,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrB,IAAI,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;YA+BjC,SAAS;YAwCT,WAAW;YA+DX,oBAAoB;IAoClC,OAAO,CAAC,OAAO;CAoBhB"}
1
+ {"version":3,"file":"feishu.d.ts","sourceRoot":"","sources":["../../src/channels/feishu.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,cAAc,EACd,eAAe,EACf,eAAe,EAChB,MAAM,YAAY,CAAC;AAEpB,UAAU,aAAa;IACrB,qCAAqC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,8DAA8D;IAC9D,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,yEAAyE;IACzE,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CAC3B;AAOD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,aAAc,YAAW,cAAc;IAClD,QAAQ,CAAC,IAAI,YAAY;IACzB,OAAO,CAAC,IAAI,CAAgB;IAC5B,OAAO,CAAC,MAAM,CAAC,CAAc;IAC7B,OAAO,CAAC,OAAO,CAAC,CAA0C;IAC1D,OAAO,CAAC,UAAU,CAAC,CAAyB;IAC5C,OAAO,CAAC,cAAc,CAAqB;gBAE/B,IAAI,EAAE,aAAa;IAkBzB,KAAK,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAQrB,IAAI,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC;YA+BjC,SAAS;YA2FT,WAAW;YA+DX,oBAAoB;IAoClC,OAAO,CAAC,OAAO;CAoBhB"}
@@ -32,6 +32,13 @@ export class FeishuChannel {
32
32
  if (!opts.appId || !opts.appSecret) {
33
33
  throw new Error("feishu: appId and appSecret are required");
34
34
  }
35
+ // Without either credential we cannot authenticate inbound events at all —
36
+ // anyone who can reach the webhook port could impersonate Feishu and feed
37
+ // arbitrary messages to the agent. Both values live on the same console
38
+ // page as the app secret (事件订阅 → Verification Token / Encrypt Key).
39
+ if (!opts.verificationToken && !opts.encryptKey) {
40
+ throw new Error("feishu: verificationToken (or encryptKey) is required — without one, inbound events are unauthenticated");
41
+ }
35
42
  this.opts = opts;
36
43
  }
37
44
  // ─── Lifecycle ────────────────────────────────────────────────────────────
@@ -78,6 +85,35 @@ export class FeishuChannel {
78
85
  return;
79
86
  }
80
87
  const rawBody = await readBody(req);
88
+ // ── Signature check (when encryptKey is configured) ─────────────────────
89
+ // Feishu signs every delivery with X-Lark-Signature =
90
+ // sha256(timestamp + nonce + encryptKey + rawBody) hex, alongside
91
+ // X-Lark-Request-Timestamp / X-Lark-Request-Nonce. Verify it (and a 5min
92
+ // freshness window against replays) whenever the headers are present.
93
+ if (this.opts.encryptKey) {
94
+ const sig = headerString(req, "x-lark-signature");
95
+ const ts = headerString(req, "x-lark-request-timestamp");
96
+ const nonce = headerString(req, "x-lark-request-nonce");
97
+ if (sig && ts && nonce) {
98
+ const expected = crypto
99
+ .createHash("sha256")
100
+ .update(ts + nonce + this.opts.encryptKey + rawBody)
101
+ .digest("hex");
102
+ if (!timingSafeEqualStr(sig, expected)) {
103
+ console.error("[feishu] rejected event: bad X-Lark-Signature");
104
+ res.writeHead(403);
105
+ res.end("bad signature");
106
+ return;
107
+ }
108
+ const ageMs = Math.abs(Date.now() - Number(ts) * 1000);
109
+ if (!Number.isFinite(ageMs) || ageMs > 5 * 60_000) {
110
+ console.error("[feishu] rejected event: stale timestamp (replay?)");
111
+ res.writeHead(403);
112
+ res.end("stale timestamp");
113
+ return;
114
+ }
115
+ }
116
+ }
81
117
  let payload;
82
118
  try {
83
119
  payload = this.opts.encryptKey
@@ -90,6 +126,25 @@ export class FeishuChannel {
90
126
  res.end("bad payload");
91
127
  return;
92
128
  }
129
+ // ── Verification-token check ─────────────────────────────────────────────
130
+ // The token rides in the payload itself: top-level `token` on the URL
131
+ // challenge (v1), `header.token` on v2 events. It was stored in config but
132
+ // never actually checked — making the webhook spoofable by anyone who
133
+ // could reach the port.
134
+ if (this.opts.verificationToken) {
135
+ const header = payload.header;
136
+ const presented = typeof payload.token === "string"
137
+ ? payload.token
138
+ : typeof header?.token === "string"
139
+ ? header.token
140
+ : "";
141
+ if (!presented || !timingSafeEqualStr(presented, this.opts.verificationToken)) {
142
+ console.error("[feishu] rejected event: verification token mismatch");
143
+ res.writeHead(403);
144
+ res.end("bad token");
145
+ return;
146
+ }
147
+ }
93
148
  // ── URL verification challenge ──────────────────────────────────────────
94
149
  if (typeof payload.challenge === "string") {
95
150
  res.writeHead(200, { "content-type": "application/json" });
@@ -202,6 +257,16 @@ export class FeishuChannel {
202
257
  }
203
258
  }
204
259
  // ─── Helpers ──────────────────────────────────────────────────────────────────
260
+ function headerString(req, name) {
261
+ const v = req.headers[name];
262
+ return typeof v === "string" ? v : Array.isArray(v) ? (v[0] ?? "") : "";
263
+ }
264
+ function timingSafeEqualStr(a, b) {
265
+ // Hash both sides so the comparison is constant-time regardless of length.
266
+ const ha = crypto.createHash("sha256").update(a).digest();
267
+ const hb = crypto.createHash("sha256").update(b).digest();
268
+ return crypto.timingSafeEqual(ha, hb);
269
+ }
205
270
  function readBody(req) {
206
271
  return new Promise((resolve, reject) => {
207
272
  let body = "";