heyhank 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/README.md +40 -0
  2. package/bin/cli.ts +168 -0
  3. package/bin/ctl.ts +528 -0
  4. package/bin/generate-token.ts +28 -0
  5. package/dist/apple-touch-icon.png +0 -0
  6. package/dist/assets/AgentsPage-BPhirnCe.js +7 -0
  7. package/dist/assets/AssistantPage-DJ-cMQfb.js +1 -0
  8. package/dist/assets/CronManager-DDbz-yiT.js +1 -0
  9. package/dist/assets/HelpPage-DMfkzERp.js +1 -0
  10. package/dist/assets/IntegrationsPage-CrOitCmJ.js +1 -0
  11. package/dist/assets/MediaPage-CE5rdvkC.js +1 -0
  12. package/dist/assets/PlatformDashboard-Do6F0O2p.js +1 -0
  13. package/dist/assets/Playground-Fc5cdc5p.js +109 -0
  14. package/dist/assets/ProcessPanel-CslEiZkI.js +2 -0
  15. package/dist/assets/PromptsPage-D2EhsdNO.js +4 -0
  16. package/dist/assets/RunsPage-C5BZF5Rx.js +1 -0
  17. package/dist/assets/SandboxManager-a1AVI5q2.js +8 -0
  18. package/dist/assets/SettingsPage-DirhjQrJ.js +51 -0
  19. package/dist/assets/SocialMediaPage-DBuM28vD.js +1 -0
  20. package/dist/assets/TailscalePage-CHiFhZXF.js +1 -0
  21. package/dist/assets/TelephonyPage-x0VV0fOo.js +1 -0
  22. package/dist/assets/TerminalPage-Drwyrnfd.js +1 -0
  23. package/dist/assets/gemini-audio-t-TSU-To.js +17 -0
  24. package/dist/assets/gemini-live-client-C7rqAW7G.js +166 -0
  25. package/dist/assets/index-C8M_PUmX.css +32 -0
  26. package/dist/assets/index-CEqZnThB.js +204 -0
  27. package/dist/assets/sw-register-LSSpj6RU.js +1 -0
  28. package/dist/assets/time-ago-B6r_l9u1.js +1 -0
  29. package/dist/assets/workbox-window.prod.es5-BIl4cyR9.js +2 -0
  30. package/dist/favicon-32-original.png +0 -0
  31. package/dist/favicon-32.png +0 -0
  32. package/dist/favicon.ico +0 -0
  33. package/dist/favicon.svg +8 -0
  34. package/dist/fonts/MesloLGSNerdFontMono-Bold.woff2 +0 -0
  35. package/dist/fonts/MesloLGSNerdFontMono-Regular.woff2 +0 -0
  36. package/dist/heyhank-mascot-poster.png +0 -0
  37. package/dist/heyhank-mascot.mp4 +0 -0
  38. package/dist/heyhank-mascot.webm +0 -0
  39. package/dist/icon-192-original.png +0 -0
  40. package/dist/icon-192.png +0 -0
  41. package/dist/icon-512-original.png +0 -0
  42. package/dist/icon-512.png +0 -0
  43. package/dist/index.html +21 -0
  44. package/dist/logo-192.png +0 -0
  45. package/dist/logo-512.png +0 -0
  46. package/dist/logo-codex.svg +14 -0
  47. package/dist/logo-docker.svg +4 -0
  48. package/dist/logo-original.png +0 -0
  49. package/dist/logo.png +0 -0
  50. package/dist/logo.svg +14 -0
  51. package/dist/manifest.json +24 -0
  52. package/dist/push-sw.js +34 -0
  53. package/dist/sw.js +1 -0
  54. package/dist/workbox-d2a0910a.js +1 -0
  55. package/package.json +109 -0
  56. package/server/agent-cron-migrator.ts +85 -0
  57. package/server/agent-executor.ts +357 -0
  58. package/server/agent-store.ts +185 -0
  59. package/server/agent-timeout.ts +107 -0
  60. package/server/agent-types.ts +122 -0
  61. package/server/ai-validation-settings.ts +37 -0
  62. package/server/ai-validator.ts +181 -0
  63. package/server/anthropic-provider-migration.ts +48 -0
  64. package/server/assistant-store.ts +272 -0
  65. package/server/auth-manager.ts +150 -0
  66. package/server/auto-approve.ts +153 -0
  67. package/server/auto-namer.ts +36 -0
  68. package/server/backend-adapter.ts +54 -0
  69. package/server/cache-headers.ts +61 -0
  70. package/server/calendar-service.ts +434 -0
  71. package/server/claude-adapter.ts +889 -0
  72. package/server/claude-container-auth.ts +30 -0
  73. package/server/claude-session-discovery.ts +157 -0
  74. package/server/claude-session-history.ts +410 -0
  75. package/server/cli-launcher.ts +1303 -0
  76. package/server/codex-adapter.ts +3027 -0
  77. package/server/codex-container-auth.ts +24 -0
  78. package/server/codex-home.ts +27 -0
  79. package/server/codex-ws-proxy.cjs +226 -0
  80. package/server/commands-discovery.ts +81 -0
  81. package/server/constants.ts +7 -0
  82. package/server/container-manager.ts +1053 -0
  83. package/server/cost-tracker.ts +222 -0
  84. package/server/cron-scheduler.ts +243 -0
  85. package/server/cron-store.ts +148 -0
  86. package/server/cron-types.ts +63 -0
  87. package/server/email-service.ts +354 -0
  88. package/server/env-manager.ts +161 -0
  89. package/server/event-bus-types.ts +75 -0
  90. package/server/event-bus.ts +124 -0
  91. package/server/execution-store.ts +170 -0
  92. package/server/federation/node-connection.ts +190 -0
  93. package/server/federation/node-manager.ts +366 -0
  94. package/server/federation/node-store.ts +86 -0
  95. package/server/federation/node-types.ts +121 -0
  96. package/server/fs-utils.ts +15 -0
  97. package/server/git-utils.ts +421 -0
  98. package/server/github-pr.ts +379 -0
  99. package/server/google-media.ts +342 -0
  100. package/server/image-pull-manager.ts +279 -0
  101. package/server/index.ts +491 -0
  102. package/server/internal-ai.ts +237 -0
  103. package/server/kill-switch.ts +99 -0
  104. package/server/llm-providers.ts +342 -0
  105. package/server/logger.ts +259 -0
  106. package/server/mcp-registry.ts +401 -0
  107. package/server/message-bus.ts +271 -0
  108. package/server/message-delivery.ts +128 -0
  109. package/server/metrics-collector.ts +350 -0
  110. package/server/metrics-types.ts +108 -0
  111. package/server/middleware/managed-auth.ts +195 -0
  112. package/server/novnc-proxy.ts +99 -0
  113. package/server/path-resolver.ts +186 -0
  114. package/server/paths.ts +13 -0
  115. package/server/pr-poller.ts +162 -0
  116. package/server/prompt-manager.ts +211 -0
  117. package/server/protocol/claude-upstream/README.md +19 -0
  118. package/server/protocol/claude-upstream/sdk.d.ts.txt +1943 -0
  119. package/server/protocol/codex-upstream/ClientNotification.ts.txt +5 -0
  120. package/server/protocol/codex-upstream/ClientRequest.ts.txt +60 -0
  121. package/server/protocol/codex-upstream/README.md +18 -0
  122. package/server/protocol/codex-upstream/ServerNotification.ts.txt +41 -0
  123. package/server/protocol/codex-upstream/ServerRequest.ts.txt +16 -0
  124. package/server/protocol/codex-upstream/v2/DynamicToolCallParams.ts.txt +6 -0
  125. package/server/protocol/codex-upstream/v2/DynamicToolCallResponse.ts.txt +6 -0
  126. package/server/protocol-monitor.ts +50 -0
  127. package/server/provider-manager.ts +111 -0
  128. package/server/provider-registry.ts +393 -0
  129. package/server/push-notifications.ts +221 -0
  130. package/server/recorder.ts +374 -0
  131. package/server/recording-hub/compat-validator.ts +284 -0
  132. package/server/recording-hub/diagnostics.ts +299 -0
  133. package/server/recording-hub/hub-config.ts +19 -0
  134. package/server/recording-hub/hub-routes.ts +236 -0
  135. package/server/recording-hub/hub-store.ts +265 -0
  136. package/server/recording-hub/replay-adapter.ts +207 -0
  137. package/server/relay-client.ts +320 -0
  138. package/server/reminder-scheduler.ts +38 -0
  139. package/server/replay.ts +78 -0
  140. package/server/routes/agent-routes.ts +264 -0
  141. package/server/routes/assistant-routes.ts +90 -0
  142. package/server/routes/cron-routes.ts +103 -0
  143. package/server/routes/env-routes.ts +95 -0
  144. package/server/routes/federation-routes.ts +76 -0
  145. package/server/routes/fs-routes.ts +622 -0
  146. package/server/routes/git-routes.ts +97 -0
  147. package/server/routes/llm-routes.ts +166 -0
  148. package/server/routes/media-routes.ts +135 -0
  149. package/server/routes/metrics-routes.ts +13 -0
  150. package/server/routes/platform-routes.ts +1379 -0
  151. package/server/routes/prompt-routes.ts +67 -0
  152. package/server/routes/provider-routes.ts +109 -0
  153. package/server/routes/sandbox-routes.ts +127 -0
  154. package/server/routes/settings-routes.ts +285 -0
  155. package/server/routes/skills-routes.ts +100 -0
  156. package/server/routes/socialmedia-routes.ts +208 -0
  157. package/server/routes/system-routes.ts +228 -0
  158. package/server/routes/tailscale-routes.ts +22 -0
  159. package/server/routes/telephony-routes.ts +259 -0
  160. package/server/routes.ts +1379 -0
  161. package/server/sandbox-manager.ts +168 -0
  162. package/server/service.ts +718 -0
  163. package/server/session-creation-service.ts +457 -0
  164. package/server/session-git-info.ts +104 -0
  165. package/server/session-names.ts +67 -0
  166. package/server/session-orchestrator.ts +824 -0
  167. package/server/session-state-machine.ts +207 -0
  168. package/server/session-store.ts +146 -0
  169. package/server/session-types.ts +511 -0
  170. package/server/settings-manager.ts +149 -0
  171. package/server/shared-context.ts +157 -0
  172. package/server/socialmedia/adapter.ts +15 -0
  173. package/server/socialmedia/adapters/ayrshare-adapter.ts +169 -0
  174. package/server/socialmedia/adapters/buffer-adapter.ts +299 -0
  175. package/server/socialmedia/adapters/postiz-adapter.ts +298 -0
  176. package/server/socialmedia/manager.ts +227 -0
  177. package/server/socialmedia/store.ts +98 -0
  178. package/server/socialmedia/types.ts +89 -0
  179. package/server/tailscale-manager.ts +451 -0
  180. package/server/telephony/audio-bridge.ts +331 -0
  181. package/server/telephony/call-manager.ts +457 -0
  182. package/server/telephony/call-types.ts +108 -0
  183. package/server/telephony/telephony-store.ts +119 -0
  184. package/server/terminal-manager.ts +240 -0
  185. package/server/update-checker.ts +192 -0
  186. package/server/usage-limits.ts +225 -0
  187. package/server/web-push.d.ts +51 -0
  188. package/server/worktree-tracker.ts +84 -0
  189. package/server/ws-auth.ts +41 -0
  190. package/server/ws-bridge-browser-ingest.ts +72 -0
  191. package/server/ws-bridge-browser.ts +112 -0
  192. package/server/ws-bridge-cli-ingest.ts +81 -0
  193. package/server/ws-bridge-codex.ts +266 -0
  194. package/server/ws-bridge-controls.ts +20 -0
  195. package/server/ws-bridge-persist.ts +66 -0
  196. package/server/ws-bridge-publish.ts +79 -0
  197. package/server/ws-bridge-replay.ts +61 -0
  198. package/server/ws-bridge-types.ts +121 -0
  199. package/server/ws-bridge.ts +1240 -0
