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.
Files changed (156) hide show
  1. package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DtjuI8NU.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BIx6a5o1.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-BINh_7NB.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-CHpd147b.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C4DbGxbG.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-D_PUi-5O.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-BahDEMqf.js} +1 -1
  8. package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-Cp5VXb_2.js} +1 -1
  9. package/dashboard/dist/assets/SessionPanel-DZVNXptx.js +1 -0
  10. package/dashboard/dist/assets/SystemTab-CLznS31f.js +1 -0
  11. package/dashboard/dist/assets/index-CLGovkkw.js +3 -0
  12. package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
  13. package/dashboard/dist/assets/index-DcYOI6rL.js +23 -0
  14. package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C8DCTJ44.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/acp-client.js +0 -59
  17. package/dist/agent/artifacts.js +1 -38
  18. package/dist/agent/auto-update.js +2 -78
  19. package/dist/agent/await-resume.js +0 -20
  20. package/dist/agent/cli/auth.js +3 -51
  21. package/dist/agent/cli/catalog.js +0 -3
  22. package/dist/agent/cli/detector.js +1 -19
  23. package/dist/agent/cli/index.js +0 -3
  24. package/dist/agent/cli/registry.js +0 -25
  25. package/dist/agent/driver.js +0 -14
  26. package/dist/agent/drivers/claude-tui.js +2 -769
  27. package/dist/agent/drivers/claude.js +12 -631
  28. package/dist/agent/drivers/codex.js +11 -162
  29. package/dist/agent/drivers/gemini.js +6 -114
  30. package/dist/agent/drivers/hermes.js +0 -128
  31. package/dist/agent/goal.js +0 -45
  32. package/dist/agent/handover.js +1 -46
  33. package/dist/agent/images.js +7 -133
  34. package/dist/agent/index.js +1 -29
  35. package/dist/agent/mcp/bridge.js +1 -197
  36. package/dist/agent/mcp/extensions.js +5 -84
  37. package/dist/agent/mcp/oauth.js +2 -69
  38. package/dist/agent/mcp/registry.js +2 -33
  39. package/dist/agent/mcp/session-server.js +2 -45
  40. package/dist/agent/mcp/tools/ask-user.js +0 -6
  41. package/dist/agent/mcp/tools/await-resume.js +0 -15
  42. package/dist/agent/mcp/tools/goal.js +0 -25
  43. package/dist/agent/mcp/tools/types.js +0 -5
  44. package/dist/agent/mcp/tools/workspace.js +0 -23
  45. package/dist/agent/npm.js +0 -14
  46. package/dist/agent/session.js +86 -196
  47. package/dist/agent/skill-installer.js +3 -70
  48. package/dist/agent/skills.js +0 -43
  49. package/dist/agent/stream.js +5 -148
  50. package/dist/agent/types.js +0 -12
  51. package/dist/agent/utils.js +2 -120
  52. package/dist/bot/bot.js +3 -468
  53. package/dist/bot/command-ui.js +0 -45
  54. package/dist/bot/commands.js +0 -51
  55. package/dist/bot/headless-bot.js +0 -21
  56. package/dist/bot/host.js +1 -5
  57. package/dist/bot/human-loop.js +0 -8
  58. package/dist/bot/menu.js +0 -3
  59. package/dist/bot/orchestration.js +0 -3
  60. package/dist/bot/render-shared.js +0 -63
  61. package/dist/bot/session-hub.js +14 -92
  62. package/dist/bot/session-status.js +1 -5
  63. package/dist/bot/streaming.js +0 -53
  64. package/dist/browser-profile.js +0 -51
  65. package/dist/browser-supervisor.js +0 -75
  66. package/dist/catalog/cli-tools.js +0 -53
  67. package/dist/catalog/index.js +0 -18
  68. package/dist/catalog/local-models.js +0 -18
  69. package/dist/catalog/mcp-servers.js +0 -35
  70. package/dist/catalog/skill-repos.js +0 -32
  71. package/dist/channels/base.js +0 -19
  72. package/dist/channels/dingtalk/bot.js +0 -3
  73. package/dist/channels/dingtalk/channel.js +0 -33
  74. package/dist/channels/discord/bot.js +0 -7
  75. package/dist/channels/discord/channel.js +0 -22
  76. package/dist/channels/feishu/bot.js +0 -71
  77. package/dist/channels/feishu/channel.js +0 -78
  78. package/dist/channels/feishu/markdown.js +0 -5
  79. package/dist/channels/feishu/render.js +0 -45
  80. package/dist/channels/health.js +0 -46
  81. package/dist/channels/slack/bot.js +0 -7
  82. package/dist/channels/slack/channel.js +1 -27
  83. package/dist/channels/states.js +0 -3
  84. package/dist/channels/telegram/bot.js +0 -60
  85. package/dist/channels/telegram/channel.js +2 -99
  86. package/dist/channels/telegram/directory.js +0 -3
  87. package/dist/channels/telegram/live-preview.js +0 -8
  88. package/dist/channels/telegram/render.js +0 -30
  89. package/dist/channels/wecom/bot.js +0 -3
  90. package/dist/channels/wecom/channel.js +0 -58
  91. package/dist/channels/weixin/api.js +0 -9
  92. package/dist/channels/weixin/bot.js +0 -88
  93. package/dist/channels/weixin/channel.js +0 -6
  94. package/dist/cli/autostart.js +1 -44
  95. package/dist/cli/channel-supervisor.js +1 -59
  96. package/dist/cli/channels.js +0 -6
  97. package/dist/cli/main.js +1 -145
  98. package/dist/cli/onboarding.js +0 -6
  99. package/dist/cli/run.js +0 -11
  100. package/dist/cli/setup-wizard.js +0 -3
  101. package/dist/core/config/runtime-config.js +0 -38
  102. package/dist/core/config/user-config.js +0 -63
  103. package/dist/core/config/validation.js +0 -9
  104. package/dist/core/constants.js +0 -268
  105. package/dist/core/git.js +0 -31
  106. package/dist/core/legacy-compat.js +1 -28
  107. package/dist/core/logging.js +0 -3
  108. package/dist/core/platform.js +0 -28
  109. package/dist/core/process-control.js +0 -8
  110. package/dist/core/secrets/index.js +0 -17
  111. package/dist/core/secrets/inline-seal.js +0 -7
  112. package/dist/core/secrets/ref.js +0 -19
  113. package/dist/core/secrets/resolver.js +0 -8
  114. package/dist/core/secrets/store.js +1 -12
  115. package/dist/core/utils.js +1 -37
  116. package/dist/core/version.js +0 -3
  117. package/dist/dashboard/platform.js +1 -35
  118. package/dist/dashboard/routes/agents.js +2 -71
  119. package/dist/dashboard/routes/cli.js +0 -27
  120. package/dist/dashboard/routes/config.js +0 -54
  121. package/dist/dashboard/routes/extensions.js +0 -132
  122. package/dist/dashboard/routes/local-models.js +0 -68
  123. package/dist/dashboard/routes/models.js +0 -46
  124. package/dist/dashboard/routes/sessions.js +0 -100
  125. package/dist/dashboard/runtime.js +1 -58
  126. package/dist/dashboard/server.js +0 -49
  127. package/dist/dashboard/session-control.js +2 -69
  128. package/dist/model/catalog.js +1 -17
  129. package/dist/model/index.js +0 -15
  130. package/dist/model/injector.js +1 -181
  131. package/dist/model/provider-models.js +1 -48
  132. package/dist/model/responses-bridge.js +3 -45
  133. package/dist/model/store.js +0 -17
  134. package/dist/model/types.js +0 -12
  135. package/dist/model/validation.js +1 -18
  136. package/dist/pikichannel/adapter-pikiloom.js +0 -31
  137. package/dist/pikichannel/code.js +0 -12
  138. package/dist/pikichannel/codec.js +0 -14
  139. package/dist/pikichannel/host.js +4 -33
  140. package/dist/pikichannel/protocol.js +0 -52
  141. package/dist/pikichannel/rendezvous-broker.js +5 -26
  142. package/dist/pikichannel/rendezvous-host.js +3 -16
  143. package/dist/pikichannel/server.js +6 -55
  144. package/dist/pikichannel/transport.js +0 -25
  145. package/dist/pikichannel/transports/webrtc-host.js +0 -18
  146. package/dist/pikichannel/transports/webrtc-shared.js +4 -34
  147. package/dist/pikichannel/transports/websocket-host.js +1 -14
  148. package/dist/pikichannel/turn.js +2 -69
  149. package/dist/pikichannel/web/sdk.js +14 -71
  150. package/package.json +1 -1
  151. package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
  152. package/dashboard/dist/assets/SessionPanel-_0dLWvEb.js +0 -1
  153. package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
  154. package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
  155. package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
  156. 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 { /* best effort */ }
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 { /* tolerate driver errors */ }
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
  }