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.
Files changed (155) hide show
  1. package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
  4. package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
  5. package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
  7. package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
  8. package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
  9. package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
  10. package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
  11. package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
  12. package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
  13. package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
  14. package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.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/SystemTab-CN_QrRKt.js +0 -1
  153. package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
  154. package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
  155. package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
package/dist/cli/main.js CHANGED
@@ -1,14 +1,6 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * cli.ts — CLI entry point for pikiloom.
4
- */
5
- // Mark this process as a Claude Code context so nested claude launches are blocked.
6
- // The spawn framework in code-agent.ts strips this before launching agent subprocesses.
7
2
  process.env.CLAUDECODE = '1';
8
3
  import { hydrateLegacyEnv, migrateLegacyStateDir } from '../core/legacy-compat.js';
9
- // Backward-compat for the pikiclaw → pikiloom rename. Runs before any config is
10
- // read or lock taken: mirror PIKICLAW_* → PIKILOOM_* and move ~/.pikiclaw →
11
- // ~/.pikiloom. Both are idempotent no-ops once an install has migrated.
12
4
  hydrateLegacyEnv();
13
5
  migrateLegacyStateDir();
14
6
  import { spawn } from 'node:child_process';
@@ -27,7 +19,6 @@ import { runSetupWizard } from './setup-wizard.js';
27
19
  import { FROM_LAUNCHD_ENV, maybePromptAutostart } from './autostart.js';
28
20
  import { applyUserConfig, loadUserConfig, startUserConfigSync, updateUserConfig, } from '../core/config/user-config.js';
29
21
  import { VERSION } from '../core/version.js';
30
- /* ── Daemon (watchdog) mode ─────────────────────────────────────────── */
31
22
  const DAEMON_RESTART_DELAY_MS = DAEMON_TIMEOUTS.restartDelay;
32
23
  const DAEMON_MAX_RESTART_DELAY_MS = DAEMON_TIMEOUTS.maxRestartDelay;
33
24
  const DAEMON_RAPID_CRASH_WINDOW_MS = DAEMON_TIMEOUTS.rapidCrashWindow;
@@ -35,28 +26,13 @@ function daemonLog(msg) {
35
26
  const ts = new Date().toTimeString().slice(0, 8);
36
27
  process.stdout.write(`[daemon ${ts}] ${msg}\n`);
37
28
  }
38
- /** Args that are daemon-specific and should not be forwarded to the child. */
39
29
  const DAEMON_STRIP_ARGS = new Set(['--daemon', '--no-daemon']);
