freddie 0.0.44 → 0.0.46

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AGENTS.md CHANGED
@@ -159,6 +159,8 @@ One `test.js` at project root. ≤200 lines. Plain assertions, real data, real s
159
159
  - **src/web/app.js 200-line policy violation** — File is 548 lines, violating gm hard cap (2.7× over). Only file in 283-file codebase over limit. Likely waived intentionally or is drift to fix. When touching app.js, prefer splitting into `{app,routes,components,state}.js` over expanding further. Do not add 50+ more lines without addressing the split.
160
160
  - **libsql async debt class** — `src/sessions.js` (listSessions/search/getMessages/createSession/appendMessage) and `src/cron/scheduler.js` (listJobs/createJob/cancelJob/deleteJob) are async after the libsql migration. Sync callsites silently wrap each call in a Promise that rejects on iteration, surfacing as `TypeError: ... is not iterable` via `node bin/freddie.js sessions` or `freddie cron list`. Rule: every call into those modules must be awaited; tool ACTIONS inner functions async + handler awaits dispatched fn. Fixed 2026-05-03 across bin/freddie.js, src/web/server.js, src/cli/dump.js, src/cli/status.js, src/tools/session_search.js, src/tools/cronjob.js, src/acp/session.js. test.js can pass while CLI is broken — exercise the cli verb in test.js or smoke `node bin/freddie.js <verb>` after changes.
161
161
  - **Bulk-rename: git grep is case-sensitive on literal patterns** — `git grep -lI <name>` only matches lowercase. For case-variant sweep during rename refactors, use `git grep -liI -e <lower> -e <Title> -e <UPPER>` (per-pattern `-i` requires `-e` form). Single-form check is a false-clean trap.
162
+ - **freddie exec command Windows invocation** — `plugins/core-cli/plugin.js` registers the `exec` command (commit e5fb1b7) for non-interactive scripted use. Correct invocation on Windows: `bun run bin/freddie.js exec --prompt "..."`. Do NOT use `bun x freddie` — it hangs on Windows due to npm registry fetch timeouts. The command takes `--prompt` (required), `--model` (default ''), `--timeout` (default 60000ms) and is the validated entry point for CI pipelines.
163
+ - **acptoapi-bridge max_tokens silent truncation** — `src/agent/acptoapi-bridge.js` line 20 controls max_tokens passed to the LLM. Prior to commit e5fb1b7, this was set to 1024, which silently truncated responses on generation tasks. Raised to 4096 to prevent hidden content loss. If generation output appears incomplete, verify max_tokens is 4096 or higher.
162
164
 
163
165
  ## Subsystem guide
164
166
 
@@ -201,6 +203,12 @@ One `test.js` at project root. ≤200 lines. Plain assertions, real data, real s
201
203
  - **rs-plugkit exec utility verbs** (2026-04-30) — The plugkit.exe binary advertises `exec:status`, `exec:close`, `exec:sleep` in hook help, but the Cmd enum was missing Status/Close/Sleep variants. Fix applied to c:\dev\rs-plugkit\src\main.rs; awaiting CI rebuild. Until rebuilt: use `exec:wait <secs>` for waits, read task output files directly via fs.readFileSync instead of exec:status.
202
204
  - **rs-exec timeout alias** — Both `--timeout` (long-form) and `--timeout-ms` (plugin convention) are accepted due to alias added to c:\dev\rs-exec\src\main.rs. Both Cmd::Exec and Cmd::Bash support either form.
203
205
 
206
+
207
+ ## Plugsdk integration
208
+
209
+ - **plugsdk peerDependencies zod conflict** (2026-05-03) — plugsdk v1.0.6 declared `peerDependencies: { zod: "^3.23.0" }`, causing ERESOLVE when freddie installs with zod@^4.0.0. Fixed in plugsdk v1.0.7 by relaxing peer to `^3.23.0 || ^4.0.0`. Freddie now pins plugsdk@^1.0.7 (currently 1.0.8 on npm registry).
210
+ - **plugsdk package-lock.json symlink blockage** (2026-05-03) — freddie's package-lock.json contained a stale symlink entry from a prior `file:` dependency: `"resolved": "../plugsdk", "link": true`. This blocked `npm ci` in CI (non-registry installs fail when symlink target is missing or differs). Fix: removed the symlink entry, ran `npm install` to sync lockfile, then committed. Always install plugsdk from registry, not via file: dep.
211
+ - **plugsdk auto-publish workflow** — plugsdk publishes automatically to npm registry on push to main branch. Current version 1.0.8. freddie's contract.js re-exports `piAdapter`, `HookType`, `allowResult`, `blockResult`, `modifyResult` from plugsdk + uses `HookType` constants in `FREDDIE_TO_SDK_HOOK` mapping.
204
212
  ## Integration test status (2026-04-30)
