thepopebot 1.2.75-beta.2 → 1.2.75-beta.4

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/lib/ai/tools.js CHANGED
@@ -54,8 +54,8 @@ const agentChatCodingTool = tool(
54
54
  const codingAgent = getConfig('CODING_AGENT') || 'claude-code';
55
55
  const containerName = `${codingAgent}-headless-${randomUUID().slice(0, 8)}`;
56
56
 
57
- const { runHeadlessContainer } = await import('../tools/docker.js');
58
- const { backendApi } = await runHeadlessContainer({
57
+ const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
58
+ await runHeadlessContainer({
59
59
  containerName,
60
60
  repo,
61
61
  branch: 'main',
@@ -66,20 +66,29 @@ const agentChatCodingTool = tool(
66
66
  injectSecrets: true,
67
67
  });
68
68
 
69
- return JSON.stringify({
70
- success: true,
71
- status: 'started',
72
- containerName,
73
- featureBranch,
74
- codingAgent,
75
- backendApi,
76
- });
69
+ const streamCallback = runtime.configurable.streamCallback;
70
+ const { parseHeadlessStream } = await import('./headless-stream.js');
71
+
72
+ const logStream = await tailContainerLogs(containerName);
73
+ let resultSummary = '';
74
+
75
+ for await (const chunk of parseHeadlessStream(logStream, codingAgent)) {
76
+ if (chunk._resultSummary) resultSummary = chunk._resultSummary;
77
+ streamCallback?.(chunk);
78
+ }
79
+
80
+ const exitCode = await waitForContainer(containerName);
81
+ await removeContainer(containerName);
82
+ streamCallback?.(null);
83
+
84
+ if (exitCode !== 0) {
85
+ return `Task exited with errors (exit code ${exitCode}).`;
86
+ }
87
+ return resultSummary || 'Task completed successfully.';
77
88
  } catch (err) {
78
89
  console.error('[coding_agent] Failed:', err);
79
- return JSON.stringify({
80
- success: false,
81
- error: err.message || 'Failed to launch investigation container',
82
- });
90
+ runtime.configurable.streamCallback?.(null);
91
+ return `Failed to run coding agent: ${err.message}`;
83
92
  }
84
93
  },
85
94
  {
@@ -91,7 +100,6 @@ const agentChatCodingTool = tool(
91
100
  'A direct copy of the coding task including all relevant context from the conversation.'
92
101
  ),
93
102
  }),
94
- returnDirect: true,
95
103
  }
96
104
  );
97
105
 
@@ -110,30 +118,39 @@ const codeChatCodingTool = tool(
110
118
  const featureBranch = workspace?.featureBranch;
111
119
  const mode = codeModeType === 'code' ? 'dangerous' : 'plan';
112
120
 
113
- const { runHeadlessContainer } = await import('../tools/docker.js');
121
+ const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
114
122
  const codingAgent = getConfig('CODING_AGENT') || 'claude-code';
115
123
  const containerName = `${codingAgent}-headless-${randomUUID().slice(0, 8)}`;
116
124
 
117
- const { backendApi } = await runHeadlessContainer({
125
+ await runHeadlessContainer({
118
126
  containerName, repo, branch, featureBranch, workspaceId,
119
127
  taskPrompt: prompt,
120
128
  mode,
121
129
  });
122
130
 
123
- return JSON.stringify({
124
- success: true,
125
- status: 'started',
126
- containerName,
127
- featureBranch,
128
- codingAgent,
129
- backendApi,
130
- });
131
+ const streamCallback = runtime.configurable.streamCallback;
132
+ const { parseHeadlessStream } = await import('./headless-stream.js');
133
+
134
+ const logStream = await tailContainerLogs(containerName);
135
+ let resultSummary = '';
136
+
137
+ for await (const chunk of parseHeadlessStream(logStream, codingAgent)) {
138
+ if (chunk._resultSummary) resultSummary = chunk._resultSummary;
139
+ streamCallback?.(chunk);
140
+ }
141
+
142
+ const exitCode = await waitForContainer(containerName);
143
+ await removeContainer(containerName);
144
+ streamCallback?.(null);
145
+
146
+ if (exitCode !== 0) {
147
+ return `Task exited with errors (exit code ${exitCode}).`;
148
+ }
149
+ return resultSummary || 'Task completed successfully.';
131
150
  } catch (err) {
132
151
  console.error('[coding_agent] Failed:', err);
133
- return JSON.stringify({
134
- success: false,
135
- error: err.message || 'Failed to launch headless coding task',
136
- });
152
+ runtime.configurable.streamCallback?.(null);
153
+ return `Failed to run coding agent: ${err.message}`;
137
154
  }
138
155
  },
139
156
  {
@@ -145,7 +162,6 @@ const codeChatCodingTool = tool(
145
162
  'A direct copy of the coding task including all relevant context from the conversation.'
146
163
  ),
147
164
  }),
