@suzuke/agend 0.0.1 → 1.0.0

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 (202) hide show
  1. package/README.md +557 -1
  2. package/README.zh-TW.md +504 -0
  3. package/dist/access-path.d.ts +7 -0
  4. package/dist/access-path.js +12 -0
  5. package/dist/access-path.js.map +1 -0
  6. package/dist/approval/approval-server.d.ts +30 -0
  7. package/dist/approval/approval-server.js +156 -0
  8. package/dist/approval/approval-server.js.map +1 -0
  9. package/dist/approval/tmux-prompt-detector.d.ts +34 -0
  10. package/dist/approval/tmux-prompt-detector.js +264 -0
  11. package/dist/approval/tmux-prompt-detector.js.map +1 -0
  12. package/dist/backend/approval-strategy.d.ts +14 -0
  13. package/dist/backend/approval-strategy.js +2 -0
  14. package/dist/backend/approval-strategy.js.map +1 -0
  15. package/dist/backend/claude-code.d.ts +13 -0
  16. package/dist/backend/claude-code.js +114 -0
  17. package/dist/backend/claude-code.js.map +1 -0
  18. package/dist/backend/codex.d.ts +10 -0
  19. package/dist/backend/codex.js +58 -0
  20. package/dist/backend/codex.js.map +1 -0
  21. package/dist/backend/factory.d.ts +2 -0
  22. package/dist/backend/factory.js +19 -0
  23. package/dist/backend/factory.js.map +1 -0
  24. package/dist/backend/gemini-cli.d.ts +10 -0
  25. package/dist/backend/gemini-cli.js +68 -0
  26. package/dist/backend/gemini-cli.js.map +1 -0
  27. package/dist/backend/hook-based-approval.d.ts +20 -0
  28. package/dist/backend/hook-based-approval.js +41 -0
  29. package/dist/backend/hook-based-approval.js.map +1 -0
  30. package/dist/backend/index.d.ts +6 -0
  31. package/dist/backend/index.js +6 -0
  32. package/dist/backend/index.js.map +1 -0
  33. package/dist/backend/opencode.d.ts +10 -0
  34. package/dist/backend/opencode.js +63 -0
  35. package/dist/backend/opencode.js.map +1 -0
  36. package/dist/backend/types.d.ts +26 -0
  37. package/dist/backend/types.js +2 -0
  38. package/dist/backend/types.js.map +1 -0
  39. package/dist/channel/access-manager.d.ts +18 -0
  40. package/dist/channel/access-manager.js +149 -0
  41. package/dist/channel/access-manager.js.map +1 -0
  42. package/dist/channel/adapters/discord.d.ts +45 -0
  43. package/dist/channel/adapters/discord.js +366 -0
  44. package/dist/channel/adapters/discord.js.map +1 -0
  45. package/dist/channel/adapters/telegram.d.ts +58 -0
  46. package/dist/channel/adapters/telegram.js +569 -0
  47. package/dist/channel/adapters/telegram.js.map +1 -0
  48. package/dist/channel/attachment-handler.d.ts +15 -0
  49. package/dist/channel/attachment-handler.js +55 -0
  50. package/dist/channel/attachment-handler.js.map +1 -0
  51. package/dist/channel/factory.d.ts +12 -0
  52. package/dist/channel/factory.js +38 -0
  53. package/dist/channel/factory.js.map +1 -0
  54. package/dist/channel/ipc-bridge.d.ts +26 -0
  55. package/dist/channel/ipc-bridge.js +170 -0
  56. package/dist/channel/ipc-bridge.js.map +1 -0
  57. package/dist/channel/mcp-server.d.ts +10 -0
  58. package/dist/channel/mcp-server.js +196 -0
  59. package/dist/channel/mcp-server.js.map +1 -0
  60. package/dist/channel/mcp-tools.d.ts +909 -0
  61. package/dist/channel/mcp-tools.js +346 -0
  62. package/dist/channel/mcp-tools.js.map +1 -0
  63. package/dist/channel/message-bus.d.ts +17 -0
  64. package/dist/channel/message-bus.js +86 -0
  65. package/dist/channel/message-bus.js.map +1 -0
  66. package/dist/channel/message-queue.d.ts +39 -0
  67. package/dist/channel/message-queue.js +248 -0
  68. package/dist/channel/message-queue.js.map +1 -0
  69. package/dist/channel/tool-router.d.ts +6 -0
  70. package/dist/channel/tool-router.js +69 -0
  71. package/dist/channel/tool-router.js.map +1 -0
  72. package/dist/channel/tool-tracker.d.ts +13 -0
  73. package/dist/channel/tool-tracker.js +58 -0
  74. package/dist/channel/tool-tracker.js.map +1 -0
  75. package/dist/channel/types.d.ts +116 -0
  76. package/dist/channel/types.js +2 -0
  77. package/dist/channel/types.js.map +1 -0
  78. package/dist/cli.d.ts +2 -0
  79. package/dist/cli.js +782 -0
  80. package/dist/cli.js.map +1 -0
  81. package/dist/config.d.ts +8 -0
  82. package/dist/config.js +85 -0
  83. package/dist/config.js.map +1 -0
  84. package/dist/container-manager.d.ts +24 -0
  85. package/dist/container-manager.js +148 -0
  86. package/dist/container-manager.js.map +1 -0
  87. package/dist/context-guardian.d.ts +29 -0
  88. package/dist/context-guardian.js +123 -0
  89. package/dist/context-guardian.js.map +1 -0
  90. package/dist/cost-guard.d.ts +21 -0
  91. package/dist/cost-guard.js +113 -0
  92. package/dist/cost-guard.js.map +1 -0
  93. package/dist/daemon-entry.d.ts +1 -0
  94. package/dist/daemon-entry.js +29 -0
  95. package/dist/daemon-entry.js.map +1 -0
  96. package/dist/daemon.d.ts +88 -0
  97. package/dist/daemon.js +820 -0
  98. package/dist/daemon.js.map +1 -0
  99. package/dist/daily-summary.d.ts +13 -0
  100. package/dist/daily-summary.js +55 -0
  101. package/dist/daily-summary.js.map +1 -0
  102. package/dist/db.d.ts +10 -0
  103. package/dist/db.js +43 -0
  104. package/dist/db.js.map +1 -0
  105. package/dist/event-log.d.ts +22 -0
  106. package/dist/event-log.js +66 -0
  107. package/dist/event-log.js.map +1 -0
  108. package/dist/export-import.d.ts +2 -0
  109. package/dist/export-import.js +110 -0
  110. package/dist/export-import.js.map +1 -0
  111. package/dist/fleet-context.d.ts +36 -0
  112. package/dist/fleet-context.js +4 -0
  113. package/dist/fleet-context.js.map +1 -0
  114. package/dist/fleet-manager.d.ts +115 -0
  115. package/dist/fleet-manager.js +1742 -0
  116. package/dist/fleet-manager.js.map +1 -0
  117. package/dist/fleet-system-prompt.d.ts +11 -0
  118. package/dist/fleet-system-prompt.js +60 -0
  119. package/dist/fleet-system-prompt.js.map +1 -0
  120. package/dist/hang-detector.d.ts +16 -0
  121. package/dist/hang-detector.js +53 -0
  122. package/dist/hang-detector.js.map +1 -0
  123. package/dist/index.d.ts +8 -0
  124. package/dist/index.js +6 -0
  125. package/dist/index.js.map +1 -0
  126. package/dist/install-recorder.d.ts +30 -0
  127. package/dist/install-recorder.js +159 -0
  128. package/dist/install-recorder.js.map +1 -0
  129. package/dist/logger.d.ts +3 -0
  130. package/dist/logger.js +63 -0
  131. package/dist/logger.js.map +1 -0
  132. package/dist/meeting/orchestrator.d.ts +30 -0
  133. package/dist/meeting/orchestrator.js +355 -0
  134. package/dist/meeting/orchestrator.js.map +1 -0
  135. package/dist/meeting/prompt-builder.d.ts +12 -0
  136. package/dist/meeting/prompt-builder.js +96 -0
  137. package/dist/meeting/prompt-builder.js.map +1 -0
  138. package/dist/meeting/role-assigner.d.ts +2 -0
  139. package/dist/meeting/role-assigner.js +25 -0
  140. package/dist/meeting/role-assigner.js.map +1 -0
  141. package/dist/meeting/types.d.ts +21 -0
  142. package/dist/meeting/types.js +2 -0
  143. package/dist/meeting/types.js.map +1 -0
  144. package/dist/meeting-manager.d.ts +10 -0
  145. package/dist/meeting-manager.js +38 -0
  146. package/dist/meeting-manager.js.map +1 -0
  147. package/dist/memory-layer.d.ts +13 -0
  148. package/dist/memory-layer.js +44 -0
  149. package/dist/memory-layer.js.map +1 -0
  150. package/dist/plugin/agend/.claude-plugin/plugin.json +5 -0
  151. package/dist/plugin/agend/.mcp.json +9 -0
  152. package/dist/plugin/ccd-channel/.claude-plugin/plugin.json +5 -0
  153. package/dist/plugin/ccd-channel/.mcp.json +9 -0
  154. package/dist/process-manager.d.ts +31 -0
  155. package/dist/process-manager.js +264 -0
  156. package/dist/process-manager.js.map +1 -0
  157. package/dist/scheduler/db.d.ts +16 -0
  158. package/dist/scheduler/db.js +132 -0
  159. package/dist/scheduler/db.js.map +1 -0
  160. package/dist/scheduler/db.test.d.ts +1 -0
  161. package/dist/scheduler/db.test.js +92 -0
  162. package/dist/scheduler/db.test.js.map +1 -0
  163. package/dist/scheduler/index.d.ts +4 -0
  164. package/dist/scheduler/index.js +4 -0
  165. package/dist/scheduler/index.js.map +1 -0
  166. package/dist/scheduler/scheduler.d.ts +25 -0
  167. package/dist/scheduler/scheduler.js +119 -0
  168. package/dist/scheduler/scheduler.js.map +1 -0
  169. package/dist/scheduler/scheduler.test.d.ts +1 -0
  170. package/dist/scheduler/scheduler.test.js +119 -0
  171. package/dist/scheduler/scheduler.test.js.map +1 -0
  172. package/dist/scheduler/types.d.ts +47 -0
  173. package/dist/scheduler/types.js +7 -0
  174. package/dist/scheduler/types.js.map +1 -0
  175. package/dist/service-installer.d.ts +14 -0
  176. package/dist/service-installer.js +91 -0
  177. package/dist/service-installer.js.map +1 -0
  178. package/dist/setup-wizard.d.ts +14 -0
  179. package/dist/setup-wizard.js +517 -0
  180. package/dist/setup-wizard.js.map +1 -0
  181. package/dist/stt.d.ts +10 -0
  182. package/dist/stt.js +33 -0
  183. package/dist/stt.js.map +1 -0
  184. package/dist/tmux-manager.d.ts +22 -0
  185. package/dist/tmux-manager.js +131 -0
  186. package/dist/tmux-manager.js.map +1 -0
  187. package/dist/topic-commands.d.ts +22 -0
  188. package/dist/topic-commands.js +176 -0
  189. package/dist/topic-commands.js.map +1 -0
  190. package/dist/transcript-monitor.d.ts +21 -0
  191. package/dist/transcript-monitor.js +149 -0
  192. package/dist/transcript-monitor.js.map +1 -0
  193. package/dist/types.d.ts +153 -0
  194. package/dist/types.js +2 -0
  195. package/dist/types.js.map +1 -0
  196. package/dist/webhook-emitter.d.ts +15 -0
  197. package/dist/webhook-emitter.js +41 -0
  198. package/dist/webhook-emitter.js.map +1 -0
  199. package/package.json +60 -4
  200. package/templates/launchd.plist.ejs +29 -0
  201. package/templates/systemd.service.ejs +15 -0
  202. package/index.js +0 -1