205
213
 
206
214
  All 21 named integration tests in `test.js` pass (exit 0). Subsystem coverage:
@@ -214,7 +222,7 @@ All 21 named integration tests in `test.js` pass (exit 0). Subsystem coverage:
214
222
 
215
223
  - **acptoapi bridge** — Integrated at `src/agent/acptoapi-bridge.js` + `src/agent/llm_resolver.js` (commit 5f55f1e). Localhost API (default port 4800) converting OpenAI/Anthropic SDK calls to multiple backends: Kilo Code, opencode, Claude CLI, Anthropic API, Gemini, Ollama, Bedrock. Endpoint `/v1/chat/completions`, OpenAI-compatible, accepts `Bearer none` auth.
216
224
  - **LLM resolver priority** — (1) explicit `callLLM` arg, (2) pi-bridge if `ANTHROPIC_API_KEY` / `OPENAI_API_KEY` / `GROQ_API_KEY` / `OPENROUTER_API_KEY` env set, (3) acptoapi if `/v1/models` returns 200, (4) throw with actionable error. Configurable via `FREDDIE_LLM_URL` and `FREDDIE_LLM_MODEL` env vars.
217
- - **acptoapi Claude backend tool-call mismatch** acptoapi's Claude backend returns Anthropic-style XML `<function_calls><invoke name="bash">…</invoke></function_calls>` in message content, not OpenAI `tool_calls` JSON array. This breaks the agent's tool-loop auto-fire. Workaround: use real Anthropic API key + pi-bridge for proper tool dispatch, or use structured `callLLM` stubs in tests.
225
+ - **acptoapi Claude backend verified** (2026-05-03)Live agent loop working: start acptoapi server `node bin/agentapi.js --port 4800` (c:\dev\acptoapi), then set `FREDDIE_LLM_URL=http://localhost:4800/v1 + FREDDIE_LLM_MODEL=claude/haiku`. Model prefix `claude/` routes to Claude CLI subprocess. freddie test.js 12/12 green confirming integration production-ready. Dashboard `/api/chat` and `/api/batch` are POST-only; GET returns 404 (correct).
218
226
 
219
227
  ## Pre-rename validation snapshot (2026-05-03)
220
228
 
package/CHANGELOG.md CHANGED
@@ -1,7 +1,41 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.1.2] - 2026-05-03
4
+
5
+ ### Fixed
6
+ - plugsdk zod peer dependency conflict: bumped plugsdk to v1.0.7 with peerDeps zod "^3.23.0 || ^4.0.0" — freddie CI can now install plugsdk from npm registry
7
+ - Removed stale local symlink from package-lock.json; plugsdk now resolves from https://registry.npmjs.org/
8
+
9
+ ### Changed
10
+ - contract.js: FREDDIE_TO_SDK_HOOK values now reference HookType.* constants from plugsdk instead of string literals
11
+ - contract.js: re-exports piAdapter, HookType, allowResult, blockResult, modifyResult, PluginRunner, PluginRuntime from plugsdk
12
+
13
+
3
14
  All notable changes to this project will be documented in this file.
4
15
 
