Compare commits
3 Commits
codex/issu
...
0fbd8640ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fbd8640ca | ||
|
|
eefc1a4c77 | ||
|
|
446076cb84 |
@@ -10,6 +10,7 @@ STALE_ALERT_COOLDOWN_MINUTES=60
|
|||||||
DASHBOARD_URL=
|
DASHBOARD_URL=
|
||||||
TERMINAL_ACTIONS_ENABLED=true
|
TERMINAL_ACTIONS_ENABLED=true
|
||||||
SWEEP_TOKEN=
|
SWEEP_TOKEN=
|
||||||
|
SSE_HEARTBEAT_INTERVAL_MS=25000
|
||||||
BRIEF_VERBOSITY=standard
|
BRIEF_VERBOSITY=standard
|
||||||
|
|
||||||
# LLM layer
|
# LLM layer
|
||||||
|
|||||||
15
README.md
15
README.md
@@ -134,6 +134,7 @@ STALE_ALERT_COOLDOWN_MINUTES=60
|
|||||||
DASHBOARD_URL=https://intelligence.example.internal
|
DASHBOARD_URL=https://intelligence.example.internal
|
||||||
TERMINAL_ACTIONS_ENABLED=true
|
TERMINAL_ACTIONS_ENABLED=true
|
||||||
SWEEP_TOKEN=
|
SWEEP_TOKEN=
|
||||||
|
SSE_HEARTBEAT_INTERVAL_MS=25000
|
||||||
BRIEF_VERBOSITY=standard
|
BRIEF_VERBOSITY=standard
|
||||||
|
|
||||||
LLM_PROVIDER=openrouter
|
LLM_PROVIDER=openrouter
|
||||||
@@ -189,6 +190,20 @@ When data remains stale past `STALE_DATA_MAX_AGE_MINUTES`, the server sends an o
|
|||||||
|
|
||||||
The dashboard Terminal Actions panel can trigger `status`, `sweep`, and `brief` through `/api/action`. Leave `TERMINAL_ACTIONS_ENABLED=true` for a private home-server deployment. For an internet-exposed deployment, set `SWEEP_TOKEN` and pass it through trusted automation, or set `TERMINAL_ACTIONS_ENABLED=false` to disable browser-triggered actions. If you protect actions with `SWEEP_TOKEN`, the browser can send it from `localStorage.crucix_sweep_token`.
|
The dashboard Terminal Actions panel can trigger `status`, `sweep`, and `brief` through `/api/action`. Leave `TERMINAL_ACTIONS_ENABLED=true` for a private home-server deployment. For an internet-exposed deployment, set `SWEEP_TOKEN` and pass it through trusted automation, or set `TERMINAL_ACTIONS_ENABLED=false` to disable browser-triggered actions. If you protect actions with `SWEEP_TOKEN`, the browser can send it from `localStorage.crucix_sweep_token`.
|
||||||
|
|
||||||
|
#### Reverse Proxy SSE
|
||||||
|
|
||||||
|
The dashboard receives live sweep updates from `GET /events` using Server-Sent Events. The server sends `retry: 10000` reconnect guidance and lightweight heartbeat comments every `SSE_HEARTBEAT_INTERVAL_MS` milliseconds so reverse proxies do not close an otherwise idle stream between 15-minute sweeps.
|
||||||
|
|
||||||
|
Recommended proxy settings:
|
||||||
|
|
||||||
|
| Proxy | Setting |
|
||||||
|
| --- | --- |
|
||||||
|
| Pangolin / Traefik-style frontends | Keep response streaming enabled and set idle timeouts above `SSE_HEARTBEAT_INTERVAL_MS`. |
|
||||||
|
| Nginx | Disable proxy buffering for `/events`, keep `proxy_read_timeout` above the heartbeat interval, and preserve `Connection: keep-alive`. |
|
||||||
|
| Cloudflare-style proxies | Keep the heartbeat below common idle cutoffs; the default 25s is intentionally conservative. |
|
||||||
|
|
||||||
|
If you raise the heartbeat interval, keep it shorter than the lowest idle timeout in the proxy chain.
|
||||||
|
|
||||||
#### Scenario Watchlist
|
#### Scenario Watchlist
|
||||||
|
|
||||||
Intelligence Terminal can track operator hypotheses across sweeps with a runtime scenario file at `runs/scenarios.json`. On first run, the server creates three disabled starter examples:
|
Intelligence Terminal can track operator hypotheses across sweeps with a runtime scenario file at `runs/scenarios.json`. On first run, the server creates three disabled starter examples:
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export default {
|
|||||||
dashboardUrl: process.env.DASHBOARD_URL || null,
|
dashboardUrl: process.env.DASHBOARD_URL || null,
|
||||||
sweepToken: process.env.SWEEP_TOKEN || null,
|
sweepToken: process.env.SWEEP_TOKEN || null,
|
||||||
terminalActionsEnabled: boolEnv('TERMINAL_ACTIONS_ENABLED', true),
|
terminalActionsEnabled: boolEnv('TERMINAL_ACTIONS_ENABLED', true),
|
||||||
|
sseHeartbeatIntervalMs: intEnv('SSE_HEARTBEAT_INTERVAL_MS', 25000),
|
||||||
|
|
||||||
llm: {
|
llm: {
|
||||||
provider: process.env.LLM_PROVIDER || null, // anthropic | openai | gemini | codex | openrouter | minimax | mistral | ollama | grok
|
provider: process.env.LLM_PROVIDER || null, // anthropic | openai | gemini | codex | openrouter | minimax | mistral | ollama | grok
|
||||||
|
|||||||
16
server.mjs
16
server.mjs
@@ -331,10 +331,24 @@ app.get('/events', (req, res) => {
|
|||||||
'Cache-Control': 'no-cache',
|
'Cache-Control': 'no-cache',
|
||||||
'Connection': 'keep-alive',
|
'Connection': 'keep-alive',
|
||||||
'Access-Control-Allow-Origin': '*',
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'X-Accel-Buffering': 'no',
|
||||||
});
|
});
|
||||||
|
res.write('retry: 10000\n');
|
||||||
res.write('data: {"type":"connected"}\n\n');
|
res.write('data: {"type":"connected"}\n\n');
|
||||||
|
const heartbeatMs = Math.max(5000, config.sseHeartbeatIntervalMs || 25000);
|
||||||
|
const heartbeat = setInterval(() => {
|
||||||
|
try {
|
||||||
|
res.write(`: heartbeat ${new Date().toISOString()}\n\n`);
|
||||||
|
} catch {
|
||||||
|
clearInterval(heartbeat);
|
||||||
|
sseClients.delete(res);
|
||||||
|
}
|
||||||
|
}, heartbeatMs);
|
||||||
sseClients.add(res);
|
sseClients.add(res);
|
||||||
req.on('close', () => sseClients.delete(res));
|
req.on('close', () => {
|
||||||
|
clearInterval(heartbeat);
|
||||||
|
sseClients.delete(res);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function broadcast(data) {
|
function broadcast(data) {
|
||||||
|
|||||||
@@ -101,6 +101,17 @@ test('safeFetchText returns text and byte count', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('SSE endpoint sends reconnect guidance and clears heartbeat timer', () => {
|
||||||
|
const server = readFileSync(new URL('../server.mjs', import.meta.url), 'utf8');
|
||||||
|
const config = readFileSync(new URL('../crucix.config.mjs', import.meta.url), 'utf8');
|
||||||
|
assert.match(config, /sseHeartbeatIntervalMs/);
|
||||||
|
assert.match(server, /retry: 10000\\n/);
|
||||||
|
assert.match(server, /setInterval\(\(\) =>/);
|
||||||
|
assert.match(server, /: heartbeat/);
|
||||||
|
assert.match(server, /clearInterval\(heartbeat\)/);
|
||||||
|
assert.match(server, /X-Accel-Buffering/);
|
||||||
|
});
|
||||||
|
|
||||||
test('server dashboard shell does not embed an operational snapshot', () => {
|
test('server dashboard shell does not embed an operational snapshot', () => {
|
||||||
const html = readFileSync(new URL('../dashboard/public/jarvis.html', import.meta.url), 'utf8');
|
const html = readFileSync(new URL('../dashboard/public/jarvis.html', import.meta.url), 'utf8');
|
||||||
assert.match(html, /let D = createDashboardShellData\(\);/);
|
assert.match(html, /let D = createDashboardShellData\(\);/);
|
||||||
|
|||||||
Reference in New Issue
Block a user