40
- /**
41
- * Runs the bot as a supervised child process. On non-zero exit the child is
42
- * restarted with exponential back-off. A clean exit (code 0) stops the daemon.
43
- * Restart requests use a dedicated exit code and are respawned immediately.
44
- */
45
30
  async function runDaemon(userArgs) {
46
- // Forward user's CLI args (strip daemon-related flags).
47
31
  const forwardedArgs = userArgs.filter(a => !DAEMON_STRIP_ARGS.has(a));
48
32
  const restartCmd = process.env.PIKILOOM_RESTART_CMD;
49
33
  const restartStateFile = createRestartStateFilePath(process.pid);
50
- // Publish the daemon PID so `pikiloom stop` can find it. Clean up on any
51
- // exit path so a stale file never points at someone else's PID.
52
34
  writeDaemonPidFile(process.pid);
53
35
  process.once('exit', clearDaemonPidFile);
54
- // Auto-start enrollment: only when the user explicitly typed `--daemon`
55
- // (the watchdog itself is on by default, so we use the explicit flag as
56
- // the signal of "I'm settling in for long-term use"). Fire-and-forget so
57
- // the bot still comes up immediately; the dialog appears a few seconds
58
- // later. No-op when already enrolled, declined, non-interactive, or
59
- // already running under launchd — see src/cli/autostart.ts.
60
36
  if (userArgs.includes('--daemon')) {
61
37
  maybePromptAutostart(daemonLog);
62
38
  }
@@ -67,7 +43,6 @@ async function runDaemon(userArgs) {
67
43
  clearRestartStateFile(restartStateFile);
68
44
  const { bin, args } = buildRestartCommand(forwardedArgs, restartCmd);
69
45
  daemonLog(`exec: ${bin} ${args.join(' ')}`);
70
- // npx/npx.cmd needs shell resolution; node.exe does not
71
46
  const needsShell = process.platform === 'win32' && !bin.endsWith('node.exe');
72
47
  return spawn(needsShell ? `"${bin}"` : bin, args, {
73
48
  stdio: 'inherit',
@@ -90,7 +65,6 @@ async function runDaemon(userArgs) {
90
65
  daemonLog(`child running (pid=${child.pid})`);
91
66
  const startedAt = Date.now();
92
67
  let shutdownSignal = null;
93
- // Forward termination and restart signals to the active child.
94
68
  const forwardShutdownSignal = (sig) => {
95
69
  shutdownSignal = sig;
96
70
  child.kill(sig);
@@ -118,15 +92,13 @@ async function runDaemon(userArgs) {
118
92
  daemonLog('child requested restart, respawning immediately');
119
93
  continue;
120
94
  }
121
- // Clean exit → stop daemon.
122
95
  if (code === 0 || code === null) {
123
96
  daemonLog(`child exited cleanly (code=${code}), daemon stopping`);
124
97
  process.exit(0);
125
98
  }
126
- // Exponential back-off for rapid crashes.
127
99
  const uptime = Date.now() - startedAt;
128
100
  if (uptime > DAEMON_RAPID_CRASH_WINDOW_MS) {
129
- restartDelay = DAEMON_RESTART_DELAY_MS; // reset if it ran for a while
101
+ restartDelay = DAEMON_RESTART_DELAY_MS;
130
102
  }
131
103
  else {
132
104
  restartDelay = Math.min(restartDelay * 2, DAEMON_MAX_RESTART_DELAY_MS);
@@ -215,15 +187,12 @@ function parseArgs(argv) {
215
187
  }
216
188
  return args;
217
189
  }
218
- /* ── Shared helpers ────────────────────────────────────────────────── */
219
190
  function processLog(message) {
220
191
  const ts = new Date().toTimeString().slice(0, 8);
221
192
  process.stdout.write(`[pikiloom ${ts}] ${message}\n`);
222
193
  }
223
194
  const listStartupAgents = () => listAgents().agents;
224
195
  const listVerboseAgents = () => listAgents({ includeVersion: true }).agents;
225
- /* ── Phase: early exits (MCP serve, --version, --help) ────────────── */
226
- /** If launched as an MCP stdio server, run that and exit. */
227
196
  async function handleMcpServeMode() {
228
197
  if (process.argv.includes('--mcp-serve')) {
229
198
  await import('../agent/mcp/session-server.js');
@@ -231,7 +200,6 @@ async function handleMcpServeMode() {
231
200
  }
232
201
  return false;
233
202
  }
234
- /** Print help text and exit. */
235
203
  function printHelp() {
236
204
  process.stdout.write(`pikiloom v${VERSION} — Run local coding agents through IM.
237
205
 
@@ -321,28 +289,11 @@ Docs: https://github.com/xiaotonng/pikiloom
321
289
  `);
322
290
  process.exit(0);
323
291
  }
324
- /* ── Phase: workdir persistence & daemon handoff ──────────────────── */
325
- /**
326
- * For a fresh CLI launch (not a daemon-managed child), persist the working
327
- * directory into setting.json so the bot defaults to where the user invoked
328
- * the command. An explicit `-w` always wins and is saved. Without `-w`, a
329
- * previously saved workdir (including one chosen later in the dashboard) is
330
- * kept as-is; we only fall back to the launch cwd on first run, when nothing
331
- * has been saved yet, so relaunching never silently reverts the workdir.
332
- */
333
292
  function persistWorkdir(args, userConfig) {
334
293
  if (process.env.PIKILOOM_DAEMON_CHILD)
335
294
  return userConfig;
336
- // launchd launches the process from `/`; without `-w`, that would clobber
337
- // the user's saved workdir to "/". Skip persistence so the config stays
338
- // whatever the user previously chose interactively.
339
295
  if (process.env[FROM_LAUNCHD_ENV])
340
296
  return userConfig;
341
- // An explicit `-w` is a deliberate choice and always wins. Without it, a
342
- // workdir the user already saved (e.g. picked in the dashboard via
343
- // `/api/switch-workdir`) must survive relaunch — otherwise every fresh
344
- // `npx pikiloom` silently reverts the workdir to its launch cwd. Only fall
345
- // back to cwd on first run, when nothing has been saved yet.
346
297
  const explicitWorkdir = typeof args.workdir === 'string' && args.workdir.trim()
347
298
  ? args.workdir.trim()
348
299
  : '';
@@ -354,21 +305,14 @@ function persistWorkdir(args, userConfig) {
354
305
  updateUserConfig({ workdir: nextWorkdir });
355
306
  return loadUserConfig();
356
307
  }
357
- /**
358
- * If daemon mode is active and we are the top-level process, become the
359
- * watchdog. This function never returns in daemon mode.
360
- */
361
308
  async function enterDaemonIfNeeded(args) {
362
309
  if (args.daemon && !process.env.PIKILOOM_DAEMON_CHILD) {
363
310
  await runDaemon(process.argv.slice(2));
364
311
  }
365
312
  if (!args.daemon) {
366
- // --no-daemon: clear inherited env so requestProcessRestart uses the
367
- // direct-spawn path instead of handing off to a non-existent daemon.
368
313
  delete process.env.PIKILOOM_DAEMON_CHILD;
369
314
  }
370
315
  }
371
- /** Install SIGUSR2 restart handler and clean it up on exit. */
372
316
  function installRestartSignalHandler() {
373
317
  const onSigusr2 = () => {
374
318
  processLog('SIGUSR2 received, restarting...');
@@ -379,21 +323,12 @@ function installRestartSignalHandler() {
379
323
  process.off('SIGUSR2', onSigusr2);
380
324
  });
381
325
  }
382
- /**
383
- * Top-level shutdown safety net. Channels install their own SIGINT/SIGTERM
384
- * handlers that do per-channel cleanup with a 3 s unref-ed force-exit timer,
385
- * but those handlers only exist while a channel is running and silently fail
386
- * if cleanup throws before the timer is set. This handler is the last-resort
387
- * guarantee: once the user hits Ctrl+C, the process exits within the grace
388
- * window no matter what state we're in.
389
- */
390
326
  function installTopLevelShutdownHandler() {
391
327
  const GRACE_MS = 5_000;
392
328
  let shuttingDown = false;
393
329
  const onSignal = (sig) => {
394
330
  const exitCode = sig === 'SIGINT' ? 130 : 143;
395
331
  if (shuttingDown) {
396
- // Second Ctrl+C — bail immediately.
397
332
  processLog(`${sig} again, forcing immediate exit`);
398
333
  process.exit(exitCode);
399
334
  }
@@ -404,12 +339,6 @@ function installTopLevelShutdownHandler() {
404
339
  process.on('SIGINT', () => onSignal('SIGINT'));
405
340
  process.on('SIGTERM', () => onSignal('SIGTERM'));
406
341
  }
407
- /* ── Phase: stop subcommand ───────────────────────────────────────── */
408
- /**
409
- * Find and terminate the running daemon. Reads the PID file written by
410
- * `runDaemon`, sends SIGTERM, waits briefly, escalates to SIGKILL if the
411
- * process is still alive. Never returns — always exits.
412
- */
413
342
  async function handleStopCommand() {
414
343
  const pid = readDaemonPidFile();
415
344
  if (!pid) {
@@ -435,7 +364,6 @@ async function handleStopCommand() {
435
364
  process.exit(1);
436
365
  }
437
366
  process.stdout.write(`pikiloom stop: SIGTERM → pid ${pid}\n`);
438
- // Poll for up to 8 s; daemon's child needs ~3 s for its force-exit timer.
439
367
  const deadline = Date.now() + 8_000;
440
368
  while (Date.now() < deadline) {
441
369
  if (!isProcessAlive(pid)) {
@@ -445,7 +373,6 @@ async function handleStopCommand() {
445
373
  }
446
374
  await new Promise(resolve => setTimeout(resolve, 250));
447
375
  }
448
- // Escalate to SIGKILL.
449
376
  process.stderr.write(`pikiloom stop: daemon (pid ${pid}) still alive after 8s, sending SIGKILL\n`);
450
377
  try {
451
378
  process.kill(pid, 'SIGKILL');
@@ -454,8 +381,6 @@ async function handleStopCommand() {
454
381
  clearDaemonPidFile();
455
382
  process.exit(0);
456
383
  }
457
- /* ── Phase: doctor check ──────────────────────────────────────────── */
458
- /** Run setup diagnostics and exit (--doctor). */
459
384
  function runDoctorCheck(channel, tokenProvided) {
460
385
  const setupState = collectSetupState({
461
386
  agents: listVerboseAgents(),
@@ -470,11 +395,6 @@ function runDoctorCheck(channel, tokenProvided) {
470
395
  process.stderr.write(guide);
471
396
  process.exit(ready ? 0 : 1);
472
397
  }
473
- /* ── Phase: setup (dashboard / wizard / guide) ────────────────────── */
474
- /**
475
- * Poll the dashboard until the user completes configuration.
476
- * Mutates `ctx` in place with freshly resolved channels.
477
- */
478
398
  async function awaitDashboardConfig(dashboard, ctx) {
479
399
  const ts = new Date().toTimeString().slice(0, 8);
480
400
  process.stdout.write(`[pikiloom ${ts}] waiting for configuration via dashboard...\n`);
@@ -493,9 +413,6 @@ async function awaitDashboardConfig(dashboard, ctx) {
493
413
  channel,
494
414
  tokenProvided: channels.length > 0 && hasConfiguredChannelToken({ ...ctx.userConfig, ...ctx.configOverrides }, channel, ctx.args.token),
495
415
  });
496
- // Dashboard-as-terminal: an installed agent is the only prerequisite to
497
- // start. IM channels are optional — the dashboard is itself a terminal, so
498
- // don't block startup waiting for a channel token.
499
416
  const nextNeedsSetup = !hasReadyAgent(nextSetupState);
500
417
  if (!nextNeedsSetup) {
501
418
  const resumeTs = new Date().toTimeString().slice(0, 8);
@@ -504,10 +421,6 @@ async function awaitDashboardConfig(dashboard, ctx) {
504
421
  }
505
422
  }
506
423
  }
507
- /**
508
- * Run the setup phase: dashboard wait-loop, terminal wizard, or guide printout.
509
- * Returns the dashboard instance (if started) and possibly-updated userConfig.
510
- */
511
424
  async function runSetupPhase(args, userConfig, configOverrides, channels, channel, tokenProvided) {
512
425
  const setupState = collectSetupState({
513
426
  agents: listStartupAgents(),
@@ -516,16 +429,10 @@ async function runSetupPhase(args, userConfig, configOverrides, channels, channe
516
429
  });
517
430
  const useDashboard = !args.noDashboard && !args.setup;
518
431
  let dashboard = null;
519
- // With the dashboard as a terminal, an installed agent is enough to start.
520
- // Without the dashboard (headless server / --no-dashboard), an IM channel is
521
- // still required as the only terminal.
522
432
  const needsSetup = useDashboard
523
433
  ? !hasReadyAgent(setupState)
524
434
  : (channels.length === 0 || !tokenProvided || !hasReadyAgent(setupState));
525
435
  if (useDashboard) {
526
- // Suppress the browser pop on auto-start when there's no user-facing
527
- // terminal: launchd-spawned bots, Docker/headless server runs, or when
528
- // the user explicitly set PIKILOOM_OPEN_BROWSER=0.
529
436
  const openBrowser = !args.server
530
437
  && !process.env[FROM_LAUNCHD_ENV]
531
438
  && !envBool('PIKILOOM_DOCKER', false)
@@ -568,7 +475,6 @@ async function runSetupPhase(args, userConfig, configOverrides, channels, channe
568
475
  }
569
476
  return { dashboard, userConfig, channels, channel };
570
477
  }
571
- /** Print the shareable connection code for `--server` (headless) mode. */
572
478
  function printServerConnectionCode(dashboard) {
573
479
  const c = loadUserConfig();
574
480
  const sc = buildServerCode({
@@ -590,13 +496,6 @@ function printServerConnectionCode(dashboard) {
590
496
  process.stdout.write(' paste it into a client → 连接 → 互联网/局域网.\n\n');
591
497
  }
592
498
  }
593
- /* ── Phase: post-setup validation ─────────────────────────────────── */
594
- /**
595
- * Re-resolve channels after the setup phase and validate we have a runnable
596
- * terminal: an installed agent is mandatory; an IM channel is required only
597
- * when the dashboard isn't serving as the terminal. Exits on failure.
598
- * Returns the (possibly empty) channel set — empty is valid in dashboard mode.
599
- */
600
499
  function validatePostSetupChannels(configOverrides, userConfig, args, useDashboard) {
601
500
  const effectiveConfig = { ...userConfig, ...configOverrides };
602
501
  const channels = resolveConfiguredChannels({
@@ -609,28 +508,18 @@ function validatePostSetupChannels(configOverrides, userConfig, args, useDashboa
609
508
  channel,
610
509
  tokenProvided: channels.length > 0,
611
510
  });
612
- // An installed agent is the hard requirement — no terminal can run a session
613
- // without one.
614
511
  if (!hasReadyAgent(refreshedSetupState)) {
615
512
  process.stderr.write(buildSetupGuide(refreshedSetupState, VERSION, { doctor: true }));
616
513
  process.exit(1);
617
514
  }
618
- // Zero IM channels is fine when the dashboard is the terminal; only bail when
619
- // there's no terminal at all (dashboard disabled AND no channel configured).
620
515
  if (channels.length === 0 && !useDashboard) {
621
516
  process.stdout.write(buildSetupGuide(refreshedSetupState, VERSION));
622
517
  process.exit(0);
623
518
  }
624
519
  return { channels, channel };
625
520
  }
626
- /* ── Phase: runtime config & env setup ────────────────────────────── */
627
- /**
628
- * Build the final runtime config, apply token/model/permission overrides to
629
- * the environment, start config file sync, and kick off agent auto-update.
630
- */
631
521
  function applyRuntimeConfig(args, userConfig, configOverrides, channel) {
632
522
  const runtimeConfig = { ...userConfig, ...configOverrides };
633
- // Inject CLI token into channel-specific config fields.
634
523
  if (args.token) {
635
524
  if (channel === 'telegram')
636
525
  runtimeConfig.telegramBotToken = args.token;
@@ -648,7 +537,6 @@ function applyRuntimeConfig(args, userConfig, configOverrides, channel) {
648
537
  agents: listAgents({ includeVersion: true, refresh: true }).agents,
649
538
  log: processLog,
650
539
  });
651
- // Model override: route to the correct agent env var.
652
540
  if (args.model) {
653
541
  const ag = args.agent || runtimeConfig.defaultAgent || 'codex';
654
542
  if (ag === 'codex')
@@ -660,7 +548,6 @@ function applyRuntimeConfig(args, userConfig, configOverrides, channel) {
660
548
  }
661
549
  if (args.timeout != null)
662
550
  process.env.PIKILOOM_TIMEOUT = String(args.timeout);
663
- // Permission mode: safe vs full-access.
664
551
  if (args.safeMode) {
665
552
  process.env.CODEX_FULL_ACCESS = 'false';
666
553
  process.env.CLAUDE_PERMISSION_MODE = 'default';
@@ -673,12 +560,6 @@ function applyRuntimeConfig(args, userConfig, configOverrides, channel) {
673
560
  process.env.GEMINI_APPROVAL_MODE = 'yolo';
674
561
  process.env.GEMINI_SANDBOX = 'false';
675
562
  }
676
- // Live-reload config file sync.
677
- //
678
- // Only pass overrides that came from CLI flags / explicit args, NOT the full
679
- // runtimeConfig. Otherwise a snapshot of every user-managed field (model,
680
- // effort, workdir, etc.) gets re-applied on every sync tick, silently
681
- // reverting changes the user just made via the menu or dashboard.
682
563
  const syncOverrides = {};
683
564
  if (args.agent)
684
565
  syncOverrides.defaultAgent = args.agent;
@@ -700,25 +581,12 @@ function applyRuntimeConfig(args, userConfig, configOverrides, channel) {
700
581
  process.once('exit', stopUserConfigSync);
701
582
  return runtimeConfig;
702
583
  }
703
- /* ── Phase: channel launch ────────────────────────────────────────── */
704
- /**
705
- * Hand off channel lifecycle to ChannelSupervisor and block forever. The
706
- * supervisor reconciles bots against the user config — adding, removing,
707
- * or replacing channels in response to dashboard saves without restarting
708
- * the pikiloom process.
709
- *
710
- * Per-bot signal handlers (and the daemon supervisor when present) drive
711
- * process exit; this promise is just a foreground keep-alive.
712
- */
713
584
  async function launchChannels(channels, dashboard) {
714
585
  processLog(`launching channels: ${channels.join(', ')}`);
715
586
  const supervisor = new ChannelSupervisor({ dashboard, log: processLog });
716
587
  await supervisor.start();
717
- // Block forever — the dashboard HTTP listener and per-channel signal
718
- // handlers keep the process alive and drive shutdown.
719
588
  await new Promise(() => { });
720
589
  }
721
- /* ── main() ───────────────────────────────────────────────────────── */
722
590
  export async function main() {
723
591
  if (await handleMcpServeMode())
724
592
  return;
@@ -732,37 +600,25 @@ export async function main() {
732
600
  printHelp();
733
601
  if (args.stop)
734
602
  await handleStopCommand();
735
- // Persist workdir for fresh (non-daemon-child) launches.
736
603
  userConfig = persistWorkdir(args, userConfig);
737
- // Daemon mode: become watchdog (never returns in daemon mode).
738
604
  await enterDaemonIfNeeded(args);
739
- // Child / no-daemon process: install restart signal handler + top-level
740
- // shutdown safety net so Ctrl+C always brings the process down.
741
605
  installRestartSignalHandler();
742
606
  installTopLevelShutdownHandler();
743
- // Apply config overrides from CLI args.
744
607
  const configOverrides = {};
745
608
  if (args.agent)
746
609
  configOverrides.defaultAgent = args.agent;
747
610
  applyUserConfig({ ...userConfig, ...configOverrides }, undefined, { overwrite: true, clearMissing: true });
748
- // Resolve initial channels.
749
611
  const effectiveConfig = () => ({ ...userConfig, ...configOverrides });
750
612
  let channels = resolveConfiguredChannels({ config: effectiveConfig(), tokenOverride: args.token });
751
613
  let channel = channels[0] || 'feishu';
752
614
  const tokenProvided = channels.length > 0 && hasConfiguredChannelToken(effectiveConfig(), channel, args.token);
753
- // Doctor mode: check and exit.
754
615
  if (args.doctor)
755
616
  runDoctorCheck(channel, tokenProvided);
756
- // Setup phase: dashboard, wizard, or guide.
757
617
  const useDashboard = !args.noDashboard && !args.setup;
758
618
  let dashboard;
759
619
  ({ dashboard, userConfig, channels, channel } = await runSetupPhase(args, userConfig, configOverrides, channels, channel, tokenProvided));
760
- // Validate the terminal is runnable after setup (channels may be empty when
761
- // the dashboard is serving as the terminal).
762
620
  ({ channels, channel } = validatePostSetupChannels(configOverrides, userConfig, args, useDashboard));
763
- // Apply runtime config, env overrides, and start config sync.
764
621
  applyRuntimeConfig(args, userConfig, configOverrides, channel);
765
- // Launch bot channel(s).
766
622
  await launchChannels(channels, dashboard);
767
623
  }
768
624
  main().catch(err => { console.error(err); process.exit(1); });
@@ -1,6 +1,3 @@
1
- /**
2
- * Setup/doctor state assessment and messaging.
3
- */
4
1
  import { getAgentInstallCommand, getAgentLabel } from '../agent/npm.js';
5
2
  function enrichAgent(agent) {
6
3
  const label = getAgentLabel(agent.agent);
@@ -118,9 +115,6 @@ export function hasInstalledAgent(state) {
118
115
  return state.agents.some(agent => agent.installed);
119
116
  }
120
117
  export function isSetupReady(state) {
121
- // The Web Dashboard is always an available terminal, so an installed agent is
122
- // the only hard prerequisite. IM channels are optional add-ons — their
123
- // readiness is surfaced separately (per-channel status), not gated here.
124
118
  return hasReadyAgent(state);
125
119
  }
126
120
  export function buildSetupGuide(state, version, options) {
package/dist/cli/run.js CHANGED
@@ -1,12 +1,4 @@
1
1
  #!/usr/bin/env node
2
- /**
3
- * run.ts — Standalone CLI commands for pikiloom.
4
- *
5
- * Usage:
6
- * npm run command -- status
7
- * npm run command -- claude-models
8
- * npm run command -- codex-models
9
- */
10
2
  import { ensureGitignore, formatThinkingForDisplay, DEFAULT_RUN_TIMEOUT_S } from '../bot/bot.js';
11
3
  import { initializeProjectSkills, listAgents, listModels, listSkills, getUsage, doStream } from '../agent/index.js';
12
4
  import { querySessions, querySessionTail } from '../bot/session-hub.js';
@@ -57,7 +49,6 @@ function parseArgs(argv) {
57
49
  }
58
50
  }
59
51
  args.command = positional[0] ?? null;
60
- // If no -p flag, treat remaining positional args as the prompt
61
52
  if (!args.prompt && positional.length > 1)
62
53
  args.prompt = positional.slice(1).join(' ');
63
54
  return args;
@@ -227,7 +218,6 @@ async function main() {
227
218
  ? 'gemini'
228
219
  : 'claude';
229
220
  let sessionId = args.session;
230
- // Default: find the latest session
231
221
  if (!sessionId) {
232
222
  const sessions = await querySessions({ agent, workdir, limit: 1 });
233
223
  if (!sessions.ok || !sessions.sessions.length) {
@@ -278,7 +268,6 @@ async function main() {
278
268
  };
279
269
  process.stdout.write(`Running ${agent}${args.model ? ` (model: ${args.model})` : ''}...\n`);
280
270
  const result = await doStream(opts);
281
- // Clear the streaming line and print final result
282
271
  process.stdout.write('\r\x1b[K');
283
272
  process.stdout.write(`--- ${agent} result ---\n`);
284
273
  process.stdout.write(`ok: ${result.ok}\n`);
@@ -1,6 +1,3 @@
1
- /**
2
- * Interactive terminal setup wizard.
3
- */
4
1
  import { spawn } from 'node:child_process';
5
2
  import { createInterface } from 'node:readline/promises';
6
3
  import { buildSetupGuide, collectSetupState } from './onboarding.js';
@@ -1,6 +1,3 @@
1
- /**
2
- * Runtime resolution of agent model and effort preferences.
3
- */
4
1
  import { normalizeClaudeModelId } from '../../agent/index.js';
5
2
  export const DEFAULT_AGENT_MODELS = {
6
3
  claude: 'claude-opus-4-8',
@@ -17,7 +14,6 @@ export const DEFAULT_AGENT_EFFORTS = {
17
14
  function trimmed(value) {
18
15
  return typeof value === 'string' ? value.trim() : '';
19
16
  }
20
- /** Parse a boolean-ish string (env var / loose config). Empty → null (unset). */
21
17
  function parseBoolish(value) {
22
18
  const v = value.trim().toLowerCase();
23
19
  if (!v)
@@ -85,17 +81,6 @@ export function resolveAgentEffort(config, agent) {
85
81
  }
86
82
  return null;
87
83
  }
88
- // ---------------------------------------------------------------------------
89
- // Workflow (multi-agent orchestration) toggle
90
- //
91
- // Orthogonal to effort: effort tunes how deeply a *single* agent reasons;
92
- // workflow grants the agent permission to author + run multi-agent Workflow
93
- // orchestrations (fan-out / pipeline / verify). Only agents whose driver
94
- // advertises `capabilities.workflow` honor it (claude today). Default OFF —
95
- // the claude driver hard-disables the Workflow tool unless this is true, so a
96
- // bare "workflow" keyword can never auto-spawn a fleet of sub-agents under the
97
- // bypassPermissions mode pikiloom runs by default.
98
- // ---------------------------------------------------------------------------
99
84
  export function agentWorkflowEnv(agent, env = process.env) {
100
85
  switch (agent) {
101
86
  case 'claude': return trimmed(env.CLAUDE_WORKFLOW);
@@ -121,16 +106,7 @@ export function setAgentWorkflowEnv(agent, value, env = process.env) {
121
106
  break;
122
107
  }
123
108
  }
124
- // Default `api` = headless `claude -p`, the standard integration path. The
125
- // interactive TUI ('subscription', Pro/Max quota) is opt-in via the dashboard
126
- // 接入模式 toggle or PIKILOOM_CLAUDE_TUI=1.
127
109
  export const DEFAULT_CLAUDE_ACCESS_MODE = 'api';
128
- /**
129
- * Access mode implied by env, or null when neither var has an opinion.
130
- * PIKILOOM_CLAUDE_PRINT takes precedence over the legacy PIKILOOM_CLAUDE_TUI so
131
- * a freshly-written print var resolves a stale tui var (setClaudeAccessModeEnv
132
- * relies on this). Mirrors isClaudePrintModeForced() in the claude driver.
133
- */
134
110
  export function claudeAccessModeEnv(env = process.env) {
135
111
  const print = parseBoolish(trimmed(env.PIKILOOM_CLAUDE_PRINT));
136
112
  if (print != null)
@@ -146,8 +122,6 @@ export function resolveClaudeAccessMode(config) {
146
122
  return raw;
147
123
  return claudeAccessModeEnv() ?? DEFAULT_CLAUDE_ACCESS_MODE;
148
124
  }
149
- /** Mirror the choice into PIKILOOM_CLAUDE_PRINT so any env-only fallback path
150
- * (and tooling that inspects the env) agrees with the persisted config. */
151
125
  export function setClaudeAccessModeEnv(value, env = process.env) {
152
126
  env.PIKILOOM_CLAUDE_PRINT = value === 'api' ? '1' : '0';
153
127
  }
@@ -183,18 +157,6 @@ export function setAgentEffortEnv(agent, value, env = process.env) {
183
157
  break;
184
158
  }
185
159
  }
186
- // ---------------------------------------------------------------------------
187
- // "Ultra" effort rung — the single user-facing knob that folds workflow in
188
- //
189
- // Surfaced as the top rung of every effort picker (IM /models + dashboard),
190
- // "ultra" means "max reasoning depth + permit multi-agent Workflow
191
- // orchestration" — the same bundle as Claude's native `ultracode` mode. It is
192
- // NOT a real --effort value (the CLI hard-rejects anything outside
193
- // low|medium|high|xhigh|max), so every effort-write path decomposes it into a
194
- // concrete effort plus the orthogonal workflow flag via this single helper.
195
- // Because the rungs are mutually exclusive, picking any concrete level clears
196
- // the orchestration opt-in. See Bot.switchEffortForChat for the IM mirror.
197
- // ---------------------------------------------------------------------------
198
160
  export const ULTRA_EFFORT = 'ultra';
199
161
  export function decomposeEffortSelection(raw) {
200
162
  const value = trimmed(raw).toLowerCase();