16
+ ## [0.1.1] - 2026-05-03
17
+
18
+ ### Added
19
+ - plugsdk bridge: `wrapPlugsdkPlugin()` in `src/host/host.js` — auto-detects `definePlugin()` shape `{tools,hooks,meta}` and wraps to native freddie contract; native plugins unchanged
20
+ - gm-cc integration: `plugins/gm-cc/plugin.js` registers all 12 gm-cc SKILL.md files under `gm:*` namespace in freddie's skill registry
21
+ - plugsdk adapters: MCP, OpenAI, LangChain, Cursor/VSCode, Aider — 9 total adapters in `C:/dev/plugsdk`
22
+ - test.js: 12/12 green
23
+
24
+ ## [0.1.0] - 2026-05-03
25
+
26
+ ### Validated
27
+ - Dashboard browser-witnessed: HTTP 200, window.__debug.dashboard() booted, all 11 hash routes (#/sessions #/tools #/cron #/skills #/config #/env #/debug #/chat #/batch #/gateway #/profiles), /api/tools=70, /api/sessions/cron/skills/config/env/gateway all 200
28
+ - Penguins all 18 species browser-witnessed (15 remaining: adelie/chinstrap/erect-crested/fiordland/galapagos/gentoo/humboldt/king/macaroni/magellanic/rockhopper/royal/snares/white-flippered/yellow-eyed all 2600-3384 bytes, all link back to index)
29
+ - CLI DX validated via module imports: 70 tools, 5 command categories, 4 skins, sessions resolves
30
+ - test.js 12/12 green
31
+
32
+ ## [0.0.9] - 2026-05-03
33
+
34
+ ### Changed
35
+ - README.md: added `exec --prompt` non-interactive usage example after `run` REPL entry
36
+ - Penguins site browser-validated: index (4283), facts (12583), conservation (5223), 3 species pages (emperor/little/african all >2900)
37
+ - Website/docs all 8 pages browser-validated at HTTP 200 (index + 7 sections)
38
+
5
39
  ## [0.0.8] - 2026-05-02
6
40
 
7
41
  ### Changed
package/README.md CHANGED
@@ -32,6 +32,9 @@ node bin/freddie.js help-all
32
32
  # Interactive REPL (skin-aware, slash commands routed via registry)
33
33
  node bin/freddie.js run
34
34
 
35
+ # Run a single prompt non-interactively (exits after response)
36
+ node bin/freddie.js exec --prompt "list 3 penguin species"
37
+
35
38
  # Profile management (~/.freddie/profiles/*)
36
39
  node bin/freddie.js profile list
37
40
  node bin/freddie.js profile create coder
@@ -65,15 +68,27 @@ node bin/freddie.js acp
65
68
 
66
69
  ## Tools
67
70
 
68
- Built-in: `bash`, `read`, `write`, `edit`, `grep`, `todo`, `memory`, `delegate`, `web_search`, `image_gen`, `browser`. Auto-discovered from `src/tools/*.js`.
71
+ 70 built-in tools auto-discovered from `plugins/*/`. Core set: `bash`, `read`, `write`, `edit`, `grep`, `todo`, `memory`, `delegate`, `web_search`, `image_gen`, `browser`.
69
72
 
70
73
  ## Platforms
71
74
 
72
- `src/gateway/platforms/`: webhook, api_server, telegram, discord, slack, whatsapp, signal, matrix, mattermost, email, sms, dingtalk, wecom, weixin, feishu, qqbot, bluebubbles, homeassistant. Each adapter exposes `getRequiredEnv()` and throws clear messages when credentials are absent.
75
+ `plugins/platform-*/`: webhook, api_server, telegram, discord, slack, whatsapp, signal, matrix, mattermost, email, sms, dingtalk, wecom, weixin, feishu, qqbot, bluebubbles, homeassistant. Each adapter exposes `getRequiredEnv()` and throws clear messages when credentials are absent.
73
76
 
74
77
  ## Memory providers
75
78
 
76
- `src/plugins/memory/`: honcho, mem0, supermemory, byterover, hindsight, holographic (local-FS), openviking, retaindb. Set `memory.provider` in `~/.freddie/config.yaml` and the corresponding `*_API_KEY`.
79
+ `plugins/memory-*/`: honcho, mem0, supermemory, byterover, hindsight, holographic (local-FS), openviking, retaindb. Set `memory.provider` in `~/.freddie/config.yaml` and the corresponding `*_API_KEY`.
80
+
81
+ ## Plugin compatibility
82
+
83
+ Freddie accepts three plugin shapes:
84
+
85
+ - **Native**: `{ name, surfaces, register(ctx) }` — the standard freddie contract
86
+ - **plugsdk** (`definePlugin()` format): `{ name, tools, hooks, meta }` — auto-detected and wrapped by `wrapPlugsdkPlugin()` in `src/host/host.js`
87
+ - **gm-cc**: installed as `gm-cc` npm dep; `plugins/gm-cc/plugin.js` discovers and registers all 12 SKILL.md files under the `gm:*` namespace in `pi.skills`
88
+
89
+ [`plugsdk`](https://www.npmjs.com/package/plugsdk) is an npm dependency (`^1.0.7`). `src/host/contract.js` re-exports `definePlugin`, `HookType`, `allowResult`, `blockResult`, `modifyResult`, `PluginRunner`, `PluginRuntime`, and `piAdapter` from it. `FREDDIE_TO_SDK_HOOK` maps freddie hook names to `HookType.*` constants (e.g. `HookType.PRE_TOOL_USE`).
90
+
91
+ plugsdk adapters: freddie/pi, MCP, OpenAI, LangChain, Cursor/VSCode, Aider (9 adapters total).
77
92
 
78
93
  ## Layout
79
94
 
@@ -86,7 +101,7 @@ freddie/
86
101
  │ ├── sessions.js # SQLite + FTS5
87
102
  │ ├── auth.js # FileAuthStore (~/.freddie/auth/)
88
103
  │ ├── batch.js # parallel batch runner
89
- │ ├── tools/ # registry + 11 built-in tools + environments/
104
+ │ ├── tools/ # registry + environments/ (tools now in plugins/*/)
90
105
  │ ├── toolsets.js
91
106
  │ ├── agent/{machine,pi-bridge}.js # xstate turn machine + pi-ai bridge
92
107
  │ ├── commands/{registry,profile}.js # CommandDef + CRUD
@@ -94,7 +109,7 @@ freddie/
94
109
  │ ├── context/engine.js # pluggable context blocks
95
110
  │ ├── cron/{scheduler,cron-parse}.js
96
111
  │ ├── web/{server,index.html} # dashboard
97
- │ ├── gateway/ # Gateway + 18 platform adapters
112
+ │ ├── gateway/ # Gateway (platform adapters now in plugins/platform-*/)
98
113
  │ ├── acp/server.js # JSON-RPC stdio
99
114
  │ ├── plugins/ # PluginManager + 8 memory backends
100
115
  │ ├── skills/index.js # SKILL.md loader
@@ -105,12 +120,12 @@ freddie/
105
120
  ├── website/ # flatspace-powered docs site (content/pages/*.yaml + theme.mjs)
106
121
  ├── AGENTS.md
107
122
  ├── CHANGELOG.md
108
- └── test.js # 21 named groups, ≤200 lines, real services
123
+ └── test.js # 12 named groups, ≤200 lines, real services
109
124
  ```
110
125
 
111
126
  ## Status
112
127
 
113
- Tier 0.3 complete and witnessed: 21 named tests passing, dashboard + website both live-witnessed via headless browser.
128
+ v0.1.1 complete and witnessed: 12/12 named tests passing, dashboard + website both live-witnessed via headless browser.
114
129
 
115
130
  - 16 gateway platforms with functional wire-format code (no throwing stubs)
116
131
  - 8 memory providers call real endpoints (or local-FS for `holographic`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "freddie",
3
- "version": "0.0.44",
3
+ "version": "0.0.46",
4
4
  "type": "module",
5
5
  "description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
6
6
  "bin": {
@@ -12,6 +12,7 @@
12
12
  "test": "node test.js"
13
13
  },
14
14
  "dependencies": {
15
+ "@libsql/client": "^0.5.0",
15
16
  "@mariozechner/pi-agent-core": "^0.70.6",
16
17
  "@mariozechner/pi-ai": "^0.70.6",
17
18
  "@mariozechner/pi-coding-agent": "^0.70.6",
@@ -21,10 +22,11 @@
21
22
  "express": "^5.0.0",
22
23
  "flatspace": "^1.0.18",
23
24
  "floosie": "^0.6.14",
25
+ "gm-cc": "^2.0.727",
24
26
  "js-yaml": "^4.1.0",
25
- "@libsql/client": "^0.5.0",
26
27
  "xstate": "^5.31.0",
27
- "zod": "^4.0.0"
28
+ "zod": "^4.0.0",
29
+ "plugsdk": "^1.0.7"
28
30
  },
29
31
  "optionalDependencies": {
30
32
  "@libsql/darwin-arm64": "0.3.19",
@@ -17,7 +17,7 @@ export async function callLLM({ messages, tools = [], model } = {}) {
17
17
  model: useModel,
18
18
  messages: messages.map(adaptMessage),
19
19
  stream: false,
20
- max_tokens: 1024,
20
+ max_tokens: 4096,
21
21
  }
22
22
  if (Array.isArray(tools) && tools.length) body.tools = tools.map(adaptTool)
23
23
  const res = await fetch(base.replace(/\/$/, '') + '/chat/completions', {
@@ -1,3 +1,5 @@
1
+ export { definePlugin, HookType, allowResult, blockResult, modifyResult, PluginRunner, PluginRuntime, piAdapter } from 'plugsdk'
2
+
1
3
  export const SURFACES = ['pi', 'gui', 'both']
2
4
 
3
5
  export const PI_VERBS = ['tool', 'env', 'command', 'cron', 'platform', 'memory', 'skill', 'context', 'agentExt', 'cli']
@@ -11,6 +13,17 @@ export const HOOK_NAMES = [
11
13
  'onMessageInbound', 'onMessageOutbound',
12
14
  ]
13
15
 
16
+ import { HookType } from 'plugsdk'
17
+
18
+ export const FREDDIE_TO_SDK_HOOK = {
19
+ preToolCall: HookType.PRE_TOOL_USE,
20
+ postToolCall: HookType.POST_TOOL_USE,
21
+ onSessionStart: HookType.SESSION_START,
22
+ onSessionEnd: HookType.SESSION_END,
23
+ onMessageInbound: HookType.PROMPT_SUBMIT,
24
+ onMessageOutbound: HookType.AFTER_RESPONSE,
25
+ }
26
+
14
27
  export function validatePlugin(p) {
15
28
  if (!p || typeof p !== 'object') throw new Error('plugin: object required')
16
29
  if (!p.name || typeof p.name !== 'string') throw new Error('plugin.name: string required')
package/src/host/host.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import { pathToFileURL } from 'node:url'
4
- import { validatePlugin, topoSort, HOOK_NAMES, PI_VERBS, GUI_VERBS } from './contract.js'
4
+ import { validatePlugin, topoSort, HOOK_NAMES, PI_VERBS, GUI_VERBS, FREDDIE_TO_SDK_HOOK, piAdapter } from './contract.js'
5
5
 
6
6
  function makePiSurface() {
7
7
  const tools = new Map()
@@ -76,8 +76,16 @@ function makeHooks() {
76
76
  reg[name].push(fn)
77
77
  },
78
78
  async invoke(name, payload) {
79
+ const sdkHook = FREDDIE_TO_SDK_HOOK[name]
79
80
  let cur = payload
80
- for (const fn of reg[name] || []) { cur = (await fn(cur)) ?? cur }
81
+ for (const fn of reg[name] || []) {
82
+ const raw = await fn(cur)
83
+ if (raw !== undefined && raw !== null && sdkHook && typeof raw === 'object' && 'behavior' in raw) {
84
+ cur = piAdapter.translateHookOutput(sdkHook, raw)
85
+ } else {
86
+ cur = raw ?? cur
87
+ }
88
+ }
81
89
  return cur
82
90
  },
83
91
  names() { return HOOK_NAMES },
@@ -111,6 +119,30 @@ function nullStore() {
111
119
  return { get: (k, d) => m.has(k) ? m.get(k) : d, set: (k, v) => m.set(k, v), all: (prefix) => Object.fromEntries([...m.entries()].filter(([k]) => k.startsWith(prefix))) }
112
120
  }
113
121
 
122
+ const SDK_TO_FREDDIE = Object.fromEntries(Object.entries(FREDDIE_TO_SDK_HOOK).map(([f, s]) => [s, f]))
123
+
124
+ function wrapPlugsdkPlugin(p) {
125
+ if (typeof p.register === 'function') return p
126
+ if (!p.tools && !p.hooks) return p
127
+ return {
128
+ name: p.name,
129
+ surfaces: 'pi',
130
+ register(ctx) {
131
+ for (const [id, tool] of Object.entries(p.tools || {})) {
132
+ ctx.pi.tools.register({
133
+ name: id,
134
+ schema: { name: id, description: tool.description, parameters: tool.parameters },
135
+ handler: (args, rctx) => tool.execute(args, rctx),
136
+ })
137
+ }
138
+ for (const [hookType, fn] of Object.entries(p.hooks || {})) {
139
+ const freddieName = SDK_TO_FREDDIE[hookType]
140
+ if (freddieName) ctx.hooks.on(freddieName, fn)
141
+ }
142
+ },
143
+ }
144
+ }
145
+
114
146
  export function createHost({ surfaces = ['pi', 'gui'], configStore = nullStore(), env = process.env } = {}) {
115
147
  const pi = makePiSurface()
116
148
  const gui = makeGuiSurface()
@@ -124,7 +156,7 @@ export function createHost({ surfaces = ['pi', 'gui'], configStore = nullStore()
124
156
  get: (name) => loaded.find(p => p.name === name) || null,
125
157
  }
126
158
  async function loadAll(plugins) {
127
- const validated = plugins.map(validatePlugin)
159
+ const validated = plugins.map(p => wrapPlugsdkPlugin(p)).map(validatePlugin)
128
160
  const sorted = topoSort(validated)
129
161
  for (const p of sorted) {
130
162
  const want = p.surfaces