thepopebot 1.2.75-beta.2 → 1.2.75-beta.21

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 (120) hide show
  1. package/README.md +1 -1
  2. package/api/CLAUDE.md +1 -1
  3. package/api/index.js +5 -12
  4. package/bin/CLAUDE.md +1 -1
  5. package/bin/cli.js +329 -14
  6. package/bin/docker-build.js +5 -0
  7. package/bin/managed-paths.js +0 -7
  8. package/bin/sync.js +84 -0
  9. package/config/CLAUDE.md +1 -29
  10. package/config/instrumentation.js +1 -1
  11. package/lib/CLAUDE.md +3 -3
  12. package/lib/ai/CLAUDE.md +24 -3
  13. package/lib/ai/agent.js +8 -5
  14. package/lib/ai/async-channel.js +51 -0
  15. package/lib/ai/headless-stream.js +3 -0
  16. package/lib/ai/index.js +149 -173
  17. package/lib/ai/line-mappers.js +72 -9
  18. package/lib/ai/tools.js +40 -28
  19. package/lib/chat/actions.js +34 -6
  20. package/lib/chat/api.js +17 -1
  21. package/lib/chat/components/chat-header.js +4 -0
  22. package/lib/chat/components/chat-header.jsx +4 -0
  23. package/lib/chat/components/chat-input.js +1 -0
  24. package/lib/chat/components/chat-input.jsx +1 -0
  25. package/lib/chat/components/chat.js +9 -1
  26. package/lib/chat/components/chat.jsx +15 -2
  27. package/lib/chat/components/chats-page.js +3 -3
  28. package/lib/chat/components/chats-page.jsx +4 -6
  29. package/lib/chat/components/crons-page.js +1 -1
  30. package/lib/chat/components/crons-page.jsx +1 -1
  31. package/lib/chat/components/message.js +12 -4
  32. package/lib/chat/components/message.jsx +17 -4
  33. package/lib/chat/components/settings-chat-page.js +2 -1
  34. package/lib/chat/components/settings-chat-page.jsx +4 -1
  35. package/lib/chat/components/settings-coding-agents-page.js +139 -1
  36. package/lib/chat/components/settings-coding-agents-page.jsx +160 -0
  37. package/lib/chat/components/settings-jobs-page.js +13 -2
  38. package/lib/chat/components/settings-jobs-page.jsx +15 -1
  39. package/lib/chat/components/settings-secrets-layout.js +1 -1
  40. package/lib/chat/components/settings-secrets-layout.jsx +1 -1
  41. package/lib/chat/components/sidebar-history-item.js +3 -3
  42. package/lib/chat/components/sidebar-history-item.jsx +4 -6
  43. package/lib/chat/components/triggers-page.js +1 -1
  44. package/lib/chat/components/triggers-page.jsx +1 -1
  45. package/lib/cluster/actions.js +4 -4
  46. package/lib/cluster/execute.js +3 -1
  47. package/lib/code/actions.js +34 -11
  48. package/lib/code/code-page.js +40 -40
  49. package/lib/code/code-page.jsx +36 -36
  50. package/lib/code/port-forwards.js +17 -3
  51. package/lib/code/terminal-view.js +16 -0
  52. package/lib/code/terminal-view.jsx +18 -0
  53. package/lib/config.js +4 -0
  54. package/lib/cron.js +3 -3
  55. package/lib/db/api-keys.js +22 -61
  56. package/lib/db/config.js +23 -0
  57. package/lib/db/index.js +3 -1
  58. package/lib/maintenance.js +34 -11
  59. package/lib/paths.js +1 -38
  60. package/lib/tools/create-agent-job.js +0 -4
  61. package/lib/tools/docker.js +23 -16
  62. package/lib/triggers.js +4 -3
  63. package/lib/utils/render-md.js +3 -1
  64. package/package.json +2 -1
  65. package/setup/setup-ssl.mjs +414 -0
  66. package/templates/.github/workflows/rebuild-event-handler.yml +3 -0
  67. package/templates/.github/workflows/upgrade-event-handler.yml +1 -1
  68. package/templates/.gitignore.template +7 -3
  69. package/templates/.tmp/CLAUDE.md.template +5 -0
  70. package/templates/CLAUDE.md +3 -2
  71. package/templates/CLAUDE.md.template +24 -357
  72. package/templates/agent-job/CLAUDE.md.template +57 -0
  73. package/templates/agent-job/CRONS.json +16 -0
  74. package/templates/{config/agent-job → agent-job}/SOUL.md +3 -3
  75. package/templates/agent-job/SYSTEM.md +60 -0
  76. package/templates/agents/CLAUDE.md.template +54 -0
  77. package/templates/data/CLAUDE.md.template +5 -0
  78. package/templates/docker-compose.custom.yml +41 -62
  79. package/templates/docker-compose.yml +14 -21
  80. package/templates/event-handler/CLAUDE.md.template +0 -0
  81. package/templates/logs/CLAUDE.md.template +5 -0
  82. package/templates/skills/CLAUDE.md.template +57 -32
  83. package/templates/skills/active/.gitkeep +0 -0
  84. package/templates/skills/library/agent-job-secrets/SKILL.md +23 -0
  85. package/templates/skills/library/agent-job-secrets/agent-job-secrets.js +62 -0
  86. package/templates/.pi/extensions/env-sanitizer/index.ts +0 -48
  87. package/templates/.pi/extensions/env-sanitizer/package.json +0 -5
  88. package/templates/README.md +0 -75
  89. package/templates/config/CLAUDE.md.template +0 -40
  90. package/templates/config/CRONS.json +0 -56
  91. package/templates/config/agent-job/AGENT_JOB.md +0 -30
  92. package/templates/cron/CLAUDE.md.template +0 -24
  93. package/templates/docker-compose.litellm.yml +0 -82
  94. package/templates/docs/CLAUDE.md.template +0 -12
  95. package/templates/docs/CLI.md +0 -59
  96. package/templates/docs/CLUSTERS.md +0 -151
  97. package/templates/docs/CONFIGURATION.md +0 -181
  98. package/templates/docs/CRONS_AND_TRIGGERS.md +0 -132
  99. package/templates/docs/GETTING_STARTED.md +0 -64
  100. package/templates/docs/SECURITY.md +0 -61
  101. package/templates/docs/SKILLS.md +0 -113
  102. package/templates/docs/UPGRADING.md +0 -92
  103. package/templates/skills/LICENSE +0 -21
  104. package/templates/skills/README.md +0 -117
  105. package/templates/skills/agent-job-secrets/SKILL.md +0 -25
  106. package/templates/skills/agent-job-secrets/agent-job-secrets.js +0 -66
  107. package/templates/traefik-dynamic.yml.example +0 -7
  108. package/templates/triggers/CLAUDE.md.template +0 -41
  109. /package/templates/{config → agent-job}/HEARTBEAT.md +0 -0
  110. /package/templates/{cron → data}/.gitkeep +0 -0
  111. /package/templates/{logs → data/clusters}/.gitkeep +0 -0
  112. /package/templates/{triggers → data/db}/.gitkeep +0 -0
  113. /package/templates/{config/agent-job → event-handler}/SUMMARY.md +0 -0
  114. /package/templates/{config → event-handler}/TRIGGERS.json +0 -0
  115. /package/templates/{config → event-handler}/agent-chat/SYSTEM.md +0 -0
  116. /package/templates/{config/cluster → event-handler/clusters}/ROLE.md +0 -0
  117. /package/templates/{config/cluster → event-handler/clusters}/SYSTEM.md +0 -0
  118. /package/templates/{config → event-handler}/code-chat/SYSTEM.md +0 -0
  119. /package/templates/{config → event-handler}/litellm/main.yaml +0 -0
  120. /package/templates/skills/{playwright-cli → library/playwright-cli}/SKILL.md +0 -0
