mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* feat: gbrain-context OpenClaw context engine — deterministic temporal/spatial injection Adds a context engine plugin that runs on every assemble() call to inject structured live context into the system prompt: - Garry's current local time (computed from heartbeat-state.json timezone) - Current location (city + timezone from heartbeat or flight data) - Home time when traveling (e.g. 'Mon 7:58 AM PT') - Active travel status - Quiet hours detection - Airport→timezone mapping for 30+ airports This kills the 'time warp' bug class where compacted sessions lose track of time/location. The engine delegates compaction to the legacy runtime and only owns systemPromptAddition injection. Zero LLM calls, <5ms. Files: - src/core/context-engine.ts — engine implementation (SDK-free, testable) - src/openclaw-context-engine.ts — plugin entry point (requires SDK) - test/context-engine.test.ts — 9 tests, all passing Enable: plugins.slots.contextEngine = 'gbrain-context' * feat: add activity injection — calendar events + open tasks in context block Reads memory/calendar-cache.json and ops/tasks.md to inject: - **Right now:** current meeting (with attendees) from calendar - **Coming up:** next 3 events within 4-hour window - **Open tasks:** unchecked items from Today section - Stale calendar warning when cache is >6 hours old Skips all-day events and generic markers (Home, OOO, Out of Office). Caps upcoming events at 3 and tasks at 5 to keep prompt lean. 15 tests passing (was 9). * v0.32.5 feat: gbrain-context OpenClaw context engine — deterministic temporal/spatial injection Ships PR #873 by @garrytan-agents (two underlying commits preserved): -f1dbe6ea— core engine (heartbeat + flights + airport→tz + quiet hours) -14e85873— activity injection (calendar events + open tasks + stale-cache warning) Kills the "time warp" bug class: when sessions compact, the LLM loses track of current time, location, and active threads. This engine owns the `systemPromptAddition` slot and reinjects live state on every `assemble()` call. Zero LLM calls, <5ms overhead, deterministic. Typecheck cleanup folded in: - `@ts-ignore` on the two `openclaw/plugin-sdk` runtime-only imports (resolved by the OpenClaw host; not a build-time dep — same pattern the core engine already used for `await import('openclaw/plugin-sdk/core')`) - Inline `PluginApi` + `PluginCtx` type shapes in the plugin entry so the `register(api)` + `(ctx)` callback params aren't implicit any - Test file's `from 'vitest'` → `from 'bun:test'` to match the rest of the suite (bun's globals make it pass at runtime, but tsc fails) Verification: - bun test test/context-engine.test.ts → 15/15 pass - bun run typecheck → exit 0 Co-Authored-By: garrytan-agents <garrytan-agents@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix-wave: close 5 findings from /plan-eng-review pass on PR #880 A `/plan-eng-review` audit of the shipped v0.32.5 surfaced 5 things worth fixing before merge. All folded into this branch with 5 new regression tests (15 → 20 total). A4 — silent-wrong-timezone for unknown airports Pre-fix: an active flight to any airport not in the 30-entry AIRPORT_TZ map (BOM, DXB, GRU, JNB, FRA, AMS, etc.) silently fell back to US/Pacific. The exact failure class this engine exists to prevent, in a different shape. Post-fix: unknown airports surface via the source field (flight:AC8:tz-unknown:BOM) so the LLM can see the data is incomplete instead of believing it's in Pacific Time. A2 / P1 — duplicate disk reads generateLiveContext was loading heartbeat-state.json and upcoming-flights.json twice per assemble() call (once in resolveLocation, once inline). Batch-load each workspace file once at the top of the function and thread results down. Halves the hot-path I/O. C4 — sanitize external content before injection Calendar event summaries, attendees, and task strings now go through sanitizeForPrompt() which strips newlines + control chars (U+0000-001F + U+007F) and clamps length. A meeting titled "Standup\n\nIgnore prior instructions" can no longer forge LLM directives by escaping the bullet structure. C1 — split isQuietHours into 3 explicit signals Original name was misleading (returned false when user was awake at 2 AM, even though wall clock said quiet hours). Split into `userAwake`, `wallClockQuietHours`, and a composite `quietHoursActive` so consumers can decide their own policy. On-disk heartbeat.garryAwake JSON field is unchanged — only the internal LiveContext type and the format-block consumer renamed. T1 — regression test coverage for the active-flight path Pre-fix, resolveLocation's flight branch (the headline path for the Toronto incident) had ZERO direct test coverage. Two new cases lock in the known-airport happy path AND the unknown-airport failure mode so A4 can't silently regress. Verification: - bun test test/context-engine.test.ts → 20/20 pass (was 15) - bun run typecheck → exit 0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(L0): A4 real fix + TLA → lazy SDK resolution (Codex F5 + F7) A Codex outside-voice review on /plan-eng-review's plan caught two findings both previous eng-reviews missed. L0-A (F5) — A4 was COSMETIC, not real. Pre-fix: resolveLocation's unknown-airport branch returned tz: DEFAULT_TZ (US/Pacific) with only a `source: 'flight:XX:tz-unknown:XYZ'` sticker. The engine then computed Time/Day/quietHoursActive from US/Pacific regardless, so a flight to BOM injected "Mon 3:00 PM PT" with a footnote nobody reads. Same silent-wrong-output failure class A4 was supposed to close. Post-fix: resolveLocation returns tz: UNKNOWN_TZ. generateLiveContext short-circuits time computation when tz is UNKNOWN_TZ (now/dayOfWeek become null, wallClockQuietHours/quietHoursActive become false). formatContextBlock renders an explicit Timezone-unavailable warning in place of Time:/Day:. The LLM sees the gap, not a guess. L0-B (F7) — Top-level `await import` is a hard module-load constraint. Any OpenClaw deployment in a non-TLA runtime (older Node, CJS bridges, certain transpilers, some test shims) fails BEFORE the plugin registers. The try/catch inside doesn't help — module load can't be caught by the consumer. Post-fix: SDK resolution moved to an `ensureSdkLoaded()` async helper called from assemble() and compact() on first invocation. Module loads cleanly in every runtime; the fallback path actually catches. Tests: - The cosmetic "tz-unknown sticker" assertion is replaced with the behavioral assertion: no US/Pacific Time, no Day field, explicit Timezone-unavailable warning present. - New L0-B contract test asserts engine creation does NOT trigger SDK load and the first compact() call exercises the lazy path. Verification: - bun test test/context-engine.test.ts → 21/21 pass (20 + L0-B contract) - bun run typecheck → exit 0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(L1): scrub real names from test fixtures + CI guard (CLAUDE.md privacy rule) The /plan-eng-review pass flagged pre-existing real-name leaks in PR #873's test fixtures. CLAUDE.md's privacy rule is unambiguous: "Never reference real people, companies, funds, or private agent names in any public-facing artifact." Tests are checked-in code, distributed with every release, and indexed by GitHub search. Fixture scrub (test/context-engine.test.ts, 5 substitutions): '1:1 with Diana' → '1:1 with @alice-example' 'diana@ycombinator.com' → 'alice@example.com' 'DM Technium re: Hermes PR' → 'DM @charlie-example re: agent-fork PR' 'Post open source manifesto — from YC Labs' → '... from a-team' '~~Reply to Bob McGrew~~ — DONE' → '~~Reply to bob-example~~ — DONE' Plus matching assertion updates. Adjacent scrub: test/link-extraction.test.ts line 523 fixture entry 'people/diana-hu' → 'people/alice-example' (single occurrence, never referenced elsewhere in the test). New CI guard (scripts/check-test-real-names.sh, ~120 lines): Designed per Codex F4 review: drop the broad corporate-email regex (@openai|google|stripe...) because legitimate billing/auth fixtures use those domains. Replace with two targeted lists: - BANNED_NAMES: exact-string list of known real identifiers (Diana, Wintermute, Hermes, Technium, McGrew, YC Labs) - BANNED_EMAILS: specific addresses (currently just diana@ycombinator.com) Plus ALLOWLIST of exact `file:string` pairs that are intentional and pre-existing (the user's own email; structural tests that ASSERT a banned name is absent and therefore MUST reference it literally). Scope: test/**/*.test.ts only. Historical CHANGELOG entries, doc examples, and skill READMEs each have their own scrub status and are out of scope for this guard. Wire-in: - New `bun run check:test-names` npm script - Added to `bun run verify` chain (pre-push gate) - Added to `bun run check:all` chain (local-only superset) Allowlist documents the structural references the guard correctly identifies but cannot meaningfully strip: - test/integrations.test.ts (regex pattern in personal-info filter test) - test/recency-decay.test.ts (regression-prevention assertions) - test/serve-stdio-lifecycle.test.ts (pre-existing comment) - test/extract.test.ts (pre-existing markdown-link fixture) These flagged-but-not-scrubbed entries belong to a broader repo-wide privacy-scrub pass (deferred TODO). Verification: - bun run check:test-names → exit 0 (no new banned strings) - bun test test/context-engine.test.ts → 21/21 pass - bun test test/link-extraction.test.ts → 98/98 pass Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(L2): plugin-shape e2e + compact fallback + selector map + race-condition JSDoc The unit suite at test/context-engine.test.ts exercised createGBrainContextEngine directly — that's the ENGINE, not the PLUGIN. Until this commit, nothing tested the actual OpenClaw plugin discovery + registration path. Codex outside-voice F1 flagged the gap: "we ship a plugin we don't test as a plugin." Layer 2 closures: T-NEW1 (plugin-shape e2e, test/e2e/openclaw-context-engine-plugin.test.ts, 3 tests): - Default export has the expected plugin-entry shape (id, name, description, register) - register() wires registerContextEngine with ENGINE_ID and a factory - Factory returns a working ContextEngine that injects Live Context and threads through the mocked memory-addition SDK call Implementation note: dropped the unused `definePluginEntry` import from src/openclaw-context-engine.ts. The wrapper was a type-tag with no behavior — OpenClaw's loader inspects the default export's shape, not the wrapping. Removing it eliminated a brittle build-time SDK import that blocked mock.module() interception (Codex F1 was right). Module now loads cleanly in any runtime. T-NEW4 (compact() fallback test, test/context-engine.test.ts): - Pins the no-runtime fallback shape so a refactor that drops the fallback or returns a different shape gets caught. - Codex F9 noted that without a real SDK boundary, a spy-on-delegate test is busywork. This commit keeps just the fallback assertion (no spy, no __internal export-for-tests hatch). T-NEW6 (heartbeat-write concurrency contract, src/core/context-engine.ts): - JSDoc on loadJsonFile documenting that producers MUST use atomic-rename writes (write-to-tmp + rename) to avoid partial-read races. The engine silent-degrades to defaults on parse failure; the contract makes the expectation explicit instead of buried in behavior. T-NEW5 (e2e selector map, scripts/e2e-test-map.ts): - Added entries mapping src/core/context-engine.ts and src/openclaw-context-engine.ts to the new plugin e2e file. ci:local:diff now narrows correctly for engine changes. Verification: - bun test test/context-engine.test.ts → 22/22 pass (21 + T-NEW4) - bun test test/e2e/openclaw-context-engine-plugin.test.ts → 3/3 pass - bun run typecheck → exit 0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore(L3): ENGINE_VERSION → ENGINE_API_VERSION semantic + tasks.md size cap C-NEW1 — Engine version constant semantic. Pre-fix: `ENGINE_VERSION = '0.1.0'` looked like it should track package.json. It doesn't — it's the engine's CONTRACT version, bumped when the ContextEngine interface shape changes. Rename to ENGINE_API_VERSION makes that explicit. ENGINE_VERSION kept as a deprecated alias so existing v0.32.5 callers don't break. C-prior C2 — tasks.md size cap. resolveTodayTasks() now refuses to read a tasks file >1MB. Defends against a runaway file (clipboard-paste accident, log capture, etc) blocking every assemble() call with a multi-megabyte sync read. The size check uses statSync — same try/catch already handles missing-file via readFileSync throwing. Verification: - bun test test/context-engine.test.ts → 23/23 pass (22 + size-cap test) - bun test test/e2e/openclaw-context-engine-plugin.test.ts → 3/3 pass - bun run typecheck → exit 0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: CHANGELOG + TODOS for the Codex recalibration wave; allowlist sibling guard CHANGELOG.md — extend v0.32.5 entry with a "Codex outside-voice recalibration" subsection covering L0-A (A4 real fix), L0-B (TLA → lazy), the privacy guard redesign, the new plugin-shape e2e, and the deferred v0.32.6 items. Credits gpt-5-codex as the driver. TODOS.md — append "v0.32.6 follow-ups from PR #880" section with 13 deferred items: - Clock-injection seam (prerequisite for perf + snapshot tests) - T-NEW2 perf budget (with Codex F2 math-bug note) - T-NEW3 full-block snapshot test - C-NEW2 exports map entry (per Codex F8 — premature public API) - A3 .ts-extension resolution coupling - A5 typed openclaw/plugin-sdk ambient module shim - C-prior C5 loadJsonFile parse-error warn - C-prior C3 fractional-hour timezone offset - DST-boundary test - Multibyte sanitizer test - Dynamic airport-tz lookup (replace 30-entry static map) - DOC1 docs/openclaw-context-engine.md workspace contract - DOC2 CLAUDE.md "Key files" annotations - Repo-wide privacy scrub (24+ non-test matches) scripts/check-privacy.sh — allowlist sibling guard scripts/check-test-real-names.sh, which literally contains 'Wintermute' in its BANNED_NAMES list (same meta-rule-enforcement exception as check-privacy.sh's self-reference). Verification: bun run verify → exit 0 (full chain green: check:privacy + check:test-names + check:jsonb + check:progress + check:test-isolation + check:wasm + check:admin-build + check:admin-scope-drift + check:cli-exec + typecheck) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(L4): real openclaw-loads-the-plugin e2e — closes Codex F1 properly Until this commit, the gbrain-context plugin had two test paths: - test/context-engine.test.ts (23 unit tests against createGBrainContextEngine) - test/e2e/openclaw-context-engine-plugin.test.ts (3 e2e tests with mocked SDK) Both call our engine directly or shim the OpenClaw SDK. Codex outside-voice F1 (cited at v0.32.5 ship) flagged that nothing in the repo proves OpenClaw's actual plugin loader walks our entry file, calls register(api) against its real api object, and accepts the registration. The reviewer was right — shipping a plugin without an "OpenClaw actually loads it" test is a credibility hit on a feature whose entire purpose is to integrate with OpenClaw. L4 — test/e2e/openclaw-plugin-load-real.test.ts (6 tests, Tier 2): beforeAll: - Detects `openclaw` CLI; skips suite if missing - bun build src/openclaw-context-engine.ts → JS bundle (same packaging shape the release ships) - Writes minimal package.json + openclaw.plugin.json from templates - openclaw plugins install --link --dangerously-force-unsafe-install against an isolated --profile dir (won't touch user's openclaw state) Tests: 1. status=loaded, imported=true, activated=true 2. Default-export id/name/description metadata round-trips through openclaw's plugin loader unchanged 3. register(api) produced zero error-level diagnostics (only the expected trust warning for --link installs) 4. plugins.slots.contextEngine binding to "gbrain-context" passes openclaw config validate 5. openclaw plugins doctor surfaces zero errors for our plugin id 6. Public-SDK round-trip: imports registerContextEngine from openclaw/plugin-sdk (resolved via realpathSync on the openclaw binary's symlink so it works for Homebrew, npm -g, nvm, asdf, volta installs uniformly), registers our factory, then exercises assemble() and asserts the Live Context block appears afterAll: - Uninstalls the plugin (best-effort) + rm -rf the isolated profile dir + the tempdir fixture Fixture: test/fixtures/openclaw-plugin-real/ holds the manifest templates (package.json.template + openclaw.plugin.json.template). The test writes fresh copies into a per-run tempdir so the fixture itself stays read-only. Selector map: scripts/e2e-test-map.ts now points BOTH source files (src/core/context-engine.ts, src/openclaw-context-engine.ts) at BOTH the mocked-SDK plugin-shape e2e AND this real-loader e2e. ci:local:diff fires both on either change. Verification: - bun test test/e2e/openclaw-plugin-load-real.test.ts → 6/6 pass - bun test test/context-engine.test.ts test/e2e/openclaw-context-engine-plugin.test.ts test/e2e/openclaw-plugin-load-real.test.ts → 32/32 pass total - bun run typecheck → exit 0 - bun run verify → exit 0 (full chain green) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: garrytan-agents <garrytan-agents@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
118 lines
5.4 KiB
JSON
118 lines
5.4 KiB
JSON
{
|
|
"name": "gbrain",
|
|
"version": "0.32.5",
|
|
"description": "Postgres-native personal knowledge brain with hybrid RAG search",
|
|
"type": "module",
|
|
"main": "src/core/index.ts",
|
|
"bin": {
|
|
"gbrain": "src/cli.ts"
|
|
},
|
|
"exports": {
|
|
".": "./src/core/index.ts",
|
|
"./engine": "./src/core/engine.ts",
|
|
"./types": "./src/core/types.ts",
|
|
"./operations": "./src/core/operations.ts",
|
|
"./minions": "./src/core/minions/index.ts",
|
|
"./engine-factory": "./src/core/engine-factory.ts",
|
|
"./pglite-engine": "./src/core/pglite-engine.ts",
|
|
"./link-extraction": "./src/core/link-extraction.ts",
|
|
"./import-file": "./src/core/import-file.ts",
|
|
"./transcription": "./src/core/transcription.ts",
|
|
"./embedding": "./src/core/embedding.ts",
|
|
"./config": "./src/core/config.ts",
|
|
"./markdown": "./src/core/markdown.ts",
|
|
"./backoff": "./src/core/backoff.ts",
|
|
"./search/hybrid": "./src/core/search/hybrid.ts",
|
|
"./search/expansion": "./src/core/search/expansion.ts",
|
|
"./extract": "./src/commands/extract.ts"
|
|
},
|
|
"scripts": {
|
|
"dev": "bun run src/cli.ts",
|
|
"build": "bun build --compile --outfile bin/gbrain src/cli.ts",
|
|
"build:all": "bun build --compile --target=bun-darwin-arm64 --outfile bin/gbrain-darwin-arm64 src/cli.ts && bun build --compile --target=bun-linux-x64 --outfile bin/gbrain-linux-x64 src/cli.ts",
|
|
"build:admin": "cd admin && bun run build",
|
|
"build:schema": "bash scripts/build-schema.sh",
|
|
"build:llms": "bun run scripts/build-llms.ts",
|
|
"build:pglite-snapshot": "bun run scripts/build-pglite-snapshot.ts",
|
|
"test": "bash scripts/run-unit-parallel.sh",
|
|
"test:full": "bun run verify && bash scripts/run-unit-parallel.sh && bun run test:slow && ([ -n \"$DATABASE_URL\" ] && bash scripts/run-e2e.sh || echo '[test:full] skipped E2E (no DATABASE_URL); run docker-compose -f docker-compose.ci.yml up + bun run test:e2e to include' 1>&2)",
|
|
"verify": "bun run check:privacy && bun run check:test-names && bun run check:jsonb && bun run check:progress && bun run check:test-isolation && bun run check:wasm && bun run check:admin-build && bun run check:admin-scope-drift && bun run check:cli-exec && bun run check:system-of-record && bun run typecheck",
|
|
"check:system-of-record": "scripts/check-system-of-record.sh",
|
|
"check:admin-scope-drift": "scripts/check-admin-scope-drift.sh",
|
|
"check:cli-exec": "scripts/check-cli-executable.sh",
|
|
"check:all": "scripts/check-privacy.sh && scripts/check-test-real-names.sh && scripts/check-jsonb-pattern.sh && scripts/check-progress-to-stdout.sh && scripts/check-no-legacy-getconnection.sh && scripts/check-test-isolation.sh && scripts/check-trailing-newline.sh && scripts/check-wasm-embedded.sh && scripts/check-exports-count.sh && scripts/check-admin-build.sh && scripts/check-admin-scope-drift.sh && scripts/check-cli-executable.sh",
|
|
"check:wasm": "scripts/check-wasm-embedded.sh",
|
|
"check:newlines": "scripts/check-trailing-newline.sh",
|
|
"test:e2e": "bash scripts/run-e2e.sh",
|
|
"test:slow": "bash scripts/run-slow-tests.sh",
|
|
"test:profile": "bash scripts/profile-tests.sh",
|
|
"test:serial": "bash scripts/run-serial-tests.sh",
|
|
"ci:local": "bash scripts/ci-local.sh",
|
|
"ci:local:diff": "bash scripts/ci-local.sh --diff",
|
|
"ci:select-e2e": "bun run scripts/select-e2e.ts",
|
|
"typecheck": "tsc --noEmit",
|
|
"check:jsonb": "scripts/check-jsonb-pattern.sh",
|
|
"check:privacy": "scripts/check-privacy.sh",
|
|
"check:test-names": "scripts/check-test-real-names.sh",
|
|
"check:progress": "scripts/check-progress-to-stdout.sh",
|
|
"check:exports-count": "scripts/check-exports-count.sh",
|
|
"check:admin-build": "scripts/check-admin-build.sh",
|
|
"check:test-isolation": "scripts/check-test-isolation.sh",
|
|
"postinstall": "command -v gbrain >/dev/null 2>&1 && gbrain apply-migrations --yes --non-interactive || echo '[gbrain] postinstall skipped. If installed via bun install -g github:...: run `gbrain doctor` and `gbrain apply-migrations --yes` manually. See https://github.com/garrytan/gbrain/issues/218' 1>&2",
|
|
"prepublish:clawhub": "bun run build:all",
|
|
"publish:clawhub": "clawhub package publish . --family bundle-plugin"
|
|
},
|
|
"openclaw": {
|
|
"compat": {
|
|
"pluginApi": ">=2026.4.0"
|
|
},
|
|
"extensions": [
|
|
"./src/openclaw-context-engine.ts"
|
|
]
|
|
},
|
|
"dependencies": {
|
|
"@ai-sdk/anthropic": "^3.0.71",
|
|
"@ai-sdk/google": "^3.0.64",
|
|
"@ai-sdk/openai": "^3.0.53",
|
|
"@ai-sdk/openai-compatible": "^2.0.41",
|
|
"@anthropic-ai/sdk": "^0.30.0",
|
|
"@aws-sdk/client-s3": "^3.1028.0",
|
|
"@dqbd/tiktoken": "^1.0.22",
|
|
"@electric-sql/pglite": "0.4.3",
|
|
"@jsquash/avif": "^2.1.1",
|
|
"@jsquash/png": "^3.1.1",
|
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
"ai": "^6.0.168",
|
|
"cookie-parser": "^1.4.7",
|
|
"cors": "^2.8.5",
|
|
"eventsource-parser": "^3.0.8",
|
|
"exifr": "^7.1.3",
|
|
"express": "^5.1.0",
|
|
"express-rate-limit": "^7.5.0",
|
|
"gray-matter": "^4.0.3",
|
|
"heic-decode": "^2.1.0",
|
|
"marked": "^18.0.0",
|
|
"openai": "^4.0.0",
|
|
"pgvector": "^0.2.0",
|
|
"postgres": "^3.4.0",
|
|
"tree-sitter-wasms": "0.1.13",
|
|
"web-tree-sitter": "0.22.6",
|
|
"zod": "^4.3.6"
|
|
},
|
|
"devDependencies": {
|
|
"@types/bun": "latest",
|
|
"@types/cookie-parser": "^1.4.7",
|
|
"@types/cors": "^2.8.19",
|
|
"@types/express": "^5.0.6",
|
|
"bun-types": "^1.3.13",
|
|
"typescript": "^5.6.0"
|
|
},
|
|
"trustedDependencies": [
|
|
"@electric-sql/pglite"
|
|
],
|
|
"engines": {
|
|
"bun": ">=1.3.10"
|
|
},
|
|
"license": "MIT"
|
|
}
|