thepopebot 1.2.76-beta.2 → 1.2.76-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 (128) hide show
  1. package/README.md +3 -3
  2. package/api/CLAUDE.md +11 -4
  3. package/api/index.js +56 -18
  4. package/bin/CLAUDE.md +7 -4
  5. package/bin/cli.js +25 -45
  6. package/config/CLAUDE.md +23 -4
  7. package/drizzle/0021_coding_agent_workspace.sql +1 -0
  8. package/drizzle/0022_organic_apocalypse.sql +16 -0
  9. package/drizzle/0023_needy_ender_wiggin.sql +1 -0
  10. package/drizzle/meta/0021_snapshot.json +639 -0
  11. package/drizzle/meta/0022_snapshot.json +743 -0
  12. package/drizzle/meta/0023_snapshot.json +750 -0
  13. package/drizzle/meta/_journal.json +21 -0
  14. package/lib/CLAUDE.md +2 -2
  15. package/lib/actions.js +9 -1
  16. package/lib/ai/CLAUDE.md +72 -57
  17. package/lib/ai/helper-llm.js +108 -0
  18. package/lib/ai/index.js +308 -438
  19. package/lib/ai/line-mappers.js +42 -24
  20. package/lib/ai/scope.js +26 -0
  21. package/lib/ai/sdk-adapters/CLAUDE.md +114 -0
  22. package/lib/ai/sdk-adapters/claude-code.js +120 -8
  23. package/lib/ai/system-prompt.js +34 -0
  24. package/lib/ai/workspace-setup.js +19 -35
  25. package/lib/channels/CLAUDE.md +14 -4
  26. package/lib/channels/base.js +6 -2
  27. package/lib/channels/commands/index.js +42 -0
  28. package/lib/channels/commands/session.js +53 -0
  29. package/lib/channels/commands/verify.js +18 -0
  30. package/lib/channels/telegram.js +79 -28
  31. package/lib/chat/CLAUDE.md +4 -4
  32. package/lib/chat/actions.js +270 -49
  33. package/lib/chat/api.js +185 -31
  34. package/lib/chat/components/CLAUDE.md +6 -2
  35. package/lib/chat/components/chat-input.js +77 -47
  36. package/lib/chat/components/chat-input.jsx +77 -40
  37. package/lib/chat/components/chat-page.js +2 -0
  38. package/lib/chat/components/chat-page.jsx +3 -0
  39. package/lib/chat/components/chat.js +62 -14
  40. package/lib/chat/components/chat.jsx +68 -10
  41. package/lib/chat/components/code-mode-toggle.js +141 -22
  42. package/lib/chat/components/code-mode-toggle.jsx +129 -20
  43. package/lib/chat/components/containers-page.js +58 -40
  44. package/lib/chat/components/containers-page.jsx +64 -25
  45. package/lib/chat/components/crons-page.js +17 -3
  46. package/lib/chat/components/crons-page.jsx +34 -6
  47. package/lib/chat/components/index.js +2 -2
  48. package/lib/chat/components/message.js +18 -3
  49. package/lib/chat/components/message.jsx +18 -3
  50. package/lib/chat/components/profile-page.js +182 -4
  51. package/lib/chat/components/profile-page.jsx +196 -1
  52. package/lib/chat/components/scope-picker.js +21 -0
  53. package/lib/chat/components/scope-picker.jsx +27 -0
  54. package/lib/chat/components/settings-chat-page.js +11 -11
  55. package/lib/chat/components/settings-chat-page.jsx +14 -18
  56. package/lib/chat/components/settings-coding-agents-page.js +110 -16
  57. package/lib/chat/components/settings-coding-agents-page.jsx +87 -3
  58. package/lib/chat/components/settings-github-page.js +5 -0
  59. package/lib/chat/components/settings-github-page.jsx +5 -0
  60. package/lib/chat/components/settings-layout.js +3 -3
  61. package/lib/chat/components/settings-layout.jsx +3 -3
  62. package/lib/chat/components/settings-secrets-layout.js +1 -2
  63. package/lib/chat/components/settings-secrets-layout.jsx +1 -2
  64. package/lib/chat/components/settings-secrets-page.js +180 -75
  65. package/lib/chat/components/settings-secrets-page.jsx +212 -66
  66. package/lib/chat/components/triggers-page.js +17 -3
  67. package/lib/chat/components/triggers-page.jsx +34 -6
  68. package/lib/chat/components/ui/combobox.js +18 -2
  69. package/lib/chat/components/ui/combobox.jsx +17 -1
  70. package/lib/chat/components/ui/dropdown-menu.js +23 -2
  71. package/lib/chat/components/ui/dropdown-menu.jsx +27 -2
  72. package/lib/chat/telegram-profile.js +33 -0
  73. package/lib/cluster/CLAUDE.md +9 -3
  74. package/lib/code/CLAUDE.md +11 -3
  75. package/lib/code/actions.js +47 -8
  76. package/lib/code/terminal-view.js +31 -21
  77. package/lib/code/terminal-view.jsx +32 -23
  78. package/lib/config.js +15 -4
  79. package/lib/containers/CLAUDE.md +16 -6
  80. package/lib/db/CLAUDE.md +5 -2
  81. package/lib/db/chats.js +9 -17
  82. package/lib/db/code-workspaces.js +8 -3
  83. package/lib/db/config.js +0 -1
  84. package/lib/db/index.js +12 -0
  85. package/lib/db/schema.js +24 -1
  86. package/lib/db/user-channels.js +129 -0
  87. package/lib/llm-providers.js +8 -0
  88. package/lib/maintenance.js +31 -21
  89. package/lib/tools/CLAUDE.md +12 -3
  90. package/lib/tools/assemblyai.js +17 -0
  91. package/lib/tools/create-agent-job.js +12 -8
  92. package/lib/tools/docker.js +34 -10
  93. package/lib/tools/github.js +34 -0
  94. package/lib/tools/telegram.js +106 -0
  95. package/lib/utils/render-md.js +44 -18
  96. package/package.json +8 -8
  97. package/setup/CLAUDE.md +11 -5
  98. package/setup/lib/providers.mjs +2 -1
  99. package/setup/lib/targets.mjs +13 -16
  100. package/setup/lib/telegram.mjs +8 -69
  101. package/templates/.env.example +0 -7
  102. package/templates/.github/workflows/rebuild-event-handler.yml +1 -1
  103. package/templates/.gitignore.template +1 -3
  104. package/templates/CLAUDE.md +1 -1
  105. package/templates/CLAUDE.md.template +29 -7
  106. package/templates/agent-job/CLAUDE.md.template +5 -3
  107. package/templates/agent-job/CRONS.json +16 -0
  108. package/templates/agent-job/SYSTEM.md +16 -11
  109. package/templates/agents/CLAUDE.md.template +17 -17
  110. package/templates/coding-workspace/CLAUDE.md.template +7 -0
  111. package/templates/data/CLAUDE.md.template +1 -1
  112. package/templates/docker-compose.custom.yml +1 -0
  113. package/templates/docker-compose.yml +1 -0
  114. package/templates/event-handler/CLAUDE.md.template +79 -0
  115. package/templates/event-handler/TRIGGERS.json +18 -2
  116. package/templates/skills/CLAUDE.md.template +20 -22
  117. package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/SKILL.md +2 -2
  118. package/lib/ai/agent.js +0 -65
  119. package/lib/ai/async-channel.js +0 -51
  120. package/lib/ai/model.js +0 -130
  121. package/lib/ai/tools.js +0 -164
  122. package/lib/tools/openai.js +0 -37
  123. package/setup/lib/telegram-verify.mjs +0 -63
  124. package/setup/setup-telegram.mjs +0 -260
  125. package/templates/agent-job/SOUL.md +0 -17
  126. /package/templates/{skills/active/.gitkeep → coding-workspace/SYSTEM.md} +0 -0
  127. /package/templates/skills/{library/agent-job-secrets → agent-job-secrets}/agent-job-secrets.js +0 -0
  128. /package/templates/skills/{library/playwright-cli → playwright-cli}/SKILL.md +0 -0
