freddie 0.0.102 → 0.0.104
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 +5 -3
- package/package.json +21 -8
- package/plugins/gm-skill/plugin.js +49 -0
- package/plugins/gui-acptoapi-chains/plugin.js +77 -0
- package/plugins/image_gen/handler.js +14 -9
- package/plugins/transcription/handler.js +10 -3
- package/plugins/tts/handler.js +15 -10
- package/plugins/vision/handler.js +19 -10
- package/src/agent/__browser_shims/alias-plugin.mjs +24 -0
- package/src/agent/__browser_shims/build.mjs +18 -0
- package/src/agent/__browser_shims/config.js +4 -0
- package/src/agent/__browser_shims/entry.js +2 -0
- package/src/agent/__browser_shims/home.js +3 -0
- package/src/agent/__browser_shims/host.js +36 -0
- package/src/agent/__browser_shims/llm_resolver.js +8 -0
- package/src/agent/__browser_shims/log.js +10 -0
- package/src/agent/__browser_shims/toolsets.js +18 -0
- package/src/agent/acptoapi-bridge.js +15 -5
- package/src/agent/codex_responses_adapter.js +9 -4
- package/src/agent/gemini_native_adapter.js +10 -5
- package/src/agent/image_gen_provider.js +19 -7
- package/src/agent/llm_resolver.js +7 -1
- package/src/agent/machine.js +46 -23
- package/src/agent/model-discovery.js +37 -84
- package/src/browser/index.js +23 -0
- package/src/host/contract.js +5 -0
- package/src/host/host_helpers.js +13 -5
- package/src/web/server.js +2 -2
- package/plugins/gm-cc/plugin.js +0 -26
- package/src/agent/model-sampler.js +0 -13
package/AGENTS.md
CHANGED
|
@@ -30,7 +30,7 @@ When you touch one of the four direct-fetch utility plugins above, the right fix
|
|
|
30
30
|
|
|
31
31
|
Consume these top-level acptoapi exports directly (no re-export shim, no helper module): `chat`, `stream`, `chain`, `chatChain`, `streamChain`, `fallback`, `buildAutoChain`, `resolveModel`, `parseCommaList`, `splitPrefix`, `listAllModelsAndQueues`, `resolveQueue`, `listAllQueues`, `loadMatrix`, `matrixScore`, `clearMatrixCache`, `peekStatus`, `getStatus`, `isAvailable`, `markFailed`, `markOk`, `resetAvailability`, `startSampler`, `stopSampler`, `createSampler`, `probe`, `probeModels`, `getCachedModels`, `getRunHistory`, `PROVIDER_KEYS`, `PROVIDER_DEFAULTS`.
|
|
32
32
|
|
|
33
|
-
Public surface reference: `node_modules/acptoapi/AGENTS.md` "Public API — unified chain SDK". Acceptable freddie-side adapters (cannot be deleted yet): `model-discovery.js` (claude-cli/ACP/ollama probing breadth acptoapi doesn't cover
|
|
33
|
+
Public surface reference: `node_modules/acptoapi/AGENTS.md` "Public API — unified chain SDK". Acceptable freddie-side adapters (cannot be deleted yet): `model-discovery.js` (claude-cli/ACP/ollama probing breadth acptoapi doesn't cover; `listKnownProviders` merges `agent.discovered_models` keys + acptoapi `PROVIDER_KEYS` + `[claude-cli,kilo,opencode,ollama]`), `model-matrix.js` (28L MATRIX_FILE path helper + `matrixUsable` predicate — freddie-side because the matrix file path is repo-local), `acptoapi-bridge.js` (HTTP daemon passthrough at FREDDIE_LLM_URL when reachable, for `claude/*` etc that need the OAuth-managed daemon). Removed 2026-05-17: `src/agent/model-sampler.js` (was 13L re-export shim — callers now import sampler funcs directly via `createRequire('acptoapi')`).
|
|
34
34
|
|
|
35
35
|
Matrix wired: shim passes `matrixSource: process.env.FREDDIE_MATRIX_URL || <repo>/.gm/model-availability.json` only for comma-list or `queue/<name>` model strings (single-shot omits to avoid leaking chain opts into upstream HTTP body — bug fixed in acptoapi 1.0.62 buildParams/_stripChainOpts, but the conditional pass-through stays as defense-in-depth).
|
|
36
36
|
|
|
@@ -64,7 +64,7 @@ Thin shims (still resolved through host, do not bypass):
|
|
|
64
64
|
|
|
65
65
|
Witness 2026-05-03: test.js 12/12 green @ 195L (asserts `host.plugins().length>=100`, `platforms.list>=18`, `memory.list>=8`, surface guard throws, cycle throws). `node bin/freddie.js tools` shows 70. `help-all` 32 lines. 11 dashboard `/api/*` routes return 200.
|
|
66
66
|
|
|
67
|
-
**gm-
|
|
67
|
+
**gm-skill plugin integration** — `plugins/gm-skill/plugin.js` registers ONE canonical skill named `gm-skill`. Resolution order: (1) `~/.claude/skills/gm-skill/SKILL.md` (the `bun x skills add AnEntrypoint/gm-skill -y -g` install), (2) `node_modules/gm-cc/skills/gm-skill/SKILL.md` (npm fallback). All other `gm-*` platform variants (gm-cc, gm-codex, gm-cursor, gm-jetbrains, gm-kilo, gm-oc, gm-vscode, gm-zed, gm-gc, gm-copilot-cli) are DEPRECATED — do not register them. `src/host/host_helpers.js::loadCcFromNodeModules` carries `CC_EXCLUDE = new Set(['gm-cc'])` so the gm-cc npm package is not auto-discovered as a cc-plugin (which would otherwise inject all 25 deprecated variants). test.js asserts exactly one gm-prefixed skill is registered, named `gm-skill`.
|
|
68
68
|
|
|
69
69
|
## Multi-project workspace system (2026-05-04)
|
|
70
70
|
|
|
@@ -269,7 +269,7 @@ All 21 named integration tests in `test.js` pass (exit 0). Subsystem coverage:
|
|
|
269
269
|
- **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.
|
|
270
270
|
- **acptoapi dep pattern** (2026-05-12) — `package.json` now pins `"acptoapi": "^1.0.55"` from the npm registry (CI auto-bumps on each acptoapi push; restore-package.cjs ROOT FIX prevents file: regressions). For local SDK iteration, swap to `file:../acptoapi` temporarily. CJS/ESM boundary bridged via `createRequire(import.meta.url)` in freddie ESM files that import acptoapi CJS exports.
|
|
271
271
|
- **LLM resolver priority** (2026-05-10) — (1) explicit provider+key, (2) acptoapi if `/v1/models` returns 200, (3) `agent.model_preference` config array (ordered failover, sampler-gated), (4) `sdk.buildAutoChain()` env-key scan, (5) throw. `PROVIDER_KEYS` and `PROVIDER_DEFAULTS` imported from `acptoapi` — not maintained in freddie. `sdk.chat()` returns OpenAI `{choices:[{message}]}` format; `sdkChat()` adapter in llm_resolver converts to freddie's `{content, tool_calls, raw}`.
|
|
272
|
-
- **Model sampler —
|
|
272
|
+
- **Model sampler — direct acptoapi import** (2026-05-17) — `src/agent/model-sampler.js` was deleted as redundant. Sampler funcs (`isAvailable`, `markFailed`, `markOk`, `resetAvailability`, `getStatus`, `probe`, `startSampler`, `stopSampler`, `createSampler`) come straight from `acptoapi` via `createRequire`. Backoff logic (5-step 30s→480s, createSampler factory, singleton) lives in `c:\dev\acptoapi\lib\sampler.js`.
|
|
273
273
|
- **model_preference config key** (2026-05-10) — `agent.model_preference: []` in `~/.freddie/config.yaml`. Array of `{ provider, model? }` objects; `resolveCallLLM` tries each in order, skipping unavailable (sampler-gated) and marking failures with backoff. Config v2 migration adds the key on upgrade from v1.
|
|
274
274
|
- **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).
|
|
275
275
|
|
|
@@ -290,6 +290,8 @@ All 12 test.js named groups passing: home+config+skin, sessions+FTS5, tools+tool
|
|
|
290
290
|
- 2026-05-03 (session 4): Plugin-architecture decomposition recorded — added "Plugin architecture" section before Layout, rewrote "Adding a tool" + "Adding a gateway platform" for plugins/<name>/{plugin,handler}.js shape. Ingested 6 facts to rs-learn (project/freddie-plugin-architecture, reference/freddie-host-contract, reference/freddie-plugin-ctx, project/freddie-migrated-subsystems, reference/freddie-thin-shims, project/freddie-plugin-witness). Audit: 5 queries fired (pi-ai env keys, profile safe paths, libsql async debt, browser inline module errors, yaml colon space trap, plus self-test on freddie-plugin-architecture) — all returned "No recall results". rs-learn ingest path live but retrieval side empty for this session (likely needs learn-build propagation). 0 items migrated; AGENTS.md items retained.
|
|
291
291
|
- 2026-05-04: Recorded freddie publish workflow root fix — `.github/workflows/restore-package.cjs` now pins anentrypoint-design via `npm view` + version (`^<latest>`) instead of file: dep; `publish.yml` runs `npm install --package-lock-only` to sync lockfile. Updated "Rebase regression trap" entry with detailed causation + recurrence tell. Added new "GitHub Actions deploy-pages@v5 duplicate artifact rejection" caveat (rerun --failed silently re-uploads; trigger fresh run instead). rs-learn store still unavailable. 0 items migrated; 2 facts added/refined in AGENTS.md.
|
|
292
292
|
- 2026-05-10: 15-provider LLM resolver expansion. Added model-sampler.js (backoff), PROVIDER_KEYS/DEFAULTS expansion, model_preference config key, config v2 migration. Updated "LLM resolver priority" + "15-provider support" + "Model sampler" + "model_preference" entries in AGENTS.md. test.js 12/12 green at exactly 200 lines. rs-learn store still unavailable. 4 new facts added to AGENTS.md.
|
|
293
|
+
- 2026-05-17 (continuation): +2 memorizes for uncommitted working-tree state — freddie-llm-resolver-anthropic-fallback-removed (project) captures the ~95L deletion of @anthropic-ai/sdk fallback in src/agent/llm_resolver.js (aligns with 'acptoapi is THE SDK' policy); freddie-stray-test-files-policy-violation (feedback) flags untracked src/agent/acptoapi-provider.js + test-acptoapi.js as violating the 'one test.js at root' rule. Total session exfiltration: 20 KV keys.
|
|
294
|
+
- 2026-05-17: AGENTS.md exfiltration to learning store. 18 memorize dispatches landed (KV keys mem-1779041362557 through mem-1779041487836, 21KB total) covering: substrate stack, dynamic stack contract, acptoapi SDK surface, plugin architecture, multi-project workspace, 3 gotcha batches (browser/async, codeinsight FPs, exec/CI), dashboard caveats, website YAML, LLM resolver+sampler, trajectory v2 + validation witness, opencode+kilo ACP, gm-cc + sync-upstream, model availability matrix, plugsdk + Rust gotchas, testing/profile/cache policies, layout map. learn-status confirms wasm-via-KV mode. In-session recall still returns empty (consistent with documented retrieval-side gap; index propagation is cross-session). PRD 18/18 resolved; phases PLAN -> EXECUTE -> VERIFY -> COMPLETE.
|
|
293
295
|
- 2026-05-10 (session 2): Deep integration audit between freddie and acptoapi SDK. Moved sampler logic into acptoapi lib/sampler.js (createSampler factory + singleton, 5-step backoff). Added lib/provider-maps.js (PROVIDER_KEYS + PROVIDER_DEFAULTS, 17 providers, derived from BRANDS+auto-chain). freddie: switched to file:../acptoapi dep, model-sampler.js replaced with 13-line re-export shim, llm_resolver.js simplified 131→95L (OPENAI_COMPAT removed, PROVIDER_KEYS/DEFAULTS from SDK, buildAutoChain() for auto-scan, sdkChat() adapter for OpenAI→freddie format). test.js 12/12 green. Both repos pushed. AGENTS.md updated with acptoapi dep pattern, re-export shim pattern, CJS/ESM bridge via createRequire.
|
|
294
296
|
|
|
295
297
|
## Dashboard web UI caveats
|
package/package.json
CHANGED
|
@@ -1,33 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "freddie",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.104",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Open JS agent harness built on pi-mono, floosie, xstate, and anentrypoint-design",
|
|
6
6
|
"bin": {
|
|
7
7
|
"freddie": "./bin/freddie.js"
|
|
8
8
|
},
|
|
9
9
|
"main": "./src/index.js",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": "./src/index.js",
|
|
12
|
+
"./browser": {
|
|
13
|
+
"browser": "./dist/browser/freddie.js",
|
|
14
|
+
"default": "./src/browser/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
10
17
|
"scripts": {
|
|
11
18
|
"start": "node bin/freddie.js",
|
|
12
|
-
"test": "node test.js"
|
|
19
|
+
"test": "node test.js",
|
|
20
|
+
"build:browser": "vite build --config vite.browser.config.js"
|
|
13
21
|
},
|
|
14
22
|
"dependencies": {
|
|
23
|
+
"@anentrypoint/libsql-plugkit-client": "github:AnEntrypoint/libsql-plugkit-client",
|
|
15
24
|
"@libsql/client": "^0.5.0",
|
|
16
25
|
"@mariozechner/pi-agent-core": "^0.70.6",
|
|
17
26
|
"@mariozechner/pi-ai": "^0.70.6",
|
|
18
27
|
"@mariozechner/pi-coding-agent": "^0.70.6",
|
|
19
28
|
"@mariozechner/pi-tui": "^0.70.6",
|
|
29
|
+
"acptoapi": "^1.0.90",
|
|
30
|
+
"anentrypoint-design": "^0.0.108",
|
|
20
31
|
"commander": "^14.0.0",
|
|
21
32
|
"express": "^5.0.0",
|
|
22
33
|
"flatspace": "^1.0.18",
|
|
23
34
|
"floosie": "^0.6.14",
|
|
24
|
-
"gm-cc": "^2.0.
|
|
35
|
+
"gm-cc": "^2.0.1081",
|
|
25
36
|
"js-yaml": "^4.1.0",
|
|
26
|
-
"
|
|
37
|
+
"libsql-plugkit-client": "^0.0.10",
|
|
38
|
+
"plugsdk": "^1.0.20",
|
|
27
39
|
"xstate": "^5.31.0",
|
|
28
|
-
"zod": "^4.0.0"
|
|
29
|
-
"anentrypoint-design": "^0.0.95",
|
|
30
|
-
"acptoapi": "latest"
|
|
40
|
+
"zod": "^4.0.0"
|
|
31
41
|
},
|
|
32
42
|
"optionalDependencies": {
|
|
33
43
|
"@libsql/darwin-arm64": "0.3.19",
|
|
@@ -71,5 +81,8 @@
|
|
|
71
81
|
"freddie",
|
|
72
82
|
"pi-mono",
|
|
73
83
|
"xstate"
|
|
74
|
-
]
|
|
84
|
+
],
|
|
85
|
+
"devDependencies": {
|
|
86
|
+
"vite": "^8.0.13"
|
|
87
|
+
}
|
|
75
88
|
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { createRequire } from 'module'
|
|
2
|
+
import path from 'path'
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
|
|
5
|
+
const _require = createRequire(import.meta.url)
|
|
6
|
+
|
|
7
|
+
function resolveSkillMd() {
|
|
8
|
+
const home = process.env.USERPROFILE || process.env.HOME
|
|
9
|
+
if (home) {
|
|
10
|
+
const userSkill = path.join(home, '.claude', 'skills', 'gm-skill', 'SKILL.md')
|
|
11
|
+
if (fs.existsSync(userSkill)) return userSkill
|
|
12
|
+
}
|
|
13
|
+
try {
|
|
14
|
+
const pkgPath = _require.resolve('gm-cc/package.json')
|
|
15
|
+
const candidate = path.join(path.dirname(pkgPath), 'skills', 'gm-skill', 'SKILL.md')
|
|
16
|
+
if (fs.existsSync(candidate)) return candidate
|
|
17
|
+
} catch {}
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function parseFrontmatter(md) {
|
|
22
|
+
const m = md.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/)
|
|
23
|
+
if (!m) return { fields: {}, body: md }
|
|
24
|
+
const fields = {}
|
|
25
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
26
|
+
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/)
|
|
27
|
+
if (kv) fields[kv[1]] = kv[2]
|
|
28
|
+
}
|
|
29
|
+
return { fields, body: m[2] }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default {
|
|
33
|
+
name: 'gm-skill',
|
|
34
|
+
surfaces: 'pi',
|
|
35
|
+
register({ pi }) {
|
|
36
|
+
const skillPath = resolveSkillMd()
|
|
37
|
+
if (!skillPath) return
|
|
38
|
+
const raw = fs.readFileSync(skillPath, 'utf8')
|
|
39
|
+
const { fields, body } = parseFrontmatter(raw)
|
|
40
|
+
pi.skills.register({
|
|
41
|
+
name: 'gm-skill',
|
|
42
|
+
description: fields.description || 'AI-native software engineering harness',
|
|
43
|
+
content: body,
|
|
44
|
+
source: 'gm-skill',
|
|
45
|
+
frontmatter: fields,
|
|
46
|
+
file: skillPath,
|
|
47
|
+
})
|
|
48
|
+
},
|
|
49
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
// freddie ↔ acptoapi chain bridge — surfaces acptoapi /v1/chains, /debug/probe-live
|
|
2
|
+
// through freddie's GUI API so the dashboard can manage named fallback chains
|
|
3
|
+
// without thebird talking directly to acptoapi.
|
|
4
|
+
import { getAcptoapiUrl } from '../../src/agent/acptoapi-bridge.js'
|
|
5
|
+
|
|
6
|
+
function base() { return getAcptoapiUrl().replace(/\/v1\/?$/, '') }
|
|
7
|
+
const HDR = () => ({ 'content-type': 'application/json', authorization: 'Bearer none' })
|
|
8
|
+
|
|
9
|
+
async function fwd(url, init) {
|
|
10
|
+
try {
|
|
11
|
+
const r = await fetch(url, init)
|
|
12
|
+
const ct = r.headers.get('content-type') || ''
|
|
13
|
+
if (ct.includes('json')) return { status: r.status, json: await r.json() }
|
|
14
|
+
return { status: r.status, text: await r.text() }
|
|
15
|
+
} catch (e) { return { status: 502, json: { error: { message: e.message, via: url } } } }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
name: 'gui-acptoapi-chains', surfaces: 'gui',
|
|
20
|
+
register({ gui }) {
|
|
21
|
+
gui.route('GET', '/api/acptoapi/health', async (_, res) => {
|
|
22
|
+
const r = await fwd(base() + '/health', { headers: HDR() })
|
|
23
|
+
res.status(r.status === 502 ? 502 : 200).json(r.json || { text: r.text })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
gui.route('GET', '/api/acptoapi/chains', async (_, res) => {
|
|
27
|
+
const r = await fwd(base() + '/v1/chains', { headers: HDR() })
|
|
28
|
+
res.status(r.status).json(r.json || {})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
gui.route('POST', '/api/acptoapi/chains', async (req, res) => {
|
|
32
|
+
const { name, links } = req.body || {}
|
|
33
|
+
if (!name || !Array.isArray(links)) return res.status(400).json({ error: { message: 'name + links[] required' } })
|
|
34
|
+
const r = await fwd(base() + '/v1/chains', {
|
|
35
|
+
method: 'POST', headers: HDR(), body: JSON.stringify({ name, links }),
|
|
36
|
+
})
|
|
37
|
+
res.status(r.status).json(r.json || {})
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
gui.route('DELETE', '/api/acptoapi/chains/:name', async (req, res) => {
|
|
41
|
+
const r = await fwd(base() + '/v1/chains?name=' + encodeURIComponent(req.params.name), {
|
|
42
|
+
method: 'DELETE', headers: HDR(),
|
|
43
|
+
})
|
|
44
|
+
res.status(r.status).json(r.json || {})
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
gui.route('GET', '/api/acptoapi/probe', async (req, res) => {
|
|
48
|
+
const force = req.query.force === '1' ? '?force=1' : ''
|
|
49
|
+
const r = await fwd(base() + '/debug/probe-live' + force, { headers: HDR() })
|
|
50
|
+
res.status(r.status).json(r.json || {})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
gui.route('GET', '/api/acptoapi/models', async (_, res) => {
|
|
54
|
+
const r = await fwd(base() + '/v1/models', { headers: HDR() })
|
|
55
|
+
res.status(r.status).json(r.json || {})
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
gui.route('GET', '/api/acptoapi/auto-chain', async (_, res) => {
|
|
59
|
+
const r = await fwd(base() + '/debug/auto-chain', { headers: HDR() })
|
|
60
|
+
res.status(r.status).json(r.json || {})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
// surface acptoapi config knobs freddie exposes via env
|
|
64
|
+
gui.route('GET', '/api/acptoapi/config', (_, res) => {
|
|
65
|
+
res.json({
|
|
66
|
+
url: base(),
|
|
67
|
+
model: process.env.FREDDIE_LLM_MODEL || 'claude/haiku',
|
|
68
|
+
envHints: {
|
|
69
|
+
FREDDIE_LLM_URL: process.env.FREDDIE_LLM_URL || null,
|
|
70
|
+
FREDDIE_LLM_MODEL: process.env.FREDDIE_LLM_MODEL || null,
|
|
71
|
+
ACPTOAPI_LIVE_PROBE: process.env.ACPTOAPI_LIVE_PROBE || null,
|
|
72
|
+
ACPTOAPI_PROBE_CAP: process.env.ACPTOAPI_PROBE_CAP || null,
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
},
|
|
77
|
+
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
// All upstream connectivity lives in acptoapi. This handler is a thin wrapper.
|
|
2
|
+
import { getAcptoapiUrl } from '../../src/agent/acptoapi-bridge.js'
|
|
3
|
+
|
|
1
4
|
export const _tool = ({
|
|
2
5
|
name: 'image_gen',
|
|
3
6
|
toolset: 'creative',
|
|
4
7
|
schema: {
|
|
5
8
|
name: 'image_gen',
|
|
6
|
-
description: 'Generate an image from a prompt.
|
|
9
|
+
description: 'Generate an image from a prompt. Routes through acptoapi /v1/images/generations.',
|
|
7
10
|
parameters: {
|
|
8
11
|
type: 'object',
|
|
9
12
|
properties: {
|
|
@@ -19,13 +22,15 @@ export const _tool = ({
|
|
|
19
22
|
requiresEnv: ['OPENAI_API_KEY or REPLICATE_API_TOKEN'],
|
|
20
23
|
handler: async ({ prompt, provider, size = '1024x1024', model }) => {
|
|
21
24
|
const which = provider || (process.env.OPENAI_API_KEY ? 'openai' : 'replicate')
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
|
|
26
|
+
const body = which === 'openai'
|
|
27
|
+
? { model: model || 'gpt-image-1', prompt, size }
|
|
28
|
+
: { version: model || 'black-forest-labs/flux-schnell', input: { prompt } }
|
|
29
|
+
const r = await fetch(base + '/v1/images/generations', {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: { 'content-type': 'application/json', 'x-provider': which, authorization: 'Bearer none' },
|
|
32
|
+
body: JSON.stringify(body),
|
|
33
|
+
})
|
|
34
|
+
return await r.json()
|
|
30
35
|
},
|
|
31
36
|
})
|
|
@@ -1,18 +1,25 @@
|
|
|
1
|
+
// Upstream connectivity lives in acptoapi.
|
|
1
2
|
import fs from 'node:fs'
|
|
3
|
+
import { getAcptoapiUrl } from '../../src/agent/acptoapi-bridge.js'
|
|
4
|
+
|
|
2
5
|
export const _tool = ({
|
|
3
6
|
name: 'transcription',
|
|
4
7
|
toolset: 'creative',
|
|
5
|
-
schema: { name: 'transcription', description: 'Transcribe audio
|
|
8
|
+
schema: { name: 'transcription', description: 'Transcribe audio via acptoapi /v1/audio/transcriptions (OpenAI Whisper).', parameters: { type: 'object', properties: { file_path: { type: 'string' }, model: { type: 'string', default: 'whisper-1' } }, required: ['file_path'] } },
|
|
6
9
|
requiresEnv: ['OPENAI_API_KEY'],
|
|
7
10
|
checkFn: () => Boolean(process.env.OPENAI_API_KEY),
|
|
8
11
|
handler: async ({ file_path, model = 'whisper-1' }) => {
|
|
9
|
-
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
10
12
|
if (!fs.existsSync(file_path)) return { error: 'file not found: ' + file_path }
|
|
13
|
+
const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
|
|
11
14
|
const blob = new Blob([fs.readFileSync(file_path)])
|
|
12
15
|
const fd = new FormData()
|
|
13
16
|
fd.append('file', blob, file_path.split(/[\\/]/).pop())
|
|
14
17
|
fd.append('model', model)
|
|
15
|
-
const r = await fetch('
|
|
18
|
+
const r = await fetch(base + '/v1/audio/transcriptions', {
|
|
19
|
+
method: 'POST',
|
|
20
|
+
headers: { authorization: 'Bearer none' },
|
|
21
|
+
body: fd,
|
|
22
|
+
})
|
|
16
23
|
return await r.json()
|
|
17
24
|
},
|
|
18
25
|
})
|
package/plugins/tts/handler.js
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
// Upstream connectivity lives in acptoapi.
|
|
2
|
+
import { getAcptoapiUrl } from '../../src/agent/acptoapi-bridge.js'
|
|
3
|
+
|
|
1
4
|
export const _tool = ({
|
|
2
5
|
name: 'tts',
|
|
3
6
|
toolset: 'creative',
|
|
4
|
-
schema: { name: 'tts', description: 'Synthesize speech (OpenAI tts-1 or ElevenLabs).', parameters: { type: 'object', properties: { text: { type: 'string' }, provider: { type: 'string', enum: ['openai', 'elevenlabs'], default: 'openai' }, voice: { type: 'string' } }, required: ['text'] } },
|
|
7
|
+
schema: { name: 'tts', description: 'Synthesize speech (OpenAI tts-1 or ElevenLabs) via acptoapi.', parameters: { type: 'object', properties: { text: { type: 'string' }, provider: { type: 'string', enum: ['openai', 'elevenlabs'], default: 'openai' }, voice: { type: 'string' } }, required: ['text'] } },
|
|
5
8
|
requiresEnv: ['OPENAI_API_KEY or ELEVENLABS_API_KEY'],
|
|
6
9
|
checkFn: () => Boolean(process.env.OPENAI_API_KEY || process.env.ELEVENLABS_API_KEY),
|
|
7
10
|
handler: async ({ text, provider = 'openai', voice = 'alloy' }) => {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
|
|
12
|
+
const body = provider === 'elevenlabs'
|
|
13
|
+
? { text, voice: voice || '21m00Tcm4TlvDq8ikWAM', provider: 'elevenlabs' }
|
|
14
|
+
: { model: 'tts-1', input: text, voice }
|
|
15
|
+
const xProv = provider === 'elevenlabs' ? 'tts.elevenlabs' : 'speech.openai'
|
|
16
|
+
const r = await fetch(base + '/v1/audio/speech', {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers: { 'content-type': 'application/json', 'x-provider': xProv, authorization: 'Bearer none' },
|
|
19
|
+
body: JSON.stringify(body),
|
|
20
|
+
})
|
|
21
|
+
return { status: r.status, contentType: r.headers.get('content-type'), bytes: (await r.arrayBuffer()).byteLength }
|
|
17
22
|
},
|
|
18
23
|
})
|
|
@@ -1,17 +1,26 @@
|
|
|
1
|
+
// All upstream connectivity lives in acptoapi. Vision is just a multimodal
|
|
2
|
+
// chat-completions call — go through the existing bridge.
|
|
3
|
+
import { callLLM, getAcptoapiUrl } from '../../src/agent/acptoapi-bridge.js'
|
|
4
|
+
|
|
1
5
|
export const _tool = ({
|
|
2
6
|
name: 'vision',
|
|
3
7
|
toolset: 'creative',
|
|
4
|
-
schema: { name: 'vision', description: 'Describe an image (URL or base64)
|
|
5
|
-
requiresEnv: ['OPENAI_API_KEY or ANTHROPIC_API_KEY'],
|
|
8
|
+
schema: { name: 'vision', description: 'Describe an image (URL or base64) via acptoapi chat-completions.', parameters: { type: 'object', properties: { image_url: { type: 'string' }, prompt: { type: 'string', default: 'Describe this image.' }, model: { type: 'string' } }, required: ['image_url'] } },
|
|
9
|
+
requiresEnv: ['OPENAI_API_KEY or ANTHROPIC_API_KEY (provided to acptoapi)'],
|
|
6
10
|
checkFn: () => Boolean(process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY),
|
|
7
|
-
handler: async ({ image_url, prompt = 'Describe this image.',
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
handler: async ({ image_url, prompt = 'Describe this image.', model }) => {
|
|
12
|
+
const messages = [{
|
|
13
|
+
role: 'user',
|
|
14
|
+
content: [
|
|
15
|
+
{ type: 'text', text: prompt },
|
|
16
|
+
{ type: 'image_url', image_url: { url: image_url } },
|
|
17
|
+
],
|
|
18
|
+
}]
|
|
19
|
+
try {
|
|
20
|
+
const r = await callLLM({ messages, model: model || 'openai/gpt-4o-mini' })
|
|
21
|
+
return { content: r.content, raw: r.raw }
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return { error: e.message, via: getAcptoapiUrl() }
|
|
12
24
|
}
|
|
13
|
-
if (!process.env.OPENAI_API_KEY) return { error: 'OPENAI_API_KEY required' }
|
|
14
|
-
const r = await fetch('https://api.openai.com/v1/chat/completions', { method: 'POST', headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY}`, 'content-type': 'application/json' }, body: JSON.stringify({ model: 'gpt-4o-mini', messages: [{ role: 'user', content: [{ type: 'text', text: prompt }, { type: 'image_url', image_url: { url: image_url } }] }] }) })
|
|
15
|
-
return await r.json()
|
|
16
25
|
},
|
|
17
26
|
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
|
|
2
|
+
import path from 'path';
|
|
3
|
+
const F='C:/dev/freddie';
|
|
4
|
+
const R=p=>path.resolve(p);
|
|
5
|
+
const ALIAS={
|
|
6
|
+
[R(F+'/src/host/index.js')]: R(F+'/src/agent/__browser_shims/host.js'),
|
|
7
|
+
[R(F+'/src/toolsets.js')]: R(F+'/src/agent/__browser_shims/toolsets.js'),
|
|
8
|
+
[R(F+'/src/agent/llm_resolver.js')]: R(F+'/src/agent/__browser_shims/llm_resolver.js'),
|
|
9
|
+
[R(F+'/src/observability/log.js')]: R(F+'/src/agent/__browser_shims/log.js'),
|
|
10
|
+
[R(F+'/src/config.js')]: R(F+'/src/agent/__browser_shims/config.js'),
|
|
11
|
+
[R(F+'/src/home.js')]: R(F+'/src/agent/__browser_shims/home.js'),
|
|
12
|
+
};
|
|
13
|
+
export default {
|
|
14
|
+
name: 'freddie-browser-alias',
|
|
15
|
+
setup(build){
|
|
16
|
+
build.onResolve({filter: /.*/}, async args=>{
|
|
17
|
+
if(args.path.startsWith('node:') || (!args.path.startsWith('.') && !path.isAbsolute(args.path))) return null;
|
|
18
|
+
const resolved = path.resolve(args.resolveDir||'', args.path);
|
|
19
|
+
const candidates=[resolved, resolved+'.js', resolved+'/index.js'];
|
|
20
|
+
for(const c of candidates) if(ALIAS[c]) return { path: ALIAS[c] };
|
|
21
|
+
return null;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
import * as esbuild from 'esbuild';
|
|
3
|
+
import alias from './alias-plugin.mjs';
|
|
4
|
+
await esbuild.build({
|
|
5
|
+
entryPoints: ['C:/dev/freddie/src/agent/__browser_shims/entry.js'],
|
|
6
|
+
bundle: true,
|
|
7
|
+
format: 'esm',
|
|
8
|
+
platform: 'browser',
|
|
9
|
+
conditions: ['browser','module','import'],
|
|
10
|
+
outfile: 'C:/dev/thebird/docs/freddie-runtime.js',
|
|
11
|
+
plugins: [alias],
|
|
12
|
+
legalComments: 'none',
|
|
13
|
+
minify: false,
|
|
14
|
+
sourcemap: false,
|
|
15
|
+
logLevel: 'info',
|
|
16
|
+
external: [],
|
|
17
|
+
});
|
|
18
|
+
console.log('bundle ok');
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export async function bootHost() {
|
|
2
|
+
const br = globalThis.__freddieRuntimeBridge
|
|
3
|
+
if (!br || !br.host) throw new Error('freddie-runtime: __freddieRuntimeBridge.host not set')
|
|
4
|
+
const host = br.host
|
|
5
|
+
return {
|
|
6
|
+
hooks: {
|
|
7
|
+
async invoke(name, payload) {
|
|
8
|
+
const map = { preToolCall: 'pre_tool_use', postToolCall: 'post_tool_use', onMessageInbound: 'user_prompt_submit', onMessageOutbound: null, onSessionStart: null, onSessionEnd: 'stop', onPreCompact: null, onPostCompact: null }
|
|
9
|
+
const key = map[name]
|
|
10
|
+
if (!key || !host.pi.hooks[key]) return null
|
|
11
|
+
let merged = null
|
|
12
|
+
for (const fn of host.pi.hooks[key]) {
|
|
13
|
+
try {
|
|
14
|
+
const r = await fn(payload || {})
|
|
15
|
+
if (!r || typeof r !== 'object') continue
|
|
16
|
+
merged = merged || {}
|
|
17
|
+
if (r.decision === 'block') return { behavior: 'block', reason: r.reason || 'denied' }
|
|
18
|
+
if (r.systemMessage) merged.systemMessage = (merged.systemMessage ? merged.systemMessage + '\n' : '') + r.systemMessage
|
|
19
|
+
if (r.additionalContext) merged.additionalContext = (merged.additionalContext ? merged.additionalContext + '\n' : '') + r.additionalContext
|
|
20
|
+
if (r.args) merged.args = r.args
|
|
21
|
+
} catch (e) { console.warn('[freddie-runtime] hook ' + key + ' threw:', e && e.message) }
|
|
22
|
+
}
|
|
23
|
+
return merged
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
pi: {
|
|
27
|
+
tools: host.pi.tools,
|
|
28
|
+
skills: host.pi.skills,
|
|
29
|
+
hooks: host.pi.hooks,
|
|
30
|
+
dispatchTool: async (name, args) => {
|
|
31
|
+
const out = await host.runTool(name, args || {})
|
|
32
|
+
return typeof out === 'string' ? out : JSON.stringify(out)
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function resolveCallLLM({ provider, model } = {}) {
|
|
2
|
+
return async (input) => {
|
|
3
|
+
const br = globalThis.__freddieRuntimeBridge
|
|
4
|
+
if (!br || !br.callLLM) throw new Error('freddie-runtime: __freddieRuntimeBridge.callLLM not set')
|
|
5
|
+
return await br.callLLM({ ...input, provider, model: model || input.model })
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
export const matrixUsable = () => false
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function log(rec){ try{ console.debug('[freddie]', rec) }catch{} }
|
|
2
|
+
export function logger(subsystem){
|
|
3
|
+
return {
|
|
4
|
+
debug: (msg, e={}) => console.debug('[freddie:'+subsystem+']', msg, e),
|
|
5
|
+
info: (msg, e={}) => console.log('[freddie:'+subsystem+']', msg, e),
|
|
6
|
+
warn: (msg, e={}) => console.warn('[freddie:'+subsystem+']', msg, e),
|
|
7
|
+
error: (msg, e={}) => console.error('[freddie:'+subsystem+']', msg, e),
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export function closeAll(){}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export async function getEnabledToolSchemas(enabled = ['core'], disabled = []) {
|
|
2
|
+
const br = globalThis.__freddieRuntimeBridge
|
|
3
|
+
if (!br || !br.host) return []
|
|
4
|
+
const host = br.host
|
|
5
|
+
const disabledSet = new Set(disabled)
|
|
6
|
+
const out = []
|
|
7
|
+
for (const [name, t] of host.pi.tools.entries()) {
|
|
8
|
+
if (disabledSet.has(name)) continue
|
|
9
|
+
out.push({ type: 'function', function: { name, description: t.description || '', parameters: t.inputSchema || { type: 'object', properties: {} } } })
|
|
10
|
+
}
|
|
11
|
+
return out
|
|
12
|
+
}
|
|
13
|
+
export async function getEnabledToolNames(enabled = ['core'], disabled = []) {
|
|
14
|
+
const br = globalThis.__freddieRuntimeBridge; if (!br || !br.host) return []
|
|
15
|
+
return [...br.host.pi.tools.keys()]
|
|
16
|
+
}
|
|
17
|
+
export async function getAvailableToolsets() { return ['core'] }
|
|
18
|
+
export const _FREDDIE_CORE_TOOLS = ['read','write','edit','grep','list']
|
|
@@ -84,11 +84,21 @@ function adaptResponse(r) {
|
|
|
84
84
|
|
|
85
85
|
function tryParseJson(s) { try { return typeof s === 'string' ? JSON.parse(s) : (s || {}) } catch { return {} } }
|
|
86
86
|
|
|
87
|
-
export async function isReachable() {
|
|
87
|
+
export async function isReachable(timeoutMs = 2000) {
|
|
88
88
|
try {
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
const controller = new AbortController()
|
|
90
|
+
const timeoutId = setTimeout(() => controller.abort(), timeoutMs)
|
|
91
|
+
try {
|
|
92
|
+
const res = await fetch(getAcptoapiUrl().replace(/\/$/, '') + '/models', {
|
|
93
|
+
headers: { authorization: 'Bearer none' },
|
|
94
|
+
signal: controller.signal
|
|
95
|
+
})
|
|
96
|
+
clearTimeout(timeoutId)
|
|
97
|
+
if (!res.ok) return false
|
|
98
|
+
const json = await res.json()
|
|
99
|
+
return Array.isArray(json.data) && json.data.length > 0
|
|
100
|
+
} finally {
|
|
101
|
+
clearTimeout(timeoutId)
|
|
102
|
+
}
|
|
93
103
|
} catch { return false }
|
|
94
104
|
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
|
|
1
|
+
// Upstream connectivity lives in acptoapi via /v1/responses passthrough.
|
|
2
|
+
import { getAcptoapiUrl } from './acptoapi-bridge.js'
|
|
2
3
|
import { isCodexModel } from '../cli/codex_models.js'
|
|
4
|
+
|
|
3
5
|
export async function chat({ input, model = 'o3-mini', tools = [], reasoning_effort = 'medium' } = {}) {
|
|
4
|
-
const k = await resolveKey('openai')
|
|
5
|
-
if (!k.value) throw new Error('OPENAI_API_KEY required (source: ' + k.source + ')')
|
|
6
6
|
if (!isCodexModel(model)) console.warn('[codex_responses] non-codex model: ' + model)
|
|
7
|
-
const
|
|
7
|
+
const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
|
|
8
|
+
const r = await fetch(base + '/v1/responses', {
|
|
9
|
+
method: 'POST',
|
|
10
|
+
headers: { authorization: 'Bearer none', 'content-type': 'application/json' },
|
|
11
|
+
body: JSON.stringify({ model, input, ...(tools.length ? { tools } : {}), reasoning: { effort: reasoning_effort } }),
|
|
12
|
+
})
|
|
8
13
|
return await r.json()
|
|
9
14
|
}
|
|
10
15
|
export const provider = 'codex_responses'
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
|
|
1
|
+
// Gemini upstream connectivity lives in acptoapi /v1beta/models/<model>:generateContent.
|
|
2
|
+
import { getAcptoapiUrl } from './acptoapi-bridge.js'
|
|
2
3
|
import { adaptToolForGemini, adaptMessagesForGemini } from './gemini_schema.js'
|
|
4
|
+
|
|
3
5
|
export async function chat({ messages, model = 'gemini-2.5-flash', tools = [] } = {}) {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
const url = 'https://generativelanguage.googleapis.com/v1beta/models/' + model + ':generateContent?key=' + k.value
|
|
6
|
+
const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
|
|
7
|
+
const url = `${base}/v1beta/models/${model}:generateContent`
|
|
7
8
|
const body = { contents: adaptMessagesForGemini(messages), ...(tools.length ? { tools: [{ function_declarations: tools.map(adaptToolForGemini) }] } : {}) }
|
|
8
|
-
const r = await fetch(url, {
|
|
9
|
+
const r = await fetch(url, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: { 'content-type': 'application/json', authorization: 'Bearer none' },
|
|
12
|
+
body: JSON.stringify(body),
|
|
13
|
+
})
|
|
9
14
|
return await r.json()
|
|
10
15
|
}
|
|
11
16
|
export const provider = 'google'
|
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
// All upstream image-generation traffic routes through acptoapi.
|
|
2
|
+
import { getAcptoapiUrl } from './acptoapi-bridge.js'
|
|
3
|
+
|
|
4
|
+
const PROVIDERS = ['openai', 'replicate', 'stability']
|
|
5
|
+
|
|
6
|
+
export async function generate({ provider = 'openai', prompt, size, model } = {}) {
|
|
7
|
+
if (!PROVIDERS.includes(provider)) throw new Error('unknown image provider: ' + provider)
|
|
8
|
+
const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
|
|
9
|
+
const body = provider === 'replicate'
|
|
10
|
+
? { version: model || 'black-forest-labs/flux-schnell', input: { prompt } }
|
|
11
|
+
: { model: model || 'gpt-image-1', prompt, size }
|
|
12
|
+
const r = await fetch(base + '/v1/images/generations', {
|
|
13
|
+
method: 'POST',
|
|
14
|
+
headers: { authorization: 'Bearer none', 'content-type': 'application/json', 'x-provider': provider },
|
|
15
|
+
body: JSON.stringify(body),
|
|
16
|
+
})
|
|
17
|
+
return await r.json()
|
|
6
18
|
}
|
|
7
|
-
|
|
8
|
-
export function listProviders() { return
|
|
19
|
+
|
|
20
|
+
export function listProviders() { return PROVIDERS.slice() }
|
|
@@ -6,6 +6,7 @@ export { matrixUsable } from './model-matrix.js'
|
|
|
6
6
|
|
|
7
7
|
const _require = createRequire(import.meta.url)
|
|
8
8
|
const sdk = _require('acptoapi')
|
|
9
|
+
|
|
9
10
|
export const PROVIDER_KEYS = sdk.PROVIDER_KEYS
|
|
10
11
|
export const DEFAULTS = sdk.PROVIDER_DEFAULTS
|
|
11
12
|
|
|
@@ -59,10 +60,15 @@ export function resolveCallLLM({ provider, model } = {}) {
|
|
|
59
60
|
}
|
|
60
61
|
try {
|
|
61
62
|
const isSimple = typeof m === 'string' && !m.includes(',') && !/^queue\//.test(m)
|
|
62
|
-
|
|
63
|
+
|
|
64
|
+
if (isSimple && await bridgeReachable()) {
|
|
65
|
+
return await bridgeCall({ ...input, model: m })
|
|
66
|
+
}
|
|
67
|
+
|
|
63
68
|
const opts = { model: m, messages: toMsgs(input.messages), tools: toTools(input.tools), onFallback: input.onFallback, output: 'openai' }
|
|
64
69
|
if (/^queue\//.test(m)) opts.queuesMap = getConfigValue('agent.model_queues', {}) || {}
|
|
65
70
|
if (m.includes(',') || /^queue\//.test(m)) opts.matrixSource = process.env.FREDDIE_MATRIX_URL || MATRIX_FILE
|
|
71
|
+
|
|
66
72
|
const r = await sdk.chat(opts)
|
|
67
73
|
return adapt(r)
|
|
68
74
|
} catch (e) {
|
package/src/agent/machine.js
CHANGED
|
@@ -74,15 +74,23 @@ export function createAgentMachine({ provider, model, maxIterations = 90, callLL
|
|
|
74
74
|
const last = input.messages[input.messages.length - 1]
|
|
75
75
|
const calls = last.tool_calls || []
|
|
76
76
|
const results = []
|
|
77
|
+
const extras = []
|
|
77
78
|
for (const call of calls) {
|
|
78
|
-
const
|
|
79
|
-
|
|
79
|
+
const tname = call.name || call.function?.name
|
|
80
|
+
const targs = call.arguments || call.function?.arguments || {}
|
|
81
|
+
const tcid = call.id || call.tool_call_id
|
|
82
|
+
const pushExtras = r => { if (r?.systemMessage) extras.push({ role: 'system', content: '[hook] ' + r.systemMessage }); if (r?.additionalContext) extras.push({ role: 'system', content: r.additionalContext }) }
|
|
83
|
+
const pre = await h.hooks.invoke('preToolCall', { name: tname, args: targs }); pushExtras(pre)
|
|
84
|
+
if (pre?.behavior === 'block') { results.push({ tool_call_id: tcid, content: JSON.stringify({ error: 'tool call denied by plugsdk hook', tool: tname, reason: pre.reason || 'denied' }) }); continue }
|
|
85
|
+
const res = await h.pi.dispatchTool(tname, (pre && pre.args) || targs)
|
|
86
|
+
pushExtras(await h.hooks.invoke('postToolCall', { name: tname, args: targs, result: res }))
|
|
87
|
+
results.push({ tool_call_id: tcid, content: res })
|
|
80
88
|
}
|
|
81
|
-
return results
|
|
89
|
+
return { results, extras }
|
|
82
90
|
}),
|
|
83
91
|
input: ({ context }) => ({ messages: context.messages }),
|
|
84
92
|
onDone: { target: 'prompting', actions: assign({
|
|
85
|
-
messages: ({ context, event }) => [...context.messages, ...event.output.map(r => ({ role: 'tool', tool_call_id: r.tool_call_id, content: r.content }))],
|
|
93
|
+
messages: ({ context, event }) => [...context.messages, ...event.output.results.map(r => ({ role: 'tool', tool_call_id: r.tool_call_id, content: r.content })), ...event.output.extras],
|
|
86
94
|
iterations: ({ context }) => context.iterations + 1,
|
|
87
95
|
}) },
|
|
88
96
|
onError: { target: 'done', actions: assign({ error: ({ event }) => String(event.error?.message || event.error) }) },
|
|
@@ -143,30 +151,45 @@ async function writeTrajectory(out, { prompt, provider, model, skill, cwd, event
|
|
|
143
151
|
} catch (_) {}
|
|
144
152
|
}
|
|
145
153
|
|
|
154
|
+
function mergeHookExtras(messages, r, tag) {
|
|
155
|
+
if (!r) return messages
|
|
156
|
+
const e = []
|
|
157
|
+
if (r.systemMessage) e.push({ role: 'system', content: '[hook:' + tag + '] ' + r.systemMessage })
|
|
158
|
+
if (r.additionalContext) e.push({ role: 'system', content: r.additionalContext })
|
|
159
|
+
return e.length ? [...messages, ...e] : messages
|
|
160
|
+
}
|
|
161
|
+
|
|
146
162
|
export async function runTurn({ prompt, messages = [], model, provider, callLLM, enabledToolsets, disabledToolsets, maxIterations = 90, timeoutMs = 30000, cwd, skill, witnessPath } = {}) {
|
|
147
|
-
const events = []
|
|
148
|
-
|
|
149
|
-
const
|
|
150
|
-
if (cwd)
|
|
151
|
-
if (skill) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (systemParts.length > 0) initMessages.unshift({ role: 'user', content: systemParts.join('\n\n') })
|
|
163
|
+
const events = []; const h = await bootHost()
|
|
164
|
+
await h.hooks.invoke('onSessionStart', { prompt, model, provider, skill, cwd })
|
|
165
|
+
let initMessages = [...messages]; const sysParts = []
|
|
166
|
+
if (cwd) sysParts.push(`Working directory: ${cwd}. Always pass cwd="${cwd}" to bash tool calls. When reading or writing files use paths relative to this directory or absolute paths under it.`)
|
|
167
|
+
if (skill) { const sd = h.pi.skills.get(skill); if (sd?.content) sysParts.push('Skill context:\n' + sd.content) }
|
|
168
|
+
if (sysParts.length) initMessages.unshift({ role: 'user', content: sysParts.join('\n\n') })
|
|
169
|
+
const inbound = await h.hooks.invoke('onMessageInbound', { content: prompt })
|
|
170
|
+
if (inbound?.behavior === 'block') { await h.hooks.invoke('onSessionEnd', { reason: 'prompt_blocked' }); return { messages: initMessages, result: null, error: 'prompt blocked by plugsdk hook: ' + (inbound.reason || 'denied'), iterations: 0 } }
|
|
171
|
+
initMessages = mergeHookExtras(initMessages, inbound, 'onMessageInbound')
|
|
157
172
|
const machine = createAgentMachine({ model, provider, callLLM, enabledToolsets, disabledToolsets, maxIterations, events })
|
|
158
|
-
const actor = createActor(machine, { input: { messages: initMessages } })
|
|
159
|
-
actor.start()
|
|
160
|
-
actor.send({ type: 'SUBMIT', prompt })
|
|
173
|
+
const actor = createActor(machine, { input: { messages: initMessages } }); actor.start(); actor.send({ type: 'SUBMIT', prompt })
|
|
161
174
|
return await new Promise((resolve, reject) => {
|
|
162
175
|
const t = setTimeout(() => { try { actor.stop() } catch {} reject(new Error('agent turn timeout')) }, timeoutMs)
|
|
163
|
-
actor.subscribe(snap => {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
176
|
+
actor.subscribe(snap => { if (snap.status !== 'done') return; clearTimeout(t)
|
|
177
|
+
;(async () => {
|
|
178
|
+
const out = snap.output
|
|
179
|
+
const outbound = await h.hooks.invoke('onMessageOutbound', { content: out?.result || '' })
|
|
180
|
+
if (outbound?.systemMessage || outbound?.additionalContext) out.messages = mergeHookExtras(out.messages || [], outbound, 'onMessageOutbound')
|
|
181
|
+
await h.hooks.invoke('onSessionEnd', { reason: out?.error ? 'error' : 'ok', iterations: out?.iterations })
|
|
182
|
+
const errorStack = out?.error ? (events.find(e => e.type === 'llm_call' && !e.ok)?.stack || null) : null
|
|
183
|
+
await writeTrajectory(out, { prompt, provider, model, skill, cwd, events, errorStack, witnessPath }); resolve(out)
|
|
184
|
+
})().catch(reject)
|
|
169
185
|
})
|
|
170
186
|
})
|
|
171
187
|
}
|
|
172
188
|
|
|
189
|
+
export async function invokeCompactHooks({ trigger = 'auto', messages = [] } = {}) {
|
|
190
|
+
const h = await bootHost()
|
|
191
|
+
const pre = await h.hooks.invoke('onPreCompact', { trigger, messages })
|
|
192
|
+
if (pre?.behavior === 'block') return { skipped: true, reason: pre.reason || 'blocked' }
|
|
193
|
+
return { pre, post: async (summary) => h.hooks.invoke('onPostCompact', { trigger, messages, summary }) }
|
|
194
|
+
}
|
|
195
|
+
|
|
@@ -1,98 +1,52 @@
|
|
|
1
|
+
// Upstream model enumeration lives in acptoapi. This module is a thin shim
|
|
2
|
+
// over GET /v1/models so freddie has zero direct vendor connectivity.
|
|
1
3
|
import { createRequire } from 'module'
|
|
2
|
-
import {
|
|
4
|
+
import { getAcptoapiUrl } from './acptoapi-bridge.js'
|
|
3
5
|
import { saveConfigValue, getConfigValue } from '../config.js'
|
|
4
6
|
import { logger } from '../observability/log.js'
|
|
5
7
|
|
|
6
8
|
const _require = createRequire(import.meta.url)
|
|
7
|
-
const
|
|
8
|
-
const { BRANDS } = _require('acptoapi/lib/openai-brands')
|
|
9
|
+
const _sdk = _require('acptoapi')
|
|
9
10
|
const log = logger('model-discovery')
|
|
10
11
|
|
|
11
|
-
const
|
|
12
|
-
anthropic: { url: 'https://api.anthropic.com/v1/models', envName: 'ANTHROPIC_API_KEY', auth: k => ({ 'x-api-key': k, 'anthropic-version': '2023-06-01' }), pick: j => (j.data || []).map(m => m.id) },
|
|
13
|
-
gemini: { url: 'https://generativelanguage.googleapis.com/v1beta/models', envName: 'GOOGLE_API_KEY', keyParam: 'key', auth: () => ({}), pick: j => (j.models || []).map(m => (m.name || '').replace(/^models\//, '')).filter(Boolean) },
|
|
14
|
-
ollama: { url: 'http://localhost:11434/api/tags', envName: null, auth: () => ({}), pick: j => (j.models || []).map(m => m.name || m.model).filter(Boolean) },
|
|
15
|
-
}
|
|
16
|
-
const ACP_BACKENDS = {
|
|
17
|
-
kilo: { url: 'http://localhost:4780/session', staticModels: ['x-ai/grok-code-fast-1:optimized:free'] },
|
|
18
|
-
opencode: { url: 'http://localhost:4790/session', staticModels: ['minimax-m2.5-free'] },
|
|
19
|
-
}
|
|
20
|
-
const CLI_BACKENDS = {
|
|
21
|
-
'claude-cli': { models: ['claude-haiku-4-5', 'claude-sonnet-4-6', 'claude-opus-4-7', 'haiku', 'sonnet', 'opus'] },
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
const prober = createModelProber()
|
|
25
|
-
|
|
26
|
-
async function probeBrand(provider) {
|
|
27
|
-
const resolved = await resolveKey(provider).catch(() => ({ value: null }))
|
|
28
|
-
const brand = BRANDS[provider]
|
|
29
|
-
const envKey = brand?.envKey
|
|
30
|
-
const key = resolved.value || (envKey ? process.env[envKey] : undefined)
|
|
31
|
-
if (!key) return { provider, error: 'no_key' }
|
|
32
|
-
try {
|
|
33
|
-
const r = await prober.probe(provider, key)
|
|
34
|
-
if (r.error) return { provider, error: r.error }
|
|
35
|
-
return { provider, models: r.models || [], last_ok_at: r.ts }
|
|
36
|
-
} catch (e) { return { provider, error: String(e.message || e) } }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async function probeExtra(provider) {
|
|
40
|
-
const ep = EXTRA[provider]
|
|
41
|
-
if (!ep) return { provider, error: 'no_extra_endpoint' }
|
|
42
|
-
const resolved = await resolveKey(provider).catch(() => ({ value: null }))
|
|
43
|
-
const key = resolved.value || (ep.envName ? process.env[ep.envName] : undefined)
|
|
44
|
-
if (!key && provider !== 'ollama') return { provider, error: 'no_key' }
|
|
45
|
-
try {
|
|
46
|
-
const url = ep.keyParam ? `${ep.url}?${ep.keyParam}=${encodeURIComponent(key)}` : ep.url
|
|
47
|
-
const headers = ep.auth(key)
|
|
48
|
-
const res = await fetch(url, { method: 'GET', headers, signal: AbortSignal.timeout(8000) })
|
|
49
|
-
if (!res.ok) { const t = await res.text(); return { provider, error: `${res.status}: ${t.slice(0, 200)}` } }
|
|
50
|
-
const json = await res.json()
|
|
51
|
-
return { provider, models: ep.pick(json) || [], last_ok_at: Date.now() }
|
|
52
|
-
} catch (e) { return { provider, error: String(e.message || e) } }
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
async function probeAcp(provider) {
|
|
56
|
-
const b = ACP_BACKENDS[provider]
|
|
57
|
-
try {
|
|
58
|
-
const r = await fetch(b.url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}', signal: AbortSignal.timeout(2000) })
|
|
59
|
-
if (!r.ok) return { provider, error: `${r.status}`, models: b.staticModels }
|
|
60
|
-
return { provider, models: b.staticModels, last_ok_at: Date.now() }
|
|
61
|
-
} catch (e) { return { provider, error: String(e.message || e), models: b.staticModels } }
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
async function probeCli(provider) {
|
|
65
|
-
if (provider !== 'claude-cli') return { provider, error: 'unknown_cli', models: [] }
|
|
66
|
-
try {
|
|
67
|
-
const { spawn } = await import('node:child_process')
|
|
68
|
-
const ok = await new Promise(res => {
|
|
69
|
-
const p = spawn('claude', ['--version'], { stdio: 'ignore', shell: false })
|
|
70
|
-
const t = setTimeout(() => { p.kill(); res(false) }, 3000)
|
|
71
|
-
p.on('exit', c => { clearTimeout(t); res(c === 0) })
|
|
72
|
-
p.on('error', () => { clearTimeout(t); res(false) })
|
|
73
|
-
})
|
|
74
|
-
if (!ok) return { provider, error: 'claude_cli_not_available', models: [] }
|
|
75
|
-
return { provider, models: CLI_BACKENDS['claude-cli'].models, last_ok_at: Date.now() }
|
|
76
|
-
} catch (e) { return { provider, error: String(e.message || e), models: [] } }
|
|
77
|
-
}
|
|
12
|
+
const NON_KEY_PROVIDERS = ['claude-cli', 'kilo', 'opencode', 'ollama']
|
|
78
13
|
|
|
79
14
|
export function listKnownProviders() {
|
|
80
|
-
|
|
15
|
+
const cached = getConfigValue('agent.discovered_models', {}) || {}
|
|
16
|
+
const set = new Set([...Object.keys(cached), ...Object.keys(_sdk.PROVIDER_KEYS || {}), ...NON_KEY_PROVIDERS])
|
|
17
|
+
return [...set]
|
|
81
18
|
}
|
|
82
19
|
|
|
83
20
|
export async function discoverModels({ provider } = {}) {
|
|
84
|
-
const
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
21
|
+
const base = getAcptoapiUrl().replace(/\/v1\/?$/, '')
|
|
22
|
+
try {
|
|
23
|
+
const r = await fetch(base + '/v1/models', {
|
|
24
|
+
headers: { authorization: 'Bearer none' },
|
|
25
|
+
signal: AbortSignal.timeout(10000),
|
|
26
|
+
})
|
|
27
|
+
if (!r.ok) {
|
|
28
|
+
const text = await r.text()
|
|
29
|
+
log.warn('discover failed', { status: r.status, body: text.slice(0, 200) })
|
|
30
|
+
return {}
|
|
31
|
+
}
|
|
32
|
+
const json = await r.json()
|
|
33
|
+
const byProvider = {}
|
|
34
|
+
for (const m of (json.data || [])) {
|
|
35
|
+
const id = m.id || ''
|
|
36
|
+
const slash = id.indexOf('/')
|
|
37
|
+
if (slash <= 0) continue
|
|
38
|
+
const p = id.slice(0, slash)
|
|
39
|
+
const modelName = id.slice(slash + 1)
|
|
40
|
+
if (provider && p !== provider) continue
|
|
41
|
+
byProvider[p] = byProvider[p] || { provider: p, models: [], last_ok_at: Date.now() }
|
|
42
|
+
byProvider[p].models.push(modelName)
|
|
43
|
+
}
|
|
44
|
+
log.info('discovered', { count: Object.keys(byProvider).length })
|
|
45
|
+
return byProvider
|
|
46
|
+
} catch (e) {
|
|
47
|
+
log.warn('discover error', { error: e.message })
|
|
48
|
+
return {}
|
|
49
|
+
}
|
|
96
50
|
}
|
|
97
51
|
|
|
98
52
|
export async function discoverAndPersist({ provider } = {}) {
|
|
@@ -101,7 +55,6 @@ export async function discoverAndPersist({ provider } = {}) {
|
|
|
101
55
|
const merged = { ...existing }
|
|
102
56
|
for (const [p, r] of Object.entries(result)) {
|
|
103
57
|
if (!r.error) merged[p] = { models: r.models, last_ok_at: r.last_ok_at }
|
|
104
|
-
else merged[p] = { ...(existing[p] || {}), models: r.models || (existing[p]?.models) || [], error: r.error, last_error_at: Date.now() }
|
|
105
58
|
}
|
|
106
59
|
saveConfigValue('agent.discovered_models', merged)
|
|
107
60
|
return result
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Browser entry for freddie. Polymorphic with the Node CLI.
|
|
2
|
+
// node:* imports are expected to be satisfied by the host environment shims
|
|
3
|
+
// (thebird provides them via docs/vendor/esm/node/ + docs/shell-node-*.js).
|
|
4
|
+
//
|
|
5
|
+
// This entry deliberately re-exports only the browser-safe surface needed
|
|
6
|
+
// to embed freddie as an agent in a web OS: the xstate-driven agent machine,
|
|
7
|
+
// the host bootstrapper, and configuration defaults. It avoids re-exporting
|
|
8
|
+
// CLI/TUI/dashboard/MCP/ACP server code which pulls in commander, express,
|
|
9
|
+
// child_process, and other Node-only subsystems.
|
|
10
|
+
|
|
11
|
+
export { bootHost, host, resetHostForTests } from '../host/index.js'
|
|
12
|
+
export { createAgentMachine, runTurn } from '../agent/machine.js'
|
|
13
|
+
export { createActor, createMachine, assign, fromPromise, waitFor } from 'xstate'
|
|
14
|
+
|
|
15
|
+
// Re-export config defaults under the documented browser name.
|
|
16
|
+
import { DEFAULT_CONFIG } from '../config.js'
|
|
17
|
+
export const FREDDIE_DEFAULT_CONFIG = DEFAULT_CONFIG
|
|
18
|
+
export { DEFAULT_CONFIG }
|
|
19
|
+
|
|
20
|
+
// Optional extras that are browser-friendly when their node:* imports are shimmed.
|
|
21
|
+
export { buildContext, blocksToSystemMessage, ContextPlugins } from '../context/engine.js'
|
|
22
|
+
export { listSkills, findSkill, skillAsUserMessage } from '../skills/index.js'
|
|
23
|
+
export { log, logger } from '../observability/log.js'
|
package/src/host/contract.js
CHANGED
|
@@ -16,6 +16,7 @@ export const HOOK_NAMES = [
|
|
|
16
16
|
'onSessionStart', 'onSessionEnd',
|
|
17
17
|
'onTurnStart', 'onTurnEnd',
|
|
18
18
|
'onMessageInbound', 'onMessageOutbound',
|
|
19
|
+
'onPreCompact', 'onPostCompact',
|
|
19
20
|
]
|
|
20
21
|
|
|
21
22
|
export const FREDDIE_TO_SDK_HOOK = {
|
|
@@ -25,6 +26,8 @@ export const FREDDIE_TO_SDK_HOOK = {
|
|
|
25
26
|
onSessionEnd: HookType.SESSION_END,
|
|
26
27
|
onMessageInbound: HookType.PROMPT_SUBMIT,
|
|
27
28
|
onMessageOutbound: HookType.STOP,
|
|
29
|
+
onPreCompact: HookType.PRE_COMPACT,
|
|
30
|
+
onPostCompact: HookType.POST_COMPACT,
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
export const FREDDIE_TO_NATIVE_HOOK = {
|
|
@@ -34,6 +37,8 @@ export const FREDDIE_TO_NATIVE_HOOK = {
|
|
|
34
37
|
onSessionEnd: 'SessionEnd',
|
|
35
38
|
onMessageInbound: 'UserPromptSubmit',
|
|
36
39
|
onMessageOutbound: 'Stop',
|
|
40
|
+
onPreCompact: 'PreCompact',
|
|
41
|
+
onPostCompact: 'PostCompact',
|
|
37
42
|
}
|
|
38
43
|
|
|
39
44
|
export function validatePlugin(p) {
|
package/src/host/host_helpers.js
CHANGED
|
@@ -48,6 +48,8 @@ function ccPayloadFor(name, payload) {
|
|
|
48
48
|
return { tool_name: payload?.name, tool_input: payload?.args || payload?.input, tool_response: payload?.result }
|
|
49
49
|
if (name === 'onMessageInbound' || name === 'onMessageOutbound')
|
|
50
50
|
return { prompt: payload?.content || payload?.text || '' }
|
|
51
|
+
if (name === 'onPreCompact' || name === 'onPostCompact')
|
|
52
|
+
return { trigger: payload?.trigger || 'auto', messages_count: payload?.messages?.length ?? 0, summary: payload?.summary ?? null }
|
|
51
53
|
return payload || {}
|
|
52
54
|
}
|
|
53
55
|
|
|
@@ -90,12 +92,17 @@ export function makeHooksRegistry(ccHost) {
|
|
|
90
92
|
let cur = payload
|
|
91
93
|
for (const fn of reg2[name] || []) cur = (await fn(cur)) ?? cur
|
|
92
94
|
const native = FREDDIE_TO_NATIVE_HOOK[name]
|
|
93
|
-
if (native && ccHost.plugins().length) {
|
|
95
|
+
if (native && ccHost.plugins().length && !process.env.FREDDIE_DISABLE_CC_HOOKS) {
|
|
94
96
|
const r = await ccHost.dispatch(native, ccPayloadFor(name, cur))
|
|
95
|
-
|
|
97
|
+
const extras = {}
|
|
98
|
+
if (typeof r.systemMessage === 'string' && r.systemMessage.length) extras.systemMessage = r.systemMessage
|
|
99
|
+
const addCtx = r.hookSpecificOutput?.additionalContext
|
|
100
|
+
if (typeof addCtx === 'string' && addCtx.length) extras.additionalContext = addCtx
|
|
101
|
+
if (r.decision === 'block') return { ...cur, ...extras, behavior: 'block', reason: r.reason }
|
|
96
102
|
const pd = r.hookSpecificOutput?.permissionDecision
|
|
97
|
-
if (pd === 'deny') return { ...cur, behavior: 'block', reason: r.hookSpecificOutput?.permissionDecisionReason || 'denied' }
|
|
98
|
-
if (r.hookSpecificOutput?.updatedInput) return { ...cur, ...r.hookSpecificOutput.updatedInput }
|
|
103
|
+
if (pd === 'deny') return { ...cur, ...extras, behavior: 'block', reason: r.hookSpecificOutput?.permissionDecisionReason || 'denied' }
|
|
104
|
+
if (r.hookSpecificOutput?.updatedInput) return { ...cur, ...extras, ...r.hookSpecificOutput.updatedInput }
|
|
105
|
+
if (Object.keys(extras).length) return { ...cur, ...extras }
|
|
99
106
|
}
|
|
100
107
|
return cur
|
|
101
108
|
},
|
|
@@ -127,6 +134,7 @@ export function makeCcLoaders(ccHost, env) {
|
|
|
127
134
|
}
|
|
128
135
|
return ccHost.plugins().length
|
|
129
136
|
}
|
|
137
|
+
const CC_EXCLUDE = new Set(['gm-cc'])
|
|
130
138
|
async function loadCcFromNodeModules(startDir) {
|
|
131
139
|
const seen = new Set(ccHost.plugins().map(p => p.root))
|
|
132
140
|
let cur = path.resolve(startDir)
|
|
@@ -138,7 +146,7 @@ export function makeCcLoaders(ccHost, env) {
|
|
|
138
146
|
? fs.readdirSync(path.join(nm, entry.name), { withFileTypes: true }).filter(e => e.isDirectory()).map(e => path.join(nm, entry.name, e.name))
|
|
139
147
|
: [path.join(nm, entry.name)]
|
|
140
148
|
for (const d of dirs) {
|
|
141
|
-
if (seen.has(d) || !isCcPluginDir(d)) continue
|
|
149
|
+
if (seen.has(d) || !isCcPluginDir(d) || CC_EXCLUDE.has(path.basename(d))) continue
|
|
142
150
|
seen.add(d); await useCcDir(d)
|
|
143
151
|
}
|
|
144
152
|
}
|
package/src/web/server.js
CHANGED
|
@@ -12,8 +12,8 @@ export async function createDashboard({ port = 0 } = {}) {
|
|
|
12
12
|
app.use(express.static(__dirname))
|
|
13
13
|
const fromNodeModules = path.join(__dirname, '..', '..', 'node_modules', 'anentrypoint-design', 'dist')
|
|
14
14
|
app.use('/vendor/anentrypoint-design', express.static(fromNodeModules))
|
|
15
|
-
const
|
|
16
|
-
app.use('/vendor/anentrypoint-design/
|
|
15
|
+
const nmKitsOs = path.join(__dirname, '..', '..', 'node_modules', 'anentrypoint-design', 'src', 'kits', 'os')
|
|
16
|
+
app.use('/vendor/anentrypoint-design/kits/os', express.static(nmKitsOs))
|
|
17
17
|
for (const r of host.gui.routes.list()) {
|
|
18
18
|
const verb = r.method.toLowerCase()
|
|
19
19
|
if (typeof app[verb] === 'function') app[verb](r.path, r.handler)
|
package/plugins/gm-cc/plugin.js
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'module'
|
|
2
|
-
import path from 'path'
|
|
3
|
-
import fs from 'fs'
|
|
4
|
-
import { loadClaudePlugin } from 'plugsdk'
|
|
5
|
-
|
|
6
|
-
const _require = createRequire(import.meta.url)
|
|
7
|
-
const gmCcBase = path.dirname(_require.resolve('gm-cc/package.json'))
|
|
8
|
-
|
|
9
|
-
export default {
|
|
10
|
-
name: 'gm-cc',
|
|
11
|
-
surfaces: 'pi',
|
|
12
|
-
register({ pi }) {
|
|
13
|
-
if (!fs.existsSync(path.join(gmCcBase, 'skills'))) return
|
|
14
|
-
const cc = loadClaudePlugin(gmCcBase)
|
|
15
|
-
for (const s of cc.skills) {
|
|
16
|
-
pi.skills.register({
|
|
17
|
-
name: 'gm:' + s.name,
|
|
18
|
-
description: s.fields.description || '',
|
|
19
|
-
content: s.body,
|
|
20
|
-
source: 'gm-cc',
|
|
21
|
-
frontmatter: s.fields,
|
|
22
|
-
file: s.file,
|
|
23
|
-
})
|
|
24
|
-
}
|
|
25
|
-
},
|
|
26
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { createRequire } from 'module'
|
|
2
|
-
const _require = createRequire(import.meta.url)
|
|
3
|
-
const _sdk = _require('acptoapi')
|
|
4
|
-
|
|
5
|
-
export const isAvailable = _sdk.isAvailable
|
|
6
|
-
export const markFailed = _sdk.markFailed
|
|
7
|
-
export const markOk = _sdk.markOk
|
|
8
|
-
export const resetAvailability = _sdk.resetAvailability
|
|
9
|
-
export const getStatus = _sdk.getStatus
|
|
10
|
-
export const probe = _sdk.probe
|
|
11
|
-
export const startSampler = _sdk.startSampler
|
|
12
|
-
export const stopSampler = _sdk.stopSampler
|
|
13
|
-
export const createSampler = _sdk.createSampler
|