@@ -0,0 +1,131 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const exec = promisify(execFile);
4
+ export class TmuxManager {
5
+ sessionName;
6
+ windowId;
7
+ constructor(sessionName, windowId) {
8
+ this.sessionName = sessionName;
9
+ this.windowId = windowId;
10
+ }
11
+ // === Static session-level methods ===
12
+ static async ensureSession(name) {
13
+ if (await TmuxManager.sessionExists(name))
14
+ return;
15
+ await exec("tmux", ["new-session", "-d", "-s", name]);
16
+ }
17
+ static async sessionExists(name) {
18
+ try {
19
+ await exec("tmux", ["has-session", "-t", name]);
20
+ return true;
21
+ }
22
+ catch {
23
+ return false;
24
+ }
25
+ }
26
+ static async killSession(name) {
27
+ try {
28
+ await exec("tmux", ["kill-session", "-t", name]);
29
+ }
30
+ catch (err) {
31
+ // Expected if session doesn't exist; unexpected errors logged via stderr
32
+ }
33
+ }
34
+ static async listWindows(sessionName) {
35
+ try {
36
+ const { stdout } = await exec("tmux", [
37
+ "list-windows", "-t", sessionName, "-F", "#{window_id}\t#{window_name}"
38
+ ]);
39
+ return stdout.trim().split("\n").filter(Boolean).map(line => {
40
+ const [id, name] = line.split("\t");
41
+ return { id, name };
42
+ });
43
+ }
44
+ catch {
45
+ return [];
46
+ }
47
+ }
48
+ // === Instance window methods ===
49
+ async createWindow(command, cwd, windowName) {
50
+ const args = ["new-window", "-t", this.sessionName, "-c", cwd];
51
+ if (windowName)
52
+ args.push("-n", windowName);
53
+ args.push("-P", "-F", "#{window_id}", command);
54
+ const { stdout } = await exec("tmux", args);
55
+ this.windowId = stdout.trim();
56
+ // Prevent the child process from overriding the window name via escape sequences
57
+ if (windowName) {
58
+ await exec("tmux", ["set-window-option", "-t", `${this.sessionName}:${this.windowId}`, "allow-rename", "off"]).catch(() => { });
59
+ }
60
+ return this.windowId;
61
+ }
62
+ async killWindow() {
63
+ if (!this.windowId)
64
+ return;
65
+ try {
66
+ await exec("tmux", ["kill-window", "-t", `${this.sessionName}:${this.windowId}`]);
67
+ }
68
+ catch (err) {
69
+ // Expected if window already exited
70
+ }
71
+ }
72
+ async isWindowAlive() {
73
+ if (!this.windowId)
74
+ return false;
75
+ try {
76
+ const windows = await TmuxManager.listWindows(this.sessionName);
77
+ return windows.some(w => w.id === this.windowId);
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ async sendKeys(text) {
84
+ try {
85
+ await exec("tmux", ["send-keys", "-l", "-t", `${this.sessionName}:${this.windowId}`, text]);
86
+ return true;
87
+ }
88
+ catch {
89
+ return false;
90
+ }
91
+ }
92
+ async sendSpecialKey(key) {
93
+ try {
94
+ await exec("tmux", ["send-keys", "-t", `${this.sessionName}:${this.windowId}`, key]);
95
+ return true;
96
+ }
97
+ catch {
98
+ return false;
99
+ }
100
+ }
101
+ /** Paste text via bracketed paste (safe for CLIs with single-char hotkeys like Gemini's '!') */
102
+ async pasteText(text) {
103
+ try {
104
+ const target = `${this.sessionName}:${this.windowId}`;
105
+ await exec("tmux", ["set-buffer", "--", text]);
106
+ await exec("tmux", ["paste-buffer", "-t", target, "-p"]);
107
+ // Small delay to let TUI process the bracketed paste before sending Enter
108
+ await new Promise(r => setTimeout(r, 200));
109
+ await exec("tmux", ["send-keys", "-t", target, "Enter"]);
110
+ return true;
111
+ }
112
+ catch {
113
+ return false;
114
+ }
115
+ }
116
+ async pipeOutput(logPath) {
117
+ const escaped = logPath.replace(/'/g, "'\\''");
118
+ await exec("tmux", [
119
+ "pipe-pane", "-t", `${this.sessionName}:${this.windowId}`,
120
+ `cat >> '${escaped}'`,
121
+ ]);
122
+ }
123
+ async capturePane() {
124
+ const { stdout } = await exec("tmux", [
125
+ "capture-pane", "-t", `${this.sessionName}:${this.windowId}`, "-p",
126
+ ]);
127
+ return stdout;
128
+ }
129
+ getWindowId() { return this.windowId; }
130
+ }
131
+ //# sourceMappingURL=tmux-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tmux-manager.js","sourceRoot":"","sources":["../src/tmux-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { FleetContext } from "./fleet-context.js";
2
+ import type { InboundMessage } from "./channel/types.js";
3
+ /** Sanitize a directory name into a valid instance name. Keeps Unicode letters (incl. CJK). */
4
+ export declare function sanitizeInstanceName(name: string): string;
5
+ export declare class TopicCommands {
6
+ private ctx;
7
+ constructor(ctx: FleetContext);
8
+ /** Parse and dispatch commands from the General topic */
9
+ handleGeneralCommand(msg: InboundMessage): Promise<boolean>;
10
+ private handleReloadCommand;
11
+ private handleStatusCommand;
12
+ /** Reply with redirect when message arrives in an unbound topic */
13
+ handleUnboundTopic(msg: InboundMessage): Promise<void>;
14
+ /** Handle topic deletion — stop daemon and remove from config */
15
+ handleTopicDeleted(threadId: number): Promise<void>;
16
+ /** Create instance config, save fleet.yaml, start daemon, connect IPC. */
17
+ bindAndStart(dirPath: string, topicId: number): Promise<string>;
18
+ /** Create Telegram topics for instances that don't have topic_id */
19
+ autoCreateTopics(): Promise<void>;
20
+ /** Register bot commands in Telegram command menu */
21
+ registerBotCommands(): Promise<void>;
22
+ }
@@ -0,0 +1,176 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join, basename } from "node:path";
3
+ import { DEFAULT_INSTANCE_CONFIG } from "./config.js";
4
+ import { formatCents } from "./cost-guard.js";
5
+ /** Sanitize a directory name into a valid instance name. Keeps Unicode letters (incl. CJK). */
6
+ export function sanitizeInstanceName(name) {
7
+ const sanitized = name.toLowerCase().replace(/[^\p{L}\d-]/gu, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
8
+ return sanitized || "project";
9
+ }
10
+ export class TopicCommands {
11
+ ctx;
12
+ constructor(ctx) {
13
+ this.ctx = ctx;
14
+ }
15
+ /** Parse and dispatch commands from the General topic */
16
+ async handleGeneralCommand(msg) {
17
+ const text = msg.text?.trim();
18
+ if (!text)
19
+ return false;
20
+ if (text === "/status" || text === "/status@" || text.startsWith("/status@")) {
21
+ await this.handleStatusCommand(msg);
22
+ return true;
23
+ }
24
+ if (text === "/reload" || text === "/reload@" || text.startsWith("/reload@")) {
25
+ await this.handleReloadCommand(msg);
26
+ return true;
27
+ }
28
+ return false;
29
+ }
30
+ async handleReloadCommand(msg) {
31
+ if (!this.ctx.adapter)
32
+ return;
33
+ const chatId = msg.chatId;
34
+ const threadId = msg.threadId;
35
+ await this.ctx.adapter.sendText(chatId, "🔄 Reloading fleet...", { threadId });
36
+ // SIGUSR1 triggers graceful shutdown; launchd restarts with new code
37
+ process.kill(process.pid, "SIGUSR1");
38
+ }
39
+ async handleStatusCommand(msg) {
40
+ if (!this.ctx.adapter || !this.ctx.fleetConfig)
41
+ return;
42
+ const lines = [];
43
+ for (const [name] of Object.entries(this.ctx.fleetConfig.instances)) {
44
+ const status = this.ctx.getInstanceStatus(name);
45
+ const paused = this.ctx.costGuard?.isLimited(name);
46
+ let contextStr = "-";
47
+ try {
48
+ const data = JSON.parse(readFileSync(join(this.ctx.dataDir, "instances", name, "statusline.json"), "utf-8"));
49
+ if (data.context_window?.used_percentage != null) {
50
+ contextStr = `${Math.round(data.context_window.used_percentage)}%`;
51
+ }
52
+ }
53
+ catch { /* file may not exist yet */ }
54
+ const costCents = this.ctx.costGuard?.getDailyCostCents(name) ?? 0;
55
+ let icon;
56
+ if (paused)
57
+ icon = "⏸";
58
+ else if (status === "running")
59
+ icon = "🟢";
60
+ else if (status === "crashed")
61
+ icon = "🔴";
62
+ else
63
+ icon = "⚪";
64
+ lines.push(`${icon} ${name} — ctx ${contextStr}, ${formatCents(costCents)} today`);
65
+ }
66
+ if (lines.length === 0) {
67
+ lines.push("No instances configured.");
68
+ }
69
+ const limitCents = this.ctx.costGuard?.getLimitCents() ?? 0;
70
+ const totalCents = this.ctx.costGuard?.getFleetTotalCents() ?? 0;
71
+ if (limitCents > 0) {
72
+ lines.push("");
73
+ lines.push(`Fleet: ${formatCents(totalCents)} / ${formatCents(limitCents)} daily`);
74
+ }
75
+ await this.ctx.adapter.sendText(msg.chatId, lines.join("\n"));
76
+ }
77
+ /** Reply with redirect when message arrives in an unbound topic */
78
+ async handleUnboundTopic(msg) {
79
+ if (!this.ctx.adapter)
80
+ return;
81
+ await this.ctx.adapter.sendText(msg.chatId, "This topic is not bound to an instance. Ask the General assistant to create one with create_instance.", { threadId: msg.threadId });
82
+ }
83
+ /** Handle topic deletion — stop daemon and remove from config */
84
+ async handleTopicDeleted(threadId) {
85
+ const target = this.ctx.routingTable.get(threadId);
86
+ if (!target)
87
+ return;
88
+ if (target.kind === "general") {
89
+ this.ctx.logger.debug({ instanceName: target.name, threadId }, "Ignoring delete event for General topic");
90
+ return;
91
+ }
92
+ this.ctx.logger.info({ instanceName: target.name, threadId }, "Topic deleted — auto-unbinding");
93
+ await this.ctx.removeInstance(target.name);
94
+ }
95
+ /** Create instance config, save fleet.yaml, start daemon, connect IPC. */
96
+ async bindAndStart(dirPath, topicId) {
97
+ if (!this.ctx.fleetConfig)
98
+ throw new Error("Fleet config not loaded");
99
+ const instanceName = `${sanitizeInstanceName(basename(dirPath))}-t${topicId}`;
100
+ this.ctx.fleetConfig.instances[instanceName] = {
101
+ working_directory: dirPath,
102
+ topic_id: topicId,
103
+ restart_policy: this.ctx.fleetConfig.defaults.restart_policy ?? DEFAULT_INSTANCE_CONFIG.restart_policy,
104
+ context_guardian: this.ctx.fleetConfig.defaults.context_guardian ?? DEFAULT_INSTANCE_CONFIG.context_guardian,
105
+ log_level: this.ctx.fleetConfig.defaults.log_level ?? DEFAULT_INSTANCE_CONFIG.log_level,
106
+ };
107
+ this.ctx.saveFleetConfig();
108
+ this.ctx.routingTable.set(topicId, { kind: "instance", name: instanceName });
109
+ await this.ctx.startInstance(instanceName, this.ctx.fleetConfig.instances[instanceName], true);
110
+ await new Promise(r => setTimeout(r, 5000));
111
+ await this.ctx.connectIpcToInstance(instanceName);
112
+ this.ctx.logger.info({ instanceName, topicId }, "Topic bound and started");
113
+ return instanceName;
114
+ }
115
+ /** Create Telegram topics for instances that don't have topic_id */
116
+ async autoCreateTopics() {
117
+ if (!this.ctx.fleetConfig?.channel?.group_id)
118
+ return;
119
+ const botToken = process.env[this.ctx.fleetConfig.channel.bot_token_env];
120
+ if (!botToken)
121
+ return;
122
+ let configChanged = false;
123
+ for (const [name, config] of Object.entries(this.ctx.fleetConfig.instances)) {
124
+ if (config.topic_id != null)
125
+ continue;
126
+ // Telegram's native General topic always has thread_id = 1
127
+ if (config.general_topic) {
128
+ config.topic_id = 1;
129
+ configChanged = true;
130
+ this.ctx.logger.info({ name, topicId: 1 }, "Bound to native General topic");
131
+ continue;
132
+ }
133
+ try {
134
+ const topicName = basename(config.working_directory);
135
+ const threadId = await this.ctx.createForumTopic(topicName);
136
+ config.topic_id = threadId;
137
+ configChanged = true;
138
+ this.ctx.logger.info({ name, topicId: config.topic_id, topicName }, "Auto-created Telegram topic");
139
+ }
140
+ catch (err) {
141
+ this.ctx.logger.warn({ name, err }, "Failed to auto-create topic");
142
+ }
143
+ }
144
+ if (configChanged) {
145
+ this.ctx.saveFleetConfig();
146
+ }
147
+ }
148
+ /** Register bot commands in Telegram command menu */
149
+ async registerBotCommands() {
150
+ const groupId = this.ctx.fleetConfig?.channel?.group_id;
151
+ const botTokenEnv = this.ctx.fleetConfig?.channel?.bot_token_env;
152
+ if (!groupId || !botTokenEnv)
153
+ return;
154
+ const botToken = process.env[botTokenEnv];
155
+ if (!botToken)
156
+ return;
157
+ try {
158
+ await fetch(`https://api.telegram.org/bot${botToken}/setMyCommands`, {
159
+ method: "POST",
160
+ headers: { "Content-Type": "application/json" },
161
+ body: JSON.stringify({
162
+ commands: [
163
+ { command: "status", description: "Show fleet status and costs" },
164
+ { command: "reload", description: "Restart fleet with new code" },
165
+ ],
166
+ scope: { type: "chat", chat_id: groupId },
167
+ }),
168
+ });
169
+ this.ctx.logger.info("Registered bot commands: /status");
170
+ }
171
+ catch (err) {
172
+ this.ctx.logger.warn({ err }, "Failed to register bot commands (non-fatal)");
173
+ }
174
+ }
175
+ }
176
+ //# sourceMappingURL=topic-commands.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"topic-commands.js","sourceRoot":"","sources":["../src/topic-commands.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { EventEmitter } from "node:events";
2
+ import type { Logger } from "./logger.js";
3
+ export declare class TranscriptMonitor extends EventEmitter {
4
+ private instanceDir;
5
+ private logger;
6
+ private fd;
7
+ private byteOffset;
8
+ private transcriptPath;
9
+ private pollTimer;
10
+ private offsetFile;
11
+ constructor(instanceDir: string, logger: Logger);
12
+ private loadOffset;
13
+ private saveOffset;
14
+ resolveTranscriptPath(): Promise<string | null>;
15
+ pollIncrement(): Promise<void>;
16
+ private processEntry;
17
+ startPolling(intervalMs?: number): void;
18
+ stop(): void;
19
+ setTranscriptPath(path: string): void;
20
+ resetOffset(): void;
21
+ }
@@ -0,0 +1,149 @@
1
+ import { EventEmitter } from "node:events";
2
+ import { open, stat } from "node:fs/promises";
3
+ import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
+ import { join } from "node:path";
5
+ export class TranscriptMonitor extends EventEmitter {
6
+ instanceDir;
7
+ logger;
8
+ fd = null;
9
+ byteOffset = 0;
10
+ transcriptPath = null;
11
+ pollTimer = null;
12
+ offsetFile;
13
+ constructor(instanceDir, logger) {
14
+ super();
15
+ this.instanceDir = instanceDir;
16
+ this.logger = logger;
17
+ this.offsetFile = join(instanceDir, "transcript-offset");
18
+ this.loadOffset();
19
+ }
20
+ loadOffset() {
21
+ try {
22
+ if (existsSync(this.offsetFile)) {
23
+ const data = JSON.parse(readFileSync(this.offsetFile, "utf-8"));
24
+ this.byteOffset = data.offset ?? 0;
25
+ this.transcriptPath = data.path ?? null;
26
+ }
27
+ }
28
+ catch {
29
+ // Start fresh if corrupt
30
+ }
31
+ }
32
+ saveOffset() {
33
+ try {
34
+ writeFileSync(this.offsetFile, JSON.stringify({
35
+ offset: this.byteOffset,
36
+ path: this.transcriptPath,
37
+ }));
38
+ }
39
+ catch {
40
+ // Non-critical — will re-read some entries on restart
41
+ }
42
+ }
43
+ async resolveTranscriptPath() {
44
+ const statusFile = join(this.instanceDir, "statusline.json");
45
+ if (existsSync(statusFile)) {
46
+ try {
47
+ const data = JSON.parse(readFileSync(statusFile, "utf-8"));
48
+ if (data.transcript_path)
49
+ return data.transcript_path;
50
+ }
51
+ catch {
52
+ // Status file may be partially written — retry on next poll
53
+ }
54
+ }
55
+ return null;
56
+ }
57
+ async pollIncrement() {
58
+ if (!this.transcriptPath) {
59
+ this.transcriptPath = await this.resolveTranscriptPath();
60
+ if (!this.transcriptPath)
61
+ return;
62
+ // If we have a saved offset for a different path, reset
63
+ // If no saved offset, skip to end (first run)
64
+ if (this.byteOffset === 0) {
65
+ try {
66
+ const initial = await stat(this.transcriptPath);
67
+ this.byteOffset = initial.size;
68
+ this.saveOffset();
69
+ return;
70
+ }
71
+ catch {
72
+ return;
73
+ }
74
+ }
75
+ }
76
+ if (!existsSync(this.transcriptPath))
77
+ return;
78
+ try {
79
+ const stats = await stat(this.transcriptPath);
80
+ if (stats.size <= this.byteOffset)
81
+ return;
82
+ const fh = await open(this.transcriptPath, "r");
83
+ try {
84
+ const length = stats.size - this.byteOffset;
85
+ const buffer = Buffer.alloc(length);
86
+ await fh.read(buffer, 0, length, this.byteOffset);
87
+ this.byteOffset = stats.size;
88
+ const text = buffer.toString("utf-8");
89
+ for (const line of text.split("\n")) {
90
+ if (!line.trim())
91
+ continue;
92
+ try {
93
+ const entry = JSON.parse(line);
94
+ this.processEntry(entry);
95
+ }
96
+ catch {
97
+ // Malformed JSONL line in transcript — skip
98
+ }
99
+ }
100
+ this.saveOffset();
101
+ }
102
+ finally {
103
+ await fh.close();
104
+ }
105
+ }
106
+ catch (err) {
107
+ this.logger.debug({ err }, "TranscriptMonitor poll error");
108
+ }
109
+ }
110
+ processEntry(entry) {
111
+ const msg = entry.message;
112
+ if (!msg?.role || !msg?.content)
113
+ return;
114
+ const contents = Array.isArray(msg.content) ? msg.content : [{ type: "text", text: msg.content }];
115
+ for (const block of contents) {
116
+ if (block.type === "tool_use") {
117
+ this.emit("tool_use", block.name ?? "unknown", block.input ?? {});
118
+ }
119
+ else if (block.type === "tool_result") {
120
+ this.emit("tool_result", block.tool_use_id ?? "unknown", block.content);
121
+ }
122
+ else if (block.type === "text" && msg.role === "assistant" && block.text?.trim()) {
123
+ this.emit("assistant_text", block.text);
124
+ }
125
+ }
126
+ }
127
+ startPolling(intervalMs = 2000) {
128
+ this.pollTimer = setInterval(() => this.pollIncrement(), intervalMs);
129
+ }
130
+ stop() {
131
+ if (this.pollTimer) {
132
+ clearInterval(this.pollTimer);
133
+ this.pollTimer = null;
134
+ }
135
+ this.saveOffset();
136
+ }
137
+ setTranscriptPath(path) {
138
+ if (this.transcriptPath !== path) {
139
+ this.resetOffset();
140
+ }
141
+ this.transcriptPath = path;
142
+ }
143
+ resetOffset() {
144
+ this.byteOffset = 0;
145
+ this.transcriptPath = null;
146
+ this.saveOffset();
147
+ }
148
+ }
149
+ //# sourceMappingURL=transcript-monitor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transcript-monitor.js","sourceRoot":"","sources":["../src/transcript-monitor.ts"],"names":[],"mappings":"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"}