pikiloom 0.4.26 → 0.4.27
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/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
- package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
- package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
- package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
- package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
- package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
- package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
- package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/acp-client.js +0 -59
- package/dist/agent/artifacts.js +1 -38
- package/dist/agent/auto-update.js +2 -78
- package/dist/agent/await-resume.js +0 -20
- package/dist/agent/cli/auth.js +3 -51
- package/dist/agent/cli/catalog.js +0 -3
- package/dist/agent/cli/detector.js +1 -19
- package/dist/agent/cli/index.js +0 -3
- package/dist/agent/cli/registry.js +0 -25
- package/dist/agent/driver.js +0 -14
- package/dist/agent/drivers/claude-tui.js +2 -769
- package/dist/agent/drivers/claude.js +12 -631
- package/dist/agent/drivers/codex.js +11 -162
- package/dist/agent/drivers/gemini.js +6 -114
- package/dist/agent/drivers/hermes.js +0 -128
- package/dist/agent/goal.js +0 -45
- package/dist/agent/handover.js +1 -46
- package/dist/agent/images.js +7 -133
- package/dist/agent/index.js +1 -29
- package/dist/agent/mcp/bridge.js +1 -197
- package/dist/agent/mcp/extensions.js +5 -84
- package/dist/agent/mcp/oauth.js +2 -69
- package/dist/agent/mcp/registry.js +2 -33
- package/dist/agent/mcp/session-server.js +2 -45
- package/dist/agent/mcp/tools/ask-user.js +0 -6
- package/dist/agent/mcp/tools/await-resume.js +0 -15
- package/dist/agent/mcp/tools/goal.js +0 -25
- package/dist/agent/mcp/tools/types.js +0 -5
- package/dist/agent/mcp/tools/workspace.js +0 -23
- package/dist/agent/npm.js +0 -14
- package/dist/agent/session.js +86 -196
- package/dist/agent/skill-installer.js +3 -70
- package/dist/agent/skills.js +0 -43
- package/dist/agent/stream.js +5 -148
- package/dist/agent/types.js +0 -12
- package/dist/agent/utils.js +2 -120
- package/dist/bot/bot.js +3 -468
- package/dist/bot/command-ui.js +0 -45
- package/dist/bot/commands.js +0 -51
- package/dist/bot/headless-bot.js +0 -21
- package/dist/bot/host.js +1 -5
- package/dist/bot/human-loop.js +0 -8
- package/dist/bot/menu.js +0 -3
- package/dist/bot/orchestration.js +0 -3
- package/dist/bot/render-shared.js +0 -63
- package/dist/bot/session-hub.js +14 -92
- package/dist/bot/session-status.js +1 -5
- package/dist/bot/streaming.js +0 -53
- package/dist/browser-profile.js +0 -51
- package/dist/browser-supervisor.js +0 -75
- package/dist/catalog/cli-tools.js +0 -53
- package/dist/catalog/index.js +0 -18
- package/dist/catalog/local-models.js +0 -18
- package/dist/catalog/mcp-servers.js +0 -35
- package/dist/catalog/skill-repos.js +0 -32
- package/dist/channels/base.js +0 -19
- package/dist/channels/dingtalk/bot.js +0 -3
- package/dist/channels/dingtalk/channel.js +0 -33
- package/dist/channels/discord/bot.js +0 -7
- package/dist/channels/discord/channel.js +0 -22
- package/dist/channels/feishu/bot.js +0 -71
- package/dist/channels/feishu/channel.js +0 -78
- package/dist/channels/feishu/markdown.js +0 -5
- package/dist/channels/feishu/render.js +0 -45
- package/dist/channels/health.js +0 -46
- package/dist/channels/slack/bot.js +0 -7
- package/dist/channels/slack/channel.js +1 -27
- package/dist/channels/states.js +0 -3
- package/dist/channels/telegram/bot.js +0 -60
- package/dist/channels/telegram/channel.js +2 -99
- package/dist/channels/telegram/directory.js +0 -3
- package/dist/channels/telegram/live-preview.js +0 -8
- package/dist/channels/telegram/render.js +0 -30
- package/dist/channels/wecom/bot.js +0 -3
- package/dist/channels/wecom/channel.js +0 -58
- package/dist/channels/weixin/api.js +0 -9
- package/dist/channels/weixin/bot.js +0 -88
- package/dist/channels/weixin/channel.js +0 -6
- package/dist/cli/autostart.js +1 -44
- package/dist/cli/channel-supervisor.js +1 -59
- package/dist/cli/channels.js +0 -6
- package/dist/cli/main.js +1 -145
- package/dist/cli/onboarding.js +0 -6
- package/dist/cli/run.js +0 -11
- package/dist/cli/setup-wizard.js +0 -3
- package/dist/core/config/runtime-config.js +0 -38
- package/dist/core/config/user-config.js +0 -63
- package/dist/core/config/validation.js +0 -9
- package/dist/core/constants.js +0 -268
- package/dist/core/git.js +0 -31
- package/dist/core/legacy-compat.js +1 -28
- package/dist/core/logging.js +0 -3
- package/dist/core/platform.js +0 -28
- package/dist/core/process-control.js +0 -8
- package/dist/core/secrets/index.js +0 -17
- package/dist/core/secrets/inline-seal.js +0 -7
- package/dist/core/secrets/ref.js +0 -19
- package/dist/core/secrets/resolver.js +0 -8
- package/dist/core/secrets/store.js +1 -12
- package/dist/core/utils.js +1 -37
- package/dist/core/version.js +0 -3
- package/dist/dashboard/platform.js +1 -35
- package/dist/dashboard/routes/agents.js +2 -71
- package/dist/dashboard/routes/cli.js +0 -27
- package/dist/dashboard/routes/config.js +0 -54
- package/dist/dashboard/routes/extensions.js +0 -132
- package/dist/dashboard/routes/local-models.js +0 -68
- package/dist/dashboard/routes/models.js +0 -46
- package/dist/dashboard/routes/sessions.js +0 -100
- package/dist/dashboard/runtime.js +1 -58
- package/dist/dashboard/server.js +0 -49
- package/dist/dashboard/session-control.js +2 -69
- package/dist/model/catalog.js +1 -17
- package/dist/model/index.js +0 -15
- package/dist/model/injector.js +1 -181
- package/dist/model/provider-models.js +1 -48
- package/dist/model/responses-bridge.js +3 -45
- package/dist/model/store.js +0 -17
- package/dist/model/types.js +0 -12
- package/dist/model/validation.js +1 -18
- package/dist/pikichannel/adapter-pikiloom.js +0 -31
- package/dist/pikichannel/code.js +0 -12
- package/dist/pikichannel/codec.js +0 -14
- package/dist/pikichannel/host.js +4 -33
- package/dist/pikichannel/protocol.js +0 -52
- package/dist/pikichannel/rendezvous-broker.js +5 -26
- package/dist/pikichannel/rendezvous-host.js +3 -16
- package/dist/pikichannel/server.js +6 -55
- package/dist/pikichannel/transport.js +0 -25
- package/dist/pikichannel/transports/webrtc-host.js +0 -18
- package/dist/pikichannel/transports/webrtc-shared.js +4 -34
- package/dist/pikichannel/transports/websocket-host.js +1 -14
- package/dist/pikichannel/turn.js +2 -69
- package/dist/pikichannel/web/sdk.js +14 -71
- package/package.json +1 -1
- package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
- package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
- package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
- package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
- package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
|
@@ -1,38 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Skill installer — wrapper around `npx skills` CLI.
|
|
3
|
-
*
|
|
4
|
-
* Skills are installed via the community-standard `npx skills add` command.
|
|
5
|
-
* Global skills go to ~/.pikiloom/skills/, project skills to <workdir>/.pikiloom/skills/.
|
|
6
|
-
*
|
|
7
|
-
* The upstream CLI doesn't recognize `pikiloom` as an agent, so we install with
|
|
8
|
-
* `--agent claude-code` (the driver pikiloom runs by default) and rely on
|
|
9
|
-
* ~/.claude/skills → ~/.pikiloom/skills being symlinked to the same directory.
|
|
10
|
-
*/
|
|
11
1
|
import { execFile } from 'node:child_process';
|
|
12
2
|
import fs from 'node:fs';
|
|
13
3
|
import os from 'node:os';
|
|
14
4
|
import path from 'node:path';
|
|
15
5
|
import { STATE_DIR_NAME } from '../core/constants.js';
|
|
16
|
-
// ---------------------------------------------------------------------------
|
|
17
|
-
// Constants
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
6
|
const GLOBAL_SKILLS_DIR = path.join(os.homedir(), STATE_DIR_NAME, 'skills');
|
|
20
7
|
const INSTALL_TIMEOUT_MS = 60_000;
|
|
21
8
|
const REMOVE_TIMEOUT_MS = 10_000;
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Install ledger — provenance for update detection
|
|
24
|
-
//
|
|
25
|
-
// `npx skills add` drops skill directories but records no source or version, so
|
|
26
|
-
// there's nothing to diff a local install against a moving remote. We keep a
|
|
27
|
-
// sidecar ledger next to the skills (one per scope: global beside
|
|
28
|
-
// ~/.pikiloom/skills, project beside <workdir>/.pikiloom/skills) mapping each
|
|
29
|
-
// installed collection (its GitHub source) to the default-branch commit SHA it
|
|
30
|
-
// was pulled from. The dashboard reads the live remote HEAD, diffs it against
|
|
31
|
-
// the ledger to surface "update available", then re-runs install to advance it.
|
|
32
|
-
//
|
|
33
|
-
// The file is a hidden sibling of the skill dirs; skill discovery and removal
|
|
34
|
-
// only ever touch named sub-directories, so it's invisible to both.
|
|
35
|
-
// ---------------------------------------------------------------------------
|
|
36
9
|
const SKILL_LEDGER_FILE = '.pikiloom-skills-ledger.json';
|
|
37
10
|
function ledgerPath(opts) {
|
|
38
11
|
if (opts.global)
|
|
@@ -41,7 +14,6 @@ function ledgerPath(opts) {
|
|
|
41
14
|
return path.join(opts.workdir, STATE_DIR_NAME, 'skills', SKILL_LEDGER_FILE);
|
|
42
15
|
return null;
|
|
43
16
|
}
|
|
44
|
-
/** Normalize a source to a stable key: lowercase `owner/repo`, URL/.git stripped. */
|
|
45
17
|
export function normalizeSkillSourceKey(source) {
|
|
46
18
|
return String(source || '')
|
|
47
19
|
.trim()
|
|
@@ -60,7 +32,7 @@ function readLedger(opts) {
|
|
|
60
32
|
return { version: 1, entries: parsed.entries };
|
|
61
33
|
}
|
|
62
34
|
}
|
|
63
|
-
catch {
|
|
35
|
+
catch { }
|
|
64
36
|
return { version: 1, entries: {} };
|
|
65
37
|
}
|
|
66
38
|
function writeLedger(ledger, opts) {
|
|
@@ -71,16 +43,14 @@ function writeLedger(ledger, opts) {
|
|
|
71
43
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
72
44
|
fs.writeFileSync(p, JSON.stringify(ledger, null, 2));
|
|
73
45
|
}
|
|
74
|
-
catch {
|
|
46
|
+
catch { }
|
|
75
47
|
}
|
|
76
|
-
/** Look up the recorded provenance for a source in the given scope. */
|
|
77
48
|
export function getSkillLedgerEntry(source, opts) {
|
|
78
49
|
const key = normalizeSkillSourceKey(source);
|
|
79
50
|
if (!key)
|
|
80
51
|
return null;
|
|
81
52
|
return readLedger(opts).entries[key] || null;
|
|
82
53
|
}
|
|
83
|
-
/** Record (or refresh) the provenance for a source after a successful install/update. */
|
|
84
54
|
export function recordSkillInstall(source, opts) {
|
|
85
55
|
const key = normalizeSkillSourceKey(source);
|
|
86
56
|
if (!key)
|
|
@@ -95,7 +65,6 @@ export function recordSkillInstall(source, opts) {
|
|
|
95
65
|
};
|
|
96
66
|
writeLedger(ledger, opts);
|
|
97
67
|
}
|
|
98
|
-
/** Drop a source's provenance — call when its skills are removed. */
|
|
99
68
|
export function forgetSkillInstall(source, opts) {
|
|
100
69
|
const key = normalizeSkillSourceKey(source);
|
|
101
70
|
if (!key)
|
|
@@ -106,15 +75,6 @@ export function forgetSkillInstall(source, opts) {
|
|
|
106
75
|
writeLedger(ledger, opts);
|
|
107
76
|
}
|
|
108
77
|
}
|
|
109
|
-
// ---------------------------------------------------------------------------
|
|
110
|
-
// Helpers
|
|
111
|
-
// ---------------------------------------------------------------------------
|
|
112
|
-
/**
|
|
113
|
-
* Make sure the global skills directory exists, and that the agent-specific
|
|
114
|
-
* dirs that the upstream skills CLI writes to (`~/.claude/skills`,
|
|
115
|
-
* `~/.agents/skills`) resolve back to it. This is what lets us install with
|
|
116
|
-
* `--agent claude-code` and still read the results from `~/.pikiloom/skills`.
|
|
117
|
-
*/
|
|
118
78
|
function ensureGlobalSkillsDir() {
|
|
119
79
|
fs.mkdirSync(GLOBAL_SKILLS_DIR, { recursive: true });
|
|
120
80
|
for (const linkDir of [
|
|
@@ -128,7 +88,6 @@ function ensureGlobalSkillsDir() {
|
|
|
128
88
|
if (real === fs.realpathSync(GLOBAL_SKILLS_DIR))
|
|
129
89
|
continue;
|
|
130
90
|
}
|
|
131
|
-
// Existing dir/link doesn't match — leave it alone rather than destroy user data.
|
|
132
91
|
continue;
|
|
133
92
|
}
|
|
134
93
|
catch {
|
|
@@ -136,7 +95,7 @@ function ensureGlobalSkillsDir() {
|
|
|
136
95
|
fs.mkdirSync(path.dirname(linkDir), { recursive: true });
|
|
137
96
|
fs.symlinkSync(GLOBAL_SKILLS_DIR, linkDir, 'dir');
|
|
138
97
|
}
|
|
139
|
-
catch {
|
|
98
|
+
catch { }
|
|
140
99
|
}
|
|
141
100
|
}
|
|
142
101
|
}
|
|
@@ -154,18 +113,9 @@ function runNpx(args, cwd, timeoutMs) {
|
|
|
154
113
|
stderr: stderr?.toString() || '',
|
|
155
114
|
});
|
|
156
115
|
});
|
|
157
|
-
// Prevent child from keeping parent alive
|
|
158
116
|
child.unref?.();
|
|
159
117
|
});
|
|
160
118
|
}
|
|
161
|
-
// ---------------------------------------------------------------------------
|
|
162
|
-
// Install
|
|
163
|
-
// ---------------------------------------------------------------------------
|
|
164
|
-
/**
|
|
165
|
-
* Install a skill from a source (GitHub owner/repo, URL, or local path).
|
|
166
|
-
*
|
|
167
|
-
* Uses `npx skills add <source>` with appropriate flags.
|
|
168
|
-
*/
|
|
169
119
|
export async function installSkill(source, opts = {}) {
|
|
170
120
|
const { global: isGlobal, skill, workdir } = opts;
|
|
171
121
|
if (!isGlobal && !workdir) {
|
|
@@ -185,8 +135,6 @@ export async function installSkill(source, opts = {}) {
|
|
|
185
135
|
const errorMsg = result.stderr.trim().split('\n').pop()?.trim() || 'installation failed';
|
|
186
136
|
return { ok: false, error: errorMsg, output: result.stdout + result.stderr };
|
|
187
137
|
}
|
|
188
|
-
// Record provenance so the dashboard can later detect remote updates. Scoped
|
|
189
|
-
// to where the skill landed; best-effort, never fails the install.
|
|
190
138
|
recordSkillInstall(source, {
|
|
191
139
|
global: isGlobal,
|
|
192
140
|
workdir,
|
|
@@ -195,19 +143,11 @@ export async function installSkill(source, opts = {}) {
|
|
|
195
143
|
});
|
|
196
144
|
return { ok: true, output: result.stdout };
|
|
197
145
|
}
|
|
198
|
-
// ---------------------------------------------------------------------------
|
|
199
|
-
// Remove
|
|
200
|
-
// ---------------------------------------------------------------------------
|
|
201
|
-
/**
|
|
202
|
-
* Remove an installed skill by name.
|
|
203
|
-
* Deletes the skill directory from the appropriate location.
|
|
204
|
-
*/
|
|
205
146
|
export function removeSkill(skillName, opts = {}) {
|
|
206
147
|
const { global: isGlobal, workdir } = opts;
|
|
207
148
|
if (!isGlobal && !workdir) {
|
|
208
149
|
return { ok: false, error: 'workdir is required for project-scoped skill removal' };
|
|
209
150
|
}
|
|
210
|
-
// Security: prevent path traversal — skill name must be a plain directory name
|
|
211
151
|
const sanitized = path.basename(skillName);
|
|
212
152
|
if (!sanitized || sanitized === '.' || sanitized === '..' || sanitized !== skillName) {
|
|
213
153
|
return { ok: false, error: 'invalid skill name' };
|
|
@@ -216,7 +156,6 @@ export function removeSkill(skillName, opts = {}) {
|
|
|
216
156
|
? GLOBAL_SKILLS_DIR
|
|
217
157
|
: path.join(workdir, STATE_DIR_NAME, 'skills');
|
|
218
158
|
const skillDir = path.join(parentDir, sanitized);
|
|
219
|
-
// Double-check the resolved path is inside the expected parent
|
|
220
159
|
const realParent = path.resolve(parentDir);
|
|
221
160
|
const realSkill = path.resolve(skillDir);
|
|
222
161
|
if (!realSkill.startsWith(realParent + path.sep)) {
|
|
@@ -233,15 +172,9 @@ export function removeSkill(skillName, opts = {}) {
|
|
|
233
172
|
return { ok: false, error: e?.message || 'removal failed' };
|
|
234
173
|
}
|
|
235
174
|
}
|
|
236
|
-
// ---------------------------------------------------------------------------
|
|
237
|
-
// List installed (enhanced)
|
|
238
|
-
// ---------------------------------------------------------------------------
|
|
239
175
|
export function getGlobalSkillsDir() {
|
|
240
176
|
return GLOBAL_SKILLS_DIR;
|
|
241
177
|
}
|
|
242
|
-
// ---------------------------------------------------------------------------
|
|
243
|
-
// Check for updates
|
|
244
|
-
// ---------------------------------------------------------------------------
|
|
245
178
|
export async function checkSkillUpdates(opts = {}) {
|
|
246
179
|
const cwd = opts.global ? os.homedir() : (opts.workdir || process.cwd());
|
|
247
180
|
const args = ['-y', 'skills', 'check'];
|
package/dist/agent/skills.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Project skill discovery from .pikiloom/skills and .claude/commands.
|
|
3
|
-
*/
|
|
4
1
|
import fs from 'node:fs';
|
|
5
2
|
import os from 'node:os';
|
|
6
3
|
import path from 'node:path';
|
|
@@ -28,7 +25,6 @@ function parseSkillMeta(content) {
|
|
|
28
25
|
const dm = fm[1].match(/^description:\s*(.+)/m);
|
|
29
26
|
if (dm)
|
|
30
27
|
description = dm[1].trim();
|
|
31
|
-
// Parse mcp_requires as YAML list
|
|
32
28
|
const mr = fm[1].match(/^mcp_requires:\s*\n((?:\s+-\s+.+\n?)+)/m);
|
|
33
29
|
if (mr) {
|
|
34
30
|
mcpRequires = mr[1]
|
|
@@ -105,11 +101,6 @@ function ensureDirSymlink(linkPath, targetDir) {
|
|
|
105
101
|
const desiredReal = realPathOrNull(targetDir);
|
|
106
102
|
if (currentTarget === desiredTarget || (currentReal && desiredReal && currentReal === desiredReal))
|
|
107
103
|
return;
|
|
108
|
-
// Symlink points elsewhere — including dangling legacy targets like the
|
|
109
|
-
// pre-rename `../.pikiclaw/skills`. `rmSync(recursive,force)` silently
|
|
110
|
-
// no-ops on a dangling symlink (it follows the link, sees the target is
|
|
111
|
-
// gone, treats it as already deleted) and leaves the link in place, which
|
|
112
|
-
// then trips EEXIST below. `unlinkSync` removes the link itself reliably.
|
|
113
104
|
fs.unlinkSync(linkPath);
|
|
114
105
|
}
|
|
115
106
|
else {
|
|
@@ -122,9 +113,6 @@ function ensureDirSymlink(linkPath, targetDir) {
|
|
|
122
113
|
fs.symlinkSync(desiredTarget, linkPath, process.platform === 'win32' ? 'junction' : 'dir');
|
|
123
114
|
}
|
|
124
115
|
catch (err) {
|
|
125
|
-
// Tolerate a concurrent creator (the prod self-bootstrap and `npm run dev`
|
|
126
|
-
// can both initialize the same workdir) — if the link now resolves to the
|
|
127
|
-
// intended target, the race is benign; otherwise surface the real error.
|
|
128
116
|
if (err?.code !== 'EEXIST' || fs.readlinkSync(linkPath) !== desiredTarget)
|
|
129
117
|
throw err;
|
|
130
118
|
}
|
|
@@ -169,33 +157,13 @@ export function getProjectSkillPaths(workdir, skillName) {
|
|
|
169
157
|
claudeSkillFile: hasFile(claudeSkillFile) ? claudeSkillFile : null,
|
|
170
158
|
};
|
|
171
159
|
}
|
|
172
|
-
// Matches the canonical prompt produced by `resolveSkillPrompt` (bot/commands)
|
|
173
|
-
// and `resolveSkillFromPrompt` (dashboard/session-control). Both build the same
|
|
174
|
-
// shape: `[Project directory: <wd>]\n\nRead the skill definition at \`<path>\`
|
|
175
|
-
// and execute the instructions defined there.[ Additional context: <args>]`.
|
|
176
|
-
//
|
|
177
|
-
// Whitespace between the segments is tolerant (`\s+`) so the regex still
|
|
178
|
-
// matches after the claude driver collapses interior `\s+` to single spaces
|
|
179
|
-
// when surfacing user text in `getClaudeSessionMessages`.
|
|
180
160
|
const SKILL_PROMPT_RE = /^\[Project directory: [^\]\n]+?\]\s+Read the skill definition at `([^`\n]+)` and execute the instructions defined there\.(?:\s+Additional context:\s+([\s\S]+?))?\s*$/;
|
|
181
|
-
/**
|
|
182
|
-
* Inverse of `resolveSkillPrompt`. When a stored user message matches the
|
|
183
|
-
* canonical skill-execution expansion, return the original `/skillname [args]`
|
|
184
|
-
* shorthand for display. Returns null when the text isn't a recognized skill
|
|
185
|
-
* prompt — callers should fall back to the raw text.
|
|
186
|
-
*
|
|
187
|
-
* The expanded form is what the agent CLI actually consumed and what gets
|
|
188
|
-
* persisted to its session log; this collapse exists purely so the dashboard
|
|
189
|
-
* (and other display surfaces) can render the slash command the user typed.
|
|
190
|
-
*/
|
|
191
161
|
export function collapseSkillPrompt(text) {
|
|
192
162
|
if (!text)
|
|
193
163
|
return null;
|
|
194
164
|
const m = SKILL_PROMPT_RE.exec(text);
|
|
195
165
|
if (!m)
|
|
196
166
|
return null;
|
|
197
|
-
// Skill files are always at `<root>/<skillName>/SKILL.md`. Split on both
|
|
198
|
-
// `/` and `\` so Windows-generated paths (path.join) resolve correctly.
|
|
199
167
|
const segments = m[1].split(/[/\\]/).filter(Boolean);
|
|
200
168
|
if (segments.length < 2 || segments[segments.length - 1] !== 'SKILL.md')
|
|
201
169
|
return null;
|
|
@@ -206,9 +174,6 @@ export function collapseSkillPrompt(text) {
|
|
|
206
174
|
return args ? `/${name} ${args}` : `/${name}`;
|
|
207
175
|
}
|
|
208
176
|
const GLOBAL_SKILLS_ROOT = path.join(os.homedir(), STATE_DIR_NAME, 'skills');
|
|
209
|
-
// Per-file cache of parsed SKILL.md metadata, keyed by file mtime. listSkills runs
|
|
210
|
-
// on every skills-menu render (IM + dashboard); without this it re-read and
|
|
211
|
-
// re-regex-parsed every SKILL.md each time. A changed skill re-parses just itself.
|
|
212
177
|
const skillMetaCache = new Map();
|
|
213
178
|
function discoverSkillsFromDir(dir, scope, seen) {
|
|
214
179
|
const skills = [];
|
|
@@ -251,24 +216,16 @@ function discoverSkillsFromDir(dir, scope, seen) {
|
|
|
251
216
|
}
|
|
252
217
|
return skills;
|
|
253
218
|
}
|
|
254
|
-
/**
|
|
255
|
-
* List all skills — project-scoped (workdir) first, then global (~/.pikiloom/skills/).
|
|
256
|
-
* Project skills with the same name shadow global ones.
|
|
257
|
-
*/
|
|
258
219
|
export function listSkills(workdir) {
|
|
259
220
|
const seen = new Set();
|
|
260
221
|
const { canonicalRoot, legacyRoots } = resolveProjectSkillRoots(workdir);
|
|
261
|
-
// Project skills take precedence. Also scan the pre-rename `.pikiclaw/skills`
|
|
262
|
-
// dir so repos that committed project skills before the rename keep working.
|
|
263
222
|
const projectSkills = [
|
|
264
223
|
...discoverSkillsFromDir(canonicalRoot, 'project', seen),
|
|
265
224
|
...legacyRoots.flatMap(root => discoverSkillsFromDir(root, 'project', seen)),
|
|
266
225
|
];
|
|
267
|
-
// Global skills fill in the rest
|
|
268
226
|
const globalSkills = discoverSkillsFromDir(GLOBAL_SKILLS_ROOT, 'global', seen);
|
|
269
227
|
return { skills: [...projectSkills, ...globalSkills], workdir };
|
|
270
228
|
}
|
|
271
|
-
/** Return the global skills root directory path. */
|
|
272
229
|
export function getGlobalSkillsRoot() {
|
|
273
230
|
return GLOBAL_SKILLS_ROOT;
|
|
274
231
|
}
|
package/dist/agent/stream.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI spawn framework, stream orchestration, agent detection, and driver delegation.
|
|
3
|
-
*/
|
|
4
1
|
import { execSync, spawn } from 'node:child_process';
|
|
5
2
|
import { createInterface } from 'node:readline';
|
|
6
3
|
import fs from 'node:fs';
|
|
@@ -12,12 +9,9 @@ import { awaitAgentUpdateIdle } from './auto-update.js';
|
|
|
12
9
|
import { getDriver, allDrivers, getAcceptedProviderKinds, hasDriver } from './driver.js';
|
|
13
10
|
import { resolveAgentInjection, getActiveProfile, getActiveProfileId, getProvider, updateProfile, listProfiles, } from '../model/index.js';
|
|
14
11
|
import { Q, agentLog, agentWarn, agentError, joinErrorMessages, normalizeErrorMessage, buildStreamPreviewMeta, computeContext, shortValue, isPendingSessionId, dedupeStrings, normalizeStreamPreviewPlan, } from './utils.js';
|
|
15
|
-
import { saveSessionRecord, setSessionRunState, applySessionRunResult, ensureSessionWorkspace, importFilesIntoWorkspace, syncManagedSessionIdentity, summarizePromptTitle, recordFork, } from './session.js';
|
|
12
|
+
import { saveSessionRecord, setSessionRunState, applySessionRunResult, ensureSessionWorkspace, importFilesIntoWorkspace, syncManagedSessionIdentity, summarizePromptTitle, recordFork, resolveCanonicalSessionId, } from './session.js';
|
|
16
13
|
import { clearAwaitResume } from './await-resume.js';
|
|
17
14
|
import { collapseSkillPrompt } from './skills.js';
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Private helpers
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
15
|
function trimSessionText(value, max = 24_000) {
|
|
22
16
|
const text = typeof value === 'string' ? value.trim() : '';
|
|
23
17
|
if (!text)
|
|
@@ -26,14 +20,6 @@ function trimSessionText(value, max = 24_000) {
|
|
|
26
20
|
return text;
|
|
27
21
|
return `${text.slice(0, Math.max(0, max - 3)).trimEnd()}...`;
|
|
28
22
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Spot known browser-MCP failure signatures inside an agent stdout line so the
|
|
31
|
-
* supervisor can force-restart Chrome before the next turn. Both patterns are
|
|
32
|
-
* narrow on purpose: `Frame has been detached` is playwright-specific; the
|
|
33
|
-
* `Connection closed` MCP error only triggers when the same line names the
|
|
34
|
-
* `pikiloom-browser` server, so failures on other MCP servers do not nuke the
|
|
35
|
-
* managed browser. The supervisor itself debounces, so this can fire freely.
|
|
36
|
-
*/
|
|
37
23
|
export function _detectBrowserMcpFailure(rawLine) {
|
|
38
24
|
if (!rawLine)
|
|
39
25
|
return null;
|
|
@@ -44,9 +30,6 @@ export function _detectBrowserMcpFailure(rawLine) {
|
|
|
44
30
|
}
|
|
45
31
|
return null;
|
|
46
32
|
}
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// Agent detection (private helpers and cache)
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
33
|
const AGENT_DETECT_TTL_MS = AGENT_DETECT_TIMEOUTS.detectTtl;
|
|
51
34
|
const AGENT_VERSION_TTL_MS = AGENT_DETECT_TIMEOUTS.versionTtl;
|
|
52
35
|
const AGENT_VERSION_TIMEOUT_MS = AGENT_DETECT_TIMEOUTS.versionCommand;
|
|
@@ -114,7 +97,6 @@ function readAgentVersion(binPath, timeoutMs) {
|
|
|
114
97
|
return null;
|
|
115
98
|
}
|
|
116
99
|
}
|
|
117
|
-
// Agent detection (used by all drivers)
|
|
118
100
|
export function detectAgentBin(cmd, agent, options = {}) {
|
|
119
101
|
const cacheKey = `${agent}:${cmd}`;
|
|
120
102
|
const now = Date.now();
|
|
@@ -155,23 +137,6 @@ export function detectAgentBin(cmd, agent, options = {}) {
|
|
|
155
137
|
export function listAgents(options = {}) {
|
|
156
138
|
return { agents: allDrivers().map(d => detectAgentBin(d.cmd, d.id, options)) };
|
|
157
139
|
}
|
|
158
|
-
/**
|
|
159
|
-
* Resolve the *effective* default agent for new conversations.
|
|
160
|
-
*
|
|
161
|
-
* The stored value is only a *preference* — a new conversation can run only an
|
|
162
|
-
* agent whose CLI is actually installed. So when the preference's CLI isn't
|
|
163
|
-
* installed, we clamp to the first installed agent (in driver-registration
|
|
164
|
-
* order: claude → codex → gemini → hermes) instead of surfacing an uninstalled
|
|
165
|
-
* default the user can't run. When the preference *is* installed it always
|
|
166
|
-
* wins, so machines with the historical 'codex' default are unaffected. When
|
|
167
|
-
* nothing is installed we keep the prior behaviour (honour a valid preference,
|
|
168
|
-
* else 'codex') so the result is always defined.
|
|
169
|
-
*
|
|
170
|
-
* Resolution is derived, never persisted: if the user later installs their
|
|
171
|
-
* preferred agent, the original preference is honoured again automatically.
|
|
172
|
-
* `agents` is injected (defaults to live detection) so the resolution is a pure
|
|
173
|
-
* function of (preference, install-state) and trivially testable.
|
|
174
|
-
*/
|
|
175
140
|
export function resolveDefaultAgent(preferred, agents = listAgents().agents) {
|
|
176
141
|
const want = typeof preferred === 'string' ? preferred.trim().toLowerCase() : '';
|
|
177
142
|
const wantValid = !!want && hasDriver(want);
|
|
@@ -182,9 +147,6 @@ export function resolveDefaultAgent(preferred, agents = listAgents().agents) {
|
|
|
182
147
|
return installed[0];
|
|
183
148
|
return wantValid ? want : 'codex';
|
|
184
149
|
}
|
|
185
|
-
// ---------------------------------------------------------------------------
|
|
186
|
-
// Shared CLI spawn framework (used by driver-claude.ts, driver-gemini.ts)
|
|
187
|
-
// ---------------------------------------------------------------------------
|
|
188
150
|
export async function run(cmd, opts, parseLine, parseStderrLine) {
|
|
189
151
|
const start = Date.now();
|
|
190
152
|
const deadline = start + opts.timeout * 1000;
|
|
@@ -192,10 +154,6 @@ export async function run(cmd, opts, parseLine, parseStderrLine) {
|
|
|
192
154
|
let lineCount = 0;
|
|
193
155
|
let timedOut = false;
|
|
194
156
|
let interrupted = false;
|
|
195
|
-
// BYOK: seed contextWindow from the provider-cached value so the live
|
|
196
|
-
// preview percent uses the real denominator (e.g. 1M for DeepSeek v4 Pro
|
|
197
|
-
// via OpenRouter) instead of whatever the CLI happens to report later.
|
|
198
|
-
// Parsers gate their cc/codex-advertised updates on `s.byokContextWindow`.
|
|
199
157
|
const byokWindow = opts.byokContextWindow && opts.byokContextWindow > 0
|
|
200
158
|
? opts.byokContextWindow
|
|
201
159
|
: null;
|
|
@@ -205,8 +163,6 @@ export async function run(cmd, opts, parseLine, parseStderrLine) {
|
|
|
205
163
|
model: opts.model, thinkingEffort: opts.thinkingEffort, errors: null,
|
|
206
164
|
inputTokens: null, outputTokens: null, cachedInputTokens: null,
|
|
207
165
|
cacheCreationInputTokens: null,
|
|
208
|
-
// Output tokens from this turn's finished LLM calls — folded in by parsers
|
|
209
|
-
// when a new call resets the per-call counter (claude message_start).
|
|
210
166
|
turnOutputTokensBase: 0,
|
|
211
167
|
contextWindow: byokWindow,
|
|
212
168
|
byokContextWindow: byokWindow,
|
|
@@ -218,14 +174,8 @@ export async function run(cmd, opts, parseLine, parseStderrLine) {
|
|
|
218
174
|
claudeToolsById: new Map(),
|
|
219
175
|
seenClaudeToolIds: new Set(),
|
|
220
176
|
geminiToolsById: new Map(),
|
|
221
|
-
// Claude-only: sub-agent invocations from the Task tool. Other drivers leave it empty.
|
|
222
177
|
subAgents: new Map(),
|
|
223
|
-
// Image blocks collected during the stream (assistant images, MCP tool
|
|
224
|
-
// results, …). Surfaced on the StreamResult so IM channels can dispatch
|
|
225
|
-
// them at end-of-turn without re-reading the session file.
|
|
226
178
|
imageBlocks: [],
|
|
227
|
-
// Wired to opts.onSessionId so parsers can broadcast id changes the instant
|
|
228
|
-
// the CLI surfaces them (see emitSessionIdUpdate in agent/utils.ts).
|
|
229
179
|
_emitSessionId: opts.onSessionId ?? null,
|
|
230
180
|
};
|
|
231
181
|
const shellCmd = cmd.map(Q).join(' ');
|
|
@@ -243,9 +193,6 @@ export async function run(cmd, opts, parseLine, parseStderrLine) {
|
|
|
243
193
|
});
|
|
244
194
|
agentLog(`[spawn] pid=${proc.pid}`);
|
|
245
195
|
proc.stdin?.on('error', (e) => {
|
|
246
|
-
// Some CLIs can exit after producing all output before we finish writing
|
|
247
|
-
// stdin. Treat EPIPE as a normal early-close race; the exit code/stdout
|
|
248
|
-
// still determine the stream result.
|
|
249
196
|
if (e?.code !== 'EPIPE')
|
|
250
197
|
agentWarn(`[stdin] write failed: ${e?.message || e}`);
|
|
251
198
|
});
|
|
@@ -370,38 +317,26 @@ export async function run(cmd, opts, parseLine, parseStderrLine) {
|
|
|
370
317
|
assistantBlocks: s.imageBlocks.length ? [...s.imageBlocks] : undefined,
|
|
371
318
|
};
|
|
372
319
|
}
|
|
373
|
-
// ---------------------------------------------------------------------------
|
|
374
|
-
// Stream orchestration
|
|
375
|
-
// ---------------------------------------------------------------------------
|
|
376
320
|
function prepareStreamOpts(opts) {
|
|
377
|
-
// For display fields (title / lastQuestion / lastMessageText) prefer the
|
|
378
|
-
// `/skillname` shorthand the user typed over the long expansion we
|
|
379
|
-
// synthesized for the agent — the expanded form is what the CLI consumes,
|
|
380
|
-
// but it shouldn't leak into session list previews or sidebar tabs.
|
|
381
321
|
const displayPrompt = collapseSkillPrompt(opts.prompt) ?? opts.prompt;
|
|
382
|
-
const
|
|
322
|
+
const resolvedInboundSessionId = opts.sessionId
|
|
323
|
+
? resolveCanonicalSessionId(opts.workdir, opts.agent, opts.sessionId)
|
|
324
|
+
: opts.sessionId;
|
|
325
|
+
const session = ensureSessionWorkspace({ agent: opts.agent, workdir: opts.workdir, sessionId: resolvedInboundSessionId, title: displayPrompt });
|
|
383
326
|
const importedFiles = importFilesIntoWorkspace(session.workspacePath, opts.attachments || []);
|
|
384
327
|
const attachmentRelPaths = dedupeStrings([...session.record.stagedFiles, ...importedFiles]);
|
|
385
|
-
// Capture staged files for MCP bridge before clearing
|
|
386
328
|
const stagedFiles = [...session.record.stagedFiles];
|
|
387
329
|
session.record.stagedFiles = [];
|
|
388
|
-
// Remember this turn's attachments so dashboard fallbacks (called while the
|
|
389
|
-
// agent CLI hasn't yet flushed the user event to its native session file)
|
|
390
|
-
// can still render the user's image bubble. Cleared/overwritten at the
|
|
391
|
-
// start of the NEXT turn — always reflects the turn currently in flight.
|
|
392
330
|
session.record.lastUserAttachments = [...attachmentRelPaths];
|
|
393
331
|
if (!session.record.title)
|
|
394
332
|
session.record.title = summarizePromptTitle(displayPrompt) || null;
|
|
395
333
|
session.record.lastQuestion = shortValue(displayPrompt, 500);
|
|
396
334
|
session.record.lastMessageText = shortValue(displayPrompt, 500);
|
|
397
335
|
setSessionRunState(session.record, 'running', null);
|
|
398
|
-
// A turn starting clears any "waiting on background work" marker the previous
|
|
399
|
-
// turn parked — the session is plainly running again, not waiting.
|
|
400
336
|
if (session.sessionId)
|
|
401
337
|
clearAwaitResume(opts.workdir, opts.agent, session.sessionId);
|
|
402
338
|
saveSessionRecord(opts.workdir, session.record);
|
|
403
339
|
const attachmentPaths = attachmentRelPaths.map(relPath => path.join(session.workspacePath, relPath));
|
|
404
|
-
// For pending sessions, pass null sessionId to the CLI so it creates a new session
|
|
405
340
|
const effectiveSessionId = isPendingSessionId(session.sessionId) ? null : session.sessionId;
|
|
406
341
|
return {
|
|
407
342
|
session,
|
|
@@ -431,19 +366,12 @@ function finalizeStreamResult(result, workdir, prompt, session, workflowEnabled)
|
|
|
431
366
|
session.record.model = result.model || session.record.model;
|
|
432
367
|
if (result.thinkingEffort)
|
|
433
368
|
session.record.thinkingEffort = result.thinkingEffort;
|
|
434
|
-
// Remember whether this turn ran with Workflow on so the synthetic `ultra`
|
|
435
|
-
// rung re-folds for display after the live stream ends and on resume — the
|
|
436
|
-
// stored `thinkingEffort` stays the concrete rung (e.g. `max`). `undefined`
|
|
437
|
-
// (driver invoked outside the bot) leaves the prior value untouched.
|
|
438
369
|
if (workflowEnabled !== undefined)
|
|
439
370
|
session.record.workflowEnabled = workflowEnabled;
|
|
440
|
-
// Capture the BYOK Profile that was in effect for this run so a future
|
|
441
|
-
// `session.switch` can re-bind it (null = native CLI auth).
|
|
442
371
|
try {
|
|
443
372
|
session.record.profileId = getActiveProfileId(session.record.agent);
|
|
444
373
|
}
|
|
445
374
|
catch {
|
|
446
|
-
/* model layer not initialised in tests — leave profileId untouched */
|
|
447
375
|
}
|
|
448
376
|
const displayPrompt = collapseSkillPrompt(prompt) ?? prompt;
|
|
449
377
|
if (!session.record.title)
|
|
@@ -477,7 +405,6 @@ export async function doStream(opts) {
|
|
|
477
405
|
codexCumulative: null, error: message, stopReason: null, incomplete: true, activity: null, plan: null,
|
|
478
406
|
};
|
|
479
407
|
}
|
|
480
|
-
// Start MCP bridge for IM tools (when sendFile is available) and/or supplemental servers (browser, etc.)
|
|
481
408
|
let bridge = null;
|
|
482
409
|
try {
|
|
483
410
|
const { startMcpBridge, redactMcpConfigForLog } = await import('./mcp/bridge.js');
|
|
@@ -515,9 +442,6 @@ export async function doStream(opts) {
|
|
|
515
442
|
catch (e) {
|
|
516
443
|
agentWarn(`[mcp] bridge start failed: ${e.message} — proceeding without MCP`);
|
|
517
444
|
}
|
|
518
|
-
// Apply BYOK injection (Provider/Profile from the model layer): merges env
|
|
519
|
-
// vars into prepared.extraEnv, overrides the per-agent model field, and
|
|
520
|
-
// hands argvAppend to drivers that consume it (Hermes via opts.extraEnv → its own argv builder).
|
|
521
445
|
try {
|
|
522
446
|
const injection = await resolveAgentInjection(prepared.agent);
|
|
523
447
|
if (injection) {
|
|
@@ -548,9 +472,6 @@ export async function doStream(opts) {
|
|
|
548
472
|
}
|
|
549
473
|
agentLog(`[byok] ${injection.detail}`);
|
|
550
474
|
}
|
|
551
|
-
// resolveAgentEffort (runtime-config) reads only the top-level hermesReasoningEffort
|
|
552
|
-
// field and cannot see the effort stored inside models.profiles[].effort. Override
|
|
553
|
-
// thinkingEffort here so the Profile's effort wins over the config default.
|
|
554
475
|
const activeProfile = getActiveProfile(prepared.agent);
|
|
555
476
|
if (activeProfile?.effort) {
|
|
556
477
|
prepared.thinkingEffort = activeProfile.effort;
|
|
@@ -559,13 +480,6 @@ export async function doStream(opts) {
|
|
|
559
480
|
catch (e) {
|
|
560
481
|
agentWarn(`[byok] failed to apply Profile injection: ${e?.message || e}`);
|
|
561
482
|
}
|
|
562
|
-
// In-memory-first: stamp the turn's resolved reasoning rung + Workflow opt-in
|
|
563
|
-
// onto the centralized index NOW — before the agent CLI has flushed its own
|
|
564
|
-
// session file — so the session list/composer reflect the user's pick during
|
|
565
|
-
// the very first turn instead of only after finalizeStreamResult. The managed
|
|
566
|
-
// record is the single source of truth for this metadata and links to the
|
|
567
|
-
// native agent-session by id on promotion; finalize re-stamps it (plus the
|
|
568
|
-
// actual model) authoritatively at turn end.
|
|
569
483
|
try {
|
|
570
484
|
if (prepared.thinkingEffort) {
|
|
571
485
|
session.record.thinkingEffort = prepared.thinkingEffort.trim().toLowerCase() || session.record.thinkingEffort;
|
|
@@ -583,18 +497,9 @@ export async function doStream(opts) {
|
|
|
583
497
|
if (opts.forkOf && !driver.capabilities?.fork) {
|
|
584
498
|
throw new Error(`Agent ${prepared.agent} does not support fork`);
|
|
585
499
|
}
|
|
586
|
-
// A background agent-CLI auto-update (`npm install -g` / `brew upgrade`, by
|
|
587
|
-
// this process OR the `npx pikiloom@latest` self-bootstrap) briefly removes
|
|
588
|
-
// the bin while it relinks; exec'ing into that window fails with exit 127
|
|
589
|
-
// "command not found". Wait out any in-flight reinstall of THIS agent before
|
|
590
|
-
// dispatching to the driver — this is the one chokepoint every agent turn
|
|
591
|
-
// (claude -p, claude TUI, codex app-server, gemini) passes through. No-op
|
|
592
|
-
// when nothing is updating.
|
|
593
500
|
await awaitAgentUpdateIdle(prepared.agent, AGENT_UPDATE_TIMEOUTS.spawnWait);
|
|
594
501
|
const result = await driver.doStream(prepared);
|
|
595
502
|
const finalized = finalizeStreamResult(result, opts.workdir, opts.prompt, session, opts.claudeWorkflowEnabled);
|
|
596
|
-
// Once the child has its real session ID, link the lineage. We do this
|
|
597
|
-
// after finalize so the child record is persisted with its native ID.
|
|
598
503
|
if (opts.forkOf && finalized.sessionId) {
|
|
599
504
|
try {
|
|
600
505
|
recordFork(opts.workdir, {
|
|
@@ -651,9 +556,6 @@ export async function doStream(opts) {
|
|
|
651
556
|
}
|
|
652
557
|
}
|
|
653
558
|
}
|
|
654
|
-
// ---------------------------------------------------------------------------
|
|
655
|
-
// Driver delegation
|
|
656
|
-
// ---------------------------------------------------------------------------
|
|
657
559
|
export function getSessions(opts) {
|
|
658
560
|
const workdir = path.resolve(opts.workdir);
|
|
659
561
|
agentLog(`[sessions] request agent=${opts.agent} workdir=${workdir} limit=${opts.limit ?? 'all'}`);
|
|
@@ -671,37 +573,11 @@ export function getSessionMessages(opts) {
|
|
|
671
573
|
export function listModels(agent, opts = {}) {
|
|
672
574
|
return getDriver(agent).listModels(opts);
|
|
673
575
|
}
|
|
674
|
-
/**
|
|
675
|
-
* Detect a Provider whose baseURL is on the local machine (Ollama / mlx-lm
|
|
676
|
-
* connected via `/api/local-models/connect`). Used only to bucket the entry
|
|
677
|
-
* into the `'local'` group in the unified picker — runtime behaviour is
|
|
678
|
-
* unchanged whether or not the baseURL is loopback.
|
|
679
|
-
*/
|
|
680
576
|
function isLocalProviderBaseURL(baseURL) {
|
|
681
577
|
return /^https?:\/\/(localhost|127\.0\.0\.1|0\.0\.0\.0|\[::1\])(?::|\/|$)/i.test(baseURL);
|
|
682
578
|
}
|
|
683
|
-
/**
|
|
684
|
-
* Resolve the model list a UI surface should show for `agent`.
|
|
685
|
-
*
|
|
686
|
-
* Returns a *union* of:
|
|
687
|
-
* 1. The agent CLI's native model catalogue (no Profile required), tagged
|
|
688
|
-
* `group: 'native'`.
|
|
689
|
-
* 2. Every Profile whose provider kind appears in the driver's
|
|
690
|
-
* `acceptedProviderKinds`, tagged `group: 'cloud'` (remote BYOK) or
|
|
691
|
-
* `group: 'local'` (loopback baseURL — Ollama / mlx-lm).
|
|
692
|
-
*
|
|
693
|
-
* The previous behaviour — filter to the active Profile's provider — meant
|
|
694
|
-
* users could not switch *across* providers from the IM picker without first
|
|
695
|
-
* unbinding through the dashboard. The unified list removes that step so
|
|
696
|
-
* `/models` is a one-screen pick.
|
|
697
|
-
*
|
|
698
|
-
* Callers that need the strictly-native list (e.g. the dashboard agent card's
|
|
699
|
-
* "Native" branch) should call `driver.listModels()` directly — this function
|
|
700
|
-
* is for the unified picker.
|
|
701
|
-
*/
|
|
702
579
|
export async function resolveAgentModels(agent, opts = {}) {
|
|
703
580
|
const driver = getDriver(agent);
|
|
704
|
-
// 1. Native — agent CLI's built-in catalogue.
|
|
705
581
|
let nativeResult;
|
|
706
582
|
try {
|
|
707
583
|
nativeResult = await driver.listModels(opts);
|
|
@@ -714,10 +590,6 @@ export async function resolveAgentModels(agent, opts = {}) {
|
|
|
714
590
|
alias: m.alias,
|
|
715
591
|
group: 'native',
|
|
716
592
|
}));
|
|
717
|
-
// 2. BYOK Profiles compatible with this driver — grouped into cloud vs local
|
|
718
|
-
// by baseURL. We never call the provider's /models endpoint here: that
|
|
719
|
-
// list can run into the hundreds for OpenRouter and would drown the
|
|
720
|
-
// picker. Profiles ARE the curated middle layer.
|
|
721
593
|
const acceptedKinds = new Set(getAcceptedProviderKinds(agent));
|
|
722
594
|
const cloud = [];
|
|
723
595
|
const local = [];
|
|
@@ -754,25 +626,10 @@ export async function resolveAgentModels(agent, opts = {}) {
|
|
|
754
626
|
export function getUsage(opts) {
|
|
755
627
|
return getDriver(opts.agent).getUsage(opts);
|
|
756
628
|
}
|
|
757
|
-
/**
|
|
758
|
-
* If the user has a BYOK Profile bound to `agent`, return its raw modelId
|
|
759
|
-
* (e.g. "deepseek/deepseek-v4-flash"). Returns null when no profile is bound.
|
|
760
|
-
* Used by display paths that need to show the profile's model rather than the
|
|
761
|
-
* pikiloom user-config model (which may be stale or unrelated to the active profile).
|
|
762
|
-
*/
|
|
763
629
|
export function getAgentBoundModelId(agent) {
|
|
764
630
|
const profile = getActiveProfile(agent);
|
|
765
631
|
return profile?.modelId ?? null;
|
|
766
632
|
}
|
|
767
|
-
/**
|
|
768
|
-
* Persist a model id to the active BYOK Profile for `agent`. Returns true when
|
|
769
|
-
* the Profile was updated (caller should skip writing the legacy
|
|
770
|
-
* `<agent>Model` user-config field), false when no Profile is bound.
|
|
771
|
-
*
|
|
772
|
-
* Hermes uses this as the *primary* persistence path because `hermes acp` does
|
|
773
|
-
* not support runtime model switching via CLI flags — the only way to change
|
|
774
|
-
* the model is the Profile (which the driver passes to ACP `session/set_model`).
|
|
775
|
-
*/
|
|
776
633
|
export function setAgentBoundModelId(agent, modelId) {
|
|
777
634
|
const profile = getActiveProfile(agent);
|
|
778
635
|
if (!profile)
|
package/dist/agent/types.js
CHANGED
|
@@ -1,13 +1 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* types.ts — Shared type definitions for the agent layer.
|
|
3
|
-
*
|
|
4
|
-
* All type aliases, interfaces, and type-level constants used across the
|
|
5
|
-
* agent subsystem (drivers, session management, streaming, listing, usage)
|
|
6
|
-
* are centralised here so that consumers can import types without pulling
|
|
7
|
-
* in runtime code.
|
|
8
|
-
*/
|
|
9
|
-
// ---------------------------------------------------------------------------
|
|
10
|
-
// Constants
|
|
11
|
-
// ---------------------------------------------------------------------------
|
|
12
|
-
/** Set of image file extensions recognised for workspace file handling. */
|
|
13
1
|
export const IMAGE_EXTS = new Set(['.jpg', '.jpeg', '.png', '.gif', '.webp']);
|