@oratis/lisa 0.3.0 → 0.4.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 (101) hide show
  1. package/README.md +34 -5
  2. package/README.zh-CN.md +34 -5
  3. package/dist/advisor/detectors.d.ts +25 -0
  4. package/dist/advisor/detectors.d.ts.map +1 -0
  5. package/dist/advisor/detectors.js +161 -0
  6. package/dist/advisor/detectors.js.map +1 -0
  7. package/dist/advisor/engine.d.ts +41 -0
  8. package/dist/advisor/engine.d.ts.map +1 -0
  9. package/dist/advisor/engine.js +156 -0
  10. package/dist/advisor/engine.js.map +1 -0
  11. package/dist/advisor/types.d.ts +68 -0
  12. package/dist/advisor/types.d.ts.map +1 -0
  13. package/dist/advisor/types.js +19 -0
  14. package/dist/advisor/types.js.map +1 -0
  15. package/dist/channels/imessage.d.ts +16 -0
  16. package/dist/channels/imessage.d.ts.map +1 -1
  17. package/dist/channels/imessage.js +29 -8
  18. package/dist/channels/imessage.js.map +1 -1
  19. package/dist/heartbeat/config.d.ts +10 -0
  20. package/dist/heartbeat/config.d.ts.map +1 -1
  21. package/dist/heartbeat/config.js +8 -2
  22. package/dist/heartbeat/config.js.map +1 -1
  23. package/dist/heartbeat/runner.d.ts.map +1 -1
  24. package/dist/heartbeat/runner.js +30 -0
  25. package/dist/heartbeat/runner.js.map +1 -1
  26. package/dist/integrations/claude-code/observer.d.ts +19 -0
  27. package/dist/integrations/claude-code/observer.d.ts.map +1 -0
  28. package/dist/integrations/claude-code/observer.js +59 -0
  29. package/dist/integrations/claude-code/observer.js.map +1 -0
  30. package/dist/integrations/claude-code/parser.d.ts +2 -0
  31. package/dist/integrations/claude-code/parser.d.ts.map +1 -1
  32. package/dist/integrations/claude-code/parser.js +153 -0
  33. package/dist/integrations/claude-code/parser.js.map +1 -1
  34. package/dist/integrations/claude-code/watcher.d.ts +9 -0
  35. package/dist/integrations/claude-code/watcher.d.ts.map +1 -1
  36. package/dist/integrations/claude-code/watcher.js +25 -4
  37. package/dist/integrations/claude-code/watcher.js.map +1 -1
  38. package/dist/integrations/codex/observer.d.ts +48 -0
  39. package/dist/integrations/codex/observer.d.ts.map +1 -0
  40. package/dist/integrations/codex/observer.js +213 -0
  41. package/dist/integrations/codex/observer.js.map +1 -0
  42. package/dist/integrations/current-hub.d.ts +13 -0
  43. package/dist/integrations/current-hub.d.ts.map +1 -0
  44. package/dist/integrations/current-hub.js +17 -0
  45. package/dist/integrations/current-hub.js.map +1 -0
  46. package/dist/integrations/hub.d.ts +45 -0
  47. package/dist/integrations/hub.d.ts.map +1 -0
  48. package/dist/integrations/hub.js +110 -0
  49. package/dist/integrations/hub.js.map +1 -0
  50. package/dist/integrations/registry.d.ts +15 -0
  51. package/dist/integrations/registry.d.ts.map +1 -0
  52. package/dist/integrations/registry.js +38 -0
  53. package/dist/integrations/registry.js.map +1 -0
  54. package/dist/integrations/types.d.ts +90 -0
  55. package/dist/integrations/types.d.ts.map +1 -0
  56. package/dist/integrations/types.js +12 -0
  57. package/dist/integrations/types.js.map +1 -0
  58. package/dist/memory/vector.d.ts +5 -1
  59. package/dist/memory/vector.d.ts.map +1 -1
  60. package/dist/memory/vector.js +58 -1
  61. package/dist/memory/vector.js.map +1 -1
  62. package/dist/soul/lock.d.ts +38 -0
  63. package/dist/soul/lock.d.ts.map +1 -0
  64. package/dist/soul/lock.js +103 -0
  65. package/dist/soul/lock.js.map +1 -0
  66. package/dist/soul/paths.d.ts.map +1 -1
  67. package/dist/soul/paths.js +12 -6
  68. package/dist/soul/paths.js.map +1 -1
  69. package/dist/soul/slug.d.ts +40 -0
  70. package/dist/soul/slug.d.ts.map +1 -0
  71. package/dist/soul/slug.js +78 -0
  72. package/dist/soul/slug.js.map +1 -0
  73. package/dist/soul/store.d.ts +12 -1
  74. package/dist/soul/store.d.ts.map +1 -1
  75. package/dist/soul/store.js +40 -23
  76. package/dist/soul/store.js.map +1 -1
  77. package/dist/soul/tools.d.ts.map +1 -1
  78. package/dist/soul/tools.js +10 -3
  79. package/dist/soul/tools.js.map +1 -1
  80. package/dist/tools/advise_now.d.ts +11 -0
  81. package/dist/tools/advise_now.d.ts.map +1 -0
  82. package/dist/tools/advise_now.js +34 -0
  83. package/dist/tools/advise_now.js.map +1 -0
  84. package/dist/tools/dispatch_agent.d.ts +38 -0
  85. package/dist/tools/dispatch_agent.d.ts.map +1 -0
  86. package/dist/tools/dispatch_agent.js +145 -0
  87. package/dist/tools/dispatch_agent.js.map +1 -0
  88. package/dist/tools/registry.d.ts.map +1 -1
  89. package/dist/tools/registry.js.map +1 -1
  90. package/dist/tools/web_fetch.d.ts +10 -0
  91. package/dist/tools/web_fetch.d.ts.map +1 -1
  92. package/dist/tools/web_fetch.js +48 -16
  93. package/dist/tools/web_fetch.js.map +1 -1
  94. package/dist/web/island.d.ts +1 -1
  95. package/dist/web/island.d.ts.map +1 -1
  96. package/dist/web/island.js +69 -5
  97. package/dist/web/island.js.map +1 -1
  98. package/dist/web/server.d.ts.map +1 -1
  99. package/dist/web/server.js +79 -16
  100. package/dist/web/server.js.map +1 -1
  101. package/package.json +3 -1
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@oratis/lisa?color=cb3837&label=npm)](https://www.npmjs.com/package/@oratis/lisa)
4
4
  [![Homebrew](https://img.shields.io/badge/homebrew-oratis%2Ftap%2Flisa-fbb040)](https://github.com/oratis/homebrew-tap)
5
+ [![Mac DMG](https://img.shields.io/github/v/release/oratis/LISA?label=Mac%20app&color=000000&logo=apple)](https://github.com/oratis/LISA/releases/latest)
5
6
  [![License: MIT](https://img.shields.io/github/license/oratis/LISA?color=blue)](./LICENSE)
6
7
  [![GitHub Repo stars](https://img.shields.io/github/stars/oratis/LISA?style=social)](https://github.com/oratis/LISA/stargazers)
7
8
 
@@ -116,7 +117,39 @@ She is yours. The code is open source. **This particular Lisa is sovereign.**
116
117
 
117
118
  ## Install
118
119
 
119
- Requires Node 20 and a key for **at least one LLM provider** — Anthropic is the default, but any of the 20+ alternatives listed below works just as well (`--model gpt-4o`, `--model deepseek-chat`, `LISA_BASE_URL=http://localhost:11434/v1` for Ollama, etc.).
120
+ Three ways, pick one. All of them need an LLM key — Anthropic is the default, but any of the 20+ alternatives listed below works just as well (`--model gpt-4o`, `--model deepseek-chat`, `LISA_BASE_URL=http://localhost:11434/v1` for Ollama, etc.).
121
+
122
+ ```sh
123
+ # 1. Configure a provider key (do this once, regardless of which install you pick)
124
+ mkdir -p ~/.lisa
125
+ echo 'ANTHROPIC_API_KEY=sk-ant-...' > ~/.lisa/config.env
126
+ ```
127
+
128
+ ### 🍎 Mac apps (recommended on macOS)
129
+
130
+ Download the **signed + notarized** disk image from the latest GitHub Release — no Gatekeeper warning, no `xattr` workaround:
131
+
132
+ **→ [Download `Lisa-Suite.dmg`](https://github.com/oratis/LISA/releases/latest)**
133
+
134
+ The DMG contains:
135
+ - **Lisa.app** — full chat client (sidebar + glass-morphism UI)
136
+ - **LisaIsland.app** — pill widget that lives by the menu bar / notch, shows her mood + Claude Code activity at a glance
137
+
138
+ Both are universal binaries (Intel + Apple Silicon). After dragging them to `/Applications`, install the LISA backend and start it:
139
+
140
+ ```sh
141
+ npm install -g @oratis/lisa
142
+ lisa serve --web # apps load http://localhost:5757
143
+ ```
144
+
145
+ ### 📟 Homebrew (CLI only)
146
+
147
+ ```sh
148
+ brew install oratis/tap/lisa
149
+ lisa # birth ritual on first run
150
+ ```
151
+
152
+ ### 🛠 From source (full control)
120
153
 
121
154
  ```sh
122
155
  git clone https://github.com/oratis/LISA.git
@@ -124,10 +157,6 @@ cd LISA
124
157
  npm install
125
158
  npm run build
126
159
 
127
- # Configure a key — Anthropic is the default model, swap for any provider below
128
- mkdir -p ~/.lisa
129
- echo 'ANTHROPIC_API_KEY=sk-ant-...' > ~/.lisa/config.env
130
-
131
160
  # First run triggers the birth ritual automatically (~30s, one-time)
132
161
  node dist/cli.js
133
162
  # or, after `npm link`:
package/README.zh-CN.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@oratis/lisa?color=cb3837&label=npm)](https://www.npmjs.com/package/@oratis/lisa)
4
4
  [![Homebrew](https://img.shields.io/badge/homebrew-oratis%2Ftap%2Flisa-fbb040)](https://github.com/oratis/homebrew-tap)
5
+ [![Mac DMG](https://img.shields.io/github/v/release/oratis/LISA?label=Mac%20app&color=000000&logo=apple)](https://github.com/oratis/LISA/releases/latest)
5
6
  [![License: MIT](https://img.shields.io/github/license/oratis/LISA?color=blue)](./LICENSE)
6
7
  [![GitHub Repo stars](https://img.shields.io/github/stars/oratis/LISA?style=social)](https://github.com/oratis/LISA/stargazers)
7
8
 
@@ -113,7 +114,39 @@
113
114
 
114
115
  ## 安装
115
116
 
116
- Node ≥ 20 + **任一 LLM provider 的 key** —— 默认走 Anthropic,但下面列的 20+ 个 provider 任何一个都行(`--model gpt-4o`、`--model deepseek-chat`、Ollama 走 `LISA_BASE_URL=http://localhost:11434/v1` 等)。
117
+ 三种方式,任选其一。都需要至少一个 LLM provider 的 key —— 默认 Anthropic,下面列的 20+ 个 provider 都行(`--model gpt-4o`、`--model deepseek-chat`、Ollama 走 `LISA_BASE_URL=http://localhost:11434/v1` 等)。
118
+
119
+ ```sh
120
+ # 1. 配置 provider key(只需要做一次,与下面装哪种无关)
121
+ mkdir -p ~/.lisa
122
+ echo 'ANTHROPIC_API_KEY=sk-ant-...' > ~/.lisa/config.env
123
+ ```
124
+
125
+ ### 🍎 Mac 原生 App(macOS 推荐)
126
+
127
+ 从 GitHub Release 下载**已签名 + 已公证**的 DMG —— 没有 Gatekeeper 警告,无需 `xattr` 解除隔离:
128
+
129
+ **→ [下载 `Lisa-Suite.dmg`](https://github.com/oratis/LISA/releases/latest)**
130
+
131
+ DMG 里有两个 app:
132
+ - **Lisa.app** —— 完整聊天客户端(侧边栏 + 玻璃拟态界面)
133
+ - **LisaIsland.app** —— 灵动岛风格的菜单栏/刘海下小条,一眼看到 Lisa 心情 + 你那边 Claude Code 的状态
134
+
135
+ 都是 universal binary(Intel + Apple Silicon)。拖到 `/Applications` 之后,装 backend 并启动:
136
+
137
+ ```sh
138
+ npm install -g @oratis/lisa
139
+ lisa serve --web # 两个 app 都从 http://localhost:5757 读
140
+ ```
141
+
142
+ ### 📟 Homebrew(只装 CLI)
143
+
144
+ ```sh
145
+ brew install oratis/tap/lisa
146
+ lisa # 第一次跑会触发 birth ritual
147
+ ```
148
+
149
+ ### 🛠 从源码(完全控制)
117
150
 
118
151
  ```sh
119
152
  git clone https://github.com/oratis/LISA.git
@@ -121,10 +154,6 @@ cd LISA
121
154
  npm install
122
155
  npm run build
123
156
 
124
- # 配置 key —— 默认模型走 Anthropic;想换 provider 看下面那张表
125
- mkdir -p ~/.lisa
126
- echo 'ANTHROPIC_API_KEY=sk-ant-...' > ~/.lisa/config.env
127
-
128
157
  # 第一次跑会自动触发 birth ritual(约 30 秒,一次性)
129
158
  node dist/cli.js
130
159
  # 或者 npm link 之后:
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Advisor detectors — pure functions: an AdvisorInput snapshot in, candidate
3
+ * Suggestions out. No I/O, no state mutation, fully testable. The engine
4
+ * (engine.ts) is what scores + throttles + dedupes them.
5
+ *
6
+ * Every detector works from STRUCTURAL signals only (state, cwd, the Tier-2
7
+ * SessionActivity) — never conversation content.
8
+ */
9
+ import type { AdvisorInput, Suggestion } from "./types.js";
10
+ export declare const STUCK_MS: number;
11
+ export declare const COST_SPIKE_TOKENS = 1500000;
12
+ /** Pending permission → urgent, actionable. */
13
+ export declare function detectPermission(input: AdvisorInput): Suggestion[];
14
+ export declare function detectStuck(input: AdvisorInput): Suggestion[];
15
+ /** Two+ active sessions sharing a cwd (or an overlapping touched file). */
16
+ export declare function detectConflict(input: AdvisorInput): Suggestion[];
17
+ /** Combined token usage across active sessions above the spike threshold. */
18
+ export declare function detectCostSpike(input: AdvisorInput): Suggestion[];
19
+ /** A session that finished a turn and is awaiting you (end_turn / done). */
20
+ export declare function detectReady(input: AdvisorInput): Suggestion[];
21
+ /** Nothing running but standing chores exist. */
22
+ export declare function detectIdleCapacity(input: AdvisorInput): Suggestion[];
23
+ /** Run every detector and concatenate their candidates. */
24
+ export declare function runAllDetectors(input: AdvisorInput): Suggestion[];
25
+ //# sourceMappingURL=detectors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectors.d.ts","sourceRoot":"","sources":["../../src/advisor/detectors.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,UAAU,EAA+B,MAAM,YAAY,CAAC;AAGxF,eAAO,MAAM,QAAQ,QAAc,CAAC;AACpC,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAoB3C,+CAA+C;AAC/C,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAAE,CAkBlE;AAoBD,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAAE,CAyC7D;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAAE,CA2BhE;AAED,6EAA6E;AAC7E,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAAE,CA6BjE;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAAE,CAqB7D;AAED,iDAAiD;AACjD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAAE,CAiBpE;AAED,2DAA2D;AAC3D,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,UAAU,EAAE,CASjE"}
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Advisor detectors — pure functions: an AdvisorInput snapshot in, candidate
3
+ * Suggestions out. No I/O, no state mutation, fully testable. The engine
4
+ * (engine.ts) is what scores + throttles + dedupes them.
5
+ *
6
+ * Every detector works from STRUCTURAL signals only (state, cwd, the Tier-2
7
+ * SessionActivity) — never conversation content.
8
+ */
9
+ // Thresholds (ms). Tuned to "a human would want this flagged".
10
+ export const STUCK_MS = 10 * 60_000; // waiting/error with no change for 10 min
11
+ export const COST_SPIKE_TOKENS = 1_500_000; // combined tokens across active sessions
12
+ function mk(id, category, urgency, text, conditionHash, now, action) {
13
+ return { id, category, urgency, text, conditionHash, ts: now, action, score: 0 };
14
+ }
15
+ function basename(p) {
16
+ if (!p)
17
+ return "";
18
+ const parts = p.split("/");
19
+ return parts[parts.length - 1] || p;
20
+ }
21
+ /** Pending permission → urgent, actionable. */
22
+ export function detectPermission(input) {
23
+ const out = [];
24
+ for (const s of input.sessions) {
25
+ const pend = s.activity?.pendingPermission;
26
+ if (!pend)
27
+ continue;
28
+ out.push(mk(`permission:${s.agent}:${s.sessionId}`, "stuck", "urgent", `${s.project} wants to run ${pend} — approve?`, `permission:${pend}`, input.now, { label: "Approve", kind: "approve", arg: s.sessionId }));
29
+ }
30
+ return out;
31
+ }
32
+ /**
33
+ * "Stuck" = a session that looks like it needs attention but isn't making
34
+ * progress. The trap (and the v1 noise bug) is that a `waiting` session is
35
+ * USUALLY fine — Claude finished its turn (end_turn) and is just idle waiting
36
+ * for the user. That's not stuck, it's normal, and flagging every >10min idle
37
+ * session buries the real signal.
38
+ *
39
+ * So a session counts as stuck only when:
40
+ * - state is `error` (something actually broke), OR
41
+ * - state is `waiting` with a reason that means "blocked / not cleanly
42
+ * finished" — i.e. NOT end_turn/stop_sequence/max_tokens (clean stops,
43
+ * surfaced quietly by detectReady instead).
44
+ * AND it's been quiet for ≥ STUCK_MS. If more than COLLAPSE_THRESHOLD qualify,
45
+ * emit ONE rolled-up line so the digest stays scannable.
46
+ */
47
+ const CLEAN_STOP_REASONS = new Set(["end_turn", "stop_sequence", "max_tokens"]);
48
+ const COLLAPSE_THRESHOLD = 3;
49
+ export function detectStuck(input) {
50
+ const stuck = input.sessions.filter((s) => {
51
+ if (s.activity?.pendingPermission)
52
+ return false; // detectPermission owns these
53
+ if (input.now - s.lastMtime < STUCK_MS)
54
+ return false;
55
+ if (s.state === "error")
56
+ return true;
57
+ if (s.state === "waiting")
58
+ return !CLEAN_STOP_REASONS.has(s.stateReason);
59
+ return false;
60
+ });
61
+ if (stuck.length === 0)
62
+ return [];
63
+ if (stuck.length > COLLAPSE_THRESHOLD) {
64
+ const errs = stuck.filter((s) => s.state === "error").length;
65
+ const detail = errs > 0 ? ` (${errs} errored)` : "";
66
+ return [
67
+ mk(`stuck-many:${stuck.length}`, "stuck", "notice", `${stuck.length} agent sessions look stuck or stalled${detail} — want the list?`, `stuck-many:${stuck.length}:${errs}`, input.now, { label: "List", kind: "look", arg: "stuck" }),
68
+ ];
69
+ }
70
+ return stuck.map((s) => {
71
+ const mins = Math.round((input.now - s.lastMtime) / 60_000);
72
+ const cmd = s.activity?.lastCommandName;
73
+ const why = s.state === "error" ? "errored" : "has stalled";
74
+ const on = cmd ? ` on \`${cmd}\`` : "";
75
+ return mk(`stuck:${s.agent}:${s.sessionId}`, "stuck", "notice", `${s.project} ${why}${on} (idle ${mins}m) — want me to look, or cancel it?`, `stuck:${s.state}:${s.stateReason}:${cmd ?? ""}`, input.now, { label: "Look", kind: "look", arg: s.sessionId });
76
+ });
77
+ }
78
+ /** Two+ active sessions sharing a cwd (or an overlapping touched file). */
79
+ export function detectConflict(input) {
80
+ const out = [];
81
+ const byCwd = new Map();
82
+ for (const s of input.sessions) {
83
+ if (!s.cwd)
84
+ continue;
85
+ if (s.state !== "working" && s.state !== "waiting")
86
+ continue;
87
+ const arr = byCwd.get(s.cwd) ?? [];
88
+ arr.push(s);
89
+ byCwd.set(s.cwd, arr);
90
+ }
91
+ for (const [cwd, group] of byCwd) {
92
+ if (group.length < 2)
93
+ continue;
94
+ const agents = [...new Set(group.map((g) => g.agent))];
95
+ const label = basename(cwd);
96
+ out.push(mk(`conflict:${cwd}`, "conflict", "notice", `${group.length} agents (${agents.join(", ")}) are both working in ${label} — high risk of clobbering. Serialize?`, `conflict:${group.map((g) => g.sessionId).sort().join(",")}`, input.now, { label: "Serialize", kind: "serialize", arg: cwd }));
97
+ }
98
+ return out;
99
+ }
100
+ /** Combined token usage across active sessions above the spike threshold. */
101
+ export function detectCostSpike(input) {
102
+ let total = 0;
103
+ let topSession = null;
104
+ let topTokens = 0;
105
+ for (const s of input.sessions) {
106
+ const t = s.activity?.tokens;
107
+ if (!t)
108
+ continue;
109
+ const sum = (t.input ?? 0) + (t.output ?? 0);
110
+ total += sum;
111
+ if (sum > topTokens) {
112
+ topTokens = sum;
113
+ topSession = s;
114
+ }
115
+ }
116
+ if (total < COST_SPIKE_TOKENS)
117
+ return [];
118
+ const millions = (total / 1_000_000).toFixed(1);
119
+ const pct = total > 0 ? Math.round((topTokens / total) * 100) : 0;
120
+ const who = topSession ? ` — ${topSession.project} is ${pct}% of it` : "";
121
+ return [
122
+ mk(`cost:${Math.round(total / 100_000)}`, // bucketed so it re-fires per ~100k step
123
+ "cost_spike", "notice", `Active agents are at ${millions}M tokens${who}.`, `cost:${Math.round(total / 500_000)}`, input.now, topSession ? { label: "Look", kind: "look", arg: topSession.sessionId } : undefined),
124
+ ];
125
+ }
126
+ /** A session that finished a turn and is awaiting you (end_turn / done). */
127
+ export function detectReady(input) {
128
+ const out = [];
129
+ for (const s of input.sessions) {
130
+ const ready = s.state === "done" ||
131
+ (s.state === "waiting" && s.stateReason === "end_turn");
132
+ if (!ready)
133
+ continue;
134
+ if (s.activity?.lastError)
135
+ continue; // erroring isn't "ready"
136
+ out.push(mk(`ready:${s.agent}:${s.sessionId}`, "ready", "info", `${s.project} finished and is waiting for you.`, `ready:${s.lastMtime}`, input.now, { label: "Open", kind: "open", arg: s.cwd ?? s.sessionId }));
137
+ }
138
+ return out;
139
+ }
140
+ /** Nothing running but standing chores exist. */
141
+ export function detectIdleCapacity(input) {
142
+ const active = input.sessions.some((s) => s.state === "working" || s.state === "waiting");
143
+ const pending = input.pendingDesireCount ?? 0;
144
+ if (active || pending <= 0)
145
+ return [];
146
+ return [
147
+ mk("idle-capacity", "idle", "info", `Nothing's running and you have ${pending} standing ${pending === 1 ? "task" : "tasks"} — want me to dispatch them?`, `idle:${pending}`, input.now, { label: "Dispatch", kind: "dispatch", arg: String(pending) }),
148
+ ];
149
+ }
150
+ /** Run every detector and concatenate their candidates. */
151
+ export function runAllDetectors(input) {
152
+ return [
153
+ ...detectPermission(input),
154
+ ...detectStuck(input),
155
+ ...detectConflict(input),
156
+ ...detectCostSpike(input),
157
+ ...detectReady(input),
158
+ ...detectIdleCapacity(input),
159
+ ];
160
+ }
161
+ //# sourceMappingURL=detectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectors.js","sourceRoot":"","sources":["../../src/advisor/detectors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Advisor engine — the anti-annoyance brain.
3
+ *
4
+ * Pure decision core: given this run's candidate suggestions + the persisted
5
+ * advisor state + the clock, decide which clear the relevance bar AND the
6
+ * throttle AND dedup, and which get suppressed (journaled, not shown). The
7
+ * caller does the I/O (load/save state, surface the survivors).
8
+ *
9
+ * See docs/ORCHESTRATOR_PLAN.md §5b.3 for the anti-annoyance contract.
10
+ */
11
+ import { type AdvisorDecision, type AdvisorInput, type AdvisorState, type Suggestion, type SuggestionCategory } from "./types.js";
12
+ export declare const ADVISOR_STATE_PATH: string;
13
+ /** Relevance score = urgency × actionability × dismissal-decay. */
14
+ export declare function scoreSuggestion(s: Suggestion, state: AdvisorState): number;
15
+ /**
16
+ * Pure decision. Returns the surface/suppress split AND the next state to
17
+ * persist. Does not touch disk.
18
+ */
19
+ export declare function decide(candidates: Suggestion[], state: AdvisorState, now: number): {
20
+ decision: AdvisorDecision;
21
+ nextState: AdvisorState;
22
+ };
23
+ /** Record a user dismissal so the category fades over time. */
24
+ export declare function applyDismissal(state: AdvisorState, id: string, category: SuggestionCategory): AdvisorState;
25
+ export declare function loadAdvisorState(p?: string): Promise<AdvisorState>;
26
+ export declare function saveAdvisorState(state: AdvisorState, p?: string): Promise<void>;
27
+ /**
28
+ * Full advise cycle: detect → decide → persist. Returns the suggestions to
29
+ * surface. Uses the soul lock so a heartbeat advise can't race a manual one.
30
+ */
31
+ export declare function advise(input: AdvisorInput, statePath?: string): Promise<AdvisorDecision>;
32
+ /**
33
+ * On-demand ("pull") advice: run the detectors against the current snapshot
34
+ * and return everything above the bar, sorted — WITHOUT the throttle/dedup
35
+ * (the user explicitly asked, so don't suppress) and WITHOUT mutating state.
36
+ * Used by the advise_now tool.
37
+ */
38
+ export declare function adviseNow(input: AdvisorInput, state?: AdvisorState): Suggestion[];
39
+ /** Render surfaced suggestions into one digest card (markdown-ish text). */
40
+ export declare function formatDigest(suggestions: Suggestion[]): string;
41
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../../src/advisor/engine.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,OAAO,EAEL,KAAK,eAAe,EACpB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AAQpB,eAAO,MAAM,kBAAkB,QAA6C,CAAC;AAE7E,mEAAmE;AACnE,wBAAgB,eAAe,CAAC,CAAC,EAAE,UAAU,EAAE,KAAK,EAAE,YAAY,GAAG,MAAM,CAO1E;AAUD;;;GAGG;AACH,wBAAgB,MAAM,CACpB,UAAU,EAAE,UAAU,EAAE,EACxB,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,MAAM,GACV;IAAE,QAAQ,EAAE,eAAe,CAAC;IAAC,SAAS,EAAE,YAAY,CAAA;CAAE,CAgDxD;AAED,+DAA+D;AAC/D,wBAAgB,cAAc,CAC5B,KAAK,EAAE,YAAY,EACnB,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,kBAAkB,GAC3B,YAAY,CASd;AAID,wBAAsB,gBAAgB,CACpC,CAAC,GAAE,MAA2B,GAC7B,OAAO,CAAC,YAAY,CAAC,CAQvB;AAED,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,YAAY,EACnB,CAAC,GAAE,MAA2B,GAC7B,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;;GAGG;AACH,wBAAsB,MAAM,CAC1B,KAAK,EAAE,YAAY,EACnB,SAAS,GAAE,MAA2B,GACrC,OAAO,CAAC,eAAe,CAAC,CAQ1B;AAED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,YAAY,EAAE,KAAK,GAAE,YAAkC,GAAG,UAAU,EAAE,CAMtG;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,WAAW,EAAE,UAAU,EAAE,GAAG,MAAM,CAQ9D"}
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Advisor engine — the anti-annoyance brain.
3
+ *
4
+ * Pure decision core: given this run's candidate suggestions + the persisted
5
+ * advisor state + the clock, decide which clear the relevance bar AND the
6
+ * throttle AND dedup, and which get suppressed (journaled, not shown). The
7
+ * caller does the I/O (load/save state, surface the survivors).
8
+ *
9
+ * See docs/ORCHESTRATOR_PLAN.md §5b.3 for the anti-annoyance contract.
10
+ */
11
+ import fsp from "node:fs/promises";
12
+ import path from "node:path";
13
+ import { LISA_HOME } from "../paths.js";
14
+ import { atomicWrite } from "../fs-utils.js";
15
+ import { withFileLock } from "../soul/lock.js";
16
+ import { runAllDetectors } from "./detectors.js";
17
+ import { emptyAdvisorState, } from "./types.js";
18
+ // Tunables.
19
+ const URGENCY_WEIGHT = { info: 1, notice: 2, urgent: 4 };
20
+ const MIN_SCORE = 1.5; // relevance bar: below this → journal, not user
21
+ const DIGEST_THROTTLE_MS = 3 * 60 * 60_000; // ≤1 non-urgent digest / 3h
22
+ const RE_ARM_MS = 24 * 60 * 60_000; // a standing condition can re-surface daily
23
+ export const ADVISOR_STATE_PATH = path.join(LISA_HOME, "advisor-state.json");
24
+ /** Relevance score = urgency × actionability × dismissal-decay. */
25
+ export function scoreSuggestion(s, state) {
26
+ const urgency = URGENCY_WEIGHT[s.urgency];
27
+ const actionability = s.action ? 1 : 0.5;
28
+ const catDismissals = state.categoryDismissals[s.category] ?? 0;
29
+ // Each dismissal of this category halves its pull (learns to shut up).
30
+ const decay = 1 / (1 + catDismissals * 1.0);
31
+ return urgency * actionability * decay;
32
+ }
33
+ /** Has this exact condition been surfaced recently (→ suppress as dup)? */
34
+ function isDuplicate(s, state, now) {
35
+ const prev = state.surfaced[s.id];
36
+ if (!prev)
37
+ return false;
38
+ if (prev.conditionHash !== s.conditionHash)
39
+ return false; // condition changed → fresh
40
+ return now - prev.ts < RE_ARM_MS;
41
+ }
42
+ /**
43
+ * Pure decision. Returns the surface/suppress split AND the next state to
44
+ * persist. Does not touch disk.
45
+ */
46
+ export function decide(candidates, state, now) {
47
+ const surface = [];
48
+ const suppressed = [];
49
+ // Score everything first.
50
+ for (const c of candidates)
51
+ c.score = scoreSuggestion(c, state);
52
+ const withinThrottle = now - state.lastDigestAt < DIGEST_THROTTLE_MS;
53
+ let surfacedAnyNonUrgent = false;
54
+ for (const c of candidates) {
55
+ // Dedup: same condition seen recently → suppress.
56
+ if (isDuplicate(c, state, now)) {
57
+ suppressed.push(c);
58
+ continue;
59
+ }
60
+ if (c.urgency === "urgent") {
61
+ // Urgent bypasses the bar + throttle — but still deduped above.
62
+ surface.push(c);
63
+ continue;
64
+ }
65
+ // Non-urgent must clear the relevance bar…
66
+ if (c.score < MIN_SCORE) {
67
+ suppressed.push(c);
68
+ continue;
69
+ }
70
+ // …and the digest throttle.
71
+ if (withinThrottle) {
72
+ suppressed.push(c);
73
+ continue;
74
+ }
75
+ surface.push(c);
76
+ surfacedAnyNonUrgent = true;
77
+ }
78
+ // Build next state: record what we surfaced + advance the digest clock.
79
+ const nextState = {
80
+ ...state,
81
+ surfaced: { ...state.surfaced },
82
+ };
83
+ for (const s of surface) {
84
+ nextState.surfaced[s.id] = { ts: now, conditionHash: s.conditionHash };
85
+ }
86
+ if (surfacedAnyNonUrgent)
87
+ nextState.lastDigestAt = now;
88
+ // Sort surfaced by score desc so the digest leads with what matters.
89
+ surface.sort((a, b) => b.score - a.score);
90
+ return { decision: { surface, suppressed }, nextState };
91
+ }
92
+ /** Record a user dismissal so the category fades over time. */
93
+ export function applyDismissal(state, id, category) {
94
+ return {
95
+ ...state,
96
+ dismissals: { ...state.dismissals, [id]: (state.dismissals[id] ?? 0) + 1 },
97
+ categoryDismissals: {
98
+ ...state.categoryDismissals,
99
+ [category]: (state.categoryDismissals[category] ?? 0) + 1,
100
+ },
101
+ };
102
+ }
103
+ // ── I/O ─────────────────────────────────────────────────────────────────
104
+ export async function loadAdvisorState(p = ADVISOR_STATE_PATH) {
105
+ try {
106
+ const raw = await fsp.readFile(p, "utf8");
107
+ const parsed = JSON.parse(raw);
108
+ return { ...emptyAdvisorState(), ...parsed };
109
+ }
110
+ catch {
111
+ return emptyAdvisorState();
112
+ }
113
+ }
114
+ export async function saveAdvisorState(state, p = ADVISOR_STATE_PATH) {
115
+ await atomicWrite(p, JSON.stringify(state, null, 2));
116
+ }
117
+ /**
118
+ * Full advise cycle: detect → decide → persist. Returns the suggestions to
119
+ * surface. Uses the soul lock so a heartbeat advise can't race a manual one.
120
+ */
121
+ export async function advise(input, statePath = ADVISOR_STATE_PATH) {
122
+ return withFileLock(statePath + ".lock", async () => {
123
+ const state = await loadAdvisorState(statePath);
124
+ const candidates = runAllDetectors(input);
125
+ const { decision, nextState } = decide(candidates, state, input.now);
126
+ await saveAdvisorState(nextState, statePath);
127
+ return decision;
128
+ });
129
+ }
130
+ /**
131
+ * On-demand ("pull") advice: run the detectors against the current snapshot
132
+ * and return everything above the bar, sorted — WITHOUT the throttle/dedup
133
+ * (the user explicitly asked, so don't suppress) and WITHOUT mutating state.
134
+ * Used by the advise_now tool.
135
+ */
136
+ export function adviseNow(input, state = emptyAdvisorState()) {
137
+ const cands = runAllDetectors(input);
138
+ for (const c of cands)
139
+ c.score = scoreSuggestion(c, state);
140
+ return cands
141
+ .filter((c) => c.urgency === "urgent" || c.score >= MIN_SCORE)
142
+ .sort((a, b) => b.score - a.score);
143
+ }
144
+ /** Render surfaced suggestions into one digest card (markdown-ish text). */
145
+ export function formatDigest(suggestions) {
146
+ if (suggestions.length === 0)
147
+ return "";
148
+ if (suggestions.length === 1)
149
+ return suggestions[0].text;
150
+ const lines = suggestions.map((s) => {
151
+ const mark = s.urgency === "urgent" ? "⚠ " : "• ";
152
+ return mark + s.text;
153
+ });
154
+ return "A few things across your agents:\n" + lines.join("\n");
155
+ }
156
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../../src/advisor/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Advisor (L5 ADVISE) — types.
3
+ *
4
+ * The advisor turns cross-agent observation into periodic, proactive
5
+ * suggestions. Its one hard problem is not being annoying, so everything is
6
+ * organized around the relevance bar + throttle + dedup (see engine.ts).
7
+ *
8
+ * See docs/ORCHESTRATOR_PLAN.md §5b.
9
+ */
10
+ import type { AgentSession } from "../integrations/types.js";
11
+ export type SuggestionCategory = "stuck" | "conflict" | "repeated_failure" | "cost_spike" | "ready" | "idle";
12
+ /** How loudly a suggestion wants to be surfaced. */
13
+ export type Urgency = "info" | "notice" | "urgent";
14
+ /** A concrete action LISA can offer to take. A suggestion without one is
15
+ * considered non-actionable and won't clear the relevance bar. */
16
+ export interface SuggestedAction {
17
+ label: string;
18
+ kind: "approve" | "cancel" | "open" | "serialize" | "look" | "dispatch";
19
+ /** Opaque arg the action handler needs (sessionId, cwd, …). */
20
+ arg?: string;
21
+ }
22
+ export interface Suggestion {
23
+ /** Stable dedup key — same underlying condition → same id. */
24
+ id: string;
25
+ category: SuggestionCategory;
26
+ urgency: Urgency;
27
+ /** One-line, user-facing. Structural — no conversation content. */
28
+ text: string;
29
+ action?: SuggestedAction;
30
+ /** Computed relevance score (urgency × novelty × actionability). */
31
+ score: number;
32
+ /** A hash of the underlying condition; re-surface only when it changes. */
33
+ conditionHash: string;
34
+ /** When this was generated (epoch ms). */
35
+ ts: number;
36
+ }
37
+ /** Persisted advisor memory (~/.lisa/advisor-state.json). */
38
+ export interface AdvisorState {
39
+ /** id → last time we surfaced it + the condition hash then. */
40
+ surfaced: Record<string, {
41
+ ts: number;
42
+ conditionHash: string;
43
+ }>;
44
+ /** id → number of times the user dismissed it (down-weights category). */
45
+ dismissals: Record<string, number>;
46
+ /** category → dismissal count, for learning what to stop saying. */
47
+ categoryDismissals: Partial<Record<SuggestionCategory, number>>;
48
+ /** last time ANY non-urgent digest was surfaced (throttle). */
49
+ lastDigestAt: number;
50
+ /** rolling memory of (command → error count) for repeated-failure detection. */
51
+ errorCommandCounts: Record<string, number>;
52
+ }
53
+ export declare function emptyAdvisorState(): AdvisorState;
54
+ /** Input snapshot for the detectors. */
55
+ export interface AdvisorInput {
56
+ sessions: AgentSession[];
57
+ now: number;
58
+ /** Count of actionable desires with nothing running (drives "idle"). */
59
+ pendingDesireCount?: number;
60
+ }
61
+ /** What the engine decided to do with this run's candidates. */
62
+ export interface AdvisorDecision {
63
+ /** Cleared the bar + throttle/dedup → surface to the user. */
64
+ surface: Suggestion[];
65
+ /** Generated but suppressed (logged to journal, not shown). */
66
+ suppressed: Suggestion[];
67
+ }
68
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/advisor/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAE7D,MAAM,MAAM,kBAAkB,GAC1B,OAAO,GACP,UAAU,GACV,kBAAkB,GAClB,YAAY,GACZ,OAAO,GACP,MAAM,CAAC;AAEX,oDAAoD;AACpD,MAAM,MAAM,OAAO,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEnD;mEACmE;AACnE,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,GAAG,UAAU,CAAC;IACxE,+DAA+D;IAC/D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,UAAU;IACzB,8DAA8D;IAC9D,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,OAAO,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,eAAe,CAAC;IACzB,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,aAAa,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;CACZ;AAED,6DAA6D;AAC7D,MAAM,WAAW,YAAY;IAC3B,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAChE,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,oEAAoE;IACpE,kBAAkB,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;IAChE,+DAA+D;IAC/D,YAAY,EAAE,MAAM,CAAC;IACrB,gFAAgF;IAChF,kBAAkB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC5C;AAED,wBAAgB,iBAAiB,IAAI,YAAY,CAQhD;AAED,wCAAwC;AACxC,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,YAAY,EAAE,CAAC;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,gEAAgE;AAChE,MAAM,WAAW,eAAe;IAC9B,8DAA8D;IAC9D,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,+DAA+D;IAC/D,UAAU,EAAE,UAAU,EAAE,CAAC;CAC1B"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Advisor (L5 ADVISE) — types.
3
+ *
4
+ * The advisor turns cross-agent observation into periodic, proactive
5
+ * suggestions. Its one hard problem is not being annoying, so everything is
6
+ * organized around the relevance bar + throttle + dedup (see engine.ts).
7
+ *
8
+ * See docs/ORCHESTRATOR_PLAN.md §5b.
9
+ */
10
+ export function emptyAdvisorState() {
11
+ return {
12
+ surfaced: {},
13
+ dismissals: {},
14
+ categoryDismissals: {},
15
+ lastDigestAt: 0,
16
+ errorCommandCounts: {},
17
+ };
18
+ }
19
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/advisor/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAsDH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,QAAQ,EAAE,EAAE;QACZ,UAAU,EAAE,EAAE;QACd,kBAAkB,EAAE,EAAE;QACtB,YAAY,EAAE,CAAC;QACf,kBAAkB,EAAE,EAAE;KACvB,CAAC;AACJ,CAAC"}