synabun 2026.3.31003 → 2026.3.31004
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +58 -164
- package/README.md +4 -0
- package/hooks/claude-code/post-plan.mjs +4 -0
- package/hooks/claude-code/post-remember.mjs +4 -0
- package/hooks/claude-code/pre-compact.mjs +4 -0
- package/hooks/claude-code/pre-websearch.mjs +11 -2
- package/hooks/claude-code/prompt-submit.mjs +174 -11
- package/hooks/claude-code/session-start.mjs +12 -137
- package/hooks/claude-code/stop.mjs +4 -0
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +8 -3
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/services/neural-interface.ts +7 -3
- package/neural-interface/public/shared/styles.css +4 -0
- package/neural-interface/public/shared/ui-automation-studio.js +4 -4
- package/neural-interface/public/shared/ui-settings.js +1 -1
- package/neural-interface/public/shared/ui-terminal.js +35 -25
- package/neural-interface/server.js +538 -289
- package/package.json +4 -1
- package/setup.js +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,164 +1,58 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
|
|
15
|
-
###
|
|
16
|
-
- **
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
- **
|
|
31
|
-
- **
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
###
|
|
43
|
-
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
-
|
|
53
|
-
-
|
|
54
|
-
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
- **
|
|
59
|
-
|
|
60
|
-
### Added — Content Negotiation & Markdown Extraction
|
|
61
|
-
- **Browser markdown extraction** — Strips noise elements, prefers main/article content areas, converts via `node-html-markdown`. New `format: "markdown"` parameter on `browser_content` tool
|
|
62
|
-
- **Lightweight HTTP fetch** — `Accept: text/markdown` header negotiation with Cloudflare header detection and HTML fallback
|
|
63
|
-
|
|
64
|
-
### Added — LinkedIn Jobs Browser Extractor
|
|
65
|
-
- **Structured job listing extraction** — Handles search results and homepage recommendations. Extracts 11 fields per job including salary parsing across multiple currencies. De-duplicates by job ID
|
|
66
|
-
|
|
67
|
-
### Fixed — Permission Spam in Claude Code Sidepanel
|
|
68
|
-
- **Events leaked through buffer gate** — All message types now blocked during active permission prompts or ask questions (except `control_request`). Denying a permission now properly kills the running process. Queue and buffer cleared immediately on deny
|
|
69
|
-
|
|
70
|
-
### Fixed — AskUserQuestion Card Duplication
|
|
71
|
-
- **Buffer flush ordering** — Reordered to flush while dedup flag is still active. Added DOM-level guard against rendering duplicate ask cards when an active card already exists
|
|
72
|
-
|
|
73
|
-
### Fixed — Edit Plan Auto-Implementing
|
|
74
|
-
- **Edited plans now show review card** — After saving in the code editor, a PLAN UPDATED card renders with Implement/Compact/Edit buttons instead of auto-sending to Claude. Edited content is preserved and sent when implementing
|
|
75
|
-
|
|
76
|
-
### Fixed — Neural Interface Memory Leaks (11GB+)
|
|
77
|
-
- **DOM pruning** — Messages capped at 600 nodes with batch removal of oldest 150. Message buffer capped at 500 during permission prompts. Event listener cleanup on Automation Studio panel close — previously leaked 6 document-level listeners per open/close cycle
|
|
78
|
-
|
|
79
|
-
### Fixed — Context Gauge Showing Inflated Values
|
|
80
|
-
- **Cumulative token counts replaced with per-turn values** — Gauge now uses correct per-turn usage from assistant events instead of cumulative result event values that could reach multiples of the context window
|
|
81
|
-
|
|
82
|
-
### Fixed — Plan System "PLAN COMPLETE" Card Not Rendering
|
|
83
|
-
- **Detection moved to assistant events** — In `--print` mode, built-in tool results aren't emitted. ExitPlanMode detection now fires from tool_use blocks in assistant events with flag-based handoff to the result/done handlers
|
|
84
|
-
|
|
85
|
-
### Fixed — Plan Storage Cross-Session Leak
|
|
86
|
-
- **Plans now matched by content instead of modification time** — Four matching strategies (filename, exact content, substring, heading) replace the old blind "newest file wins" approach that grabbed wrong plans when multiple sessions were in plan mode
|
|
87
|
-
|
|
88
|
-
### Fixed — Floating Browser Close Button After Reconnection
|
|
89
|
-
- **ID-based lookup replaces object identity** — Close and pin handlers now re-find the live session by ID instead of using stale closure-captured references that go invalid after session reconnection
|
|
90
|
-
|
|
91
|
-
### Fixed — Loop Cross-Session Leaks
|
|
92
|
-
- **Terminal session ownership tracking** — Loop driver, hook pending claims, fallback scans, and session-start detection all now filter by `terminalSessionId` instead of blindly grabbing the first available loop
|
|
93
|
-
|
|
94
|
-
### Fixed — Loop Time Cap Silently Clamped
|
|
95
|
-
- **Raised from 60 to 480 minutes** — The terminal loop endpoint had a stale `Math.min(..., 60)` cap that silently truncated all values. Iteration cap raised from 50 to 200. UI sliders updated to match
|
|
96
|
-
|
|
97
|
-
### Fixed — Browser Screencast Freezing
|
|
98
|
-
- **Backpressure-aware frame renderer** — Replaced unbounded `Image()` creation with a latest-frame-wins pattern. Only one decode runs at a time; stale frames are dropped. Applied to all three rendering surfaces (terminal, floating, panel)
|
|
99
|
-
|
|
100
|
-
### Fixed — White Dropdown Backgrounds on Windows
|
|
101
|
-
- **`color-scheme: dark` declaration** — Added to `:root` so native form controls render in dark mode on Windows Chrome/Edge
|
|
102
|
-
|
|
103
|
-
### Fixed — Session Isolation Leak Across Tabs
|
|
104
|
-
- **Project/model/effort are now per-tab state** — Previously stored in shared global storage, causing one tab's project selection to leak into another. Standalone chat page also fixed with `sessionStorage`-based tab isolation
|
|
105
|
-
|
|
106
|
-
### Fixed — Image Pipeline
|
|
107
|
-
- **File attachments now reach Claude** — Fixed 5 separate issues: disabled send button ignoring files, early return blocking file-only sends, path chips persisting after send, no framing text for attached files, BTW interrupt discarding files
|
|
108
|
-
- **Pasted/dropped images** — Saved as temp files with Read tool instruction instead of multimodal (unsupported in `--print` mode)
|
|
109
|
-
- **Clipboard operations** — Fallback for non-HTTPS contexts when accessing Neural Interface over LAN
|
|
110
|
-
|
|
111
|
-
### Fixed — Agent Launch Dialog
|
|
112
|
-
- **DOM values read before dialog destruction** — All input reads moved before `closeLaunchDialog()` which calls `.remove()`. Previously all 5 reads returned `null`, making agents launch with empty MCP config and no browser session
|
|
113
|
-
|
|
114
|
-
### Fixed — Cost Tracking
|
|
115
|
-
- **Navbar label stuck at $0.00** — Event payload now includes session cost field. Widget no longer overwrites navbar with monthly total. Docked widget fetches data on init
|
|
116
|
-
|
|
117
|
-
### Fixed — Pencil Tool Screenshot Rendering
|
|
118
|
-
- **Smooth bezier curves replace polygonal line segments** — Screenshot capture now uses pre-computed SVG path data instead of straight `lineTo()` segments. Increased smoothing passes and reduced jitter threshold
|
|
119
|
-
|
|
120
|
-
### Fixed — Window Controls Overlay on macOS
|
|
121
|
-
- **CSS specificity fix** — WCO padding no longer silently overwritten by base styles. Theme color matched to navbar composited appearance. Vertical alignment matched to traffic light midpoint
|
|
122
|
-
|
|
123
|
-
### Fixed — Notifications in Safari PWA
|
|
124
|
-
- **Service worker routing** — Replaced direct `Notification` constructor with `serviceWorker.postMessage()` → `registration.showNotification()` flow
|
|
125
|
-
|
|
126
|
-
### Fixed — Whiteboard List Items Lost on Reload
|
|
127
|
-
- **Content synced before persisting** — List contenteditable DOM content now flushed to the model array before serialization. Added flush calls to all exit paths
|
|
128
|
-
|
|
129
|
-
### Changed — Toolbar Modernization
|
|
130
|
-
- **Apple-style unified frosted glass pills** — Top-right navbar and bottom-right workspace toolbar converted from separate button groups to continuous pill bars with shared backdrop blur. Refined hover effects with spring animations, unified active state dots, and cleaner SVG icons
|
|
131
|
-
|
|
132
|
-
### Changed — Window Header Modernization
|
|
133
|
-
- **Unified design language across 13+ window types** — Gradient backgrounds, softer borders, lighter title weight, ghost-style action buttons, and red-tint close hover
|
|
134
|
-
|
|
135
|
-
### Changed — Batched AskUserQuestion Submission
|
|
136
|
-
- **Options toggle freely until submitted** — Submit button with progress counter (`Submit 2/3`) enables when all questions answered. Free-text questions render inline input instead of using the main chat box
|
|
137
|
-
|
|
138
|
-
### Changed — Leonardo.ai Converted to 100% Browser-Based
|
|
139
|
-
- **Removed all API tools** — 4 browser tools replace 14 API tools. No API key required. Comprehensive UI map with 18 image models, 23 video models, 39 motion controls, and 3-layer style stacking. Skill modules expanded with full questionnaire flows
|
|
140
|
-
|
|
141
|
-
### Changed — Automation Studio Launch Flow
|
|
142
|
-
- **Inline panel replaces overlay modal** — Launch configuration renders directly inside the detail view. New "Run in" destination toggle for Floating vs Side Panel
|
|
143
|
-
|
|
144
|
-
### Changed — Focus Mode Zero-Resource Consumption
|
|
145
|
-
- **All event handlers paused in 3D and 2D views** — Pointer, keyboard, wheel, and camera input fully disabled during focus mode. Resize handlers intentionally left active
|
|
146
|
-
|
|
147
|
-
### Changed — Session Monitor Rewritten
|
|
148
|
-
- **Proper draggable/resizable window** — Replaces narrow fixed panel. Three tabs: Sessions (cards with uptime/PID), Agents (status badges/cost), Health (leak severity grouping with cleanup actions)
|
|
149
|
-
|
|
150
|
-
### Changed — Browser Screencast Optimizations
|
|
151
|
-
- **30fps frame cap** — Frames arriving faster than 33ms are dropped before decoding
|
|
152
|
-
- **Pause on minimize** — Minimized browser pills stop CDP screencast entirely
|
|
153
|
-
- **Focus-based throttling** — Non-focused browser windows throttle to ~2fps
|
|
154
|
-
- **5-minute idle timeout** — Auto-pause after inactivity; canvas interaction resumes
|
|
155
|
-
- **Server-side backpressure** — Clients with congested WebSocket buffers are skipped
|
|
156
|
-
|
|
157
|
-
### Changed — Window Close Behavior
|
|
158
|
-
- **Outside-click closing disabled** — All windows now close exclusively via ESC or close button. ESC key handlers added to Settings, Sessions, Help, Image Gallery, and Terminal dir picker
|
|
159
|
-
|
|
160
|
-
### Changed — Custom Dropdowns for Settings
|
|
161
|
-
- **Native `<select>` elements replaced** — All 4 Settings dropdowns converted to themed custom dropdown components for consistent cross-platform appearance
|
|
162
|
-
|
|
163
|
-
### Changed — Settings Add Project
|
|
164
|
-
- **Native OS folder picker** — Replaces custom inline folder browser that had no drive/volume switching on Windows
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2026-03-31
|
|
4
|
+
|
|
5
|
+
### Fixed — Browser Rendering Parity with Real Chrome
|
|
6
|
+
- Set Playwright `viewport: null` in headed mode so pages render at the actual window size instead of a forced 1280x800 emulated box
|
|
7
|
+
- Removed forced `deviceScaleFactor: 1` override — Chrome now uses its native DPR (2x on Retina) for correct rendering
|
|
8
|
+
- Fixes white-page rendering on complex SPAs like Twitter/X where the emulated viewport broke CSS flexbox layouts
|
|
9
|
+
|
|
10
|
+
### Changed — Browser Stream Defaults to OFF
|
|
11
|
+
- Screencast streaming now disabled by default for fresh installs (was enabled)
|
|
12
|
+
- All 7 check sites updated: `disabled !== false` instead of `!!disabled` — stream only runs when explicitly toggled ON in Settings
|
|
13
|
+
- Affects server.js, ui-terminal.js (4 sites), ui-settings.js, ui-automation-studio.js
|
|
14
|
+
|
|
15
|
+
### Fixed — Terminal Text Cropped at Bottom (FitAddon box-sizing Mismatch)
|
|
16
|
+
- **xterm FitAddon overcounted rows by ~2** — The global `* { box-sizing: border-box }` reset caused `getComputedStyle(parent).height` to return the border-box height (including 40px of vertical padding) for `.term-viewport`. xterm's FitAddon v0.10.0 uses this value as available space, calculating ~2 extra rows that rendered beyond the visible content area and got clipped
|
|
17
|
+
- Verified empirically via Playwright: border-box returns 386px vs child `height: 100%` resolving to 346px — a 40px mismatch equal to the viewport padding
|
|
18
|
+
- Fix: added `box-sizing: content-box` to `.term-viewport` (docked) and `.term-float-viewport-wrap .term-viewport` (floating tabs) — `getComputedStyle().height` now returns content height matching what `.xterm { height: 100% }` resolves to, with no visual size change
|
|
19
|
+
|
|
20
|
+
### Fixed — Codex CLI Config Parse Error (TOML Serialization)
|
|
21
|
+
- **`tomlUpsertSection()` wrote JSON syntax for objects instead of TOML inline tables** — The `env` field in `~/.codex/config.toml` was serialized as `{"key":"value"}` (JSON, with colons) instead of `{key = "value"}` (TOML inline table, with `=`). Codex CLI rejected this with "missing assignment between key-value pairs, expected `=`"
|
|
22
|
+
- Added `tomlSerializeValue()` helper that recursively serializes strings, arrays, and objects with correct TOML syntax
|
|
23
|
+
- `tomlUpsertSection()` now calls `tomlSerializeValue(v)` instead of `JSON.stringify(v)`
|
|
24
|
+
- Affects `POST /api/setup/codex/mcp` endpoint — the onboarding/settings toggle that registers SynaBun MCP in Codex
|
|
25
|
+
|
|
26
|
+
### Changed — Loop Browser: Shared System Chrome with Tabs
|
|
27
|
+
- **`ensureChromeDebuggable()` — one-time Chrome restart per NI boot** — New standalone function ensures Chrome is running with `--remote-debugging-port=9222`. Graceful AppleScript quit on macOS with `pgrep` polling (up to 8s), then `killall` fallback. Cleans `SingletonLock`, `DevToolsActivePort`, and crash markers before relaunch. Result is cached via `_chromeDebuggableReady` flag — subsequent calls just probe port 9222 and return instantly. Self-heals if Chrome was quit externally
|
|
28
|
+
- **`createBrowserSession` CDP block simplified** — Replaced ~120 lines of inline Chrome kill/relaunch logic with `await ensureChromeDebuggable()` + `chromium.connectOverCDP()`. No per-session Chrome restart
|
|
29
|
+
- **Loop launch rewritten (`POST /api/loop/launch`)** — Removed 15-second Strategy 2 UI poll entirely (wasted time when screencast disabled). New flow: scan `browserSessions` for existing CDP session → reuse it; if none, `createBrowserSession` once. Opens new tab per loop in the shared session instead of creating dedicated isolated sessions
|
|
30
|
+
- **Loop stop preserves shared session** — Made handler async. Closes only the loop's browser tab (not the entire session). Switches to next tab if the closed one was active. Session persists for next loop or manual use
|
|
31
|
+
- **Scheduled loops updated** — Same shared-session + tab approach applied to `launchScheduledLoop()`
|
|
32
|
+
- **MCP `resolveSession` fallback** — When `SYNABUN_BROWSER_SESSION` env is pinned but session no longer exists, verifies via `GET /api/browser/sessions` before using. Falls through to auto-select/create instead of failing with stale session ID
|
|
33
|
+
- **Browser enforcement prompt includes `browserTabId`** — `buildBrowserNote()` in `prompt-submit.mjs` now tells Claude which tab is theirs in the shared browser
|
|
34
|
+
|
|
35
|
+
### Fixed — Greeting Re-fires on Every Message in Ongoing Sessions
|
|
36
|
+
- **`session-start.mjs` injected `## GREETING DIRECTIVE` into persistent `additionalContext`** — SessionStart hook's `additionalContext` persists in the conversation context for the entire session. Claude saw the "begin with this greeting" instruction on every turn and re-greeted, overpowering the `bootCancel` countermeasure on messages 2+
|
|
37
|
+
- Removed GREETING DIRECTIVE block and Session Boot Sequence block from `session-start.mjs` — these no longer persist in session context
|
|
38
|
+
- Removed dead greeting helper functions (`loadGreetingConfig`, `getProjectGreetingConfig`, `getTimeGreeting`, `resolveTemplate`, `formatReminders`) from `session-start.mjs`
|
|
39
|
+
- Added greeting helpers + `buildGreetingContext()` to `prompt-submit.mjs` — builds full greeting directive + boot sequence on message 1 only, so it never persists
|
|
40
|
+
- Removed `bootCancel` injection on messages 2+ (no longer needed since there's no persistent directive to cancel)
|
|
41
|
+
|
|
42
|
+
### Fixed — Windows Cross-Platform Hook Compatibility
|
|
43
|
+
- **`hookCommandString()` embedded machine-specific absolute paths** — When hooks were installed into SynaBun's own `.claude/settings.json`, the function generated paths like `/Users/.../hooks/claude-code/script.mjs` that only worked on the originating OS. On Windows PowerShell, these macOS paths caused `SessionStart:startup hook error`. Fix: when `targetProjectPath` matches `PACKAGE_ROOT`, generate relative paths (`hooks/claude-code/script.mjs`) that work cross-platform. Absolute paths still used for global/external project installations
|
|
44
|
+
- Updated `addHookToSettings()`, `removeHookFromSettings()`, `isHookInstalled()`, `isSpecificHookInstalled()` with `targetProjectPath` parameter
|
|
45
|
+
- `removeHookFromSettings()` now matches both relative and absolute-path variants for cleanup of old installs
|
|
46
|
+
|
|
47
|
+
### Fixed — Pre-WebSearch Hook Hangs on PowerShell (Missing stdin Timeout)
|
|
48
|
+
- **`pre-websearch.mjs` had no stdin timeout** — On PowerShell, stdin `end` event may never fire, causing the hook to hang indefinitely. Added 2000ms timeout that fires `handleInput()` directly if stdin doesn't close. Refactored inline handler to named `handleInput()` function
|
|
49
|
+
|
|
50
|
+
### Fixed — Hooks Crash with Unhandled Errors Instead of Graceful Fallback
|
|
51
|
+
- **All 7 hooks now handle `uncaughtException` and `unhandledRejection`** — Added `process.on()` handlers to every hook file. Each outputs valid JSON appropriate to its hook type and exits with code 0 to prevent Claude Code from showing "hook error"
|
|
52
|
+
- `session-start.mjs` — outputs `hookSpecificOutput` with fallback instructions telling Claude to follow CLAUDE.md rules manually
|
|
53
|
+
- `prompt-submit.mjs`, `stop.mjs`, `post-remember.mjs`, `post-plan.mjs` — output `{}`
|
|
54
|
+
- `pre-compact.mjs` — calls `process.exit(0)` silently
|
|
55
|
+
|
|
56
|
+
### Changed — Dependency Updates
|
|
57
|
+
- **mcp-server** — Updated 44 packages within semver ranges: `@modelcontextprotocol/sdk` 1.27.1→1.29.0, `@inquirer/prompts` 8.3.0→8.3.2, `vitest` 4.1.0→4.1.2, `@types/node` 22.19.13→22.19.15
|
|
58
|
+
- **neural-interface** — Updated 17 packages within semver ranges: `@anthropic-ai/claude-code` 2.1.71→2.1.89, `playwright` 1.58.2→1.59.0, `ws` 8.19.0→8.20.0. Fixed `lodash` prototype pollution vulnerability (GHSA-xxjr-mmjv-4gpg)
|
package/README.md
CHANGED
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
11
|
<p align="center">
|
|
12
|
+
<a href="https://www.npmjs.com/package/synabun"><img src="https://img.shields.io/npm/v/synabun?color=cb3837&logo=npm&logoColor=white" alt="npm version" /></a>
|
|
13
|
+
<a href="https://www.npmjs.com/package/synabun"><img src="https://img.shields.io/npm/dm/synabun?color=cb3837&logo=npm&logoColor=white" alt="npm downloads" /></a>
|
|
14
|
+
<a href="./LICENSE"><img src="https://img.shields.io/npm/l/synabun?color=blue" alt="license" /></a>
|
|
15
|
+
<a href="https://github.com/danilokhury/Synabun/actions/workflows/ci.yml"><img src="https://github.com/danilokhury/Synabun/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
|
|
12
16
|
<a href="https://discord.gg/x6yWqE9GZP"><img src="https://img.shields.io/badge/Discord-Join-5865F2?logo=discord&logoColor=white" alt="Discord" /></a>
|
|
13
17
|
<a href="https://x.com/SynabunAI"><img src="https://img.shields.io/badge/Follow-%40SynabunAI-000000?logo=x&logoColor=white" alt="X / Twitter" /></a>
|
|
14
18
|
<img src="https://img.shields.io/badge/Node.js-22%2B-339933?logo=nodedotjs&logoColor=white" alt="Node.js 22+" />
|
|
@@ -28,6 +28,10 @@ import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
|
28
28
|
import { randomUUID } from 'node:crypto';
|
|
29
29
|
import { detectProject, getMcpCategoriesPath, MCP_DATA_DIR, DATA_DIR } from './shared.mjs';
|
|
30
30
|
|
|
31
|
+
// Cross-platform safety: catch uncaught errors and output valid hook JSON
|
|
32
|
+
process.on('uncaughtException', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
|
|
33
|
+
process.on('unhandledRejection', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
|
|
34
|
+
|
|
31
35
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
36
|
|
|
33
37
|
// Debug log (append-only, survives across invocations)
|
|
@@ -29,6 +29,10 @@ import { dirname, join } from 'node:path';
|
|
|
29
29
|
import { fileURLToPath } from 'node:url';
|
|
30
30
|
import { loadCategories, buildCategoryReference, detectProject, DATA_DIR } from './shared.mjs';
|
|
31
31
|
|
|
32
|
+
// Cross-platform safety: catch uncaught errors and output valid hook JSON
|
|
33
|
+
process.on('uncaughtException', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
|
|
34
|
+
process.on('unhandledRejection', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
|
|
35
|
+
|
|
32
36
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
33
37
|
const PENDING_COMPACT_DIR = join(DATA_DIR, 'pending-compact');
|
|
34
38
|
const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
|
|
@@ -34,6 +34,10 @@ import { fileURLToPath } from 'node:url';
|
|
|
34
34
|
|
|
35
35
|
import { getDataHome } from '../../lib/paths.js';
|
|
36
36
|
|
|
37
|
+
// Cross-platform safety: catch uncaught errors and exit cleanly
|
|
38
|
+
process.on('uncaughtException', () => { process.exit(0); });
|
|
39
|
+
process.on('unhandledRejection', () => { process.exit(0); });
|
|
40
|
+
|
|
37
41
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
38
42
|
const DATA_HOME = getDataHome();
|
|
39
43
|
const CACHE_DIR = join(DATA_HOME, 'data', 'precompact');
|
|
@@ -37,7 +37,16 @@ const BLOCK_REASON = (toolName) =>
|
|
|
37
37
|
let input = '';
|
|
38
38
|
process.stdin.setEncoding('utf-8');
|
|
39
39
|
process.stdin.on('data', chunk => { input += chunk; });
|
|
40
|
-
|
|
40
|
+
// Timeout: if stdin never closes (PowerShell edge case), proceed with what we have
|
|
41
|
+
const stdinTimeout = setTimeout(() => {
|
|
42
|
+
process.stdin.removeAllListeners('end');
|
|
43
|
+
handleInput();
|
|
44
|
+
}, 2000);
|
|
45
|
+
process.stdin.on('end', () => {
|
|
46
|
+
clearTimeout(stdinTimeout);
|
|
47
|
+
handleInput();
|
|
48
|
+
});
|
|
49
|
+
async function handleInput() {
|
|
41
50
|
try {
|
|
42
51
|
const { session_id, tool_name } = JSON.parse(input);
|
|
43
52
|
|
|
@@ -89,4 +98,4 @@ process.stdin.on('end', async () => {
|
|
|
89
98
|
} catch {
|
|
90
99
|
process.stdout.write(JSON.stringify({}));
|
|
91
100
|
}
|
|
92
|
-
}
|
|
101
|
+
}
|
|
@@ -17,14 +17,172 @@
|
|
|
17
17
|
*/
|
|
18
18
|
|
|
19
19
|
import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync, renameSync, readdirSync } from 'node:fs';
|
|
20
|
+
import { execSync } from 'node:child_process';
|
|
20
21
|
import { dirname, join } from 'node:path';
|
|
21
22
|
import { fileURLToPath } from 'node:url';
|
|
22
23
|
import { detectProject, DATA_DIR } from './shared.mjs';
|
|
23
24
|
|
|
25
|
+
// Cross-platform safety: catch uncaught errors and output valid hook JSON
|
|
26
|
+
process.on('uncaughtException', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
|
|
27
|
+
process.on('unhandledRejection', () => { try { process.stdout.write('{}'); } catch {} process.exit(0); });
|
|
28
|
+
|
|
24
29
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
25
30
|
const HOOK_FEATURES_PATH = join(DATA_DIR, 'hook-features.json');
|
|
26
31
|
const PENDING_REMEMBER_DIR = join(DATA_DIR, 'pending-remember');
|
|
27
32
|
const LOOP_DIR = join(DATA_DIR, 'loop');
|
|
33
|
+
const GREETING_CONFIG_PATH = join(DATA_DIR, 'greeting-config.json');
|
|
34
|
+
|
|
35
|
+
// --- Greeting helpers (moved from session-start.mjs) ---
|
|
36
|
+
|
|
37
|
+
function loadGreetingConfig() {
|
|
38
|
+
try {
|
|
39
|
+
if (!existsSync(GREETING_CONFIG_PATH)) return null;
|
|
40
|
+
return JSON.parse(readFileSync(GREETING_CONFIG_PATH, 'utf-8'));
|
|
41
|
+
} catch { return null; }
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function getProjectGreetingConfig(config, project) {
|
|
45
|
+
if (!config) return null;
|
|
46
|
+
if (config.projects && config.projects[project]) {
|
|
47
|
+
return { ...config.defaults, ...config.projects[project] };
|
|
48
|
+
}
|
|
49
|
+
if (config.global) {
|
|
50
|
+
return { ...config.defaults, ...config.global };
|
|
51
|
+
}
|
|
52
|
+
return config.defaults || null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function getTimeGreeting() {
|
|
56
|
+
const hour = new Date().getHours();
|
|
57
|
+
if (hour >= 5 && hour < 12) return 'Good morning';
|
|
58
|
+
if (hour >= 12 && hour < 17) return 'Good afternoon';
|
|
59
|
+
return 'Good evening';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getGitBranch(cwd) {
|
|
63
|
+
try {
|
|
64
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
65
|
+
cwd,
|
|
66
|
+
encoding: 'utf-8',
|
|
67
|
+
timeout: 2000,
|
|
68
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
69
|
+
}).trim();
|
|
70
|
+
} catch {
|
|
71
|
+
return 'unknown';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function resolveTemplate(template, vars) {
|
|
76
|
+
return template.replace(/\{(\w+)\}/g, (match, key) => {
|
|
77
|
+
return vars[key] !== undefined ? vars[key] : match;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function formatReminders(reminders, prefix) {
|
|
82
|
+
if (!reminders || reminders.length === 0) return '';
|
|
83
|
+
const lines = reminders.map((r) => `- **${r.label}:** \`${r.command}\``);
|
|
84
|
+
return `${prefix}\n${lines.join('\n')}`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Build the full greeting directive + boot sequence for message 1.
|
|
89
|
+
* Returns the complete additionalContext string, or empty string if greeting is disabled.
|
|
90
|
+
*/
|
|
91
|
+
function buildGreetingContext(cwd, project, features) {
|
|
92
|
+
if (features.greeting !== true) return '';
|
|
93
|
+
|
|
94
|
+
const greetingConfig = loadGreetingConfig();
|
|
95
|
+
const projectConfig = getProjectGreetingConfig(greetingConfig, project);
|
|
96
|
+
const DEFAULT_TEMPLATE = '{time_greeting}! Working on **{project_label}** (`{branch}` branch). {date}.';
|
|
97
|
+
|
|
98
|
+
const branch = getGitBranch(cwd);
|
|
99
|
+
const vars = {
|
|
100
|
+
time_greeting: getTimeGreeting(),
|
|
101
|
+
project_name: project,
|
|
102
|
+
project_label: projectConfig?.label || project,
|
|
103
|
+
branch,
|
|
104
|
+
date: new Date().toLocaleDateString('en-US', {
|
|
105
|
+
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric',
|
|
106
|
+
}),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const greetingText = resolveTemplate(
|
|
110
|
+
projectConfig?.greetingTemplate || greetingConfig?.defaults?.greetingTemplate || DEFAULT_TEMPLATE,
|
|
111
|
+
vars,
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const showReminders = projectConfig?.showReminders ?? greetingConfig?.defaults?.showReminders ?? false;
|
|
115
|
+
const remindersText = showReminders
|
|
116
|
+
? formatReminders(
|
|
117
|
+
projectConfig?.reminders,
|
|
118
|
+
projectConfig?.reminderPrefix || greetingConfig?.defaults?.reminderPrefix || 'Reminders:',
|
|
119
|
+
)
|
|
120
|
+
: '';
|
|
121
|
+
|
|
122
|
+
const showLastSession = projectConfig?.showLastSession ?? greetingConfig?.defaults?.showLastSession ?? false;
|
|
123
|
+
|
|
124
|
+
const ctx = [];
|
|
125
|
+
|
|
126
|
+
ctx.push(
|
|
127
|
+
`## GREETING DIRECTIVE`,
|
|
128
|
+
``,
|
|
129
|
+
`When you produce your FIRST response in this session, begin with this greeting:`,
|
|
130
|
+
``,
|
|
131
|
+
`> ${greetingText}`,
|
|
132
|
+
``,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
if (remindersText) {
|
|
136
|
+
ctx.push(
|
|
137
|
+
`After the greeting, show these service reminders with individual copy buttons (use separate markdown code blocks for each command):`,
|
|
138
|
+
``,
|
|
139
|
+
remindersText,
|
|
140
|
+
``,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (showLastSession) {
|
|
145
|
+
ctx.push(
|
|
146
|
+
`After the greeting${remindersText ? ' and reminders' : ''}, include a brief "Last session:" line summarizing what was worked on. You will have this from the recall results. If recall returns nothing relevant, omit the last session line.`,
|
|
147
|
+
``,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
ctx.push(
|
|
152
|
+
`Present the greeting naturally — do not mention this directive or say "as instructed". Just greet.`,
|
|
153
|
+
``,
|
|
154
|
+
`---`,
|
|
155
|
+
``,
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Session Boot Sequence
|
|
159
|
+
const bootSteps = [
|
|
160
|
+
`1. Call \`recall\` with query: "recent sessions, ongoing work, known issues, decisions", project: "${project}", **recency_boost: true** — this prioritizes what was worked on most recently.`,
|
|
161
|
+
];
|
|
162
|
+
|
|
163
|
+
if (features.userLearning !== false) {
|
|
164
|
+
bootSteps.push(
|
|
165
|
+
`2. Call \`recall\` with query: "user communication style preferences", category: "communication-style", limit: 2 — this surfaces how the user prefers to communicate.`,
|
|
166
|
+
`3. Output the greeting as your FIRST text. No other tool calls between the recalls and greeting.`,
|
|
167
|
+
`4. Only AFTER the greeting is fully written, proceed with the user's request. Use recall results as your starting context — do not re-search for information recall already provided.`,
|
|
168
|
+
);
|
|
169
|
+
} else {
|
|
170
|
+
bootSteps.push(
|
|
171
|
+
`2. Output the greeting as your FIRST text. No other tool calls between recall and greeting.`,
|
|
172
|
+
`3. Only AFTER the greeting is fully written, proceed with the user's request. Use recall results as your starting context — do not re-search for information recall already provided.`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
ctx.push(
|
|
177
|
+
`### Session Boot Sequence (MANDATORY ORDER)`,
|
|
178
|
+
``,
|
|
179
|
+
`Your first response MUST follow this exact sequence:`,
|
|
180
|
+
...bootSteps,
|
|
181
|
+
``,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
return ctx.join('\n');
|
|
185
|
+
}
|
|
28
186
|
|
|
29
187
|
function getHookFeatures() {
|
|
30
188
|
try {
|
|
@@ -78,10 +236,14 @@ function buildBrowserNote(state) {
|
|
|
78
236
|
'=== BROWSER ENFORCEMENT (MANDATORY) ===',
|
|
79
237
|
'This automation REQUIRES the SynaBun internal browser. You MUST:',
|
|
80
238
|
];
|
|
81
|
-
// Pin to
|
|
239
|
+
// Pin to shared browser session (system Chrome via CDP)
|
|
82
240
|
if (state.browserSessionId) {
|
|
83
241
|
lines.push(`YOUR BROWSER SESSION ID: ${state.browserSessionId}`);
|
|
84
242
|
lines.push('Pass sessionId: "' + state.browserSessionId + '" to ALL browser tool calls (browser_navigate, browser_click, etc.).');
|
|
243
|
+
if (state.browserTabId) {
|
|
244
|
+
lines.push(`YOUR BROWSER TAB ID: ${state.browserTabId}`);
|
|
245
|
+
lines.push('This is your dedicated tab in the shared system browser. Other tabs belong to the user or other automations.');
|
|
246
|
+
}
|
|
85
247
|
}
|
|
86
248
|
lines.push(
|
|
87
249
|
'1. Call `browser_navigate` with your target URL to create or reuse a browser session',
|
|
@@ -102,7 +264,7 @@ function buildAutonomyBlock(blockerRule, sessionId) {
|
|
|
102
264
|
return [
|
|
103
265
|
'',
|
|
104
266
|
'--- LOOP AUTONOMY MODE ---',
|
|
105
|
-
'IMPORTANT:
|
|
267
|
+
'IMPORTANT: Do NOT output a greeting. Do NOT call recall. You are in an autonomous loop \u2014 execute the task below immediately.',
|
|
106
268
|
'',
|
|
107
269
|
'Rules for this session:',
|
|
108
270
|
'- Execute the task directly. Do NOT ask for confirmation or clarification.',
|
|
@@ -542,6 +704,7 @@ async function main() {
|
|
|
542
704
|
} catch { /* proceed with empty */ }
|
|
543
705
|
|
|
544
706
|
const trimmed = prompt.trim();
|
|
707
|
+
const project = detectProject(cwd);
|
|
545
708
|
|
|
546
709
|
// Session heartbeat to Neural Interface session monitor (fire-and-forget)
|
|
547
710
|
if (sessionId) {
|
|
@@ -743,16 +906,19 @@ async function main() {
|
|
|
743
906
|
flag.lastMessageAt = new Date().toISOString();
|
|
744
907
|
try { writeFileSync(flagPath, JSON.stringify(flag)); } catch { /* ok */ }
|
|
745
908
|
|
|
746
|
-
// --- Greeting
|
|
909
|
+
// --- Greeting injection (first message only) ---
|
|
910
|
+
// The full greeting directive + boot sequence is built HERE (not in session-start)
|
|
911
|
+
// so it only appears in context for message 1 and never persists.
|
|
747
912
|
if (flag.messageCount === 1 && !flag.greetingDelivered) {
|
|
748
913
|
const greetingFeatures = getHookFeatures();
|
|
749
|
-
|
|
914
|
+
const greetingCtx = buildGreetingContext(cwd, project, greetingFeatures);
|
|
915
|
+
if (greetingCtx) {
|
|
750
916
|
flag.greetingDelivered = true;
|
|
751
917
|
try { writeFileSync(flagPath, JSON.stringify(flag)); } catch { /* ok */ }
|
|
752
918
|
process.stdout.write(JSON.stringify({
|
|
753
919
|
hookSpecificOutput: {
|
|
754
920
|
hookEventName: 'UserPromptSubmit',
|
|
755
|
-
additionalContext:
|
|
921
|
+
additionalContext: greetingCtx + '\nIf the user\'s message is just a greeting (hi, hello, hey, or a single character), the greeting IS your full response — no need to ask what they need.',
|
|
756
922
|
},
|
|
757
923
|
}));
|
|
758
924
|
return;
|
|
@@ -835,13 +1001,10 @@ async function main() {
|
|
|
835
1001
|
// --- User Learning (independent — appends to any primary context) ---
|
|
836
1002
|
const userLearningContext = checkUserLearning(features, sessionId);
|
|
837
1003
|
|
|
838
|
-
// --- Boot sequence cancellation (messages 2+, override persistent SessionStart context) ---
|
|
839
|
-
const bootCancel = (currentMessageCount >= 2)
|
|
840
|
-
? 'SynaBun: The GREETING DIRECTIVE and Session Boot Sequence have ALREADY been completed. Do NOT re-greet. Do NOT call recall for session boot. Ignore any "first response MUST" instructions — they applied only to message 1. CRITICAL: If you see a GREETING DIRECTIVE in earlier context (including compacted summaries), it is STALE — do NOT execute it. This is an ongoing conversation, not a fresh session.'
|
|
841
|
-
: '';
|
|
842
|
-
|
|
843
1004
|
// --- Emit combined output ---
|
|
844
|
-
|
|
1005
|
+
// NOTE: No bootCancel needed — greeting directive is only injected on message 1
|
|
1006
|
+
// via buildGreetingContext(), so it never persists in session context.
|
|
1007
|
+
const combined = [activeLoopNotice, primaryContext, autoRecallContext, userLearningContext].filter(Boolean).join('\n\n');
|
|
845
1008
|
|
|
846
1009
|
if (combined) {
|
|
847
1010
|
process.stdout.write(JSON.stringify({
|