@@ -52,6 +52,9 @@ function DefaultAgentSection({ settings, onReload }) {
52
52
  if (settings.openCode?.enabled && isOpenCodeReady(settings)) {
53
53
  available.push({ value: "opencode", label: "OpenCode" });
54
54
  }
55
+ if (settings.kimiCli?.enabled && isKimiCliReady(settings)) {
56
+ available.push({ value: "kimi-cli", label: "Kimi CLI" });
57
+ }
55
58
  const handleChange = async (e) => {
56
59
  setSaving(true);
57
60
  const result = await setCodingAgentDefault(e.target.value);
@@ -99,7 +102,8 @@ function AgentCards({ settings, onReload }) {
99
102
  /* @__PURE__ */ jsx(PiCard, { settings, onReload }),
100
103
  /* @__PURE__ */ jsx(GeminiCliCard, { settings, onReload }),
101
104
  /* @__PURE__ */ jsx(CodexCliCard, { settings, onReload }),
102
- /* @__PURE__ */ jsx(OpenCodeCard, { settings, onReload })
105
+ /* @__PURE__ */ jsx(OpenCodeCard, { settings, onReload }),
106
+ /* @__PURE__ */ jsx(KimiCliCard, { settings, onReload })
103
107
  ] })
104
108
  ] });
105
109
  }
@@ -687,6 +691,136 @@ function OpenCodeCard({ settings, onReload }) {
687
691
  ) })
688
692
  ] });
689
693
  }
