freddie 0.0.44 → 0.0.45

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
 
@@ -214,7 +216,7 @@ All 21 named integration tests in `test.js` pass (exit 0). Subsystem coverage:
214
216
 
215
217
  - **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
218
  - **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.
219
+ - **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
220
 
219
221
  ## Pre-rename validation snapshot (2026-05-03)
220
222
 
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,25 @@ 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
+ plugdsdk adapters: freddie/pi, MCP, OpenAI, LangChain, Cursor/VSCode, Aider (9 adapters total).
77
90
 
78
91
  ## Layout
79
92
 
@@ -86,7 +99,7 @@ freddie/
86
99
  │ ├── sessions.js # SQLite + FTS5
87
100
  │ ├── auth.js # FileAuthStore (~/.freddie/auth/)
88
101
  │ ├── batch.js # parallel batch runner
89
- │ ├── tools/ # registry + 11 built-in tools + environments/
102
+ │ ├── tools/ # registry + environments/ (tools now in plugins/*/)
90
103
  │ ├── toolsets.js
91
104
  │ ├── agent/{machine,pi-bridge}.js # xstate turn machine + pi-ai bridge
92
105
  │ ├── commands/{registry,profile}.js # CommandDef + CRUD
@@ -94,7 +107,7 @@ freddie/
94
107
  │ ├── context/engine.js # pluggable context blocks
95
108
  │ ├── cron/{scheduler,cron-parse}.js
96
109
  │ ├── web/{server,index.html} # dashboard
97
- │ ├── gateway/ # Gateway + 18 platform adapters
110
+ │ ├── gateway/ # Gateway (platform adapters now in plugins/platform-*/)
98
111
  │ ├── acp/server.js # JSON-RPC stdio
99
112
  │ ├── plugins/ # PluginManager + 8 memory backends
100
113
  │ ├── skills/index.js # SKILL.md loader
@@ -105,12 +118,12 @@ freddie/
105
118
  ├── website/ # flatspace-powered docs site (content/pages/*.yaml + theme.mjs)
106
119
  ├── AGENTS.md
107
120
  ├── CHANGELOG.md
108
- └── test.js # 21 named groups, ≤200 lines, real services
121
+ └── test.js # 12 named groups, ≤200 lines, real services
109
122
  ```
110
123
 
111
124
  ## Status
112
125
 
113
- Tier 0.3 complete and witnessed: 21 named tests passing, dashboard + website both live-witnessed via headless browser.
126
+ v0.1.1 complete and witnessed: 12/12 named tests passing, dashboard + website both live-witnessed via headless browser.
114
127
 
115
128
  - 16 gateway platforms with functional wire-format code (no throwing stubs)
116
129
  - 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.45",
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