@@ -0,0 +1,150 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { mkdirSync, readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { homedir } from "node:os";
5
+ import { networkInterfaces } from "node:os";
6
+
7
+ const AUTH_FILE = join(homedir(), ".heyhank", "auth.json");
8
+ const TOKEN_BYTES = 32; // 64 hex characters
9
+
10
+ interface AuthData {
11
+ token: string;
12
+ createdAt: number;
13
+ }
14
+
15
+ let cachedToken: string | null = null;
16
+
17
+ /**
18
+ * Get the auth token. Priority:
19
+ * 1. HEYHANK_AUTH_TOKEN env var (falls back to COMPANION_AUTH_TOKEN)
20
+ * 2. Persisted token from ~/.heyhank/auth.json
21
+ * 3. Auto-generate and persist a new token
22
+ */
23
+ export function getToken(): string {
24
+ // Env var override (always takes priority)
25
+ const envToken = process.env.HEYHANK_AUTH_TOKEN || process.env.COMPANION_AUTH_TOKEN;
26
+ if (envToken && envToken.trim()) {
27
+ cachedToken = envToken.trim();
28
+ return cachedToken;
29
+ }
30
+
31
+ // Return cached token if available
32
+ if (cachedToken) return cachedToken;
33
+
34
+ // Try reading from file
35
+ try {
36
+ if (existsSync(AUTH_FILE)) {
37
+ const raw = readFileSync(AUTH_FILE, "utf-8");
38
+ const data = JSON.parse(raw) as Partial<AuthData>;
39
+ if (typeof data.token === "string" && data.token.length >= 32) {
40
+ cachedToken = data.token;
41
+ return cachedToken;
42
+ }
43
+ }
44
+ } catch {
45
+ // File corrupt or unreadable — generate new
46
+ }
47
+
48
+ // Generate new token
49
+ const token = randomBytes(TOKEN_BYTES).toString("hex");
50
+ const data: AuthData = { token, createdAt: Date.now() };
51
+ try {
52
+ mkdirSync(dirname(AUTH_FILE), { recursive: true });
53
+ writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
54
+ } catch (err) {
55
+ console.error("[auth] Failed to persist auth token:", err);
56
+ }
57
+ cachedToken = token;
58
+ return token;
59
+ }
60
+
61
+ /**
62
+ * Verify a candidate token using constant-time comparison.
63
+ */
64
+ export function verifyToken(candidate: string | null | undefined): boolean {
65
+ if (!candidate) return false;
66
+ const expected = getToken();
67
+ const candidateBuf = Buffer.from(candidate);
68
+ const expectedBuf = Buffer.from(expected);
69
+ if (candidateBuf.length !== expectedBuf.length) return false;
70
+ return timingSafeEqual(candidateBuf, expectedBuf);
71
+ }
72
+
73
+ /**
74
+ * Get the primary LAN IP address for QR code URL generation.
75
+ * Falls back to "localhost" if no LAN IP is found.
76
+ */
77
+ export function getLanAddress(): string {
78
+ const interfaces = networkInterfaces();
79
+ for (const name of Object.keys(interfaces)) {
80
+ const addrs = interfaces[name];
81
+ if (!addrs) continue;
82
+ for (const addr of addrs) {
83
+ if (addr.family === "IPv4" && !addr.internal) {
84
+ return addr.address;
85
+ }
86
+ }
87
+ }
88
+ return "localhost";
89
+ }
90
+
91
+ /**
92
+ * Get all available access addresses: localhost, LAN IP, and Tailscale IP.
93
+ * Tailscale uses 100.x.x.x addresses (CGNAT range) on utun / tailscale interfaces.
94
+ */
95
+ export function getAllAddresses(): { label: string; ip: string }[] {
96
+ const result: { label: string; ip: string }[] = [
97
+ { label: "Localhost", ip: "localhost" },
98
+ ];
99
+
100
+ const interfaces = networkInterfaces();
101
+ let lanIp: string | null = null;
102
+ let tailscaleIp: string | null = null;
103
+
104
+ for (const name of Object.keys(interfaces)) {
105
+ const addrs = interfaces[name];
106
+ if (!addrs) continue;
107
+ for (const addr of addrs) {
108
+ if (addr.family !== "IPv4" || addr.internal) continue;
109
+
110
+ // Tailscale uses 100.64.0.0/10 (CGNAT) — detect by IP range
111
+ if (addr.address.startsWith("100.")) {
112
+ const second = parseInt(addr.address.split(".")[1], 10);
113
+ if (second >= 64 && second <= 127) {
114
+ tailscaleIp = addr.address;
115
+ continue;
116
+ }
117
+ }
118
+
119
+ if (!lanIp) lanIp = addr.address;
120
+ }
121
+ }
122
+
123
+ if (lanIp) result.push({ label: "LAN", ip: lanIp });
124
+ if (tailscaleIp) result.push({ label: "Tailscale", ip: tailscaleIp });
125
+
126
+ return result;
127
+ }
128
+
129
+ /**
130
+ * Regenerate the auth token — creates a new random token, persists it,
131
+ * and returns the new value. Existing sessions using the old token will
132
+ * be invalidated on their next request.
133
+ */
134
+ export function regenerateToken(): string {
135
+ const token = randomBytes(TOKEN_BYTES).toString("hex");
136
+ const data: AuthData = { token, createdAt: Date.now() };
137
+ try {
138
+ mkdirSync(dirname(AUTH_FILE), { recursive: true });
139
+ writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), { mode: 0o600 });
140
+ } catch (err) {
141
+ console.error("[auth] Failed to persist regenerated token:", err);
142
+ }
143
+ cachedToken = token;
144
+ return token;
145
+ }
146
+
147
+ /** Reset cached state — for testing only */
148
+ export function _resetForTest(): void {
149
+ cachedToken = null;
150
+ }
@@ -0,0 +1,153 @@
1
+ // ─── Auto-Approve System ─────────────────────────────────────────────────────
2
+ // Agent Max 2.0 can automatically approve/deny permission requests
3
+ // for trusted agents based on risk assessment rules.
4
+
5
+ import type { AgentConfig } from "./agent-types.js";
6
+ import * as agentStore from "./agent-store.js";
7
+ import { isKilled } from "./kill-switch.js";
8
+
9
+ // ─── Types ───────────────────────────────────────────────────────────────────
10
+
11
+ export interface AutoApproveRule {
12
+ /** Agent ID this rule applies to ("*" for all) */
13
+ agentId: string;
14
+ /** Tool names to auto-approve (empty = all) */
15
+ allowedTools: string[];
16
+ /** Tool names to always deny */
17
+ deniedTools: string[];
18
+ /** Whether to auto-approve all safe verdicts */
19
+ autoApproveSafe: boolean;
20
+ /** Whether to auto-deny all dangerous verdicts */
21
+ autoDenyDangerous: boolean;
22
+ /** Max cost per action before requiring manual approval (USD) */
23
+ maxCostPerAction?: number;
24
+ }
25
+
26
+ export interface PermissionEvaluation {
27
+ action: "approve" | "deny" | "manual";
28
+ reason: string;
29
+ }
30
+
31
+ // ─── Default Rules ───────────────────────────────────────────────────────────
32
+
33
+ const DEFAULT_RULES: AutoApproveRule[] = [
34
+ {
35
+ // Monitoring Agent: read-only, always approve
36
+ agentId: "monitoring-agent",
37
+ allowedTools: ["bash", "read", "glob", "grep"],
38
+ deniedTools: ["write", "edit"],
39
+ autoApproveSafe: true,
40
+ autoDenyDangerous: true,
41
+ },
42
+ {
43
+ // Coding Agent: full access, approve safe, manual for dangerous
44
+ agentId: "coding-agent",
45
+ allowedTools: [],
46
+ deniedTools: [],
47
+ autoApproveSafe: true,
48
+ autoDenyDangerous: false,
49
+ },
50
+ {
51
+ // Default: approve safe, deny dangerous
52
+ agentId: "*",
53
+ allowedTools: [],
54
+ deniedTools: [],
55
+ autoApproveSafe: true,
56
+ autoDenyDangerous: true,
57
+ },
58
+ ];
59
+
60
+ // ─── Dangerous Patterns ──────────────────────────────────────────────────────
61
+
62
+ const DANGEROUS_BASH_PATTERNS = [
63
+ /rm\s+-rf?\s+\//, // rm -rf /
64
+ /mkfs/, // format disk
65
+ /dd\s+if=/, // dd
66
+ />\s*\/dev\/sd/, // write to disk device
67
+ /chmod\s+777/, // world-writable
68
+ /curl.*\|\s*(ba)?sh/, // curl | bash
69
+ /wget.*\|\s*(ba)?sh/, // wget | bash
70
+ /:(){ :\|:& };:/, // fork bomb
71
+ /--force.*push/, // force push
72
+ /git\s+push.*--force/, // git force push
73
+ /DROP\s+(TABLE|DATABASE)/i, // SQL drop
74
+ /DELETE\s+FROM.*WHERE\s+1/i, // SQL mass delete
75
+ ];
76
+
77
+ const SAFE_READ_ONLY_TOOLS = new Set([
78
+ "Read", "Glob", "Grep", "WebSearch", "WebFetch",
79
+ "TaskGet", "TaskList",
80
+ ]);
81
+
82
+ // ─── Evaluation ──────────────────────────────────────────────────────────────
83
+
84
+ /** Evaluate whether to auto-approve a permission request. */
85
+ export function evaluate(
86
+ agentId: string,
87
+ toolName: string,
88
+ toolInput: Record<string, unknown>,
89
+ aiVerdict?: "safe" | "dangerous" | "uncertain",
90
+ ): PermissionEvaluation {
91
+ // Kill switch overrides everything
92
+ if (isKilled()) {
93
+ return { action: "deny", reason: "Kill switch active" };
94
+ }
95
+
96
+ // Find matching rule (specific agent first, then wildcard)
97
+ const rule =
98
+ DEFAULT_RULES.find((r) => r.agentId === agentId) ||
99
+ DEFAULT_RULES.find((r) => r.agentId === "*");
100
+
101
+ if (!rule) {
102
+ return { action: "manual", reason: "No matching rule" };
103
+ }
104
+
105
+ // Check denied tools
106
+ if (rule.deniedTools.length > 0 && rule.deniedTools.includes(toolName.toLowerCase())) {
107
+ return { action: "deny", reason: `Tool "${toolName}" is denied for agent` };
108
+ }
109
+
110
+ // Check allowed tools (if specified, only these are allowed)
111
+ if (rule.allowedTools.length > 0 && !rule.allowedTools.includes(toolName.toLowerCase())) {
112
+ return { action: "deny", reason: `Tool "${toolName}" not in allowed list` };
113
+ }
114
+
115
+ // Read-only tools are always safe
116
+ if (SAFE_READ_ONLY_TOOLS.has(toolName)) {
117
+ return { action: "approve", reason: "Read-only tool" };
118
+ }
119
+
120
+ // Check bash commands for dangerous patterns
121
+ if (toolName.toLowerCase() === "bash" || toolName === "Bash") {
122
+ const command = String(toolInput?.command || toolInput?.cmd || "");
123
+ for (const pattern of DANGEROUS_BASH_PATTERNS) {
124
+ if (pattern.test(command)) {
125
+ return { action: "deny", reason: `Dangerous bash pattern: ${pattern}` };
126
+ }
127
+ }
128
+ }
129
+
130
+ // Use AI verdict if available
131
+ if (aiVerdict === "safe" && rule.autoApproveSafe) {
132
+ return { action: "approve", reason: "AI verdict: safe" };
133
+ }
134
+ if (aiVerdict === "dangerous" && rule.autoDenyDangerous) {
135
+ return { action: "deny", reason: "AI verdict: dangerous" };
136
+ }
137
+
138
+ // Default to manual review
139
+ return { action: "manual", reason: "Requires manual review" };
140
+ }
141
+
142
+ /** Check if an agent session should use auto-approve. */
143
+ export function shouldAutoApprove(agentId: string): boolean {
144
+ const agent = agentStore.getAgent(agentId);
145
+ if (!agent) return false;
146
+ // Auto-approve for agents running with bypassPermissions
147
+ return agent.permissionMode === "bypassPermissions";
148
+ }
149
+
150
+ /** Get all rules (for admin UI). */
151
+ export function getRules(): AutoApproveRule[] {
152
+ return [...DEFAULT_RULES];
153
+ }
@@ -0,0 +1,36 @@
1
+ import { callInternalAI } from "./internal-ai.js";
2
+
3
+ function sanitizeTitle(raw: string): string | null {
4
+ const title = raw.replace(/^"|"$/g, "").replace(/^'|'$/g, "").trim();
5
+ if (!title || title.length >= 100) return null;
6
+ return title;
7
+ }
8
+
9
+ /**
10
+ * Generates a short session title using the configured internal AI provider.
11
+ * Returns null if no provider is configured or if generation fails.
12
+ */
13
+ export async function generateSessionTitle(
14
+ firstUserMessage: string,
15
+ _model: string,
16
+ options?: {
17
+ timeoutMs?: number;
18
+ },
19
+ ): Promise<string | null> {
20
+ const truncated = firstUserMessage.slice(0, 500);
21
+ const userPrompt = `Generate a concise 3-5 word session title for this user request. Output only the title.\n\nRequest: ${truncated}`;
22
+
23
+ const result = await callInternalAI({
24
+ userPrompt,
25
+ maxTokens: 256,
26
+ temperature: 0.2,
27
+ timeoutMs: options?.timeoutMs || 15_000,
28
+ });
29
+
30
+ if (!result.ok) {
31
+ console.warn(`[auto-namer] Failed to generate title: ${result.error}`);
32
+ return null;
33
+ }
34
+
35
+ return sanitizeTitle(result.text);
36
+ }
@@ -0,0 +1,54 @@
1
+ import type { BrowserIncomingMessage, BrowserOutgoingMessage } from "./session-types.js";
2
+
3
+ // ─── Unified Backend Adapter Interface ───────────────────────────────────────
4
+ // Both Claude Code (NDJSON WebSocket) and Codex (JSON-RPC stdio/WS) implement
5
+ // this so that application code never branches on BackendType for message routing.
6
+
7
+ /**
8
+ * Unified interface for backend communication.
9
+ *
10
+ * Adapters translate between the backend-native protocol and the common
11
+ * BrowserIncomingMessage / BrowserOutgoingMessage types used by the bridge
12
+ * and the frontend.
13
+ */
14
+ export interface IBackendAdapter {
15
+ /** Send a browser-originated message to the backend. Returns true if accepted. */
16
+ send(msg: BrowserOutgoingMessage): boolean;
17
+
18
+ /** Whether the backend transport is currently connected and ready. */
19
+ isConnected(): boolean;
20
+
21
+ /** Gracefully disconnect the backend transport. */
22
+ disconnect(): Promise<void>;
23
+
24
+ // ── Event registration (called once at attachment time) ──
25
+
26
+ /**
27
+ * Register callback for messages to forward to browsers.
28
+ * The adapter translates backend-native protocol into BrowserIncomingMessage.
29
+ */
30
+ onBrowserMessage(cb: (msg: BrowserIncomingMessage) => void): void;
31
+
32
+ /**
33
+ * Register callback for session metadata updates (CLI session ID, model, cwd).
34
+ * Used for --resume tracking and state synchronization.
35
+ */
36
+ onSessionMeta(cb: (meta: { cliSessionId?: string; model?: string; cwd?: string }) => void): void;
37
+
38
+ /** Register callback for transport disconnection. */
39
+ onDisconnect(cb: () => void): void;
40
+
41
+ /** Register callback for initialization errors. */
42
+ onInitError?(cb: (error: string) => void): void;
43
+
44
+ // ── Optional capabilities (not all backends support these) ──
45
+
46
+ /** Return backend-specific rate limits, if available (Codex only). */
47
+ getRateLimits?(): {
48
+ primary: { usedPercent: number; windowDurationMins: number; resetsAt: number } | null;
49
+ secondary: { usedPercent: number; windowDurationMins: number; resetsAt: number } | null;
50
+ } | null;
51
+
52
+ /** Handle transport-level close (used when WS proxy drops). */
53
+ handleTransportClose?(): void;
54
+ }
@@ -0,0 +1,61 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+
3
+ /**
4
+ * Hono middleware that sets Cache-Control headers for production static assets.
5
+ *
6
+ * Must be registered BEFORE serveStatic so it can modify the response headers
7
+ * after the static file is served (via await next()).
8
+ *
9
+ * Header strategy:
10
+ * - sw.js / workbox-*.js: no-cache (browser must revalidate on each load)
11
+ * - index.html: no-cache (fresh HTML triggers SW update detection)
12
+ * - manifest.json: no-cache (browser checks on each visit)
13
+ * - /assets/*: immutable, 1 year (Vite content-hashed filenames)
14
+ * - /fonts/*.woff2: immutable, 1 year (stable font files)
15
+ * - Icons/images: 1 day cache
16
+ */
17
+ export function cacheControlMiddleware(): MiddlewareHandler {
18
+ return async (c, next) => {
19
+ await next();
20
+
21
+ // Only set cache headers on successful responses
22
+ if (c.res.status !== 200) return;
23
+
24
+ const path = c.req.path;
25
+
26
+ // Service worker files: browsers must always revalidate
27
+ if (path === "/sw.js" || path.startsWith("/workbox-")) {
28
+ c.header("Cache-Control", "no-cache");
29
+ return;
30
+ }
31
+
32
+ // index.html (served for / and /index.html): must be fresh
33
+ if (path === "/" || path === "/index.html") {
34
+ c.header("Cache-Control", "no-cache");
35
+ return;
36
+ }
37
+
38
+ // manifest.json: must be fresh
39
+ if (path === "/manifest.json") {
40
+ c.header("Cache-Control", "no-cache");
41
+ return;
42
+ }
43
+
44
+ // Vite hashed assets: immutable (filename changes on content change)
45
+ if (path.startsWith("/assets/")) {
46
+ c.header("Cache-Control", "public, max-age=31536000, immutable");
47
+ return;
48
+ }
49
+
50
+ // Font files: immutable
51
+ if (path.startsWith("/fonts/") && path.endsWith(".woff2")) {
52
+ c.header("Cache-Control", "public, max-age=31536000, immutable");
53
+ return;
54
+ }
55
+
56
+ // Other static files (icons, images): cache for 1 day
57
+ if (path.endsWith(".png") || path.endsWith(".svg") || path.endsWith(".ico")) {
58
+ c.header("Cache-Control", "public, max-age=86400");
59
+ }
60
+ };
61
+ }