@openacp/cli 0.3.2 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +1 -1
  2. package/dist/{autostart-N4HIL6C3.js → autostart-DZ3MHHMM.js} +3 -3
  3. package/dist/{chunk-LVSQQRCF.js → chunk-2SY7Y2VB.js} +3 -3
  4. package/dist/{chunk-7W5SOJPD.js → chunk-3QACY5E3.js} +2 -2
  5. package/dist/{chunk-CA6FXPLH.js → chunk-BLVZFCKN.js} +4 -4
  6. package/dist/chunk-KSIQZC3J.js +98 -0
  7. package/dist/chunk-KSIQZC3J.js.map +1 -0
  8. package/dist/{chunk-JOSJGZGF.js → chunk-LYKCQTH5.js} +1 -9
  9. package/dist/{chunk-JOSJGZGF.js.map → chunk-LYKCQTH5.js.map} +1 -1
  10. package/dist/{chunk-5E6ZXCNN.js → chunk-MRKYJ422.js} +2 -2
  11. package/dist/{chunk-RBDPCHGD.js → chunk-V3BA2MJ6.js} +2 -2
  12. package/dist/{chunk-YXMRR2E3.js → chunk-WF5XDN4D.js} +65 -40
  13. package/dist/chunk-WF5XDN4D.js.map +1 -0
  14. package/dist/{chunk-NS2L445T.js → chunk-WHKLPZGK.js} +4 -4
  15. package/dist/{chunk-66PHSLNS.js → chunk-WXPN5UOT.js} +599 -323
  16. package/dist/chunk-WXPN5UOT.js.map +1 -0
  17. package/dist/cli.js +258 -316
  18. package/dist/cli.js.map +1 -1
  19. package/dist/{config-2CBRLF3R.js → config-J5YQOMDU.js} +3 -3
  20. package/dist/config-editor-IXL4BFG3.js +11 -0
  21. package/dist/{daemon-UXC7PB4P.js → daemon-SLGQGRKO.js} +4 -4
  22. package/dist/index.d.ts +184 -71
  23. package/dist/index.js +18 -10
  24. package/dist/install-cloudflared-ILUXKLAC.js +8 -0
  25. package/dist/{main-P3OUOY7X.js → main-5QGMP7VG.js} +53 -17
  26. package/dist/main-5QGMP7VG.js.map +1 -0
  27. package/dist/{setup-UKWBLJIT.js → setup-JQZBPXWS.js} +4 -4
  28. package/dist/{tunnel-service-4GISQZNP.js → tunnel-service-DASSH7OA.js} +3 -3
  29. package/dist/version-VC5CPXBX.js +15 -0
  30. package/dist/version-VC5CPXBX.js.map +1 -0
  31. package/package.json +1 -1
  32. package/dist/chunk-66PHSLNS.js.map +0 -1
  33. package/dist/chunk-YXMRR2E3.js.map +0 -1
  34. package/dist/config-editor-UN56HQCW.js +0 -11
  35. package/dist/install-cloudflared-LMM7MFQX.js +0 -8
  36. package/dist/main-P3OUOY7X.js.map +0 -1
  37. /package/dist/{autostart-N4HIL6C3.js.map → autostart-DZ3MHHMM.js.map} +0 -0
  38. /package/dist/{chunk-LVSQQRCF.js.map → chunk-2SY7Y2VB.js.map} +0 -0
  39. /package/dist/{chunk-7W5SOJPD.js.map → chunk-3QACY5E3.js.map} +0 -0
  40. /package/dist/{chunk-CA6FXPLH.js.map → chunk-BLVZFCKN.js.map} +0 -0
  41. /package/dist/{chunk-5E6ZXCNN.js.map → chunk-MRKYJ422.js.map} +0 -0
  42. /package/dist/{chunk-RBDPCHGD.js.map → chunk-V3BA2MJ6.js.map} +0 -0
  43. /package/dist/{chunk-NS2L445T.js.map → chunk-WHKLPZGK.js.map} +0 -0
  44. /package/dist/{config-2CBRLF3R.js.map → config-J5YQOMDU.js.map} +0 -0
  45. /package/dist/{config-editor-UN56HQCW.js.map → config-editor-IXL4BFG3.js.map} +0 -0
  46. /package/dist/{daemon-UXC7PB4P.js.map → daemon-SLGQGRKO.js.map} +0 -0
  47. /package/dist/{install-cloudflared-LMM7MFQX.js.map → install-cloudflared-ILUXKLAC.js.map} +0 -0
  48. /package/dist/{setup-UKWBLJIT.js.map → setup-JQZBPXWS.js.map} +0 -0
  49. /package/dist/{tunnel-service-4GISQZNP.js.map → tunnel-service-DASSH7OA.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createChildLogger,
3
3
  createSessionLogger
4
- } from "./chunk-JOSJGZGF.js";
4
+ } from "./chunk-LYKCQTH5.js";
5
5
 
6
6
  // src/core/streams.ts
7
7
  function nodeToWebWritable(nodeStream) {
@@ -504,10 +504,174 @@ var AgentManager = class {
504
504
  }
505
505
  };
506
506
 
507
+ // src/core/typed-emitter.ts
508
+ var TypedEmitter = class {
509
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
510
+ listeners = /* @__PURE__ */ new Map();
511
+ paused = false;
512
+ buffer = [];
513
+ on(event, listener) {
514
+ let set = this.listeners.get(event);
515
+ if (!set) {
516
+ set = /* @__PURE__ */ new Set();
517
+ this.listeners.set(event, set);
518
+ }
519
+ set.add(listener);
520
+ return this;
521
+ }
522
+ off(event, listener) {
523
+ this.listeners.get(event)?.delete(listener);
524
+ return this;
525
+ }
526
+ emit(event, ...args) {
527
+ if (this.paused) {
528
+ if (this.passthroughFn?.(event, args)) {
529
+ this.deliver(event, args);
530
+ } else {
531
+ this.buffer.push({ event, args });
532
+ }
533
+ return;
534
+ }
535
+ this.deliver(event, args);
536
+ }
537
+ /**
538
+ * Pause event delivery. Events emitted while paused are buffered.
539
+ * Optionally pass a filter to allow specific events through even while paused.
540
+ */
541
+ pause(passthrough) {
542
+ this.paused = true;
543
+ this.passthroughFn = passthrough;
544
+ }
545
+ passthroughFn;
546
+ /** Resume event delivery and replay buffered events in order. */
547
+ resume() {
548
+ this.paused = false;
549
+ this.passthroughFn = void 0;
550
+ const buffered = this.buffer.splice(0);
551
+ for (const { event, args } of buffered) {
552
+ this.deliver(event, args);
553
+ }
554
+ }
555
+ /** Discard all buffered events without delivering them. */
556
+ clearBuffer() {
557
+ this.buffer.length = 0;
558
+ }
559
+ get isPaused() {
560
+ return this.paused;
561
+ }
562
+ get bufferSize() {
563
+ return this.buffer.length;
564
+ }
565
+ removeAllListeners(event) {
566
+ if (event) {
567
+ this.listeners.delete(event);
568
+ } else {
569
+ this.listeners.clear();
570
+ }
571
+ }
572
+ deliver(event, args) {
573
+ const set = this.listeners.get(event);
574
+ if (!set) return;
575
+ for (const listener of set) {
576
+ listener(...args);
577
+ }
578
+ }
579
+ };
580
+
581
+ // src/core/prompt-queue.ts
582
+ var PromptQueue = class {
583
+ constructor(processor, onError) {
584
+ this.processor = processor;
585
+ this.onError = onError;
586
+ }
587
+ queue = [];
588
+ processing = false;
589
+ async enqueue(text) {
590
+ if (this.processing) {
591
+ return new Promise((resolve) => {
592
+ this.queue.push({ text, resolve });
593
+ });
594
+ }
595
+ await this.process(text);
596
+ }
597
+ async process(text) {
598
+ this.processing = true;
599
+ try {
600
+ await this.processor(text);
601
+ } catch (err) {
602
+ this.onError?.(err);
603
+ } finally {
604
+ this.processing = false;
605
+ this.drainNext();
606
+ }
607
+ }
608
+ drainNext() {
609
+ const next = this.queue.shift();
610
+ if (next) {
611
+ this.process(next.text).then(next.resolve);
612
+ }
613
+ }
614
+ clear() {
615
+ for (const item of this.queue) {
616
+ item.resolve();
617
+ }
618
+ this.queue = [];
619
+ }
620
+ get pending() {
621
+ return this.queue.length;
622
+ }
623
+ get isProcessing() {
624
+ return this.processing;
625
+ }
626
+ };
627
+
628
+ // src/core/permission-gate.ts
629
+ var PermissionGate = class {
630
+ request;
631
+ resolveFn;
632
+ rejectFn;
633
+ settled = false;
634
+ setPending(request) {
635
+ this.request = request;
636
+ this.settled = false;
637
+ return new Promise((resolve, reject) => {
638
+ this.resolveFn = resolve;
639
+ this.rejectFn = reject;
640
+ });
641
+ }
642
+ resolve(optionId) {
643
+ if (this.settled || !this.resolveFn) return;
644
+ this.settled = true;
645
+ this.resolveFn(optionId);
646
+ this.cleanup();
647
+ }
648
+ reject(reason) {
649
+ if (this.settled || !this.rejectFn) return;
650
+ this.settled = true;
651
+ this.rejectFn(new Error(reason ?? "Permission rejected"));
652
+ this.cleanup();
653
+ }
654
+ get isPending() {
655
+ return !!this.request && !this.settled;
656
+ }
657
+ get currentRequest() {
658
+ return this.isPending ? this.request : void 0;
659
+ }
660
+ /** The request ID of the current pending request, undefined after settlement */
661
+ get requestId() {
662
+ return this.request?.id;
663
+ }
664
+ cleanup() {
665
+ this.request = void 0;
666
+ this.resolveFn = void 0;
667
+ this.rejectFn = void 0;
668
+ }
669
+ };
670
+
507
671
  // src/core/session.ts