694
+ function KimiCliCard({ settings, onReload }) {
695
+ const config = settings.kimiCli;
696
+ const [modelText, setModelText] = useState(config.model || "");
697
+ const [saved, setSaved] = useState(false);
698
+ const showSaved = () => {
699
+ setSaved(true);
700
+ setTimeout(() => setSaved(false), 2e3);
701
+ };
702
+ const handleToggle = async () => {
703
+ await updateCodingAgentConfig("kimi-cli", { enabled: !config.enabled });
704
+ await onReload();
705
+ };
706
+ const handleProviderChange = async (e) => {
707
+ const newProvider = e.target.value;
708
+ const cp = settings?.customProviders?.find((p) => p.key === newProvider);
709
+ const models = getAgentModels(settings, newProvider);
710
+ const newModel = models.length > 0 ? models[0].id : cp?.models?.[0] || "";
711
+ setModelText(newModel);
712
+ await updateCodingAgentConfig("kimi-cli", { provider: newProvider, model: newModel });
713
+ await onReload();
714
+ showSaved();
715
+ };
716
+ const handleModelChange = async (e) => {
717
+ await updateCodingAgentConfig("kimi-cli", { model: e.target.value });
718
+ await onReload();
719
+ showSaved();
720
+ };
721
+ const handleModelTextSave = async () => {
722
+ await updateCodingAgentConfig("kimi-cli", { model: modelText });
723
+ await onReload();
724
+ showSaved();
725
+ };
726
+ const availableProviders = [];
727
+ if (settings?.builtinProviders && settings?.credentialStatuses) {
728
+ const statusMap = new Map(settings.credentialStatuses.map((s) => [s.key, s.isSet]));
729
+ for (const [slug, prov] of Object.entries(settings.builtinProviders)) {
730
+ const hasKey = prov.credentials.some((c) => statusMap.get(c.key));
731
+ if (hasKey) {
732
+ availableProviders.push({ slug, name: prov.name });
733
+ }
734
+ }
735
+ }
736
+ if (settings?.customProviders) {
737
+ for (const cp of settings.customProviders) {
738
+ availableProviders.push({ slug: cp.key, name: cp.name });
739
+ }
740
+ }
741
+ const ready = isKimiCliReady(settings);
742
+ const selectedProviderReady = availableProviders.some((p) => p.slug === config.provider);
743
+ const builtinModels = config.provider ? getAgentModels(settings, config.provider) : [];
744
+ const customProvider = settings?.customProviders?.find((p) => p.key === config.provider);
745
+ const providerModels = builtinModels.length > 0 ? builtinModels : (customProvider?.models || []).map((m) => ({ id: m, name: m }));
746
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border bg-card p-4", children: [
747
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
748
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
749
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: "Kimi CLI" }),
750
+ config.enabled && /* @__PURE__ */ jsx(StatusDot, { ready })
751
+ ] }),
752
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
753
+ saved && /* @__PURE__ */ jsxs("span", { className: "text-xs text-green-500 inline-flex items-center gap-1", children: [
754
+ /* @__PURE__ */ jsx(CheckIcon, { size: 12 }),
755
+ " Saved"
756
+ ] }),
757
+ /* @__PURE__ */ jsx(ToggleSwitch, { checked: config.enabled, onChange: handleToggle })
758
+ ] })
759
+ ] }),
760
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground mb-3", children: "Coding agent by MoonshotAI with multi-provider support." }),
761
+ config.enabled && /* @__PURE__ */ jsx("div", { className: "border-t border-border pt-3 space-y-3", children: availableProviders.length > 0 ? /* @__PURE__ */ jsxs(Fragment, { children: [
762
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
763
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Provider" }),
764
+ /* @__PURE__ */ jsxs(
765
+ "select",
766
+ {
767
+ value: config.provider || "",
768
+ onChange: handleProviderChange,
769
+ className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground",
770
+ children: [
771
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select provider..." }),
772
+ availableProviders.map((p) => /* @__PURE__ */ jsx("option", { value: p.slug, children: p.name }, p.slug))
773
+ ]
774
+ }
775
+ )
776
+ ] }),
777
+ config.provider && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
778
+ /* @__PURE__ */ jsx("label", { className: "text-sm font-medium", children: "Model" }),
779
+ providerModels.length > 0 ? /* @__PURE__ */ jsx(
780
+ "select",
781
+ {
782
+ value: config.model || "",
783
+ onChange: handleModelChange,
784
+ className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground",
785
+ children: providerModels.map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: m.name }, m.id))
786
+ }
787
+ ) : /* @__PURE__ */ jsx(
788
+ "input",
789
+ {
790
+ type: "text",
791
+ value: modelText,
792
+ onChange: (e) => setModelText(e.target.value),
793
+ onKeyDown: (e) => e.key === "Enter" && handleModelTextSave(),
794
+ placeholder: "Model name",
795
+ className: "w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
796
+ }
797
+ )
798
+ ] }),
799
+ config.provider && !selectedProviderReady && /* @__PURE__ */ jsx(
800
+ CredentialHint,
801
+ {
802
+ ready: false,
803
+ missingText: `${config.provider} API Key is not set. Configure it on the LLMs page.`
804
+ }
805
+ ),
806
+ config.provider && providerModels.length === 0 && /* @__PURE__ */ jsx("div", { className: "flex justify-end mt-1", children: /* @__PURE__ */ jsx(
807
+ "button",
808
+ {
809
+ onClick: handleModelTextSave,
810
+ disabled: modelText === (config.model || ""),
811
+ className: "rounded-md px-3 py-1.5 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 transition-colors",
812
+ children: "Save"
813
+ }
814
+ ) })
815
+ ] }) : /* @__PURE__ */ jsx(
816
+ CredentialHint,
817
+ {
818
+ ready: false,
819
+ missingText: "Configure at least one LLM provider on the LLMs page to use Kimi CLI"
820
+ }
821
+ ) })
822
+ ] });
823
+ }
690
824
  function getAgentModels(settings, providerSlug) {
691
825
  const provider = settings?.builtinProviders?.[providerSlug];
692
826
  if (!provider?.models) return [];
@@ -720,6 +854,10 @@ function isOpenCodeReady(settings) {
720
854
  if (!settings.openCode?.enabled || !settings.openCode?.provider) return false;
721
855
  return isProviderReady(settings, settings.openCode.provider);
722
856
  }
857
+ function isKimiCliReady(settings) {
858
+ if (!settings.kimiCli?.enabled || !settings.kimiCli?.provider) return false;
859
+ return isProviderReady(settings, settings.kimiCli.provider);
860
+ }
723
861
  function isProviderReady(settings, provider) {
724
862
  const statusMap = new Map((settings.credentialStatuses || []).map((s) => [s.key, s.isSet]));
725
863
  const builtin = settings.builtinProviders?.[provider];
@@ -72,6 +72,9 @@ function DefaultAgentSection({ settings, onReload }) {
72
72
  if (settings.openCode?.enabled && isOpenCodeReady(settings)) {
73
73
  available.push({ value: 'opencode', label: 'OpenCode' });
74
74
  }
75
+ if (settings.kimiCli?.enabled && isKimiCliReady(settings)) {
76
+ available.push({ value: 'kimi-cli', label: 'Kimi CLI' });
77
+ }
75
78
 
76
79
  const handleChange = async (e) => {
77
80
  setSaving(true);
@@ -133,6 +136,7 @@ function AgentCards({ settings, onReload }) {
133
136
  <GeminiCliCard settings={settings} onReload={onReload} />
134
137
  <CodexCliCard settings={settings} onReload={onReload} />
135
138
  <OpenCodeCard settings={settings} onReload={onReload} />
139
+ <KimiCliCard settings={settings} onReload={onReload} />
136
140
  </div>
137
141
  </div>
138
142
  );
@@ -834,6 +838,157 @@ function OpenCodeCard({ settings, onReload }) {
834
838
  );
835
839
  }
836
840
 
841
+ // ─────────────────────────────────────────────────────────────────────────────
842
+ // Kimi CLI card
843
+ // ─────────────────────────────────────────────────────────────────────────────
844
+
845
+ function KimiCliCard({ settings, onReload }) {
846
+ const config = settings.kimiCli;
847
+ const [modelText, setModelText] = useState(config.model || '');
848
+ const [saved, setSaved] = useState(false);
849
+
850
+ const showSaved = () => {
851
+ setSaved(true);
852
+ setTimeout(() => setSaved(false), 2000);
853
+ };
854
+
855
+ const handleToggle = async () => {
856
+ await updateCodingAgentConfig('kimi-cli', { enabled: !config.enabled });
857
+ await onReload();
858
+ };
859
+
860
+ const handleProviderChange = async (e) => {
861
+ const newProvider = e.target.value;
862
+ const cp = settings?.customProviders?.find((p) => p.key === newProvider);
863
+ const models = getAgentModels(settings, newProvider);
864
+ const newModel = models.length > 0 ? models[0].id : (cp?.models?.[0] || '');
865
+ setModelText(newModel);
866
+ await updateCodingAgentConfig('kimi-cli', { provider: newProvider, model: newModel });
867
+ await onReload();
868
+ showSaved();
869
+ };
870
+
871
+ const handleModelChange = async (e) => {
872
+ await updateCodingAgentConfig('kimi-cli', { model: e.target.value });
873
+ await onReload();
874
+ showSaved();
875
+ };
876
+
877
+ const handleModelTextSave = async () => {
878
+ await updateCodingAgentConfig('kimi-cli', { model: modelText });
879
+ await onReload();
880
+ showSaved();
881
+ };
882
+
883
+ const availableProviders = [];
884
+ if (settings?.builtinProviders && settings?.credentialStatuses) {
885
+ const statusMap = new Map(settings.credentialStatuses.map((s) => [s.key, s.isSet]));
886
+ for (const [slug, prov] of Object.entries(settings.builtinProviders)) {
887
+ const hasKey = prov.credentials.some((c) => statusMap.get(c.key));
888
+ if (hasKey) {
889
+ availableProviders.push({ slug, name: prov.name });
890
+ }
891
+ }
892
+ }
893
+ if (settings?.customProviders) {
894
+ for (const cp of settings.customProviders) {
895
+ availableProviders.push({ slug: cp.key, name: cp.name });
896
+ }
897
+ }
898
+
899
+ const ready = isKimiCliReady(settings);
900
+ const selectedProviderReady = availableProviders.some(p => p.slug === config.provider);
901
+
902
+ const builtinModels = config.provider ? getAgentModels(settings, config.provider) : [];
903
+ const customProvider = settings?.customProviders?.find((p) => p.key === config.provider);
904
+ const providerModels = builtinModels.length > 0 ? builtinModels : (customProvider?.models || []).map((m) => ({ id: m, name: m }));
905
+
906
+ return (
907
+ <div className="rounded-lg border bg-card p-4">
908
+ <div className="flex items-center justify-between mb-1">
909
+ <div className="flex items-center gap-2">
910
+ <span className="text-sm font-medium">Kimi CLI</span>
911
+ {config.enabled && <StatusDot ready={ready} />}
912
+ </div>
913
+ <div className="flex items-center gap-3">
914
+ {saved && <span className="text-xs text-green-500 inline-flex items-center gap-1"><CheckIcon size={12} /> Saved</span>}
915
+ <ToggleSwitch checked={config.enabled} onChange={handleToggle} />
916
+ </div>
917
+ </div>
918
+ <p className="text-xs text-muted-foreground mb-3">Coding agent by MoonshotAI with multi-provider support.</p>
919
+
920
+ {config.enabled && (
921
+ <div className="border-t border-border pt-3 space-y-3">
922
+ {availableProviders.length > 0 ? (
923
+ <>
924
+ <div className="flex items-center justify-between">
925
+ <label className="text-sm font-medium">Provider</label>
926
+ <select
927
+ value={config.provider || ''}
928
+ onChange={handleProviderChange}
929
+ className="w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
930
+ >
931
+ <option value="">Select provider...</option>
932
+ {availableProviders.map((p) => (
933
+ <option key={p.slug} value={p.slug}>{p.name}</option>
934
+ ))}
935
+ </select>
936
+ </div>
937
+
938
+ {config.provider && (
939
+ <div className="flex items-center justify-between">
940
+ <label className="text-sm font-medium">Model</label>
941
+ {providerModels.length > 0 ? (
942
+ <select
943
+ value={config.model || ''}
944
+ onChange={handleModelChange}
945
+ className="w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
946
+ >
947
+ {providerModels.map((m) => (
948
+ <option key={m.id} value={m.id}>{m.name}</option>
949
+ ))}
950
+ </select>
951
+ ) : (
952
+ <input
953
+ type="text"
954
+ value={modelText}
955
+ onChange={(e) => setModelText(e.target.value)}
956
+ onKeyDown={(e) => e.key === 'Enter' && handleModelTextSave()}
957
+ placeholder="Model name"
958
+ className="w-48 rounded-md border border-border bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-1 focus:ring-foreground"
959
+ />
960
+ )}
961
+ </div>
962
+ )}
963
+
964
+ {config.provider && !selectedProviderReady && (
965
+ <CredentialHint
966
+ ready={false}
967
+ missingText={`${config.provider} API Key is not set. Configure it on the LLMs page.`}
968
+ />
969
+ )}
970
+
971
+ {config.provider && providerModels.length === 0 && (
972
+ <div className="flex justify-end mt-1">
973
+ <button onClick={handleModelTextSave} disabled={modelText === (config.model || '')}
974
+ className="rounded-md px-3 py-1.5 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 transition-colors">
975
+ Save
976
+ </button>
977
+ </div>
978
+ )}
979
+ </>
980
+ ) : (
981
+ <CredentialHint
982
+ ready={false}
983
+ missingText="Configure at least one LLM provider on the LLMs page to use Kimi CLI"
984
+ />
985
+ )}
986
+ </div>
987
+ )}
988
+ </div>
989
+ );
990
+ }
991
+
837
992
  // ─────────────────────────────────────────────────────────────────────────────
838
993
  // Shared helpers
839
994
  // ─────────────────────────────────────────────────────────────────────────────
@@ -881,6 +1036,11 @@ function isOpenCodeReady(settings) {
881
1036
  return isProviderReady(settings, settings.openCode.provider);
882
1037
  }
883
1038
 
1039
+ function isKimiCliReady(settings) {
1040
+ if (!settings.kimiCli?.enabled || !settings.kimiCli?.provider) return false;
1041
+ return isProviderReady(settings, settings.kimiCli.provider);
1042
+ }
1043
+
884
1044
  function isProviderReady(settings, provider) {
885
1045
  // Check if selected provider has credentials
886
1046
  const statusMap = new Map((settings.credentialStatuses || []).map(s => [s.key, s.isSet]));
@@ -8,7 +8,8 @@ import {
8
8
  getAgentJobSecrets,
9
9
  updateAgentJobSecret,
10
10
  deleteAgentJobSecretAction,
11
- initiateOAuthFlow
11
+ initiateOAuthFlow,
12
+ getOAuthSecretCredentials
12
13
  } from "../actions.js";
13
14
  function buildProviderOptions() {
14
15
  const options = [];
@@ -170,6 +171,16 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
170
171
  setCopied(false);
171
172
  setRedirectUri(`${window.location.origin}/api/oauth/callback`);
172
173
  if (!editingSecret) setTimeout(() => nameRef.current?.focus(), 50);
174
+ if (editingSecret?.key) {
175
+ getOAuthSecretCredentials(editingSecret.key).then((creds) => {
176
+ if (creds && !creds.error) {
177
+ setClientId(creds.clientId);
178
+ setClientSecret(creds.clientSecret);
179
+ const match = PROVIDER_OPTIONS.find((o) => o.tokenUrl === creds.tokenUrl);
180
+ if (match) setSelectedOption(match.id);
181
+ }
182
+ });
183
+ }
173
184
  }
174
185
  return () => {
175
186
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -236,7 +247,7 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
236
247
  tokenUrl: opt.tokenUrl,
237
248
  scopes,
238
249
  secretType: "agent_job_secret",
239
- returnPath: "/admin/event-handler/agent-jobs"
250
+ returnPath: "/admin/event-handler/agent-secrets"
240
251
  });
241
252
  if (result?.error) {
242
253
  setError(result.error);
@@ -9,6 +9,7 @@ import {
9
9
  updateAgentJobSecret,
10
10
  deleteAgentJobSecretAction,
11
11
  initiateOAuthFlow,
12
+ getOAuthSecretCredentials,
12
13
  } from '../actions.js';
13
14
 
14
15
  // ─────────────────────────────────────────────────────────────────────────────
@@ -225,6 +226,19 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
225
226
  setCopied(false);
226
227
  setRedirectUri(`${window.location.origin}/api/oauth/callback`);
227
228
  if (!editingSecret) setTimeout(() => nameRef.current?.focus(), 50);
229
+
230
+ // Pre-fill OAuth credentials from stored secret
231
+ if (editingSecret?.key) {
232
+ getOAuthSecretCredentials(editingSecret.key).then((creds) => {
233
+ if (creds && !creds.error) {
234
+ setClientId(creds.clientId);
235
+ setClientSecret(creds.clientSecret);
236
+ // Auto-select provider by matching tokenUrl
237
+ const match = PROVIDER_OPTIONS.find((o) => o.tokenUrl === creds.tokenUrl);
238
+ if (match) setSelectedOption(match.id);
239
+ }
240
+ });
241
+ }
228
242
  }
229
243
  return () => {
230
244
  if (timeoutRef.current) clearTimeout(timeoutRef.current);
@@ -306,7 +320,7 @@ function AddSecretDialog({ open, onAdd, onCancel, onOAuthSuccess, editingSecret
306
320
  tokenUrl: opt.tokenUrl,
307
321
  scopes,
308
322
  secretType: 'agent_job_secret',
309
- returnPath: '/admin/event-handler/agent-jobs',
323
+ returnPath: '/admin/event-handler/agent-secrets',
310
324
  });
311
325
 
312
326
  if (result?.error) {
@@ -30,7 +30,7 @@ const EVENT_HANDLER_TABS = [
30
30
  { id: "llms", label: "LLMs", href: "/admin/event-handler/llms" },
31
31
  { id: "chat", label: "Chat", href: "/admin/event-handler/chat" },
32
32
  { id: "coding-agents", label: "Coding Agents", href: "/admin/event-handler/coding-agents" },
33
- { id: "agent-jobs", label: "Agent Jobs", href: "/admin/event-handler/agent-jobs" },
33
+ { id: "agent-secrets", label: "Agent Secrets", href: "/admin/event-handler/agent-secrets" },
34
34
  { id: "webhooks", label: "Webhooks", href: "/admin/event-handler/webhooks" },
35
35
  { id: "telegram", label: "Telegram", href: "/admin/event-handler/telegram" },
36
36
  { id: "voice", label: "Voice", href: "/admin/event-handler/voice" }
@@ -52,7 +52,7 @@ const EVENT_HANDLER_TABS = [
52
52
  { id: 'llms', label: 'LLMs', href: '/admin/event-handler/llms' },
53
53
  { id: 'chat', label: 'Chat', href: '/admin/event-handler/chat' },
54
54
  { id: 'coding-agents', label: 'Coding Agents', href: '/admin/event-handler/coding-agents' },
55
- { id: 'agent-jobs', label: 'Agent Jobs', href: '/admin/event-handler/agent-jobs' },
55
+ { id: 'agent-secrets', label: 'Agent Secrets', href: '/admin/event-handler/agent-secrets' },
56
56
  { id: 'webhooks', label: 'Webhooks', href: '/admin/event-handler/webhooks' },
57
57
  { id: 'telegram', label: 'Telegram', href: '/admin/event-handler/telegram' },
58
58
  { id: 'voice', label: 'Voice', href: '/admin/event-handler/voice' },
@@ -40,10 +40,10 @@ function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename }) {
40
40
  setOpenMobile(false);
41
41
  },
42
42
  children: [
43
- chat.chatMode === "code" ? /* @__PURE__ */ jsxs("span", { className: "relative", children: [
44
- /* @__PURE__ */ jsx(CodeIcon, { size: 14 }),
43
+ /* @__PURE__ */ jsxs("span", { className: "relative", children: [
44
+ chat.chatMode === "code" ? /* @__PURE__ */ jsx(CodeIcon, { size: 14 }) : /* @__PURE__ */ jsx(AgentIcon, { size: 14 }),
45
45
  chat.hasChanges ? /* @__PURE__ */ jsx("span", { className: "absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" }) : null
46
- ] }) : /* @__PURE__ */ jsx(AgentIcon, { size: 14 }),
46
+ ] }),
47
47
  /* @__PURE__ */ jsx("span", { className: "truncate flex-1", children: chat.title })
48
48
  ]
49
49
  }
@@ -40,12 +40,10 @@ export function SidebarHistoryItem({ chat, isActive, onDelete, onStar, onRename
40
40
  setOpenMobile(false);
41
41
  }}
42
42
  >
43
- {chat.chatMode === 'code' ? (
44
- <span className="relative">
45
- <CodeIcon size={14} />
46
- {chat.hasChanges ? <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" /> : null}
47
- </span>
48
- ) : <AgentIcon size={14} />}
43
+ <span className="relative">
44
+ {chat.chatMode === 'code' ? <CodeIcon size={14} /> : <AgentIcon size={14} />}
45
+ {chat.hasChanges ? <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" /> : null}
46
+ </span>
49
47
  <span className="truncate flex-1">
50
48
  {chat.title}
51
49
  </span>
@@ -133,7 +133,7 @@ function TriggersPage() {
133
133
  /* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No triggers configured" }),
134
134
  /* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground max-w-sm", children: [
135
135
  "Add webhook triggers by editing ",
136
- /* @__PURE__ */ jsx("span", { className: "font-mono", children: "config/TRIGGERS.json" }),
136
+ /* @__PURE__ */ jsx("span", { className: "font-mono", children: "event-handler/TRIGGERS.json" }),
137
137
  " in your project."
138
138
  ] })
139
139
  ] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
@@ -193,7 +193,7 @@ export function TriggersPage() {
193
193
  </div>
194
194
  <p className="text-sm font-medium mb-1">No triggers configured</p>
195
195
  <p className="text-xs text-muted-foreground max-w-sm">
196
- Add webhook triggers by editing <span className="font-mono">config/TRIGGERS.json</span> in your project.
196
+ Add webhook triggers by editing <span className="font-mono">event-handler/TRIGGERS.json</span> in your project.
197
197
  </p>
198
198
  </div>
199
199
  ) : (
@@ -3,7 +3,7 @@
3
3
  import fs from 'fs';
4
4
  import path from 'path';
5
5
  import { auth } from '../auth/index.js';
6
- import { configDir } from '../paths.js';
6
+ import { PROJECT_ROOT } from '../paths.js';
7
7
  import {
8
8
  createCluster as dbCreateCluster,
9
9
  getClusterById,
@@ -25,7 +25,7 @@ import {
25
25
  } from '../db/clusters.js';
26
26
 
27
27
  function readDefault(filename) {
28
- try { return fs.readFileSync(path.join(configDir, filename), 'utf8'); }
28
+ try { return fs.readFileSync(path.join(PROJECT_ROOT, filename), 'utf8'); }
29
29
  catch { return ''; }
30
30
  }
31
31
 
@@ -62,7 +62,7 @@ export async function getCluster(clusterId) {
62
62
 
63
63
  export async function createCluster(name = 'New Cluster') {
64
64
  const user = await requireAuth();
65
- const cluster = dbCreateCluster(user.id, { name, systemPrompt: readDefault('cluster/SYSTEM.md') });
65
+ const cluster = dbCreateCluster(user.id, { name, systemPrompt: readDefault('event-handler/clusters/SYSTEM.md') });
66
66
  const { clusterDir } = await import('./execute.js');
67
67
  const dir = clusterDir(cluster);
68
68
  fs.mkdirSync(`${dir}/shared`, { recursive: true });
@@ -130,7 +130,7 @@ export async function createClusterRoleAction(clusterId, roleName, role = '') {
130
130
  const user = await requireAuth();
131
131
  const cluster = getClusterById(clusterId);
132
132
  if (!cluster || cluster.userId !== user.id) return { success: false };
133
- const record = dbCreateClusterRole(clusterId, { roleName, role: role || readDefault('cluster/ROLE.md') });
133
+ const record = dbCreateClusterRole(clusterId, { roleName, role: role || readDefault('event-handler/clusters/ROLE.md') });
134
134
  // Create role directory
135
135
  const { roleDir } = await import('./execute.js');
136
136
  const dir = roleDir(cluster, record);
@@ -1,7 +1,9 @@
1
1
  import { randomUUID } from 'crypto';
2
2
  import path from 'path';
3
3
  import fs from 'fs';
4
- import { clusterDataDir } from '../paths.js';
4
+ import { PROJECT_ROOT } from '../paths.js';
5
+
6
+ const clusterDataDir = process.env.CLUSTER_DATA_PATH || path.join(PROJECT_ROOT, 'data/clusters');
5
7
  import { getConfig } from '../config.js';
6
8
  import { stopContainer as dockerStopContainer, removeContainer, runClusterWorkerContainer, resolveHostPath, listContainers } from '../tools/docker.js';
7
9
  import { getRoleWithCluster, getClusterRolesByCluster, roleShortId } from '../db/clusters.js';
@@ -543,13 +543,21 @@ export async function getWorkspaceDiffStats(id, authenticatedUser) {
543
543
  const { execSync } = await import('child_process');
544
544
  const opts = { cwd: repoPath, encoding: 'utf8', timeout: 5000 };
545
545
 
546
- const featureBranch = workspace.featureBranch || workspace.branch || 'main';
547
546
  const baseBranch = workspace.branch || 'main';
548
- let diffRef;
547
+ let currentBranch;
549
548
  try {
550
- execSync(`git -c safe.directory='*' rev-parse --verify origin/${featureBranch} 2>/dev/null`, opts);
551
- diffRef = `origin/${featureBranch}`;
552
- } catch {
549
+ const detected = execSync(`git -c safe.directory='*' rev-parse --abbrev-ref HEAD`, opts).trim();
550
+ currentBranch = (detected && detected !== 'HEAD') ? detected : null;
551
+ } catch { /* leave null */ }
552
+ let diffRef;
553
+ if (currentBranch) {
554
+ try {
555
+ execSync(`git -c safe.directory='*' rev-parse --verify origin/${currentBranch} 2>/dev/null`, opts);
556
+ diffRef = `origin/${currentBranch}`;
557
+ } catch {
558
+ diffRef = `origin/${baseBranch}`;
559
+ }
560
+ } else {
553
561
  diffRef = `origin/${baseBranch}`;
554
562
  }
555
563
 
@@ -584,7 +592,14 @@ export async function getWorkspaceDiffStats(id, authenticatedUser) {
584
592
  }
585
593
 
586
594
  updateHasChanges(id, insertions > 0 || deletions > 0);
587
- return { success: true, insertions, deletions };
595
+
596
+ // Sync featureBranch in DB if the actual branch differs
597
+ if (currentBranch && currentBranch !== workspace.featureBranch) {
598
+ const { updateFeatureBranch } = await import('../db/code-workspaces.js');
599
+ updateFeatureBranch(id, currentBranch);
600
+ }
601
+
602
+ return { success: true, insertions, deletions, currentBranch };
588
603
  } catch (err) {
589
604
  console.error(`[getWorkspaceDiffStats] workspace=${id}`, err);
590
605
  return { success: false };
@@ -618,13 +633,21 @@ export async function getWorkspaceDiffFull(id, authenticatedUser) {
618
633
  const { execSync } = await import('child_process');
619
634
  const opts = { cwd: repoPath, encoding: 'utf8', timeout: 10000 };
620
635
 
621
- const featureBranch = workspace.featureBranch || workspace.branch || 'main';
622
636
  const baseBranch = workspace.branch || 'main';
623
- let diffRef;
637
+ let currentBranch;
624
638
  try {
625
- execSync(`git -c safe.directory='*' rev-parse --verify origin/${featureBranch} 2>/dev/null`, opts);
626
- diffRef = `origin/${featureBranch}`;
627
- } catch {
639
+ const detected = execSync(`git -c safe.directory='*' rev-parse --abbrev-ref HEAD`, opts).trim();
640
+ currentBranch = (detected && detected !== 'HEAD') ? detected : null;
641
+ } catch { /* leave null */ }
642
+ let diffRef;
643
+ if (currentBranch) {
644
+ try {
645
+ execSync(`git -c safe.directory='*' rev-parse --verify origin/${currentBranch} 2>/dev/null`, opts);
646
+ diffRef = `origin/${currentBranch}`;
647
+ } catch {
648
+ diffRef = `origin/${baseBranch}`;
649
+ }
650
+ } else {
628
651
  diffRef = `origin/${baseBranch}`;
629
652
  }
630
653