Compare commits
12 Commits
codex/issu
...
codex/prod
| Author | SHA1 | Date | |
|---|---|---|---|
| 9f3d7dc6a9 | |||
| f10bff9ba4 | |||
| 14d9276c30 | |||
| 84b2c9ebc9 | |||
| 9263157a9e | |||
| f7b527763d | |||
| dda1d23a30 | |||
| ebe2906d1c | |||
| 6d78c119c0 | |||
| 374340d71a | |||
| 28c5e7955a | |||
| 5c4bf80eb0 |
@@ -29,6 +29,7 @@ OPENROUTER_APP_NAME=Intelligence Terminal
|
|||||||
|
|
||||||
# Local OpenAI-compatible examples
|
# Local OpenAI-compatible examples
|
||||||
# LiteLLM: LLM_PROVIDER=litellm, LLM_BASE_URL=https://llm.example.com/v1, LLM_API_KEY=your-proxy-key, LLM_MODEL=your-model-alias
|
# LiteLLM: LLM_PROVIDER=litellm, LLM_BASE_URL=https://llm.example.com/v1, LLM_API_KEY=your-proxy-key, LLM_MODEL=your-model-alias
|
||||||
|
# Local 20B+ models may need LLM_TIMEOUT_MS=300000 for full intelligence sweeps.
|
||||||
# LM Studio: LLM_PROVIDER=lmstudio, LLM_BASE_URL=http://host.docker.internal:1234/v1, LLM_MODEL=local-model
|
# LM Studio: LLM_PROVIDER=lmstudio, LLM_BASE_URL=http://host.docker.internal:1234/v1, LLM_MODEL=local-model
|
||||||
# Ollama: LLM_PROVIDER=ollama, LLM_BASE_URL=http://host.docker.internal:11434, LLM_MODEL=llama3.1:8b
|
# Ollama: LLM_PROVIDER=ollama, LLM_BASE_URL=http://host.docker.internal:11434, LLM_MODEL=llama3.1:8b
|
||||||
# Generic: LLM_PROVIDER=openai-compatible, LLM_BASE_URL=http://host.docker.internal:8000/v1, LLM_MODEL=your-model
|
# Generic: LLM_PROVIDER=openai-compatible, LLM_BASE_URL=http://host.docker.internal:8000/v1, LLM_MODEL=your-model
|
||||||
|
|||||||
36
README.md
36
README.md
@@ -85,7 +85,7 @@ npm run dev
|
|||||||
> ```
|
> ```
|
||||||
> This bypasses npm's script runner, which can swallow errors on some systems (particularly PowerShell on Windows). You can also run `node diag.mjs` to diagnose the exact issue — it checks your Node version, tests each module import individually, and verifies port availability. See [Troubleshooting](#troubleshooting) for more.
|
> This bypasses npm's script runner, which can swallow errors on some systems (particularly PowerShell on Windows). You can also run `node diag.mjs` to diagnose the exact issue — it checks your Node version, tests each module import individually, and verifies port availability. See [Troubleshooting](#troubleshooting) for more.
|
||||||
|
|
||||||
The dashboard opens automatically at `http://localhost:3117` and immediately begins its first intelligence sweep. This initial sweep queries all 27 sources in parallel and typically takes 30–60 seconds — the dashboard will appear empty until the sweep completes and pushes the first data update. After that, it auto-refreshes every 15 minutes via SSE (Server-Sent Events). No manual page refresh needed.
|
The server starts at `http://localhost:3117` and immediately begins its first intelligence sweep. Browser auto-open is disabled by default; open the URL yourself or explicitly set `AUTO_OPEN_BROWSER=true` for a supported desktop environment. The initial sweep queries all 27 sources in parallel and typically takes 30–60 seconds — the dashboard will appear empty until the sweep completes and pushes the first data update. After that, it auto-refreshes every 15 minutes via SSE (Server-Sent Events). No manual page refresh is needed.
|
||||||
|
|
||||||
**Requirements:** Node.js 22+ (uses native `fetch`, top-level `await`, ESM)
|
**Requirements:** Node.js 22+ (uses native `fetch`, top-level `await`, ESM)
|
||||||
|
|
||||||
@@ -157,9 +157,13 @@ ACLED_EMAIL=
|
|||||||
ACLED_PASSWORD=
|
ACLED_PASSWORD=
|
||||||
CLOUDFLARE_API_TOKEN=
|
CLOUDFLARE_API_TOKEN=
|
||||||
BLS_API_KEY=
|
BLS_API_KEY=
|
||||||
|
REDDIT_CLIENT_ID=
|
||||||
|
REDDIT_CLIENT_SECRET=
|
||||||
|
|
||||||
TELEGRAM_BOT_TOKEN=
|
TELEGRAM_BOT_TOKEN=
|
||||||
TELEGRAM_CHAT_ID=
|
TELEGRAM_CHAT_ID=
|
||||||
|
TELEGRAM_POLL_INTERVAL=5000
|
||||||
|
TELEGRAM_CHANNELS=
|
||||||
DISCORD_BOT_TOKEN=
|
DISCORD_BOT_TOKEN=
|
||||||
DISCORD_CHANNEL_ID=
|
DISCORD_CHANNEL_ID=
|
||||||
DISCORD_GUILD_ID=
|
DISCORD_GUILD_ID=
|
||||||
@@ -174,6 +178,7 @@ LLM_PROVIDER=litellm
|
|||||||
LLM_BASE_URL=https://llm.example.com/v1
|
LLM_BASE_URL=https://llm.example.com/v1
|
||||||
LLM_API_KEY=your-litellm-api-key
|
LLM_API_KEY=your-litellm-api-key
|
||||||
LLM_MODEL=your-model-alias
|
LLM_MODEL=your-model-alias
|
||||||
|
LLM_TIMEOUT_MS=300000
|
||||||
|
|
||||||
# LM Studio
|
# LM Studio
|
||||||
LLM_PROVIDER=lmstudio
|
LLM_PROVIDER=lmstudio
|
||||||
@@ -436,14 +441,18 @@ Reddit is OAuth-only in this fork. If the Reddit credentials are missing or reje
|
|||||||
|
|
||||||
### LLM Provider (optional, for AI-enhanced ideas)
|
### LLM Provider (optional, for AI-enhanced ideas)
|
||||||
|
|
||||||
Set `LLM_PROVIDER` to one of: `anthropic`, `openai`, `gemini`, `codex`, `openrouter`, `minimax`, `mistral`, `grok`
|
Set `LLM_PROVIDER` to one of: `litellm`, `openrouter`, `openai-compatible`, `lmstudio`, `ollama`, `anthropic`, `openai`, `gemini`, `codex`, `minimax`, `mistral`, or `grok`.
|
||||||
|
|
||||||
| Provider | Key Required | Default Model |
|
| Provider | Key Required | Default Model / Requirement |
|
||||||
|----------|-------------|---------------|
|
|----------|--------------|-----------------------------|
|
||||||
|
| `litellm` | `LLM_API_KEY` | Explicit `LLM_BASE_URL` and `LLM_MODEL` required |
|
||||||
|
| `openrouter` | `LLM_API_KEY` | `openrouter/free` |
|
||||||
|
| `openai-compatible` | Endpoint-dependent | `local-model`; set `LLM_BASE_URL` |
|
||||||
|
| `lmstudio` | No | `local-model` |
|
||||||
|
| `ollama` | No | `llama3.1:8b` |
|
||||||
| `anthropic` | `LLM_API_KEY` | claude-sonnet-4-6 |
|
| `anthropic` | `LLM_API_KEY` | claude-sonnet-4-6 |
|
||||||
| `openai` | `LLM_API_KEY` | gpt-5.4 |
|
| `openai` | `LLM_API_KEY` | `gpt-4o-mini` |
|
||||||
| `gemini` | `LLM_API_KEY` | gemini-3.1-pro |
|
| `gemini` | `LLM_API_KEY` | gemini-3.1-pro |
|
||||||
| `openrouter` | `LLM_API_KEY` | openrouter/auto |
|
|
||||||
| `codex` | None (uses `~/.codex/auth.json`) | gpt-5.3-codex |
|
| `codex` | None (uses `~/.codex/auth.json`) | gpt-5.3-codex |
|
||||||
| `minimax` | `LLM_API_KEY` | MiniMax-M2.5 |
|
| `minimax` | `LLM_API_KEY` | MiniMax-M2.5 |
|
||||||
| `mistral` | `LLM_API_KEY` | mistral-large-latest |
|
| `mistral` | `LLM_API_KEY` | mistral-large-latest |
|
||||||
@@ -631,9 +640,22 @@ All settings are in `.env` with sensible defaults:
|
|||||||
| `STALE_DATA_MAX_AGE_MINUTES` | `60` | Data age threshold for stale health state |
|
| `STALE_DATA_MAX_AGE_MINUTES` | `60` | Data age threshold for stale health state |
|
||||||
| `STALE_ALERT_COOLDOWN_MINUTES` | `60` | Minimum time between repeated operator stale-data alerts |
|
| `STALE_ALERT_COOLDOWN_MINUTES` | `60` | Minimum time between repeated operator stale-data alerts |
|
||||||
| `DASHBOARD_URL` | local URL | Dashboard URL included in operator alerts |
|
| `DASHBOARD_URL` | local URL | Dashboard URL included in operator alerts |
|
||||||
|
| `AUTO_OPEN_BROWSER` | `false` | Open the dashboard in a host browser; keep disabled in Docker |
|
||||||
|
| `TERMINAL_ACTIONS_ENABLED` | environment-dependent | Enable guarded dashboard actions such as sweep and brief |
|
||||||
|
| `SWEEP_TOKEN` | disabled | Shared token required for remote action requests |
|
||||||
|
| `SSE_HEARTBEAT_INTERVAL_MS` | `25000` | Heartbeat interval for reverse-proxy SSE connections |
|
||||||
|
| `TERMINAL_ACTION_RATE_LIMIT_WINDOW_MS` | `60000` | Terminal action rate-limit window |
|
||||||
|
| `TERMINAL_ACTION_RATE_LIMIT_MAX` | `10` | Maximum terminal actions per client/window |
|
||||||
|
| `BRIEF_VERBOSITY` | `standard` | Briefing detail level |
|
||||||
| `LLM_PROVIDER` | disabled | `litellm`, `openrouter`, `openai-compatible`, `lmstudio`, `ollama`, `anthropic`, `openai`, `gemini`, `codex`, `minimax`, `mistral`, or `grok` |
|
| `LLM_PROVIDER` | disabled | `litellm`, `openrouter`, `openai-compatible`, `lmstudio`, `ollama`, `anthropic`, `openai`, `gemini`, `codex`, `minimax`, `mistral`, or `grok` |
|
||||||
| `LLM_API_KEY` | — | API key (not needed for codex) |
|
| `LLM_BASE_URL` | provider default | API base URL; required for LiteLLM and custom endpoints |
|
||||||
|
| `LLM_API_KEY` | — | Provider or proxy API key; required for LiteLLM |
|
||||||
| `LLM_MODEL` | per-provider default | Override model selection |
|
| `LLM_MODEL` | per-provider default | Override model selection |
|
||||||
|
| `LLM_TEMPERATURE` | `0.2` | Sampling temperature for OpenAI-compatible providers |
|
||||||
|
| `LLM_MAX_TOKENS` | `2000` | Maximum completion token budget |
|
||||||
|
| `LLM_TIMEOUT_MS` | `90000` | LLM request timeout in milliseconds |
|
||||||
|
| `OPENROUTER_SITE_URL` | repository URL | OpenRouter attribution URL |
|
||||||
|
| `OPENROUTER_APP_NAME` | `Intelligence Terminal` | OpenRouter application title |
|
||||||
| `TELEGRAM_BOT_TOKEN` | disabled | For Telegram alerts + bot commands |
|
| `TELEGRAM_BOT_TOKEN` | disabled | For Telegram alerts + bot commands |
|
||||||
| `TELEGRAM_CHAT_ID` | — | Your Telegram chat ID |
|
| `TELEGRAM_CHAT_ID` | — | Your Telegram chat ID |
|
||||||
| `TELEGRAM_CHANNELS` | — | Extra channel IDs to monitor (comma-separated) |
|
| `TELEGRAM_CHANNELS` | — | Extra channel IDs to monitor (comma-separated) |
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
# Agent Handoff
|
# Agent Handoff
|
||||||
|
|
||||||
Last updated: 2026-07-03
|
Last updated: 2026-07-04
|
||||||
|
|
||||||
## Current Work
|
## Latest Completed Work
|
||||||
|
|
||||||
- Canonical repository: `https://git.wilkensxl.de/Code-Inc/intelligence-terminal`
|
- Canonical repository: `https://git.wilkensxl.de/Code-Inc/intelligence-terminal`
|
||||||
- Production baseline before this work: `c159c83a0768486c8c6f445b458b760dba4ba385`
|
- LiteLLM implementation merge: `5c4bf80eb0c19bd59080f5432a2a344798d7a3ce`
|
||||||
- Active branch: `codex/issue-47-litellm-provider`
|
- Merged PR: `#48 feat: add LiteLLM provider and publish Code-Inc image`
|
||||||
- Issue: `#47 Add first-class LiteLLM provider and publish updated image`
|
- Completed issue: `#47 Add first-class LiteLLM provider and publish updated image`
|
||||||
- LiteLLM is implemented through the OpenAI-compatible `/chat/completions` API and requires `LLM_BASE_URL`, `LLM_API_KEY`, and `LLM_MODEL`.
|
- LiteLLM is implemented through the OpenAI-compatible `/chat/completions` API and requires `LLM_BASE_URL`, `LLM_API_KEY`, and `LLM_MODEL`.
|
||||||
- The build workflow now targets `git.wilkensxl.de/code-inc/intelligence-terminal` and publishes only from the production branch, not from pull requests.
|
- The build workflow now targets `git.wilkensxl.de/code-inc/intelligence-terminal` and publishes only from the production branch, not from pull requests.
|
||||||
- Runner build/test/image verification and the first `code-inc` registry publication must be recorded here after the branch is pushed and merged.
|
- Gitea Actions runs 231-235 passed for the PR and production merge, including unit tests, Compose validation, Docker build, release dry-run, and template compliance.
|
||||||
- Related maintenance: issue #21 tracks the failing security scan, #45 tracks the dependency workflow, and #46 tracks remaining namespace/handoff cleanup.
|
- The first `code-inc` registry publication was verified through the Gitea Package API on 2026-07-03.
|
||||||
|
- PR #52 / issue #51 removed the hard-coded 90-second/4096-token idea-generation override. LLM ideas now respect `LLM_TIMEOUT_MS` and `LLM_MAX_TOKENS`.
|
||||||
|
- PR #54 / issue #53 fixed prediction persistence after successful LLM generation and added a SQLite-backed regression test.
|
||||||
|
- Live Dockge verification on 2026-07-04 used `LLM_TIMEOUT_MS=300000` and `LLM_MAX_TOKENS=4096` with the `heim-llm` LiteLLM alias. The completed sweep produced six parsed ideas, reported `ideasSource=llm`, persisted memory, and had no `lastSweepError`.
|
||||||
|
- Production implementation commit: `14d9276c30e06cafcaee8177ba7377fdf5f26277`.
|
||||||
|
- Issues #47, #51, and #53 are complete. Issue #21 tracks the failing security scan and #45 tracks the dependency workflow.
|
||||||
|
|
||||||
## Repository State
|
## Repository State
|
||||||
|
|
||||||
@@ -236,27 +241,41 @@ README includes:
|
|||||||
|
|
||||||
## Registry And Images
|
## Registry And Images
|
||||||
|
|
||||||
Target registry image after the current production merge:
|
Production registry image:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
git.wilkensxl.de/code-inc/intelligence-terminal
|
git.wilkensxl.de/code-inc/intelligence-terminal
|
||||||
```
|
```
|
||||||
|
|
||||||
The legacy `mrsphay` package remains available. Verify these new `code-inc` tags after the current runner publication:
|
The legacy `mrsphay` package remains available. Verified `code-inc` tags from the LiteLLM production merge:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
latest
|
latest
|
||||||
YYYYMMDD
|
20260703
|
||||||
<production-commit-sha>
|
5c4bf80eb0c19bd59080f5432a2a344798d7a3ce
|
||||||
```
|
```
|
||||||
|
|
||||||
Required pull verification after publication:
|
Operator pull command:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
Record the resulting digest after the runner push.
|
Published manifest digest:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sha256:5e29483ebfd9baae368673adc790789f02aed2d5d5d3a550fe55a4b71b5b62dd
|
||||||
|
```
|
||||||
|
|
||||||
|
Relevant successful runner executions:
|
||||||
|
|
||||||
|
```text
|
||||||
|
PR build: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/231
|
||||||
|
PR template check: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/232
|
||||||
|
Production publish: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/233
|
||||||
|
Release dry-run: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/234
|
||||||
|
Template compliance: https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/235
|
||||||
|
```
|
||||||
|
|
||||||
## Gitea Actions
|
## Gitea Actions
|
||||||
|
|
||||||
@@ -285,12 +304,12 @@ template-compliance.yml on codex/production-intelligence-terminal: success
|
|||||||
Relevant run URLs:
|
Relevant run URLs:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/23
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/23
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/24
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/24
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/25
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/25
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/26
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/26
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/27
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/27
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/actions/runs/28
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/actions/runs/28
|
||||||
```
|
```
|
||||||
|
|
||||||
Repository secret expected by the registry publish workflow:
|
Repository secret expected by the registry publish workflow:
|
||||||
@@ -320,28 +339,28 @@ The following Gitea issues were created for real remaining work:
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
#1 Reddit source must stop unauthenticated .json scraping
|
#1 Reddit source must stop unauthenticated .json scraping
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/1
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/1
|
||||||
|
|
||||||
#2 Send operator alerts when dashboard data remains stale
|
#2 Send operator alerts when dashboard data remains stale
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/2
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/2
|
||||||
|
|
||||||
#3 ACLED credentialed integration needs regression test and diagnostics
|
#3 ACLED credentialed integration needs regression test and diagnostics
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/3
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/3
|
||||||
|
|
||||||
#4 Complete memory and prediction loop beyond Phase-1 SQLite
|
#4 Complete memory and prediction loop beyond Phase-1 SQLite
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/4
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/4
|
||||||
|
|
||||||
#5 Remove old inline dashboard snapshot from production builds
|
#5 Remove old inline dashboard snapshot from production builds
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/5
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/5
|
||||||
|
|
||||||
#6 Harden Terminal Actions for public reverse-proxy deployments
|
#6 Harden Terminal Actions for public reverse-proxy deployments
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/6
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/6
|
||||||
|
|
||||||
#7 Replace ADS-B stub with real disabled/degraded source handling
|
#7 Replace ADS-B stub with real disabled/degraded source handling
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/7
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/7
|
||||||
|
|
||||||
#8 Clean inherited public-demo and upstream marketing references
|
#8 Clean inherited public-demo and upstream marketing references
|
||||||
https://git.wilkensxl.de/MrSphay/intelligence-terminal/issues/8
|
https://git.wilkensxl.de/Code-Inc/intelligence-terminal/issues/8
|
||||||
```
|
```
|
||||||
|
|
||||||
## Verification Already Performed
|
## Verification Already Performed
|
||||||
@@ -371,7 +390,7 @@ Audit result:
|
|||||||
0 high vulnerabilities
|
0 high vulnerabilities
|
||||||
```
|
```
|
||||||
|
|
||||||
Docker build and smoke test were performed locally earlier:
|
Docker build and smoke test were performed locally earlier against the now-legacy `mrsphay` image namespace. Current deployments must use the `code-inc` image documented above:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:latest .
|
docker build -t git.wilkensxl.de/mrsphay/intelligence-terminal:latest .
|
||||||
@@ -425,7 +444,7 @@ origin/main
|
|||||||
1. Clone the Gitea repository:
|
1. Clone the Gitea repository:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git clone https://git.wilkensxl.de/MrSphay/intelligence-terminal.git
|
git clone https://git.wilkensxl.de/Code-Inc/intelligence-terminal.git
|
||||||
cd intelligence-terminal
|
cd intelligence-terminal
|
||||||
git checkout codex/production-intelligence-terminal
|
git checkout codex/production-intelligence-terminal
|
||||||
```
|
```
|
||||||
@@ -490,5 +509,5 @@ docker pull git.wilkensxl.de/code-inc/intelligence-terminal:latest
|
|||||||
For a pinned deployment:
|
For a pinned deployment:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:YYYYMMDD
|
docker pull git.wilkensxl.de/code-inc/intelligence-terminal:20260703
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export class IntelligenceStore {
|
|||||||
_recordPredictions(data, timestamp) {
|
_recordPredictions(data, timestamp) {
|
||||||
for (const idea of data.ideas || []) {
|
for (const idea of data.ideas || []) {
|
||||||
const title = idea.title || 'Untitled idea';
|
const title = idea.title || 'Untitled idea';
|
||||||
const stableId = stableId('prediction', title, idea.type || '', idea.ticker || '', idea.horizon || '');
|
const predictionId = stableId('prediction', title, idea.type || '', idea.ticker || '', idea.horizon || '');
|
||||||
const evidence = Array.isArray(idea.signals) ? idea.signals : [];
|
const evidence = Array.isArray(idea.signals) ? idea.signals : [];
|
||||||
this.db.prepare(`INSERT INTO predictions (
|
this.db.prepare(`INSERT INTO predictions (
|
||||||
stable_id, created_at, updated_at, title, type, hypothesis, evidence_json, confidence,
|
stable_id, created_at, updated_at, title, type, hypothesis, evidence_json, confidence,
|
||||||
@@ -217,7 +217,7 @@ export class IntelligenceStore {
|
|||||||
confidence=excluded.confidence,
|
confidence=excluded.confidence,
|
||||||
evidence_json=excluded.evidence_json,
|
evidence_json=excluded.evidence_json,
|
||||||
payload_json=excluded.payload_json`).run(
|
payload_json=excluded.payload_json`).run(
|
||||||
stableId,
|
predictionId,
|
||||||
timestamp,
|
timestamp,
|
||||||
timestamp,
|
timestamp,
|
||||||
title,
|
title,
|
||||||
|
|||||||
@@ -43,7 +43,13 @@ Output ONLY valid JSON array. Each object:
|
|||||||
}`;
|
}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await provider.complete(systemPrompt, context, { maxTokens: 4096, timeout: 90000 });
|
const maxTokens = Number.isFinite(provider.maxTokens) && provider.maxTokens > 0
|
||||||
|
? provider.maxTokens
|
||||||
|
: 4096;
|
||||||
|
const timeout = Number.isFinite(provider.timeoutMs) && provider.timeoutMs > 0
|
||||||
|
? provider.timeoutMs
|
||||||
|
: 90000;
|
||||||
|
const result = await provider.complete(systemPrompt, context, { maxTokens, timeout });
|
||||||
const ideas = parseIdeasResponse(result.text);
|
const ideas = parseIdeasResponse(result.text);
|
||||||
if (ideas && ideas.length > 0) {
|
if (ideas && ideas.length > 0) {
|
||||||
return ideas;
|
return ideas;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"brief:save": "node apis/save-briefing.mjs",
|
"brief:save": "node apis/save-briefing.mjs",
|
||||||
"diag": "node diag.mjs",
|
"diag": "node diag.mjs",
|
||||||
"test": "npm run test:unit",
|
"test": "npm run test:unit",
|
||||||
"test:unit": "node --test test/llm-openrouter.test.mjs test/llm-ollama.test.mjs test/llm-openai-compatible.test.mjs test/llm-litellm.test.mjs test/fetch-utils.test.mjs test/reddit-source.test.mjs test/acled-source.test.mjs test/mojibake-text.test.mjs test/adsb.test.mjs test/dashboard-geotagging.test.mjs",
|
"test:unit": "node --test test/llm-openrouter.test.mjs test/llm-ollama.test.mjs test/llm-openai-compatible.test.mjs test/llm-litellm.test.mjs test/llm-ideas.test.mjs test/intelligence-store.test.mjs test/fetch-utils.test.mjs test/reddit-source.test.mjs test/acled-source.test.mjs test/mojibake-text.test.mjs test/adsb.test.mjs test/dashboard-geotagging.test.mjs",
|
||||||
"compose:config": "docker compose config",
|
"compose:config": "docker compose config",
|
||||||
"clean": "node scripts/clean.mjs",
|
"clean": "node scripts/clean.mjs",
|
||||||
"fresh-start": "npm run clean && npm start"
|
"fresh-start": "npm run clean && npm start"
|
||||||
|
|||||||
44
test/intelligence-store.test.mjs
Normal file
44
test/intelligence-store.test.mjs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { mkdtempSync, rmSync } from 'node:fs';
|
||||||
|
import { tmpdir } from 'node:os';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { IntelligenceStore } from '../lib/intelligence-store.mjs';
|
||||||
|
|
||||||
|
test('records LLM ideas as stable predictions', async (t) => {
|
||||||
|
const directory = mkdtempSync(join(tmpdir(), 'intelligence-store-'));
|
||||||
|
t.after(() => rmSync(directory, { recursive: true, force: true }));
|
||||||
|
|
||||||
|
const store = await new IntelligenceStore(join(directory, 'intelligence.db')).init();
|
||||||
|
if (!store.available) {
|
||||||
|
t.skip(`node:sqlite unavailable: ${store.reason}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
store.recordRun({
|
||||||
|
meta: {
|
||||||
|
timestamp: '2026-07-04T10:17:51.011Z',
|
||||||
|
sourcesOk: 22,
|
||||||
|
sourcesDegraded: 7,
|
||||||
|
sourcesFailed: 0,
|
||||||
|
},
|
||||||
|
ideasSource: 'llm',
|
||||||
|
ideas: [{
|
||||||
|
title: 'Gold safe-haven hedge',
|
||||||
|
type: 'HEDGE',
|
||||||
|
ticker: 'GLD',
|
||||||
|
confidence: 'MEDIUM',
|
||||||
|
rationale: 'Geopolitical risk remains elevated.',
|
||||||
|
risk: 'Risk appetite recovers.',
|
||||||
|
horizon: 'Weeks',
|
||||||
|
signals: ['geopolitical escalation'],
|
||||||
|
source: 'llm',
|
||||||
|
}],
|
||||||
|
}, { summary: { direction: 'risk-off' } });
|
||||||
|
|
||||||
|
const result = store.listPredictions({ limit: 10 });
|
||||||
|
assert.equal(result.available, true);
|
||||||
|
assert.equal(result.predictions.length, 1);
|
||||||
|
assert.equal(result.predictions[0].title, 'Gold safe-haven hedge');
|
||||||
|
assert.match(result.predictions[0].stable_id, /^[a-f0-9]{24}$/);
|
||||||
|
});
|
||||||
48
test/llm-ideas.test.mjs
Normal file
48
test/llm-ideas.test.mjs
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import test from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { generateLLMIdeas } from '../lib/llm/ideas.mjs';
|
||||||
|
|
||||||
|
const response = JSON.stringify([{
|
||||||
|
title: 'Test idea',
|
||||||
|
type: 'WATCH',
|
||||||
|
ticker: 'SPY',
|
||||||
|
confidence: 'LOW',
|
||||||
|
rationale: 'Test rationale',
|
||||||
|
risk: 'Test risk',
|
||||||
|
horizon: 'Days',
|
||||||
|
signals: ['test'],
|
||||||
|
}]);
|
||||||
|
|
||||||
|
test('idea generation respects provider token and timeout configuration', async () => {
|
||||||
|
let capturedOptions;
|
||||||
|
const provider = {
|
||||||
|
isConfigured: true,
|
||||||
|
maxTokens: 2000,
|
||||||
|
timeoutMs: 300000,
|
||||||
|
async complete(_systemPrompt, _context, options) {
|
||||||
|
capturedOptions = options;
|
||||||
|
return { text: response };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ideas = await generateLLMIdeas(provider, {}, null, []);
|
||||||
|
|
||||||
|
assert.deepEqual(capturedOptions, { maxTokens: 2000, timeout: 300000 });
|
||||||
|
assert.equal(ideas.length, 1);
|
||||||
|
assert.equal(ideas[0].source, 'llm');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('idea generation keeps safe defaults for providers without limits', async () => {
|
||||||
|
let capturedOptions;
|
||||||
|
const provider = {
|
||||||
|
isConfigured: true,
|
||||||
|
async complete(_systemPrompt, _context, options) {
|
||||||
|
capturedOptions = options;
|
||||||
|
return { text: response };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await generateLLMIdeas(provider, {}, null, []);
|
||||||
|
|
||||||
|
assert.deepEqual(capturedOptions, { maxTokens: 4096, timeout: 90000 });
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user