@openacp/cli 2026.404.1 → 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-CmTWKnpK.d.ts → channel-DstweC6V.d.ts} +3 -2
- package/dist/cli.js +1745 -1394
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +88 -426
- package/dist/index.js +1376 -1233
- 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,120 +2316,31 @@ 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
|
-
}
|
|
2593
|
-
const logsDir = config ? expandHome3(config.logging.logDir) :
|
|
2594
|
-
return {
|
|
2595
|
-
config,
|
|
2596
|
-
rawConfig,
|
|
2597
|
-
configPath,
|
|
2598
|
-
dataDir,
|
|
2599
|
-
sessionsPath:
|
|
2600
|
-
pidPath:
|
|
2601
|
-
portFilePath:
|
|
2602
|
-
pluginsDir:
|
|
2603
|
-
logsDir
|
|
2604
|
-
};
|
|
2605
|
-
}
|
|
2606
|
-
};
|
|
2607
|
-
}
|
|
2608
|
-
});
|
|
2609
|
-
|
|
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 })
|
|
2329
|
+
}
|
|
2330
|
+
const logsDir = config ? expandHome3(config.logging.logDir) : path23.join(dataDir, "logs");
|
|
2331
|
+
return {
|
|
2332
|
+
config,
|
|
2333
|
+
rawConfig,
|
|
2334
|
+
configPath,
|
|
2335
|
+
dataDir,
|
|
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"),
|
|
2340
|
+
logsDir
|
|
2341
|
+
};
|
|
2672
2342
|
}
|
|
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
2343
|
};
|
|
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
2344
|
}
|
|
2697
2345
|
});
|
|
2698
2346
|
|
|
@@ -2702,18 +2350,18 @@ __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
|
}
|
|
@@ -3474,7 +3126,10 @@ var init_sse_manager = __esm({
|
|
|
3474
3126
|
"session:updated",
|
|
3475
3127
|
"session:deleted",
|
|
3476
3128
|
"agent:event",
|
|
3477
|
-
"permission:request"
|
|
3129
|
+
"permission:request",
|
|
3130
|
+
"permission:resolved",
|
|
3131
|
+
"message:queued",
|
|
3132
|
+
"message:processing"
|
|
3478
3133
|
];
|
|
3479
3134
|
for (const eventName of events) {
|
|
3480
3135
|
const handler = (data) => {
|
|
@@ -3539,7 +3194,10 @@ data: ${JSON.stringify(data)}
|
|
|
3539
3194
|
const sessionEvents = [
|
|
3540
3195
|
"agent:event",
|
|
3541
3196
|
"permission:request",
|
|
3542
|
-
"
|
|
3197
|
+
"permission:resolved",
|
|
3198
|
+
"session:updated",
|
|
3199
|
+
"message:queued",
|
|
3200
|
+
"message:processing"
|
|
3543
3201
|
];
|
|
3544
3202
|
for (const res of this.sseConnections) {
|
|
3545
3203
|
const filter = res.sessionFilter;
|
|
@@ -3582,8 +3240,8 @@ data: ${JSON.stringify(data)}
|
|
|
3582
3240
|
});
|
|
3583
3241
|
|
|
3584
3242
|
// src/plugins/api-server/static-server.ts
|
|
3585
|
-
import * as
|
|
3586
|
-
import * as
|
|
3243
|
+
import * as fs31 from "fs";
|
|
3244
|
+
import * as path31 from "path";
|
|
3587
3245
|
import { fileURLToPath } from "url";
|
|
3588
3246
|
var MIME_TYPES, StaticServer;
|
|
3589
3247
|
var init_static_server = __esm({
|
|
@@ -3607,16 +3265,16 @@ var init_static_server = __esm({
|
|
|
3607
3265
|
this.uiDir = uiDir;
|
|
3608
3266
|
if (!this.uiDir) {
|
|
3609
3267
|
const __filename = fileURLToPath(import.meta.url);
|
|
3610
|
-
const candidate =
|
|
3611
|
-
if (
|
|
3268
|
+
const candidate = path31.resolve(path31.dirname(__filename), "../../ui/dist");
|
|
3269
|
+
if (fs31.existsSync(path31.join(candidate, "index.html"))) {
|
|
3612
3270
|
this.uiDir = candidate;
|
|
3613
3271
|
}
|
|
3614
3272
|
if (!this.uiDir) {
|
|
3615
|
-
const publishCandidate =
|
|
3616
|
-
|
|
3273
|
+
const publishCandidate = path31.resolve(
|
|
3274
|
+
path31.dirname(__filename),
|
|
3617
3275
|
"../ui"
|
|
3618
3276
|
);
|
|
3619
|
-
if (
|
|
3277
|
+
if (fs31.existsSync(path31.join(publishCandidate, "index.html"))) {
|
|
3620
3278
|
this.uiDir = publishCandidate;
|
|
3621
3279
|
}
|
|
3622
3280
|
}
|
|
@@ -3628,23 +3286,23 @@ var init_static_server = __esm({
|
|
|
3628
3286
|
serve(req, res) {
|
|
3629
3287
|
if (!this.uiDir) return false;
|
|
3630
3288
|
const urlPath = (req.url || "/").split("?")[0];
|
|
3631
|
-
const safePath =
|
|
3632
|
-
const filePath =
|
|
3633
|
-
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)
|
|
3634
3292
|
return false;
|
|
3635
3293
|
let realFilePath;
|
|
3636
3294
|
try {
|
|
3637
|
-
realFilePath =
|
|
3295
|
+
realFilePath = fs31.realpathSync(filePath);
|
|
3638
3296
|
} catch {
|
|
3639
3297
|
realFilePath = null;
|
|
3640
3298
|
}
|
|
3641
3299
|
if (realFilePath !== null) {
|
|
3642
|
-
const realUiDir =
|
|
3643
|
-
if (!realFilePath.startsWith(realUiDir +
|
|
3300
|
+
const realUiDir = fs31.realpathSync(this.uiDir);
|
|
3301
|
+
if (!realFilePath.startsWith(realUiDir + path31.sep) && realFilePath !== realUiDir)
|
|
3644
3302
|
return false;
|
|
3645
3303
|
}
|
|
3646
|
-
if (realFilePath !== null &&
|
|
3647
|
-
const ext =
|
|
3304
|
+
if (realFilePath !== null && fs31.existsSync(realFilePath) && fs31.statSync(realFilePath).isFile()) {
|
|
3305
|
+
const ext = path31.extname(filePath);
|
|
3648
3306
|
const contentType = MIME_TYPES[ext] ?? "application/octet-stream";
|
|
3649
3307
|
const isHashed = /\.[a-zA-Z0-9]{8,}\.(js|css)$/.test(filePath);
|
|
3650
3308
|
const cacheControl = isHashed ? "public, max-age=31536000, immutable" : "no-cache";
|
|
@@ -3652,16 +3310,16 @@ var init_static_server = __esm({
|
|
|
3652
3310
|
"Content-Type": contentType,
|
|
3653
3311
|
"Cache-Control": cacheControl
|
|
3654
3312
|
});
|
|
3655
|
-
|
|
3313
|
+
fs31.createReadStream(realFilePath).pipe(res);
|
|
3656
3314
|
return true;
|
|
3657
3315
|
}
|
|
3658
|
-
const indexPath =
|
|
3659
|
-
if (
|
|
3316
|
+
const indexPath = path31.join(this.uiDir, "index.html");
|
|
3317
|
+
if (fs31.existsSync(indexPath)) {
|
|
3660
3318
|
res.writeHead(200, {
|
|
3661
3319
|
"Content-Type": "text/html; charset=utf-8",
|
|
3662
3320
|
"Cache-Control": "no-cache"
|
|
3663
3321
|
});
|
|
3664
|
-
|
|
3322
|
+
fs31.createReadStream(indexPath).pipe(res);
|
|
3665
3323
|
return true;
|
|
3666
3324
|
}
|
|
3667
3325
|
return false;
|
|
@@ -3816,8 +3474,8 @@ var init_exports = __esm({
|
|
|
3816
3474
|
});
|
|
3817
3475
|
|
|
3818
3476
|
// src/plugins/context/context-cache.ts
|
|
3819
|
-
import * as
|
|
3820
|
-
import * as
|
|
3477
|
+
import * as fs32 from "fs";
|
|
3478
|
+
import * as path32 from "path";
|
|
3821
3479
|
import * as crypto2 from "crypto";
|
|
3822
3480
|
var DEFAULT_TTL_MS, ContextCache;
|
|
3823
3481
|
var init_context_cache = __esm({
|
|
@@ -3828,29 +3486,29 @@ var init_context_cache = __esm({
|
|
|
3828
3486
|
constructor(cacheDir, ttlMs = DEFAULT_TTL_MS) {
|
|
3829
3487
|
this.cacheDir = cacheDir;
|
|
3830
3488
|
this.ttlMs = ttlMs;
|
|
3831
|
-
|
|
3489
|
+
fs32.mkdirSync(cacheDir, { recursive: true });
|
|
3832
3490
|
}
|
|
3833
3491
|
keyHash(repoPath, queryKey) {
|
|
3834
3492
|
return crypto2.createHash("sha256").update(`${repoPath}:${queryKey}`).digest("hex").slice(0, 16);
|
|
3835
3493
|
}
|
|
3836
3494
|
filePath(repoPath, queryKey) {
|
|
3837
|
-
return
|
|
3495
|
+
return path32.join(this.cacheDir, `${this.keyHash(repoPath, queryKey)}.json`);
|
|
3838
3496
|
}
|
|
3839
3497
|
get(repoPath, queryKey) {
|
|
3840
3498
|
const fp = this.filePath(repoPath, queryKey);
|
|
3841
3499
|
try {
|
|
3842
|
-
const stat =
|
|
3500
|
+
const stat = fs32.statSync(fp);
|
|
3843
3501
|
if (Date.now() - stat.mtimeMs > this.ttlMs) {
|
|
3844
|
-
|
|
3502
|
+
fs32.unlinkSync(fp);
|
|
3845
3503
|
return null;
|
|
3846
3504
|
}
|
|
3847
|
-
return JSON.parse(
|
|
3505
|
+
return JSON.parse(fs32.readFileSync(fp, "utf-8"));
|
|
3848
3506
|
} catch {
|
|
3849
3507
|
return null;
|
|
3850
3508
|
}
|
|
3851
3509
|
}
|
|
3852
3510
|
set(repoPath, queryKey, result) {
|
|
3853
|
-
|
|
3511
|
+
fs32.writeFileSync(this.filePath(repoPath, queryKey), JSON.stringify(result));
|
|
3854
3512
|
}
|
|
3855
3513
|
};
|
|
3856
3514
|
}
|
|
@@ -3858,7 +3516,7 @@ var init_context_cache = __esm({
|
|
|
3858
3516
|
|
|
3859
3517
|
// src/plugins/context/context-manager.ts
|
|
3860
3518
|
import * as os15 from "os";
|
|
3861
|
-
import * as
|
|
3519
|
+
import * as path33 from "path";
|
|
3862
3520
|
var ContextManager;
|
|
3863
3521
|
var init_context_manager = __esm({
|
|
3864
3522
|
"src/plugins/context/context-manager.ts"() {
|
|
@@ -3868,12 +3526,25 @@ var init_context_manager = __esm({
|
|
|
3868
3526
|
providers = [];
|
|
3869
3527
|
cache;
|
|
3870
3528
|
historyStore;
|
|
3529
|
+
sessionFlusher;
|
|
3871
3530
|
constructor(cachePath) {
|
|
3872
|
-
this.cache = new ContextCache(cachePath ??
|
|
3531
|
+
this.cache = new ContextCache(cachePath ?? path33.join(os15.homedir(), ".openacp", "cache", "entire"));
|
|
3873
3532
|
}
|
|
3874
3533
|
setHistoryStore(store) {
|
|
3875
3534
|
this.historyStore = store;
|
|
3876
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
|
+
}
|
|
3877
3548
|
async getHistory(sessionId) {
|
|
3878
3549
|
if (!this.historyStore) return null;
|
|
3879
3550
|
return this.historyStore.read(sessionId);
|
|
@@ -3897,13 +3568,17 @@ var init_context_manager = __esm({
|
|
|
3897
3568
|
}
|
|
3898
3569
|
async buildContext(query, options) {
|
|
3899
3570
|
const queryKey = `${query.type}:${query.value}:${options?.limit ?? ""}:${options?.maxTokens ?? ""}:${options?.labelAgent ?? ""}`;
|
|
3900
|
-
|
|
3901
|
-
|
|
3571
|
+
if (!options?.noCache) {
|
|
3572
|
+
const cached = this.cache.get(query.repoPath, queryKey);
|
|
3573
|
+
if (cached) return cached;
|
|
3574
|
+
}
|
|
3902
3575
|
for (const provider of this.providers) {
|
|
3903
3576
|
if (!await provider.isAvailable(query.repoPath)) continue;
|
|
3904
3577
|
const result = await provider.buildContext(query, options);
|
|
3905
3578
|
if (result && result.markdown) {
|
|
3906
|
-
|
|
3579
|
+
if (!options?.noCache) {
|
|
3580
|
+
this.cache.set(query.repoPath, queryKey, result);
|
|
3581
|
+
}
|
|
3907
3582
|
return result;
|
|
3908
3583
|
}
|
|
3909
3584
|
}
|
|
@@ -3924,7 +3599,7 @@ var init_context_provider = __esm({
|
|
|
3924
3599
|
});
|
|
3925
3600
|
|
|
3926
3601
|
// src/plugins/context/entire/checkpoint-reader.ts
|
|
3927
|
-
import { execFileSync as
|
|
3602
|
+
import { execFileSync as execFileSync7 } from "child_process";
|
|
3928
3603
|
var ENTIRE_BRANCH, CHECKPOINT_ID_RE, SESSION_ID_RE, CheckpointReader;
|
|
3929
3604
|
var init_checkpoint_reader = __esm({
|
|
3930
3605
|
"src/plugins/context/entire/checkpoint-reader.ts"() {
|
|
@@ -3943,7 +3618,7 @@ var init_checkpoint_reader = __esm({
|
|
|
3943
3618
|
*/
|
|
3944
3619
|
git(...args) {
|
|
3945
3620
|
try {
|
|
3946
|
-
return
|
|
3621
|
+
return execFileSync7("git", ["-C", this.repoPath, ...args], {
|
|
3947
3622
|
encoding: "utf-8"
|
|
3948
3623
|
}).trim();
|
|
3949
3624
|
} catch {
|
|
@@ -4770,8 +4445,8 @@ function formatToolSummary(name, rawInput, displaySummary) {
|
|
|
4770
4445
|
}
|
|
4771
4446
|
if (lowerName === "grep") {
|
|
4772
4447
|
const pattern = args.pattern ?? "";
|
|
4773
|
-
const
|
|
4774
|
-
return pattern ? `\u{1F50D} Grep "${pattern}"${
|
|
4448
|
+
const path34 = args.path ?? "";
|
|
4449
|
+
return pattern ? `\u{1F50D} Grep "${pattern}"${path34 ? ` in ${path34}` : ""}` : `\u{1F527} ${name}`;
|
|
4775
4450
|
}
|
|
4776
4451
|
if (lowerName === "glob") {
|
|
4777
4452
|
const pattern = args.pattern ?? "";
|
|
@@ -4807,8 +4482,8 @@ function formatToolTitle(name, rawInput, displayTitle) {
|
|
|
4807
4482
|
}
|
|
4808
4483
|
if (lowerName === "grep") {
|
|
4809
4484
|
const pattern = args.pattern ?? "";
|
|
4810
|
-
const
|
|
4811
|
-
return pattern ? `"${pattern}"${
|
|
4485
|
+
const path34 = args.path ?? "";
|
|
4486
|
+
return pattern ? `"${pattern}"${path34 ? ` in ${path34}` : ""}` : name;
|
|
4812
4487
|
}
|
|
4813
4488
|
if (lowerName === "glob") {
|
|
4814
4489
|
return String(args.pattern ?? name);
|
|
@@ -5486,12 +5161,34 @@ function asRecord(value) {
|
|
|
5486
5161
|
function capitalize(s) {
|
|
5487
5162
|
return s.length === 0 ? s : s[0].toUpperCase() + s.slice(1);
|
|
5488
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
|
+
}
|
|
5489
5185
|
function buildTitle(entry, kind) {
|
|
5490
5186
|
if (entry.displayTitle) return entry.displayTitle;
|
|
5491
5187
|
if (entry.displaySummary) return entry.displaySummary;
|
|
5492
5188
|
const input2 = asRecord(entry.rawInput);
|
|
5189
|
+
const nameLower = entry.name.toLowerCase();
|
|
5493
5190
|
if (kind === "read") {
|
|
5494
|
-
const filePath =
|
|
5191
|
+
const filePath = getStringField(input2, ["file_path", "filePath", "path"]);
|
|
5495
5192
|
if (filePath) {
|
|
5496
5193
|
const startLine = typeof input2.start_line === "number" ? input2.start_line : null;
|
|
5497
5194
|
const endLine = typeof input2.end_line === "number" ? input2.end_line : null;
|
|
@@ -5506,7 +5203,7 @@ function buildTitle(entry, kind) {
|
|
|
5506
5203
|
return capitalize(entry.name);
|
|
5507
5204
|
}
|
|
5508
5205
|
if (kind === "edit" || kind === "write" || kind === "delete") {
|
|
5509
|
-
const filePath =
|
|
5206
|
+
const filePath = getStringField(input2, ["file_path", "filePath", "path"]);
|
|
5510
5207
|
if (filePath) return filePath;
|
|
5511
5208
|
return capitalize(entry.name);
|
|
5512
5209
|
}
|
|
@@ -5538,6 +5235,36 @@ function buildTitle(entry, kind) {
|
|
|
5538
5235
|
}
|
|
5539
5236
|
return capitalize(entry.name);
|
|
5540
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
|
+
}
|
|
5541
5268
|
if (kind === "fetch" || kind === "web") {
|
|
5542
5269
|
const url = typeof input2.url === "string" ? input2.url : null;
|
|
5543
5270
|
if (url && url !== "undefined") return url.length > 60 ? url.slice(0, 57) + "..." : url;
|
|
@@ -5545,11 +5272,36 @@ function buildTitle(entry, kind) {
|
|
|
5545
5272
|
if (query && query !== "undefined") return query.length > 60 ? query.slice(0, 57) + "..." : query;
|
|
5546
5273
|
return capitalize(entry.name);
|
|
5547
5274
|
}
|
|
5548
|
-
if (
|
|
5275
|
+
if (nameLower === "skill" && typeof input2.skill === "string" && input2.skill) {
|
|
5549
5276
|
return input2.skill;
|
|
5550
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
|
+
}
|
|
5551
5300
|
return entry.name;
|
|
5552
5301
|
}
|
|
5302
|
+
function isRecord(value) {
|
|
5303
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
5304
|
+
}
|
|
5553
5305
|
function buildOutputSummary(content) {
|
|
5554
5306
|
const lines = content.split("\n").length;
|
|
5555
5307
|
return `${lines} line${lines === 1 ? "" : "s"} of output`;
|
|
@@ -5624,6 +5376,7 @@ var init_display_spec_builder = __esm({
|
|
|
5624
5376
|
viewerLinks: entry.viewerLinks,
|
|
5625
5377
|
outputViewerLink,
|
|
5626
5378
|
outputFallbackContent,
|
|
5379
|
+
workingDirectory: sessionContext?.workingDirectory,
|
|
5627
5380
|
status: entry.status,
|
|
5628
5381
|
isNoise: entry.isNoise,
|
|
5629
5382
|
isHidden
|
|
@@ -5854,20 +5607,33 @@ function renderToolCard(snap) {
|
|
|
5854
5607
|
}
|
|
5855
5608
|
return sections.join("\n\n");
|
|
5856
5609
|
}
|
|
5857
|
-
function
|
|
5858
|
-
|
|
5610
|
+
function normalizePathLike(pathLike) {
|
|
5611
|
+
return pathLike.replace(/\\/g, "/");
|
|
5612
|
+
}
|
|
5613
|
+
function shortenTitle(title, kind, workingDirectory) {
|
|
5614
|
+
if (!title.includes("/")) return title;
|
|
5859
5615
|
const parenIdx = title.indexOf(" (");
|
|
5860
5616
|
const pathPart = parenIdx > 0 ? title.slice(0, parenIdx) : title;
|
|
5861
5617
|
const rangePart = parenIdx > 0 ? title.slice(parenIdx) : "";
|
|
5862
|
-
|
|
5863
|
-
|
|
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;
|
|
5864
5630
|
}
|
|
5865
5631
|
function renderSpecSection(spec) {
|
|
5866
5632
|
const lines = [];
|
|
5867
5633
|
const DONE = /* @__PURE__ */ new Set(["completed", "done", "failed", "error"]);
|
|
5868
5634
|
const statusPrefix = spec.status === "error" || spec.status === "failed" ? "\u274C " : DONE.has(spec.status) ? "\u2705 " : "\u{1F504} ";
|
|
5869
5635
|
const kindLabel = KIND_LABELS[spec.kind];
|
|
5870
|
-
const displayTitle = shortenTitle(spec.title, spec.kind);
|
|
5636
|
+
const displayTitle = shortenTitle(spec.title, spec.kind, spec.workingDirectory);
|
|
5871
5637
|
const hasUniqueTitle = displayTitle && displayTitle.toLowerCase() !== kindLabel?.toLowerCase() && displayTitle.toLowerCase() !== spec.kind;
|
|
5872
5638
|
let titleLine;
|
|
5873
5639
|
if (kindLabel) {
|
|
@@ -5896,9 +5662,9 @@ function renderSpecSection(spec) {
|
|
|
5896
5662
|
}
|
|
5897
5663
|
if (spec.viewerLinks?.file || spec.viewerLinks?.diff || spec.outputViewerLink) {
|
|
5898
5664
|
const linkParts = [];
|
|
5899
|
-
const
|
|
5665
|
+
const linkName = basename(displayTitle || kindLabel || spec.kind);
|
|
5900
5666
|
if (spec.viewerLinks?.file)
|
|
5901
|
-
linkParts.push(`<a href="${escapeHtml(spec.viewerLinks.file)}">View ${escapeHtml(
|
|
5667
|
+
linkParts.push(`<a href="${escapeHtml(spec.viewerLinks.file)}">View ${escapeHtml(linkName)}</a>`);
|
|
5902
5668
|
if (spec.viewerLinks?.diff)
|
|
5903
5669
|
linkParts.push(`<a href="${escapeHtml(spec.viewerLinks.diff)}">View diff</a>`);
|
|
5904
5670
|
if (spec.outputViewerLink)
|
|
@@ -5949,13 +5715,13 @@ __export(version_exports, {
|
|
|
5949
5715
|
runUpdate: () => runUpdate
|
|
5950
5716
|
});
|
|
5951
5717
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
5952
|
-
import { dirname as
|
|
5953
|
-
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";
|
|
5954
5720
|
function findPackageJson() {
|
|
5955
|
-
let dir =
|
|
5721
|
+
let dir = dirname10(fileURLToPath2(import.meta.url));
|
|
5956
5722
|
for (let i = 0; i < 5; i++) {
|
|
5957
|
-
const candidate =
|
|
5958
|
-
if (
|
|
5723
|
+
const candidate = join20(dir, "package.json");
|
|
5724
|
+
if (existsSync17(candidate)) return candidate;
|
|
5959
5725
|
const parent = resolve5(dir, "..");
|
|
5960
5726
|
if (parent === dir) break;
|
|
5961
5727
|
dir = parent;
|
|
@@ -5966,7 +5732,7 @@ function getCurrentVersion() {
|
|
|
5966
5732
|
try {
|
|
5967
5733
|
const pkgPath = findPackageJson();
|
|
5968
5734
|
if (!pkgPath) return "0.0.0-dev";
|
|
5969
|
-
const pkg = JSON.parse(
|
|
5735
|
+
const pkg = JSON.parse(readFileSync15(pkgPath, "utf-8"));
|
|
5970
5736
|
return pkg.version;
|
|
5971
5737
|
} catch {
|
|
5972
5738
|
return "0.0.0-dev";
|
|
@@ -6343,11 +6109,16 @@ async function createSessionDirect(ctx, core, chatId, agentName, workspace, onCo
|
|
|
6343
6109
|
}
|
|
6344
6110
|
function cacheWorkspace(agentKey, workspace) {
|
|
6345
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
|
+
}
|
|
6346
6117
|
if (workspaceCache.size > WS_CACHE_MAX) {
|
|
6347
|
-
|
|
6348
|
-
|
|
6349
|
-
|
|
6350
|
-
|
|
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);
|
|
6351
6122
|
}
|
|
6352
6123
|
}
|
|
6353
6124
|
const id = nextWsId++;
|
|
@@ -6789,9 +6560,12 @@ __export(integrate_exports, {
|
|
|
6789
6560
|
listIntegrations: () => listIntegrations,
|
|
6790
6561
|
uninstallIntegration: () => uninstallIntegration
|
|
6791
6562
|
});
|
|
6792
|
-
import { existsSync as
|
|
6793
|
-
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";
|
|
6794
6565
|
import { homedir as homedir10 } from "os";
|
|
6566
|
+
function isHooksIntegrationSpec(spec) {
|
|
6567
|
+
return spec.strategy === "hooks";
|
|
6568
|
+
}
|
|
6795
6569
|
function expandPath(p) {
|
|
6796
6570
|
return p.replace(/^~/, homedir10());
|
|
6797
6571
|
}
|
|
@@ -6909,12 +6683,48 @@ Examples:
|
|
|
6909
6683
|
/openacp:handoff telegram
|
|
6910
6684
|
`;
|
|
6911
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
|
+
}
|
|
6912
6722
|
function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
|
|
6913
6723
|
const fullPath = expandPath(settingsPath);
|
|
6914
6724
|
let settings = {};
|
|
6915
|
-
if (
|
|
6916
|
-
const raw =
|
|
6917
|
-
|
|
6725
|
+
if (existsSync18(fullPath)) {
|
|
6726
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6727
|
+
writeFileSync11(`${fullPath}.bak`, raw);
|
|
6918
6728
|
settings = JSON.parse(raw);
|
|
6919
6729
|
}
|
|
6920
6730
|
const hooks = settings.hooks ?? {};
|
|
@@ -6929,15 +6739,15 @@ function mergeSettingsJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
6929
6739
|
hooks: [{ type: "command", command: hookScriptPath }]
|
|
6930
6740
|
});
|
|
6931
6741
|
}
|
|
6932
|
-
|
|
6933
|
-
|
|
6742
|
+
mkdirSync11(dirname11(fullPath), { recursive: true });
|
|
6743
|
+
writeFileSync11(fullPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6934
6744
|
}
|
|
6935
6745
|
function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
6936
6746
|
const fullPath = expandPath(settingsPath);
|
|
6937
6747
|
let config = { version: 1 };
|
|
6938
|
-
if (
|
|
6939
|
-
const raw =
|
|
6940
|
-
|
|
6748
|
+
if (existsSync18(fullPath)) {
|
|
6749
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6750
|
+
writeFileSync11(`${fullPath}.bak`, raw);
|
|
6941
6751
|
config = JSON.parse(raw);
|
|
6942
6752
|
}
|
|
6943
6753
|
const hooks = config.hooks ?? {};
|
|
@@ -6948,13 +6758,13 @@ function mergeHooksJson(settingsPath, hookEvent, hookScriptPath) {
|
|
|
6948
6758
|
if (!alreadyInstalled) {
|
|
6949
6759
|
eventHooks.push({ command: hookScriptPath });
|
|
6950
6760
|
}
|
|
6951
|
-
|
|
6952
|
-
|
|
6761
|
+
mkdirSync11(dirname11(fullPath), { recursive: true });
|
|
6762
|
+
writeFileSync11(fullPath, JSON.stringify(config, null, 2) + "\n");
|
|
6953
6763
|
}
|
|
6954
6764
|
function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
6955
6765
|
const fullPath = expandPath(settingsPath);
|
|
6956
|
-
if (!
|
|
6957
|
-
const raw =
|
|
6766
|
+
if (!existsSync18(fullPath)) return;
|
|
6767
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6958
6768
|
const settings = JSON.parse(raw);
|
|
6959
6769
|
const hooks = settings.hooks;
|
|
6960
6770
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -6964,12 +6774,12 @@ function removeFromSettingsJson(settingsPath, hookEvent) {
|
|
|
6964
6774
|
if (hooks[hookEvent].length === 0) {
|
|
6965
6775
|
delete hooks[hookEvent];
|
|
6966
6776
|
}
|
|
6967
|
-
|
|
6777
|
+
writeFileSync11(fullPath, JSON.stringify(settings, null, 2) + "\n");
|
|
6968
6778
|
}
|
|
6969
6779
|
function removeFromHooksJson(settingsPath, hookEvent) {
|
|
6970
6780
|
const fullPath = expandPath(settingsPath);
|
|
6971
|
-
if (!
|
|
6972
|
-
const raw =
|
|
6781
|
+
if (!existsSync18(fullPath)) return;
|
|
6782
|
+
const raw = readFileSync16(fullPath, "utf-8");
|
|
6973
6783
|
const config = JSON.parse(raw);
|
|
6974
6784
|
const hooks = config.hooks;
|
|
6975
6785
|
if (!hooks?.[hookEvent]) return;
|
|
@@ -6979,9 +6789,9 @@ function removeFromHooksJson(settingsPath, hookEvent) {
|
|
|
6979
6789
|
if (hooks[hookEvent].length === 0) {
|
|
6980
6790
|
delete hooks[hookEvent];
|
|
6981
6791
|
}
|
|
6982
|
-
|
|
6792
|
+
writeFileSync11(fullPath, JSON.stringify(config, null, 2) + "\n");
|
|
6983
6793
|
}
|
|
6984
|
-
async function
|
|
6794
|
+
async function installHooksIntegration(agentKey, spec) {
|
|
6985
6795
|
const logs = [];
|
|
6986
6796
|
try {
|
|
6987
6797
|
if (!commandExists("jq")) {
|
|
@@ -6991,31 +6801,31 @@ async function installIntegration(agentKey, spec) {
|
|
|
6991
6801
|
};
|
|
6992
6802
|
}
|
|
6993
6803
|
const hooksDir = expandPath(spec.hooksDirPath);
|
|
6994
|
-
|
|
6995
|
-
const injectPath =
|
|
6996
|
-
|
|
6804
|
+
mkdirSync11(hooksDir, { recursive: true });
|
|
6805
|
+
const injectPath = join21(hooksDir, "openacp-inject-session.sh");
|
|
6806
|
+
writeFileSync11(injectPath, generateInjectScript(agentKey, spec));
|
|
6997
6807
|
chmodSync(injectPath, 493);
|
|
6998
6808
|
logs.push(`Created ${injectPath}`);
|
|
6999
|
-
const handoffPath =
|
|
7000
|
-
|
|
6809
|
+
const handoffPath = join21(hooksDir, "openacp-handoff.sh");
|
|
6810
|
+
writeFileSync11(handoffPath, generateHandoffScript(agentKey));
|
|
7001
6811
|
chmodSync(handoffPath, 493);
|
|
7002
6812
|
logs.push(`Created ${handoffPath}`);
|
|
7003
6813
|
if (spec.commandsPath && spec.handoffCommandName) {
|
|
7004
6814
|
if (spec.commandFormat === "skill") {
|
|
7005
|
-
const skillDir = expandPath(
|
|
7006
|
-
|
|
7007
|
-
const skillPath =
|
|
7008
|
-
|
|
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));
|
|
7009
6819
|
logs.push(`Created ${skillPath}`);
|
|
7010
6820
|
} else {
|
|
7011
6821
|
const cmdsDir = expandPath(spec.commandsPath);
|
|
7012
|
-
|
|
7013
|
-
const cmdPath =
|
|
7014
|
-
|
|
6822
|
+
mkdirSync11(cmdsDir, { recursive: true });
|
|
6823
|
+
const cmdPath = join21(cmdsDir, `${spec.handoffCommandName}.md`);
|
|
6824
|
+
writeFileSync11(cmdPath, generateHandoffCommand(agentKey, spec));
|
|
7015
6825
|
logs.push(`Created ${cmdPath}`);
|
|
7016
6826
|
}
|
|
7017
6827
|
}
|
|
7018
|
-
const injectFullPath =
|
|
6828
|
+
const injectFullPath = join21(hooksDir, "openacp-inject-session.sh");
|
|
7019
6829
|
if (spec.settingsFormat === "hooks_json") {
|
|
7020
6830
|
mergeHooksJson(spec.settingsPath, spec.hookEvent, injectFullPath);
|
|
7021
6831
|
} else {
|
|
@@ -7028,22 +6838,22 @@ async function installIntegration(agentKey, spec) {
|
|
|
7028
6838
|
return { success: false, logs };
|
|
7029
6839
|
}
|
|
7030
6840
|
}
|
|
7031
|
-
async function
|
|
6841
|
+
async function uninstallHooksIntegration(agentKey, spec) {
|
|
7032
6842
|
const logs = [];
|
|
7033
6843
|
try {
|
|
7034
6844
|
const hooksDir = expandPath(spec.hooksDirPath);
|
|
7035
6845
|
for (const filename of ["openacp-inject-session.sh", "openacp-handoff.sh"]) {
|
|
7036
|
-
const filePath =
|
|
7037
|
-
if (
|
|
6846
|
+
const filePath = join21(hooksDir, filename);
|
|
6847
|
+
if (existsSync18(filePath)) {
|
|
7038
6848
|
unlinkSync7(filePath);
|
|
7039
6849
|
logs.push(`Removed ${filePath}`);
|
|
7040
6850
|
}
|
|
7041
6851
|
}
|
|
7042
6852
|
if (spec.commandsPath && spec.handoffCommandName) {
|
|
7043
6853
|
if (spec.commandFormat === "skill") {
|
|
7044
|
-
const skillDir = expandPath(
|
|
7045
|
-
const skillPath =
|
|
7046
|
-
if (
|
|
6854
|
+
const skillDir = expandPath(join21(spec.commandsPath, spec.handoffCommandName));
|
|
6855
|
+
const skillPath = join21(skillDir, "SKILL.md");
|
|
6856
|
+
if (existsSync18(skillPath)) {
|
|
7047
6857
|
unlinkSync7(skillPath);
|
|
7048
6858
|
try {
|
|
7049
6859
|
rmdirSync(skillDir);
|
|
@@ -7052,8 +6862,8 @@ async function uninstallIntegration(agentKey, spec) {
|
|
|
7052
6862
|
logs.push(`Removed ${skillPath}`);
|
|
7053
6863
|
}
|
|
7054
6864
|
} else {
|
|
7055
|
-
const cmdPath = expandPath(
|
|
7056
|
-
if (
|
|
6865
|
+
const cmdPath = expandPath(join21(spec.commandsPath, `${spec.handoffCommandName}.md`));
|
|
6866
|
+
if (existsSync18(cmdPath)) {
|
|
7057
6867
|
unlinkSync7(cmdPath);
|
|
7058
6868
|
logs.push(`Removed ${cmdPath}`);
|
|
7059
6869
|
}
|
|
@@ -7071,14 +6881,82 @@ async function uninstallIntegration(agentKey, spec) {
|
|
|
7071
6881
|
return { success: false, logs };
|
|
7072
6882
|
}
|
|
7073
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
|
+
}
|
|
7074
6947
|
function buildHandoffItem(agentKey, spec) {
|
|
7075
|
-
const hooksDir = expandPath(spec.hooksDirPath);
|
|
7076
6948
|
return {
|
|
7077
6949
|
id: "handoff",
|
|
7078
6950
|
name: "Handoff",
|
|
7079
6951
|
description: "Transfer sessions between terminal and messaging platforms",
|
|
7080
6952
|
isInstalled() {
|
|
7081
|
-
|
|
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);
|
|
7082
6960
|
},
|
|
7083
6961
|
install: () => installIntegration(agentKey, spec),
|
|
7084
6962
|
uninstall: () => uninstallIntegration(agentKey, spec)
|
|
@@ -7090,23 +6968,24 @@ function getSkillBasePath(spec) {
|
|
|
7090
6968
|
return expandPath(skillsBase);
|
|
7091
6969
|
}
|
|
7092
6970
|
function buildTunnelItem(spec) {
|
|
7093
|
-
if (!spec.commandsPath) return null;
|
|
6971
|
+
if (!isHooksIntegrationSpec(spec) || !spec.commandsPath) return null;
|
|
6972
|
+
const hooksSpec = spec;
|
|
7094
6973
|
function getTunnelPath() {
|
|
7095
|
-
return
|
|
6974
|
+
return join21(getSkillBasePath(hooksSpec), "openacp-tunnel", "SKILL.md");
|
|
7096
6975
|
}
|
|
7097
6976
|
return {
|
|
7098
6977
|
id: "tunnel",
|
|
7099
6978
|
name: "Tunnel",
|
|
7100
6979
|
description: "Expose local ports to the internet via OpenACP tunnel",
|
|
7101
6980
|
isInstalled() {
|
|
7102
|
-
return
|
|
6981
|
+
return existsSync18(getTunnelPath());
|
|
7103
6982
|
},
|
|
7104
6983
|
async install() {
|
|
7105
6984
|
const logs = [];
|
|
7106
6985
|
try {
|
|
7107
6986
|
const skillPath = getTunnelPath();
|
|
7108
|
-
|
|
7109
|
-
|
|
6987
|
+
mkdirSync11(dirname11(skillPath), { recursive: true });
|
|
6988
|
+
writeFileSync11(skillPath, generateTunnelCommand());
|
|
7110
6989
|
logs.push(`Created ${skillPath}`);
|
|
7111
6990
|
return { success: true, logs };
|
|
7112
6991
|
} catch (err) {
|
|
@@ -7118,10 +6997,10 @@ function buildTunnelItem(spec) {
|
|
|
7118
6997
|
const logs = [];
|
|
7119
6998
|
try {
|
|
7120
6999
|
const skillPath = getTunnelPath();
|
|
7121
|
-
if (
|
|
7000
|
+
if (existsSync18(skillPath)) {
|
|
7122
7001
|
unlinkSync7(skillPath);
|
|
7123
7002
|
try {
|
|
7124
|
-
rmdirSync(
|
|
7003
|
+
rmdirSync(dirname11(skillPath));
|
|
7125
7004
|
} catch {
|
|
7126
7005
|
}
|
|
7127
7006
|
logs.push(`Removed ${skillPath}`);
|
|
@@ -7557,7 +7436,7 @@ async function buildSettingsKeyboard(core) {
|
|
|
7557
7436
|
const fields = getSafeFields();
|
|
7558
7437
|
const kb = new InlineKeyboard6();
|
|
7559
7438
|
for (const field of fields) {
|
|
7560
|
-
const value =
|
|
7439
|
+
const value = getConfigValue(core.configManager.get(), field.path);
|
|
7561
7440
|
const label = formatFieldLabel(field, value);
|
|
7562
7441
|
if (field.type === "toggle") {
|
|
7563
7442
|
kb.text(`${label}`, `s:toggle:${field.path}`).row();
|
|
@@ -7600,11 +7479,10 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
|
7600
7479
|
const fieldPath = ctx.callbackQuery.data.replace("s:toggle:", "");
|
|
7601
7480
|
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
7602
7481
|
if (!fieldDef) return;
|
|
7603
|
-
const
|
|
7604
|
-
const currentValue = await getFieldValueAsync(fieldDef, core.configManager, settingsManager);
|
|
7482
|
+
const currentValue = getConfigValue(core.configManager.get(), fieldDef.path);
|
|
7605
7483
|
const newValue = !currentValue;
|
|
7606
7484
|
try {
|
|
7607
|
-
await setFieldValueAsync(fieldDef, newValue, core.configManager
|
|
7485
|
+
await setFieldValueAsync(fieldDef, newValue, core.configManager);
|
|
7608
7486
|
const toast = isHotReloadable(fieldPath) ? `\u2705 ${fieldPath} = ${newValue}` : `\u2705 ${fieldPath} = ${newValue} (restart needed)`;
|
|
7609
7487
|
try {
|
|
7610
7488
|
await ctx.answerCallbackQuery({ text: toast });
|
|
@@ -7628,7 +7506,7 @@ function setupSettingsCallbacks(bot, core, getAssistantSession) {
|
|
|
7628
7506
|
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
7629
7507
|
if (!fieldDef) return;
|
|
7630
7508
|
const options = resolveOptions(fieldDef, config) ?? [];
|
|
7631
|
-
const currentValue =
|
|
7509
|
+
const currentValue = getConfigValue(core.configManager.get(), fieldDef.path);
|
|
7632
7510
|
const kb = new InlineKeyboard6();
|
|
7633
7511
|
for (const opt of options) {
|
|
7634
7512
|
const marker = opt === String(currentValue) ? " \u2713" : "";
|
|
@@ -7662,9 +7540,7 @@ Select a value:`, {
|
|
|
7662
7540
|
const speechSettings = await sm.loadSettings("@openacp/speech");
|
|
7663
7541
|
hasApiKey = !!speechSettings.groqApiKey;
|
|
7664
7542
|
} else {
|
|
7665
|
-
|
|
7666
|
-
const providerConfig = config.speech?.stt?.providers?.[newValue];
|
|
7667
|
-
hasApiKey = !!providerConfig?.apiKey;
|
|
7543
|
+
hasApiKey = false;
|
|
7668
7544
|
}
|
|
7669
7545
|
if (!hasApiKey) {
|
|
7670
7546
|
const assistant = getAssistantSession();
|
|
@@ -7684,7 +7560,7 @@ Select a value:`, {
|
|
|
7684
7560
|
return;
|
|
7685
7561
|
}
|
|
7686
7562
|
}
|
|
7687
|
-
await setFieldValueAsync(fieldDef, newValue, core.configManager
|
|
7563
|
+
await setFieldValueAsync(fieldDef, newValue, core.configManager);
|
|
7688
7564
|
try {
|
|
7689
7565
|
await ctx.answerCallbackQuery({ text: `\u2705 ${fieldPath} = ${newValue}` });
|
|
7690
7566
|
} catch {
|
|
@@ -7709,7 +7585,7 @@ Tap to change:`, {
|
|
|
7709
7585
|
const fieldPath = ctx.callbackQuery.data.replace("s:input:", "");
|
|
7710
7586
|
const fieldDef = getSafeFields().find((f) => f.path === fieldPath);
|
|
7711
7587
|
if (!fieldDef) return;
|
|
7712
|
-
const currentValue =
|
|
7588
|
+
const currentValue = getConfigValue(core.configManager.get(), fieldDef.path);
|
|
7713
7589
|
const assistant = getAssistantSession();
|
|
7714
7590
|
if (!assistant) {
|
|
7715
7591
|
try {
|
|
@@ -8362,7 +8238,7 @@ var init_commands = __esm({
|
|
|
8362
8238
|
|
|
8363
8239
|
// src/plugins/telegram/permissions.ts
|
|
8364
8240
|
import { InlineKeyboard as InlineKeyboard11 } from "grammy";
|
|
8365
|
-
import { nanoid as
|
|
8241
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
8366
8242
|
var log30, PermissionHandler;
|
|
8367
8243
|
var init_permissions = __esm({
|
|
8368
8244
|
"src/plugins/telegram/permissions.ts"() {
|
|
@@ -8381,7 +8257,7 @@ var init_permissions = __esm({
|
|
|
8381
8257
|
pending = /* @__PURE__ */ new Map();
|
|
8382
8258
|
async sendPermissionRequest(session, request) {
|
|
8383
8259
|
const threadId = Number(session.threadId);
|
|
8384
|
-
const callbackKey =
|
|
8260
|
+
const callbackKey = nanoid4(8);
|
|
8385
8261
|
this.pending.set(callbackKey, {
|
|
8386
8262
|
sessionId: session.id,
|
|
8387
8263
|
requestId: request.id,
|
|
@@ -9270,6 +9146,130 @@ var init_renderer2 = __esm({
|
|
|
9270
9146
|
}
|
|
9271
9147
|
});
|
|
9272
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
|
+
|
|
9273
9273
|
// src/plugins/telegram/adapter.ts
|
|
9274
9274
|
import { Bot, InputFile } from "grammy";
|
|
9275
9275
|
function patchedFetch(input2, init) {
|
|
@@ -9339,6 +9339,10 @@ var init_adapter = __esm({
|
|
|
9339
9339
|
controlMsgIds = /* @__PURE__ */ new Map();
|
|
9340
9340
|
_threadReadyHandler;
|
|
9341
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;
|
|
9342
9346
|
/** Store control message ID in memory + persist to session record */
|
|
9343
9347
|
storeControlMsgId(sessionId, msgId) {
|
|
9344
9348
|
this.controlMsgIds.set(sessionId, msgId);
|
|
@@ -9467,25 +9471,6 @@ var init_adapter = __esm({
|
|
|
9467
9471
|
if (chatId !== this.telegramConfig.chatId) return;
|
|
9468
9472
|
return next();
|
|
9469
9473
|
});
|
|
9470
|
-
const topics = await this.retryWithBackoff(
|
|
9471
|
-
() => ensureTopics(
|
|
9472
|
-
this.bot,
|
|
9473
|
-
this.telegramConfig.chatId,
|
|
9474
|
-
this.telegramConfig,
|
|
9475
|
-
async (updates) => {
|
|
9476
|
-
if (this.saveTopicIds) {
|
|
9477
|
-
await this.saveTopicIds(updates);
|
|
9478
|
-
} else {
|
|
9479
|
-
await this.core.configManager.save({
|
|
9480
|
-
channels: { telegram: updates }
|
|
9481
|
-
});
|
|
9482
|
-
}
|
|
9483
|
-
}
|
|
9484
|
-
),
|
|
9485
|
-
"ensureTopics"
|
|
9486
|
-
);
|
|
9487
|
-
this.notificationTopicId = topics.notificationTopicId;
|
|
9488
|
-
this.assistantTopicId = topics.assistantTopicId;
|
|
9489
9474
|
this.permissionHandler = new PermissionHandler(
|
|
9490
9475
|
this.bot,
|
|
9491
9476
|
this.telegramConfig.chatId,
|
|
@@ -9495,6 +9480,13 @@ var init_adapter = __esm({
|
|
|
9495
9480
|
this.bot.on("message:text", async (ctx, next) => {
|
|
9496
9481
|
const text3 = ctx.message?.text;
|
|
9497
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
|
+
}
|
|
9498
9490
|
const registry = this.core.lifecycleManager?.serviceRegistry?.get(
|
|
9499
9491
|
"command-registry"
|
|
9500
9492
|
);
|
|
@@ -9552,6 +9544,11 @@ var init_adapter = __esm({
|
|
|
9552
9544
|
}
|
|
9553
9545
|
});
|
|
9554
9546
|
this.bot.callbackQuery(/^c\//, async (ctx) => {
|
|
9547
|
+
if (!this._topicsInitialized) {
|
|
9548
|
+
await ctx.answerCallbackQuery().catch(() => {
|
|
9549
|
+
});
|
|
9550
|
+
return;
|
|
9551
|
+
}
|
|
9555
9552
|
const data = ctx.callbackQuery.data;
|
|
9556
9553
|
const command = this.fromCallbackData(data);
|
|
9557
9554
|
const registry = this.core.lifecycleManager?.serviceRegistry?.get(
|
|
@@ -9604,10 +9601,84 @@ var init_adapter = __esm({
|
|
|
9604
9601
|
await ctx.answerCallbackQuery({ text: "Command failed" });
|
|
9605
9602
|
}
|
|
9606
9603
|
});
|
|
9607
|
-
setupDangerousModeCallbacks(this.bot, this.core);
|
|
9608
|
-
setupTTSCallbacks(this.bot, this.core);
|
|
9609
|
-
setupVerbosityCallbacks(this.bot, this.core);
|
|
9610
|
-
setupIntegrateCallbacks(this.bot, this.core);
|
|
9604
|
+
setupDangerousModeCallbacks(this.bot, this.core);
|
|
9605
|
+
setupTTSCallbacks(this.bot, this.core);
|
|
9606
|
+
setupVerbosityCallbacks(this.bot, this.core);
|
|
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;
|
|
9611
9682
|
setupAllCallbacks(
|
|
9612
9683
|
this.bot,
|
|
9613
9684
|
this.core,
|
|
@@ -9637,7 +9708,6 @@ ${p}` : p;
|
|
|
9637
9708
|
this.storeControlMsgId(sessionId, msgId);
|
|
9638
9709
|
}
|
|
9639
9710
|
);
|
|
9640
|
-
this.permissionHandler.setupCallbackHandler();
|
|
9641
9711
|
this._threadReadyHandler = ({ sessionId, channelId, threadId }) => {
|
|
9642
9712
|
if (channelId !== "telegram") return;
|
|
9643
9713
|
const session = this.core.sessionManager.getSession(sessionId);
|
|
@@ -9675,13 +9745,6 @@ ${p}` : p;
|
|
|
9675
9745
|
};
|
|
9676
9746
|
this.core.eventBus.on("session:configChanged", this._configChangedHandler);
|
|
9677
9747
|
this.setupRoutes();
|
|
9678
|
-
this.bot.start({
|
|
9679
|
-
allowed_updates: ["message", "callback_query"],
|
|
9680
|
-
onStart: () => log33.info(
|
|
9681
|
-
{ chatId: this.telegramConfig.chatId },
|
|
9682
|
-
"Telegram bot started"
|
|
9683
|
-
)
|
|
9684
|
-
});
|
|
9685
9748
|
try {
|
|
9686
9749
|
const config = this.core.configManager.get();
|
|
9687
9750
|
const agents = this.core.agentManager.getAvailableAgents();
|
|
@@ -9711,42 +9774,56 @@ ${p}` : p;
|
|
|
9711
9774
|
} catch (err) {
|
|
9712
9775
|
log33.error({ err }, "Failed to spawn assistant");
|
|
9713
9776
|
}
|
|
9777
|
+
this._topicsInitialized = true;
|
|
9778
|
+
log33.info("Telegram adapter fully initialized");
|
|
9714
9779
|
}
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
|
|
9725
|
-
const delay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
9726
|
-
log33.warn(
|
|
9727
|
-
{ err, attempt, maxRetries, delayMs: delay, operation: label },
|
|
9728
|
-
`${label} failed, retrying in ${delay}ms`
|
|
9729
|
-
);
|
|
9730
|
-
await new Promise((r) => setTimeout(r, delay));
|
|
9731
|
-
}
|
|
9732
|
-
}
|
|
9733
|
-
throw new Error("unreachable");
|
|
9734
|
-
}
|
|
9735
|
-
/**
|
|
9736
|
-
* Register Telegram commands in the background with retries.
|
|
9737
|
-
* Non-critical — bot works fine without autocomplete commands.
|
|
9738
|
-
*/
|
|
9739
|
-
registerCommandsWithRetry() {
|
|
9740
|
-
this.retryWithBackoff(
|
|
9741
|
-
() => this.bot.api.setMyCommands(STATIC_COMMANDS, {
|
|
9742
|
-
scope: { type: "chat", chat_id: this.telegramConfig.chatId }
|
|
9743
|
-
}),
|
|
9744
|
-
"setMyCommands"
|
|
9745
|
-
).catch((err) => {
|
|
9746
|
-
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");
|
|
9747
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]);
|
|
9748
9821
|
}
|
|
9749
9822
|
async stop() {
|
|
9823
|
+
if (this._prerequisiteWatcher !== null) {
|
|
9824
|
+
clearTimeout(this._prerequisiteWatcher);
|
|
9825
|
+
this._prerequisiteWatcher = null;
|
|
9826
|
+
}
|
|
9750
9827
|
for (const tracker of this.sessionTrackers.values()) {
|
|
9751
9828
|
tracker.destroy();
|
|
9752
9829
|
}
|
|
@@ -10532,8 +10609,16 @@ function nodeToWebWritable(nodeStream) {
|
|
|
10532
10609
|
resolve6();
|
|
10533
10610
|
return;
|
|
10534
10611
|
}
|
|
10535
|
-
|
|
10536
|
-
|
|
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);
|
|
10537
10622
|
});
|
|
10538
10623
|
},
|
|
10539
10624
|
close() {
|
|
@@ -10580,13 +10665,13 @@ init_config();
|
|
|
10580
10665
|
// src/core/agents/agent-instance.ts
|
|
10581
10666
|
import { spawn as spawn2, execFileSync } from "child_process";
|
|
10582
10667
|
import { Transform } from "stream";
|
|
10583
|
-
import
|
|
10584
|
-
import
|
|
10668
|
+
import fs8 from "fs";
|
|
10669
|
+
import path7 from "path";
|
|
10585
10670
|
import { ClientSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
10586
10671
|
|
|
10587
10672
|
// src/core/security/path-guard.ts
|
|
10588
|
-
import
|
|
10589
|
-
import
|
|
10673
|
+
import fs5 from "fs";
|
|
10674
|
+
import path5 from "path";
|
|
10590
10675
|
import ignore from "ignore";
|
|
10591
10676
|
var DEFAULT_DENY_PATTERNS = [
|
|
10592
10677
|
".env",
|
|
@@ -10606,15 +10691,15 @@ var PathGuard = class {
|
|
|
10606
10691
|
ig;
|
|
10607
10692
|
constructor(options) {
|
|
10608
10693
|
try {
|
|
10609
|
-
this.cwd =
|
|
10694
|
+
this.cwd = fs5.realpathSync(path5.resolve(options.cwd));
|
|
10610
10695
|
} catch {
|
|
10611
|
-
this.cwd =
|
|
10696
|
+
this.cwd = path5.resolve(options.cwd);
|
|
10612
10697
|
}
|
|
10613
10698
|
this.allowedPaths = options.allowedPaths.map((p) => {
|
|
10614
10699
|
try {
|
|
10615
|
-
return
|
|
10700
|
+
return fs5.realpathSync(path5.resolve(p));
|
|
10616
10701
|
} catch {
|
|
10617
|
-
return
|
|
10702
|
+
return path5.resolve(p);
|
|
10618
10703
|
}
|
|
10619
10704
|
});
|
|
10620
10705
|
this.ig = ignore();
|
|
@@ -10624,19 +10709,19 @@ var PathGuard = class {
|
|
|
10624
10709
|
}
|
|
10625
10710
|
}
|
|
10626
10711
|
validatePath(targetPath, operation) {
|
|
10627
|
-
const resolved =
|
|
10712
|
+
const resolved = path5.resolve(targetPath);
|
|
10628
10713
|
let realPath;
|
|
10629
10714
|
try {
|
|
10630
|
-
realPath =
|
|
10715
|
+
realPath = fs5.realpathSync(resolved);
|
|
10631
10716
|
} catch {
|
|
10632
10717
|
realPath = resolved;
|
|
10633
10718
|
}
|
|
10634
|
-
if (operation === "write" &&
|
|
10719
|
+
if (operation === "write" && path5.basename(realPath) === ".openacpignore") {
|
|
10635
10720
|
return { allowed: false, reason: "Cannot write to .openacpignore" };
|
|
10636
10721
|
}
|
|
10637
|
-
const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd +
|
|
10722
|
+
const isWithinCwd = realPath === this.cwd || realPath.startsWith(this.cwd + path5.sep);
|
|
10638
10723
|
const isWithinAllowed = this.allowedPaths.some(
|
|
10639
|
-
(ap) => realPath === ap || realPath.startsWith(ap +
|
|
10724
|
+
(ap) => realPath === ap || realPath.startsWith(ap + path5.sep)
|
|
10640
10725
|
);
|
|
10641
10726
|
if (!isWithinCwd && !isWithinAllowed) {
|
|
10642
10727
|
return {
|
|
@@ -10645,7 +10730,7 @@ var PathGuard = class {
|
|
|
10645
10730
|
};
|
|
10646
10731
|
}
|
|
10647
10732
|
if (isWithinCwd && !isWithinAllowed) {
|
|
10648
|
-
const relativePath =
|
|
10733
|
+
const relativePath = path5.relative(this.cwd, realPath);
|
|
10649
10734
|
if (relativePath === ".openacpignore") {
|
|
10650
10735
|
return { allowed: true, reason: "" };
|
|
10651
10736
|
}
|
|
@@ -10660,15 +10745,15 @@ var PathGuard = class {
|
|
|
10660
10745
|
}
|
|
10661
10746
|
addAllowedPath(p) {
|
|
10662
10747
|
try {
|
|
10663
|
-
this.allowedPaths.push(
|
|
10748
|
+
this.allowedPaths.push(fs5.realpathSync(path5.resolve(p)));
|
|
10664
10749
|
} catch {
|
|
10665
|
-
this.allowedPaths.push(
|
|
10750
|
+
this.allowedPaths.push(path5.resolve(p));
|
|
10666
10751
|
}
|
|
10667
10752
|
}
|
|
10668
10753
|
static loadIgnoreFile(cwd) {
|
|
10669
|
-
const ignorePath =
|
|
10754
|
+
const ignorePath = path5.join(cwd, ".openacpignore");
|
|
10670
10755
|
try {
|
|
10671
|
-
const content =
|
|
10756
|
+
const content = fs5.readFileSync(ignorePath, "utf-8");
|
|
10672
10757
|
return content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
10673
10758
|
} catch {
|
|
10674
10759
|
return [];
|
|
@@ -10723,7 +10808,8 @@ function filterEnv(processEnv, agentEnv, whitelist) {
|
|
|
10723
10808
|
}
|
|
10724
10809
|
|
|
10725
10810
|
// src/core/utils/typed-emitter.ts
|
|
10726
|
-
var TypedEmitter = class {
|
|
10811
|
+
var TypedEmitter = class _TypedEmitter {
|
|
10812
|
+
static MAX_BUFFER_SIZE = 1e4;
|
|
10727
10813
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10728
10814
|
listeners = /* @__PURE__ */ new Map();
|
|
10729
10815
|
paused = false;
|
|
@@ -10747,6 +10833,10 @@ var TypedEmitter = class {
|
|
|
10747
10833
|
this.deliver(event, args);
|
|
10748
10834
|
} else {
|
|
10749
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
|
+
}
|
|
10750
10840
|
}
|
|
10751
10841
|
return;
|
|
10752
10842
|
}
|
|
@@ -10791,7 +10881,11 @@ var TypedEmitter = class {
|
|
|
10791
10881
|
const set = this.listeners.get(event);
|
|
10792
10882
|
if (!set) return;
|
|
10793
10883
|
for (const listener of set) {
|
|
10794
|
-
|
|
10884
|
+
try {
|
|
10885
|
+
listener(...args);
|
|
10886
|
+
} catch (err) {
|
|
10887
|
+
console.error(`[EventBus] Listener error on "${String(event)}":`, err);
|
|
10888
|
+
}
|
|
10795
10889
|
}
|
|
10796
10890
|
}
|
|
10797
10891
|
};
|
|
@@ -10882,6 +10976,11 @@ var TerminalManager = class {
|
|
|
10882
10976
|
}, async (p) => p).catch(() => {
|
|
10883
10977
|
});
|
|
10884
10978
|
}
|
|
10979
|
+
setTimeout(() => {
|
|
10980
|
+
if (this.terminals.has(terminalId)) {
|
|
10981
|
+
this.terminals.delete(terminalId);
|
|
10982
|
+
}
|
|
10983
|
+
}, 3e4).unref();
|
|
10885
10984
|
});
|
|
10886
10985
|
return { terminalId };
|
|
10887
10986
|
}
|
|
@@ -10914,6 +11013,12 @@ var TerminalManager = class {
|
|
|
10914
11013
|
state.process.on("exit", (code, signal) => {
|
|
10915
11014
|
resolve6({ exitCode: code, signal });
|
|
10916
11015
|
});
|
|
11016
|
+
if (state.exitStatus !== null) {
|
|
11017
|
+
resolve6({
|
|
11018
|
+
exitCode: state.exitStatus.exitCode,
|
|
11019
|
+
signal: state.exitStatus.signal
|
|
11020
|
+
});
|
|
11021
|
+
}
|
|
10917
11022
|
});
|
|
10918
11023
|
}
|
|
10919
11024
|
kill(terminalId) {
|
|
@@ -10951,24 +11056,24 @@ var McpManager = class {
|
|
|
10951
11056
|
};
|
|
10952
11057
|
|
|
10953
11058
|
// src/core/utils/debug-tracer.ts
|
|
10954
|
-
import
|
|
10955
|
-
import
|
|
11059
|
+
import fs7 from "fs";
|
|
11060
|
+
import path6 from "path";
|
|
10956
11061
|
var DEBUG_ENABLED = process.env.OPENACP_DEBUG === "true" || process.env.OPENACP_DEBUG === "1";
|
|
10957
11062
|
var DebugTracer = class {
|
|
10958
11063
|
constructor(sessionId, workingDirectory) {
|
|
10959
11064
|
this.sessionId = sessionId;
|
|
10960
11065
|
this.workingDirectory = workingDirectory;
|
|
10961
|
-
this.logDir =
|
|
11066
|
+
this.logDir = path6.join(workingDirectory, ".log");
|
|
10962
11067
|
}
|
|
10963
11068
|
dirCreated = false;
|
|
10964
11069
|
logDir;
|
|
10965
11070
|
log(layer, data) {
|
|
10966
11071
|
try {
|
|
10967
11072
|
if (!this.dirCreated) {
|
|
10968
|
-
|
|
11073
|
+
fs7.mkdirSync(this.logDir, { recursive: true });
|
|
10969
11074
|
this.dirCreated = true;
|
|
10970
11075
|
}
|
|
10971
|
-
const filePath =
|
|
11076
|
+
const filePath = path6.join(this.logDir, `${this.sessionId}_${layer}.jsonl`);
|
|
10972
11077
|
const seen = /* @__PURE__ */ new WeakSet();
|
|
10973
11078
|
const line = JSON.stringify({ ts: Date.now(), ...data }, (_key, value) => {
|
|
10974
11079
|
if (typeof value === "object" && value !== null) {
|
|
@@ -10977,7 +11082,7 @@ var DebugTracer = class {
|
|
|
10977
11082
|
}
|
|
10978
11083
|
return value;
|
|
10979
11084
|
}) + "\n";
|
|
10980
|
-
|
|
11085
|
+
fs7.appendFileSync(filePath, line);
|
|
10981
11086
|
} catch {
|
|
10982
11087
|
}
|
|
10983
11088
|
}
|
|
@@ -10995,11 +11100,11 @@ init_log();
|
|
|
10995
11100
|
var log4 = createChildLogger({ module: "agent-instance" });
|
|
10996
11101
|
function findPackageRoot(startDir) {
|
|
10997
11102
|
let dir = startDir;
|
|
10998
|
-
while (dir !==
|
|
10999
|
-
if (
|
|
11103
|
+
while (dir !== path7.dirname(dir)) {
|
|
11104
|
+
if (fs8.existsSync(path7.join(dir, "package.json"))) {
|
|
11000
11105
|
return dir;
|
|
11001
11106
|
}
|
|
11002
|
-
dir =
|
|
11107
|
+
dir = path7.dirname(dir);
|
|
11003
11108
|
}
|
|
11004
11109
|
return startDir;
|
|
11005
11110
|
}
|
|
@@ -11011,26 +11116,26 @@ function resolveAgentCommand(cmd) {
|
|
|
11011
11116
|
}
|
|
11012
11117
|
for (const root of searchRoots) {
|
|
11013
11118
|
const packageDirs = [
|
|
11014
|
-
|
|
11015
|
-
|
|
11119
|
+
path7.resolve(root, "node_modules", "@zed-industries", cmd, "dist", "index.js"),
|
|
11120
|
+
path7.resolve(root, "node_modules", cmd, "dist", "index.js")
|
|
11016
11121
|
];
|
|
11017
11122
|
for (const jsPath of packageDirs) {
|
|
11018
|
-
if (
|
|
11123
|
+
if (fs8.existsSync(jsPath)) {
|
|
11019
11124
|
return { command: process.execPath, args: [jsPath] };
|
|
11020
11125
|
}
|
|
11021
11126
|
}
|
|
11022
11127
|
}
|
|
11023
11128
|
for (const root of searchRoots) {
|
|
11024
|
-
const localBin =
|
|
11025
|
-
if (
|
|
11026
|
-
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");
|
|
11027
11132
|
if (content.startsWith("#!/usr/bin/env node")) {
|
|
11028
11133
|
return { command: process.execPath, args: [localBin] };
|
|
11029
11134
|
}
|
|
11030
11135
|
const match = content.match(/"([^"]+\.js)"/);
|
|
11031
11136
|
if (match) {
|
|
11032
|
-
const target =
|
|
11033
|
-
if (
|
|
11137
|
+
const target = path7.resolve(path7.dirname(localBin), match[1]);
|
|
11138
|
+
if (fs8.existsSync(target)) {
|
|
11034
11139
|
return { command: process.execPath, args: [target] };
|
|
11035
11140
|
}
|
|
11036
11141
|
}
|
|
@@ -11039,7 +11144,7 @@ function resolveAgentCommand(cmd) {
|
|
|
11039
11144
|
try {
|
|
11040
11145
|
const fullPath = execFileSync("which", [cmd], { encoding: "utf-8" }).trim();
|
|
11041
11146
|
if (fullPath) {
|
|
11042
|
-
const content =
|
|
11147
|
+
const content = fs8.readFileSync(fullPath, "utf-8");
|
|
11043
11148
|
if (content.startsWith("#!/usr/bin/env node")) {
|
|
11044
11149
|
return { command: process.execPath, args: [fullPath] };
|
|
11045
11150
|
}
|
|
@@ -11471,8 +11576,8 @@ ${stderr}`
|
|
|
11471
11576
|
writePath = result.path;
|
|
11472
11577
|
writeContent = result.content;
|
|
11473
11578
|
}
|
|
11474
|
-
await
|
|
11475
|
-
await
|
|
11579
|
+
await fs8.promises.mkdir(path7.dirname(writePath), { recursive: true });
|
|
11580
|
+
await fs8.promises.writeFile(writePath, writeContent, "utf-8");
|
|
11476
11581
|
return {};
|
|
11477
11582
|
},
|
|
11478
11583
|
// ── Terminal operations (delegated to TerminalManager) ─────────────
|
|
@@ -11574,7 +11679,7 @@ ${stderr}`
|
|
|
11574
11679
|
[Attachment access denied: ${attCheck.reason}]`;
|
|
11575
11680
|
continue;
|
|
11576
11681
|
}
|
|
11577
|
-
const data = await
|
|
11682
|
+
const data = await fs8.promises.readFile(att.filePath);
|
|
11578
11683
|
contentBlocks.push({ type: "image", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11579
11684
|
} else if (att.type === "audio" && this.promptCapabilities?.audio && !tooLarge) {
|
|
11580
11685
|
const attCheck = this.pathGuard.validatePath(att.filePath, "read");
|
|
@@ -11584,7 +11689,7 @@ ${stderr}`
|
|
|
11584
11689
|
[Attachment access denied: ${attCheck.reason}]`;
|
|
11585
11690
|
continue;
|
|
11586
11691
|
}
|
|
11587
|
-
const data = await
|
|
11692
|
+
const data = await fs8.promises.readFile(att.filePath);
|
|
11588
11693
|
contentBlocks.push({ type: "audio", data: data.toString("base64"), mimeType: att.mimeType });
|
|
11589
11694
|
} else {
|
|
11590
11695
|
if ((att.type === "image" || att.type === "audio") && !tooLarge) {
|
|
@@ -11669,21 +11774,27 @@ var PromptQueue = class {
|
|
|
11669
11774
|
queue = [];
|
|
11670
11775
|
processing = false;
|
|
11671
11776
|
abortController = null;
|
|
11672
|
-
|
|
11777
|
+
/** Set when abort is triggered; drainNext waits for the current processor to settle before starting the next item. */
|
|
11778
|
+
processorSettled = null;
|
|
11779
|
+
async enqueue(text3, attachments, routing, turnId) {
|
|
11673
11780
|
if (this.processing) {
|
|
11674
11781
|
return new Promise((resolve6) => {
|
|
11675
|
-
this.queue.push({ text: text3, attachments, routing, resolve: resolve6 });
|
|
11782
|
+
this.queue.push({ text: text3, attachments, routing, turnId, resolve: resolve6 });
|
|
11676
11783
|
});
|
|
11677
11784
|
}
|
|
11678
|
-
await this.process(text3, attachments, routing);
|
|
11785
|
+
await this.process(text3, attachments, routing, turnId);
|
|
11679
11786
|
}
|
|
11680
|
-
async process(text3, attachments, routing) {
|
|
11787
|
+
async process(text3, attachments, routing, turnId) {
|
|
11681
11788
|
this.processing = true;
|
|
11682
11789
|
this.abortController = new AbortController();
|
|
11683
11790
|
const { signal } = this.abortController;
|
|
11791
|
+
let settledResolve;
|
|
11792
|
+
this.processorSettled = new Promise((r) => {
|
|
11793
|
+
settledResolve = r;
|
|
11794
|
+
});
|
|
11684
11795
|
try {
|
|
11685
11796
|
await Promise.race([
|
|
11686
|
-
this.processor(text3, attachments, routing),
|
|
11797
|
+
this.processor(text3, attachments, routing, turnId),
|
|
11687
11798
|
new Promise((_, reject) => {
|
|
11688
11799
|
signal.addEventListener("abort", () => reject(new Error("Prompt aborted")), { once: true });
|
|
11689
11800
|
})
|
|
@@ -11695,13 +11806,15 @@ var PromptQueue = class {
|
|
|
11695
11806
|
} finally {
|
|
11696
11807
|
this.abortController = null;
|
|
11697
11808
|
this.processing = false;
|
|
11809
|
+
settledResolve();
|
|
11810
|
+
this.processorSettled = null;
|
|
11698
11811
|
this.drainNext();
|
|
11699
11812
|
}
|
|
11700
11813
|
}
|
|
11701
11814
|
drainNext() {
|
|
11702
11815
|
const next = this.queue.shift();
|
|
11703
11816
|
if (next) {
|
|
11704
|
-
this.process(next.text, next.attachments, next.routing).then(next.resolve);
|
|
11817
|
+
this.process(next.text, next.attachments, next.routing, next.turnId).then(next.resolve);
|
|
11705
11818
|
}
|
|
11706
11819
|
}
|
|
11707
11820
|
clear() {
|
|
@@ -11790,13 +11903,13 @@ var PermissionGate = class {
|
|
|
11790
11903
|
|
|
11791
11904
|
// src/core/sessions/session.ts
|
|
11792
11905
|
init_log();
|
|
11793
|
-
import * as
|
|
11906
|
+
import * as fs9 from "fs";
|
|
11794
11907
|
|
|
11795
11908
|
// src/core/sessions/turn-context.ts
|
|
11796
11909
|
import { nanoid } from "nanoid";
|
|
11797
|
-
function createTurnContext(sourceAdapterId, responseAdapterId) {
|
|
11910
|
+
function createTurnContext(sourceAdapterId, responseAdapterId, turnId) {
|
|
11798
11911
|
return {
|
|
11799
|
-
turnId: nanoid(8),
|
|
11912
|
+
turnId: turnId ?? nanoid(8),
|
|
11800
11913
|
sourceAdapterId,
|
|
11801
11914
|
responseAdapterId
|
|
11802
11915
|
};
|
|
@@ -11870,6 +11983,9 @@ var Session = class extends TypedEmitter {
|
|
|
11870
11983
|
threadIds = /* @__PURE__ */ new Map();
|
|
11871
11984
|
/** Active turn context — sealed on prompt dequeue, cleared on turn end */
|
|
11872
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;
|
|
11873
11989
|
permissionGate = new PermissionGate();
|
|
11874
11990
|
queue;
|
|
11875
11991
|
speechService;
|
|
@@ -11888,7 +12004,7 @@ var Session = class extends TypedEmitter {
|
|
|
11888
12004
|
this.log = createSessionLogger(this.id, moduleLog);
|
|
11889
12005
|
this.log.info({ agentName: this.agentName }, "Session created");
|
|
11890
12006
|
this.queue = new PromptQueue(
|
|
11891
|
-
(text3, attachments, routing) => this.processPrompt(text3, attachments, routing),
|
|
12007
|
+
(text3, attachments, routing, turnId) => this.processPrompt(text3, attachments, routing, turnId),
|
|
11892
12008
|
(err) => {
|
|
11893
12009
|
this.log.error({ err }, "Prompt execution failed");
|
|
11894
12010
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -11963,22 +12079,26 @@ var Session = class extends TypedEmitter {
|
|
|
11963
12079
|
this.log.info({ voiceMode: mode }, "TTS mode changed");
|
|
11964
12080
|
}
|
|
11965
12081
|
// --- Public API ---
|
|
11966
|
-
async enqueuePrompt(text3, attachments, routing) {
|
|
12082
|
+
async enqueuePrompt(text3, attachments, routing, externalTurnId) {
|
|
12083
|
+
const turnId = externalTurnId ?? nanoid2(8);
|
|
11967
12084
|
if (this.middlewareChain) {
|
|
11968
12085
|
const payload = { text: text3, attachments, sessionId: this.id };
|
|
11969
12086
|
const result = await this.middlewareChain.execute("agent:beforePrompt", payload, async (p) => p);
|
|
11970
|
-
if (!result) return;
|
|
12087
|
+
if (!result) return turnId;
|
|
11971
12088
|
text3 = result.text;
|
|
11972
12089
|
attachments = result.attachments;
|
|
11973
12090
|
}
|
|
11974
|
-
await this.queue.enqueue(text3, attachments, routing);
|
|
12091
|
+
await this.queue.enqueue(text3, attachments, routing, turnId);
|
|
12092
|
+
return turnId;
|
|
11975
12093
|
}
|
|
11976
|
-
async processPrompt(text3, attachments, routing) {
|
|
12094
|
+
async processPrompt(text3, attachments, routing, turnId) {
|
|
11977
12095
|
if (this._status === "finished") return;
|
|
11978
12096
|
this.activeTurnContext = createTurnContext(
|
|
11979
12097
|
routing?.sourceAdapterId ?? this.channelId,
|
|
11980
|
-
routing?.responseAdapterId
|
|
12098
|
+
routing?.responseAdapterId,
|
|
12099
|
+
turnId
|
|
11981
12100
|
);
|
|
12101
|
+
this.emit("turn_started", this.activeTurnContext);
|
|
11982
12102
|
this.promptCount++;
|
|
11983
12103
|
this.emit("prompt_count_changed", this.promptCount);
|
|
11984
12104
|
if (this._status === "initializing" || this._status === "cancelled" || this._status === "error") {
|
|
@@ -12016,6 +12136,7 @@ ${text3}`;
|
|
|
12016
12136
|
});
|
|
12017
12137
|
}
|
|
12018
12138
|
let stopReason = "end_turn";
|
|
12139
|
+
let promptError;
|
|
12019
12140
|
try {
|
|
12020
12141
|
const response = await this.agentInstance.prompt(processed.text, processed.attachments);
|
|
12021
12142
|
if (response && typeof response === "object" && "stopReason" in response) {
|
|
@@ -12027,14 +12148,21 @@ ${text3}`;
|
|
|
12027
12148
|
if (ttsActive && this.voiceMode === "next") {
|
|
12028
12149
|
this.voiceMode = "off";
|
|
12029
12150
|
}
|
|
12151
|
+
} catch (err) {
|
|
12152
|
+
stopReason = "error";
|
|
12153
|
+
promptError = err;
|
|
12030
12154
|
} finally {
|
|
12031
12155
|
if (accumulatorListener) {
|
|
12032
12156
|
this.off("agent_event", accumulatorListener);
|
|
12033
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;
|
|
12034
12163
|
}
|
|
12035
|
-
if (
|
|
12036
|
-
|
|
12037
|
-
});
|
|
12164
|
+
if (promptError !== void 0) {
|
|
12165
|
+
throw promptError;
|
|
12038
12166
|
}
|
|
12039
12167
|
this.log.info(
|
|
12040
12168
|
{ durationMs: Date.now() - promptStart },
|
|
@@ -12045,7 +12173,6 @@ ${text3}`;
|
|
|
12045
12173
|
this.log.warn({ err }, "TTS post-processing failed");
|
|
12046
12174
|
});
|
|
12047
12175
|
}
|
|
12048
|
-
this.activeTurnContext = null;
|
|
12049
12176
|
if (!this.name) {
|
|
12050
12177
|
await this.autoName();
|
|
12051
12178
|
}
|
|
@@ -12071,7 +12198,7 @@ ${text3}`;
|
|
|
12071
12198
|
try {
|
|
12072
12199
|
const audioPath = att.originalFilePath || att.filePath;
|
|
12073
12200
|
const audioMime = att.originalFilePath ? "audio/ogg" : att.mimeType;
|
|
12074
|
-
const audioBuffer = await
|
|
12201
|
+
const audioBuffer = await fs9.promises.readFile(audioPath);
|
|
12075
12202
|
const result = await this.speechService.transcribe(audioBuffer, audioMime);
|
|
12076
12203
|
this.log.info({ provider: "stt", duration: result.duration }, "Voice transcribed");
|
|
12077
12204
|
this.emit("agent_event", {
|
|
@@ -12303,7 +12430,7 @@ ${result.text}` : result.text;
|
|
|
12303
12430
|
};
|
|
12304
12431
|
|
|
12305
12432
|
// src/core/message-transformer.ts
|
|
12306
|
-
import * as
|
|
12433
|
+
import * as path8 from "path";
|
|
12307
12434
|
|
|
12308
12435
|
// src/core/utils/extract-file-info.ts
|
|
12309
12436
|
function extractFileInfo(name, kind, content, rawInput, meta) {
|
|
@@ -12613,6 +12740,20 @@ var MessageTransformer = class {
|
|
|
12613
12740
|
return { type: "text", text: "" };
|
|
12614
12741
|
}
|
|
12615
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
|
+
}
|
|
12616
12757
|
/** Check if rawInput is a non-empty object (not null, not {}) */
|
|
12617
12758
|
isNonEmptyInput(input2) {
|
|
12618
12759
|
return input2 !== null && input2 !== void 0 && typeof input2 === "object" && !Array.isArray(input2) && Object.keys(input2).length > 0;
|
|
@@ -12661,7 +12802,7 @@ var MessageTransformer = class {
|
|
|
12661
12802
|
);
|
|
12662
12803
|
return;
|
|
12663
12804
|
}
|
|
12664
|
-
const fileExt =
|
|
12805
|
+
const fileExt = path8.extname(fileInfo.filePath).toLowerCase();
|
|
12665
12806
|
if (BINARY_VIEWER_EXTENSIONS.has(fileExt)) {
|
|
12666
12807
|
log5.debug({ kind, filePath: fileInfo.filePath }, "enrichWithViewerLinks: skipping binary file");
|
|
12667
12808
|
return;
|
|
@@ -12807,6 +12948,7 @@ var SessionManager = class {
|
|
|
12807
12948
|
} catch {
|
|
12808
12949
|
}
|
|
12809
12950
|
session.markCancelled();
|
|
12951
|
+
await session.destroy();
|
|
12810
12952
|
this.sessions.delete(sessionId);
|
|
12811
12953
|
}
|
|
12812
12954
|
if (this.store) {
|
|
@@ -13008,9 +13150,12 @@ var SessionBridge = class {
|
|
|
13008
13150
|
connect() {
|
|
13009
13151
|
if (this.connected) return;
|
|
13010
13152
|
this.connected = true;
|
|
13011
|
-
this.
|
|
13012
|
-
this.session.
|
|
13013
|
-
|
|
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
|
+
}
|
|
13014
13159
|
this.listen(this.session, "agent_event", (event) => {
|
|
13015
13160
|
if (this.shouldForward(event)) {
|
|
13016
13161
|
this.dispatchAgentEvent(event);
|
|
@@ -13063,6 +13208,16 @@ var SessionBridge = class {
|
|
|
13063
13208
|
this.listen(this.session, "prompt_count_changed", (count) => {
|
|
13064
13209
|
this.deps.sessionManager.patchRecord(this.session.id, { currentPromptCount: count });
|
|
13065
13210
|
});
|
|
13211
|
+
this.listen(this.session, "turn_started", (ctx) => {
|
|
13212
|
+
if (ctx.sourceAdapterId !== "sse" && ctx.sourceAdapterId !== "api") {
|
|
13213
|
+
this.deps.eventBus?.emit("message:processing", {
|
|
13214
|
+
sessionId: this.session.id,
|
|
13215
|
+
turnId: ctx.turnId,
|
|
13216
|
+
sourceAdapterId: ctx.sourceAdapterId,
|
|
13217
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
13218
|
+
});
|
|
13219
|
+
}
|
|
13220
|
+
});
|
|
13066
13221
|
if (this.session.latestCommands !== null) {
|
|
13067
13222
|
this.session.emit("agent_event", { type: "commands_update", commands: this.session.latestCommands });
|
|
13068
13223
|
}
|
|
@@ -13079,6 +13234,7 @@ var SessionBridge = class {
|
|
|
13079
13234
|
if (current?.__bridgeId === this.adapterId) {
|
|
13080
13235
|
this.session.agentInstance.onPermissionRequest = async () => "";
|
|
13081
13236
|
}
|
|
13237
|
+
this.deps.messageTransformer.clearSessionCaches?.(this.session.id);
|
|
13082
13238
|
}
|
|
13083
13239
|
/** Dispatch an agent event through middleware and to the adapter */
|
|
13084
13240
|
async dispatchAgentEvent(event) {
|
|
@@ -13161,12 +13317,12 @@ var SessionBridge = class {
|
|
|
13161
13317
|
break;
|
|
13162
13318
|
case "image_content": {
|
|
13163
13319
|
if (this.deps.fileService) {
|
|
13164
|
-
const
|
|
13320
|
+
const fs33 = this.deps.fileService;
|
|
13165
13321
|
const sid = this.session.id;
|
|
13166
13322
|
const { data, mimeType } = event;
|
|
13167
13323
|
const buffer = Buffer.from(data, "base64");
|
|
13168
|
-
const ext =
|
|
13169
|
-
|
|
13324
|
+
const ext = fs33.extensionFromMime(mimeType);
|
|
13325
|
+
fs33.saveFile(sid, `agent-image${ext}`, buffer, mimeType).then((att) => {
|
|
13170
13326
|
this.sendMessage(sid, {
|
|
13171
13327
|
type: "attachment",
|
|
13172
13328
|
text: "",
|
|
@@ -13178,12 +13334,12 @@ var SessionBridge = class {
|
|
|
13178
13334
|
}
|
|
13179
13335
|
case "audio_content": {
|
|
13180
13336
|
if (this.deps.fileService) {
|
|
13181
|
-
const
|
|
13337
|
+
const fs33 = this.deps.fileService;
|
|
13182
13338
|
const sid = this.session.id;
|
|
13183
13339
|
const { data, mimeType } = event;
|
|
13184
13340
|
const buffer = Buffer.from(data, "base64");
|
|
13185
|
-
const ext =
|
|
13186
|
-
|
|
13341
|
+
const ext = fs33.extensionFromMime(mimeType);
|
|
13342
|
+
fs33.saveFile(sid, `agent-audio${ext}`, buffer, mimeType).then((att) => {
|
|
13187
13343
|
this.sendMessage(sid, {
|
|
13188
13344
|
type: "attachment",
|
|
13189
13345
|
text: "",
|
|
@@ -13369,14 +13525,29 @@ var SessionFactory = class {
|
|
|
13369
13525
|
}
|
|
13370
13526
|
let agentInstance;
|
|
13371
13527
|
try {
|
|
13372
|
-
|
|
13373
|
-
|
|
13374
|
-
|
|
13375
|
-
|
|
13376
|
-
|
|
13377
|
-
|
|
13378
|
-
|
|
13379
|
-
|
|
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
|
+
}
|
|
13380
13551
|
} catch (err) {
|
|
13381
13552
|
const message = err instanceof Error ? err.message : String(err);
|
|
13382
13553
|
const guidanceLines = [
|
|
@@ -13405,31 +13576,9 @@ var SessionFactory = class {
|
|
|
13405
13576
|
type: "system_message",
|
|
13406
13577
|
message: guidanceLines.join("\n")
|
|
13407
13578
|
};
|
|
13408
|
-
const
|
|
13409
|
-
id: createParams.existingSessionId,
|
|
13410
|
-
channelId: createParams.channelId,
|
|
13411
|
-
agentName: createParams.agentName,
|
|
13412
|
-
workingDirectory: createParams.workingDirectory,
|
|
13413
|
-
// Dummy agent instance — will never be prompted
|
|
13414
|
-
agentInstance: {
|
|
13415
|
-
sessionId: "",
|
|
13416
|
-
prompt: async () => {
|
|
13417
|
-
},
|
|
13418
|
-
cancel: async () => {
|
|
13419
|
-
},
|
|
13420
|
-
destroy: async () => {
|
|
13421
|
-
},
|
|
13422
|
-
on: () => {
|
|
13423
|
-
},
|
|
13424
|
-
off: () => {
|
|
13425
|
-
}
|
|
13426
|
-
},
|
|
13427
|
-
speechService: this.speechService
|
|
13428
|
-
});
|
|
13429
|
-
this.sessionManager.registerSession(failedSession);
|
|
13430
|
-
failedSession.emit("agent_event", guidance);
|
|
13579
|
+
const failedSessionId = createParams.existingSessionId ?? `failed-${Date.now()}`;
|
|
13431
13580
|
this.eventBus.emit("agent:event", {
|
|
13432
|
-
sessionId:
|
|
13581
|
+
sessionId: failedSessionId,
|
|
13433
13582
|
event: guidance
|
|
13434
13583
|
});
|
|
13435
13584
|
throw err;
|
|
@@ -13590,6 +13739,25 @@ var SessionFactory = class {
|
|
|
13590
13739
|
session.setAgentCapabilities(record.acpState.agentCapabilities);
|
|
13591
13740
|
}
|
|
13592
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
|
+
}
|
|
13593
13761
|
log7.info({ sessionId: session.id, threadId }, "Lazy resume successful");
|
|
13594
13762
|
return session;
|
|
13595
13763
|
} catch (err) {
|
|
@@ -13714,13 +13882,14 @@ var SessionFactory = class {
|
|
|
13714
13882
|
};
|
|
13715
13883
|
|
|
13716
13884
|
// src/core/core.ts
|
|
13717
|
-
import
|
|
13885
|
+
import path16 from "path";
|
|
13718
13886
|
import os8 from "os";
|
|
13887
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
13719
13888
|
|
|
13720
13889
|
// src/core/sessions/session-store.ts
|
|
13721
13890
|
init_log();
|
|
13722
|
-
import
|
|
13723
|
-
import
|
|
13891
|
+
import fs10 from "fs";
|
|
13892
|
+
import path9 from "path";
|
|
13724
13893
|
var log8 = createChildLogger({ module: "session-store" });
|
|
13725
13894
|
var DEBOUNCE_MS = 2e3;
|
|
13726
13895
|
var JsonFileSessionStore = class {
|
|
@@ -13794,9 +13963,9 @@ var JsonFileSessionStore = class {
|
|
|
13794
13963
|
version: 1,
|
|
13795
13964
|
sessions: Object.fromEntries(this.records)
|
|
13796
13965
|
};
|
|
13797
|
-
const dir =
|
|
13798
|
-
if (!
|
|
13799
|
-
|
|
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));
|
|
13800
13969
|
}
|
|
13801
13970
|
destroy() {
|
|
13802
13971
|
if (this.debounceTimer) clearTimeout(this.debounceTimer);
|
|
@@ -13809,10 +13978,10 @@ var JsonFileSessionStore = class {
|
|
|
13809
13978
|
}
|
|
13810
13979
|
}
|
|
13811
13980
|
load() {
|
|
13812
|
-
if (!
|
|
13981
|
+
if (!fs10.existsSync(this.filePath)) return;
|
|
13813
13982
|
try {
|
|
13814
13983
|
const raw = JSON.parse(
|
|
13815
|
-
|
|
13984
|
+
fs10.readFileSync(this.filePath, "utf-8")
|
|
13816
13985
|
);
|
|
13817
13986
|
if (raw.version !== 1) {
|
|
13818
13987
|
log8.warn(
|
|
@@ -13828,7 +13997,7 @@ var JsonFileSessionStore = class {
|
|
|
13828
13997
|
} catch (err) {
|
|
13829
13998
|
log8.error({ err }, "Failed to load session store, backing up corrupt file");
|
|
13830
13999
|
try {
|
|
13831
|
-
|
|
14000
|
+
fs10.renameSync(this.filePath, `${this.filePath}.bak`);
|
|
13832
14001
|
} catch {
|
|
13833
14002
|
}
|
|
13834
14003
|
}
|
|
@@ -13852,7 +14021,10 @@ var JsonFileSessionStore = class {
|
|
|
13852
14021
|
for (const [id, record] of this.records) {
|
|
13853
14022
|
if (record.status === "active" || record.status === "initializing")
|
|
13854
14023
|
continue;
|
|
13855
|
-
const
|
|
14024
|
+
const raw = record.lastActiveAt;
|
|
14025
|
+
if (!raw) continue;
|
|
14026
|
+
const lastActive = new Date(raw).getTime();
|
|
14027
|
+
if (isNaN(lastActive)) continue;
|
|
13856
14028
|
if (lastActive < cutoff) {
|
|
13857
14029
|
this.records.delete(id);
|
|
13858
14030
|
removed++;
|
|
@@ -13910,8 +14082,8 @@ var AgentSwitchHandler = class {
|
|
|
13910
14082
|
if (middlewareChain && !result) throw new Error("Agent switch blocked by middleware");
|
|
13911
14083
|
const lastEntry = session.findLastSwitchEntry(toAgent);
|
|
13912
14084
|
const caps = getAgentCapabilities(toAgent);
|
|
13913
|
-
const canResume = !!(lastEntry && caps.supportsResume
|
|
13914
|
-
|
|
14085
|
+
const canResume = !!(lastEntry && caps.supportsResume);
|
|
14086
|
+
let resumed = false;
|
|
13915
14087
|
const startEvent = {
|
|
13916
14088
|
type: "system_message",
|
|
13917
14089
|
message: `Switching from ${fromAgent} to ${toAgent}...`
|
|
@@ -13945,29 +14117,34 @@ var AgentSwitchHandler = class {
|
|
|
13945
14117
|
try {
|
|
13946
14118
|
await session.switchAgent(toAgent, async () => {
|
|
13947
14119
|
if (canResume) {
|
|
13948
|
-
const instance = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
|
|
13949
|
-
if (fileService) instance.addAllowedPath(fileService.baseDir);
|
|
13950
|
-
return instance;
|
|
13951
|
-
} else {
|
|
13952
|
-
const instance = await agentManager.spawn(toAgent, session.workingDirectory);
|
|
13953
|
-
if (fileService) instance.addAllowedPath(fileService.baseDir);
|
|
13954
14120
|
try {
|
|
13955
|
-
const
|
|
13956
|
-
if (
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
const contextResult = await contextService.buildContext(
|
|
13960
|
-
{ type: "session", value: sessionId, repoPath: session.workingDirectory },
|
|
13961
|
-
{ labelAgent }
|
|
13962
|
-
);
|
|
13963
|
-
if (contextResult?.markdown) {
|
|
13964
|
-
session.setContext(contextResult.markdown);
|
|
13965
|
-
}
|
|
13966
|
-
}
|
|
14121
|
+
const instance2 = await agentManager.resume(toAgent, session.workingDirectory, lastEntry.agentSessionId);
|
|
14122
|
+
if (fileService) instance2.addAllowedPath(fileService.baseDir);
|
|
14123
|
+
resumed = true;
|
|
14124
|
+
return instance2;
|
|
13967
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
|
+
}
|
|
13968
14144
|
}
|
|
13969
|
-
|
|
14145
|
+
} catch {
|
|
13970
14146
|
}
|
|
14147
|
+
return instance;
|
|
13971
14148
|
});
|
|
13972
14149
|
const successEvent = {
|
|
13973
14150
|
type: "system_message",
|
|
@@ -14051,14 +14228,14 @@ var AgentSwitchHandler = class {
|
|
|
14051
14228
|
};
|
|
14052
14229
|
|
|
14053
14230
|
// src/core/agents/agent-catalog.ts
|
|
14054
|
-
import * as
|
|
14055
|
-
import * as
|
|
14231
|
+
import * as fs14 from "fs";
|
|
14232
|
+
import * as path13 from "path";
|
|
14056
14233
|
import * as os6 from "os";
|
|
14057
14234
|
|
|
14058
14235
|
// src/core/agents/agent-store.ts
|
|
14059
14236
|
init_log();
|
|
14060
|
-
import * as
|
|
14061
|
-
import * as
|
|
14237
|
+
import * as fs12 from "fs";
|
|
14238
|
+
import * as path11 from "path";
|
|
14062
14239
|
import * as os4 from "os";
|
|
14063
14240
|
import { z as z2 } from "zod";
|
|
14064
14241
|
var log10 = createChildLogger({ module: "agent-store" });
|
|
@@ -14082,15 +14259,15 @@ var AgentStore = class {
|
|
|
14082
14259
|
data = { version: 1, installed: {} };
|
|
14083
14260
|
filePath;
|
|
14084
14261
|
constructor(filePath) {
|
|
14085
|
-
this.filePath = filePath ??
|
|
14262
|
+
this.filePath = filePath ?? path11.join(os4.homedir(), ".openacp", "agents.json");
|
|
14086
14263
|
}
|
|
14087
14264
|
load() {
|
|
14088
|
-
if (!
|
|
14265
|
+
if (!fs12.existsSync(this.filePath)) {
|
|
14089
14266
|
this.data = { version: 1, installed: {} };
|
|
14090
14267
|
return;
|
|
14091
14268
|
}
|
|
14092
14269
|
try {
|
|
14093
|
-
const raw = JSON.parse(
|
|
14270
|
+
const raw = JSON.parse(fs12.readFileSync(this.filePath, "utf-8"));
|
|
14094
14271
|
const result = AgentStoreSchema.safeParse(raw);
|
|
14095
14272
|
if (result.success) {
|
|
14096
14273
|
this.data = result.data;
|
|
@@ -14104,7 +14281,7 @@ var AgentStore = class {
|
|
|
14104
14281
|
}
|
|
14105
14282
|
}
|
|
14106
14283
|
exists() {
|
|
14107
|
-
return
|
|
14284
|
+
return fs12.existsSync(this.filePath);
|
|
14108
14285
|
}
|
|
14109
14286
|
getInstalled() {
|
|
14110
14287
|
return this.data.installed;
|
|
@@ -14124,10 +14301,10 @@ var AgentStore = class {
|
|
|
14124
14301
|
return key in this.data.installed;
|
|
14125
14302
|
}
|
|
14126
14303
|
save() {
|
|
14127
|
-
|
|
14304
|
+
fs12.mkdirSync(path11.dirname(this.filePath), { recursive: true });
|
|
14128
14305
|
const tmpPath = this.filePath + ".tmp";
|
|
14129
|
-
|
|
14130
|
-
|
|
14306
|
+
fs12.writeFileSync(tmpPath, JSON.stringify(this.data, null, 2), { mode: 384 });
|
|
14307
|
+
fs12.renameSync(tmpPath, this.filePath);
|
|
14131
14308
|
}
|
|
14132
14309
|
};
|
|
14133
14310
|
|
|
@@ -14145,7 +14322,7 @@ var AgentCatalog = class {
|
|
|
14145
14322
|
agentsDir;
|
|
14146
14323
|
constructor(store, cachePath, agentsDir) {
|
|
14147
14324
|
this.store = store ?? new AgentStore();
|
|
14148
|
-
this.cachePath = cachePath ??
|
|
14325
|
+
this.cachePath = cachePath ?? path13.join(os6.homedir(), ".openacp", "registry-cache.json");
|
|
14149
14326
|
this.agentsDir = agentsDir;
|
|
14150
14327
|
}
|
|
14151
14328
|
load() {
|
|
@@ -14166,8 +14343,8 @@ var AgentCatalog = class {
|
|
|
14166
14343
|
ttlHours: DEFAULT_TTL_HOURS,
|
|
14167
14344
|
data
|
|
14168
14345
|
};
|
|
14169
|
-
|
|
14170
|
-
|
|
14346
|
+
fs14.mkdirSync(path13.dirname(this.cachePath), { recursive: true });
|
|
14347
|
+
fs14.writeFileSync(this.cachePath, JSON.stringify(cache, null, 2), { mode: 384 });
|
|
14171
14348
|
log12.info({ count: this.registryAgents.length }, "Registry updated");
|
|
14172
14349
|
} catch (err) {
|
|
14173
14350
|
log12.warn({ err }, "Failed to fetch registry, using cached data");
|
|
@@ -14335,9 +14512,9 @@ var AgentCatalog = class {
|
|
|
14335
14512
|
}
|
|
14336
14513
|
}
|
|
14337
14514
|
isCacheStale() {
|
|
14338
|
-
if (!
|
|
14515
|
+
if (!fs14.existsSync(this.cachePath)) return true;
|
|
14339
14516
|
try {
|
|
14340
|
-
const raw = JSON.parse(
|
|
14517
|
+
const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
|
|
14341
14518
|
const fetchedAt = new Date(raw.fetchedAt).getTime();
|
|
14342
14519
|
const ttlMs = (raw.ttlHours ?? DEFAULT_TTL_HOURS) * 60 * 60 * 1e3;
|
|
14343
14520
|
return Date.now() - fetchedAt > ttlMs;
|
|
@@ -14346,9 +14523,9 @@ var AgentCatalog = class {
|
|
|
14346
14523
|
}
|
|
14347
14524
|
}
|
|
14348
14525
|
loadRegistryFromCacheOrSnapshot() {
|
|
14349
|
-
if (
|
|
14526
|
+
if (fs14.existsSync(this.cachePath)) {
|
|
14350
14527
|
try {
|
|
14351
|
-
const raw = JSON.parse(
|
|
14528
|
+
const raw = JSON.parse(fs14.readFileSync(this.cachePath, "utf-8"));
|
|
14352
14529
|
if (raw.data?.agents) {
|
|
14353
14530
|
this.registryAgents = raw.data.agents;
|
|
14354
14531
|
log12.debug({ count: this.registryAgents.length }, "Loaded registry from cache");
|
|
@@ -14360,13 +14537,13 @@ var AgentCatalog = class {
|
|
|
14360
14537
|
}
|
|
14361
14538
|
try {
|
|
14362
14539
|
const candidates = [
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
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")
|
|
14366
14543
|
];
|
|
14367
14544
|
for (const candidate of candidates) {
|
|
14368
|
-
if (
|
|
14369
|
-
const raw = JSON.parse(
|
|
14545
|
+
if (fs14.existsSync(candidate)) {
|
|
14546
|
+
const raw = JSON.parse(fs14.readFileSync(candidate, "utf-8"));
|
|
14370
14547
|
this.registryAgents = raw.agents ?? [];
|
|
14371
14548
|
log12.debug({ count: this.registryAgents.length }, "Loaded registry from bundled snapshot");
|
|
14372
14549
|
return;
|
|
@@ -14627,31 +14804,31 @@ var ErrorTracker = class {
|
|
|
14627
14804
|
};
|
|
14628
14805
|
|
|
14629
14806
|
// src/core/plugin/plugin-context.ts
|
|
14630
|
-
import
|
|
14807
|
+
import path15 from "path";
|
|
14631
14808
|
import os7 from "os";
|
|
14632
14809
|
|
|
14633
14810
|
// src/core/plugin/plugin-storage.ts
|
|
14634
|
-
import
|
|
14635
|
-
import
|
|
14811
|
+
import fs15 from "fs";
|
|
14812
|
+
import path14 from "path";
|
|
14636
14813
|
var PluginStorageImpl = class {
|
|
14637
14814
|
kvPath;
|
|
14638
14815
|
dataDir;
|
|
14639
14816
|
writeChain = Promise.resolve();
|
|
14640
14817
|
constructor(baseDir) {
|
|
14641
|
-
this.dataDir =
|
|
14642
|
-
this.kvPath =
|
|
14643
|
-
|
|
14818
|
+
this.dataDir = path14.join(baseDir, "data");
|
|
14819
|
+
this.kvPath = path14.join(baseDir, "kv.json");
|
|
14820
|
+
fs15.mkdirSync(baseDir, { recursive: true });
|
|
14644
14821
|
}
|
|
14645
14822
|
readKv() {
|
|
14646
14823
|
try {
|
|
14647
|
-
const raw =
|
|
14824
|
+
const raw = fs15.readFileSync(this.kvPath, "utf-8");
|
|
14648
14825
|
return JSON.parse(raw);
|
|
14649
14826
|
} catch {
|
|
14650
14827
|
return {};
|
|
14651
14828
|
}
|
|
14652
14829
|
}
|
|
14653
14830
|
writeKv(data) {
|
|
14654
|
-
|
|
14831
|
+
fs15.writeFileSync(this.kvPath, JSON.stringify(data), "utf-8");
|
|
14655
14832
|
}
|
|
14656
14833
|
async get(key) {
|
|
14657
14834
|
const data = this.readKv();
|
|
@@ -14677,7 +14854,7 @@ var PluginStorageImpl = class {
|
|
|
14677
14854
|
return Object.keys(this.readKv());
|
|
14678
14855
|
}
|
|
14679
14856
|
getDataDir() {
|
|
14680
|
-
|
|
14857
|
+
fs15.mkdirSync(this.dataDir, { recursive: true });
|
|
14681
14858
|
return this.dataDir;
|
|
14682
14859
|
}
|
|
14683
14860
|
};
|
|
@@ -14701,9 +14878,11 @@ function createPluginContext(opts) {
|
|
|
14701
14878
|
config,
|
|
14702
14879
|
core
|
|
14703
14880
|
} = opts;
|
|
14704
|
-
const instanceRoot = opts.instanceRoot ??
|
|
14881
|
+
const instanceRoot = opts.instanceRoot ?? path15.join(os7.homedir(), ".openacp");
|
|
14705
14882
|
const registeredListeners = [];
|
|
14706
14883
|
const registeredCommands = [];
|
|
14884
|
+
const registeredMenuItemIds = [];
|
|
14885
|
+
const registeredAssistantSectionIds = [];
|
|
14707
14886
|
const noopLog = {
|
|
14708
14887
|
trace() {
|
|
14709
14888
|
},
|
|
@@ -14798,7 +14977,9 @@ function createPluginContext(opts) {
|
|
|
14798
14977
|
requirePermission(permissions, "commands:register", "registerMenuItem()");
|
|
14799
14978
|
const menuRegistry = serviceRegistry.get("menu-registry");
|
|
14800
14979
|
if (!menuRegistry) return;
|
|
14801
|
-
|
|
14980
|
+
const qualifiedId = `${pluginName}:${item.id}`;
|
|
14981
|
+
menuRegistry.register({ ...item, id: qualifiedId });
|
|
14982
|
+
registeredMenuItemIds.push(qualifiedId);
|
|
14802
14983
|
},
|
|
14803
14984
|
unregisterMenuItem(id) {
|
|
14804
14985
|
requirePermission(permissions, "commands:register", "unregisterMenuItem()");
|
|
@@ -14810,7 +14991,9 @@ function createPluginContext(opts) {
|
|
|
14810
14991
|
requirePermission(permissions, "commands:register", "registerAssistantSection()");
|
|
14811
14992
|
const assistantRegistry = serviceRegistry.get("assistant-registry");
|
|
14812
14993
|
if (!assistantRegistry) return;
|
|
14813
|
-
|
|
14994
|
+
const qualifiedId = `${pluginName}:${section.id}`;
|
|
14995
|
+
assistantRegistry.register({ ...section, id: qualifiedId });
|
|
14996
|
+
registeredAssistantSectionIds.push(qualifiedId);
|
|
14814
14997
|
},
|
|
14815
14998
|
unregisterAssistantSection(id) {
|
|
14816
14999
|
requirePermission(permissions, "commands:register", "unregisterAssistantSection()");
|
|
@@ -14818,6 +15001,14 @@ function createPluginContext(opts) {
|
|
|
14818
15001
|
if (!assistantRegistry) return;
|
|
14819
15002
|
assistantRegistry.unregister(`${pluginName}:${id}`);
|
|
14820
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
|
+
},
|
|
14821
15012
|
get sessions() {
|
|
14822
15013
|
requirePermission(permissions, "kernel:access", "sessions");
|
|
14823
15014
|
return sessions;
|
|
@@ -14846,6 +15037,20 @@ function createPluginContext(opts) {
|
|
|
14846
15037
|
if (cmdRegistry && typeof cmdRegistry.unregisterByPlugin === "function") {
|
|
14847
15038
|
cmdRegistry.unregisterByPlugin(pluginName);
|
|
14848
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;
|
|
14849
15054
|
registeredCommands.length = 0;
|
|
14850
15055
|
}
|
|
14851
15056
|
};
|
|
@@ -14880,11 +15085,8 @@ function resolvePluginConfig(pluginName, configManager) {
|
|
|
14880
15085
|
"@openacp/file-service": "files",
|
|
14881
15086
|
"@openacp/api-server": "api",
|
|
14882
15087
|
"@openacp/telegram": "channels.telegram",
|
|
14883
|
-
"@openacp/discord": "channels.discord",
|
|
14884
|
-
"@openacp/adapter
|
|
14885
|
-
"@openacp/plugin-discord": "channels.discord",
|
|
14886
|
-
// alias for old name
|
|
14887
|
-
"@openacp/slack": "channels.slack"
|
|
15088
|
+
"@openacp/discord-adapter": "channels.discord",
|
|
15089
|
+
"@openacp/slack-adapter": "channels.slack"
|
|
14888
15090
|
};
|
|
14889
15091
|
const legacyKey = legacyMap[pluginName];
|
|
14890
15092
|
if (legacyKey) {
|
|
@@ -15369,8 +15571,10 @@ function createConfigSection(core) {
|
|
|
15369
15571
|
priority: 30,
|
|
15370
15572
|
buildContext: () => {
|
|
15371
15573
|
const config = core.configManager.get();
|
|
15574
|
+
const speechSvc = core.lifecycleManager?.serviceRegistry.get("speech");
|
|
15575
|
+
const sttActive = speechSvc ? speechSvc.isSTTAvailable() : false;
|
|
15372
15576
|
return `Workspace base: ${config.workspace.baseDir}
|
|
15373
|
-
STT: ${
|
|
15577
|
+
STT: ${sttActive ? "configured \u2705" : "Not configured"}`;
|
|
15374
15578
|
},
|
|
15375
15579
|
commands: [
|
|
15376
15580
|
{ command: "openacp config", description: "View config" },
|
|
@@ -15531,7 +15735,7 @@ var OpenACPCore = class {
|
|
|
15531
15735
|
);
|
|
15532
15736
|
this.agentCatalog.load();
|
|
15533
15737
|
this.agentManager = new AgentManager(this.agentCatalog);
|
|
15534
|
-
const storePath = ctx?.paths.sessions ??
|
|
15738
|
+
const storePath = ctx?.paths.sessions ?? path16.join(os8.homedir(), ".openacp", "sessions.json");
|
|
15535
15739
|
this.sessionStore = new JsonFileSessionStore(
|
|
15536
15740
|
storePath,
|
|
15537
15741
|
config.sessionStore.ttlDays
|
|
@@ -15555,7 +15759,7 @@ var OpenACPCore = class {
|
|
|
15555
15759
|
sessions: this.sessionManager,
|
|
15556
15760
|
config: this.configManager,
|
|
15557
15761
|
core: this,
|
|
15558
|
-
storagePath: ctx?.paths.pluginsData ??
|
|
15762
|
+
storagePath: ctx?.paths.pluginsData ?? path16.join(os8.homedir(), ".openacp", "plugins", "data"),
|
|
15559
15763
|
instanceRoot: ctx?.root,
|
|
15560
15764
|
log: createChildLogger({ module: "plugin" })
|
|
15561
15765
|
});
|
|
@@ -15621,7 +15825,7 @@ var OpenACPCore = class {
|
|
|
15621
15825
|
);
|
|
15622
15826
|
registerCoreMenuItems(this.menuRegistry);
|
|
15623
15827
|
if (ctx?.root) {
|
|
15624
|
-
this.assistantRegistry.setInstanceRoot(
|
|
15828
|
+
this.assistantRegistry.setInstanceRoot(path16.dirname(ctx.root));
|
|
15625
15829
|
}
|
|
15626
15830
|
this.assistantRegistry.register(createSessionsSection(this));
|
|
15627
15831
|
this.assistantRegistry.register(createAgentsSection(this));
|
|
@@ -15759,7 +15963,22 @@ User message:
|
|
|
15759
15963
|
${text3}`;
|
|
15760
15964
|
}
|
|
15761
15965
|
}
|
|
15762
|
-
|
|
15966
|
+
const sourceAdapterId = message.routing?.sourceAdapterId ?? message.channelId;
|
|
15967
|
+
if (sourceAdapterId && sourceAdapterId !== "sse" && sourceAdapterId !== "api") {
|
|
15968
|
+
const turnId = nanoid3(8);
|
|
15969
|
+
this.eventBus.emit("message:queued", {
|
|
15970
|
+
sessionId: session.id,
|
|
15971
|
+
turnId,
|
|
15972
|
+
text: text3,
|
|
15973
|
+
sourceAdapterId,
|
|
15974
|
+
attachments: message.attachments,
|
|
15975
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
15976
|
+
queueDepth: session.queueDepth
|
|
15977
|
+
});
|
|
15978
|
+
await session.enqueuePrompt(text3, message.attachments, message.routing, turnId);
|
|
15979
|
+
} else {
|
|
15980
|
+
await session.enqueuePrompt(text3, message.attachments, message.routing);
|
|
15981
|
+
}
|
|
15763
15982
|
}
|
|
15764
15983
|
// --- Unified Session Creation Pipeline ---
|
|
15765
15984
|
async createSession(params) {
|
|
@@ -15873,15 +16092,15 @@ ${text3}`;
|
|
|
15873
16092
|
message: `Agent '${agentName}' not found`
|
|
15874
16093
|
};
|
|
15875
16094
|
}
|
|
15876
|
-
const { existsSync:
|
|
15877
|
-
if (!
|
|
16095
|
+
const { existsSync: existsSync19 } = await import("fs");
|
|
16096
|
+
if (!existsSync19(cwd)) {
|
|
15878
16097
|
return {
|
|
15879
16098
|
ok: false,
|
|
15880
16099
|
error: "invalid_cwd",
|
|
15881
16100
|
message: `Directory does not exist: ${cwd}`
|
|
15882
16101
|
};
|
|
15883
16102
|
}
|
|
15884
|
-
const maxSessions =
|
|
16103
|
+
const maxSessions = 20;
|
|
15885
16104
|
if (this.sessionManager.listSessions().length >= maxSessions) {
|
|
15886
16105
|
return {
|
|
15887
16106
|
ok: false,
|
|
@@ -16138,7 +16357,9 @@ var CommandRegistry = class _CommandRegistry {
|
|
|
16138
16357
|
this.commands.delete(name);
|
|
16139
16358
|
if (cmd.scope) {
|
|
16140
16359
|
this.commands.delete(`${cmd.scope}:${cmd.name}`);
|
|
16141
|
-
this.commands.
|
|
16360
|
+
if (this.commands.get(cmd.name) === cmd) {
|
|
16361
|
+
this.commands.delete(cmd.name);
|
|
16362
|
+
}
|
|
16142
16363
|
}
|
|
16143
16364
|
}
|
|
16144
16365
|
/** Remove all commands registered by a given plugin. */
|
|
@@ -16218,19 +16439,19 @@ init_doctor();
|
|
|
16218
16439
|
init_config_registry();
|
|
16219
16440
|
|
|
16220
16441
|
// src/core/config/config-editor.ts
|
|
16221
|
-
import * as
|
|
16442
|
+
import * as path28 from "path";
|
|
16222
16443
|
import * as clack2 from "@clack/prompts";
|
|
16223
16444
|
|
|
16224
16445
|
// src/cli/autostart.ts
|
|
16225
16446
|
init_log();
|
|
16226
|
-
import { execFileSync as
|
|
16227
|
-
import * as
|
|
16228
|
-
import * as
|
|
16447
|
+
import { execFileSync as execFileSync6 } from "child_process";
|
|
16448
|
+
import * as fs26 from "fs";
|
|
16449
|
+
import * as path24 from "path";
|
|
16229
16450
|
import * as os11 from "os";
|
|
16230
16451
|
var log18 = createChildLogger({ module: "autostart" });
|
|
16231
16452
|
var LAUNCHD_LABEL = "com.openacp.daemon";
|
|
16232
|
-
var LAUNCHD_PLIST_PATH =
|
|
16233
|
-
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");
|
|
16234
16455
|
function isAutoStartSupported() {
|
|
16235
16456
|
return process.platform === "darwin" || process.platform === "linux";
|
|
16236
16457
|
}
|
|
@@ -16242,7 +16463,7 @@ function escapeSystemdValue(str) {
|
|
|
16242
16463
|
return `"${escaped}"`;
|
|
16243
16464
|
}
|
|
16244
16465
|
function generateLaunchdPlist(nodePath, cliPath, logDir2) {
|
|
16245
|
-
const logFile =
|
|
16466
|
+
const logFile = path24.join(logDir2, "openacp.log");
|
|
16246
16467
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
16247
16468
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
16248
16469
|
<plist version="1.0">
|
|
@@ -16287,25 +16508,25 @@ function installAutoStart(logDir2) {
|
|
|
16287
16508
|
return { success: false, error: "Auto-start not supported on this platform" };
|
|
16288
16509
|
}
|
|
16289
16510
|
const nodePath = process.execPath;
|
|
16290
|
-
const cliPath =
|
|
16291
|
-
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;
|
|
16292
16513
|
try {
|
|
16293
16514
|
if (process.platform === "darwin") {
|
|
16294
16515
|
const plist = generateLaunchdPlist(nodePath, cliPath, resolvedLogDir);
|
|
16295
|
-
const dir =
|
|
16296
|
-
|
|
16297
|
-
|
|
16298
|
-
|
|
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" });
|
|
16299
16520
|
log18.info("LaunchAgent installed");
|
|
16300
16521
|
return { success: true };
|
|
16301
16522
|
}
|
|
16302
16523
|
if (process.platform === "linux") {
|
|
16303
16524
|
const unit = generateSystemdUnit(nodePath, cliPath);
|
|
16304
|
-
const dir =
|
|
16305
|
-
|
|
16306
|
-
|
|
16307
|
-
|
|
16308
|
-
|
|
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" });
|
|
16309
16530
|
log18.info("systemd user service installed");
|
|
16310
16531
|
return { success: true };
|
|
16311
16532
|
}
|
|
@@ -16322,24 +16543,24 @@ function uninstallAutoStart() {
|
|
|
16322
16543
|
}
|
|
16323
16544
|
try {
|
|
16324
16545
|
if (process.platform === "darwin") {
|
|
16325
|
-
if (
|
|
16546
|
+
if (fs26.existsSync(LAUNCHD_PLIST_PATH)) {
|
|
16326
16547
|
try {
|
|
16327
|
-
|
|
16548
|
+
execFileSync6("launchctl", ["unload", LAUNCHD_PLIST_PATH], { stdio: "pipe" });
|
|
16328
16549
|
} catch {
|
|
16329
16550
|
}
|
|
16330
|
-
|
|
16551
|
+
fs26.unlinkSync(LAUNCHD_PLIST_PATH);
|
|
16331
16552
|
log18.info("LaunchAgent removed");
|
|
16332
16553
|
}
|
|
16333
16554
|
return { success: true };
|
|
16334
16555
|
}
|
|
16335
16556
|
if (process.platform === "linux") {
|
|
16336
|
-
if (
|
|
16557
|
+
if (fs26.existsSync(SYSTEMD_SERVICE_PATH)) {
|
|
16337
16558
|
try {
|
|
16338
|
-
|
|
16559
|
+
execFileSync6("systemctl", ["--user", "disable", "openacp"], { stdio: "pipe" });
|
|
16339
16560
|
} catch {
|
|
16340
16561
|
}
|
|
16341
|
-
|
|
16342
|
-
|
|
16562
|
+
fs26.unlinkSync(SYSTEMD_SERVICE_PATH);
|
|
16563
|
+
execFileSync6("systemctl", ["--user", "daemon-reload"], { stdio: "pipe" });
|
|
16343
16564
|
log18.info("systemd user service removed");
|
|
16344
16565
|
}
|
|
16345
16566
|
return { success: true };
|
|
@@ -16353,10 +16574,10 @@ function uninstallAutoStart() {
|
|
|
16353
16574
|
}
|
|
16354
16575
|
function isAutoStartInstalled() {
|
|
16355
16576
|
if (process.platform === "darwin") {
|
|
16356
|
-
return
|
|
16577
|
+
return fs26.existsSync(LAUNCHD_PLIST_PATH);
|
|
16357
16578
|
}
|
|
16358
16579
|
if (process.platform === "linux") {
|
|
16359
|
-
return
|
|
16580
|
+
return fs26.existsSync(SYSTEMD_SERVICE_PATH);
|
|
16360
16581
|
}
|
|
16361
16582
|
return false;
|
|
16362
16583
|
}
|
|
@@ -16407,42 +16628,19 @@ var header = (title) => `
|
|
|
16407
16628
|
${c.cyan}${c.bold}[${title}]${c.reset}
|
|
16408
16629
|
`;
|
|
16409
16630
|
async function editTelegram(config, updates, settingsManager) {
|
|
16410
|
-
const
|
|
16411
|
-
|
|
16412
|
-
|
|
16413
|
-
|
|
16414
|
-
if (settingsManager) {
|
|
16415
|
-
const ps = await settingsManager.loadSettings("@openacp/telegram");
|
|
16416
|
-
if (Object.keys(ps).length > 0) {
|
|
16417
|
-
currentToken = ps.botToken ?? currentToken;
|
|
16418
|
-
currentChatId = ps.chatId ?? currentChatId;
|
|
16419
|
-
currentEnabled = ps.enabled ?? currentEnabled;
|
|
16420
|
-
}
|
|
16421
|
-
}
|
|
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;
|
|
16422
16635
|
console.log(header("Telegram"));
|
|
16423
|
-
console.log(` Enabled : ${currentEnabled ? ok("yes") : dim("no")}`);
|
|
16424
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")}`);
|
|
16425
16638
|
console.log(` Bot Token : ${tokenDisplay}`);
|
|
16426
16639
|
console.log(` Chat ID : ${currentChatId || dim("(not set)")}`);
|
|
16427
16640
|
console.log("");
|
|
16428
|
-
const ensureTelegramUpdates = () => {
|
|
16429
|
-
if (!updates.channels) updates.channels = {};
|
|
16430
|
-
if (!updates.channels.telegram) {
|
|
16431
|
-
updates.channels.telegram = {};
|
|
16432
|
-
}
|
|
16433
|
-
return updates.channels.telegram;
|
|
16434
|
-
};
|
|
16435
16641
|
while (true) {
|
|
16436
|
-
const
|
|
16437
|
-
|
|
16438
|
-
const ps = await settingsManager.loadSettings("@openacp/telegram");
|
|
16439
|
-
if ("enabled" in ps) return ps.enabled;
|
|
16440
|
-
}
|
|
16441
|
-
const ch = updates.channels;
|
|
16442
|
-
const tgUp = ch?.telegram;
|
|
16443
|
-
if (tgUp && "enabled" in tgUp) return tgUp.enabled;
|
|
16444
|
-
return currentEnabled;
|
|
16445
|
-
})();
|
|
16642
|
+
const freshSettings = settingsManager ? await settingsManager.loadSettings("@openacp/telegram") : ps;
|
|
16643
|
+
const isEnabled = freshSettings.enabled ?? currentEnabled;
|
|
16446
16644
|
const choice = await select3({
|
|
16447
16645
|
message: "Telegram settings:",
|
|
16448
16646
|
choices: [
|
|
@@ -16456,75 +16654,32 @@ async function editTelegram(config, updates, settingsManager) {
|
|
|
16456
16654
|
if (choice === "toggle") {
|
|
16457
16655
|
if (settingsManager) {
|
|
16458
16656
|
await settingsManager.updatePluginSettings("@openacp/telegram", { enabled: !isEnabled });
|
|
16459
|
-
|
|
16460
|
-
const tgUp = ensureTelegramUpdates();
|
|
16461
|
-
tgUp.enabled = !isEnabled;
|
|
16657
|
+
console.log(!isEnabled ? ok("Telegram enabled") : ok("Telegram disabled"));
|
|
16462
16658
|
}
|
|
16463
|
-
console.log(!isEnabled ? ok("Telegram enabled") : ok("Telegram disabled"));
|
|
16464
16659
|
}
|
|
16465
16660
|
if (choice === "token") {
|
|
16466
16661
|
const token = await input({
|
|
16467
16662
|
message: "New bot token:",
|
|
16468
|
-
default: currentToken,
|
|
16469
16663
|
validate: (val) => val.trim().length > 0 || "Token cannot be empty"
|
|
16470
16664
|
});
|
|
16471
|
-
try {
|
|
16472
|
-
const { validateBotToken: validateBotToken2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
16473
|
-
const result = await validateBotToken2(token.trim());
|
|
16474
|
-
if (result.ok) {
|
|
16475
|
-
console.log(ok(`Connected to @${result.botUsername}`));
|
|
16476
|
-
} else {
|
|
16477
|
-
console.log(warn(`Validation failed: ${result.error} \u2014 saving anyway`));
|
|
16478
|
-
}
|
|
16479
|
-
} catch {
|
|
16480
|
-
console.log(warn("Telegram validator not available \u2014 skipping validation"));
|
|
16481
|
-
}
|
|
16482
16665
|
if (settingsManager) {
|
|
16483
|
-
await settingsManager.updatePluginSettings("@openacp/telegram", { botToken: token.trim()
|
|
16484
|
-
|
|
16485
|
-
const tgUp = ensureTelegramUpdates();
|
|
16486
|
-
tgUp.botToken = token.trim();
|
|
16487
|
-
tgUp.enabled = true;
|
|
16666
|
+
await settingsManager.updatePluginSettings("@openacp/telegram", { botToken: token.trim() });
|
|
16667
|
+
console.log(ok("Bot token updated"));
|
|
16488
16668
|
}
|
|
16489
16669
|
}
|
|
16490
16670
|
if (choice === "chatid") {
|
|
16491
|
-
const
|
|
16492
|
-
message: "New chat ID
|
|
16493
|
-
|
|
16494
|
-
validate: (val) => {
|
|
16495
|
-
const n = Number(val.trim());
|
|
16496
|
-
if (isNaN(n) || !Number.isInteger(n)) return "Chat ID must be an integer";
|
|
16497
|
-
return true;
|
|
16498
|
-
}
|
|
16671
|
+
const chatId = await input({
|
|
16672
|
+
message: "New chat ID:",
|
|
16673
|
+
validate: (val) => !isNaN(Number(val.trim())) || "Must be a number"
|
|
16499
16674
|
});
|
|
16500
|
-
const chatId = Number(chatIdStr.trim());
|
|
16501
|
-
const tokenForValidation = (() => {
|
|
16502
|
-
const ch = updates.channels;
|
|
16503
|
-
const tgUp = ch?.telegram;
|
|
16504
|
-
if (typeof tgUp?.botToken === "string") return tgUp.botToken;
|
|
16505
|
-
return currentToken;
|
|
16506
|
-
})();
|
|
16507
|
-
try {
|
|
16508
|
-
const { validateChatId: validateChatId2 } = await Promise.resolve().then(() => (init_validators(), validators_exports));
|
|
16509
|
-
const result = await validateChatId2(tokenForValidation, chatId);
|
|
16510
|
-
if (result.ok) {
|
|
16511
|
-
console.log(ok(`Group: ${result.title}${result.isForum ? "" : warn(" (topics not enabled)")}`));
|
|
16512
|
-
} else {
|
|
16513
|
-
console.log(warn(`Validation failed: ${result.error} \u2014 saving anyway`));
|
|
16514
|
-
}
|
|
16515
|
-
} catch {
|
|
16516
|
-
console.log(warn("Telegram validator not available \u2014 skipping validation"));
|
|
16517
|
-
}
|
|
16518
16675
|
if (settingsManager) {
|
|
16519
|
-
await settingsManager.updatePluginSettings("@openacp/telegram", { chatId });
|
|
16520
|
-
|
|
16521
|
-
const tgUp = ensureTelegramUpdates();
|
|
16522
|
-
tgUp.chatId = chatId;
|
|
16676
|
+
await settingsManager.updatePluginSettings("@openacp/telegram", { chatId: Number(chatId.trim()) });
|
|
16677
|
+
console.log(ok(`Chat ID set to ${chatId.trim()}`));
|
|
16523
16678
|
}
|
|
16524
16679
|
}
|
|
16525
16680
|
}
|
|
16526
16681
|
}
|
|
16527
|
-
var DISCORD_PACKAGE = "@openacp/adapter
|
|
16682
|
+
var DISCORD_PACKAGE = "@openacp/discord-adapter";
|
|
16528
16683
|
async function ensureDiscordPlugin() {
|
|
16529
16684
|
try {
|
|
16530
16685
|
return await import(DISCORD_PACKAGE);
|
|
@@ -16561,7 +16716,7 @@ async function editDiscord(_config, _updates) {
|
|
|
16561
16716
|
const { createInstallContext: createInstallContext2 } = await Promise.resolve().then(() => (init_install_context(), install_context_exports));
|
|
16562
16717
|
const { getGlobalRoot: getGlobalRoot2 } = await Promise.resolve().then(() => (init_instance_context(), instance_context_exports));
|
|
16563
16718
|
const root = getGlobalRoot2();
|
|
16564
|
-
const basePath =
|
|
16719
|
+
const basePath = path28.join(root, "plugins", "data");
|
|
16565
16720
|
const settingsManager = new SettingsManager2(basePath);
|
|
16566
16721
|
const ctx = createInstallContext2({
|
|
16567
16722
|
pluginName: plugin.name,
|
|
@@ -16575,12 +16730,12 @@ async function editDiscord(_config, _updates) {
|
|
|
16575
16730
|
}
|
|
16576
16731
|
}
|
|
16577
16732
|
async function editChannels(config, updates, settingsManager) {
|
|
16578
|
-
let tgConfigured =
|
|
16579
|
-
let dcConfigured =
|
|
16733
|
+
let tgConfigured = false;
|
|
16734
|
+
let dcConfigured = false;
|
|
16580
16735
|
if (settingsManager) {
|
|
16581
16736
|
const tgPs = await settingsManager.loadSettings("@openacp/telegram");
|
|
16582
16737
|
if (tgPs.botToken && tgPs.chatId) tgConfigured = true;
|
|
16583
|
-
const dcPs = await settingsManager.loadSettings("@openacp/adapter
|
|
16738
|
+
const dcPs = await settingsManager.loadSettings("@openacp/discord-adapter");
|
|
16584
16739
|
if (dcPs.guildId || dcPs.token) dcConfigured = true;
|
|
16585
16740
|
}
|
|
16586
16741
|
console.log(header("Channels"));
|
|
@@ -16602,7 +16757,7 @@ async function editChannels(config, updates, settingsManager) {
|
|
|
16602
16757
|
}
|
|
16603
16758
|
}
|
|
16604
16759
|
async function editAgent(config, updates) {
|
|
16605
|
-
const agentNames =
|
|
16760
|
+
const agentNames = [];
|
|
16606
16761
|
const currentDefault = config.defaultAgent;
|
|
16607
16762
|
console.log(header("Agent"));
|
|
16608
16763
|
console.log(` Default agent : ${c.bold}${currentDefault}${c.reset}`);
|
|
@@ -16645,17 +16800,12 @@ async function editWorkspace(config, updates) {
|
|
|
16645
16800
|
console.log(ok(`Workspace set to ${newDir.trim()}`));
|
|
16646
16801
|
}
|
|
16647
16802
|
async function editSecurity(config, updates, settingsManager) {
|
|
16648
|
-
|
|
16649
|
-
|
|
16650
|
-
|
|
16651
|
-
|
|
16652
|
-
|
|
16653
|
-
|
|
16654
|
-
maxConcurrentSessions: ps.maxConcurrentSessions ?? sec.maxConcurrentSessions,
|
|
16655
|
-
sessionTimeoutMinutes: ps.sessionTimeoutMinutes ?? sec.sessionTimeoutMinutes
|
|
16656
|
-
};
|
|
16657
|
-
}
|
|
16658
|
-
}
|
|
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
|
+
};
|
|
16659
16809
|
console.log(header("Security"));
|
|
16660
16810
|
console.log(` Allowed user IDs : ${sec.allowedUserIds?.length ? sec.allowedUserIds.join(", ") : dim("(all users allowed)")}`);
|
|
16661
16811
|
console.log(` Max concurrent sessions : ${sec.maxConcurrentSessions}`);
|
|
@@ -16683,9 +16833,6 @@ async function editSecurity(config, updates, settingsManager) {
|
|
|
16683
16833
|
});
|
|
16684
16834
|
if (settingsManager) {
|
|
16685
16835
|
await settingsManager.updatePluginSettings("@openacp/security", { maxConcurrentSessions: Number(val.trim()) });
|
|
16686
|
-
} else {
|
|
16687
|
-
if (!updates.security) updates.security = {};
|
|
16688
|
-
updates.security.maxConcurrentSessions = Number(val.trim());
|
|
16689
16836
|
}
|
|
16690
16837
|
console.log(ok(`Max concurrent sessions set to ${val.trim()}`));
|
|
16691
16838
|
}
|
|
@@ -16701,9 +16848,6 @@ async function editSecurity(config, updates, settingsManager) {
|
|
|
16701
16848
|
});
|
|
16702
16849
|
if (settingsManager) {
|
|
16703
16850
|
await settingsManager.updatePluginSettings("@openacp/security", { sessionTimeoutMinutes: Number(val.trim()) });
|
|
16704
|
-
} else {
|
|
16705
|
-
if (!updates.security) updates.security = {};
|
|
16706
|
-
updates.security.sessionTimeoutMinutes = Number(val.trim());
|
|
16707
16851
|
}
|
|
16708
16852
|
console.log(ok(`Session timeout set to ${val.trim()} minutes`));
|
|
16709
16853
|
}
|
|
@@ -16830,20 +16974,16 @@ async function editRunMode(config, updates) {
|
|
|
16830
16974
|
}
|
|
16831
16975
|
}
|
|
16832
16976
|
async function editApi(config, updates, settingsManager) {
|
|
16833
|
-
|
|
16834
|
-
|
|
16835
|
-
|
|
16836
|
-
if (Object.keys(ps).length > 0) {
|
|
16837
|
-
api = { port: ps.port ?? api.port, host: ps.host ?? api.host };
|
|
16838
|
-
}
|
|
16839
|
-
}
|
|
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";
|
|
16840
16980
|
console.log(header("API"));
|
|
16841
|
-
console.log(` Port : ${
|
|
16842
|
-
console.log(` Host : ${
|
|
16981
|
+
console.log(` Port : ${currentPort}`);
|
|
16982
|
+
console.log(` Host : ${currentHost} ${dim("(localhost only)")}`);
|
|
16843
16983
|
console.log("");
|
|
16844
16984
|
const newPort = await input({
|
|
16845
16985
|
message: "API port:",
|
|
16846
|
-
default: String(
|
|
16986
|
+
default: String(currentPort),
|
|
16847
16987
|
validate: (v) => {
|
|
16848
16988
|
const n = Number(v.trim());
|
|
16849
16989
|
if (!Number.isInteger(n) || n < 1 || n > 65535) return "Must be a valid port (1-65535)";
|
|
@@ -16852,24 +16992,24 @@ async function editApi(config, updates, settingsManager) {
|
|
|
16852
16992
|
});
|
|
16853
16993
|
if (settingsManager) {
|
|
16854
16994
|
await settingsManager.updatePluginSettings("@openacp/api-server", { port: Number(newPort.trim()) });
|
|
16855
|
-
} else {
|
|
16856
|
-
updates.api = { port: Number(newPort.trim()) };
|
|
16857
16995
|
}
|
|
16858
16996
|
console.log(ok(`API port set to ${newPort.trim()}`));
|
|
16859
16997
|
}
|
|
16860
16998
|
async function editTunnel(config, updates, settingsManager) {
|
|
16861
|
-
|
|
16862
|
-
|
|
16863
|
-
|
|
16864
|
-
|
|
16865
|
-
|
|
16866
|
-
}
|
|
16867
|
-
|
|
16868
|
-
|
|
16869
|
-
|
|
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;
|
|
16870
17010
|
console.log(header("Tunnel"));
|
|
16871
17011
|
console.log(` Enabled : ${getVal("enabled", false) ? ok("yes") : dim("no")}`);
|
|
16872
|
-
console.log(` Provider : ${
|
|
17012
|
+
console.log(` Provider : ${getVal("provider", "openacp")}`);
|
|
16873
17013
|
console.log(` Port : ${getVal("port", 3100)}`);
|
|
16874
17014
|
const authEnabled = getVal("auth", { enabled: false }).enabled;
|
|
16875
17015
|
console.log(` Auth : ${authEnabled ? ok("enabled") : dim("disabled")}`);
|
|
@@ -16887,8 +17027,6 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16887
17027
|
]
|
|
16888
17028
|
});
|
|
16889
17029
|
if (choice === "back") break;
|
|
16890
|
-
if (!updates.tunnel) updates.tunnel = { ...tunnel };
|
|
16891
|
-
const tun = updates.tunnel;
|
|
16892
17030
|
if (choice === "toggle") {
|
|
16893
17031
|
const current = getVal("enabled", false);
|
|
16894
17032
|
if (settingsManager) {
|
|
@@ -16901,7 +17039,8 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16901
17039
|
const provider = await select3({
|
|
16902
17040
|
message: "Select tunnel provider:",
|
|
16903
17041
|
choices: [
|
|
16904
|
-
{ name: "
|
|
17042
|
+
{ name: "OpenACP (managed)", value: "openacp" },
|
|
17043
|
+
{ name: "Cloudflare", value: "cloudflare" },
|
|
16905
17044
|
{ name: "ngrok", value: "ngrok" },
|
|
16906
17045
|
{ name: "bore", value: "bore" },
|
|
16907
17046
|
{ name: "Tailscale Funnel", value: "tailscale" }
|
|
@@ -16931,7 +17070,7 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16931
17070
|
console.log(ok(`Tunnel port set to ${val.trim()}`));
|
|
16932
17071
|
}
|
|
16933
17072
|
if (choice === "options") {
|
|
16934
|
-
const provider = getVal("provider", "
|
|
17073
|
+
const provider = getVal("provider", "openacp");
|
|
16935
17074
|
const currentOptions = getVal("options", {});
|
|
16936
17075
|
await editProviderOptions(provider, currentOptions, tun);
|
|
16937
17076
|
if (settingsManager) {
|
|
@@ -16941,20 +17080,21 @@ async function editTunnel(config, updates, settingsManager) {
|
|
|
16941
17080
|
if (choice === "auth") {
|
|
16942
17081
|
const currentAuth = getVal("auth", { enabled: false });
|
|
16943
17082
|
if (currentAuth.enabled) {
|
|
16944
|
-
tun.auth = { enabled: false };
|
|
16945
17083
|
if (settingsManager) {
|
|
16946
17084
|
await settingsManager.updatePluginSettings("@openacp/tunnel", { auth: { enabled: false } });
|
|
16947
17085
|
}
|
|
17086
|
+
tun.auth = { enabled: false };
|
|
16948
17087
|
console.log(ok("Tunnel auth disabled"));
|
|
16949
17088
|
} else {
|
|
16950
17089
|
const token = await input({
|
|
16951
17090
|
message: "Auth token (leave empty to auto-generate):",
|
|
16952
17091
|
default: ""
|
|
16953
17092
|
});
|
|
16954
|
-
|
|
17093
|
+
const newAuth = token.trim() ? { enabled: true, token: token.trim() } : { enabled: true };
|
|
16955
17094
|
if (settingsManager) {
|
|
16956
|
-
await settingsManager.updatePluginSettings("@openacp/tunnel", { auth:
|
|
17095
|
+
await settingsManager.updatePluginSettings("@openacp/tunnel", { auth: newAuth });
|
|
16957
17096
|
}
|
|
17097
|
+
tun.auth = newAuth;
|
|
16958
17098
|
console.log(ok("Tunnel auth enabled"));
|
|
16959
17099
|
}
|
|
16960
17100
|
}
|
|
@@ -17082,17 +17222,17 @@ ${c.cyan}${c.bold}OpenACP Config Editor${c.reset}`);
|
|
|
17082
17222
|
async function sendConfigViaApi(port, updates) {
|
|
17083
17223
|
const { apiCall: call } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
17084
17224
|
const paths = flattenToPaths(updates);
|
|
17085
|
-
for (const { path:
|
|
17225
|
+
for (const { path: path34, value } of paths) {
|
|
17086
17226
|
const res = await call(port, "/api/config", {
|
|
17087
17227
|
method: "PATCH",
|
|
17088
17228
|
headers: { "Content-Type": "application/json" },
|
|
17089
|
-
body: JSON.stringify({ path:
|
|
17229
|
+
body: JSON.stringify({ path: path34, value })
|
|
17090
17230
|
});
|
|
17091
17231
|
const data = await res.json();
|
|
17092
17232
|
if (!res.ok) {
|
|
17093
|
-
console.log(warn(`Failed to update ${
|
|
17233
|
+
console.log(warn(`Failed to update ${path34}: ${data.error}`));
|
|
17094
17234
|
} else if (data.needsRestart) {
|
|
17095
|
-
console.log(warn(`${
|
|
17235
|
+
console.log(warn(`${path34} updated \u2014 restart required`));
|
|
17096
17236
|
}
|
|
17097
17237
|
}
|
|
17098
17238
|
}
|
|
@@ -17112,30 +17252,30 @@ function flattenToPaths(obj, prefix = "") {
|
|
|
17112
17252
|
// src/cli/daemon.ts
|
|
17113
17253
|
init_config();
|
|
17114
17254
|
import { spawn as spawn3 } from "child_process";
|
|
17115
|
-
import * as
|
|
17116
|
-
import * as
|
|
17255
|
+
import * as fs29 from "fs";
|
|
17256
|
+
import * as path29 from "path";
|
|
17117
17257
|
import * as os14 from "os";
|
|
17118
|
-
var DEFAULT_ROOT2 =
|
|
17258
|
+
var DEFAULT_ROOT2 = path29.join(os14.homedir(), ".openacp");
|
|
17119
17259
|
function getPidPath(root) {
|
|
17120
17260
|
const base = root ?? DEFAULT_ROOT2;
|
|
17121
|
-
return
|
|
17261
|
+
return path29.join(base, "openacp.pid");
|
|
17122
17262
|
}
|
|
17123
17263
|
function getLogDir(root) {
|
|
17124
17264
|
const base = root ?? DEFAULT_ROOT2;
|
|
17125
|
-
return
|
|
17265
|
+
return path29.join(base, "logs");
|
|
17126
17266
|
}
|
|
17127
17267
|
function getRunningMarker(root) {
|
|
17128
17268
|
const base = root ?? DEFAULT_ROOT2;
|
|
17129
|
-
return
|
|
17269
|
+
return path29.join(base, "running");
|
|
17130
17270
|
}
|
|
17131
17271
|
function writePidFile(pidPath, pid) {
|
|
17132
|
-
const dir =
|
|
17133
|
-
|
|
17134
|
-
|
|
17272
|
+
const dir = path29.dirname(pidPath);
|
|
17273
|
+
fs29.mkdirSync(dir, { recursive: true });
|
|
17274
|
+
fs29.writeFileSync(pidPath, String(pid));
|
|
17135
17275
|
}
|
|
17136
17276
|
function readPidFile(pidPath) {
|
|
17137
17277
|
try {
|
|
17138
|
-
const content =
|
|
17278
|
+
const content = fs29.readFileSync(pidPath, "utf-8").trim();
|
|
17139
17279
|
const pid = parseInt(content, 10);
|
|
17140
17280
|
return isNaN(pid) ? null : pid;
|
|
17141
17281
|
} catch {
|
|
@@ -17144,7 +17284,7 @@ function readPidFile(pidPath) {
|
|
|
17144
17284
|
}
|
|
17145
17285
|
function removePidFile(pidPath) {
|
|
17146
17286
|
try {
|
|
17147
|
-
|
|
17287
|
+
fs29.unlinkSync(pidPath);
|
|
17148
17288
|
} catch {
|
|
17149
17289
|
}
|
|
17150
17290
|
}
|
|
@@ -17177,12 +17317,12 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
|
|
|
17177
17317
|
return { error: `Already running (PID ${pid})` };
|
|
17178
17318
|
}
|
|
17179
17319
|
const resolvedLogDir = logDir2 ? expandHome3(logDir2) : getLogDir(instanceRoot);
|
|
17180
|
-
|
|
17181
|
-
const logFile =
|
|
17182
|
-
const cliPath =
|
|
17320
|
+
fs29.mkdirSync(resolvedLogDir, { recursive: true });
|
|
17321
|
+
const logFile = path29.join(resolvedLogDir, "openacp.log");
|
|
17322
|
+
const cliPath = path29.resolve(process.argv[1]);
|
|
17183
17323
|
const nodePath = process.execPath;
|
|
17184
|
-
const out =
|
|
17185
|
-
const err =
|
|
17324
|
+
const out = fs29.openSync(logFile, "a");
|
|
17325
|
+
const err = fs29.openSync(logFile, "a");
|
|
17186
17326
|
const child = spawn3(nodePath, [cliPath, "--daemon-child"], {
|
|
17187
17327
|
detached: true,
|
|
17188
17328
|
stdio: ["ignore", out, err],
|
|
@@ -17191,8 +17331,8 @@ function startDaemon(pidPath = getPidPath(), logDir2, instanceRoot) {
|
|
|
17191
17331
|
...instanceRoot ? { OPENACP_INSTANCE_ROOT: instanceRoot } : {}
|
|
17192
17332
|
}
|
|
17193
17333
|
});
|
|
17194
|
-
|
|
17195
|
-
|
|
17334
|
+
fs29.closeSync(out);
|
|
17335
|
+
fs29.closeSync(err);
|
|
17196
17336
|
if (!child.pid) {
|
|
17197
17337
|
return { error: "Failed to spawn daemon process" };
|
|
17198
17338
|
}
|
|
@@ -17263,12 +17403,12 @@ async function stopDaemon(pidPath = getPidPath(), instanceRoot) {
|
|
|
17263
17403
|
}
|
|
17264
17404
|
function markRunning(root) {
|
|
17265
17405
|
const marker = getRunningMarker(root);
|
|
17266
|
-
|
|
17267
|
-
|
|
17406
|
+
fs29.mkdirSync(path29.dirname(marker), { recursive: true });
|
|
17407
|
+
fs29.writeFileSync(marker, "");
|
|
17268
17408
|
}
|
|
17269
17409
|
function clearRunning(root) {
|
|
17270
17410
|
try {
|
|
17271
|
-
|
|
17411
|
+
fs29.unlinkSync(getRunningMarker(root));
|
|
17272
17412
|
} catch {
|
|
17273
17413
|
}
|
|
17274
17414
|
}
|
|
@@ -17493,6 +17633,9 @@ var Draft = class {
|
|
|
17493
17633
|
} finally {
|
|
17494
17634
|
this.firstFlushPending = false;
|
|
17495
17635
|
}
|
|
17636
|
+
if (this.buffer !== snapshot) {
|
|
17637
|
+
return this.flush();
|
|
17638
|
+
}
|
|
17496
17639
|
}
|
|
17497
17640
|
};
|
|
17498
17641
|
var DraftManager = class {
|
|
@@ -17770,8 +17913,8 @@ Configure via \`security.sessionTimeoutMinutes\` in config.
|
|
|
17770
17913
|
3. Copy and run it in your terminal \u2014 the session continues there with full conversation history
|
|
17771
17914
|
|
|
17772
17915
|
### Terminal \u2192 Chat
|
|
17773
|
-
1. First time: run \`openacp integrate
|
|
17774
|
-
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
|
|
17775
17918
|
3. The session appears as a new topic/thread and you can continue chatting there
|
|
17776
17919
|
|
|
17777
17920
|
### How it works
|