@oratis/lisa 0.3.0 → 0.3.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 (44) hide show
  1. package/README.md +34 -5
  2. package/README.zh-CN.md +34 -5
  3. package/dist/channels/imessage.d.ts +16 -0
  4. package/dist/channels/imessage.d.ts.map +1 -1
  5. package/dist/channels/imessage.js +29 -8
  6. package/dist/channels/imessage.js.map +1 -1
  7. package/dist/heartbeat/config.d.ts +10 -0
  8. package/dist/heartbeat/config.d.ts.map +1 -1
  9. package/dist/heartbeat/config.js +8 -2
  10. package/dist/heartbeat/config.js.map +1 -1
  11. package/dist/heartbeat/runner.d.ts.map +1 -1
  12. package/dist/heartbeat/runner.js +30 -0
  13. package/dist/heartbeat/runner.js.map +1 -1
  14. package/dist/memory/vector.d.ts +5 -1
  15. package/dist/memory/vector.d.ts.map +1 -1
  16. package/dist/memory/vector.js +58 -1
  17. package/dist/memory/vector.js.map +1 -1
  18. package/dist/soul/lock.d.ts +38 -0
  19. package/dist/soul/lock.d.ts.map +1 -0
  20. package/dist/soul/lock.js +103 -0
  21. package/dist/soul/lock.js.map +1 -0
  22. package/dist/soul/paths.d.ts.map +1 -1
  23. package/dist/soul/paths.js +12 -6
  24. package/dist/soul/paths.js.map +1 -1
  25. package/dist/soul/slug.d.ts +40 -0
  26. package/dist/soul/slug.d.ts.map +1 -0
  27. package/dist/soul/slug.js +78 -0
  28. package/dist/soul/slug.js.map +1 -0
  29. package/dist/soul/store.d.ts +12 -1
  30. package/dist/soul/store.d.ts.map +1 -1
  31. package/dist/soul/store.js +40 -23
  32. package/dist/soul/store.js.map +1 -1
  33. package/dist/soul/tools.d.ts.map +1 -1
  34. package/dist/soul/tools.js +10 -3
  35. package/dist/soul/tools.js.map +1 -1
  36. package/dist/tools/web_fetch.d.ts +10 -0
  37. package/dist/tools/web_fetch.d.ts.map +1 -1
  38. package/dist/tools/web_fetch.js +48 -16
  39. package/dist/tools/web_fetch.js.map +1 -1
  40. package/dist/web/island.d.ts +1 -1
  41. package/dist/web/island.d.ts.map +1 -1
  42. package/dist/web/island.js +26 -5
  43. package/dist/web/island.js.map +1 -1
  44. package/package.json +3 -1
