@openacp/cli 0.4.10 → 0.4.11
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 +17 -2
- package/dist/api-client-UN7BXQOQ.js +11 -0
- package/dist/{autostart-DZ3MHHMM.js → autostart-K73RQZVV.js} +3 -3
- package/dist/chunk-3DIPXFZJ.js +650 -0
- package/dist/chunk-3DIPXFZJ.js.map +1 -0
- package/dist/{chunk-KPI4HGJC.js → chunk-66RVSUAR.js} +1423 -1141
- package/dist/chunk-66RVSUAR.js.map +1 -0
- package/dist/chunk-BGKQHQB4.js +276 -0
- package/dist/chunk-BGKQHQB4.js.map +1 -0
- package/dist/chunk-C33LTDZV.js +97 -0
- package/dist/chunk-C33LTDZV.js.map +1 -0
- package/dist/{chunk-LYKCQTH5.js → chunk-ESOPMQAY.js} +5 -1
- package/dist/chunk-ESOPMQAY.js.map +1 -0
- package/dist/{chunk-6MJLVZXV.js → chunk-FKOARMAE.js} +58 -21
- package/dist/{chunk-6MJLVZXV.js.map → chunk-FKOARMAE.js.map} +1 -1
- package/dist/chunk-OORPX73T.js +30 -0
- package/dist/chunk-OORPX73T.js.map +1 -0
- package/dist/{chunk-V3BA2MJ6.js → chunk-RF3DUYFO.js} +2 -2
- package/dist/{chunk-UAUTLC4E.js → chunk-W7QQA6CW.js} +4 -4
- package/dist/{chunk-ZRFBLD3W.js → chunk-WYZFGHHI.js} +14 -4
- package/dist/chunk-WYZFGHHI.js.map +1 -0
- package/dist/{chunk-MRKYJ422.js → chunk-X6LLG7XN.js} +2 -2
- package/dist/{chunk-C6YIUTGR.js → chunk-YRJEZD7R.js} +2 -2
- package/dist/{chunk-HZD3CGPK.js → chunk-ZW444AQY.js} +3 -3
- package/dist/cli.js +141 -52
- package/dist/cli.js.map +1 -1
- package/dist/{config-H2DSEHNW.js → config-XURP6B3S.js} +3 -3
- package/dist/config-editor-AALY3URF.js +11 -0
- package/dist/config-registry-OGX4YM2U.js +17 -0
- package/dist/{daemon-VF6HJQXD.js → daemon-GWJM2S4A.js} +4 -4
- package/dist/doctor-X477CVZN.js +9 -0
- package/dist/doctor-X477CVZN.js.map +1 -0
- package/dist/index.d.ts +76 -27
- package/dist/index.js +33 -13
- package/dist/install-cloudflared-BTGUD7SW.js +8 -0
- package/dist/install-cloudflared-BTGUD7SW.js.map +1 -0
- package/dist/log-SPS2S6FO.js +19 -0
- package/dist/log-SPS2S6FO.js.map +1 -0
- package/dist/{main-G6XDM7EZ.js → main-2QKD2EI2.js} +17 -14
- package/dist/{main-G6XDM7EZ.js.map → main-2QKD2EI2.js.map} +1 -1
- package/dist/menu-CARRTW2F.js +17 -0
- package/dist/menu-CARRTW2F.js.map +1 -0
- package/dist/{setup-FCVL75K6.js → setup-TTOL7XAN.js} +4 -4
- package/dist/setup-TTOL7XAN.js.map +1 -0
- package/dist/{tunnel-service-DASSH7OA.js → tunnel-service-LEVPLXAZ.js} +3 -3
- package/package.json +1 -1
- package/dist/chunk-KPI4HGJC.js.map +0 -1
- package/dist/chunk-LYKCQTH5.js.map +0 -1
- package/dist/chunk-ZRFBLD3W.js.map +0 -1
- package/dist/config-editor-SKS4LJLT.js +0 -11
- package/dist/install-cloudflared-ILUXKLAC.js +0 -8
- /package/dist/{autostart-DZ3MHHMM.js.map → api-client-UN7BXQOQ.js.map} +0 -0
- /package/dist/{config-H2DSEHNW.js.map → autostart-K73RQZVV.js.map} +0 -0
- /package/dist/{chunk-V3BA2MJ6.js.map → chunk-RF3DUYFO.js.map} +0 -0
- /package/dist/{chunk-UAUTLC4E.js.map → chunk-W7QQA6CW.js.map} +0 -0
- /package/dist/{chunk-MRKYJ422.js.map → chunk-X6LLG7XN.js.map} +0 -0
- /package/dist/{chunk-C6YIUTGR.js.map → chunk-YRJEZD7R.js.map} +0 -0
- /package/dist/{chunk-HZD3CGPK.js.map → chunk-ZW444AQY.js.map} +0 -0
- /package/dist/{config-editor-SKS4LJLT.js.map → config-XURP6B3S.js.map} +0 -0
- /package/dist/{daemon-VF6HJQXD.js.map → config-editor-AALY3URF.js.map} +0 -0
- /package/dist/{install-cloudflared-ILUXKLAC.js.map → config-registry-OGX4YM2U.js.map} +0 -0
- /package/dist/{setup-FCVL75K6.js.map → daemon-GWJM2S4A.js.map} +0 -0
- /package/dist/{tunnel-service-DASSH7OA.js.map → tunnel-service-LEVPLXAZ.js.map} +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
// src/adapters/telegram/commands/menu.ts
|
|
2
|
+
import { InlineKeyboard } from "grammy";
|
|
3
|
+
|
|
4
|
+
// src/adapters/telegram/formatting.ts
|
|
5
|
+
function escapeHtml(text) {
|
|
6
|
+
if (!text) return "";
|
|
7
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
8
|
+
}
|
|
9
|
+
function markdownToTelegramHtml(md) {
|
|
10
|
+
const codeBlocks = [];
|
|
11
|
+
const inlineCodes = [];
|
|
12
|
+
let text = md.replace(/```(\w*)\n?([\s\S]*?)```/g, (_match, lang, code) => {
|
|
13
|
+
const index = codeBlocks.length;
|
|
14
|
+
const escapedCode = escapeHtml(code);
|
|
15
|
+
const langAttr = lang ? ` class="language-${escapeHtml(lang)}"` : "";
|
|
16
|
+
codeBlocks.push(`<pre><code${langAttr}>${escapedCode}</code></pre>`);
|
|
17
|
+
return `\0CODE_BLOCK_${index}\0`;
|
|
18
|
+
});
|
|
19
|
+
text = text.replace(/`([^`]+)`/g, (_match, code) => {
|
|
20
|
+
const index = inlineCodes.length;
|
|
21
|
+
inlineCodes.push(`<code>${escapeHtml(code)}</code>`);
|
|
22
|
+
return `\0INLINE_CODE_${index}\0`;
|
|
23
|
+
});
|
|
24
|
+
text = escapeHtml(text);
|
|
25
|
+
text = text.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>");
|
|
26
|
+
text = text.replace(/(?<!\*)\*(?!\*)(.+?)(?<!\*)\*(?!\*)/g, "<i>$1</i>");
|
|
27
|
+
text = text.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2">$1</a>');
|
|
28
|
+
text = text.replace(/\x00CODE_BLOCK_(\d+)\x00/g, (_match, idx) => {
|
|
29
|
+
return codeBlocks[parseInt(idx, 10)];
|
|
30
|
+
});
|
|
31
|
+
text = text.replace(/\x00INLINE_CODE_(\d+)\x00/g, (_match, idx) => {
|
|
32
|
+
return inlineCodes[parseInt(idx, 10)];
|
|
33
|
+
});
|
|
34
|
+
return text;
|
|
35
|
+
}
|
|
36
|
+
var STATUS_ICON = {
|
|
37
|
+
pending: "\u23F3",
|
|
38
|
+
in_progress: "\u{1F504}",
|
|
39
|
+
completed: "\u2705",
|
|
40
|
+
failed: "\u274C"
|
|
41
|
+
};
|
|
42
|
+
var KIND_ICON = {
|
|
43
|
+
read: "\u{1F4D6}",
|
|
44
|
+
edit: "\u270F\uFE0F",
|
|
45
|
+
delete: "\u{1F5D1}\uFE0F",
|
|
46
|
+
execute: "\u25B6\uFE0F",
|
|
47
|
+
search: "\u{1F50D}",
|
|
48
|
+
fetch: "\u{1F310}",
|
|
49
|
+
think: "\u{1F9E0}",
|
|
50
|
+
move: "\u{1F4E6}",
|
|
51
|
+
other: "\u{1F6E0}\uFE0F"
|
|
52
|
+
};
|
|
53
|
+
function extractContentText(content, depth = 0) {
|
|
54
|
+
if (!content || depth > 5) return "";
|
|
55
|
+
if (typeof content === "string") return content;
|
|
56
|
+
if (Array.isArray(content)) {
|
|
57
|
+
return content.map((c) => extractContentText(c, depth + 1)).filter(Boolean).join("\n");
|
|
58
|
+
}
|
|
59
|
+
if (typeof content === "object" && content !== null) {
|
|
60
|
+
const c = content;
|
|
61
|
+
if (c.type === "text" && typeof c.text === "string") return c.text;
|
|
62
|
+
if (typeof c.text === "string") return c.text;
|
|
63
|
+
if (typeof c.content === "string") return c.content;
|
|
64
|
+
if (c.content && typeof c.content === "object") return extractContentText(c.content, depth + 1);
|
|
65
|
+
if (c.input) return extractContentText(c.input, depth + 1);
|
|
66
|
+
if (c.output) return extractContentText(c.output, depth + 1);
|
|
67
|
+
const keys = Object.keys(c).filter((k) => k !== "type");
|
|
68
|
+
if (keys.length === 0) return "";
|
|
69
|
+
return JSON.stringify(c, null, 2);
|
|
70
|
+
}
|
|
71
|
+
return String(content);
|
|
72
|
+
}
|
|
73
|
+
function truncateContent(text, maxLen = 3800) {
|
|
74
|
+
if (text.length <= maxLen) return text;
|
|
75
|
+
return text.slice(0, maxLen) + "\n\u2026 (truncated)";
|
|
76
|
+
}
|
|
77
|
+
function formatToolCall(tool) {
|
|
78
|
+
const si = STATUS_ICON[tool.status || ""] || "\u{1F527}";
|
|
79
|
+
const ki = KIND_ICON[tool.kind || ""] || "\u{1F6E0}\uFE0F";
|
|
80
|
+
let text = `${si} ${ki} <b>${escapeHtml(tool.name || "Tool")}</b>`;
|
|
81
|
+
text += formatViewerLinks(tool.viewerLinks, tool.viewerFilePath);
|
|
82
|
+
if (!tool.viewerLinks) {
|
|
83
|
+
const details = extractContentText(tool.content);
|
|
84
|
+
if (details) {
|
|
85
|
+
text += `
|
|
86
|
+
<pre>${escapeHtml(truncateContent(details))}</pre>`;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return text;
|
|
90
|
+
}
|
|
91
|
+
function formatToolUpdate(update) {
|
|
92
|
+
const si = STATUS_ICON[update.status] || "\u{1F527}";
|
|
93
|
+
const ki = KIND_ICON[update.kind || ""] || "\u{1F6E0}\uFE0F";
|
|
94
|
+
const name = update.name || "Tool";
|
|
95
|
+
let text = `${si} ${ki} <b>${escapeHtml(name)}</b>`;
|
|
96
|
+
text += formatViewerLinks(update.viewerLinks, update.viewerFilePath);
|
|
97
|
+
if (!update.viewerLinks) {
|
|
98
|
+
const details = extractContentText(update.content);
|
|
99
|
+
if (details) {
|
|
100
|
+
text += `
|
|
101
|
+
<pre>${escapeHtml(truncateContent(details))}</pre>`;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return text;
|
|
105
|
+
}
|
|
106
|
+
function formatViewerLinks(links, filePath) {
|
|
107
|
+
if (!links) return "";
|
|
108
|
+
const fileName = filePath ? filePath.split("/").pop() || filePath : "";
|
|
109
|
+
let text = "\n";
|
|
110
|
+
if (links.file) text += `
|
|
111
|
+
\u{1F4C4} <a href="${escapeHtml(links.file)}">View ${escapeHtml(fileName || "file")}</a>`;
|
|
112
|
+
if (links.diff) text += `
|
|
113
|
+
\u{1F4DD} <a href="${escapeHtml(links.diff)}">View diff${fileName ? ` \u2014 ${escapeHtml(fileName)}` : ""}</a>`;
|
|
114
|
+
return text;
|
|
115
|
+
}
|
|
116
|
+
function formatTokens(n) {
|
|
117
|
+
return n >= 1e3 ? `${Math.round(n / 1e3)}k` : String(n);
|
|
118
|
+
}
|
|
119
|
+
function progressBar(ratio) {
|
|
120
|
+
const filled = Math.round(Math.min(ratio, 1) * 10);
|
|
121
|
+
return "\u2593".repeat(filled) + "\u2591".repeat(10 - filled);
|
|
122
|
+
}
|
|
123
|
+
function formatUsage(usage) {
|
|
124
|
+
const { tokensUsed, contextSize } = usage;
|
|
125
|
+
if (tokensUsed == null) return "\u{1F4CA} Usage data unavailable";
|
|
126
|
+
if (contextSize == null) return `\u{1F4CA} ${formatTokens(tokensUsed)} tokens`;
|
|
127
|
+
const ratio = tokensUsed / contextSize;
|
|
128
|
+
const pct = Math.round(ratio * 100);
|
|
129
|
+
const bar = progressBar(ratio);
|
|
130
|
+
const emoji = pct >= 85 ? "\u26A0\uFE0F" : "\u{1F4CA}";
|
|
131
|
+
return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens
|
|
132
|
+
${bar} ${pct}%`;
|
|
133
|
+
}
|
|
134
|
+
function splitMessage(text, maxLength = 3800) {
|
|
135
|
+
if (text.length <= maxLength) return [text];
|
|
136
|
+
const chunks = [];
|
|
137
|
+
let remaining = text;
|
|
138
|
+
while (remaining.length > 0) {
|
|
139
|
+
if (remaining.length <= maxLength) {
|
|
140
|
+
chunks.push(remaining);
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
const wouldLeaveSmall = remaining.length < maxLength * 1.3;
|
|
144
|
+
const searchLimit = wouldLeaveSmall ? Math.floor(remaining.length / 2) + 300 : maxLength;
|
|
145
|
+
let splitAt = remaining.lastIndexOf("\n\n", searchLimit);
|
|
146
|
+
if (splitAt === -1 || splitAt < searchLimit * 0.2) {
|
|
147
|
+
splitAt = remaining.lastIndexOf("\n", searchLimit);
|
|
148
|
+
}
|
|
149
|
+
if (splitAt === -1 || splitAt < searchLimit * 0.2) {
|
|
150
|
+
splitAt = searchLimit;
|
|
151
|
+
}
|
|
152
|
+
const candidate = remaining.slice(0, splitAt);
|
|
153
|
+
const fences = candidate.match(/```/g);
|
|
154
|
+
if (fences && fences.length % 2 !== 0) {
|
|
155
|
+
const closingFence = remaining.indexOf("```", splitAt);
|
|
156
|
+
if (closingFence !== -1) {
|
|
157
|
+
const afterFence = remaining.indexOf("\n", closingFence + 3);
|
|
158
|
+
splitAt = afterFence !== -1 ? afterFence + 1 : closingFence + 3;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
chunks.push(remaining.slice(0, splitAt));
|
|
162
|
+
remaining = remaining.slice(splitAt).replace(/^\n+/, "");
|
|
163
|
+
}
|
|
164
|
+
return chunks;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/adapters/telegram/commands/menu.ts
|
|
168
|
+
function buildMenuKeyboard() {
|
|
169
|
+
return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4CB} Sessions", "m:topics").row().text("\u{1F4CA} Status", "m:status").text("\u{1F916} Agents", "m:agents").row().text("\u2699\uFE0F Settings", "m:settings").text("\u{1F517} Integrate", "m:integrate").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update").row().text("\u2753 Help", "m:help").text("\u{1FA7A} Doctor", "m:doctor");
|
|
170
|
+
}
|
|
171
|
+
async function handleMenu(ctx) {
|
|
172
|
+
await ctx.reply(`<b>OpenACP Menu</b>
|
|
173
|
+
Choose an action:`, {
|
|
174
|
+
parse_mode: "HTML",
|
|
175
|
+
reply_markup: buildMenuKeyboard()
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async function handleAgents(ctx, core) {
|
|
179
|
+
const agents = core.agentManager.getAvailableAgents();
|
|
180
|
+
const defaultAgent = core.configManager.get().defaultAgent;
|
|
181
|
+
const lines = agents.map(
|
|
182
|
+
(a) => `\u2022 <b>${escapeHtml(a.name)}</b>${a.name === defaultAgent ? " (default)" : ""}
|
|
183
|
+
<code>${escapeHtml(a.command)} ${a.args.map((arg) => escapeHtml(arg)).join(" ")}</code>`
|
|
184
|
+
);
|
|
185
|
+
const text = lines.length > 0 ? `<b>Available Agents:</b>
|
|
186
|
+
|
|
187
|
+
${lines.join("\n")}` : `<b>Available Agents:</b>
|
|
188
|
+
|
|
189
|
+
No agents configured.`;
|
|
190
|
+
await ctx.reply(text, { parse_mode: "HTML" });
|
|
191
|
+
}
|
|
192
|
+
async function handleHelp(ctx) {
|
|
193
|
+
await ctx.reply(
|
|
194
|
+
`\u{1F4D6} <b>OpenACP Help</b>
|
|
195
|
+
|
|
196
|
+
\u{1F680} <b>Getting Started</b>
|
|
197
|
+
Tap \u{1F195} New Session to start coding with AI.
|
|
198
|
+
Each session gets its own topic \u2014 chat there to work with the agent.
|
|
199
|
+
|
|
200
|
+
\u{1F4A1} <b>Common Tasks</b>
|
|
201
|
+
/new [agent] [workspace] \u2014 Create new session
|
|
202
|
+
/cancel \u2014 Cancel session (in session topic)
|
|
203
|
+
/status \u2014 Show session or system status
|
|
204
|
+
/sessions \u2014 List all sessions
|
|
205
|
+
/agents \u2014 List available agents
|
|
206
|
+
|
|
207
|
+
\u2699\uFE0F <b>System</b>
|
|
208
|
+
/restart \u2014 Restart OpenACP
|
|
209
|
+
/update \u2014 Update to latest version
|
|
210
|
+
/integrate \u2014 Manage agent integrations
|
|
211
|
+
/menu \u2014 Show action menu
|
|
212
|
+
|
|
213
|
+
\u{1F512} <b>Session Options</b>
|
|
214
|
+
/enable_dangerous \u2014 Auto-approve permissions
|
|
215
|
+
/disable_dangerous \u2014 Restore permission prompts
|
|
216
|
+
/handoff \u2014 Continue session in terminal
|
|
217
|
+
/clear \u2014 Clear assistant history
|
|
218
|
+
|
|
219
|
+
\u{1F4AC} Need help? Just ask me in this topic!`,
|
|
220
|
+
{ parse_mode: "HTML" }
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
async function handleClear(ctx, assistant) {
|
|
224
|
+
if (!assistant) {
|
|
225
|
+
await ctx.reply("\u26A0\uFE0F Assistant is not available.", { parse_mode: "HTML" });
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const threadId = ctx.message?.message_thread_id;
|
|
229
|
+
if (threadId !== assistant.topicId) {
|
|
230
|
+
await ctx.reply("\u2139\uFE0F /clear only works in the Assistant topic.", { parse_mode: "HTML" });
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
await ctx.reply("\u{1F504} Clearing assistant history...", { parse_mode: "HTML" });
|
|
234
|
+
try {
|
|
235
|
+
await assistant.respawn();
|
|
236
|
+
await ctx.reply("\u2705 Assistant history cleared.", { parse_mode: "HTML" });
|
|
237
|
+
} catch (err) {
|
|
238
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
239
|
+
await ctx.reply(`\u274C Failed to clear: <code>${message}</code>`, { parse_mode: "HTML" });
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
var TELEGRAM_MSG_LIMIT = 4096;
|
|
243
|
+
function buildSkillMessages(commands) {
|
|
244
|
+
const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));
|
|
245
|
+
const header = "\u{1F6E0} <b>Available Skills</b>\n";
|
|
246
|
+
const lines = sorted.map((c) => `<code>/${c.name}</code>`);
|
|
247
|
+
const messages = [];
|
|
248
|
+
let current = header;
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
const candidate = current + "\n" + line;
|
|
251
|
+
if (candidate.length > TELEGRAM_MSG_LIMIT) {
|
|
252
|
+
messages.push(current);
|
|
253
|
+
current = line;
|
|
254
|
+
} else {
|
|
255
|
+
current = candidate;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
if (current) messages.push(current);
|
|
259
|
+
return messages;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
export {
|
|
263
|
+
escapeHtml,
|
|
264
|
+
markdownToTelegramHtml,
|
|
265
|
+
formatToolCall,
|
|
266
|
+
formatToolUpdate,
|
|
267
|
+
formatUsage,
|
|
268
|
+
splitMessage,
|
|
269
|
+
buildMenuKeyboard,
|
|
270
|
+
handleMenu,
|
|
271
|
+
handleAgents,
|
|
272
|
+
handleHelp,
|
|
273
|
+
handleClear,
|
|
274
|
+
buildSkillMessages
|
|
275
|
+
};
|
|
276
|
+
//# sourceMappingURL=chunk-BGKQHQB4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/telegram/commands/menu.ts","../../src/adapters/telegram/formatting.ts"],"sourcesContent":["import type { Context } from \"grammy\";\nimport { InlineKeyboard } from \"grammy\";\nimport type { OpenACPCore } from \"../../../core/index.js\";\nimport type { AgentCommand } from \"../../../core/index.js\";\nimport { escapeHtml } from \"../formatting.js\";\nimport type { CommandsAssistantContext } from \"../types.js\";\n\nexport function buildMenuKeyboard(): InlineKeyboard {\n return new InlineKeyboard()\n .text(\"🆕 New Session\", \"m:new\")\n .text(\"📋 Sessions\", \"m:topics\")\n .row()\n .text(\"📊 Status\", \"m:status\")\n .text(\"🤖 Agents\", \"m:agents\")\n .row()\n .text(\"⚙️ Settings\", \"m:settings\")\n .text(\"🔗 Integrate\", \"m:integrate\")\n .row()\n .text(\"🔄 Restart\", \"m:restart\")\n .text(\"⬆️ Update\", \"m:update\")\n .row()\n .text(\"❓ Help\", \"m:help\")\n .text(\"🩺 Doctor\", \"m:doctor\");\n}\n\nexport async function handleMenu(ctx: Context): Promise<void> {\n await ctx.reply(`<b>OpenACP Menu</b>\\nChoose an action:`, {\n parse_mode: \"HTML\",\n reply_markup: buildMenuKeyboard(),\n });\n}\n\nexport async function handleAgents(ctx: Context, core: OpenACPCore): Promise<void> {\n const agents = core.agentManager.getAvailableAgents();\n const defaultAgent = core.configManager.get().defaultAgent;\n const lines = agents.map(\n (a) =>\n `• <b>${escapeHtml(a.name)}</b>${a.name === defaultAgent ? \" (default)\" : \"\"}\\n` +\n ` <code>${escapeHtml(a.command)} ${a.args.map((arg) => escapeHtml(arg)).join(\" \")}</code>`,\n );\n const text =\n lines.length > 0\n ? `<b>Available Agents:</b>\\n\\n${lines.join(\"\\n\")}`\n : `<b>Available Agents:</b>\\n\\nNo agents configured.`;\n await ctx.reply(text, { parse_mode: \"HTML\" });\n}\n\nexport async function handleHelp(ctx: Context): Promise<void> {\n await ctx.reply(\n `📖 <b>OpenACP Help</b>\\n\\n` +\n `🚀 <b>Getting Started</b>\\n` +\n `Tap 🆕 New Session to start coding with AI.\\n` +\n `Each session gets its own topic — chat there to work with the agent.\\n\\n` +\n `💡 <b>Common Tasks</b>\\n` +\n `/new [agent] [workspace] — Create new session\\n` +\n `/cancel — Cancel session (in session topic)\\n` +\n `/status — Show session or system status\\n` +\n `/sessions — List all sessions\\n` +\n `/agents — List available agents\\n\\n` +\n `⚙️ <b>System</b>\\n` +\n `/restart — Restart OpenACP\\n` +\n `/update — Update to latest version\\n` +\n `/integrate — Manage agent integrations\\n` +\n `/menu — Show action menu\\n\\n` +\n `🔒 <b>Session Options</b>\\n` +\n `/enable_dangerous — Auto-approve permissions\\n` +\n `/disable_dangerous — Restore permission prompts\\n` +\n `/handoff — Continue session in terminal\\n` +\n `/clear — Clear assistant history\\n\\n` +\n `💬 Need help? Just ask me in this topic!`,\n { parse_mode: \"HTML\" },\n );\n}\n\nexport async function handleClear(ctx: Context, assistant?: CommandsAssistantContext): Promise<void> {\n if (!assistant) {\n await ctx.reply(\"⚠️ Assistant is not available.\", { parse_mode: \"HTML\" });\n return;\n }\n\n const threadId = ctx.message?.message_thread_id;\n if (threadId !== assistant.topicId) {\n await ctx.reply(\"ℹ️ /clear only works in the Assistant topic.\", { parse_mode: \"HTML\" });\n return;\n }\n\n await ctx.reply(\"🔄 Clearing assistant history...\", { parse_mode: \"HTML\" });\n\n try {\n await assistant.respawn();\n await ctx.reply(\"✅ Assistant history cleared.\", { parse_mode: \"HTML\" });\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n await ctx.reply(`❌ Failed to clear: <code>${message}</code>`, { parse_mode: \"HTML\" });\n }\n}\n\nconst TELEGRAM_MSG_LIMIT = 4096;\n\n/**\n * Build plain-text skill command messages. Each command is on its own line\n * wrapped in <code> for tap-to-copy. If the list exceeds Telegram's message\n * limit, it is split into multiple messages (cut at line boundaries).\n */\nexport function buildSkillMessages(commands: AgentCommand[]): string[] {\n const sorted = [...commands].sort((a, b) => a.name.localeCompare(b.name));\n const header = \"🛠 <b>Available Skills</b>\\n\";\n const lines = sorted.map((c) => `<code>/${c.name}</code>`);\n\n const messages: string[] = [];\n let current = header;\n\n for (const line of lines) {\n const candidate = current + \"\\n\" + line;\n if (candidate.length > TELEGRAM_MSG_LIMIT) {\n messages.push(current);\n current = line;\n } else {\n current = candidate;\n }\n }\n if (current) messages.push(current);\n return messages;\n}\n","export function escapeHtml(text: string | undefined | null): string {\n if (!text) return ''\n return text\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n}\n\nexport function markdownToTelegramHtml(md: string): string {\n // Step 1: Extract code blocks and inline code into placeholders\n const codeBlocks: string[] = []\n const inlineCodes: string[] = []\n\n // Extract fenced code blocks (```lang\\n...\\n```)\n let text = md.replace(/```(\\w*)\\n?([\\s\\S]*?)```/g, (_match, lang: string, code: string) => {\n const index = codeBlocks.length\n const escapedCode = escapeHtml(code)\n const langAttr = lang ? ` class=\"language-${escapeHtml(lang)}\"` : ''\n codeBlocks.push(`<pre><code${langAttr}>${escapedCode}</code></pre>`)\n return `\\x00CODE_BLOCK_${index}\\x00`\n })\n\n // Extract inline code (`...`)\n text = text.replace(/`([^`]+)`/g, (_match, code: string) => {\n const index = inlineCodes.length\n inlineCodes.push(`<code>${escapeHtml(code)}</code>`)\n return `\\x00INLINE_CODE_${index}\\x00`\n })\n\n // Step 2: Escape HTML in remaining text\n text = escapeHtml(text)\n\n // Step 3: Apply markdown transformations\n // Bold: **text** → <b>text</b>\n text = text.replace(/\\*\\*(.+?)\\*\\*/g, '<b>$1</b>')\n\n // Italic: *text* → <i>text</i> (but not the ** used for bold)\n text = text.replace(/(?<!\\*)\\*(?!\\*)(.+?)(?<!\\*)\\*(?!\\*)/g, '<i>$1</i>')\n\n // Links: [text](url) → <a href=\"url\">text</a>\n // Note: after escapeHtml, parentheses are not affected, but we need to handle\n // the escaped brackets properly. Since [ ] and ( ) are not escaped, this works directly.\n text = text.replace(/\\[([^\\]]+)\\]\\(([^)]+)\\)/g, '<a href=\"$2\">$1</a>')\n\n // Step 4: Restore fenced code blocks\n text = text.replace(/\\x00CODE_BLOCK_(\\d+)\\x00/g, (_match, idx: string) => {\n return codeBlocks[parseInt(idx, 10)]\n })\n\n // Step 5: Restore inline code\n text = text.replace(/\\x00INLINE_CODE_(\\d+)\\x00/g, (_match, idx: string) => {\n return inlineCodes[parseInt(idx, 10)]\n })\n\n return text\n}\n\nconst STATUS_ICON: Record<string, string> = {\n pending: '⏳',\n in_progress: '🔄',\n completed: '✅',\n failed: '❌',\n}\n\nconst KIND_ICON: Record<string, string> = {\n read: '📖', edit: '✏️', delete: '🗑️', execute: '▶️',\n search: '🔍', fetch: '🌐', think: '🧠', move: '📦', other: '🛠️',\n}\n\nfunction extractContentText(content: unknown, depth = 0): string {\n if (!content || depth > 5) return ''\n if (typeof content === 'string') return content\n if (Array.isArray(content)) {\n return content\n .map((c: unknown) => extractContentText(c, depth + 1))\n .filter(Boolean)\n .join('\\n')\n }\n if (typeof content === 'object' && content !== null) {\n const c = content as Record<string, unknown>\n // ACP content blocks: {type: ..., text: ...} or {type: ..., content: ...}\n if (c.type === 'text' && typeof c.text === 'string') return c.text\n if (typeof c.text === 'string') return c.text\n if (typeof c.content === 'string') return c.content\n // ACP content wrapper: {type: \"content\", content: {type: \"text\", text: \"...\"}}\n if (c.content && typeof c.content === 'object') return extractContentText(c.content, depth + 1)\n // Tool input/output objects\n if (c.input) return extractContentText(c.input, depth + 1)\n if (c.output) return extractContentText(c.output, depth + 1)\n // Fallback: pretty-print JSON (but skip type-only objects)\n const keys = Object.keys(c).filter(k => k !== 'type')\n if (keys.length === 0) return ''\n return JSON.stringify(c, null, 2)\n }\n return String(content)\n}\n\nfunction truncateContent(text: string, maxLen = 3800): string {\n if (text.length <= maxLen) return text\n return text.slice(0, maxLen) + '\\n… (truncated)'\n}\n\nexport function formatToolCall(tool: { id: string; name?: string; kind?: string; status?: string; content?: unknown; viewerLinks?: { file?: string; diff?: string }; viewerFilePath?: string }): string {\n const si = STATUS_ICON[tool.status || ''] || '🔧'\n const ki = KIND_ICON[tool.kind || ''] || '🛠️'\n let text = `${si} ${ki} <b>${escapeHtml(tool.name || 'Tool')}</b>`\n text += formatViewerLinks(tool.viewerLinks, tool.viewerFilePath)\n if (!tool.viewerLinks) {\n const details = extractContentText(tool.content)\n if (details) {\n text += `\\n<pre>${escapeHtml(truncateContent(details))}</pre>`\n }\n }\n return text\n}\n\nexport function formatToolUpdate(update: { id: string; name?: string; kind?: string; status: string; content?: unknown; viewerLinks?: { file?: string; diff?: string }; viewerFilePath?: string }): string {\n const si = STATUS_ICON[update.status] || '🔧'\n const ki = KIND_ICON[update.kind || ''] || '🛠️'\n const name = update.name || 'Tool'\n let text = `${si} ${ki} <b>${escapeHtml(name)}</b>`\n text += formatViewerLinks(update.viewerLinks, update.viewerFilePath)\n if (!update.viewerLinks) {\n const details = extractContentText(update.content)\n if (details) {\n text += `\\n<pre>${escapeHtml(truncateContent(details))}</pre>`\n }\n }\n return text\n}\n\nfunction formatViewerLinks(links?: { file?: string; diff?: string }, filePath?: string): string {\n if (!links) return ''\n const fileName = filePath ? filePath.split('/').pop() || filePath : ''\n let text = '\\n'\n if (links.file) text += `\\n📄 <a href=\"${escapeHtml(links.file)}\">View ${escapeHtml(fileName || 'file')}</a>`\n if (links.diff) text += `\\n📝 <a href=\"${escapeHtml(links.diff)}\">View diff${fileName ? ` — ${escapeHtml(fileName)}` : ''}</a>`\n return text\n}\n\nexport function formatPlan(plan: { entries: Array<{ content: string; status: string }> }): string {\n const statusIcon: Record<string, string> = { pending: '⬜', in_progress: '🔄', completed: '✅' }\n const lines = plan.entries.map((e, i) =>\n `${statusIcon[e.status] || '⬜'} ${i + 1}. ${escapeHtml(e.content)}`\n )\n return `<b>Plan:</b>\\n${lines.join('\\n')}`\n}\n\nfunction formatTokens(n: number): string {\n return n >= 1000 ? `${Math.round(n / 1000)}k` : String(n)\n}\n\nfunction progressBar(ratio: number): string {\n const filled = Math.round(Math.min(ratio, 1) * 10)\n return '▓'.repeat(filled) + '░'.repeat(10 - filled)\n}\n\nexport function formatUsage(usage: { tokensUsed?: number; contextSize?: number }): string {\n const { tokensUsed, contextSize } = usage\n if (tokensUsed == null) return '📊 Usage data unavailable'\n if (contextSize == null) return `📊 ${formatTokens(tokensUsed)} tokens`\n\n const ratio = tokensUsed / contextSize\n const pct = Math.round(ratio * 100)\n const bar = progressBar(ratio)\n const emoji = pct >= 85 ? '⚠️' : '📊'\n return `${emoji} ${formatTokens(tokensUsed)} / ${formatTokens(contextSize)} tokens\\n${bar} ${pct}%`\n}\n\nexport function splitMessage(text: string, maxLength = 3800): string[] {\n if (text.length <= maxLength) return [text]\n const chunks: string[] = []\n let remaining = text\n while (remaining.length > 0) {\n if (remaining.length <= maxLength) {\n chunks.push(remaining)\n break\n }\n\n // If only slightly over limit, split roughly in half for balanced chunks\n // instead of creating a large chunk + tiny remainder\n const wouldLeaveSmall = remaining.length < maxLength * 1.3\n const searchLimit = wouldLeaveSmall\n ? Math.floor(remaining.length / 2) + 300\n : maxLength\n\n let splitAt = remaining.lastIndexOf('\\n\\n', searchLimit)\n if (splitAt === -1 || splitAt < searchLimit * 0.2) {\n splitAt = remaining.lastIndexOf('\\n', searchLimit)\n }\n if (splitAt === -1 || splitAt < searchLimit * 0.2) {\n splitAt = searchLimit\n }\n\n // Avoid splitting inside a fenced code block (odd number of ``` before split point)\n const candidate = remaining.slice(0, splitAt)\n const fences = candidate.match(/```/g)\n if (fences && fences.length % 2 !== 0) {\n // Find the closing fence after split point\n const closingFence = remaining.indexOf('```', splitAt)\n if (closingFence !== -1) {\n const afterFence = remaining.indexOf('\\n', closingFence + 3)\n splitAt = afterFence !== -1 ? afterFence + 1 : closingFence + 3\n }\n // If no closing fence, split anyway (incomplete code block)\n }\n\n chunks.push(remaining.slice(0, splitAt))\n remaining = remaining.slice(splitAt).replace(/^\\n+/, '')\n }\n return chunks\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// src/core/config-registry.ts
|
|
2
|
+
var CONFIG_REGISTRY = [
|
|
3
|
+
{
|
|
4
|
+
path: "defaultAgent",
|
|
5
|
+
displayName: "Default Agent",
|
|
6
|
+
group: "agent",
|
|
7
|
+
type: "select",
|
|
8
|
+
options: (config) => Object.keys(config.agents),
|
|
9
|
+
scope: "safe",
|
|
10
|
+
hotReload: true
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
path: "logging.level",
|
|
14
|
+
displayName: "Log Level",
|
|
15
|
+
group: "logging",
|
|
16
|
+
type: "select",
|
|
17
|
+
options: ["silent", "debug", "info", "warn", "error", "fatal"],
|
|
18
|
+
scope: "safe",
|
|
19
|
+
hotReload: true
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
path: "tunnel.enabled",
|
|
23
|
+
displayName: "Tunnel",
|
|
24
|
+
group: "tunnel",
|
|
25
|
+
type: "toggle",
|
|
26
|
+
scope: "safe",
|
|
27
|
+
hotReload: false
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
path: "security.maxConcurrentSessions",
|
|
31
|
+
displayName: "Max Concurrent Sessions",
|
|
32
|
+
group: "security",
|
|
33
|
+
type: "number",
|
|
34
|
+
scope: "safe",
|
|
35
|
+
hotReload: true
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: "security.sessionTimeoutMinutes",
|
|
39
|
+
displayName: "Session Timeout (min)",
|
|
40
|
+
group: "security",
|
|
41
|
+
type: "number",
|
|
42
|
+
scope: "safe",
|
|
43
|
+
hotReload: true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
path: "workspace.baseDir",
|
|
47
|
+
displayName: "Workspace Directory",
|
|
48
|
+
group: "workspace",
|
|
49
|
+
type: "string",
|
|
50
|
+
scope: "safe",
|
|
51
|
+
hotReload: true
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
path: "sessionStore.ttlDays",
|
|
55
|
+
displayName: "Session Store TTL (days)",
|
|
56
|
+
group: "storage",
|
|
57
|
+
type: "number",
|
|
58
|
+
scope: "safe",
|
|
59
|
+
hotReload: true
|
|
60
|
+
}
|
|
61
|
+
];
|
|
62
|
+
function getFieldDef(path) {
|
|
63
|
+
return CONFIG_REGISTRY.find((f) => f.path === path);
|
|
64
|
+
}
|
|
65
|
+
function getSafeFields() {
|
|
66
|
+
return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
|
|
67
|
+
}
|
|
68
|
+
function isHotReloadable(path) {
|
|
69
|
+
const def = getFieldDef(path);
|
|
70
|
+
return def?.hotReload ?? false;
|
|
71
|
+
}
|
|
72
|
+
function resolveOptions(def, config) {
|
|
73
|
+
if (!def.options) return void 0;
|
|
74
|
+
return typeof def.options === "function" ? def.options(config) : def.options;
|
|
75
|
+
}
|
|
76
|
+
function getConfigValue(config, path) {
|
|
77
|
+
const parts = path.split(".");
|
|
78
|
+
let current = config;
|
|
79
|
+
for (const part of parts) {
|
|
80
|
+
if (current && typeof current === "object" && part in current) {
|
|
81
|
+
current = current[part];
|
|
82
|
+
} else {
|
|
83
|
+
return void 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return current;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export {
|
|
90
|
+
CONFIG_REGISTRY,
|
|
91
|
+
getFieldDef,
|
|
92
|
+
getSafeFields,
|
|
93
|
+
isHotReloadable,
|
|
94
|
+
resolveOptions,
|
|
95
|
+
getConfigValue
|
|
96
|
+
};
|
|
97
|
+
//# sourceMappingURL=chunk-C33LTDZV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/config-registry.ts"],"sourcesContent":["import type { Config } from './config.js'\n\nexport interface ConfigFieldDef {\n path: string\n displayName: string\n group: string\n type: 'toggle' | 'select' | 'number' | 'string'\n options?: string[] | ((config: Config) => string[])\n scope: 'safe' | 'sensitive'\n hotReload: boolean\n}\n\nexport const CONFIG_REGISTRY: ConfigFieldDef[] = [\n {\n path: 'defaultAgent',\n displayName: 'Default Agent',\n group: 'agent',\n type: 'select',\n options: (config) => Object.keys(config.agents),\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'logging.level',\n displayName: 'Log Level',\n group: 'logging',\n type: 'select',\n options: ['silent', 'debug', 'info', 'warn', 'error', 'fatal'],\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'tunnel.enabled',\n displayName: 'Tunnel',\n group: 'tunnel',\n type: 'toggle',\n scope: 'safe',\n hotReload: false,\n },\n {\n path: 'security.maxConcurrentSessions',\n displayName: 'Max Concurrent Sessions',\n group: 'security',\n type: 'number',\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'security.sessionTimeoutMinutes',\n displayName: 'Session Timeout (min)',\n group: 'security',\n type: 'number',\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'workspace.baseDir',\n displayName: 'Workspace Directory',\n group: 'workspace',\n type: 'string',\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'sessionStore.ttlDays',\n displayName: 'Session Store TTL (days)',\n group: 'storage',\n type: 'number',\n scope: 'safe',\n hotReload: true,\n },\n]\n\nexport function getFieldDef(path: string): ConfigFieldDef | undefined {\n return CONFIG_REGISTRY.find((f) => f.path === path)\n}\n\nexport function getSafeFields(): ConfigFieldDef[] {\n return CONFIG_REGISTRY.filter((f) => f.scope === 'safe')\n}\n\nexport function isHotReloadable(path: string): boolean {\n const def = getFieldDef(path)\n return def?.hotReload ?? false\n}\n\nexport function resolveOptions(def: ConfigFieldDef, config: Config): string[] | undefined {\n if (!def.options) return undefined\n return typeof def.options === 'function' ? def.options(config) : def.options\n}\n\nexport function getConfigValue(config: Config, path: string): unknown {\n const parts = path.split('.')\n let current: unknown = config\n for (const part of parts) {\n if (current && typeof current === 'object' && part in current) {\n current = (current as Record<string, unknown>)[part]\n } else {\n return undefined\n }\n }\n return current\n}\n"],"mappings":";AAYO,IAAM,kBAAoC;AAAA,EAC/C;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CAAC,WAAW,OAAO,KAAK,OAAO,MAAM;AAAA,IAC9C,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CAAC,UAAU,SAAS,QAAQ,QAAQ,SAAS,OAAO;AAAA,IAC7D,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,OAAO;AAAA,IACP,WAAW;AAAA,EACb;AACF;AAEO,SAAS,YAAY,MAA0C;AACpE,SAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,SAAS,IAAI;AACpD;AAEO,SAAS,gBAAkC;AAChD,SAAO,gBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM;AACzD;AAEO,SAAS,gBAAgB,MAAuB;AACrD,QAAM,MAAM,YAAY,IAAI;AAC5B,SAAO,KAAK,aAAa;AAC3B;AAEO,SAAS,eAAe,KAAqB,QAAsC;AACxF,MAAI,CAAC,IAAI,QAAS,QAAO;AACzB,SAAO,OAAO,IAAI,YAAY,aAAa,IAAI,QAAQ,MAAM,IAAI,IAAI;AACvE;AAEO,SAAS,eAAe,QAAgB,MAAuB;AACpE,QAAM,QAAQ,KAAK,MAAM,GAAG;AAC5B,MAAI,UAAmB;AACvB,aAAW,QAAQ,OAAO;AACxB,QAAI,WAAW,OAAO,YAAY,YAAY,QAAQ,SAAS;AAC7D,gBAAW,QAAoC,IAAI;AAAA,IACrD,OAAO;AACL,aAAO;AAAA,IACT;AAAA,EACF;AACA,SAAO;AACT;","names":[]}
|
|
@@ -94,6 +94,9 @@ function initLogger(config) {
|
|
|
94
94
|
Object.assign(log, wrapVariadic(rootLogger));
|
|
95
95
|
return rootLogger;
|
|
96
96
|
}
|
|
97
|
+
function setLogLevel(level) {
|
|
98
|
+
rootLogger.level = level;
|
|
99
|
+
}
|
|
97
100
|
function createChildLogger(context) {
|
|
98
101
|
return new Proxy({}, {
|
|
99
102
|
get(_target, prop, receiver) {
|
|
@@ -189,9 +192,10 @@ async function cleanupOldSessionLogs(retentionDays) {
|
|
|
189
192
|
export {
|
|
190
193
|
log,
|
|
191
194
|
initLogger,
|
|
195
|
+
setLogLevel,
|
|
192
196
|
createChildLogger,
|
|
193
197
|
createSessionLogger,
|
|
194
198
|
shutdownLogger,
|
|
195
199
|
cleanupOldSessionLogs
|
|
196
200
|
};
|
|
197
|
-
//# sourceMappingURL=chunk-
|
|
201
|
+
//# sourceMappingURL=chunk-ESOPMQAY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/log.ts"],"sourcesContent":["import pino from 'pino'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport os from 'node:os'\nimport type { LoggingConfig } from './config.js'\n\nexport type Logger = pino.Logger\n\n// --- Default console-only logger (pre-init) ---\nlet rootLogger: pino.Logger = pino({\n level: 'debug',\n transport: { target: 'pino-pretty', options: { colorize: true, translateTime: 'SYS:standard' } },\n})\nlet initialized = false\nlet logDir: string | undefined\nlet currentTransport: ReturnType<typeof pino.transport> | undefined\n\nfunction expandHome(p: string): string {\n return p.startsWith('~') ? path.join(os.homedir(), p.slice(1)) : p\n}\n\n// --- Variadic wrapper for backward compatibility ---\nfunction wrapVariadic(logger: pino.Logger) {\n return {\n info: (...args: unknown[]) => {\n if (args.length === 0) return\n if (typeof args[0] === 'object' && args[0] !== null && !(args[0] instanceof Error)) {\n logger.info(args[0] as object, args.slice(1).join(' '))\n } else {\n logger.info(args.map(String).join(' '))\n }\n },\n warn: (...args: unknown[]) => {\n if (args.length === 0) return\n if (typeof args[0] === 'object' && args[0] !== null && !(args[0] instanceof Error)) {\n logger.warn(args[0] as object, args.slice(1).join(' '))\n } else {\n logger.warn(args.map(String).join(' '))\n }\n },\n error: (...args: unknown[]) => {\n if (args.length === 0) return\n if (typeof args[0] === 'object' && args[0] !== null && !(args[0] instanceof Error)) {\n logger.error(args[0] as object, args.slice(1).join(' '))\n } else {\n logger.error(args.map(String).join(' '))\n }\n },\n debug: (...args: unknown[]) => {\n if (args.length === 0) return\n if (typeof args[0] === 'object' && args[0] !== null && !(args[0] instanceof Error)) {\n logger.debug(args[0] as object, args.slice(1).join(' '))\n } else {\n logger.debug(args.map(String).join(' '))\n }\n },\n fatal: (...args: unknown[]) => {\n if (args.length === 0) return\n if (typeof args[0] === 'object' && args[0] !== null && !(args[0] instanceof Error)) {\n logger.fatal(args[0] as object, args.slice(1).join(' '))\n } else {\n logger.fatal(args.map(String).join(' '))\n }\n },\n child: (bindings: pino.Bindings) => logger.child(bindings),\n }\n}\n\nexport const log = wrapVariadic(rootLogger)\n\n// --- Public API ---\n\nexport function initLogger(config: LoggingConfig): Logger {\n if (initialized) return rootLogger\n\n const resolvedLogDir = expandHome(config.logDir)\n logDir = resolvedLogDir\n\n try {\n fs.mkdirSync(resolvedLogDir, { recursive: true })\n fs.mkdirSync(path.join(resolvedLogDir, 'sessions'), { recursive: true })\n } catch (err) {\n console.error(`[WARN] Failed to create log directory ${resolvedLogDir}, falling back to console-only:`, err)\n return rootLogger\n }\n\n const transports = pino.transport({\n targets: [\n {\n target: 'pino-pretty',\n options: { colorize: true, translateTime: 'SYS:standard' },\n level: config.level,\n },\n {\n target: 'pino-roll',\n options: {\n file: path.join(resolvedLogDir, 'openacp.log'),\n size: config.maxFileSize,\n limit: { count: config.maxFiles },\n },\n level: config.level,\n },\n ],\n })\n\n currentTransport = transports\n rootLogger = pino({ level: config.level }, transports)\n initialized = true\n\n // Update the default log wrapper to use the new root logger\n Object.assign(log, wrapVariadic(rootLogger))\n\n return rootLogger\n}\n\n/** Change log level at runtime. Pino transport targets respect parent level changes automatically. */\nexport function setLogLevel(level: string): void {\n rootLogger.level = level\n}\n\nexport function createChildLogger(context: { module: string; [key: string]: unknown }): Logger {\n // Return a proxy that always delegates to the current rootLogger.\n // This ensures child loggers created at module-level (before initLogger)\n // pick up the initialized logger with pino-pretty transport.\n return new Proxy({} as Logger, {\n get(_target, prop, receiver) {\n const child = rootLogger.child(context)\n const value = Reflect.get(child, prop, receiver)\n return typeof value === 'function' ? value.bind(child) : value\n },\n })\n}\n\nexport function createSessionLogger(sessionId: string, parentLogger: Logger): Logger {\n const sessionLogDir = logDir ? path.join(logDir, 'sessions') : undefined\n if (!sessionLogDir) {\n return parentLogger.child({ sessionId })\n }\n\n try {\n const sessionLogPath = path.join(sessionLogDir, `${sessionId}.log`)\n const dest = pino.destination(sessionLogPath)\n const sessionFileLogger = pino({ level: parentLogger.level }, dest).child({ sessionId })\n\n // Create a logger that writes to both parent (combined) and session file\n const combinedChild = parentLogger.child({ sessionId })\n const originalInfo = combinedChild.info.bind(combinedChild)\n const originalWarn = combinedChild.warn.bind(combinedChild)\n const originalError = combinedChild.error.bind(combinedChild)\n const originalDebug = combinedChild.debug.bind(combinedChild)\n const originalFatal = combinedChild.fatal.bind(combinedChild)\n\n // Proxy log methods to write to both destinations\n combinedChild.info = ((objOrMsg: any, ...rest: any[]) => {\n sessionFileLogger.info(objOrMsg, ...rest)\n return originalInfo(objOrMsg, ...rest)\n }) as any\n combinedChild.warn = ((objOrMsg: any, ...rest: any[]) => {\n sessionFileLogger.warn(objOrMsg, ...rest)\n return originalWarn(objOrMsg, ...rest)\n }) as any\n combinedChild.error = ((objOrMsg: any, ...rest: any[]) => {\n sessionFileLogger.error(objOrMsg, ...rest)\n return originalError(objOrMsg, ...rest)\n }) as any\n combinedChild.debug = ((objOrMsg: any, ...rest: any[]) => {\n sessionFileLogger.debug(objOrMsg, ...rest)\n return originalDebug(objOrMsg, ...rest)\n }) as any\n combinedChild.fatal = ((objOrMsg: any, ...rest: any[]) => {\n sessionFileLogger.fatal(objOrMsg, ...rest)\n return originalFatal(objOrMsg, ...rest)\n }) as any\n\n // Store dest for cleanup\n ;(combinedChild as any).__sessionDest = dest\n\n return combinedChild\n } catch (err) {\n // Graceful degradation: session file failed, just use combined log\n parentLogger.warn({ sessionId, err }, 'Failed to create session log file, using combined log only')\n return parentLogger.child({ sessionId })\n }\n}\n\nexport async function shutdownLogger(): Promise<void> {\n if (!initialized) return\n\n const transport = currentTransport\n\n // Reset state immediately so re-init is possible\n rootLogger = pino({ level: 'debug' })\n Object.assign(log, wrapVariadic(rootLogger))\n currentTransport = undefined\n logDir = undefined\n initialized = false\n\n if (transport) {\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(resolve, 3000)\n transport.on('close', () => {\n clearTimeout(timeout)\n resolve()\n })\n transport.end()\n })\n }\n}\n\nexport async function cleanupOldSessionLogs(retentionDays: number): Promise<void> {\n if (!logDir) return\n\n const sessionsDir = path.join(logDir, 'sessions')\n try {\n const files = await fs.promises.readdir(sessionsDir)\n const cutoff = Date.now() - retentionDays * 24 * 60 * 60 * 1000\n\n for (const file of files) {\n try {\n const filePath = path.join(sessionsDir, file)\n const stat = await fs.promises.stat(filePath)\n if (stat.mtimeMs < cutoff) {\n await fs.promises.unlink(filePath)\n rootLogger.debug({ file }, 'Deleted old session log')\n }\n } catch (err) {\n rootLogger.warn({ file, err }, 'Failed to delete old session log')\n }\n }\n } catch {\n // Sessions directory doesn't exist — no-op\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import {
|
|
2
|
-
validateBotToken,
|
|
3
|
-
validateChatId
|
|
4
|
-
} from "./chunk-UAUTLC4E.js";
|
|
5
|
-
import {
|
|
6
|
-
expandHome
|
|
7
|
-
} from "./chunk-ZRFBLD3W.js";
|
|
8
1
|
import {
|
|
9
2
|
installAutoStart,
|
|
10
3
|
isAutoStartInstalled,
|
|
11
4
|
isAutoStartSupported,
|
|
12
5
|
uninstallAutoStart
|
|
13
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-X6LLG7XN.js";
|
|
7
|
+
import {
|
|
8
|
+
validateBotToken,
|
|
9
|
+
validateChatId
|
|
10
|
+
} from "./chunk-W7QQA6CW.js";
|
|
11
|
+
import {
|
|
12
|
+
expandHome
|
|
13
|
+
} from "./chunk-WYZFGHHI.js";
|
|
14
14
|
|
|
15
15
|
// src/core/config-editor.ts
|
|
16
16
|
import { select, input } from "@inquirer/prompts";
|
|
@@ -469,7 +469,7 @@ async function editProviderOptions(provider, currentOptions, tun) {
|
|
|
469
469
|
console.log(dim(`No configurable options for provider "${provider}"`));
|
|
470
470
|
}
|
|
471
471
|
}
|
|
472
|
-
async function runConfigEditor(configManager) {
|
|
472
|
+
async function runConfigEditor(configManager, mode = "file", apiPort) {
|
|
473
473
|
await configManager.load();
|
|
474
474
|
const config = configManager.get();
|
|
475
475
|
const updates = {};
|
|
@@ -479,7 +479,7 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
479
479
|
console.log("");
|
|
480
480
|
try {
|
|
481
481
|
while (true) {
|
|
482
|
-
const hasChanges = Object.keys(updates).length > 0;
|
|
482
|
+
const hasChanges = mode === "file" ? Object.keys(updates).length > 0 : false;
|
|
483
483
|
const choice = await select({
|
|
484
484
|
message: `What would you like to edit?${hasChanges ? ` ${c.yellow}(unsaved changes)${c.reset}` : ""}`,
|
|
485
485
|
choices: [
|
|
@@ -495,22 +495,30 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
495
495
|
]
|
|
496
496
|
});
|
|
497
497
|
if (choice === "exit") {
|
|
498
|
-
if (hasChanges) {
|
|
498
|
+
if (mode === "file" && hasChanges) {
|
|
499
499
|
await configManager.save(updates);
|
|
500
500
|
console.log(ok(`Config saved to ${configManager.getConfigPath()}`));
|
|
501
|
-
} else {
|
|
501
|
+
} else if (mode === "file") {
|
|
502
502
|
console.log(dim("No changes made."));
|
|
503
503
|
}
|
|
504
504
|
break;
|
|
505
505
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
else if (choice === "
|
|
509
|
-
else if (choice === "
|
|
510
|
-
else if (choice === "
|
|
511
|
-
else if (choice === "
|
|
512
|
-
else if (choice === "
|
|
513
|
-
else if (choice === "
|
|
506
|
+
const sectionUpdates = {};
|
|
507
|
+
if (choice === "telegram") await editTelegram(config, sectionUpdates);
|
|
508
|
+
else if (choice === "agent") await editAgent(config, sectionUpdates);
|
|
509
|
+
else if (choice === "workspace") await editWorkspace(config, sectionUpdates);
|
|
510
|
+
else if (choice === "security") await editSecurity(config, sectionUpdates);
|
|
511
|
+
else if (choice === "logging") await editLogging(config, sectionUpdates);
|
|
512
|
+
else if (choice === "runMode") await editRunMode(config, sectionUpdates);
|
|
513
|
+
else if (choice === "api") await editApi(config, sectionUpdates);
|
|
514
|
+
else if (choice === "tunnel") await editTunnel(config, sectionUpdates);
|
|
515
|
+
if (mode === "api" && Object.keys(sectionUpdates).length > 0) {
|
|
516
|
+
await sendConfigViaApi(apiPort, sectionUpdates);
|
|
517
|
+
await configManager.load();
|
|
518
|
+
Object.assign(config, configManager.get());
|
|
519
|
+
} else {
|
|
520
|
+
Object.assign(updates, sectionUpdates);
|
|
521
|
+
}
|
|
514
522
|
}
|
|
515
523
|
} catch (err) {
|
|
516
524
|
if (err.name === "ExitPromptError") {
|
|
@@ -520,8 +528,37 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
520
528
|
throw err;
|
|
521
529
|
}
|
|
522
530
|
}
|
|
531
|
+
async function sendConfigViaApi(port, updates) {
|
|
532
|
+
const { apiCall: call } = await import("./api-client-UN7BXQOQ.js");
|
|
533
|
+
const paths = flattenToPaths(updates);
|
|
534
|
+
for (const { path, value } of paths) {
|
|
535
|
+
const res = await call(port, "/api/config", {
|
|
536
|
+
method: "PATCH",
|
|
537
|
+
headers: { "Content-Type": "application/json" },
|
|
538
|
+
body: JSON.stringify({ path, value })
|
|
539
|
+
});
|
|
540
|
+
const data = await res.json();
|
|
541
|
+
if (!res.ok) {
|
|
542
|
+
console.log(warn(`Failed to update ${path}: ${data.error}`));
|
|
543
|
+
} else if (data.needsRestart) {
|
|
544
|
+
console.log(warn(`${path} updated \u2014 restart required`));
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
function flattenToPaths(obj, prefix = "") {
|
|
549
|
+
const result = [];
|
|
550
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
551
|
+
const fullPath = prefix ? `${prefix}.${key}` : key;
|
|
552
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
553
|
+
result.push(...flattenToPaths(val, fullPath));
|
|
554
|
+
} else {
|
|
555
|
+
result.push({ path: fullPath, value: val });
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return result;
|
|
559
|
+
}
|
|
523
560
|
|
|
524
561
|
export {
|
|
525
562
|
runConfigEditor
|
|
526
563
|
};
|
|
527
|
-
//# sourceMappingURL=chunk-
|
|
564
|
+
//# sourceMappingURL=chunk-FKOARMAE.js.map
|