pikiloom 0.4.26 → 0.4.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dashboard/dist/assets/{AgentTab-BNJ73P0m.js → AgentTab-DIP-y4dl.js} +1 -1
- package/dashboard/dist/assets/ConnectionModal-BCfvhkiI.js +1 -0
- package/dashboard/dist/assets/{DirBrowser-D-GT-G-w.js → DirBrowser-Dot7ua9-.js} +1 -1
- package/dashboard/dist/assets/{ExtensionsTab-BrJMulJR.js → ExtensionsTab-GioMx4S6.js} +1 -1
- package/dashboard/dist/assets/{IMAccessTab-auPnx468.js → IMAccessTab-C9_r8jJP.js} +1 -1
- package/dashboard/dist/assets/{Modal-lYfO9m-I.js → Modal-M1AP1sgR.js} +1 -1
- package/dashboard/dist/assets/{Modals-PRdWOIY9.js → Modals-CCm3UUfJ.js} +1 -1
- package/dashboard/dist/assets/{Select-BhbU-rll.js → Select-YubDM16P.js} +1 -1
- package/dashboard/dist/assets/{SessionPanel-_0dLWvEb.js → SessionPanel-Cnv9o96n.js} +1 -1
- package/dashboard/dist/assets/SystemTab-VgNivIe3.js +1 -0
- package/dashboard/dist/assets/index-BY7l20bi.js +23 -0
- package/dashboard/dist/assets/index-BlqvzDUL.js +3 -0
- package/dashboard/dist/assets/index-CXIN3nTr.css +1 -0
- package/dashboard/dist/assets/{shared-C5uXvhZJ.js → shared-C0SAInnT.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/agent/acp-client.js +0 -59
- package/dist/agent/artifacts.js +1 -38
- package/dist/agent/auto-update.js +2 -78
- package/dist/agent/await-resume.js +0 -20
- package/dist/agent/cli/auth.js +3 -51
- package/dist/agent/cli/catalog.js +0 -3
- package/dist/agent/cli/detector.js +1 -19
- package/dist/agent/cli/index.js +0 -3
- package/dist/agent/cli/registry.js +0 -25
- package/dist/agent/driver.js +0 -14
- package/dist/agent/drivers/claude-tui.js +2 -769
- package/dist/agent/drivers/claude.js +12 -631
- package/dist/agent/drivers/codex.js +11 -162
- package/dist/agent/drivers/gemini.js +6 -114
- package/dist/agent/drivers/hermes.js +0 -128
- package/dist/agent/goal.js +0 -45
- package/dist/agent/handover.js +1 -46
- package/dist/agent/images.js +7 -133
- package/dist/agent/index.js +1 -29
- package/dist/agent/mcp/bridge.js +1 -197
- package/dist/agent/mcp/extensions.js +5 -84
- package/dist/agent/mcp/oauth.js +2 -69
- package/dist/agent/mcp/registry.js +2 -33
- package/dist/agent/mcp/session-server.js +2 -45
- package/dist/agent/mcp/tools/ask-user.js +0 -6
- package/dist/agent/mcp/tools/await-resume.js +0 -15
- package/dist/agent/mcp/tools/goal.js +0 -25
- package/dist/agent/mcp/tools/types.js +0 -5
- package/dist/agent/mcp/tools/workspace.js +0 -23
- package/dist/agent/npm.js +0 -14
- package/dist/agent/session.js +86 -196
- package/dist/agent/skill-installer.js +3 -70
- package/dist/agent/skills.js +0 -43
- package/dist/agent/stream.js +5 -148
- package/dist/agent/types.js +0 -12
- package/dist/agent/utils.js +2 -120
- package/dist/bot/bot.js +3 -468
- package/dist/bot/command-ui.js +0 -45
- package/dist/bot/commands.js +0 -51
- package/dist/bot/headless-bot.js +0 -21
- package/dist/bot/host.js +1 -5
- package/dist/bot/human-loop.js +0 -8
- package/dist/bot/menu.js +0 -3
- package/dist/bot/orchestration.js +0 -3
- package/dist/bot/render-shared.js +0 -63
- package/dist/bot/session-hub.js +14 -92
- package/dist/bot/session-status.js +1 -5
- package/dist/bot/streaming.js +0 -53
- package/dist/browser-profile.js +0 -51
- package/dist/browser-supervisor.js +0 -75
- package/dist/catalog/cli-tools.js +0 -53
- package/dist/catalog/index.js +0 -18
- package/dist/catalog/local-models.js +0 -18
- package/dist/catalog/mcp-servers.js +0 -35
- package/dist/catalog/skill-repos.js +0 -32
- package/dist/channels/base.js +0 -19
- package/dist/channels/dingtalk/bot.js +0 -3
- package/dist/channels/dingtalk/channel.js +0 -33
- package/dist/channels/discord/bot.js +0 -7
- package/dist/channels/discord/channel.js +0 -22
- package/dist/channels/feishu/bot.js +0 -71
- package/dist/channels/feishu/channel.js +0 -78
- package/dist/channels/feishu/markdown.js +0 -5
- package/dist/channels/feishu/render.js +0 -45
- package/dist/channels/health.js +0 -46
- package/dist/channels/slack/bot.js +0 -7
- package/dist/channels/slack/channel.js +1 -27
- package/dist/channels/states.js +0 -3
- package/dist/channels/telegram/bot.js +0 -60
- package/dist/channels/telegram/channel.js +2 -99
- package/dist/channels/telegram/directory.js +0 -3
- package/dist/channels/telegram/live-preview.js +0 -8
- package/dist/channels/telegram/render.js +0 -30
- package/dist/channels/wecom/bot.js +0 -3
- package/dist/channels/wecom/channel.js +0 -58
- package/dist/channels/weixin/api.js +0 -9
- package/dist/channels/weixin/bot.js +0 -88
- package/dist/channels/weixin/channel.js +0 -6
- package/dist/cli/autostart.js +1 -44
- package/dist/cli/channel-supervisor.js +1 -59
- package/dist/cli/channels.js +0 -6
- package/dist/cli/main.js +1 -145
- package/dist/cli/onboarding.js +0 -6
- package/dist/cli/run.js +0 -11
- package/dist/cli/setup-wizard.js +0 -3
- package/dist/core/config/runtime-config.js +0 -38
- package/dist/core/config/user-config.js +0 -63
- package/dist/core/config/validation.js +0 -9
- package/dist/core/constants.js +0 -268
- package/dist/core/git.js +0 -31
- package/dist/core/legacy-compat.js +1 -28
- package/dist/core/logging.js +0 -3
- package/dist/core/platform.js +0 -28
- package/dist/core/process-control.js +0 -8
- package/dist/core/secrets/index.js +0 -17
- package/dist/core/secrets/inline-seal.js +0 -7
- package/dist/core/secrets/ref.js +0 -19
- package/dist/core/secrets/resolver.js +0 -8
- package/dist/core/secrets/store.js +1 -12
- package/dist/core/utils.js +1 -37
- package/dist/core/version.js +0 -3
- package/dist/dashboard/platform.js +1 -35
- package/dist/dashboard/routes/agents.js +2 -71
- package/dist/dashboard/routes/cli.js +0 -27
- package/dist/dashboard/routes/config.js +0 -54
- package/dist/dashboard/routes/extensions.js +0 -132
- package/dist/dashboard/routes/local-models.js +0 -68
- package/dist/dashboard/routes/models.js +0 -46
- package/dist/dashboard/routes/sessions.js +0 -100
- package/dist/dashboard/runtime.js +1 -58
- package/dist/dashboard/server.js +0 -49
- package/dist/dashboard/session-control.js +2 -69
- package/dist/model/catalog.js +1 -17
- package/dist/model/index.js +0 -15
- package/dist/model/injector.js +1 -181
- package/dist/model/provider-models.js +1 -48
- package/dist/model/responses-bridge.js +3 -45
- package/dist/model/store.js +0 -17
- package/dist/model/types.js +0 -12
- package/dist/model/validation.js +1 -18
- package/dist/pikichannel/adapter-pikiloom.js +0 -31
- package/dist/pikichannel/code.js +0 -12
- package/dist/pikichannel/codec.js +0 -14
- package/dist/pikichannel/host.js +4 -33
- package/dist/pikichannel/protocol.js +0 -52
- package/dist/pikichannel/rendezvous-broker.js +5 -26
- package/dist/pikichannel/rendezvous-host.js +3 -16
- package/dist/pikichannel/server.js +6 -55
- package/dist/pikichannel/transport.js +0 -25
- package/dist/pikichannel/transports/webrtc-host.js +0 -18
- package/dist/pikichannel/transports/webrtc-shared.js +4 -34
- package/dist/pikichannel/transports/websocket-host.js +1 -14
- package/dist/pikichannel/turn.js +2 -69
- package/dist/pikichannel/web/sdk.js +14 -71
- package/package.json +1 -1
- package/dashboard/dist/assets/ConnectionModal-Dt05H2f9.js +0 -1
- package/dashboard/dist/assets/SystemTab-CN_QrRKt.js +0 -1
- package/dashboard/dist/assets/index-CP3LYJR2.js +0 -23
- package/dashboard/dist/assets/index-C_neLnaO.js +0 -3
- package/dashboard/dist/assets/index-CyCspgtc.css +0 -1
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WeChat bot orchestration.
|
|
3
|
-
*/
|
|
4
1
|
import fs from 'node:fs';
|
|
5
2
|
import os from 'node:os';
|
|
6
3
|
import path from 'node:path';
|
|
@@ -21,11 +18,6 @@ const SHUTDOWN_EXIT_CODE = {
|
|
|
21
18
|
function describeError(error) {
|
|
22
19
|
return error instanceof Error ? error.message : String(error ?? 'unknown error');
|
|
23
20
|
}
|
|
24
|
-
/**
|
|
25
|
-
* Plain-text echo of a resolved human-loop prompt — WeChat doesn't render
|
|
26
|
-
* markdown or cards, so we keep the format minimal and obviously distinct
|
|
27
|
-
* from a regular agent reply.
|
|
28
|
-
*/
|
|
29
21
|
function buildInteractionEchoPlain(summary) {
|
|
30
22
|
if (summary.status === 'cancelled')
|
|
31
23
|
return '⊘ Prompt cancelled.';
|
|
@@ -143,9 +135,6 @@ export class WeixinBot extends Bot {
|
|
|
143
135
|
const [rawCommand, ...rest] = text.trim().slice(1).split(/\s+/);
|
|
144
136
|
const command = rawCommand?.toLowerCase() || '';
|
|
145
137
|
const args = rest.join(' ').trim();
|
|
146
|
-
// `/cancel` aborts any pending command-UI / im_ask_user prompt for this
|
|
147
|
-
// chat. Special-cased before the auto-cancel below so a bare `/cancel`
|
|
148
|
-
// doesn't get treated as the start of a new interactive command.
|
|
149
138
|
if (command === 'cancel' || command === 'quit') {
|
|
150
139
|
const pending = this.pendingHumanLoopPrompt(ctx.chatId);
|
|
151
140
|
if (pending) {
|
|
@@ -156,8 +145,6 @@ export class WeixinBot extends Bot {
|
|
|
156
145
|
}
|
|
157
146
|
return true;
|
|
158
147
|
}
|
|
159
|
-
// Auto-clear any stale picker before starting a new interactive command
|
|
160
|
-
// so the user isn't trapped behind a forgotten `/agent` / `/models` etc.
|
|
161
148
|
const pendingPrompt = this.pendingHumanLoopPrompt(ctx.chatId);
|
|
162
149
|
if (pendingPrompt) {
|
|
163
150
|
this.humanLoopCancel(pendingPrompt.promptId, 'Cancelled — new command issued.');
|
|
@@ -292,9 +279,6 @@ export class WeixinBot extends Bot {
|
|
|
292
279
|
}
|
|
293
280
|
async cmdAgent(ctx, args) {
|
|
294
281
|
if (args) {
|
|
295
|
-
// Power-user shortcut: `/agent claude` switches directly without the
|
|
296
|
-
// interactive picker — same behaviour as before, preserved for muscle
|
|
297
|
-
// memory and scripted flows.
|
|
298
282
|
try {
|
|
299
283
|
const agent = normalizeAgent(args);
|
|
300
284
|
this.switchAgentForChat(ctx.chatId, agent);
|
|
@@ -309,9 +293,6 @@ export class WeixinBot extends Bot {
|
|
|
309
293
|
}
|
|
310
294
|
async cmdModels(ctx, args) {
|
|
311
295
|
if (args) {
|
|
312
|
-
// Power-user shortcut: `/models <number|name>` switches directly. Skips
|
|
313
|
-
// the multi-step picker (model + effort + Apply) used by the interactive
|
|
314
|
-
// path; effort stays unchanged.
|
|
315
296
|
const d = await getModelsListData(this, ctx.chatId);
|
|
316
297
|
const idx = parseInt(args, 10);
|
|
317
298
|
let modelId = null;
|
|
@@ -378,7 +359,6 @@ export class WeixinBot extends Bot {
|
|
|
378
359
|
}
|
|
379
360
|
const trimmed = args.trim();
|
|
380
361
|
if (trimmed) {
|
|
381
|
-
// Power-user shortcut: `/workspaces <#>` switches directly.
|
|
382
362
|
const idx = parseInt(trimmed, 10);
|
|
383
363
|
if (Number.isNaN(idx) || idx < 1 || idx > data.workspaces.length) {
|
|
384
364
|
await ctx.reply(`Workspace #${trimmed} not found. Use /workspaces to list.`);
|
|
@@ -393,10 +373,6 @@ export class WeixinBot extends Bot {
|
|
|
393
373
|
await ctx.reply(`Workdir switched:\n${oldPath}\n→ ${ws.path}`);
|
|
394
374
|
return;
|
|
395
375
|
}
|
|
396
|
-
// Interactive picker — there is no `command-ui` builder for workspaces
|
|
397
|
-
// (Telegram/Feishu render this via channel-local helpers), so we open a
|
|
398
|
-
// tailored human-loop prompt here. Disabled rows for missing paths are
|
|
399
|
-
// marked so users see them but can't pick.
|
|
400
376
|
const taskId = `wxcmd-ws-${Date.now().toString(36)}`;
|
|
401
377
|
const promptLines = [
|
|
402
378
|
'【Workspaces】',
|
|
@@ -480,8 +456,6 @@ export class WeixinBot extends Bot {
|
|
|
480
456
|
}
|
|
481
457
|
const idx = parseInt(arg, 10);
|
|
482
458
|
if (!isNaN(idx) && idx >= 1) {
|
|
483
|
-
// Power-user shortcut: `/sessions <#>` jumps directly using the same
|
|
484
|
-
// first-100 lookup the old text flow used.
|
|
485
459
|
const d = await getSessionsPageData(this, ctx.chatId, 0, 100);
|
|
486
460
|
const target = d.sessions[idx - 1];
|
|
487
461
|
if (target) {
|
|
@@ -499,8 +473,6 @@ export class WeixinBot extends Bot {
|
|
|
499
473
|
await ctx.reply(`Session #${idx} not found.`);
|
|
500
474
|
return;
|
|
501
475
|
}
|
|
502
|
-
// Interactive list with pagination — `executeCommandAction` returns the
|
|
503
|
-
// next page as a fresh view, so the loop re-renders without leaving.
|
|
504
476
|
await this.runCommandUiLoop(ctx, () => buildSessionsCommandView(this, ctx.chatId, 0));
|
|
505
477
|
}
|
|
506
478
|
async cmdSkills(ctx) {
|
|
@@ -553,12 +525,6 @@ export class WeixinBot extends Bot {
|
|
|
553
525
|
: ['Task failed.', result.error || result.message || 'Unknown error.'].filter(Boolean).join('\n');
|
|
554
526
|
await this.channel.send(chatId, text);
|
|
555
527
|
}
|
|
556
|
-
/**
|
|
557
|
-
* Format a human-in-the-loop prompt as plain text for WeChat (no card / button
|
|
558
|
-
* support on personal accounts). Options are numbered so the user can either
|
|
559
|
-
* reply with the number or freeform text — both are routed back through the
|
|
560
|
-
* same handler in `handleMessage`.
|
|
561
|
-
*/
|
|
562
528
|
formatHumanLoopPromptText(prompt) {
|
|
563
529
|
const question = currentHumanLoopQuestion(prompt);
|
|
564
530
|
const lines = [];
|
|
@@ -586,17 +552,10 @@ export class WeixinBot extends Bot {
|
|
|
586
552
|
}
|
|
587
553
|
return lines.join('\n');
|
|
588
554
|
}
|
|
589
|
-
/**
|
|
590
|
-
* IM presenter for programmatic submissions (e.g. /goal-driven turns).
|
|
591
|
-
* WeChat has no edit / card primitive, so streaming preview isn't possible
|
|
592
|
-
* — the result is just posted once when the turn completes, mirroring
|
|
593
|
-
* handleMessage's plain-text path.
|
|
594
|
-
*/
|
|
595
555
|
async createImTaskPresenter(opts) {
|
|
596
556
|
const chatId = String(opts.chatId);
|
|
597
557
|
return {
|
|
598
558
|
onText: () => {
|
|
599
|
-
// No streaming render in WeChat — text accumulates and is sent once.
|
|
600
559
|
},
|
|
601
560
|
onSuccess: async (result) => {
|
|
602
561
|
await this.sendResult(chatId, result);
|
|
@@ -621,11 +580,6 @@ export class WeixinBot extends Bot {
|
|
|
621
580
|
this.log(`weixin renderInteractionPrompt failed: ${describeError(error)}`);
|
|
622
581
|
}
|
|
623
582
|
}
|
|
624
|
-
/**
|
|
625
|
-
* WeChat has no message-edit primitive, so the original prompt text stays
|
|
626
|
-
* frozen in chat history. The echo message is the only way to record the
|
|
627
|
-
* decision — keep it short and prefix-marked so it's easy to skim.
|
|
628
|
-
*/
|
|
629
583
|
async onInteractionAnswered(prompt, summary) {
|
|
630
584
|
const text = buildInteractionEchoPlain(summary);
|
|
631
585
|
if (!text)
|
|
@@ -637,11 +591,6 @@ export class WeixinBot extends Bot {
|
|
|
637
591
|
this.log(`weixin onInteractionAnswered echo failed: ${describeError(error)}`);
|
|
638
592
|
}
|
|
639
593
|
}
|
|
640
|
-
/**
|
|
641
|
-
* If the incoming text looks like an option number (e.g. "1", "2.", "③"),
|
|
642
|
-
* resolve it against the current question's options. Otherwise return null
|
|
643
|
-
* and let the freeform path take over.
|
|
644
|
-
*/
|
|
645
594
|
parseHumanLoopOptionPick(text, prompt) {
|
|
646
595
|
const question = currentHumanLoopQuestion(prompt);
|
|
647
596
|
if (!question?.options?.length)
|
|
@@ -656,14 +605,6 @@ export class WeixinBot extends Bot {
|
|
|
656
605
|
const opt = question.options[idx - 1];
|
|
657
606
|
return opt?.value || opt?.label || null;
|
|
658
607
|
}
|
|
659
|
-
// ---- Command-UI adapter (numbered-text fallback for non-card IMs) -------
|
|
660
|
-
//
|
|
661
|
-
// Telegram / Feishu render `CommandSelectionView` as tap-able button cards.
|
|
662
|
-
// WeChat (personal accounts) can't render cards, so we flatten each view's
|
|
663
|
-
// button rows into a numbered text prompt and let the user reply with a
|
|
664
|
-
// digit. Re-uses the SAME `executeCommandAction` data layer — no parallel
|
|
665
|
-
// logic — so `/agents` / `/models` / `/sessions` / `/skills` / `/mode`
|
|
666
|
-
// behave identically across channels, just rendered differently.
|
|
667
608
|
decorateCommandButtonLabel(button) {
|
|
668
609
|
let label = button.label.trim();
|
|
669
610
|
if (button.state === 'current' || button.primary)
|
|
@@ -674,11 +615,6 @@ export class WeixinBot extends Bot {
|
|
|
674
615
|
label += ' [n/a]';
|
|
675
616
|
return label;
|
|
676
617
|
}
|
|
677
|
-
/**
|
|
678
|
-
* Render a `CommandSelectionView` as plain WeChat text: title + meta lines +
|
|
679
|
-
* detailed item list (when present) + numbered button list. Always ends with
|
|
680
|
-
* "回复编号选择" so the user knows what to do next.
|
|
681
|
-
*/
|
|
682
618
|
formatCommandViewText(view, buttons) {
|
|
683
619
|
const lines = [];
|
|
684
620
|
lines.push(`【${view.title}】`);
|
|
@@ -717,12 +653,6 @@ export class WeixinBot extends Bot {
|
|
|
717
653
|
parts.push(notice.detail);
|
|
718
654
|
return parts.join('\n');
|
|
719
655
|
}
|
|
720
|
-
/**
|
|
721
|
-
* Open one prompt for a `CommandSelectionView` and resolve once the user
|
|
722
|
-
* picks an option (or cancels). The picked CommandAction is executed via the
|
|
723
|
-
* shared `executeCommandAction` so the side effects (state mutations,
|
|
724
|
-
* notices) match every other channel exactly.
|
|
725
|
-
*/
|
|
726
656
|
async promptCommandView(ctx, view) {
|
|
727
657
|
const buttons = view.rows.flat();
|
|
728
658
|
if (!buttons.length) {
|
|
@@ -779,13 +709,6 @@ export class WeixinBot extends Bot {
|
|
|
779
709
|
.catch(() => resolve(null));
|
|
780
710
|
});
|
|
781
711
|
}
|
|
782
|
-
/**
|
|
783
|
-
* Multi-step command flow driver. Some views (notably `/models`'s "select
|
|
784
|
-
* model → set effort → Apply") return another `CommandSelectionView` from
|
|
785
|
-
* `executeCommandAction` rather than a terminal notice; this loop keeps
|
|
786
|
-
* re-prompting until we hit a notice / skill / noop / cancellation. Capped
|
|
787
|
-
* at 12 iterations to guard against pathological loops.
|
|
788
|
-
*/
|
|
789
712
|
async runCommandUiLoop(ctx, viewBuilder) {
|
|
790
713
|
let view = await Promise.resolve(viewBuilder());
|
|
791
714
|
let safety = 12;
|
|
@@ -801,9 +724,6 @@ export class WeixinBot extends Bot {
|
|
|
801
724
|
await ctx.reply(this.formatCommandNotice(result.notice));
|
|
802
725
|
return;
|
|
803
726
|
case 'skill':
|
|
804
|
-
// Dispatch the resolved skill prompt back through the regular task
|
|
805
|
-
// pipeline (same machinery as a typed message), so the agent picks
|
|
806
|
-
// it up with the right session + workspace.
|
|
807
727
|
await ctx.reply(`Running /${result.skillName}…`);
|
|
808
728
|
await this.dispatchUserPrompt(ctx, result.prompt, []);
|
|
809
729
|
return;
|
|
@@ -815,11 +735,6 @@ export class WeixinBot extends Bot {
|
|
|
815
735
|
if (safety <= 0)
|
|
816
736
|
await ctx.reply('Command UI loop terminated (too many steps).');
|
|
817
737
|
}
|
|
818
|
-
/**
|
|
819
|
-
* Submit a user-authored prompt through the same path `handleMessage` uses
|
|
820
|
-
* for natural-language messages. Lets the command-UI loop forward a resolved
|
|
821
|
-
* `/skill` expansion as if the user had typed it.
|
|
822
|
-
*/
|
|
823
738
|
async dispatchUserPrompt(ctx, text, files) {
|
|
824
739
|
const session = this.resolveSession(ctx.chatId, text, files);
|
|
825
740
|
const prompt = buildPrompt(text, files);
|
|
@@ -879,8 +794,6 @@ export class WeixinBot extends Bot {
|
|
|
879
794
|
await ctx.reply('This Weixin channel currently supports text input only.');
|
|
880
795
|
return;
|
|
881
796
|
}
|
|
882
|
-
// Active human-in-the-loop prompt takes priority — text-only replies are
|
|
883
|
-
// routed back to the pending question (option-number pick OR freeform).
|
|
884
797
|
const pendingPrompt = this.pendingHumanLoopPrompt(ctx.chatId);
|
|
885
798
|
if (pendingPrompt && text && !msg.files.length) {
|
|
886
799
|
const optionValue = this.parseHumanLoopOptionPick(text, pendingPrompt);
|
|
@@ -892,7 +805,6 @@ export class WeixinBot extends Bot {
|
|
|
892
805
|
return;
|
|
893
806
|
}
|
|
894
807
|
if (result.completed) {
|
|
895
|
-
// Closing message comes from onInteractionAnswered — no extra reply.
|
|
896
808
|
}
|
|
897
809
|
else if (result.advanced) {
|
|
898
810
|
const next = this.humanLoopPrompt(pendingPrompt.promptId);
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* WeChat channel transport.
|
|
3
|
-
*/
|
|
4
1
|
import { Channel, DEFAULT_CHANNEL_CAPABILITIES, splitText, sleep, } from '../base.js';
|
|
5
2
|
import { WEIXIN_LIMITS } from '../../core/constants.js';
|
|
6
3
|
import { ChannelHealth } from '../health.js';
|
|
@@ -82,9 +79,6 @@ export class WeixinChannel extends Channel {
|
|
|
82
79
|
});
|
|
83
80
|
if (response.get_updates_buf !== undefined)
|
|
84
81
|
this.updateBuf = response.get_updates_buf || '';
|
|
85
|
-
// Errcode check BEFORE recordSuccess(): an HTTP 200 carrying a
|
|
86
|
-
// business error code (e.g. "session timeout") must not silently
|
|
87
|
-
// reset the exponential backoff.
|
|
88
82
|
if ((response.ret ?? 0) !== 0 || (response.errcode ?? 0) !== 0) {
|
|
89
83
|
throw new Error(`Weixin getupdates failed: ${response.errmsg || response.errcode || response.ret}`);
|
|
90
84
|
}
|
package/dist/cli/autostart.js
CHANGED
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* macOS LaunchAgent integration for `pikiloom --daemon`.
|
|
3
|
-
*
|
|
4
|
-
* Every time the user runs pikiloom with an explicit `--daemon` flag on
|
|
5
|
-
* macOS *without* a LaunchAgent already installed, an osascript dialog asks
|
|
6
|
-
* whether to enable login auto-start. Choosing Enable writes
|
|
7
|
-
* `~/Library/LaunchAgents/ai.pikiloom.gateway.plist` and loads it via
|
|
8
|
-
* `launchctl bootstrap`. There is intentionally no CLI to disable: the user
|
|
9
|
-
* toggles it off under System Settings → General → Login Items, which is
|
|
10
|
-
* where macOS surfaces every LaunchAgent installed in
|
|
11
|
-
* `~/Library/LaunchAgents`.
|
|
12
|
-
*
|
|
13
|
-
* Decision flow (`maybePromptAutostart`):
|
|
14
|
-
* 1. Non-darwin → no-op.
|
|
15
|
-
* 2. Already running under launchd (PIKILOOM_FROM_LAUNCHD set) → no-op.
|
|
16
|
-
* 3. plist exists but its ProgramArguments no longer point to a valid
|
|
17
|
-
* binary (e.g. Homebrew migration moved node) → silently rewrite.
|
|
18
|
-
* 4. plist already valid → no-op.
|
|
19
|
-
* 5. Non-interactive (no TTY, CI=1) → no-op.
|
|
20
|
-
* 6. Otherwise → show dialog after a short delay so the bot is already
|
|
21
|
-
* live when the user sees it. "Not now" silently dismisses; the next
|
|
22
|
-
* `--daemon` run will ask again.
|
|
23
|
-
*/
|
|
24
1
|
import { exec, execFile } from 'node:child_process';
|
|
25
2
|
import fs from 'node:fs';
|
|
26
3
|
import os from 'node:os';
|
|
@@ -33,16 +10,9 @@ const PLIST_LABEL = 'ai.pikiloom.gateway';
|
|
|
33
10
|
const PLIST_DIR = path.join(os.homedir(), 'Library', 'LaunchAgents');
|
|
34
11
|
const PLIST_PATH = path.join(PLIST_DIR, `${PLIST_LABEL}.plist`);
|
|
35
12
|
const PIKILOOM_HOME = path.join(os.homedir(), STATE_DIR_NAME);
|
|
36
|
-
// Pre-rename LaunchAgent (pikiclaw) — removed on (re)install so an upgraded
|
|
37
|
-
// machine never runs two daemons. Delete a couple releases post-rename.
|
|
38
13
|
const LEGACY_PLIST_LABELS = ['ai.pikiclaw.gateway'];
|
|
39
14
|
const PROMPT_DELAY_MS = 3000;
|
|
40
15
|
export const FROM_LAUNCHD_ENV = 'PIKILOOM_FROM_LAUNCHD';
|
|
41
|
-
/**
|
|
42
|
-
* Resolve the command used to launch pikiloom, so the plist can re-launch it
|
|
43
|
-
* the same way. We distinguish npx (`.../_npx/<hash>/.../main.js`) from a
|
|
44
|
-
* globally installed binary (`pikiloom` on PATH).
|
|
45
|
-
*/
|
|
46
16
|
function detectInvocation() {
|
|
47
17
|
const entry = process.argv[1] || '';
|
|
48
18
|
const userArgs = process.argv.slice(2);
|
|
@@ -146,15 +116,12 @@ async function showEnableDialog() {
|
|
|
146
116
|
return 'closed';
|
|
147
117
|
}
|
|
148
118
|
catch {
|
|
149
|
-
// User pressed Cmd-. or closed the dialog — osascript exits non-zero.
|
|
150
119
|
return 'closed';
|
|
151
120
|
}
|
|
152
121
|
}
|
|
153
122
|
async function bootstrapLaunchAgent(log) {
|
|
154
123
|
const uid = process.getuid?.() ?? 0;
|
|
155
124
|
const domain = `gui/${uid}`;
|
|
156
|
-
// bootout first so a reinstall replaces any previously-loaded copy. We
|
|
157
|
-
// swallow errors here since bootout fails harmlessly when nothing is loaded.
|
|
158
125
|
await new Promise(resolve => {
|
|
159
126
|
exec(`launchctl bootout ${domain}/${PLIST_LABEL}`, () => resolve());
|
|
160
127
|
});
|
|
@@ -167,11 +134,6 @@ async function bootstrapLaunchAgent(log) {
|
|
|
167
134
|
return false;
|
|
168
135
|
}
|
|
169
136
|
}
|
|
170
|
-
/**
|
|
171
|
-
* Remove the pre-rename LaunchAgent (`ai.pikiclaw.gateway`) if present, so an
|
|
172
|
-
* upgraded install never runs two daemons. Best-effort; safe when nothing is
|
|
173
|
-
* loaded. Delete a couple releases post-rename.
|
|
174
|
-
*/
|
|
175
137
|
async function cleanupLegacyAutostart(log) {
|
|
176
138
|
const uid = process.getuid?.() ?? 0;
|
|
177
139
|
for (const label of LEGACY_PLIST_LABELS) {
|
|
@@ -193,7 +155,7 @@ async function cleanupLegacyAutostart(log) {
|
|
|
193
155
|
log(`autostart: removed legacy LaunchAgent ${label}`);
|
|
194
156
|
}
|
|
195
157
|
}
|
|
196
|
-
catch {
|
|
158
|
+
catch { }
|
|
197
159
|
}
|
|
198
160
|
}
|
|
199
161
|
async function installAutostart(log, invocation) {
|
|
@@ -213,10 +175,6 @@ async function installAutostart(log, invocation) {
|
|
|
213
175
|
}
|
|
214
176
|
return loaded;
|
|
215
177
|
}
|
|
216
|
-
/**
|
|
217
|
-
* Top-level entry called once from the watchdog process when the user passed
|
|
218
|
-
* an explicit `--daemon` flag. Fires and forgets; never throws.
|
|
219
|
-
*/
|
|
220
178
|
export function maybePromptAutostart(log) {
|
|
221
179
|
if (process.platform !== 'darwin')
|
|
222
180
|
return;
|
|
@@ -250,7 +208,6 @@ export function maybePromptAutostart(log) {
|
|
|
250
208
|
})();
|
|
251
209
|
}, PROMPT_DELAY_MS).unref?.();
|
|
252
210
|
}
|
|
253
|
-
// ─── exports for tests ─────────────────────────────────────────────────
|
|
254
211
|
export const __test = {
|
|
255
212
|
PLIST_LABEL,
|
|
256
213
|
PLIST_PATH,
|
|
@@ -1,20 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* channel-supervisor.ts — Owns the set of running IM channel bots and
|
|
3
|
-
* reconciles it against the current user config without restarting the
|
|
4
|
-
* pikiloom process.
|
|
5
|
-
*
|
|
6
|
-
* Subscribes to onUserConfigChange. Whenever the channels array or any
|
|
7
|
-
* channel's credentials change, the supervisor stops the affected bot,
|
|
8
|
-
* disposes its transport, and (if still configured) launches a fresh
|
|
9
|
-
* instance with the new config — all in-process, leaving the dashboard,
|
|
10
|
-
* other channels, and active coding sessions on unaffected bots running.
|
|
11
|
-
*
|
|
12
|
-
* Dashboard-as-terminal: the Web Dashboard is a first-class terminal, equal to
|
|
13
|
-
* the IM channels. When no IM channel is configured, the supervisor keeps a
|
|
14
|
-
* headless bot attached to the dashboard so it's a fully usable terminal on its
|
|
15
|
-
* own; when an IM channel is added, that channel's bot takes over the dashboard
|
|
16
|
-
* attachment and the headless bot is torn down.
|
|
17
|
-
*/
|
|
18
1
|
import { loadUserConfig, onUserConfigChange } from '../core/config/user-config.js';
|
|
19
2
|
import { resolveConfiguredChannels } from './channels.js';
|
|
20
3
|
const CHANNEL_AFFECTING_KEYS = new Set([
|
|
@@ -35,26 +18,10 @@ const CHANNEL_AFFECTING_KEYS = new Set([
|
|
|
35
18
|
'wecomBotSecret',
|
|
36
19
|
'wecomEndpoint',
|
|
37
20
|
]);
|
|
38
|
-
/**
|
|
39
|
-
* How long to wait between stopping a channel and starting a replacement
|
|
40
|
-
* with new credentials. Some IM providers hold the old connection slot
|
|
41
|
-
* server-side for a moment after we close the socket; if a new client
|
|
42
|
-
* with the same credentials connects too quickly the server rejects with
|
|
43
|
-
* "system busy" and the SDK ends up in a broken state.
|
|
44
|
-
*
|
|
45
|
-
* Feishu's lark SDK is the known offender — its WSClient handshake races
|
|
46
|
-
* with a "PingInterval" setup that crashes when the server rejects the
|
|
47
|
-
* second connect. Telegram (long-poll over HTTP) and Weixin (HTTP long-
|
|
48
|
-
* poll) tolerate immediate replacement.
|
|
49
|
-
*/
|
|
50
21
|
const CHANNEL_REPLACE_SETTLE_MS = {
|
|
51
22
|
telegram: 0,
|
|
52
23
|
weixin: 0,
|
|
53
24
|
feishu: 5_000,
|
|
54
|
-
// The Slack / Discord / DingTalk / WeChat-Work providers all hold the
|
|
55
|
-
// single-bot socket slot for a moment after disconnect; give them a
|
|
56
|
-
// small grace window so token rotation doesn't trip "already connected"
|
|
57
|
-
// style errors on the immediate replacement connection.
|
|
58
25
|
slack: 3_000,
|
|
59
26
|
discord: 3_000,
|
|
60
27
|
dingtalk: 3_000,
|
|
@@ -140,7 +107,6 @@ function describeError(error) {
|
|
|
140
107
|
}
|
|
141
108
|
export class ChannelSupervisor {
|
|
142
109
|
running = new Map();
|
|
143
|
-
/** Headless dashboard-terminal bot, present only while no IM channel runs. */
|
|
144
110
|
headless = null;
|
|
145
111
|
dashboard;
|
|
146
112
|
log;
|
|
@@ -152,10 +118,6 @@ export class ChannelSupervisor {
|
|
|
152
118
|
this.dashboard = opts.dashboard;
|
|
153
119
|
this.log = opts.log;
|
|
154
120
|
}
|
|
155
|
-
/**
|
|
156
|
-
* Initial launch + subscribe to config changes. Idempotent: calling
|
|
157
|
-
* twice is a no-op for the second call.
|
|
158
|
-
*/
|
|
159
121
|
async start() {
|
|
160
122
|
if (this.started)
|
|
161
123
|
return;
|
|
@@ -167,7 +129,6 @@ export class ChannelSupervisor {
|
|
|
167
129
|
void this.reconcile(config);
|
|
168
130
|
});
|
|
169
131
|
}
|
|
170
|
-
/** Stop every running channel and unsubscribe from config changes. */
|
|
171
132
|
async stop() {
|
|
172
133
|
this.unsubscribe?.();
|
|
173
134
|
this.unsubscribe = null;
|
|
@@ -175,7 +136,6 @@ export class ChannelSupervisor {
|
|
|
175
136
|
await Promise.all(channels.map(channel => this.stopChannel(channel)));
|
|
176
137
|
await this.stopHeadless();
|
|
177
138
|
}
|
|
178
|
-
/** Reconcile running bots against the desired channel set + credentials. */
|
|
179
139
|
async reconcile(config) {
|
|
180
140
|
if (this.reconcileInFlight) {
|
|
181
141
|
this.reconcilePending = true;
|
|
@@ -189,8 +149,6 @@ export class ChannelSupervisor {
|
|
|
189
149
|
await this.doReconcile(current);
|
|
190
150
|
if (!this.reconcilePending)
|
|
191
151
|
break;
|
|
192
|
-
// Newer config arrived during reconcile — re-read from disk so the
|
|
193
|
-
// next pass acts on the latest state.
|
|
194
152
|
current = loadUserConfig();
|
|
195
153
|
}
|
|
196
154
|
}
|
|
@@ -201,7 +159,6 @@ export class ChannelSupervisor {
|
|
|
201
159
|
async doReconcile(config) {
|
|
202
160
|
const desired = resolveConfiguredChannels({ config });
|
|
203
161
|
const desiredSet = new Set(desired);
|
|
204
|
-
// Stop channels that are no longer desired OR whose credentials changed.
|
|
205
162
|
const toStop = [];
|
|
206
163
|
for (const [channel, entry] of this.running) {
|
|
207
164
|
if (!desiredSet.has(channel)) {
|
|
@@ -215,18 +172,12 @@ export class ChannelSupervisor {
|
|
|
215
172
|
if (toStop.length) {
|
|
216
173
|
await Promise.all(toStop.map(channel => this.stopChannel(channel)));
|
|
217
174
|
}
|
|
218
|
-
// For channels being replaced (still desired, just stopped above for
|
|
219
|
-
// credential rotation), wait the per-channel settle delay so the
|
|
220
|
-
// remote server has time to release the old connection slot before
|
|
221
|
-
// we open a new one.
|
|
222
175
|
const replacing = toStop.filter(channel => desiredSet.has(channel));
|
|
223
176
|
const settleMs = replacing.reduce((max, channel) => Math.max(max, CHANNEL_REPLACE_SETTLE_MS[channel] ?? 0), 0);
|
|
224
177
|
if (settleMs > 0) {
|
|
225
178
|
this.log(`waiting ${settleMs}ms for ${replacing.join(', ')} to settle before relaunch`);
|
|
226
179
|
await sleep(settleMs);
|
|
227
180
|
}
|
|
228
|
-
// Start channels that should be running but aren't (either added or
|
|
229
|
-
// just stopped above due to credential rotation).
|
|
230
181
|
for (const channel of desired) {
|
|
231
182
|
if (this.running.has(channel))
|
|
232
183
|
continue;
|
|
@@ -237,11 +188,6 @@ export class ChannelSupervisor {
|
|
|
237
188
|
this.log(`channel ${channel}: failed to start — ${describeError(err)}`);
|
|
238
189
|
}
|
|
239
190
|
}
|
|
240
|
-
// Dashboard-as-terminal: keep a headless bot attached whenever no channel
|
|
241
|
-
// bot is actually running (none configured, or all failed to start), so the
|
|
242
|
-
// dashboard stays a usable terminal on its own. Gate on `running.size`
|
|
243
|
-
// rather than `desired.length` so a channel that fails to come up still
|
|
244
|
-
// leaves the dashboard with a working bot.
|
|
245
191
|
if (this.running.size > 0) {
|
|
246
192
|
await this.stopHeadless();
|
|
247
193
|
}
|
|
@@ -249,7 +195,6 @@ export class ChannelSupervisor {
|
|
|
249
195
|
await this.startHeadless();
|
|
250
196
|
}
|
|
251
197
|
}
|
|
252
|
-
/** Launch the headless dashboard-terminal bot and attach it to the dashboard. */
|
|
253
198
|
async startHeadless() {
|
|
254
199
|
if (this.headless)
|
|
255
200
|
return;
|
|
@@ -263,7 +208,6 @@ export class ChannelSupervisor {
|
|
|
263
208
|
});
|
|
264
209
|
this.headless = { bot, runPromise };
|
|
265
210
|
}
|
|
266
|
-
/** Tear down the headless bot once a real channel bot owns the dashboard. */
|
|
267
211
|
async stopHeadless() {
|
|
268
212
|
if (!this.headless)
|
|
269
213
|
return;
|
|
@@ -274,7 +218,7 @@ export class ChannelSupervisor {
|
|
|
274
218
|
try {
|
|
275
219
|
await runPromise;
|
|
276
220
|
}
|
|
277
|
-
catch {
|
|
221
|
+
catch { }
|
|
278
222
|
}
|
|
279
223
|
async startChannel(channel, config) {
|
|
280
224
|
this.log(`channel ${channel}: starting`);
|
|
@@ -295,8 +239,6 @@ export class ChannelSupervisor {
|
|
|
295
239
|
if (!entry)
|
|
296
240
|
return;
|
|
297
241
|
this.log(`channel ${channel}: stopping`);
|
|
298
|
-
// Remove from the map up-front so a concurrent reconcile pass doesn't
|
|
299
|
-
// try to stop the same instance twice.
|
|
300
242
|
this.running.delete(channel);
|
|
301
243
|
try {
|
|
302
244
|
entry.bot.requestStop();
|
package/dist/cli/channels.js
CHANGED
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Channel resolution helpers for CLI startup.
|
|
3
|
-
*/
|
|
4
1
|
import { applyChannelEnvFallback } from '../core/config/user-config.js';
|
|
5
2
|
export function hasConfiguredChannelToken(rawConfig, channel, tokenOverride) {
|
|
6
|
-
// Channel transports already fall back to env vars at runtime
|
|
7
|
-
// (e.g. TELEGRAM_BOT_TOKEN). Mirror that here so a docker-only env setup
|
|
8
|
-
// doesn't make startup think there's nothing configured.
|
|
9
3
|
const config = applyChannelEnvFallback(rawConfig);
|
|
10
4
|
switch (channel) {
|
|
11
5
|
case 'telegram':
|