ftown 0.0.0-stage → 0.19.41

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 (262) hide show
  1. package/bin/ftown-sessions +5 -0
  2. package/dist/agent-commands.d.ts +19 -0
  3. package/dist/agent-commands.js +15 -0
  4. package/dist/agent-commands.js.map +1 -0
  5. package/dist/attention-events.d.ts +32 -0
  6. package/dist/attention-events.js +67 -0
  7. package/dist/attention-events.js.map +1 -0
  8. package/dist/attention-store.d.ts +39 -0
  9. package/dist/attention-store.js +234 -0
  10. package/dist/attention-store.js.map +1 -0
  11. package/dist/bridge-auth.d.ts +21 -0
  12. package/dist/bridge-auth.js +56 -0
  13. package/dist/bridge-auth.js.map +1 -0
  14. package/dist/browser-access.d.ts +44 -0
  15. package/dist/browser-access.js +384 -0
  16. package/dist/browser-access.js.map +1 -0
  17. package/dist/centrifugo-client.d.ts +37 -0
  18. package/dist/centrifugo-client.js +356 -0
  19. package/dist/centrifugo-client.js.map +1 -0
  20. package/dist/claude-runner.d.ts +55 -0
  21. package/dist/claude-runner.js +314 -0
  22. package/dist/claude-runner.js.map +1 -0
  23. package/dist/cli-client.d.ts +17 -0
  24. package/dist/cli-client.js +76 -0
  25. package/dist/cli-client.js.map +1 -0
  26. package/dist/cli-entry.d.ts +2 -0
  27. package/dist/cli-entry.js +22 -0
  28. package/dist/cli-entry.js.map +1 -0
  29. package/dist/codex-installer.d.ts +17 -0
  30. package/dist/codex-installer.js +204 -0
  31. package/dist/codex-installer.js.map +1 -0
  32. package/dist/command-rpc.d.ts +14 -0
  33. package/dist/command-rpc.js +250 -0
  34. package/dist/command-rpc.js.map +1 -0
  35. package/dist/create-ftown-session.d.ts +143 -0
  36. package/dist/create-ftown-session.js +434 -0
  37. package/dist/create-ftown-session.js.map +1 -0
  38. package/dist/cursor-hook-installer.d.ts +2 -0
  39. package/dist/cursor-hook-installer.js +98 -0
  40. package/dist/cursor-hook-installer.js.map +1 -0
  41. package/dist/direct-shell.d.ts +5 -0
  42. package/dist/direct-shell.js +14 -0
  43. package/dist/direct-shell.js.map +1 -0
  44. package/dist/direct-transport/contract.d.ts +84 -0
  45. package/dist/direct-transport/contract.js +22 -0
  46. package/dist/direct-transport/contract.js.map +1 -0
  47. package/dist/direct-transport/loopback-server.d.ts +80 -0
  48. package/dist/direct-transport/loopback-server.js +328 -0
  49. package/dist/direct-transport/loopback-server.js.map +1 -0
  50. package/dist/direct-transport/peer-manager.d.ts +66 -0
  51. package/dist/direct-transport/peer-manager.js +364 -0
  52. package/dist/direct-transport/peer-manager.js.map +1 -0
  53. package/dist/direct-transport/publish-router.d.ts +60 -0
  54. package/dist/direct-transport/publish-router.js +99 -0
  55. package/dist/direct-transport/publish-router.js.map +1 -0
  56. package/dist/direct-transport/watch-registry.d.ts +38 -0
  57. package/dist/direct-transport/watch-registry.js +92 -0
  58. package/dist/direct-transport/watch-registry.js.map +1 -0
  59. package/dist/ensure-ftown-path.d.ts +30 -0
  60. package/dist/ensure-ftown-path.js +79 -0
  61. package/dist/ensure-ftown-path.js.map +1 -0
  62. package/dist/env-commands.d.ts +32 -0
  63. package/dist/env-commands.js +162 -0
  64. package/dist/env-commands.js.map +1 -0
  65. package/dist/ftown-cli.d.ts +4 -0
  66. package/dist/ftown-cli.js +37 -0
  67. package/dist/ftown-cli.js.map +1 -0
  68. package/dist/ftown-env-cli.d.ts +3 -0
  69. package/dist/ftown-env-cli.js +10 -0
  70. package/dist/ftown-env-cli.js.map +1 -0
  71. package/dist/ftown-home.d.ts +28 -0
  72. package/dist/ftown-home.js +47 -0
  73. package/dist/ftown-home.js.map +1 -0
  74. package/dist/ftown-sessions-cli.d.ts +2 -0
  75. package/dist/ftown-sessions-cli.js +9 -0
  76. package/dist/ftown-sessions-cli.js.map +1 -0
  77. package/dist/harness-cli.d.ts +2 -0
  78. package/dist/harness-cli.js +9 -0
  79. package/dist/harness-cli.js.map +1 -0
  80. package/dist/harness-commands.d.ts +2 -0
  81. package/dist/harness-commands.js +576 -0
  82. package/dist/harness-commands.js.map +1 -0
  83. package/dist/harness-format.d.ts +3 -0
  84. package/dist/harness-format.js +15 -0
  85. package/dist/harness-format.js.map +1 -0
  86. package/dist/harness-installer.d.ts +7 -0
  87. package/dist/harness-installer.js +26 -0
  88. package/dist/harness-installer.js.map +1 -0
  89. package/dist/harness-registry.d.ts +182 -0
  90. package/dist/harness-registry.js +292 -0
  91. package/dist/harness-registry.js.map +1 -0
  92. package/dist/hook-installer.d.ts +3 -0
  93. package/dist/hook-installer.js +120 -0
  94. package/dist/hook-installer.js.map +1 -0
  95. package/dist/hook-usage.d.ts +15 -0
  96. package/dist/hook-usage.js +65 -0
  97. package/dist/hook-usage.js.map +1 -0
  98. package/dist/index.d.ts +2 -0
  99. package/dist/index.js +786 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/install-ftown-command-cli.d.ts +3 -0
  102. package/dist/install-ftown-command-cli.js +70 -0
  103. package/dist/install-ftown-command-cli.js.map +1 -0
  104. package/dist/install-ftown-skill.d.ts +16 -0
  105. package/dist/install-ftown-skill.js +299 -0
  106. package/dist/install-ftown-skill.js.map +1 -0
  107. package/dist/install-notify-script.d.ts +6 -0
  108. package/dist/install-notify-script.js +24 -0
  109. package/dist/install-notify-script.js.map +1 -0
  110. package/dist/local-api-server.d.ts +91 -0
  111. package/dist/local-api-server.js +934 -0
  112. package/dist/local-api-server.js.map +1 -0
  113. package/dist/local-pair-cli.d.ts +11 -0
  114. package/dist/local-pair-cli.js +97 -0
  115. package/dist/local-pair-cli.js.map +1 -0
  116. package/dist/mail-delivery.d.ts +113 -0
  117. package/dist/mail-delivery.js +325 -0
  118. package/dist/mail-delivery.js.map +1 -0
  119. package/dist/mail-store.d.ts +29 -0
  120. package/dist/mail-store.js +116 -0
  121. package/dist/mail-store.js.map +1 -0
  122. package/dist/mcp/fleet.d.ts +218 -0
  123. package/dist/mcp/fleet.js +281 -0
  124. package/dist/mcp/fleet.js.map +1 -0
  125. package/dist/mcp/http.d.ts +335 -0
  126. package/dist/mcp/http.js +225 -0
  127. package/dist/mcp/http.js.map +1 -0
  128. package/dist/mcp/legacy-relay.d.ts +3 -0
  129. package/dist/mcp/legacy-relay.js +45 -0
  130. package/dist/mcp/legacy-relay.js.map +1 -0
  131. package/dist/mcp/oauth-store.d.ts +17 -0
  132. package/dist/mcp/oauth-store.js +58 -0
  133. package/dist/mcp/oauth-store.js.map +1 -0
  134. package/dist/mcp/oauth.d.ts +79 -0
  135. package/dist/mcp/oauth.js +236 -0
  136. package/dist/mcp/oauth.js.map +1 -0
  137. package/dist/mcp/relay-handler.d.ts +1 -0
  138. package/dist/mcp/relay-handler.js +43 -0
  139. package/dist/mcp/relay-handler.js.map +1 -0
  140. package/dist/mcp/relay.d.ts +13 -0
  141. package/dist/mcp/relay.js +145 -0
  142. package/dist/mcp/relay.js.map +1 -0
  143. package/dist/mcp/server.d.ts +12 -0
  144. package/dist/mcp/server.js +764 -0
  145. package/dist/mcp/server.js.map +1 -0
  146. package/dist/mcp-cli.d.ts +2 -0
  147. package/dist/mcp-cli.js +85 -0
  148. package/dist/mcp-cli.js.map +1 -0
  149. package/dist/muse-plugin-installer.d.ts +63 -0
  150. package/dist/muse-plugin-installer.js +344 -0
  151. package/dist/muse-plugin-installer.js.map +1 -0
  152. package/dist/opencode-plugin-installer.d.ts +12 -0
  153. package/dist/opencode-plugin-installer.js +37 -0
  154. package/dist/opencode-plugin-installer.js.map +1 -0
  155. package/dist/pairing-client.d.ts +47 -0
  156. package/dist/pairing-client.js +90 -0
  157. package/dist/pairing-client.js.map +1 -0
  158. package/dist/pi-extension-installer.d.ts +2 -0
  159. package/dist/pi-extension-installer.js +12 -0
  160. package/dist/pi-extension-installer.js.map +1 -0
  161. package/dist/provider-env-store.d.ts +26 -0
  162. package/dist/provider-env-store.js +105 -0
  163. package/dist/provider-env-store.js.map +1 -0
  164. package/dist/remove-ftown-session.d.ts +23 -0
  165. package/dist/remove-ftown-session.js +32 -0
  166. package/dist/remove-ftown-session.js.map +1 -0
  167. package/dist/retired-automation.d.ts +14 -0
  168. package/dist/retired-automation.js +18 -0
  169. package/dist/retired-automation.js.map +1 -0
  170. package/dist/retired-skill-assets.d.ts +5 -0
  171. package/dist/retired-skill-assets.js +32 -0
  172. package/dist/retired-skill-assets.js.map +1 -0
  173. package/dist/rotating-token-refresher.d.ts +25 -0
  174. package/dist/rotating-token-refresher.js +52 -0
  175. package/dist/rotating-token-refresher.js.map +1 -0
  176. package/dist/session-controller.d.ts +110 -0
  177. package/dist/session-controller.js +177 -0
  178. package/dist/session-controller.js.map +1 -0
  179. package/dist/session-ids.d.ts +19 -0
  180. package/dist/session-ids.js +118 -0
  181. package/dist/session-ids.js.map +1 -0
  182. package/dist/session-registry.d.ts +17 -0
  183. package/dist/session-registry.js +98 -0
  184. package/dist/session-registry.js.map +1 -0
  185. package/dist/session-resurrection.d.ts +32 -0
  186. package/dist/session-resurrection.js +170 -0
  187. package/dist/session-resurrection.js.map +1 -0
  188. package/dist/session-store.d.ts +39 -0
  189. package/dist/session-store.js +207 -0
  190. package/dist/session-store.js.map +1 -0
  191. package/dist/session-wire.d.ts +11 -0
  192. package/dist/session-wire.js +14 -0
  193. package/dist/session-wire.js.map +1 -0
  194. package/dist/sessions-commands.d.ts +2 -0
  195. package/dist/sessions-commands.js +445 -0
  196. package/dist/sessions-commands.js.map +1 -0
  197. package/dist/setup-migrations.d.ts +6 -0
  198. package/dist/setup-migrations.js +28 -0
  199. package/dist/setup-migrations.js.map +1 -0
  200. package/dist/setup-state.d.ts +16 -0
  201. package/dist/setup-state.js +163 -0
  202. package/dist/setup-state.js.map +1 -0
  203. package/dist/setup.d.ts +25 -0
  204. package/dist/setup.js +120 -0
  205. package/dist/setup.js.map +1 -0
  206. package/dist/solo/contract.d.ts +133 -0
  207. package/dist/solo/contract.js +361 -0
  208. package/dist/solo/contract.js.map +1 -0
  209. package/dist/solo/hub-manager.d.ts +85 -0
  210. package/dist/solo/hub-manager.js +425 -0
  211. package/dist/solo/hub-manager.js.map +1 -0
  212. package/dist/solo/panel-manager.d.ts +129 -0
  213. package/dist/solo/panel-manager.js +715 -0
  214. package/dist/solo/panel-manager.js.map +1 -0
  215. package/dist/solo/solo-auth.d.ts +44 -0
  216. package/dist/solo/solo-auth.js +84 -0
  217. package/dist/solo/solo-auth.js.map +1 -0
  218. package/dist/solo/solo-server.d.ts +105 -0
  219. package/dist/solo/solo-server.js +399 -0
  220. package/dist/solo/solo-server.js.map +1 -0
  221. package/dist/solo/ws-proxy.d.ts +62 -0
  222. package/dist/solo/ws-proxy.js +306 -0
  223. package/dist/solo/ws-proxy.js.map +1 -0
  224. package/dist/spawn-stagger.d.ts +22 -0
  225. package/dist/spawn-stagger.js +34 -0
  226. package/dist/spawn-stagger.js.map +1 -0
  227. package/dist/terminal-manager.d.ts +41 -0
  228. package/dist/terminal-manager.js +145 -0
  229. package/dist/terminal-manager.js.map +1 -0
  230. package/dist/terminal-pump.d.ts +60 -0
  231. package/dist/terminal-pump.js +192 -0
  232. package/dist/terminal-pump.js.map +1 -0
  233. package/dist/tmux.d.ts +33 -0
  234. package/dist/tmux.js +199 -0
  235. package/dist/tmux.js.map +1 -0
  236. package/dist/types.d.ts +193 -0
  237. package/dist/types.js +2 -0
  238. package/dist/types.js.map +1 -0
  239. package/dist/usage-collector.d.ts +49 -0
  240. package/dist/usage-collector.js +641 -0
  241. package/dist/usage-collector.js.map +1 -0
  242. package/dist/wire-types.d.ts +80 -0
  243. package/dist/wire-types.js +23 -0
  244. package/dist/wire-types.js.map +1 -0
  245. package/dist/xterm-theme.d.ts +30 -0
  246. package/dist/xterm-theme.js +38 -0
  247. package/dist/xterm-theme.js.map +1 -0
  248. package/hooks/notify.sh +72 -0
  249. package/muse-plugin/.muse-plugin/plugin.json +40 -0
  250. package/muse-plugin/hooks/ftown-session-start.sh +26 -0
  251. package/muse-plugin/hooks/ftown-stop.sh +26 -0
  252. package/muse-plugin/lib/ftown-hook.sh +121 -0
  253. package/opencode-plugin/ftown.js +225 -0
  254. package/package.json +75 -4
  255. package/pi-extension/API.md +70 -0
  256. package/pi-extension/ftown.js +727 -0
  257. package/skills/ftown/SKILL.md +85 -0
  258. package/skills/ftown/agents/openai.yaml +4 -0
  259. package/skills/ftown/references/orchestrator.md +30 -0
  260. package/skills/ftown/references/sessions.md +176 -0
  261. package/skills/ftown/scripts/ftown +4 -0
  262. package/README.md +0 -3
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bash
2
+ # Dev/repo launcher — production copy lives at ~/.ftown/ftown-sessions
3
+ set -euo pipefail
4
+ ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
+ exec node "${ROOT}/dist/ftown-sessions-cli.js" "$@"
@@ -0,0 +1,19 @@
1
+ import type { ShellType } from './types.js';
2
+ export { buildOpencodeCommand, shellQuote, buildCursorAgentCommand, buildCodexCommand, buildGrokCommand, buildMuseCommand, buildPiCommand, buildKimiCodeCommand, } from './harness-registry.js';
3
+ export interface BuildSessionCommandInput {
4
+ shellType?: ShellType;
5
+ workingDir?: string;
6
+ model?: string;
7
+ claudeSessionId?: string;
8
+ cursorSessionId?: string;
9
+ codexSessionId?: string;
10
+ piSessionId?: string;
11
+ opencodeSessionId?: string;
12
+ museSessionId?: string;
13
+ command?: string;
14
+ /** Initial prompt passed as a CLI argument — avoids racing the TUI with typed input. */
15
+ initialPrompt?: string;
16
+ /** Resurrection resume — workdir-based harnesses (Pi/kimi-code) append their continue flag; id-based harnesses (incl. muse) ignore it and key off their id fields. */
17
+ resume?: boolean;
18
+ }
19
+ export declare function buildSessionCommand(input: BuildSessionCommandInput): string;
@@ -0,0 +1,15 @@
1
+ import { HARNESSES } from './harness-registry.js';
2
+ // The per-harness builders live in the registry; re-exported here so existing
3
+ // importers (and tests) keep their import paths.
4
+ export { buildOpencodeCommand, shellQuote, buildCursorAgentCommand, buildCodexCommand, buildGrokCommand, buildMuseCommand, buildPiCommand, buildKimiCodeCommand, } from './harness-registry.js';
5
+ export function buildSessionCommand(input) {
6
+ if (input.command?.trim()) {
7
+ return input.command.trim();
8
+ }
9
+ const shellType = input.shellType ?? 'claude';
10
+ // Runtime shellType may come from unvalidated JSON — unknown values fall back
11
+ // to the claude launch, exactly like the old if/else chain's final branch.
12
+ const spec = HARNESSES[shellType] ?? HARNESSES.claude;
13
+ return spec.buildCommand(input);
14
+ }
15
+ //# sourceMappingURL=agent-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-commands.js","sourceRoot":"","sources":["../src/agent-commands.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAKlD,8EAA8E;AAC9E,iDAAiD;AACjD,OAAO,EACL,oBAAoB,EACpB,UAAU,EACV,uBAAuB,EACvB,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,oBAAoB,GACrB,MAAM,uBAAuB,CAAC;AAmB/B,MAAM,UAAU,mBAAmB,CAAC,KAA+B;IACjE,IAAI,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE,EAAE,CAAC;QAC1B,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,QAAQ,CAAC;IAC9C,8EAA8E;IAC9E,2EAA2E;IAC3E,MAAM,IAAI,GAAiB,SAAyC,CAAC,SAAS,CAAC,IAAI,SAAS,CAAC,MAAM,CAAC;IACpG,OAAO,IAAI,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC;AAClC,CAAC"}
@@ -0,0 +1,32 @@
1
+ export type AttentionType = 'permission_prompt' | 'elicitation_dialog' | 'agent_needs_input' | 'ask_user';
2
+ export type Resolution = 'manual' | 'user_prompt' | 'elicitation_complete' | 'session_end';
3
+ export interface Activity {
4
+ state: 'thinking' | 'tool_use' | 'idle' | 'unknown';
5
+ lastActivityAt?: string;
6
+ }
7
+ export interface AttentionItem {
8
+ id: string;
9
+ sequence: number;
10
+ type: AttentionType;
11
+ title: string;
12
+ message: string;
13
+ createdAt: string;
14
+ lastSeenAt: string;
15
+ occurrences: number;
16
+ acknowledgedAt?: string;
17
+ resolvedAt?: string;
18
+ resolution?: Resolution;
19
+ }
20
+ export interface AttentionObservation {
21
+ activity?: Activity['state'];
22
+ attention?: {
23
+ type: AttentionType;
24
+ title: string;
25
+ message: string;
26
+ sourceId?: string;
27
+ };
28
+ resolve?: Exclude<Resolution, 'manual'>;
29
+ }
30
+ /** Only the selected human-facing excerpt crosses the API; never the hook payload. */
31
+ export declare function sanitizeAttentionText(value: string, maxLength: number): string;
32
+ export declare function classifyAttentionHook(eventName: string, data: Record<string, unknown>): AttentionObservation;
@@ -0,0 +1,67 @@
1
+ /** Only the selected human-facing excerpt crosses the API; never the hook payload. */
2
+ export function sanitizeAttentionText(value, maxLength) {
3
+ return value
4
+ .replace(/\x1b\][^\x07]*(?:\x07|\x1b\\)/g, '')
5
+ .replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, '')
6
+ .replace(/[\x00-\x08\x0b-\x1f\x7f-\x9f]/g, '')
7
+ .replace(/\b(Bearer\s+)[A-Za-z0-9._~+\/-]+=*/gi, '$1[redacted]')
8
+ .replace(/\b((?:[A-Z_]*(?:API_KEY|AUTH_TOKEN|ACCESS_TOKEN|SECRET|PASSWORD)[A-Z_]*|authorization)\s*[:=]\s*)[^\s,;]+/gi, '$1[redacted]')
9
+ .replace(/\b(?:sk-(?:ant-)?[A-Za-z0-9_-]{12,}|gh[pousr]_[A-Za-z0-9]{20,})\b/g, '[redacted]')
10
+ .trim()
11
+ .slice(0, maxLength);
12
+ }
13
+ const askTools = new Set(['askuserquestion', 'askquestion', 'askuser', 'requestuserinput', 'ftownaskuser']);
14
+ const notificationTypes = new Set(['permission_prompt', 'elicitation_dialog', 'agent_needs_input']);
15
+ const text = (value) => typeof value === 'string' && value.trim() ? value : undefined;
16
+ function question(data) {
17
+ const raw = data.tool_input ?? data.toolInput ?? data.input;
18
+ if (!raw || typeof raw !== 'object' || Array.isArray(raw))
19
+ return;
20
+ const input = raw;
21
+ const direct = text(input.question) ?? text(input.prompt) ?? text(input.message);
22
+ if (direct)
23
+ return direct;
24
+ const first = Array.isArray(input.questions) ? input.questions[0] : undefined;
25
+ if (!first || typeof first !== 'object' || Array.isArray(first))
26
+ return;
27
+ const item = first;
28
+ return text(item.question) ?? text(item.prompt) ?? text(item.message);
29
+ }
30
+ export function classifyAttentionHook(eventName, data) {
31
+ const observation = {};
32
+ if (['UserPromptSubmit', 'beforeSubmitPrompt', 'sessionStart'].includes(eventName))
33
+ observation.activity = 'thinking';
34
+ if (['PreToolUse', 'preToolUse', 'beforeShellExecution', 'beforeMCPExecution'].includes(eventName))
35
+ observation.activity = 'tool_use';
36
+ if (['PostToolUse', 'postToolUse', 'afterShellExecution', 'afterFileEdit', 'afterMCPExecution'].includes(eventName))
37
+ observation.activity = 'thinking';
38
+ if (['Stop', 'stop', 'SessionEnd', 'sessionEnd', 'postToolUseFailure'].includes(eventName))
39
+ observation.activity = 'idle';
40
+ if (['UserPromptSubmit', 'beforeSubmitPrompt'].includes(eventName))
41
+ observation.resolve = 'user_prompt';
42
+ if (['SessionEnd', 'sessionEnd'].includes(eventName))
43
+ observation.resolve = 'session_end';
44
+ if (eventName === 'Notification' && ['elicitation_complete', 'elicitation_response'].includes(String(data.notification_type))) {
45
+ observation.resolve = 'elicitation_complete';
46
+ }
47
+ const sourceId = text(data.notification_id) ?? text(data.tool_use_id) ?? text(data.event_id);
48
+ if (eventName === 'Notification' && notificationTypes.has(String(data.notification_type))) {
49
+ observation.attention = {
50
+ type: data.notification_type,
51
+ title: sanitizeAttentionText(text(data.title) ?? 'Session needs your input', 200),
52
+ message: sanitizeAttentionText(text(data.message) ?? 'Open the session to respond.', 2000),
53
+ ...(sourceId ? { sourceId: sourceId.slice(0, 200) } : {}),
54
+ };
55
+ }
56
+ const tool = (text(data.tool_name) ?? text(data.toolName))?.split(/\.|\/|::|__/).at(-1)?.replace(/[^a-z0-9]/gi, '').toLowerCase();
57
+ if (['PreToolUse', 'preToolUse'].includes(eventName) && tool && askTools.has(tool)) {
58
+ observation.attention = {
59
+ type: 'ask_user',
60
+ title: 'Session is asking a question',
61
+ message: sanitizeAttentionText(question(data) ?? 'Open the session to respond.', 2000),
62
+ ...(sourceId ? { sourceId: sourceId.slice(0, 200) } : {}),
63
+ };
64
+ }
65
+ return observation;
66
+ }
67
+ //# sourceMappingURL=attention-events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-events.js","sourceRoot":"","sources":["../src/attention-events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,39 @@
1
+ import { type Activity, type AttentionItem } from './attention-events.js';
2
+ export interface AttentionPage {
3
+ events: AttentionItem[];
4
+ cursor: string;
5
+ hasMore: boolean;
6
+ cursorReset: boolean;
7
+ activity: Activity;
8
+ openCount: number;
9
+ latest?: AttentionItem;
10
+ }
11
+ export interface AttentionReadOptions {
12
+ cursor?: string;
13
+ limit?: number;
14
+ includeResolved?: boolean;
15
+ actionableOnly?: boolean;
16
+ }
17
+ export declare class AttentionError extends Error {
18
+ readonly code: string;
19
+ readonly status: number;
20
+ constructor(code: string, message: string, status?: number);
21
+ }
22
+ /** Single-process, serialized snapshots. Missing/corrupt files never masquerade as recovered history. */
23
+ export declare class AttentionStore {
24
+ private resolveDir;
25
+ private resolvedRetention;
26
+ private locks;
27
+ private listeners;
28
+ constructor(resolveDir: (sessionId: string) => string, resolvedRetention?: number);
29
+ private withLock;
30
+ private load;
31
+ private save;
32
+ private cursor;
33
+ private parseCursor;
34
+ private prune;
35
+ record(id: string, eventName: string, data: Record<string, unknown>): Promise<void>;
36
+ list(id: string, options?: AttentionReadOptions): Promise<AttentionPage>;
37
+ mutate(id: string, ids: string[], operation: 'ack' | 'resolve', reason?: string): Promise<AttentionItem[]>;
38
+ wait(id: string, options: AttentionReadOptions, waitSeconds: number, signal?: AbortSignal): Promise<AttentionPage>;
39
+ }
@@ -0,0 +1,234 @@
1
+ import { open, readFile, rename, rm } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import { z } from 'zod';
5
+ import { classifyAttentionHook, sanitizeAttentionText } from './attention-events.js';
6
+ const itemSchema = z.object({
7
+ id: z.string(), sequence: z.number().int().positive(),
8
+ type: z.enum(['permission_prompt', 'elicitation_dialog', 'agent_needs_input', 'ask_user']),
9
+ title: z.string().max(200), message: z.string().max(2000),
10
+ createdAt: z.string(), lastSeenAt: z.string(), occurrences: z.number().int().positive(),
11
+ acknowledgedAt: z.string().optional(), resolvedAt: z.string().optional(),
12
+ resolution: z.enum(['manual', 'user_prompt', 'elicitation_complete', 'session_end']).optional(),
13
+ fingerprint: z.string(), sourceId: z.string().optional(), reason: z.string().optional(),
14
+ }).strict();
15
+ const ledgerSchema = z.object({
16
+ version: z.literal(1), epoch: z.string().uuid(), sequence: z.number().int().nonnegative(),
17
+ prunedThrough: z.number().int().nonnegative(),
18
+ activity: z.object({ state: z.enum(['thinking', 'tool_use', 'idle', 'unknown']), lastActivityAt: z.string().optional() }).strict(),
19
+ events: z.array(itemSchema),
20
+ }).strict();
21
+ export class AttentionError extends Error {
22
+ code;
23
+ status;
24
+ constructor(code, message, status = 400) {
25
+ super(message);
26
+ this.code = code;
27
+ this.status = status;
28
+ }
29
+ }
30
+ const publicItem = ({ fingerprint: _fingerprint, sourceId: _sourceId, reason: _reason, ...item }) => item;
31
+ /** Single-process, serialized snapshots. Missing/corrupt files never masquerade as recovered history. */
32
+ export class AttentionStore {
33
+ resolveDir;
34
+ resolvedRetention;
35
+ locks = new Map();
36
+ listeners = new Map();
37
+ constructor(resolveDir, resolvedRetention = 500) {
38
+ this.resolveDir = resolveDir;
39
+ this.resolvedRetention = resolvedRetention;
40
+ }
41
+ withLock(id, fn) {
42
+ if (!/^[a-zA-Z0-9_-]+$/.test(id))
43
+ return Promise.reject(new AttentionError('invalid_session', 'Invalid session ID'));
44
+ const previous = this.locks.get(id) ?? Promise.resolve();
45
+ const next = previous.then(fn, fn);
46
+ const settled = next.catch(() => undefined);
47
+ this.locks.set(id, settled);
48
+ void settled.then(() => { if (this.locks.get(id) === settled)
49
+ this.locks.delete(id); });
50
+ return next;
51
+ }
52
+ async load(id) {
53
+ try {
54
+ return ledgerSchema.parse(JSON.parse(await readFile(join(this.resolveDir(id), 'attention.json'), 'utf8')));
55
+ }
56
+ catch (error) {
57
+ if (error.code === 'ENOENT') {
58
+ const ledger = { version: 1, epoch: randomUUID(), sequence: 0, prunedThrough: 0, activity: { state: 'unknown' }, events: [] };
59
+ await this.save(id, ledger);
60
+ return ledger;
61
+ }
62
+ throw new AttentionError('attention_storage_error', 'Attention history could not be read', 500);
63
+ }
64
+ }
65
+ async save(id, ledger) {
66
+ const dir = this.resolveDir(id);
67
+ const file = join(dir, 'attention.json');
68
+ const tmp = `${file}.${randomUUID()}.tmp`;
69
+ try {
70
+ // Never mkdir here: deleting a session must not be undone by an in-flight hook.
71
+ const handle = await open(tmp, 'wx', 0o600);
72
+ try {
73
+ await handle.writeFile(JSON.stringify(ledger), 'utf8');
74
+ await handle.sync();
75
+ }
76
+ finally {
77
+ await handle.close();
78
+ }
79
+ await rename(tmp, file);
80
+ const directory = await open(dir, 'r');
81
+ try {
82
+ await directory.sync();
83
+ }
84
+ finally {
85
+ await directory.close();
86
+ }
87
+ }
88
+ catch {
89
+ await rm(tmp, { force: true }).catch(() => undefined);
90
+ throw new AttentionError('attention_storage_error', 'Attention history could not be persisted', 500);
91
+ }
92
+ }
93
+ cursor(id, ledger, sequence) {
94
+ return Buffer.from(JSON.stringify({ v: 1, session: id, epoch: ledger.epoch, sequence })).toString('base64url');
95
+ }
96
+ parseCursor(id, cursor) {
97
+ try {
98
+ if (cursor.length > 1000 || !/^[A-Za-z0-9_-]+$/.test(cursor))
99
+ throw new Error();
100
+ const value = z.object({ v: z.literal(1), session: z.literal(id), epoch: z.string().uuid(), sequence: z.number().int().nonnegative() }).strict().parse(JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')));
101
+ return value;
102
+ }
103
+ catch {
104
+ throw new AttentionError('invalid_cursor', 'Cursor is malformed or belongs to another session');
105
+ }
106
+ }
107
+ prune(ledger) {
108
+ const resolved = ledger.events.filter((event) => event.resolvedAt).sort((a, b) => b.sequence - a.sequence);
109
+ const drop = new Set(resolved.slice(this.resolvedRetention).map((event) => event.id));
110
+ for (const event of ledger.events)
111
+ if (drop.has(event.id))
112
+ ledger.prunedThrough = Math.max(ledger.prunedThrough, event.sequence);
113
+ ledger.events = ledger.events.filter((event) => !drop.has(event.id));
114
+ }
115
+ async record(id, eventName, data) {
116
+ const observation = classifyAttentionHook(eventName, data);
117
+ if (!observation.activity && !observation.attention && !observation.resolve)
118
+ return;
119
+ await this.withLock(id, async () => {
120
+ const ledger = await this.load(id);
121
+ const timestamp = new Date().toISOString();
122
+ if (observation.activity)
123
+ ledger.activity = { state: observation.activity, lastActivityAt: timestamp };
124
+ if (observation.resolve)
125
+ for (const event of ledger.events) {
126
+ if (event.resolvedAt || (observation.resolve === 'elicitation_complete' && event.type !== 'elicitation_dialog'))
127
+ continue;
128
+ event.resolvedAt = timestamp;
129
+ event.resolution = observation.resolve;
130
+ }
131
+ let created = false;
132
+ if (observation.attention) {
133
+ const { sourceId: rawSourceId, ...attention } = observation.attention;
134
+ const sourceId = rawSourceId ? createHash('sha256').update(rawSourceId).digest('hex') : undefined;
135
+ const fingerprint = createHash('sha256').update(JSON.stringify(attention)).digest('hex');
136
+ const existing = ledger.events.find((event) => !event.resolvedAt && (sourceId ? event.sourceId === sourceId : event.fingerprint === fingerprint));
137
+ if (existing) {
138
+ existing.lastSeenAt = timestamp;
139
+ existing.occurrences++;
140
+ }
141
+ else {
142
+ ledger.events.push({ ...attention, id: randomUUID(), sequence: ++ledger.sequence, createdAt: timestamp, lastSeenAt: timestamp, occurrences: 1, fingerprint, ...(sourceId ? { sourceId } : {}) });
143
+ created = true;
144
+ }
145
+ }
146
+ this.prune(ledger);
147
+ await this.save(id, ledger);
148
+ if (created)
149
+ for (const listener of this.listeners.get(id) ?? [])
150
+ listener();
151
+ });
152
+ }
153
+ async list(id, options = {}) {
154
+ return this.withLock(id, async () => {
155
+ const ledger = await this.load(id);
156
+ const incoming = options.cursor ? this.parseCursor(id, options.cursor) : undefined;
157
+ const cursorReset = !!incoming && (incoming.epoch !== ledger.epoch || incoming.sequence < ledger.prunedThrough || incoming.sequence > ledger.sequence);
158
+ const after = incoming && !cursorReset ? incoming.sequence : 0;
159
+ const limit = Math.min(100, Math.max(1, options.limit ?? 50));
160
+ const eligible = ledger.events.filter((event) => event.sequence > after && (options.includeResolved || !event.resolvedAt) && (!options.actionableOnly || (!event.resolvedAt && !event.acknowledgedAt))).sort((a, b) => a.sequence - b.sequence);
161
+ const events = eligible.slice(0, limit);
162
+ const hasMore = eligible.length > events.length;
163
+ // Do not skip records omitted by this page; filtered-out rows do not block later actionable events.
164
+ const sequence = hasMore ? events.at(-1).sequence : ledger.sequence;
165
+ return { events: events.map(publicItem), cursor: this.cursor(id, ledger, sequence), hasMore, cursorReset, activity: { ...ledger.activity }, openCount: ledger.events.filter((event) => !event.resolvedAt).length, ...(ledger.events.filter((event) => !event.resolvedAt).at(-1) ? { latest: publicItem(ledger.events.filter((event) => !event.resolvedAt).at(-1)) } : {}) };
166
+ });
167
+ }
168
+ async mutate(id, ids, operation, reason) {
169
+ return this.withLock(id, async () => {
170
+ const ledger = await this.load(id);
171
+ const wanted = [...new Set(ids)];
172
+ if (!wanted.length || wanted.length > 100 || wanted.some((eventId) => !ledger.events.some((event) => event.id === eventId))) {
173
+ throw new AttentionError('unknown_attention', 'One or more attention IDs are unknown');
174
+ }
175
+ const timestamp = new Date().toISOString();
176
+ const result = wanted.map((eventId) => ledger.events.find((event) => event.id === eventId));
177
+ for (const event of result) {
178
+ if (operation === 'ack' && !event.acknowledgedAt)
179
+ event.acknowledgedAt = timestamp;
180
+ if (operation === 'resolve' && !event.resolvedAt) {
181
+ event.resolvedAt = timestamp;
182
+ event.resolution = 'manual';
183
+ if (reason)
184
+ event.reason = sanitizeAttentionText(reason, 500);
185
+ }
186
+ }
187
+ const output = result.map(publicItem);
188
+ this.prune(ledger);
189
+ await this.save(id, ledger);
190
+ return output;
191
+ });
192
+ }
193
+ async wait(id, options, waitSeconds, signal) {
194
+ const deadline = Date.now() + Math.min(25, Math.max(0, waitSeconds)) * 1000;
195
+ // Install before the first read so a committed event cannot fall between read and listener setup.
196
+ let notify;
197
+ const listeners = this.listeners.get(id) ?? new Set();
198
+ const listener = () => notify?.();
199
+ listeners.add(listener);
200
+ this.listeners.set(id, listeners);
201
+ try {
202
+ while (true) {
203
+ if (signal?.aborted)
204
+ throw new AttentionError('cancelled', 'Attention wait cancelled', 499);
205
+ let wake;
206
+ const change = new Promise((resolve) => { wake = resolve; });
207
+ notify = wake;
208
+ const page = await this.list(id, { ...options, actionableOnly: true, includeResolved: false });
209
+ if (page.events.length || Date.now() >= deadline)
210
+ return page;
211
+ let timer;
212
+ const cancel = () => wake();
213
+ try {
214
+ const timeout = new Promise((resolve) => { timer = setTimeout(resolve, Math.max(0, deadline - Date.now())); });
215
+ signal?.addEventListener('abort', cancel, { once: true });
216
+ if (signal?.aborted)
217
+ wake();
218
+ await Promise.race([change, timeout]);
219
+ }
220
+ finally {
221
+ if (timer)
222
+ clearTimeout(timer);
223
+ signal?.removeEventListener('abort', cancel);
224
+ }
225
+ }
226
+ }
227
+ finally {
228
+ listeners.delete(listener);
229
+ if (!listeners.size)
230
+ this.listeners.delete(id);
231
+ }
232
+ }
233
+ }
234
+ //# sourceMappingURL=attention-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"attention-store.js","sourceRoot":"","sources":["../src/attention-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ /** Loopback advert embedded in the Centrifugo connection JWT `info` claim (L2). */
2
+ export interface BridgeLocalAdvert {
3
+ localPort: number;
4
+ localNonce: string;
5
+ }
6
+ export interface BridgeAuthResponse {
7
+ token: string;
8
+ refreshToken: string;
9
+ centrifugoUrl: string;
10
+ userId: string;
11
+ }
12
+ export declare function fetchBridgeToken(apiUrl: string, authToken: string, bridgeId: string, local: BridgeLocalAdvert): Promise<BridgeAuthResponse>;
13
+ /**
14
+ * Exchange a (rotating) refresh token for a fresh Centrifugo connect token.
15
+ *
16
+ * The server rotates the refresh token on every use (audit finding F3): the
17
+ * response carries a NEW refreshToken that supersedes the one just sent, so the
18
+ * caller must persist it and send the newest value next time. Reusing an old
19
+ * refresh token is rejected.
20
+ */
21
+ export declare function refreshBridgeToken(apiUrl: string, refreshToken: string, bridgeId: string, local: BridgeLocalAdvert): Promise<BridgeAuthResponse>;
@@ -0,0 +1,56 @@
1
+ import { hostname as osHostname } from 'node:os';
2
+ import { UnauthorizedError } from 'centrifuge';
3
+ export async function fetchBridgeToken(apiUrl, authToken, bridgeId, local) {
4
+ const res = await fetch(`${apiUrl}/api/auth/bridge`, {
5
+ method: 'POST',
6
+ headers: { 'Content-Type': 'application/json' },
7
+ body: JSON.stringify({
8
+ token: authToken,
9
+ bridgeId,
10
+ hostname: osHostname(),
11
+ // Embedded in the Centrifugo connection JWT `info` claim so the owning
12
+ // user's clients discover the loopback WS rung via presence (L2).
13
+ localPort: local.localPort,
14
+ localNonce: local.localNonce,
15
+ }),
16
+ });
17
+ if (!res.ok) {
18
+ const body = await res.text();
19
+ throw new Error(`Bridge auth failed (${res.status}): ${body}`);
20
+ }
21
+ return res.json();
22
+ }
23
+ /**
24
+ * Exchange a (rotating) refresh token for a fresh Centrifugo connect token.
25
+ *
26
+ * The server rotates the refresh token on every use (audit finding F3): the
27
+ * response carries a NEW refreshToken that supersedes the one just sent, so the
28
+ * caller must persist it and send the newest value next time. Reusing an old
29
+ * refresh token is rejected.
30
+ */
31
+ export async function refreshBridgeToken(apiUrl, refreshToken, bridgeId, local) {
32
+ const res = await fetch(`${apiUrl}/api/auth/bridge/refresh`, {
33
+ method: 'POST',
34
+ headers: { 'Content-Type': 'application/json' },
35
+ body: JSON.stringify({
36
+ refreshToken,
37
+ bridgeId,
38
+ hostname: osHostname(),
39
+ localPort: local.localPort,
40
+ localNonce: local.localNonce,
41
+ }),
42
+ });
43
+ if (!res.ok) {
44
+ const body = await res.text();
45
+ const message = `Token refresh failed (${res.status}): ${body}`;
46
+ // A rejected bridge credential cannot become valid through retrying.
47
+ // centrifuge-js only stops its token retry loop for UnauthorizedError;
48
+ // ordinary errors are treated as transient and retried indefinitely.
49
+ if (res.status === 401) {
50
+ throw new UnauthorizedError(message);
51
+ }
52
+ throw new Error(message);
53
+ }
54
+ return res.json();
55
+ }
56
+ //# sourceMappingURL=bridge-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bridge-auth.js","sourceRoot":"","sources":["../src/bridge-auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,IAAI,UAAU,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAe/C,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,MAAc,EACd,SAAiB,EACjB,QAAgB,EAChB,KAAwB;IAExB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,kBAAkB,EAAE;QACnD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,SAAS;YAChB,QAAQ;YACR,QAAQ,EAAE,UAAU,EAAE;YACtB,uEAAuE;YACvE,kEAAkE;YAClE,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,uBAAuB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,EAAiC,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,MAAc,EACd,YAAoB,EACpB,QAAgB,EAChB,KAAwB;IAExB,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,0BAA0B,EAAE;QAC3D,MAAM,EAAE,MAAM;QACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;QAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,YAAY;YACZ,QAAQ;YACR,QAAQ,EAAE,UAAU,EAAE;YACtB,SAAS,EAAE,KAAK,CAAC,SAAS;YAC1B,UAAU,EAAE,KAAK,CAAC,UAAU;SAC7B,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,yBAAyB,GAAG,CAAC,MAAM,MAAM,IAAI,EAAE,CAAC;QAChE,qEAAqE;QACrE,uEAAuE;QACvE,qEAAqE;QACrE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YACvB,MAAM,IAAI,iBAAiB,CAAC,OAAO,CAAC,CAAC;QACvC,CAAC;QACD,MAAM,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC;IAC3B,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,EAAiC,CAAC;AACnD,CAAC"}
@@ -0,0 +1,44 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { Command, CommandResponse } from './types.js';
3
+ export type BrowserBootstrap = {
4
+ version: 1;
5
+ userId: string;
6
+ bridgeId: string;
7
+ hostname: string;
8
+ localPort: number;
9
+ localNonce: string;
10
+ };
11
+ type Options = {
12
+ cloudNonce?: string;
13
+ dataDir: string;
14
+ allowedOrigins: string[];
15
+ bootstrap: () => BrowserBootstrap;
16
+ execute: (command: Command) => Promise<CommandResponse>;
17
+ onRevoke?: () => void;
18
+ };
19
+ /** Loopback browser consent and control. No cloud dependency and no raw persisted tokens. */
20
+ export declare class BrowserAccess {
21
+ private readonly opts;
22
+ private devices;
23
+ private pairings;
24
+ private rates;
25
+ private commands;
26
+ private events;
27
+ private eventBytes;
28
+ private sequence;
29
+ private waiters;
30
+ private windowExpires;
31
+ private closed;
32
+ private readonly file;
33
+ constructor(opts: Options);
34
+ private persist;
35
+ private device;
36
+ authorize(token: string, origin: string): boolean;
37
+ private sweep;
38
+ private send;
39
+ private body;
40
+ handle(req: IncomingMessage, res: ServerResponse, admin: boolean): Promise<void>;
41
+ publish(channel: string, data: unknown): void;
42
+ close(): void;
43
+ }
44
+ export {};