package/lib/ai/agent.js DELETED
@@ -1,65 +0,0 @@
1
- import { createReactAgent } from '@langchain/langgraph/prebuilt';
2
- import { SystemMessage } from '@langchain/core/messages';
3
- import { createModel } from './model.js';
4
- import { agentJobTool, agentChatCodingTool, codeChatCodingTool } from './tools.js';
5
- import { SqliteSaver } from '@langchain/langgraph-checkpoint-sqlite';
6
- import path from 'path';
7
- import { PROJECT_ROOT } from '../paths.js';
8
- import { render_md } from '../utils/render-md.js';
9
-
10
- // Singletons on globalThis to survive Next.js webpack chunk duplication.
11
- // Server actions and route handlers may be bundled into separate chunks, each
12
- // with their own copy of module-level variables. globalThis is shared across all chunks.
13
-
14
- /**
15
- * Get or create the LangGraph agent chat singleton.
16
- * Uses createReactAgent which handles the tool loop automatically.
17
- * Prompt is a function so {{datetime}} resolves fresh each invocation.
18
- */
19
- export async function getAgentChat() {
20
- if (!globalThis.__popebotAgentChat) {
21
- const model = await createModel();
22
- const tools = [agentJobTool, agentChatCodingTool];
23
-
24
- const dbPath = process.env.DATABASE_PATH || path.join(PROJECT_ROOT, 'data/db/thepopebot.sqlite');
25
- const checkpointer = SqliteSaver.fromConnString(dbPath);
26
-
27
- globalThis.__popebotAgentChat = createReactAgent({
28
- llm: model,
29
- tools,
30
- checkpointSaver: checkpointer,
31
- prompt: (state) => [new SystemMessage(render_md(path.join(PROJECT_ROOT, 'event-handler/agent-chat/SYSTEM.md'))), ...state.messages],
32
- });
33
- }
34
- return globalThis.__popebotAgentChat;
35
- }
36
-
37
- /**
38
- * Get or create the code chat singleton.
39
- * Uses a static codeChatCodingTool that reads context from runtime.configurable.
40
- */
41
- export async function getCodeChat() {
42
- if (!globalThis.__popebotCodeChat) {
43
- const model = await createModel();
44
- const tools = [codeChatCodingTool];
45
-
46
- const dbPath = process.env.DATABASE_PATH || path.join(PROJECT_ROOT, 'data/db/thepopebot.sqlite');
47
- const checkpointer = SqliteSaver.fromConnString(dbPath);
48
-
49
- globalThis.__popebotCodeChat = createReactAgent({
50
- llm: model,
51
- tools,
52
- checkpointSaver: checkpointer,
53
- prompt: (state) => [new SystemMessage(render_md(path.join(PROJECT_ROOT, 'event-handler/code-chat/SYSTEM.md'))), ...state.messages],
54
- });
55
- }
56
- return globalThis.__popebotCodeChat;
57
- }
58
-
59
- /**
60
- * Reset all agent singletons (e.g., when config changes).
61
- */
62
- export function resetAgentChats() {
63
- globalThis.__popebotAgentChat = null;
64
- globalThis.__popebotCodeChat = null;
65
- }
@@ -1,51 +0,0 @@
1
- /**
2
- * Async push/pull queue. Producer calls push()/done(), consumer uses for-await.
3
- */
4
- export function createChannel() {
5
- const queue = [];
6
- const waiters = [];
7
- let isDone = false;
8
-
9
- return {
10
- push(value) {
11
- if (waiters.length > 0) waiters.shift()(value);
12
- else queue.push(value);
13
- },
14
- done() {
15
- isDone = true;
16
- while (waiters.length > 0) waiters.shift()(Symbol.for('done'));
17
- },
18
- async *[Symbol.asyncIterator]() {
19
- while (true) {
20
- if (queue.length > 0) {
21
- yield queue.shift();
22
- } else if (isDone) {
23
- return;
24
- } else {
25
- const value = await new Promise(resolve => waiters.push(resolve));
26
- if (value === Symbol.for('done')) return;
27
- yield value;
28
- }
29
- }
30
- }
31
- };
32
- }
33
-
34
- /**
35
- * Merge two async iterables — yields from whichever has data first.
36
- * Completes when BOTH are exhausted.
37
- */
38
- export async function* mergeAsyncIterables(iter1, iter2) {
39
- const channel = createChannel();
40
- let active = 2;
41
-
42
- const consume = async (iter) => {
43
- for await (const item of iter) channel.push(item);
44
- if (--active === 0) channel.done();
45
- };
46
-
47
- consume(iter1);
48
- consume(iter2);
49
-
50
- yield* channel;
51
- }
package/lib/ai/model.js DELETED
@@ -1,130 +0,0 @@
1
- import { ChatAnthropic } from '@langchain/anthropic';
2
- import { getConfig } from '../config.js';
3
- import { BUILTIN_PROVIDERS } from '../llm-providers.js';
4
-
5
- // These models require thought_signature round-tripping which @langchain/google-genai doesn't support.
6
- // Auto-replace with gemini-2.5-flash until we migrate to @langchain/google (see issue #201).
7
- const GEMINI_UNSUPPORTED_MODELS = ['gemini-2.5-pro', 'gemini-3'];
8
- const GEMINI_FALLBACK = 'gemini-2.5-flash';
9
-
10
- /**
11
- * Create a LangChain chat model based on DB/env configuration.
12
- *
13
- * @param {object} [options]
14
- * @param {number} [options.maxTokens] - Max tokens for the response
15
- * @returns {import('@langchain/core/language_models/chat_models').BaseChatModel}
16
- */
17
- export async function createModel(options = {}) {
18
- const provider = getConfig('LLM_PROVIDER');
19
- const modelName = getConfig('LLM_MODEL');
20
- const maxTokens = options.maxTokens || Number(getConfig('LLM_MAX_TOKENS')) || 4096;
21
-
22
- // Custom provider (not in BUILTIN_PROVIDERS) → OpenAI-compatible
23
- if (!BUILTIN_PROVIDERS[provider]) {
24
- const { ChatOpenAI } = await import('@langchain/openai');
25
- const { getCustomProvider } = await import('../db/config.js');
26
- const custom = getCustomProvider(provider);
27
- if (!custom) throw new Error(`Unknown LLM provider: ${provider}`);
28
- const config = { modelName: custom.models?.[0] || modelName, maxTokens };
29
- config.apiKey = custom.apiKey || 'not-needed';
30
- if (custom.baseUrl) {
31
- config.configuration = { baseURL: custom.baseUrl };
32
- }
33
- return new ChatOpenAI(config);
34
- }
35
-
36
- const LLM_NOT_CONFIGURED = 'No chat LLM configured — set one up in Admin > Event Handler > LLMs';
37
-
38
- switch (provider) {
39
- case 'anthropic': {
40
- const apiKey = getConfig('ANTHROPIC_API_KEY');
41
- if (!apiKey) {
42
- throw new Error(LLM_NOT_CONFIGURED);
43
- }
44
- return new ChatAnthropic({
45
- modelName,
46
- maxTokens,
47
- anthropicApiKey: apiKey,
48
- });
49
- }
50
- case 'openai': {
51
- const { ChatOpenAI } = await import('@langchain/openai');
52
- const apiKey = getConfig('OPENAI_API_KEY');
53
- const baseURL = getConfig('CUSTOM_OPENAI_BASE_URL');
54
- if (!apiKey && !baseURL) {
55
- throw new Error(LLM_NOT_CONFIGURED);
56
- }
57
- const config = { modelName, maxTokens };
58
- config.apiKey = apiKey || 'not-needed';
59
- if (baseURL) {
60
- config.configuration = { baseURL };
61
- }
62
- return new ChatOpenAI(config);
63
- }
64
- case 'google': {
65
- const { ChatGoogleGenerativeAI } = await import('@langchain/google-genai');
66
- const apiKey = getConfig('GOOGLE_API_KEY');
67
- if (!apiKey) {
68
- throw new Error(LLM_NOT_CONFIGURED);
69
- }
70
- let resolvedModel = modelName;
71
- const isUnsupported = GEMINI_UNSUPPORTED_MODELS.some(m => resolvedModel.startsWith(m));
72
- if (isUnsupported) {
73
- console.warn(
74
- `[model] ${resolvedModel} requires thought_signature support not yet available in @langchain/google-genai. ` +
75
- `Falling back to ${GEMINI_FALLBACK}. See https://github.com/stephengpope/thepopebot/issues/201.`
76
- );
77
- resolvedModel = GEMINI_FALLBACK;
78
- }
79
- return new ChatGoogleGenerativeAI({
80
- model: resolvedModel,
81
- maxOutputTokens: maxTokens,
82
- apiKey,
83
- });
84
- }
85
- case 'deepseek': {
86
- const { ChatOpenAI } = await import('@langchain/openai');
87
- const apiKey = getConfig('DEEPSEEK_API_KEY');
88
- if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
89
- return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://api.deepseek.com' } });
90
- }
91
- case 'minimax': {
92
- const { ChatOpenAI } = await import('@langchain/openai');
93
- const apiKey = getConfig('MINIMAX_API_KEY');
94
- if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
95
- return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://api.minimax.io/v1' } });
96
- }
97
- case 'mistral': {
98
- const { ChatOpenAI } = await import('@langchain/openai');
99
- const apiKey = getConfig('MISTRAL_API_KEY');
100
- if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
101
- return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://api.mistral.ai/v1' } });
102
- }
103
- case 'xai': {
104
- const { ChatOpenAI } = await import('@langchain/openai');
105
- const apiKey = getConfig('XAI_API_KEY');
106
- if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
107
- return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://api.x.ai/v1' } });
108
- }
109
- case 'kimi': {
110
- const { ChatOpenAI } = await import('@langchain/openai');
111
- const apiKey = getConfig('MOONSHOT_API_KEY');
112
- if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
113
- return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://api.moonshot.cn/v1' } });
114
- }
115
- case 'openrouter': {
116
- const { ChatOpenAI } = await import('@langchain/openai');
117
- const apiKey = getConfig('OPENROUTER_API_KEY');
118
- if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
119
- return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://openrouter.ai/api/v1' } });
120
- }
121
- case 'nvidia': {
122
- const { ChatOpenAI } = await import('@langchain/openai');
123
- const apiKey = getConfig('NVIDIA_API_KEY');
124
- if (!apiKey) throw new Error(LLM_NOT_CONFIGURED);
125
- return new ChatOpenAI({ modelName, maxTokens, apiKey, configuration: { baseURL: 'https://integrate.api.nvidia.com/v1' } });
126
- }
127
- default:
128
- throw new Error(`Unknown LLM provider: ${provider}`);
129
- }
130
- }
package/lib/ai/tools.js DELETED
@@ -1,164 +0,0 @@
1
- import { tool } from '@langchain/core/tools';
2
- import { z } from 'zod';
3
- import { createAgentJob } from '../tools/create-agent-job.js';
4
-
5
- import { getConfig } from '../config.js';
6
-
7
- const agentJobTool = tool(
8
- async ({ prompt }) => {
9
- const result = await createAgentJob(prompt);
10
- return JSON.stringify({
11
- success: true,
12
- agent_job_id: result.agent_job_id,
13
- branch: result.branch,
14
- title: result.title,
15
- });
16
- },
17
- {
18
- name: 'agent_job',
19
- description:
20
- 'Use when chat mode is job. Dispatches an autonomous task — the agent executes work in a Docker container with full filesystem, browser, and shell access. Results do not stream back. Always present the full job description and get explicit approval before calling.',
21
- schema: z.object({
22
- prompt: z
23
- .string()
24
- .describe(
25
- 'Detailed agent job description including context and requirements. Be specific about what needs to be done.'
26
- ),
27
- }),
28
- }
29
- );
30
-
31
-
32
- /**
33
- * Coding agent tool for agent chat (thepopebot repo).
34
- * Reads workspaceId and codeModeType from runtime.configurable.
35
- */
36
- const agentChatCodingTool = tool(
37
- async ({ prompt }, runtime) => {
38
- try {
39
- const { randomUUID } = await import('crypto');
40
- const { workspaceId, codeModeType } = runtime.configurable;
41
-
42
- const ghOwner = getConfig('GH_OWNER');
43
- const ghRepo = getConfig('GH_REPO');
44
- if (!ghOwner || !ghRepo) {
45
- return JSON.stringify({ success: false, error: 'GH_OWNER or GH_REPO not configured' });
46
- }
47
- const repo = `${ghOwner}/${ghRepo}`;
48
-
49
- const { getCodeWorkspaceById } = await import('../db/code-workspaces.js');
50
- const workspace = getCodeWorkspaceById(workspaceId);
51
- const featureBranch = workspace?.featureBranch;
52
- const mode = codeModeType === 'code' ? 'dangerous' : 'plan';
53
-
54
- const codingAgent = getConfig('CODING_AGENT');
55
- const containerName = `${codingAgent}-headless-${randomUUID().slice(0, 8)}`;
56
-
57
- const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
58
- const { backendApi } = await runHeadlessContainer({
59
- containerName,
60
- repo,
61
- branch: 'main',
62
- featureBranch,
63
- workspaceId,
64
- taskPrompt: prompt,
65
- mode,
66
- injectSecrets: true,
67
- });
68
-
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);
86
- } catch (err) {
87
- console.error('[coding_agent] Failed:', err);
88
- runtime.configurable.streamCallback?.(null);
89
- return JSON.stringify([{ type: 'error', message: err.message }]);
90
- }
91
- },
92
- {
93
- name: 'coding_agent',
94
- description:
95
- 'Use when chat mode is plan or code. Investigates or modifies the PopeBot — its configuration, abilities, personality, skills, crons, triggers, prompts, or code. Results stream directly into this conversation.',
96
- schema: z.object({
97
- prompt: z.string().describe(
98
- 'A direct copy of the coding task including all relevant context from the conversation.'
99
- ),
100
- }),
101
- }
102
- );
103
-
104
- /**
105
- * Coding agent tool for code chat (any repo).
106
- * Reads repo, branch, workspaceId, codeModeType from runtime.configurable.
107
- */
108
- const codeChatCodingTool = tool(
109
- async ({ prompt }, runtime) => {
110
- try {
111
- const { randomUUID } = await import('crypto');
112
- const { repo, branch, workspaceId, codeModeType } = runtime.configurable;
113
-
114
- const { getCodeWorkspaceById } = await import('../db/code-workspaces.js');
115
- const workspace = getCodeWorkspaceById(workspaceId);
116
- const featureBranch = workspace?.featureBranch;
117
- const mode = codeModeType === 'code' ? 'dangerous' : 'plan';
118
-
119
- const { runHeadlessContainer, tailContainerLogs, waitForContainer, removeContainer } = await import('../tools/docker.js');
120
- const codingAgent = getConfig('CODING_AGENT');
121
- const containerName = `${codingAgent}-headless-${randomUUID().slice(0, 8)}`;
122
-
123
- const { backendApi } = await runHeadlessContainer({
124
- containerName, repo, branch, featureBranch, workspaceId,
125
- taskPrompt: prompt,
126
- mode,
127
- });
128
-
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);
146
- } catch (err) {
147
- console.error('[coding_agent] Failed:', err);
148
- runtime.configurable.streamCallback?.(null);
149
- return JSON.stringify([{ type: 'error', message: err.message }]);
150
- }
151
- },
152
- {
153
- name: 'coding_agent',
154
- description:
155
- 'Use when you need to plan or execute a coding task.',
156
- schema: z.object({
157
- prompt: z.string().describe(
158
- 'A direct copy of the coding task including all relevant context from the conversation.'
159
- ),
160
- }),
161
- }
162
- );
163
-
164
- export { agentJobTool, agentChatCodingTool, codeChatCodingTool };
@@ -1,37 +0,0 @@
1
- import { getConfig } from '../config.js';
2
-
3
- /**
4
- * Check if Whisper transcription is enabled
5
- * @returns {boolean}
6
- */
7
- function isWhisperEnabled() {
8
- return Boolean(getConfig('OPENAI_API_KEY'));
9
- }
10
-
11
- /**
12
- * Transcribe audio using OpenAI Whisper API
13
- * @param {Buffer} audioBuffer - Audio file buffer
14
- * @param {string} filename - Original filename (e.g., "voice.ogg")
15
- * @returns {Promise<string>} Transcribed text
16
- */
17
- async function transcribeAudio(audioBuffer, filename) {
18
- const formData = new FormData();
19
- formData.append('file', new Blob([audioBuffer]), filename);
20
- formData.append('model', 'whisper-1');
21
-
22
- const response = await fetch('https://api.openai.com/v1/audio/transcriptions', {
23
- method: 'POST',
24
- headers: { 'Authorization': `Bearer ${getConfig('OPENAI_API_KEY')}` },
25
- body: formData,
26
- });
27
-
28
- if (!response.ok) {
29
- const error = await response.text();
30
- throw new Error(`OpenAI API error: ${response.status} ${error}`);
31
- }
32
-
33
- const result = await response.json();
34
- return result.text;
35
- }
36
-
37
- export { isWhisperEnabled, transcribeAudio };
@@ -1,63 +0,0 @@
1
- import * as clack from '@clack/prompts';
2
-
3
- /**
4
- * Run the chat ID verification flow
5
- * @param {string} verificationCode - The code user should send to bot
6
- * @param {object} [options] - Options
7
- * @param {boolean} [options.allowSkip=false] - Allow pressing Enter to skip
8
- * @returns {Promise<string|null>} - The chat ID or null if skipped
9
- */
10
- export async function runVerificationFlow(verificationCode, { allowSkip = false } = {}) {
11
- clack.log.warn('Chat ID Verification');
12
- clack.log.info(
13
- 'To lock the bot to your chat, send the verification code.\n' +
14
- ` Send this message to your bot: ${verificationCode}\n` +
15
- ' The bot will reply with your chat ID. Paste it below.'
16
- );
17
-
18
- const message = allowSkip
19
- ? 'Paste your chat ID from the bot (or press Enter to skip):'
20
- : 'Paste your chat ID from the bot:';
21
-
22
- const chatId = await clack.text({
23
- message,
24
- defaultValue: allowSkip ? '' : undefined,
25
- validate: (input) => {
26
- if (!input) return allowSkip ? undefined : 'Chat ID is required';
27
- if (!/^-?\d+$/.test(input.trim())) {
28
- return 'Chat ID should be a number (can be negative for groups)';
29
- }
30
- },
31
- });
32
-
33
- if (clack.isCancel(chatId)) {
34
- clack.cancel('Setup cancelled.');
35
- process.exit(0);
36
- }
37
-
38
- return chatId.trim() || null;
39
- }
40
-
41
- /**
42
- * Wait for server to pick up .env changes and verify it's running
43
- * @param {string} ngrokUrl - The ngrok URL
44
- * @returns {Promise<boolean>} - True if verified successfully
45
- */
46
- export async function verifyRestart(ngrokUrl) {
47
- const s = clack.spinner();
48
- s.start('Waiting for server to pick up changes...');
49
- await new Promise(resolve => setTimeout(resolve, 3000));
50
-
51
- try {
52
- await fetch(`${ngrokUrl}/api/ping`, {
53
- method: 'GET',
54
- signal: AbortSignal.timeout(10000)
55
- });
56
- } catch (err) {
57
- s.stop(`Server not reachable: ${err.message}`);
58
- return false;
59
- }
60
-
61
- s.stop('Server is running');
62
- return true;
63
- }