@openacp/cli 2026.404.2 → 2026.405.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 +90 -11
- package/dist/{channel-DQWwxUKX.d.ts → channel-DstweC6V.d.ts} +1 -1
- package/dist/cli.js +1694 -1377
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +69 -423
- package/dist/index.js +1311 -1202
- package/dist/index.js.map +1 -1
- package/dist/testing.d.ts +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -244,6 +244,45 @@ var init_log = __esm({
|
|
|
244
244
|
}
|
|
245
245
|
});
|
|
246
246
|
|
|
247
|
+
// src/core/config/config-migrations.ts
|
|
248
|
+
function applyMigrations(raw, migrationList = migrations, ctx) {
|
|
249
|
+
let changed = false;
|
|
250
|
+
for (const migration of migrationList) {
|
|
251
|
+
if (migration.apply(raw, ctx)) {
|
|
252
|
+
changed = true;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
return { changed };
|
|
256
|
+
}
|
|
257
|
+
var log2, migrations;
|
|
258
|
+
var init_config_migrations = __esm({
|
|
259
|
+
"src/core/config/config-migrations.ts"() {
|
|
260
|
+
"use strict";
|
|
261
|
+
init_log();
|
|
262
|
+
log2 = createChildLogger({ module: "config-migrations" });
|
|
263
|
+
migrations = [
|
|
264
|
+
{
|
|
265
|
+
name: "add-instance-name",
|
|
266
|
+
apply(raw) {
|
|
267
|
+
if (raw.instanceName) return false;
|
|
268
|
+
raw.instanceName = "Main";
|
|
269
|
+
log2.info("Added instanceName to config");
|
|
270
|
+
return true;
|
|
271
|
+
}
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
name: "delete-display-verbosity",
|
|
275
|
+
apply(raw) {
|
|
276
|
+
if (!("displayVerbosity" in raw)) return false;
|
|
277
|
+
delete raw.displayVerbosity;
|
|
278
|
+
log2.info("Removed legacy displayVerbosity key from config");
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
247
286
|
// src/core/instance/instance-context.ts
|
|
248
287
|
var instance_context_exports = {};
|
|
249
288
|
__export(instance_context_exports, {
|
|
@@ -308,145 +347,6 @@ var init_instance_context = __esm({
|
|
|
308
347
|
}
|
|
309
348
|
});
|
|
310
349
|
|
|
311
|
-
// src/core/config/config-migrations.ts
|
|
312
|
-
import * as fs3 from "fs";
|
|
313
|
-
import * as path3 from "path";
|
|
314
|
-
function applyMigrations(raw, migrationList = migrations, ctx) {
|
|
315
|
-
let changed = false;
|
|
316
|
-
for (const migration of migrationList) {
|
|
317
|
-
if (migration.apply(raw, ctx)) {
|
|
318
|
-
changed = true;
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
return { changed };
|
|
322
|
-
}
|
|
323
|
-
var log2, migrations;
|
|
324
|
-
var init_config_migrations = __esm({
|
|
325
|
-
"src/core/config/config-migrations.ts"() {
|
|
326
|
-
"use strict";
|
|
327
|
-
init_log();
|
|
328
|
-
init_instance_context();
|
|
329
|
-
log2 = createChildLogger({ module: "config-migrations" });
|
|
330
|
-
migrations = [
|
|
331
|
-
{
|
|
332
|
-
name: "add-tunnel-section",
|
|
333
|
-
apply(raw) {
|
|
334
|
-
if (raw.tunnel) return false;
|
|
335
|
-
raw.tunnel = {
|
|
336
|
-
enabled: true,
|
|
337
|
-
port: 3100,
|
|
338
|
-
provider: "cloudflare",
|
|
339
|
-
options: {},
|
|
340
|
-
storeTtlMinutes: 60,
|
|
341
|
-
auth: { enabled: false }
|
|
342
|
-
};
|
|
343
|
-
log2.info("Added tunnel section to config (enabled by default with cloudflare)");
|
|
344
|
-
return true;
|
|
345
|
-
}
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
name: "fix-agent-commands",
|
|
349
|
-
apply(raw) {
|
|
350
|
-
const COMMAND_MIGRATIONS = {
|
|
351
|
-
"claude-agent-acp": ["claude", "claude-code"]
|
|
352
|
-
};
|
|
353
|
-
const agents = raw.agents;
|
|
354
|
-
if (!agents || typeof agents !== "object") return false;
|
|
355
|
-
let changed = false;
|
|
356
|
-
for (const [agentName, agentDef] of Object.entries(agents)) {
|
|
357
|
-
if (!agentDef || typeof agentDef !== "object" || !("command" in agentDef)) continue;
|
|
358
|
-
const def = agentDef;
|
|
359
|
-
if (typeof def.command !== "string") continue;
|
|
360
|
-
for (const [correctCmd, legacyCmds] of Object.entries(COMMAND_MIGRATIONS)) {
|
|
361
|
-
if (legacyCmds.includes(def.command)) {
|
|
362
|
-
log2.warn(
|
|
363
|
-
{ agent: agentName, oldCommand: def.command, newCommand: correctCmd },
|
|
364
|
-
`Auto-migrating agent command: "${def.command}" \u2192 "${correctCmd}"`
|
|
365
|
-
);
|
|
366
|
-
def.command = correctCmd;
|
|
367
|
-
changed = true;
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
return changed;
|
|
372
|
-
}
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
name: "migrate-agents-to-store",
|
|
376
|
-
apply(raw, ctx) {
|
|
377
|
-
const agentsJsonPath = path3.join(ctx?.configDir ?? getGlobalRoot(), "agents.json");
|
|
378
|
-
if (fs3.existsSync(agentsJsonPath)) return false;
|
|
379
|
-
const agents = raw.agents;
|
|
380
|
-
if (!agents || Object.keys(agents).length === 0) return false;
|
|
381
|
-
const COMMAND_TO_REGISTRY = {
|
|
382
|
-
"claude-agent-acp": "claude-acp",
|
|
383
|
-
"codex": "codex-acp"
|
|
384
|
-
};
|
|
385
|
-
const installed = {};
|
|
386
|
-
for (const [key, val] of Object.entries(agents)) {
|
|
387
|
-
const cfg = val;
|
|
388
|
-
const command = typeof cfg.command === "string" ? cfg.command : "";
|
|
389
|
-
const registryId = COMMAND_TO_REGISTRY[command] ?? null;
|
|
390
|
-
installed[key] = {
|
|
391
|
-
registryId,
|
|
392
|
-
name: key.charAt(0).toUpperCase() + key.slice(1),
|
|
393
|
-
version: "unknown",
|
|
394
|
-
distribution: "custom",
|
|
395
|
-
command: cfg.command,
|
|
396
|
-
args: cfg.args ?? [],
|
|
397
|
-
env: cfg.env ?? {},
|
|
398
|
-
workingDirectory: cfg.workingDirectory ?? void 0,
|
|
399
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
400
|
-
binaryPath: null
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
fs3.mkdirSync(path3.dirname(agentsJsonPath), { recursive: true });
|
|
404
|
-
fs3.writeFileSync(agentsJsonPath, JSON.stringify({ version: 1, installed }, null, 2));
|
|
405
|
-
raw.agents = {};
|
|
406
|
-
return true;
|
|
407
|
-
}
|
|
408
|
-
},
|
|
409
|
-
{
|
|
410
|
-
name: "add-instance-name",
|
|
411
|
-
apply(raw) {
|
|
412
|
-
if (raw.instanceName) return false;
|
|
413
|
-
raw.instanceName = "Main";
|
|
414
|
-
log2.info("Added instanceName to config");
|
|
415
|
-
return true;
|
|
416
|
-
}
|
|
417
|
-
},
|
|
418
|
-
{
|
|
419
|
-
name: "migrate-display-verbosity-to-output-mode",
|
|
420
|
-
apply(raw) {
|
|
421
|
-
const channels = raw.channels;
|
|
422
|
-
if (!channels) return false;
|
|
423
|
-
let changed = false;
|
|
424
|
-
for (const [, channelCfg] of Object.entries(channels)) {
|
|
425
|
-
if (!channelCfg || typeof channelCfg !== "object") continue;
|
|
426
|
-
const cfg = channelCfg;
|
|
427
|
-
if (cfg.displayVerbosity && !cfg.outputMode) {
|
|
428
|
-
cfg.outputMode = cfg.displayVerbosity;
|
|
429
|
-
changed = true;
|
|
430
|
-
}
|
|
431
|
-
}
|
|
432
|
-
return changed;
|
|
433
|
-
}
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
name: "migrate-tunnel-provider-to-openacp",
|
|
437
|
-
apply(raw) {
|
|
438
|
-
const tunnel = raw.tunnel;
|
|
439
|
-
if (!tunnel) return false;
|
|
440
|
-
if (tunnel.provider !== "cloudflare") return false;
|
|
441
|
-
tunnel.provider = "openacp";
|
|
442
|
-
log2.info("Migrated tunnel provider: cloudflare \u2192 openacp (OpenACP managed tunnel)");
|
|
443
|
-
return true;
|
|
444
|
-
}
|
|
445
|
-
}
|
|
446
|
-
];
|
|
447
|
-
}
|
|
448
|
-
});
|
|
449
|
-
|
|
450
350
|
// src/core/config/config-registry.ts
|
|
451
351
|
var config_registry_exports = {};
|
|
452
352
|
__export(config_registry_exports, {
|
|
@@ -454,30 +354,29 @@ __export(config_registry_exports, {
|
|
|
454
354
|
ConfigValidationError: () => ConfigValidationError,
|
|
455
355
|
getConfigValue: () => getConfigValue,
|
|
456
356
|
getFieldDef: () => getFieldDef,
|
|
457
|
-
getFieldValueAsync: () => getFieldValueAsync,
|
|
458
357
|
getSafeFields: () => getSafeFields,
|
|
459
358
|
isHotReloadable: () => isHotReloadable,
|
|
460
359
|
resolveOptions: () => resolveOptions,
|
|
461
360
|
setFieldValueAsync: () => setFieldValueAsync
|
|
462
361
|
});
|
|
463
|
-
import * as
|
|
464
|
-
import * as
|
|
465
|
-
function getFieldDef(
|
|
466
|
-
return CONFIG_REGISTRY.find((f) => f.path ===
|
|
362
|
+
import * as fs3 from "fs";
|
|
363
|
+
import * as path3 from "path";
|
|
364
|
+
function getFieldDef(path34) {
|
|
365
|
+
return CONFIG_REGISTRY.find((f) => f.path === path34);
|
|
467
366
|
}
|
|
468
367
|
function getSafeFields() {
|
|
469
368
|
return CONFIG_REGISTRY.filter((f) => f.scope === "safe");
|
|
470
369
|
}
|
|
471
|
-
function isHotReloadable(
|
|
472
|
-
const def = getFieldDef(
|
|
370
|
+
function isHotReloadable(path34) {
|
|
371
|
+
const def = getFieldDef(path34);
|
|
473
372
|
return def?.hotReload ?? false;
|
|
474
373
|
}
|
|
475
374
|
function resolveOptions(def, config) {
|
|
476
375
|
if (!def.options) return void 0;
|
|
477
376
|
return typeof def.options === "function" ? def.options(config) : def.options;
|
|
478
377
|
}
|
|
479
|
-
function getConfigValue(config,
|
|
480
|
-
const parts =
|
|
378
|
+
function getConfigValue(config, path34) {
|
|
379
|
+
const parts = path34.split(".");
|
|
481
380
|
let current = config;
|
|
482
381
|
for (const part of parts) {
|
|
483
382
|
if (current && typeof current === "object" && part in current) {
|
|
@@ -488,13 +387,6 @@ function getConfigValue(config, path35) {
|
|
|
488
387
|
}
|
|
489
388
|
return current;
|
|
490
389
|
}
|
|
491
|
-
async function getFieldValueAsync(field, configManager, settingsManager) {
|
|
492
|
-
if (field.plugin && settingsManager) {
|
|
493
|
-
const settings = await settingsManager.loadSettings(field.plugin.name);
|
|
494
|
-
return settings[field.plugin.key];
|
|
495
|
-
}
|
|
496
|
-
return getConfigValue(configManager.get(), field.path);
|
|
497
|
-
}
|
|
498
390
|
function validateFieldValue(field, value) {
|
|
499
391
|
switch (field.type) {
|
|
500
392
|
case "number":
|
|
@@ -520,17 +412,8 @@ function validateFieldValue(field, value) {
|
|
|
520
412
|
}
|
|
521
413
|
}
|
|
522
414
|
}
|
|
523
|
-
async function setFieldValueAsync(field, value, configManager
|
|
415
|
+
async function setFieldValueAsync(field, value, configManager) {
|
|
524
416
|
validateFieldValue(field, value);
|
|
525
|
-
if (field.plugin && settingsManager) {
|
|
526
|
-
await settingsManager.updatePluginSettings(field.plugin.name, {
|
|
527
|
-
[field.plugin.key]: value
|
|
528
|
-
});
|
|
529
|
-
if (configManager.emit) {
|
|
530
|
-
configManager.emit("config:changed", { path: field.path, value, oldValue: void 0 });
|
|
531
|
-
}
|
|
532
|
-
return { needsRestart: !field.hotReload };
|
|
533
|
-
}
|
|
534
417
|
await configManager.setPath(field.path, value);
|
|
535
418
|
return { needsRestart: !field.hotReload };
|
|
536
419
|
}
|
|
@@ -545,38 +428,20 @@ var init_config_registry = __esm({
|
|
|
545
428
|
displayName: "Default Agent",
|
|
546
429
|
group: "agent",
|
|
547
430
|
type: "select",
|
|
548
|
-
options: (
|
|
431
|
+
options: () => {
|
|
549
432
|
try {
|
|
550
|
-
const agentsPath =
|
|
551
|
-
if (
|
|
552
|
-
const data = JSON.parse(
|
|
433
|
+
const agentsPath = path3.join(getGlobalRoot(), "agents.json");
|
|
434
|
+
if (fs3.existsSync(agentsPath)) {
|
|
435
|
+
const data = JSON.parse(fs3.readFileSync(agentsPath, "utf-8"));
|
|
553
436
|
return Object.keys(data.installed ?? {});
|
|
554
437
|
}
|
|
555
438
|
} catch {
|
|
556
439
|
}
|
|
557
|
-
return
|
|
440
|
+
return [];
|
|
558
441
|
},
|
|
559
442
|
scope: "safe",
|
|
560
443
|
hotReload: true
|
|
561
444
|
},
|
|
562
|
-
{
|
|
563
|
-
path: "channels.telegram.outputMode",
|
|
564
|
-
displayName: "Telegram Output Mode",
|
|
565
|
-
group: "display",
|
|
566
|
-
type: "select",
|
|
567
|
-
options: ["low", "medium", "high"],
|
|
568
|
-
scope: "safe",
|
|
569
|
-
hotReload: true
|
|
570
|
-
},
|
|
571
|
-
{
|
|
572
|
-
path: "channels.discord.outputMode",
|
|
573
|
-
displayName: "Discord Output Mode",
|
|
574
|
-
group: "display",
|
|
575
|
-
type: "select",
|
|
576
|
-
options: ["low", "medium", "high"],
|
|
577
|
-
scope: "safe",
|
|
578
|
-
hotReload: true
|
|
579
|
-
},
|
|
580
445
|
{
|
|
581
446
|
path: "logging.level",
|
|
582
447
|
displayName: "Log Level",
|
|
@@ -586,33 +451,6 @@ var init_config_registry = __esm({
|
|
|
586
451
|
scope: "safe",
|
|
587
452
|
hotReload: true
|
|
588
453
|
},
|
|
589
|
-
{
|
|
590
|
-
path: "tunnel.enabled",
|
|
591
|
-
displayName: "Tunnel",
|
|
592
|
-
group: "tunnel",
|
|
593
|
-
type: "toggle",
|
|
594
|
-
scope: "safe",
|
|
595
|
-
hotReload: false,
|
|
596
|
-
plugin: { name: "@openacp/tunnel", key: "enabled" }
|
|
597
|
-
},
|
|
598
|
-
{
|
|
599
|
-
path: "security.maxConcurrentSessions",
|
|
600
|
-
displayName: "Max Concurrent Sessions",
|
|
601
|
-
group: "security",
|
|
602
|
-
type: "number",
|
|
603
|
-
scope: "safe",
|
|
604
|
-
hotReload: true,
|
|
605
|
-
plugin: { name: "@openacp/security", key: "maxConcurrentSessions" }
|
|
606
|
-
},
|
|
607
|
-
{
|
|
608
|
-
path: "security.sessionTimeoutMinutes",
|
|
609
|
-
displayName: "Session Timeout (min)",
|
|
610
|
-
group: "security",
|
|
611
|
-
type: "number",
|
|
612
|
-
scope: "safe",
|
|
613
|
-
hotReload: true,
|
|
614
|
-
plugin: { name: "@openacp/security", key: "sessionTimeoutMinutes" }
|
|
615
|
-
},
|
|
616
454
|
{
|
|
617
455
|
path: "workspace.baseDir",
|
|
618
456
|
displayName: "Workspace Directory",
|
|
@@ -629,25 +467,6 @@ var init_config_registry = __esm({
|
|
|
629
467
|
scope: "safe",
|
|
630
468
|
hotReload: true
|
|
631
469
|
},
|
|
632
|
-
{
|
|
633
|
-
path: "speech.stt.provider",
|
|
634
|
-
displayName: "Speech to Text",
|
|
635
|
-
group: "speech",
|
|
636
|
-
type: "select",
|
|
637
|
-
options: ["groq"],
|
|
638
|
-
scope: "safe",
|
|
639
|
-
hotReload: true,
|
|
640
|
-
plugin: { name: "@openacp/speech", key: "sttProvider" }
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
path: "speech.stt.apiKey",
|
|
644
|
-
displayName: "STT API Key",
|
|
645
|
-
group: "speech",
|
|
646
|
-
type: "string",
|
|
647
|
-
scope: "sensitive",
|
|
648
|
-
hotReload: true,
|
|
649
|
-
plugin: { name: "@openacp/speech", key: "groqApiKey" }
|
|
650
|
-
},
|
|
651
470
|
{
|
|
652
471
|
path: "agentSwitch.labelHistory",
|
|
653
472
|
displayName: "Label Agent in History",
|
|
@@ -668,36 +487,24 @@ var init_config_registry = __esm({
|
|
|
668
487
|
|
|
669
488
|
// src/core/config/config.ts
|
|
670
489
|
import { z } from "zod";
|
|
671
|
-
import * as
|
|
672
|
-
import * as
|
|
490
|
+
import * as fs4 from "fs";
|
|
491
|
+
import * as path4 from "path";
|
|
673
492
|
import * as os3 from "os";
|
|
493
|
+
import { randomBytes } from "crypto";
|
|
674
494
|
import { EventEmitter } from "events";
|
|
675
495
|
function expandHome3(p) {
|
|
676
496
|
if (p.startsWith("~")) {
|
|
677
|
-
return
|
|
497
|
+
return path4.join(os3.homedir(), p.slice(1));
|
|
678
498
|
}
|
|
679
499
|
return p;
|
|
680
500
|
}
|
|
681
|
-
var log3,
|
|
501
|
+
var log3, LoggingSchema, ConfigSchema, DEFAULT_CONFIG, ConfigManager;
|
|
682
502
|
var init_config = __esm({
|
|
683
503
|
"src/core/config/config.ts"() {
|
|
684
504
|
"use strict";
|
|
685
505
|
init_config_migrations();
|
|
686
506
|
init_log();
|
|
687
507
|
log3 = createChildLogger({ module: "config" });
|
|
688
|
-
BaseChannelSchema = z.object({
|
|
689
|
-
enabled: z.boolean().default(false),
|
|
690
|
-
adapter: z.string().optional(),
|
|
691
|
-
// package name for plugin adapters
|
|
692
|
-
displayVerbosity: z.enum(["low", "medium", "high"]).optional(),
|
|
693
|
-
outputMode: z.enum(["low", "medium", "high"]).optional()
|
|
694
|
-
}).passthrough();
|
|
695
|
-
AgentSchema = z.object({
|
|
696
|
-
command: z.string(),
|
|
697
|
-
args: z.array(z.string()).default([]),
|
|
698
|
-
workingDirectory: z.string().optional(),
|
|
699
|
-
env: z.record(z.string(), z.string()).default({})
|
|
700
|
-
});
|
|
701
508
|
LoggingSchema = z.object({
|
|
702
509
|
level: z.enum(["silent", "debug", "info", "warn", "error", "fatal"]).default("info"),
|
|
703
510
|
logDir: z.string().default("~/.openacp/logs"),
|
|
@@ -705,44 +512,8 @@ var init_config = __esm({
|
|
|
705
512
|
maxFiles: z.number().default(7),
|
|
706
513
|
sessionLogRetentionDays: z.number().default(30)
|
|
707
514
|
}).default({});
|
|
708
|
-
TunnelAuthSchema = z.object({
|
|
709
|
-
enabled: z.boolean().default(false),
|
|
710
|
-
token: z.string().optional()
|
|
711
|
-
}).default({});
|
|
712
|
-
TunnelSchema = z.object({
|
|
713
|
-
enabled: z.boolean().default(false),
|
|
714
|
-
port: z.number().default(3100),
|
|
715
|
-
provider: z.enum(["openacp", "cloudflare", "ngrok", "bore", "tailscale"]).default("openacp"),
|
|
716
|
-
options: z.record(z.string(), z.unknown()).default({}),
|
|
717
|
-
maxUserTunnels: z.number().default(5),
|
|
718
|
-
storeTtlMinutes: z.number().default(60),
|
|
719
|
-
auth: TunnelAuthSchema
|
|
720
|
-
}).default({});
|
|
721
|
-
UsageSchema = z.object({
|
|
722
|
-
enabled: z.boolean().default(true),
|
|
723
|
-
monthlyBudget: z.number().optional(),
|
|
724
|
-
warningThreshold: z.number().default(0.8),
|
|
725
|
-
currency: z.string().default("USD"),
|
|
726
|
-
retentionDays: z.number().default(90)
|
|
727
|
-
}).default({});
|
|
728
|
-
SpeechProviderSchema = z.object({
|
|
729
|
-
apiKey: z.string().min(1).optional(),
|
|
730
|
-
model: z.string().optional()
|
|
731
|
-
}).passthrough();
|
|
732
|
-
SpeechSchema = z.object({
|
|
733
|
-
stt: z.object({
|
|
734
|
-
provider: z.string().nullable().default(null),
|
|
735
|
-
providers: z.record(SpeechProviderSchema).default({})
|
|
736
|
-
}).default({}),
|
|
737
|
-
tts: z.object({
|
|
738
|
-
provider: z.string().nullable().default(null),
|
|
739
|
-
providers: z.record(SpeechProviderSchema).default({})
|
|
740
|
-
}).default({})
|
|
741
|
-
}).optional().default({});
|
|
742
515
|
ConfigSchema = z.object({
|
|
743
516
|
instanceName: z.string().optional(),
|
|
744
|
-
channels: z.object({}).catchall(BaseChannelSchema).default({}),
|
|
745
|
-
agents: z.record(z.string(), AgentSchema).optional().default({}),
|
|
746
517
|
defaultAgent: z.string(),
|
|
747
518
|
workspace: z.object({
|
|
748
519
|
baseDir: z.string().default("~/openacp-workspace"),
|
|
@@ -751,23 +522,12 @@ var init_config = __esm({
|
|
|
751
522
|
envWhitelist: z.array(z.string()).default([])
|
|
752
523
|
}).default({})
|
|
753
524
|
}).default({}),
|
|
754
|
-
security: z.object({
|
|
755
|
-
allowedUserIds: z.array(z.string()).default([]),
|
|
756
|
-
maxConcurrentSessions: z.number().default(20),
|
|
757
|
-
sessionTimeoutMinutes: z.number().default(60)
|
|
758
|
-
}).default({}),
|
|
759
525
|
logging: LoggingSchema,
|
|
760
526
|
runMode: z.enum(["foreground", "daemon"]).default("foreground"),
|
|
761
527
|
autoStart: z.boolean().default(false),
|
|
762
|
-
api: z.object({
|
|
763
|
-
port: z.number().default(21420),
|
|
764
|
-
host: z.string().default("127.0.0.1")
|
|
765
|
-
}).default({}),
|
|
766
528
|
sessionStore: z.object({
|
|
767
529
|
ttlDays: z.number().default(30)
|
|
768
530
|
}).default({}),
|
|
769
|
-
tunnel: TunnelSchema,
|
|
770
|
-
usage: UsageSchema,
|
|
771
531
|
integrations: z.record(
|
|
772
532
|
z.string(),
|
|
773
533
|
z.object({
|
|
@@ -775,51 +535,15 @@ var init_config = __esm({
|
|
|
775
535
|
installedAt: z.string().optional()
|
|
776
536
|
})
|
|
777
537
|
).default({}),
|
|
778
|
-
speech: SpeechSchema,
|
|
779
538
|
outputMode: z.enum(["low", "medium", "high"]).default("medium").optional(),
|
|
780
539
|
agentSwitch: z.object({
|
|
781
540
|
labelHistory: z.boolean().default(true)
|
|
782
541
|
}).default({})
|
|
783
542
|
});
|
|
784
543
|
DEFAULT_CONFIG = {
|
|
785
|
-
channels: {
|
|
786
|
-
telegram: {
|
|
787
|
-
enabled: false,
|
|
788
|
-
botToken: "YOUR_BOT_TOKEN_HERE",
|
|
789
|
-
chatId: 0,
|
|
790
|
-
notificationTopicId: null,
|
|
791
|
-
assistantTopicId: null
|
|
792
|
-
},
|
|
793
|
-
discord: {
|
|
794
|
-
enabled: false,
|
|
795
|
-
botToken: "YOUR_DISCORD_BOT_TOKEN_HERE",
|
|
796
|
-
guildId: "",
|
|
797
|
-
forumChannelId: null,
|
|
798
|
-
notificationChannelId: null,
|
|
799
|
-
assistantThreadId: null
|
|
800
|
-
}
|
|
801
|
-
},
|
|
802
|
-
agents: {
|
|
803
|
-
claude: { command: "claude-agent-acp", args: [], env: {} },
|
|
804
|
-
codex: { command: "codex", args: ["--acp"], env: {} }
|
|
805
|
-
},
|
|
806
544
|
defaultAgent: "claude",
|
|
807
545
|
workspace: { baseDir: "~/openacp-workspace" },
|
|
808
|
-
|
|
809
|
-
allowedUserIds: [],
|
|
810
|
-
maxConcurrentSessions: 20,
|
|
811
|
-
sessionTimeoutMinutes: 60
|
|
812
|
-
},
|
|
813
|
-
sessionStore: { ttlDays: 30 },
|
|
814
|
-
tunnel: {
|
|
815
|
-
enabled: true,
|
|
816
|
-
port: 3100,
|
|
817
|
-
provider: "openacp",
|
|
818
|
-
options: {},
|
|
819
|
-
storeTtlMinutes: 60,
|
|
820
|
-
auth: { enabled: false }
|
|
821
|
-
},
|
|
822
|
-
usage: {}
|
|
546
|
+
sessionStore: { ttlDays: 30 }
|
|
823
547
|
};
|
|
824
548
|
ConfigManager = class extends EventEmitter {
|
|
825
549
|
config;
|
|
@@ -829,23 +553,23 @@ var init_config = __esm({
|
|
|
829
553
|
this.configPath = process.env.OPENACP_CONFIG_PATH || configPath || expandHome3("~/.openacp/config.json");
|
|
830
554
|
}
|
|
831
555
|
async load() {
|
|
832
|
-
const dir =
|
|
833
|
-
|
|
834
|
-
if (!
|
|
835
|
-
|
|
556
|
+
const dir = path4.dirname(this.configPath);
|
|
557
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
558
|
+
if (!fs4.existsSync(this.configPath)) {
|
|
559
|
+
fs4.writeFileSync(
|
|
836
560
|
this.configPath,
|
|
837
561
|
JSON.stringify(DEFAULT_CONFIG, null, 2)
|
|
838
562
|
);
|
|
839
563
|
log3.info({ configPath: this.configPath }, "Config created");
|
|
840
564
|
log3.info(
|
|
841
|
-
"
|
|
565
|
+
"Run 'openacp setup' to configure channels and agents, then restart."
|
|
842
566
|
);
|
|
843
567
|
process.exit(1);
|
|
844
568
|
}
|
|
845
|
-
const raw = JSON.parse(
|
|
846
|
-
const { changed: configUpdated } = applyMigrations(raw, void 0, { configDir:
|
|
569
|
+
const raw = JSON.parse(fs4.readFileSync(this.configPath, "utf-8"));
|
|
570
|
+
const { changed: configUpdated } = applyMigrations(raw, void 0, { configDir: path4.dirname(this.configPath) });
|
|
847
571
|
if (configUpdated) {
|
|
848
|
-
|
|
572
|
+
fs4.writeFileSync(this.configPath, JSON.stringify(raw, null, 2));
|
|
849
573
|
}
|
|
850
574
|
this.applyEnvOverrides(raw);
|
|
851
575
|
const result = ConfigSchema.safeParse(raw);
|
|
@@ -866,14 +590,16 @@ var init_config = __esm({
|
|
|
866
590
|
}
|
|
867
591
|
async save(updates, changePath) {
|
|
868
592
|
const oldConfig = this.config ? structuredClone(this.config) : void 0;
|
|
869
|
-
const raw = JSON.parse(
|
|
593
|
+
const raw = JSON.parse(fs4.readFileSync(this.configPath, "utf-8"));
|
|
870
594
|
this.deepMerge(raw, updates);
|
|
871
595
|
const result = ConfigSchema.safeParse(raw);
|
|
872
596
|
if (!result.success) {
|
|
873
597
|
log3.error({ errors: result.error.issues }, "Config validation failed, not saving");
|
|
874
598
|
return;
|
|
875
599
|
}
|
|
876
|
-
|
|
600
|
+
const tmpPath = this.configPath + `.tmp.${randomBytes(4).toString("hex")}`;
|
|
601
|
+
fs4.writeFileSync(tmpPath, JSON.stringify(raw, null, 2), "utf-8");
|
|
602
|
+
fs4.renameSync(tmpPath, this.configPath);
|
|
877
603
|
this.config = result.data;
|
|
878
604
|
if (changePath) {
|
|
879
605
|
const { getConfigValue: getConfigValue2 } = await Promise.resolve().then(() => (init_config_registry(), config_registry_exports));
|
|
@@ -883,7 +609,7 @@ var init_config = __esm({
|
|
|
883
609
|
}
|
|
884
610
|
}
|
|
885
611
|
/**
|
|
886
|
-
* Set a single config value by dot-path (e.g. "
|
|
612
|
+
* Set a single config value by dot-path (e.g. "logging.level").
|
|
887
613
|
* Builds the nested update object, validates, and saves.
|
|
888
614
|
* Throws if the path contains blocked keys or the value fails Zod validation.
|
|
889
615
|
*/
|
|
@@ -905,14 +631,14 @@ var init_config = __esm({
|
|
|
905
631
|
resolveWorkspace(input2) {
|
|
906
632
|
if (!input2) {
|
|
907
633
|
const resolved2 = expandHome3(this.config.workspace.baseDir);
|
|
908
|
-
|
|
634
|
+
fs4.mkdirSync(resolved2, { recursive: true });
|
|
909
635
|
return resolved2;
|
|
910
636
|
}
|
|
911
637
|
if (input2.startsWith("/") || input2.startsWith("~")) {
|
|
912
638
|
const resolved2 = expandHome3(input2);
|
|
913
639
|
const base = expandHome3(this.config.workspace.baseDir);
|
|
914
|
-
if (resolved2 === base || resolved2.startsWith(base +
|
|
915
|
-
|
|
640
|
+
if (resolved2 === base || resolved2.startsWith(base + path4.sep)) {
|
|
641
|
+
fs4.mkdirSync(resolved2, { recursive: true });
|
|
916
642
|
return resolved2;
|
|
917
643
|
}
|
|
918
644
|
throw new Error(
|
|
@@ -925,23 +651,23 @@ var init_config = __esm({
|
|
|
925
651
|
`Invalid workspace name: "${input2}". Only alphanumeric characters, hyphens, and underscores are allowed.`
|
|
926
652
|
);
|
|
927
653
|
}
|
|
928
|
-
const resolved =
|
|
654
|
+
const resolved = path4.join(
|
|
929
655
|
expandHome3(this.config.workspace.baseDir),
|
|
930
656
|
name.toLowerCase()
|
|
931
657
|
);
|
|
932
|
-
|
|
658
|
+
fs4.mkdirSync(resolved, { recursive: true });
|
|
933
659
|
return resolved;
|
|
934
660
|
}
|
|
935
661
|
async exists() {
|
|
936
|
-
return
|
|
662
|
+
return fs4.existsSync(this.configPath);
|
|
937
663
|
}
|
|
938
664
|
getConfigPath() {
|
|
939
665
|
return this.configPath;
|
|
940
666
|
}
|
|
941
667
|
async writeNew(config) {
|
|
942
|
-
const dir =
|
|
943
|
-
|
|
944
|
-
|
|
668
|
+
const dir = path4.dirname(this.configPath);
|
|
669
|
+
fs4.mkdirSync(dir, { recursive: true });
|
|
670
|
+
fs4.writeFileSync(this.configPath, JSON.stringify(config, null, 2));
|
|
945
671
|
}
|
|
946
672
|
async applyEnvToPluginSettings(settingsManager) {
|
|
947
673
|
const pluginOverrides = [
|
|
@@ -952,7 +678,13 @@ var init_config = __esm({
|
|
|
952
678
|
{ envVar: "OPENACP_SPEECH_STT_PROVIDER", pluginName: "@openacp/speech", key: "sttProvider" },
|
|
953
679
|
{ envVar: "OPENACP_SPEECH_GROQ_API_KEY", pluginName: "@openacp/speech", key: "groqApiKey" },
|
|
954
680
|
{ envVar: "OPENACP_TELEGRAM_BOT_TOKEN", pluginName: "@openacp/telegram", key: "botToken" },
|
|
955
|
-
{ envVar: "OPENACP_TELEGRAM_CHAT_ID", pluginName: "@openacp/telegram", key: "chatId", transform: (v) => Number(v) }
|
|
681
|
+
{ envVar: "OPENACP_TELEGRAM_CHAT_ID", pluginName: "@openacp/telegram", key: "chatId", transform: (v) => Number(v) },
|
|
682
|
+
// Future adapters — no-ops if plugin settings don't exist
|
|
683
|
+
{ envVar: "OPENACP_DISCORD_BOT_TOKEN", pluginName: "@openacp/discord-adapter", key: "botToken" },
|
|
684
|
+
{ envVar: "OPENACP_DISCORD_GUILD_ID", pluginName: "@openacp/discord-adapter", key: "guildId" },
|
|
685
|
+
{ envVar: "OPENACP_SLACK_BOT_TOKEN", pluginName: "@openacp/slack-adapter", key: "botToken" },
|
|
686
|
+
{ envVar: "OPENACP_SLACK_APP_TOKEN", pluginName: "@openacp/slack-adapter", key: "appToken" },
|
|
687
|
+
{ envVar: "OPENACP_SLACK_SIGNING_SECRET", pluginName: "@openacp/slack-adapter", key: "signingSecret" }
|
|
956
688
|
];
|
|
957
689
|
for (const { envVar, pluginName, key, transform } of pluginOverrides) {
|
|
958
690
|
const value = process.env[envVar];
|
|
@@ -965,16 +697,8 @@ var init_config = __esm({
|
|
|
965
697
|
}
|
|
966
698
|
applyEnvOverrides(raw) {
|
|
967
699
|
const overrides = [
|
|
968
|
-
["OPENACP_TELEGRAM_BOT_TOKEN", ["channels", "telegram", "botToken"]],
|
|
969
|
-
["OPENACP_TELEGRAM_CHAT_ID", ["channels", "telegram", "chatId"]],
|
|
970
|
-
["OPENACP_DISCORD_BOT_TOKEN", ["channels", "discord", "botToken"]],
|
|
971
|
-
["OPENACP_DISCORD_GUILD_ID", ["channels", "discord", "guildId"]],
|
|
972
|
-
["OPENACP_SLACK_BOT_TOKEN", ["channels", "slack", "botToken"]],
|
|
973
|
-
["OPENACP_SLACK_APP_TOKEN", ["channels", "slack", "appToken"]],
|
|
974
|
-
["OPENACP_SLACK_SIGNING_SECRET", ["channels", "slack", "signingSecret"]],
|
|
975
700
|
["OPENACP_DEFAULT_AGENT", ["defaultAgent"]],
|
|
976
|
-
["OPENACP_RUN_MODE", ["runMode"]]
|
|
977
|
-
["OPENACP_API_PORT", ["api", "port"]]
|
|
701
|
+
["OPENACP_RUN_MODE", ["runMode"]]
|
|
978
702
|
];
|
|
979
703
|
for (const [envVar, configPath] of overrides) {
|
|
980
704
|
const value = process.env[envVar];
|
|
@@ -985,7 +709,7 @@ var init_config = __esm({
|
|
|
985
709
|
target = target[configPath[i]];
|
|
986
710
|
}
|
|
987
711
|
const key = configPath[configPath.length - 1];
|
|
988
|
-
target[key] =
|
|
712
|
+
target[key] = value;
|
|
989
713
|
}
|
|
990
714
|
}
|
|
991
715
|
if (process.env.OPENACP_LOG_LEVEL) {
|
|
@@ -1000,39 +724,11 @@ var init_config = __esm({
|
|
|
1000
724
|
raw.logging = raw.logging || {};
|
|
1001
725
|
raw.logging.level = "debug";
|
|
1002
726
|
}
|
|
1003
|
-
if (process.env.OPENACP_TUNNEL_ENABLED) {
|
|
1004
|
-
raw.tunnel = raw.tunnel || {};
|
|
1005
|
-
raw.tunnel.enabled = process.env.OPENACP_TUNNEL_ENABLED === "true";
|
|
1006
|
-
}
|
|
1007
|
-
if (process.env.OPENACP_TUNNEL_PORT) {
|
|
1008
|
-
raw.tunnel = raw.tunnel || {};
|
|
1009
|
-
raw.tunnel.port = Number(
|
|
1010
|
-
process.env.OPENACP_TUNNEL_PORT
|
|
1011
|
-
);
|
|
1012
|
-
}
|
|
1013
|
-
if (process.env.OPENACP_TUNNEL_PROVIDER) {
|
|
1014
|
-
raw.tunnel = raw.tunnel || {};
|
|
1015
|
-
raw.tunnel.provider = process.env.OPENACP_TUNNEL_PROVIDER;
|
|
1016
|
-
}
|
|
1017
|
-
if (process.env.OPENACP_SPEECH_STT_PROVIDER) {
|
|
1018
|
-
raw.speech = raw.speech || {};
|
|
1019
|
-
const speech = raw.speech;
|
|
1020
|
-
speech.stt = speech.stt || {};
|
|
1021
|
-
speech.stt.provider = process.env.OPENACP_SPEECH_STT_PROVIDER;
|
|
1022
|
-
}
|
|
1023
|
-
if (process.env.OPENACP_SPEECH_GROQ_API_KEY) {
|
|
1024
|
-
raw.speech = raw.speech || {};
|
|
1025
|
-
const speech = raw.speech;
|
|
1026
|
-
speech.stt = speech.stt || {};
|
|
1027
|
-
const stt = speech.stt;
|
|
1028
|
-
stt.providers = stt.providers || {};
|
|
1029
|
-
const providers = stt.providers;
|
|
1030
|
-
providers.groq = providers.groq || {};
|
|
1031
|
-
providers.groq.apiKey = process.env.OPENACP_SPEECH_GROQ_API_KEY;
|
|
1032
|
-
}
|
|
1033
727
|
}
|
|
1034
728
|
deepMerge(target, source) {
|
|
729
|
+
const DANGEROUS_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1035
730
|
for (const key of Object.keys(source)) {
|
|
731
|
+
if (DANGEROUS_KEYS.has(key)) continue;
|
|
1036
732
|
const val = source[key];
|
|
1037
733
|
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
1038
734
|
if (!target[key]) target[key] = {};
|
|
@@ -1054,9 +750,9 @@ var read_text_file_exports = {};
|
|
|
1054
750
|
__export(read_text_file_exports, {
|
|
1055
751
|
readTextFileWithRange: () => readTextFileWithRange
|
|
1056
752
|
});
|
|
1057
|
-
import
|
|
753
|
+
import fs6 from "fs";
|
|
1058
754
|
async function readTextFileWithRange(filePath, options) {
|
|
1059
|
-
const content = await
|
|
755
|
+
const content = await fs6.promises.readFile(filePath, "utf-8");
|
|
1060
756
|
if (!options?.line && !options?.limit) return content;
|
|
1061
757
|
const lines = content.split("\n");
|
|
1062
758
|
const start = Math.max(0, (options.line ?? 1) - 1);
|
|
@@ -1096,8 +792,8 @@ __export(agent_dependencies_exports, {
|
|
|
1096
792
|
listAgentsWithIntegration: () => listAgentsWithIntegration
|
|
1097
793
|
});
|
|
1098
794
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
1099
|
-
import * as
|
|
1100
|
-
import * as
|
|
795
|
+
import * as fs11 from "fs";
|
|
796
|
+
import * as path10 from "path";
|
|
1101
797
|
function getAgentSetup(registryId) {
|
|
1102
798
|
return AGENT_SETUP[registryId];
|
|
1103
799
|
}
|
|
@@ -1121,9 +817,9 @@ function commandExists(cmd) {
|
|
|
1121
817
|
}
|
|
1122
818
|
let dir = process.cwd();
|
|
1123
819
|
while (true) {
|
|
1124
|
-
const binPath =
|
|
1125
|
-
if (
|
|
1126
|
-
const parent =
|
|
820
|
+
const binPath = path10.join(dir, "node_modules", ".bin", cmd);
|
|
821
|
+
if (fs11.existsSync(binPath)) return true;
|
|
822
|
+
const parent = path10.dirname(dir);
|
|
1127
823
|
if (parent === dir) break;
|
|
1128
824
|
dir = parent;
|
|
1129
825
|
}
|
|
@@ -1291,6 +987,7 @@ var init_agent_dependencies = __esm({
|
|
|
1291
987
|
supportsResume: true,
|
|
1292
988
|
resumeCommand: (sid) => `claude --resume ${sid}`,
|
|
1293
989
|
integration: {
|
|
990
|
+
strategy: "hooks",
|
|
1294
991
|
hookEvent: "UserPromptSubmit",
|
|
1295
992
|
settingsPath: "~/.claude/settings.json",
|
|
1296
993
|
settingsFormat: "settings_json",
|
|
@@ -1308,6 +1005,7 @@ var init_agent_dependencies = __esm({
|
|
|
1308
1005
|
supportsResume: true,
|
|
1309
1006
|
resumeCommand: (sid) => `cursor --resume ${sid}`,
|
|
1310
1007
|
integration: {
|
|
1008
|
+
strategy: "hooks",
|
|
1311
1009
|
hookEvent: "beforeSubmitPrompt",
|
|
1312
1010
|
settingsPath: "~/.cursor/hooks.json",
|
|
1313
1011
|
settingsFormat: "hooks_json",
|
|
@@ -1323,6 +1021,7 @@ var init_agent_dependencies = __esm({
|
|
|
1323
1021
|
supportsResume: true,
|
|
1324
1022
|
resumeCommand: (sid) => `gemini --resume ${sid}`,
|
|
1325
1023
|
integration: {
|
|
1024
|
+
strategy: "hooks",
|
|
1326
1025
|
hookEvent: "BeforeAgent",
|
|
1327
1026
|
settingsPath: "~/.gemini/settings.json",
|
|
1328
1027
|
settingsFormat: "settings_json",
|
|
@@ -1335,6 +1034,7 @@ var init_agent_dependencies = __esm({
|
|
|
1335
1034
|
supportsResume: true,
|
|
1336
1035
|
resumeCommand: () => `cline --continue`,
|
|
1337
1036
|
integration: {
|
|
1037
|
+
strategy: "hooks",
|
|
1338
1038
|
hookEvent: "TaskStart",
|
|
1339
1039
|
settingsPath: "~/.cline/settings.json",
|
|
1340
1040
|
settingsFormat: "settings_json",
|
|
@@ -1354,6 +1054,19 @@ var init_agent_dependencies = __esm({
|
|
|
1354
1054
|
amp: {
|
|
1355
1055
|
supportsResume: true,
|
|
1356
1056
|
resumeCommand: (sid) => `amp threads continue ${sid}`
|
|
1057
|
+
},
|
|
1058
|
+
opencode: {
|
|
1059
|
+
supportsResume: true,
|
|
1060
|
+
resumeCommand: (sid) => `opencode --session ${sid}`,
|
|
1061
|
+
integration: {
|
|
1062
|
+
strategy: "plugin",
|
|
1063
|
+
pluginProvider: "opencode",
|
|
1064
|
+
commandsPath: "~/.config/opencode/commands/",
|
|
1065
|
+
pluginsPath: "~/.config/opencode/plugins/",
|
|
1066
|
+
handoffCommandName: "openacp:handoff",
|
|
1067
|
+
handoffCommandFile: "openacp-handoff.md",
|
|
1068
|
+
pluginFileName: "openacp-handoff.js"
|
|
1069
|
+
}
|
|
1357
1070
|
}
|
|
1358
1071
|
};
|
|
1359
1072
|
REGISTRY_AGENT_ALIASES = {
|
|
@@ -1379,11 +1092,11 @@ var init_agent_registry = __esm({
|
|
|
1379
1092
|
});
|
|
1380
1093
|
|
|
1381
1094
|
// src/core/agents/agent-installer.ts
|
|
1382
|
-
import * as
|
|
1383
|
-
import * as
|
|
1095
|
+
import * as fs13 from "fs";
|
|
1096
|
+
import * as path12 from "path";
|
|
1384
1097
|
import * as os5 from "os";
|
|
1385
1098
|
import crypto from "crypto";
|
|
1386
|
-
function
|
|
1099
|
+
function validateArchiveContents(entries, destDir) {
|
|
1387
1100
|
for (const entry of entries) {
|
|
1388
1101
|
const segments = entry.split("/");
|
|
1389
1102
|
if (segments.includes("..")) {
|
|
@@ -1395,9 +1108,9 @@ function validateTarContents(entries, destDir) {
|
|
|
1395
1108
|
}
|
|
1396
1109
|
}
|
|
1397
1110
|
function validateUninstallPath(binaryPath, agentsDir) {
|
|
1398
|
-
const realPath =
|
|
1399
|
-
const realAgentsDir =
|
|
1400
|
-
if (!realPath.startsWith(realAgentsDir +
|
|
1111
|
+
const realPath = path12.resolve(binaryPath);
|
|
1112
|
+
const realAgentsDir = path12.resolve(agentsDir);
|
|
1113
|
+
if (!realPath.startsWith(realAgentsDir + path12.sep) && realPath !== realAgentsDir) {
|
|
1401
1114
|
throw new Error(`Refusing to delete path outside agents directory: ${realPath}`);
|
|
1402
1115
|
}
|
|
1403
1116
|
}
|
|
@@ -1451,7 +1164,7 @@ function buildInstalledAgent(registryId, name, version, dist, binaryPath) {
|
|
|
1451
1164
|
binaryPath: null
|
|
1452
1165
|
};
|
|
1453
1166
|
}
|
|
1454
|
-
const absCmd =
|
|
1167
|
+
const absCmd = path12.resolve(binaryPath, dist.cmd);
|
|
1455
1168
|
return {
|
|
1456
1169
|
registryId,
|
|
1457
1170
|
name,
|
|
@@ -1524,8 +1237,8 @@ Install it with: pip install uv`;
|
|
|
1524
1237
|
return { ok: true, agentKey, setupSteps: setup?.setupSteps };
|
|
1525
1238
|
}
|
|
1526
1239
|
async function downloadAndExtract(agentId, archiveUrl, progress, agentsDir) {
|
|
1527
|
-
const destDir =
|
|
1528
|
-
|
|
1240
|
+
const destDir = path12.join(agentsDir ?? DEFAULT_AGENTS_DIR, agentId);
|
|
1241
|
+
fs13.mkdirSync(destDir, { recursive: true });
|
|
1529
1242
|
await progress?.onStep("Downloading...");
|
|
1530
1243
|
log11.info({ agentId, url: archiveUrl }, "Downloading agent binary");
|
|
1531
1244
|
const response = await fetch(archiveUrl);
|
|
@@ -1566,68 +1279,72 @@ async function readResponseWithProgress(response, contentLength, progress) {
|
|
|
1566
1279
|
return Buffer.concat(chunks);
|
|
1567
1280
|
}
|
|
1568
1281
|
function validateExtractedPaths(destDir) {
|
|
1569
|
-
const realDest =
|
|
1570
|
-
const entries =
|
|
1282
|
+
const realDest = fs13.realpathSync(destDir);
|
|
1283
|
+
const entries = fs13.readdirSync(destDir, { recursive: true, withFileTypes: true });
|
|
1571
1284
|
for (const entry of entries) {
|
|
1572
1285
|
const dirent = entry;
|
|
1573
1286
|
const parentPath = dirent.parentPath ?? dirent.path ?? destDir;
|
|
1574
|
-
const fullPath =
|
|
1287
|
+
const fullPath = path12.join(parentPath, entry.name);
|
|
1575
1288
|
let realPath;
|
|
1576
1289
|
try {
|
|
1577
|
-
realPath =
|
|
1290
|
+
realPath = fs13.realpathSync(fullPath);
|
|
1578
1291
|
} catch {
|
|
1579
|
-
const linkTarget =
|
|
1580
|
-
realPath =
|
|
1292
|
+
const linkTarget = fs13.readlinkSync(fullPath);
|
|
1293
|
+
realPath = path12.resolve(path12.dirname(fullPath), linkTarget);
|
|
1581
1294
|
}
|
|
1582
|
-
if (!realPath.startsWith(realDest +
|
|
1583
|
-
|
|
1295
|
+
if (!realPath.startsWith(realDest + path12.sep) && realPath !== realDest) {
|
|
1296
|
+
fs13.rmSync(destDir, { recursive: true, force: true });
|
|
1584
1297
|
throw new Error(`Archive contains unsafe path: ${entry.name}`);
|
|
1585
1298
|
}
|
|
1586
1299
|
}
|
|
1587
1300
|
}
|
|
1588
1301
|
async function extractTarGz(buffer, destDir) {
|
|
1589
|
-
const { execFileSync:
|
|
1590
|
-
const tmpFile =
|
|
1591
|
-
|
|
1302
|
+
const { execFileSync: execFileSync8 } = await import("child_process");
|
|
1303
|
+
const tmpFile = path12.join(destDir, "_archive.tar.gz");
|
|
1304
|
+
fs13.writeFileSync(tmpFile, buffer);
|
|
1592
1305
|
try {
|
|
1593
|
-
const listing =
|
|
1594
|
-
|
|
1595
|
-
|
|
1306
|
+
const listing = execFileSync8("tar", ["tf", tmpFile], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
1307
|
+
validateArchiveContents(listing, destDir);
|
|
1308
|
+
execFileSync8("tar", ["xzf", tmpFile, "-C", destDir], { stdio: "pipe" });
|
|
1596
1309
|
} finally {
|
|
1597
|
-
|
|
1310
|
+
fs13.unlinkSync(tmpFile);
|
|
1598
1311
|
}
|
|
1599
1312
|
validateExtractedPaths(destDir);
|
|
1600
1313
|
}
|
|
1601
1314
|
async function extractZip(buffer, destDir) {
|
|
1602
|
-
const { execFileSync:
|
|
1603
|
-
const tmpFile =
|
|
1604
|
-
|
|
1315
|
+
const { execFileSync: execFileSync8 } = await import("child_process");
|
|
1316
|
+
const tmpFile = path12.join(destDir, "_archive.zip");
|
|
1317
|
+
fs13.writeFileSync(tmpFile, buffer);
|
|
1605
1318
|
try {
|
|
1606
|
-
|
|
1319
|
+
const listing = execFileSync8("unzip", ["-l", tmpFile], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
1320
|
+
const entries = listing.slice(3, -2).map((line) => line.trim().split(/\s+/).slice(3).join(" ")).filter(Boolean);
|
|
1321
|
+
validateArchiveContents(entries, destDir);
|
|
1322
|
+
execFileSync8("unzip", ["-o", tmpFile, "-d", destDir], { stdio: "pipe" });
|
|
1607
1323
|
} finally {
|
|
1608
|
-
|
|
1324
|
+
fs13.unlinkSync(tmpFile);
|
|
1609
1325
|
}
|
|
1610
1326
|
validateExtractedPaths(destDir);
|
|
1611
1327
|
}
|
|
1612
1328
|
async function uninstallAgent(agentKey, store, agentsDir) {
|
|
1613
1329
|
const agent = store.getAgent(agentKey);
|
|
1614
1330
|
if (!agent) return;
|
|
1615
|
-
if (agent.binaryPath &&
|
|
1331
|
+
if (agent.binaryPath && fs13.existsSync(agent.binaryPath)) {
|
|
1616
1332
|
validateUninstallPath(agent.binaryPath, agentsDir ?? DEFAULT_AGENTS_DIR);
|
|
1617
|
-
|
|
1333
|
+
fs13.rmSync(agent.binaryPath, { recursive: true, force: true });
|
|
1618
1334
|
log11.info({ agentKey, binaryPath: agent.binaryPath }, "Deleted agent binary");
|
|
1619
1335
|
}
|
|
1620
1336
|
store.removeAgent(agentKey);
|
|
1621
1337
|
}
|
|
1622
|
-
var log11, DEFAULT_AGENTS_DIR, MAX_DOWNLOAD_SIZE, ARCH_MAP, PLATFORM_MAP;
|
|
1338
|
+
var log11, DEFAULT_AGENTS_DIR, MAX_DOWNLOAD_SIZE, validateTarContents, ARCH_MAP, PLATFORM_MAP;
|
|
1623
1339
|
var init_agent_installer = __esm({
|
|
1624
1340
|
"src/core/agents/agent-installer.ts"() {
|
|
1625
1341
|
"use strict";
|
|
1626
1342
|
init_log();
|
|
1627
1343
|
init_agent_dependencies();
|
|
1628
1344
|
log11 = createChildLogger({ module: "agent-installer" });
|
|
1629
|
-
DEFAULT_AGENTS_DIR =
|
|
1345
|
+
DEFAULT_AGENTS_DIR = path12.join(os5.homedir(), ".openacp", "agents");
|
|
1630
1346
|
MAX_DOWNLOAD_SIZE = 500 * 1024 * 1024;
|
|
1347
|
+
validateTarContents = validateArchiveContents;
|
|
1631
1348
|
ARCH_MAP = {
|
|
1632
1349
|
arm64: "aarch64",
|
|
1633
1350
|
x64: "x86_64"
|
|
@@ -1641,7 +1358,7 @@ var init_agent_installer = __esm({
|
|
|
1641
1358
|
});
|
|
1642
1359
|
|
|
1643
1360
|
// src/core/doctor/checks/config.ts
|
|
1644
|
-
import * as
|
|
1361
|
+
import * as fs16 from "fs";
|
|
1645
1362
|
var configCheck;
|
|
1646
1363
|
var init_config2 = __esm({
|
|
1647
1364
|
"src/core/doctor/checks/config.ts"() {
|
|
@@ -1653,14 +1370,14 @@ var init_config2 = __esm({
|
|
|
1653
1370
|
order: 1,
|
|
1654
1371
|
async run(ctx) {
|
|
1655
1372
|
const results = [];
|
|
1656
|
-
if (!
|
|
1373
|
+
if (!fs16.existsSync(ctx.configPath)) {
|
|
1657
1374
|
results.push({ status: "fail", message: "Config file not found" });
|
|
1658
1375
|
return results;
|
|
1659
1376
|
}
|
|
1660
1377
|
results.push({ status: "pass", message: "Config file exists" });
|
|
1661
1378
|
let raw;
|
|
1662
1379
|
try {
|
|
1663
|
-
raw = JSON.parse(
|
|
1380
|
+
raw = JSON.parse(fs16.readFileSync(ctx.configPath, "utf-8"));
|
|
1664
1381
|
} catch (err) {
|
|
1665
1382
|
results.push({
|
|
1666
1383
|
status: "fail",
|
|
@@ -1679,7 +1396,7 @@ var init_config2 = __esm({
|
|
|
1679
1396
|
fixRisk: "safe",
|
|
1680
1397
|
fix: async () => {
|
|
1681
1398
|
applyMigrations(raw);
|
|
1682
|
-
|
|
1399
|
+
fs16.writeFileSync(ctx.configPath, JSON.stringify(raw, null, 2));
|
|
1683
1400
|
return { success: true, message: "applied migrations" };
|
|
1684
1401
|
}
|
|
1685
1402
|
});
|
|
@@ -1703,8 +1420,8 @@ var init_config2 = __esm({
|
|
|
1703
1420
|
|
|
1704
1421
|
// src/core/doctor/checks/agents.ts
|
|
1705
1422
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
1706
|
-
import * as
|
|
1707
|
-
import * as
|
|
1423
|
+
import * as fs17 from "fs";
|
|
1424
|
+
import * as path17 from "path";
|
|
1708
1425
|
function commandExists2(cmd) {
|
|
1709
1426
|
try {
|
|
1710
1427
|
execFileSync3("which", [cmd], { stdio: "pipe" });
|
|
@@ -1713,9 +1430,9 @@ function commandExists2(cmd) {
|
|
|
1713
1430
|
}
|
|
1714
1431
|
let dir = process.cwd();
|
|
1715
1432
|
while (true) {
|
|
1716
|
-
const binPath =
|
|
1717
|
-
if (
|
|
1718
|
-
const parent =
|
|
1433
|
+
const binPath = path17.join(dir, "node_modules", ".bin", cmd);
|
|
1434
|
+
if (fs17.existsSync(binPath)) return true;
|
|
1435
|
+
const parent = path17.dirname(dir);
|
|
1719
1436
|
if (parent === dir) break;
|
|
1720
1437
|
dir = parent;
|
|
1721
1438
|
}
|
|
@@ -1734,8 +1451,16 @@ var init_agents = __esm({
|
|
|
1734
1451
|
results.push({ status: "fail", message: "Cannot check agents \u2014 config not loaded" });
|
|
1735
1452
|
return results;
|
|
1736
1453
|
}
|
|
1737
|
-
const agents = ctx.config.agents;
|
|
1738
1454
|
const defaultAgent = ctx.config.defaultAgent;
|
|
1455
|
+
let agents = {};
|
|
1456
|
+
try {
|
|
1457
|
+
const agentsPath = path17.join(ctx.dataDir, "agents.json");
|
|
1458
|
+
if (fs17.existsSync(agentsPath)) {
|
|
1459
|
+
const data = JSON.parse(fs17.readFileSync(agentsPath, "utf-8"));
|
|
1460
|
+
agents = data.installed ?? {};
|
|
1461
|
+
}
|
|
1462
|
+
} catch {
|
|
1463
|
+
}
|
|
1739
1464
|
if (!agents[defaultAgent]) {
|
|
1740
1465
|
results.push({
|
|
1741
1466
|
status: "fail",
|
|
@@ -1744,15 +1469,17 @@ var init_agents = __esm({
|
|
|
1744
1469
|
}
|
|
1745
1470
|
for (const [name, agent] of Object.entries(agents)) {
|
|
1746
1471
|
const isDefault = name === defaultAgent;
|
|
1747
|
-
|
|
1472
|
+
const agentEntry = agent;
|
|
1473
|
+
const agentCommand = agentEntry.command ?? name;
|
|
1474
|
+
if (commandExists2(agentCommand)) {
|
|
1748
1475
|
results.push({
|
|
1749
1476
|
status: "pass",
|
|
1750
|
-
message: `${
|
|
1477
|
+
message: `${agentCommand} found${isDefault ? " (default)" : ""}`
|
|
1751
1478
|
});
|
|
1752
1479
|
} else {
|
|
1753
1480
|
results.push({
|
|
1754
1481
|
status: isDefault ? "fail" : "warn",
|
|
1755
|
-
message: `${
|
|
1482
|
+
message: `${agentCommand} not found in PATH${isDefault ? " (default agent!)" : ""}`
|
|
1756
1483
|
});
|
|
1757
1484
|
}
|
|
1758
1485
|
}
|
|
@@ -1767,8 +1494,8 @@ var settings_manager_exports = {};
|
|
|
1767
1494
|
__export(settings_manager_exports, {
|
|
1768
1495
|
SettingsManager: () => SettingsManager
|
|
1769
1496
|
});
|
|
1770
|
-
import
|
|
1771
|
-
import
|
|
1497
|
+
import fs18 from "fs";
|
|
1498
|
+
import path18 from "path";
|
|
1772
1499
|
var SettingsManager, SettingsAPIImpl;
|
|
1773
1500
|
var init_settings_manager = __esm({
|
|
1774
1501
|
"src/core/plugin/settings-manager.ts"() {
|
|
@@ -1787,7 +1514,7 @@ var init_settings_manager = __esm({
|
|
|
1787
1514
|
async loadSettings(pluginName) {
|
|
1788
1515
|
const settingsPath = this.getSettingsPath(pluginName);
|
|
1789
1516
|
try {
|
|
1790
|
-
const content =
|
|
1517
|
+
const content = fs18.readFileSync(settingsPath, "utf-8");
|
|
1791
1518
|
return JSON.parse(content);
|
|
1792
1519
|
} catch {
|
|
1793
1520
|
return {};
|
|
@@ -1805,7 +1532,7 @@ var init_settings_manager = __esm({
|
|
|
1805
1532
|
};
|
|
1806
1533
|
}
|
|
1807
1534
|
getSettingsPath(pluginName) {
|
|
1808
|
-
return
|
|
1535
|
+
return path18.join(this.basePath, pluginName, "settings.json");
|
|
1809
1536
|
}
|
|
1810
1537
|
async getPluginSettings(pluginName) {
|
|
1811
1538
|
return this.loadSettings(pluginName);
|
|
@@ -1824,7 +1551,7 @@ var init_settings_manager = __esm({
|
|
|
1824
1551
|
readFile() {
|
|
1825
1552
|
if (this.cache !== null) return this.cache;
|
|
1826
1553
|
try {
|
|
1827
|
-
const content =
|
|
1554
|
+
const content = fs18.readFileSync(this.settingsPath, "utf-8");
|
|
1828
1555
|
this.cache = JSON.parse(content);
|
|
1829
1556
|
return this.cache;
|
|
1830
1557
|
} catch {
|
|
@@ -1833,9 +1560,9 @@ var init_settings_manager = __esm({
|
|
|
1833
1560
|
}
|
|
1834
1561
|
}
|
|
1835
1562
|
writeFile(data) {
|
|
1836
|
-
const dir =
|
|
1837
|
-
|
|
1838
|
-
|
|
1563
|
+
const dir = path18.dirname(this.settingsPath);
|
|
1564
|
+
fs18.mkdirSync(dir, { recursive: true });
|
|
1565
|
+
fs18.writeFileSync(this.settingsPath, JSON.stringify(data, null, 2));
|
|
1839
1566
|
this.cache = data;
|
|
1840
1567
|
}
|
|
1841
1568
|
async get(key) {
|
|
@@ -1870,7 +1597,7 @@ var init_settings_manager = __esm({
|
|
|
1870
1597
|
});
|
|
1871
1598
|
|
|
1872
1599
|
// src/core/doctor/checks/telegram.ts
|
|
1873
|
-
import * as
|
|
1600
|
+
import * as path19 from "path";
|
|
1874
1601
|
var BOT_TOKEN_REGEX, telegramCheck;
|
|
1875
1602
|
var init_telegram = __esm({
|
|
1876
1603
|
"src/core/doctor/checks/telegram.ts"() {
|
|
@@ -1886,11 +1613,10 @@ var init_telegram = __esm({
|
|
|
1886
1613
|
return results;
|
|
1887
1614
|
}
|
|
1888
1615
|
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
1889
|
-
const sm = new SettingsManager2(
|
|
1616
|
+
const sm = new SettingsManager2(path19.join(ctx.pluginsDir, "data"));
|
|
1890
1617
|
const ps = await sm.loadSettings("@openacp/telegram");
|
|
1891
|
-
const
|
|
1892
|
-
const
|
|
1893
|
-
const chatId = ps.chatId ?? legacyCh?.chatId;
|
|
1618
|
+
const botToken = ps.botToken;
|
|
1619
|
+
const chatId = ps.chatId;
|
|
1894
1620
|
if (!botToken && !chatId) {
|
|
1895
1621
|
results.push({ status: "pass", message: "Telegram not configured (skipped)" });
|
|
1896
1622
|
return results;
|
|
@@ -1970,7 +1696,7 @@ var init_telegram = __esm({
|
|
|
1970
1696
|
});
|
|
1971
1697
|
|
|
1972
1698
|
// src/core/doctor/checks/storage.ts
|
|
1973
|
-
import * as
|
|
1699
|
+
import * as fs19 from "fs";
|
|
1974
1700
|
var storageCheck;
|
|
1975
1701
|
var init_storage = __esm({
|
|
1976
1702
|
"src/core/doctor/checks/storage.ts"() {
|
|
@@ -1980,28 +1706,28 @@ var init_storage = __esm({
|
|
|
1980
1706
|
order: 4,
|
|
1981
1707
|
async run(ctx) {
|
|
1982
1708
|
const results = [];
|
|
1983
|
-
if (!
|
|
1709
|
+
if (!fs19.existsSync(ctx.dataDir)) {
|
|
1984
1710
|
results.push({
|
|
1985
1711
|
status: "fail",
|
|
1986
1712
|
message: "Data directory ~/.openacp does not exist",
|
|
1987
1713
|
fixable: true,
|
|
1988
1714
|
fixRisk: "safe",
|
|
1989
1715
|
fix: async () => {
|
|
1990
|
-
|
|
1716
|
+
fs19.mkdirSync(ctx.dataDir, { recursive: true });
|
|
1991
1717
|
return { success: true, message: "created directory" };
|
|
1992
1718
|
}
|
|
1993
1719
|
});
|
|
1994
1720
|
} else {
|
|
1995
1721
|
try {
|
|
1996
|
-
|
|
1722
|
+
fs19.accessSync(ctx.dataDir, fs19.constants.W_OK);
|
|
1997
1723
|
results.push({ status: "pass", message: "Data directory exists and writable" });
|
|
1998
1724
|
} catch {
|
|
1999
1725
|
results.push({ status: "fail", message: "Data directory not writable" });
|
|
2000
1726
|
}
|
|
2001
1727
|
}
|
|
2002
|
-
if (
|
|
1728
|
+
if (fs19.existsSync(ctx.sessionsPath)) {
|
|
2003
1729
|
try {
|
|
2004
|
-
const content =
|
|
1730
|
+
const content = fs19.readFileSync(ctx.sessionsPath, "utf-8");
|
|
2005
1731
|
const data = JSON.parse(content);
|
|
2006
1732
|
if (typeof data === "object" && data !== null && "sessions" in data) {
|
|
2007
1733
|
results.push({ status: "pass", message: "Sessions file valid" });
|
|
@@ -2012,7 +1738,7 @@ var init_storage = __esm({
|
|
|
2012
1738
|
fixable: true,
|
|
2013
1739
|
fixRisk: "risky",
|
|
2014
1740
|
fix: async () => {
|
|
2015
|
-
|
|
1741
|
+
fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
2016
1742
|
return { success: true, message: "reset sessions file" };
|
|
2017
1743
|
}
|
|
2018
1744
|
});
|
|
@@ -2024,7 +1750,7 @@ var init_storage = __esm({
|
|
|
2024
1750
|
fixable: true,
|
|
2025
1751
|
fixRisk: "risky",
|
|
2026
1752
|
fix: async () => {
|
|
2027
|
-
|
|
1753
|
+
fs19.writeFileSync(ctx.sessionsPath, JSON.stringify({ version: 1, sessions: {} }, null, 2));
|
|
2028
1754
|
return { success: true, message: "reset sessions file" };
|
|
2029
1755
|
}
|
|
2030
1756
|
});
|
|
@@ -2032,20 +1758,20 @@ var init_storage = __esm({
|
|
|
2032
1758
|
} else {
|
|
2033
1759
|
results.push({ status: "pass", message: "Sessions file not present yet (created on first session)" });
|
|
2034
1760
|
}
|
|
2035
|
-
if (!
|
|
1761
|
+
if (!fs19.existsSync(ctx.logsDir)) {
|
|
2036
1762
|
results.push({
|
|
2037
1763
|
status: "warn",
|
|
2038
1764
|
message: "Log directory does not exist",
|
|
2039
1765
|
fixable: true,
|
|
2040
1766
|
fixRisk: "safe",
|
|
2041
1767
|
fix: async () => {
|
|
2042
|
-
|
|
1768
|
+
fs19.mkdirSync(ctx.logsDir, { recursive: true });
|
|
2043
1769
|
return { success: true, message: "created log directory" };
|
|
2044
1770
|
}
|
|
2045
1771
|
});
|
|
2046
1772
|
} else {
|
|
2047
1773
|
try {
|
|
2048
|
-
|
|
1774
|
+
fs19.accessSync(ctx.logsDir, fs19.constants.W_OK);
|
|
2049
1775
|
results.push({ status: "pass", message: "Log directory exists and writable" });
|
|
2050
1776
|
} catch {
|
|
2051
1777
|
results.push({ status: "fail", message: "Log directory not writable" });
|
|
@@ -2058,7 +1784,7 @@ var init_storage = __esm({
|
|
|
2058
1784
|
});
|
|
2059
1785
|
|
|
2060
1786
|
// src/core/doctor/checks/workspace.ts
|
|
2061
|
-
import * as
|
|
1787
|
+
import * as fs20 from "fs";
|
|
2062
1788
|
var workspaceCheck;
|
|
2063
1789
|
var init_workspace = __esm({
|
|
2064
1790
|
"src/core/doctor/checks/workspace.ts"() {
|
|
@@ -2074,20 +1800,20 @@ var init_workspace = __esm({
|
|
|
2074
1800
|
return results;
|
|
2075
1801
|
}
|
|
2076
1802
|
const baseDir = expandHome3(ctx.config.workspace.baseDir);
|
|
2077
|
-
if (!
|
|
1803
|
+
if (!fs20.existsSync(baseDir)) {
|
|
2078
1804
|
results.push({
|
|
2079
1805
|
status: "warn",
|
|
2080
1806
|
message: `Workspace directory does not exist: ${baseDir}`,
|
|
2081
1807
|
fixable: true,
|
|
2082
1808
|
fixRisk: "safe",
|
|
2083
1809
|
fix: async () => {
|
|
2084
|
-
|
|
1810
|
+
fs20.mkdirSync(baseDir, { recursive: true });
|
|
2085
1811
|
return { success: true, message: "created directory" };
|
|
2086
1812
|
}
|
|
2087
1813
|
});
|
|
2088
1814
|
} else {
|
|
2089
1815
|
try {
|
|
2090
|
-
|
|
1816
|
+
fs20.accessSync(baseDir, fs20.constants.W_OK);
|
|
2091
1817
|
results.push({ status: "pass", message: `Workspace directory exists: ${baseDir}` });
|
|
2092
1818
|
} catch {
|
|
2093
1819
|
results.push({ status: "fail", message: `Workspace directory not writable: ${baseDir}` });
|
|
@@ -2100,8 +1826,8 @@ var init_workspace = __esm({
|
|
|
2100
1826
|
});
|
|
2101
1827
|
|
|
2102
1828
|
// src/core/doctor/checks/plugins.ts
|
|
2103
|
-
import * as
|
|
2104
|
-
import * as
|
|
1829
|
+
import * as fs21 from "fs";
|
|
1830
|
+
import * as path20 from "path";
|
|
2105
1831
|
var pluginsCheck;
|
|
2106
1832
|
var init_plugins = __esm({
|
|
2107
1833
|
"src/core/doctor/checks/plugins.ts"() {
|
|
@@ -2111,16 +1837,16 @@ var init_plugins = __esm({
|
|
|
2111
1837
|
order: 6,
|
|
2112
1838
|
async run(ctx) {
|
|
2113
1839
|
const results = [];
|
|
2114
|
-
if (!
|
|
1840
|
+
if (!fs21.existsSync(ctx.pluginsDir)) {
|
|
2115
1841
|
results.push({
|
|
2116
1842
|
status: "warn",
|
|
2117
1843
|
message: "Plugins directory does not exist",
|
|
2118
1844
|
fixable: true,
|
|
2119
1845
|
fixRisk: "safe",
|
|
2120
1846
|
fix: async () => {
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
1847
|
+
fs21.mkdirSync(ctx.pluginsDir, { recursive: true });
|
|
1848
|
+
fs21.writeFileSync(
|
|
1849
|
+
path20.join(ctx.pluginsDir, "package.json"),
|
|
2124
1850
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2125
1851
|
);
|
|
2126
1852
|
return { success: true, message: "initialized plugins directory" };
|
|
@@ -2129,15 +1855,15 @@ var init_plugins = __esm({
|
|
|
2129
1855
|
return results;
|
|
2130
1856
|
}
|
|
2131
1857
|
results.push({ status: "pass", message: "Plugins directory exists" });
|
|
2132
|
-
const pkgPath =
|
|
2133
|
-
if (!
|
|
1858
|
+
const pkgPath = path20.join(ctx.pluginsDir, "package.json");
|
|
1859
|
+
if (!fs21.existsSync(pkgPath)) {
|
|
2134
1860
|
results.push({
|
|
2135
1861
|
status: "warn",
|
|
2136
1862
|
message: "Plugins package.json missing",
|
|
2137
1863
|
fixable: true,
|
|
2138
1864
|
fixRisk: "safe",
|
|
2139
1865
|
fix: async () => {
|
|
2140
|
-
|
|
1866
|
+
fs21.writeFileSync(
|
|
2141
1867
|
pkgPath,
|
|
2142
1868
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2143
1869
|
);
|
|
@@ -2147,7 +1873,7 @@ var init_plugins = __esm({
|
|
|
2147
1873
|
return results;
|
|
2148
1874
|
}
|
|
2149
1875
|
try {
|
|
2150
|
-
const pkg = JSON.parse(
|
|
1876
|
+
const pkg = JSON.parse(fs21.readFileSync(pkgPath, "utf-8"));
|
|
2151
1877
|
const deps = pkg.dependencies || {};
|
|
2152
1878
|
const count = Object.keys(deps).length;
|
|
2153
1879
|
results.push({ status: "pass", message: `Plugins package.json valid (${count} plugins)` });
|
|
@@ -2158,7 +1884,7 @@ var init_plugins = __esm({
|
|
|
2158
1884
|
fixable: true,
|
|
2159
1885
|
fixRisk: "risky",
|
|
2160
1886
|
fix: async () => {
|
|
2161
|
-
|
|
1887
|
+
fs21.writeFileSync(
|
|
2162
1888
|
pkgPath,
|
|
2163
1889
|
JSON.stringify({ name: "openacp-plugins", private: true, dependencies: {} }, null, 2)
|
|
2164
1890
|
);
|
|
@@ -2173,7 +1899,7 @@ var init_plugins = __esm({
|
|
|
2173
1899
|
});
|
|
2174
1900
|
|
|
2175
1901
|
// src/core/doctor/checks/daemon.ts
|
|
2176
|
-
import * as
|
|
1902
|
+
import * as fs22 from "fs";
|
|
2177
1903
|
import * as net from "net";
|
|
2178
1904
|
function isProcessAlive(pid) {
|
|
2179
1905
|
try {
|
|
@@ -2203,8 +1929,8 @@ var init_daemon = __esm({
|
|
|
2203
1929
|
order: 7,
|
|
2204
1930
|
async run(ctx) {
|
|
2205
1931
|
const results = [];
|
|
2206
|
-
if (
|
|
2207
|
-
const content =
|
|
1932
|
+
if (fs22.existsSync(ctx.pidPath)) {
|
|
1933
|
+
const content = fs22.readFileSync(ctx.pidPath, "utf-8").trim();
|
|
2208
1934
|
const pid = parseInt(content, 10);
|
|
2209
1935
|
if (isNaN(pid)) {
|
|
2210
1936
|
results.push({
|
|
@@ -2213,7 +1939,7 @@ var init_daemon = __esm({
|
|
|
2213
1939
|
fixable: true,
|
|
2214
1940
|
fixRisk: "safe",
|
|
2215
1941
|
fix: async () => {
|
|
2216
|
-
|
|
1942
|
+
fs22.unlinkSync(ctx.pidPath);
|
|
2217
1943
|
return { success: true, message: "removed invalid PID file" };
|
|
2218
1944
|
}
|
|
2219
1945
|
});
|
|
@@ -2224,7 +1950,7 @@ var init_daemon = __esm({
|
|
|
2224
1950
|
fixable: true,
|
|
2225
1951
|
fixRisk: "safe",
|
|
2226
1952
|
fix: async () => {
|
|
2227
|
-
|
|
1953
|
+
fs22.unlinkSync(ctx.pidPath);
|
|
2228
1954
|
return { success: true, message: "removed stale PID file" };
|
|
2229
1955
|
}
|
|
2230
1956
|
});
|
|
@@ -2232,8 +1958,8 @@ var init_daemon = __esm({
|
|
|
2232
1958
|
results.push({ status: "pass", message: `Daemon running (PID ${pid})` });
|
|
2233
1959
|
}
|
|
2234
1960
|
}
|
|
2235
|
-
if (
|
|
2236
|
-
const content =
|
|
1961
|
+
if (fs22.existsSync(ctx.portFilePath)) {
|
|
1962
|
+
const content = fs22.readFileSync(ctx.portFilePath, "utf-8").trim();
|
|
2237
1963
|
const port = parseInt(content, 10);
|
|
2238
1964
|
if (isNaN(port)) {
|
|
2239
1965
|
results.push({
|
|
@@ -2242,7 +1968,7 @@ var init_daemon = __esm({
|
|
|
2242
1968
|
fixable: true,
|
|
2243
1969
|
fixRisk: "safe",
|
|
2244
1970
|
fix: async () => {
|
|
2245
|
-
|
|
1971
|
+
fs22.unlinkSync(ctx.portFilePath);
|
|
2246
1972
|
return { success: true, message: "removed invalid port file" };
|
|
2247
1973
|
}
|
|
2248
1974
|
});
|
|
@@ -2251,11 +1977,11 @@ var init_daemon = __esm({
|
|
|
2251
1977
|
}
|
|
2252
1978
|
}
|
|
2253
1979
|
if (ctx.config) {
|
|
2254
|
-
const apiPort =
|
|
1980
|
+
const apiPort = 21420;
|
|
2255
1981
|
const inUse = await checkPortInUse(apiPort);
|
|
2256
1982
|
if (inUse) {
|
|
2257
|
-
if (
|
|
2258
|
-
const pid = parseInt(
|
|
1983
|
+
if (fs22.existsSync(ctx.pidPath)) {
|
|
1984
|
+
const pid = parseInt(fs22.readFileSync(ctx.pidPath, "utf-8").trim(), 10);
|
|
2259
1985
|
if (!isNaN(pid) && isProcessAlive(pid)) {
|
|
2260
1986
|
results.push({ status: "pass", message: `API port ${apiPort} in use by OpenACP daemon` });
|
|
2261
1987
|
} else {
|
|
@@ -2275,25 +2001,32 @@ var init_daemon = __esm({
|
|
|
2275
2001
|
});
|
|
2276
2002
|
|
|
2277
2003
|
// src/core/utils/install-binary.ts
|
|
2278
|
-
import
|
|
2279
|
-
import
|
|
2004
|
+
import fs23 from "fs";
|
|
2005
|
+
import path21 from "path";
|
|
2280
2006
|
import https from "https";
|
|
2281
2007
|
import os9 from "os";
|
|
2282
|
-
import {
|
|
2283
|
-
function downloadFile(url, dest) {
|
|
2008
|
+
import { execFileSync as execFileSync4 } from "child_process";
|
|
2009
|
+
function downloadFile(url, dest, maxRedirects = 10) {
|
|
2284
2010
|
return new Promise((resolve6, reject) => {
|
|
2285
|
-
const file =
|
|
2011
|
+
const file = fs23.createWriteStream(dest);
|
|
2286
2012
|
const cleanup = () => {
|
|
2287
2013
|
try {
|
|
2288
|
-
if (
|
|
2014
|
+
if (fs23.existsSync(dest)) fs23.unlinkSync(dest);
|
|
2289
2015
|
} catch {
|
|
2290
2016
|
}
|
|
2291
2017
|
};
|
|
2292
2018
|
https.get(url, (response) => {
|
|
2293
2019
|
if (response.statusCode === 301 || response.statusCode === 302) {
|
|
2020
|
+
if (maxRedirects <= 0) {
|
|
2021
|
+
file.close(() => {
|
|
2022
|
+
cleanup();
|
|
2023
|
+
reject(new Error("Too many redirects"));
|
|
2024
|
+
});
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
2294
2027
|
file.close(() => {
|
|
2295
2028
|
cleanup();
|
|
2296
|
-
downloadFile(response.headers.location, dest).then(resolve6).catch(reject);
|
|
2029
|
+
downloadFile(response.headers.location, dest, maxRedirects - 1).then(resolve6).catch(reject);
|
|
2297
2030
|
});
|
|
2298
2031
|
return;
|
|
2299
2032
|
}
|
|
@@ -2344,36 +2077,36 @@ function getDownloadUrl(spec) {
|
|
|
2344
2077
|
async function ensureBinary(spec, binDir) {
|
|
2345
2078
|
const resolvedBinDir = binDir ?? DEFAULT_BIN_DIR;
|
|
2346
2079
|
const binName = IS_WINDOWS ? `${spec.name}.exe` : spec.name;
|
|
2347
|
-
const binPath =
|
|
2080
|
+
const binPath = path21.join(resolvedBinDir, binName);
|
|
2348
2081
|
if (commandExists(spec.name)) {
|
|
2349
2082
|
log17.debug({ name: spec.name }, "Found in PATH");
|
|
2350
2083
|
return spec.name;
|
|
2351
2084
|
}
|
|
2352
|
-
if (
|
|
2353
|
-
if (!IS_WINDOWS)
|
|
2085
|
+
if (fs23.existsSync(binPath)) {
|
|
2086
|
+
if (!IS_WINDOWS) fs23.chmodSync(binPath, "755");
|
|
2354
2087
|
log17.debug({ name: spec.name, path: binPath }, "Found in ~/.openacp/bin");
|
|
2355
2088
|
return binPath;
|
|
2356
2089
|
}
|
|
2357
2090
|
log17.info({ name: spec.name }, "Not found, downloading from GitHub...");
|
|
2358
|
-
|
|
2091
|
+
fs23.mkdirSync(resolvedBinDir, { recursive: true });
|
|
2359
2092
|
const url = getDownloadUrl(spec);
|
|
2360
2093
|
const isArchive = spec.isArchive?.(url) ?? false;
|
|
2361
|
-
const downloadDest = isArchive ?
|
|
2094
|
+
const downloadDest = isArchive ? path21.join(resolvedBinDir, `${spec.name}.tgz`) : binPath;
|
|
2362
2095
|
await downloadFile(url, downloadDest);
|
|
2363
2096
|
if (isArchive) {
|
|
2364
|
-
const listing =
|
|
2097
|
+
const listing = execFileSync4("tar", ["-tf", downloadDest], { stdio: "pipe" }).toString().trim().split("\n").filter(Boolean);
|
|
2365
2098
|
validateTarContents(listing, resolvedBinDir);
|
|
2366
|
-
|
|
2099
|
+
execFileSync4("tar", ["-xzf", downloadDest, "-C", resolvedBinDir], { stdio: "pipe" });
|
|
2367
2100
|
try {
|
|
2368
|
-
|
|
2101
|
+
fs23.unlinkSync(downloadDest);
|
|
2369
2102
|
} catch {
|
|
2370
2103
|
}
|
|
2371
2104
|
}
|
|
2372
|
-
if (!
|
|
2105
|
+
if (!fs23.existsSync(binPath)) {
|
|
2373
2106
|
throw new Error(`${spec.name}: binary not found at ${binPath} after download/extraction. The archive structure may have changed.`);
|
|
2374
2107
|
}
|
|
2375
2108
|
if (!IS_WINDOWS) {
|
|
2376
|
-
|
|
2109
|
+
fs23.chmodSync(binPath, "755");
|
|
2377
2110
|
}
|
|
2378
2111
|
log17.info({ name: spec.name, path: binPath }, "Installed successfully");
|
|
2379
2112
|
return binPath;
|
|
@@ -2386,7 +2119,7 @@ var init_install_binary = __esm({
|
|
|
2386
2119
|
init_agent_dependencies();
|
|
2387
2120
|
init_agent_installer();
|
|
2388
2121
|
log17 = createChildLogger({ module: "binary-installer" });
|
|
2389
|
-
DEFAULT_BIN_DIR =
|
|
2122
|
+
DEFAULT_BIN_DIR = path21.join(os9.homedir(), ".openacp", "bin");
|
|
2390
2123
|
IS_WINDOWS = os9.platform() === "win32";
|
|
2391
2124
|
}
|
|
2392
2125
|
});
|
|
@@ -2427,10 +2160,10 @@ var init_install_cloudflared = __esm({
|
|
|
2427
2160
|
});
|
|
2428
2161
|
|
|
2429
2162
|
// src/core/doctor/checks/tunnel.ts
|
|
2430
|
-
import * as
|
|
2431
|
-
import * as
|
|
2163
|
+
import * as fs24 from "fs";
|
|
2164
|
+
import * as path22 from "path";
|
|
2432
2165
|
import * as os10 from "os";
|
|
2433
|
-
import { execFileSync as
|
|
2166
|
+
import { execFileSync as execFileSync5 } from "child_process";
|
|
2434
2167
|
var tunnelCheck;
|
|
2435
2168
|
var init_tunnel = __esm({
|
|
2436
2169
|
"src/core/doctor/checks/tunnel.ts"() {
|
|
@@ -2444,21 +2177,26 @@ var init_tunnel = __esm({
|
|
|
2444
2177
|
results.push({ status: "fail", message: "Cannot check tunnel \u2014 config not loaded" });
|
|
2445
2178
|
return results;
|
|
2446
2179
|
}
|
|
2447
|
-
|
|
2180
|
+
const { SettingsManager: SettingsManager2 } = await Promise.resolve().then(() => (init_settings_manager(), settings_manager_exports));
|
|
2181
|
+
const sm = new SettingsManager2(path22.join(ctx.pluginsDir, "data"));
|
|
2182
|
+
const tunnelSettings = await sm.loadSettings("@openacp/tunnel");
|
|
2183
|
+
const tunnelEnabled = tunnelSettings.enabled ?? false;
|
|
2184
|
+
const provider = tunnelSettings.provider ?? "cloudflare";
|
|
2185
|
+
const tunnelPort = tunnelSettings.port ?? 3100;
|
|
2186
|
+
if (!tunnelEnabled) {
|
|
2448
2187
|
results.push({ status: "pass", message: "Tunnel not enabled (skipped)" });
|
|
2449
2188
|
return results;
|
|
2450
2189
|
}
|
|
2451
|
-
const provider = ctx.config.tunnel.provider;
|
|
2452
2190
|
results.push({ status: "pass", message: `Tunnel provider: ${provider}` });
|
|
2453
2191
|
if (provider === "cloudflare") {
|
|
2454
2192
|
const binName = os10.platform() === "win32" ? "cloudflared.exe" : "cloudflared";
|
|
2455
|
-
const binPath =
|
|
2193
|
+
const binPath = path22.join(ctx.dataDir, "bin", binName);
|
|
2456
2194
|
let found = false;
|
|
2457
|
-
if (
|
|
2195
|
+
if (fs24.existsSync(binPath)) {
|
|
2458
2196
|
found = true;
|
|
2459
2197
|
} else {
|
|
2460
2198
|
try {
|
|
2461
|
-
|
|
2199
|
+
execFileSync5("which", ["cloudflared"], { stdio: "pipe" });
|
|
2462
2200
|
found = true;
|
|
2463
2201
|
} catch {
|
|
2464
2202
|
}
|
|
@@ -2483,7 +2221,6 @@ var init_tunnel = __esm({
|
|
|
2483
2221
|
});
|
|
2484
2222
|
}
|
|
2485
2223
|
}
|
|
2486
|
-
const tunnelPort = ctx.config.tunnel.port;
|
|
2487
2224
|
if (tunnelPort < 1 || tunnelPort > 65535) {
|
|
2488
2225
|
results.push({ status: "fail", message: `Invalid tunnel port: ${tunnelPort}` });
|
|
2489
2226
|
} else {
|
|
@@ -2496,8 +2233,8 @@ var init_tunnel = __esm({
|
|
|
2496
2233
|
});
|
|
2497
2234
|
|
|
2498
2235
|
// src/core/doctor/index.ts
|
|
2499
|
-
import * as
|
|
2500
|
-
import * as
|
|
2236
|
+
import * as fs25 from "fs";
|
|
2237
|
+
import * as path23 from "path";
|
|
2501
2238
|
var ALL_CHECKS, CHECK_TIMEOUT_MS, DoctorEngine;
|
|
2502
2239
|
var init_doctor = __esm({
|
|
2503
2240
|
"src/core/doctor/index.ts"() {
|
|
@@ -2579,27 +2316,27 @@ var init_doctor = __esm({
|
|
|
2579
2316
|
}
|
|
2580
2317
|
async buildContext() {
|
|
2581
2318
|
const dataDir = this.dataDir;
|
|
2582
|
-
const configPath = process.env.OPENACP_CONFIG_PATH ||
|
|
2319
|
+
const configPath = process.env.OPENACP_CONFIG_PATH || path23.join(dataDir, "config.json");
|
|
2583
2320
|
let config = null;
|
|
2584
2321
|
let rawConfig = null;
|
|
2585
2322
|
try {
|
|
2586
|
-
const content =
|
|
2323
|
+
const content = fs25.readFileSync(configPath, "utf-8");
|
|
2587
2324
|
rawConfig = JSON.parse(content);
|
|
2588
2325
|
const cm = new ConfigManager(configPath);
|
|
2589
2326
|
await cm.load();
|
|
2590
2327
|
config = cm.get();
|
|
2591
2328
|
} catch {
|
|
2592
2329
|
}
|
|
2593
|
-
const logsDir = config ? expandHome3(config.logging.logDir) :
|
|
2330
|
+
const logsDir = config ? expandHome3(config.logging.logDir) : path23.join(dataDir, "logs");
|
|
2594
2331
|
return {
|
|
2595
2332
|
config,
|
|
2596
2333
|
rawConfig,
|
|
2597
2334
|
configPath,
|
|
2598
2335
|
dataDir,
|
|
2599
|
-
sessionsPath:
|
|
2600
|
-
pidPath:
|
|
2601
|
-
portFilePath:
|
|
2602
|
-
pluginsDir:
|
|
2336
|
+
sessionsPath: path23.join(dataDir, "sessions.json"),
|
|
2337
|
+
pidPath: path23.join(dataDir, "openacp.pid"),
|
|
2338
|
+
portFilePath: path23.join(dataDir, "api.port"),
|
|
2339
|
+
pluginsDir: path23.join(dataDir, "plugins"),
|
|
2603
2340
|
logsDir
|
|
2604
2341
|
};
|
|
2605
2342
|
}
|
|
@@ -2607,113 +2344,24 @@ var init_doctor = __esm({
|
|
|
2607
2344
|
}
|
|
2608
2345
|
});
|
|
2609
2346
|
|
|
2610
|
-
// src/plugins/telegram/validators.ts
|
|
2611
|
-
var validators_exports = {};
|
|
2612
|
-
__export(validators_exports, {
|
|
2613
|
-
validateBotAdmin: () => validateBotAdmin,
|
|
2614
|
-
validateBotToken: () => validateBotToken,
|
|
2615
|
-
validateChatId: () => validateChatId
|
|
2616
|
-
});
|
|
2617
|
-
async function validateBotToken(token) {
|
|
2618
|
-
try {
|
|
2619
|
-
const res = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
2620
|
-
const data = await res.json();
|
|
2621
|
-
if (data.ok && data.result) {
|
|
2622
|
-
return {
|
|
2623
|
-
ok: true,
|
|
2624
|
-
botName: data.result.first_name,
|
|
2625
|
-
botUsername: data.result.username
|
|
2626
|
-
};
|
|
2627
|
-
}
|
|
2628
|
-
return { ok: false, error: data.description || "Invalid token" };
|
|
2629
|
-
} catch (err) {
|
|
2630
|
-
return { ok: false, error: err.message };
|
|
2631
|
-
}
|
|
2632
|
-
}
|
|
2633
|
-
async function validateChatId(token, chatId) {
|
|
2634
|
-
try {
|
|
2635
|
-
const res = await fetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
2636
|
-
method: "POST",
|
|
2637
|
-
headers: { "Content-Type": "application/json" },
|
|
2638
|
-
body: JSON.stringify({ chat_id: chatId })
|
|
2639
|
-
});
|
|
2640
|
-
const data = await res.json();
|
|
2641
|
-
if (!data.ok || !data.result) {
|
|
2642
|
-
return { ok: false, error: data.description || "Invalid chat ID" };
|
|
2643
|
-
}
|
|
2644
|
-
if (data.result.type !== "supergroup") {
|
|
2645
|
-
return {
|
|
2646
|
-
ok: false,
|
|
2647
|
-
error: `Chat is "${data.result.type}", must be a supergroup`
|
|
2648
|
-
};
|
|
2649
|
-
}
|
|
2650
|
-
return {
|
|
2651
|
-
ok: true,
|
|
2652
|
-
title: data.result.title,
|
|
2653
|
-
isForum: data.result.is_forum === true
|
|
2654
|
-
};
|
|
2655
|
-
} catch (err) {
|
|
2656
|
-
return { ok: false, error: err.message };
|
|
2657
|
-
}
|
|
2658
|
-
}
|
|
2659
|
-
async function validateBotAdmin(token, chatId) {
|
|
2660
|
-
try {
|
|
2661
|
-
const meRes = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
2662
|
-
const meData = await meRes.json();
|
|
2663
|
-
if (!meData.ok || !meData.result) {
|
|
2664
|
-
return { ok: false, error: "Could not retrieve bot info" };
|
|
2665
|
-
}
|
|
2666
|
-
const res = await fetch(
|
|
2667
|
-
`https://api.telegram.org/bot${token}/getChatMember`,
|
|
2668
|
-
{
|
|
2669
|
-
method: "POST",
|
|
2670
|
-
headers: { "Content-Type": "application/json" },
|
|
2671
|
-
body: JSON.stringify({ chat_id: chatId, user_id: meData.result.id })
|
|
2672
|
-
}
|
|
2673
|
-
);
|
|
2674
|
-
const data = await res.json();
|
|
2675
|
-
if (!data.ok || !data.result) {
|
|
2676
|
-
return {
|
|
2677
|
-
ok: false,
|
|
2678
|
-
error: data.description || "Could not check bot membership"
|
|
2679
|
-
};
|
|
2680
|
-
}
|
|
2681
|
-
const { status } = data.result;
|
|
2682
|
-
if (status === "administrator" || status === "creator") {
|
|
2683
|
-
return { ok: true };
|
|
2684
|
-
}
|
|
2685
|
-
return {
|
|
2686
|
-
ok: false,
|
|
2687
|
-
error: `Bot is "${status}" in this group. It must be an admin. Please promote the bot to admin in group settings.`
|
|
2688
|
-
};
|
|
2689
|
-
} catch (err) {
|
|
2690
|
-
return { ok: false, error: err.message };
|
|
2691
|
-
}
|
|
2692
|
-
}
|
|
2693
|
-
var init_validators = __esm({
|
|
2694
|
-
"src/plugins/telegram/validators.ts"() {
|
|
2695
|
-
"use strict";
|
|
2696
|
-
}
|
|
2697
|
-
});
|
|
2698
|
-
|
|
2699
2347
|
// src/core/plugin/plugin-installer.ts
|
|
2700
2348
|
var plugin_installer_exports = {};
|
|
2701
2349
|
__export(plugin_installer_exports, {
|
|
2702
2350
|
importFromDir: () => importFromDir,
|
|
2703
2351
|
installNpmPlugin: () => installNpmPlugin
|
|
2704
2352
|
});
|
|
2705
|
-
import {
|
|
2353
|
+
import { execFile } from "child_process";
|
|
2706
2354
|
import { promisify } from "util";
|
|
2707
|
-
import * as
|
|
2355
|
+
import * as fs27 from "fs/promises";
|
|
2708
2356
|
import * as os12 from "os";
|
|
2709
|
-
import * as
|
|
2357
|
+
import * as path25 from "path";
|
|
2710
2358
|
import { pathToFileURL } from "url";
|
|
2711
2359
|
async function importFromDir(packageName, dir) {
|
|
2712
|
-
const pkgDir =
|
|
2713
|
-
const pkgJsonPath =
|
|
2360
|
+
const pkgDir = path25.join(dir, "node_modules", ...packageName.split("/"));
|
|
2361
|
+
const pkgJsonPath = path25.join(pkgDir, "package.json");
|
|
2714
2362
|
let pkgJson;
|
|
2715
2363
|
try {
|
|
2716
|
-
pkgJson = JSON.parse(await
|
|
2364
|
+
pkgJson = JSON.parse(await fs27.readFile(pkgJsonPath, "utf-8"));
|
|
2717
2365
|
} catch (err) {
|
|
2718
2366
|
throw new Error(`Cannot read package.json for "${packageName}" at ${pkgJsonPath}: ${err.message}`);
|
|
2719
2367
|
}
|
|
@@ -2726,9 +2374,9 @@ async function importFromDir(packageName, dir) {
|
|
|
2726
2374
|
} else {
|
|
2727
2375
|
entry = pkgJson.main ?? "index.js";
|
|
2728
2376
|
}
|
|
2729
|
-
const entryPath =
|
|
2377
|
+
const entryPath = path25.join(pkgDir, entry);
|
|
2730
2378
|
try {
|
|
2731
|
-
await
|
|
2379
|
+
await fs27.access(entryPath);
|
|
2732
2380
|
} catch {
|
|
2733
2381
|
throw new Error(`Entry point "${entry}" not found for "${packageName}" at ${entryPath}`);
|
|
2734
2382
|
}
|
|
@@ -2738,21 +2386,21 @@ async function installNpmPlugin(packageName, pluginsDir) {
|
|
|
2738
2386
|
if (!VALID_NPM_NAME.test(packageName)) {
|
|
2739
2387
|
throw new Error(`Invalid package name: "${packageName}". Must be a valid npm package name.`);
|
|
2740
2388
|
}
|
|
2741
|
-
const dir = pluginsDir ??
|
|
2389
|
+
const dir = pluginsDir ?? path25.join(os12.homedir(), ".openacp", "plugins");
|
|
2742
2390
|
try {
|
|
2743
2391
|
return await importFromDir(packageName, dir);
|
|
2744
2392
|
} catch {
|
|
2745
2393
|
}
|
|
2746
|
-
await
|
|
2394
|
+
await execFileAsync("npm", ["install", packageName, "--prefix", dir, "--save", "--ignore-scripts"], {
|
|
2747
2395
|
timeout: 6e4
|
|
2748
2396
|
});
|
|
2749
2397
|
return await importFromDir(packageName, dir);
|
|
2750
2398
|
}
|
|
2751
|
-
var
|
|
2399
|
+
var execFileAsync, VALID_NPM_NAME;
|
|
2752
2400
|
var init_plugin_installer = __esm({
|
|
2753
2401
|
"src/core/plugin/plugin-installer.ts"() {
|
|
2754
2402
|
"use strict";
|
|
2755
|
-
|
|
2403
|
+
execFileAsync = promisify(execFile);
|
|
2756
2404
|
VALID_NPM_NAME = /^(@[a-z0-9][\w.-]*\/)?[a-z0-9][\w.-]*(@[\w.^~>=<|-]+)?$/i;
|
|
2757
2405
|
}
|
|
2758
2406
|
});
|
|
@@ -2820,15 +2468,14 @@ var install_context_exports = {};
|
|
|
2820
2468
|
__export(install_context_exports, {
|
|
2821
2469
|
createInstallContext: () => createInstallContext
|
|
2822
2470
|
});
|
|
2823
|
-
import
|
|
2471
|
+
import path26 from "path";
|
|
2824
2472
|
function createInstallContext(opts) {
|
|
2825
|
-
const { pluginName, settingsManager, basePath,
|
|
2826
|
-
const dataDir =
|
|
2473
|
+
const { pluginName, settingsManager, basePath, instanceRoot } = opts;
|
|
2474
|
+
const dataDir = path26.join(basePath, pluginName, "data");
|
|
2827
2475
|
return {
|
|
2828
2476
|
pluginName,
|
|
2829
2477
|
terminal: createTerminalIO(),
|
|
2830
2478
|
settings: settingsManager.createAPI(pluginName),
|
|
2831
|
-
legacyConfig,
|
|
2832
2479
|
dataDir,
|
|
2833
2480
|
log: log.child({ plugin: pluginName }),
|
|
2834
2481
|
instanceRoot
|
|
@@ -2850,19 +2497,19 @@ __export(api_client_exports, {
|
|
|
2850
2497
|
readApiSecret: () => readApiSecret,
|
|
2851
2498
|
removeStalePortFile: () => removeStalePortFile
|
|
2852
2499
|
});
|
|
2853
|
-
import * as
|
|
2854
|
-
import * as
|
|
2500
|
+
import * as fs28 from "fs";
|
|
2501
|
+
import * as path27 from "path";
|
|
2855
2502
|
import * as os13 from "os";
|
|
2856
2503
|
function defaultPortFile(root) {
|
|
2857
|
-
return
|
|
2504
|
+
return path27.join(root ?? DEFAULT_ROOT, "api.port");
|
|
2858
2505
|
}
|
|
2859
2506
|
function defaultSecretFile(root) {
|
|
2860
|
-
return
|
|
2507
|
+
return path27.join(root ?? DEFAULT_ROOT, "api-secret");
|
|
2861
2508
|
}
|
|
2862
2509
|
function readApiPort(portFilePath, instanceRoot) {
|
|
2863
2510
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
2864
2511
|
try {
|
|
2865
|
-
const content =
|
|
2512
|
+
const content = fs28.readFileSync(filePath, "utf-8").trim();
|
|
2866
2513
|
const port = parseInt(content, 10);
|
|
2867
2514
|
return isNaN(port) ? null : port;
|
|
2868
2515
|
} catch {
|
|
@@ -2872,7 +2519,7 @@ function readApiPort(portFilePath, instanceRoot) {
|
|
|
2872
2519
|
function readApiSecret(secretFilePath, instanceRoot) {
|
|
2873
2520
|
const filePath = secretFilePath ?? defaultSecretFile(instanceRoot);
|
|
2874
2521
|
try {
|
|
2875
|
-
const content =
|
|
2522
|
+
const content = fs28.readFileSync(filePath, "utf-8").trim();
|
|
2876
2523
|
return content || null;
|
|
2877
2524
|
} catch {
|
|
2878
2525
|
return null;
|
|
@@ -2881,7 +2528,7 @@ function readApiSecret(secretFilePath, instanceRoot) {
|
|
|
2881
2528
|
function removeStalePortFile(portFilePath, instanceRoot) {
|
|
2882
2529
|
const filePath = portFilePath ?? defaultPortFile(instanceRoot);
|
|
2883
2530
|
try {
|
|
2884
|
-
|
|
2531
|
+
fs28.unlinkSync(filePath);
|
|
2885
2532
|
} catch {
|
|
2886
2533
|
}
|
|
2887
2534
|
}
|
|
@@ -2897,7 +2544,7 @@ var DEFAULT_ROOT;
|
|
|
2897
2544
|
var init_api_client = __esm({
|
|
2898
2545
|
"src/cli/api-client.ts"() {
|
|
2899
2546
|
"use strict";
|
|
2900
|
-
DEFAULT_ROOT =
|
|
2547
|
+
DEFAULT_ROOT = path27.join(os13.homedir(), ".openacp");
|
|
2901
2548
|
}
|
|
2902
2549
|
});
|
|
2903
2550
|
|
|
@@ -2931,8 +2578,8 @@ var init_notification = __esm({
|
|
|
2931
2578
|
});
|
|
2932
2579
|
|
|
2933
2580
|
// src/plugins/file-service/file-service.ts
|
|
2934
|
-
import
|
|
2935
|
-
import
|
|
2581
|
+
import fs30 from "fs";
|
|
2582
|
+
import path30 from "path";
|
|
2936
2583
|
import { OggOpusDecoder } from "ogg-opus-decoder";
|
|
2937
2584
|
import wav from "node-wav";
|
|
2938
2585
|
function classifyMime(mimeType) {
|
|
@@ -2988,14 +2635,14 @@ var init_file_service = __esm({
|
|
|
2988
2635
|
const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1e3;
|
|
2989
2636
|
let removed = 0;
|
|
2990
2637
|
try {
|
|
2991
|
-
const entries = await
|
|
2638
|
+
const entries = await fs30.promises.readdir(this.baseDir, { withFileTypes: true });
|
|
2992
2639
|
for (const entry of entries) {
|
|
2993
2640
|
if (!entry.isDirectory()) continue;
|
|
2994
|
-
const dirPath =
|
|
2641
|
+
const dirPath = path30.join(this.baseDir, entry.name);
|
|
2995
2642
|
try {
|
|
2996
|
-
const stat = await
|
|
2643
|
+
const stat = await fs30.promises.stat(dirPath);
|
|
2997
2644
|
if (stat.mtimeMs < cutoff) {
|
|
2998
|
-
await
|
|
2645
|
+
await fs30.promises.rm(dirPath, { recursive: true, force: true });
|
|
2999
2646
|
removed++;
|
|
3000
2647
|
}
|
|
3001
2648
|
} catch {
|
|
@@ -3006,11 +2653,11 @@ var init_file_service = __esm({
|
|
|
3006
2653
|
return removed;
|
|
3007
2654
|
}
|
|
3008
2655
|
async saveFile(sessionId, fileName, data, mimeType) {
|
|
3009
|
-
const sessionDir =
|
|
3010
|
-
await
|
|
2656
|
+
const sessionDir = path30.join(this.baseDir, sessionId);
|
|
2657
|
+
await fs30.promises.mkdir(sessionDir, { recursive: true });
|
|
3011
2658
|
const safeName = `${Date.now()}-${fileName.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
|
|
3012
|
-
const filePath =
|
|
3013
|
-
await
|
|
2659
|
+
const filePath = path30.join(sessionDir, safeName);
|
|
2660
|
+
await fs30.promises.writeFile(filePath, data);
|
|
3014
2661
|
return {
|
|
3015
2662
|
type: classifyMime(mimeType),
|
|
3016
2663
|
filePath,
|
|
@@ -3021,14 +2668,14 @@ var init_file_service = __esm({
|
|
|
3021
2668
|
}
|
|
3022
2669
|
async resolveFile(filePath) {
|
|
3023
2670
|
try {
|
|
3024
|
-
const stat = await
|
|
2671
|
+
const stat = await fs30.promises.stat(filePath);
|
|
3025
2672
|
if (!stat.isFile()) return null;
|
|
3026
|
-
const ext =
|
|
2673
|
+
const ext = path30.extname(filePath).toLowerCase();
|
|
3027
2674
|
const mimeType = EXT_TO_MIME[ext] || "application/octet-stream";
|
|
3028
2675
|
return {
|
|
3029
2676
|
type: classifyMime(mimeType),
|
|
3030
2677
|
filePath,
|
|
3031
|
-
fileName:
|
|
2678
|
+
fileName: path30.basename(filePath),
|
|
3032
2679
|
mimeType,
|
|
3033
2680
|
size: stat.size
|
|
3034
2681
|
};
|
|
@@ -3240,6 +2887,7 @@ var init_roles = __esm({
|
|
|
3240
2887
|
"sessions:prompt",
|
|
3241
2888
|
"sessions:permission",
|
|
3242
2889
|
"agents:read",
|
|
2890
|
+
"agents:write",
|
|
3243
2891
|
"commands:execute",
|
|
3244
2892
|
"system:health",
|
|
3245
2893
|
"config:read"
|
|
@@ -3358,12 +3006,16 @@ async function createApiServer(options) {
|
|
|
3358
3006
|
// per-tunnel. Only the first value in X-Forwarded-For is taken (the client); the
|
|
3359
3007
|
// rest may be added by intermediate proxies and must not be trusted for limiting.
|
|
3360
3008
|
keyGenerator: (request) => {
|
|
3361
|
-
const
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3009
|
+
const bindHost = options.host;
|
|
3010
|
+
const isBehindProxy = !bindHost || bindHost === "127.0.0.1" || bindHost === "localhost" || bindHost === "::1";
|
|
3011
|
+
if (isBehindProxy) {
|
|
3012
|
+
const cfIp = request.headers["cf-connecting-ip"];
|
|
3013
|
+
if (cfIp && typeof cfIp === "string") return cfIp;
|
|
3014
|
+
const xff = request.headers["x-forwarded-for"];
|
|
3015
|
+
if (xff) {
|
|
3016
|
+
const first = (Array.isArray(xff) ? xff[0] : xff).split(",")[0]?.trim();
|
|
3017
|
+
if (first) return first;
|
|
3018
|
+
}
|
|
3367
3019
|
}
|
|
3368
3020
|
return request.ip;
|
|
3369
3021
|
}
|
|
@@ -3475,6 +3127,7 @@ var init_sse_manager = __esm({
|
|
|
3475
3127
|
"session:deleted",
|
|
3476
3128
|
"agent:event",
|
|
3477
3129
|
"permission:request",
|
|
3130
|
+
"permission:resolved",
|
|
3478
3131
|
"message:queued",
|
|
3479
3132
|
"message:processing"
|
|
3480
3133
|
];
|
|
@@ -3541,6 +3194,7 @@ data: ${JSON.stringify(data)}
|
|
|
3541
3194
|
const sessionEvents = [
|
|
3542
3195
|
"agent:event",
|
|
3543
3196
|
"permission:request",
|
|
3197
|
+
"permission:resolved",
|
|
3544
3198
|
"session:updated",
|
|
3545
3199
|
"message:queued",
|
|
3546
3200
|
"message:processing"
|
|
@@ -3586,8 +3240,8 @@ data: ${JSON.stringify(data)}
|
|
|
3586
3240
|
});
|
|
3587
3241
|
|
|
3588
3242
|
// src/plugins/api-server/static-server.ts
|
|
3589
|
-
import * as
|
|
3590
|
-
import * as
|
|
3243
|
+
import * as fs31 from "fs";
|
|
3244
|
+
import * as path31 from "path";
|
|
3591
3245
|
import { fileURLToPath } from "url";
|
|
3592
3246
|
var MIME_TYPES, StaticServer;
|
|
3593
3247
|
var init_static_server = __esm({
|
|
@@ -3611,16 +3265,16 @@ var init_static_server = __esm({
|
|
|
3611
3265
|
this.uiDir = uiDir;
|
|
3612
3266
|
if (!this.uiDir) {
|
|
3613
3267
|
const __filename = fileURLToPath(import.meta.url);
|
|
3614
|
-
const candidate =
|
|
3615
|
-
if (
|
|
3268
|
+
const candidate = path31.resolve(path31.dirname(__filename), "../../ui/dist");
|
|
3269
|
+
if (fs31.existsSync(path31.join(candidate, "index.html"))) {
|
|
3616
3270
|
this.uiDir = candidate;
|
|
3617
3271
|
}
|
|
3618
3272
|
if (!this.uiDir) {
|
|
3619
|
-
const publishCandidate =
|
|
3620
|
-
|
|
3273
|
+
const publishCandidate = path31.resolve(
|
|
3274
|
+
path31.dirname(__filename),
|
|
3621
3275
|
"../ui"
|
|
3622
3276
|
);
|
|
3623
|
-
if (
|
|
3277
|
+
if (fs31.existsSync(path31.join(publishCandidate, "index.html"))) {
|
|
3624
3278
|
this.uiDir = publishCandidate;
|
|
3625
3279
|
}
|
|
3626
3280
|
}
|
|
@@ -3632,23 +3286,23 @@ var init_static_server = __esm({
|
|
|
3632
3286
|
serve(req, res) {
|
|
3633
3287
|
if (!this.uiDir) return false;
|
|
3634
3288
|
const urlPath = (req.url || "/").split("?")[0];
|
|
3635
|
-
const safePath =
|
|
3636
|
-
const filePath =
|
|
3637
|
-
if (!filePath.startsWith(this.uiDir +
|
|
3289
|
+
const safePath = path31.normalize(urlPath);
|
|
3290
|
+
const filePath = path31.join(this.uiDir, safePath);
|
|
3291
|
+
if (!filePath.startsWith(this.uiDir + path31.sep) && filePath !== this.uiDir)
|
|
3638
3292
|
return false;
|
|
3639
3293
|
let realFilePath;
|
|
3640
3294
|
try {
|
|
3641
|
-
realFilePath =
|
|
3295
|
+
realFilePath = fs31.realpathSync(filePath);
|
|
3642
3296
|
} catch {
|
|
3643
3297
|
realFilePath = null;
|
|
3644
3298
|
}
|
|
3645
3299
|
if (realFilePath !== null) {
|
|
3646
|
-
const realUiDir =
|
|
3647
|
-
if (!realFilePath.startsWith(realUiDir +
|
|
3300
|
+
const realUiDir = fs31.realpathSync(this.uiDir);
|
|
3301
|
+
if (!realFilePath.startsWith(realUiDir + path31.sep) && realFilePath !== realUiDir)
|
|
3648
3302
|
return false;
|
|
3649
3303
|
}
|
|
3650
|
-
if (realFilePath !== null &&
|
|
3651
|
-
const ext =
|
|
3304
|
+
if (realFilePath !== null && fs31.existsSync(realFilePath) && fs31.statSync(realFilePath).isFile()) {
|
|
3305
|
+
const ext = path31.extname(filePath);
|
|
3652
3306
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
3653
3307
|
const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
|
|
3654
3308
|
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
|
|
@@ -3656,16 +3310,16 @@ var init_static_server = __esm({
|
|
|
3656
3310
|
"Content-Type": contentType,
|
|
3657
3311
|
"Cache-Control": cacheControl
|
|
3658
3312
|
});
|
|
3659
|
-
|
|
3313
|
+
fs31.createReadStream(realFilePath).pipe(res);
|
|
3660
3314
|
return true;
|
|
3661
3315
|
}
|
|
3662
|
-
const indexPath =
|
|
3663
|
-
if (
|
|
3316
|
+
const indexPath = path31.join(this.uiDir, "index.html");
|
|
3317
|
+
if (fs31.existsSync(indexPath)) {
|
|
3664
3318
|
res.writeHead(200, {
|
|
3665
3319
|
"Content-Type": "text/html; charset=utf-8",
|
|
3666
3320
|
"Cache-Control": "no-cache"
|
|
3667
3321
|
});
|
|
3668
|
-
|
|
3322
|
+
fs31.createReadStream(indexPath).pipe(res);
|
|
3669
3323
|
return true;
|
|
3670
3324
|
}
|
|
3671
3325
|
return false;
|
|
@@ -3820,8 +3474,8 @@ var init_exports = __esm({
|
|
|
3820
3474
|
});
|
|
3821
3475
|
|
|
3822
3476
|
// src/plugins/context/context-cache.ts
|
|
3823
|
-
import * as
|
|
3824
|
-
import * as
|
|
3477
|
+
import * as fs32 from "fs";
|
|
3478
|
+
import * as path32 from "path";
|
|
3825
3479
|
import * as crypto2 from "crypto";
|
|
3826
3480
|
var DEFAULT_TTL_MS, ContextCache;
|
|
3827
3481
|
var init_context_cache = __esm({
|
|
@@ -3832,29 +3486,29 @@ var init_context_cache = __esm({
|
|
|
3832
3486
|
constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
|
|
3833
3487
|
this.cacheDir = cacheDir;
|
|
3834
3488
|
this.ttlMs = ttlMs;
|
|
3835
|
-
|
|
3489
|
+
fs32.mkdirSync(cacheDir, { recursive: true });
|
|
3836
3490
|
}
|
|
3837
3491
|
keyHash(repoPath, queryKey) {
|
|
3838
3492
|
return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
|
|
3839
3493
|
}
|
|
3840
3494
|
filePath(repoPath, queryKey) {
|
|
3841
|
-
return
|
|
3495
|
+
return path32.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
|
|
3842
3496
|
}
|
|
3843
3497
|
get(repoPath, queryKey) {
|
|
3844
3498
|
const fp = this.filePath(repoPath, queryKey);
|
|
3845
3499
|
try {
|
|
3846
|
-
const stat =
|
|
3500
|
+
const stat = fs32.statSync(fp);
|
|
3847
3501
|
if (Date.now() - stat.mtimeMs > this.ttlMs) {
|
|
3848
|
-
|
|
3502
|
+
fs32.unlinkSync(fp);
|
|
3849
3503
|
return null;
|
|
3850
3504
|
}
|
|
3851
|
-
return JSON.parse(
|
|
3505
|
+
return JSON.parse(fs32.readFileSync(fp, "utf-8"));
|
|
3852
3506
|
} catch {
|
|
3853
3507
|
return null;
|
|
3854
3508
|
}
|
|
3855
3509
|
}
|
|
3856
3510
|
set(repoPath, queryKey, result) {
|
|
3857
|
-
|
|
3511
|
+
fs32.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
|
|
3858
3512
|
}
|
|
3859
3513
|
};
|
|
3860
3514
|
}
|
|
@@ -3862,7 +3516,7 @@ var init_context_cache = __esm({
|
|
|
3862
3516
|
|
|
3863
3517
|
// src/plugins/context/context-manager.ts
|
|
3864
3518
|
import * as os15 from "os";
|
|
3865
|
-
import * as
|
|
3519
|
+
import * as path33 from "path";
|
|
3866
3520
|
var ContextManager;
|
|
3867
3521
|
var init_context_manager = __esm({
|
|
3868
3522
|
"src/plugins/context/context-manager.ts"() {
|
|
@@ -3872,12 +3526,25 @@ var init_context_manager = __esm({
|
|
|
3872
3526
|
providers = [];
|
|
3873
3527
|
cache;
|
|
3874
3528
|
historyStore;
|
|
3529
|
+
sessionFlusher;
|
|
3875
3530
|
constructor(cachePath) {
|
|
3876
|
-
this.cache = new ContextCache(cachePath ??
|
|
3531
|
+
this.cache = new ContextCache(cachePath ?? path33.join(os15.homedir(), ".openacp", "cache", "entire"));
|
|
3877
3532
|
}
|
|
3878
3533
|
setHistoryStore(store) {
|
|
3879
3534
|
this.historyStore = store;
|
|
3880
3535
|
}
|
|
3536
|
+
/** Register a callback that flushes in-memory recorder state for a session to disk. */
|
|
3537
|
+
registerFlusher(fn) {
|
|
3538
|
+
this.sessionFlusher = fn;
|
|
3539
|
+
}
|
|
3540
|
+
/**
|
|
3541
|
+
* Flush the recorder state for a session to disk before reading its context.
|
|
3542
|
+
* Call this before buildContext() when switching agents to avoid a race
|
|
3543
|
+
* where the last turn hasn't been persisted yet.
|
|
3544
|
+
*/
|
|
3545
|
+
async flushSession(sessionId) {
|
|
3546
|
+
if (this.sessionFlusher) await this.sessionFlusher(sessionId);
|
|
3547
|
+
}
|
|
3881
3548
|
async getHistory(sessionId) {
|
|
3882
3549
|
if (!this.historyStore) return null;
|
|
3883
3550
|
return this.historyStore.read(sessionId);
|
|
@@ -3901,13 +3568,17 @@ var init_context_manager = __esm({
|
|
|
3901
3568
|
}
|
|
3902
3569
|
async buildContext(query, options) {
|
|
3903
3570
|
const queryKey = `${query.type}:${query.value}:${options?.limit ?? ""}:${options?.maxTokens ?? ""}:${options?.labelAgent ?? ""}`;
|
|
3904
|
-
|
|
3905
|
-
|
|
3571
|
+
if (!options?.noCache) {
|
|
3572
|
+
const cached = this.cache.get(query.repoPath, queryKey);
|
|
3573
|
+
if (cached) return cached;
|
|
3574
|
+
}
|
|
3906
3575
|
for (const provider of this.providers) {
|
|
3907
3576
|
if (!await provider.isAvailable(query.repoPath)) continue;
|
|
3908
3577
|
const result = await provider.buildContext(query, options);
|
|
3909
3578
|
if (result && result.markdown) {
|
|
3910
|
-
|
|
3579
|
+
if (!options?.noCache) {
|
|
3580
|
+
this.cache.set(query.repoPath, queryKey, result);
|
|
3581
|
+
}
|
|
3911
3582
|
return result;
|
|
3912
3583
|
}
|
|
3913
3584
|
}
|
|
@@ -3928,7 +3599,7 @@ var init_context_provider = __esm({
|
|
|
3928
3599
|
});
|
|
3929
3600
|
|
|
3930
3601
|
// src/plugins/context/entire/checkpoint-reader.ts
|
|
3931
|
-
import { execFileSync as
|
|
3602
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
3932
3603
|
var ENTIRE_BRANCH, CHECKPOINT_ID_RE, SESSION_ID_RE, CheckpointReader;
|
|
3933
3604
|
var init_checkpoint_reader = __esm({
|
|
3934
3605
|
"src/plugins/context/entire/checkpoint-reader.ts"() {
|
|
@@ -3947,7 +3618,7 @@ var init_checkpoint_reader = __esm({
|
|
|
3947
3618
|
*/
|
|
3948
3619
|
git(...args) {
|
|
3949
3620
|
try {
|
|
3950
|
-
return
|
|
3621
|
+
return execFileSync7("git", ["-C", this.repoPath, ...args], {
|
|
3951
3622
|
encoding: "utf-8"
|
|
3952
3623
|
}).trim();
|
|
3953
3624
|
} catch {
|
|
@@ -4774,8 +4445,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
|
|
|
4774
4445
|
}
|
|
4775
4446
|
if (lowerName === "grep") {
|
|
4776
4447
|
const pattern = args.pattern ?? "";
|
|
4777
|
-
const
|
|
4778
|
-
return pattern ? `\u{1F50D} Grep "${pattern}"${
|
|
4448
|
+
const path34 = args.path ?? "";
|
|
4449
|
+
return pattern ? `\u{1F50D} Grep "${pattern}"${path34 ? ` in ${path34}` : ""}` : `\u{1F527} ${name}`;
|
|
4779
4450
|
}
|
|
4780
4451
|
if (lowerName === "glob") {
|
|
4781
4452
|
const pattern = args.pattern ?? "";
|
|
@@ -4811,8 +4482,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
|
|
|
4811
4482
|
}
|
|
4812
4483
|
if (lowerName === "grep") {
|
|
4813
4484
|
const pattern = args.pattern ?? "";
|
|
4814
|
-
const
|
|
4815
|
-
return pattern ? `"${pattern}"${
|
|
4485
|
+
const path34 = args.path ?? "";
|
|
4486
|
+
return pattern ? `"${pattern}"${path34 ? ` in ${path34}` : ""}` : name;
|
|
4816
4487
|
}
|
|
4817
4488
|
if (lowerName === "glob") {
|
|
4818
4489
|
return String(args.pattern ?? name);
|
|
@@ -5490,12 +5161,34 @@ function asRecord(value) {
|
|
|
5490
5161
|
function capitalize(s) {
|
|
5491
5162
|
return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
|
|
5492
5163
|
}
|
|
5164
|
+
function getStringField(input2, keys) {
|
|
5165
|
+
for (const key of keys) {
|
|
5166
|
+
const value = input2[key];
|
|
5167
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
5168
|
+
}
|
|
5169
|
+
return null;
|
|
5170
|
+
}
|
|
5171
|
+
function parseApplyPatchTargets(patchText) {
|
|
5172
|
+
const targets = [];
|
|
5173
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5174
|
+
for (const line of patchText.split("\n")) {
|
|
5175
|
+
const match = line.match(/^\*\*\*\s+(?:Update|Add|Delete)\s+File:\s+(.+)$/);
|
|
5176
|
+
if (!match) continue;
|
|
5177
|
+
const p = match[1].trim();
|
|
5178
|
+
if (p && !seen.has(p)) {
|
|
5179
|
+
seen.add(p);
|
|
5180
|
+
targets.push(p);
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
return targets;
|
|
5184
|
+
}
|
|
5493
5185
|
function buildTitle(entry, kind) {
|
|
5494
5186
|
if (entry.displayTitle) return entry.displayTitle;
|
|
5495
5187
|
if (entry.displaySummary) return entry.displaySummary;
|
|
5496
5188
|
const input2 = asRecord(entry.rawInput);
|
|
5189
|
+
const nameLower = entry.name.toLowerCase();
|
|
5497
5190
|
if (kind === "read") {
|
|
5498
|
-
const filePath =
|
|
5191
|
+
const filePath = getStringField(input2, ["file_path", "filePath", "path"]);
|
|
5499
5192
|
if (filePath) {
|
|
5500
5193
|
const startLine = typeof input2.start_line === "number" ? input2.start_line : null;
|
|
5501
5194
|
const endLine = typeof input2.end_line === "number" ? input2.end_line : null;
|
|
@@ -5510,7 +5203,7 @@ function buildTitle(entry, kind) {
|
|
|
5510
5203
|
return capitalize(entry.name);
|
|
5511
5204
|
}
|
|
5512
5205
|
if (kind === "edit" || kind === "write" || kind === "delete") {
|
|
5513
|
-
const filePath =
|
|
5206
|
+
const filePath = getStringField(input2, ["file_path", "filePath", "path"]);
|
|
5514
5207
|
if (filePath) return filePath;
|
|
5515
5208
|
return capitalize(entry.name);
|
|
5516
5209
|
}
|
|
@@ -5542,6 +5235,36 @@ function buildTitle(entry, kind) {
|
|
|
5542
5235
|
}
|
|
5543
5236
|
return capitalize(entry.name);
|
|
5544
5237
|
}
|
|
5238
|
+
if (nameLower === "apply_patch") {
|
|
5239
|
+
const patchText = getStringField(input2, ["patchText", "patch_text"]);
|
|
5240
|
+
if (patchText) {
|
|
5241
|
+
const targets = parseApplyPatchTargets(patchText);
|
|
5242
|
+
if (targets.length === 1) return targets[0];
|
|
5243
|
+
if (targets.length > 1) {
|
|
5244
|
+
const shown = targets.slice(0, 2).join(", ");
|
|
5245
|
+
const remaining = targets.length - 2;
|
|
5246
|
+
return remaining > 0 ? `${shown} (+${remaining} more)` : shown;
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
return "apply_patch";
|
|
5250
|
+
}
|
|
5251
|
+
if (nameLower === "todowrite") {
|
|
5252
|
+
const todos = Array.isArray(input2.todos) ? input2.todos : [];
|
|
5253
|
+
if (todos.length > 0) {
|
|
5254
|
+
const inProgress = todos.filter((t) => {
|
|
5255
|
+
if (!t || typeof t !== "object") return false;
|
|
5256
|
+
const status = t.status;
|
|
5257
|
+
return status === "in_progress";
|
|
5258
|
+
}).length;
|
|
5259
|
+
const completed = todos.filter((t) => {
|
|
5260
|
+
if (!t || typeof t !== "object") return false;
|
|
5261
|
+
const status = t.status;
|
|
5262
|
+
return status === "completed";
|
|
5263
|
+
}).length;
|
|
5264
|
+
return `Todo list (${completed}/${todos.length} done${inProgress > 0 ? `, ${inProgress} active` : ""})`;
|
|
5265
|
+
}
|
|
5266
|
+
return "Todo list";
|
|
5267
|
+
}
|
|
5545
5268
|
if (kind === "fetch" || kind === "web") {
|
|
5546
5269
|
const url = typeof input2.url === "string" ? input2.url : null;
|
|
5547
5270
|
if (url && url !== "undefined") return url.length > 60 ? url.slice(0, 57) + "..." : url;
|
|
@@ -5549,11 +5272,36 @@ function buildTitle(entry, kind) {
|
|
|
5549
5272
|
if (query && query !== "undefined") return query.length > 60 ? query.slice(0, 57) + "..." : query;
|
|
5550
5273
|
return capitalize(entry.name);
|
|
5551
5274
|
}
|
|
5552
|
-
if (
|
|
5275
|
+
if (nameLower === "skill" && typeof input2.skill === "string" && input2.skill) {
|
|
5553
5276
|
return input2.skill;
|
|
5554
5277
|
}
|
|
5278
|
+
if (nameLower === "apply_patch") {
|
|
5279
|
+
const patchText = getStringField(input2, ["patchText", "patch_text"]);
|
|
5280
|
+
if (patchText) {
|
|
5281
|
+
const targets = parseApplyPatchTargets(patchText);
|
|
5282
|
+
if (targets.length === 1) return targets[0];
|
|
5283
|
+
if (targets.length > 1) {
|
|
5284
|
+
const shown = targets.slice(0, 2).join(", ");
|
|
5285
|
+
const rest = targets.length - 2;
|
|
5286
|
+
return rest > 0 ? `${shown} (+${rest} more)` : shown;
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
return "apply_patch";
|
|
5290
|
+
}
|
|
5291
|
+
if (nameLower === "todowrite") {
|
|
5292
|
+
const todos = Array.isArray(input2.todos) ? input2.todos : [];
|
|
5293
|
+
if (todos.length > 0) {
|
|
5294
|
+
const completed = todos.filter((t) => isRecord(t) && t.status === "completed").length;
|
|
5295
|
+
const active = todos.filter((t) => isRecord(t) && t.status === "in_progress").length;
|
|
5296
|
+
return `Todo list (${completed}/${todos.length} done${active > 0 ? `, ${active} active` : ""})`;
|
|
5297
|
+
}
|
|
5298
|
+
return "Todo list";
|
|
5299
|
+
}
|
|
5555
5300
|
return entry.name;
|
|
5556
5301
|
}
|
|
5302
|
+
function isRecord(value) {
|
|
5303
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5304
|
+
}
|
|
5557
5305
|
function buildOutputSummary(content) {
|
|
5558
5306
|
const lines = content.split("\n").length;
|
|
5559
5307
|
return `${lines} line${lines === 1 ? "" : "s"} of output`;
|
|
@@ -5628,6 +5376,7 @@ var init_display_spec_builder = __esm({
|
|
|
5628
5376
|
viewerLinks: entry.viewerLinks,
|
|
5629
5377
|
outputViewerLink,
|
|
5630
5378
|
outputFallbackContent,
|
|
5379
|
+
workingDirectory: sessionContext?.workingDirectory,
|
|
5631
5380
|
status: entry.status,
|
|
5632
5381
|
isNoise: entry.isNoise,
|
|
5633
5382
|
isHidden
|
|
@@ -5858,20 +5607,33 @@ function renderToolCard(snap) {
|
|
|
5858
5607
|
}
|
|
5859
5608
|
return sections.join("\n\n");
|
|
5860
5609
|
}
|
|
5861
|
-
function
|
|
5862
|
-
|
|
5610
|
+
function normalizePathLike(pathLike) {
|
|
5611
|
+
return pathLike.replace(/\\/g, "/");
|
|
5612
|
+
}
|
|
5613
|
+
function shortenTitle(title, kind, workingDirectory) {
|
|
5614
|
+
if (!title.includes("/")) return title;
|
|
5863
5615
|
const parenIdx = title.indexOf(" (");
|
|
5864
5616
|
const pathPart = parenIdx > 0 ? title.slice(0, parenIdx) : title;
|
|
5865
5617
|
const rangePart = parenIdx > 0 ? title.slice(parenIdx) : "";
|
|
5866
|
-
|
|
5867
|
-
|
|
5618
|
+
if (workingDirectory) {
|
|
5619
|
+
const normalizedPathPart = normalizePathLike(pathPart);
|
|
5620
|
+
const normalizedCwd = normalizePathLike(workingDirectory).replace(/\/+$/, "");
|
|
5621
|
+
const prefix = `${normalizedCwd}/`;
|
|
5622
|
+
const relativized = normalizedPathPart.split(", ").map((segment) => segment.startsWith(prefix) ? segment.slice(prefix.length) : segment).join(", ");
|
|
5623
|
+
if (relativized !== normalizedPathPart) return relativized + rangePart;
|
|
5624
|
+
}
|
|
5625
|
+
if (FILE_KINDS.has(kind)) return basename(pathPart) + rangePart;
|
|
5626
|
+
return title;
|
|
5627
|
+
}
|
|
5628
|
+
function basename(pathLike) {
|
|
5629
|
+
return pathLike.replace(/\\/g, "/").split("/").pop() || pathLike;
|
|
5868
5630
|
}
|
|
5869
5631
|
function renderSpecSection(spec) {
|
|
5870
5632
|
const lines = [];
|
|
5871
5633
|
const DONE = /* @__PURE__ */ new Set(["completed", "done", "failed", "error"]);
|
|
5872
5634
|
const statusPrefix = spec.status === "error" || spec.status === "failed" ? "\u274C " : DONE.has(spec.status) ? "\u2705 " : "\u{1F504} ";
|
|
5873
5635
|
const kindLabel = KIND_LABELS[spec.kind];
|
|
5874
|
-
const displayTitle = shortenTitle(spec.title, spec.kind);
|
|
5636
|
+
const displayTitle = shortenTitle(spec.title, spec.kind, spec.workingDirectory);
|
|
5875
5637
|
const hasUniqueTitle = displayTitle && displayTitle.toLowerCase() !== kindLabel?.toLowerCase() && displayTitle.toLowerCase() !== spec.kind;
|
|
5876
5638
|
let titleLine;
|
|
5877
5639
|
if (kindLabel) {
|
|
@@ -5900,9 +5662,9 @@ function renderSpecSection(spec) {
|
|
|
5900
5662
|
}
|
|
5901
5663
|
if (spec.viewerLinks?.file || spec.viewerLinks?.diff || spec.outputViewerLink) {
|
|
5902
5664
|
const linkParts = [];
|
|
5903
|
-
const
|
|
5665
|
+
const linkName = basename(displayTitle || kindLabel || spec.kind);
|
|
5904
5666
|
if (spec.viewerLinks?.file)
|
|
5905
|
-
linkParts.push(`<a href="${escapeHtml(spec.viewerLinks.file)}">View ${escapeHtml(
|
|
5667
|
+
linkParts.push(`<a href="${escapeHtml(spec.viewerLinks.file)}">View ${escapeHtml(linkName)}</a>`);
|
|
5906
5668
|
if (spec.viewerLinks?.diff)
|
|
5907
5669
|
linkParts.push(`<a href="${escapeHtml(spec.viewerLinks.diff)}">View diff</a>`);
|
|
5908
5670
|
if (spec.outputViewerLink)
|
|
@@ -5953,13 +5715,13 @@ __export(version_exports, {
|
|
|
5953
5715
|
runUpdate: () => runUpdate
|
|
5954
5716
|
});
|
|
5955
5717
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5956
|
-
import { dirname as
|
|
5957
|
-
import { existsSync as
|
|
5718
|
+
import { dirname as dirname10, join as join20, resolve as resolve5 } from "path";
|
|
5719
|
+
import { existsSync as existsSync17, readFileSync as readFileSync15 } from "fs";
|
|
5958
5720
|
function findPackageJson() {
|
|
5959
|
-
let dir =
|
|
5721
|
+
let dir = dirname10(fileURLToPath2(import.meta.url));
|
|
5960
5722
|
for (let i = 0; i < 5; i++) {
|
|
5961
|
-
const candidate =
|
|
5962
|
-
if (
|
|
5723
|
+
const candidate = join20(dir, "package.json");
|
|
5724
|
+
if (existsSync17(candidate)) return candidate;
|
|
5963
5725
|
const parent = resolve5(dir, "..");
|
|
5964
5726
|
if (parent === dir) break;
|
|
5965
5727
|
dir = parent;
|
|
@@ -5970,7 +5732,7 @@ function getCurrentVersion() {
|
|
|
5970
5732
|
try {
|
|
5971
5733
|
const pkgPath = findPackageJson();
|
|
5972
5734
|
if (!pkgPath) return "0.0.0-dev";
|
|
5973
|
-
const pkg = JSON.parse(
|
|
5735
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
5974
5736
|
return pkg.version;
|
|
5975
5737
|
} catch {
|
|
5976
5738
|
return "0.0.0-dev";
|
|
@@ -6347,11 +6109,16 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onCo
|
|
|
6347
6109
|
}
|
|
6348
6110
|
function cacheWorkspace(agentKey, workspace) {
|
|
6349
6111
|
const now = Date.now();
|
|
6112
|
+
for (const [id2, entry] of workspaceCache) {
|
|
6113
|
+
if (now - entry.ts > 5 * 6e4) {
|
|
6114
|
+
workspaceCache.delete(id2);
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6350
6117
|
if (workspaceCache.size > WS_CACHE_MAX) {
|
|
6351
|
-
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6118
|
+
const sorted = [...workspaceCache.entries()].sort((a, b) => a[1].ts - b[1].ts);
|
|
6119
|
+
const toDelete = sorted.slice(0, workspaceCache.size - WS_CACHE_MAX);
|
|
6120
|
+
for (const [id2] of toDelete) {
|
|
6121
|
+
workspaceCache.delete(id2);
|
|
6355
6122
|
}
|
|
6356
6123
|
}
|
|
6357
6124
|
const id = nextWsId++;
|
|
@@ -6793,9 +6560,12 @@ __export(integrate_exports, {
|
|
|
6793
6560
|
listIntegrations: () => listIntegrations,
|
|
6794
6561
|
uninstallIntegration: () => uninstallIntegration
|
|
6795
6562
|
});
|
|
6796
|
-
import { existsSync as
|
|
6797
|
-
import { join as
|
|
6563
|
+
import { existsSync as existsSync18, mkdirSync as mkdirSync11, readFileSync as readFileSync16, writeFileSync as writeFileSync11, unlinkSync as unlinkSync7, chmodSync, rmdirSync } from "fs";
|
|
6564
|
+
import { join as join21, dirname as dirname11 } from "path";
|
|
6798
6565
|
import { homedir as homedir10 } from "os";
|
|
6566
|
+
function isHooksIntegrationSpec(spec) {
|
|
6567
|
+
return spec.strategy === "hooks";
|
|
6568
|
+
}
|
|
6799
6569
|
function expandPath(p) {
|
|
6800
6570
|
return p.replace(/^~/, homedir10());
|
|
6801
6571
|
}
|
|
@@ -6913,12 +6683,48 @@ Examples:
|
|
|
6913
6683
|
/openacp:handoff telegram
|
|
6914
6684
|
`;
|
|
6915
6685
|
}
|
|
6686
|
+
function generateOpencodeHandoffCommand(spec) {
|
|
6687
|
+
return `---
|
|
6688
|
+
name: ${spec.handoffCommandName}
|
|
6689
|
+
description: Transfer current OpenCode session to OpenACP (Telegram/Discord)
|
|
6690
|
+
---
|
|
6691
|
+
|
|
6692
|
+
Use OPENCODE_SESSION_ID from injected context, then run:
|
|
6693
|
+
|
|
6694
|
+
openacp adopt opencode <OPENCODE_SESSION_ID>
|
|
6695
|
+
|
|
6696
|
+
If a channel argument is provided, append:
|
|
6697
|
+
|
|
6698
|
+
--channel <channel_name>
|
|
6699
|
+
|
|
6700
|
+
Usage:
|
|
6701
|
+
/${spec.handoffCommandName}
|
|
6702
|
+
/${spec.handoffCommandName} telegram
|
|
6703
|
+
`;
|
|
6704
|
+
}
|
|
6705
|
+
function generateOpencodePlugin(spec) {
|
|
6706
|
+
return `export const OpenACPHandoffPlugin = async () => {
|
|
6707
|
+
return {
|
|
6708
|
+
"command.execute.before": async (input, output) => {
|
|
6709
|
+
if (input.command !== ${JSON.stringify(spec.handoffCommandName)}) return
|
|
6710
|
+
output.parts.unshift({
|
|
6711
|
+
id: "openacp-session-inject",
|
|
6712
|
+
sessionID: input.sessionID,
|
|
6713
|
+
messageID: "openacp-inject",
|
|
6714
|
+
type: "text",
|
|
6715
|
+
text: \`OPENCODE_SESSION_ID: \${input.sessionID}\\n\`,
|
|
6716
|
+
})
|
|
6717
|
+
},
|
|
6718
|
+
}
|
|
6719
|
+
}
|
|
6720
|
+
`;
|
|
6721
|
+
}
|
|
6916
6722
|
function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
|
|
6917
6723
|
const fullPath = expandPath(settingsPath);
|
|
6918
6724
|
let settings = {};
|
|
6919
|
-
if (
|
|
6920
|
-
const raw =
|
|
6921
|
-
|
|
6725
|
+
if (existsSync18(fullPath)) {
|
|
6726
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6727
|
+
writeFileSync11(`${fullPath}.bak`, raw);
|
|
6922
6728
|
settings = JSON.parse(raw);
|
|
6923
6729
|
}
|
|
6924
6730
|
const hooks = settings.hooks ?? {};
|
|
@@ -6933,15 +6739,15 @@ function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
6933
6739
|
hooks: [{ type: "command", command: hookScriptPath }]
|
|
6934
6740
|
});
|
|
6935
6741
|
}
|
|
6936
|
-
|
|
6937
|
-
|
|
6742
|
+
mkdirSync11(dirname11(fullPath), { recursive: true });
|
|
6743
|
+
writeFileSync11(fullPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6938
6744
|
}
|
|
6939
6745
|
function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
6940
6746
|
const fullPath = expandPath(settingsPath);
|
|
6941
6747
|
let config = { version: 1 };
|
|
6942
|
-
if (
|
|
6943
|
-
const raw =
|
|
6944
|
-
|
|
6748
|
+
if (existsSync18(fullPath)) {
|
|
6749
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6750
|
+
writeFileSync11(`${fullPath}.bak`, raw);
|
|
6945
6751
|
config = JSON.parse(raw);
|
|
6946
6752
|
}
|
|
6947
6753
|
const hooks = config.hooks ?? {};
|
|
@@ -6952,13 +6758,13 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
6952
6758
|
if (!alreadyInstalled) {
|
|
6953
6759
|
eventHooks.push({ command: hookScriptPath });
|
|
6954
6760
|
}
|
|
6955
|
-
|
|
6956
|
-
|
|
6761
|
+
mkdirSync11(dirname11(fullPath), { recursive: true });
|
|
6762
|
+
writeFileSync11(fullPath, JSON.stringify(config, null, 2) + "\n");
|
|
6957
6763
|
}
|
|
6958
6764
|
function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
6959
6765
|
const fullPath = expandPath(settingsPath);
|
|
6960
|
-
if (!
|
|
6961
|
-
const raw =
|
|
6766
|
+
if (!existsSync18(fullPath)) return;
|
|
6767
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6962
6768
|
const settings = JSON.parse(raw);
|
|
6963
6769
|
const hooks = settings.hooks;
|
|
6964
6770
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -6968,12 +6774,12 @@ function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
|
6968
6774
|
if (hooks[hookEvent].length === 0) {
|
|
6969
6775
|
delete hooks[hookEvent];
|
|
6970
6776
|
}
|
|
6971
|
-
|
|
6777
|
+
writeFileSync11(fullPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6972
6778
|
}
|
|
6973
6779
|
function removeFromHooksJson(settingsPath, hookEvent) {
|
|
6974
6780
|
const fullPath = expandPath(settingsPath);
|
|
6975
|
-
if (!
|
|
6976
|
-
const raw =
|
|
6781
|
+
if (!existsSync18(fullPath)) return;
|
|
6782
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6977
6783
|
const config = JSON.parse(raw);
|
|
6978
6784
|
const hooks = config.hooks;
|
|
6979
6785
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -6983,9 +6789,9 @@ function removeFromHooksJson(settingsPath, hookEvent) {
|
|
|
6983
6789
|
if (hooks[hookEvent].length === 0) {
|
|
6984
6790
|
delete hooks[hookEvent];
|
|
6985
6791
|
}
|
|
6986
|
-
|
|
6792
|
+
writeFileSync11(fullPath, JSON.stringify(config, null, 2) + "\n");
|
|
6987
6793
|
}
|
|
6988
|
-
async function
|
|
6794
|
+
async function installHooksIntegration(agentKey, spec) {
|
|
6989
6795
|
const logs = [];
|
|
6990
6796
|
try {
|
|
6991
6797
|
if (!commandExists("jq")) {
|
|
@@ -6995,31 +6801,31 @@ async function installIntegration(agentKey, spec) {
|
|
|
6995
6801
|
};
|
|
6996
6802
|
}
|
|
6997
6803
|
const hooksDir = expandPath(spec.hooksDirPath);
|
|
6998
|
-
|
|
6999
|
-
const injectPath =
|
|
7000
|
-
|
|
6804
|
+
mkdirSync11(hooksDir, { recursive: true });
|
|
6805
|
+
const injectPath = join21(hooksDir, "openacp-inject-session.sh");
|
|
6806
|
+
writeFileSync11(injectPath, generateInjectScript(agentKey, spec));
|
|
7001
6807
|
chmodSync(injectPath, 493);
|
|
7002
6808
|
logs.push(`Created ${injectPath}`);
|
|
7003
|
-
const handoffPath =
|
|
7004
|
-
|
|
6809
|
+
const handoffPath = join21(hooksDir, "openacp-handoff.sh");
|
|
6810
|
+
writeFileSync11(handoffPath, generateHandoffScript(agentKey));
|
|
7005
6811
|
chmodSync(handoffPath, 493);
|
|
7006
6812
|
logs.push(`Created ${handoffPath}`);
|
|
7007
6813
|
if (spec.commandsPath && spec.handoffCommandName) {
|
|
7008
6814
|
if (spec.commandFormat === "skill") {
|
|
7009
|
-
const skillDir = expandPath(
|
|
7010
|
-
|
|
7011
|
-
const skillPath =
|
|
7012
|
-
|
|
6815
|
+
const skillDir = expandPath(join21(spec.commandsPath, spec.handoffCommandName));
|
|
6816
|
+
mkdirSync11(skillDir, { recursive: true });
|
|
6817
|
+
const skillPath = join21(skillDir, "SKILL.md");
|
|
6818
|
+
writeFileSync11(skillPath, generateHandoffCommand(agentKey, spec));
|
|
7013
6819
|
logs.push(`Created ${skillPath}`);
|
|
7014
6820
|
} else {
|
|
7015
6821
|
const cmdsDir = expandPath(spec.commandsPath);
|
|
7016
|
-
|
|
7017
|
-
const cmdPath =
|
|
7018
|
-
|
|
6822
|
+
mkdirSync11(cmdsDir, { recursive: true });
|
|
6823
|
+
const cmdPath = join21(cmdsDir, `${spec.handoffCommandName}.md`);
|
|
6824
|
+
writeFileSync11(cmdPath, generateHandoffCommand(agentKey, spec));
|
|
7019
6825
|
logs.push(`Created ${cmdPath}`);
|
|
7020
6826
|
}
|
|
7021
6827
|
}
|
|
7022
|
-
const injectFullPath =
|
|
6828
|
+
const injectFullPath = join21(hooksDir, "openacp-inject-session.sh");
|
|
7023
6829
|
if (spec.settingsFormat === "hooks_json") {
|
|
7024
6830
|
mergeHooksJson(spec.settingsPath, spec.hookEvent, injectFullPath);
|
|
7025
6831
|
} else {
|
|
@@ -7032,22 +6838,22 @@ async function installIntegration(agentKey, spec) {
|
|
|
7032
6838
|
return { success: false, logs };
|
|
7033
6839
|
}
|
|
7034
6840
|
}
|
|
7035
|
-
async function
|
|
6841
|
+
async function uninstallHooksIntegration(agentKey, spec) {
|
|
7036
6842
|
const logs = [];
|
|
7037
6843
|
try {
|
|
7038
6844
|
const hooksDir = expandPath(spec.hooksDirPath);
|
|
7039
6845
|
for (const filename of ["openacp-inject-session.sh", "openacp-handoff.sh"]) {
|
|
7040
|
-
const filePath =
|
|
7041
|
-
if (
|
|
6846
|
+
const filePath = join21(hooksDir, filename);
|
|
6847
|
+
if (existsSync18(filePath)) {
|
|
7042
6848
|
unlinkSync7(filePath);
|
|
7043
6849
|
logs.push(`Removed ${filePath}`);
|
|
7044
6850
|
}
|
|
7045
6851
|
}
|
|
7046
6852
|
if (spec.commandsPath && spec.handoffCommandName) {
|
|
7047
6853
|
if (spec.commandFormat === "skill") {
|
|
7048
|
-
const skillDir = expandPath(
|
|
7049
|
-
const skillPath =
|
|
7050
|
-
if (
|
|
6854
|
+
const skillDir = expandPath(join21(spec.commandsPath, spec.handoffCommandName));
|
|
6855
|
+
const skillPath = join21(skillDir, "SKILL.md");
|
|
6856
|
+
if (existsSync18(skillPath)) {
|
|
7051
6857
|
unlinkSync7(skillPath);
|
|
7052
6858
|
try {
|
|
7053
6859
|
rmdirSync(skillDir);
|
|
@@ -7056,8 +6862,8 @@ async function uninstallIntegration(agentKey, spec) {
|
|
|
7056
6862
|
logs.push(`Removed ${skillPath}`);
|
|
7057
6863
|
}
|
|
7058
6864
|
} else {
|
|
7059
|
-
const cmdPath = expandPath(
|
|
7060
|
-
if (
|
|
6865
|
+
const cmdPath = expandPath(join21(spec.commandsPath, `${spec.handoffCommandName}.md`));
|
|
6866
|
+
if (existsSync18(cmdPath)) {
|
|
7061
6867
|
unlinkSync7(cmdPath);
|
|
7062
6868
|
logs.push(`Removed ${cmdPath}`);
|
|
7063
6869
|
}
|
|
@@ -7075,14 +6881,82 @@ async function uninstallIntegration(agentKey, spec) {
|
|
|
7075
6881
|
return { success: false, logs };
|
|
7076
6882
|
}
|
|
7077
6883
|
}
|
|
6884
|
+
async function installPluginIntegration(_agentKey, spec) {
|
|
6885
|
+
const logs = [];
|
|
6886
|
+
try {
|
|
6887
|
+
const commandsDir = expandPath(spec.commandsPath);
|
|
6888
|
+
mkdirSync11(commandsDir, { recursive: true });
|
|
6889
|
+
const commandPath = join21(commandsDir, spec.handoffCommandFile);
|
|
6890
|
+
const pluginsDir = expandPath(spec.pluginsPath);
|
|
6891
|
+
mkdirSync11(pluginsDir, { recursive: true });
|
|
6892
|
+
const pluginPath = join21(pluginsDir, spec.pluginFileName);
|
|
6893
|
+
if (existsSync18(commandPath) && existsSync18(pluginPath)) {
|
|
6894
|
+
logs.push("Already installed, skipping.");
|
|
6895
|
+
return { success: true, logs };
|
|
6896
|
+
}
|
|
6897
|
+
if (existsSync18(commandPath) || existsSync18(pluginPath)) {
|
|
6898
|
+
logs.push("Overwriting existing files.");
|
|
6899
|
+
}
|
|
6900
|
+
writeFileSync11(commandPath, generateOpencodeHandoffCommand(spec));
|
|
6901
|
+
logs.push(`Created ${commandPath}`);
|
|
6902
|
+
writeFileSync11(pluginPath, generateOpencodePlugin(spec));
|
|
6903
|
+
logs.push(`Created ${pluginPath}`);
|
|
6904
|
+
return { success: true, logs };
|
|
6905
|
+
} catch (err) {
|
|
6906
|
+
logs.push(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6907
|
+
return { success: false, logs };
|
|
6908
|
+
}
|
|
6909
|
+
}
|
|
6910
|
+
async function uninstallPluginIntegration(_agentKey, spec) {
|
|
6911
|
+
const logs = [];
|
|
6912
|
+
try {
|
|
6913
|
+
const commandPath = join21(expandPath(spec.commandsPath), spec.handoffCommandFile);
|
|
6914
|
+
let removedCount = 0;
|
|
6915
|
+
if (existsSync18(commandPath)) {
|
|
6916
|
+
unlinkSync7(commandPath);
|
|
6917
|
+
logs.push(`Removed ${commandPath}`);
|
|
6918
|
+
removedCount += 1;
|
|
6919
|
+
}
|
|
6920
|
+
const pluginPath = join21(expandPath(spec.pluginsPath), spec.pluginFileName);
|
|
6921
|
+
if (existsSync18(pluginPath)) {
|
|
6922
|
+
unlinkSync7(pluginPath);
|
|
6923
|
+
logs.push(`Removed ${pluginPath}`);
|
|
6924
|
+
removedCount += 1;
|
|
6925
|
+
}
|
|
6926
|
+
if (removedCount === 0) {
|
|
6927
|
+
logs.push("Nothing to remove.");
|
|
6928
|
+
}
|
|
6929
|
+
return { success: true, logs };
|
|
6930
|
+
} catch (err) {
|
|
6931
|
+
logs.push(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6932
|
+
return { success: false, logs };
|
|
6933
|
+
}
|
|
6934
|
+
}
|
|
6935
|
+
async function installIntegration(agentKey, spec) {
|
|
6936
|
+
if (isHooksIntegrationSpec(spec)) {
|
|
6937
|
+
return installHooksIntegration(agentKey, spec);
|
|
6938
|
+
}
|
|
6939
|
+
return installPluginIntegration(agentKey, spec);
|
|
6940
|
+
}
|
|
6941
|
+
async function uninstallIntegration(agentKey, spec) {
|
|
6942
|
+
if (isHooksIntegrationSpec(spec)) {
|
|
6943
|
+
return uninstallHooksIntegration(agentKey, spec);
|
|
6944
|
+
}
|
|
6945
|
+
return uninstallPluginIntegration(agentKey, spec);
|
|
6946
|
+
}
|
|
7078
6947
|
function buildHandoffItem(agentKey, spec) {
|
|
7079
|
-
const hooksDir = expandPath(spec.hooksDirPath);
|
|
7080
6948
|
return {
|
|
7081
6949
|
id: "handoff",
|
|
7082
6950
|
name: "Handoff",
|
|
7083
6951
|
description: "Transfer sessions between terminal and messaging platforms",
|
|
7084
6952
|
isInstalled() {
|
|
7085
|
-
|
|
6953
|
+
if (isHooksIntegrationSpec(spec)) {
|
|
6954
|
+
const hooksDir = expandPath(spec.hooksDirPath);
|
|
6955
|
+
return existsSync18(join21(hooksDir, "openacp-inject-session.sh")) && existsSync18(join21(hooksDir, "openacp-handoff.sh"));
|
|
6956
|
+
}
|
|
6957
|
+
const commandPath = join21(expandPath(spec.commandsPath), spec.handoffCommandFile);
|
|
6958
|
+
const pluginPath = join21(expandPath(spec.pluginsPath), spec.pluginFileName);
|
|
6959
|
+
return existsSync18(commandPath) && existsSync18(pluginPath);
|
|
7086
6960
|
},
|
|
7087
6961
|
install: () => installIntegration(agentKey, spec),
|
|
7088
6962
|
uninstall: () => uninstallIntegration(agentKey, spec)
|
|
@@ -7094,23 +6968,24 @@ function getSkillBasePath(spec) {
|
|
|
7094
6968
|
return expandPath(skillsBase);
|
|
7095
6969
|
}
|
|
7096
6970
|
function buildTunnelItem(spec) {
|
|
7097
|
-
if (!spec.commandsPath) return null;
|
|
6971
|
+
if (!isHooksIntegrationSpec(spec) || !spec.commandsPath) return null;
|
|
6972
|
+
const hooksSpec = spec;
|
|
7098
6973
|
function getTunnelPath() {
|
|
7099
|
-
return
|
|
6974
|
+
return join21(getSkillBasePath(hooksSpec), "openacp-tunnel", "SKILL.md");
|
|
7100
6975
|
}
|
|
7101
6976
|
return {
|
|
7102
6977
|
id: "tunnel",
|
|
7103
6978
|
name: "Tunnel",
|
|
7104
6979
|
description: "Expose local ports to the internet via OpenACP tunnel",
|
|
7105
6980
|
isInstalled() {
|
|
7106
|
-
return
|
|
6981
|
+
return existsSync18(getTunnelPath());
|
|
7107
6982
|
},
|
|
7108
6983
|
async install() {
|
|
7109
6984
|
const logs = [];
|
|
7110
6985
|
try {
|
|
7111
6986
|
const skillPath = getTunnelPath();
|
|
7112
|
-
|
|
7113
|
-
|
|
6987
|
+
mkdirSync11(dirname11(skillPath), { recursive: true });
|
|
6988
|
+
writeFileSync11(skillPath, generateTunnelCommand());
|
|
7114
6989
|
logs.push(`Created ${skillPath}`);
|
|
7115
6990
|
return { success: true, logs };
|
|
7116
6991
|
} catch (err) {
|
|
@@ -7122,10 +6997,10 @@ function buildTunnelItem(spec) {
|
|
|
7122
6997
|
const logs = [];
|
|
7123
6998
|
try {
|
|
7124
6999
|
const skillPath = getTunnelPath();
|
|
7125
|
-
if (
|
|
7000
|
+
if (existsSync18(skillPath)) {
|
|
7126
7001
|
unlinkSync7(skillPath);
|
|
7127
7002
|
try {
|
|
7128
|
-
rmdirSync(
|
|
7003
|
+
rmdirSync(dirname11(skillPath));
|
|
7129
7004
|
} catch {
|
|
7130
7005
|
}
|
|
7131
7006
|
logs.push(`Removed ${skillPath}`);
|
|
@@ -7561,7 +7436,7 @@ async function buildSettingsKeyboard(core) {
|
|
|
7561
7436
|
const fields = getSafeFields();
|
|
7562
7437
|
const kb = new InlineKeyboard6();
|
|
7563
7438
|
for (const field of fields) {
|
|
7564
|
-
const value =
|
|
7439
|
+
const value = getConfigValue(core.configManager.get(), field.path);
|
|
7565
7440
|
const label = formatFieldLabel(field, value);
|
|
7566
7441
|
if (field.type === "toggle") {
|
|
7567
7442
|
kb.text(`${label}`, `s:toggle:${field.path}`).row();
|
|
@@ -7604,11 +7479,10 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
|
7604
7479
|
const fieldPath = ctx.callbackQuery.data.replace("s:toggle:", "");
|
|
7605
7480
|
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
7606
7481
|
if (!fieldDef) return;
|
|
7607
|
-
const
|
|
7608
|
-
const currentValue = await getFieldValueAsync(fieldDef, core.configManager, settingsManager);
|
|
7482
|
+
const currentValue = getConfigValue(core.configManager.get(), fieldDef.path);
|
|
7609
7483
|
const newValue = !currentValue;
|
|
7610
7484
|
try {
|
|
7611
|
-
await setFieldValueAsync(fieldDef, newValue, core.configManager
|
|
7485
|
+
await setFieldValueAsync(fieldDef, newValue, core.configManager);
|
|
7612
7486
|
const toast = isHotReloadable(fieldPath) ? `\u2705 ${fieldPath} = ${newValue}` : `\u2705 ${fieldPath} = ${newValue} (restart needed)`;
|
|
7613
7487
|
try {
|
|
7614
7488
|
await ctx.answerCallbackQuery({ text: toast });
|
|
@@ -7632,7 +7506,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
|
7632
7506
|
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
7633
7507
|
if (!fieldDef) return;
|
|
7634
7508
|
const options = resolveOptions(fieldDef, config) ?? [];
|
|
7635
|
-
const currentValue =
|
|
7509
|
+
const currentValue = getConfigValue(core.configManager.get(), fieldDef.path);
|
|
7636
7510
|
const kb = new InlineKeyboard6();
|
|
7637
7511
|
for (const opt of options) {
|
|
7638
7512
|
const marker = opt === String(currentValue) ? " \u2713" : "";
|
|
@@ -7666,9 +7540,7 @@ Select a value:`, {
|
|
|
7666
7540
|
const speechSettings = await sm.loadSettings("@openacp/speech");
|
|
7667
7541
|
hasApiKey = !!speechSettings.groqApiKey;
|
|
7668
7542
|
} else {
|
|
7669
|
-
|
|
7670
|
-
const providerConfig = config.speech?.stt?.providers?.[newValue];
|
|
7671
|
-
hasApiKey = !!providerConfig?.apiKey;
|
|
7543
|
+
hasApiKey = false;
|
|
7672
7544
|
}
|
|
7673
7545
|
if (!hasApiKey) {
|
|
7674
7546
|
const assistant = getAssistantSession();
|
|
@@ -7688,7 +7560,7 @@ Select a value:`, {
|
|
|
7688
7560
|
return;
|
|
7689
7561
|
}
|
|
7690
7562
|
}
|
|
7691
|
-
await setFieldValueAsync(fieldDef, newValue, core.configManager
|
|
7563
|
+
await setFieldValueAsync(fieldDef, newValue, core.configManager);
|
|
7692
7564
|
try {
|
|
7693
7565
|
await ctx.answerCallbackQuery({ text: `\u2705 ${fieldPath} = ${newValue}` });
|
|
7694
7566
|
} catch {
|
|
@@ -7713,7 +7585,7 @@ Tap to change:`, {
|
|
|
7713
7585
|
const fieldPath = ctx.callbackQuery.data.replace("s:input:", "");
|
|
7714
7586
|
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
7715
7587
|
if (!fieldDef) return;
|
|
7716
|
-
const currentValue =
|
|
7588
|
+
const currentValue = getConfigValue(core.configManager.get(), fieldDef.path);
|
|
7717
7589
|
const assistant = getAssistantSession();
|
|
7718
7590
|
if (!assistant) {
|
|
7719
7591
|
try {
|
|
@@ -9274,6 +9146,130 @@ var init_renderer2 = __esm({
|
|
|
9274
9146
|
}
|
|
9275
9147
|
});
|
|
9276
9148
|
|
|
9149
|
+
// src/plugins/telegram/validators.ts
|
|
9150
|
+
var validators_exports = {};
|
|
9151
|
+
__export(validators_exports, {
|
|
9152
|
+
checkTopicsPrerequisites: () => checkTopicsPrerequisites,
|
|
9153
|
+
validateBotAdmin: () => validateBotAdmin,
|
|
9154
|
+
validateBotToken: () => validateBotToken,
|
|
9155
|
+
validateChatId: () => validateChatId
|
|
9156
|
+
});
|
|
9157
|
+
async function validateBotToken(token) {
|
|
9158
|
+
try {
|
|
9159
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
9160
|
+
const data = await res.json();
|
|
9161
|
+
if (data.ok && data.result) {
|
|
9162
|
+
return {
|
|
9163
|
+
ok: true,
|
|
9164
|
+
botName: data.result.first_name,
|
|
9165
|
+
botUsername: data.result.username
|
|
9166
|
+
};
|
|
9167
|
+
}
|
|
9168
|
+
return { ok: false, error: data.description || "Invalid token" };
|
|
9169
|
+
} catch (err) {
|
|
9170
|
+
return { ok: false, error: err.message };
|
|
9171
|
+
}
|
|
9172
|
+
}
|
|
9173
|
+
async function validateChatId(token, chatId) {
|
|
9174
|
+
try {
|
|
9175
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
9176
|
+
method: "POST",
|
|
9177
|
+
headers: { "Content-Type": "application/json" },
|
|
9178
|
+
body: JSON.stringify({ chat_id: chatId })
|
|
9179
|
+
});
|
|
9180
|
+
const data = await res.json();
|
|
9181
|
+
if (!data.ok || !data.result) {
|
|
9182
|
+
return { ok: false, error: data.description || "Invalid chat ID" };
|
|
9183
|
+
}
|
|
9184
|
+
if (data.result.type !== "supergroup") {
|
|
9185
|
+
return {
|
|
9186
|
+
ok: false,
|
|
9187
|
+
error: `Chat must be a group (not a channel or private chat). Got: "${data.result.type}"`
|
|
9188
|
+
};
|
|
9189
|
+
}
|
|
9190
|
+
return {
|
|
9191
|
+
ok: true,
|
|
9192
|
+
title: data.result.title,
|
|
9193
|
+
isForum: data.result.is_forum === true
|
|
9194
|
+
};
|
|
9195
|
+
} catch (err) {
|
|
9196
|
+
return { ok: false, error: err.message };
|
|
9197
|
+
}
|
|
9198
|
+
}
|
|
9199
|
+
async function validateBotAdmin(token, chatId) {
|
|
9200
|
+
try {
|
|
9201
|
+
const meRes = await fetch(`https://api.telegram.org/bot${token}/getMe`);
|
|
9202
|
+
const meData = await meRes.json();
|
|
9203
|
+
if (!meData.ok || !meData.result) {
|
|
9204
|
+
return { ok: false, error: "Could not retrieve bot info" };
|
|
9205
|
+
}
|
|
9206
|
+
const res = await fetch(
|
|
9207
|
+
`https://api.telegram.org/bot${token}/getChatMember`,
|
|
9208
|
+
{
|
|
9209
|
+
method: "POST",
|
|
9210
|
+
headers: { "Content-Type": "application/json" },
|
|
9211
|
+
body: JSON.stringify({ chat_id: chatId, user_id: meData.result.id })
|
|
9212
|
+
}
|
|
9213
|
+
);
|
|
9214
|
+
const data = await res.json();
|
|
9215
|
+
if (!data.ok || !data.result) {
|
|
9216
|
+
return {
|
|
9217
|
+
ok: false,
|
|
9218
|
+
error: data.description || "Could not check bot membership"
|
|
9219
|
+
};
|
|
9220
|
+
}
|
|
9221
|
+
const { status } = data.result;
|
|
9222
|
+
if (status === "creator") {
|
|
9223
|
+
return { ok: true, canManageTopics: true };
|
|
9224
|
+
}
|
|
9225
|
+
if (status === "administrator") {
|
|
9226
|
+
return { ok: true, canManageTopics: data.result.can_manage_topics === true };
|
|
9227
|
+
}
|
|
9228
|
+
return {
|
|
9229
|
+
ok: false,
|
|
9230
|
+
error: `Bot is "${status}" in this group. It must be an admin. Please promote the bot to admin in group settings.`
|
|
9231
|
+
};
|
|
9232
|
+
} catch (err) {
|
|
9233
|
+
return { ok: false, error: err.message };
|
|
9234
|
+
}
|
|
9235
|
+
}
|
|
9236
|
+
async function checkTopicsPrerequisites(token, chatId) {
|
|
9237
|
+
const issues = [];
|
|
9238
|
+
try {
|
|
9239
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/getChat`, {
|
|
9240
|
+
method: "POST",
|
|
9241
|
+
headers: { "Content-Type": "application/json" },
|
|
9242
|
+
body: JSON.stringify({ chat_id: chatId })
|
|
9243
|
+
});
|
|
9244
|
+
const data = await res.json();
|
|
9245
|
+
if (data.ok && data.result && !data.result.is_forum) {
|
|
9246
|
+
issues.push(
|
|
9247
|
+
'\u274C Topics are not enabled on this group.\n\u2192 Go to Group Settings \u2192 Edit \u2192 enable "Topics"'
|
|
9248
|
+
);
|
|
9249
|
+
}
|
|
9250
|
+
} catch {
|
|
9251
|
+
issues.push("\u274C Could not check if Topics are enabled (network error).");
|
|
9252
|
+
}
|
|
9253
|
+
const adminResult = await validateBotAdmin(token, chatId);
|
|
9254
|
+
if (!adminResult.ok) {
|
|
9255
|
+
issues.push(
|
|
9256
|
+
`\u274C Bot is not an admin.
|
|
9257
|
+
\u2192 Go to Group Settings \u2192 Administrators \u2192 add the bot \u2192 save`
|
|
9258
|
+
);
|
|
9259
|
+
} else if (!adminResult.canManageTopics) {
|
|
9260
|
+
issues.push(
|
|
9261
|
+
'\u274C Bot cannot manage topics.\n\u2192 In Admin settings, enable the "Manage Topics" permission'
|
|
9262
|
+
);
|
|
9263
|
+
}
|
|
9264
|
+
if (issues.length > 0) return { ok: false, issues };
|
|
9265
|
+
return { ok: true };
|
|
9266
|
+
}
|
|
9267
|
+
var init_validators = __esm({
|
|
9268
|
+
"src/plugins/telegram/validators.ts"() {
|
|
9269
|
+
"use strict";
|
|
9270
|
+
}
|
|
9271
|
+
});
|
|
9272
|
+
|
|
9277
9273
|
// src/plugins/telegram/adapter.ts
|
|
9278
9274
|
import { Bot, InputFile } from "grammy";
|
|
9279
9275
|
function patchedFetch(input2, init) {
|
|
@@ -9343,6 +9339,10 @@ var init_adapter = __esm({
|
|
|
9343
9339
|
controlMsgIds = /* @__PURE__ */ new Map();
|
|
9344
9340
|
_threadReadyHandler;
|
|
9345
9341
|
_configChangedHandler;
|
|
9342
|
+
/** True once topics are initialized and Phase 2 is complete */
|
|
9343
|
+
_topicsInitialized = false;
|
|
9344
|
+
/** Background watcher timer — cancelled on stop() or when topics succeed */
|
|
9345
|
+
_prerequisiteWatcher = null;
|
|
9346
9346
|
/** Store control message ID in memory + persist to session record */
|
|
9347
9347
|
storeControlMsgId(sessionId, msgId) {
|
|
9348
9348
|
this.controlMsgIds.set(sessionId, msgId);
|
|
@@ -9471,25 +9471,6 @@ var init_adapter = __esm({
|
|
|
9471
9471
|
if (chatId !== this.telegramConfig.chatId) return;
|
|
9472
9472
|
return next();
|
|
9473
9473
|
});
|
|
9474
|
-
const topics = await this.retryWithBackoff(
|
|
9475
|
-
() => ensureTopics(
|
|
9476
|
-
this.bot,
|
|
9477
|
-
this.telegramConfig.chatId,
|
|
9478
|
-
this.telegramConfig,
|
|
9479
|
-
async (updates) => {
|
|
9480
|
-
if (this.saveTopicIds) {
|
|
9481
|
-
await this.saveTopicIds(updates);
|
|
9482
|
-
} else {
|
|
9483
|
-
await this.core.configManager.save({
|
|
9484
|
-
channels: { telegram: updates }
|
|
9485
|
-
});
|
|
9486
|
-
}
|
|
9487
|
-
}
|
|
9488
|
-
),
|
|
9489
|
-
"ensureTopics"
|
|
9490
|
-
);
|
|
9491
|
-
this.notificationTopicId = topics.notificationTopicId;
|
|
9492
|
-
this.assistantTopicId = topics.assistantTopicId;
|
|
9493
9474
|
this.permissionHandler = new PermissionHandler(
|
|
9494
9475
|
this.bot,
|
|
9495
9476
|
this.telegramConfig.chatId,
|
|
@@ -9499,6 +9480,13 @@ var init_adapter = __esm({
|
|
|
9499
9480
|
this.bot.on("message:text", async (ctx, next) => {
|
|
9500
9481
|
const text3 = ctx.message?.text;
|
|
9501
9482
|
if (!text3?.startsWith("/")) return next();
|
|
9483
|
+
if (!this._topicsInitialized) {
|
|
9484
|
+
await ctx.reply(
|
|
9485
|
+
"\u23F3 OpenACP is still setting up. Check the General topic for instructions."
|
|
9486
|
+
).catch(() => {
|
|
9487
|
+
});
|
|
9488
|
+
return;
|
|
9489
|
+
}
|
|
9502
9490
|
const registry = this.core.lifecycleManager?.serviceRegistry?.get(
|
|
9503
9491
|
"command-registry"
|
|
9504
9492
|
);
|
|
@@ -9556,6 +9544,11 @@ var init_adapter = __esm({
|
|
|
9556
9544
|
}
|
|
9557
9545
|
});
|
|
9558
9546
|
this.bot.callbackQuery(/^c\//, async (ctx) => {
|
|
9547
|
+
if (!this._topicsInitialized) {
|
|
9548
|
+
await ctx.answerCallbackQuery().catch(() => {
|
|
9549
|
+
});
|
|
9550
|
+
return;
|
|
9551
|
+
}
|
|
9559
9552
|
const data = ctx.callbackQuery.data;
|
|
9560
9553
|
const command = this.fromCallbackData(data);
|
|
9561
9554
|
const registry = this.core.lifecycleManager?.serviceRegistry?.get(
|
|
@@ -9612,6 +9605,80 @@ var init_adapter = __esm({
|
|
|
9612
9605
|
setupTTSCallbacks(this.bot, this.core);
|
|
9613
9606
|
setupVerbosityCallbacks(this.bot, this.core);
|
|
9614
9607
|
setupIntegrateCallbacks(this.bot, this.core);
|
|
9608
|
+
this.permissionHandler.setupCallbackHandler();
|
|
9609
|
+
this.bot.start({
|
|
9610
|
+
allowed_updates: ["message", "callback_query"],
|
|
9611
|
+
onStart: () => log33.info({ chatId: this.telegramConfig.chatId }, "Telegram bot started")
|
|
9612
|
+
});
|
|
9613
|
+
const { checkTopicsPrerequisites: checkTopicsPrerequisites2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
9614
|
+
const prereqResult = await checkTopicsPrerequisites2(
|
|
9615
|
+
this.telegramConfig.botToken,
|
|
9616
|
+
this.telegramConfig.chatId
|
|
9617
|
+
);
|
|
9618
|
+
if (prereqResult.ok) {
|
|
9619
|
+
await this.initTopicDependentFeatures();
|
|
9620
|
+
} else {
|
|
9621
|
+
for (const issue of prereqResult.issues) {
|
|
9622
|
+
log33.warn({ issue }, "Telegram prerequisite not met");
|
|
9623
|
+
}
|
|
9624
|
+
this.startPrerequisiteWatcher(prereqResult.issues);
|
|
9625
|
+
}
|
|
9626
|
+
}
|
|
9627
|
+
/**
|
|
9628
|
+
* Retry an async operation with exponential backoff.
|
|
9629
|
+
* Used for Telegram API calls that may fail due to transient network issues.
|
|
9630
|
+
*/
|
|
9631
|
+
async retryWithBackoff(fn, label, maxRetries = 5, baseDelayMs = 2e3) {
|
|
9632
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
9633
|
+
try {
|
|
9634
|
+
return await fn();
|
|
9635
|
+
} catch (err) {
|
|
9636
|
+
if (attempt === maxRetries) throw err;
|
|
9637
|
+
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
9638
|
+
log33.warn(
|
|
9639
|
+
{ err, attempt, maxRetries, delayMs: delay, operation: label },
|
|
9640
|
+
`${label} failed, retrying in ${delay}ms`
|
|
9641
|
+
);
|
|
9642
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
9643
|
+
}
|
|
9644
|
+
}
|
|
9645
|
+
throw new Error("unreachable");
|
|
9646
|
+
}
|
|
9647
|
+
/**
|
|
9648
|
+
* Register Telegram commands in the background with retries.
|
|
9649
|
+
* Non-critical — bot works fine without autocomplete commands.
|
|
9650
|
+
*/
|
|
9651
|
+
registerCommandsWithRetry() {
|
|
9652
|
+
this.retryWithBackoff(
|
|
9653
|
+
() => this.bot.api.setMyCommands(STATIC_COMMANDS, {
|
|
9654
|
+
scope: { type: "chat", chat_id: this.telegramConfig.chatId }
|
|
9655
|
+
}),
|
|
9656
|
+
"setMyCommands"
|
|
9657
|
+
).catch((err) => {
|
|
9658
|
+
log33.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
|
|
9659
|
+
});
|
|
9660
|
+
}
|
|
9661
|
+
async initTopicDependentFeatures() {
|
|
9662
|
+
if (this._topicsInitialized) return;
|
|
9663
|
+
const topics = await this.retryWithBackoff(
|
|
9664
|
+
() => ensureTopics(
|
|
9665
|
+
this.bot,
|
|
9666
|
+
this.telegramConfig.chatId,
|
|
9667
|
+
this.telegramConfig,
|
|
9668
|
+
async (updates) => {
|
|
9669
|
+
if (this.saveTopicIds) {
|
|
9670
|
+
await this.saveTopicIds(updates);
|
|
9671
|
+
} else {
|
|
9672
|
+
await this.core.configManager.save({
|
|
9673
|
+
channels: { telegram: updates }
|
|
9674
|
+
});
|
|
9675
|
+
}
|
|
9676
|
+
}
|
|
9677
|
+
),
|
|
9678
|
+
"ensureTopics"
|
|
9679
|
+
);
|
|
9680
|
+
this.notificationTopicId = topics.notificationTopicId;
|
|
9681
|
+
this.assistantTopicId = topics.assistantTopicId;
|
|
9615
9682
|
setupAllCallbacks(
|
|
9616
9683
|
this.bot,
|
|
9617
9684
|
this.core,
|
|
@@ -9641,7 +9708,6 @@ ${p}` : p;
|
|
|
9641
9708
|
this.storeControlMsgId(sessionId, msgId);
|
|
9642
9709
|
}
|
|
9643
9710
|
);
|
|
9644
|
-
this.permissionHandler.setupCallbackHandler();
|
|
9645
9711
|
this._threadReadyHandler = ({ sessionId, channelId, threadId }) => {
|
|
9646
9712
|
if (channelId !== "telegram") return;
|
|
9647
9713
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
@@ -9679,13 +9745,6 @@ ${p}` : p;
|
|
|
9679
9745
|
};
|
|
9680
9746
|
this.core.eventBus.on("session:configChanged", this._configChangedHandler);
|
|
9681
9747
|
this.setupRoutes();
|
|
9682
|
-
this.bot.start({
|
|
9683
|
-
allowed_updates: ["message", "callback_query"],
|
|
9684
|
-
onStart: () => log33.info(
|
|
9685
|
-
{ chatId: this.telegramConfig.chatId },
|
|
9686
|
-
"Telegram bot started"
|
|
9687
|
-
)
|
|
9688
|
-
});
|
|
9689
9748
|
try {
|
|
9690
9749
|
const config = this.core.configManager.get();
|
|
9691
9750
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
@@ -9715,42 +9774,56 @@ ${p}` : p;
|
|
|
9715
9774
|
} catch (err) {
|
|
9716
9775
|
log33.error({ err }, "Failed to spawn assistant");
|
|
9717
9776
|
}
|
|
9777
|
+
this._topicsInitialized = true;
|
|
9778
|
+
log33.info("Telegram adapter fully initialized");
|
|
9718
9779
|
}
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
9730
|
-
log33.warn(
|
|
9731
|
-
{ err, attempt, maxRetries, delayMs: delay, operation: label },
|
|
9732
|
-
`${label} failed, retrying in ${delay}ms`
|
|
9733
|
-
);
|
|
9734
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
9735
|
-
}
|
|
9736
|
-
}
|
|
9737
|
-
throw new Error("unreachable");
|
|
9738
|
-
}
|
|
9739
|
-
/**
|
|
9740
|
-
* Register Telegram commands in the background with retries.
|
|
9741
|
-
* Non-critical — bot works fine without autocomplete commands.
|
|
9742
|
-
*/
|
|
9743
|
-
registerCommandsWithRetry() {
|
|
9744
|
-
this.retryWithBackoff(
|
|
9745
|
-
() => this.bot.api.setMyCommands(STATIC_COMMANDS, {
|
|
9746
|
-
scope: { type: "chat", chat_id: this.telegramConfig.chatId }
|
|
9747
|
-
}),
|
|
9748
|
-
"setMyCommands"
|
|
9749
|
-
).catch((err) => {
|
|
9750
|
-
log33.warn({ err }, "Failed to register Telegram commands after retries (non-critical)");
|
|
9780
|
+
startPrerequisiteWatcher(issues) {
|
|
9781
|
+
const setupMessage = `\u26A0\uFE0F <b>OpenACP needs setup before it can start.</b>
|
|
9782
|
+
|
|
9783
|
+
` + issues.join("\n\n") + `
|
|
9784
|
+
|
|
9785
|
+
OpenACP will automatically retry until this is resolved.`;
|
|
9786
|
+
this.bot.api.sendMessage(this.telegramConfig.chatId, setupMessage, {
|
|
9787
|
+
parse_mode: "HTML"
|
|
9788
|
+
}).catch((err) => {
|
|
9789
|
+
log33.warn({ err }, "Failed to send setup guidance to General topic");
|
|
9751
9790
|
});
|
|
9791
|
+
const schedule = [5e3, 1e4, 3e4];
|
|
9792
|
+
let attempt = 1;
|
|
9793
|
+
const retry = async () => {
|
|
9794
|
+
if (this._prerequisiteWatcher === null) return;
|
|
9795
|
+
const { checkTopicsPrerequisites: checkTopicsPrerequisites2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
9796
|
+
const result = await checkTopicsPrerequisites2(
|
|
9797
|
+
this.telegramConfig.botToken,
|
|
9798
|
+
this.telegramConfig.chatId
|
|
9799
|
+
);
|
|
9800
|
+
if (result.ok) {
|
|
9801
|
+
this._prerequisiteWatcher = null;
|
|
9802
|
+
log33.info("Prerequisites met \u2014 completing Telegram adapter initialization");
|
|
9803
|
+
try {
|
|
9804
|
+
await this.initTopicDependentFeatures();
|
|
9805
|
+
await this.bot.api.sendMessage(
|
|
9806
|
+
this.telegramConfig.chatId,
|
|
9807
|
+
"\u2705 <b>OpenACP is ready!</b>\n\nSystem topics have been created. Use the \u{1F916} Assistant topic to get started.",
|
|
9808
|
+
{ parse_mode: "HTML" }
|
|
9809
|
+
);
|
|
9810
|
+
} catch (err) {
|
|
9811
|
+
log33.error({ err }, "Failed to complete initialization after prerequisites met");
|
|
9812
|
+
}
|
|
9813
|
+
return;
|
|
9814
|
+
}
|
|
9815
|
+
log33.debug({ issues: result.issues }, "Prerequisites not yet met, retrying");
|
|
9816
|
+
const delay = schedule[Math.min(attempt, schedule.length - 1)];
|
|
9817
|
+
attempt++;
|
|
9818
|
+
this._prerequisiteWatcher = setTimeout(retry, delay);
|
|
9819
|
+
};
|
|
9820
|
+
this._prerequisiteWatcher = setTimeout(retry, schedule[0]);
|
|
9752
9821
|
}
|
|
9753
9822
|
async stop() {
|
|
9823
|
+
if (this._prerequisiteWatcher !== null) {
|
|
9824
|
+
clearTimeout(this._prerequisiteWatcher);
|
|
9825
|
+
this._prerequisiteWatcher = null;
|
|
9826
|
+
}
|
|
9754
9827
|
for (const tracker of this.sessionTrackers.values()) {
|
|
9755
9828
|
tracker.destroy();
|
|
9756
9829
|
}
|
|
@@ -10536,8 +10609,16 @@ function nodeToWebWritable(nodeStream) {
|
|
|
10536
10609
|
resolve6();
|
|
10537
10610
|
return;
|
|
10538
10611
|
}
|
|
10539
|
-
|
|
10540
|
-
|
|
10612
|
+
const onDrain = () => {
|
|
10613
|
+
nodeStream.removeListener("error", onError);
|
|
10614
|
+
resolve6();
|
|
10615
|
+
};
|
|
10616
|
+
const onError = (err) => {
|
|
10617
|
+
nodeStream.removeListener("drain", onDrain);
|
|
10618
|
+
reject(err);
|
|
10619
|
+
};
|
|
10620
|
+
nodeStream.once("drain", onDrain);
|
|
10621
|
+
nodeStream.once("error", onError);
|
|
10541
10622
|
});
|
|
10542
10623
|
},
|
|
10543
10624
|
close() {
|
|
@@ -10584,13 +10665,13 @@ init_config();
|
|
|
10584
10665
|
// src/core/agents/agent-instance.ts
|
|
10585
10666
|
import { spawn as spawn2, execFileSync } from "child_process";
|
|
10586
10667
|
import { Transform } from "stream";
|
|
10587
|
-
import
|
|
10588
|
-
import
|
|
10668
|
+
import fs8 from "fs";
|
|
10669
|
+
import path7 from "path";
|
|
10589
10670
|
import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
10590
10671
|
|
|
10591
10672
|
// src/core/security/path-guard.ts
|
|
10592
|
-
import
|
|
10593
|
-
import
|
|
10673
|
+
import fs5 from "fs";
|
|
10674
|
+
import path5 from "path";
|
|
10594
10675
|
import ignore from "ignore";
|
|
10595
10676
|
var DEFAULT_DENY_PATTERNS = [
|
|
10596
10677
|
".env",
|
|
@@ -10610,15 +10691,15 @@ var PathGuard = class {
|
|
|
10610
10691
|
ig;
|
|
10611
10692
|
constructor(options) {
|
|
10612
10693
|
try {
|
|
10613
|
-
this.cwd =
|
|
10694
|
+
this.cwd = fs5.realpathSync(path5.resolve(options.cwd));
|
|
10614
10695
|
} catch {
|
|
10615
|
-
this.cwd =
|
|
10696
|
+
this.cwd = path5.resolve(options.cwd);
|
|
10616
10697
|
}
|
|
10617
10698
|
this.allowedPaths = options.allowedPaths.map((p) => {
|
|
10618
10699
|
try {
|
|
10619
|
-
return
|
|
10700
|
+
return fs5.realpathSync(path5.resolve(p));
|
|
10620
10701
|
} catch {
|
|
10621
|
-
return
|
|
10702
|
+
return path5.resolve(p);
|
|
10622
10703
|
}
|
|
10623
10704
|
});
|
|
10624
10705
|
this.ig = ignore();
|
|
@@ -10628,19 +10709,19 @@ var PathGuard = class {
|
|
|
10628
10709
|
}
|
|
10629
10710
|
}
|
|
10630
10711
|
validatePath(targetPath, operation) {
|
|
10631
|
-
const resolved =
|
|
10712
|
+
const resolved = path5.resolve(targetPath);
|
|
10632
10713
|
let realPath;
|
|
10633
10714
|
try {
|
|
10634
|
-
realPath =
|
|
10715
|
+
realPath = fs5.realpathSync(resolved);
|
|
10635
10716
|
} catch {
|
|
10636
10717
|
realPath = resolved;
|
|
10637
10718
|
}
|
|
10638
|
-
if (operation === "write" &&
|
|
10719
|
+
if (operation === "write" && path5.basename(realPath) === ".openacpignore") {
|
|
10639
10720
|
return { allowed: false, reason: "Cannot write to .openacpignore" };
|
|
10640
10721
|
}
|
|
10641
|
-
const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd +
|
|
10722
|
+
const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd + path5.sep);
|
|
10642
10723
|
const isWithinAllowed = this.allowedPaths.some(
|
|
10643
|
-
(ap) => realPath === ap || realPath.startsWith(ap +
|
|
10724
|
+
(ap) => realPath === ap || realPath.startsWith(ap + path5.sep)
|
|
10644
10725
|
);
|
|
10645
10726
|
if (!isWithinCwd && !isWithinAllowed) {
|
|
10646
10727
|
return {
|
|
@@ -10649,7 +10730,7 @@ var PathGuard = class {
|
|
|
10649
10730
|
};
|
|
10650
10731
|
}
|
|
10651
10732
|
if (isWithinCwd && !isWithinAllowed) {
|
|
10652
|
-
const relativePath =
|
|
10733
|
+
const relativePath = path5.relative(this.cwd, realPath);
|
|
10653
10734
|
if (relativePath === ".openacpignore") {
|
|
10654
10735
|
return { allowed: true, reason: "" };
|
|
10655
10736
|
}
|
|
@@ -10664,15 +10745,15 @@ var PathGuard = class {
|
|
|
10664
10745
|
}
|
|
10665
10746
|
addAllowedPath(p) {
|
|
10666
10747
|
try {
|
|
10667
|
-
this.allowedPaths.push(
|
|
10748
|
+
this.allowedPaths.push(fs5.realpathSync(path5.resolve(p)));
|
|
10668
10749
|
} catch {
|
|
10669
|
-
this.allowedPaths.push(
|
|
10750
|
+
this.allowedPaths.push(path5.resolve(p));
|
|
10670
10751
|
}
|
|
10671
10752
|
}
|
|
10672
10753
|
static loadIgnoreFile(cwd) {
|
|
10673
|
-
const ignorePath =
|
|
10754
|
+
const ignorePath = path5.join(cwd, ".openacpignore");
|
|
10674
10755
|
try {
|
|
10675
|
-
const content =
|
|
10756
|
+
const content = fs5.readFileSync(ignorePath, "utf-8");
|
|
10676
10757
|
return content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
10677
10758
|
} catch {
|
|
10678
10759
|
return [];
|
|
@@ -10727,7 +10808,8 @@ function filterEnv(processEnv, agentEnv, whitelist) {
|
|
|
10727
10808
|
}
|
|
10728
10809
|
|
|
10729
10810
|
// src/core/utils/typed-emitter.ts
|
|
10730
|
-
var TypedEmitter = class {
|
|
10811
|
+
var TypedEmitter = class _TypedEmitter {
|
|
10812
|
+
static MAX_BUFFER_SIZE = 1e4;
|
|
10731
10813
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10732
10814
|
listeners = /* @__PURE__ */ new Map();
|
|
10733
10815
|
paused = false;
|
|
@@ -10751,6 +10833,10 @@ var TypedEmitter = class {
|
|
|
10751
10833
|
this.deliver(event, args);
|
|
10752
10834
|
} else {
|
|
10753
10835
|
this.buffer.push({ event, args });
|
|
10836
|
+
if (this.buffer.length > _TypedEmitter.MAX_BUFFER_SIZE) {
|
|
10837
|
+
console.warn(`[TypedEmitter] Buffer exceeded ${_TypedEmitter.MAX_BUFFER_SIZE} events, dropping oldest`);
|
|
10838
|
+
this.buffer.shift();
|
|
10839
|
+
}
|
|
10754
10840
|
}
|
|
10755
10841
|
return;
|
|
10756
10842
|
}
|
|
@@ -10795,7 +10881,11 @@ var TypedEmitter = class {
|
|
|
10795
10881
|
const set = this.listeners.get(event);
|
|
10796
10882
|
if (!set) return;
|
|
10797
10883
|
for (const listener of set) {
|
|
10798
|
-
|
|
10884
|
+
try {
|
|
10885
|
+
listener(...args);
|
|
10886
|
+
} catch (err) {
|
|
10887
|
+
console.error(`[EventBus] Listener error on "${String(event)}":`, err);
|
|
10888
|
+
}
|
|
10799
10889
|
}
|
|
10800
10890
|
}
|
|
10801
10891
|
};
|
|
@@ -10886,6 +10976,11 @@ var TerminalManager = class {
|
|
|
10886
10976
|
}, async (p) => p).catch(() => {
|
|
10887
10977
|
});
|
|
10888
10978
|
}
|
|
10979
|
+
setTimeout(() => {
|
|
10980
|
+
if (this.terminals.has(terminalId)) {
|
|
10981
|
+
this.terminals.delete(terminalId);
|
|
10982
|
+
}
|
|
10983
|
+
}, 3e4).unref();
|
|
10889
10984
|
});
|
|
10890
10985
|
return { terminalId };
|
|
10891
10986
|
}
|
|
@@ -10918,6 +11013,12 @@ var TerminalManager = class {
|
|
|
10918
11013
|
state.process.on("exit", (code, signal) => {
|
|
10919
11014
|
resolve6({ exitCode: code, signal });
|
|
10920
11015
|
});
|
|
11016
|
+
if (state.exitStatus !== null) {
|
|
11017
|
+
resolve6({
|
|
11018
|
+
exitCode: state.exitStatus.exitCode,
|
|
11019
|
+
signal: state.exitStatus.signal
|
|
11020
|
+
});
|
|
11021
|
+
}
|
|
10921
11022
|
});
|
|
10922
11023
|
}
|
|
10923
11024
|
kill(terminalId) {
|
|
@@ -10955,24 +11056,24 @@ var McpManager = class {
|
|
|
10955
11056
|
};
|
|
10956
11057
|
|
|
10957
11058
|
// src/core/utils/debug-tracer.ts
|
|
10958
|
-
import
|
|
10959
|
-
import
|
|
11059
|
+
import fs7 from "fs";
|
|
11060
|
+
import path6 from "path";
|
|
10960
11061
|
var DEBUG_ENABLED = process.env.OPENACP_DEBUG === "true" || process.env.OPENACP_DEBUG === "1";
|
|
10961
11062
|
var DebugTracer = class {
|
|
10962
11063
|
constructor(sessionId, workingDirectory) {
|
|
10963
11064
|
this.sessionId = sessionId;
|
|
10964
11065
|
this.workingDirectory = workingDirectory;
|
|
10965
|
-
this.logDir =
|
|
11066
|
+
this.logDir = path6.join(workingDirectory, ".log");
|
|
10966
11067
|
}
|
|
10967
11068
|
dirCreated = false;
|
|
10968
11069
|
logDir;
|
|
10969
11070
|
log(layer, data) {
|
|
10970
11071
|
try {
|
|
10971
11072
|
if (!this.dirCreated) {
|
|
10972
|
-
|
|
11073
|
+
fs7.mkdirSync(this.logDir, { recursive: true });
|
|
10973
11074
|
this.dirCreated = true;
|
|
10974
11075
|
}
|
|
10975
|
-
const filePath =
|
|
11076
|
+
const filePath = path6.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
|
|
10976
11077
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
10977
11078
|
const line = JSON.stringify({ ts: Date.now(), ...data }, (_key, value) => {
|
|
10978
11079
|
if (typeof value === "object" && value !== null) {
|
|
@@ -10981,7 +11082,7 @@ var DebugTracer = class {
|
|
|
10981
11082
|
}
|
|
10982
11083
|
return value;
|
|
10983
11084
|
}) + "\n";
|
|
10984
|
-
|
|
11085
|
+
fs7.appendFileSync(filePath, line);
|
|
10985
11086
|
} catch {
|
|
10986
11087
|
}
|
|
10987
11088
|
}
|
|
@@ -10999,11 +11100,11 @@ init_log();
|
|
|
10999
11100
|
var log4 = createChildLogger({ module: "agent-instance" });
|
|
11000
11101
|
function findPackageRoot(startDir) {
|
|
11001
11102
|
let dir = startDir;
|
|
11002
|
-
while (dir !==
|
|
11003
|
-
if (
|
|
11103
|
+
while (dir !== path7.dirname(dir)) {
|
|
11104
|
+
if (fs8.existsSync(path7.join(dir, "package.json"))) {
|
|
11004
11105
|
return dir;
|
|
11005
11106
|
}
|
|
11006
|
-
dir =
|
|
11107
|
+
dir = path7.dirname(dir);
|
|
11007
11108
|
}
|
|
11008
11109
|
return startDir;
|
|
11009
11110
|
}
|
|
@@ -11015,26 +11116,26 @@ function resolveAgentCommand(cmd) {
|
|
|
11015
11116
|
}
|
|
11016
11117
|
for (const root of searchRoots) {
|
|
11017
11118
|
const packageDirs = [
|
|
11018
|
-
|
|
11019
|
-
|
|
11119
|
+
path7.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
|
|
11120
|
+
path7.resolve(root, "node_modules", cmd, "dist", "index.js")
|
|
11020
11121
|
];
|
|
11021
11122
|
for (const jsPath of packageDirs) {
|
|
11022
|
-
if (
|
|
11123
|
+
if (fs8.existsSync(jsPath)) {
|
|
11023
11124
|
return { command: process.execPath, args: [jsPath] };
|
|
11024
11125
|
}
|
|
11025
11126
|
}
|
|
11026
11127
|
}
|
|
11027
11128
|
for (const root of searchRoots) {
|
|
11028
|
-
const localBin =
|
|
11029
|
-
if (
|
|
11030
|
-
const content =
|
|
11129
|
+
const localBin = path7.resolve(root, "node_modules", ".bin", cmd);
|
|
11130
|
+
if (fs8.existsSync(localBin)) {
|
|
11131
|
+
const content = fs8.readFileSync(localBin, "utf-8");
|
|
11031
11132
|
if (content.startsWith("#!/usr/bin/env node")) {
|
|
11032
11133
|
return { command: process.execPath, args: [localBin] };
|
|
11033
11134
|
}
|
|
11034
11135
|
const match = content.match(/"([^"]+\.js)"/);
|
|
11035
11136
|
if (match) {
|
|
11036
|
-
const target =
|
|
11037
|
-
if (
|
|
11137
|
+
const target = path7.resolve(path7.dirname(localBin), match[1]);
|
|
11138
|
+
if (fs8.existsSync(target)) {
|
|
11038
11139
|
return { command: process.execPath, args: [target] };
|
|
11039
11140
|
}
|
|
11040
11141
|
}
|
|
@@ -11043,7 +11144,7 @@ function resolveAgentCommand(cmd) {
|
|
|
11043
11144
|
try {
|
|
11044
11145
|
const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim();
|
|
11045
11146
|
if (fullPath) {
|
|
11046
|
-
const content =
|
|
11147
|
+
const content = fs8.readFileSync(fullPath, "utf-8");
|
|
11047
11148
|
if (content.startsWith("#!/usr/bin/env node")) {
|
|
11048
11149
|
return { command: process.execPath, args: [fullPath] };
|
|
11049
11150
|
}
|
|
@@ -11475,8 +11576,8 @@ ${stderr}`
|
|
|
11475
11576
|
writePath = result.path;
|
|
11476
11577
|
writeContent = result.content;
|
|
11477
11578
|
}
|
|
11478
|
-
await
|
|
11479
|
-
await
|
|
11579
|
+
await fs8.promises.mkdir(path7.dirname(writePath), { recursive: true });
|
|
11580
|
+
await fs8.promises.writeFile(writePath, writeContent, "utf-8");
|
|
11480
11581
|
return {};
|
|
11481
11582
|
},
|
|
11482
11583
|
// ── Terminal operations (delegated to TerminalManager) ─────────────
|
|
@@ -11578,7 +11679,7 @@ ${stderr}`
|
|
|
11578
11679
|
[Attachment access denied: ${attCheck.reason}]`;
|
|
11579
11680
|
continue;
|
|
11580
11681
|
}
|
|
11581
|
-
const data = await
|
|
11682
|
+
const data = await fs8.promises.readFile(att.filePath);
|
|
11582
11683
|
contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11583
11684
|
} else if (att.type === "audio" && this.promptCapabilities?.audio && !tooLarge) {
|
|
11584
11685
|
const attCheck = this.pathGuard.validatePath(att.filePath, "read");
|
|
@@ -11588,7 +11689,7 @@ ${stderr}`
|
|
|
11588
11689
|
[Attachment access denied: ${attCheck.reason}]`;
|
|
11589
11690
|
continue;
|
|
11590
11691
|
}
|
|
11591
|
-
const data = await
|
|
11692
|
+
const data = await fs8.promises.readFile(att.filePath);
|
|
11592
11693
|
contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11593
11694
|
} else {
|
|
11594
11695
|
if ((att.type === "image" || att.type === "audio") && !tooLarge) {
|
|
@@ -11673,6 +11774,8 @@ var PromptQueue = class {
|
|
|
11673
11774
|
queue = [];
|
|
11674
11775
|
processing = false;
|
|
11675
11776
|
abortController = null;
|
|
11777
|
+
/** Set when abort is triggered; drainNext waits for the current processor to settle before starting the next item. */
|
|
11778
|
+
processorSettled = null;
|
|
11676
11779
|
async enqueue(text3, attachments, routing, turnId) {
|
|
11677
11780
|
if (this.processing) {
|
|
11678
11781
|
return new Promise((resolve6) => {
|
|
@@ -11685,6 +11788,10 @@ var PromptQueue = class {
|
|
|
11685
11788
|
this.processing = true;
|
|
11686
11789
|
this.abortController = new AbortController();
|
|
11687
11790
|
const { signal } = this.abortController;
|
|
11791
|
+
let settledResolve;
|
|
11792
|
+
this.processorSettled = new Promise((r) => {
|
|
11793
|
+
settledResolve = r;
|
|
11794
|
+
});
|
|
11688
11795
|
try {
|
|
11689
11796
|
await Promise.race([
|
|
11690
11797
|
this.processor(text3, attachments, routing, turnId),
|
|
@@ -11699,6 +11806,8 @@ var PromptQueue = class {
|
|
|
11699
11806
|
} finally {
|
|
11700
11807
|
this.abortController = null;
|
|
11701
11808
|
this.processing = false;
|
|
11809
|
+
settledResolve();
|
|
11810
|
+
this.processorSettled = null;
|
|
11702
11811
|
this.drainNext();
|
|
11703
11812
|
}
|
|
11704
11813
|
}
|
|
@@ -11794,7 +11903,7 @@ var PermissionGate = class {
|
|
|
11794
11903
|
|
|
11795
11904
|
// src/core/sessions/session.ts
|
|
11796
11905
|
init_log();
|
|
11797
|
-
import * as
|
|
11906
|
+
import * as fs9 from "fs";
|
|
11798
11907
|
|
|
11799
11908
|
// src/core/sessions/turn-context.ts
|
|
11800
11909
|
import { nanoid } from "nanoid";
|
|
@@ -11874,6 +11983,9 @@ var Session = class extends TypedEmitter {
|
|
|
11874
11983
|
threadIds = /* @__PURE__ */ new Map();
|
|
11875
11984
|
/** Active turn context — sealed on prompt dequeue, cleared on turn end */
|
|
11876
11985
|
activeTurnContext = null;
|
|
11986
|
+
/** The agentInstance for which the agent→session event relay is wired (prevents duplicate relays from multiple bridges).
|
|
11987
|
+
* When the agent is swapped, the relay must be re-wired to the new instance. */
|
|
11988
|
+
agentRelaySource = null;
|
|
11877
11989
|
permissionGate = new PermissionGate();
|
|
11878
11990
|
queue;
|
|
11879
11991
|
speechService;
|
|
@@ -12024,6 +12136,7 @@ ${text3}`;
|
|
|
12024
12136
|
});
|
|
12025
12137
|
}
|
|
12026
12138
|
let stopReason = "end_turn";
|
|
12139
|
+
let promptError;
|
|
12027
12140
|
try {
|
|
12028
12141
|
const response = await this.agentInstance.prompt(processed.text, processed.attachments);
|
|
12029
12142
|
if (response && typeof response === "object" && "stopReason" in response) {
|
|
@@ -12035,14 +12148,21 @@ ${text3}`;
|
|
|
12035
12148
|
if (ttsActive && this.voiceMode === "next") {
|
|
12036
12149
|
this.voiceMode = "off";
|
|
12037
12150
|
}
|
|
12151
|
+
} catch (err) {
|
|
12152
|
+
stopReason = "error";
|
|
12153
|
+
promptError = err;
|
|
12038
12154
|
} finally {
|
|
12039
12155
|
if (accumulatorListener) {
|
|
12040
12156
|
this.off("agent_event", accumulatorListener);
|
|
12041
12157
|
}
|
|
12158
|
+
if (this.middlewareChain) {
|
|
12159
|
+
this.middlewareChain.execute("turn:end", { sessionId: this.id, stopReason, durationMs: Date.now() - promptStart }, async (p) => p).catch(() => {
|
|
12160
|
+
});
|
|
12161
|
+
}
|
|
12162
|
+
this.activeTurnContext = null;
|
|
12042
12163
|
}
|
|
12043
|
-
if (
|
|
12044
|
-
|
|
12045
|
-
});
|
|
12164
|
+
if (promptError !== void 0) {
|
|
12165
|
+
throw promptError;
|
|
12046
12166
|
}
|
|
12047
12167
|
this.log.info(
|
|
12048
12168
|
{ durationMs: Date.now() - promptStart },
|
|
@@ -12053,7 +12173,6 @@ ${text3}`;
|
|
|
12053
12173
|
this.log.warn({ err }, "TTS post-processing failed");
|
|
12054
12174
|
});
|
|
12055
12175
|
}
|
|
12056
|
-
this.activeTurnContext = null;
|
|
12057
12176
|
if (!this.name) {
|
|
12058
12177
|
await this.autoName();
|
|
12059
12178
|
}
|
|
@@ -12079,7 +12198,7 @@ ${text3}`;
|
|
|
12079
12198
|
try {
|
|
12080
12199
|
const audioPath = att.originalFilePath || att.filePath;
|
|
12081
12200
|
const audioMime = att.originalFilePath ? "audio/ogg" : att.mimeType;
|
|
12082
|
-
const audioBuffer = await
|
|
12201
|
+
const audioBuffer = await fs9.promises.readFile(audioPath);
|
|
12083
12202
|
const result = await this.speechService.transcribe(audioBuffer, audioMime);
|
|
12084
12203
|
this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
|
|
12085
12204
|
this.emit("agent_event", {
|
|
@@ -12311,7 +12430,7 @@ ${result.text}` : result.text;
|
|
|
12311
12430
|
};
|
|
12312
12431
|
|
|
12313
12432
|
// src/core/message-transformer.ts
|
|
12314
|
-
import * as
|
|
12433
|
+
import * as path8 from "path";
|
|
12315
12434
|
|
|
12316
12435
|
// src/core/utils/extract-file-info.ts
|
|
12317
12436
|
function extractFileInfo(name, kind, content, rawInput, meta) {
|
|
@@ -12621,6 +12740,20 @@ var MessageTransformer = class {
|
|
|
12621
12740
|
return { type: "text", text: "" };
|
|
12622
12741
|
}
|
|
12623
12742
|
}
|
|
12743
|
+
/** Clear cached entries whose key starts with the given prefix. */
|
|
12744
|
+
clearSessionCaches(prefix) {
|
|
12745
|
+
for (const key of this.toolRawInputCache.keys()) {
|
|
12746
|
+
if (key.startsWith(prefix)) this.toolRawInputCache.delete(key);
|
|
12747
|
+
}
|
|
12748
|
+
for (const key of this.toolViewerCache.keys()) {
|
|
12749
|
+
if (key.startsWith(prefix)) this.toolViewerCache.delete(key);
|
|
12750
|
+
}
|
|
12751
|
+
}
|
|
12752
|
+
/** Clear all caches. */
|
|
12753
|
+
clearCaches() {
|
|
12754
|
+
this.toolRawInputCache.clear();
|
|
12755
|
+
this.toolViewerCache.clear();
|
|
12756
|
+
}
|
|
12624
12757
|
/** Check if rawInput is a non-empty object (not null, not {}) */
|
|
12625
12758
|
isNonEmptyInput(input2) {
|
|
12626
12759
|
return input2 !== null && input2 !== void 0 && typeof input2 === "object" && !Array.isArray(input2) && Object.keys(input2).length > 0;
|
|
@@ -12669,7 +12802,7 @@ var MessageTransformer = class {
|
|
|
12669
12802
|
);
|
|
12670
12803
|
return;
|
|
12671
12804
|
}
|
|
12672
|
-
const fileExt =
|
|
12805
|
+
const fileExt = path8.extname(fileInfo.filePath).toLowerCase();
|
|
12673
12806
|
if (BINARY_VIEWER_EXTENSIONS.has(fileExt)) {
|
|
12674
12807
|
log5.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping binary file");
|
|
12675
12808
|
return;
|
|
@@ -12815,6 +12948,7 @@ var SessionManager = class {
|
|
|
12815
12948
|
} catch {
|
|
12816
12949
|
}
|
|
12817
12950
|
session.markCancelled();
|
|
12951
|
+
await session.destroy();
|
|
12818
12952
|
this.sessions.delete(sessionId);
|
|
12819
12953
|
}
|
|
12820
12954
|
if (this.store) {
|
|
@@ -13016,9 +13150,12 @@ var SessionBridge = class {
|
|
|
13016
13150
|
connect() {
|
|
13017
13151
|
if (this.connected) return;
|
|
13018
13152
|
this.connected = true;
|
|
13019
|
-
this.
|
|
13020
|
-
this.session.
|
|
13021
|
-
|
|
13153
|
+
if (this.session.agentRelaySource !== this.session.agentInstance) {
|
|
13154
|
+
this.listen(this.session.agentInstance, "agent_event", (event) => {
|
|
13155
|
+
this.session.emit("agent_event", event);
|
|
13156
|
+
});
|
|
13157
|
+
this.session.agentRelaySource = this.session.agentInstance;
|
|
13158
|
+
}
|
|
13022
13159
|
this.listen(this.session, "agent_event", (event) => {
|
|
13023
13160
|
if (this.shouldForward(event)) {
|
|
13024
13161
|
this.dispatchAgentEvent(event);
|
|
@@ -13097,6 +13234,7 @@ var SessionBridge = class {
|
|
|
13097
13234
|
if (current?.__bridgeId === this.adapterId) {
|
|
13098
13235
|
this.session.agentInstance.onPermissionRequest = async () => "";
|
|
13099
13236
|
}
|
|
13237
|
+
this.deps.messageTransformer.clearSessionCaches?.(this.session.id);
|
|
13100
13238
|
}
|
|
13101
13239
|
/** Dispatch an agent event through middleware and to the adapter */
|
|
13102
13240
|
async dispatchAgentEvent(event) {
|
|
@@ -13179,12 +13317,12 @@ var SessionBridge = class {
|
|
|
13179
13317
|
break;
|
|
13180
13318
|
case "image_content": {
|
|
13181
13319
|
if (this.deps.fileService) {
|
|
13182
|
-
const
|
|
13320
|
+
const fs33 = this.deps.fileService;
|
|
13183
13321
|
const sid = this.session.id;
|
|
13184
13322
|
const { data, mimeType } = event;
|
|
13185
13323
|
const buffer = Buffer.from(data, "base64");
|
|
13186
|
-
const ext =
|
|
13187
|
-
|
|
13324
|
+
const ext = fs33.extensionFromMime(mimeType);
|
|
13325
|
+
fs33.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
|
|
13188
13326
|
this.sendMessage(sid, {
|
|
13189
13327
|
type: "attachment",
|
|
13190
13328
|
text: "",
|
|
@@ -13196,12 +13334,12 @@ var SessionBridge = class {
|
|
|
13196
13334
|
}
|
|
13197
13335
|
case "audio_content": {
|
|
13198
13336
|
if (this.deps.fileService) {
|
|
13199
|
-
const
|
|
13337
|
+
const fs33 = this.deps.fileService;
|
|
13200
13338
|
const sid = this.session.id;
|
|
13201
13339
|
const { data, mimeType } = event;
|
|
13202
13340
|
const buffer = Buffer.from(data, "base64");
|
|
13203
|
-
const ext =
|
|
13204
|
-
|
|
13341
|
+
const ext = fs33.extensionFromMime(mimeType);
|
|
13342
|
+
fs33.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
|
|
13205
13343
|
this.sendMessage(sid, {
|
|
13206
13344
|
type: "attachment",
|
|
13207
13345
|
text: "",
|
|
@@ -13387,14 +13525,29 @@ var SessionFactory = class {
|
|
|
13387
13525
|
}
|
|
13388
13526
|
let agentInstance;
|
|
13389
13527
|
try {
|
|
13390
|
-
|
|
13391
|
-
|
|
13392
|
-
|
|
13393
|
-
|
|
13394
|
-
|
|
13395
|
-
|
|
13396
|
-
|
|
13397
|
-
|
|
13528
|
+
if (createParams.resumeAgentSessionId) {
|
|
13529
|
+
try {
|
|
13530
|
+
agentInstance = await this.agentManager.resume(
|
|
13531
|
+
createParams.agentName,
|
|
13532
|
+
createParams.workingDirectory,
|
|
13533
|
+
createParams.resumeAgentSessionId
|
|
13534
|
+
);
|
|
13535
|
+
} catch (resumeErr) {
|
|
13536
|
+
log7.warn(
|
|
13537
|
+
{ agentName: createParams.agentName, resumeErr },
|
|
13538
|
+
"Agent session resume failed, falling back to fresh spawn"
|
|
13539
|
+
);
|
|
13540
|
+
agentInstance = await this.agentManager.spawn(
|
|
13541
|
+
createParams.agentName,
|
|
13542
|
+
createParams.workingDirectory
|
|
13543
|
+
);
|
|
13544
|
+
}
|
|
13545
|
+
} else {
|
|
13546
|
+
agentInstance = await this.agentManager.spawn(
|
|
13547
|
+
createParams.agentName,
|
|
13548
|
+
createParams.workingDirectory
|
|
13549
|
+
);
|
|
13550
|
+
}
|
|
13398
13551
|
} catch (err) {
|
|
13399
13552
|
const message = err instanceof Error ? err.message : String(err);
|
|
13400
13553
|
const guidanceLines = [
|
|
@@ -13423,31 +13576,9 @@ var SessionFactory = class {
|
|
|
13423
13576
|
type: "system_message",
|
|
13424
13577
|
message: guidanceLines.join("\n")
|
|
13425
13578
|
};
|
|
13426
|
-
const
|
|
13427
|
-
id: createParams.existingSessionId,
|
|
13428
|
-
channelId: createParams.channelId,
|
|
13429
|
-
agentName: createParams.agentName,
|
|
13430
|
-
workingDirectory: createParams.workingDirectory,
|
|
13431
|
-
// Dummy agent instance — will never be prompted
|
|
13432
|
-
agentInstance: {
|
|
13433
|
-
sessionId: "",
|
|
13434
|
-
prompt: async () => {
|
|
13435
|
-
},
|
|
13436
|
-
cancel: async () => {
|
|
13437
|
-
},
|
|
13438
|
-
destroy: async () => {
|
|
13439
|
-
},
|
|
13440
|
-
on: () => {
|
|
13441
|
-
},
|
|
13442
|
-
off: () => {
|
|
13443
|
-
}
|
|
13444
|
-
},
|
|
13445
|
-
speechService: this.speechService
|
|
13446
|
-
});
|
|
13447
|
-
this.sessionManager.registerSession(failedSession);
|
|
13448
|
-
failedSession.emit("agent_event", guidance);
|
|
13579
|
+
const failedSessionId = createParams.existingSessionId ?? `failed-${Date.now()}`;
|
|
13449
13580
|
this.eventBus.emit("agent:event", {
|
|
13450
|
-
sessionId:
|
|
13581
|
+
sessionId: failedSessionId,
|
|
13451
13582
|
event: guidance
|
|
13452
13583
|
});
|
|
13453
13584
|
throw err;
|
|
@@ -13608,6 +13739,25 @@ var SessionFactory = class {
|
|
|
13608
13739
|
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
13609
13740
|
}
|
|
13610
13741
|
}
|
|
13742
|
+
const resumeFalledBack = record.agentSessionId && session.agentSessionId !== record.agentSessionId;
|
|
13743
|
+
if (resumeFalledBack) {
|
|
13744
|
+
log7.info({ sessionId: session.id }, "Resume fell back to fresh spawn \u2014 injecting conversation history");
|
|
13745
|
+
const contextManager = this.getContextManager?.();
|
|
13746
|
+
if (contextManager) {
|
|
13747
|
+
try {
|
|
13748
|
+
const config = this.configManager?.get();
|
|
13749
|
+
const labelAgent = config?.agentSwitch?.labelHistory ?? true;
|
|
13750
|
+
const contextResult = await contextManager.buildContext(
|
|
13751
|
+
{ type: "session", value: record.sessionId, repoPath: record.workingDir },
|
|
13752
|
+
{ labelAgent, noCache: true }
|
|
13753
|
+
);
|
|
13754
|
+
if (contextResult?.markdown) {
|
|
13755
|
+
session.setContext(contextResult.markdown);
|
|
13756
|
+
}
|
|
13757
|
+
} catch {
|
|
13758
|
+
}
|
|
13759
|
+
}
|
|
13760
|
+
}
|
|
13611
13761
|
log7.info({ sessionId: session.id, threadId }, "Lazy resume successful");
|
|
13612
13762
|
return session;
|
|
13613
13763
|
} catch (err) {
|
|
@@ -13732,14 +13882,14 @@ var SessionFactory = class {
|
|
|
13732
13882
|
};
|
|
13733
13883
|
|
|
13734
13884
|
// src/core/core.ts
|
|
13735
|
-
import
|
|
13885
|
+
import path16 from "path";
|
|
13736
13886
|
import os8 from "os";
|
|
13737
13887
|
import { nanoid as nanoid3 } from "nanoid";
|
|
13738
13888
|
|
|
13739
13889
|
// src/core/sessions/session-store.ts
|
|
13740
13890
|
init_log();
|
|
13741
|
-
import
|
|
13742
|
-
import
|
|
13891
|
+
import fs10 from "fs";
|
|
13892
|
+
import path9 from "path";
|
|
13743
13893
|
var log8 = createChildLogger({ module: "session-store" });
|
|
13744
13894
|
var DEBOUNCE_MS = 2e3;
|
|
13745
13895
|
var JsonFileSessionStore = class {
|
|
@@ -13813,9 +13963,9 @@ var JsonFileSessionStore = class {
|
|
|
13813
13963
|
version: 1,
|
|
13814
13964
|
sessions: Object.fromEntries(this.records)
|
|
13815
13965
|
};
|
|
13816
|
-
const dir =
|
|
13817
|
-
if (!
|
|
13818
|
-
|
|
13966
|
+
const dir = path9.dirname(this.filePath);
|
|
13967
|
+
if (!fs10.existsSync(dir)) fs10.mkdirSync(dir, { recursive: true });
|
|
13968
|
+
fs10.writeFileSync(this.filePath, JSON.stringify(data, null, 2));
|
|
13819
13969
|
}
|
|
13820
13970
|
destroy() {
|
|
13821
13971
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
@@ -13828,10 +13978,10 @@ var JsonFileSessionStore = class {
|
|
|
13828
13978
|
}
|
|
13829
13979
|
}
|
|
13830
13980
|
load() {
|
|
13831
|
-
if (!
|
|
13981
|
+
if (!fs10.existsSync(this.filePath)) return;
|
|
13832
13982
|
try {
|
|
13833
13983
|
const raw = JSON.parse(
|
|
13834
|
-
|
|
13984
|
+
fs10.readFileSync(this.filePath, "utf-8")
|
|
13835
13985
|
);
|
|
13836
13986
|
if (raw.version !== 1) {
|
|
13837
13987
|
log8.warn(
|
|
@@ -13847,7 +13997,7 @@ var JsonFileSessionStore = class {
|
|
|
13847
13997
|
} catch (err) {
|
|
13848
13998
|
log8.error({ err }, "Failed to load session store, backing up corrupt file");
|
|
13849
13999
|
try {
|
|
13850
|
-
|
|
14000
|
+
fs10.renameSync(this.filePath, `${this.filePath}.bak`);
|
|
13851
14001
|
} catch {
|
|
13852
14002
|
}
|
|
13853
14003
|
}
|
|
@@ -13871,7 +14021,10 @@ var JsonFileSessionStore = class {
|
|
|
13871
14021
|
for (const [id, record] of this.records) {
|
|
13872
14022
|
if (record.status === "active" || record.status === "initializing")
|
|
13873
14023
|
continue;
|
|
13874
|
-
const
|
|
14024
|
+
const raw = record.lastActiveAt;
|
|
14025
|
+
if (!raw) continue;
|
|
14026
|
+
const lastActive = new Date(raw).getTime();
|
|
14027
|
+
if (isNaN(lastActive)) continue;
|
|
13875
14028
|
if (lastActive < cutoff) {
|
|
13876
14029
|
this.records.delete(id);
|
|
13877
14030
|
removed++;
|
|
@@ -13929,8 +14082,8 @@ var AgentSwitchHandler = class {
|
|
|
13929
14082
|
if (middlewareChain && !result) throw new Error("Agent switch blocked by middleware");
|
|
13930
14083
|
const lastEntry = session.findLastSwitchEntry(toAgent);
|
|
13931
14084
|
const caps = getAgentCapabilities(toAgent);
|
|
13932
|
-
const canResume = !!(lastEntry && caps.supportsResume
|
|
13933
|
-
|
|
14085
|
+
const canResume = !!(lastEntry && caps.supportsResume);
|
|
14086
|
+
let resumed = false;
|
|
13934
14087
|
const startEvent = {
|
|
13935
14088
|
type: "system_message",
|
|
13936
14089
|
message: `Switching from ${fromAgent} to ${toAgent}...`
|
|
@@ -13964,29 +14117,34 @@ var AgentSwitchHandler = class {
|
|
|
13964
14117
|
try {
|
|
13965
14118
|
await session.switchAgent(toAgent, async () => {
|
|
13966
14119
|
if (canResume) {
|
|
13967
|
-
const instance = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
|
|
13968
|
-
if (fileService) instance.addAllowedPath(fileService.baseDir);
|
|
13969
|
-
return instance;
|
|
13970
|
-
} else {
|
|
13971
|
-
const instance = await agentManager.spawn(toAgent, session.workingDirectory);
|
|
13972
|
-
if (fileService) instance.addAllowedPath(fileService.baseDir);
|
|
13973
14120
|
try {
|
|
13974
|
-
const
|
|
13975
|
-
if (
|
|
13976
|
-
|
|
13977
|
-
|
|
13978
|
-
const contextResult = await contextService.buildContext(
|
|
13979
|
-
{ type: "session", value: sessionId, repoPath: session.workingDirectory },
|
|
13980
|
-
{ labelAgent }
|
|
13981
|
-
);
|
|
13982
|
-
if (contextResult?.markdown) {
|
|
13983
|
-
session.setContext(contextResult.markdown);
|
|
13984
|
-
}
|
|
13985
|
-
}
|
|
14121
|
+
const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
|
|
14122
|
+
if (fileService) instance2.addAllowedPath(fileService.baseDir);
|
|
14123
|
+
resumed = true;
|
|
14124
|
+
return instance2;
|
|
13986
14125
|
} catch {
|
|
14126
|
+
log9.warn({ sessionId, toAgent }, "Resume failed, falling back to new agent with context injection");
|
|
14127
|
+
}
|
|
14128
|
+
}
|
|
14129
|
+
const instance = await agentManager.spawn(toAgent, session.workingDirectory);
|
|
14130
|
+
if (fileService) instance.addAllowedPath(fileService.baseDir);
|
|
14131
|
+
try {
|
|
14132
|
+
const contextService = this.deps.getService("context");
|
|
14133
|
+
if (contextService) {
|
|
14134
|
+
const config = configManager.get();
|
|
14135
|
+
const labelAgent = config.agentSwitch?.labelHistory ?? true;
|
|
14136
|
+
await contextService.flushSession(sessionId);
|
|
14137
|
+
const contextResult = await contextService.buildContext(
|
|
14138
|
+
{ type: "session", value: sessionId, repoPath: session.workingDirectory },
|
|
14139
|
+
{ labelAgent, noCache: true }
|
|
14140
|
+
);
|
|
14141
|
+
if (contextResult?.markdown) {
|
|
14142
|
+
session.setContext(contextResult.markdown);
|
|
14143
|
+
}
|
|
13987
14144
|
}
|
|
13988
|
-
|
|
14145
|
+
} catch {
|
|
13989
14146
|
}
|
|
14147
|
+
return instance;
|
|
13990
14148
|
});
|
|
13991
14149
|
const successEvent = {
|
|
13992
14150
|
type: "system_message",
|
|
@@ -14070,14 +14228,14 @@ var AgentSwitchHandler = class {
|
|
|
14070
14228
|
};
|
|
14071
14229
|
|
|
14072
14230
|
// src/core/agents/agent-catalog.ts
|
|
14073
|
-
import * as
|
|
14074
|
-
import * as
|
|
14231
|
+
import * as fs14 from "fs";
|
|
14232
|
+
import * as path13 from "path";
|
|
14075
14233
|
import * as os6 from "os";
|
|
14076
14234
|
|
|
14077
14235
|
// src/core/agents/agent-store.ts
|
|
14078
14236
|
init_log();
|
|
14079
|
-
import * as
|
|
14080
|
-
import * as
|
|
14237
|
+
import * as fs12 from "fs";
|
|
14238
|
+
import * as path11 from "path";
|
|
14081
14239
|
import * as os4 from "os";
|
|
14082
14240
|
import { z as z2 } from "zod";
|
|
14083
14241
|
var log10 = createChildLogger({ module: "agent-store" });
|
|
@@ -14101,15 +14259,15 @@ var AgentStore = class {
|
|
|
14101
14259
|
data = { version: 1, installed: {} };
|
|
14102
14260
|
filePath;
|
|
14103
14261
|
constructor(filePath) {
|
|
14104
|
-
this.filePath = filePath ??
|
|
14262
|
+
this.filePath = filePath ?? path11.join(os4.homedir(), ".openacp", "agents.json");
|
|
14105
14263
|
}
|
|
14106
14264
|
load() {
|
|
14107
|
-
if (!
|
|
14265
|
+
if (!fs12.existsSync(this.filePath)) {
|
|
14108
14266
|
this.data = { version: 1, installed: {} };
|
|
14109
14267
|
return;
|
|
14110
14268
|
}
|
|
14111
14269
|
try {
|
|
14112
|
-
const raw = JSON.parse(
|
|
14270
|
+
const raw = JSON.parse(fs12.readFileSync(this.filePath, "utf-8"));
|
|
14113
14271
|
const result = AgentStoreSchema.safeParse(raw);
|
|
14114
14272
|
if (result.success) {
|
|
14115
14273
|
this.data = result.data;
|
|
@@ -14123,7 +14281,7 @@ var AgentStore = class {
|
|
|
14123
14281
|
}
|
|
14124
14282
|
}
|
|
14125
14283
|
exists() {
|
|
14126
|
-
return
|
|
14284
|
+
return fs12.existsSync(this.filePath);
|
|
14127
14285
|
}
|
|
14128
14286
|
getInstalled() {
|
|
14129
14287
|
return this.data.installed;
|
|
@@ -14143,10 +14301,10 @@ var AgentStore = class {
|
|
|
14143
14301
|
return key in this.data.installed;
|
|
14144
14302
|
}
|
|
14145
14303
|
save() {
|
|
14146
|
-
|
|
14304
|
+
fs12.mkdirSync(path11.dirname(this.filePath), { recursive: true });
|
|
14147
14305
|
const tmpPath = this.filePath + ".tmp";
|
|
14148
|
-
|
|
14149
|
-
|
|
14306
|
+
fs12.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), { mode: 384 });
|
|
14307
|
+
fs12.renameSync(tmpPath, this.filePath);
|
|
14150
14308
|
}
|
|
14151
14309
|
};
|
|
14152
14310
|
|
|
@@ -14164,7 +14322,7 @@ var AgentCatalog = class {
|
|
|
14164
14322
|
agentsDir;
|
|
14165
14323
|
constructor(store, cachePath, agentsDir) {
|
|
14166
14324
|
this.store = store ?? new AgentStore();
|
|
14167
|
-
this.cachePath = cachePath ??
|
|
14325
|
+
this.cachePath = cachePath ?? path13.join(os6.homedir(), ".openacp", "registry-cache.json");
|
|
14168
14326
|
this.agentsDir = agentsDir;
|
|
14169
14327
|
}
|
|
14170
14328
|
load() {
|
|
@@ -14185,8 +14343,8 @@ var AgentCatalog = class {
|
|
|
14185
14343
|
ttlHours: DEFAULT_TTL_HOURS,
|
|
14186
14344
|
data
|
|
14187
14345
|
};
|
|
14188
|
-
|
|
14189
|
-
|
|
14346
|
+
fs14.mkdirSync(path13.dirname(this.cachePath), { recursive: true });
|
|
14347
|
+
fs14.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
|
|
14190
14348
|
log12.info({ count: this.registryAgents.length }, "Registry updated");
|
|
14191
14349
|
} catch (err) {
|
|
14192
14350
|
log12.warn({ err }, "Failed to fetch registry, using cached data");
|
|
@@ -14354,9 +14512,9 @@ var AgentCatalog = class {
|
|
|
14354
14512
|
}
|
|
14355
14513
|
}
|
|
14356
14514
|
isCacheStale() {
|
|
14357
|
-
if (!
|
|
14515
|
+
if (!fs14.existsSync(this.cachePath)) return true;
|
|
14358
14516
|
try {
|
|
14359
|
-
const raw = JSON.parse(
|
|
14517
|
+
const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
|
|
14360
14518
|
const fetchedAt = new Date(raw.fetchedAt).getTime();
|
|
14361
14519
|
const ttlMs = (raw.ttlHours ?? DEFAULT_TTL_HOURS) * 60 * 60 * 1e3;
|
|
14362
14520
|
return Date.now() - fetchedAt > ttlMs;
|
|
@@ -14365,9 +14523,9 @@ var AgentCatalog = class {
|
|
|
14365
14523
|
}
|
|
14366
14524
|
}
|
|
14367
14525
|
loadRegistryFromCacheOrSnapshot() {
|
|
14368
|
-
if (
|
|
14526
|
+
if (fs14.existsSync(this.cachePath)) {
|
|
14369
14527
|
try {
|
|
14370
|
-
const raw = JSON.parse(
|
|
14528
|
+
const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
|
|
14371
14529
|
if (raw.data?.agents) {
|
|
14372
14530
|
this.registryAgents = raw.data.agents;
|
|
14373
14531
|
log12.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
|
|
@@ -14379,13 +14537,13 @@ var AgentCatalog = class {
|
|
|
14379
14537
|
}
|
|
14380
14538
|
try {
|
|
14381
14539
|
const candidates = [
|
|
14382
|
-
|
|
14383
|
-
|
|
14384
|
-
|
|
14540
|
+
path13.join(import.meta.dirname, "data", "registry-snapshot.json"),
|
|
14541
|
+
path13.join(import.meta.dirname, "..", "data", "registry-snapshot.json"),
|
|
14542
|
+
path13.join(import.meta.dirname, "..", "..", "data", "registry-snapshot.json")
|
|
14385
14543
|
];
|
|
14386
14544
|
for (const candidate of candidates) {
|
|
14387
|
-
if (
|
|
14388
|
-
const raw = JSON.parse(
|
|
14545
|
+
if (fs14.existsSync(candidate)) {
|
|
14546
|
+
const raw = JSON.parse(fs14.readFileSync(candidate, "utf-8"));
|
|
14389
14547
|
this.registryAgents = raw.agents ?? [];
|
|
14390
14548
|
log12.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
|
|
14391
14549
|
return;
|
|
@@ -14646,31 +14804,31 @@ var ErrorTracker = class {
|
|
|
14646
14804
|
};
|
|
14647
14805
|
|
|
14648
14806
|
// src/core/plugin/plugin-context.ts
|
|
14649
|
-
import
|
|
14807
|
+
import path15 from "path";
|
|
14650
14808
|
import os7 from "os";
|
|
14651
14809
|
|
|
14652
14810
|
// src/core/plugin/plugin-storage.ts
|
|
14653
|
-
import
|
|
14654
|
-
import
|
|
14811
|
+
import fs15 from "fs";
|
|
14812
|
+
import path14 from "path";
|
|
14655
14813
|
var PluginStorageImpl = class {
|
|
14656
14814
|
kvPath;
|
|
14657
14815
|
dataDir;
|
|
14658
14816
|
writeChain = Promise.resolve();
|
|
14659
14817
|
constructor(baseDir) {
|
|
14660
|
-
this.dataDir =
|
|
14661
|
-
this.kvPath =
|
|
14662
|
-
|
|
14818
|
+
this.dataDir = path14.join(baseDir, "data");
|
|
14819
|
+
this.kvPath = path14.join(baseDir, "kv.json");
|
|
14820
|
+
fs15.mkdirSync(baseDir, { recursive: true });
|
|
14663
14821
|
}
|
|
14664
14822
|
readKv() {
|
|
14665
14823
|
try {
|
|
14666
|
-
const raw =
|
|
14824
|
+
const raw = fs15.readFileSync(this.kvPath, "utf-8");
|
|
14667
14825
|
return JSON.parse(raw);
|
|
14668
14826
|
} catch {
|
|
14669
14827
|
return {};
|
|
14670
14828
|
}
|
|
14671
14829
|
}
|
|
14672
14830
|
writeKv(data) {
|
|
14673
|
-
|
|
14831
|
+
fs15.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
|
|
14674
14832
|
}
|
|
14675
14833
|
async get(key) {
|
|
14676
14834
|
const data = this.readKv();
|
|
@@ -14696,7 +14854,7 @@ var PluginStorageImpl = class {
|
|
|
14696
14854
|
return Object.keys(this.readKv());
|
|
14697
14855
|
}
|
|
14698
14856
|
getDataDir() {
|
|
14699
|
-
|
|
14857
|
+
fs15.mkdirSync(this.dataDir, { recursive: true });
|
|
14700
14858
|
return this.dataDir;
|
|
14701
14859
|
}
|
|
14702
14860
|
};
|
|
@@ -14720,9 +14878,11 @@ function createPluginContext(opts) {
|
|
|
14720
14878
|
config,
|
|
14721
14879
|
core
|
|
14722
14880
|
} = opts;
|
|
14723
|
-
const instanceRoot = opts.instanceRoot ??
|
|
14881
|
+
const instanceRoot = opts.instanceRoot ?? path15.join(os7.homedir(), ".openacp");
|
|
14724
14882
|
const registeredListeners = [];
|
|
14725
14883
|
const registeredCommands = [];
|
|
14884
|
+
const registeredMenuItemIds = [];
|
|
14885
|
+
const registeredAssistantSectionIds = [];
|
|
14726
14886
|
const noopLog = {
|
|
14727
14887
|
trace() {
|
|
14728
14888
|
},
|
|
@@ -14817,7 +14977,9 @@ function createPluginContext(opts) {
|
|
|
14817
14977
|
requirePermission(permissions, "commands:register", "registerMenuItem()");
|
|
14818
14978
|
const menuRegistry = serviceRegistry.get("menu-registry");
|
|
14819
14979
|
if (!menuRegistry) return;
|
|
14820
|
-
|
|
14980
|
+
const qualifiedId = `${pluginName}:${item.id}`;
|
|
14981
|
+
menuRegistry.register({ ...item, id: qualifiedId });
|
|
14982
|
+
registeredMenuItemIds.push(qualifiedId);
|
|
14821
14983
|
},
|
|
14822
14984
|
unregisterMenuItem(id) {
|
|
14823
14985
|
requirePermission(permissions, "commands:register", "unregisterMenuItem()");
|
|
@@ -14829,7 +14991,9 @@ function createPluginContext(opts) {
|
|
|
14829
14991
|
requirePermission(permissions, "commands:register", "registerAssistantSection()");
|
|
14830
14992
|
const assistantRegistry = serviceRegistry.get("assistant-registry");
|
|
14831
14993
|
if (!assistantRegistry) return;
|
|
14832
|
-
|
|
14994
|
+
const qualifiedId = `${pluginName}:${section.id}`;
|
|
14995
|
+
assistantRegistry.register({ ...section, id: qualifiedId });
|
|
14996
|
+
registeredAssistantSectionIds.push(qualifiedId);
|
|
14833
14997
|
},
|
|
14834
14998
|
unregisterAssistantSection(id) {
|
|
14835
14999
|
requirePermission(permissions, "commands:register", "unregisterAssistantSection()");
|
|
@@ -14837,6 +15001,14 @@ function createPluginContext(opts) {
|
|
|
14837
15001
|
if (!assistantRegistry) return;
|
|
14838
15002
|
assistantRegistry.unregister(`${pluginName}:${id}`);
|
|
14839
15003
|
},
|
|
15004
|
+
registerEditableFields(fields) {
|
|
15005
|
+
requirePermission(permissions, "commands:register", "registerEditableFields()");
|
|
15006
|
+
const registry = serviceRegistry.get("field-registry");
|
|
15007
|
+
if (registry && typeof registry.register === "function") {
|
|
15008
|
+
registry.register(pluginName, fields);
|
|
15009
|
+
log34.debug(`Registered ${fields.length} editable field(s) for ${pluginName}`);
|
|
15010
|
+
}
|
|
15011
|
+
},
|
|
14840
15012
|
get sessions() {
|
|
14841
15013
|
requirePermission(permissions, "kernel:access", "sessions");
|
|
14842
15014
|
return sessions;
|
|
@@ -14865,6 +15037,20 @@ function createPluginContext(opts) {
|
|
|
14865
15037
|
if (cmdRegistry && typeof cmdRegistry.unregisterByPlugin === "function") {
|
|
14866
15038
|
cmdRegistry.unregisterByPlugin(pluginName);
|
|
14867
15039
|
}
|
|
15040
|
+
const menuRegistry = serviceRegistry.get("menu-registry");
|
|
15041
|
+
if (menuRegistry) {
|
|
15042
|
+
for (const id of registeredMenuItemIds) {
|
|
15043
|
+
menuRegistry.unregister(id);
|
|
15044
|
+
}
|
|
15045
|
+
}
|
|
15046
|
+
registeredMenuItemIds.length = 0;
|
|
15047
|
+
const assistantRegistry = serviceRegistry.get("assistant-registry");
|
|
15048
|
+
if (assistantRegistry) {
|
|
15049
|
+
for (const id of registeredAssistantSectionIds) {
|
|
15050
|
+
assistantRegistry.unregister(id);
|
|
15051
|
+
}
|
|
15052
|
+
}
|
|
15053
|
+
registeredAssistantSectionIds.length = 0;
|
|
14868
15054
|
registeredCommands.length = 0;
|
|
14869
15055
|
}
|
|
14870
15056
|
};
|
|
@@ -14899,11 +15085,8 @@ function resolvePluginConfig(pluginName, configManager) {
|
|
|
14899
15085
|
"@openacp/file-service": "files",
|
|
14900
15086
|
"@openacp/api-server": "api",
|
|
14901
15087
|
"@openacp/telegram": "channels.telegram",
|
|
14902
|
-
"@openacp/discord": "channels.discord",
|
|
14903
|
-
"@openacp/adapter
|
|
14904
|
-
"@openacp/plugin-discord": "channels.discord",
|
|
14905
|
-
// alias for old name
|
|
14906
|
-
"@openacp/slack": "channels.slack"
|
|
15088
|
+
"@openacp/discord-adapter": "channels.discord",
|
|
15089
|
+
"@openacp/slack-adapter": "channels.slack"
|
|
14907
15090
|
};
|
|
14908
15091
|
const legacyKey = legacyMap[pluginName];
|
|
14909
15092
|
if (legacyKey) {
|
|
@@ -15388,8 +15571,10 @@ function createConfigSection(core) {
|
|
|
15388
15571
|
priority: 30,
|
|
15389
15572
|
buildContext: () => {
|
|
15390
15573
|
const config = core.configManager.get();
|
|
15574
|
+
const speechSvc = core.lifecycleManager?.serviceRegistry.get("speech");
|
|
15575
|
+
const sttActive = speechSvc ? speechSvc.isSTTAvailable() : false;
|
|
15391
15576
|
return `Workspace base: ${config.workspace.baseDir}
|
|
15392
|
-
STT: ${
|
|
15577
|
+
STT: ${sttActive ? "configured \u2705" : "Not configured"}`;
|
|
15393
15578
|
},
|
|
15394
15579
|
commands: [
|
|
15395
15580
|
{ command: "openacp config", description: "View config" },
|
|
@@ -15550,7 +15735,7 @@ var OpenACPCore = class {
|
|
|
15550
15735
|
);
|
|
15551
15736
|
this.agentCatalog.load();
|
|
15552
15737
|
this.agentManager = new AgentManager(this.agentCatalog);
|
|
15553
|
-
const storePath = ctx?.paths.sessions ??
|
|
15738
|
+
const storePath = ctx?.paths.sessions ?? path16.join(os8.homedir(), ".openacp", "sessions.json");
|
|
15554
15739
|
this.sessionStore = new JsonFileSessionStore(
|
|
15555
15740
|
storePath,
|
|
15556
15741
|
config.sessionStore.ttlDays
|
|
@@ -15574,7 +15759,7 @@ var OpenACPCore = class {
|
|
|
15574
15759
|
sessions: this.sessionManager,
|
|
15575
15760
|
config: this.configManager,
|
|
15576
15761
|
core: this,
|
|
15577
|
-
storagePath: ctx?.paths.pluginsData ??
|
|
15762
|
+
storagePath: ctx?.paths.pluginsData ?? path16.join(os8.homedir(), ".openacp", "plugins", "data"),
|
|
15578
15763
|
instanceRoot: ctx?.root,
|
|
15579
15764
|
log: createChildLogger({ module: "plugin" })
|
|
15580
15765
|
});
|
|
@@ -15640,7 +15825,7 @@ var OpenACPCore = class {
|
|
|
15640
15825
|
);
|
|
15641
15826
|
registerCoreMenuItems(this.menuRegistry);
|
|
15642
15827
|
if (ctx?.root) {
|
|
15643
|
-
this.assistantRegistry.setInstanceRoot(
|
|
15828
|
+
this.assistantRegistry.setInstanceRoot(path16.dirname(ctx.root));
|
|
15644
15829
|
}
|
|
15645
15830
|
this.assistantRegistry.register(createSessionsSection(this));
|
|
15646
15831
|
this.assistantRegistry.register(createAgentsSection(this));
|
|
@@ -15907,15 +16092,15 @@ ${text3}`;
|
|
|
15907
16092
|
message: `Agent '${agentName}' not found`
|
|
15908
16093
|
};
|
|
15909
16094
|
}
|
|
15910
|
-
const { existsSync:
|
|
15911
|
-
if (!
|
|
16095
|
+
const { existsSync: existsSync19 } = await import("fs");
|
|
16096
|
+
if (!existsSync19(cwd)) {
|
|
15912
16097
|
return {
|
|
15913
16098
|
ok: false,
|
|
15914
16099
|
error: "invalid_cwd",
|
|
15915
16100
|
message: `Directory does not exist: ${cwd}`
|
|
15916
16101
|
};
|
|
15917
16102
|
}
|
|
15918
|
-
const maxSessions =
|
|
16103
|
+
const maxSessions = 20;
|
|
15919
16104
|
if (this.sessionManager.listSessions().length >= maxSessions) {
|
|
15920
16105
|
return {
|
|
15921
16106
|
ok: false,
|
|
@@ -16172,7 +16357,9 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
16172
16357
|
this.commands.delete(name);
|
|
16173
16358
|
if (cmd.scope) {
|
|
16174
16359
|
this.commands.delete(`${cmd.scope}:${cmd.name}`);
|
|
16175
|
-
this.commands.
|
|
16360
|
+
if (this.commands.get(cmd.name) === cmd) {
|
|
16361
|
+
this.commands.delete(cmd.name);
|
|
16362
|
+
}
|
|
16176
16363
|
}
|
|
16177
16364
|
}
|
|
16178
16365
|
/** Remove all commands registered by a given plugin. */
|
|
@@ -16252,19 +16439,19 @@ init_doctor();
|
|
|
16252
16439
|
init_config_registry();
|
|
16253
16440
|
|
|
16254
16441
|
// src/core/config/config-editor.ts
|
|
16255
|
-
import * as
|
|
16442
|
+
import * as path28 from "path";
|
|
16256
16443
|
import * as clack2 from "@clack/prompts";
|
|
16257
16444
|
|
|
16258
16445
|
// src/cli/autostart.ts
|
|
16259
16446
|
init_log();
|
|
16260
|
-
import { execFileSync as
|
|
16261
|
-
import * as
|
|
16262
|
-
import * as
|
|
16447
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
16448
|
+
import * as fs26 from "fs";
|
|
16449
|
+
import * as path24 from "path";
|
|
16263
16450
|
import * as os11 from "os";
|
|
16264
16451
|
var log18 = createChildLogger({ module: "autostart" });
|
|
16265
16452
|
var LAUNCHD_LABEL = "com.openacp.daemon";
|
|
16266
|
-
var LAUNCHD_PLIST_PATH =
|
|
16267
|
-
var SYSTEMD_SERVICE_PATH =
|
|
16453
|
+
var LAUNCHD_PLIST_PATH = path24.join(os11.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
16454
|
+
var SYSTEMD_SERVICE_PATH = path24.join(os11.homedir(), ".config", "systemd", "user", "openacp.service");
|
|
16268
16455
|
function isAutoStartSupported() {
|
|
16269
16456
|
return process.platform === "darwin" || process.platform === "linux";
|
|
16270
16457
|
}
|
|
@@ -16276,7 +16463,7 @@ function escapeSystemdValue(str) {
|
|
|
16276
16463
|
return `"${escaped}"`;
|
|
16277
16464
|
}
|
|
16278
16465
|
function generateLaunchdPlist(nodePath, cliPath, logDir2) {
|
|
16279
|
-
const logFile =
|
|
16466
|
+
const logFile = path24.join(logDir2, "openacp.log");
|
|
16280
16467
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
16281
16468
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
16282
16469
|
<plist version="1.0">
|
|
@@ -16321,25 +16508,25 @@ function installAutoStart(logDir2) {
|
|
|
16321
16508
|
return { success: false, error: "Auto-start not supported on this platform" };
|
|
16322
16509
|
}
|
|
16323
16510
|
const nodePath = process.execPath;
|
|
16324
|
-
const cliPath =
|
|
16325
|
-
const resolvedLogDir = logDir2.startsWith("~") ?
|
|
16511
|
+
const cliPath = path24.resolve(process.argv[1]);
|
|
16512
|
+
const resolvedLogDir = logDir2.startsWith("~") ? path24.join(os11.homedir(), logDir2.slice(1)) : logDir2;
|
|
16326
16513
|
try {
|
|
16327
16514
|
if (process.platform === "darwin") {
|
|
16328
16515
|
const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir);
|
|
16329
|
-
const dir =
|
|
16330
|
-
|
|
16331
|
-
|
|
16332
|
-
|
|
16516
|
+
const dir = path24.dirname(LAUNCHD_PLIST_PATH);
|
|
16517
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
16518
|
+
fs26.writeFileSync(LAUNCHD_PLIST_PATH, plist);
|
|
16519
|
+
execFileSync6("launchctl", ["load", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
|
|
16333
16520
|
log18.info("LaunchAgent installed");
|
|
16334
16521
|
return { success: true };
|
|
16335
16522
|
}
|
|
16336
16523
|
if (process.platform === "linux") {
|
|
16337
16524
|
const unit = generateSystemdUnit(nodePath, cliPath);
|
|
16338
|
-
const dir =
|
|
16339
|
-
|
|
16340
|
-
|
|
16341
|
-
|
|
16342
|
-
|
|
16525
|
+
const dir = path24.dirname(SYSTEMD_SERVICE_PATH);
|
|
16526
|
+
fs26.mkdirSync(dir, { recursive: true });
|
|
16527
|
+
fs26.writeFileSync(SYSTEMD_SERVICE_PATH, unit);
|
|
16528
|
+
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
16529
|
+
execFileSync6("systemctl", ["--user", "enable", "openacp"], { stdio: "pipe" });
|
|
16343
16530
|
log18.info("systemd user service installed");
|
|
16344
16531
|
return { success: true };
|
|
16345
16532
|
}
|
|
@@ -16356,24 +16543,24 @@ function uninstallAutoStart() {
|
|
|
16356
16543
|
}
|
|
16357
16544
|
try {
|
|
16358
16545
|
if (process.platform === "darwin") {
|
|
16359
|
-
if (
|
|
16546
|
+
if (fs26.existsSync(LAUNCHD_PLIST_PATH)) {
|
|
16360
16547
|
try {
|
|
16361
|
-
|
|
16548
|
+
execFileSync6("launchctl", ["unload", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
|
|
16362
16549
|
} catch {
|
|
16363
16550
|
}
|
|
16364
|
-
|
|
16551
|
+
fs26.unlinkSync(LAUNCHD_PLIST_PATH);
|
|
16365
16552
|
log18.info("LaunchAgent removed");
|
|
16366
16553
|
}
|
|
16367
16554
|
return { success: true };
|
|
16368
16555
|
}
|
|
16369
16556
|
if (process.platform === "linux") {
|
|
16370
|
-
if (
|
|
16557
|
+
if (fs26.existsSync(SYSTEMD_SERVICE_PATH)) {
|
|
16371
16558
|
try {
|
|
16372
|
-
|
|
16559
|
+
execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
|
|
16373
16560
|
} catch {
|
|
16374
16561
|
}
|
|
16375
|
-
|
|
16376
|
-
|
|
16562
|
+
fs26.unlinkSync(SYSTEMD_SERVICE_PATH);
|
|
16563
|
+
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
16377
16564
|
log18.info("systemd user service removed");
|
|
16378
16565
|
}
|
|
16379
16566
|
return { success: true };
|
|
@@ -16387,10 +16574,10 @@ function uninstallAutoStart() {
|
|
|
16387
16574
|
}
|
|
16388
16575
|
function isAutoStartInstalled() {
|
|
16389
16576
|
if (process.platform === "darwin") {
|
|
16390
|
-
return
|
|
16577
|
+
return fs26.existsSync(LAUNCHD_PLIST_PATH);
|
|
16391
16578
|
}
|
|
16392
16579
|
if (process.platform === "linux") {
|
|
16393
|
-
return
|
|
16580
|
+
return fs26.existsSync(SYSTEMD_SERVICE_PATH);
|
|
16394
16581
|
}
|
|
16395
16582
|
return false;
|
|
16396
16583
|
}
|
|
@@ -16441,42 +16628,19 @@ var header = (title) => `
|
|
|
16441
16628
|
${c.cyan}${c.bold}[${title}]${c.reset}
|
|
16442
16629
|
`;
|
|
16443
16630
|
async function editTelegram(config, updates, settingsManager) {
|
|
16444
|
-
const
|
|
16445
|
-
|
|
16446
|
-
|
|
16447
|
-
|
|
16448
|
-
if (settingsManager) {
|
|
16449
|
-
const ps = await settingsManager.loadSettings("@openacp/telegram");
|
|
16450
|
-
if (Object.keys(ps).length > 0) {
|
|
16451
|
-
currentToken = ps.botToken ?? currentToken;
|
|
16452
|
-
currentChatId = ps.chatId ?? currentChatId;
|
|
16453
|
-
currentEnabled = ps.enabled ?? currentEnabled;
|
|
16454
|
-
}
|
|
16455
|
-
}
|
|
16631
|
+
const ps = settingsManager ? await settingsManager.loadSettings("@openacp/telegram") : {};
|
|
16632
|
+
const currentToken = ps.botToken ?? "";
|
|
16633
|
+
const currentChatId = ps.chatId ?? 0;
|
|
16634
|
+
const currentEnabled = ps.enabled ?? false;
|
|
16456
16635
|
console.log(header("Telegram"));
|
|
16457
|
-
console.log(` Enabled : ${currentEnabled ? ok("yes") : dim("no")}`);
|
|
16458
16636
|
const tokenDisplay = currentToken.length > 12 ? currentToken.slice(0, 6) + "..." + currentToken.slice(-6) : currentToken || dim("(not set)");
|
|
16637
|
+
console.log(` Enabled : ${currentEnabled ? ok("yes") : dim("no")}`);
|
|
16459
16638
|
console.log(` Bot Token : ${tokenDisplay}`);
|
|
16460
16639
|
console.log(` Chat ID : ${currentChatId || dim("(not set)")}`);
|
|
16461
16640
|
console.log("");
|
|
16462
|
-
const ensureTelegramUpdates = () => {
|
|
16463
|
-
if (!updates.channels) updates.channels = {};
|
|
16464
|
-
if (!updates.channels.telegram) {
|
|
16465
|
-
updates.channels.telegram = {};
|
|
16466
|
-
}
|
|
16467
|
-
return updates.channels.telegram;
|
|
16468
|
-
};
|
|
16469
16641
|
while (true) {
|
|
16470
|
-
const
|
|
16471
|
-
|
|
16472
|
-
const ps = await settingsManager.loadSettings("@openacp/telegram");
|
|
16473
|
-
if ("enabled" in ps) return ps.enabled;
|
|
16474
|
-
}
|
|
16475
|
-
const ch = updates.channels;
|
|
16476
|
-
const tgUp = ch?.telegram;
|
|
16477
|
-
if (tgUp && "enabled" in tgUp) return tgUp.enabled;
|
|
16478
|
-
return currentEnabled;
|
|
16479
|
-
})();
|
|
16642
|
+
const freshSettings = settingsManager ? await settingsManager.loadSettings("@openacp/telegram") : ps;
|
|
16643
|
+
const isEnabled = freshSettings.enabled ?? currentEnabled;
|
|
16480
16644
|
const choice = await select3({
|
|
16481
16645
|
message: "Telegram settings:",
|
|
16482
16646
|
choices: [
|
|
@@ -16490,75 +16654,32 @@ async function editTelegram(config, updates, settingsManager) {
|
|
|
16490
16654
|
if (choice === "toggle") {
|
|
16491
16655
|
if (settingsManager) {
|
|
16492
16656
|
await settingsManager.updatePluginSettings("@openacp/telegram", { enabled: !isEnabled });
|
|
16493
|
-
|
|
16494
|
-
const tgUp = ensureTelegramUpdates();
|
|
16495
|
-
tgUp.enabled = !isEnabled;
|
|
16657
|
+
console.log(!isEnabled ? ok("Telegram enabled") : ok("Telegram disabled"));
|
|
16496
16658
|
}
|
|
16497
|
-
console.log(!isEnabled ? ok("Telegram enabled") : ok("Telegram disabled"));
|
|
16498
16659
|
}
|
|
16499
16660
|
if (choice === "token") {
|
|
16500
16661
|
const token = await input({
|
|
16501
16662
|
message: "New bot token:",
|
|
16502
|
-
default: currentToken,
|
|
16503
16663
|
validate: (val) => val.trim().length > 0 || "Token cannot be empty"
|
|
16504
16664
|
});
|
|
16505
|
-
try {
|
|
16506
|
-
const { validateBotToken: validateBotToken2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
16507
|
-
const result = await validateBotToken2(token.trim());
|
|
16508
|
-
if (result.ok) {
|
|
16509
|
-
console.log(ok(`Connected to @${result.botUsername}`));
|
|
16510
|
-
} else {
|
|
16511
|
-
console.log(warn(`Validation failed: ${result.error} \u2014 saving anyway`));
|
|
16512
|
-
}
|
|
16513
|
-
} catch {
|
|
16514
|
-
console.log(warn("Telegram validator not available \u2014 skipping validation"));
|
|
16515
|
-
}
|
|
16516
16665
|
if (settingsManager) {
|
|
16517
|
-
await settingsManager.updatePluginSettings("@openacp/telegram", { botToken: token.trim()
|
|
16518
|
-
|
|
16519
|
-
const tgUp = ensureTelegramUpdates();
|
|
16520
|
-
tgUp.botToken = token.trim();
|
|
16521
|
-
tgUp.enabled = true;
|
|
16666
|
+
await settingsManager.updatePluginSettings("@openacp/telegram", { botToken: token.trim() });
|
|
16667
|
+
console.log(ok("Bot token updated"));
|
|
16522
16668
|
}
|
|
16523
16669
|
}
|
|
16524
16670
|
if (choice === "chatid") {
|
|
16525
|
-
const
|
|
16526
|
-
message: "New chat ID
|
|
16527
|
-
|
|
16528
|
-
validate: (val) => {
|
|
16529
|
-
const n = Number(val.trim());
|
|
16530
|
-
if (isNaN(n) || !Number.isInteger(n)) return "Chat ID must be an integer";
|
|
16531
|
-
return true;
|
|
16532
|
-
}
|
|
16671
|
+
const chatId = await input({
|
|
16672
|
+
message: "New chat ID:",
|
|
16673
|
+
validate: (val) => !isNaN(Number(val.trim())) || "Must be a number"
|
|
16533
16674
|
});
|
|
16534
|
-
const chatId = Number(chatIdStr.trim());
|
|
16535
|
-
const tokenForValidation = (() => {
|
|
16536
|
-
const ch = updates.channels;
|
|
16537
|
-
const tgUp = ch?.telegram;
|
|
16538
|
-
if (typeof tgUp?.botToken === "string") return tgUp.botToken;
|
|
16539
|
-
return currentToken;
|
|
16540
|
-
})();
|
|
16541
|
-
try {
|
|
16542
|
-
const { validateChatId: validateChatId2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
16543
|
-
const result = await validateChatId2(tokenForValidation, chatId);
|
|
16544
|
-
if (result.ok) {
|
|
16545
|
-
console.log(ok(`Group: ${result.title}${result.isForum ? "" : warn(" (topics not enabled)")}`));
|
|
16546
|
-
} else {
|
|
16547
|
-
console.log(warn(`Validation failed: ${result.error} \u2014 saving anyway`));
|
|
16548
|
-
}
|
|
16549
|
-
} catch {
|
|
16550
|
-
console.log(warn("Telegram validator not available \u2014 skipping validation"));
|
|
16551
|
-
}
|
|
16552
16675
|
if (settingsManager) {
|
|
16553
|
-
await settingsManager.updatePluginSettings("@openacp/telegram", { chatId });
|
|
16554
|
-
|
|
16555
|
-
const tgUp = ensureTelegramUpdates();
|
|
16556
|
-
tgUp.chatId = chatId;
|
|
16676
|
+
await settingsManager.updatePluginSettings("@openacp/telegram", { chatId: Number(chatId.trim()) });
|
|
16677
|
+
console.log(ok(`Chat ID set to ${chatId.trim()}`));
|
|
16557
16678
|
}
|
|
16558
16679
|
}
|
|
16559
16680
|
}
|
|
16560
16681
|
}
|
|
16561
|
-
var DISCORD_PACKAGE = "@openacp/adapter
|
|
16682
|
+
var DISCORD_PACKAGE = "@openacp/discord-adapter";
|
|
16562
16683
|
async function ensureDiscordPlugin() {
|
|
16563
16684
|
try {
|
|
16564
16685
|
return await import(DISCORD_PACKAGE);
|
|
@@ -16595,7 +16716,7 @@ async function editDiscord(_config, _updates) {
|
|
|
16595
16716
|
const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
|
|
16596
16717
|
const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
|
|
16597
16718
|
const root = getGlobalRoot2();
|
|
16598
|
-
const basePath =
|
|
16719
|
+
const basePath = path28.join(root, "plugins", "data");
|
|
16599
16720
|
const settingsManager = new SettingsManager2(basePath);
|
|
16600
16721
|
const ctx = createInstallContext2({
|
|
16601
16722
|
pluginName: plugin.name,
|
|
@@ -16609,12 +16730,12 @@ async function editDiscord(_config, _updates) {
|
|
|
16609
16730
|
}
|
|
16610
16731
|
}
|
|
16611
16732
|
async function editChannels(config, updates, settingsManager) {
|
|
16612
|
-
let tgConfigured =
|
|
16613
|
-
let dcConfigured =
|
|
16733
|
+
let tgConfigured = false;
|
|
16734
|
+
let dcConfigured = false;
|
|
16614
16735
|
if (settingsManager) {
|
|
16615
16736
|
const tgPs = await settingsManager.loadSettings("@openacp/telegram");
|
|
16616
16737
|
if (tgPs.botToken && tgPs.chatId) tgConfigured = true;
|
|
16617
|
-
const dcPs = await settingsManager.loadSettings("@openacp/adapter
|
|
16738
|
+
const dcPs = await settingsManager.loadSettings("@openacp/discord-adapter");
|
|
16618
16739
|
if (dcPs.guildId || dcPs.token) dcConfigured = true;
|
|
16619
16740
|
}
|
|
16620
16741
|
console.log(header("Channels"));
|
|
@@ -16636,7 +16757,7 @@ async function editChannels(config, updates, settingsManager) {
|
|
|
16636
16757
|
}
|
|
16637
16758
|
}
|
|
16638
16759
|
async function editAgent(config, updates) {
|
|
16639
|
-
const agentNames =
|
|
16760
|
+
const agentNames = [];
|
|
16640
16761
|
const currentDefault = config.defaultAgent;
|
|
16641
16762
|
console.log(header("Agent"));
|
|
16642
16763
|
console.log(` Default agent : ${c.bold}${currentDefault}${c.reset}`);
|
|
@@ -16679,17 +16800,12 @@ async function editWorkspace(config, updates) {
|
|
|
16679
16800
|
console.log(ok(`Workspace set to ${newDir.trim()}`));
|
|
16680
16801
|
}
|
|
16681
16802
|
async function editSecurity(config, updates, settingsManager) {
|
|
16682
|
-
|
|
16683
|
-
|
|
16684
|
-
|
|
16685
|
-
|
|
16686
|
-
|
|
16687
|
-
|
|
16688
|
-
maxConcurrentSessions: ps.maxConcurrentSessions ?? sec.maxConcurrentSessions,
|
|
16689
|
-
sessionTimeoutMinutes: ps.sessionTimeoutMinutes ?? sec.sessionTimeoutMinutes
|
|
16690
|
-
};
|
|
16691
|
-
}
|
|
16692
|
-
}
|
|
16803
|
+
const ps = settingsManager ? await settingsManager.loadSettings("@openacp/security") : {};
|
|
16804
|
+
const sec = {
|
|
16805
|
+
allowedUserIds: ps.allowedUserIds ?? [],
|
|
16806
|
+
maxConcurrentSessions: ps.maxConcurrentSessions ?? 20,
|
|
16807
|
+
sessionTimeoutMinutes: ps.sessionTimeoutMinutes ?? 60
|
|
16808
|
+
};
|
|
16693
16809
|
console.log(header("Security"));
|
|
16694
16810
|
console.log(` Allowed user IDs : ${sec.allowedUserIds?.length ? sec.allowedUserIds.join(", ") : dim("(all users allowed)")}`);
|
|
16695
16811
|
console.log(` Max concurrent sessions : ${sec.maxConcurrentSessions}`);
|
|
@@ -16717,9 +16833,6 @@ async function editSecurity(config, updates, settingsManager) {
|
|
|
16717
16833
|
});
|
|
16718
16834
|
if (settingsManager) {
|
|
16719
16835
|
await settingsManager.updatePluginSettings("@openacp/security", { maxConcurrentSessions: Number(val.trim()) });
|
|
16720
|
-
} else {
|
|
16721
|
-
if (!updates.security) updates.security = {};
|
|
16722
|
-
updates.security.maxConcurrentSessions = Number(val.trim());
|
|
16723
16836
|
}
|
|
16724
16837
|
console.log(ok(`Max concurrent sessions set to ${val.trim()}`));
|
|
16725
16838
|
}
|
|
@@ -16735,9 +16848,6 @@ async function editSecurity(config, updates, settingsManager) {
|
|
|
16735
16848
|
});
|
|
16736
16849
|
if (settingsManager) {
|
|
16737
16850
|
await settingsManager.updatePluginSettings("@openacp/security", { sessionTimeoutMinutes: Number(val.trim()) });
|
|
16738
|
-
} else {
|
|
16739
|
-
if (!updates.security) updates.security = {};
|
|
16740
|
-
updates.security.sessionTimeoutMinutes = Number(val.trim());
|
|
16741
16851
|
}
|
|
16742
16852
|
console.log(ok(`Session timeout set to ${val.trim()} minutes`));
|
|
16743
16853
|
}
|
|
@@ -16864,20 +16974,16 @@ async function editRunMode(config, updates) {
|
|
|
16864
16974
|
}
|
|
16865
16975
|
}
|
|
16866
16976
|
async function editApi(config, updates, settingsManager) {
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
16870
|
-
if (Object.keys(ps).length > 0) {
|
|
16871
|
-
api = { port: ps.port ?? api.port, host: ps.host ?? api.host };
|
|
16872
|
-
}
|
|
16873
|
-
}
|
|
16977
|
+
const ps = settingsManager ? await settingsManager.loadSettings("@openacp/api-server") : {};
|
|
16978
|
+
const currentPort = ps.port ?? 21420;
|
|
16979
|
+
const currentHost = ps.host ?? "127.0.0.1";
|
|
16874
16980
|
console.log(header("API"));
|
|
16875
|
-
console.log(` Port : ${
|
|
16876
|
-
console.log(` Host : ${
|
|
16981
|
+
console.log(` Port : ${currentPort}`);
|
|
16982
|
+
console.log(` Host : ${currentHost} ${dim("(localhost only)")}`);
|
|
16877
16983
|
console.log("");
|
|
16878
16984
|
const newPort = await input({
|
|
16879
16985
|
message: "API port:",
|
|
16880
|
-
default: String(
|
|
16986
|
+
default: String(currentPort),
|
|
16881
16987
|
validate: (v) => {
|
|
16882
16988
|
const n = Number(v.trim());
|
|
16883
16989
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return "Must be a valid port (1-65535)";
|
|
@@ -16886,24 +16992,24 @@ async function editApi(config, updates, settingsManager) {
|
|
|
16886
16992
|
});
|
|
16887
16993
|
if (settingsManager) {
|
|
16888
16994
|
await settingsManager.updatePluginSettings("@openacp/api-server", { port: Number(newPort.trim()) });
|
|
16889
|
-
} else {
|
|
16890
|
-
updates.api = { port: Number(newPort.trim()) };
|
|
16891
16995
|
}
|
|
16892
16996
|
console.log(ok(`API port set to ${newPort.trim()}`));
|
|
16893
16997
|
}
|
|
16894
16998
|
async function editTunnel(config, updates, settingsManager) {
|
|
16895
|
-
|
|
16896
|
-
|
|
16897
|
-
|
|
16898
|
-
|
|
16899
|
-
|
|
16900
|
-
}
|
|
16901
|
-
|
|
16902
|
-
|
|
16903
|
-
|
|
16999
|
+
const ps = settingsManager ? await settingsManager.loadSettings("@openacp/tunnel") : {};
|
|
17000
|
+
const tunnel = {
|
|
17001
|
+
enabled: ps.enabled ?? false,
|
|
17002
|
+
port: ps.port ?? 3100,
|
|
17003
|
+
provider: ps.provider ?? "openacp",
|
|
17004
|
+
options: ps.options ?? {},
|
|
17005
|
+
storeTtlMinutes: ps.storeTtlMinutes ?? 60,
|
|
17006
|
+
auth: ps.auth ?? { enabled: false }
|
|
17007
|
+
};
|
|
17008
|
+
const tun = { ...tunnel };
|
|
17009
|
+
const getVal = (key, fallback) => key in tun ? tun[key] : tunnel[key] ?? fallback;
|
|
16904
17010
|
console.log(header("Tunnel"));
|
|
16905
17011
|
console.log(` Enabled : ${getVal("enabled", false) ? ok("yes") : dim("no")}`);
|
|
16906
|
-
console.log(` Provider : ${
|
|
17012
|
+
console.log(` Provider : ${getVal("provider", "openacp")}`);
|
|
16907
17013
|
console.log(` Port : ${getVal("port", 3100)}`);
|
|
16908
17014
|
const authEnabled = getVal("auth", { enabled: false }).enabled;
|
|
16909
17015
|
console.log(` Auth : ${authEnabled ? ok("enabled") : dim("disabled")}`);
|
|
@@ -16921,8 +17027,6 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16921
17027
|
]
|
|
16922
17028
|
});
|
|
16923
17029
|
if (choice === "back") break;
|
|
16924
|
-
if (!updates.tunnel) updates.tunnel = { ...tunnel };
|
|
16925
|
-
const tun = updates.tunnel;
|
|
16926
17030
|
if (choice === "toggle") {
|
|
16927
17031
|
const current = getVal("enabled", false);
|
|
16928
17032
|
if (settingsManager) {
|
|
@@ -16935,7 +17039,8 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16935
17039
|
const provider = await select3({
|
|
16936
17040
|
message: "Select tunnel provider:",
|
|
16937
17041
|
choices: [
|
|
16938
|
-
{ name: "
|
|
17042
|
+
{ name: "OpenACP (managed)", value: "openacp" },
|
|
17043
|
+
{ name: "Cloudflare", value: "cloudflare" },
|
|
16939
17044
|
{ name: "ngrok", value: "ngrok" },
|
|
16940
17045
|
{ name: "bore", value: "bore" },
|
|
16941
17046
|
{ name: "Tailscale Funnel", value: "tailscale" }
|
|
@@ -16965,7 +17070,7 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16965
17070
|
console.log(ok(`Tunnel port set to ${val.trim()}`));
|
|
16966
17071
|
}
|
|
16967
17072
|
if (choice === "options") {
|
|
16968
|
-
const provider = getVal("provider", "
|
|
17073
|
+
const provider = getVal("provider", "openacp");
|
|
16969
17074
|
const currentOptions = getVal("options", {});
|
|
16970
17075
|
await editProviderOptions(provider, currentOptions, tun);
|
|
16971
17076
|
if (settingsManager) {
|
|
@@ -16975,20 +17080,21 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16975
17080
|
if (choice === "auth") {
|
|
16976
17081
|
const currentAuth = getVal("auth", { enabled: false });
|
|
16977
17082
|
if (currentAuth.enabled) {
|
|
16978
|
-
tun.auth = { enabled: false };
|
|
16979
17083
|
if (settingsManager) {
|
|
16980
17084
|
await settingsManager.updatePluginSettings("@openacp/tunnel", { auth: { enabled: false } });
|
|
16981
17085
|
}
|
|
17086
|
+
tun.auth = { enabled: false };
|
|
16982
17087
|
console.log(ok("Tunnel auth disabled"));
|
|
16983
17088
|
} else {
|
|
16984
17089
|
const token = await input({
|
|
16985
17090
|
message: "Auth token (leave empty to auto-generate):",
|
|
16986
17091
|
default: ""
|
|
16987
17092
|
});
|
|
16988
|
-
|
|
17093
|
+
const newAuth = token.trim() ? { enabled: true, token: token.trim() } : { enabled: true };
|
|
16989
17094
|
if (settingsManager) {
|
|
16990
|
-
await settingsManager.updatePluginSettings("@openacp/tunnel", { auth:
|
|
17095
|
+
await settingsManager.updatePluginSettings("@openacp/tunnel", { auth: newAuth });
|
|
16991
17096
|
}
|
|
17097
|
+
tun.auth = newAuth;
|
|
16992
17098
|
console.log(ok("Tunnel auth enabled"));
|
|
16993
17099
|
}
|
|
16994
17100
|
}
|
|
@@ -17116,17 +17222,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
17116
17222
|
async function sendConfigViaApi(port, updates) {
|
|
17117
17223
|
const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
17118
17224
|
const paths = flattenToPaths(updates);
|
|
17119
|
-
for (const { path:
|
|
17225
|
+
for (const { path: path34, value } of paths) {
|
|
17120
17226
|
const res = await call(port, "/api/config", {
|
|
17121
17227
|
method: "PATCH",
|
|
17122
17228
|
headers: { "Content-Type": "application/json" },
|
|
17123
|
-
body: JSON.stringify({ path:
|
|
17229
|
+
body: JSON.stringify({ path: path34, value })
|
|
17124
17230
|
});
|
|
17125
17231
|
const data = await res.json();
|
|
17126
17232
|
if (!res.ok) {
|
|
17127
|
-
console.log(warn(`Failed to update ${
|
|
17233
|
+
console.log(warn(`Failed to update ${path34}: ${data.error}`));
|
|
17128
17234
|
} else if (data.needsRestart) {
|
|
17129
|
-
console.log(warn(`${
|
|
17235
|
+
console.log(warn(`${path34} updated \u2014 restart required`));
|
|
17130
17236
|
}
|
|
17131
17237
|
}
|
|
17132
17238
|
}
|
|
@@ -17146,30 +17252,30 @@ function flattenToPaths(obj, prefix = "") {
|
|
|
17146
17252
|
// src/cli/daemon.ts
|
|
17147
17253
|
init_config();
|
|
17148
17254
|
import { spawn as spawn3 } from "child_process";
|
|
17149
|
-
import * as
|
|
17150
|
-
import * as
|
|
17255
|
+
import * as fs29 from "fs";
|
|
17256
|
+
import * as path29 from "path";
|
|
17151
17257
|
import * as os14 from "os";
|
|
17152
|
-
var DEFAULT_ROOT2 =
|
|
17258
|
+
var DEFAULT_ROOT2 = path29.join(os14.homedir(), ".openacp");
|
|
17153
17259
|
function getPidPath(root) {
|
|
17154
17260
|
const base = root ?? DEFAULT_ROOT2;
|
|
17155
|
-
return
|
|
17261
|
+
return path29.join(base, "openacp.pid");
|
|
17156
17262
|
}
|
|
17157
17263
|
function getLogDir(root) {
|
|
17158
17264
|
const base = root ?? DEFAULT_ROOT2;
|
|
17159
|
-
return
|
|
17265
|
+
return path29.join(base, "logs");
|
|
17160
17266
|
}
|
|
17161
17267
|
function getRunningMarker(root) {
|
|
17162
17268
|
const base = root ?? DEFAULT_ROOT2;
|
|
17163
|
-
return
|
|
17269
|
+
return path29.join(base, "running");
|
|
17164
17270
|
}
|
|
17165
17271
|
function writePidFile(pidPath, pid) {
|
|
17166
|
-
const dir =
|
|
17167
|
-
|
|
17168
|
-
|
|
17272
|
+
const dir = path29.dirname(pidPath);
|
|
17273
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
17274
|
+
fs29.writeFileSync(pidPath, String(pid));
|
|
17169
17275
|
}
|
|
17170
17276
|
function readPidFile(pidPath) {
|
|
17171
17277
|
try {
|
|
17172
|
-
const content =
|
|
17278
|
+
const content = fs29.readFileSync(pidPath, "utf-8").trim();
|
|
17173
17279
|
const pid = parseInt(content, 10);
|
|
17174
17280
|
return isNaN(pid) ? null : pid;
|
|
17175
17281
|
} catch {
|
|
@@ -17178,7 +17284,7 @@ function readPidFile(pidPath) {
|
|
|
17178
17284
|
}
|
|
17179
17285
|
function removePidFile(pidPath) {
|
|
17180
17286
|
try {
|
|
17181
|
-
|
|
17287
|
+
fs29.unlinkSync(pidPath);
|
|
17182
17288
|
} catch {
|
|
17183
17289
|
}
|
|
17184
17290
|
}
|
|
@@ -17211,12 +17317,12 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
|
|
|
17211
17317
|
return { error: `Already running (PID ${pid})` };
|
|
17212
17318
|
}
|
|
17213
17319
|
const resolvedLogDir = logDir2 ? expandHome3(logDir2) : getLogDir(instanceRoot);
|
|
17214
|
-
|
|
17215
|
-
const logFile =
|
|
17216
|
-
const cliPath =
|
|
17320
|
+
fs29.mkdirSync(resolvedLogDir, { recursive: true });
|
|
17321
|
+
const logFile = path29.join(resolvedLogDir, "openacp.log");
|
|
17322
|
+
const cliPath = path29.resolve(process.argv[1]);
|
|
17217
17323
|
const nodePath = process.execPath;
|
|
17218
|
-
const out =
|
|
17219
|
-
const err =
|
|
17324
|
+
const out = fs29.openSync(logFile, "a");
|
|
17325
|
+
const err = fs29.openSync(logFile, "a");
|
|
17220
17326
|
const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
|
|
17221
17327
|
detached: true,
|
|
17222
17328
|
stdio: ["ignore", out, err],
|
|
@@ -17225,8 +17331,8 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
|
|
|
17225
17331
|
...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
|
|
17226
17332
|
}
|
|
17227
17333
|
});
|
|
17228
|
-
|
|
17229
|
-
|
|
17334
|
+
fs29.closeSync(out);
|
|
17335
|
+
fs29.closeSync(err);
|
|
17230
17336
|
if (!child.pid) {
|
|
17231
17337
|
return { error: "Failed to spawn daemon process" };
|
|
17232
17338
|
}
|
|
@@ -17297,12 +17403,12 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
|
|
|
17297
17403
|
}
|
|
17298
17404
|
function markRunning(root) {
|
|
17299
17405
|
const marker = getRunningMarker(root);
|
|
17300
|
-
|
|
17301
|
-
|
|
17406
|
+
fs29.mkdirSync(path29.dirname(marker), { recursive: true });
|
|
17407
|
+
fs29.writeFileSync(marker, "");
|
|
17302
17408
|
}
|
|
17303
17409
|
function clearRunning(root) {
|
|
17304
17410
|
try {
|
|
17305
|
-
|
|
17411
|
+
fs29.unlinkSync(getRunningMarker(root));
|
|
17306
17412
|
} catch {
|
|
17307
17413
|
}
|
|
17308
17414
|
}
|
|
@@ -17527,6 +17633,9 @@ var Draft = class {
|
|
|
17527
17633
|
} finally {
|
|
17528
17634
|
this.firstFlushPending = false;
|
|
17529
17635
|
}
|
|
17636
|
+
if (this.buffer !== snapshot) {
|
|
17637
|
+
return this.flush();
|
|
17638
|
+
}
|
|
17530
17639
|
}
|
|
17531
17640
|
};
|
|
17532
17641
|
var DraftManager = class {
|
|
@@ -17804,8 +17913,8 @@ Configure via \`security.sessionTimeoutMinutes\` in config.
|
|
|
17804
17913
|
3. Copy and run it in your terminal \u2014 the session continues there with full conversation history
|
|
17805
17914
|
|
|
17806
17915
|
### Terminal \u2192 Chat
|
|
17807
|
-
1. First time: run \`openacp integrate
|
|
17808
|
-
2. In Claude Code, use
|
|
17916
|
+
1. First time: run \`openacp integrate <agent>\` to install handoff integration (one-time setup)
|
|
17917
|
+
2. In supported agents (for example Claude Code or OpenCode), use /openacp:handoff
|
|
17809
17918
|
3. The session appears as a new topic/thread and you can continue chatting there
|
|
17810
17919
|
|
|
17811
17920
|
### How it works
|