cli-jaw 2.2.11 → 2.2.13
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/dist/bin/commands/doctor.js +20 -0
- package/dist/bin/commands/doctor.js.map +1 -1
- package/dist/bin/commands/init.js +7 -0
- package/dist/bin/commands/init.js.map +1 -1
- package/dist/bin/commands/slack.js +47 -5
- package/dist/bin/commands/slack.js.map +1 -1
- package/dist/bin/commands/tui/slash-command-runner.js +11 -2
- package/dist/bin/commands/tui/slash-command-runner.js.map +1 -1
- package/dist/server.js +18 -2
- package/dist/server.js.map +1 -1
- package/dist/src/agent/error-classifier.js +11 -2
- package/dist/src/agent/error-classifier.js.map +1 -1
- package/dist/src/agent/events/claude.js +7 -1
- package/dist/src/agent/events/claude.js.map +1 -1
- package/dist/src/agent/events/fulltext-bound.js +32 -0
- package/dist/src/agent/events/fulltext-bound.js.map +1 -0
- package/dist/src/agent/events/grok.js +7 -1
- package/dist/src/agent/events/grok.js.map +1 -1
- package/dist/src/agent/events/helpers.js +17 -4
- package/dist/src/agent/events/helpers.js.map +1 -1
- package/dist/src/agent/lifecycle-handler.js +56 -5
- package/dist/src/agent/lifecycle-handler.js.map +1 -1
- package/dist/src/agent/pi-runtime.js +30 -1
- package/dist/src/agent/pi-runtime.js.map +1 -1
- package/dist/src/agent/spawn/exit-drain.js +96 -0
- package/dist/src/agent/spawn/exit-drain.js.map +1 -0
- package/dist/src/agent/spawn/line-buffer.js +26 -0
- package/dist/src/agent/spawn/line-buffer.js.map +1 -0
- package/dist/src/agent/spawn/process-kill.js +31 -0
- package/dist/src/agent/spawn/process-kill.js.map +1 -1
- package/dist/src/agent/spawn.js +80 -23
- package/dist/src/agent/spawn.js.map +1 -1
- package/dist/src/agent/watchdog.js +21 -5
- package/dist/src/agent/watchdog.js.map +1 -1
- package/dist/src/cli/commands.js +68 -15
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/opencodex-models.js +67 -14
- package/dist/src/cli/opencodex-models.js.map +1 -1
- package/dist/src/cli/registry-live.js +55 -2
- package/dist/src/cli/registry-live.js.map +1 -1
- package/dist/src/cli/registry.js +8 -2
- package/dist/src/cli/registry.js.map +1 -1
- package/dist/src/core/config.js +26 -1
- package/dist/src/core/config.js.map +1 -1
- package/dist/src/messaging/channel-health.js +6 -0
- package/dist/src/messaging/channel-health.js.map +1 -1
- package/dist/src/messaging/channel-validate.js +85 -0
- package/dist/src/messaging/channel-validate.js.map +1 -0
- package/dist/src/routes/link-preview.js +14 -0
- package/dist/src/routes/link-preview.js.map +1 -1
- package/dist/src/routes/messaging.js +9 -0
- package/dist/src/routes/messaging.js.map +1 -1
- package/dist/src/routes/system.js +5 -0
- package/dist/src/routes/system.js.map +1 -1
- package/dist/src/slack/api.js +32 -3
- package/dist/src/slack/api.js.map +1 -1
- package/dist/src/slack/bot.js +37 -1
- package/dist/src/slack/bot.js.map +1 -1
- package/dist/src/slack/events.js +23 -0
- package/dist/src/slack/events.js.map +1 -1
- package/dist/src/slack/hot-notify.js +33 -0
- package/dist/src/slack/hot-notify.js.map +1 -0
- package/dist/src/slack/manifest.js +2 -1
- package/dist/src/slack/manifest.js.map +1 -1
- package/dist/src/slack/progress.js +98 -0
- package/dist/src/slack/progress.js.map +1 -0
- package/dist/src/slack/send-only-client.js +3 -2
- package/dist/src/slack/send-only-client.js.map +1 -1
- package/dist/src/slack/slack-file.js +4 -2
- package/dist/src/slack/slack-file.js.map +1 -1
- package/package.json +3 -1
- package/public/css/sidebar.css +200 -0
- package/public/dist/assets/Agent-BjbZVZ9p.js +1 -0
- package/public/dist/assets/CodeCanvas-CbfPP7Ad.js +3 -0
- package/public/dist/assets/{DocPanel-DtYwv3n3.js → DocPanel-Dz-J_l5M.js} +1 -1
- package/public/dist/assets/{Heartbeat-vDGhPfct.js → Heartbeat-DDmZARWw.js} +1 -1
- package/public/dist/assets/MarkdownRenderer-CXrilOsG.js +15 -0
- package/public/dist/assets/MarkdownRenderer-DZVh_xX2.js +1 -0
- package/public/dist/assets/{Memory-B_UZ-iCA.js → Memory-CUbviW7N.js} +1 -1
- package/public/dist/assets/{MilkdownWysiwygEditor-BNoLGbFC.js → MilkdownWysiwygEditor-Em7iC6y9.js} +8 -8
- package/public/dist/assets/ModelProvider-ngg8xTuB.js +1 -0
- package/public/dist/assets/{agent-meta-C8DqYXM6.js → agent-meta-Y3DE6sxh.js} +1 -1
- package/public/dist/assets/app-BzD_ZLvt.js +55 -0
- package/public/dist/assets/{app-Nu2VPaO5.css → app-DX4e6isX.css} +1 -1
- package/public/dist/assets/bgtask-badge-Dqbp-Y4e.js +1 -0
- package/public/dist/assets/{bgtask-badge-DXca8E7c.js → bgtask-badge-qMkKOZx0.js} +1 -1
- package/public/dist/assets/{react-dom-AWKMTIe7.js → bounded-set-CFlZiEF4.js} +1 -1
- package/public/dist/assets/{constants-CSZ011-j.js → constants-Df7-7Rky.js} +1 -1
- package/public/dist/assets/{employees-BL4qMxJj.js → employees-BnobWM_2.js} +1 -1
- package/public/dist/assets/{iframe-renderer-DUI0LYBy.js → iframe-renderer-C5vRbbl9.js} +2 -2
- package/public/dist/assets/iframe-renderer-Df4KIVyx.js +1 -0
- package/public/dist/assets/manager-DaN201mN.js +13 -0
- package/public/dist/assets/memory-D-bULTHl.js +1 -0
- package/public/dist/assets/{memory-B_Y0L0KD.js → memory-DF4kPwwC.js} +1 -1
- package/public/dist/assets/{message-history-Dl8dJzez.js → message-history-C4WOisGx.js} +1 -1
- package/public/dist/assets/message-history-ZBoza6ew.js +1 -0
- package/public/dist/assets/{render-BI1aqKoc.js → render-DYX6GRQ0.js} +1 -1
- package/public/dist/assets/{settings-B0Sj4sUH.js → settings-DbJpGoaX.js} +1 -1
- package/public/dist/assets/settings-SreihUje.js +1 -0
- package/public/dist/assets/settings-core-B1Y5Nb4Z.js +1 -0
- package/public/dist/assets/settings-core-DobZWIZ7.js +140 -0
- package/public/dist/assets/{sidebar-DOR9fTgN.js → sidebar-Cp22g0Gv.js} +3 -3
- package/public/dist/assets/skills-DwPIP08e.js +1 -0
- package/public/dist/assets/{skills-wt0fkV9Y.js → skills-IGquen_j.js} +1 -1
- package/public/dist/assets/slash-commands-BBVfwcnW.js +1 -0
- package/public/dist/assets/{slash-commands-BZbqTg3v.js → slash-commands-ZRDOL99E.js} +1 -1
- package/public/dist/assets/{trace-drawer-kMYzaVFW.js → trace-drawer-D8To8fYt.js} +1 -1
- package/public/dist/assets/ui-CAicpUZc.js +1 -0
- package/public/dist/assets/{ui-D4D5HUfo.js → ui-Hx_mNoDf.js} +1 -1
- package/public/dist/assets/{wiki-link-resolver-BfNXN9px.js → wiki-link-resolver-DyFzBTIQ.js} +1 -1
- package/public/dist/index.html +90 -47
- package/public/dist/manager/index.html +1 -1
- package/public/index.html +88 -45
- package/public/js/constants.ts +50 -0
- package/public/js/features/channel-onboarding-flow.ts +197 -0
- package/public/js/features/channel-onboarding.ts +322 -0
- package/public/js/features/channel-setup-guide.ts +26 -0
- package/public/js/features/channel-setup-rules.ts +32 -0
- package/public/js/features/notifications.ts +38 -0
- package/public/js/features/settings-channel.ts +4 -0
- package/public/js/features/settings-core.ts +73 -9
- package/public/js/features/settings-discord.ts +2 -0
- package/public/js/features/settings-slack.ts +60 -2
- package/public/js/features/settings-telegram.ts +2 -0
- package/public/js/features/settings-types.ts +1 -1
- package/public/js/features/settings.ts +1 -1
- package/public/js/features/transport-status-row.ts +7 -3
- package/public/js/main.ts +5 -1
- package/public/locales/en.json +65 -3
- package/public/locales/ja.json +76 -3
- package/public/locales/ko.json +65 -3
- package/public/locales/zh.json +76 -3
- package/public/manager/src/SidebarRailRouter.tsx +9 -3
- package/public/manager/src/browser-panel/use-embedded-target-sync.ts +19 -1
- package/public/manager/src/code/CodeCanvas.tsx +14 -2
- package/public/manager/src/code/CodeTranscript.tsx +33 -9
- package/public/manager/src/code/CodeWorkbench.tsx +1 -1
- package/public/manager/src/code/code-event-dedupe.ts +27 -3
- package/public/manager/src/code/use-code-transcript-scroll.ts +28 -3
- package/public/manager/src/code/use-throttled-markdown.ts +75 -0
- package/public/manager/src/code/useCodeTranscriptVirtualRows.ts +19 -0
- package/public/manager/src/electron-metrics.tsx +7 -0
- package/public/manager/src/jaw-ceo/useJawCeo.ts +6 -1
- package/public/manager/src/jaw-ceo/useJawCeoVirtualTimeline.ts +5 -0
- package/public/manager/src/lib/bounded-set.ts +15 -0
- package/public/manager/src/lib/fnv1a.ts +14 -0
- package/public/manager/src/notes/rendering/CodeBlock.tsx +13 -4
- package/public/manager/src/notes/rendering/MarkdownRenderer.tsx +14 -2
- package/public/manager/src/notes/rendering/highlight-cache.ts +111 -0
- package/public/manager/src/notes/useNotesExternalSync.ts +6 -1
- package/public/manager/src/settings/pages/Agent.tsx +25 -4
- package/public/manager/src/settings/pages/components/PerCliRow.tsx +13 -5
- package/public/manager/src/settings/pages/components/agent/agent-meta.ts +98 -0
- package/scripts/bundle-sidecar.sh +8 -0
- package/scripts/check-electron-no-native.cjs +90 -4
- package/scripts/check-native-load.cjs +157 -0
- package/scripts/check-sidecar-smoke.mjs +151 -0
- package/scripts/ensure-native-modules.cjs +222 -18
- package/scripts/release-gates.mjs +29 -0
- package/public/dist/assets/Agent-BgkP4EnS.js +0 -1
- package/public/dist/assets/CodeCanvas-BZo3dohZ.js +0 -3
- package/public/dist/assets/MarkdownRenderer-CHacL2M_.js +0 -15
- package/public/dist/assets/MarkdownRenderer-DyrB9FbZ.js +0 -1
- package/public/dist/assets/ModelProvider-CgUvs6pE.js +0 -1
- package/public/dist/assets/app-BGIAy4he.js +0 -55
- package/public/dist/assets/bgtask-badge-GRSiNILQ.js +0 -1
- package/public/dist/assets/iframe-renderer-BUSk0Rw9.js +0 -1
- package/public/dist/assets/manager-B-gpf-3j.js +0 -13
- package/public/dist/assets/memory-CX6YahTc.js +0 -1
- package/public/dist/assets/message-history-Det7u76S.js +0 -1
- package/public/dist/assets/settings-C7NutP6X.js +0 -1
- package/public/dist/assets/settings-core-CyX-px6_.js +0 -100
- package/public/dist/assets/settings-core-DVpFmrhY.js +0 -1
- package/public/dist/assets/skills-BWhKreQ2.js +0 -1
- package/public/dist/assets/slash-commands-Di4NDA4w.js +0 -1
- package/public/dist/assets/ui-BghexWrB.js +0 -1
package/public/index.html
CHANGED
|
@@ -468,8 +468,12 @@
|
|
|
468
468
|
<div id="channelTelegramSettings" class="settings-group">
|
|
469
469
|
<h4 class="section-title-row">
|
|
470
470
|
<span><span data-provider="telegram"></span> Telegram</span>
|
|
471
|
-
<
|
|
472
|
-
|
|
471
|
+
<span class="section-title-actions">
|
|
472
|
+
<button class="perm-btn onboard-trigger" type="button" data-onboard-channel="telegram"
|
|
473
|
+
data-i18n="onboarding.open">설정 마법사</button>
|
|
474
|
+
<button class="help-trigger" type="button" data-help-topic="telegram"
|
|
475
|
+
data-i18n-aria="help.telegram.aria" aria-label="Telegram 도움말">?</button>
|
|
476
|
+
</span>
|
|
473
477
|
</h4>
|
|
474
478
|
<div class="settings-row">
|
|
475
479
|
<label data-i18n="label.enabled">활성화</label>
|
|
@@ -507,8 +511,12 @@
|
|
|
507
511
|
<div id="channelDiscordSettings" class="settings-group" style="display:none">
|
|
508
512
|
<h4 class="section-title-row">
|
|
509
513
|
<span><span data-provider="discord"></span> Discord</span>
|
|
510
|
-
<
|
|
511
|
-
|
|
514
|
+
<span class="section-title-actions">
|
|
515
|
+
<button class="perm-btn onboard-trigger" type="button" data-onboard-channel="discord"
|
|
516
|
+
data-i18n="onboarding.open">설정 마법사</button>
|
|
517
|
+
<button class="help-trigger" type="button" data-help-topic="discord"
|
|
518
|
+
data-i18n-aria="help.discord.aria" aria-label="Discord 도움말">?</button>
|
|
519
|
+
</span>
|
|
512
520
|
</h4>
|
|
513
521
|
<div class="settings-row">
|
|
514
522
|
<label data-i18n="label.enabled">활성화</label>
|
|
@@ -556,8 +564,12 @@
|
|
|
556
564
|
<div id="channelSlackSettings" class="settings-group" style="display:none">
|
|
557
565
|
<h4 class="section-title-row">
|
|
558
566
|
<span><span data-provider="slack"></span> Slack</span>
|
|
559
|
-
<
|
|
560
|
-
|
|
567
|
+
<span class="section-title-actions">
|
|
568
|
+
<button class="perm-btn onboard-trigger" type="button" data-onboard-channel="slack"
|
|
569
|
+
data-i18n="onboarding.open">설정 마법사</button>
|
|
570
|
+
<button class="help-trigger" type="button" data-help-topic="slack"
|
|
571
|
+
data-i18n-aria="help.slack.aria" aria-label="Slack 도움말">?</button>
|
|
572
|
+
</span>
|
|
561
573
|
</h4>
|
|
562
574
|
<div class="settings-row">
|
|
563
575
|
<label data-i18n="label.enabled">활성화</label>
|
|
@@ -566,50 +578,81 @@
|
|
|
566
578
|
<button id="slOn" class="perm-btn" data-i18n="btn.on">켬</button>
|
|
567
579
|
</div>
|
|
568
580
|
</div>
|
|
569
|
-
<div class="
|
|
570
|
-
<
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
</div>
|
|
581
|
-
<div class="settings-row sub-row">
|
|
582
|
-
<label>Channel IDs</label>
|
|
583
|
-
<input type="text" id="slChannelIds" placeholder="콤마 구분 (비워두면 전체 허용)" class="input-sm">
|
|
584
|
-
</div>
|
|
585
|
-
<div class="settings-row sub-row">
|
|
586
|
-
<label data-i18n="label.mentionOnly">Mention Only</label>
|
|
587
|
-
<div>
|
|
588
|
-
<button id="slMentionOff" class="perm-btn" data-i18n="btn.off">끔</button>
|
|
589
|
-
<button id="slMentionOn" class="perm-btn active" data-i18n="btn.on">켬</button>
|
|
581
|
+
<div class="slack-setup-card">
|
|
582
|
+
<div class="slack-setup-step">
|
|
583
|
+
<span class="slack-step-num">1</span>
|
|
584
|
+
<div class="slack-step-body">
|
|
585
|
+
<div class="slack-step-title" data-i18n="settings.slack.guide.step1Title">Slack 앱 만들기</div>
|
|
586
|
+
<div class="slack-step-desc" data-i18n="settings.slack.guide.step1Desc">Create an App → From an app manifest → 워크스페이스 선택 → 붙여넣기</div>
|
|
587
|
+
<div class="slack-step-actions">
|
|
588
|
+
<button id="slack-copy-manifest" class="perm-btn" type="button" data-i18n="settings.slack.guide.copyManifest">매니페스트 복사</button>
|
|
589
|
+
<button id="slack-open-apps" class="perm-btn" type="button" data-i18n="settings.slack.guide.openApps">앱 페이지 열기</button>
|
|
590
|
+
</div>
|
|
591
|
+
</div>
|
|
590
592
|
</div>
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
593
|
+
<div class="slack-setup-step">
|
|
594
|
+
<span class="slack-step-num">2</span>
|
|
595
|
+
<div class="slack-step-body">
|
|
596
|
+
<div class="slack-step-title" data-i18n="settings.slack.guide.step2Title">봇 토큰 붙여넣기</div>
|
|
597
|
+
<div class="slack-step-desc" data-i18n="settings.slack.guide.step2Desc">앱 설정 → OAuth & Permissions → Install to Workspace → Bot User OAuth Token 복사</div>
|
|
598
|
+
<input type="password" id="slBotToken" placeholder="xoxb-..." class="input-sm">
|
|
599
|
+
<div class="slack-field-error" id="slack-bot-token-error" style="display:none" data-i18n="settings.slack.guide.botTokenError">xoxb-로 시작하는 봇 토큰이어야 합니다</div>
|
|
600
|
+
</div>
|
|
597
601
|
</div>
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
602
|
+
<div class="slack-setup-step">
|
|
603
|
+
<span class="slack-step-num">3</span>
|
|
604
|
+
<div class="slack-step-body">
|
|
605
|
+
<div class="slack-step-title" data-i18n="settings.slack.guide.step3Title">수신용 앱 토큰 (선택)</div>
|
|
606
|
+
<div class="slack-step-desc" data-i18n="settings.slack.guide.step3Location">앱 설정 → Basic Information → App-Level Tokens → Generate Token (connections:write)</div>
|
|
607
|
+
<input type="password" id="slAppToken" placeholder="xapp-... (Socket Mode)" class="input-sm">
|
|
608
|
+
<div class="slack-field-error" id="slack-app-token-error" style="display:none" data-i18n="settings.slack.guide.appTokenError">xapp-로 시작하는 앱 레벨 토큰이어야 합니다</div>
|
|
609
|
+
<div class="slack-step-desc" data-i18n="settings.slack.guide.step3Desc">비워두면 전송 전용(outbound-only)으로 동작합니다</div>
|
|
610
|
+
</div>
|
|
604
611
|
</div>
|
|
605
612
|
</div>
|
|
606
|
-
<
|
|
607
|
-
<
|
|
608
|
-
<div>
|
|
609
|
-
<
|
|
610
|
-
<
|
|
613
|
+
<details class="slack-advanced">
|
|
614
|
+
<summary data-i18n="settings.slack.advanced">고급 설정</summary>
|
|
615
|
+
<div class="settings-row sub-row">
|
|
616
|
+
<label>Team ID</label>
|
|
617
|
+
<input type="text" id="slTeamId" placeholder="T... (검증 시 자동 입력)" class="input-sm">
|
|
611
618
|
</div>
|
|
612
|
-
|
|
619
|
+
<div class="settings-row sub-row">
|
|
620
|
+
<label>Channel IDs</label>
|
|
621
|
+
<input type="text" id="slChannelIds" placeholder="콤마 구분 (비워두면 전체 허용)" class="input-sm">
|
|
622
|
+
</div>
|
|
623
|
+
<div class="settings-row sub-row">
|
|
624
|
+
<label data-i18n="settings.slack.attachPort">연결 인스턴스 포트</label>
|
|
625
|
+
<input type="text" id="slAttachPort" placeholder="3457 (이 포트의 인스턴스만 연결)" class="input-sm">
|
|
626
|
+
</div>
|
|
627
|
+
<div class="settings-row sub-row">
|
|
628
|
+
<label data-i18n="label.mentionOnly">Mention Only</label>
|
|
629
|
+
<div>
|
|
630
|
+
<button id="slMentionOff" class="perm-btn" data-i18n="btn.off">끔</button>
|
|
631
|
+
<button id="slMentionOn" class="perm-btn active" data-i18n="btn.on">켬</button>
|
|
632
|
+
</div>
|
|
633
|
+
</div>
|
|
634
|
+
<div class="settings-row sub-row">
|
|
635
|
+
<label>Reply in Thread</label>
|
|
636
|
+
<div>
|
|
637
|
+
<button id="slThreadOff" class="perm-btn" data-i18n="btn.off">끔</button>
|
|
638
|
+
<button id="slThreadOn" class="perm-btn active" data-i18n="btn.on">켬</button>
|
|
639
|
+
</div>
|
|
640
|
+
</div>
|
|
641
|
+
<div class="settings-row sub-row">
|
|
642
|
+
<label data-i18n="label.forwardAll">자동 전송</label>
|
|
643
|
+
<div>
|
|
644
|
+
<button id="slForwardOff" class="perm-btn" data-i18n="btn.off">끔</button>
|
|
645
|
+
<button id="slForwardOn" class="perm-btn active" data-i18n="btn.on">켬</button>
|
|
646
|
+
</div>
|
|
647
|
+
</div>
|
|
648
|
+
<div class="settings-row sub-row">
|
|
649
|
+
<label>Allow Bots</label>
|
|
650
|
+
<div>
|
|
651
|
+
<button id="slAllowBotsOff" class="perm-btn active" data-i18n="btn.off">끔</button>
|
|
652
|
+
<button id="slAllowBotsOn" class="perm-btn" data-i18n="btn.on">켬</button>
|
|
653
|
+
</div>
|
|
654
|
+
</div>
|
|
655
|
+
</details>
|
|
613
656
|
</div>
|
|
614
657
|
|
|
615
658
|
<!-- MCP Servers -->
|
package/public/js/constants.ts
CHANGED
|
@@ -9,6 +9,20 @@ export interface CliEntry {
|
|
|
9
9
|
providers?: string[];
|
|
10
10
|
modelsByProvider?: Record<string, string[]>;
|
|
11
11
|
effortsByProvider?: Record<string, string[]>;
|
|
12
|
+
/**
|
|
13
|
+
* Per-model reasoning-effort sets from a live opencodex catalog. An entry
|
|
14
|
+
* that exists but is EMPTY means the model takes no effort at all, so it
|
|
15
|
+
* must not fall back to `efforts`.
|
|
16
|
+
*/
|
|
17
|
+
effortsByModel?: Record<string, string[]>;
|
|
18
|
+
defaultEffortByModel?: Record<string, string>;
|
|
19
|
+
/**
|
|
20
|
+
* Provider-scoped per-model efforts for provider-split runtimes (`ai-e`).
|
|
21
|
+
* A flat map would collide on ids shared across providers.
|
|
22
|
+
*/
|
|
23
|
+
effortsByModelByProvider?: Record<string, Record<string, string[]>>;
|
|
24
|
+
defaultEffortByModelByProvider?: Record<string, Record<string, string>>;
|
|
25
|
+
modelSource?: string;
|
|
12
26
|
effortNote?: string;
|
|
13
27
|
modelNote?: string;
|
|
14
28
|
}
|
|
@@ -257,6 +271,42 @@ function normalizeRegistry(input: Record<string, unknown>): CliRegistry {
|
|
|
257
271
|
.map(([provider, efforts]) => [provider, [...efforts]])
|
|
258
272
|
);
|
|
259
273
|
}
|
|
274
|
+
if (v['effortsByModel'] && typeof v['effortsByModel'] === 'object') {
|
|
275
|
+
normalized.effortsByModel = Object.fromEntries(
|
|
276
|
+
Object.entries(v['effortsByModel'] as Record<string, unknown>)
|
|
277
|
+
.filter((entry): entry is [string, string[]] => Array.isArray(entry[1]))
|
|
278
|
+
.map(([model, efforts]) => [model, efforts.filter((e): e is string => typeof e === 'string')])
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
if (v['defaultEffortByModel'] && typeof v['defaultEffortByModel'] === 'object') {
|
|
282
|
+
normalized.defaultEffortByModel = Object.fromEntries(
|
|
283
|
+
Object.entries(v['defaultEffortByModel'] as Record<string, unknown>)
|
|
284
|
+
.filter((entry): entry is [string, string] => typeof entry[1] === 'string')
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
if (typeof v['modelSource'] === 'string') normalized.modelSource = v['modelSource'];
|
|
288
|
+
if (v['effortsByModelByProvider'] && typeof v['effortsByModelByProvider'] === 'object') {
|
|
289
|
+
normalized.effortsByModelByProvider = Object.fromEntries(
|
|
290
|
+
Object.entries(v['effortsByModelByProvider'] as Record<string, unknown>)
|
|
291
|
+
.filter((entry): entry is [string, Record<string, unknown>] =>
|
|
292
|
+
Boolean(entry[1]) && typeof entry[1] === 'object' && !Array.isArray(entry[1]))
|
|
293
|
+
.map(([provider, byModel]) => [provider, Object.fromEntries(
|
|
294
|
+
Object.entries(byModel)
|
|
295
|
+
.filter((e): e is [string, string[]] => Array.isArray(e[1]))
|
|
296
|
+
.map(([model, efforts]) => [model, efforts.filter((x): x is string => typeof x === 'string')])
|
|
297
|
+
)])
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
if (v['defaultEffortByModelByProvider'] && typeof v['defaultEffortByModelByProvider'] === 'object') {
|
|
301
|
+
normalized.defaultEffortByModelByProvider = Object.fromEntries(
|
|
302
|
+
Object.entries(v['defaultEffortByModelByProvider'] as Record<string, unknown>)
|
|
303
|
+
.filter((entry): entry is [string, Record<string, unknown>] =>
|
|
304
|
+
Boolean(entry[1]) && typeof entry[1] === 'object' && !Array.isArray(entry[1]))
|
|
305
|
+
.map(([provider, byModel]) => [provider, Object.fromEntries(
|
|
306
|
+
Object.entries(byModel).filter((e): e is [string, string] => typeof e[1] === 'string')
|
|
307
|
+
)])
|
|
308
|
+
);
|
|
309
|
+
}
|
|
260
310
|
out[key] = normalized;
|
|
261
311
|
}
|
|
262
312
|
return out;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
// ── Channel onboarding flow: pure state machine ──
|
|
2
|
+
// Four steps for every channel (안내 → 입력 → 검증 → 저장), so the popup can
|
|
3
|
+
// show "2/4" honestly and refuse to advance until THAT step's own contract is
|
|
4
|
+
// satisfied. Kept DOM-free and import-free so every gate below is unit-tested
|
|
5
|
+
// without a browser — the UI module owns rendering only.
|
|
6
|
+
//
|
|
7
|
+
// Draft values live here and survive step movement in both directions: a user
|
|
8
|
+
// who goes back to fix a typo must not lose the token they already pasted.
|
|
9
|
+
|
|
10
|
+
export type OnboardChannel = 'telegram' | 'discord' | 'slack';
|
|
11
|
+
|
|
12
|
+
export type FieldDef = {
|
|
13
|
+
/** Payload key sent to the validate route and the settings patch. */
|
|
14
|
+
key: string;
|
|
15
|
+
/** Matching input id in the settings section, for value mirroring. */
|
|
16
|
+
settingsId: string;
|
|
17
|
+
secret: boolean;
|
|
18
|
+
optional?: boolean;
|
|
19
|
+
/** Required prefix, when the provider namespaces its tokens. */
|
|
20
|
+
prefix?: string;
|
|
21
|
+
/** Illustrative shape shown as the input placeholder — never a real value. */
|
|
22
|
+
example: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const CHANNEL_FIELDS: Record<OnboardChannel, readonly FieldDef[]> = {
|
|
26
|
+
telegram: [
|
|
27
|
+
{ key: 'botToken', settingsId: 'tgToken', secret: true, example: '8123456789:AAH...' },
|
|
28
|
+
],
|
|
29
|
+
discord: [
|
|
30
|
+
{ key: 'botToken', settingsId: 'dcToken', secret: true, example: 'MTIzNDU2Nzg5...' },
|
|
31
|
+
{ key: 'guildId', settingsId: 'dcGuildId', secret: false, example: '123456789012345678' },
|
|
32
|
+
],
|
|
33
|
+
slack: [
|
|
34
|
+
{ key: 'botToken', settingsId: 'slBotToken', secret: true, prefix: 'xoxb-', example: 'xoxb-1234-5678-abcd...' },
|
|
35
|
+
{ key: 'appToken', settingsId: 'slAppToken', secret: true, optional: true, prefix: 'xapp-', example: 'xapp-1-A01...' },
|
|
36
|
+
],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Where each channel issues its credentials — the step-1 primary action. */
|
|
40
|
+
export const ISSUER_URLS: Record<OnboardChannel, string> = {
|
|
41
|
+
telegram: 'https://t.me/BotFather',
|
|
42
|
+
discord: 'https://discord.com/developers/applications',
|
|
43
|
+
slack: 'https://api.slack.com/apps',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const TOTAL_STEPS = 4;
|
|
47
|
+
/** 1 = 안내, 2 = 자격 증명 입력, 3 = 연결 검증, 4 = 저장 완료. */
|
|
48
|
+
export type StepIndex = 1 | 2 | 3 | 4;
|
|
49
|
+
|
|
50
|
+
export type Draft = Record<string, string>;
|
|
51
|
+
|
|
52
|
+
export type FlowState = {
|
|
53
|
+
channel: OnboardChannel;
|
|
54
|
+
step: StepIndex;
|
|
55
|
+
draft: Draft;
|
|
56
|
+
/** Set once step 3 passes; cleared whenever a credential changes. */
|
|
57
|
+
validatedIdentity: string | null;
|
|
58
|
+
validatedTeamId: string;
|
|
59
|
+
/** i18n key suffix for the blocking reason, or null when the step is clear. */
|
|
60
|
+
error: string | null;
|
|
61
|
+
/** Scopes the provider reported as missing, shown with the error. */
|
|
62
|
+
missingScopes: string[];
|
|
63
|
+
saved: boolean;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export function createFlow(channel: OnboardChannel, draft: Draft = {}): FlowState {
|
|
67
|
+
return {
|
|
68
|
+
channel,
|
|
69
|
+
step: 1,
|
|
70
|
+
draft: { ...draft },
|
|
71
|
+
validatedIdentity: null,
|
|
72
|
+
validatedTeamId: '',
|
|
73
|
+
error: null,
|
|
74
|
+
missingScopes: [],
|
|
75
|
+
saved: false,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function fieldsFor(channel: OnboardChannel): readonly FieldDef[] {
|
|
80
|
+
return CHANNEL_FIELDS[channel];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Local (offline) contract for the credential step: required fields present
|
|
85
|
+
* and prefixes correct. This runs BEFORE the network call so a swapped paste
|
|
86
|
+
* costs zero requests, and it is the same rule the CLI wizard enforces.
|
|
87
|
+
*/
|
|
88
|
+
export function checkCredentials(channel: OnboardChannel, draft: Draft): string | null {
|
|
89
|
+
for (const field of fieldsFor(channel)) {
|
|
90
|
+
const value = (draft[field.key] || '').trim();
|
|
91
|
+
if (!value) {
|
|
92
|
+
if (field.optional) continue;
|
|
93
|
+
return field.key === 'guildId' ? 'guild_required' : 'token_required';
|
|
94
|
+
}
|
|
95
|
+
if (field.prefix && !value.startsWith(field.prefix)) {
|
|
96
|
+
return field.key === 'appToken' ? 'app_prefix' : 'bot_prefix';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Why the given step cannot be left yet, or null when it may advance. */
|
|
103
|
+
export function blockerForStep(state: FlowState): string | null {
|
|
104
|
+
if (state.step === 1) return null;
|
|
105
|
+
if (state.step === 2) return checkCredentials(state.channel, state.draft);
|
|
106
|
+
if (state.step === 3) return state.validatedIdentity ? null : 'validation_required';
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function canAdvance(state: FlowState): boolean {
|
|
111
|
+
return state.step < TOTAL_STEPS && blockerForStep(state) === null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Move forward one step, or record why it is blocked. Never skips a gate. */
|
|
115
|
+
export function advance(state: FlowState): FlowState {
|
|
116
|
+
if (state.step >= TOTAL_STEPS) return state;
|
|
117
|
+
const blocker = blockerForStep(state);
|
|
118
|
+
if (blocker) return { ...state, error: blocker };
|
|
119
|
+
return { ...state, step: (state.step + 1) as StepIndex, error: null };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Move back one step. Draft and validation results are preserved. */
|
|
123
|
+
export function goBack(state: FlowState): FlowState {
|
|
124
|
+
if (state.step <= 1) return state;
|
|
125
|
+
return { ...state, step: (state.step - 1) as StepIndex, error: null };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Record a credential edit. Any change invalidates a previous validation:
|
|
130
|
+
* otherwise a user could validate a good token, paste a bad one, and still
|
|
131
|
+
* reach the save step on the stale pass.
|
|
132
|
+
*/
|
|
133
|
+
export function setField(state: FlowState, key: string, value: string): FlowState {
|
|
134
|
+
if ((state.draft[key] ?? '') === value) return state;
|
|
135
|
+
return {
|
|
136
|
+
...state,
|
|
137
|
+
draft: { ...state.draft, [key]: value },
|
|
138
|
+
validatedIdentity: null,
|
|
139
|
+
validatedTeamId: '',
|
|
140
|
+
error: null,
|
|
141
|
+
missingScopes: [],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function applyValidation(
|
|
146
|
+
state: FlowState,
|
|
147
|
+
result: { ok?: boolean; identity?: string; teamId?: string; error?: string; missing?: string[] },
|
|
148
|
+
): FlowState {
|
|
149
|
+
if (result?.ok) {
|
|
150
|
+
return {
|
|
151
|
+
...state,
|
|
152
|
+
validatedIdentity: result.identity || 'ok',
|
|
153
|
+
validatedTeamId: result.teamId || '',
|
|
154
|
+
error: null,
|
|
155
|
+
missingScopes: [],
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
return {
|
|
159
|
+
...state,
|
|
160
|
+
validatedIdentity: null,
|
|
161
|
+
validatedTeamId: '',
|
|
162
|
+
error: result?.error || 'network',
|
|
163
|
+
missingScopes: Array.isArray(result?.missing) ? result.missing : [],
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function markSaved(state: FlowState): FlowState {
|
|
168
|
+
return { ...state, saved: true, step: TOTAL_STEPS, error: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Payload for POST /api/channels/validate. */
|
|
172
|
+
export function validationPayload(state: FlowState): Record<string, string> {
|
|
173
|
+
const payload: Record<string, string> = { channel: state.channel };
|
|
174
|
+
for (const field of fieldsFor(state.channel)) {
|
|
175
|
+
payload[field.key] = (state.draft[field.key] || '').trim();
|
|
176
|
+
}
|
|
177
|
+
return payload;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/** Settings patch for PUT /api/settings, shaped per channel. */
|
|
181
|
+
export function settingsPatch(state: FlowState): Record<string, unknown> {
|
|
182
|
+
const value = (key: string): string => (state.draft[key] || '').trim();
|
|
183
|
+
if (state.channel === 'telegram') {
|
|
184
|
+
return { telegram: { enabled: true, token: value('botToken') } };
|
|
185
|
+
}
|
|
186
|
+
if (state.channel === 'discord') {
|
|
187
|
+
return { discord: { enabled: true, token: value('botToken'), guildId: value('guildId') } };
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
slack: {
|
|
191
|
+
enabled: true,
|
|
192
|
+
botToken: value('botToken'),
|
|
193
|
+
appToken: value('appToken'),
|
|
194
|
+
...(state.validatedTeamId ? { teamId: state.validatedTeamId } : {}),
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|