148
- returnDirect: true,
149
165
  }
150
166
  );
151
167
 
@@ -653,6 +653,9 @@ export async function getCodingAgentSettings() {
653
653
  const openCodeEnabled = getConfig('CODING_AGENT_OPENCODE_ENABLED');
654
654
  const openCodeProvider = getConfig('CODING_AGENT_OPENCODE_PROVIDER') || '';
655
655
  const openCodeModel = getConfig('CODING_AGENT_OPENCODE_MODEL') || '';
656
+ const kimiCliEnabled = getConfig('CODING_AGENT_KIMI_CLI_ENABLED');
657
+ const kimiCliProvider = getConfig('CODING_AGENT_KIMI_CLI_PROVIDER') || '';
658
+ const kimiCliModel = getConfig('CODING_AGENT_KIMI_CLI_MODEL') || '';
656
659
 
657
660
  // Credential readiness
658
661
  const oauthTokenCount = getOAuthTokenCount('claudeCode');
@@ -694,6 +697,11 @@ export async function getCodingAgentSettings() {
694
697
  provider: openCodeProvider,
695
698
  model: openCodeModel,
696
699
  },
700
+ kimiCli: {
701
+ enabled: kimiCliEnabled === 'true',
702
+ provider: kimiCliProvider,
703
+ model: kimiCliModel,
704
+ },
697
705
  builtinProviders: BUILTIN_PROVIDERS,
698
706
  credentialStatuses,
699
707
  customProviders,
@@ -735,6 +743,10 @@ export async function updateCodingAgentConfig(agent, config) {
735
743
  if (config.enabled !== undefined) setConfigValue('CODING_AGENT_OPENCODE_ENABLED', String(config.enabled));
736
744
  if (config.provider !== undefined) setConfigValue('CODING_AGENT_OPENCODE_PROVIDER', config.provider);
737
745
  if (config.model !== undefined) setConfigValue('CODING_AGENT_OPENCODE_MODEL', config.model);
746
+ } else if (agent === 'kimi-cli') {
747
+ if (config.enabled !== undefined) setConfigValue('CODING_AGENT_KIMI_CLI_ENABLED', String(config.enabled));
748
+ if (config.provider !== undefined) setConfigValue('CODING_AGENT_KIMI_CLI_PROVIDER', config.provider);
749
+ if (config.model !== undefined) setConfigValue('CODING_AGENT_KIMI_CLI_MODEL', config.model);
738
750
  } else {
739
751
  return { error: 'Invalid agent' };
740
752
  }
@@ -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]));
@@ -98,7 +98,10 @@ export function clearWorkspaceForwards(workspaceId) {
98
98
 
99
99
  /**
100
100
  * Write Traefik dynamic config as YAML for all active port forwards.
101
- * Routes use *.localhost for local dev. No TLS on forwarded ports.
101
+ *
102
+ * When SSL_DOMAIN is set (custom compose with Let's Encrypt), routes use
103
+ * *.SSL_DOMAIN on the websecure entrypoint with the letsencrypt cert resolver.
104
+ * Otherwise, routes use *.localhost on the web entrypoint (local dev).
102
105
  *
103
106
  * MUST be YAML, not JSON — Traefik's file provider treats backticks in
104
107
  * JSON as Go template delimiters, silently breaking Host() rules.
@@ -125,14 +128,22 @@ function writeTraefikConfig() {
125
128
  return;
126
129
  }
127
130
 
131
+ const sslDomain = process.env.SSL_DOMAIN;
132
+ const entrypoint = sslDomain ? 'websecure' : 'web';
133
+
128
134
  const lines = ['http:', ' routers:'];
129
135
  for (const { workspaceId, port } of entries) {
130
136
  const key = `wksp-${workspaceId}-${port}`;
131
- const host = `${workspaceId.slice(0, 8)}-${port}.localhost`;
137
+ const subdomain = `${workspaceId.slice(0, 8)}-${port}`;
138
+ const host = sslDomain ? `${subdomain}.${sslDomain}` : `${subdomain}.localhost`;
132
139
  lines.push(` ${key}:`);
133
140
  lines.push(' rule: Host(`' + host + '`)');
134
141
  lines.push(' entryPoints:');
135
- lines.push(' - web');
142
+ lines.push(` - ${entrypoint}`);
143
+ if (sslDomain) {
144
+ lines.push(' tls:');
145
+ lines.push(' certResolver: letsencrypt');
146
+ }
136
147
  lines.push(` service: ${key}`);
137
148
  }
138
149
  lines.push(' services:');
package/lib/config.js CHANGED
@@ -54,6 +54,9 @@ const CONFIG_KEYS = new Set([
54
54
  'CODING_AGENT_OPENCODE_ENABLED',
55
55
  'CODING_AGENT_OPENCODE_PROVIDER',
56
56
  'CODING_AGENT_OPENCODE_MODEL',
57
+ 'CODING_AGENT_KIMI_CLI_ENABLED',
58
+ 'CODING_AGENT_KIMI_CLI_PROVIDER',
59
+ 'CODING_AGENT_KIMI_CLI_MODEL',
57
60
  ]);
58
61
 
59
62
  // Default values
@@ -69,6 +72,7 @@ const DEFAULTS = {
69
72
  CODING_AGENT_CODEX_CLI_ENABLED: 'false',
70
73
  CODING_AGENT_CODEX_CLI_AUTH: 'api-key',
71
74
  CODING_AGENT_OPENCODE_ENABLED: 'false',
75
+ CODING_AGENT_KIMI_CLI_ENABLED: 'false',
72
76
  };
73
77
 
74
78
  // In-memory cache on globalThis to survive Next.js webpack chunk duplication.
@@ -334,9 +334,9 @@ function buildAgentAuthEnv(agent) {
334
334
  }
335
335
  }
336
336
  }
337
- } else if (agent === 'pi-coding-agent' || agent === 'opencode') {
338
- // Pi and OpenCode share the same multi-provider auth pattern
339
- const configPrefix = agent === 'opencode' ? 'CODING_AGENT_OPENCODE' : 'CODING_AGENT_PI';
337
+ } else if (agent === 'pi-coding-agent' || agent === 'opencode' || agent === 'kimi-cli') {
338
+ // Pi, OpenCode, and Kimi share the same multi-provider auth pattern
339
+ const configPrefix = agent === 'opencode' ? 'CODING_AGENT_OPENCODE' : agent === 'kimi-cli' ? 'CODING_AGENT_KIMI_CLI' : 'CODING_AGENT_PI';
340
340
  const provider = getConfig(`${configPrefix}_PROVIDER`) || 'anthropic';
341
341
  backendApi = provider;
342
342
  const model = getConfig(`${configPrefix}_MODEL`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thepopebot",
3
- "version": "1.2.75-beta.2",
3
+ "version": "1.2.75-beta.4",
4
4
  "type": "module",
5
5
  "description": "Create autonomous AI agents with a two-layer architecture: Next.js Event Handler + Docker Agent.",
6
6
  "bin": {