@oratis/lisa 0.8.0 → 0.11.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.
- package/README.md +123 -16
- package/README.zh-CN.md +101 -15
- package/completions/_lisa +56 -0
- package/completions/lisa.bash +35 -1
- package/completions/lisa.fish +18 -1
- package/dist/advisor/engine.d.ts +7 -0
- package/dist/advisor/engine.d.ts.map +1 -1
- package/dist/advisor/engine.js +12 -0
- package/dist/advisor/engine.js.map +1 -1
- package/dist/agent.d.ts +14 -0
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +58 -7
- package/dist/agent.js.map +1 -1
- package/dist/agents/managed.d.ts +108 -0
- package/dist/agents/managed.d.ts.map +1 -0
- package/dist/agents/managed.js +241 -0
- package/dist/agents/managed.js.map +1 -0
- package/dist/agents/pty.d.ts +155 -0
- package/dist/agents/pty.d.ts.map +1 -0
- package/dist/agents/pty.js +299 -0
- package/dist/agents/pty.js.map +1 -0
- package/dist/approval.d.ts +13 -0
- package/dist/approval.d.ts.map +1 -1
- package/dist/approval.js +25 -2
- package/dist/approval.js.map +1 -1
- package/dist/autonomy/runs.d.ts +33 -0
- package/dist/autonomy/runs.d.ts.map +1 -0
- package/dist/autonomy/runs.js +106 -0
- package/dist/autonomy/runs.js.map +1 -0
- package/dist/channels/feishu.d.ts.map +1 -1
- package/dist/channels/feishu.js +65 -0
- package/dist/channels/feishu.js.map +1 -1
- package/dist/channels/router.d.ts +7 -0
- package/dist/channels/router.d.ts.map +1 -1
- package/dist/channels/router.js +1 -1
- package/dist/channels/router.js.map +1 -1
- package/dist/cli/agents-pty.d.ts +18 -0
- package/dist/cli/agents-pty.d.ts.map +1 -0
- package/dist/cli/agents-pty.js +182 -0
- package/dist/cli/agents-pty.js.map +1 -0
- package/dist/cli/agents.d.ts +2 -0
- package/dist/cli/agents.d.ts.map +1 -0
- package/dist/cli/agents.js +65 -0
- package/dist/cli/agents.js.map +1 -0
- package/dist/cli/consent.d.ts +2 -0
- package/dist/cli/consent.d.ts.map +1 -0
- package/dist/cli/consent.js +57 -0
- package/dist/cli/consent.js.map +1 -0
- package/dist/cli/model.d.ts +2 -0
- package/dist/cli/model.d.ts.map +1 -0
- package/dist/cli/model.js +121 -0
- package/dist/cli/model.js.map +1 -0
- package/dist/cli/sense.d.ts +2 -0
- package/dist/cli/sense.d.ts.map +1 -0
- package/dist/cli/sense.js +36 -0
- package/dist/cli/sense.js.map +1 -0
- package/dist/cli.js +117 -12
- package/dist/cli.js.map +1 -1
- package/dist/consent/blacklist.d.ts +28 -0
- package/dist/consent/blacklist.d.ts.map +1 -0
- package/dist/consent/blacklist.js +78 -0
- package/dist/consent/blacklist.js.map +1 -0
- package/dist/consent/store.d.ts +45 -0
- package/dist/consent/store.d.ts.map +1 -0
- package/dist/consent/store.js +126 -0
- package/dist/consent/store.js.map +1 -0
- package/dist/control/policy.d.ts +13 -0
- package/dist/control/policy.d.ts.map +1 -0
- package/dist/control/policy.js +57 -0
- package/dist/control/policy.js.map +1 -0
- package/dist/heartbeat/runner.d.ts.map +1 -1
- package/dist/heartbeat/runner.js +70 -14
- package/dist/heartbeat/runner.js.map +1 -1
- package/dist/idle/runner.d.ts.map +1 -1
- package/dist/idle/runner.js +87 -17
- package/dist/idle/runner.js.map +1 -1
- package/dist/integrations/aider/observer.d.ts +21 -1
- package/dist/integrations/aider/observer.d.ts.map +1 -1
- package/dist/integrations/aider/observer.js +110 -1
- package/dist/integrations/aider/observer.js.map +1 -1
- package/dist/integrations/claude-code/liveness.d.ts +10 -0
- package/dist/integrations/claude-code/liveness.d.ts.map +1 -0
- package/dist/integrations/claude-code/liveness.js +62 -0
- package/dist/integrations/claude-code/liveness.js.map +1 -0
- package/dist/integrations/claude-code/watcher.d.ts +39 -4
- package/dist/integrations/claude-code/watcher.d.ts.map +1 -1
- package/dist/integrations/claude-code/watcher.js +94 -57
- package/dist/integrations/claude-code/watcher.js.map +1 -1
- package/dist/integrations/codex/observer.d.ts +19 -6
- package/dist/integrations/codex/observer.d.ts.map +1 -1
- package/dist/integrations/codex/observer.js +228 -4
- package/dist/integrations/codex/observer.js.map +1 -1
- package/dist/integrations/dispatch-ledger.d.ts +30 -1
- package/dist/integrations/dispatch-ledger.d.ts.map +1 -1
- package/dist/integrations/dispatch-ledger.js +75 -9
- package/dist/integrations/dispatch-ledger.js.map +1 -1
- package/dist/integrations/git/branch.d.ts +11 -0
- package/dist/integrations/git/branch.d.ts.map +1 -0
- package/dist/integrations/git/branch.js +49 -0
- package/dist/integrations/git/branch.js.map +1 -0
- package/dist/integrations/git/observer.d.ts +39 -0
- package/dist/integrations/git/observer.d.ts.map +1 -0
- package/dist/integrations/git/observer.js +252 -0
- package/dist/integrations/git/observer.js.map +1 -0
- package/dist/integrations/github-pr/observer.d.ts.map +1 -1
- package/dist/integrations/github-pr/observer.js +7 -2
- package/dist/integrations/github-pr/observer.js.map +1 -1
- package/dist/integrations/hub.d.ts +8 -0
- package/dist/integrations/hub.d.ts.map +1 -1
- package/dist/integrations/hub.js +39 -1
- package/dist/integrations/hub.js.map +1 -1
- package/dist/integrations/managed/observer.d.ts +17 -0
- package/dist/integrations/managed/observer.d.ts.map +1 -0
- package/dist/integrations/managed/observer.js +46 -0
- package/dist/integrations/managed/observer.js.map +1 -0
- package/dist/integrations/opencode/observer.d.ts +53 -5
- package/dist/integrations/opencode/observer.d.ts.map +1 -1
- package/dist/integrations/opencode/observer.js +242 -20
- package/dist/integrations/opencode/observer.js.map +1 -1
- package/dist/integrations/pty/observer.d.ts +25 -0
- package/dist/integrations/pty/observer.d.ts.map +1 -0
- package/dist/integrations/pty/observer.js +48 -0
- package/dist/integrations/pty/observer.js.map +1 -0
- package/dist/integrations/registry.d.ts.map +1 -1
- package/dist/integrations/registry.js +5 -0
- package/dist/integrations/registry.js.map +1 -1
- package/dist/integrations/shell/observer.d.ts +33 -0
- package/dist/integrations/shell/observer.d.ts.map +1 -0
- package/dist/integrations/shell/observer.js +223 -0
- package/dist/integrations/shell/observer.js.map +1 -0
- package/dist/integrations/takoapi/a2a.d.ts +38 -0
- package/dist/integrations/takoapi/a2a.d.ts.map +1 -0
- package/dist/integrations/takoapi/a2a.js +70 -0
- package/dist/integrations/takoapi/a2a.js.map +1 -0
- package/dist/integrations/takoapi/ledger.d.ts +32 -0
- package/dist/integrations/takoapi/ledger.d.ts.map +1 -0
- package/dist/integrations/takoapi/ledger.js +96 -0
- package/dist/integrations/takoapi/ledger.js.map +1 -0
- package/dist/integrations/takoapi/observer.d.ts +39 -0
- package/dist/integrations/takoapi/observer.d.ts.map +1 -0
- package/dist/integrations/takoapi/observer.js +102 -0
- package/dist/integrations/takoapi/observer.js.map +1 -0
- package/dist/integrations/types.d.ts +17 -0
- package/dist/integrations/types.d.ts.map +1 -1
- package/dist/mcp/client.d.ts +5 -0
- package/dist/mcp/client.d.ts.map +1 -1
- package/dist/mcp/client.js +1 -1
- package/dist/mcp/client.js.map +1 -1
- package/dist/mcp/config.d.ts +4 -0
- package/dist/mcp/config.d.ts.map +1 -1
- package/dist/mcp/config.js +34 -0
- package/dist/mcp/config.js.map +1 -1
- package/dist/memory/embedding.d.ts +45 -0
- package/dist/memory/embedding.d.ts.map +1 -0
- package/dist/memory/embedding.js +139 -0
- package/dist/memory/embedding.js.map +1 -0
- package/dist/memory/search_tool.d.ts.map +1 -1
- package/dist/memory/search_tool.js +6 -2
- package/dist/memory/search_tool.js.map +1 -1
- package/dist/memory/vector.d.ts +14 -3
- package/dist/memory/vector.d.ts.map +1 -1
- package/dist/memory/vector.js +50 -0
- package/dist/memory/vector.js.map +1 -1
- package/dist/model/local.d.ts +43 -0
- package/dist/model/local.d.ts.map +1 -0
- package/dist/model/local.js +118 -0
- package/dist/model/local.js.map +1 -0
- package/dist/model/plan-usage.d.ts +41 -0
- package/dist/model/plan-usage.d.ts.map +1 -0
- package/dist/model/plan-usage.js +168 -0
- package/dist/model/plan-usage.js.map +1 -0
- package/dist/model/plans.d.ts +73 -0
- package/dist/model/plans.d.ts.map +1 -0
- package/dist/model/plans.js +246 -0
- package/dist/model/plans.js.map +1 -0
- package/dist/orchestrator/recent-recap.d.ts +2 -0
- package/dist/orchestrator/recent-recap.d.ts.map +1 -0
- package/dist/orchestrator/recent-recap.js +20 -0
- package/dist/orchestrator/recent-recap.js.map +1 -0
- package/dist/providers/anthropic.d.ts +1 -0
- package/dist/providers/anthropic.d.ts.map +1 -1
- package/dist/providers/anthropic.js +10 -2
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/fallback.d.ts +23 -0
- package/dist/providers/fallback.d.ts.map +1 -0
- package/dist/providers/fallback.js +29 -0
- package/dist/providers/fallback.js.map +1 -0
- package/dist/providers/gemini.d.ts +2 -0
- package/dist/providers/gemini.d.ts.map +1 -1
- package/dist/providers/gemini.js +24 -10
- package/dist/providers/gemini.js.map +1 -1
- package/dist/providers/openai.d.ts.map +1 -1
- package/dist/providers/openai.js +4 -1
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/registry.d.ts +18 -2
- package/dist/providers/registry.d.ts.map +1 -1
- package/dist/providers/registry.js +56 -9
- package/dist/providers/registry.js.map +1 -1
- package/dist/reflect.d.ts +55 -0
- package/dist/reflect.d.ts.map +1 -1
- package/dist/reflect.js +148 -54
- package/dist/reflect.js.map +1 -1
- package/dist/sense/log.d.ts +9 -0
- package/dist/sense/log.d.ts.map +1 -0
- package/dist/sense/log.js +63 -0
- package/dist/sense/log.js.map +1 -0
- package/dist/sense/screen.d.ts +43 -0
- package/dist/sense/screen.d.ts.map +1 -0
- package/dist/sense/screen.js +107 -0
- package/dist/sense/screen.js.map +1 -0
- package/dist/sense/service.d.ts +16 -0
- package/dist/sense/service.d.ts.map +1 -0
- package/dist/sense/service.js +33 -0
- package/dist/sense/service.js.map +1 -0
- package/dist/sense/types.d.ts +35 -0
- package/dist/sense/types.d.ts.map +1 -0
- package/dist/sense/types.js +11 -0
- package/dist/sense/types.js.map +1 -0
- package/dist/sense/voice.d.ts +28 -0
- package/dist/sense/voice.d.ts.map +1 -0
- package/dist/sense/voice.js +64 -0
- package/dist/sense/voice.js.map +1 -0
- package/dist/soul/git.d.ts.map +1 -1
- package/dist/soul/git.js +35 -16
- package/dist/soul/git.js.map +1 -1
- package/dist/soul/store.d.ts +22 -0
- package/dist/soul/store.d.ts.map +1 -1
- package/dist/soul/store.js +69 -14
- package/dist/soul/store.js.map +1 -1
- package/dist/soul/summary.d.ts +10 -0
- package/dist/soul/summary.d.ts.map +1 -0
- package/dist/soul/summary.js +57 -0
- package/dist/soul/summary.js.map +1 -0
- package/dist/soul/tools.d.ts.map +1 -1
- package/dist/soul/tools.js +13 -24
- package/dist/soul/tools.js.map +1 -1
- package/dist/soul/types.d.ts +9 -0
- package/dist/soul/types.d.ts.map +1 -1
- package/dist/soul/types.js.map +1 -1
- package/dist/subagent.d.ts +7 -0
- package/dist/subagent.d.ts.map +1 -1
- package/dist/subagent.js +3 -1
- package/dist/subagent.js.map +1 -1
- package/dist/tools/dispatch_agent.d.ts +13 -1
- package/dist/tools/dispatch_agent.d.ts.map +1 -1
- package/dist/tools/dispatch_agent.js +56 -12
- package/dist/tools/dispatch_agent.js.map +1 -1
- package/dist/tools/dispatch_status.d.ts +22 -0
- package/dist/tools/dispatch_status.d.ts.map +1 -0
- package/dist/tools/dispatch_status.js +57 -0
- package/dist/tools/dispatch_status.js.map +1 -0
- package/dist/tools/github.d.ts +34 -0
- package/dist/tools/github.d.ts.map +1 -0
- package/dist/tools/github.js +140 -0
- package/dist/tools/github.js.map +1 -0
- package/dist/tools/github_link.d.ts +36 -0
- package/dist/tools/github_link.d.ts.map +1 -0
- package/dist/tools/github_link.js +127 -0
- package/dist/tools/github_link.js.map +1 -0
- package/dist/tools/mcp.d.ts +28 -0
- package/dist/tools/mcp.d.ts.map +1 -0
- package/dist/tools/mcp.js +55 -0
- package/dist/tools/mcp.js.map +1 -0
- package/dist/tools/npm_info.d.ts +21 -0
- package/dist/tools/npm_info.d.ts.map +1 -0
- package/dist/tools/npm_info.js +102 -0
- package/dist/tools/npm_info.js.map +1 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.d.ts.map +1 -1
- package/dist/tools/registry.js +75 -0
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/run_on_plan.d.ts +46 -0
- package/dist/tools/run_on_plan.d.ts.map +1 -0
- package/dist/tools/run_on_plan.js +89 -0
- package/dist/tools/run_on_plan.js.map +1 -0
- package/dist/tools/takoapi.d.ts +60 -0
- package/dist/tools/takoapi.d.ts.map +1 -0
- package/dist/tools/takoapi.js +181 -0
- package/dist/tools/takoapi.js.map +1 -0
- package/dist/tools/validate.d.ts +22 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +50 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/voice/dictation.d.ts +51 -0
- package/dist/voice/dictation.d.ts.map +1 -0
- package/dist/voice/dictation.js +70 -0
- package/dist/voice/dictation.js.map +1 -0
- package/dist/web/agent-roster.d.ts +56 -0
- package/dist/web/agent-roster.d.ts.map +1 -0
- package/dist/web/agent-roster.js +109 -0
- package/dist/web/agent-roster.js.map +1 -0
- package/dist/web/devices.d.ts +27 -0
- package/dist/web/devices.d.ts.map +1 -0
- package/dist/web/devices.js +113 -0
- package/dist/web/devices.js.map +1 -0
- package/dist/web/island.d.ts +5 -1
- package/dist/web/island.d.ts.map +1 -1
- package/dist/web/island.js +427 -67
- package/dist/web/island.js.map +1 -1
- package/dist/web/lisa-client.d.ts +20 -0
- package/dist/web/lisa-client.d.ts.map +1 -0
- package/dist/web/lisa-client.js +1323 -0
- package/dist/web/lisa-client.js.map +1 -0
- package/dist/web/lisa-css.d.ts +13 -0
- package/dist/web/lisa-css.d.ts.map +1 -0
- package/dist/web/lisa-css.js +1163 -0
- package/dist/web/lisa-css.js.map +1 -0
- package/dist/web/lisa-html.d.ts +1 -1
- package/dist/web/lisa-html.d.ts.map +1 -1
- package/dist/web/lisa-html.js +16 -2091
- package/dist/web/lisa-html.js.map +1 -1
- package/dist/web/push.d.ts +76 -0
- package/dist/web/push.d.ts.map +1 -0
- package/dist/web/push.js +163 -0
- package/dist/web/push.js.map +1 -0
- package/dist/web/server.d.ts +19 -0
- package/dist/web/server.d.ts.map +1 -1
- package/dist/web/server.js +898 -65
- package/dist/web/server.js.map +1 -1
- package/package.json +5 -2
package/dist/web/island.js
CHANGED
|
@@ -7,7 +7,12 @@
|
|
|
7
7
|
* unread flag). See docs/MAC_ISLAND_PLAN.md.
|
|
8
8
|
*
|
|
9
9
|
* No build step, no framework — single string of inline HTML/CSS/JS.
|
|
10
|
+
*
|
|
11
|
+
* D4a: the multi-agent roster logic (merge/aggregate) lives in agent-roster.ts
|
|
12
|
+
* and is SOURCE-INJECTED into the inline <script> below via `${mergeAgentSession}`
|
|
13
|
+
* so the browser runs the exact unit-tested code instead of a drifting copy.
|
|
10
14
|
*/
|
|
15
|
+
import { mergeAgentSession, aggregateAgentState, rosterLabel, formatActivity } from "./agent-roster.js";
|
|
11
16
|
export const ISLAND_HTML = `<!doctype html>
|
|
12
17
|
<html lang="en">
|
|
13
18
|
<head>
|
|
@@ -125,8 +130,10 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
125
130
|
#dot.unread { background: var(--accent-warm); }
|
|
126
131
|
/* Phase 2: pill dot reflects the strongest signal across all
|
|
127
132
|
Claude sessions. */
|
|
128
|
-
|
|
129
|
-
|
|
133
|
+
/* working = a calm slow breathe (it's running, not actionable);
|
|
134
|
+
waiting = solid + a soft halo that draws the eye ("needs you"). */
|
|
135
|
+
#dot.claude-working { background: var(--accent-claude); opacity: 1; animation: breathe 2.6s ease-in-out infinite; }
|
|
136
|
+
#dot.claude-waiting { background: var(--accent-claude); opacity: 1; animation: needsYou 2s ease-in-out infinite; }
|
|
130
137
|
#dot.claude-error { background: #ff5577; animation: pulse 0.8s ease-in-out infinite; }
|
|
131
138
|
#dot.offline { background: var(--fg-faint); }
|
|
132
139
|
|
|
@@ -134,6 +141,17 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
134
141
|
0%, 100% { opacity: 0.35; }
|
|
135
142
|
50% { opacity: 1; }
|
|
136
143
|
}
|
|
144
|
+
/* Gentle breathing for "working" — present but not jumpy. */
|
|
145
|
+
@keyframes breathe {
|
|
146
|
+
0%, 100% { opacity: 0.55; }
|
|
147
|
+
50% { opacity: 1; }
|
|
148
|
+
}
|
|
149
|
+
/* "needs you" — solid dot with a pulsing warm halo, more prominent than
|
|
150
|
+
working without the harsh on/off flash. */
|
|
151
|
+
@keyframes needsYou {
|
|
152
|
+
0%, 100% { box-shadow: 0 0 0 0 rgba(255, 140, 66, 0); }
|
|
153
|
+
50% { box-shadow: 0 0 7px 2px rgba(255, 140, 66, 0.65); }
|
|
154
|
+
}
|
|
137
155
|
|
|
138
156
|
/* Expanded panel — appears below the pill on hover/click.
|
|
139
157
|
The native LisaIsland.app window is a fixed 360×440 pt; the pill
|
|
@@ -258,6 +276,24 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
258
276
|
background: rgba(255, 255, 255, 0.05);
|
|
259
277
|
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
260
278
|
}
|
|
279
|
+
/* D4a — agent-kind chip (codex / opencode / git / …) so the multi-agent
|
|
280
|
+
roster reads at a glance which tool each row belongs to. */
|
|
281
|
+
#claude-list .agent-badge {
|
|
282
|
+
flex-shrink: 0;
|
|
283
|
+
font-size: 9px;
|
|
284
|
+
font-weight: 600;
|
|
285
|
+
letter-spacing: 0.02em;
|
|
286
|
+
text-transform: lowercase;
|
|
287
|
+
color: var(--accent-claude);
|
|
288
|
+
padding: 1px 6px;
|
|
289
|
+
border-radius: 999px;
|
|
290
|
+
background: rgba(255, 140, 66, 0.12);
|
|
291
|
+
border: 1px solid rgba(255, 140, 66, 0.22);
|
|
292
|
+
max-width: 84px;
|
|
293
|
+
overflow: hidden;
|
|
294
|
+
text-overflow: ellipsis;
|
|
295
|
+
white-space: nowrap;
|
|
296
|
+
}
|
|
261
297
|
#claude-list .empty { padding: 8px 12px; color: var(--fg-faint); font-style: italic; }
|
|
262
298
|
/* O2 — Tier-2 activity line: what the session is structurally doing. */
|
|
263
299
|
#claude-list .act {
|
|
@@ -286,8 +322,8 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
286
322
|
background: var(--fg-faint);
|
|
287
323
|
margin-right: 4px;
|
|
288
324
|
}
|
|
289
|
-
#claude-list .pip.working { background: var(--accent-claude); animation:
|
|
290
|
-
#claude-list .pip.waiting { background: var(--accent-claude); }
|
|
325
|
+
#claude-list .pip.working { background: var(--accent-claude); opacity: 1; animation: breathe 2.6s ease-in-out infinite; }
|
|
326
|
+
#claude-list .pip.waiting { background: var(--accent-claude); opacity: 1; animation: needsYou 2s ease-in-out infinite; }
|
|
291
327
|
#claude-list .pip.error { background: #ff5577; }
|
|
292
328
|
#claude-list .pip.unknown { background: var(--fg-faint); }
|
|
293
329
|
|
|
@@ -340,6 +376,77 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
340
376
|
#claude-list .trail .tdot.error { background: #ff5577; }
|
|
341
377
|
/* (older stub removed — .head is a real flex strip now, see above) */
|
|
342
378
|
|
|
379
|
+
/* ── Sense consent (FOUNDATIONS §1) — "always visible + one-tap stop" ── */
|
|
380
|
+
#sense-list { list-style: none; margin: 6px 0 0; padding: 0; }
|
|
381
|
+
#sense-list li {
|
|
382
|
+
display: flex;
|
|
383
|
+
align-items: center;
|
|
384
|
+
gap: 8px;
|
|
385
|
+
padding: 6px 4px;
|
|
386
|
+
font-size: 11.5px;
|
|
387
|
+
}
|
|
388
|
+
#sense-list li + li { border-top: 1px solid rgba(255, 255, 255, 0.05); }
|
|
389
|
+
#sense-list .s-name { font-weight: 600; flex-shrink: 0; }
|
|
390
|
+
#sense-list .s-desc {
|
|
391
|
+
color: var(--fg-faint);
|
|
392
|
+
flex: 1;
|
|
393
|
+
min-width: 0;
|
|
394
|
+
overflow: hidden;
|
|
395
|
+
text-overflow: ellipsis;
|
|
396
|
+
white-space: nowrap;
|
|
397
|
+
font-size: 10px;
|
|
398
|
+
}
|
|
399
|
+
/* Toggle pill: off is muted, on is amber. Click flips it (grant shows the
|
|
400
|
+
description inline so the user sees what they're enabling). */
|
|
401
|
+
#sense-list .s-toggle {
|
|
402
|
+
flex-shrink: 0;
|
|
403
|
+
cursor: pointer;
|
|
404
|
+
font-size: 9px;
|
|
405
|
+
font-weight: 700;
|
|
406
|
+
letter-spacing: 0.04em;
|
|
407
|
+
text-transform: uppercase;
|
|
408
|
+
padding: 2px 8px;
|
|
409
|
+
border-radius: 999px;
|
|
410
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
411
|
+
background: rgba(255, 255, 255, 0.05);
|
|
412
|
+
color: var(--fg-dim);
|
|
413
|
+
}
|
|
414
|
+
#sense-list .s-toggle.on {
|
|
415
|
+
color: #0b0e18;
|
|
416
|
+
background: var(--accent-warm);
|
|
417
|
+
border-color: var(--accent-warm);
|
|
418
|
+
}
|
|
419
|
+
/* The label glows amber while anything is being sensed. */
|
|
420
|
+
body.sensing #sense-label { color: var(--accent-warm); }
|
|
421
|
+
#sense-stop {
|
|
422
|
+
display: none;
|
|
423
|
+
margin-top: 8px;
|
|
424
|
+
width: 100%;
|
|
425
|
+
padding: 7px 12px;
|
|
426
|
+
border-radius: 10px;
|
|
427
|
+
border: 1px solid rgba(255, 85, 119, 0.35);
|
|
428
|
+
background: rgba(255, 85, 119, 0.12);
|
|
429
|
+
color: #ffd6de;
|
|
430
|
+
font-size: 11px;
|
|
431
|
+
font-weight: 600;
|
|
432
|
+
cursor: pointer;
|
|
433
|
+
}
|
|
434
|
+
#sense-stop:hover { background: rgba(255, 85, 119, 0.2); }
|
|
435
|
+
body.sensing #sense-stop { display: block; }
|
|
436
|
+
/* Live "recently sensed" feed — structural summaries only. */
|
|
437
|
+
#sense-events { list-style: none; margin: 8px 0 0; padding: 0; }
|
|
438
|
+
#sense-events li {
|
|
439
|
+
display: flex;
|
|
440
|
+
gap: 8px;
|
|
441
|
+
align-items: baseline;
|
|
442
|
+
padding: 3px 4px;
|
|
443
|
+
font-size: 10.5px;
|
|
444
|
+
color: var(--fg-dim);
|
|
445
|
+
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
446
|
+
}
|
|
447
|
+
#sense-events .se-when { color: var(--fg-faint); flex-shrink: 0; font-variant-numeric: tabular-nums; }
|
|
448
|
+
#sense-events .se-sum { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
449
|
+
|
|
343
450
|
/* Phase 3 — notification opt-in chip */
|
|
344
451
|
#notify-cta {
|
|
345
452
|
display: none;
|
|
@@ -395,6 +502,23 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
395
502
|
body.offline #avatar { filter: grayscale(1); opacity: 0.5; }
|
|
396
503
|
body.offline #label { color: var(--fg-faint); }
|
|
397
504
|
|
|
505
|
+
/* Agents-advisor card (cross-agent suggestions with actions) */
|
|
506
|
+
#advisor-section { display: none; }
|
|
507
|
+
body.has-advisor #advisor-section { display: block; }
|
|
508
|
+
#advisor-list { list-style: none; margin: 0; padding: 0; }
|
|
509
|
+
#advisor-list li { display: flex; flex-direction: column; gap: 4px; padding: 4px 0; }
|
|
510
|
+
#advisor-list .advisor-text { font-size: 11px; line-height: 1.35; }
|
|
511
|
+
#advisor-list .advisor-text.urgent { font-weight: 600; }
|
|
512
|
+
#advisor-list .advisor-actions { display: flex; gap: 6px; }
|
|
513
|
+
#advisor-list .advisor-actions button {
|
|
514
|
+
font: inherit; font-size: 10px; padding: 2px 8px; border-radius: 6px;
|
|
515
|
+
border: 1px solid var(--line, rgba(255,255,255,0.15));
|
|
516
|
+
background: transparent; color: inherit; cursor: pointer;
|
|
517
|
+
}
|
|
518
|
+
#advisor-list .advisor-actions button.primary {
|
|
519
|
+
background: var(--accent, #5b8cff); border-color: transparent; color: #fff;
|
|
520
|
+
}
|
|
521
|
+
#advisor-list .advisor-actions button:hover { filter: brightness(1.1); }
|
|
398
522
|
/* Screen-advisor suggestion card */
|
|
399
523
|
#suggestion-section { display: none; }
|
|
400
524
|
body.has-suggestion #suggestion-section { display: block; }
|
|
@@ -431,11 +555,21 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
431
555
|
<div id="suggestion-rationale"></div>
|
|
432
556
|
<button id="suggestion-act" type="button">Optimize ▸</button>
|
|
433
557
|
</div>
|
|
558
|
+
<div id="advisor-section">
|
|
559
|
+
<div class="section-label">🛰 across your agents</div>
|
|
560
|
+
<ul id="advisor-list"></ul>
|
|
561
|
+
</div>
|
|
434
562
|
<div id="claude-section">
|
|
435
|
-
<div class="section-label">
|
|
563
|
+
<div class="section-label">agents · <span id="claude-count">0</span> active</div>
|
|
436
564
|
<ul id="claude-list"></ul>
|
|
437
565
|
<div id="notify-cta" role="button" tabindex="0">🔔 Notify me when Claude is waiting</div>
|
|
438
566
|
</div>
|
|
567
|
+
<div id="sense-section">
|
|
568
|
+
<div class="section-label" id="sense-label">👁 sense · <span id="sense-summary">all off</span></div>
|
|
569
|
+
<ul id="sense-list"></ul>
|
|
570
|
+
<button id="sense-stop" type="button">Stop all sensing</button>
|
|
571
|
+
<ul id="sense-events"></ul>
|
|
572
|
+
</div>
|
|
439
573
|
<div id="actions">
|
|
440
574
|
<button id="btn-open">Open chat</button>
|
|
441
575
|
<button id="btn-dismiss" class="muted">Dismiss ★</button>
|
|
@@ -444,6 +578,21 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
444
578
|
|
|
445
579
|
<script>
|
|
446
580
|
(() => {
|
|
581
|
+
// ── Multi-agent roster reducer (Dispatch D4a) ──────────────────────
|
|
582
|
+
// Source-injected from agent-roster.ts so the browser runs the exact,
|
|
583
|
+
// unit-tested merge/aggregate logic instead of a hand-maintained copy.
|
|
584
|
+
// These must stay self-contained (no imports / closure refs) — the
|
|
585
|
+
// injection-safety test in agent-roster.test.ts guards that.
|
|
586
|
+
${mergeAgentSession}
|
|
587
|
+
${aggregateAgentState}
|
|
588
|
+
${rosterLabel}
|
|
589
|
+
${formatActivity}
|
|
590
|
+
// Composite identity for a roster row: the same sessionId seen under two
|
|
591
|
+
// agents (e.g. a git + a shell observer) are DISTINCT rows. Mirrors the
|
|
592
|
+
// key mergeAgentSession uses; the UI keys per-row history, open-state, and
|
|
593
|
+
// notify throttling by it too.
|
|
594
|
+
function agentKey(s) { return (s.agent || 'agent') + '::' + s.sessionId; }
|
|
595
|
+
|
|
447
596
|
const pill = document.getElementById('pill');
|
|
448
597
|
const avatar = document.getElementById('avatar');
|
|
449
598
|
const dot = document.getElementById('dot');
|
|
@@ -458,6 +607,10 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
458
607
|
const suggTitle = document.getElementById('suggestion-title');
|
|
459
608
|
const suggRationale= document.getElementById('suggestion-rationale');
|
|
460
609
|
const suggAct = document.getElementById('suggestion-act');
|
|
610
|
+
const senseList = document.getElementById('sense-list');
|
|
611
|
+
const senseSummary = document.getElementById('sense-summary');
|
|
612
|
+
const senseStop = document.getElementById('sense-stop');
|
|
613
|
+
const senseEvents = document.getElementById('sense-events');
|
|
461
614
|
const body = document.body;
|
|
462
615
|
|
|
463
616
|
const state = {
|
|
@@ -468,8 +621,11 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
468
621
|
desire: null,
|
|
469
622
|
thinking: false,
|
|
470
623
|
dreaming: false,
|
|
471
|
-
|
|
624
|
+
agentSessions: [], // [{agent, project, sessionId, lastMtime, state, activity}, …] — all agents, not just Claude
|
|
472
625
|
suggestion: null, // {title, rationale, task, at} from the screen advisor
|
|
626
|
+
advisor: [], // [{id, category, urgency, text, action}] from the cross-agent advisor
|
|
627
|
+
sense: [], // [{signal, granted, grantedAt}] from /api/consent (FOUNDATIONS §1)
|
|
628
|
+
senseEvents: [], // [{signal, summary, ts}] recent ambient events (S2)
|
|
473
629
|
};
|
|
474
630
|
|
|
475
631
|
// 30-min activity window matches the watcher's ACTIVE_WINDOW_MS.
|
|
@@ -542,11 +698,13 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
542
698
|
function maybeNotifyWaiting(prevState, info) {
|
|
543
699
|
if (info.state !== 'waiting') return;
|
|
544
700
|
if (prevState === 'waiting') return; // already in this state
|
|
545
|
-
const
|
|
701
|
+
const nk = agentKey(info);
|
|
702
|
+
const last = lastNotifyAt.get(nk) || 0;
|
|
546
703
|
if (Date.now() - last < NOTIFY_THROTTLE_MS) return;
|
|
547
|
-
lastNotifyAt.set(
|
|
704
|
+
lastNotifyAt.set(nk, Date.now());
|
|
548
705
|
const reasonLabel = info.stateReason === 'permission' ? 'needs permission' : 'is waiting';
|
|
549
|
-
const
|
|
706
|
+
const who = info.agent && info.agent !== 'claude-code' ? info.agent : 'Claude';
|
|
707
|
+
const title = who + ' ' + reasonLabel + ' in ' + info.project;
|
|
550
708
|
const bodyText = info.sessionId.slice(0, 8) + ' · click to open Lisa';
|
|
551
709
|
if (hasBridge) {
|
|
552
710
|
window.webkit.messageHandlers.island.postMessage({
|
|
@@ -570,24 +728,18 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
570
728
|
}
|
|
571
729
|
function recentSessions() {
|
|
572
730
|
const cutoff = Date.now() - CLAUDE_ACTIVE_WINDOW_MS;
|
|
573
|
-
return state.
|
|
731
|
+
return state.agentSessions.filter(
|
|
574
732
|
(s) => new Date(s.lastMtime).getTime() >= cutoff
|
|
575
733
|
);
|
|
576
734
|
}
|
|
577
735
|
/**
|
|
578
|
-
*
|
|
579
|
-
* "loudest signal wins": an error anywhere dominates
|
|
580
|
-
*
|
|
581
|
-
*
|
|
582
|
-
* than "working" which is passive observing).
|
|
736
|
+
* Aggregate state across all agent sessions for the pill dot. Priority is
|
|
737
|
+
* "loudest signal wins": an error anywhere dominates; otherwise "waiting"
|
|
738
|
+
* (an agent needs the user) beats "working" (passive observing). Delegates
|
|
739
|
+
* to the source-injected, unit-tested reducer so the dot and the tests agree.
|
|
583
740
|
*/
|
|
584
741
|
function aggregateClaudeState() {
|
|
585
|
-
|
|
586
|
-
if (recent.length === 0) return null;
|
|
587
|
-
if (recent.some((s) => s.state === 'error')) return 'error';
|
|
588
|
-
if (recent.some((s) => s.state === 'waiting')) return 'waiting';
|
|
589
|
-
if (recent.some((s) => s.state === 'working')) return 'working';
|
|
590
|
-
return null;
|
|
742
|
+
return aggregateAgentState(state.agentSessions, Date.now(), CLAUDE_ACTIVE_WINDOW_MS);
|
|
591
743
|
}
|
|
592
744
|
|
|
593
745
|
function setAvatar(slug) {
|
|
@@ -617,7 +769,7 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
617
769
|
function refreshPanel() {
|
|
618
770
|
body.classList.toggle('offline', !state.online);
|
|
619
771
|
body.classList.toggle('has-unread', state.unread);
|
|
620
|
-
body.classList.toggle('has-claude', state.
|
|
772
|
+
body.classList.toggle('has-claude', state.agentSessions.length > 0);
|
|
621
773
|
desireBody.textContent = state.desire || '(nothing actively pursued)';
|
|
622
774
|
idleBody.textContent = state.idleText || '';
|
|
623
775
|
btnDismiss.classList.toggle('muted', !state.unread);
|
|
@@ -627,7 +779,100 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
627
779
|
suggTitle.textContent = state.suggestion.title || '';
|
|
628
780
|
suggRationale.textContent = state.suggestion.rationale || '';
|
|
629
781
|
}
|
|
782
|
+
body.classList.toggle('has-advisor', state.advisor.length > 0);
|
|
783
|
+
renderAdvisorList();
|
|
630
784
|
renderClaudeList();
|
|
785
|
+
renderSense();
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// ── Cross-agent advisor card ───────────────────────────────────────
|
|
789
|
+
// Each suggestion gets at most two buttons: the action (never auto-runs —
|
|
790
|
+
// "open" reveals the folder, everything else prefills the chat composer
|
|
791
|
+
// with a concrete ask so the user confirms by sending) and ✕ dismiss
|
|
792
|
+
// (persisted server-side; repeated dismissals down-weight the category).
|
|
793
|
+
function advisorPrefill(s) {
|
|
794
|
+
const a = s.action || {};
|
|
795
|
+
const arg = a.arg || '';
|
|
796
|
+
switch (a.kind) {
|
|
797
|
+
case 'approve':
|
|
798
|
+
return 'One of my agent sessions is waiting for permission: "' + s.text + '". ' +
|
|
799
|
+
'Inspect it' + (arg ? ' (inspect_agent ' + arg + ')' : '') +
|
|
800
|
+
' and tell me exactly what it wants to run and whether it looks safe.';
|
|
801
|
+
case 'serialize':
|
|
802
|
+
return 'Two of my agents may be about to collide: "' + s.text + '". ' +
|
|
803
|
+
'Propose how to serialize them — do not schedule or cancel anything until I confirm.';
|
|
804
|
+
case 'dispatch':
|
|
805
|
+
return s.text + ' — list the pending actionable desires and propose a dispatch plan. ' +
|
|
806
|
+
'Do not dispatch anything until I confirm.';
|
|
807
|
+
case 'cancel':
|
|
808
|
+
return 'Consider cancelling this: "' + s.text + '". Show me the dispatch details first ' +
|
|
809
|
+
'and wait for my confirmation.';
|
|
810
|
+
default: // 'look' and anything new
|
|
811
|
+
return 'Look into this for me: "' + s.text + '"' +
|
|
812
|
+
(arg && arg !== 'stuck' ? ' (session ' + arg + ')' : '') + ' — and report back.';
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
|
|
816
|
+
function renderAdvisorList() {
|
|
817
|
+
const list = document.getElementById('advisor-list');
|
|
818
|
+
if (!list) return;
|
|
819
|
+
while (list.firstChild) list.removeChild(list.firstChild);
|
|
820
|
+
for (const s of state.advisor.slice(0, 3)) {
|
|
821
|
+
const li = document.createElement('li');
|
|
822
|
+
const text = document.createElement('div');
|
|
823
|
+
text.className = 'advisor-text' + (s.urgency === 'urgent' ? ' urgent' : '');
|
|
824
|
+
text.textContent = (s.urgency === 'urgent' ? '⚠ ' : '') + s.text;
|
|
825
|
+
li.appendChild(text);
|
|
826
|
+
const actions = document.createElement('div');
|
|
827
|
+
actions.className = 'advisor-actions';
|
|
828
|
+
const a = s.action || null;
|
|
829
|
+
if (a && a.kind === 'open' && a.arg && String(a.arg).startsWith('/')) {
|
|
830
|
+
const openBtn = document.createElement('button');
|
|
831
|
+
openBtn.type = 'button';
|
|
832
|
+
openBtn.className = 'primary';
|
|
833
|
+
openBtn.textContent = '📁 ' + (a.label || 'Open');
|
|
834
|
+
openBtn.title = a.arg;
|
|
835
|
+
openBtn.addEventListener('click', (e) => {
|
|
836
|
+
e.stopPropagation();
|
|
837
|
+
if (hasBridge) {
|
|
838
|
+
window.webkit.messageHandlers.island.postMessage({ type: 'open_path', path: a.arg });
|
|
839
|
+
} else {
|
|
840
|
+
navigator.clipboard.writeText(a.arg).catch(() => {});
|
|
841
|
+
}
|
|
842
|
+
});
|
|
843
|
+
actions.appendChild(openBtn);
|
|
844
|
+
} else if (a) {
|
|
845
|
+
const actBtn = document.createElement('button');
|
|
846
|
+
actBtn.type = 'button';
|
|
847
|
+
actBtn.className = 'primary';
|
|
848
|
+
actBtn.textContent = (a.label || 'Ask Lisa') + ' ▸';
|
|
849
|
+
actBtn.title = 'Prefills the chat — nothing runs until you send';
|
|
850
|
+
actBtn.addEventListener('click', (e) => {
|
|
851
|
+
e.stopPropagation();
|
|
852
|
+
openFull(advisorPrefill(s));
|
|
853
|
+
});
|
|
854
|
+
actions.appendChild(actBtn);
|
|
855
|
+
}
|
|
856
|
+
const dismissBtn = document.createElement('button');
|
|
857
|
+
dismissBtn.type = 'button';
|
|
858
|
+
dismissBtn.textContent = '✕';
|
|
859
|
+
dismissBtn.title = 'Dismiss — repeated dismissals teach the advisor to quiet this category';
|
|
860
|
+
dismissBtn.addEventListener('click', async (e) => {
|
|
861
|
+
e.stopPropagation();
|
|
862
|
+
state.advisor = state.advisor.filter((x) => x.id !== s.id);
|
|
863
|
+
refreshPanel();
|
|
864
|
+
try {
|
|
865
|
+
await fetch('/api/advisor/dismiss', {
|
|
866
|
+
method: 'POST',
|
|
867
|
+
headers: { 'content-type': 'application/json' },
|
|
868
|
+
body: JSON.stringify({ id: s.id, category: s.category }),
|
|
869
|
+
});
|
|
870
|
+
} catch (_) {}
|
|
871
|
+
});
|
|
872
|
+
actions.appendChild(dismissBtn);
|
|
873
|
+
li.appendChild(actions);
|
|
874
|
+
list.appendChild(li);
|
|
875
|
+
}
|
|
631
876
|
}
|
|
632
877
|
|
|
633
878
|
function relativeTime(iso) {
|
|
@@ -638,28 +883,9 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
638
883
|
return Math.round(ms / 3600_000) + 'h ago';
|
|
639
884
|
}
|
|
640
885
|
|
|
641
|
-
// O2 — compact one-line
|
|
642
|
-
//
|
|
643
|
-
//
|
|
644
|
-
function basename(p) {
|
|
645
|
-
if (!p) return '';
|
|
646
|
-
const parts = String(p).split('/');
|
|
647
|
-
return parts[parts.length - 1] || p;
|
|
648
|
-
}
|
|
649
|
-
function formatActivity(s) {
|
|
650
|
-
const a = s.activity;
|
|
651
|
-
if (!a) return '';
|
|
652
|
-
if (a.pendingPermission) return '⚠ wants to run ' + a.pendingPermission;
|
|
653
|
-
const bits = [];
|
|
654
|
-
if (a.lastError) bits.push('✗ ' + a.lastError);
|
|
655
|
-
if (a.lastCommandName) bits.push('$ ' + a.lastCommandName);
|
|
656
|
-
const tool = a.lastTools && a.lastTools.length ? a.lastTools[a.lastTools.length - 1] : '';
|
|
657
|
-
const file = a.filesTouched && a.filesTouched.length ? basename(a.filesTouched[a.filesTouched.length - 1]) : '';
|
|
658
|
-
if (tool && file) bits.push(tool + ' ' + file);
|
|
659
|
-
else if (tool) bits.push(tool);
|
|
660
|
-
else if (file) bits.push(file);
|
|
661
|
-
return bits.join(' · ');
|
|
662
|
-
}
|
|
886
|
+
// O2 — compact one-line Tier-2 activity summary is the source-injected
|
|
887
|
+
// formatActivity (from agent-roster.ts, above) — structural only, now also
|
|
888
|
+
// surfacing turn count + tokens. Shared with the GUI sidebar.
|
|
663
889
|
|
|
664
890
|
function renderClaudeList() {
|
|
665
891
|
const recent = recentSessions();
|
|
@@ -679,10 +905,10 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
679
905
|
const rb = stateRank[b.state] ?? 9;
|
|
680
906
|
if (ra !== rb) return ra - rb;
|
|
681
907
|
return new Date(b.lastMtime).getTime() - new Date(a.lastMtime).getTime();
|
|
682
|
-
}).slice(0,
|
|
908
|
+
}).slice(0, 8);
|
|
683
909
|
for (const s of rows) {
|
|
684
910
|
const li = document.createElement('li');
|
|
685
|
-
if (rowOpen.has(s
|
|
911
|
+
if (rowOpen.has(agentKey(s))) li.classList.add('row-open');
|
|
686
912
|
// pip + project + relative-time render as a single horizontal
|
|
687
913
|
// .head strip. The trail + actions render BELOW the head when
|
|
688
914
|
// the row is open (li is flex-column).
|
|
@@ -692,11 +918,22 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
692
918
|
pip.className = 'pip ' + (s.state || 'unknown');
|
|
693
919
|
const proj = document.createElement('span');
|
|
694
920
|
proj.className = 'proj';
|
|
695
|
-
|
|
921
|
+
// Label by git branch when available (more meaningful than a worktree
|
|
922
|
+
// hash); the project/cwd stays visible in the row tooltip below.
|
|
923
|
+
proj.textContent = rosterLabel(s);
|
|
696
924
|
const when = document.createElement('span');
|
|
697
925
|
when.className = 'when';
|
|
698
926
|
when.textContent = relativeTime(s.lastMtime);
|
|
699
927
|
head.appendChild(pip);
|
|
928
|
+
// D4a — agent-kind chip; omitted for plain Claude so existing rows read
|
|
929
|
+
// unchanged, shown for every other agent (codex / opencode / git / …).
|
|
930
|
+
if (s.agent && s.agent !== 'claude-code') {
|
|
931
|
+
const badge = document.createElement('span');
|
|
932
|
+
badge.className = 'agent-badge';
|
|
933
|
+
badge.textContent = s.agent;
|
|
934
|
+
badge.title = s.agent;
|
|
935
|
+
head.appendChild(badge);
|
|
936
|
+
}
|
|
700
937
|
head.appendChild(proj);
|
|
701
938
|
head.appendChild(when);
|
|
702
939
|
li.appendChild(head);
|
|
@@ -724,11 +961,13 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
724
961
|
li.appendChild(actions);
|
|
725
962
|
|
|
726
963
|
li.title = s.state + (s.stateReason ? ' (' + s.stateReason + ')' : '')
|
|
964
|
+
+ ' · ' + s.project
|
|
727
965
|
+ ' · ' + s.sessionId
|
|
728
966
|
+ '\\nclick: expand timeline · double-click: copy sessionId';
|
|
729
967
|
li.addEventListener('click', () => {
|
|
730
|
-
|
|
731
|
-
|
|
968
|
+
const k = agentKey(s);
|
|
969
|
+
if (rowOpen.has(k)) rowOpen.delete(k);
|
|
970
|
+
else rowOpen.add(k);
|
|
732
971
|
renderClaudeList();
|
|
733
972
|
});
|
|
734
973
|
li.addEventListener('dblclick', async (ev) => {
|
|
@@ -791,7 +1030,7 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
791
1030
|
}
|
|
792
1031
|
|
|
793
1032
|
function renderTrail(container, s) {
|
|
794
|
-
const h = stateHistory.get(s
|
|
1033
|
+
const h = stateHistory.get(agentKey(s)) || [];
|
|
795
1034
|
if (h.length === 0) {
|
|
796
1035
|
container.textContent = '(no transitions recorded yet)';
|
|
797
1036
|
return;
|
|
@@ -980,15 +1219,28 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
980
1219
|
);
|
|
981
1220
|
}
|
|
982
1221
|
break;
|
|
983
|
-
case '
|
|
1222
|
+
case 'advisor_suggestions':
|
|
1223
|
+
state.advisor = Array.isArray(m.suggestions) ? m.suggestions : [];
|
|
1224
|
+
refreshPanel();
|
|
1225
|
+
break;
|
|
1226
|
+
case 'sense_event':
|
|
1227
|
+
// Newest first; cap the in-memory feed.
|
|
1228
|
+
state.senseEvents = [m].concat(state.senseEvents || []).slice(0, 30);
|
|
1229
|
+
renderSenseEvents();
|
|
1230
|
+
break;
|
|
1231
|
+
case 'agent_session_update':
|
|
1232
|
+
// D4a — generalized multi-agent event (claude-code + codex + …).
|
|
1233
|
+
// Payload is the normalized AgentSession (lastMtime is epoch ms;
|
|
1234
|
+
// the reducer's new Date() handles that and the ISO fetch shape).
|
|
984
1235
|
upsertClaudeSession({
|
|
985
|
-
|
|
986
|
-
|
|
1236
|
+
agent: m.agent,
|
|
1237
|
+
project: m.project,
|
|
987
1238
|
sessionId: m.sessionId,
|
|
988
|
-
lastMtime: m.
|
|
1239
|
+
lastMtime: m.lastMtime,
|
|
989
1240
|
state: m.state || 'unknown',
|
|
990
1241
|
stateReason: m.stateReason || '',
|
|
991
1242
|
cwd: m.cwd || '',
|
|
1243
|
+
activity: m.activity || null,
|
|
992
1244
|
});
|
|
993
1245
|
refreshDot();
|
|
994
1246
|
refreshPanel();
|
|
@@ -1007,16 +1259,17 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
1007
1259
|
// ── Claude Code session helpers ────────────────────────────────────
|
|
1008
1260
|
|
|
1009
1261
|
function upsertClaudeSession(s) {
|
|
1010
|
-
const
|
|
1011
|
-
|
|
1262
|
+
const k = agentKey(s);
|
|
1263
|
+
const prev = state.agentSessions.find((x) => agentKey(x) === k);
|
|
1264
|
+
const prevState = prev ? prev.state : null;
|
|
1265
|
+
// Source-injected reducer: upserts by (agent, sessionId) and prunes the
|
|
1266
|
+
// window in one pass, returning a fresh array.
|
|
1267
|
+
state.agentSessions = mergeAgentSession(
|
|
1268
|
+
state.agentSessions, s, Date.now(), CLAUDE_ACTIVE_WINDOW_MS,
|
|
1012
1269
|
);
|
|
1013
|
-
const prevState = idx >= 0 ? state.claudeSessions[idx].state : null;
|
|
1014
|
-
if (idx >= 0) state.claudeSessions[idx] = s;
|
|
1015
|
-
else state.claudeSessions.push(s);
|
|
1016
|
-
pruneClaudeSessions();
|
|
1017
1270
|
|
|
1018
1271
|
// Phase 3 — record transition + maybe notify on entering "waiting".
|
|
1019
|
-
const transitioned = recordStateHistory(
|
|
1272
|
+
const transitioned = recordStateHistory(k, {
|
|
1020
1273
|
state: s.state,
|
|
1021
1274
|
reason: s.stateReason,
|
|
1022
1275
|
lastMtime: s.lastMtime,
|
|
@@ -1026,31 +1279,33 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
1026
1279
|
|
|
1027
1280
|
function pruneClaudeSessions() {
|
|
1028
1281
|
const cutoff = Date.now() - CLAUDE_ACTIVE_WINDOW_MS;
|
|
1029
|
-
state.
|
|
1282
|
+
state.agentSessions = state.agentSessions.filter(
|
|
1030
1283
|
(s) => new Date(s.lastMtime).getTime() >= cutoff
|
|
1031
1284
|
);
|
|
1032
1285
|
}
|
|
1033
1286
|
|
|
1034
1287
|
async function fetchClaudeSessions() {
|
|
1035
1288
|
try {
|
|
1036
|
-
|
|
1289
|
+
// D4a — the multi-agent snapshot (all agents), lastMtime as ISO string.
|
|
1290
|
+
const r = await fetch('/api/agents/sessions', { cache: 'no-store' });
|
|
1037
1291
|
if (!r.ok) return;
|
|
1038
1292
|
const j = await r.json();
|
|
1039
1293
|
if (Array.isArray(j.sessions)) {
|
|
1040
|
-
state.
|
|
1294
|
+
state.agentSessions = j.sessions.map((s) => ({
|
|
1295
|
+
agent: s.agent,
|
|
1041
1296
|
project: s.project,
|
|
1042
|
-
projectEncoded: s.projectEncoded,
|
|
1043
1297
|
sessionId: s.sessionId,
|
|
1044
1298
|
lastMtime: s.lastMtime,
|
|
1045
1299
|
state: s.state || 'unknown',
|
|
1046
1300
|
stateReason: s.stateReason || '',
|
|
1047
1301
|
cwd: s.cwd || '',
|
|
1302
|
+
activity: s.activity || null,
|
|
1048
1303
|
}));
|
|
1049
1304
|
// Phase 3 — seed each session's history with its current state
|
|
1050
1305
|
// so the trail isn't empty on first open. Doesn't notify (no
|
|
1051
1306
|
// transition implied by initial load).
|
|
1052
|
-
for (const s of state.
|
|
1053
|
-
recordStateHistory(s
|
|
1307
|
+
for (const s of state.agentSessions) {
|
|
1308
|
+
recordStateHistory(agentKey(s), {
|
|
1054
1309
|
state: s.state,
|
|
1055
1310
|
reason: s.stateReason,
|
|
1056
1311
|
lastMtime: s.lastMtime,
|
|
@@ -1062,6 +1317,97 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
1062
1317
|
}
|
|
1063
1318
|
}
|
|
1064
1319
|
|
|
1320
|
+
// ── Sense consent (FOUNDATIONS §1) ─────────────────────────────────
|
|
1321
|
+
// "Always visible + one-tap stop": list each sensitive signal with a
|
|
1322
|
+
// toggle; the label + Stop-all button light up while anything is on.
|
|
1323
|
+
function renderSense() {
|
|
1324
|
+
const grants = state.sense || [];
|
|
1325
|
+
const onCount = grants.filter((g) => g.granted).length;
|
|
1326
|
+
senseSummary.textContent = onCount > 0 ? (onCount + ' on') : 'all off';
|
|
1327
|
+
body.classList.toggle('sensing', onCount > 0);
|
|
1328
|
+
while (senseList.firstChild) senseList.removeChild(senseList.firstChild);
|
|
1329
|
+
for (const g of grants) {
|
|
1330
|
+
const li = document.createElement('li');
|
|
1331
|
+
const name = document.createElement('span');
|
|
1332
|
+
name.className = 's-name';
|
|
1333
|
+
name.textContent = g.signal;
|
|
1334
|
+
const desc = document.createElement('span');
|
|
1335
|
+
desc.className = 's-desc';
|
|
1336
|
+
desc.textContent = g.description || '';
|
|
1337
|
+
desc.title = g.description || '';
|
|
1338
|
+
const toggle = document.createElement('span');
|
|
1339
|
+
toggle.className = 's-toggle' + (g.granted ? ' on' : '');
|
|
1340
|
+
toggle.textContent = g.granted ? 'on' : 'off';
|
|
1341
|
+
toggle.setAttribute('role', 'button');
|
|
1342
|
+
toggle.tabIndex = 0;
|
|
1343
|
+
toggle.addEventListener('click', (e) => {
|
|
1344
|
+
e.stopPropagation();
|
|
1345
|
+
setSenseSignal(g.signal, !g.granted);
|
|
1346
|
+
});
|
|
1347
|
+
li.appendChild(name);
|
|
1348
|
+
li.appendChild(desc);
|
|
1349
|
+
li.appendChild(toggle);
|
|
1350
|
+
senseList.appendChild(li);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
function applyConsent(j) {
|
|
1355
|
+
if (j && Array.isArray(j.grants)) { state.sense = j.grants; renderSense(); }
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
async function fetchConsent() {
|
|
1359
|
+
try {
|
|
1360
|
+
const r = await fetch('/api/consent', { cache: 'no-store' });
|
|
1361
|
+
if (r.ok) applyConsent(await r.json());
|
|
1362
|
+
} catch (_) { /* older server without /api/consent — silent */ }
|
|
1363
|
+
}
|
|
1364
|
+
|
|
1365
|
+
async function setSenseSignal(signal, on) {
|
|
1366
|
+
try {
|
|
1367
|
+
const r = await fetch('/api/consent/' + (on ? 'grant' : 'revoke'), {
|
|
1368
|
+
method: 'POST',
|
|
1369
|
+
headers: { 'content-type': 'application/json' },
|
|
1370
|
+
body: JSON.stringify({ signal: signal }),
|
|
1371
|
+
});
|
|
1372
|
+
if (r.ok) applyConsent(await r.json());
|
|
1373
|
+
} catch (_) {}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
async function stopAllSensing() {
|
|
1377
|
+
try {
|
|
1378
|
+
const r = await fetch('/api/consent/revoke-all', { method: 'POST' });
|
|
1379
|
+
if (r.ok) applyConsent(await r.json());
|
|
1380
|
+
} catch (_) {}
|
|
1381
|
+
}
|
|
1382
|
+
senseStop.addEventListener('click', (e) => { e.stopPropagation(); stopAllSensing(); });
|
|
1383
|
+
|
|
1384
|
+
// Recently-sensed feed (structural summaries only).
|
|
1385
|
+
function renderSenseEvents() {
|
|
1386
|
+
while (senseEvents.firstChild) senseEvents.removeChild(senseEvents.firstChild);
|
|
1387
|
+
for (const e of (state.senseEvents || []).slice(0, 8)) {
|
|
1388
|
+
const li = document.createElement('li');
|
|
1389
|
+
const when = document.createElement('span');
|
|
1390
|
+
when.className = 'se-when';
|
|
1391
|
+
when.textContent = relativeTime(e.ts);
|
|
1392
|
+
const sum = document.createElement('span');
|
|
1393
|
+
sum.className = 'se-sum';
|
|
1394
|
+
sum.textContent = '[' + (e.signal || '?') + '] ' + (e.summary || '');
|
|
1395
|
+
sum.title = sum.textContent;
|
|
1396
|
+
li.appendChild(when);
|
|
1397
|
+
li.appendChild(sum);
|
|
1398
|
+
senseEvents.appendChild(li);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
async function fetchSenseEvents() {
|
|
1403
|
+
try {
|
|
1404
|
+
const r = await fetch('/api/sense/recent', { cache: 'no-store' });
|
|
1405
|
+
if (!r.ok) return;
|
|
1406
|
+
const j = await r.json();
|
|
1407
|
+
if (Array.isArray(j.events)) { state.senseEvents = j.events; renderSenseEvents(); }
|
|
1408
|
+
} catch (_) { /* older server without the endpoint — silent */ }
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1065
1411
|
// Phase 3 — notification permission opt-in. The CTA is only visible
|
|
1066
1412
|
// when permission is in the default (un-asked) state.
|
|
1067
1413
|
notifyCta.addEventListener('click', (e) => {
|
|
@@ -1076,6 +1422,8 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
1076
1422
|
setAvatar('neutral');
|
|
1077
1423
|
pollPing();
|
|
1078
1424
|
fetchClaudeSessions().then(() => { refreshDot(); refreshPanel(); });
|
|
1425
|
+
fetchConsent();
|
|
1426
|
+
fetchSenseEvents();
|
|
1079
1427
|
subscribe();
|
|
1080
1428
|
refreshNotifyCta();
|
|
1081
1429
|
// A fresh island picks up the most recent screen-advisor suggestion (if the
|
|
@@ -1084,10 +1432,22 @@ export const ISLAND_HTML = `<!doctype html>
|
|
|
1084
1432
|
.then((r) => r.ok ? r.json() : null)
|
|
1085
1433
|
.then((j) => { if (j && j.suggestion) { state.suggestion = j.suggestion; refreshPanel(); } })
|
|
1086
1434
|
.catch(() => {});
|
|
1435
|
+
// …and the latest cross-agent advisor suggestions.
|
|
1436
|
+
fetch('/api/advisor/latest', { cache: 'no-store' })
|
|
1437
|
+
.then((r) => r.ok ? r.json() : null)
|
|
1438
|
+
.then((j) => {
|
|
1439
|
+
if (j && Array.isArray(j.suggestions) && j.suggestions.length) {
|
|
1440
|
+
state.advisor = j.suggestions;
|
|
1441
|
+
refreshPanel();
|
|
1442
|
+
}
|
|
1443
|
+
})
|
|
1444
|
+
.catch(() => {});
|
|
1087
1445
|
// Lightweight resync — covers cases where SSE silently disconnected
|
|
1088
1446
|
// (laptop sleep, network blip) and we need to refresh state.
|
|
1089
1447
|
setInterval(pollPing, 30_000);
|
|
1090
1448
|
setInterval(fetchClaudeSessions, 60_000);
|
|
1449
|
+
// Pick up consent changes made elsewhere (e.g. the lisa consent CLI).
|
|
1450
|
+
setInterval(fetchConsent, 30_000);
|
|
1091
1451
|
// Re-render every 15s so "Xs ago" / "Xm ago" labels stay fresh and
|
|
1092
1452
|
// stale sessions fall off the list without a fresh update event.
|
|
1093
1453
|
setInterval(() => {
|