@openacp/cli 0.5.3 → 0.6.1
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 +51 -16
- package/dist/action-detect-6M5GCGAU.js +15 -0
- package/dist/admin-IKPS5PFC.js +16 -0
- package/dist/agents-55NX3DHM.js +14 -0
- package/dist/{api-client-UN7BXQOQ.js → api-client-BH2JFHQW.js} +4 -2
- package/dist/{autostart-K73RQZVV.js → autostart-A7JRU4WJ.js} +6 -2
- package/dist/{chunk-ECBD5I5R.js → chunk-2KJC3ILH.js} +123 -16
- package/dist/chunk-2KJC3ILH.js.map +1 -0
- package/dist/{chunk-2Z2XPUD5.js → chunk-4LFDEW22.js} +53 -5
- package/dist/chunk-4LFDEW22.js.map +1 -0
- package/dist/{chunk-Z46LGZ7R.js → chunk-4TR5Y3MP.js} +18 -1
- package/dist/chunk-4TR5Y3MP.js.map +1 -0
- package/dist/chunk-7G5QKLLF.js +105 -0
- package/dist/chunk-7G5QKLLF.js.map +1 -0
- package/dist/{chunk-IURZ4QHG.js → chunk-7QJS2XBD.js} +2 -1
- package/dist/chunk-7QJS2XBD.js.map +1 -0
- package/dist/chunk-AKIU4JBF.js +145 -0
- package/dist/chunk-AKIU4JBF.js.map +1 -0
- package/dist/{chunk-KSIQZC3J.js → chunk-EVFJW45N.js} +1 -1
- package/dist/chunk-EVFJW45N.js.map +1 -0
- package/dist/chunk-GINCOFNW.js +134 -0
- package/dist/chunk-GINCOFNW.js.map +1 -0
- package/dist/chunk-H7ZMPBZC.js +203 -0
- package/dist/chunk-H7ZMPBZC.js.map +1 -0
- package/dist/chunk-I7WC6E5S.js +71 -0
- package/dist/chunk-I7WC6E5S.js.map +1 -0
- package/dist/{chunk-6DAZSKE5.js → chunk-IMILOCR5.js} +2 -2
- package/dist/chunk-LGQYTK55.js +442 -0
- package/dist/chunk-LGQYTK55.js.map +1 -0
- package/dist/{chunk-X6LLG7XN.js → chunk-PMGNLNSH.js} +15 -6
- package/dist/chunk-PMGNLNSH.js.map +1 -0
- package/dist/{chunk-LCJIPE5S.js → chunk-R3UJUOXI.js} +889 -591
- package/dist/chunk-R3UJUOXI.js.map +1 -0
- package/dist/chunk-SM3G6UAX.js +122 -0
- package/dist/chunk-SM3G6UAX.js.map +1 -0
- package/dist/chunk-T22OLSET.js +265 -0
- package/dist/chunk-T22OLSET.js.map +1 -0
- package/dist/chunk-THBR6OXH.js +62 -0
- package/dist/chunk-THBR6OXH.js.map +1 -0
- package/dist/{chunk-5KYLXEG3.js → chunk-TOZQ3JFN.js} +52 -9
- package/dist/chunk-TOZQ3JFN.js.map +1 -0
- package/dist/{chunk-IQIPQTQT.js → chunk-UB7XUO7C.js} +171 -26
- package/dist/chunk-UB7XUO7C.js.map +1 -0
- package/dist/{chunk-OORPX73T.js → chunk-W3EYKZNQ.js} +17 -2
- package/dist/chunk-W3EYKZNQ.js.map +1 -0
- package/dist/{chunk-K53OZH5Y.js → chunk-ZCHNAM3B.js} +76 -2
- package/dist/chunk-ZCHNAM3B.js.map +1 -0
- package/dist/cli.js +30 -29
- package/dist/cli.js.map +1 -1
- package/dist/{config-OH26EIWN.js → config-AK2W3E67.js} +2 -2
- package/dist/config-editor-VIA7A72X.js +12 -0
- package/dist/{config-registry-SNKA2EH2.js → config-registry-QQOJ2GQP.js} +2 -2
- package/dist/{daemon-VKCONJUY.js → daemon-G27YZUWB.js} +3 -3
- package/dist/discord-2DKRH45T.js +2044 -0
- package/dist/discord-2DKRH45T.js.map +1 -0
- package/dist/doctor-AN6AZ3PF.js +9 -0
- package/dist/doctor-CHCYUTV5.js +14 -0
- package/dist/doctor-CHCYUTV5.js.map +1 -0
- package/dist/index.d.ts +331 -6
- package/dist/index.js +21 -11
- package/dist/{main-NEYPQHB4.js → main-56SPFYW4.js} +32 -24
- package/dist/main-56SPFYW4.js.map +1 -0
- package/dist/{menu-J5YVH665.js → menu-XR2GET2B.js} +2 -2
- package/dist/menu-XR2GET2B.js.map +1 -0
- package/dist/new-session-DRRP2J7E.js +16 -0
- package/dist/new-session-DRRP2J7E.js.map +1 -0
- package/dist/session-FVFLBREJ.js +19 -0
- package/dist/session-FVFLBREJ.js.map +1 -0
- package/dist/settings-LPOLJ6SA.js +12 -0
- package/dist/settings-LPOLJ6SA.js.map +1 -0
- package/dist/{setup-ZCWGOEAH.js → setup-IPWJCIJM.js} +9 -5
- package/dist/setup-IPWJCIJM.js.map +1 -0
- package/dist/{version-VC5CPXBX.js → version-ALWGGVKM.js} +2 -2
- package/dist/version-ALWGGVKM.js.map +1 -0
- package/package.json +2 -1
- package/dist/chunk-2Z2XPUD5.js.map +0 -1
- package/dist/chunk-5KYLXEG3.js.map +0 -1
- package/dist/chunk-ECBD5I5R.js.map +0 -1
- package/dist/chunk-IQIPQTQT.js.map +0 -1
- package/dist/chunk-IURZ4QHG.js.map +0 -1
- package/dist/chunk-K53OZH5Y.js.map +0 -1
- package/dist/chunk-KSIQZC3J.js.map +0 -1
- package/dist/chunk-LCJIPE5S.js.map +0 -1
- package/dist/chunk-OORPX73T.js.map +0 -1
- package/dist/chunk-X6LLG7XN.js.map +0 -1
- package/dist/chunk-Z46LGZ7R.js.map +0 -1
- package/dist/config-editor-5TICUK3K.js +0 -12
- package/dist/doctor-X6UCE7GQ.js +0 -9
- package/dist/main-NEYPQHB4.js.map +0 -1
- /package/dist/{api-client-UN7BXQOQ.js.map → action-detect-6M5GCGAU.js.map} +0 -0
- /package/dist/{autostart-K73RQZVV.js.map → admin-IKPS5PFC.js.map} +0 -0
- /package/dist/{config-OH26EIWN.js.map → agents-55NX3DHM.js.map} +0 -0
- /package/dist/{config-editor-5TICUK3K.js.map → api-client-BH2JFHQW.js.map} +0 -0
- /package/dist/{config-registry-SNKA2EH2.js.map → autostart-A7JRU4WJ.js.map} +0 -0
- /package/dist/{chunk-6DAZSKE5.js.map → chunk-IMILOCR5.js.map} +0 -0
- /package/dist/{daemon-VKCONJUY.js.map → config-AK2W3E67.js.map} +0 -0
- /package/dist/{doctor-X6UCE7GQ.js.map → config-editor-VIA7A72X.js.map} +0 -0
- /package/dist/{menu-J5YVH665.js.map → config-registry-QQOJ2GQP.js.map} +0 -0
- /package/dist/{setup-ZCWGOEAH.js.map → daemon-G27YZUWB.js.map} +0 -0
- /package/dist/{version-VC5CPXBX.js.map → doctor-AN6AZ3PF.js.map} +0 -0
|
@@ -134,6 +134,27 @@ var TunnelSchema = z.object({
|
|
|
134
134
|
storeTtlMinutes: z.number().default(60),
|
|
135
135
|
auth: TunnelAuthSchema
|
|
136
136
|
}).default({});
|
|
137
|
+
var UsageSchema = z.object({
|
|
138
|
+
enabled: z.boolean().default(true),
|
|
139
|
+
monthlyBudget: z.number().optional(),
|
|
140
|
+
warningThreshold: z.number().default(0.8),
|
|
141
|
+
currency: z.string().default("USD"),
|
|
142
|
+
retentionDays: z.number().default(90)
|
|
143
|
+
}).default({});
|
|
144
|
+
var SpeechProviderSchema = z.object({
|
|
145
|
+
apiKey: z.string().min(1),
|
|
146
|
+
model: z.string().optional()
|
|
147
|
+
}).passthrough();
|
|
148
|
+
var SpeechSchema = z.object({
|
|
149
|
+
stt: z.object({
|
|
150
|
+
provider: z.string().nullable().default(null),
|
|
151
|
+
providers: z.record(SpeechProviderSchema).default({})
|
|
152
|
+
}).default({}),
|
|
153
|
+
tts: z.object({
|
|
154
|
+
provider: z.string().nullable().default(null),
|
|
155
|
+
providers: z.record(SpeechProviderSchema).default({})
|
|
156
|
+
}).default({})
|
|
157
|
+
}).optional().default({});
|
|
137
158
|
var ConfigSchema = z.object({
|
|
138
159
|
channels: z.record(z.string(), BaseChannelSchema),
|
|
139
160
|
agents: z.record(z.string(), AgentSchema).optional().default({}),
|
|
@@ -157,10 +178,12 @@ var ConfigSchema = z.object({
|
|
|
157
178
|
ttlDays: z.number().default(30)
|
|
158
179
|
}).default({}),
|
|
159
180
|
tunnel: TunnelSchema,
|
|
181
|
+
usage: UsageSchema,
|
|
160
182
|
integrations: z.record(z.string(), z.object({
|
|
161
183
|
installed: z.boolean(),
|
|
162
184
|
installedAt: z.string().optional()
|
|
163
|
-
})).default({})
|
|
185
|
+
})).default({}),
|
|
186
|
+
speech: SpeechSchema
|
|
164
187
|
});
|
|
165
188
|
function expandHome(p) {
|
|
166
189
|
if (p.startsWith("~")) {
|
|
@@ -176,6 +199,14 @@ var DEFAULT_CONFIG = {
|
|
|
176
199
|
chatId: 0,
|
|
177
200
|
notificationTopicId: null,
|
|
178
201
|
assistantTopicId: null
|
|
202
|
+
},
|
|
203
|
+
discord: {
|
|
204
|
+
enabled: false,
|
|
205
|
+
botToken: "YOUR_DISCORD_BOT_TOKEN_HERE",
|
|
206
|
+
guildId: "",
|
|
207
|
+
forumChannelId: null,
|
|
208
|
+
notificationChannelId: null,
|
|
209
|
+
assistantThreadId: null
|
|
179
210
|
}
|
|
180
211
|
},
|
|
181
212
|
agents: {
|
|
@@ -197,7 +228,8 @@ var DEFAULT_CONFIG = {
|
|
|
197
228
|
options: {},
|
|
198
229
|
storeTtlMinutes: 60,
|
|
199
230
|
auth: { enabled: false }
|
|
200
|
-
}
|
|
231
|
+
},
|
|
232
|
+
usage: {}
|
|
201
233
|
};
|
|
202
234
|
var ConfigManager = class extends EventEmitter {
|
|
203
235
|
config;
|
|
@@ -216,7 +248,7 @@ var ConfigManager = class extends EventEmitter {
|
|
|
216
248
|
);
|
|
217
249
|
log2.info({ configPath: this.configPath }, "Config created");
|
|
218
250
|
log2.info(
|
|
219
|
-
"Please edit it with your Telegram bot token
|
|
251
|
+
"Please edit it with your channel credentials (Telegram bot token, Discord bot token, etc.), then restart."
|
|
220
252
|
);
|
|
221
253
|
process.exit(1);
|
|
222
254
|
}
|
|
@@ -252,7 +284,7 @@ var ConfigManager = class extends EventEmitter {
|
|
|
252
284
|
this.config = result.data;
|
|
253
285
|
}
|
|
254
286
|
if (changePath) {
|
|
255
|
-
const { getConfigValue } = await import("./config-registry-
|
|
287
|
+
const { getConfigValue } = await import("./config-registry-QQOJ2GQP.js");
|
|
256
288
|
const value = getConfigValue(this.config, changePath);
|
|
257
289
|
const oldValue = oldConfig ? getConfigValue(oldConfig, changePath) : void 0;
|
|
258
290
|
this.emit("config:changed", { path: changePath, value, oldValue });
|
|
@@ -289,6 +321,8 @@ var ConfigManager = class extends EventEmitter {
|
|
|
289
321
|
const overrides = [
|
|
290
322
|
["OPENACP_TELEGRAM_BOT_TOKEN", ["channels", "telegram", "botToken"]],
|
|
291
323
|
["OPENACP_TELEGRAM_CHAT_ID", ["channels", "telegram", "chatId"]],
|
|
324
|
+
["OPENACP_DISCORD_BOT_TOKEN", ["channels", "discord", "botToken"]],
|
|
325
|
+
["OPENACP_DISCORD_GUILD_ID", ["channels", "discord", "guildId"]],
|
|
292
326
|
["OPENACP_DEFAULT_AGENT", ["defaultAgent"]],
|
|
293
327
|
["OPENACP_RUN_MODE", ["runMode"]],
|
|
294
328
|
["OPENACP_API_PORT", ["api", "port"]]
|
|
@@ -331,6 +365,20 @@ var ConfigManager = class extends EventEmitter {
|
|
|
331
365
|
raw.tunnel = raw.tunnel || {};
|
|
332
366
|
raw.tunnel.provider = process.env.OPENACP_TUNNEL_PROVIDER;
|
|
333
367
|
}
|
|
368
|
+
if (process.env.OPENACP_SPEECH_STT_PROVIDER) {
|
|
369
|
+
raw.speech = raw.speech || {};
|
|
370
|
+
const speech = raw.speech;
|
|
371
|
+
speech.stt = speech.stt || {};
|
|
372
|
+
speech.stt.provider = process.env.OPENACP_SPEECH_STT_PROVIDER;
|
|
373
|
+
}
|
|
374
|
+
if (process.env.OPENACP_SPEECH_GROQ_API_KEY) {
|
|
375
|
+
raw.speech = raw.speech || {};
|
|
376
|
+
const speech = raw.speech;
|
|
377
|
+
speech.stt = speech.stt || {};
|
|
378
|
+
speech.stt.providers = speech.stt.providers || {};
|
|
379
|
+
speech.stt.providers.groq = speech.stt.providers.groq || {};
|
|
380
|
+
speech.stt.providers.groq.apiKey = process.env.OPENACP_SPEECH_GROQ_API_KEY;
|
|
381
|
+
}
|
|
334
382
|
}
|
|
335
383
|
deepMerge(target, source) {
|
|
336
384
|
for (const key of Object.keys(source)) {
|
|
@@ -351,4 +399,4 @@ export {
|
|
|
351
399
|
expandHome,
|
|
352
400
|
ConfigManager
|
|
353
401
|
};
|
|
354
|
-
//# sourceMappingURL=chunk-
|
|
402
|
+
//# sourceMappingURL=chunk-4LFDEW22.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/config.ts","../../src/core/config-migrations.ts"],"sourcesContent":["import { z } from \"zod\";\nimport * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { EventEmitter } from \"node:events\";\nimport { applyMigrations } from \"./config-migrations.js\";\nimport { createChildLogger } from \"./log.js\";\nconst log = createChildLogger({ module: \"config\" });\n\nconst BaseChannelSchema = z\n .object({\n enabled: z.boolean().default(false),\n adapter: z.string().optional(), // package name for plugin adapters\n })\n .passthrough();\n\nexport const PLUGINS_DIR = path.join(os.homedir(), \".openacp\", \"plugins\");\n\nconst AgentSchema = z.object({\n command: z.string(),\n args: z.array(z.string()).default([]),\n workingDirectory: z.string().optional(),\n env: z.record(z.string(), z.string()).default({}),\n});\n\nconst LoggingSchema = z\n .object({\n level: z\n .enum([\"silent\", \"debug\", \"info\", \"warn\", \"error\", \"fatal\"])\n .default(\"info\"),\n logDir: z.string().default(\"~/.openacp/logs\"),\n maxFileSize: z.union([z.string(), z.number()]).default(\"10m\"),\n maxFiles: z.number().default(7),\n sessionLogRetentionDays: z.number().default(30),\n })\n .default({});\n\nexport type LoggingConfig = z.infer<typeof LoggingSchema>;\n\nconst TunnelAuthSchema = z\n .object({\n enabled: z.boolean().default(false),\n token: z.string().optional(),\n })\n .default({});\n\nconst TunnelSchema = z\n .object({\n enabled: z.boolean().default(false),\n port: z.number().default(3100),\n provider: z.enum([\"cloudflare\", \"ngrok\", \"bore\", \"tailscale\"]).default(\"cloudflare\"),\n options: z.record(z.string(), z.unknown()).default({}),\n maxUserTunnels: z.number().default(5),\n storeTtlMinutes: z.number().default(60),\n auth: TunnelAuthSchema,\n })\n .default({});\n\nexport type TunnelConfig = z.infer<typeof TunnelSchema>;\n\nconst UsageSchema = z\n .object({\n enabled: z.boolean().default(true),\n monthlyBudget: z.number().optional(),\n warningThreshold: z.number().default(0.8),\n currency: z.string().default(\"USD\"),\n retentionDays: z.number().default(90),\n })\n .default({});\n\nexport type UsageConfig = z.infer<typeof UsageSchema>;\n\nconst SpeechProviderSchema = z.object({\n apiKey: z.string().min(1),\n model: z.string().optional(),\n}).passthrough();\n\nconst SpeechSchema = z.object({\n stt: z.object({\n provider: z.string().nullable().default(null),\n providers: z.record(SpeechProviderSchema).default({}),\n }).default({}),\n tts: z.object({\n provider: z.string().nullable().default(null),\n providers: z.record(SpeechProviderSchema).default({}),\n }).default({}),\n}).optional().default({});\n\nexport const ConfigSchema = z.object({\n channels: z.record(z.string(), BaseChannelSchema),\n agents: z.record(z.string(), AgentSchema).optional().default({}),\n defaultAgent: z.string(),\n workspace: z\n .object({\n baseDir: z.string().default(\"~/openacp-workspace\"),\n })\n .default({}),\n security: z\n .object({\n allowedUserIds: z.array(z.string()).default([]),\n maxConcurrentSessions: z.number().default(20),\n sessionTimeoutMinutes: z.number().default(60),\n })\n .default({}),\n logging: LoggingSchema,\n runMode: z.enum(['foreground', 'daemon']).default('foreground'),\n autoStart: z.boolean().default(false),\n api: z.object({\n port: z.number().default(21420),\n host: z.string().default('127.0.0.1'),\n }).default({}),\n sessionStore: z\n .object({\n ttlDays: z.number().default(30),\n })\n .default({}),\n tunnel: TunnelSchema,\n usage: UsageSchema,\n integrations: z.record(z.string(), z.object({\n installed: z.boolean(),\n installedAt: z.string().optional(),\n })).default({}),\n speech: SpeechSchema,\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n\nexport function expandHome(p: string): string {\n if (p.startsWith(\"~\")) {\n return path.join(os.homedir(), p.slice(1));\n }\n return p;\n}\n\nconst DEFAULT_CONFIG = {\n channels: {\n telegram: {\n enabled: false,\n botToken: \"YOUR_BOT_TOKEN_HERE\",\n chatId: 0,\n notificationTopicId: null,\n assistantTopicId: null,\n },\n discord: {\n enabled: false,\n botToken: \"YOUR_DISCORD_BOT_TOKEN_HERE\",\n guildId: \"\",\n forumChannelId: null,\n notificationChannelId: null,\n assistantThreadId: null,\n },\n },\n agents: {\n claude: { command: \"claude-agent-acp\", args: [], env: {} },\n codex: { command: \"codex\", args: [\"--acp\"], env: {} },\n },\n defaultAgent: \"claude\",\n workspace: { baseDir: \"~/openacp-workspace\" },\n security: {\n allowedUserIds: [],\n maxConcurrentSessions: 20,\n sessionTimeoutMinutes: 60,\n },\n sessionStore: { ttlDays: 30 },\n tunnel: {\n enabled: true,\n port: 3100,\n provider: \"cloudflare\",\n options: {},\n storeTtlMinutes: 60,\n auth: { enabled: false },\n },\n usage: {},\n};\n\nexport class ConfigManager extends EventEmitter {\n private config!: Config;\n private configPath: string;\n\n constructor() {\n super();\n this.configPath =\n process.env.OPENACP_CONFIG_PATH || expandHome(\"~/.openacp/config.json\");\n }\n\n async load(): Promise<void> {\n // 1. Ensure directory exists\n const dir = path.dirname(this.configPath);\n fs.mkdirSync(dir, { recursive: true });\n\n // 2. If config file doesn't exist, create default\n if (!fs.existsSync(this.configPath)) {\n fs.writeFileSync(\n this.configPath,\n JSON.stringify(DEFAULT_CONFIG, null, 2),\n );\n log.info({ configPath: this.configPath }, \"Config created\");\n log.info(\n \"Please edit it with your channel credentials (Telegram bot token, Discord bot token, etc.), then restart.\",\n );\n process.exit(1);\n }\n\n // 3. Read and parse\n const raw = JSON.parse(fs.readFileSync(this.configPath, \"utf-8\"));\n\n // 3.5. Auto-migrate config\n const { changed: configUpdated } = applyMigrations(raw);\n if (configUpdated) {\n fs.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));\n }\n\n // 4. Apply env var overrides\n this.applyEnvOverrides(raw);\n\n // 5. Validate with Zod\n const result = ConfigSchema.safeParse(raw);\n if (!result.success) {\n log.error(\"Config validation failed\");\n for (const issue of result.error.issues) {\n log.error(\n { path: issue.path.join(\".\"), message: issue.message },\n \"Validation error\",\n );\n }\n process.exit(1);\n }\n this.config = result.data;\n }\n\n get(): Config {\n return this.config;\n }\n\n async save(updates: Record<string, unknown>, changePath?: string): Promise<void> {\n const oldConfig = this.config ? structuredClone(this.config) : undefined;\n // Read current file, merge updates, write back\n const raw = JSON.parse(fs.readFileSync(this.configPath, \"utf-8\"));\n this.deepMerge(raw, updates);\n fs.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));\n // Re-validate and update in-memory config\n const result = ConfigSchema.safeParse(raw);\n if (result.success) {\n this.config = result.data;\n }\n // Emit change event if path provided\n if (changePath) {\n const { getConfigValue } = await import('./config-registry.js')\n const value = getConfigValue(this.config, changePath)\n const oldValue = oldConfig ? getConfigValue(oldConfig, changePath) : undefined\n this.emit('config:changed', { path: changePath, value, oldValue })\n }\n }\n\n resolveWorkspace(input?: string): string {\n if (!input) {\n const resolved = expandHome(this.config.workspace.baseDir);\n fs.mkdirSync(resolved, { recursive: true });\n return resolved;\n }\n if (input.startsWith(\"/\") || input.startsWith(\"~\")) {\n const resolved = expandHome(input);\n fs.mkdirSync(resolved, { recursive: true });\n return resolved;\n }\n // Named workspace → lowercase, under baseDir\n const name = input.toLowerCase();\n const resolved = path.join(expandHome(this.config.workspace.baseDir), name);\n fs.mkdirSync(resolved, { recursive: true });\n return resolved;\n }\n\n async exists(): Promise<boolean> {\n return fs.existsSync(this.configPath);\n }\n\n getConfigPath(): string {\n return this.configPath;\n }\n\n async writeNew(config: Config): Promise<void> {\n const dir = path.dirname(this.configPath);\n fs.mkdirSync(dir, { recursive: true });\n fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2));\n }\n\n private applyEnvOverrides(raw: Record<string, unknown>): void {\n const overrides: [string, string[]][] = [\n [\"OPENACP_TELEGRAM_BOT_TOKEN\", [\"channels\", \"telegram\", \"botToken\"]],\n [\"OPENACP_TELEGRAM_CHAT_ID\", [\"channels\", \"telegram\", \"chatId\"]],\n [\"OPENACP_DISCORD_BOT_TOKEN\", [\"channels\", \"discord\", \"botToken\"]],\n [\"OPENACP_DISCORD_GUILD_ID\", [\"channels\", \"discord\", \"guildId\"]],\n [\"OPENACP_DEFAULT_AGENT\", [\"defaultAgent\"]],\n [\"OPENACP_RUN_MODE\", [\"runMode\"]],\n [\"OPENACP_API_PORT\", [\"api\", \"port\"]],\n ];\n for (const [envVar, configPath] of overrides) {\n const value = process.env[envVar];\n if (value !== undefined) {\n let target = raw as Record<string, any>;\n for (let i = 0; i < configPath.length - 1; i++) {\n if (!target[configPath[i]]) target[configPath[i]] = {};\n target = target[configPath[i]];\n }\n const key = configPath[configPath.length - 1];\n // Convert numeric fields to number\n target[key] = (key === \"chatId\" || key === \"port\") ? Number(value) : value;\n }\n }\n\n // Logging env var overrides\n if (process.env.OPENACP_LOG_LEVEL) {\n raw.logging = raw.logging || {};\n (raw.logging as Record<string, unknown>).level =\n process.env.OPENACP_LOG_LEVEL;\n }\n if (process.env.OPENACP_LOG_DIR) {\n raw.logging = raw.logging || {};\n (raw.logging as Record<string, unknown>).logDir =\n process.env.OPENACP_LOG_DIR;\n }\n if (process.env.OPENACP_DEBUG && !process.env.OPENACP_LOG_LEVEL) {\n raw.logging = raw.logging || {};\n (raw.logging as Record<string, unknown>).level = \"debug\";\n }\n\n // Tunnel env var overrides\n if (process.env.OPENACP_TUNNEL_ENABLED) {\n raw.tunnel = raw.tunnel || {};\n (raw.tunnel as Record<string, unknown>).enabled =\n process.env.OPENACP_TUNNEL_ENABLED === \"true\";\n }\n if (process.env.OPENACP_TUNNEL_PORT) {\n raw.tunnel = raw.tunnel || {};\n (raw.tunnel as Record<string, unknown>).port = Number(\n process.env.OPENACP_TUNNEL_PORT,\n );\n }\n if (process.env.OPENACP_TUNNEL_PROVIDER) {\n raw.tunnel = raw.tunnel || {};\n (raw.tunnel as Record<string, unknown>).provider =\n process.env.OPENACP_TUNNEL_PROVIDER;\n }\n\n // Speech env var overrides\n if (process.env.OPENACP_SPEECH_STT_PROVIDER) {\n raw.speech = raw.speech || {};\n const speech = raw.speech as Record<string, any>;\n speech.stt = speech.stt || {};\n speech.stt.provider = process.env.OPENACP_SPEECH_STT_PROVIDER;\n }\n if (process.env.OPENACP_SPEECH_GROQ_API_KEY) {\n raw.speech = raw.speech || {};\n const speech = raw.speech as Record<string, any>;\n speech.stt = speech.stt || {};\n speech.stt.providers = speech.stt.providers || {};\n speech.stt.providers.groq = speech.stt.providers.groq || {};\n speech.stt.providers.groq.apiKey = process.env.OPENACP_SPEECH_GROQ_API_KEY;\n }\n }\n\n private deepMerge(\n target: Record<string, any>,\n source: Record<string, any>,\n ): void {\n for (const key of Object.keys(source)) {\n if (\n source[key] &&\n typeof source[key] === \"object\" &&\n !Array.isArray(source[key])\n ) {\n if (!target[key]) target[key] = {};\n this.deepMerge(target[key], source[key]);\n } else {\n target[key] = source[key];\n }\n }\n }\n}\n","import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport { createChildLogger } from \"./log.js\";\nconst log = createChildLogger({ module: \"config-migrations\" });\n\ntype RawConfig = Record<string, unknown>;\n\nexport interface Migration {\n name: string;\n apply: (raw: RawConfig) => boolean; // returns true if config was modified\n}\n\nexport const migrations: Migration[] = [\n {\n name: \"add-tunnel-section\",\n apply(raw) {\n if (raw.tunnel) return false;\n raw.tunnel = {\n enabled: true,\n port: 3100,\n provider: \"cloudflare\",\n options: {},\n storeTtlMinutes: 60,\n auth: { enabled: false },\n };\n log.info(\"Added tunnel section to config (enabled by default with cloudflare)\");\n return true;\n },\n },\n {\n name: \"fix-agent-commands\",\n apply(raw) {\n const COMMAND_MIGRATIONS: Record<string, string[]> = {\n \"claude-agent-acp\": [\"claude\", \"claude-code\"],\n };\n\n const agents = raw.agents;\n if (!agents || typeof agents !== \"object\") return false;\n\n let changed = false;\n for (const [agentName, agentDef] of Object.entries(agents as Record<string, any>)) {\n if (!agentDef?.command) continue;\n for (const [correctCmd, legacyCmds] of Object.entries(COMMAND_MIGRATIONS)) {\n if (legacyCmds.includes(agentDef.command)) {\n log.warn(\n { agent: agentName, oldCommand: agentDef.command, newCommand: correctCmd },\n `Auto-migrating agent command: \"${agentDef.command}\" → \"${correctCmd}\"`,\n );\n agentDef.command = correctCmd;\n changed = true;\n }\n }\n }\n return changed;\n },\n },\n {\n name: \"migrate-agents-to-store\",\n apply(raw) {\n const agentsJsonPath = path.join(os.homedir(), \".openacp\", \"agents.json\");\n if (fs.existsSync(agentsJsonPath)) return false;\n\n const agents = raw.agents as Record<string, any> | undefined;\n if (!agents || Object.keys(agents).length === 0) return false;\n\n const COMMAND_TO_REGISTRY: Record<string, string> = {\n \"claude-agent-acp\": \"claude-acp\",\n \"codex\": \"codex-acp\",\n };\n\n const installed: Record<string, any> = {};\n for (const [key, cfg] of Object.entries(agents)) {\n const registryId = COMMAND_TO_REGISTRY[cfg.command] ?? null;\n installed[key] = {\n registryId,\n name: key.charAt(0).toUpperCase() + key.slice(1),\n version: \"unknown\",\n distribution: \"custom\",\n command: cfg.command,\n args: cfg.args ?? [],\n env: cfg.env ?? {},\n workingDirectory: cfg.workingDirectory ?? undefined,\n installedAt: new Date().toISOString(),\n binaryPath: null,\n };\n }\n\n fs.mkdirSync(path.dirname(agentsJsonPath), { recursive: true });\n fs.writeFileSync(agentsJsonPath, JSON.stringify({ version: 1, installed }, null, 2));\n\n raw.agents = {};\n return true;\n },\n },\n];\n\n/**\n * Apply all migrations to raw config (mutates in place).\n * Returns whether any changes were made.\n */\nexport function applyMigrations(\n raw: RawConfig,\n migrationList: Migration[] = migrations,\n): { changed: boolean } {\n let changed = false;\n for (const migration of migrationList) {\n if (migration.apply(raw)) {\n changed = true;\n }\n }\n return { changed };\n}\n"],"mappings":"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","names":["fs","path","os","log","resolved"]}
|
|
@@ -70,6 +70,23 @@ var CONFIG_REGISTRY = [
|
|
|
70
70
|
type: "number",
|
|
71
71
|
scope: "safe",
|
|
72
72
|
hotReload: true
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
path: "speech.stt.provider",
|
|
76
|
+
displayName: "Speech to Text",
|
|
77
|
+
group: "speech",
|
|
78
|
+
type: "select",
|
|
79
|
+
options: ["groq"],
|
|
80
|
+
scope: "safe",
|
|
81
|
+
hotReload: true
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
path: "speech.stt.apiKey",
|
|
85
|
+
displayName: "STT API Key",
|
|
86
|
+
group: "speech",
|
|
87
|
+
type: "string",
|
|
88
|
+
scope: "sensitive",
|
|
89
|
+
hotReload: true
|
|
73
90
|
}
|
|
74
91
|
];
|
|
75
92
|
function getFieldDef(path2) {
|
|
@@ -107,4 +124,4 @@ export {
|
|
|
107
124
|
resolveOptions,
|
|
108
125
|
getConfigValue
|
|
109
126
|
};
|
|
110
|
-
//# sourceMappingURL=chunk-
|
|
127
|
+
//# sourceMappingURL=chunk-4TR5Y3MP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/config-registry.ts"],"sourcesContent":["import * as fs from \"node:fs\";\nimport * as path from \"node:path\";\nimport * as os from \"node:os\";\nimport 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) => {\n try {\n const agentsPath = path.join(os.homedir(), \".openacp\", \"agents.json\");\n if (fs.existsSync(agentsPath)) {\n const data = JSON.parse(fs.readFileSync(agentsPath, \"utf-8\"));\n return Object.keys(data.installed ?? {});\n }\n } catch { /* fallback */ }\n return Object.keys(config.agents ?? {});\n },\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 path: 'speech.stt.provider',\n displayName: 'Speech to Text',\n group: 'speech',\n type: 'select',\n options: ['groq'],\n scope: 'safe',\n hotReload: true,\n },\n {\n path: 'speech.stt.apiKey',\n displayName: 'STT API Key',\n group: 'speech',\n type: 'string',\n scope: 'sensitive',\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":";AAAA,YAAY,QAAQ;AACpB,YAAY,UAAU;AACtB,YAAY,QAAQ;AAab,IAAM,kBAAoC;AAAA,EAC/C;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CAAC,WAAW;AACnB,UAAI;AACF,cAAM,aAAkB,UAAQ,WAAQ,GAAG,YAAY,aAAa;AACpE,YAAO,cAAW,UAAU,GAAG;AAC7B,gBAAM,OAAO,KAAK,MAAS,gBAAa,YAAY,OAAO,CAAC;AAC5D,iBAAO,OAAO,KAAK,KAAK,aAAa,CAAC,CAAC;AAAA,QACzC;AAAA,MACF,QAAQ;AAAA,MAAiB;AACzB,aAAO,OAAO,KAAK,OAAO,UAAU,CAAC,CAAC;AAAA,IACxC;AAAA,IACA,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;AAAA,EACA;AAAA,IACE,MAAM;AAAA,IACN,aAAa;AAAA,IACb,OAAO;AAAA,IACP,MAAM;AAAA,IACN,SAAS,CAAC,MAAM;AAAA,IAChB,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,YAAYA,OAA0C;AACpE,SAAO,gBAAgB,KAAK,CAAC,MAAM,EAAE,SAASA,KAAI;AACpD;AAEO,SAAS,gBAAkC;AAChD,SAAO,gBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,MAAM;AACzD;AAEO,SAAS,gBAAgBA,OAAuB;AACrD,QAAM,MAAM,YAAYA,KAAI;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,QAAgBA,OAAuB;AACpE,QAAM,QAAQA,MAAK,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":["path"]}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import {
|
|
2
|
+
log
|
|
3
|
+
} from "./chunk-ESOPMQAY.js";
|
|
4
|
+
|
|
5
|
+
// src/adapters/discord/commands/admin.ts
|
|
6
|
+
import {
|
|
7
|
+
ActionRowBuilder,
|
|
8
|
+
ButtonBuilder,
|
|
9
|
+
ButtonStyle
|
|
10
|
+
} from "discord.js";
|
|
11
|
+
function buildDangerousModeKeyboard(sessionId, isDangerous) {
|
|
12
|
+
return new ActionRowBuilder().addComponents(
|
|
13
|
+
new ButtonBuilder().setCustomId(`d:${sessionId}`).setLabel(isDangerous ? "\u{1F510} Disable Dangerous Mode" : "\u2620\uFE0F Enable Dangerous Mode").setStyle(isDangerous ? ButtonStyle.Secondary : ButtonStyle.Danger)
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
async function handleDangerous(interaction, adapter) {
|
|
17
|
+
await interaction.deferReply({ ephemeral: true });
|
|
18
|
+
const channelId = interaction.channelId;
|
|
19
|
+
const session = adapter.core.sessionManager.getSessionByThread("discord", channelId);
|
|
20
|
+
if (session) {
|
|
21
|
+
session.dangerousMode = !session.dangerousMode;
|
|
22
|
+
adapter.core.sessionManager.patchRecord(session.id, { dangerousMode: session.dangerousMode }).catch(() => {
|
|
23
|
+
});
|
|
24
|
+
log.info({ sessionId: session.id, dangerousMode: session.dangerousMode }, "[discord-admin] Dangerous mode toggled via command");
|
|
25
|
+
const msg2 = session.dangerousMode ? "\u2620\uFE0F **Dangerous mode enabled** \u2014 All permission requests will be auto-approved." : "\u{1F510} **Dangerous mode disabled** \u2014 Permission requests will be shown normally.";
|
|
26
|
+
await interaction.editReply(msg2);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const record = adapter.core.sessionManager.getRecordByThread("discord", channelId);
|
|
30
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
31
|
+
await interaction.editReply("\u26A0\uFE0F No active session in this channel.");
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
35
|
+
adapter.core.sessionManager.patchRecord(record.sessionId, { dangerousMode: newDangerousMode }).catch(() => {
|
|
36
|
+
});
|
|
37
|
+
log.info({ sessionId: record.sessionId, dangerousMode: newDangerousMode }, "[discord-admin] Dangerous mode toggled via command (store-only)");
|
|
38
|
+
const msg = newDangerousMode ? "\u2620\uFE0F **Dangerous mode enabled** \u2014 All permission requests will be auto-approved." : "\u{1F510} **Dangerous mode disabled** \u2014 Permission requests will be shown normally.";
|
|
39
|
+
await interaction.editReply(msg);
|
|
40
|
+
}
|
|
41
|
+
async function handleDangerousButton(interaction, adapter) {
|
|
42
|
+
const sessionId = interaction.customId.slice(2);
|
|
43
|
+
const session = adapter.core.sessionManager.getSession(sessionId);
|
|
44
|
+
if (session) {
|
|
45
|
+
session.dangerousMode = !session.dangerousMode;
|
|
46
|
+
adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {
|
|
47
|
+
});
|
|
48
|
+
log.info({ sessionId, dangerousMode: session.dangerousMode }, "[discord-admin] Dangerous mode toggled via button");
|
|
49
|
+
const toastText2 = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
50
|
+
try {
|
|
51
|
+
await interaction.update({
|
|
52
|
+
components: [buildDangerousModeKeyboard(sessionId, session.dangerousMode)]
|
|
53
|
+
});
|
|
54
|
+
} catch {
|
|
55
|
+
}
|
|
56
|
+
try {
|
|
57
|
+
await interaction.followUp({ content: toastText2, ephemeral: true });
|
|
58
|
+
} catch {
|
|
59
|
+
}
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const record = adapter.core.sessionManager.getSessionRecord(sessionId);
|
|
63
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
64
|
+
await interaction.reply({ content: "\u26A0\uFE0F Session not found or already ended.", ephemeral: true });
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const newDangerousMode = !(record.dangerousMode ?? false);
|
|
68
|
+
adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {
|
|
69
|
+
});
|
|
70
|
+
log.info({ sessionId, dangerousMode: newDangerousMode }, "[discord-admin] Dangerous mode toggled via button (store-only)");
|
|
71
|
+
const toastText = newDangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
|
|
72
|
+
try {
|
|
73
|
+
await interaction.update({
|
|
74
|
+
components: [buildDangerousModeKeyboard(sessionId, newDangerousMode)]
|
|
75
|
+
});
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
await interaction.followUp({ content: toastText, ephemeral: true });
|
|
80
|
+
} catch {
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async function handleRestart(interaction, adapter) {
|
|
84
|
+
await interaction.deferReply({ ephemeral: true });
|
|
85
|
+
if (!adapter.core.requestRestart) {
|
|
86
|
+
await interaction.editReply("\u26A0\uFE0F Restart is not available (no restart handler registered).");
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await interaction.editReply("\u{1F504} **Restarting OpenACP...**\nRebuilding and restarting. Be back shortly.");
|
|
90
|
+
await new Promise((r) => setTimeout(r, 500));
|
|
91
|
+
await adapter.core.requestRestart();
|
|
92
|
+
}
|
|
93
|
+
async function handleUpdate(interaction, adapter) {
|
|
94
|
+
await interaction.deferReply({ ephemeral: true });
|
|
95
|
+
await interaction.editReply("\u26A0\uFE0F Update via Discord is not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal, then use `/restart`.");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export {
|
|
99
|
+
buildDangerousModeKeyboard,
|
|
100
|
+
handleDangerous,
|
|
101
|
+
handleDangerousButton,
|
|
102
|
+
handleRestart,
|
|
103
|
+
handleUpdate
|
|
104
|
+
};
|
|
105
|
+
//# sourceMappingURL=chunk-7G5QKLLF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/discord/commands/admin.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\n\n// TODO: Replace `any` with DiscordAdapter once Task 12 is implemented\n\nexport function buildDangerousModeKeyboard(\n sessionId: string,\n isDangerous: boolean,\n): ActionRowBuilder<ButtonBuilder> {\n return new ActionRowBuilder<ButtonBuilder>().addComponents(\n new ButtonBuilder()\n .setCustomId(`d:${sessionId}`)\n .setLabel(isDangerous ? '🔐 Disable Dangerous Mode' : '☠️ Enable Dangerous Mode')\n .setStyle(isDangerous ? ButtonStyle.Secondary : ButtonStyle.Danger),\n )\n}\n\nexport async function handleDangerous(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const channelId = interaction.channelId\n const session = adapter.core.sessionManager.getSessionByThread('discord', channelId)\n\n if (session) {\n session.dangerousMode = !session.dangerousMode\n adapter.core.sessionManager.patchRecord(session.id, { dangerousMode: session.dangerousMode }).catch(() => {})\n log.info({ sessionId: session.id, dangerousMode: session.dangerousMode }, '[discord-admin] Dangerous mode toggled via command')\n\n const msg = session.dangerousMode\n ? '☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.'\n : '🔐 **Dangerous mode disabled** — Permission requests will be shown normally.'\n await interaction.editReply(msg)\n return\n }\n\n // Session not in memory — update store directly\n const record = adapter.core.sessionManager.getRecordByThread('discord', channelId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.editReply('⚠️ No active session in this channel.')\n return\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false)\n adapter.core.sessionManager.patchRecord(record.sessionId, { dangerousMode: newDangerousMode }).catch(() => {})\n log.info({ sessionId: record.sessionId, dangerousMode: newDangerousMode }, '[discord-admin] Dangerous mode toggled via command (store-only)')\n\n const msg = newDangerousMode\n ? '☠️ **Dangerous mode enabled** — All permission requests will be auto-approved.'\n : '🔐 **Dangerous mode disabled** — Permission requests will be shown normally.'\n await interaction.editReply(msg)\n}\n\nexport async function handleDangerousButton(\n interaction: ButtonInteraction,\n adapter: any,\n): Promise<void> {\n const sessionId = interaction.customId.slice(2) // strip 'd:'\n const session = adapter.core.sessionManager.getSession(sessionId)\n\n // Session live in memory — toggle directly\n if (session) {\n session.dangerousMode = !session.dangerousMode\n adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: session.dangerousMode }).catch(() => {})\n log.info({ sessionId, dangerousMode: session.dangerousMode }, '[discord-admin] Dangerous mode toggled via button')\n\n const toastText = session.dangerousMode\n ? '☠️ Dangerous mode enabled — permissions auto-approved'\n : '🔐 Dangerous mode disabled — permissions shown normally'\n\n try {\n await interaction.update({\n components: [buildDangerousModeKeyboard(sessionId, session.dangerousMode)],\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toastText, ephemeral: true }) } catch { /* ignore */ }\n return\n }\n\n // Session not in memory — toggle in store\n const record = adapter.core.sessionManager.getSessionRecord(sessionId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.reply({ content: '⚠️ Session not found or already ended.', ephemeral: true })\n return\n }\n\n const newDangerousMode = !(record.dangerousMode ?? false)\n adapter.core.sessionManager.patchRecord(sessionId, { dangerousMode: newDangerousMode }).catch(() => {})\n log.info({ sessionId, dangerousMode: newDangerousMode }, '[discord-admin] Dangerous mode toggled via button (store-only)')\n\n const toastText = newDangerousMode\n ? '☠️ Dangerous mode enabled — permissions auto-approved'\n : '🔐 Dangerous mode disabled — permissions shown normally'\n\n try {\n await interaction.update({\n components: [buildDangerousModeKeyboard(sessionId, newDangerousMode)],\n })\n } catch { /* ignore */ }\n\n try { await interaction.followUp({ content: toastText, ephemeral: true }) } catch { /* ignore */ }\n}\n\nexport async function handleRestart(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n if (!adapter.core.requestRestart) {\n await interaction.editReply('⚠️ Restart is not available (no restart handler registered).')\n return\n }\n\n await interaction.editReply('🔄 **Restarting OpenACP...**\\nRebuilding and restarting. Be back shortly.')\n await new Promise((r) => setTimeout(r, 500))\n await adapter.core.requestRestart()\n}\n\nexport async function handleUpdate(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n // Stub: not implemented yet\n await interaction.editReply('⚠️ Update via Discord is not implemented yet. Run `npm install -g @openacp/cli@latest` in your terminal, then use `/restart`.')\n}\n"],"mappings":"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","names":["msg","toastText"]}
|
|
@@ -36,6 +36,7 @@ Each session gets its own topic \u2014 chat there to work with the agent.
|
|
|
36
36
|
/enable_dangerous \u2014 Auto-approve permissions
|
|
37
37
|
/disable_dangerous \u2014 Restore permission prompts
|
|
38
38
|
/handoff \u2014 Continue session in terminal
|
|
39
|
+
/archive \u2014 Archive session topic
|
|
39
40
|
/clear \u2014 Clear assistant history
|
|
40
41
|
|
|
41
42
|
\u{1F4AC} Need help? Just ask me in this topic!`,
|
|
@@ -88,4 +89,4 @@ export {
|
|
|
88
89
|
handleClear,
|
|
89
90
|
buildSkillMessages
|
|
90
91
|
};
|
|
91
|
-
//# sourceMappingURL=chunk-
|
|
92
|
+
//# sourceMappingURL=chunk-7QJS2XBD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/telegram/commands/menu.ts"],"sourcesContent":["import type { Context } from \"grammy\";\nimport { InlineKeyboard } from \"grammy\";\nimport type { AgentCommand } from \"../../../core/index.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 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 — Browse & install agents\\n` +\n `/install <name> — Install an agent\\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 `/archive — Archive session topic\\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"],"mappings":";AACA,SAAS,sBAAsB;AAIxB,SAAS,oBAAoC;AAClD,SAAO,IAAI,eAAe,EACvB,KAAK,yBAAkB,OAAO,EAC9B,KAAK,sBAAe,UAAU,EAC9B,IAAI,EACJ,KAAK,oBAAa,UAAU,EAC5B,KAAK,oBAAa,UAAU,EAC5B,IAAI,EACJ,KAAK,yBAAe,YAAY,EAChC,KAAK,uBAAgB,aAAa,EAClC,IAAI,EACJ,KAAK,qBAAc,WAAW,EAC9B,KAAK,uBAAa,UAAU,EAC5B,IAAI,EACJ,KAAK,eAAU,QAAQ,EACvB,KAAK,oBAAa,UAAU;AACjC;AAEA,eAAsB,WAAW,KAA6B;AAC5D,QAAM,IAAI,MAAM;AAAA,oBAA0C;AAAA,IACxD,YAAY;AAAA,IACZ,cAAc,kBAAkB;AAAA,EAClC,CAAC;AACH;AAEA,eAAsB,WAAW,KAA6B;AAC5D,QAAM,IAAI;AAAA,IACR;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAuBA,EAAE,YAAY,OAAO;AAAA,EACvB;AACF;AAEA,eAAsB,YAAY,KAAc,WAAqD;AACnG,MAAI,CAAC,WAAW;AACd,UAAM,IAAI,MAAM,4CAAkC,EAAE,YAAY,OAAO,CAAC;AACxE;AAAA,EACF;AAEA,QAAM,WAAW,IAAI,SAAS;AAC9B,MAAI,aAAa,UAAU,SAAS;AAClC,UAAM,IAAI,MAAM,0DAAgD,EAAE,YAAY,OAAO,CAAC;AACtF;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,2CAAoC,EAAE,YAAY,OAAO,CAAC;AAE1E,MAAI;AACF,UAAM,UAAU,QAAQ;AACxB,UAAM,IAAI,MAAM,qCAAgC,EAAE,YAAY,OAAO,CAAC;AAAA,EACxE,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAM,IAAI,MAAM,iCAA4B,OAAO,WAAW,EAAE,YAAY,OAAO,CAAC;AAAA,EACtF;AACF;AAEA,IAAM,qBAAqB;AAOpB,SAAS,mBAAmB,UAAoC;AACrE,QAAM,SAAS,CAAC,GAAG,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,IAAI,CAAC;AACxE,QAAM,SAAS;AACf,QAAM,QAAQ,OAAO,IAAI,CAAC,MAAM,UAAU,EAAE,IAAI,SAAS;AAEzD,QAAM,WAAqB,CAAC;AAC5B,MAAI,UAAU;AAEd,aAAW,QAAQ,OAAO;AACxB,UAAM,YAAY,UAAU,OAAO;AACnC,QAAI,UAAU,SAAS,oBAAoB;AACzC,eAAS,KAAK,OAAO;AACrB,gBAAU;AAAA,IACZ,OAAO;AACL,gBAAU;AAAA,IACZ;AAAA,EACF;AACA,MAAI,QAAS,UAAS,KAAK,OAAO;AAClC,SAAO;AACT;","names":[]}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createSessionThread,
|
|
3
|
+
deleteSessionThread
|
|
4
|
+
} from "./chunk-SM3G6UAX.js";
|
|
5
|
+
import {
|
|
6
|
+
buildDangerousModeKeyboard
|
|
7
|
+
} from "./chunk-7G5QKLLF.js";
|
|
8
|
+
import {
|
|
9
|
+
log
|
|
10
|
+
} from "./chunk-ESOPMQAY.js";
|
|
11
|
+
|
|
12
|
+
// src/adapters/discord/commands/new-session.ts
|
|
13
|
+
import {
|
|
14
|
+
ActionRowBuilder,
|
|
15
|
+
ButtonBuilder,
|
|
16
|
+
ButtonStyle
|
|
17
|
+
} from "discord.js";
|
|
18
|
+
async function handleNew(interaction, adapter) {
|
|
19
|
+
await interaction.deferReply({ ephemeral: true });
|
|
20
|
+
const agentName = interaction.options.getString("agent") ?? void 0;
|
|
21
|
+
const workspace = interaction.options.getString("workspace") ?? void 0;
|
|
22
|
+
if (agentName) {
|
|
23
|
+
await executeNewSession(interaction, adapter, agentName, workspace);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
const installedEntries = adapter.core.agentCatalog.getInstalledEntries();
|
|
27
|
+
const agentKeys = Object.keys(installedEntries);
|
|
28
|
+
const config = adapter.core.configManager.get();
|
|
29
|
+
if (agentKeys.length === 0) {
|
|
30
|
+
await interaction.editReply("\u274C No agents installed. Use `/install` to install an agent first.");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (agentKeys.length === 1) {
|
|
34
|
+
await executeNewSession(interaction, adapter, config.defaultAgent, workspace);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
const row = new ActionRowBuilder();
|
|
38
|
+
for (const key of agentKeys) {
|
|
39
|
+
const agent = installedEntries[key];
|
|
40
|
+
const label = key === config.defaultAgent ? `${agent.name} (default)` : agent.name;
|
|
41
|
+
row.addComponents(
|
|
42
|
+
new ButtonBuilder().setCustomId(`m:new:agent:${key}`).setLabel(label).setStyle(ButtonStyle.Primary)
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
await interaction.editReply({
|
|
46
|
+
content: "\u{1F916} **Choose an agent:**",
|
|
47
|
+
components: [row]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
async function handleNewChat(interaction, adapter) {
|
|
51
|
+
await interaction.deferReply({ ephemeral: true });
|
|
52
|
+
const channelId = interaction.channelId;
|
|
53
|
+
const currentSession = adapter.core.sessionManager.getSessionByThread("discord", channelId);
|
|
54
|
+
let agentName;
|
|
55
|
+
let workspace;
|
|
56
|
+
if (currentSession) {
|
|
57
|
+
agentName = currentSession.agentName;
|
|
58
|
+
workspace = currentSession.workingDirectory;
|
|
59
|
+
} else {
|
|
60
|
+
const record = adapter.core.sessionManager.getRecordByThread("discord", channelId);
|
|
61
|
+
if (!record || record.status === "cancelled" || record.status === "error") {
|
|
62
|
+
await interaction.editReply("No active session in this channel. Use `/new` to start one.");
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
agentName = record.agentName;
|
|
66
|
+
workspace = record.workingDir;
|
|
67
|
+
}
|
|
68
|
+
await executeNewSession(interaction, adapter, agentName, workspace);
|
|
69
|
+
}
|
|
70
|
+
async function executeNewSession(interaction, adapter, agentName, workspace) {
|
|
71
|
+
const config = adapter.core.configManager.get();
|
|
72
|
+
const resolvedAgent = agentName || config.defaultAgent;
|
|
73
|
+
log.info({ agentName: resolvedAgent, workspace }, "[discord-new-session] Creating session");
|
|
74
|
+
const forumChannel = adapter.getForumChannel();
|
|
75
|
+
if (!forumChannel) {
|
|
76
|
+
const msg = "\u274C Forum channel not configured. Please run setup first.";
|
|
77
|
+
if (interaction.deferred || interaction.replied) {
|
|
78
|
+
await interaction.editReply(msg);
|
|
79
|
+
} else {
|
|
80
|
+
await interaction.reply({ content: msg, ephemeral: true });
|
|
81
|
+
}
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
let thread;
|
|
85
|
+
try {
|
|
86
|
+
thread = await createSessionThread(forumChannel, `\u{1F504} ${resolvedAgent} \u2014 New Session`);
|
|
87
|
+
const session = await adapter.core.handleNewSession("discord", resolvedAgent, workspace);
|
|
88
|
+
session.threadId = thread.id;
|
|
89
|
+
await adapter.core.sessionManager.patchRecord(session.id, {
|
|
90
|
+
platform: { threadId: thread.id }
|
|
91
|
+
});
|
|
92
|
+
const dangerousRow = buildDangerousModeKeyboard(session.id, false);
|
|
93
|
+
await thread.send({
|
|
94
|
+
content: `\u2705 **Session started**
|
|
95
|
+
**Agent:** ${session.agentName}
|
|
96
|
+
**Workspace:** \`${session.workingDirectory}\`
|
|
97
|
+
|
|
98
|
+
This is your coding session \u2014 chat here to work with the agent.`,
|
|
99
|
+
components: [dangerousRow]
|
|
100
|
+
});
|
|
101
|
+
const replyMsg = `\u2705 Session created \u2192 [Open thread](https://discord.com/channels/${adapter.getGuildId()}/${thread.id})`;
|
|
102
|
+
if (interaction.deferred || interaction.replied) {
|
|
103
|
+
await interaction.editReply(replyMsg);
|
|
104
|
+
} else {
|
|
105
|
+
await interaction.reply({ content: replyMsg, ephemeral: true });
|
|
106
|
+
}
|
|
107
|
+
session.warmup().catch((err) => log.error({ err }, "[discord-new-session] Warm-up error"));
|
|
108
|
+
} catch (err) {
|
|
109
|
+
log.error({ err }, "[discord-new-session] Session creation failed");
|
|
110
|
+
if (thread) {
|
|
111
|
+
try {
|
|
112
|
+
await deleteSessionThread(adapter.getGuild(), thread.id);
|
|
113
|
+
} catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const errMsg = `\u274C ${err instanceof Error ? err.message : String(err)}`;
|
|
117
|
+
try {
|
|
118
|
+
if (interaction.deferred || interaction.replied) {
|
|
119
|
+
await interaction.editReply(errMsg);
|
|
120
|
+
} else {
|
|
121
|
+
await interaction.reply({ content: errMsg, ephemeral: true });
|
|
122
|
+
}
|
|
123
|
+
} catch {
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async function handleNewSessionButton(interaction, adapter) {
|
|
128
|
+
const { customId } = interaction;
|
|
129
|
+
if (customId.startsWith("m:new:agent:")) {
|
|
130
|
+
const agentKey = customId.replace("m:new:agent:", "");
|
|
131
|
+
try {
|
|
132
|
+
await interaction.deferUpdate();
|
|
133
|
+
} catch {
|
|
134
|
+
}
|
|
135
|
+
await executeNewSession(interaction, adapter, agentKey, void 0);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export {
|
|
140
|
+
handleNew,
|
|
141
|
+
handleNewChat,
|
|
142
|
+
executeNewSession,
|
|
143
|
+
handleNewSessionButton
|
|
144
|
+
};
|
|
145
|
+
//# sourceMappingURL=chunk-AKIU4JBF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/discord/commands/new-session.ts"],"sourcesContent":["import {\n ActionRowBuilder,\n ButtonBuilder,\n ButtonStyle,\n} from 'discord.js'\nimport type { ChatInputCommandInteraction, ButtonInteraction } from 'discord.js'\nimport { log } from '../../../core/log.js'\nimport { buildDangerousModeKeyboard } from './admin.js'\nimport { createSessionThread, deleteSessionThread } from '../forums.js'\n\n// TODO: Replace `any` with DiscordAdapter once Task 12 is implemented\n\nexport async function handleNew(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n const agentName = interaction.options.getString('agent') ?? undefined\n const workspace = interaction.options.getString('workspace') ?? undefined\n\n if (agentName) {\n await executeNewSession(interaction, adapter, agentName, workspace)\n return\n }\n\n // No agent specified — show agent picker\n const installedEntries = adapter.core.agentCatalog.getInstalledEntries()\n const agentKeys = Object.keys(installedEntries)\n const config = adapter.core.configManager.get()\n\n if (agentKeys.length === 0) {\n await interaction.editReply('❌ No agents installed. Use `/install` to install an agent first.')\n return\n }\n\n if (agentKeys.length === 1) {\n await executeNewSession(interaction, adapter, config.defaultAgent, workspace)\n return\n }\n\n // Multiple agents — show picker buttons\n const row = new ActionRowBuilder<ButtonBuilder>()\n for (const key of agentKeys) {\n const agent = installedEntries[key]!\n const label = key === config.defaultAgent ? `${agent.name} (default)` : agent.name\n row.addComponents(\n new ButtonBuilder()\n .setCustomId(`m:new:agent:${key}`)\n .setLabel(label)\n .setStyle(ButtonStyle.Primary),\n )\n }\n\n await interaction.editReply({\n content: '🤖 **Choose an agent:**',\n components: [row],\n })\n}\n\nexport async function handleNewChat(\n interaction: ChatInputCommandInteraction,\n adapter: any,\n): Promise<void> {\n await interaction.deferReply({ ephemeral: true })\n\n // Get current thread to inherit config from\n const channelId = interaction.channelId\n const currentSession = adapter.core.sessionManager.getSessionByThread('discord', channelId)\n\n let agentName: string | undefined\n let workspace: string | undefined\n\n if (currentSession) {\n agentName = currentSession.agentName\n workspace = currentSession.workingDirectory\n } else {\n const record = adapter.core.sessionManager.getRecordByThread('discord', channelId)\n if (!record || record.status === 'cancelled' || record.status === 'error') {\n await interaction.editReply('No active session in this channel. Use `/new` to start one.')\n return\n }\n agentName = record.agentName\n workspace = record.workingDir\n }\n\n await executeNewSession(interaction, adapter, agentName, workspace)\n}\n\nexport async function executeNewSession(\n interaction: ChatInputCommandInteraction | ButtonInteraction,\n adapter: any,\n agentName?: string,\n workspace?: string,\n): Promise<void> {\n const config = adapter.core.configManager.get()\n const resolvedAgent = agentName || config.defaultAgent\n\n log.info({ agentName: resolvedAgent, workspace }, '[discord-new-session] Creating session')\n\n const forumChannel = adapter.getForumChannel()\n if (!forumChannel) {\n const msg = '❌ Forum channel not configured. Please run setup first.'\n if ((interaction as any).deferred || (interaction as any).replied) {\n await interaction.editReply(msg)\n } else {\n await (interaction as ChatInputCommandInteraction).reply({ content: msg, ephemeral: true })\n }\n return\n }\n\n let thread: import('discord.js').ThreadChannel | undefined\n\n try {\n // Create forum thread BEFORE creating session to avoid race condition\n thread = await createSessionThread(forumChannel, `🔄 ${resolvedAgent} — New Session`)\n\n // Create session via core\n const session = await adapter.core.handleNewSession('discord', resolvedAgent, workspace)\n session.threadId = thread.id\n\n // Patch platform record with Discord thread ID\n await adapter.core.sessionManager.patchRecord(session.id, {\n platform: { threadId: thread.id },\n })\n\n // Send welcome message in the new thread\n const dangerousRow = buildDangerousModeKeyboard(session.id, false)\n await thread.send({\n content:\n `✅ **Session started**\\n` +\n `**Agent:** ${session.agentName}\\n` +\n `**Workspace:** \\`${session.workingDirectory}\\`\\n\\n` +\n `This is your coding session — chat here to work with the agent.`,\n components: [dangerousRow],\n })\n\n // Reply to the interaction with a link to the thread\n const replyMsg = `✅ Session created → [Open thread](https://discord.com/channels/${adapter.getGuildId()}/${thread.id})`\n if ((interaction as any).deferred || (interaction as any).replied) {\n await interaction.editReply(replyMsg)\n } else {\n await (interaction as ChatInputCommandInteraction).reply({ content: replyMsg, ephemeral: true })\n }\n\n // Warm up model cache in background\n session.warmup().catch((err: unknown) => log.error({ err }, '[discord-new-session] Warm-up error'))\n } catch (err) {\n log.error({ err }, '[discord-new-session] Session creation failed')\n\n // Clean up orphaned thread on failure (archive+lock instead of permanent delete)\n if (thread) {\n try { await deleteSessionThread(adapter.getGuild(), thread.id) } catch { /* ignore */ }\n }\n\n const errMsg = `❌ ${err instanceof Error ? err.message : String(err)}`\n try {\n if ((interaction as any).deferred || (interaction as any).replied) {\n await interaction.editReply(errMsg)\n } else {\n await (interaction as ChatInputCommandInteraction).reply({ content: errMsg, ephemeral: true })\n }\n } catch { /* ignore */ }\n }\n}\n\nexport async function handleNewSessionButton(\n interaction: ButtonInteraction,\n adapter: any,\n): Promise<void> {\n const { customId } = interaction\n\n if (customId.startsWith('m:new:agent:')) {\n const agentKey = customId.replace('m:new:agent:', '')\n try { await interaction.deferUpdate() } catch { /* ignore */ }\n await executeNewSession(interaction, adapter, agentKey, undefined)\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/cli/version.ts"],"sourcesContent":["import { fileURLToPath } from 'node:url'\nimport { dirname, join, resolve } from 'node:path'\nimport { existsSync, readFileSync } from 'node:fs'\n\nconst NPM_PACKAGE = '@openacp/cli'\n\nfunction findPackageJson(): string | null {\n let dir = dirname(fileURLToPath(import.meta.url))\n for (let i = 0; i < 5; i++) {\n const candidate = join(dir, 'package.json')\n if (existsSync(candidate)) return candidate\n const parent = resolve(dir, '..')\n if (parent === dir) break\n dir = parent\n }\n return null\n}\n\nexport function getCurrentVersion(): string {\n try {\n const pkgPath = findPackageJson()\n if (!pkgPath) return '0.0.0-dev'\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n return pkg.version as string\n } catch {\n return '0.0.0-dev'\n }\n}\n\nexport async function getLatestVersion(): Promise<string | null> {\n try {\n const res = await fetch(`https://registry.npmjs.org/${NPM_PACKAGE}/latest`, {\n signal: AbortSignal.timeout(5000),\n })\n if (!res.ok) return null\n const data = (await res.json()) as { version?: string }\n return data.version ?? null\n } catch {\n return null\n }\n}\n\nexport function compareVersions(current: string, latest: string): -1 | 0 | 1 {\n const a = current.split('.').map(Number)\n const b = latest.split('.').map(Number)\n for (let i = 0; i < 3; i++) {\n if ((a[i] ?? 0) < (b[i] ?? 0)) return -1\n if ((a[i] ?? 0) > (b[i] ?? 0)) return 1\n }\n return 0\n}\n\nexport async function runUpdate(): Promise<boolean> {\n const { spawn } = await import('node:child_process')\n return new Promise((resolve) => {\n const child = spawn('npm', ['install', '-g', `${NPM_PACKAGE}@latest`], {\n stdio: 'inherit',\n shell: true,\n })\n const onSignal = () => {\n child.kill('SIGTERM')\n resolve(false)\n }\n process.on('SIGINT', onSignal)\n process.on('SIGTERM', onSignal)\n child.on('close', (code) => {\n process.off('SIGINT', onSignal)\n process.off('SIGTERM', onSignal)\n resolve(code === 0)\n })\n })\n}\n\nexport async function checkAndPromptUpdate(): Promise<void> {\n if (process.env.OPENACP_DEV_LOOP || process.env.OPENACP_SKIP_UPDATE_CHECK) return\n\n const current = getCurrentVersion()\n if (current === '0.0.0-dev') return\n\n const latest = await getLatestVersion()\n if (!latest || compareVersions(current, latest) >= 0) return\n\n console.log(`\\x1b[33mUpdate available: v${current} → v${latest}\\x1b[0m`)\n const { confirm } = await import('@inquirer/prompts')\n const yes = await confirm({\n message: 'Update now before starting?',\n default: true,\n })\n if (yes) {\n const ok = await runUpdate()\n if (ok) {\n console.log(`\\x1b[32m✓ Updated to v${latest}. Please re-run your command.\\x1b[0m`)\n process.exit(0)\n } else {\n console.error('\\x1b[31mUpdate failed. Continuing with current version.\\x1b[0m')\n }\n }\n}\n"],"mappings":"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","names":["resolve"]}
|