508
672
  import { nanoid } from "nanoid";
509
673
  var moduleLog = createChildLogger({ module: "session" });
510
- var Session = class {
674
+ var Session = class extends TypedEmitter {
511
675
  id;
512
676
  channelId;
513
677
  threadId = "";
@@ -517,15 +681,15 @@ var Session = class {
517
681
  agentSessionId = "";
518
682
  status = "initializing";
519
683
  name;
520
- promptQueue = [];
521
- promptRunning = false;
522
684
  createdAt = /* @__PURE__ */ new Date();
523
685
  adapter;
524
686
  // Set by wireSessionEvents for renaming
525
- pendingPermission;
526
687
  dangerousMode = false;
527
688
  log;
689
+ permissionGate = new PermissionGate();
690
+ queue;
528
691
  constructor(opts) {
692
+ super();
529
693
  this.id = opts.id || nanoid(12);
530
694
  this.channelId = opts.channelId;
531
695
  this.agentName = opts.agentName;
@@ -533,45 +697,57 @@ var Session = class {
533
697
  this.agentInstance = opts.agentInstance;
534
698
  this.log = createSessionLogger(this.id, moduleLog);
535
699
  this.log.info({ agentName: this.agentName }, "Session created");
700
+ this.queue = new PromptQueue(
701
+ (text) => this.processPrompt(text),
702
+ (err) => {
703
+ this.status = "error";
704
+ this.log.error({ err }, "Prompt execution failed");
705
+ }
706
+ );
707
+ }
708
+ // --- Backward-compatible properties ---
709
+ /** @deprecated Use permissionGate directly */
710
+ get pendingPermission() {
711
+ if (!this.permissionGate.isPending) return void 0;
712
+ return {
713
+ requestId: this.permissionGate.requestId,
714
+ resolve: (optionId) => this.permissionGate.resolve(optionId)
715
+ };
716
+ }
717
+ set pendingPermission(val) {
536
718
  }
719
+ /** Number of prompts waiting in queue */
720
+ get queueDepth() {
721
+ return this.queue.pending;
722
+ }
723
+ get promptRunning() {
724
+ return this.queue.isProcessing;
725
+ }
726
+ // --- Public API ---
537
727
  async enqueuePrompt(text) {
538
- if (this.promptRunning) {
539
- this.promptQueue.push(text);
540
- this.log.debug({ queueDepth: this.promptQueue.length }, "Prompt queued");
728
+ await this.queue.enqueue(text);
729
+ }
730
+ async processPrompt(text) {
731
+ if (text === "\0__warmup__") {
732
+ await this.runWarmup();
541
733
  return;
542
734
  }
543
- await this.runPrompt(text);
544
- }
545
- async runPrompt(text) {
546
- this.promptRunning = true;
547
735
  this.status = "active";
548
736
  const promptStart = Date.now();
549
737
  this.log.debug("Prompt execution started");
550
- try {
551
- await this.agentInstance.prompt(text);
552
- this.log.info(
553
- { durationMs: Date.now() - promptStart },
554
- "Prompt execution completed"
555
- );
556
- if (!this.name) {
557
- await this.autoName();
558
- }
559
- } catch (err) {
560
- this.status = "error";
561
- this.log.error({ err }, "Prompt execution failed");
562
- } finally {
563
- this.promptRunning = false;
564
- if (this.promptQueue.length > 0) {
565
- const next = this.promptQueue.shift();
566
- await this.runPrompt(next);
567
- }
738
+ await this.agentInstance.prompt(text);
739
+ this.log.info(
740
+ { durationMs: Date.now() - promptStart },
741
+ "Prompt execution completed"
742
+ );
743
+ if (!this.name) {
744
+ await this.autoName();
568
745
  }
569
746
  }
570
747
  // NOTE: This injects a summary prompt into the agent's conversation history.
571
- // Known Phase 1 limitation — the agent sees this prompt in its context.
572
748
  async autoName() {
573
749
  let title = "";
574
- const prevHandler = this.agentInstance.onSessionUpdate;
750
+ const originalHandler = this.agentInstance.onSessionUpdate;
575
751
  this.agentInstance.onSessionUpdate = (event) => {
576
752
  if (event.type === "text") title += event.content;
577
753
  };
@@ -579,7 +755,7 @@ var Session = class {
579
755
  await this.agentInstance.prompt(
580
756
  "Summarize this conversation in max 5 words for a topic title. Reply ONLY with the title, nothing else."
581
757
  );
582
- this.name = title.trim().slice(0, 50);
758
+ this.name = title.trim().slice(0, 50) || `Session ${this.id.slice(0, 6)}`;
583
759
  this.log.info({ name: this.name }, "Session auto-named");
584
760
  if (this.adapter && this.name) {
585
761
  await this.adapter.renameSessionThread(this.id, this.name);
@@ -587,16 +763,18 @@ var Session = class {
587
763
  } catch {
588
764
  this.name = `Session ${this.id.slice(0, 6)}`;
589
765
  } finally {
590
- this.agentInstance.onSessionUpdate = prevHandler;
766
+ this.agentInstance.onSessionUpdate = originalHandler;
591
767
  }
592
768
  }
593
769
  /** Fire-and-forget warm-up: primes model cache while user types their first message */
594
770
  async warmup() {
595
- this.promptRunning = true;
596
- const prevHandler = this.agentInstance.onSessionUpdate;
597
- this.agentInstance.onSessionUpdate = (event) => {
598
- if (event.type === "commands_update") prevHandler(event);
599
- };
771
+ await this.queue.enqueue("\0__warmup__");
772
+ }
773
+ async runWarmup() {
774
+ this.pause((_event, args) => {
775
+ const agentEvent = args[0];
776
+ return agentEvent?.type === "commands_update";
777
+ });
600
778
  try {
601
779
  const start = Date.now();
602
780
  await this.agentInstance.prompt('Reply with only "ready".');
@@ -605,19 +783,14 @@ var Session = class {
605
783
  } catch (err) {
606
784
  this.log.error({ err }, "Warm-up failed");
607
785
  } finally {
608
- this.agentInstance.onSessionUpdate = prevHandler;
609
- this.promptRunning = false;
610
- if (this.promptQueue.length > 0) {
611
- const next = this.promptQueue.shift();
612
- await this.runPrompt(next);
613
- }
786
+ this.clearBuffer();
787
+ this.resume();
614
788
  }
615
789
  }
616
790
  async cancel() {
617
- this.promptQueue = [];
791
+ this.queue.clear();
618
792
  this.log.info("Session cancelled");
619
793
  await this.agentInstance.cancel();
620
- this.promptRunning = false;
621
794
  this.status = "active";
622
795
  }
623
796
  async destroy() {
@@ -764,6 +937,206 @@ var NotificationManager = class {
764
937
  }
765
938
  };
766
939
 
940
+ // src/tunnel/extract-file-info.ts
941
+ function extractFileInfo(name, kind, content, rawInput, meta) {
942
+ if (kind && !["read", "edit", "write"].includes(kind)) return null;
943
+ let info = null;
944
+ if (meta) {
945
+ const m = meta;
946
+ const tr = m?.claudeCode?.toolResponse;
947
+ const file = tr?.file;
948
+ if (file?.filePath && file?.content) {
949
+ info = { filePath: file.filePath, content: file.content };
950
+ }
951
+ if (!info && tr?.filePath && tr?.content) {
952
+ info = { filePath: tr.filePath, content: tr.content };
953
+ }
954
+ }
955
+ if (!info && rawInput) {
956
+ const ri = rawInput;
957
+ const filePath = ri?.file_path || ri?.filePath || ri?.path;
958
+ if (typeof filePath === "string") {
959
+ const parsed = content ? parseContent(content) : null;
960
+ info = { filePath, content: parsed?.content || ri?.content, oldContent: parsed?.oldContent };
961
+ }
962
+ }
963
+ if (!info && content) {
964
+ info = parseContent(content);
965
+ }
966
+ if (!info) return null;
967
+ if (!info.filePath) {
968
+ const pathMatch = name.match(/(?:Read|Edit|Write|View)\s+(.+)/i);
969
+ if (pathMatch) info.filePath = pathMatch[1].trim();
970
+ }
971
+ if (!info.filePath || !info.content) return null;
972
+ return info;
973
+ }
974
+ function parseContent(content) {
975
+ if (typeof content === "string") {
976
+ return { content };
977
+ }
978
+ if (Array.isArray(content)) {
979
+ for (const block of content) {
980
+ const result = parseContent(block);
981
+ if (result?.content || result?.filePath) return result;
982
+ }
983
+ return null;
984
+ }
985
+ if (typeof content === "object" && content !== null) {
986
+ const c = content;
987
+ if (c.type === "diff" && typeof c.path === "string") {
988
+ const newText = c.newText;
989
+ const oldText = c.oldText;
990
+ if (newText) {
991
+ return {
992
+ filePath: c.path,
993
+ content: newText,
994
+ oldContent: oldText ?? void 0
995
+ };
996
+ }
997
+ }
998
+ if (c.type === "content" && c.content) {
999
+ return parseContent(c.content);
1000
+ }
1001
+ if (c.type === "text" && typeof c.text === "string") {
1002
+ return { content: c.text, filePath: c.filePath };
1003
+ }
1004
+ if (typeof c.text === "string") {
1005
+ return { content: c.text, filePath: c.filePath };
1006
+ }
1007
+ if (typeof c.file_path === "string" || typeof c.filePath === "string" || typeof c.path === "string") {
1008
+ const filePath = c.file_path || c.filePath || c.path;
1009
+ const fileContent = c.content || c.text || c.output || c.newText;
1010
+ if (typeof fileContent === "string") {
1011
+ return {
1012
+ filePath,
1013
+ content: fileContent,
1014
+ oldContent: c.old_content || c.oldText
1015
+ };
1016
+ }
1017
+ }
1018
+ if (c.input) {
1019
+ const result = parseContent(c.input);
1020
+ if (result) return result;
1021
+ }
1022
+ if (c.output) {
1023
+ const result = parseContent(c.output);
1024
+ if (result) return result;
1025
+ }
1026
+ }
1027
+ return null;
1028
+ }
1029
+
1030
+ // src/core/message-transformer.ts
1031
+ var log2 = createChildLogger({ module: "message-transformer" });
1032
+ var MessageTransformer = class {
1033
+ constructor(tunnelService) {
1034
+ this.tunnelService = tunnelService;
1035
+ }
1036
+ transform(event, sessionContext) {
1037
+ switch (event.type) {
1038
+ case "text":
1039
+ return { type: "text", text: event.content };
1040
+ case "thought":
1041
+ return { type: "thought", text: event.content };
1042
+ case "tool_call": {
1043
+ const metadata = {
1044
+ id: event.id,
1045
+ name: event.name,
1046
+ kind: event.kind,
1047
+ status: event.status,
1048
+ content: event.content,
1049
+ locations: event.locations
1050
+ };
1051
+ this.enrichWithViewerLinks(event, metadata, sessionContext);
1052
+ return { type: "tool_call", text: event.name, metadata };
1053
+ }
1054
+ case "tool_update": {
1055
+ const metadata = {
1056
+ id: event.id,
1057
+ name: event.name,
1058
+ kind: event.kind,
1059
+ status: event.status,
1060
+ content: event.content
1061
+ };
1062
+ this.enrichWithViewerLinks(event, metadata, sessionContext);
1063
+ return { type: "tool_update", text: "", metadata };
1064
+ }
1065
+ case "plan":
1066
+ return {
1067
+ type: "plan",
1068
+ text: "",
1069
+ metadata: { entries: event.entries }
1070
+ };
1071
+ case "usage":
1072
+ return {
1073
+ type: "usage",
1074
+ text: "",
1075
+ metadata: {
1076
+ tokensUsed: event.tokensUsed,
1077
+ contextSize: event.contextSize,
1078
+ cost: event.cost
1079
+ }
1080
+ };
1081
+ case "session_end":
1082
+ return { type: "session_end", text: `Done (${event.reason})` };
1083
+ case "error":
1084
+ return { type: "error", text: event.message };
1085
+ default:
1086
+ return { type: "text", text: "" };
1087
+ }
1088
+ }
1089
+ enrichWithViewerLinks(event, metadata, sessionContext) {
1090
+ if (!this.tunnelService || !sessionContext) return;
1091
+ const name = "name" in event ? event.name || "" : "";
1092
+ const kind = "kind" in event ? event.kind : void 0;
1093
+ log2.debug(
1094
+ { name, kind, status: event.status, hasContent: !!event.content },
1095
+ "enrichWithViewerLinks: inspecting event"
1096
+ );
1097
+ const fileInfo = extractFileInfo(
1098
+ name,
1099
+ kind,
1100
+ event.content,
1101
+ event.rawInput,
1102
+ event.meta
1103
+ );
1104
+ if (!fileInfo) return;
1105
+ log2.info(
1106
+ {
1107
+ name,
1108
+ kind,
1109
+ filePath: fileInfo.filePath,
1110
+ hasOldContent: !!fileInfo.oldContent
1111
+ },
1112
+ "enrichWithViewerLinks: extracted file info"
1113
+ );
1114
+ const store = this.tunnelService.getStore();
1115
+ const viewerLinks = {};
1116
+ if (fileInfo.oldContent) {
1117
+ const id2 = store.storeDiff(
1118
+ sessionContext.id,
1119
+ fileInfo.filePath,
1120
+ fileInfo.oldContent,
1121
+ fileInfo.content,
1122
+ sessionContext.workingDirectory
1123
+ );
1124
+ if (id2) viewerLinks.diff = this.tunnelService.diffUrl(id2);
1125
+ }
1126
+ const id = store.storeFile(
1127
+ sessionContext.id,
1128
+ fileInfo.filePath,
1129
+ fileInfo.content,
1130
+ sessionContext.workingDirectory
1131
+ );
1132
+ if (id) viewerLinks.file = this.tunnelService.fileUrl(id);
1133
+ if (Object.keys(viewerLinks).length > 0) {
1134
+ metadata.viewerLinks = viewerLinks;
1135
+ metadata.viewerFilePath = fileInfo.filePath;
1136
+ }
1137
+ }
1138
+ };
1139
+
767
1140
  // src/core/core.ts
768
1141
  import path3 from "path";
769
1142
  import os from "os";
@@ -771,7 +1144,7 @@ import os from "os";
771
1144
  // src/core/session-store.ts
772
1145
  import fs2 from "fs";
773
1146
  import path2 from "path";
774
- var log2 = createChildLogger({ module: "session-store" });
1147
+ var log3 = createChildLogger({ module: "session-store" });
775
1148
  var DEBOUNCE_MS = 2e3;
776
1149
  var JsonFileSessionStore = class {
777
1150
  records = /* @__PURE__ */ new Map();
@@ -848,7 +1221,7 @@ var JsonFileSessionStore = class {
848
1221
  fs2.readFileSync(this.filePath, "utf-8")
849
1222
  );
850
1223
  if (raw.version !== 1) {
851
- log2.warn(
1224
+ log3.warn(
852
1225
  { version: raw.version },
853
1226
  "Unknown session store version, skipping load"
854
1227
  );
@@ -857,9 +1230,9 @@ var JsonFileSessionStore = class {
857
1230
  for (const [id, record] of Object.entries(raw.sessions)) {
858
1231
  this.records.set(id, record);
859
1232
  }
860
- log2.info({ count: this.records.size }, "Loaded session records");
1233
+ log3.info({ count: this.records.size }, "Loaded session records");
861
1234
  } catch (err) {
862
- log2.error({ err }, "Failed to load session store");
1235
+ log3.error({ err }, "Failed to load session store");
863
1236
  }
864
1237
  }
865
1238
  cleanup() {
@@ -875,7 +1248,7 @@ var JsonFileSessionStore = class {
875
1248
  }
876
1249
  }
877
1250
  if (removed > 0) {
878
- log2.info({ removed }, "Cleaned up expired session records");
1251
+ log3.info({ removed }, "Cleaned up expired session records");
879
1252
  this.scheduleDiskWrite();
880
1253
  }
881
1254
  }
@@ -887,105 +1260,18 @@ var JsonFileSessionStore = class {
887
1260
  }
888
1261
  };
889
1262
 
890
- // src/tunnel/extract-file-info.ts
891
- function extractFileInfo(name, kind, content, rawInput, meta) {
892
- if (kind && !["read", "edit", "write"].includes(kind)) return null;
893
- let info = null;
894
- if (meta) {
895
- const m = meta;
896
- const tr = m?.claudeCode?.toolResponse;
897
- const file = tr?.file;
898
- if (file?.filePath && file?.content) {
899
- info = { filePath: file.filePath, content: file.content };
900
- }
901
- if (!info && tr?.filePath && tr?.content) {
902
- info = { filePath: tr.filePath, content: tr.content };
903
- }
904
- }
905
- if (!info && rawInput) {
906
- const ri = rawInput;
907
- const filePath = ri?.file_path || ri?.filePath || ri?.path;
908
- if (typeof filePath === "string") {
909
- const parsed = content ? parseContent(content) : null;
910
- info = { filePath, content: parsed?.content || ri?.content, oldContent: parsed?.oldContent };
911
- }
912
- }
913
- if (!info && content) {
914
- info = parseContent(content);
915
- }
916
- if (!info) return null;
917
- if (!info.filePath) {
918
- const pathMatch = name.match(/(?:Read|Edit|Write|View)\s+(.+)/i);
919
- if (pathMatch) info.filePath = pathMatch[1].trim();
920
- }
921
- if (!info.filePath || !info.content) return null;
922
- return info;
923
- }
924
- function parseContent(content) {
925
- if (typeof content === "string") {
926
- return { content };
927
- }
928
- if (Array.isArray(content)) {
929
- for (const block of content) {
930
- const result = parseContent(block);
931
- if (result?.content || result?.filePath) return result;
932
- }
933
- return null;
934
- }
935
- if (typeof content === "object" && content !== null) {
936
- const c = content;
937
- if (c.type === "diff" && typeof c.path === "string") {
938
- const newText = c.newText;
939
- const oldText = c.oldText;
940
- if (newText) {
941
- return {
942
- filePath: c.path,
943
- content: newText,
944
- oldContent: oldText ?? void 0
945
- };
946
- }
947
- }
948
- if (c.type === "content" && c.content) {
949
- return parseContent(c.content);
950
- }
951
- if (c.type === "text" && typeof c.text === "string") {
952
- return { content: c.text, filePath: c.filePath };
953
- }
954
- if (typeof c.text === "string") {
955
- return { content: c.text, filePath: c.filePath };
956
- }
957
- if (typeof c.file_path === "string" || typeof c.filePath === "string" || typeof c.path === "string") {
958
- const filePath = c.file_path || c.filePath || c.path;
959
- const fileContent = c.content || c.text || c.output || c.newText;
960
- if (typeof fileContent === "string") {
961
- return {
962
- filePath,
963
- content: fileContent,
964
- oldContent: c.old_content || c.oldText
965
- };
966
- }
967
- }
968
- if (c.input) {
969
- const result = parseContent(c.input);
970
- if (result) return result;
971
- }
972
- if (c.output) {
973
- const result = parseContent(c.output);
974
- if (result) return result;
975
- }
976
- }
977
- return null;
978
- }
979
-
980
1263
  // src/core/core.ts
981
- var log3 = createChildLogger({ module: "core" });
1264
+ var log4 = createChildLogger({ module: "core" });
982
1265
  var OpenACPCore = class {
983
1266
  configManager;
984
1267
  agentManager;
985
1268
  sessionManager;
986
1269
  notificationManager;
1270
+ messageTransformer;
987
1271
  adapters = /* @__PURE__ */ new Map();
988
- tunnelService;
1272
+ /** Set by main.ts — triggers graceful shutdown with restart exit code */
1273
+ requestRestart = null;
1274
+ _tunnelService;
989
1275
  sessionStore = null;
990
1276
  resumeLocks = /* @__PURE__ */ new Map();
991
1277
  constructor(configManager) {
@@ -999,6 +1285,14 @@ var OpenACPCore = class {
999
1285
  );
1000
1286
  this.sessionManager = new SessionManager(this.sessionStore);
1001
1287
  this.notificationManager = new NotificationManager(this.adapters);
1288
+ this.messageTransformer = new MessageTransformer();
1289
+ }
1290
+ get tunnelService() {
1291
+ return this._tunnelService;
1292
+ }
1293
+ set tunnelService(service) {
1294
+ this._tunnelService = service;
1295
+ this.messageTransformer = new MessageTransformer(service);
1002
1296
  }
1003
1297
  registerAdapter(name, adapter) {
1004
1298
  this.adapters.set(name, adapter);
@@ -1025,7 +1319,7 @@ var OpenACPCore = class {
1025
1319
  // --- Message Routing ---
1026
1320
  async handleMessage(message) {
1027
1321
  const config = this.configManager.get();
1028
- log3.debug(
1322
+ log4.debug(
1029
1323
  {
1030
1324
  channelId: message.channelId,
1031
1325
  threadId: message.threadId,
@@ -1035,7 +1329,7 @@ var OpenACPCore = class {
1035
1329
  );
1036
1330
  if (config.security.allowedUserIds.length > 0) {
1037
1331
  if (!config.security.allowedUserIds.includes(message.userId)) {
1038
- log3.warn(
1332
+ log4.warn(
1039
1333
  { userId: message.userId },
1040
1334
  "Rejected message from unauthorized user"
1041
1335
  );
@@ -1044,7 +1338,7 @@ var OpenACPCore = class {
1044
1338
  }
1045
1339
  const activeSessions = this.sessionManager.listSessions().filter((s) => s.status === "active" || s.status === "initializing");
1046
1340
  if (activeSessions.length >= config.security.maxConcurrentSessions) {
1047
- log3.warn(
1341
+ log4.warn(
1048
1342
  {
1049
1343
  userId: message.userId,
1050
1344
  currentCount: activeSessions.length,
@@ -1075,7 +1369,7 @@ var OpenACPCore = class {
1075
1369
  async handleNewSession(channelId, agentName, workspacePath) {
1076
1370
  const config = this.configManager.get();
1077
1371
  const resolvedAgent = agentName || config.defaultAgent;
1078
- log3.info({ channelId, agentName: resolvedAgent }, "New session request");
1372
+ log4.info({ channelId, agentName: resolvedAgent }, "New session request");
1079
1373
  const resolvedWorkspace = this.configManager.resolveWorkspace(
1080
1374
  workspacePath || config.agents[resolvedAgent]?.workingDirectory
1081
1375
  );
@@ -1153,13 +1447,13 @@ var OpenACPCore = class {
1153
1447
  status: "active",
1154
1448
  lastActiveAt: (/* @__PURE__ */ new Date()).toISOString()
1155
1449
  });
1156
- log3.info(
1450
+ log4.info(
1157
1451
  { sessionId: session.id, threadId: message.threadId },
1158
1452
  "Lazy resume successful"
1159
1453
  );
1160
1454
  return session;
1161
1455
  } catch (err) {
1162
- log3.error({ err, record }, "Lazy resume failed");
1456
+ log4.error({ err, record }, "Lazy resume failed");
1163
1457
  return null;
1164
1458
  } finally {
1165
1459
  this.resumeLocks.delete(lockKey);
@@ -1169,98 +1463,27 @@ var OpenACPCore = class {
1169
1463
  return resumePromise;
1170
1464
  }
1171
1465
  // --- Event Wiring ---
1172
- toOutgoingMessage(event, session) {
1173
- switch (event.type) {
1174
- case "text":
1175
- return { type: "text", text: event.content };
1176
- case "thought":
1177
- return { type: "thought", text: event.content };
1178
- case "tool_call": {
1179
- const metadata = {
1180
- id: event.id,
1181
- name: event.name,
1182
- kind: event.kind,
1183
- status: event.status,
1184
- content: event.content,
1185
- locations: event.locations
1186
- };
1187
- this.enrichWithViewerLinks(event, metadata, session);
1188
- return { type: "tool_call", text: event.name, metadata };
1189
- }
1190
- case "tool_update": {
1191
- const metadata = {
1192
- id: event.id,
1193
- name: event.name,
1194
- kind: event.kind,
1195
- status: event.status,
1196
- content: event.content
1197
- };
1198
- this.enrichWithViewerLinks(event, metadata, session);
1199
- return { type: "tool_update", text: "", metadata };
1200
- }
1201
- case "plan":
1202
- return { type: "plan", text: "", metadata: { entries: event.entries } };
1203
- case "usage":
1204
- return {
1205
- type: "usage",
1206
- text: "",
1207
- metadata: {
1208
- tokensUsed: event.tokensUsed,
1209
- contextSize: event.contextSize,
1210
- cost: event.cost
1211
- }
1212
- };
1213
- default:
1214
- return { type: "text", text: "" };
1215
- }
1216
- }
1217
- enrichWithViewerLinks(event, metadata, session) {
1218
- if (!this.tunnelService || !session) return;
1219
- const name = "name" in event ? event.name || "" : "";
1220
- const kind = "kind" in event ? event.kind : void 0;
1221
- log3.debug(
1222
- { name, kind, status: event.status, hasContent: !!event.content },
1223
- "enrichWithViewerLinks: inspecting event"
1224
- );
1225
- const fileInfo = extractFileInfo(name, kind, event.content, event.rawInput, event.meta);
1226
- if (!fileInfo) return;
1227
- log3.info(
1228
- {
1229
- name,
1230
- kind,
1231
- filePath: fileInfo.filePath,
1232
- hasOldContent: !!fileInfo.oldContent
1233
- },
1234
- "enrichWithViewerLinks: extracted file info"
1235
- );
1236
- const store = this.tunnelService.getStore();
1237
- const viewerLinks = {};
1238
- if (fileInfo.oldContent) {
1239
- const id2 = store.storeDiff(
1240
- session.id,
1241
- fileInfo.filePath,
1242
- fileInfo.oldContent,
1243
- fileInfo.content,
1244
- session.workingDirectory
1245
- );
1246
- if (id2) viewerLinks.diff = this.tunnelService.diffUrl(id2);
1247
- }
1248
- const id = store.storeFile(
1249
- session.id,
1250
- fileInfo.filePath,
1251
- fileInfo.content,
1252
- session.workingDirectory
1253
- );
1254
- if (id) viewerLinks.file = this.tunnelService.fileUrl(id);
1255
- if (Object.keys(viewerLinks).length > 0) {
1256
- metadata.viewerLinks = viewerLinks;
1257
- metadata.viewerFilePath = fileInfo.filePath;
1258
- }
1259
- }
1260
1466
  // Public — adapters call this for assistant session wiring
1261
1467
  wireSessionEvents(session, adapter) {
1262
1468
  session.adapter = adapter;
1263
1469
  session.agentInstance.onSessionUpdate = (event) => {
1470
+ session.emit("agent_event", event);
1471
+ };
1472
+ session.agentInstance.onPermissionRequest = async (request) => {
1473
+ session.emit("permission_request", request);
1474
+ const promise = session.permissionGate.setPending(request);
1475
+ await adapter.sendPermissionRequest(session.id, request);
1476
+ return promise;
1477
+ };
1478
+ const sessionContext = {
1479
+ get id() {
1480
+ return session.id;
1481
+ },
1482
+ get workingDirectory() {
1483
+ return session.workingDirectory;
1484
+ }
1485
+ };
1486
+ session.on("agent_event", (event) => {
1264
1487
  switch (event.type) {
1265
1488
  case "text":
1266
1489
  case "thought":
@@ -1270,17 +1493,17 @@ var OpenACPCore = class {
1270
1493
  case "usage":
1271
1494
  adapter.sendMessage(
1272
1495
  session.id,
1273
- this.toOutgoingMessage(event, session)
1496
+ this.messageTransformer.transform(event, sessionContext)
1274
1497
  );
1275
1498
  break;
1276
1499
  case "session_end":
1277
1500
  session.status = "finished";
1278
1501
  this.sessionManager.updateSessionStatus(session.id, "finished");
1279
1502
  adapter.cleanupSkillCommands(session.id);
1280
- adapter.sendMessage(session.id, {
1281
- type: "session_end",
1282
- text: `Done (${event.reason})`
1283
- });
1503
+ adapter.sendMessage(
1504
+ session.id,
1505
+ this.messageTransformer.transform(event)
1506
+ );
1284
1507
  this.notificationManager.notify(session.channelId, {
1285
1508
  sessionId: session.id,
1286
1509
  sessionName: session.name,
@@ -1291,10 +1514,10 @@ var OpenACPCore = class {
1291
1514
  case "error":
1292
1515
  this.sessionManager.updateSessionStatus(session.id, "error");
1293
1516
  adapter.cleanupSkillCommands(session.id);
1294
- adapter.sendMessage(session.id, {
1295
- type: "error",
1296
- text: event.message
1297
- });
1517
+ adapter.sendMessage(
1518
+ session.id,
1519
+ this.messageTransformer.transform(event)
1520
+ );
1298
1521
  this.notificationManager.notify(session.channelId, {
1299
1522
  sessionId: session.id,
1300
1523
  sessionName: session.name,
@@ -1303,18 +1526,11 @@ var OpenACPCore = class {
1303
1526
  });
1304
1527
  break;
1305
1528
  case "commands_update":
1306
- log3.debug({ commands: event.commands }, "Commands available");
1529
+ log4.debug({ commands: event.commands }, "Commands available");
1307
1530
  adapter.sendSkillCommands(session.id, event.commands);
1308
1531
  break;
1309
1532
  }
1310
- };
1311
- session.agentInstance.onPermissionRequest = async (request) => {
1312
- const promise = new Promise((resolve) => {
1313
- session.pendingPermission = { requestId: request.id, resolve };
1314
- });
1315
- await adapter.sendPermissionRequest(session.id, request);
1316
- return promise;
1317
- };
1533
+ });
1318
1534
  }
1319
1535
  };
1320
1536
 
@@ -1336,7 +1552,7 @@ import * as http from "http";
1336
1552
  import * as fs3 from "fs";
1337
1553
  import * as path4 from "path";
1338
1554
  import * as os2 from "os";
1339
- var log4 = createChildLogger({ module: "api-server" });
1555
+ var log5 = createChildLogger({ module: "api-server" });
1340
1556
  var DEFAULT_PORT_FILE = path4.join(os2.homedir(), ".openacp", "api.port");
1341
1557
  var ApiServer = class {
1342
1558
  constructor(core, config, portFilePath) {
@@ -1352,7 +1568,7 @@ var ApiServer = class {
1352
1568
  await new Promise((resolve, reject) => {
1353
1569
  this.server.on("error", (err) => {
1354
1570
  if (err.code === "EADDRINUSE") {
1355
- log4.warn({ port: this.config.port }, "API port in use, continuing without API server");
1571
+ log5.warn({ port: this.config.port }, "API port in use, continuing without API server");
1356
1572
  this.server = null;
1357
1573
  resolve();
1358
1574
  } else {
@@ -1365,7 +1581,7 @@ var ApiServer = class {
1365
1581
  this.actualPort = addr.port;
1366
1582
  }
1367
1583
  this.writePortFile();
1368
- log4.info({ host: this.config.host, port: this.actualPort }, "API server listening");
1584
+ log5.info({ host: this.config.host, port: this.actualPort }, "API server listening");
1369
1585
  resolve();
1370
1586
  });
1371
1587
  });
@@ -1410,7 +1626,7 @@ var ApiServer = class {
1410
1626
  this.sendJson(res, 404, { error: "Not found" });
1411
1627
  }
1412
1628
  } catch (err) {
1413
- log4.error({ err }, "API request error");
1629
+ log5.error({ err }, "API request error");
1414
1630
  this.sendJson(res, 500, { error: "Internal server error" });
1415
1631
  }
1416
1632
  }
@@ -1445,17 +1661,17 @@ var ApiServer = class {
1445
1661
  session.threadId = threadId;
1446
1662
  this.core.wireSessionEvents(session, adapter);
1447
1663
  } catch (err) {
1448
- log4.warn({ err, sessionId: session.id }, "Failed to create session thread on adapter, running headless");
1664
+ log5.warn({ err, sessionId: session.id }, "Failed to create session thread on adapter, running headless");
1449
1665
  }
1450
1666
  }
1451
1667
  if (!adapter) {
1452
1668
  session.agentInstance.onPermissionRequest = async (request) => {
1453
1669
  const allowOption = request.options.find((o) => o.isAllow);
1454
- log4.debug({ sessionId: session.id, permissionId: request.id, option: allowOption?.id }, "Auto-approving permission for API session");
1670
+ log5.debug({ sessionId: session.id, permissionId: request.id, option: allowOption?.id }, "Auto-approving permission for API session");
1455
1671
  return allowOption?.id ?? request.options[0]?.id ?? "";
1456
1672
  };
1457
1673
  }
1458
- session.warmup().catch((err) => log4.warn({ err, sessionId: session.id }, "API session warmup failed"));
1674
+ session.warmup().catch((err) => log5.warn({ err, sessionId: session.id }, "API session warmup failed"));
1459
1675
  this.sendJson(res, 200, {
1460
1676
  sessionId: session.id,
1461
1677
  agent: session.agentName,
@@ -1825,10 +2041,10 @@ function buildDeepLink(chatId, messageId) {
1825
2041
  // src/adapters/telegram/commands.ts
1826
2042
  import { InlineKeyboard } from "grammy";
1827
2043
  import { nanoid as nanoid2 } from "nanoid";
1828
- var log6 = createChildLogger({ module: "telegram-commands" });
2044
+ var log7 = createChildLogger({ module: "telegram-commands" });
1829
2045
  function setupCommands(bot, core, chatId, assistant) {
1830
2046
  bot.command("new", (ctx) => handleNew(ctx, core, chatId, assistant));
1831
- bot.command("new_chat", (ctx) => handleNewChat(ctx, core, chatId));
2047
+ bot.command("newchat", (ctx) => handleNewChat(ctx, core, chatId));
1832
2048
  bot.command("cancel", (ctx) => handleCancel(ctx, core, assistant));
1833
2049
  bot.command("status", (ctx) => handleStatus(ctx, core));
1834
2050
  bot.command("agents", (ctx) => handleAgents(ctx, core));
@@ -1836,9 +2052,11 @@ function setupCommands(bot, core, chatId, assistant) {
1836
2052
  bot.command("menu", (ctx) => handleMenu(ctx));
1837
2053
  bot.command("enable_dangerous", (ctx) => handleEnableDangerous(ctx, core));
1838
2054
  bot.command("disable_dangerous", (ctx) => handleDisableDangerous(ctx, core));
2055
+ bot.command("restart", (ctx) => handleRestart(ctx, core));
2056
+ bot.command("update", (ctx) => handleUpdate(ctx, core));
1839
2057
  }
1840
2058
  function buildMenuKeyboard() {
1841
- return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:new_chat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F916} Agents", "m:agents").text("\u2753 Help", "m:help");
2059
+ return new InlineKeyboard().text("\u{1F195} New Session", "m:new").text("\u{1F4AC} New Chat", "m:newchat").row().text("\u26D4 Cancel", "m:cancel").text("\u{1F4CA} Status", "m:status").row().text("\u{1F916} Agents", "m:agents").text("\u2753 Help", "m:help").row().text("\u{1F504} Restart", "m:restart").text("\u2B06\uFE0F Update", "m:update");
1842
2060
  }
1843
2061
  function setupMenuCallbacks(bot, core, chatId) {
1844
2062
  bot.callbackQuery(/^m:/, async (ctx) => {
@@ -1851,7 +2069,7 @@ function setupMenuCallbacks(bot, core, chatId) {
1851
2069
  case "m:new":
1852
2070
  await handleNew(ctx, core, chatId);
1853
2071
  break;
1854
- case "m:new_chat":
2072
+ case "m:newchat":
1855
2073
  await handleNewChat(ctx, core, chatId);
1856
2074
  break;
1857
2075
  case "m:cancel":
@@ -1866,6 +2084,12 @@ function setupMenuCallbacks(bot, core, chatId) {
1866
2084
  case "m:help":
1867
2085
  await handleHelp(ctx);
1868
2086
  break;
2087
+ case "m:restart":
2088
+ await handleRestart(ctx, core);
2089
+ break;
2090
+ case "m:update":
2091
+ await handleUpdate(ctx, core);
2092
+ break;
1869
2093
  }
1870
2094
  });
1871
2095
  }
@@ -1891,7 +2115,7 @@ async function handleNew(ctx, core, chatId, assistant) {
1891
2115
  return;
1892
2116
  }
1893
2117
  }
1894
- log6.info({ userId: ctx.from?.id, agentName }, "New session command");
2118
+ log7.info({ userId: ctx.from?.id, agentName }, "New session command");
1895
2119
  let threadId;
1896
2120
  try {
1897
2121
  const topicName = `\u{1F504} New Session`;
@@ -1925,9 +2149,9 @@ async function handleNew(ctx, core, chatId, assistant) {
1925
2149
  reply_markup: buildDangerousModeKeyboard(session.id, false)
1926
2150
  }
1927
2151
  );
1928
- session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
2152
+ session.warmup().catch((err) => log7.error({ err }, "Warm-up error"));
1929
2153
  } catch (err) {
1930
- log6.error({ err }, "Session creation failed");
2154
+ log7.error({ err }, "Session creation failed");
1931
2155
  if (threadId) {
1932
2156
  try {
1933
2157
  await ctx.api.deleteForumTopic(chatId, threadId);
@@ -1942,7 +2166,7 @@ async function handleNewChat(ctx, core, chatId) {
1942
2166
  const threadId = ctx.message?.message_thread_id;
1943
2167
  if (!threadId) {
1944
2168
  await ctx.reply(
1945
- "Use /new_chat inside a session topic to inherit its config.",
2169
+ "Use /newchat inside a session topic to inherit its config.",
1946
2170
  { parse_mode: "HTML" }
1947
2171
  );
1948
2172
  return;
@@ -1999,7 +2223,7 @@ async function handleNewChat(ctx, core, chatId) {
1999
2223
  reply_markup: buildDangerousModeKeyboard(session.id, false)
2000
2224
  }
2001
2225
  );
2002
- session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
2226
+ session.warmup().catch((err) => log7.error({ err }, "Warm-up error"));
2003
2227
  } catch (err) {
2004
2228
  if (newThreadId) {
2005
2229
  try {
@@ -2028,14 +2252,14 @@ async function handleCancel(ctx, core, assistant) {
2028
2252
  String(threadId)
2029
2253
  );
2030
2254
  if (session) {
2031
- log6.info({ sessionId: session.id }, "Cancel session command");
2255
+ log7.info({ sessionId: session.id }, "Cancel session command");
2032
2256
  await session.cancel();
2033
2257
  await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
2034
2258
  return;
2035
2259
  }
2036
2260
  const record = core.sessionManager.getRecordByThread("telegram", String(threadId));
2037
2261
  if (record && record.status !== "cancelled" && record.status !== "error") {
2038
- log6.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
2262
+ log7.info({ sessionId: record.sessionId }, "Cancel session command (from store)");
2039
2263
  await core.sessionManager.cancelSession(record.sessionId);
2040
2264
  await ctx.reply("\u26D4 Session cancelled.", { parse_mode: "HTML" });
2041
2265
  }
@@ -2053,7 +2277,7 @@ async function handleStatus(ctx, core) {
2053
2277
  <b>Agent:</b> ${escapeHtml(session.agentName)}
2054
2278
  <b>Status:</b> ${escapeHtml(session.status)}
2055
2279
  <b>Workspace:</b> <code>${escapeHtml(session.workingDirectory)}</code>
2056
- <b>Queue:</b> ${session.promptQueue.length} pending`,
2280
+ <b>Queue:</b> ${session.queueDepth} pending`,
2057
2281
  { parse_mode: "HTML" }
2058
2282
  );
2059
2283
  } else {
@@ -2104,11 +2328,13 @@ async function handleHelp(ctx) {
2104
2328
  `<b>OpenACP Commands:</b>
2105
2329
 
2106
2330
  /new [agent] [workspace] \u2014 Create new session
2107
- /new_chat \u2014 New chat, same agent &amp; workspace
2331
+ /newchat \u2014 New chat, same agent &amp; workspace
2108
2332
  /cancel \u2014 Cancel current session
2109
2333
  /status \u2014 Show session/system status
2110
2334
  /agents \u2014 List available agents
2111
2335
  /menu \u2014 Show interactive menu
2336
+ /restart \u2014 Restart OpenACP
2337
+ /update \u2014 Update to latest version and restart
2112
2338
  /help \u2014 Show this help
2113
2339
 
2114
2340
  Or just chat in the \u{1F916} Assistant topic for help!`,
@@ -2133,7 +2359,7 @@ function setupDangerousModeCallbacks(bot, core) {
2133
2359
  return;
2134
2360
  }
2135
2361
  session.dangerousMode = !session.dangerousMode;
2136
- log6.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
2362
+ log7.info({ sessionId, dangerousMode: session.dangerousMode }, "Dangerous mode toggled via button");
2137
2363
  const toastText = session.dangerousMode ? "\u2620\uFE0F Dangerous mode enabled \u2014 permissions auto-approved" : "\u{1F510} Dangerous mode disabled \u2014 permissions shown normally";
2138
2364
  try {
2139
2365
  await ctx.answerCallbackQuery({ text: toastText });
@@ -2172,6 +2398,52 @@ Use /disable_dangerous to restore normal behaviour.`,
2172
2398
  { parse_mode: "HTML" }
2173
2399
  );
2174
2400
  }
2401
+ async function handleUpdate(ctx, core) {
2402
+ if (!core.requestRestart) {
2403
+ await ctx.reply("\u26A0\uFE0F Update is not available (no restart handler registered).", { parse_mode: "HTML" });
2404
+ return;
2405
+ }
2406
+ const { getCurrentVersion, getLatestVersion, compareVersions, runUpdate } = await import("./version-VC5CPXBX.js");
2407
+ const current = getCurrentVersion();
2408
+ const statusMsg = await ctx.reply(`\u{1F50D} Checking for updates... (current: v${escapeHtml(current)})`, { parse_mode: "HTML" });
2409
+ const latest = await getLatestVersion();
2410
+ if (!latest) {
2411
+ await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Could not check for updates.", { parse_mode: "HTML" });
2412
+ return;
2413
+ }
2414
+ if (compareVersions(current, latest) >= 0) {
2415
+ await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, `\u2705 Already up to date (v${escapeHtml(current)}).`, { parse_mode: "HTML" });
2416
+ return;
2417
+ }
2418
+ await ctx.api.editMessageText(
2419
+ ctx.chat.id,
2420
+ statusMsg.message_id,
2421
+ `\u2B07\uFE0F Updating v${escapeHtml(current)} \u2192 v${escapeHtml(latest)}...`,
2422
+ { parse_mode: "HTML" }
2423
+ );
2424
+ const ok = await runUpdate();
2425
+ if (!ok) {
2426
+ await ctx.api.editMessageText(ctx.chat.id, statusMsg.message_id, "\u274C Update failed. Try manually: <code>npm install -g @openacp/cli@latest</code>", { parse_mode: "HTML" });
2427
+ return;
2428
+ }
2429
+ await ctx.api.editMessageText(
2430
+ ctx.chat.id,
2431
+ statusMsg.message_id,
2432
+ `\u2705 Updated to v${escapeHtml(latest)}. Restarting...`,
2433
+ { parse_mode: "HTML" }
2434
+ );
2435
+ await new Promise((r) => setTimeout(r, 500));
2436
+ await core.requestRestart();
2437
+ }
2438
+ async function handleRestart(ctx, core) {
2439
+ if (!core.requestRestart) {
2440
+ await ctx.reply("\u26A0\uFE0F Restart is not available (no restart handler registered).", { parse_mode: "HTML" });
2441
+ return;
2442
+ }
2443
+ await ctx.reply("\u{1F504} <b>Restarting OpenACP...</b>\nRebuilding and restarting. Be back shortly.", { parse_mode: "HTML" });
2444
+ await new Promise((r) => setTimeout(r, 500));
2445
+ await core.requestRestart();
2446
+ }
2175
2447
  async function handleDisableDangerous(ctx, core) {
2176
2448
  const threadId = ctx.message?.message_thread_id;
2177
2449
  if (!threadId) {
@@ -2248,7 +2520,7 @@ async function executeNewSession(bot, core, chatId, agentName, workspace) {
2248
2520
  });
2249
2521
  const finalName = `\u{1F504} ${session.agentName} \u2014 New Session`;
2250
2522
  await renameSessionTopic(bot, chatId, threadId, finalName);
2251
- session.warmup().catch((err) => log6.error({ err }, "Warm-up error"));
2523
+ session.warmup().catch((err) => log7.error({ err }, "Warm-up error"));
2252
2524
  return { session, threadId, firstMsgId };
2253
2525
  } catch (err) {
2254
2526
  try {
@@ -2267,20 +2539,22 @@ async function executeCancelSession(core, excludeSessionId) {
2267
2539
  }
2268
2540
  var STATIC_COMMANDS = [
2269
2541
  { command: "new", description: "Create new session" },
2270
- { command: "new_chat", description: "New chat, same agent & workspace" },
2542
+ { command: "newchat", description: "New chat, same agent & workspace" },
2271
2543
  { command: "cancel", description: "Cancel current session" },
2272
2544
  { command: "status", description: "Show status" },
2273
2545
  { command: "agents", description: "List available agents" },
2274
2546
  { command: "help", description: "Help" },
2275
2547
  { command: "menu", description: "Show menu" },
2276
2548
  { command: "enable_dangerous", description: "Auto-approve all permission requests (session only)" },
2277
- { command: "disable_dangerous", description: "Restore normal permission prompts (session only)" }
2549
+ { command: "disable_dangerous", description: "Restore normal permission prompts (session only)" },
2550
+ { command: "restart", description: "Restart OpenACP" },
2551
+ { command: "update", description: "Update to latest version and restart" }
2278
2552
  ];
2279
2553
 
2280
2554
  // src/adapters/telegram/permissions.ts
2281
2555
  import { InlineKeyboard as InlineKeyboard2 } from "grammy";
2282
2556
  import { nanoid as nanoid3 } from "nanoid";
2283
- var log7 = createChildLogger({ module: "telegram-permissions" });
2557
+ var log8 = createChildLogger({ module: "telegram-permissions" });
2284
2558
  var PermissionHandler = class {
2285
2559
  constructor(bot, chatId, getSession, sendNotification) {
2286
2560
  this.bot = bot;
@@ -2340,10 +2614,9 @@ ${escapeHtml(request.description)}`,
2340
2614
  }
2341
2615
  const session = this.getSession(pending.sessionId);
2342
2616
  const isAllow = pending.options.find((o) => o.id === optionId)?.isAllow ?? false;
2343
- log7.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
2344
- if (session?.pendingPermission?.requestId === pending.requestId) {
2345
- session.pendingPermission.resolve(optionId);
2346
- session.pendingPermission = void 0;
2617
+ log8.info({ requestId: pending.requestId, optionId, isAllow }, "Permission responded");
2618
+ if (session?.permissionGate.requestId === pending.requestId) {
2619
+ session.permissionGate.resolve(optionId);
2347
2620
  }
2348
2621
  this.pending.delete(callbackKey);
2349
2622
  try {
@@ -2359,10 +2632,10 @@ ${escapeHtml(request.description)}`,
2359
2632
  };
2360
2633
 
2361
2634
  // src/adapters/telegram/assistant.ts
2362
- var log8 = createChildLogger({ module: "telegram-assistant" });
2635
+ var log9 = createChildLogger({ module: "telegram-assistant" });
2363
2636
  async function spawnAssistant(core, adapter, assistantTopicId) {
2364
2637
  const config = core.configManager.get();
2365
- log8.info({ agent: config.defaultAgent }, "Creating assistant session...");
2638
+ log9.info({ agent: config.defaultAgent }, "Creating assistant session...");
2366
2639
  const session = await core.sessionManager.createSession(
2367
2640
  "telegram",
2368
2641
  config.defaultAgent,
@@ -2371,13 +2644,13 @@ async function spawnAssistant(core, adapter, assistantTopicId) {
2371
2644
  );
2372
2645
  session.threadId = String(assistantTopicId);
2373
2646
  session.name = "Assistant";
2374
- log8.info({ sessionId: session.id }, "Assistant agent spawned");
2647
+ log9.info({ sessionId: session.id }, "Assistant agent spawned");
2375
2648
  core.wireSessionEvents(session, adapter);
2376
2649
  const systemPrompt = buildAssistantSystemPrompt(config);
2377
2650
  const ready = session.enqueuePrompt(systemPrompt).then(() => {
2378
- log8.info({ sessionId: session.id }, "Assistant system prompt completed");
2651
+ log9.info({ sessionId: session.id }, "Assistant system prompt completed");
2379
2652
  }).catch((err) => {
2380
- log8.warn({ err }, "Assistant system prompt failed");
2653
+ log9.warn({ err }, "Assistant system prompt failed");
2381
2654
  });
2382
2655
  return { session, ready };
2383
2656
  }
@@ -2396,7 +2669,7 @@ When a user wants to create a session, guide them through:
2396
2669
 
2397
2670
  Commands reference:
2398
2671
  - /new [agent] [workspace] \u2014 Create new session
2399
- - /new_chat \u2014 New chat with same agent & workspace
2672
+ - /newchat \u2014 New chat with same agent & workspace
2400
2673
  - /cancel \u2014 Cancel current session
2401
2674
  - /status \u2014 Show status
2402
2675
  - /agents \u2014 List agents
@@ -2415,7 +2688,7 @@ function redirectToAssistant(chatId, assistantTopicId) {
2415
2688
  }
2416
2689
 
2417
2690
  // src/adapters/telegram/activity.ts
2418
- var log9 = createChildLogger({ module: "telegram:activity" });
2691
+ var log10 = createChildLogger({ module: "telegram:activity" });
2419
2692
  var THINKING_REFRESH_MS = 15e3;
2420
2693
  var THINKING_MAX_MS = 3 * 60 * 1e3;
2421
2694
  var ThinkingIndicator = class {
@@ -2447,7 +2720,7 @@ var ThinkingIndicator = class {
2447
2720
  this.startRefreshTimer();
2448
2721
  }
2449
2722
  } catch (err) {
2450
- log9.warn({ err }, "ThinkingIndicator.show() failed");
2723
+ log10.warn({ err }, "ThinkingIndicator.show() failed");
2451
2724
  } finally {
2452
2725
  this.sending = false;
2453
2726
  }
@@ -2520,7 +2793,7 @@ var UsageMessage = class {
2520
2793
  if (result) this.msgId = result.message_id;
2521
2794
  }
2522
2795
  } catch (err) {
2523
- log9.warn({ err }, "UsageMessage.send() failed");
2796
+ log10.warn({ err }, "UsageMessage.send() failed");
2524
2797
  }
2525
2798
  }
2526
2799
  getMsgId() {
@@ -2533,7 +2806,7 @@ var UsageMessage = class {
2533
2806
  try {
2534
2807
  await this.sendQueue.enqueue(() => this.api.deleteMessage(this.chatId, id));
2535
2808
  } catch (err) {
2536
- log9.warn({ err }, "UsageMessage.delete() failed");
2809
+ log10.warn({ err }, "UsageMessage.delete() failed");
2537
2810
  }
2538
2811
  }
2539
2812
  };
@@ -2616,7 +2889,7 @@ var PlanCard = class {
2616
2889
  if (result) this.msgId = result.message_id;
2617
2890
  }
2618
2891
  } catch (err) {
2619
- log9.warn({ err }, "PlanCard flush failed");
2892
+ log10.warn({ err }, "PlanCard flush failed");
2620
2893
  }
2621
2894
  }
2622
2895
  };
@@ -2679,7 +2952,7 @@ var ActivityTracker = class {
2679
2952
  })
2680
2953
  );
2681
2954
  } catch (err) {
2682
- log9.warn({ err }, "ActivityTracker.onComplete() Done send failed");
2955
+ log10.warn({ err }, "ActivityTracker.onComplete() Done send failed");
2683
2956
  }
2684
2957
  }
2685
2958
  }
@@ -2906,7 +3179,7 @@ function setupActionCallbacks(bot, core, chatId, getAssistantSessionId) {
2906
3179
  }
2907
3180
 
2908
3181
  // src/adapters/telegram/adapter.ts
2909
- var log10 = createChildLogger({ module: "telegram" });
3182
+ var log11 = createChildLogger({ module: "telegram" });
2910
3183
  function patchedFetch(input, init) {
2911
3184
  if (init?.signal && !(init.signal instanceof AbortSignal)) {
2912
3185
  const nativeController = new AbortController();
@@ -2957,7 +3230,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2957
3230
  this.bot = new Bot(this.telegramConfig.botToken, { client: { fetch: patchedFetch } });
2958
3231
  this.bot.catch((err) => {
2959
3232
  const rootCause = err.error instanceof Error ? err.error : err;
2960
- log10.error({ err: rootCause }, "Telegram bot error");
3233
+ log11.error({ err: rootCause }, "Telegram bot error");
2961
3234
  });
2962
3235
  this.bot.api.config.use(async (prev, method, payload, signal) => {
2963
3236
  const maxRetries = 3;
@@ -2971,7 +3244,7 @@ var TelegramAdapter = class extends ChannelAdapter {
2971
3244
  if (rateLimitedMethods.includes(method)) {
2972
3245
  this.sendQueue.onRateLimited();
2973
3246
  }
2974
- log10.warn(
3247
+ log11.warn(
2975
3248
  { method, retryAfter, attempt: attempt + 1 },
2976
3249
  "Rate limited by Telegram, retrying"
2977
3250
  );
@@ -3041,7 +3314,7 @@ var TelegramAdapter = class extends ChannelAdapter {
3041
3314
  this.setupRoutes();
3042
3315
  this.bot.start({
3043
3316
  allowed_updates: ["message", "callback_query"],
3044
- onStart: () => log10.info(
3317
+ onStart: () => log11.info(
3045
3318
  { chatId: this.telegramConfig.chatId },
3046
3319
  "Telegram bot started"
3047
3320
  )
@@ -3063,10 +3336,10 @@ Workspace: <code>${workspace}</code>
3063
3336
  reply_markup: buildMenuKeyboard()
3064
3337
  });
3065
3338
  } catch (err) {
3066
- log10.warn({ err }, "Failed to send welcome message");
3339
+ log11.warn({ err }, "Failed to send welcome message");
3067
3340
  }
3068
3341
  try {
3069
- log10.info("Spawning assistant session...");
3342
+ log11.info("Spawning assistant session...");
3070
3343
  const { session, ready } = await spawnAssistant(
3071
3344
  this.core,
3072
3345
  this,
@@ -3074,13 +3347,13 @@ Workspace: <code>${workspace}</code>
3074
3347
  );
3075
3348
  this.assistantSession = session;
3076
3349
  this.assistantInitializing = true;
3077
- log10.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
3350
+ log11.info({ sessionId: session.id }, "Assistant session ready, system prompt running in background");
3078
3351
  ready.then(() => {
3079
3352
  this.assistantInitializing = false;
3080
- log10.info({ sessionId: session.id }, "Assistant ready for user messages");
3353
+ log11.info({ sessionId: session.id }, "Assistant ready for user messages");
3081
3354
  });
3082
3355
  } catch (err) {
3083
- log10.error({ err }, "Failed to spawn assistant");
3356
+ log11.error({ err }, "Failed to spawn assistant");
3084
3357
  this.bot.api.sendMessage(
3085
3358
  this.telegramConfig.chatId,
3086
3359
  `\u26A0\uFE0F <b>Failed to start assistant session.</b>
@@ -3096,7 +3369,7 @@ Workspace: <code>${workspace}</code>
3096
3369
  await this.assistantSession.destroy();
3097
3370
  }
3098
3371
  await this.bot.stop();
3099
- log10.info("Telegram bot stopped");
3372
+ log11.info("Telegram bot stopped");
3100
3373
  }
3101
3374
  setupRoutes() {
3102
3375
  this.bot.on("message:text", async (ctx) => {
@@ -3119,7 +3392,7 @@ Workspace: <code>${workspace}</code>
3119
3392
  ctx.replyWithChatAction("typing").catch(() => {
3120
3393
  });
3121
3394
  handleAssistantMessage(this.assistantSession, ctx.message.text).catch(
3122
- (err) => log10.error({ err }, "Assistant error")
3395
+ (err) => log11.error({ err }, "Assistant error")
3123
3396
  );
3124
3397
  return;
3125
3398
  }
@@ -3136,7 +3409,7 @@ Workspace: <code>${workspace}</code>
3136
3409
  threadId: String(threadId),
3137
3410
  userId: String(ctx.from.id),
3138
3411
  text: ctx.message.text
3139
- }).catch((err) => log10.error({ err }, "handleMessage error"));
3412
+ }).catch((err) => log11.error({ err }, "handleMessage error"));
3140
3413
  });
3141
3414
  }
3142
3415
  // --- ChannelAdapter implementations ---
@@ -3216,7 +3489,7 @@ Workspace: <code>${workspace}</code>
3216
3489
  if (toolState) {
3217
3490
  if (meta.viewerLinks) {
3218
3491
  toolState.viewerLinks = meta.viewerLinks;
3219
- log10.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
3492
+ log11.debug({ toolId: meta.id, viewerLinks: meta.viewerLinks }, "Accumulated viewerLinks");
3220
3493
  }
3221
3494
  const viewerFilePath = content.metadata?.viewerFilePath;
3222
3495
  if (viewerFilePath) toolState.viewerFilePath = viewerFilePath;
@@ -3225,7 +3498,7 @@ Workspace: <code>${workspace}</code>
3225
3498
  const isTerminal = meta.status === "completed" || meta.status === "failed";
3226
3499
  if (!isTerminal && !meta.viewerLinks) break;
3227
3500
  await toolState.ready;
3228
- log10.debug(
3501
+ log11.debug(
3229
3502
  { toolId: meta.id, status: meta.status, hasViewerLinks: !!toolState.viewerLinks, viewerLinks: toolState.viewerLinks, name: toolState.name, msgId: toolState.msgId },
3230
3503
  "Tool completed, preparing edit"
3231
3504
  );
@@ -3247,7 +3520,7 @@ Workspace: <code>${workspace}</code>
3247
3520
  )
3248
3521
  );
3249
3522
  } catch (err) {
3250
- log10.warn(
3523
+ log11.warn(
3251
3524
  { err, msgId: toolState.msgId, textLen: formattedText.length, hasViewerLinks: !!merged.viewerLinks },
3252
3525
  "Tool update edit failed"
3253
3526
  );
@@ -3342,17 +3615,16 @@ Task completed.
3342
3615
  }
3343
3616
  }
3344
3617
  async sendPermissionRequest(sessionId, request) {
3345
- log10.info({ sessionId, requestId: request.id }, "Permission request sent");
3618
+ log11.info({ sessionId, requestId: request.id }, "Permission request sent");
3346
3619
  const session = this.core.sessionManager.getSession(
3347
3620
  sessionId
3348
3621
  );
3349
3622
  if (!session) return;
3350
3623
  if (session.dangerousMode) {
3351
3624
  const allowOption = request.options.find((o) => o.isAllow);
3352
- if (allowOption && session.pendingPermission?.requestId === request.id) {
3353
- log10.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
3354
- session.pendingPermission.resolve(allowOption.id);
3355
- session.pendingPermission = void 0;
3625
+ if (allowOption && session.permissionGate.requestId === request.id) {
3626
+ log11.info({ sessionId, requestId: request.id, optionId: allowOption.id }, "Dangerous mode: auto-approving permission");
3627
+ session.permissionGate.resolve(allowOption.id);
3356
3628
  }
3357
3629
  return;
3358
3630
  }
@@ -3362,7 +3634,7 @@ Task completed.
3362
3634
  }
3363
3635
  async sendNotification(notification) {
3364
3636
  if (notification.sessionId === this.assistantSession?.id) return;
3365
- log10.info(
3637
+ log11.info(
3366
3638
  { sessionId: notification.sessionId, type: notification.type },
3367
3639
  "Notification sent"
3368
3640
  );
@@ -3398,7 +3670,7 @@ Task completed.
3398
3670
  );
3399
3671
  }
3400
3672
  async createSessionThread(sessionId, name) {
3401
- log10.info({ sessionId, name }, "Session topic created");
3673
+ log11.info({ sessionId, name }, "Session topic created");
3402
3674
  return String(
3403
3675
  await createSessionTopic(this.bot, this.telegramConfig.chatId, name)
3404
3676
  );
@@ -3483,7 +3755,7 @@ Task completed.
3483
3755
  }
3484
3756
  );
3485
3757
  } catch (err) {
3486
- log10.error({ err, sessionId }, "Failed to send skill commands");
3758
+ log11.error({ err, sessionId }, "Failed to send skill commands");
3487
3759
  }
3488
3760
  await this.updateCommandAutocomplete(session.agentName, commands);
3489
3761
  }
@@ -3519,12 +3791,12 @@ Task completed.
3519
3791
  await this.bot.api.setMyCommands(all, {
3520
3792
  scope: { type: "chat", chat_id: this.telegramConfig.chatId }
3521
3793
  });
3522
- log10.info(
3794
+ log11.info(
3523
3795
  { count: all.length, skills: validSkills.length },
3524
3796
  "Updated command autocomplete"
3525
3797
  );
3526
3798
  } catch (err) {
3527
- log10.error(
3799
+ log11.error(
3528
3800
  { err, commands: all },
3529
3801
  "Failed to update command autocomplete"
3530
3802
  );
@@ -3565,12 +3837,16 @@ export {
3565
3837
  StderrCapture,
3566
3838
  AgentInstance,
3567
3839
  AgentManager,
3840
+ TypedEmitter,
3841
+ PromptQueue,
3842
+ PermissionGate,
3568
3843
  Session,
3569
3844
  SessionManager,
3570
3845
  NotificationManager,
3846
+ MessageTransformer,
3571
3847
  OpenACPCore,
3572
3848
  ChannelAdapter,
3573
3849
  ApiServer,
3574
3850
  TelegramAdapter
3575
3851
  };
3576
- //# sourceMappingURL=chunk-66PHSLNS.js.map
3852
+ //# sourceMappingURL=chunk-WXPN5UOT.js.map