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.
- package/README.md +1 -1
- package/api/CLAUDE.md +1 -1
- package/api/index.js +5 -12
- package/bin/CLAUDE.md +1 -1
- package/bin/cli.js +329 -14
- package/bin/docker-build.js +5 -0
- package/bin/managed-paths.js +0 -7
- package/bin/sync.js +84 -0
- package/config/CLAUDE.md +1 -29
- package/config/instrumentation.js +1 -1
- package/lib/CLAUDE.md +3 -3
- package/lib/ai/CLAUDE.md +24 -3
- package/lib/ai/agent.js +8 -5
- package/lib/ai/async-channel.js +51 -0
- package/lib/ai/headless-stream.js +3 -0
- package/lib/ai/index.js +149 -173
- package/lib/ai/line-mappers.js +72 -9
- package/lib/ai/tools.js +40 -28
- package/lib/chat/actions.js +34 -6
- package/lib/chat/api.js +17 -1
- package/lib/chat/components/chat-header.js +4 -0
- package/lib/chat/components/chat-header.jsx +4 -0
- package/lib/chat/components/chat-input.js +1 -0
- package/lib/chat/components/chat-input.jsx +1 -0
- package/lib/chat/components/chat.js +9 -1
- package/lib/chat/components/chat.jsx +15 -2
- package/lib/chat/components/chats-page.js +3 -3
- package/lib/chat/components/chats-page.jsx +4 -6
- package/lib/chat/components/crons-page.js +1 -1
- package/lib/chat/components/crons-page.jsx +1 -1
- package/lib/chat/components/message.js +12 -4
- package/lib/chat/components/message.jsx +17 -4
- package/lib/chat/components/settings-chat-page.js +2 -1
- package/lib/chat/components/settings-chat-page.jsx +4 -1
- package/lib/chat/components/settings-coding-agents-page.js +139 -1
- package/lib/chat/components/settings-coding-agents-page.jsx +160 -0
- package/lib/chat/components/settings-jobs-page.js +13 -2
- package/lib/chat/components/settings-jobs-page.jsx +15 -1
- package/lib/chat/components/settings-secrets-layout.js +1 -1
- package/lib/chat/components/settings-secrets-layout.jsx +1 -1
- package/lib/chat/components/sidebar-history-item.js +3 -3
- package/lib/chat/components/sidebar-history-item.jsx +4 -6
- package/lib/chat/components/triggers-page.js +1 -1
- package/lib/chat/components/triggers-page.jsx +1 -1
- package/lib/cluster/actions.js +4 -4
- package/lib/cluster/execute.js +3 -1
- package/lib/code/actions.js +34 -11
- package/lib/code/code-page.js +40 -40
- package/lib/code/code-page.jsx +36 -36
- package/lib/code/port-forwards.js +17 -3
- package/lib/code/terminal-view.js +16 -0
- package/lib/code/terminal-view.jsx +18 -0
- package/lib/config.js +4 -0
- package/lib/cron.js +3 -3
- package/lib/db/api-keys.js +22 -61
- package/lib/db/config.js +23 -0
- package/lib/db/index.js +3 -1
- package/lib/maintenance.js +34 -11
- package/lib/paths.js +1 -38
- package/lib/tools/create-agent-job.js +0 -4
- package/lib/tools/docker.js +23 -16
- package/lib/triggers.js +4 -3
- package/lib/utils/render-md.js +3 -1
- package/package.json +2 -1
- package/setup/setup-ssl.mjs +414 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +3 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +1 -1
- package/templates/.gitignore.template +7 -3
- package/templates/.tmp/CLAUDE.md.template +5 -0
- package/templates/CLAUDE.md +3 -2
- package/templates/CLAUDE.md.template +24 -357
- package/templates/agent-job/CLAUDE.md.template +57 -0
- package/templates/agent-job/CRONS.json +16 -0
- package/templates/{config/agent-job → agent-job}/SOUL.md +3 -3
- package/templates/agent-job/SYSTEM.md +60 -0
- package/templates/agents/CLAUDE.md.template +54 -0
- package/templates/data/CLAUDE.md.template +5 -0
- package/templates/docker-compose.custom.yml +41 -62
- package/templates/docker-compose.yml +14 -21
- package/templates/event-handler/CLAUDE.md.template +0 -0
- package/templates/logs/CLAUDE.md.template +5 -0
- package/templates/skills/CLAUDE.md.template +57 -32
- package/templates/skills/active/.gitkeep +0 -0
- package/templates/skills/library/agent-job-secrets/SKILL.md +23 -0
- package/templates/skills/library/agent-job-secrets/agent-job-secrets.js +62 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +0 -48
- package/templates/.pi/extensions/env-sanitizer/package.json +0 -5
- package/templates/README.md +0 -75
- package/templates/config/CLAUDE.md.template +0 -40
- package/templates/config/CRONS.json +0 -56
- package/templates/config/agent-job/AGENT_JOB.md +0 -30
- package/templates/cron/CLAUDE.md.template +0 -24
- package/templates/docker-compose.litellm.yml +0 -82
- package/templates/docs/CLAUDE.md.template +0 -12
- package/templates/docs/CLI.md +0 -59
- package/templates/docs/CLUSTERS.md +0 -151
- package/templates/docs/CONFIGURATION.md +0 -181
- package/templates/docs/CRONS_AND_TRIGGERS.md +0 -132
- package/templates/docs/GETTING_STARTED.md +0 -64
- package/templates/docs/SECURITY.md +0 -61
- package/templates/docs/SKILLS.md +0 -113
- package/templates/docs/UPGRADING.md +0 -92
- package/templates/skills/LICENSE +0 -21
- package/templates/skills/README.md +0 -117
- package/templates/skills/agent-job-secrets/SKILL.md +0 -25
- package/templates/skills/agent-job-secrets/agent-job-secrets.js +0 -66
- package/templates/traefik-dynamic.yml.example +0 -7
- package/templates/triggers/CLAUDE.md.template +0 -41
- /package/templates/{config → agent-job}/HEARTBEAT.md +0 -0
- /package/templates/{cron → data}/.gitkeep +0 -0
- /package/templates/{logs → data/clusters}/.gitkeep +0 -0
- /package/templates/{triggers → data/db}/.gitkeep +0 -0
- /package/templates/{config/agent-job → event-handler}/SUMMARY.md +0 -0
- /package/templates/{config → event-handler}/TRIGGERS.json +0 -0
- /package/templates/{config → event-handler}/agent-chat/SYSTEM.md +0 -0
- /package/templates/{config/cluster → event-handler/clusters}/ROLE.md +0 -0
- /package/templates/{config/cluster → event-handler/clusters}/SYSTEM.md +0 -0
- /package/templates/{config → event-handler}/code-chat/SYSTEM.md +0 -0
- /package/templates/{config → event-handler}/litellm/main.yaml +0 -0
- /package/templates/skills/{playwright-cli → library/playwright-cli}/SKILL.md +0 -0
package/lib/ai/line-mappers.js
CHANGED
|
@@ -107,7 +107,7 @@ export function mapClaudeCodeLine(parsed) {
|
|
|
107
107
|
// User messages without tool_result (e.g. subagent prompts) — skip
|
|
108
108
|
if (events.length === 0) return [{ type: 'skip' }];
|
|
109
109
|
} else if (type === 'result' && result) {
|
|
110
|
-
events.push({ type: 'text', text: result
|
|
110
|
+
events.push({ type: 'text', text: result });
|
|
111
111
|
}
|
|
112
112
|
|
|
113
113
|
return events;
|
|
@@ -176,7 +176,7 @@ export function mapPiLine(parsed) {
|
|
|
176
176
|
.map(b => b.text)
|
|
177
177
|
.join('');
|
|
178
178
|
if (text) {
|
|
179
|
-
events.push({ type: 'text', text
|
|
179
|
+
events.push({ type: 'text', text });
|
|
180
180
|
}
|
|
181
181
|
}
|
|
182
182
|
}
|
|
@@ -233,7 +233,7 @@ export function mapGeminiLine(parsed) {
|
|
|
233
233
|
const stats = parsed.stats;
|
|
234
234
|
if (stats) {
|
|
235
235
|
const summary = `Completed (${stats.total_tokens || 0} tokens, ${stats.tool_calls || 0} tool calls, ${((stats.duration_ms || 0) / 1000).toFixed(1)}s)`;
|
|
236
|
-
events.push({ type: 'text', text: summary
|
|
236
|
+
events.push({ type: 'text', text: summary });
|
|
237
237
|
}
|
|
238
238
|
return events.length ? events : [{ type: 'skip' }];
|
|
239
239
|
} else if (type === 'error') {
|
|
@@ -245,6 +245,74 @@ export function mapGeminiLine(parsed) {
|
|
|
245
245
|
return events;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
249
|
+
// Kimi CLI: OpenAI-style function calling JSON
|
|
250
|
+
//
|
|
251
|
+
// Event shapes (from real output):
|
|
252
|
+
// role: "assistant", content: [], tool_calls: [{ type: "function", id, function: { name, arguments } }]
|
|
253
|
+
// role: "assistant", content: "text..." (with or without tool_calls)
|
|
254
|
+
// role: "tool", content: "...", tool_call_id: "..."
|
|
255
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Map a Kimi CLI line to chat events.
|
|
259
|
+
* @param {object} parsed - Parsed JSON object
|
|
260
|
+
* @returns {Array<object>}
|
|
261
|
+
*/
|
|
262
|
+
export function mapKimiLine(parsed) {
|
|
263
|
+
const events = [];
|
|
264
|
+
const { role, content, tool_calls, tool_call_id } = parsed;
|
|
265
|
+
|
|
266
|
+
if (role === 'assistant') {
|
|
267
|
+
// Text content — can be a string or an array of blocks
|
|
268
|
+
if (typeof content === 'string' && content) {
|
|
269
|
+
events.push({ type: 'text', text: content });
|
|
270
|
+
} else if (Array.isArray(content)) {
|
|
271
|
+
for (const block of content) {
|
|
272
|
+
if (typeof block === 'string' && block) {
|
|
273
|
+
events.push({ type: 'text', text: block });
|
|
274
|
+
} else if (block?.type === 'text' && block.text) {
|
|
275
|
+
events.push({ type: 'text', text: block.text });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Tool calls — OpenAI function calling format
|
|
281
|
+
if (Array.isArray(tool_calls)) {
|
|
282
|
+
for (const tc of tool_calls) {
|
|
283
|
+
if (tc.type === 'function' && tc.function) {
|
|
284
|
+
let args = {};
|
|
285
|
+
try {
|
|
286
|
+
args = typeof tc.function.arguments === 'string'
|
|
287
|
+
? JSON.parse(tc.function.arguments)
|
|
288
|
+
: tc.function.arguments || {};
|
|
289
|
+
} catch { /* leave as empty object */ }
|
|
290
|
+
events.push({
|
|
291
|
+
type: 'tool-call',
|
|
292
|
+
toolCallId: tc.id || '',
|
|
293
|
+
toolName: tc.function.name || '',
|
|
294
|
+
args,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Assistant message with only empty content and no tool calls — skip
|
|
301
|
+
if (events.length === 0) return [{ type: 'skip' }];
|
|
302
|
+
} else if (role === 'tool') {
|
|
303
|
+
const resultText = typeof content === 'string' ? content :
|
|
304
|
+
Array.isArray(content) ? content.map(b => (typeof b === 'string' ? b : b?.text || '')).join('') :
|
|
305
|
+
JSON.stringify(content);
|
|
306
|
+
events.push({
|
|
307
|
+
type: 'tool-result',
|
|
308
|
+
toolCallId: tool_call_id || '',
|
|
309
|
+
result: resultText,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return events;
|
|
314
|
+
}
|
|
315
|
+
|
|
248
316
|
// ─────────────────────────────────────────────────────────────────────────────
|
|
249
317
|
// Codex CLI: --json
|
|
250
318
|
//
|
|
@@ -312,7 +380,7 @@ export function mapCodexLine(parsed) {
|
|
|
312
380
|
const usage = parsed.usage;
|
|
313
381
|
if (usage) {
|
|
314
382
|
const summary = `Completed (${usage.input_tokens || 0} input, ${usage.output_tokens || 0} output tokens)`;
|
|
315
|
-
events.push({ type: 'text', text: summary
|
|
383
|
+
events.push({ type: 'text', text: summary });
|
|
316
384
|
}
|
|
317
385
|
return events.length ? events : [{ type: 'skip' }];
|
|
318
386
|
} else if (type === 'turn.failed') {
|
|
@@ -350,11 +418,6 @@ export function mapOpenCodeLine(parsed) {
|
|
|
350
418
|
// Text output — part.text contains the assistant's response
|
|
351
419
|
if (type === 'text' && part?.text) {
|
|
352
420
|
events.push({ type: 'text', text: part.text });
|
|
353
|
-
// If step_finish follows with reason "stop", this is the final answer
|
|
354
|
-
// We mark it as result summary so it gets captured in LangGraph memory
|
|
355
|
-
if (part.text.length > 50) {
|
|
356
|
-
events[events.length - 1]._resultSummary = part.text;
|
|
357
|
-
}
|
|
358
421
|
}
|
|
359
422
|
|
|
360
423
|
// Tool use — OpenCode emits a single event with completed state (input + output)
|
package/lib/ai/tools.js
CHANGED
|
@@ -54,7 +54,7 @@ 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');
|
|
57
|
+
const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
|
|
58
58
|
const { backendApi } = await runHeadlessContainer({
|
|
59
59
|
containerName,
|
|
60
60
|
repo,
|
|
@@ -66,20 +66,27 @@ const agentChatCodingTool = tool(
|
|
|
66
66
|
injectSecrets: true,
|
|
67
67
|
});
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
const chunks = [{ type: 'meta', codingAgent, backendApi }];
|
|
70
|
+
const streamCallback = runtime.configurable.streamCallback;
|
|
71
|
+
const { parseHeadlessStream } = await import('./headless-stream.js');
|
|
72
|
+
|
|
73
|
+
const logStream = await tailContainerLogs(containerName);
|
|
74
|
+
|
|
75
|
+
for await (const chunk of parseHeadlessStream(logStream, codingAgent)) {
|
|
76
|
+
chunks.push(chunk);
|
|
77
|
+
streamCallback?.(chunk);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const exitCode = await waitForContainer(containerName);
|
|
81
|
+
await removeContainer(containerName);
|
|
82
|
+
streamCallback?.(null);
|
|
83
|
+
|
|
84
|
+
chunks.push({ type: 'exit', exitCode });
|
|
85
|
+
return JSON.stringify(chunks);
|
|
77
86
|
} catch (err) {
|
|
78
87
|
console.error('[coding_agent] Failed:', err);
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
error: err.message || 'Failed to launch investigation container',
|
|
82
|
-
});
|
|
88
|
+
runtime.configurable.streamCallback?.(null);
|
|
89
|
+
return JSON.stringify([{ type: 'error', message: err.message }]);
|
|
83
90
|
}
|
|
84
91
|
},
|
|
85
92
|
{
|
|
@@ -91,7 +98,6 @@ const agentChatCodingTool = tool(
|
|
|
91
98
|
'A direct copy of the coding task including all relevant context from the conversation.'
|
|
92
99
|
),
|
|
93
100
|
}),
|
|
94
|
-
returnDirect: true,
|
|
95
101
|
}
|
|
96
102
|
);
|
|
97
103
|
|
|
@@ -110,7 +116,7 @@ const codeChatCodingTool = tool(
|
|
|
110
116
|
const featureBranch = workspace?.featureBranch;
|
|
111
117
|
const mode = codeModeType === 'code' ? 'dangerous' : 'plan';
|
|
112
118
|
|
|
113
|
-
const { runHeadlessContainer } = await import('../tools/docker.js');
|
|
119
|
+
const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
|
|
114
120
|
const codingAgent = getConfig('CODING_AGENT') || 'claude-code';
|
|
115
121
|
const containerName = `${codingAgent}-headless-${randomUUID().slice(0, 8)}`;
|
|
116
122
|
|
|
@@ -120,20 +126,27 @@ const codeChatCodingTool = tool(
|
|
|
120
126
|
mode,
|
|
121
127
|
});
|
|
122
128
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
const chunks = [{ type: 'meta', codingAgent, backendApi }];
|
|
130
|
+
const streamCallback = runtime.configurable.streamCallback;
|
|
131
|
+
const { parseHeadlessStream } = await import('./headless-stream.js');
|
|
132
|
+
|
|
133
|
+
const logStream = await tailContainerLogs(containerName);
|
|
134
|
+
|
|
135
|
+
for await (const chunk of parseHeadlessStream(logStream, codingAgent)) {
|
|
136
|
+
chunks.push(chunk);
|
|
137
|
+
streamCallback?.(chunk);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const exitCode = await waitForContainer(containerName);
|
|
141
|
+
await removeContainer(containerName);
|
|
142
|
+
streamCallback?.(null);
|
|
143
|
+
|
|
144
|
+
chunks.push({ type: 'exit', exitCode });
|
|
145
|
+
return JSON.stringify(chunks);
|
|
131
146
|
} catch (err) {
|
|
132
147
|
console.error('[coding_agent] Failed:', err);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
error: err.message || 'Failed to launch headless coding task',
|
|
136
|
-
});
|
|
148
|
+
runtime.configurable.streamCallback?.(null);
|
|
149
|
+
return JSON.stringify([{ type: 'error', message: err.message }]);
|
|
137
150
|
}
|
|
138
151
|
},
|
|
139
152
|
{
|
|
@@ -145,7 +158,6 @@ const codeChatCodingTool = tool(
|
|
|
145
158
|
'A direct copy of the coding task including all relevant context from the conversation.'
|
|
146
159
|
),
|
|
147
160
|
}),
|
|
148
|
-
returnDirect: true,
|
|
149
161
|
}
|
|
150
162
|
);
|
|
151
163
|
|
package/lib/chat/actions.js
CHANGED
|
@@ -556,12 +556,13 @@ export async function getRunnersStatus(page = 1) {
|
|
|
556
556
|
*/
|
|
557
557
|
export async function getRunnersConfig() {
|
|
558
558
|
await requireAuth();
|
|
559
|
-
const {
|
|
559
|
+
const { PROJECT_ROOT } = await import('../paths.js');
|
|
560
560
|
const fs = await import('fs');
|
|
561
|
+
const path = await import('path');
|
|
561
562
|
let crons = [];
|
|
562
563
|
let triggers = [];
|
|
563
|
-
try { crons = JSON.parse(fs.readFileSync(
|
|
564
|
-
try { triggers = JSON.parse(fs.readFileSync(
|
|
564
|
+
try { crons = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, 'agent-job/CRONS.json'), 'utf8')); } catch {}
|
|
565
|
+
try { triggers = JSON.parse(fs.readFileSync(path.join(PROJECT_ROOT, 'event-handler/TRIGGERS.json'), 'utf8')); } catch {}
|
|
565
566
|
return { crons, triggers };
|
|
566
567
|
}
|
|
567
568
|
|
|
@@ -653,6 +654,9 @@ export async function getCodingAgentSettings() {
|
|
|
653
654
|
const openCodeEnabled = getConfig('CODING_AGENT_OPENCODE_ENABLED');
|
|
654
655
|
const openCodeProvider = getConfig('CODING_AGENT_OPENCODE_PROVIDER') || '';
|
|
655
656
|
const openCodeModel = getConfig('CODING_AGENT_OPENCODE_MODEL') || '';
|
|
657
|
+
const kimiCliEnabled = getConfig('CODING_AGENT_KIMI_CLI_ENABLED');
|
|
658
|
+
const kimiCliProvider = getConfig('CODING_AGENT_KIMI_CLI_PROVIDER') || '';
|
|
659
|
+
const kimiCliModel = getConfig('CODING_AGENT_KIMI_CLI_MODEL') || '';
|
|
656
660
|
|
|
657
661
|
// Credential readiness
|
|
658
662
|
const oauthTokenCount = getOAuthTokenCount('claudeCode');
|
|
@@ -694,6 +698,11 @@ export async function getCodingAgentSettings() {
|
|
|
694
698
|
provider: openCodeProvider,
|
|
695
699
|
model: openCodeModel,
|
|
696
700
|
},
|
|
701
|
+
kimiCli: {
|
|
702
|
+
enabled: kimiCliEnabled === 'true',
|
|
703
|
+
provider: kimiCliProvider,
|
|
704
|
+
model: kimiCliModel,
|
|
705
|
+
},
|
|
697
706
|
builtinProviders: BUILTIN_PROVIDERS,
|
|
698
707
|
credentialStatuses,
|
|
699
708
|
customProviders,
|
|
@@ -735,6 +744,10 @@ export async function updateCodingAgentConfig(agent, config) {
|
|
|
735
744
|
if (config.enabled !== undefined) setConfigValue('CODING_AGENT_OPENCODE_ENABLED', String(config.enabled));
|
|
736
745
|
if (config.provider !== undefined) setConfigValue('CODING_AGENT_OPENCODE_PROVIDER', config.provider);
|
|
737
746
|
if (config.model !== undefined) setConfigValue('CODING_AGENT_OPENCODE_MODEL', config.model);
|
|
747
|
+
} else if (agent === 'kimi-cli') {
|
|
748
|
+
if (config.enabled !== undefined) setConfigValue('CODING_AGENT_KIMI_CLI_ENABLED', String(config.enabled));
|
|
749
|
+
if (config.provider !== undefined) setConfigValue('CODING_AGENT_KIMI_CLI_PROVIDER', config.provider);
|
|
750
|
+
if (config.model !== undefined) setConfigValue('CODING_AGENT_KIMI_CLI_MODEL', config.model);
|
|
738
751
|
} else {
|
|
739
752
|
return { error: 'Invalid agent' };
|
|
740
753
|
}
|
|
@@ -973,8 +986,8 @@ async function syncLitellmConfig() {
|
|
|
973
986
|
try {
|
|
974
987
|
const fs = await import('fs');
|
|
975
988
|
const path = await import('path');
|
|
976
|
-
const {
|
|
977
|
-
const litellmDir = path.default.join(
|
|
989
|
+
const { PROJECT_ROOT } = await import('../paths.js');
|
|
990
|
+
const litellmDir = path.default.join(PROJECT_ROOT, 'event-handler/litellm');
|
|
978
991
|
if (!fs.default.existsSync(litellmDir)) return;
|
|
979
992
|
|
|
980
993
|
const { getConfig } = await import('../config.js');
|
|
@@ -1258,7 +1271,7 @@ export async function initiateOAuthFlow({ secretName, clientId, clientSecret, to
|
|
|
1258
1271
|
try {
|
|
1259
1272
|
const { createOAuthState } = await import('../oauth/helper.js');
|
|
1260
1273
|
const redirectUri = `${process.env.AUTH_URL}/api/oauth/callback`;
|
|
1261
|
-
const state = createOAuthState({ secretName, clientId, clientSecret, tokenUrl, secretType: secretType || 'agent_job_secret', returnPath: returnPath || '/admin/event-handler/agent-
|
|
1274
|
+
const state = createOAuthState({ secretName, clientId, clientSecret, tokenUrl, secretType: secretType || 'agent_job_secret', returnPath: returnPath || '/admin/event-handler/agent-secrets' });
|
|
1262
1275
|
return { state, redirectUri };
|
|
1263
1276
|
} catch (err) {
|
|
1264
1277
|
console.error('Failed to initiate OAuth flow:', err);
|
|
@@ -1266,6 +1279,21 @@ export async function initiateOAuthFlow({ secretName, clientId, clientSecret, to
|
|
|
1266
1279
|
}
|
|
1267
1280
|
}
|
|
1268
1281
|
|
|
1282
|
+
/**
|
|
1283
|
+
* Get stored OAuth credentials for an agent job secret (for re-authorization pre-fill).
|
|
1284
|
+
* @param {string} name
|
|
1285
|
+
*/
|
|
1286
|
+
export async function getOAuthSecretCredentials(name) {
|
|
1287
|
+
await requireAuth();
|
|
1288
|
+
try {
|
|
1289
|
+
const { getAgentJobSecretOAuthCredentials } = await import('../db/config.js');
|
|
1290
|
+
return getAgentJobSecretOAuthCredentials(name) || { error: 'Not an OAuth secret' };
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
console.error('Failed to get OAuth credentials:', err);
|
|
1293
|
+
return { error: 'Failed to get credentials' };
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1269
1297
|
/**
|
|
1270
1298
|
* Delete an agent job secret.
|
|
1271
1299
|
* @param {string} name
|
package/lib/chat/api.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { auth } from '../auth/index.js';
|
|
2
2
|
import { chatStream } from '../ai/index.js';
|
|
3
3
|
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
import { getConfig } from '../config.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* POST handler for /stream/chat — streaming chat with session auth.
|
|
@@ -114,11 +115,26 @@ export async function POST(request) {
|
|
|
114
115
|
toolCallId: chunk.toolCallId,
|
|
115
116
|
toolName: chunk.toolName,
|
|
116
117
|
});
|
|
118
|
+
// Enrich coding_agent input with active agent identity from config
|
|
119
|
+
let input = chunk.args;
|
|
120
|
+
if (chunk.toolName === 'coding_agent') {
|
|
121
|
+
const agent = getConfig('CODING_AGENT') || 'claude-code';
|
|
122
|
+
const providerKeys = {
|
|
123
|
+
'claude-code': 'CODING_AGENT_CLAUDE_CODE_BACKEND',
|
|
124
|
+
'pi-coding-agent': 'CODING_AGENT_PI_PROVIDER',
|
|
125
|
+
'gemini-cli': 'CODING_AGENT_GEMINI_CLI_PROVIDER',
|
|
126
|
+
'codex-cli': 'CODING_AGENT_CODEX_CLI_PROVIDER',
|
|
127
|
+
'opencode': 'CODING_AGENT_OPENCODE_PROVIDER',
|
|
128
|
+
'kimi-cli': 'CODING_AGENT_KIMI_CLI_PROVIDER',
|
|
129
|
+
};
|
|
130
|
+
const backendApi = getConfig(providerKeys[agent]) || 'anthropic';
|
|
131
|
+
input = { ...chunk.args, codingAgent: agent, backendApi };
|
|
132
|
+
}
|
|
117
133
|
writer.write({
|
|
118
134
|
type: 'tool-input-available',
|
|
119
135
|
toolCallId: chunk.toolCallId,
|
|
120
136
|
toolName: chunk.toolName,
|
|
121
|
-
input
|
|
137
|
+
input,
|
|
122
138
|
});
|
|
123
139
|
|
|
124
140
|
} else if (chunk.type === 'tool-result') {
|
|
@@ -30,6 +30,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
30
30
|
setStarred(data.starred || 0);
|
|
31
31
|
setResolvedChatId(data.chatId);
|
|
32
32
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
33
|
+
document.title = data.title;
|
|
33
34
|
}
|
|
34
35
|
}).catch(() => {
|
|
35
36
|
});
|
|
@@ -41,6 +42,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
41
42
|
setTitle(data.title);
|
|
42
43
|
setStarred(data.starred || 0);
|
|
43
44
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
45
|
+
document.title = data.title;
|
|
44
46
|
}
|
|
45
47
|
}).catch(() => {
|
|
46
48
|
});
|
|
@@ -51,6 +53,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
51
53
|
if (e.detail.chatId === chatId) {
|
|
52
54
|
setTitle(e.detail.title);
|
|
53
55
|
if (e.detail.chatMode) setChatMode(e.detail.chatMode);
|
|
56
|
+
document.title = e.detail.title;
|
|
54
57
|
}
|
|
55
58
|
};
|
|
56
59
|
const starHandler = (e) => {
|
|
@@ -63,6 +66,7 @@ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
63
66
|
return () => {
|
|
64
67
|
window.removeEventListener("chatTitleUpdated", titleHandler);
|
|
65
68
|
window.removeEventListener("chatStarUpdated", starHandler);
|
|
69
|
+
document.title = "ThePopeBot";
|
|
66
70
|
};
|
|
67
71
|
}, [fetchMeta, chatId]);
|
|
68
72
|
useEffect(() => {
|
|
@@ -38,6 +38,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
38
38
|
setStarred(data.starred || 0);
|
|
39
39
|
setResolvedChatId(data.chatId);
|
|
40
40
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
41
|
+
document.title = data.title;
|
|
41
42
|
}
|
|
42
43
|
})
|
|
43
44
|
.catch(() => {});
|
|
@@ -51,6 +52,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
51
52
|
setTitle(data.title);
|
|
52
53
|
setStarred(data.starred || 0);
|
|
53
54
|
if (data.chatMode) setChatMode(data.chatMode);
|
|
55
|
+
document.title = data.title;
|
|
54
56
|
}
|
|
55
57
|
})
|
|
56
58
|
.catch(() => {});
|
|
@@ -62,6 +64,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
62
64
|
if (e.detail.chatId === chatId) {
|
|
63
65
|
setTitle(e.detail.title);
|
|
64
66
|
if (e.detail.chatMode) setChatMode(e.detail.chatMode);
|
|
67
|
+
document.title = e.detail.title;
|
|
65
68
|
}
|
|
66
69
|
};
|
|
67
70
|
const starHandler = (e) => {
|
|
@@ -74,6 +77,7 @@ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
|
|
|
74
77
|
return () => {
|
|
75
78
|
window.removeEventListener('chatTitleUpdated', titleHandler);
|
|
76
79
|
window.removeEventListener('chatStarUpdated', starHandler);
|
|
80
|
+
document.title = 'ThePopeBot';
|
|
77
81
|
};
|
|
78
82
|
}, [fetchMeta, chatId]);
|
|
79
83
|
|
|
@@ -88,6 +88,7 @@ function ChatInput({ input, setInput, onSubmit, status, stop, files, setFiles, d
|
|
|
88
88
|
if (!textarea) return;
|
|
89
89
|
textarea.style.height = "auto";
|
|
90
90
|
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
91
|
+
textarea.scrollTop = textarea.scrollHeight;
|
|
91
92
|
}, []);
|
|
92
93
|
useEffect(() => {
|
|
93
94
|
adjustHeight();
|
|
@@ -70,6 +70,7 @@ export function ChatInput({ input, setInput, onSubmit, status, stop, files, setF
|
|
|
70
70
|
if (!textarea) return;
|
|
71
71
|
textarea.style.height = 'auto';
|
|
72
72
|
textarea.style.height = `${textarea.scrollHeight}px`;
|
|
73
|
+
textarea.scrollTop = textarea.scrollHeight;
|
|
73
74
|
}, []);
|
|
74
75
|
|
|
75
76
|
useEffect(() => {
|
|
@@ -94,7 +94,12 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
|
|
|
94
94
|
const isFinished = prevStatus.current !== "ready" && status === "ready";
|
|
95
95
|
if (isMount || isFinished) {
|
|
96
96
|
fetch(`/code/workspace-diff/${workspaceState.id}`).then((r) => r.json()).then((r) => {
|
|
97
|
-
if (r.success)
|
|
97
|
+
if (r.success) {
|
|
98
|
+
setDiffStats(r);
|
|
99
|
+
if (r.currentBranch) {
|
|
100
|
+
setWorkspaceState((prev) => prev && r.currentBranch !== prev.featureBranch ? { ...prev, featureBranch: r.currentBranch } : prev);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
98
103
|
}).catch(() => {
|
|
99
104
|
});
|
|
100
105
|
}
|
|
@@ -199,6 +204,9 @@ function Chat({ chatId, initialMessages = [], workspace = null, chatMode = null
|
|
|
199
204
|
const data = await r.json();
|
|
200
205
|
if (data.success) {
|
|
201
206
|
setDiffStats(data);
|
|
207
|
+
if (data.currentBranch) {
|
|
208
|
+
setWorkspaceState((prev) => prev && data.currentBranch !== prev.featureBranch ? { ...prev, featureBranch: data.currentBranch } : prev);
|
|
209
|
+
}
|
|
202
210
|
return data;
|
|
203
211
|
}
|
|
204
212
|
} catch {
|
|
@@ -118,7 +118,14 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
|
|
|
118
118
|
if (isMount || isFinished) {
|
|
119
119
|
fetch(`/code/workspace-diff/${workspaceState.id}`)
|
|
120
120
|
.then(r => r.json())
|
|
121
|
-
.then(r => {
|
|
121
|
+
.then(r => {
|
|
122
|
+
if (r.success) {
|
|
123
|
+
setDiffStats(r);
|
|
124
|
+
if (r.currentBranch) {
|
|
125
|
+
setWorkspaceState(prev => prev && r.currentBranch !== prev.featureBranch ? { ...prev, featureBranch: r.currentBranch } : prev);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
122
129
|
.catch(() => {});
|
|
123
130
|
}
|
|
124
131
|
prevStatus.current = status;
|
|
@@ -244,7 +251,13 @@ export function Chat({ chatId, initialMessages = [], workspace = null, chatMode
|
|
|
244
251
|
try {
|
|
245
252
|
const r = await fetch(`/code/workspace-diff/${workspaceState.id}`);
|
|
246
253
|
const data = await r.json();
|
|
247
|
-
if (data.success) {
|
|
254
|
+
if (data.success) {
|
|
255
|
+
setDiffStats(data);
|
|
256
|
+
if (data.currentBranch) {
|
|
257
|
+
setWorkspaceState(prev => prev && data.currentBranch !== prev.featureBranch ? { ...prev, featureBranch: data.currentBranch } : prev);
|
|
258
|
+
}
|
|
259
|
+
return data;
|
|
260
|
+
}
|
|
248
261
|
} catch {}
|
|
249
262
|
return null;
|
|
250
263
|
}, [workspaceState?.id]);
|
|
@@ -263,10 +263,10 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
263
263
|
}
|
|
264
264
|
},
|
|
265
265
|
children: [
|
|
266
|
-
|
|
267
|
-
/* @__PURE__ */ jsx(CodeIcon, { size: 16 }),
|
|
266
|
+
/* @__PURE__ */ jsxs("span", { className: "relative", children: [
|
|
267
|
+
chat.chatMode === "code" ? /* @__PURE__ */ jsx(CodeIcon, { size: 16 }) : /* @__PURE__ */ jsx(AgentIcon, { size: 16 }),
|
|
268
268
|
chat.hasChanges ? /* @__PURE__ */ jsx("span", { className: "absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" }) : null
|
|
269
|
-
] })
|
|
269
|
+
] }),
|
|
270
270
|
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
271
271
|
editing ? /* @__PURE__ */ jsx(
|
|
272
272
|
"input",
|
|
@@ -312,12 +312,10 @@ function ChatRow({ chat, onNavigate, onDelete, onStar, onRename }) {
|
|
|
312
312
|
}
|
|
313
313
|
}}
|
|
314
314
|
>
|
|
315
|
-
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
</span>
|
|
320
|
-
) : <AgentIcon size={16} />}
|
|
315
|
+
<span className="relative">
|
|
316
|
+
{chat.chatMode === 'code' ? <CodeIcon size={16} /> : <AgentIcon size={16} />}
|
|
317
|
+
{chat.hasChanges ? <span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-destructive" /> : null}
|
|
318
|
+
</span>
|
|
321
319
|
<div className="flex-1 min-w-0">
|
|
322
320
|
{editing ? (
|
|
323
321
|
<input
|
|
@@ -152,7 +152,7 @@ function CronsPage() {
|
|
|
152
152
|
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium mb-1", children: "No cron jobs configured" }),
|
|
153
153
|
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground max-w-sm", children: [
|
|
154
154
|
"Add scheduled jobs by editing ",
|
|
155
|
-
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "
|
|
155
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono", children: "agent-job/CRONS.json" }),
|
|
156
156
|
" in your project."
|
|
157
157
|
] })
|
|
158
158
|
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
@@ -216,7 +216,7 @@ export function CronsPage() {
|
|
|
216
216
|
</div>
|
|
217
217
|
<p className="text-sm font-medium mb-1">No cron jobs configured</p>
|
|
218
218
|
<p className="text-xs text-muted-foreground max-w-sm">
|
|
219
|
-
Add scheduled jobs by editing <span className="font-mono">
|
|
219
|
+
Add scheduled jobs by editing <span className="font-mono">agent-job/CRONS.json</span> in your project.
|
|
220
220
|
</p>
|
|
221
221
|
</div>
|
|
222
222
|
) : (
|
|
@@ -150,11 +150,19 @@ function ToolCall({ part, className }) {
|
|
|
150
150
|
/* @__PURE__ */ jsx(WrenchIcon, { size: 14, className: "text-muted-foreground shrink-0 mt-0.5" }),
|
|
151
151
|
/* @__PURE__ */ jsx("span", { className: "flex flex-col min-w-0 flex-1", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
152
152
|
/* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: displayName }),
|
|
153
|
-
|
|
153
|
+
(() => {
|
|
154
154
|
try {
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
const agent = part.input?.codingAgent;
|
|
156
|
+
const backend = part.input?.backendApi;
|
|
157
|
+
if (agent || backend) {
|
|
158
|
+
return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: [agent, backend].filter(Boolean).join(" \xB7 ") });
|
|
159
|
+
}
|
|
160
|
+
if (isDone) {
|
|
161
|
+
const o = typeof part.output === "string" ? JSON.parse(part.output) : part.output;
|
|
162
|
+
const meta = Array.isArray(o) ? o.find((e) => e.type === "meta") : o;
|
|
163
|
+
if (meta?.codingAgent || meta?.backendApi) {
|
|
164
|
+
return /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: [meta.codingAgent, meta.backendApi].filter(Boolean).join(" \xB7 ") });
|
|
165
|
+
}
|
|
158
166
|
}
|
|
159
167
|
} catch {
|
|
160
168
|
}
|
|
@@ -154,16 +154,29 @@ function ToolCall({ part, className }) {
|
|
|
154
154
|
<span className="flex flex-col min-w-0 flex-1">
|
|
155
155
|
<span className="flex items-center gap-2">
|
|
156
156
|
<span className="font-medium text-foreground">{displayName}</span>
|
|
157
|
-
{
|
|
157
|
+
{(() => {
|
|
158
158
|
try {
|
|
159
|
-
|
|
160
|
-
|
|
159
|
+
// Read from input (available immediately) or output meta (historical chats)
|
|
160
|
+
const agent = part.input?.codingAgent;
|
|
161
|
+
const backend = part.input?.backendApi;
|
|
162
|
+
if (agent || backend) {
|
|
161
163
|
return (
|
|
162
164
|
<span className="text-xs text-muted-foreground">
|
|
163
|
-
{[
|
|
165
|
+
{[agent, backend].filter(Boolean).join(' · ')}
|
|
164
166
|
</span>
|
|
165
167
|
);
|
|
166
168
|
}
|
|
169
|
+
if (isDone) {
|
|
170
|
+
const o = typeof part.output === 'string' ? JSON.parse(part.output) : part.output;
|
|
171
|
+
const meta = Array.isArray(o) ? o.find(e => e.type === 'meta') : o;
|
|
172
|
+
if (meta?.codingAgent || meta?.backendApi) {
|
|
173
|
+
return (
|
|
174
|
+
<span className="text-xs text-muted-foreground">
|
|
175
|
+
{[meta.codingAgent, meta.backendApi].filter(Boolean).join(' · ')}
|
|
176
|
+
</span>
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
167
180
|
} catch {}
|
|
168
181
|
return null;
|
|
169
182
|
})()}
|
|
@@ -74,7 +74,7 @@ function ActiveConfig({ settings, onSave }) {
|
|
|
74
74
|
}
|
|
75
75
|
useEffect(() => {
|
|
76
76
|
if (settings?.active) {
|
|
77
|
-
const prov = settings.active.provider ||
|
|
77
|
+
const prov = settings.active.provider || "";
|
|
78
78
|
const resolved = availableProviders.find((p) => p.slug === prov);
|
|
79
79
|
const models = resolved?.models || [];
|
|
80
80
|
const def = models.find((m) => m.default);
|
|
@@ -149,6 +149,7 @@ function ActiveConfig({ settings, onSave }) {
|
|
|
149
149
|
onChange: (e) => handleProviderChange(e.target.value),
|
|
150
150
|
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",
|
|
151
151
|
children: [
|
|
152
|
+
!provider && availableProviders.length > 0 && /* @__PURE__ */ jsx("option", { value: "", children: "Select Provider" }),
|
|
152
153
|
availableProviders.map((p) => /* @__PURE__ */ jsx("option", { value: p.slug, children: p.name }, p.slug)),
|
|
153
154
|
availableProviders.length === 0 && /* @__PURE__ */ jsx("option", { value: "", disabled: true, children: "No providers configured" })
|
|
154
155
|
]
|
|
@@ -91,7 +91,7 @@ function ActiveConfig({ settings, onSave }) {
|
|
|
91
91
|
|
|
92
92
|
useEffect(() => {
|
|
93
93
|
if (settings?.active) {
|
|
94
|
-
const prov = settings.active.provider ||
|
|
94
|
+
const prov = settings.active.provider || '';
|
|
95
95
|
const resolved = availableProviders.find((p) => p.slug === prov);
|
|
96
96
|
const models = resolved?.models || [];
|
|
97
97
|
const def = models.find((m) => m.default);
|
|
@@ -171,6 +171,9 @@ function ActiveConfig({ settings, onSave }) {
|
|
|
171
171
|
onChange={(e) => handleProviderChange(e.target.value)}
|
|
172
172
|
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"
|
|
173
173
|
>
|
|
174
|
+
{!provider && availableProviders.length > 0 && (
|
|
175
|
+
<option value="">Select Provider</option>
|
|
176
|
+
)}
|
|
174
177
|
{availableProviders.map((p) => (
|
|
175
178
|
<option key={p.slug} value={p.slug}>{p.name}</option>
|
|
176
179
|
))}
|