@@ -1 +1 @@
1
- {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/soul/tools.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/soul/tools.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,16 @@ interface WebFetchInput {
5
5
  max_chars?: number;
6
6
  }
7
7
  export declare const webFetchTool: ToolDefinition<WebFetchInput, string>;
8
+ /** Throw if the URL isn't http(s) or resolves to a private/loopback host. */
9
+ export declare function assertAllowedUrl(u: URL): void;
10
+ /**
11
+ * fetch() with manual redirect handling. Validates the host of EACH hop
12
+ * (initial + every Location) against the private-IP blocklist before
13
+ * issuing the request — closing the SSRF redirect bypass. Caps at
14
+ * MAX_REDIRECTS to avoid loops.
15
+ */
16
+ export declare function fetchFollowingSafeRedirects(startUrl: string, signal: AbortSignal | undefined): Promise<Response>;
17
+ export declare function isPrivateHost(host: string): boolean;
8
18
  export declare function htmlToText(html: string): string;
9
19
  export {};
10
20
  //# sourceMappingURL=web_fetch.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"web_fetch.d.ts","sourceRoot":"","sources":["../../src/tools/web_fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,aAAa,EAAE,MAAM,CAmD9D,CAAC;AAgBF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB/C"}
1
+ {"version":3,"file":"web_fetch.d.ts","sourceRoot":"","sources":["../../src/tools/web_fetch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAElD,UAAU,aAAa;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAKD,eAAO,MAAM,YAAY,EAAE,cAAc,CAAC,aAAa,EAAE,MAAM,CAyC9D,CAAC;AAKF,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,GAAG,GAAG,IAAI,CAQ7C;AAED;;;;;GAKG;AACH,wBAAsB,2BAA2B,CAC/C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,WAAW,GAAG,SAAS,GAC9B,OAAO,CAAC,QAAQ,CAAC,CAqBnB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAYnD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAqB/C"}
@@ -23,22 +23,13 @@ export const webFetchTool = {
23
23
  catch {
24
24
  throw new Error(`bad URL: ${input.url}`);
25
25
  }
26
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
27
- throw new Error(`only http(s) URLs allowed (got ${parsed.protocol})`);
28
- }
29
- const host = parsed.hostname.toLowerCase();
30
- if (isPrivateHost(host)) {
31
- throw new Error(`refusing to fetch private/loopback host: ${host}`);
32
- }
26
+ assertAllowedUrl(parsed);
33
27
  const max = Math.min(input.max_chars ?? DEFAULT_MAX, HARD_MAX);
34
- const res = await fetch(input.url, {
35
- signal: ctx.signal,
36
- redirect: "follow",
37
- headers: {
38
- "user-agent": "Lisa/0.1 (web_fetch)",
39
- accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*;q=0.8",
40
- },
41
- });
28
+ // Follow redirects MANUALLY so every hop's host is re-validated. With
29
+ // redirect:"follow" a public URL could 301 → http://127.0.0.1:8000 and
30
+ // the fetch would reach the internal service (SSRF). We re-run the
31
+ // private-host + protocol check on each Location before following.
32
+ const res = await fetchFollowingSafeRedirects(input.url, ctx?.signal);
42
33
  const ct = res.headers.get("content-type") ?? "";
43
34
  let body = await res.text();
44
35
  if (input.format !== "raw" && /html|xml/i.test(ct)) {
@@ -50,7 +41,48 @@ export const webFetchTool = {
50
41
  return `HTTP ${res.status} ${res.statusText} ${input.url}\ncontent-type: ${ct}\n\n${body}`;
51
42
  },
52
43
  };
53
- function isPrivateHost(host) {
44
+ const MAX_REDIRECTS = 5;
45
+ const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
46
+ /** Throw if the URL isn't http(s) or resolves to a private/loopback host. */
47
+ export function assertAllowedUrl(u) {
48
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
49
+ throw new Error(`only http(s) URLs allowed (got ${u.protocol})`);
50
+ }
51
+ const host = u.hostname.toLowerCase().replace(/^\[|\]$/g, ""); // strip IPv6 brackets
52
+ if (isPrivateHost(host)) {
53
+ throw new Error(`refusing to fetch private/loopback host: ${host}`);
54
+ }
55
+ }
56
+ /**
57
+ * fetch() with manual redirect handling. Validates the host of EACH hop
58
+ * (initial + every Location) against the private-IP blocklist before
59
+ * issuing the request — closing the SSRF redirect bypass. Caps at
60
+ * MAX_REDIRECTS to avoid loops.
61
+ */
62
+ export async function fetchFollowingSafeRedirects(startUrl, signal) {
63
+ let current = startUrl;
64
+ for (let hop = 0; hop <= MAX_REDIRECTS; hop++) {
65
+ assertAllowedUrl(new URL(current));
66
+ const res = await fetch(current, {
67
+ signal,
68
+ redirect: "manual",
69
+ headers: {
70
+ "user-agent": "Lisa/0.1 (web_fetch)",
71
+ accept: "text/html,application/xhtml+xml,application/json,text/plain,*/*;q=0.8",
72
+ },
73
+ });
74
+ if (!REDIRECT_STATUSES.has(res.status))
75
+ return res;
76
+ const location = res.headers.get("location");
77
+ if (!location)
78
+ return res; // redirect with no target — return as-is
79
+ // Resolve relative Location against the current URL, then loop to
80
+ // re-validate the new host before following.
81
+ current = new URL(location, current).toString();
82
+ }
83
+ throw new Error(`too many redirects (>${MAX_REDIRECTS}) starting from ${startUrl}`);
84
+ }
85
+ export function isPrivateHost(host) {
54
86
  if (host === "localhost")
55
87
  return true;
56
88
  if (host === "::1" || host === "0:0:0:0:0:0:0:1")
@@ -1 +1 @@
1
- {"version":3,"file":"web_fetch.js","sourceRoot":"","sources":["../../src/tools/web_fetch.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"web_fetch.js","sourceRoot":"","sources":["../../src/tools/web_fetch.ts"],"names":[],"mappings":"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"}
@@ -8,5 +8,5 @@
8
8
  *
9
9
  * No build step, no framework — single string of inline HTML/CSS/JS.
10
10
  */
11
- export declare const ISLAND_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Lisa \u00B7 island</title>\n<link rel=\"manifest\" href=\"/manifest.webmanifest\">\n<style>\n :root {\n color-scheme: dark;\n --bg: rgba(8, 12, 24, 0.92);\n --bg-strong: rgba(8, 12, 24, 0.96);\n --fg: #e4e4e6;\n --fg-dim: #9ba3b8;\n --fg-faint: #6b7280;\n --accent: #6ad4ff;\n --accent-warm: #ffd066;\n --accent-dream: #b487ff;\n --accent-claude: #ff8c42;\n --border: rgba(255, 255, 255, 0.06);\n }\n html, body {\n margin: 0;\n padding: 0;\n background: transparent;\n overflow: hidden;\n height: 100vh;\n font-family: -apple-system, BlinkMacSystemFont, \"SF Pro Text\", system-ui, sans-serif;\n -webkit-font-smoothing: antialiased;\n color: var(--fg);\n user-select: none;\n cursor: default;\n }\n body {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 4px 8px;\n }\n\n #pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 22px;\n padding: 5px 14px 5px 5px;\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);\n cursor: pointer;\n transition: transform 200ms ease, opacity 200ms ease;\n max-width: 280px;\n }\n #pill:hover { transform: translateY(1px); }\n\n /* Avatar is an <img> not a background-image \u2014 more reliable in\n WKWebView and lets us crop into the face via object-position.\n The 512\u00D7512 source has ~15% transparent padding around the\n character; we scale up via object-fit + anchor toward the top\n so the face dominates the small circle.\n pointer-events: none + draggable=false so the img never steals\n or hijacks mouse events from the pill (HTML <img> default is\n draggable, which interferes with our Swift-side click/drag\n resolution). */\n #avatar {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n object-fit: cover;\n object-position: 50% 22%;\n background: #15192a;\n flex-shrink: 0;\n image-rendering: pixelated;\n border: 1px solid rgba(255, 255, 255, 0.10);\n box-shadow: 0 0 0 2px rgba(106, 212, 255, 0.10);\n pointer-events: none;\n -webkit-user-drag: none;\n user-select: none;\n }\n\n #label {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--fg);\n }\n\n #dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: transparent;\n flex-shrink: 0;\n }\n #dot.thinking { background: var(--accent); animation: pulse 1.2s ease-in-out infinite; }\n #dot.dreaming { background: var(--accent-dream); animation: pulse 2.4s ease-in-out infinite; }\n #dot.unread { background: var(--accent-warm); }\n /* Phase 2: pill dot reflects the strongest signal across all\n Claude sessions. */\n #dot.claude-working { background: var(--accent-claude); animation: pulse 1.8s ease-in-out infinite; }\n #dot.claude-waiting { background: var(--accent-claude); } /* solid \u2014 \"needs you\" */\n #dot.claude-error { background: #ff5577; animation: pulse 0.8s ease-in-out infinite; }\n #dot.offline { background: var(--fg-faint); }\n\n @keyframes pulse {\n 0%, 100% { opacity: 0.35; }\n 50% { opacity: 1; }\n }\n\n /* Expanded panel \u2014 appears below the pill on hover/click */\n #expand {\n margin-top: 10px;\n width: 336px;\n background: var(--bg-strong);\n border: 1px solid var(--border);\n border-radius: 18px;\n padding: 16px 18px;\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);\n font-size: 12.5px;\n line-height: 1.55;\n box-sizing: border-box;\n opacity: 0;\n transform: translateY(-4px);\n pointer-events: none;\n transition: opacity 200ms ease, transform 200ms ease;\n }\n body.expanded #expand {\n opacity: 1;\n transform: none;\n pointer-events: auto;\n }\n\n /* Stack section blocks with consistent vertical rhythm. */\n #expand > div + div { margin-top: 14px; }\n\n .section-label {\n color: var(--accent);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.10em;\n font-size: 10.5px;\n margin-bottom: 8px;\n }\n .section-body {\n color: var(--fg-dim);\n word-wrap: break-word;\n }\n\n #idle-section { display: none; }\n body.has-unread #idle-section { display: block; }\n #idle-body {\n background: rgba(255, 208, 102, 0.07);\n border-left: 2px solid var(--accent-warm);\n padding: 8px 12px;\n border-radius: 6px;\n color: var(--fg);\n max-height: 110px;\n overflow-y: auto;\n white-space: pre-wrap;\n }\n\n /* Claude Code section \u2014 appears when there's active Claude Code activity */\n #claude-section { display: none; }\n body.has-claude #claude-section { display: block; }\n #claude-section .section-label { color: var(--accent-claude); }\n #claude-list {\n list-style: none;\n padding: 4px 0;\n margin: 0;\n border-left: 2px solid var(--accent-claude);\n background: rgba(255, 140, 66, 0.06);\n border-radius: 6px;\n overflow-y: auto;\n max-height: 200px;\n }\n #claude-list li {\n padding: 8px 12px;\n color: var(--fg);\n font-size: 11.5px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n cursor: pointer;\n transition: background 120ms ease;\n }\n #claude-list li:hover { background: rgba(255, 140, 66, 0.10); }\n #claude-list li + li { border-top: 1px solid rgba(255, 140, 66, 0.10); }\n /* Row \"head\" \u2014 the pip + project + relative-time line. Always rendered.\n Stays as a horizontal flex strip even when the row is expanded; the\n trail + actions render BELOW it because the parent <li> is flex-column. */\n #claude-list .head {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n #claude-list .proj { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n #claude-list .when { color: var(--fg-dim); flex-shrink: 0; font-variant-numeric: tabular-nums; font-size: 11px; }\n #claude-list .empty { padding: 8px 12px; color: var(--fg-faint); font-style: italic; }\n\n /* Phase 2 \u2014 per-session state pip prefix */\n #claude-list .pip {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--fg-faint);\n margin-right: 4px;\n }\n #claude-list .pip.working { background: var(--accent-claude); animation: pulse 1.8s ease-in-out infinite; }\n #claude-list .pip.waiting { background: var(--accent-claude); }\n #claude-list .pip.error { background: #ff5577; }\n #claude-list .pip.unknown { background: var(--fg-faint); }\n\n /* Phase 3 \u2014 state transition trail, shown below the row head when\n the parent <li> has the .row-open class. */\n #claude-list .trail {\n display: none;\n margin: 4px 0 0 14px;\n padding: 6px 0 2px;\n border-top: 1px dashed rgba(255, 140, 66, 0.18);\n font-size: 10px;\n color: var(--fg-faint);\n line-height: 1.7;\n word-spacing: 0.05em;\n }\n #claude-list li.row-open .trail { display: block; }\n\n /* Phase 3.5 \u2014 inline action row when the session is expanded */\n #claude-list .actions {\n display: none;\n margin: 6px 0 0 14px;\n gap: 6px;\n flex-wrap: wrap;\n }\n #claude-list li.row-open .actions { display: flex; }\n #claude-list .actions button {\n flex: 0 0 auto;\n background: rgba(255, 140, 66, 0.10);\n border: 1px solid rgba(255, 140, 66, 0.22);\n color: var(--fg);\n padding: 4px 8px;\n border-radius: 6px;\n font-size: 10.5px;\n cursor: pointer;\n font-family: inherit;\n }\n #claude-list .actions button:hover { background: rgba(255, 140, 66, 0.16); }\n #claude-list .actions button:disabled { opacity: 0.35; cursor: not-allowed; }\n #claude-list .trail .tdot {\n display: inline-block;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n margin-right: 4px;\n vertical-align: 0;\n background: var(--fg-faint);\n }\n #claude-list .trail .tdot.working { background: var(--accent-claude); }\n #claude-list .trail .tdot.waiting { background: var(--accent-claude); opacity: 0.7; }\n #claude-list .trail .tdot.error { background: #ff5577; }\n /* (older stub removed \u2014 .head is a real flex strip now, see above) */\n\n /* Phase 3 \u2014 notification opt-in chip */\n #notify-cta {\n display: none;\n margin-top: 10px;\n padding: 8px 12px;\n border-radius: 10px;\n background: rgba(255, 140, 66, 0.12);\n border: 1px solid rgba(255, 140, 66, 0.30);\n font-size: 11px;\n color: var(--fg);\n text-align: center;\n cursor: pointer;\n transition: background 120ms ease;\n }\n #notify-cta:hover { background: rgba(255, 140, 66, 0.18); }\n body.notify-default #notify-cta { display: block; }\n\n #actions {\n display: flex;\n gap: 8px;\n }\n button {\n flex: 1;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid var(--border);\n color: var(--fg);\n padding: 8px 12px;\n border-radius: 10px;\n font-size: 11.5px;\n cursor: pointer;\n font-family: inherit;\n transition: background 150ms ease;\n }\n button:hover { background: rgba(255, 255, 255, 0.10); }\n button:active { background: rgba(255, 255, 255, 0.15); }\n button.muted { opacity: 0.5; }\n\n /* Offline state \u2014 desaturate + dim */\n body.offline #avatar { filter: grayscale(1); opacity: 0.5; }\n body.offline #label { color: var(--fg-faint); }\n</style>\n</head>\n<body>\n <div id=\"pill\" role=\"button\" tabindex=\"0\" aria-label=\"Lisa island\">\n <img id=\"avatar\" alt=\"\" draggable=\"false\" src=\"/assets/lisa/neutral.png\" />\n <div id=\"label\">Lisa</div>\n <div id=\"dot\" aria-hidden=\"true\"></div>\n </div>\n <div id=\"expand\" role=\"region\" aria-label=\"Lisa status detail\">\n <div id=\"desire-section\">\n <div class=\"section-label\">currently wanting</div>\n <div class=\"section-body\" id=\"desire-body\">\u2014</div>\n </div>\n <div id=\"idle-section\">\n <div class=\"section-label\">\u2605 while you were away</div>\n <div id=\"idle-body\"></div>\n </div>\n <div id=\"claude-section\">\n <div class=\"section-label\">claude code \u00B7 <span id=\"claude-count\">0</span> active</div>\n <ul id=\"claude-list\"></ul>\n <div id=\"notify-cta\" role=\"button\" tabindex=\"0\">\uD83D\uDD14 Notify me when Claude is waiting</div>\n </div>\n <div id=\"actions\">\n <button id=\"btn-open\">Open chat</button>\n <button id=\"btn-dismiss\" class=\"muted\">Dismiss \u2605</button>\n </div>\n </div>\n\n<script>\n(() => {\n const pill = document.getElementById('pill');\n const avatar = document.getElementById('avatar');\n const dot = document.getElementById('dot');\n const expand = document.getElementById('expand');\n const desireBody = document.getElementById('desire-body');\n const idleBody = document.getElementById('idle-body');\n const claudeList = document.getElementById('claude-list');\n const claudeCount = document.getElementById('claude-count');\n const notifyCta = document.getElementById('notify-cta');\n const btnOpen = document.getElementById('btn-open');\n const btnDismiss = document.getElementById('btn-dismiss');\n const body = document.body;\n\n const state = {\n mood: 'neutral',\n online: false,\n unread: false,\n idleText: '',\n desire: null,\n thinking: false,\n dreaming: false,\n claudeSessions: [], // [{project, sessionId, lastMtime}, \u2026]\n };\n\n // 30-min activity window matches the watcher's ACTIVE_WINDOW_MS.\n const CLAUDE_ACTIVE_WINDOW_MS = 30 * 60 * 1000;\n\n // Phase 3 \u2014 in-memory state transition history per session.\n // Map<sessionId, [{state, reason, ts}, \u2026]> capped at MAX_HISTORY.\n // Pure UI memory; not persisted, not transmitted.\n const stateHistory = new Map();\n const MAX_HISTORY = 8;\n // Per-session opened-state for the inline trail in the expand list.\n const rowOpen = new Set();\n\n function recordStateHistory(sessionId, info) {\n let h = stateHistory.get(sessionId);\n if (!h) { h = []; stateHistory.set(sessionId, h); }\n const last = h[h.length - 1];\n if (last && last.state === info.state && last.reason === info.reason) {\n // Same state, just refresh timestamp \u2014 collapse repeats.\n last.ts = info.lastMtime;\n return false;\n }\n h.push({ state: info.state, reason: info.reason, ts: info.lastMtime });\n while (h.length > MAX_HISTORY) h.shift();\n return true; // transition happened\n }\n\n // \u2500\u2500 Phase 3 \u2014 notifications (native via LisaIsland.app, or web fallback)\n // Fires \"Claude is waiting in <project>\" alerts when a session\n // transitions INTO waiting. Throttled per session so a flaky tool\n // that bounces between waiting/working doesn't spam.\n //\n // Phase 3.5: when running inside LisaIsland.app, we delegate to the\n // native UNUserNotificationCenter via postMessage \u2014 better permission\n // flow, integrates with macOS Focus / DnD. In a plain browser tab we\n // fall back to the Notification API.\n const NOTIFY_THROTTLE_MS = 60_000;\n const lastNotifyAt = new Map();\n const hasBridge = !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island);\n\n function notifPermission() {\n if (hasBridge) return 'granted'; // native side asks the user; we just request and trust\n return ('Notification' in window) ? Notification.permission : 'unsupported';\n }\n\n function refreshNotifyCta() {\n // The \uD83D\uDD14 chip is only needed when running in a browser AND\n // permission hasn't been answered yet. Native bridge handles its\n // own permission prompt.\n if (hasBridge) {\n body.classList.remove('notify-default');\n return;\n }\n body.classList.toggle('notify-default', notifPermission() === 'default');\n }\n\n function requestNotificationPermission() {\n if (hasBridge) {\n window.webkit.messageHandlers.island.postMessage({ type: 'ensure_notify_permission' });\n refreshNotifyCta();\n return;\n }\n if (!('Notification' in window) || Notification.permission !== 'default') {\n refreshNotifyCta();\n return;\n }\n Notification.requestPermission().then(() => refreshNotifyCta()).catch(() => {});\n }\n\n function maybeNotifyWaiting(prevState, info) {\n if (info.state !== 'waiting') return;\n if (prevState === 'waiting') return; // already in this state\n const last = lastNotifyAt.get(info.sessionId) || 0;\n if (Date.now() - last < NOTIFY_THROTTLE_MS) return;\n lastNotifyAt.set(info.sessionId, Date.now());\n const reasonLabel = info.stateReason === 'permission' ? 'needs permission' : 'is waiting';\n const title = 'Claude ' + reasonLabel + ' in ' + info.project;\n const bodyText = info.sessionId.slice(0, 8) + ' \u00B7 click to open Lisa';\n if (hasBridge) {\n window.webkit.messageHandlers.island.postMessage({\n type: 'notify',\n title: title,\n body: bodyText,\n sessionId: info.sessionId,\n });\n return;\n }\n if (notifPermission() !== 'granted') return;\n try {\n const n = new Notification(title, {\n body: bodyText,\n tag: 'lisa-claude-' + info.sessionId,\n icon: '/assets/lisa-mascot.png',\n silent: false,\n });\n n.onclick = () => { window.focus(); window.open('/', '_blank'); n.close(); };\n } catch (_) { /* unsupported, ignore */ }\n }\n function recentSessions() {\n const cutoff = Date.now() - CLAUDE_ACTIVE_WINDOW_MS;\n return state.claudeSessions.filter(\n (s) => new Date(s.lastMtime).getTime() >= cutoff\n );\n }\n /**\n * Phase 2: aggregate Claude state for the pill dot. Priority is\n * \"loudest signal wins\": an error anywhere dominates everything;\n * otherwise a \"waiting\" session beats a \"working\" session (because\n * \"waiting\" means Claude needs the user \u2014 more attention-worthy\n * than \"working\" which is passive observing).\n */\n function aggregateClaudeState() {\n const recent = recentSessions();\n if (recent.length === 0) return null;\n if (recent.some((s) => s.state === 'error')) return 'error';\n if (recent.some((s) => s.state === 'waiting')) return 'waiting';\n if (recent.some((s) => s.state === 'working')) return 'working';\n return null;\n }\n\n function setAvatar(slug) {\n if (!slug) return;\n state.mood = slug;\n // Use the <img> src attribute \u2014 far more reliable than CSS\n // background-image in WKWebView, and lets the browser's standard\n // image cache + retry logic do its thing.\n avatar.src = '/assets/lisa/' + encodeURIComponent(slug) + '.png';\n }\n\n function refreshDot() {\n dot.className = '';\n // Priority: LISA's own state (offline / thinking / dreaming / unread)\n // always wins over the Claude-Code-monitor indicator \u2014 the pill is\n // primarily about her, the Claude dot is a quieter \"by the way\".\n if (!state.online) { dot.classList.add('offline'); return; }\n if (state.thinking) { dot.classList.add('thinking'); return; }\n if (state.dreaming) { dot.classList.add('dreaming'); return; }\n if (state.unread) { dot.classList.add('unread'); return; }\n const claude = aggregateClaudeState();\n if (claude === 'error') { dot.classList.add('claude-error'); return; }\n if (claude === 'waiting') { dot.classList.add('claude-waiting'); return; }\n if (claude === 'working') { dot.classList.add('claude-working'); return; }\n }\n\n function refreshPanel() {\n body.classList.toggle('offline', !state.online);\n body.classList.toggle('has-unread', state.unread);\n body.classList.toggle('has-claude', state.claudeSessions.length > 0);\n desireBody.textContent = state.desire || '(nothing actively pursued)';\n idleBody.textContent = state.idleText || '';\n btnDismiss.classList.toggle('muted', !state.unread);\n btnDismiss.disabled = !state.unread;\n renderClaudeList();\n }\n\n function relativeTime(iso) {\n const ms = Date.now() - new Date(iso).getTime();\n if (ms < 30_000) return 'just now';\n if (ms < 60_000) return Math.round(ms / 1000) + 's ago';\n if (ms < 3600_000) return Math.round(ms / 60_000) + 'm ago';\n return Math.round(ms / 3600_000) + 'h ago';\n }\n\n function renderClaudeList() {\n const recent = recentSessions();\n claudeCount.textContent = String(recent.length);\n while (claudeList.firstChild) claudeList.removeChild(claudeList.firstChild);\n if (recent.length === 0) {\n const li = document.createElement('li');\n li.className = 'empty';\n li.textContent = '(idle)';\n claudeList.appendChild(li);\n return;\n }\n // Sort: errors first, then waiting, then working, then by mtime.\n const stateRank = { error: 0, waiting: 1, working: 2, unknown: 3 };\n const rows = recent.slice().sort((a, b) => {\n const ra = stateRank[a.state] ?? 9;\n const rb = stateRank[b.state] ?? 9;\n if (ra !== rb) return ra - rb;\n return new Date(b.lastMtime).getTime() - new Date(a.lastMtime).getTime();\n }).slice(0, 5);\n for (const s of rows) {\n const li = document.createElement('li');\n if (rowOpen.has(s.sessionId)) li.classList.add('row-open');\n // pip + project + relative-time render as a single horizontal\n // .head strip. The trail + actions render BELOW the head when\n // the row is open (li is flex-column).\n const head = document.createElement('div');\n head.className = 'head';\n const pip = document.createElement('span');\n pip.className = 'pip ' + (s.state || 'unknown');\n const proj = document.createElement('span');\n proj.className = 'proj';\n proj.textContent = s.project;\n const when = document.createElement('span');\n when.className = 'when';\n when.textContent = relativeTime(s.lastMtime);\n head.appendChild(pip);\n head.appendChild(proj);\n head.appendChild(when);\n li.appendChild(head);\n\n // Phase 3 \u2014 collapsible state-transition trail\n const trail = document.createElement('div');\n trail.className = 'trail';\n renderTrail(trail, s);\n li.appendChild(trail);\n\n // Phase 3.5 \u2014 action buttons (Open in Finder / Copy resume)\n const actions = document.createElement('div');\n actions.className = 'actions';\n renderActions(actions, s);\n li.appendChild(actions);\n\n li.title = s.state + (s.stateReason ? ' (' + s.stateReason + ')' : '')\n + ' \u00B7 ' + s.sessionId\n + '\\nclick: expand timeline \u00B7 double-click: copy sessionId';\n li.addEventListener('click', () => {\n if (rowOpen.has(s.sessionId)) rowOpen.delete(s.sessionId);\n else rowOpen.add(s.sessionId);\n renderClaudeList();\n });\n li.addEventListener('dblclick', async (ev) => {\n ev.stopPropagation();\n try { await navigator.clipboard.writeText(s.sessionId); } catch (_) {}\n });\n claudeList.appendChild(li);\n }\n }\n\n /**\n * Phase 3.5 \u2014 render the inline action buttons for one Claude session.\n * Each session has a cwd (from .cwd top-level field in the jsonl)\n * and a sessionId. We expose two actions:\n * - Open in Finder \u2014 opens the cwd folder\n * - Copy resume cmd \u2014 clipboard: cd \"<cwd>\" && claude --resume <sid>\n */\n function renderActions(container, s) {\n const cwd = s.cwd || '';\n const hasCwd = cwd.startsWith('/');\n\n const openBtn = document.createElement('button');\n openBtn.type = 'button';\n openBtn.textContent = '\uD83D\uDCC1 Open folder';\n openBtn.disabled = !hasCwd;\n openBtn.title = hasCwd ? cwd : 'No cwd recorded in this session';\n openBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (!hasCwd) return;\n if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island) {\n window.webkit.messageHandlers.island.postMessage({\n type: 'open_path',\n path: cwd,\n });\n } else {\n // Browser fallback: copy the path so the user can paste in Finder.\n navigator.clipboard.writeText(cwd).catch(() => {});\n }\n });\n container.appendChild(openBtn);\n\n const copyBtn = document.createElement('button');\n copyBtn.type = 'button';\n copyBtn.textContent = '\uD83D\uDCCB Resume cmd';\n copyBtn.disabled = !hasCwd;\n const cmd = hasCwd\n ? 'cd ' + JSON.stringify(cwd) + ' && claude --resume ' + s.sessionId\n : 'claude --resume ' + s.sessionId;\n copyBtn.title = cmd;\n copyBtn.addEventListener('click', async (e) => {\n e.stopPropagation();\n try {\n await navigator.clipboard.writeText(cmd);\n const orig = copyBtn.textContent;\n copyBtn.textContent = '\u2713 copied';\n setTimeout(() => { copyBtn.textContent = orig; }, 1200);\n } catch (_) {}\n });\n container.appendChild(copyBtn);\n }\n\n function renderTrail(container, s) {\n const h = stateHistory.get(s.sessionId) || [];\n if (h.length === 0) {\n container.textContent = '(no transitions recorded yet)';\n return;\n }\n // Render newest first so the right-most reads as \"right now\".\n const ordered = h.slice();\n for (let i = 0; i < ordered.length; i++) {\n const entry = ordered[i];\n const tdot = document.createElement('span');\n tdot.className = 'tdot ' + (entry.state || 'unknown');\n container.appendChild(tdot);\n const span = document.createElement('span');\n span.textContent = entry.state + ' \u00B7 ' + relativeTime(entry.ts);\n container.appendChild(span);\n if (i < ordered.length - 1) {\n const sep = document.createElement('span');\n sep.textContent = ' \u2192 ';\n sep.style.color = 'rgba(255,255,255,0.15)';\n container.appendChild(sep);\n }\n }\n }\n\n function expandPanel(open) {\n if (body.classList.contains('expanded') === open) return;\n body.classList.toggle('expanded', open);\n // Tell the native container (LisaIsland.app, Phase 2.2+) so it can\n // resize its NSWindow \u2014 the pill window is sized just for the pill\n // when collapsed, and grows to host the expand panel on open.\n // Falls back gracefully when running in a plain browser tab.\n if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island) {\n window.webkit.messageHandlers.island.postMessage({\n type: open ? 'expand' : 'collapse'\n });\n }\n }\n\n // \u2500\u2500 Interaction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // When running inside LisaIsland.app (Phase 2.1+), the native window\n // owns drag and click-vs-drag resolution: Swift's IslandWindow\n // intercepts mouseDown in sendEvent and runs a synchronous\n // mouseDragged loop. If movement > 4px \u2192 drag (setFrameOrigin each\n // tick, no IPC roundtrip). If no movement \u2192 Swift synthesizes\n // pill.click() here so this click handler still fires.\n //\n // In a plain browser tab there's no native container \u2014 the click\n // handler runs normally. Hover-to-expand also works in both modes:\n // when the native window is in \"passthrough\" state for the\n // non-pill area, mouseenter still fires on the WKWebView once the\n // cursor crosses INTO the pill region.\n\n let hoverTimer = null;\n pill.addEventListener('mouseenter', () => {\n clearTimeout(hoverTimer);\n hoverTimer = setTimeout(() => expandPanel(true), 250);\n });\n pill.addEventListener('mouseleave', () => {\n clearTimeout(hoverTimer);\n // Don't collapse if the mouse just moved into the expand panel.\n setTimeout(() => {\n if (!expand.matches(':hover')) expandPanel(false);\n }, 200);\n });\n expand.addEventListener('mouseleave', () => expandPanel(false));\n\n pill.addEventListener('click', (e) => {\n e.preventDefault();\n expandPanel(!body.classList.contains('expanded'));\n });\n pill.addEventListener('dblclick', (e) => {\n e.preventDefault();\n openFull();\n });\n\n function openFull() {\n // If a Swift container is present (Phase 2), prefer to delegate.\n if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island) {\n window.webkit.messageHandlers.island.postMessage({ type: 'open_full_gui' });\n } else {\n window.open('/', '_blank');\n }\n }\n btnOpen.addEventListener('click', (e) => { e.stopPropagation(); openFull(); });\n btnDismiss.addEventListener('click', async (e) => {\n e.stopPropagation();\n if (!state.unread) return;\n try { await fetch('/api/island/dismiss-unread', { method: 'POST' }); } catch (_) {}\n state.unread = false;\n state.idleText = '';\n refreshDot();\n refreshPanel();\n });\n\n // \u2500\u2500 Server polling for richer state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let backoff = 5_000;\n async function pollPing() {\n try {\n const r = await fetch('/api/island/ping', { cache: 'no-store' });\n if (!r.ok) throw new Error('not ok');\n const j = await r.json();\n state.online = !!j.online;\n state.unread = !!j.has_unread_idle_message;\n state.idleText = j.last_idle_message_text || '';\n state.desire = j.current_desire || null;\n if (j.mood) setAvatar(j.mood);\n backoff = 5_000;\n } catch (_) {\n state.online = false;\n }\n refreshDot();\n refreshPanel();\n }\n\n // \u2500\u2500 SSE subscription for instant pulses \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let es = null;\n function subscribe() {\n try { if (es) es.close(); } catch (_) {}\n es = new EventSource('/events');\n es.addEventListener('open', () => {\n state.online = true;\n refreshDot();\n refreshPanel();\n });\n es.addEventListener('message', (ev) => {\n let m;\n try { m = JSON.parse(ev.data); } catch (_) { return; }\n switch (m.type) {\n case 'mood':\n setAvatar(m.slug);\n break;\n case 'chat_start':\n state.thinking = true;\n refreshDot();\n break;\n case 'chat_end':\n state.thinking = false;\n refreshDot();\n break;\n case 'idle_start':\n state.dreaming = true;\n refreshDot();\n break;\n case 'idle_done':\n case 'idle_error':\n state.dreaming = false;\n refreshDot();\n break;\n case 'idle_message':\n state.dreaming = false;\n state.unread = true;\n state.idleText = (m.text || '').slice(0, 1000);\n refreshDot();\n refreshPanel();\n // One subtle pulse so a watching user notices.\n document.body.animate(\n [{ opacity: 0.7 }, { opacity: 1 }, { opacity: 0.85 }],\n { duration: 600, iterations: 2 },\n );\n break;\n case 'claude_session_update':\n upsertClaudeSession({\n project: m.projectLabel,\n projectEncoded: m.projectEncoded,\n sessionId: m.sessionId,\n lastMtime: m.ts,\n state: m.state || 'unknown',\n stateReason: m.stateReason || '',\n cwd: m.cwd || '',\n });\n refreshDot();\n refreshPanel();\n break;\n }\n });\n es.addEventListener('error', () => {\n state.online = false;\n refreshDot();\n refreshPanel();\n // Auto-retry: SSE EventSource reconnects on its own, but if the page\n // server is down entirely we'll keep firing 'error' until it's back.\n });\n }\n\n // \u2500\u2500 Claude Code session helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n function upsertClaudeSession(s) {\n const idx = state.claudeSessions.findIndex(\n (x) => x.sessionId === s.sessionId\n );\n const prevState = idx >= 0 ? state.claudeSessions[idx].state : null;\n if (idx >= 0) state.claudeSessions[idx] = s;\n else state.claudeSessions.push(s);\n pruneClaudeSessions();\n\n // Phase 3 \u2014 record transition + maybe notify on entering \"waiting\".\n const transitioned = recordStateHistory(s.sessionId, {\n state: s.state,\n reason: s.stateReason,\n lastMtime: s.lastMtime,\n });\n if (transitioned) maybeNotifyWaiting(prevState, s);\n }\n\n function pruneClaudeSessions() {\n const cutoff = Date.now() - CLAUDE_ACTIVE_WINDOW_MS;\n state.claudeSessions = state.claudeSessions.filter(\n (s) => new Date(s.lastMtime).getTime() >= cutoff\n );\n }\n\n async function fetchClaudeSessions() {\n try {\n const r = await fetch('/api/claude/sessions', { cache: 'no-store' });\n if (!r.ok) return;\n const j = await r.json();\n if (Array.isArray(j.sessions)) {\n state.claudeSessions = j.sessions.map((s) => ({\n project: s.project,\n projectEncoded: s.projectEncoded,\n sessionId: s.sessionId,\n lastMtime: s.lastMtime,\n state: s.state || 'unknown',\n stateReason: s.stateReason || '',\n cwd: s.cwd || '',\n }));\n // Phase 3 \u2014 seed each session's history with its current state\n // so the trail isn't empty on first open. Doesn't notify (no\n // transition implied by initial load).\n for (const s of state.claudeSessions) {\n recordStateHistory(s.sessionId, {\n state: s.state,\n reason: s.stateReason,\n lastMtime: s.lastMtime,\n });\n }\n }\n } catch (_) {\n // server might not yet have the endpoint (older LISA) \u2014 silent\n }\n }\n\n // Phase 3 \u2014 notification permission opt-in. The CTA is only visible\n // when permission is in the default (un-asked) state.\n notifyCta.addEventListener('click', (e) => {\n e.stopPropagation();\n requestNotificationPermission();\n });\n notifyCta.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') requestNotificationPermission();\n });\n\n // \u2500\u2500 Bootstrap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n setAvatar('neutral');\n pollPing();\n fetchClaudeSessions().then(() => { refreshDot(); refreshPanel(); });\n subscribe();\n refreshNotifyCta();\n // Lightweight resync \u2014 covers cases where SSE silently disconnected\n // (laptop sleep, network blip) and we need to refresh state.\n setInterval(pollPing, 30_000);\n setInterval(fetchClaudeSessions, 60_000);\n // Re-render every 15s so \"Xs ago\" / \"Xm ago\" labels stay fresh and\n // stale sessions fall off the list without a fresh update event.\n setInterval(() => {\n pruneClaudeSessions();\n refreshDot();\n refreshPanel();\n }, 15_000);\n})();\n</script>\n</body>\n</html>\n";
11
+ export declare const ISLAND_HTML = "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>Lisa \u00B7 island</title>\n<link rel=\"manifest\" href=\"/manifest.webmanifest\">\n<style>\n :root {\n color-scheme: dark;\n --bg: rgba(8, 12, 24, 0.92);\n --bg-strong: rgba(8, 12, 24, 0.96);\n --fg: #e4e4e6;\n --fg-dim: #9ba3b8;\n --fg-faint: #6b7280;\n --accent: #6ad4ff;\n --accent-warm: #ffd066;\n --accent-dream: #b487ff;\n --accent-claude: #ff8c42;\n --border: rgba(255, 255, 255, 0.06);\n }\n html, body {\n margin: 0;\n padding: 0;\n background: transparent;\n overflow: hidden;\n height: 100vh;\n font-family: -apple-system, BlinkMacSystemFont, \"SF Pro Text\", system-ui, sans-serif;\n -webkit-font-smoothing: antialiased;\n color: var(--fg);\n user-select: none;\n cursor: default;\n }\n body {\n display: flex;\n flex-direction: column;\n align-items: center;\n padding: 4px 8px;\n }\n\n #pill {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n background: var(--bg);\n border: 1px solid var(--border);\n border-radius: 22px;\n padding: 5px 14px 5px 5px;\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n box-shadow: 0 6px 20px rgba(0, 0, 0, 0.4);\n cursor: pointer;\n transition: transform 200ms ease, opacity 200ms ease;\n max-width: 280px;\n }\n #pill:hover { transform: translateY(1px); }\n\n /* Avatar is an <img> not a background-image \u2014 more reliable in\n WKWebView and lets us crop into the face via object-position.\n The 512\u00D7512 source has ~15% transparent padding around the\n character; we scale up via object-fit + anchor toward the top\n so the face dominates the small circle.\n pointer-events: none + draggable=false so the img never steals\n or hijacks mouse events from the pill (HTML <img> default is\n draggable, which interferes with our Swift-side click/drag\n resolution). */\n #avatar {\n width: 36px;\n height: 36px;\n border-radius: 50%;\n object-fit: cover;\n object-position: 50% 22%;\n background: #15192a;\n flex-shrink: 0;\n image-rendering: pixelated;\n border: 1px solid rgba(255, 255, 255, 0.10);\n box-shadow: 0 0 0 2px rgba(106, 212, 255, 0.10);\n pointer-events: none;\n -webkit-user-drag: none;\n user-select: none;\n }\n\n #label {\n font-size: 13px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--fg);\n }\n\n #dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: transparent;\n flex-shrink: 0;\n }\n #dot.thinking { background: var(--accent); animation: pulse 1.2s ease-in-out infinite; }\n #dot.dreaming { background: var(--accent-dream); animation: pulse 2.4s ease-in-out infinite; }\n #dot.unread { background: var(--accent-warm); }\n /* Phase 2: pill dot reflects the strongest signal across all\n Claude sessions. */\n #dot.claude-working { background: var(--accent-claude); animation: pulse 1.8s ease-in-out infinite; }\n #dot.claude-waiting { background: var(--accent-claude); } /* solid \u2014 \"needs you\" */\n #dot.claude-error { background: #ff5577; animation: pulse 0.8s ease-in-out infinite; }\n #dot.offline { background: var(--fg-faint); }\n\n @keyframes pulse {\n 0%, 100% { opacity: 0.35; }\n 50% { opacity: 1; }\n }\n\n /* Expanded panel \u2014 appears below the pill on hover/click.\n The native LisaIsland.app window is a fixed 360\u00D7440 pt; the pill\n takes the top ~58pt (height + 8pt margin around). The expand\n panel fills the rest. When content (long \u2605 reflection + many\n active Claude sessions + their state trails when row-open)\n exceeds that, the panel scrolls internally rather than letting\n anything clip out of the window. */\n #expand {\n margin-top: 10px;\n width: 336px;\n max-height: calc(100vh - 70px);\n overflow-y: auto;\n overscroll-behavior: contain;\n background: var(--bg-strong);\n border: 1px solid var(--border);\n border-radius: 18px;\n padding: 16px 18px;\n backdrop-filter: blur(20px);\n -webkit-backdrop-filter: blur(20px);\n box-shadow: 0 12px 36px rgba(0, 0, 0, 0.5);\n font-size: 12.5px;\n line-height: 1.55;\n box-sizing: border-box;\n opacity: 0;\n transform: translateY(-4px);\n pointer-events: none;\n transition: opacity 200ms ease, transform 200ms ease;\n }\n body.expanded #expand {\n opacity: 1;\n transform: none;\n pointer-events: auto;\n }\n /* Subtle scrollbar \u2014 visible only while scrolling/hovering. The\n default WKWebView scrollbar is too chunky for a 336px panel. */\n #expand::-webkit-scrollbar { width: 6px; }\n #expand::-webkit-scrollbar-track { background: transparent; }\n #expand::-webkit-scrollbar-thumb {\n background: rgba(255, 255, 255, 0.10);\n border-radius: 3px;\n }\n #expand::-webkit-scrollbar-thumb:hover {\n background: rgba(255, 255, 255, 0.20);\n }\n\n /* Stack section blocks with consistent vertical rhythm. */\n #expand > div + div { margin-top: 14px; }\n\n .section-label {\n color: var(--accent);\n font-weight: 600;\n text-transform: uppercase;\n letter-spacing: 0.10em;\n font-size: 10.5px;\n margin-bottom: 8px;\n }\n .section-body {\n color: var(--fg-dim);\n word-wrap: break-word;\n }\n\n #idle-section { display: none; }\n body.has-unread #idle-section { display: block; }\n #idle-body {\n background: rgba(255, 208, 102, 0.07);\n border-left: 2px solid var(--accent-warm);\n padding: 8px 12px;\n border-radius: 6px;\n color: var(--fg);\n /* No inner max-height \u2014 the outer #expand panel scrolls if total\n content overflows the window. One scrollbar, not nested. */\n white-space: pre-wrap;\n }\n\n /* Claude Code section \u2014 appears when there's active Claude Code activity */\n #claude-section { display: none; }\n body.has-claude #claude-section { display: block; }\n #claude-section .section-label { color: var(--accent-claude); }\n #claude-list {\n list-style: none;\n padding: 4px 0;\n margin: 0;\n border-left: 2px solid var(--accent-claude);\n background: rgba(255, 140, 66, 0.06);\n border-radius: 6px;\n /* No inner overflow either \u2014 outer #expand scrolls. Avoids the\n nested-scrollbar UX where the user scrolls inside this card by\n accident and can't reach the action buttons below. */\n }\n #claude-list li {\n padding: 8px 12px;\n color: var(--fg);\n font-size: 11.5px;\n display: flex;\n flex-direction: column;\n gap: 4px;\n cursor: pointer;\n transition: background 120ms ease;\n }\n #claude-list li:hover { background: rgba(255, 140, 66, 0.10); }\n #claude-list li + li { border-top: 1px solid rgba(255, 140, 66, 0.10); }\n /* Row \"head\" \u2014 the pip + project + relative-time line. Always rendered.\n Stays as a horizontal flex strip even when the row is expanded; the\n trail + actions render BELOW it because the parent <li> is flex-column. */\n #claude-list .head {\n display: flex;\n align-items: center;\n gap: 10px;\n }\n #claude-list .proj { font-weight: 600; flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }\n #claude-list .when { color: var(--fg-dim); flex-shrink: 0; font-variant-numeric: tabular-nums; font-size: 11px; }\n #claude-list .empty { padding: 8px 12px; color: var(--fg-faint); font-style: italic; }\n\n /* Phase 2 \u2014 per-session state pip prefix */\n #claude-list .pip {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n flex-shrink: 0;\n background: var(--fg-faint);\n margin-right: 4px;\n }\n #claude-list .pip.working { background: var(--accent-claude); animation: pulse 1.8s ease-in-out infinite; }\n #claude-list .pip.waiting { background: var(--accent-claude); }\n #claude-list .pip.error { background: #ff5577; }\n #claude-list .pip.unknown { background: var(--fg-faint); }\n\n /* Phase 3 \u2014 state transition trail, shown below the row head when\n the parent <li> has the .row-open class. */\n #claude-list .trail {\n display: none;\n margin: 4px 0 0 14px;\n padding: 6px 0 2px;\n border-top: 1px dashed rgba(255, 140, 66, 0.18);\n font-size: 10px;\n color: var(--fg-faint);\n line-height: 1.7;\n word-spacing: 0.05em;\n }\n #claude-list li.row-open .trail { display: block; }\n\n /* Phase 3.5 \u2014 inline action row when the session is expanded */\n #claude-list .actions {\n display: none;\n margin: 6px 0 0 14px;\n gap: 6px;\n flex-wrap: wrap;\n }\n #claude-list li.row-open .actions { display: flex; }\n #claude-list .actions button {\n flex: 0 0 auto;\n background: rgba(255, 140, 66, 0.10);\n border: 1px solid rgba(255, 140, 66, 0.22);\n color: var(--fg);\n padding: 4px 8px;\n border-radius: 6px;\n font-size: 10.5px;\n cursor: pointer;\n font-family: inherit;\n }\n #claude-list .actions button:hover { background: rgba(255, 140, 66, 0.16); }\n #claude-list .actions button:disabled { opacity: 0.35; cursor: not-allowed; }\n #claude-list .trail .tdot {\n display: inline-block;\n width: 5px;\n height: 5px;\n border-radius: 50%;\n margin-right: 4px;\n vertical-align: 0;\n background: var(--fg-faint);\n }\n #claude-list .trail .tdot.working { background: var(--accent-claude); }\n #claude-list .trail .tdot.waiting { background: var(--accent-claude); opacity: 0.7; }\n #claude-list .trail .tdot.error { background: #ff5577; }\n /* (older stub removed \u2014 .head is a real flex strip now, see above) */\n\n /* Phase 3 \u2014 notification opt-in chip */\n #notify-cta {\n display: none;\n margin-top: 10px;\n padding: 8px 12px;\n border-radius: 10px;\n background: rgba(255, 140, 66, 0.12);\n border: 1px solid rgba(255, 140, 66, 0.30);\n font-size: 11px;\n color: var(--fg);\n text-align: center;\n cursor: pointer;\n transition: background 120ms ease;\n }\n #notify-cta:hover { background: rgba(255, 140, 66, 0.18); }\n body.notify-default #notify-cta { display: block; }\n\n #actions {\n display: flex;\n gap: 8px;\n }\n button {\n flex: 1;\n background: rgba(255, 255, 255, 0.05);\n border: 1px solid var(--border);\n color: var(--fg);\n padding: 8px 12px;\n border-radius: 10px;\n font-size: 11.5px;\n cursor: pointer;\n font-family: inherit;\n transition: background 150ms ease;\n }\n button:hover { background: rgba(255, 255, 255, 0.10); }\n button:active { background: rgba(255, 255, 255, 0.15); }\n button.muted { opacity: 0.5; }\n\n /* Offline state \u2014 desaturate + dim */\n body.offline #avatar { filter: grayscale(1); opacity: 0.5; }\n body.offline #label { color: var(--fg-faint); }\n</style>\n</head>\n<body>\n <div id=\"pill\" role=\"button\" tabindex=\"0\" aria-label=\"Lisa island\">\n <img id=\"avatar\" alt=\"\" draggable=\"false\" src=\"/assets/lisa/neutral.png\" />\n <div id=\"label\">Lisa</div>\n <div id=\"dot\" aria-hidden=\"true\"></div>\n </div>\n <div id=\"expand\" role=\"region\" aria-label=\"Lisa status detail\">\n <div id=\"desire-section\">\n <div class=\"section-label\">currently wanting</div>\n <div class=\"section-body\" id=\"desire-body\">\u2014</div>\n </div>\n <div id=\"idle-section\">\n <div class=\"section-label\">\u2605 while you were away</div>\n <div id=\"idle-body\"></div>\n </div>\n <div id=\"claude-section\">\n <div class=\"section-label\">claude code \u00B7 <span id=\"claude-count\">0</span> active</div>\n <ul id=\"claude-list\"></ul>\n <div id=\"notify-cta\" role=\"button\" tabindex=\"0\">\uD83D\uDD14 Notify me when Claude is waiting</div>\n </div>\n <div id=\"actions\">\n <button id=\"btn-open\">Open chat</button>\n <button id=\"btn-dismiss\" class=\"muted\">Dismiss \u2605</button>\n </div>\n </div>\n\n<script>\n(() => {\n const pill = document.getElementById('pill');\n const avatar = document.getElementById('avatar');\n const dot = document.getElementById('dot');\n const expand = document.getElementById('expand');\n const desireBody = document.getElementById('desire-body');\n const idleBody = document.getElementById('idle-body');\n const claudeList = document.getElementById('claude-list');\n const claudeCount = document.getElementById('claude-count');\n const notifyCta = document.getElementById('notify-cta');\n const btnOpen = document.getElementById('btn-open');\n const btnDismiss = document.getElementById('btn-dismiss');\n const body = document.body;\n\n const state = {\n mood: 'neutral',\n online: false,\n unread: false,\n idleText: '',\n desire: null,\n thinking: false,\n dreaming: false,\n claudeSessions: [], // [{project, sessionId, lastMtime}, \u2026]\n };\n\n // 30-min activity window matches the watcher's ACTIVE_WINDOW_MS.\n const CLAUDE_ACTIVE_WINDOW_MS = 30 * 60 * 1000;\n\n // Phase 3 \u2014 in-memory state transition history per session.\n // Map<sessionId, [{state, reason, ts}, \u2026]> capped at MAX_HISTORY.\n // Pure UI memory; not persisted, not transmitted.\n const stateHistory = new Map();\n const MAX_HISTORY = 8;\n // Per-session opened-state for the inline trail in the expand list.\n const rowOpen = new Set();\n\n function recordStateHistory(sessionId, info) {\n let h = stateHistory.get(sessionId);\n if (!h) { h = []; stateHistory.set(sessionId, h); }\n const last = h[h.length - 1];\n if (last && last.state === info.state && last.reason === info.reason) {\n // Same state, just refresh timestamp \u2014 collapse repeats.\n last.ts = info.lastMtime;\n return false;\n }\n h.push({ state: info.state, reason: info.reason, ts: info.lastMtime });\n while (h.length > MAX_HISTORY) h.shift();\n return true; // transition happened\n }\n\n // \u2500\u2500 Phase 3 \u2014 notifications (native via LisaIsland.app, or web fallback)\n // Fires \"Claude is waiting in <project>\" alerts when a session\n // transitions INTO waiting. Throttled per session so a flaky tool\n // that bounces between waiting/working doesn't spam.\n //\n // Phase 3.5: when running inside LisaIsland.app, we delegate to the\n // native UNUserNotificationCenter via postMessage \u2014 better permission\n // flow, integrates with macOS Focus / DnD. In a plain browser tab we\n // fall back to the Notification API.\n const NOTIFY_THROTTLE_MS = 60_000;\n const lastNotifyAt = new Map();\n const hasBridge = !!(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island);\n\n function notifPermission() {\n if (hasBridge) return 'granted'; // native side asks the user; we just request and trust\n return ('Notification' in window) ? Notification.permission : 'unsupported';\n }\n\n function refreshNotifyCta() {\n // The \uD83D\uDD14 chip is only needed when running in a browser AND\n // permission hasn't been answered yet. Native bridge handles its\n // own permission prompt.\n if (hasBridge) {\n body.classList.remove('notify-default');\n return;\n }\n body.classList.toggle('notify-default', notifPermission() === 'default');\n }\n\n function requestNotificationPermission() {\n if (hasBridge) {\n window.webkit.messageHandlers.island.postMessage({ type: 'ensure_notify_permission' });\n refreshNotifyCta();\n return;\n }\n if (!('Notification' in window) || Notification.permission !== 'default') {\n refreshNotifyCta();\n return;\n }\n Notification.requestPermission().then(() => refreshNotifyCta()).catch(() => {});\n }\n\n function maybeNotifyWaiting(prevState, info) {\n if (info.state !== 'waiting') return;\n if (prevState === 'waiting') return; // already in this state\n const last = lastNotifyAt.get(info.sessionId) || 0;\n if (Date.now() - last < NOTIFY_THROTTLE_MS) return;\n lastNotifyAt.set(info.sessionId, Date.now());\n const reasonLabel = info.stateReason === 'permission' ? 'needs permission' : 'is waiting';\n const title = 'Claude ' + reasonLabel + ' in ' + info.project;\n const bodyText = info.sessionId.slice(0, 8) + ' \u00B7 click to open Lisa';\n if (hasBridge) {\n window.webkit.messageHandlers.island.postMessage({\n type: 'notify',\n title: title,\n body: bodyText,\n sessionId: info.sessionId,\n });\n return;\n }\n if (notifPermission() !== 'granted') return;\n try {\n const n = new Notification(title, {\n body: bodyText,\n tag: 'lisa-claude-' + info.sessionId,\n icon: '/assets/lisa-mascot.png',\n silent: false,\n });\n n.onclick = () => { window.focus(); window.open('/', '_blank'); n.close(); };\n } catch (_) { /* unsupported, ignore */ }\n }\n function recentSessions() {\n const cutoff = Date.now() - CLAUDE_ACTIVE_WINDOW_MS;\n return state.claudeSessions.filter(\n (s) => new Date(s.lastMtime).getTime() >= cutoff\n );\n }\n /**\n * Phase 2: aggregate Claude state for the pill dot. Priority is\n * \"loudest signal wins\": an error anywhere dominates everything;\n * otherwise a \"waiting\" session beats a \"working\" session (because\n * \"waiting\" means Claude needs the user \u2014 more attention-worthy\n * than \"working\" which is passive observing).\n */\n function aggregateClaudeState() {\n const recent = recentSessions();\n if (recent.length === 0) return null;\n if (recent.some((s) => s.state === 'error')) return 'error';\n if (recent.some((s) => s.state === 'waiting')) return 'waiting';\n if (recent.some((s) => s.state === 'working')) return 'working';\n return null;\n }\n\n function setAvatar(slug) {\n if (!slug) return;\n state.mood = slug;\n // Use the <img> src attribute \u2014 far more reliable than CSS\n // background-image in WKWebView, and lets the browser's standard\n // image cache + retry logic do its thing.\n avatar.src = '/assets/lisa/' + encodeURIComponent(slug) + '.png';\n }\n\n function refreshDot() {\n dot.className = '';\n // Priority: LISA's own state (offline / thinking / dreaming / unread)\n // always wins over the Claude-Code-monitor indicator \u2014 the pill is\n // primarily about her, the Claude dot is a quieter \"by the way\".\n if (!state.online) { dot.classList.add('offline'); return; }\n if (state.thinking) { dot.classList.add('thinking'); return; }\n if (state.dreaming) { dot.classList.add('dreaming'); return; }\n if (state.unread) { dot.classList.add('unread'); return; }\n const claude = aggregateClaudeState();\n if (claude === 'error') { dot.classList.add('claude-error'); return; }\n if (claude === 'waiting') { dot.classList.add('claude-waiting'); return; }\n if (claude === 'working') { dot.classList.add('claude-working'); return; }\n }\n\n function refreshPanel() {\n body.classList.toggle('offline', !state.online);\n body.classList.toggle('has-unread', state.unread);\n body.classList.toggle('has-claude', state.claudeSessions.length > 0);\n desireBody.textContent = state.desire || '(nothing actively pursued)';\n idleBody.textContent = state.idleText || '';\n btnDismiss.classList.toggle('muted', !state.unread);\n btnDismiss.disabled = !state.unread;\n renderClaudeList();\n }\n\n function relativeTime(iso) {\n const ms = Date.now() - new Date(iso).getTime();\n if (ms < 30_000) return 'just now';\n if (ms < 60_000) return Math.round(ms / 1000) + 's ago';\n if (ms < 3600_000) return Math.round(ms / 60_000) + 'm ago';\n return Math.round(ms / 3600_000) + 'h ago';\n }\n\n function renderClaudeList() {\n const recent = recentSessions();\n claudeCount.textContent = String(recent.length);\n while (claudeList.firstChild) claudeList.removeChild(claudeList.firstChild);\n if (recent.length === 0) {\n const li = document.createElement('li');\n li.className = 'empty';\n li.textContent = '(idle)';\n claudeList.appendChild(li);\n return;\n }\n // Sort: errors first, then waiting, then working, then by mtime.\n const stateRank = { error: 0, waiting: 1, working: 2, unknown: 3 };\n const rows = recent.slice().sort((a, b) => {\n const ra = stateRank[a.state] ?? 9;\n const rb = stateRank[b.state] ?? 9;\n if (ra !== rb) return ra - rb;\n return new Date(b.lastMtime).getTime() - new Date(a.lastMtime).getTime();\n }).slice(0, 5);\n for (const s of rows) {\n const li = document.createElement('li');\n if (rowOpen.has(s.sessionId)) li.classList.add('row-open');\n // pip + project + relative-time render as a single horizontal\n // .head strip. The trail + actions render BELOW the head when\n // the row is open (li is flex-column).\n const head = document.createElement('div');\n head.className = 'head';\n const pip = document.createElement('span');\n pip.className = 'pip ' + (s.state || 'unknown');\n const proj = document.createElement('span');\n proj.className = 'proj';\n proj.textContent = s.project;\n const when = document.createElement('span');\n when.className = 'when';\n when.textContent = relativeTime(s.lastMtime);\n head.appendChild(pip);\n head.appendChild(proj);\n head.appendChild(when);\n li.appendChild(head);\n\n // Phase 3 \u2014 collapsible state-transition trail\n const trail = document.createElement('div');\n trail.className = 'trail';\n renderTrail(trail, s);\n li.appendChild(trail);\n\n // Phase 3.5 \u2014 action buttons (Open in Finder / Copy resume)\n const actions = document.createElement('div');\n actions.className = 'actions';\n renderActions(actions, s);\n li.appendChild(actions);\n\n li.title = s.state + (s.stateReason ? ' (' + s.stateReason + ')' : '')\n + ' \u00B7 ' + s.sessionId\n + '\\nclick: expand timeline \u00B7 double-click: copy sessionId';\n li.addEventListener('click', () => {\n if (rowOpen.has(s.sessionId)) rowOpen.delete(s.sessionId);\n else rowOpen.add(s.sessionId);\n renderClaudeList();\n });\n li.addEventListener('dblclick', async (ev) => {\n ev.stopPropagation();\n try { await navigator.clipboard.writeText(s.sessionId); } catch (_) {}\n });\n claudeList.appendChild(li);\n }\n }\n\n /**\n * Phase 3.5 \u2014 render the inline action buttons for one Claude session.\n * Each session has a cwd (from .cwd top-level field in the jsonl)\n * and a sessionId. We expose two actions:\n * - Open in Finder \u2014 opens the cwd folder\n * - Copy resume cmd \u2014 clipboard: cd \"<cwd>\" && claude --resume <sid>\n */\n function renderActions(container, s) {\n const cwd = s.cwd || '';\n const hasCwd = cwd.startsWith('/');\n\n const openBtn = document.createElement('button');\n openBtn.type = 'button';\n openBtn.textContent = '\uD83D\uDCC1 Open folder';\n openBtn.disabled = !hasCwd;\n openBtn.title = hasCwd ? cwd : 'No cwd recorded in this session';\n openBtn.addEventListener('click', (e) => {\n e.stopPropagation();\n if (!hasCwd) return;\n if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island) {\n window.webkit.messageHandlers.island.postMessage({\n type: 'open_path',\n path: cwd,\n });\n } else {\n // Browser fallback: copy the path so the user can paste in Finder.\n navigator.clipboard.writeText(cwd).catch(() => {});\n }\n });\n container.appendChild(openBtn);\n\n const copyBtn = document.createElement('button');\n copyBtn.type = 'button';\n copyBtn.textContent = '\uD83D\uDCCB Resume cmd';\n copyBtn.disabled = !hasCwd;\n const cmd = hasCwd\n ? 'cd ' + JSON.stringify(cwd) + ' && claude --resume ' + s.sessionId\n : 'claude --resume ' + s.sessionId;\n copyBtn.title = cmd;\n copyBtn.addEventListener('click', async (e) => {\n e.stopPropagation();\n try {\n await navigator.clipboard.writeText(cmd);\n const orig = copyBtn.textContent;\n copyBtn.textContent = '\u2713 copied';\n setTimeout(() => { copyBtn.textContent = orig; }, 1200);\n } catch (_) {}\n });\n container.appendChild(copyBtn);\n }\n\n function renderTrail(container, s) {\n const h = stateHistory.get(s.sessionId) || [];\n if (h.length === 0) {\n container.textContent = '(no transitions recorded yet)';\n return;\n }\n // Render newest first so the right-most reads as \"right now\".\n const ordered = h.slice();\n for (let i = 0; i < ordered.length; i++) {\n const entry = ordered[i];\n const tdot = document.createElement('span');\n tdot.className = 'tdot ' + (entry.state || 'unknown');\n container.appendChild(tdot);\n const span = document.createElement('span');\n span.textContent = entry.state + ' \u00B7 ' + relativeTime(entry.ts);\n container.appendChild(span);\n if (i < ordered.length - 1) {\n const sep = document.createElement('span');\n sep.textContent = ' \u2192 ';\n sep.style.color = 'rgba(255,255,255,0.15)';\n container.appendChild(sep);\n }\n }\n }\n\n function expandPanel(open) {\n if (body.classList.contains('expanded') === open) return;\n body.classList.toggle('expanded', open);\n // Tell the native container (LisaIsland.app, Phase 2.2+) so it can\n // resize its NSWindow \u2014 the pill window is sized just for the pill\n // when collapsed, and grows to host the expand panel on open.\n // Falls back gracefully when running in a plain browser tab.\n if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island) {\n window.webkit.messageHandlers.island.postMessage({\n type: open ? 'expand' : 'collapse'\n });\n }\n }\n\n // \u2500\u2500 Interaction \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n //\n // When running inside LisaIsland.app (Phase 2.1+), the native window\n // owns drag and click-vs-drag resolution: Swift's IslandWindow\n // intercepts mouseDown in sendEvent and runs a synchronous\n // mouseDragged loop. If movement > 4px \u2192 drag (setFrameOrigin each\n // tick, no IPC roundtrip). If no movement \u2192 Swift synthesizes\n // pill.click() here so this click handler still fires.\n //\n // In a plain browser tab there's no native container \u2014 the click\n // handler runs normally. Hover-to-expand also works in both modes:\n // when the native window is in \"passthrough\" state for the\n // non-pill area, mouseenter still fires on the WKWebView once the\n // cursor crosses INTO the pill region.\n\n let hoverTimer = null;\n pill.addEventListener('mouseenter', () => {\n clearTimeout(hoverTimer);\n hoverTimer = setTimeout(() => expandPanel(true), 250);\n });\n pill.addEventListener('mouseleave', () => {\n clearTimeout(hoverTimer);\n // Don't collapse if the mouse just moved into the expand panel.\n setTimeout(() => {\n if (!expand.matches(':hover')) expandPanel(false);\n }, 200);\n });\n expand.addEventListener('mouseleave', () => expandPanel(false));\n\n pill.addEventListener('click', (e) => {\n e.preventDefault();\n expandPanel(!body.classList.contains('expanded'));\n });\n pill.addEventListener('dblclick', (e) => {\n e.preventDefault();\n openFull();\n });\n\n function openFull() {\n // If a Swift container is present (Phase 2), prefer to delegate.\n if (window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.island) {\n window.webkit.messageHandlers.island.postMessage({ type: 'open_full_gui' });\n } else {\n window.open('/', '_blank');\n }\n }\n btnOpen.addEventListener('click', (e) => { e.stopPropagation(); openFull(); });\n btnDismiss.addEventListener('click', async (e) => {\n e.stopPropagation();\n if (!state.unread) return;\n try { await fetch('/api/island/dismiss-unread', { method: 'POST' }); } catch (_) {}\n state.unread = false;\n state.idleText = '';\n refreshDot();\n refreshPanel();\n });\n\n // \u2500\u2500 Server polling for richer state \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let backoff = 5_000;\n async function pollPing() {\n try {\n const r = await fetch('/api/island/ping', { cache: 'no-store' });\n if (!r.ok) throw new Error('not ok');\n const j = await r.json();\n state.online = !!j.online;\n state.unread = !!j.has_unread_idle_message;\n state.idleText = j.last_idle_message_text || '';\n state.desire = j.current_desire || null;\n if (j.mood) setAvatar(j.mood);\n backoff = 5_000;\n } catch (_) {\n state.online = false;\n }\n refreshDot();\n refreshPanel();\n }\n\n // \u2500\u2500 SSE subscription for instant pulses \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n let es = null;\n function subscribe() {\n try { if (es) es.close(); } catch (_) {}\n es = new EventSource('/events');\n es.addEventListener('open', () => {\n state.online = true;\n refreshDot();\n refreshPanel();\n });\n es.addEventListener('message', (ev) => {\n let m;\n try { m = JSON.parse(ev.data); } catch (_) { return; }\n switch (m.type) {\n case 'mood':\n setAvatar(m.slug);\n break;\n case 'chat_start':\n state.thinking = true;\n refreshDot();\n break;\n case 'chat_end':\n state.thinking = false;\n refreshDot();\n break;\n case 'idle_start':\n state.dreaming = true;\n refreshDot();\n break;\n case 'idle_done':\n case 'idle_error':\n state.dreaming = false;\n refreshDot();\n break;\n case 'idle_message':\n state.dreaming = false;\n state.unread = true;\n state.idleText = (m.text || '').slice(0, 1000);\n refreshDot();\n refreshPanel();\n // One subtle pulse so a watching user notices.\n document.body.animate(\n [{ opacity: 0.7 }, { opacity: 1 }, { opacity: 0.85 }],\n { duration: 600, iterations: 2 },\n );\n break;\n case 'claude_session_update':\n upsertClaudeSession({\n project: m.projectLabel,\n projectEncoded: m.projectEncoded,\n sessionId: m.sessionId,\n lastMtime: m.ts,\n state: m.state || 'unknown',\n stateReason: m.stateReason || '',\n cwd: m.cwd || '',\n });\n refreshDot();\n refreshPanel();\n break;\n }\n });\n es.addEventListener('error', () => {\n state.online = false;\n refreshDot();\n refreshPanel();\n // Auto-retry: SSE EventSource reconnects on its own, but if the page\n // server is down entirely we'll keep firing 'error' until it's back.\n });\n }\n\n // \u2500\u2500 Claude Code session helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n function upsertClaudeSession(s) {\n const idx = state.claudeSessions.findIndex(\n (x) => x.sessionId === s.sessionId\n );\n const prevState = idx >= 0 ? state.claudeSessions[idx].state : null;\n if (idx >= 0) state.claudeSessions[idx] = s;\n else state.claudeSessions.push(s);\n pruneClaudeSessions();\n\n // Phase 3 \u2014 record transition + maybe notify on entering \"waiting\".\n const transitioned = recordStateHistory(s.sessionId, {\n state: s.state,\n reason: s.stateReason,\n lastMtime: s.lastMtime,\n });\n if (transitioned) maybeNotifyWaiting(prevState, s);\n }\n\n function pruneClaudeSessions() {\n const cutoff = Date.now() - CLAUDE_ACTIVE_WINDOW_MS;\n state.claudeSessions = state.claudeSessions.filter(\n (s) => new Date(s.lastMtime).getTime() >= cutoff\n );\n }\n\n async function fetchClaudeSessions() {\n try {\n const r = await fetch('/api/claude/sessions', { cache: 'no-store' });\n if (!r.ok) return;\n const j = await r.json();\n if (Array.isArray(j.sessions)) {\n state.claudeSessions = j.sessions.map((s) => ({\n project: s.project,\n projectEncoded: s.projectEncoded,\n sessionId: s.sessionId,\n lastMtime: s.lastMtime,\n state: s.state || 'unknown',\n stateReason: s.stateReason || '',\n cwd: s.cwd || '',\n }));\n // Phase 3 \u2014 seed each session's history with its current state\n // so the trail isn't empty on first open. Doesn't notify (no\n // transition implied by initial load).\n for (const s of state.claudeSessions) {\n recordStateHistory(s.sessionId, {\n state: s.state,\n reason: s.stateReason,\n lastMtime: s.lastMtime,\n });\n }\n }\n } catch (_) {\n // server might not yet have the endpoint (older LISA) \u2014 silent\n }\n }\n\n // Phase 3 \u2014 notification permission opt-in. The CTA is only visible\n // when permission is in the default (un-asked) state.\n notifyCta.addEventListener('click', (e) => {\n e.stopPropagation();\n requestNotificationPermission();\n });\n notifyCta.addEventListener('keydown', (e) => {\n if (e.key === 'Enter' || e.key === ' ') requestNotificationPermission();\n });\n\n // \u2500\u2500 Bootstrap \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n setAvatar('neutral');\n pollPing();\n fetchClaudeSessions().then(() => { refreshDot(); refreshPanel(); });\n subscribe();\n refreshNotifyCta();\n // Lightweight resync \u2014 covers cases where SSE silently disconnected\n // (laptop sleep, network blip) and we need to refresh state.\n setInterval(pollPing, 30_000);\n setInterval(fetchClaudeSessions, 60_000);\n // Re-render every 15s so \"Xs ago\" / \"Xm ago\" labels stay fresh and\n // stale sessions fall off the list without a fresh update event.\n setInterval(() => {\n pruneClaudeSessions();\n refreshDot();\n refreshPanel();\n }, 15_000);\n})();\n</script>\n</body>\n</html>\n";
12
12
  //# sourceMappingURL=island.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"island.d.ts","sourceRoot":"","sources":["../../src/web/island.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,gvjCAm5BvB,CAAC"}
1
+ {"version":3,"file":"island.d.ts","sourceRoot":"","sources":["../../src/web/island.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,eAAO,MAAM,WAAW,g6lCAw6BvB,CAAC"}
@@ -119,10 +119,19 @@ export const ISLAND_HTML = `<!doctype html>
119
119
  50% { opacity: 1; }
120
120
  }
121
121
 
122
- /* Expanded panel — appears below the pill on hover/click */
122
+ /* Expanded panel — appears below the pill on hover/click.
123
+ The native LisaIsland.app window is a fixed 360×440 pt; the pill
124
+ takes the top ~58pt (height + 8pt margin around). The expand
125
+ panel fills the rest. When content (long ★ reflection + many
126
+ active Claude sessions + their state trails when row-open)
127
+ exceeds that, the panel scrolls internally rather than letting
128
+ anything clip out of the window. */
123
129
  #expand {
124
130
  margin-top: 10px;
125
131
  width: 336px;
132
+ max-height: calc(100vh - 70px);
133
+ overflow-y: auto;
134
+ overscroll-behavior: contain;
126
135
  background: var(--bg-strong);
127
136
  border: 1px solid var(--border);
128
137
  border-radius: 18px;
@@ -143,6 +152,17 @@ export const ISLAND_HTML = `<!doctype html>
143
152
  transform: none;
144
153
  pointer-events: auto;
145
154
  }
155
+ /* Subtle scrollbar — visible only while scrolling/hovering. The
156
+ default WKWebView scrollbar is too chunky for a 336px panel. */
157
+ #expand::-webkit-scrollbar { width: 6px; }
158
+ #expand::-webkit-scrollbar-track { background: transparent; }
159
+ #expand::-webkit-scrollbar-thumb {
160
+ background: rgba(255, 255, 255, 0.10);
161
+ border-radius: 3px;
162
+ }
163
+ #expand::-webkit-scrollbar-thumb:hover {
164
+ background: rgba(255, 255, 255, 0.20);
165
+ }
146
166
 
147
167
  /* Stack section blocks with consistent vertical rhythm. */
148
168
  #expand > div + div { margin-top: 14px; }
@@ -168,8 +188,8 @@ export const ISLAND_HTML = `<!doctype html>
168
188
  padding: 8px 12px;
169
189
  border-radius: 6px;
170
190
  color: var(--fg);
171
- max-height: 110px;
172
- overflow-y: auto;
191
+ /* No inner max-height — the outer #expand panel scrolls if total
192
+ content overflows the window. One scrollbar, not nested. */
173
193
  white-space: pre-wrap;
174
194
  }
175
195
 
@@ -184,8 +204,9 @@ export const ISLAND_HTML = `<!doctype html>
184
204
  border-left: 2px solid var(--accent-claude);
185
205
  background: rgba(255, 140, 66, 0.06);
186
206
  border-radius: 6px;
187
- overflow-y: auto;
188
- max-height: 200px;
207
+ /* No inner overflow either — outer #expand scrolls. Avoids the
208
+ nested-scrollbar UX where the user scrolls inside this card by
209
+ accident and can't reach the action buttons below. */
189
210
  }
190
211
  #claude-list li {
191
212
  padding: 8px 12px;
@@ -1 +1 @@
1
- {"version":3,"file":"island.js","sourceRoot":"","sources":["../../src/web/island.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAm5B1B,CAAC"}
1
+ {"version":3,"file":"island.js","sourceRoot":"","sources":["../../src/web/island.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,MAAM,CAAC,MAAM,WAAW,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAw6B1B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oratis/lisa",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "An AI agent with a real self — runs locally, has desires, keeps a journal, evolves over time.",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,6 +55,8 @@
55
55
  "start": "node dist/cli.js",
56
56
  "lisa": "node --enable-source-maps dist/cli.js",
57
57
  "typecheck": "tsc -p tsconfig.json --noEmit",
58
+ "test": "node --import tsx --test \"src/**/*.test.ts\"",
59
+ "test:watch": "node --import tsx --test --watch \"src/**/*.test.ts\"",
58
60
  "generate-assets": "tsx scripts/generate-pixel-assets.ts",
59
61
  "prepublishOnly": "npm run build && rm -rf dist/web/assets && cp -R src/web/assets dist/web/assets",
60
62
  "postpublish": "npm run copy-assets"