Compare commits

...
Author SHA1 Message Date
Nicolò Boschi 9e16dadaa4 fix(ci): use file: dep for agent-sdk in openclaw, whitelist in lockfile checker
- openclaw depends on @vectorize-io/hindsight-agent-sdk via file: ref
  (matching how control-plane depends on hindsight-client)
- Lockfile checker whitelists hindsight-tools/* workspace deps
- Regenerate openclaw lockfile
2026-04-29 15:49:08 +02:00
Nicolò Boschi 300a45c2aa fix(ci): add hindsight-tools to npm workspaces, build agent-sdk before openclaw
- Add hindsight-tools/* to root workspaces so npm resolves the agent-sdk
- Build agent-sdk before openclaw in all 3 openclaw CI jobs
- Use root npm ci + workspace builds for tool CI jobs
- Regenerate lockfiles
2026-04-29 15:37:16 +02:00
Nicolò Boschi f6d522a650 refactor: move tests to tests/ dirs, add prettier for hindsight-tools
- Move tests from src/ to tests/ matching repo conventions
- Add hindsight-tools/ prettier block to lint.sh
- Format all files with prettier
2026-04-29 15:28:00 +02:00
Nicolò Boschi 3485ea2541 test: add tests for hindsight-agent-sdk and self-driving-agents
Agent SDK (11 tests): tool creation, endpoint routing, request bodies,
auth headers, page defaults (delta mode, observation facts).

Self-driving-agents CLI (23 tests): recursive content discovery,
local/GitHub path detection, ANSI JSON parsing, bank ID resolution
from plugin config.

CI: add test-hindsight-agent-sdk and test-self-driving-agents jobs
with detect-changes filtering.
2026-04-29 15:24:57 +02:00
Nicolò Boschi 12e5d2a217 feat: create hindsight-agent-sdk, move tools under hindsight-tools/
- New @vectorize-io/hindsight-agent-sdk package with harness-agnostic
  knowledge tools using @vectorize-io/hindsight-client (no raw HTTP)
- OpenClaw plugin now imports from the SDK instead of inline knowledge-tools.ts
- Move self-driving-agents and hindsight-agent-sdk under hindsight-tools/
- Update release-tool.sh for new paths
2026-04-29 15:24:11 +02:00
Nicolò Boschi 086bcac64d feat: knowledge tools opt-in via enableKnowledgeTools config flag
Plugin: agent_knowledge_* tools only register when enableKnowledgeTools
is true in the plugin config (default: false).

CLI: automatically sets enableKnowledgeTools=true in openclaw.json
during install.
2026-04-29 15:24:11 +02:00
Nicolò Boschi 552f9d19c8 cleanup: remove hindsight-agent-sdk/skill, now bundled in self-driving-agents 2026-04-29 15:24:11 +02:00
Nicolò Boschi cd2da82819 refactor(self-driving-agents): bundle SKILL.md as file, read at runtime
Move the skill from a hardcoded string to a bundled file at skill/SKILL.md.
Each CLI version ships its own skill — re-running install upgrades it.
2026-04-29 15:24:11 +02:00
Nicolò Boschi 402f0d3dab cleanup: remove unrelated files (screenshots, PDF, pretext-poc) 2026-04-29 15:22:53 +02:00
Nicolò Boschi 2bf399c2ee feat(self-driving-agents): recursive content discovery, drop content/ convention
Content files (.md, .txt, etc.) are now found recursively from the
agent directory root. No special content/ subdirectory needed.

This enables nested agent repos where pointing at any level ingests
all files below it:
- install marketing → all 30 files + root bank-template.json
- install marketing/seo → only SEO files + seo/bank-template.json
2026-04-29 15:22:52 +02:00
Nicolò Boschi 71ad98c7e0 feat(self-driving-agents): TUI wizard, TS client, GitHub agent sources
- Replace raw HTTP with @vectorize-io/hindsight-client SDK
- Add @clack/prompts for polished terminal UI (spinners, confirms, notes)
- Support GitHub agent sources: bare name defaults to vectorize-io/self-driving-agents,
  org/repo/path fetches from any public repo, local paths still work
- Remove bootstrap code from openclaw plugin (CLI handles all API calls)
- Fix ANSI-polluted JSON parsing for openclaw agents list
- Run setup wizard inline when user declines current config
2026-04-29 15:22:52 +02:00
Nicolò Boschi 0017ee0877 feat: CLI checks plugin install+config, runs wizard if needed 2026-04-29 15:22:52 +02:00
Nicolò Boschi 1b7357d99a refactor: CLI does zero API calls, plugin bootstraps template+content on first session 2026-04-29 15:22:52 +02:00
Nicolò Boschi c88e6e4cac cleanup: rename wiki→knowledge, add release-tool.sh, interactive cloud setup, remove SDKs 2026-04-29 15:22:52 +02:00
Nicolò Boschi 858809ec06 cleanup: remove Rust CLI + Python CLI (superseded by self-driving-agents TS CLI) 2026-04-29 15:22:52 +02:00
Nicolò Boschi ac7c1fcd8c cleanup: remove MCP tool changes, Python/TS SDKs, Claude Code wiki — keep only openclaw tools + skill + CLI 2026-04-29 15:22:52 +02:00
Nicolò Boschi dc1a741642 rename: hindsight-agent-setup → self-driving-agents 2026-04-29 15:22:45 +02:00
Nicolò Boschi 729639a557 chore: publish-ready package.json, README, .gitignore for self-driving-agents 2026-04-29 15:22:45 +02:00
Nicolò Boschi b66b36c080 fix: list_pages uses detail=metadata to avoid blowing up context 2026-04-29 15:22:45 +02:00
Nicolò Boschi b92346b696 cleanup: remove setup backwards compat 2026-04-29 15:22:45 +02:00
Nicolò Boschi 28a7caa63b rename: @vectorize-io/self-driving-agents, setup→install 2026-04-29 15:22:45 +02:00
Nicolò Boschi 46a4db3b24 refactor: setup reads directory layout (bank-template.json + content/), agent name from dir 2026-04-29 15:22:45 +02:00
Nicolò Boschi fd7f218488 fix(openclaw): set tools optional=false so they're not filtered by allowlist 2026-04-29 15:22:45 +02:00
Nicolò Boschi d46e4b1e61 rename: agent_knowledge_* tools + cleaner skill (no hindsight/wiki/mental_model confusion) 2026-04-29 15:22:45 +02:00
Nicolò Boschi eb48593e6e fix(openclaw): static import for wiki-tools (ESM compat) 2026-04-29 15:22:45 +02:00
Nicolò Boschi 838f8e9be1 feat: standalone hindsight-agent-setup (npx-able) for all harnesses 2026-04-29 15:22:45 +02:00
Nicolò Boschi ba8f119448 feat(openclaw): register wiki tools via registerTool API 2026-04-29 15:22:45 +02:00
Nicolò Boschi 3ad390d834 feat: add trigger params to MCP create_mental_model + MCP-based skill
- MCP create_mental_model now accepts trigger_mode, trigger_exclude_mental_models,
  trigger_fact_types params (both multi-bank and single-bank modes)
- Skill uses mcp__hindsight__* tools directly — no CLI, no scripts
- Bank scoped via MCP URL: /mcp/banks/{bank_id}/
2026-04-29 15:22:45 +02:00
Nicolò Boschi 514697a81f refactor: move skill to SDK, remove harness-specific skill from claude-code 2026-04-29 15:22:45 +02:00
Nicolò Boschi e2f5bd0380 feat: hindsight-agent-sdk (Python + TypeScript) + Claude Code wiki integration 2026-04-29 15:22:45 +02:00
Nicolò Boschi d18e83b083 feat(claude-code): add wiki script + agent-knowledge skill
wiki.py: CLI for knowledge pages, recall, ingest, documents.
Uses the existing plugin lib/ for bank resolution and API calls.
No separate config — reads from the same settings.json as retain/recall hooks.

agent-knowledge skill: teaches the agent to use wiki.py commands.
Bank resolution is automatic (same as retain hooks).
Pages default to: delta mode, observation-only, exclude mental models.
2026-04-29 15:22:45 +02:00
23 changed files with 4560 additions and 169 deletions
+83
View File
@@ -53,6 +53,8 @@ jobs:
integrations-pipecat: ${{ steps.filter.outputs.integrations-pipecat }}
integrations-agentcore: ${{ steps.filter.outputs.integrations-agentcore }}
integrations-smolagents: ${{ steps.filter.outputs.integrations-smolagents }}
tools-agent-sdk: ${{ steps.filter.outputs.tools-agent-sdk }}
tools-self-driving-agents: ${{ steps.filter.outputs.tools-self-driving-agents }}
dev: ${{ steps.filter.outputs.dev }}
ci: ${{ steps.filter.outputs.ci }}
# Secrets are available for internal PRs, pull_request_review, and workflow_dispatch.
@@ -145,6 +147,10 @@ jobs:
- 'hindsight-integrations/agentcore/**'
integrations-smolagents:
- 'hindsight-integrations/smolagents/**'
tools-agent-sdk:
- 'hindsight-tools/hindsight-agent-sdk/**'
tools-self-driving-agents:
- 'hindsight-tools/self-driving-agents/**'
dev:
- 'hindsight-dev/**'
ci:
@@ -298,6 +304,9 @@ jobs:
- name: Build hindsight-all-npm (openclaw dep)
run: npm run build --workspace=hindsight-all-npm
- name: Build hindsight-agent-sdk (openclaw dep)
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
- name: Install openclaw dependencies
working-directory: ./hindsight-integrations/openclaw
run: npm ci
@@ -361,6 +370,9 @@ jobs:
- name: Build hindsight-all-npm (openclaw dep)
run: npm run build --workspace=hindsight-all-npm
- name: Build hindsight-agent-sdk (openclaw dep)
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
- name: Install openclaw dependencies
working-directory: ./hindsight-integrations/openclaw
run: npm ci
@@ -515,6 +527,72 @@ jobs:
working-directory: ./hindsight-integrations/opencode
run: npm run build
test-hindsight-agent-sdk:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.tools-agent-sdk == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install root workspace dependencies
run: npm ci
- name: Build hindsight-client (agent-sdk dep)
run: npm run build --workspace=hindsight-clients/typescript
- name: Run tests
run: npm test --workspace=hindsight-tools/hindsight-agent-sdk
- name: Build
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
test-self-driving-agents:
needs: [detect-changes]
if: >-
github.event_name != 'pull_request_review' &&
(github.event_name == 'workflow_dispatch' ||
needs.detect-changes.outputs.tools-self-driving-agents == 'true' ||
needs.detect-changes.outputs.ci == 'true')
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha || '' }}
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: package-lock.json
- name: Install root workspace dependencies
run: npm ci
- name: Build hindsight-client (self-driving-agents dep)
run: npm run build --workspace=hindsight-clients/typescript
- name: Run tests
run: npm test --workspace=hindsight-tools/self-driving-agents
- name: Build
run: npm run build --workspace=hindsight-tools/self-driving-agents
test-cloudflare-oauth-proxy-integration:
needs: [detect-changes]
if: >-
@@ -1883,6 +1961,9 @@ jobs:
- name: Build hindsight-all-npm (openclaw dep)
run: npm run build --workspace=hindsight-all-npm
- name: Build hindsight-agent-sdk (openclaw dep)
run: npm run build --workspace=hindsight-tools/hindsight-agent-sdk
- name: Install openclaw integration dependencies
working-directory: ./hindsight-integrations/openclaw
run: npm ci
@@ -3178,6 +3259,8 @@ jobs:
- test-embed
- test-embed-windows
- test-hindsight-all
- test-hindsight-agent-sdk
- test-self-driving-agents
- test-doc-examples
- test-upgrade
- verify-generated-files
+173 -156
View File
@@ -1,15 +1,16 @@
{
"name": "@vectorize-io/hindsight-openclaw",
"version": "0.6.0",
"version": "0.6.6",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@vectorize-io/hindsight-openclaw",
"version": "0.6.0",
"version": "0.6.6",
"license": "MIT",
"dependencies": {
"@clack/prompts": "^1.2.0",
"@vectorize-io/hindsight-agent-sdk": "file:../../hindsight-tools/hindsight-agent-sdk",
"@vectorize-io/hindsight-all": "^0.1.0",
"@vectorize-io/hindsight-client": "^0.5.0"
},
@@ -27,6 +28,18 @@
"node": ">=22"
}
},
"../../hindsight-tools/hindsight-agent-sdk": {
"name": "@vectorize-io/hindsight-agent-sdk",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@vectorize-io/hindsight-client": "^0.5.6"
},
"devDependencies": {
"typescript": "^5.4",
"vitest": "^4.1.2"
}
},
"node_modules/@clack/core": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-1.2.0.tgz",
@@ -50,9 +63,9 @@
}
},
"node_modules/@emnapi/core": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz",
"integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz",
"integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -62,9 +75,9 @@
}
},
"node_modules/@emnapi/runtime": {
"version": "1.9.2",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz",
"integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==",
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz",
"integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -91,9 +104,9 @@
"license": "MIT"
},
"node_modules/@napi-rs/wasm-runtime": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz",
"integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==",
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz",
"integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -110,9 +123,9 @@
}
},
"node_modules/@oxc-project/types": {
"version": "0.124.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz",
"integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==",
"version": "0.127.0",
"resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.127.0.tgz",
"integrity": "sha512-aIYXQBo4lCbO4z0R3FHeucQHpF46l2LbMdxRvqvuRuW2OxdnSkcng5B8+K12spgLDj93rtN3+J2Vac/TIO+ciQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -127,9 +140,9 @@
"license": "MIT"
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz",
"integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-s70pVGhw4zqGeFnXWvAzJDlvxhlRollagdCCKRgOsgUOH3N1l0LIxf83AtGzmb5SiVM4Hjl5HyarMRfdfj3DaQ==",
"cpu": [
"arm64"
],
@@ -144,9 +157,9 @@
}
},
"node_modules/@rolldown/binding-darwin-arm64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz",
"integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-4ksWc9n0mhlZpZ9PMZgTGjeOPRu8MB1Z3Tz0Mo02eWfWCHMW1zN82Qz/pL/rC+yQa+8ZnutMF0JjJe7PjwasYw==",
"cpu": [
"arm64"
],
@@ -161,9 +174,9 @@
}
},
"node_modules/@rolldown/binding-darwin-x64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz",
"integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.17.tgz",
"integrity": "sha512-SUSDOI6WwUVNcWxd02QEBjLdY1VPHvlEkw6T/8nYG322iYWCTxRb1vzk4E+mWWYehTp7ERibq54LSJGjmouOsw==",
"cpu": [
"x64"
],
@@ -178,9 +191,9 @@
}
},
"node_modules/@rolldown/binding-freebsd-x64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz",
"integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.17.tgz",
"integrity": "sha512-hwnz3nw9dbJ05EDO/PvcjaaewqqDy7Y1rn1UO81l8iIK1GjenME75dl16ajbvSSMfv66WXSRCYKIqfgq2KCfxw==",
"cpu": [
"x64"
],
@@ -195,9 +208,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz",
"integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.17.tgz",
"integrity": "sha512-IS+W7epTcwANmFSQFrS1SivEXHtl1JtuQA9wlxrZTcNi6mx+FDOYrakGevvvTwgj2JvWiK8B29/qD9BELZPyXQ==",
"cpu": [
"arm"
],
@@ -212,9 +225,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-e6usGaHKW5BMNZOymS1UcEYGowQMWcgZ71Z17Sl/h2+ZziNJ1a9n3Zvcz6LdRyIW5572wBCTH/Z+bKuZouGk9Q==",
"cpu": [
"arm64"
],
@@ -229,9 +242,9 @@
}
},
"node_modules/@rolldown/binding-linux-arm64-musl": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz",
"integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.17.tgz",
"integrity": "sha512-b/CgbwAJpmrRLp02RPfhbudf5tZnN9nsPWK82znefso832etkem8H7FSZwxrOI9djcdTP7U6YfNhbRnh7djErg==",
"cpu": [
"arm64"
],
@@ -246,9 +259,9 @@
}
},
"node_modules/@rolldown/binding-linux-ppc64-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-4EII1iNGRUN5WwGbF/kOh/EIkoDN9HsupgLQoXfY+D1oyJm7/F4t5PYU5n8SWZgG0FEwakyM8pGgwcBYruGTlA==",
"cpu": [
"ppc64"
],
@@ -263,9 +276,9 @@
}
},
"node_modules/@rolldown/binding-linux-s390x-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-AH8oq3XqQo4IibpVXvPeLDI5pzkpYn0WiZAfT05kFzoJ6tQNzwRdDYQ45M8I/gslbodRZwW8uxLhbSBbkv96rA==",
"cpu": [
"s390x"
],
@@ -280,9 +293,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-gnu": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz",
"integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.17.tgz",
"integrity": "sha512-cLnjV3xfo7KslbU41Z7z8BH/E1y5mzUYzAqih1d1MDaIGZRCMqTijqLv76/P7fyHuvUcfGsIpqCdddbxLLK9rA==",
"cpu": [
"x64"
],
@@ -297,9 +310,9 @@
}
},
"node_modules/@rolldown/binding-linux-x64-musl": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz",
"integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.17.tgz",
"integrity": "sha512-0phclDw1spsL7dUB37sIARuis2tAgomCJXAHZlpt8PXZ4Ba0dRP1e+66lsRqrfhISeN9bEGNjQs+T/Fbd7oYGw==",
"cpu": [
"x64"
],
@@ -314,9 +327,9 @@
}
},
"node_modules/@rolldown/binding-openharmony-arm64": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz",
"integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.17.tgz",
"integrity": "sha512-0ag/hEgXOwgw4t8QyQvUCxvEg+V0KBcA6YuOx9g0r02MprutRF5dyljgm3EmR02O292UX7UeS6HzWHAl6KgyhA==",
"cpu": [
"arm64"
],
@@ -331,9 +344,9 @@
}
},
"node_modules/@rolldown/binding-wasm32-wasi": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz",
"integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.17.tgz",
"integrity": "sha512-LEXei6vo0E5wTGwpkJ4KoT3OZJRnglwldt5ziLzOlc6qqb55z4tWNq2A+PFqCJuvWWdP53CVhG1Z9NtToDPJrA==",
"cpu": [
"wasm32"
],
@@ -341,18 +354,18 @@
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "1.9.2",
"@emnapi/runtime": "1.9.2",
"@napi-rs/wasm-runtime": "^1.1.3"
"@emnapi/core": "1.10.0",
"@emnapi/runtime": "1.10.0",
"@napi-rs/wasm-runtime": "^1.1.4"
},
"engines": {
"node": ">=14.0.0"
"node": "^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz",
"integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.17.tgz",
"integrity": "sha512-gUmyzBl3SPMa6hrqFUth9sVfcLBlYsbMzBx5PlexMroZStgzGqlZ26pYG89rBb45Mnia+oil6YAIFeEWGWhoZA==",
"cpu": [
"arm64"
],
@@ -367,9 +380,9 @@
}
},
"node_modules/@rolldown/binding-win32-x64-msvc": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz",
"integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.17.tgz",
"integrity": "sha512-3hkiolcUAvPB9FLb3UZdfjVVNWherN1f/skkGWJP/fgSQhYUZpSIRr0/I8ZK9TkF3F7kxvJAk0+IcKvPHk9qQg==",
"cpu": [
"x64"
],
@@ -384,9 +397,9 @@
}
},
"node_modules/@rolldown/pluginutils": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz",
"integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.17.tgz",
"integrity": "sha512-n8iosDOt6Ig1UhJ2AYqoIhHWh/isz0xpicHTzpKBeotdVsTEcxsSA/i3EVM7gQAj0rU27OLAxCjzlj15IWY7bg==",
"dev": true,
"license": "MIT"
},
@@ -443,6 +456,10 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@vectorize-io/hindsight-agent-sdk": {
"resolved": "../../hindsight-tools/hindsight-agent-sdk",
"link": true
},
"node_modules/@vectorize-io/hindsight-all": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@vectorize-io/hindsight-all/-/hindsight-all-0.1.0.tgz",
@@ -453,22 +470,22 @@
}
},
"node_modules/@vectorize-io/hindsight-client": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@vectorize-io/hindsight-client/-/hindsight-client-0.5.0.tgz",
"integrity": "sha512-Ob4hSIpx1bb1BU0A7SDPQdFkDhJPi+haA1HoaQFrE2qz2VanSeoKulGEmnbH5RgVJuKRTjqQPAYrgiBN5r3Xlg==",
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/@vectorize-io/hindsight-client/-/hindsight-client-0.5.6.tgz",
"integrity": "sha512-VBMkCQP7dCy9BqkoqhZxxl5sVW6opKT3l6wexval7ZM4syTOGlmpu7BRhtFycxWmkWjXwH+XM6n4lzBoWTSPdA==",
"license": "MIT"
},
"node_modules/@vitest/expect": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz",
"integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz",
"integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@standard-schema/spec": "^1.1.0",
"@types/chai": "^5.2.2",
"@vitest/spy": "4.1.4",
"@vitest/utils": "4.1.4",
"@vitest/spy": "4.1.5",
"@vitest/utils": "4.1.5",
"chai": "^6.2.2",
"tinyrainbow": "^3.1.0"
},
@@ -477,13 +494,13 @@
}
},
"node_modules/@vitest/mocker": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz",
"integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz",
"integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/spy": "4.1.4",
"@vitest/spy": "4.1.5",
"estree-walker": "^3.0.3",
"magic-string": "^0.30.21"
},
@@ -504,9 +521,9 @@
}
},
"node_modules/@vitest/pretty-format": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz",
"integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz",
"integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -517,13 +534,13 @@
}
},
"node_modules/@vitest/runner": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz",
"integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz",
"integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.1.4",
"@vitest/utils": "4.1.5",
"pathe": "^2.0.3"
},
"funding": {
@@ -531,14 +548,14 @@
}
},
"node_modules/@vitest/snapshot": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz",
"integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz",
"integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.4",
"@vitest/utils": "4.1.4",
"@vitest/pretty-format": "4.1.5",
"@vitest/utils": "4.1.5",
"magic-string": "^0.30.21",
"pathe": "^2.0.3"
},
@@ -547,9 +564,9 @@
}
},
"node_modules/@vitest/spy": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz",
"integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz",
"integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==",
"dev": true,
"license": "MIT",
"funding": {
@@ -557,13 +574,13 @@
}
},
"node_modules/@vitest/ui": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.4.tgz",
"integrity": "sha512-EgFR7nlj5iTDYZYCvavjFokNYwr3c3ry0sFiCg+N7B233Nwp+NNx7eoF/XvMWDCKY71xXAG3kFkt97ZHBJVL8A==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/ui/-/ui-4.1.5.tgz",
"integrity": "sha512-3Z9HNFiV0IF1fk0JPiK+7kE1GcaIPefQQIBYur6PM5yFIq6agys3uqP/0t966e1wXfmjbRCHDe7qW236Xjwnag==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/utils": "4.1.4",
"@vitest/utils": "4.1.5",
"fflate": "^0.8.2",
"flatted": "^3.4.2",
"pathe": "^2.0.3",
@@ -575,17 +592,17 @@
"url": "https://opencollective.com/vitest"
},
"peerDependencies": {
"vitest": "4.1.4"
"vitest": "4.1.5"
}
},
"node_modules/@vitest/utils": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz",
"integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz",
"integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/pretty-format": "4.1.4",
"@vitest/pretty-format": "4.1.5",
"convert-source-map": "^2.0.0",
"tinyrainbow": "^3.1.0"
},
@@ -631,9 +648,9 @@
}
},
"node_modules/es-module-lexer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
"integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz",
"integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==",
"dev": true,
"license": "MIT"
},
@@ -1067,9 +1084,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.9",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
"integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==",
"version": "8.5.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",
"integrity": "sha512-W62t/Se6rA0Az3DfCL0AqJwXuKwBeYg6nOaIgzP+xZ7N5BFCI7DYi1qs6ygUYT6rvfi6t9k65UMLJC+PHZpDAA==",
"dev": true,
"funding": [
{
@@ -1096,14 +1113,14 @@
}
},
"node_modules/rolldown": {
"version": "1.0.0-rc.15",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz",
"integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==",
"version": "1.0.0-rc.17",
"resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.17.tgz",
"integrity": "sha512-ZrT53oAKrtA4+YtBWPQbtPOxIbVDbxT0orcYERKd63VJTF13zPcgXTvD4843L8pcsI7M6MErt8QtON6lrB9tyA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.124.0",
"@rolldown/pluginutils": "1.0.0-rc.15"
"@oxc-project/types": "=0.127.0",
"@rolldown/pluginutils": "1.0.0-rc.17"
},
"bin": {
"rolldown": "bin/cli.mjs"
@@ -1112,21 +1129,21 @@
"node": "^20.19.0 || >=22.12.0"
},
"optionalDependencies": {
"@rolldown/binding-android-arm64": "1.0.0-rc.15",
"@rolldown/binding-darwin-arm64": "1.0.0-rc.15",
"@rolldown/binding-darwin-x64": "1.0.0-rc.15",
"@rolldown/binding-freebsd-x64": "1.0.0-rc.15",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15",
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15",
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.15",
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.15",
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.15",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15",
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15"
"@rolldown/binding-android-arm64": "1.0.0-rc.17",
"@rolldown/binding-darwin-arm64": "1.0.0-rc.17",
"@rolldown/binding-darwin-x64": "1.0.0-rc.17",
"@rolldown/binding-freebsd-x64": "1.0.0-rc.17",
"@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.17",
"@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-arm64-musl": "1.0.0-rc.17",
"@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-x64-gnu": "1.0.0-rc.17",
"@rolldown/binding-linux-x64-musl": "1.0.0-rc.17",
"@rolldown/binding-openharmony-arm64": "1.0.0-rc.17",
"@rolldown/binding-wasm32-wasi": "1.0.0-rc.17",
"@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.17",
"@rolldown/binding-win32-x64-msvc": "1.0.0-rc.17"
}
},
"node_modules/siginfo": {
@@ -1175,9 +1192,9 @@
"license": "MIT"
},
"node_modules/std-env": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
"integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz",
"integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==",
"dev": true,
"license": "MIT"
},
@@ -1189,9 +1206,9 @@
"license": "MIT"
},
"node_modules/tinyexec": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz",
"integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==",
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz",
"integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1265,17 +1282,17 @@
"license": "MIT"
},
"node_modules/vite": {
"version": "8.0.8",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz",
"integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==",
"version": "8.0.10",
"resolved": "https://registry.npmjs.org/vite/-/vite-8.0.10.tgz",
"integrity": "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw==",
"dev": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.32.0",
"picomatch": "^4.0.4",
"postcss": "^8.5.8",
"rolldown": "1.0.0-rc.15",
"tinyglobby": "^0.2.15"
"postcss": "^8.5.10",
"rolldown": "1.0.0-rc.17",
"tinyglobby": "^0.2.16"
},
"bin": {
"vite": "bin/vite.js"
@@ -1343,19 +1360,19 @@
}
},
"node_modules/vitest": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz",
"integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==",
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz",
"integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@vitest/expect": "4.1.4",
"@vitest/mocker": "4.1.4",
"@vitest/pretty-format": "4.1.4",
"@vitest/runner": "4.1.4",
"@vitest/snapshot": "4.1.4",
"@vitest/spy": "4.1.4",
"@vitest/utils": "4.1.4",
"@vitest/expect": "4.1.5",
"@vitest/mocker": "4.1.5",
"@vitest/pretty-format": "4.1.5",
"@vitest/runner": "4.1.5",
"@vitest/snapshot": "4.1.5",
"@vitest/spy": "4.1.5",
"@vitest/utils": "4.1.5",
"es-module-lexer": "^2.0.0",
"expect-type": "^1.3.0",
"magic-string": "^0.30.21",
@@ -1383,12 +1400,12 @@
"@edge-runtime/vm": "*",
"@opentelemetry/api": "^1.9.0",
"@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
"@vitest/browser-playwright": "4.1.4",
"@vitest/browser-preview": "4.1.4",
"@vitest/browser-webdriverio": "4.1.4",
"@vitest/coverage-istanbul": "4.1.4",
"@vitest/coverage-v8": "4.1.4",
"@vitest/ui": "4.1.4",
"@vitest/browser-playwright": "4.1.5",
"@vitest/browser-preview": "4.1.5",
"@vitest/browser-webdriverio": "4.1.5",
"@vitest/coverage-istanbul": "4.1.5",
"@vitest/coverage-v8": "4.1.5",
"@vitest/ui": "4.1.5",
"happy-dom": "*",
"jsdom": "*",
"vite": "^6.0.0 || ^7.0.0 || ^8.0.0"
+3 -2
View File
@@ -46,8 +46,9 @@
},
"dependencies": {
"@clack/prompts": "^1.2.0",
"@vectorize-io/hindsight-client": "^0.5.0",
"@vectorize-io/hindsight-all": "^0.1.0"
"@vectorize-io/hindsight-agent-sdk": "file:../../hindsight-tools/hindsight-agent-sdk",
"@vectorize-io/hindsight-all": "^0.1.0",
"@vectorize-io/hindsight-client": "^0.5.0"
},
"devDependencies": {
"@types/node": "^20.0.0",
+39 -1
View File
@@ -2,6 +2,7 @@ import type {
MoltbotPluginAPI,
PluginConfig,
PluginHookAgentContext,
PluginToolContext,
MemoryResult,
RetainRequest,
} from "./types.js";
@@ -14,8 +15,10 @@ import { dirname, join } from "path";
import { fileURLToPath } from "url";
import * as log from "./logger.js";
import { configureLogger, setApiLogger, stopLogger } from "./logger.js";
import { mkdirSync, readFileSync } from "fs";
import { mkdirSync, readFileSync, writeFileSync, existsSync, readdirSync } from "fs";
import { extname } from "path";
import { homedir } from "os";
import { createKnowledgeTools, TOOL_NAMES } from "@vectorize-io/hindsight-agent-sdk";
function loadPackageVersion(): string {
try {
@@ -1878,6 +1881,7 @@ export default function (api: MoltbotPluginAPI) {
debug(
`[Hindsight] before_agent_start - bank: ${bankId}, channel: ${resolvedCtx.messageProvider}/${resolvedCtx.channelId}`
);
} catch (error) {
log.warn(`before_agent_start identity resolution error: ${error}`);
}
@@ -2353,6 +2357,40 @@ ${memoriesFormatted}
});
debug("[Hindsight] Hooks registered");
log.info("agent hooks registered");
// Register knowledge tools (opt-in via enableKnowledgeTools config flag)
if (pluginConfig.enableKnowledgeTools && typeof api.registerTool === "function") {
try {
const apiUrl = (() => {
const ext = detectExternalApi(pluginConfig);
return ext?.apiUrl || `http://localhost:${pluginConfig.apiPort || 9077}`;
})();
const apiToken = pluginConfig.hindsightApiToken || undefined;
// Factory: called per session with agent context, returns tools scoped to that bank
const factory = (ctx: PluginToolContext) => {
const bankId = deriveBankId(ctx as any, pluginConfig);
const tools = createKnowledgeTools({ apiUrl, apiToken, bankId });
return tools.map((t) => ({
name: t.name,
label: t.label,
description: t.description,
parameters: t.parameters,
async execute(_id: string, params: Record<string, unknown>) {
return { ...await t.execute(params), details: {} };
},
}));
};
api.registerTool(factory, {
names: [...TOOL_NAMES],
optional: false,
});
log.info("knowledge tools registered");
} catch (err) {
log.warn(`knowledge tools registration failed: ${err}`);
}
}
} catch (error) {
log.error("plugin loading error", error);
if (error instanceof Error) {
+13 -1
View File
@@ -14,13 +14,24 @@ export interface MoltbotPluginAPI {
event: string,
handler: (event: any, ctx?: any) => void | Promise<void | PluginPromptHookResult>
): void;
// Register a tool or tool factory for agents
registerTool?(
factory: (ctx: PluginToolContext) => any | any[] | null | undefined,
opts?: { name?: string; names?: string[]; optional?: boolean },
): void;
// OpenClaw framework logger — handles coloring/formatting consistently across plugins
logger: {
info(msg: string): void;
warn(msg: string): void;
error(msg: string): void;
};
// Add more as needed
}
export interface PluginToolContext {
config?: MoltbotConfig;
agentId?: string;
sessionKey?: string;
workspaceDir?: string;
}
export interface MoltbotConfig {
@@ -99,6 +110,7 @@ export interface PluginConfig {
retainQueuePath?: string; // Path to JSONL file for buffering failed retains. Default: ~/.openclaw/data/hindsight-retain-queue.jsonl
retainQueueMaxAgeMs?: number; // Max age in ms for queued items. -1 = keep forever (default: -1)
retainQueueFlushIntervalMs?: number; // How often to attempt flushing the queue in ms. Default: 60000 (1 min)
enableKnowledgeTools?: boolean; // Register agent_knowledge_* tools. Default: false. Set to true by the self-driving-agents CLI.
}
export interface ServiceConfig {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,36 @@
{
"name": "@vectorize-io/hindsight-agent-sdk",
"version": "0.1.0",
"description": "Agent knowledge tools powered by Hindsight — harness-agnostic SDK",
"type": "module",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "tsc",
"test": "vitest run tests",
"prepublishOnly": "npm run build"
},
"keywords": [
"ai-agents",
"hindsight",
"memory",
"knowledge",
"self-driving"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/vectorize-io/hindsight.git",
"directory": "hindsight-agent-sdk"
},
"dependencies": {
"@vectorize-io/hindsight-client": "^0.5.6"
},
"devDependencies": {
"typescript": "^5.4",
"vitest": "^4.1.2"
}
}
@@ -0,0 +1,242 @@
/**
* Hindsight Agent SDK — harness-agnostic knowledge tools.
*
* Provides agent_knowledge_* tool definitions that any harness can register.
* Uses @vectorize-io/hindsight-client for all API calls.
*/
import {
HindsightClient,
sdk,
createClient,
createConfig,
type HindsightClientOptions,
} from "@vectorize-io/hindsight-client";
// ── Types ──────────────────────────────────────────────
export interface KnowledgeTool {
name: string;
label: string;
description: string;
parameters: Record<string, unknown>;
execute: (params: Record<string, unknown>) => Promise<KnowledgeToolResult>;
}
export interface KnowledgeToolResult {
content: Array<{ type: "text"; text: string }>;
}
export interface CreateKnowledgeToolsOptions {
/** Hindsight API base URL */
apiUrl: string;
/** Optional API token */
apiToken?: string;
/** Memory bank ID for this agent */
bankId: string;
}
// ── Constants ──────────────────────────────────────────
const PAGE_DEFAULTS = {
mode: "delta" as const,
refresh_after_consolidation: true,
exclude_mental_models: true,
fact_types: ["observation" as const],
};
export const TOOL_NAMES = [
"agent_knowledge_list_pages",
"agent_knowledge_get_page",
"agent_knowledge_create_page",
"agent_knowledge_update_page",
"agent_knowledge_delete_page",
"agent_knowledge_recall",
"agent_knowledge_ingest",
] as const;
export type KnowledgeToolName = (typeof TOOL_NAMES)[number];
// ── Helpers ────────────────────────────────────────────
function ok(data: unknown): KnowledgeToolResult {
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
}
// ── Factory ────────────────────────────────────────────
/**
* Create the full set of agent_knowledge_* tools for a given bank.
*
* Returns harness-agnostic tool definitions. Each harness adapter
* wraps these into its native tool format.
*/
export function createKnowledgeTools(opts: CreateKnowledgeToolsOptions): KnowledgeTool[] {
const client = new HindsightClient({
baseUrl: opts.apiUrl,
apiKey: opts.apiToken,
userAgent: "hindsight-agent-sdk/0.1.0",
});
const lowLevel = createClient(
createConfig({
baseUrl: opts.apiUrl,
headers: {
...(opts.apiToken ? { Authorization: `Bearer ${opts.apiToken}` } : {}),
"User-Agent": "hindsight-agent-sdk/0.1.0",
},
})
);
const bankId = opts.bankId;
return [
{
name: "agent_knowledge_list_pages",
label: "List knowledge pages",
description:
"List all your knowledge pages (IDs and names only). Use agent_knowledge_get_page to read the full content of a specific page.",
parameters: { type: "object", properties: {} },
async execute() {
const resp = await sdk.listMentalModels({
client: lowLevel,
path: { bank_id: bankId },
query: { detail: "metadata" },
});
return ok(resp.data);
},
},
{
name: "agent_knowledge_get_page",
label: "Read a knowledge page",
description:
"Read a specific knowledge page by its ID. Returns the full synthesized content.",
parameters: {
type: "object",
properties: {
page_id: { type: "string", description: "Page ID (e.g. 'user-preferences')" },
},
required: ["page_id"],
},
async execute(params: Record<string, unknown>) {
const result = await client.getMentalModel(bankId, params.page_id as string);
return ok(result);
},
},
{
name: "agent_knowledge_create_page",
label: "Create a knowledge page",
description:
"Create a new knowledge page. The source_query is a question the system re-asks after each consolidation to rebuild the page from conversation observations. " +
"Pages auto-update as you have more conversations. Use for: user preferences, procedures, performance data, best practices.",
parameters: {
type: "object",
properties: {
page_id: {
type: "string",
description: "Unique page ID, lowercase with hyphens (e.g. 'editorial-preferences')",
},
name: { type: "string", description: "Human-readable page name" },
source_query: {
type: "string",
description:
"The question that rebuilds this page (e.g. 'What are the user\\'s editorial preferences?')",
},
},
required: ["page_id", "name", "source_query"],
},
async execute(params: Record<string, unknown>) {
const resp = await sdk.createMentalModel({
client: lowLevel,
path: { bank_id: bankId },
body: {
id: params.page_id as string,
name: params.name as string,
source_query: params.source_query as string,
max_tokens: 4096,
trigger: PAGE_DEFAULTS,
},
});
return ok(resp.data);
},
},
{
name: "agent_knowledge_update_page",
label: "Update a knowledge page",
description:
"Update a page's name or source query. The content will re-synthesize on next consolidation.",
parameters: {
type: "object",
properties: {
page_id: { type: "string", description: "Page ID to update" },
name: { type: "string", description: "New name (optional)" },
source_query: { type: "string", description: "New source query (optional)" },
},
required: ["page_id"],
},
async execute(params: Record<string, unknown>) {
const result = await client.updateMentalModel(bankId, params.page_id as string, {
name: params.name as string | undefined,
sourceQuery: params.source_query as string | undefined,
});
return ok(result);
},
},
{
name: "agent_knowledge_delete_page",
label: "Delete a knowledge page",
description: "Permanently delete a knowledge page.",
parameters: {
type: "object",
properties: { page_id: { type: "string", description: "Page ID to delete" } },
required: ["page_id"],
},
async execute(params: Record<string, unknown>) {
await client.deleteMentalModel(bankId, params.page_id as string);
return ok({ success: true });
},
},
{
name: "agent_knowledge_recall",
label: "Search memories",
description:
"Search across all retained conversations and documents for specific facts, numbers, or details not covered by your knowledge pages.",
parameters: {
type: "object",
properties: {
query: { type: "string", description: "What to search for" },
max_results: { type: "number", description: "Max results (default 10)" },
},
required: ["query"],
},
async execute(params: Record<string, unknown>) {
const result = await client.recall(bankId, params.query as string, {
maxTokens: (params.max_results as number | undefined) ?? 10,
});
return ok(result);
},
},
{
name: "agent_knowledge_ingest",
label: "Ingest a document",
description:
"Upload a document into your memory bank. Pass the full raw content — never summarize before ingesting. " +
"The system handles chunking and fact extraction. The title becomes the document ID (re-ingesting replaces it).",
parameters: {
type: "object",
properties: {
title: { type: "string", description: "Document title (becomes the document ID)" },
content: { type: "string", description: "Full raw document content" },
},
required: ["title", "content"],
},
async execute(params: Record<string, unknown>) {
const docId = (params.title as string).toLowerCase().replace(/ /g, "-");
const result = await client.retainBatch(
bankId,
[{ content: params.content as string, document_id: docId }],
{ async: true }
);
return ok(result);
},
},
];
}
@@ -0,0 +1,186 @@
import { describe, it, expect, vi, beforeEach } from "vitest";
import { createKnowledgeTools, TOOL_NAMES } from "../src/index.js";
import type { KnowledgeTool } from "../src/index.js";
// Mock fetch globally — the SDK uses @vectorize-io/hindsight-client which calls fetch.
// The generated client passes a Request object (not a plain URL string).
const mockFetch = vi.fn();
vi.stubGlobal("fetch", mockFetch);
function mockResponse(data: unknown, status = 200) {
return Promise.resolve({
ok: status >= 200 && status < 300,
status,
json: () => Promise.resolve(data),
text: () => Promise.resolve(JSON.stringify(data)),
headers: new Headers({ "content-type": "application/json" }),
clone: function () {
return this;
},
});
}
/** Extract the URL string from the first argument (Request object or string). */
function getUrl(call: any[]): string {
const arg = call[0];
return typeof arg === "string" ? arg : (arg?.url ?? String(arg));
}
/** Extract request init/options from the fetch call. */
function getOpts(call: any[]): any {
const arg = call[0];
if (typeof arg === "object" && arg?.method) return arg;
return call[1] ?? {};
}
/** Extract the body as a parsed object from a fetch call. */
async function getBody(call: any[]): Promise<any> {
const opts = getOpts(call);
if (typeof opts.body === "string") return JSON.parse(opts.body);
// Request objects have a .json() or .text() method
if (typeof opts.json === "function") return opts.json();
if (typeof opts.text === "function") return JSON.parse(await opts.text());
return undefined;
}
describe("createKnowledgeTools", () => {
let tools: KnowledgeTool[];
beforeEach(() => {
mockFetch.mockReset();
tools = createKnowledgeTools({
apiUrl: "http://localhost:9077",
apiToken: "test-token",
bankId: "test-bank",
});
});
it("returns all 7 tools", () => {
expect(tools).toHaveLength(7);
const names = tools.map((t) => t.name);
expect(names).toEqual([...TOOL_NAMES]);
});
it("each tool has required fields", () => {
for (const tool of tools) {
expect(tool.name).toBeTruthy();
expect(tool.label).toBeTruthy();
expect(tool.description).toBeTruthy();
expect(tool.parameters).toBeDefined();
expect(typeof tool.execute).toBe("function");
}
});
it("list_pages calls the correct endpoint", async () => {
mockFetch.mockReturnValueOnce(mockResponse({ models: [] }));
const tool = tools.find((t) => t.name === "agent_knowledge_list_pages")!;
const result = await tool.execute({});
expect(mockFetch).toHaveBeenCalledTimes(1);
const url = getUrl(mockFetch.mock.calls[0]);
expect(url).toContain("/v1/default/banks/test-bank/mental-models");
expect(url).toContain("detail=metadata");
expect(result.content[0].type).toBe("text");
});
it("get_page calls the correct endpoint", async () => {
mockFetch.mockReturnValueOnce(mockResponse({ id: "my-page", content: "hello" }));
const tool = tools.find((t) => t.name === "agent_knowledge_get_page")!;
await tool.execute({ page_id: "my-page" });
const url = getUrl(mockFetch.mock.calls[0]);
expect(url).toContain("/v1/default/banks/test-bank/mental-models/my-page");
});
it("create_page sends correct body with defaults", async () => {
mockFetch.mockReturnValueOnce(mockResponse({ id: "prefs" }));
const tool = tools.find((t) => t.name === "agent_knowledge_create_page")!;
await tool.execute({
page_id: "prefs",
name: "Preferences",
source_query: "What are the user's preferences?",
});
const url = getUrl(mockFetch.mock.calls[0]);
expect(url).toContain("/v1/default/banks/test-bank/mental-models");
const body = await getBody(mockFetch.mock.calls[0]);
expect(body.id).toBe("prefs");
expect(body.name).toBe("Preferences");
expect(body.source_query).toBe("What are the user's preferences?");
expect(body.max_tokens).toBe(4096);
expect(body.trigger.mode).toBe("delta");
expect(body.trigger.refresh_after_consolidation).toBe(true);
expect(body.trigger.exclude_mental_models).toBe(true);
expect(body.trigger.fact_types).toEqual(["observation"]);
});
it("update_page sends PATCH", async () => {
mockFetch.mockReturnValueOnce(mockResponse({ id: "prefs" }));
const tool = tools.find((t) => t.name === "agent_knowledge_update_page")!;
await tool.execute({ page_id: "prefs", name: "New Name" });
const url = getUrl(mockFetch.mock.calls[0]);
const opts = getOpts(mockFetch.mock.calls[0]);
expect(url).toContain("/v1/default/banks/test-bank/mental-models/prefs");
expect(opts.method).toBe("PATCH");
});
it("delete_page sends DELETE", async () => {
mockFetch.mockReturnValueOnce(mockResponse({}));
const tool = tools.find((t) => t.name === "agent_knowledge_delete_page")!;
const result = await tool.execute({ page_id: "old-page" });
const url = getUrl(mockFetch.mock.calls[0]);
const opts = getOpts(mockFetch.mock.calls[0]);
expect(url).toContain("/v1/default/banks/test-bank/mental-models/old-page");
expect(opts.method).toBe("DELETE");
expect(JSON.parse(result.content[0].text)).toEqual({ success: true });
});
it("recall sends POST with query", async () => {
mockFetch.mockReturnValueOnce(mockResponse({ results: [{ text: "found" }] }));
const tool = tools.find((t) => t.name === "agent_knowledge_recall")!;
await tool.execute({ query: "what happened?" });
const url = getUrl(mockFetch.mock.calls[0]);
const opts = getOpts(mockFetch.mock.calls[0]);
expect(url).toContain("/v1/default/banks/test-bank/memories/recall");
expect(opts.method).toBe("POST");
});
it("ingest sends POST with content and document_id", async () => {
mockFetch.mockReturnValueOnce(mockResponse({ status: "queued" }));
const tool = tools.find((t) => t.name === "agent_knowledge_ingest")!;
await tool.execute({ title: "My Document", content: "Full text here" });
const url = getUrl(mockFetch.mock.calls[0]);
expect(url).toContain("/v1/default/banks/test-bank/memories");
const body = await getBody(mockFetch.mock.calls[0]);
expect(body.items[0].document_id).toBe("my-document");
expect(body.items[0].content).toBe("Full text here");
expect(body.async).toBe(true);
});
it("works without apiToken", () => {
const noAuthTools = createKnowledgeTools({
apiUrl: "http://localhost:9077",
bankId: "test-bank",
});
expect(noAuthTools).toHaveLength(7);
});
});
describe("TOOL_NAMES", () => {
it("exports all 7 tool names", () => {
expect(TOOL_NAMES).toHaveLength(7);
expect(TOOL_NAMES).toContain("agent_knowledge_list_pages");
expect(TOOL_NAMES).toContain("agent_knowledge_ingest");
});
});
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"declaration": true,
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
},
"include": ["src"]
}
@@ -0,0 +1,2 @@
node_modules/
dist/
@@ -0,0 +1,52 @@
# @vectorize-io/self-driving-agents
Install self-driving agents with portable memory on any harness.
```bash
npx @vectorize-io/self-driving-agents install ./my-agent --harness openclaw
```
## What it does
1. Reads `bank-template.json` from the agent directory (knowledge pages, missions, directives)
2. Reads `content/` directory for reference docs to ingest
3. Resolves the Hindsight bank from the harness config
4. Imports the template and ingests content
5. Creates the harness agent, installs the skill, patches startup
## Agent directory layout
```
my-agent/
bank-template.json # optional: bank config + knowledge pages
content/ # optional: reference docs (.md, .txt, .html, etc.)
```
Agent name defaults to the directory name. Override with `--agent <name>`.
## Options
```
npx @vectorize-io/self-driving-agents install <dir> --harness <harness> [options]
--harness <h> Required. openclaw | hermes | claude-code
--agent <name> Agent name (defaults to directory name)
--api-url <url> Override Hindsight API URL
--api-token <t> Override API token
```
## Example
```bash
# Clone an agent repo
git clone https://github.com/vectorize-io/self-driving-agents
cd self-driving-agents
# Install the SEO blog writer
npx @vectorize-io/self-driving-agents install ./marketing-seo-blog-posts --harness openclaw
# Create and start the agent
openclaw agents add marketing-seo-blog-posts --workspace ~/.hindsight-agents/openclaw/marketing-seo-blog-posts --non-interactive
openclaw gateway restart
openclaw tui --session agent:marketing-seo-blog-posts:main:session1
```
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,42 @@
{
"name": "@vectorize-io/self-driving-agents",
"version": "0.1.0",
"description": "Install self-driving agents with portable memory on any harness",
"type": "module",
"main": "dist/cli.js",
"bin": {
"self-driving-agents": "dist/cli.js"
},
"files": [
"dist",
"skill"
],
"scripts": {
"build": "tsc",
"test": "vitest run tests",
"prepublishOnly": "npm run build"
},
"keywords": [
"ai-agents",
"self-driving",
"hindsight",
"memory",
"openclaw",
"hermes",
"claude-code"
],
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/vectorize-io/hindsight.git",
"directory": "hindsight-agent-setup"
},
"devDependencies": {
"typescript": "^5.4",
"vitest": "^4.1.2"
},
"dependencies": {
"@clack/prompts": "^1.2.0",
"@vectorize-io/hindsight-client": "^0.5.6"
}
}
@@ -0,0 +1,56 @@
---
name: agent-knowledge
description: Your long-term knowledge pages. Read them at session start. Create new pages when you learn something worth remembering across sessions. Pages auto-update from your conversations via Hindsight.
---
# Agent Knowledge
You have knowledge pages that persist across sessions and auto-update from your conversations.
**How it works:** Your conversations are automatically retained into a Hindsight memory bank. The system extracts observations and uses them to keep your pages current. Each page has a "source query" — a question the system re-answers after every consolidation cycle to rebuild the page content. You create pages; the system maintains them.
## At session start
Call `agent_knowledge_list_pages` to see what pages exist, then `agent_knowledge_get_page` for each one you need.
## Reading
- `agent_knowledge_list_pages()` — list page IDs and names (no content)
- `agent_knowledge_get_page(page_id)` — read the full content of a page
## Creating pages
When you learn something durable — a user preference, a working procedure, performance data — create a page immediately.
`agent_knowledge_create_page(page_id, name, source_query)`
- `page_id`: lowercase with hyphens (`editorial-preferences`)
- `source_query`: a question that produces the page content from observations
Examples:
- `"What are the user's preferences for tone, length, and formatting?"`
- `"What content strategies have performed well or poorly? Include numbers."`
- `"What are the best practices for [topic], preferring our data over generic advice?"`
## Searching memories
`agent_knowledge_recall(query)` — search across all retained conversations and documents for specific facts.
Use when pages don't cover what you need.
## Ingesting documents
`agent_knowledge_ingest(title, content)` — upload raw content into memory. Never summarize before ingesting. Save large content to a file first, read it, then pass the full text.
## Updating and deleting
- `agent_knowledge_update_page(page_id, name?, source_query?)` — change what a page tracks
- `agent_knowledge_delete_page(page_id)` — remove a page
## Important
- Pages update automatically — don't edit content directly
- State preferences clearly in your responses so the system captures them
- Create pages silently — don't announce it to the user
- Prefer fewer broad pages over many narrow ones
@@ -0,0 +1,465 @@
#!/usr/bin/env node
/**
* self-driving-agents — install a self-driving agent.
*
* npx @vectorize-io/self-driving-agents install <agent> --harness openclaw [--agent <name>]
*
* Agent resolution:
* marketing-agent → vectorize-io/self-driving-agents/marketing-agent (default repo)
* my-org/my-repo/my-agent → my-org/my-repo/my-agent on GitHub
* ./local-dir → local directory
* /absolute/path → local directory
*
* Directory layout (recursive):
* bank-template.json — optional: bank config at this level
* *.md, *.txt, ... — content files (found recursively, excluding bank-template.json)
*/
import {
readFileSync,
writeFileSync,
mkdirSync,
existsSync,
readdirSync,
statSync,
rmSync,
} from "fs";
import { join, resolve, extname, basename, relative, dirname } from "path";
import { homedir, tmpdir } from "os";
import { fileURLToPath } from "url";
import { execSync } from "child_process";
import * as p from "@clack/prompts";
import color from "picocolors";
import { HindsightClient, sdk, createClient, createConfig } from "@vectorize-io/hindsight-client";
const DEFAULT_REPO = "vectorize-io/self-driving-agents";
// ── Content discovery ──────────────────────────────────
const CONTENT_EXTS = new Set([".md", ".txt", ".html", ".json", ".csv", ".xml"]);
const IGNORED_FILES = new Set(["bank-template.json"]);
/** Recursively find all content files under `dir`, returning paths relative to `dir`. */
function findContentFiles(dir: string): string[] {
const results: string[] = [];
function walk(current: string) {
for (const entry of readdirSync(current)) {
const full = join(current, entry);
if (statSync(full).isDirectory()) {
walk(full);
} else if (CONTENT_EXTS.has(extname(entry).toLowerCase()) && !IGNORED_FILES.has(entry)) {
results.push(relative(dir, full));
}
}
}
walk(dir);
return results.sort();
}
// ── Agent resolution ───────────────────────────────────
function isLocalPath(input: string): boolean {
return (
input.startsWith("./") ||
input.startsWith("../") ||
input.startsWith("/") ||
input.startsWith("~")
);
}
/**
* Resolve the agent specifier to a local directory.
*
* - Local paths (./foo, /foo, ~/foo) → resolve directly
* - "name" → GitHub: vectorize-io/self-driving-agents/name
* - "org/repo/path" → GitHub: org/repo/path
*/
async function resolveAgentDir(
input: string,
spinner: ReturnType<typeof p.spinner>
): Promise<{ dir: string; source: string; cleanup?: () => void }> {
if (isLocalPath(input)) {
const dir = resolve(input.replace(/^~/, homedir()));
if (!existsSync(dir)) throw new Error(`Directory not found: ${dir}`);
return { dir, source: dir };
}
// Parse GitHub reference: "name" or "org/repo/path/to/agent"
const parts = input.split("/");
let org: string, repo: string, subpath: string;
if (parts.length === 1) {
// Just a name → default repo
org = "vectorize-io";
repo = "self-driving-agents";
subpath = parts[0];
} else if (parts.length >= 3) {
// org/repo/path...
org = parts[0];
repo = parts[1];
subpath = parts.slice(2).join("/");
} else {
throw new Error(
`Invalid agent reference: '${input}'\n` +
` Use: <name>, <org>/<repo>/<path>, or a local path (./dir)`
);
}
spinner.start(`Fetching ${color.cyan(`${org}/${repo}/${subpath}`)} from GitHub...`);
const tmp = join(tmpdir(), `sda-${Date.now()}`);
mkdirSync(tmp, { recursive: true });
try {
// Download repo tarball and extract the specific subdirectory
const tarballUrl = `https://github.com/${org}/${repo}/archive/refs/heads/main.tar.gz`;
execSync(
`curl -sL "${tarballUrl}" | tar xz -C "${tmp}" --strip-components=1 "${repo}-main/${subpath}"`,
{ stdio: "pipe" }
);
} catch {
rmSync(tmp, { recursive: true, force: true });
throw new Error(
`Failed to fetch ${org}/${repo}/${subpath}\n` +
` Make sure the repository and path exist on GitHub.`
);
}
const dir = join(tmp, subpath);
if (!existsSync(dir)) {
rmSync(tmp, { recursive: true, force: true });
throw new Error(`Path '${subpath}' not found in ${org}/${repo}`);
}
const source = `github.com/${org}/${repo}/${subpath}`;
spinner.stop(`Fetched ${color.cyan(source)}`);
return { dir, source, cleanup: () => rmSync(tmp, { recursive: true, force: true }) };
}
// ── Skill ───────────────────────────────────────────────
const __dirname = dirname(fileURLToPath(import.meta.url));
const SKILL_PATH = join(__dirname, "..", "skill", "SKILL.md");
const SKILL_MD = readFileSync(SKILL_PATH, "utf-8");
// ── Plugin management ───────────────────────────────────
const OPENCLAW_CONFIG_PATH = join(homedir(), ".openclaw", "openclaw.json");
function readOpenClawConfig(): any {
if (!existsSync(OPENCLAW_CONFIG_PATH)) return null;
return JSON.parse(readFileSync(OPENCLAW_CONFIG_PATH, "utf-8"));
}
function enableKnowledgeTools(): void {
const config = readOpenClawConfig();
if (!config) return;
const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config;
if (!pc) return;
if (pc.enableKnowledgeTools === true) return;
pc.enableKnowledgeTools = true;
writeFileSync(OPENCLAW_CONFIG_PATH, JSON.stringify(config, null, 2) + "\n");
}
function isPluginInstalled(): boolean {
const config = readOpenClawConfig();
if (!config) return false;
return (
config.plugins?.entries?.["hindsight-openclaw"]?.enabled !== false &&
config.plugins?.entries?.["hindsight-openclaw"] !== undefined
);
}
function isPluginConfigured(): boolean {
const config = readOpenClawConfig();
if (!config) return false;
const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config || {};
return !!(pc.hindsightApiUrl || pc.embedVersion || pc.llmProvider);
}
function resolveFromPlugin(agentId: string): { apiUrl: string; bankId: string; apiToken?: string } {
const config = readOpenClawConfig();
if (!config) throw new Error("OpenClaw config not found");
const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config || {};
const apiUrl = pc.hindsightApiUrl || `http://localhost:${pc.apiPort || 9077}`;
const apiToken = pc.hindsightApiToken || undefined;
let bankId: string;
if (pc.dynamicBankId === false && pc.bankId) {
bankId = pc.bankId;
} else {
const granularity: string[] = pc.dynamicBankGranularity || ["agent", "channel", "user"];
const fieldMap: Record<string, string> = {
agent: agentId,
channel: "unknown",
user: "anonymous",
provider: "unknown",
};
const base = granularity.map((f) => encodeURIComponent(fieldMap[f] || "unknown")).join("::");
bankId = pc.bankIdPrefix ? `${pc.bankIdPrefix}-${base}` : base;
}
return { apiUrl, bankId, apiToken };
}
function getPluginSummary(): string {
const config = readOpenClawConfig();
if (!config) return "Not found";
const pc = config.plugins?.entries?.["hindsight-openclaw"]?.config || {};
if (pc.hindsightApiUrl) return `External: ${pc.hindsightApiUrl}`;
if (pc.embedVersion) return `Embedded v${pc.embedVersion}`;
return "Not configured";
}
function parseAgentsJson(raw: string): any[] {
const clean = raw.replace(/\n?\x1b\[[0-9;]*m[^\n]*/g, "").trim();
const arrStart = clean.indexOf("\n[");
const jsonStr = arrStart >= 0 ? clean.slice(arrStart + 1) : clean.startsWith("[") ? clean : "[]";
return JSON.parse(jsonStr);
}
async function ensurePlugin(): Promise<void> {
if (!isPluginInstalled()) {
p.log.warn("Hindsight plugin not found. Installing...");
try {
execSync("openclaw plugins install @vectorize-io/hindsight-openclaw", { stdio: "inherit" });
} catch {
p.cancel(
"Failed to install plugin. Run manually:\n openclaw plugins install @vectorize-io/hindsight-openclaw"
);
process.exit(1);
}
}
if (!isPluginConfigured()) {
p.log.warn("Hindsight plugin needs configuration.");
try {
execSync("npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup", {
stdio: "inherit",
});
} catch {
p.cancel(
"Run the wizard manually:\n npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup"
);
process.exit(1);
}
} else {
const summary = getPluginSummary();
if (process.stdin.isTTY) {
const ok = await p.confirm({
message: `Hindsight: ${color.cyan(summary)}. Use this?\n${color.dim(" Changing this will affect all existing agents — one OpenClaw instance shares a single Hindsight instance.")}`,
});
if (p.isCancel(ok)) {
p.cancel("Cancelled.");
process.exit(0);
}
if (!ok) {
p.log.info("Launching configuration wizard...");
try {
execSync(
"npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup",
{ stdio: "inherit" }
);
} catch {
p.cancel(
"Configuration failed. Run manually:\n npx --yes --package @vectorize-io/hindsight-openclaw hindsight-openclaw-setup"
);
process.exit(1);
}
}
} else {
p.log.info(`Hindsight: ${color.cyan(summary)}`);
}
}
}
// ── Main ────────────────────────────────────────────────
async function main() {
const args = process.argv.slice(2);
if (args.length < 1 || args[0] === "--help" || args[0] === "-h") {
console.log(`
${color.bold("self-driving-agents")} — install a self-driving agent
${color.dim("Usage:")}
npx @vectorize-io/self-driving-agents install <agent> --harness <harness> [--agent <name>]
${color.dim("Agent sources:")}
${color.cyan("marketing-agent")}${DEFAULT_REPO}/marketing-agent
${color.cyan("org/repo/my-agent")} → org/repo/my-agent on GitHub
${color.cyan("./local-dir")} → local directory
${color.dim("Options:")}
${color.cyan("--harness <h>")} Required. openclaw | hermes | claude-code
${color.cyan("--agent <name>")} Agent name (defaults to directory name)
`);
process.exit(0);
}
let dirArg = args[0] === "install" ? args[1] : args[0];
const restArgs = args[0] === "install" ? args.slice(2) : args.slice(1);
if (!dirArg) {
p.cancel("Agent argument required.");
process.exit(1);
}
let harness: string | undefined;
let agentName: string | undefined;
for (let i = 0; i < restArgs.length; i++) {
if (restArgs[i] === "--harness" && restArgs[i + 1]) harness = restArgs[++i];
else if (restArgs[i] === "--agent" && restArgs[i + 1]) agentName = restArgs[++i];
}
if (!harness) {
p.cancel("--harness required (openclaw | hermes | claude-code)");
process.exit(1);
}
p.intro(color.bgCyan(color.black(` self-driving-agents `)));
// Step 0: Resolve agent directory (local or GitHub)
const spin = p.spinner();
const { dir, source, cleanup } = await resolveAgentDir(dirArg, spin);
try {
const agentId = agentName || basename(dir);
// Step 1: Ensure plugin
if (harness === "openclaw") {
await ensurePlugin();
enableKnowledgeTools();
}
// Step 2: Resolve bank + API from plugin config
const { apiUrl, bankId, apiToken } = resolveFromPlugin(agentId);
const workspaceDir = join(homedir(), ".self-driving-agents", "openclaw", agentId);
p.log.info(
[
`Agent: ${color.bold(agentId)}`,
`Source: ${color.dim(source)}`,
`Bank: ${color.dim(bankId)}`,
`API: ${color.dim(apiUrl)}`,
`Workspace: ${color.dim(workspaceDir)}`,
].join("\n")
);
// Step 3: Create client + health check
const client = new HindsightClient({
baseUrl: apiUrl,
apiKey: apiToken,
userAgent: "self-driving-agents/0.1.0",
});
const lowLevel = createClient(
createConfig({
baseUrl: apiUrl,
headers: {
...(apiToken ? { Authorization: `Bearer ${apiToken}` } : {}),
"User-Agent": "self-driving-agents/0.1.0",
},
})
);
spin.start("Connecting to Hindsight...");
try {
await sdk.healthEndpointHealthGet({ client: lowLevel });
spin.stop("Connected to Hindsight");
} catch {
spin.stop("Connection failed");
p.cancel(`Cannot reach Hindsight at ${apiUrl}\nStart the server or reconfigure the plugin.`);
process.exit(1);
}
// Step 4: Import bank template
const templatePath = join(dir, "bank-template.json");
if (existsSync(templatePath)) {
spin.start("Importing bank template...");
const template = JSON.parse(readFileSync(templatePath, "utf-8"));
await sdk.importBankTemplate({
client: lowLevel,
path: { bank_id: bankId },
body: template,
});
spin.stop("Bank template imported");
}
// Step 5: Ingest content (recursive — all text files except bank-template.json)
const contentFiles = findContentFiles(dir);
if (contentFiles.length > 0) {
spin.start(`Ingesting ${contentFiles.length} file(s)...`);
for (const relPath of contentFiles) {
const content = readFileSync(join(dir, relPath), "utf-8");
if (!content.trim()) continue;
// Use relative path (without extension) as document ID, e.g. "seo/keyword-research"
const docId = relPath.replace(/\.[^.]+$/, "");
await client.retainBatch(bankId, [{ content, document_id: docId }], { async: true });
spin.message(`Ingesting ${relPath}...`);
}
spin.stop(`Ingested ${contentFiles.length} file(s)`);
}
// Step 6: Create agent + install skill
mkdirSync(workspaceDir, { recursive: true });
const skillDir = join(workspaceDir, "skills", "agent-knowledge");
mkdirSync(skillDir, { recursive: true });
writeFileSync(join(skillDir, "SKILL.md"), SKILL_MD);
p.log.success("Knowledge skill installed");
if (harness === "openclaw") {
try {
const listOut = execSync("openclaw agents list --json 2>/dev/null", { encoding: "utf-8" });
const agents = parseAgentsJson(listOut);
if (!agents.some((a: any) => a.name === agentId || a.id === agentId)) {
execSync(`openclaw agents add ${agentId} --workspace ${workspaceDir} --non-interactive`, {
stdio: "pipe",
});
p.log.success(`Agent '${agentId}' created`);
} else {
p.log.info(`Agent '${agentId}' already exists`);
}
} catch {
p.log.warn(
`Create agent manually:\n openclaw agents add ${agentId} --workspace ${workspaceDir} --non-interactive`
);
}
}
// Step 7: Patch startup
const startupFile = join(workspaceDir, "AGENTS.md");
if (existsSync(startupFile)) {
let text = readFileSync(startupFile, "utf-8");
if (!text.includes("agent-knowledge")) {
text = text.replace(
"Don't ask permission. Just do it.",
"5. Read `skills/agent-knowledge/SKILL.md` and **execute its mandatory startup sequence**\n\nDon't ask permission. Just do it."
);
writeFileSync(startupFile, text);
p.log.success("Startup patched");
}
}
p.note(
[
`${color.dim("1.")} openclaw gateway restart`,
`${color.dim("2.")} openclaw tui --session agent:${agentId}:main:session1`,
].join("\n"),
"Next steps"
);
p.outro(color.green(`'${agentId}' is ready`));
} finally {
cleanup?.();
}
}
main().catch((err) => {
p.cancel(err.message);
process.exit(1);
});
@@ -0,0 +1,257 @@
import { describe, it, expect, beforeEach, afterEach } from "vitest";
import { mkdtemp, rm, writeFile, mkdir } from "fs/promises";
import { tmpdir } from "os";
import { join } from "path";
import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from "fs";
import { extname, relative, basename } from "path";
// ── Extracted helpers (mirroring cli.ts logic for unit testing) ──
const CONTENT_EXTS = new Set([".md", ".txt", ".html", ".json", ".csv", ".xml"]);
const IGNORED_FILES = new Set(["bank-template.json"]);
function findContentFiles(dir: string): string[] {
const results: string[] = [];
function walk(current: string) {
for (const entry of readdirSync(current)) {
const full = join(current, entry);
if (statSync(full).isDirectory()) {
walk(full);
} else if (CONTENT_EXTS.has(extname(entry).toLowerCase()) && !IGNORED_FILES.has(entry)) {
results.push(relative(dir, full));
}
}
}
walk(dir);
return results.sort();
}
function isLocalPath(input: string): boolean {
return (
input.startsWith("./") ||
input.startsWith("../") ||
input.startsWith("/") ||
input.startsWith("~")
);
}
function parseAgentsJson(raw: string): any[] {
const clean = raw.replace(/\n?\x1b\[[0-9;]*m[^\n]*/g, "").trim();
const arrStart = clean.indexOf("\n[");
const jsonStr = arrStart >= 0 ? clean.slice(arrStart + 1) : clean.startsWith("[") ? clean : "[]";
return JSON.parse(jsonStr);
}
function resolveFromPluginConfig(
agentId: string,
pc: Record<string, any>
): { apiUrl: string; bankId: string; apiToken?: string } {
const apiUrl = pc.hindsightApiUrl || `http://localhost:${pc.apiPort || 9077}`;
const apiToken = pc.hindsightApiToken || undefined;
let bankId: string;
if (pc.dynamicBankId === false && pc.bankId) {
bankId = pc.bankId;
} else {
const granularity: string[] = pc.dynamicBankGranularity || ["agent", "channel", "user"];
const fieldMap: Record<string, string> = {
agent: agentId,
channel: "unknown",
user: "anonymous",
provider: "unknown",
};
const base = granularity.map((f) => encodeURIComponent(fieldMap[f] || "unknown")).join("::");
bankId = pc.bankIdPrefix ? `${pc.bankIdPrefix}-${base}` : base;
}
return { apiUrl, bankId, apiToken };
}
// ── Tests ──
describe("findContentFiles", () => {
let tmpDir: string;
beforeEach(async () => {
tmpDir = await mkdtemp(join(tmpdir(), "sda-test-"));
});
afterEach(async () => {
await rm(tmpDir, { recursive: true, force: true });
});
it("finds .md files recursively", async () => {
await writeFile(join(tmpDir, "root.md"), "hello");
await mkdir(join(tmpDir, "sub"));
await writeFile(join(tmpDir, "sub", "nested.md"), "world");
const files = findContentFiles(tmpDir);
expect(files).toEqual(["root.md", "sub/nested.md"]);
});
it("finds multiple content extensions", async () => {
await writeFile(join(tmpDir, "a.md"), "md");
await writeFile(join(tmpDir, "b.txt"), "txt");
await writeFile(join(tmpDir, "c.html"), "html");
await writeFile(join(tmpDir, "d.csv"), "csv");
const files = findContentFiles(tmpDir);
expect(files).toEqual(["a.md", "b.txt", "c.html", "d.csv"]);
});
it("excludes bank-template.json", async () => {
await writeFile(join(tmpDir, "bank-template.json"), "{}");
await writeFile(join(tmpDir, "readme.md"), "hello");
const files = findContentFiles(tmpDir);
expect(files).toEqual(["readme.md"]);
});
it("excludes non-content files", async () => {
await writeFile(join(tmpDir, "image.png"), "binary");
await writeFile(join(tmpDir, "script.js"), "code");
await writeFile(join(tmpDir, "doc.md"), "content");
const files = findContentFiles(tmpDir);
expect(files).toEqual(["doc.md"]);
});
it("handles deeply nested directories", async () => {
await mkdir(join(tmpDir, "a", "b", "c"), { recursive: true });
await writeFile(join(tmpDir, "a", "b", "c", "deep.md"), "deep");
const files = findContentFiles(tmpDir);
expect(files).toEqual(["a/b/c/deep.md"]);
});
it("returns empty for directory with no content", async () => {
await writeFile(join(tmpDir, "bank-template.json"), "{}");
await writeFile(join(tmpDir, "image.png"), "binary");
const files = findContentFiles(tmpDir);
expect(files).toEqual([]);
});
it("ignores bank-template.json in subdirectories too", async () => {
await mkdir(join(tmpDir, "sub"));
await writeFile(join(tmpDir, "sub", "bank-template.json"), "{}");
await writeFile(join(tmpDir, "sub", "guide.md"), "content");
const files = findContentFiles(tmpDir);
expect(files).toEqual(["sub/guide.md"]);
});
});
describe("isLocalPath", () => {
it("detects relative paths", () => {
expect(isLocalPath("./my-agent")).toBe(true);
expect(isLocalPath("../parent/agent")).toBe(true);
});
it("detects absolute paths", () => {
expect(isLocalPath("/Users/me/agent")).toBe(true);
});
it("detects home paths", () => {
expect(isLocalPath("~/dev/agent")).toBe(true);
});
it("rejects GitHub-style references", () => {
expect(isLocalPath("marketing")).toBe(false);
expect(isLocalPath("org/repo/path")).toBe(false);
});
});
describe("parseAgentsJson", () => {
it("parses clean JSON array", () => {
const agents = parseAgentsJson('[{"id": "main"}]');
expect(agents).toEqual([{ id: "main" }]);
});
it("strips ANSI log lines before JSON", () => {
const raw =
"\x1b[38;5;103mhindsight:\x1b[0m plugin entry invoked\n" +
'[\n {"id": "main"},\n {"id": "seo-writer", "name": "seo-writer"}\n]';
const agents = parseAgentsJson(raw);
expect(agents).toHaveLength(2);
expect(agents[1].id).toBe("seo-writer");
});
it("returns empty array for unparseable output", () => {
const agents = parseAgentsJson("some random text");
expect(agents).toEqual([]);
});
it("handles multiple ANSI lines", () => {
const raw = [
"Config warnings:",
"\x1b[35m[plugins]\x1b[39m registering plugin",
"\x1b[35m[plugins]\x1b[39m hooks registered",
'[{"id": "test"}]',
].join("\n");
const agents = parseAgentsJson(raw);
expect(agents).toEqual([{ id: "test" }]);
});
});
describe("resolveFromPluginConfig", () => {
it("uses external API URL when set", () => {
const result = resolveFromPluginConfig("my-agent", {
hindsightApiUrl: "https://api.example.com",
hindsightApiToken: "tok-123",
dynamicBankGranularity: ["agent"],
});
expect(result.apiUrl).toBe("https://api.example.com");
expect(result.apiToken).toBe("tok-123");
expect(result.bankId).toBe("my-agent");
});
it("falls back to localhost with apiPort", () => {
const result = resolveFromPluginConfig("my-agent", {
apiPort: 8888,
dynamicBankGranularity: ["agent"],
});
expect(result.apiUrl).toBe("http://localhost:8888");
expect(result.apiToken).toBeUndefined();
});
it("defaults to port 9077", () => {
const result = resolveFromPluginConfig("my-agent", {});
expect(result.apiUrl).toBe("http://localhost:9077");
});
it("computes bank ID with prefix", () => {
const result = resolveFromPluginConfig("seo-writer", {
bankIdPrefix: "nicolo",
dynamicBankGranularity: ["agent"],
});
expect(result.bankId).toBe("nicolo-seo-writer");
});
it("computes bank ID without prefix", () => {
const result = resolveFromPluginConfig("seo-writer", {
dynamicBankGranularity: ["agent"],
});
expect(result.bankId).toBe("seo-writer");
});
it("uses multi-field granularity", () => {
const result = resolveFromPluginConfig("my-agent", {
dynamicBankGranularity: ["agent", "channel", "user"],
});
expect(result.bankId).toBe("my-agent::unknown::anonymous");
});
it("uses default granularity when not specified", () => {
const result = resolveFromPluginConfig("my-agent", {});
expect(result.bankId).toBe("my-agent::unknown::anonymous");
});
it("uses static bankId when dynamicBankId is false", () => {
const result = resolveFromPluginConfig("my-agent", {
dynamicBankId: false,
bankId: "static-bank",
});
expect(result.bankId).toBe("static-bank");
});
});
@@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "bundler",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"declaration": true,
"skipLibCheck": true
},
"include": ["src"]
}
+114 -4
View File
@@ -9,12 +9,14 @@
"hindsight-clients/typescript",
"hindsight-control-plane",
"hindsight-docs",
"hindsight-all-npm"
"hindsight-all-npm",
"hindsight-tools/hindsight-agent-sdk",
"hindsight-tools/self-driving-agents"
]
},
"hindsight-all-npm": {
"name": "@vectorize-io/hindsight-all",
"version": "0.5.1",
"version": "0.5.6",
"license": "MIT",
"devDependencies": {
"@types/node": "^22.0.0",
@@ -138,7 +140,7 @@
},
"hindsight-clients/typescript": {
"name": "@vectorize-io/hindsight-client",
"version": "0.5.1",
"version": "0.5.6",
"license": "MIT",
"devDependencies": {
"@hey-api/openapi-ts": "0.88.0",
@@ -357,7 +359,7 @@
},
"hindsight-control-plane": {
"name": "@vectorize-io/hindsight-control-plane",
"version": "0.5.1",
"version": "0.5.6",
"license": "ISC",
"dependencies": {
"@chenglou/pretext": "^0.0.3",
@@ -687,6 +689,34 @@
"node": ">=14.17"
}
},
"hindsight-tools/hindsight-agent-sdk": {
"name": "@vectorize-io/hindsight-agent-sdk",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@vectorize-io/hindsight-client": "^0.5.6"
},
"devDependencies": {
"typescript": "^5.4",
"vitest": "^4.1.2"
}
},
"hindsight-tools/self-driving-agents": {
"name": "@vectorize-io/self-driving-agents",
"version": "0.1.0",
"license": "MIT",
"dependencies": {
"@clack/prompts": "^1.2.0",
"@vectorize-io/hindsight-client": "^0.5.6"
},
"bin": {
"self-driving-agents": "dist/cli.js"
},
"devDependencies": {
"typescript": "^5.4",
"vitest": "^4.1.2"
}
},
"node_modules/@ai-sdk/gateway": {
"version": "2.0.24",
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-2.0.24.tgz",
@@ -2964,6 +2994,28 @@
"integrity": "sha512-4mudFAQ6H+MqBTfqLmU7G1ZwRzCLfJEooL/fsF6rCX5eePMbGhoy5n4g+G4vlh2muDcsCTJtL+uKbOzWxs5LHA==",
"license": "Apache-2.0"
},
"node_modules/@clack/core": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@clack/core/-/core-1.2.0.tgz",
"integrity": "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg==",
"license": "MIT",
"dependencies": {
"fast-wrap-ansi": "^0.1.3",
"sisteransi": "^1.0.5"
}
},
"node_modules/@clack/prompts": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@clack/prompts/-/prompts-1.2.0.tgz",
"integrity": "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w==",
"license": "MIT",
"dependencies": {
"@clack/core": "1.2.0",
"fast-string-width": "^1.1.0",
"fast-wrap-ansi": "^0.1.3",
"sisteransi": "^1.0.5"
}
},
"node_modules/@colors/colors": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz",
@@ -5266,6 +5318,7 @@
"os": [
"aix"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5283,6 +5336,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5300,6 +5354,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5317,6 +5372,7 @@
"os": [
"android"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5334,6 +5390,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5351,6 +5408,7 @@
"os": [
"darwin"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5368,6 +5426,7 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5385,6 +5444,7 @@
"os": [
"freebsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5402,6 +5462,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5419,6 +5480,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5436,6 +5498,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5453,6 +5516,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5470,6 +5534,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5487,6 +5552,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5504,6 +5570,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5521,6 +5588,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5538,6 +5606,7 @@
"os": [
"linux"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5555,6 +5624,7 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5572,6 +5642,7 @@
"os": [
"netbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5589,6 +5660,7 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5606,6 +5678,7 @@
"os": [
"openbsd"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5623,6 +5696,7 @@
"os": [
"openharmony"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5640,6 +5714,7 @@
"os": [
"sunos"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5657,6 +5732,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5674,6 +5750,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -5691,6 +5768,7 @@
"os": [
"win32"
],
"peer": true,
"engines": {
"node": ">=18"
}
@@ -11265,6 +11343,10 @@
"d3-transition": "^3.0.1"
}
},
"node_modules/@vectorize-io/hindsight-agent-sdk": {
"resolved": "hindsight-tools/hindsight-agent-sdk",
"link": true
},
"node_modules/@vectorize-io/hindsight-all": {
"resolved": "hindsight-all-npm",
"link": true
@@ -11277,6 +11359,10 @@
"resolved": "hindsight-control-plane",
"link": true
},
"node_modules/@vectorize-io/self-driving-agents": {
"resolved": "hindsight-tools/self-driving-agents",
"link": true
},
"node_modules/@vercel/oidc": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.0.5.tgz",
@@ -16617,6 +16703,21 @@
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"license": "MIT"
},
"node_modules/fast-string-truncated-width": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/fast-string-truncated-width/-/fast-string-truncated-width-1.2.1.tgz",
"integrity": "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow==",
"license": "MIT"
},
"node_modules/fast-string-width": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fast-string-width/-/fast-string-width-1.1.0.tgz",
"integrity": "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ==",
"license": "MIT",
"dependencies": {
"fast-string-truncated-width": "^1.2.0"
}
},
"node_modules/fast-uri": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz",
@@ -16633,6 +16734,15 @@
],
"license": "BSD-3-Clause"
},
"node_modules/fast-wrap-ansi": {
"version": "0.1.6",
"resolved": "https://registry.npmjs.org/fast-wrap-ansi/-/fast-wrap-ansi-0.1.6.tgz",
"integrity": "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w==",
"license": "MIT",
"dependencies": {
"fast-string-width": "^1.1.0"
}
},
"node_modules/fast-xml-builder": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
+3 -1
View File
@@ -5,7 +5,9 @@
"hindsight-clients/typescript",
"hindsight-control-plane",
"hindsight-docs",
"hindsight-all-npm"
"hindsight-all-npm",
"hindsight-tools/hindsight-agent-sdk",
"hindsight-tools/self-driving-agents"
],
"scripts": {
"prepare": "./scripts/setup-hooks.sh"
+9 -4
View File
@@ -69,19 +69,24 @@ for name, info in data.get("packages", {}).items():
resolved = info.get("resolved", "")
link = info.get("link")
if link is True:
# Packages under hindsight-tools/ are monorepo workspace deps that
# aren't published yet. The CI build pre-builds them before running
# openclaw, so workspace resolution is expected and safe.
is_tools_pkg = resolved.startswith(("../../hindsight-tools/", "file:../../hindsight-tools/"))
if link is True and not is_tools_pkg:
# Workspace symlink, no registry URL. This is the bug that broke
# the openclaw 0.6.0 release — npm had resolved a registry-ranged
# dep to a workspace symlink because `npm install` was run from
# the monorepo root.
bad.append(f"{name}: (link=true — workspace symlink)")
elif not resolved:
elif not resolved and not is_tools_pkg:
# Empty resolved on a non-link entry shouldn't happen, but flag
# it anyway rather than silently accepting it.
bad.append(f"{name}: (no `resolved` URL)")
elif resolved.startswith("file:"):
elif resolved.startswith("file:") and not is_tools_pkg:
bad.append(f"{name}: {resolved}")
elif resolved.startswith(("../", "./")):
elif resolved.startswith(("../", "./")) and not is_tools_pkg:
bad.append(f"{name}: {resolved}")
if bad:
+23
View File
@@ -91,6 +91,29 @@ if [ -d "$INTEGRATIONS_DIR" ] && { [ "$LINT_ALL" = "1" ] || [ -n "$CHANGED_FILES
done
fi
# Hindsight tools (Node packages under hindsight-tools/)
TOOLS_DIR="$REPO_ROOT/hindsight-tools"
if [ -n "$CI" ] || [ -n "$LINT_ALL_INTEGRATIONS" ]; then
LINT_TOOLS_ALL=1
TOOLS_CHANGED=""
else
LINT_TOOLS_ALL=0
TOOLS_CHANGED=$( { git -C "$REPO_ROOT" diff --name-only HEAD -- "hindsight-tools/"; \
git -C "$REPO_ROOT" ls-files --others --exclude-standard -- "hindsight-tools/"; } | sort -u )
fi
if [ -d "$TOOLS_DIR" ] && { [ "$LINT_TOOLS_ALL" = "1" ] || [ -n "$TOOLS_CHANGED" ]; }; then
for dir in "$TOOLS_DIR"/*/; do
name=$(basename "$dir")
if [ "$LINT_TOOLS_ALL" != "1" ]; then
echo "$TOOLS_CHANGED" | grep -q "^hindsight-tools/$name/" || continue
fi
if [ -f "$dir/package.json" ]; then
run_task "prettier-tool-$name" "$dir" "npx --yes prettier --write --config $REPO_ROOT/.prettierrc.json --ignore-path $REPO_ROOT/.gitignore ."
fi
done
fi
# Wait for all tasks to complete
for pid in "${PIDS[@]}"; do
wait "$pid" 2>/dev/null || true
+106
View File
@@ -0,0 +1,106 @@
#!/bin/bash
set -e
cd "$(dirname "$0")/.."
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
print_info() { echo -e "${YELLOW}$1${NC}"; }
print_success() { echo -e "${GREEN}$1${NC}"; }
print_error() { echo -e "${RED}$1${NC}" >&2; }
VALID_TOOLS=("self-driving-agents" "hindsight-agent-sdk")
usage() {
echo "Usage: $0 <tool> <version>"
echo " tool One of: ${VALID_TOOLS[*]}"
echo " version Semver (e.g., 0.1.0, 1.0.0)"
exit 1
}
if [ $# -ne 2 ]; then usage; fi
TOOL="$1"
VERSION="$2"
# Validate tool name
VALID=false
for v in "${VALID_TOOLS[@]}"; do
if [ "$v" = "$TOOL" ]; then VALID=true; break; fi
done
if [ "$VALID" = false ]; then
print_error "Unknown tool: $TOOL"
print_info "Valid tools: ${VALID_TOOLS[*]}"
exit 1
fi
# Validate version format
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.]+)?$'; then
print_error "Invalid version format: $VERSION (expected semver like 0.1.0)"
exit 1
fi
# Resolve directory
TOOL_DIR="hindsight-tools/$TOOL"
if [ ! -d "$TOOL_DIR" ]; then
print_error "Tool directory not found: $TOOL_DIR"
exit 1
fi
# Check we're on main
BRANCH=$(git branch --show-current)
if [ "$BRANCH" != "main" ]; then
print_info "Warning: not on main branch (current: $BRANCH)"
read -p "Continue? (y/n) " -n 1 -r
echo
if [[ ! $REPLY =~ ^[Yy]$ ]]; then exit 1; fi
fi
# Check working tree is clean
if [ -n "$(git status --porcelain)" ]; then
print_error "Working tree is not clean. Commit or stash changes first."
exit 1
fi
# Read current version
if [ -f "$TOOL_DIR/package.json" ]; then
CURRENT=$(grep '"version"' "$TOOL_DIR/package.json" | head -1 | sed 's/.*"version": "\(.*\)".*/\1/')
elif [ -f "$TOOL_DIR/pyproject.toml" ]; then
CURRENT=$(grep '^version = ' "$TOOL_DIR/pyproject.toml" | sed 's/version = "\(.*\)"/\1/')
else
CURRENT="unknown"
fi
print_info "Releasing $TOOL: $CURRENT$VERSION"
echo
# Update version in package manifest
if [ -f "$TOOL_DIR/package.json" ]; then
sed -i '' "s/\"version\": \"$CURRENT\"/\"version\": \"$VERSION\"/" "$TOOL_DIR/package.json"
print_success "Updated package.json"
elif [ -f "$TOOL_DIR/pyproject.toml" ]; then
sed -i '' "s/version = \"$CURRENT\"/version = \"$VERSION\"/" "$TOOL_DIR/pyproject.toml"
print_success "Updated pyproject.toml"
fi
# Build
if [ -f "$TOOL_DIR/package.json" ]; then
(cd "$TOOL_DIR" && npm run build)
print_success "Built"
fi
# Commit, tag, push
TAG="tools/$TOOL/v$VERSION"
git add "$TOOL_DIR/"
git commit -m "release($TOOL): v$VERSION"
git tag "$TAG"
git push origin main "$TAG"
print_success "Released $TOOL v$VERSION"
print_info "Tag: $TAG"
echo
print_info "To publish to npm:"
echo " cd $TOOL_DIR && npm publish --access public"