pikiloom 0.4.26 → 0.4.28
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-DtjuI8NU.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BIx6a5o1.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-BINh_7NB.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-CHpd147b.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C4DbGxbG.js} +1 -1
- package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-D_PUi-5O.js} +1 -1
- package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-BahDEMqf.js} +1 -1
- package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-Cp5VXb_2.js} +1 -1
- package/dashboard/dist/assets/SessionPanel-DZVNXptx.js +1 -0
- package/dashboard/dist/assets/SystemTab-CLznS31f.js +1 -0
- package/dashboard/dist/assets/index-CLGovkkw.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/index-DcYOI6rL.js +23 -0
- package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C8DCTJ44.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/SessionPanel-_0dLWvEb.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,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard API routes: agent detection, model listing, installation.
|
|
3
|
-
*/
|
|
4
1
|
import { Hono } from 'hono';
|
|
5
2
|
import { spawn } from 'node:child_process';
|
|
6
3
|
import fs from 'node:fs';
|
|
@@ -15,16 +12,10 @@ import { getActiveProfile, getProvider, peekProviderModelList, prefetchProviderM
|
|
|
15
12
|
import { DASHBOARD_TIMEOUTS } from '../../core/constants.js';
|
|
16
13
|
import { withTimeoutFallback } from '../../core/utils.js';
|
|
17
14
|
import { runtime } from '../runtime.js';
|
|
18
|
-
// ---------------------------------------------------------------------------
|
|
19
|
-
// Constants
|
|
20
|
-
// ---------------------------------------------------------------------------
|
|
21
15
|
const AGENT_STATUS_MODELS_TIMEOUT_MS = DASHBOARD_TIMEOUTS.agentStatusModels;
|
|
22
16
|
const AGENT_STATUS_USAGE_TIMEOUT_MS = DASHBOARD_TIMEOUTS.agentStatusUsage;
|
|
23
17
|
const AGENT_STATUS_CACHE_TTL_MS = DASHBOARD_TIMEOUTS.agentStatusCacheTtl;
|
|
24
18
|
const AGENT_INSTALL_TIMEOUT_MS = DASHBOARD_TIMEOUTS.agentInstall;
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// Helpers
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
19
|
function dedupeModels(models) {
|
|
29
20
|
const seen = new Set();
|
|
30
21
|
const deduped = [];
|
|
@@ -78,11 +69,6 @@ function runCommand(cmd, args, opts = {}) {
|
|
|
78
69
|
});
|
|
79
70
|
});
|
|
80
71
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Parse `ENOTEMPTY: ... rename 'A' -> 'B'` paths out of npm stderr and remove
|
|
83
|
-
* any staging dirs (siblings of the package dir whose name starts with `.`).
|
|
84
|
-
* Never touches the live package dir itself.
|
|
85
|
-
*/
|
|
86
72
|
function cleanupNpmStagingFromError(stderr) {
|
|
87
73
|
const removed = [];
|
|
88
74
|
const re = /rename\s+'([^']+)'\s+->\s+'([^']+)'/g;
|
|
@@ -99,16 +85,11 @@ function cleanupNpmStagingFromError(stderr) {
|
|
|
99
85
|
fs.rmSync(p, { recursive: true, force: true });
|
|
100
86
|
removed.push(p);
|
|
101
87
|
}
|
|
102
|
-
catch {
|
|
88
|
+
catch { }
|
|
103
89
|
}
|
|
104
90
|
return removed;
|
|
105
91
|
}
|
|
106
92
|
async function installAgentViaNpm(agent, log) {
|
|
107
|
-
// Only npm-published agents can be installed unattended. Manual agents
|
|
108
|
-
// (e.g. Hermes — a Python CLI with its own installer) surface their command
|
|
109
|
-
// + docs in the dashboard instead; the UI never POSTs here for them, but we
|
|
110
|
-
// still guard with an actionable message rather than an opaque
|
|
111
|
-
// "Unsupported agent" in case the endpoint is hit directly (CLI/API).
|
|
112
93
|
const spec = getAgentInstall(agent);
|
|
113
94
|
if (!spec)
|
|
114
95
|
throw new Error(`Unknown agent: ${agent}`);
|
|
@@ -134,9 +115,6 @@ async function installAgentViaNpm(agent, log) {
|
|
|
134
115
|
throw new Error(result.error || `Failed to install ${pkg}`);
|
|
135
116
|
log(`${getAgentLabel(agent)} installation complete.`);
|
|
136
117
|
}
|
|
137
|
-
// ---------------------------------------------------------------------------
|
|
138
|
-
// Agent status builder
|
|
139
|
-
// ---------------------------------------------------------------------------
|
|
140
118
|
function emptyUsage(agent, error) {
|
|
141
119
|
return { ok: false, agent, source: null, capturedAt: null, status: null, windows: [], error };
|
|
142
120
|
}
|
|
@@ -169,17 +147,10 @@ async function buildAgentStatusResponse(config = loadUserConfig(), agentOptions
|
|
|
169
147
|
try {
|
|
170
148
|
nativeConfig = driver.getNativeConfig();
|
|
171
149
|
}
|
|
172
|
-
catch {
|
|
150
|
+
catch { }
|
|
173
151
|
}
|
|
174
152
|
const modelFallback = runtimeSelectedModel ? [{ id: runtimeSelectedModel, alias: null }] : [];
|
|
175
153
|
const cachedUsage = driver.getUsage({ agent: agentId, model: runtimeSelectedModel });
|
|
176
|
-
// The dashboard agent card lets the user *edit* the binding — when
|
|
177
|
-
// they toggle the provider to "Native", the model field must show
|
|
178
|
-
// the agent CLI's own catalogue, not the provider's. We deliberately
|
|
179
|
-
// call the driver's `listModels` directly (bypassing
|
|
180
|
-
// `resolveAgentModels`'s BYOK substitution) so `models` is always the
|
|
181
|
-
// native list; the BYOK catalogue is exposed separately as
|
|
182
|
-
// `byokModels` below.
|
|
183
154
|
const [resolvedModels, resolvedUsage] = await Promise.all([
|
|
184
155
|
withTimeoutFallback(driver.listModels({ workdir, currentModel: runtimeSelectedModel }).then(result => dedupeModels([
|
|
185
156
|
...modelFallback,
|
|
@@ -198,33 +169,13 @@ async function buildAgentStatusResponse(config = loadUserConfig(), agentOptions
|
|
|
198
169
|
}
|
|
199
170
|
}
|
|
200
171
|
const updateState = getAgentUpdateState(agentId);
|
|
201
|
-
// BYOK binding — when an active Profile exists, it overrides the native
|
|
202
|
-
// model/effort surfaces. Otherwise the values fall through to the user's
|
|
203
|
-
// runtime override and then to the driver's native config.
|
|
204
172
|
const activeProfile = getActiveProfile(agentId);
|
|
205
173
|
const byokProvider = activeProfile ? getProvider(activeProfile.providerId) : null;
|
|
206
174
|
const byokProviderName = byokProvider?.name || null;
|
|
207
|
-
// Native model/effort — what the user would run under the agent CLI's
|
|
208
|
-
// own auth, independent of any active BYOK Profile. AgentTab uses these
|
|
209
|
-
// as defaults when the user toggles a card's provider back to "Native".
|
|
210
175
|
const nativeSelectedModel = runtimeSelectedModel || nativeConfig?.model || null;
|
|
211
176
|
const nativeSelectedEffort = runtimeSelectedEffort || nativeConfig?.effort || null;
|
|
212
|
-
// The BYOK-bound model is what the agent will ACTUALLY run (the injector
|
|
213
|
-
// overrides `--model`/codex `model` at spawn). Surface it everywhere the
|
|
214
|
-
// UI quotes "current model" — the InputComposer pill, the cascade label,
|
|
215
|
-
// the agent card. Falling back to the native values when no Profile is
|
|
216
|
-
// bound preserves the existing native-auth path.
|
|
217
177
|
const selectedModel = activeProfile?.modelId || nativeSelectedModel;
|
|
218
178
|
const selectedEffort = activeProfile?.effort || nativeSelectedEffort;
|
|
219
|
-
// Likewise, the InputComposer cascade should list the bound provider's
|
|
220
|
-
// catalogue — those are the models the agent can actually serve through
|
|
221
|
-
// BYOK, not the native CLI's hardcoded list. We expose it as a SEPARATE
|
|
222
|
-
// `byokModels` field rather than overwriting `models`, because AgentTab's
|
|
223
|
-
// provider/model row falls back to `models` whenever the user temporarily
|
|
224
|
-
// switches the editor to the native provider — we mustn't silently leak
|
|
225
|
-
// BYOK ids into that view. Read from the provider-models cache
|
|
226
|
-
// synchronously; miss triggers a background refresh and we degrade to the
|
|
227
|
-
// bound model id alone so the user can at least see it selected.
|
|
228
179
|
let byokModels = null;
|
|
229
180
|
if (activeProfile && byokProvider) {
|
|
230
181
|
const cachedList = peekProviderModelList(byokProvider.id);
|
|
@@ -282,9 +233,6 @@ function invalidateAgentStatus(config, opts) {
|
|
|
282
233
|
return refreshStatusCache(config, opts);
|
|
283
234
|
}
|
|
284
235
|
export function preloadAgentStatus() { void refreshStatusCache(); }
|
|
285
|
-
// ---------------------------------------------------------------------------
|
|
286
|
-
// Routes
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
236
|
const app = new Hono();
|
|
289
237
|
app.get('/api/agent-status', async (c) => {
|
|
290
238
|
return c.json(await getCachedAgentStatus());
|
|
@@ -294,9 +242,6 @@ app.post('/api/agent-install', async (c) => {
|
|
|
294
242
|
const agent = String(body?.agent || '').trim();
|
|
295
243
|
if (!runtime.isAgent(agent))
|
|
296
244
|
return c.json({ ok: false, error: 'Invalid agent' }, 400);
|
|
297
|
-
// Manual-install agents (e.g. Hermes) aren't npm-installable — the dashboard
|
|
298
|
-
// shows their command + docs instead of an Install button, so this is a bad
|
|
299
|
-
// request rather than a server error. Return the actionable command.
|
|
300
245
|
const installSpec = getAgentInstall(agent);
|
|
301
246
|
if (installSpec && installSpec.method !== 'npm') {
|
|
302
247
|
const docs = installSpec.docsUrl ? ` See ${installSpec.docsUrl}.` : '';
|
|
@@ -316,7 +261,6 @@ app.post('/api/agent-install', async (c) => {
|
|
|
316
261
|
return c.json({ ok: false, error: detail }, 500);
|
|
317
262
|
}
|
|
318
263
|
});
|
|
319
|
-
// Agent list (lightweight)
|
|
320
264
|
app.get('/api/agents', (c) => {
|
|
321
265
|
return c.json({ agents: runtime.getSetupState(loadUserConfig(), { includeVersion: true }).agents });
|
|
322
266
|
});
|
|
@@ -372,9 +316,6 @@ app.post('/api/runtime-agent', async (c) => {
|
|
|
372
316
|
const targetAgent = body?.agent;
|
|
373
317
|
const model = typeof body?.model === 'string' ? body.model.trim() : '';
|
|
374
318
|
const rawEffort = typeof body?.effort === 'string' ? body.effort.trim().toLowerCase() : '';
|
|
375
|
-
// "ultra" folds max depth + Workflow orchestration into one rung; decompose
|
|
376
|
-
// so the stored effort is always a real --effort value and orchestration
|
|
377
|
-
// follows the pick (any concrete rung ⇒ off). Mirrors Bot.switchEffortForChat.
|
|
378
319
|
const { effort, workflow: effortWorkflow } = decomposeEffortSelection(rawEffort);
|
|
379
320
|
const hasEffort = rawEffort !== '';
|
|
380
321
|
const botRef = runtime.getBotRef();
|
|
@@ -400,9 +341,6 @@ app.post('/api/runtime-agent', async (c) => {
|
|
|
400
341
|
if (targetAgent === 'gemini')
|
|
401
342
|
nextConfig.geminiModel = model;
|
|
402
343
|
if (targetAgent === 'hermes') {
|
|
403
|
-
// Prefer the active BYOK Profile (the only surface `hermes acp` honors
|
|
404
|
-
// at runtime); fall back to the legacy `hermesModel` field only when no
|
|
405
|
-
// Profile is bound, so older configs keep working.
|
|
406
344
|
if (!setAgentBoundModelId('hermes', model))
|
|
407
345
|
nextConfig.hermesModel = model;
|
|
408
346
|
}
|
|
@@ -422,9 +360,6 @@ app.post('/api/runtime-agent', async (c) => {
|
|
|
422
360
|
nextConfig.hermesReasoningEffort = effort;
|
|
423
361
|
if (botRef)
|
|
424
362
|
botRef.setEffortForAgent(targetAgent, effort);
|
|
425
|
-
// The effort pick is also the single knob for orchestration: Ultra turns
|
|
426
|
-
// it on, any concrete rung turns it off (mutual exclusion). Only claude
|
|
427
|
-
// advertises the capability today.
|
|
428
363
|
if (targetAgent === 'claude') {
|
|
429
364
|
runtime.runtimePrefs.workflow.claude = effortWorkflow;
|
|
430
365
|
runtime.setWorkflowEnv('claude', effortWorkflow);
|
|
@@ -434,8 +369,6 @@ app.post('/api/runtime-agent', async (c) => {
|
|
|
434
369
|
}
|
|
435
370
|
}
|
|
436
371
|
}
|
|
437
|
-
// Workflow orchestration toggle — independent of model/effort (orthogonal
|
|
438
|
-
// axis), gated to drivers that advertise the capability.
|
|
439
372
|
if (typeof body?.workflow === 'boolean') {
|
|
440
373
|
if (!runtime.isAgent(targetAgent))
|
|
441
374
|
return c.json({ ok: false, error: 'Invalid agent' }, 400);
|
|
@@ -450,8 +383,6 @@ app.post('/api/runtime-agent', async (c) => {
|
|
|
450
383
|
if (botRef)
|
|
451
384
|
botRef.setWorkflowEnabledForAgent(targetAgent, enabled);
|
|
452
385
|
}
|
|
453
|
-
// Access mode — Claude only (subscription TUI vs `claude -p` API credits).
|
|
454
|
-
// Persisted preference; takes effect on the next spawned turn.
|
|
455
386
|
if (typeof body?.accessMode === 'string') {
|
|
456
387
|
if (targetAgent !== 'claude')
|
|
457
388
|
return c.json({ ok: false, error: 'accessMode is only supported for claude' }, 400);
|
|
@@ -1,26 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard API routes for CLI extension management.
|
|
3
|
-
*
|
|
4
|
-
* GET /api/extensions/cli/catalog — merged recommended + detection
|
|
5
|
-
* POST /api/extensions/cli/refresh — { id } re-detect one entry
|
|
6
|
-
* POST /api/extensions/cli/auth/start — { id } start oauth-web flow
|
|
7
|
-
* GET /api/extensions/cli/auth/stream — SSE stream of a running session
|
|
8
|
-
* POST /api/extensions/cli/auth/cancel — { sessionId } kill the child
|
|
9
|
-
* POST /api/extensions/cli/auth/token — { id, values } token auth
|
|
10
|
-
* POST /api/extensions/cli/install — { id } start auto-install (npm-only)
|
|
11
|
-
* POST /api/extensions/cli/logout — { id } sign out
|
|
12
|
-
*
|
|
13
|
-
* Install sessions share the same SESSIONS map and event protocol as the
|
|
14
|
-
* oauth-web flow — the frontend connects to `/auth/stream` and cancels via
|
|
15
|
-
* `/auth/cancel` regardless of how the session was started.
|
|
16
|
-
*/
|
|
17
1
|
import { Hono } from 'hono';
|
|
18
2
|
import { stream } from 'hono/streaming';
|
|
19
3
|
import { getCliCatalog, refreshCliStatus, startCliAuthSession, getAuthSession, cancelAuthSession, applyCliToken, logoutCli, startCliInstallSession, } from '../../agent/index.js';
|
|
20
4
|
const app = new Hono();
|
|
21
|
-
// ---------------------------------------------------------------------------
|
|
22
|
-
// Catalog
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
5
|
app.get('/api/extensions/cli/catalog', async (c) => {
|
|
25
6
|
try {
|
|
26
7
|
const items = await getCliCatalog();
|
|
@@ -44,9 +25,6 @@ app.post('/api/extensions/cli/refresh', async (c) => {
|
|
|
44
25
|
return c.json({ ok: false, error: e?.message || 'refresh failed' }, 500);
|
|
45
26
|
}
|
|
46
27
|
});
|
|
47
|
-
// ---------------------------------------------------------------------------
|
|
48
|
-
// OAuth-web auth flow
|
|
49
|
-
// ---------------------------------------------------------------------------
|
|
50
28
|
app.post('/api/extensions/cli/auth/start', async (c) => {
|
|
51
29
|
try {
|
|
52
30
|
const { id } = await c.req.json();
|
|
@@ -74,7 +52,6 @@ app.get('/api/extensions/cli/auth/stream', (c) => {
|
|
|
74
52
|
c.header('X-Accel-Buffering', 'no');
|
|
75
53
|
return stream(c, async (s) => {
|
|
76
54
|
const format = (ev) => `data: ${JSON.stringify(ev)}\n\n`;
|
|
77
|
-
// Replay backlog so a late client still sees what has been printed.
|
|
78
55
|
if (session.backlog.length) {
|
|
79
56
|
for (const chunk of session.backlog) {
|
|
80
57
|
await s.write(format({ type: 'output', chunk }));
|
|
@@ -101,7 +78,6 @@ app.get('/api/extensions/cli/auth/stream', (c) => {
|
|
|
101
78
|
await onEvent({ type: 'done', ok: session.ok, exitCode: session.exitCode });
|
|
102
79
|
return;
|
|
103
80
|
}
|
|
104
|
-
// Heartbeat every 15s so proxies don't close the stream on us.
|
|
105
81
|
const heartbeat = setInterval(() => {
|
|
106
82
|
if (closed) {
|
|
107
83
|
clearInterval(heartbeat);
|
|
@@ -142,9 +118,6 @@ app.post('/api/extensions/cli/auth/cancel', async (c) => {
|
|
|
142
118
|
return c.json({ ok: false, error: e?.message || 'cancel failed' }, 500);
|
|
143
119
|
}
|
|
144
120
|
});
|
|
145
|
-
// ---------------------------------------------------------------------------
|
|
146
|
-
// Token auth
|
|
147
|
-
// ---------------------------------------------------------------------------
|
|
148
121
|
app.post('/api/extensions/cli/auth/token', async (c) => {
|
|
149
122
|
try {
|
|
150
123
|
const { id, values } = await c.req.json();
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Dashboard API routes: configuration, channels, extensions, permissions.
|
|
3
|
-
*/
|
|
4
1
|
import { Hono } from 'hono';
|
|
5
2
|
import fs from 'node:fs';
|
|
6
3
|
import path from 'node:path';
|
|
@@ -20,14 +17,8 @@ import { getPermissionsStatus, getHostTerminalApp, isValidPermissionKey, request
|
|
|
20
17
|
import { VERSION } from '../../core/version.js';
|
|
21
18
|
import { runtime } from '../runtime.js';
|
|
22
19
|
import { writeScopedLog } from '../../core/logging.js';
|
|
23
|
-
// ---------------------------------------------------------------------------
|
|
24
|
-
// Helpers
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
20
|
export async function buildBrowserStatusResponse(config = loadUserConfig(), browserState = getManagedBrowserStatus()) {
|
|
27
21
|
const gui = resolveGuiIntegrationConfig(config);
|
|
28
|
-
// In remote mode the local Chrome state is irrelevant — pikiloom attaches to
|
|
29
|
-
// the external CDP endpoint and never launches/manages a local browser. Report
|
|
30
|
-
// the endpoint so the dashboard shows the truth instead of "Chrome missing".
|
|
31
22
|
const remoteCdpUrl = gui.browserEnabled ? getConfiguredRemoteCdpUrl() : null;
|
|
32
23
|
return {
|
|
33
24
|
browser: {
|
|
@@ -238,11 +229,7 @@ function openPathWithTarget(filePath, target, isDirectory, location) {
|
|
|
238
229
|
return;
|
|
239
230
|
}
|
|
240
231
|
}
|
|
241
|
-
// ---------------------------------------------------------------------------
|
|
242
|
-
// Routes
|
|
243
|
-
// ---------------------------------------------------------------------------
|
|
244
232
|
const app = new Hono();
|
|
245
|
-
// Full state (config from file only)
|
|
246
233
|
app.get('/api/state', async (c) => {
|
|
247
234
|
const config = loadUserConfig();
|
|
248
235
|
const setupState = await runtime.buildValidatedSetupState(config);
|
|
@@ -271,7 +258,6 @@ app.get('/api/state', async (c) => {
|
|
|
271
258
|
} : null,
|
|
272
259
|
});
|
|
273
260
|
});
|
|
274
|
-
// Host info
|
|
275
261
|
app.get('/api/host', (c) => {
|
|
276
262
|
const botRef = runtime.getBotRef();
|
|
277
263
|
if (botRef)
|
|
@@ -285,15 +271,10 @@ app.get('/api/host', (c) => {
|
|
|
285
271
|
platform: process.platform, arch: os.arch(),
|
|
286
272
|
});
|
|
287
273
|
});
|
|
288
|
-
// Permissions
|
|
289
274
|
app.get('/api/permissions', (c) => {
|
|
290
275
|
const data = { ...getPermissionsStatus(), hostApp: getHostTerminalApp() };
|
|
291
276
|
return c.json(data);
|
|
292
277
|
});
|
|
293
|
-
// Save config (to ~/.pikiloom/setting.json). Channel reconciliation is
|
|
294
|
-
// handled by ChannelSupervisor via the onUserConfigChange listener — adding,
|
|
295
|
-
// removing, or swapping credentials of an IM channel takes effect in-process
|
|
296
|
-
// without restarting pikiloom.
|
|
297
278
|
app.post('/api/config', async (c) => {
|
|
298
279
|
const body = await c.req.json();
|
|
299
280
|
const merged = { ...loadUserConfig(), ...body };
|
|
@@ -301,7 +282,6 @@ app.post('/api/config', async (c) => {
|
|
|
301
282
|
applyUserConfig(loadUserConfig());
|
|
302
283
|
return c.json({ ok: true, configPath });
|
|
303
284
|
});
|
|
304
|
-
// Validate Telegram token
|
|
305
285
|
app.post('/api/validate-telegram-token', async (c) => {
|
|
306
286
|
const body = await c.req.json();
|
|
307
287
|
const result = await validateTelegramConfig(body.token || '', body.allowedChatIds || '');
|
|
@@ -312,7 +292,6 @@ app.post('/api/validate-telegram-token', async (c) => {
|
|
|
312
292
|
normalizedAllowedChatIds: result.normalizedAllowedChatIds,
|
|
313
293
|
});
|
|
314
294
|
});
|
|
315
|
-
// Validate Feishu credentials
|
|
316
295
|
app.post('/api/validate-feishu-config', async (c) => {
|
|
317
296
|
const body = await c.req.json();
|
|
318
297
|
const startedAt = Date.now();
|
|
@@ -331,7 +310,6 @@ app.post('/api/validate-feishu-config', async (c) => {
|
|
|
331
310
|
app: result.app,
|
|
332
311
|
});
|
|
333
312
|
});
|
|
334
|
-
// Validate Weixin credentials
|
|
335
313
|
app.post('/api/validate-weixin-config', async (c) => {
|
|
336
314
|
const body = await c.req.json();
|
|
337
315
|
const result = await validateWeixinConfig(body.baseUrl || '', body.botToken || '', body.accountId || '');
|
|
@@ -342,7 +320,6 @@ app.post('/api/validate-weixin-config', async (c) => {
|
|
|
342
320
|
normalizedBaseUrl: result.normalizedBaseUrl,
|
|
343
321
|
});
|
|
344
322
|
});
|
|
345
|
-
// Validate Slack credentials
|
|
346
323
|
app.post('/api/validate-slack-config', async (c) => {
|
|
347
324
|
const body = await c.req.json();
|
|
348
325
|
const result = await validateSlackConfig(body.botToken || '', body.appToken || '');
|
|
@@ -352,7 +329,6 @@ app.post('/api/validate-slack-config', async (c) => {
|
|
|
352
329
|
bot: result.bot,
|
|
353
330
|
});
|
|
354
331
|
});
|
|
355
|
-
// Validate Discord credentials
|
|
356
332
|
app.post('/api/validate-discord-config', async (c) => {
|
|
357
333
|
const body = await c.req.json();
|
|
358
334
|
const result = await validateDiscordConfig(body.botToken || '');
|
|
@@ -362,7 +338,6 @@ app.post('/api/validate-discord-config', async (c) => {
|
|
|
362
338
|
bot: result.bot,
|
|
363
339
|
});
|
|
364
340
|
});
|
|
365
|
-
// Validate DingTalk credentials
|
|
366
341
|
app.post('/api/validate-dingtalk-config', async (c) => {
|
|
367
342
|
const body = await c.req.json();
|
|
368
343
|
const result = await validateDingtalkConfig(body.clientId || '', body.clientSecret || '');
|
|
@@ -372,7 +347,6 @@ app.post('/api/validate-dingtalk-config', async (c) => {
|
|
|
372
347
|
app: result.app,
|
|
373
348
|
});
|
|
374
349
|
});
|
|
375
|
-
// Validate WeChat Work (企业微信) credentials
|
|
376
350
|
app.post('/api/validate-wecom-config', async (c) => {
|
|
377
351
|
const body = await c.req.json();
|
|
378
352
|
const result = await validateWecomConfig(body.botId || '', body.botSecret || '');
|
|
@@ -382,7 +356,6 @@ app.post('/api/validate-wecom-config', async (c) => {
|
|
|
382
356
|
bot: result.bot,
|
|
383
357
|
});
|
|
384
358
|
});
|
|
385
|
-
// Start Weixin QR login
|
|
386
359
|
app.post('/api/weixin-login/start', async (c) => {
|
|
387
360
|
const body = await c.req.json();
|
|
388
361
|
const result = await startWeixinQrLogin({
|
|
@@ -391,7 +364,6 @@ app.post('/api/weixin-login/start', async (c) => {
|
|
|
391
364
|
});
|
|
392
365
|
return c.json(result, result.ok ? 200 : 500);
|
|
393
366
|
});
|
|
394
|
-
// Wait for Weixin QR login
|
|
395
367
|
app.post('/api/weixin-login/wait', async (c) => {
|
|
396
368
|
const body = await c.req.json();
|
|
397
369
|
const result = await waitForWeixinQrLogin({
|
|
@@ -400,7 +372,6 @@ app.post('/api/weixin-login/wait', async (c) => {
|
|
|
400
372
|
});
|
|
401
373
|
return c.json(result, result.ok ? 200 : 500);
|
|
402
374
|
});
|
|
403
|
-
// Open macOS preferences
|
|
404
375
|
app.post('/api/open-preferences', async (c) => {
|
|
405
376
|
const body = await c.req.json();
|
|
406
377
|
const permission = String(body.permission || '');
|
|
@@ -417,15 +388,7 @@ app.post('/api/open-preferences', async (c) => {
|
|
|
417
388
|
runtime.log(`[permissions] permission=${permission} action=${result.action} granted=${result.granted} manual=${result.requiresManualGrant} ok=${result.ok}`);
|
|
418
389
|
return c.json(result, result.ok ? 200 : 500);
|
|
419
390
|
});
|
|
420
|
-
// Restart process
|
|
421
391
|
app.post('/api/restart', (c) => {
|
|
422
|
-
// A successful restart exits this process, so its result can never reach the
|
|
423
|
-
// client — the route fires it async and returns ok:true. But the restart is
|
|
424
|
-
// REFUSED while any turn is still running (requestProcessRestart guards on
|
|
425
|
-
// active tasks so it won't kill in-flight work). Surface that refusal
|
|
426
|
-
// synchronously: otherwise the dashboard sends ok:true into a "reconnecting"
|
|
427
|
-
// wait for a restart that never happens and shows a generic failure, with no
|
|
428
|
-
// hint that a running task is the reason.
|
|
429
392
|
const activeTasks = getActiveTaskCount();
|
|
430
393
|
if (activeTasks > 0) {
|
|
431
394
|
return c.json({
|
|
@@ -439,7 +402,6 @@ app.post('/api/restart', (c) => {
|
|
|
439
402
|
}, 50);
|
|
440
403
|
return c.json({ ok: true });
|
|
441
404
|
});
|
|
442
|
-
// Switch workdir
|
|
443
405
|
app.post('/api/switch-workdir', async (c) => {
|
|
444
406
|
const body = await c.req.json();
|
|
445
407
|
const newPath = body.path;
|
|
@@ -455,13 +417,11 @@ app.post('/api/switch-workdir', async (c) => {
|
|
|
455
417
|
const saved = setUserWorkdir(resolvedPath);
|
|
456
418
|
return c.json({ ok: true, workdir: saved.workdir });
|
|
457
419
|
});
|
|
458
|
-
// Browser profile status
|
|
459
420
|
app.get('/api/browser', async (c) => {
|
|
460
421
|
const config = loadUserConfig();
|
|
461
422
|
const data = await buildBrowserStatusResponse(config);
|
|
462
423
|
return c.json(data);
|
|
463
424
|
});
|
|
464
|
-
// Launch managed browser profile for login/setup
|
|
465
425
|
app.post('/api/browser/setup', async (c) => {
|
|
466
426
|
runtime.log('[browser] setup requested');
|
|
467
427
|
try {
|
|
@@ -473,9 +433,6 @@ app.post('/api/browser/setup', async (c) => {
|
|
|
473
433
|
error: 'Browser automation is disabled. Enable it first if you want pikiloom to launch the managed browser profile.',
|
|
474
434
|
}, 400);
|
|
475
435
|
}
|
|
476
|
-
// Remote mode: pikiloom attaches to an external CDP endpoint and owns no
|
|
477
|
-
// local Chrome. There is nothing to launch — return the (enabled) status as
|
|
478
|
-
// a clean no-op instead of failing with "Chrome is not available".
|
|
479
436
|
if (getConfiguredRemoteCdpUrl()) {
|
|
480
437
|
runtime.log('[browser] setup skipped: PIKILOOM_BROWSER_CDP_URL configured (external CDP, no local browser to launch)');
|
|
481
438
|
return c.json({ ok: true, ...(await buildBrowserStatusResponse(config)) });
|
|
@@ -499,7 +456,6 @@ app.post('/api/browser/setup', async (c) => {
|
|
|
499
456
|
return c.json({ ok: false, error: detail }, 500);
|
|
500
457
|
}
|
|
501
458
|
});
|
|
502
|
-
// List directory entries for tree browser
|
|
503
459
|
app.get('/api/ls-dir', (c) => {
|
|
504
460
|
const dir = c.req.query('path') || os.homedir();
|
|
505
461
|
const includeFiles = c.req.query('files') === '1';
|
|
@@ -521,7 +477,6 @@ app.get('/api/ls-dir', (c) => {
|
|
|
521
477
|
return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 400);
|
|
522
478
|
}
|
|
523
479
|
});
|
|
524
|
-
// Git changes for a directory (uncommitted + staged)
|
|
525
480
|
app.get('/api/git-changes', (c) => {
|
|
526
481
|
const dir = c.req.query('path');
|
|
527
482
|
if (!dir)
|
|
@@ -530,7 +485,6 @@ app.get('/api/git-changes', (c) => {
|
|
|
530
485
|
if (!fs.existsSync(path.join(dir, '.git'))) {
|
|
531
486
|
return c.json({ ok: true, changes: [], isGit: false });
|
|
532
487
|
}
|
|
533
|
-
// --no-optional-locks avoids contention with other git processes
|
|
534
488
|
const result = spawnSync('git', ['diff', '--name-status', 'HEAD', '--no-renames'], {
|
|
535
489
|
cwd: dir,
|
|
536
490
|
timeout: 5_000,
|
|
@@ -555,8 +509,6 @@ app.get('/api/git-changes', (c) => {
|
|
|
555
509
|
return c.json({ ok: false, error: err instanceof Error ? err.message : String(err) }, 500);
|
|
556
510
|
}
|
|
557
511
|
});
|
|
558
|
-
// Git status summary for a workspace: branch, ahead/behind, dirty count.
|
|
559
|
-
// Backed by the same readGitStatus helper that feeds the IM /status command.
|
|
560
512
|
app.get('/api/workspace-git', (c) => {
|
|
561
513
|
const dir = c.req.query('path');
|
|
562
514
|
if (!dir)
|
|
@@ -564,7 +516,6 @@ app.get('/api/workspace-git', (c) => {
|
|
|
564
516
|
const git = readGitStatus(dir);
|
|
565
517
|
return c.json({ ok: true, isGit: git !== null, git });
|
|
566
518
|
});
|
|
567
|
-
// Open file/directory in a selected editor or file browser
|
|
568
519
|
app.post('/api/open-in-editor', async (c) => {
|
|
569
520
|
try {
|
|
570
521
|
const body = await c.req.json();
|
|
@@ -590,7 +541,6 @@ app.post('/api/open-in-editor', async (c) => {
|
|
|
590
541
|
return c.json({ ok: false, error: detail }, 500);
|
|
591
542
|
}
|
|
592
543
|
});
|
|
593
|
-
// Open git diff for a file in the selected editor
|
|
594
544
|
app.post('/api/open-diff', async (c) => {
|
|
595
545
|
try {
|
|
596
546
|
const body = await c.req.json();
|
|
@@ -600,7 +550,6 @@ app.post('/api/open-diff', async (c) => {
|
|
|
600
550
|
return c.json({ ok: false, error: 'filePath is required' }, 400);
|
|
601
551
|
const dir = path.dirname(filePath);
|
|
602
552
|
const relFile = path.basename(filePath);
|
|
603
|
-
// Write the original (HEAD) version to a temp file
|
|
604
553
|
const origResult = spawnSync('git', ['show', `HEAD:${path.relative(findGitRoot(dir), filePath)}`], {
|
|
605
554
|
cwd: dir,
|
|
606
555
|
timeout: 5_000,
|
|
@@ -608,14 +557,12 @@ app.post('/api/open-diff', async (c) => {
|
|
|
608
557
|
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0' },
|
|
609
558
|
});
|
|
610
559
|
if (origResult.status !== 0) {
|
|
611
|
-
// New file — no HEAD version, just open the file
|
|
612
560
|
openPathWithTarget(filePath, target, false);
|
|
613
561
|
return c.json({ ok: true });
|
|
614
562
|
}
|
|
615
563
|
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'pikiloom-diff-'));
|
|
616
564
|
const origPath = path.join(tmpDir, `${relFile}.orig`);
|
|
617
565
|
fs.writeFileSync(origPath, origResult.stdout);
|
|
618
|
-
// Use editor CLI diff command (fire-and-forget)
|
|
619
566
|
const cli = target === 'cursor' ? 'cursor' : target === 'windsurf' ? 'windsurf' : 'code';
|
|
620
567
|
const child = spawn(cli, ['--diff', origPath, filePath], {
|
|
621
568
|
cwd: dir,
|
|
@@ -623,7 +570,6 @@ app.post('/api/open-diff', async (c) => {
|
|
|
623
570
|
detached: true,
|
|
624
571
|
});
|
|
625
572
|
child.unref();
|
|
626
|
-
// Clean up temp after a delay
|
|
627
573
|
setTimeout(() => fs.promises.rm(tmpDir, { recursive: true, force: true }).catch(() => { }), 30_000);
|
|
628
574
|
return c.json({ ok: true });
|
|
629
575
|
}
|