@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.
- package/README.md +557 -1
- package/README.zh-TW.md +504 -0
- package/dist/access-path.d.ts +7 -0
- package/dist/access-path.js +12 -0
- package/dist/access-path.js.map +1 -0
- package/dist/approval/approval-server.d.ts +30 -0
- package/dist/approval/approval-server.js +156 -0
- package/dist/approval/approval-server.js.map +1 -0
- package/dist/approval/tmux-prompt-detector.d.ts +34 -0
- package/dist/approval/tmux-prompt-detector.js +264 -0
- package/dist/approval/tmux-prompt-detector.js.map +1 -0
- package/dist/backend/approval-strategy.d.ts +14 -0
- package/dist/backend/approval-strategy.js +2 -0
- package/dist/backend/approval-strategy.js.map +1 -0
- package/dist/backend/claude-code.d.ts +13 -0
- package/dist/backend/claude-code.js +114 -0
- package/dist/backend/claude-code.js.map +1 -0
- package/dist/backend/codex.d.ts +10 -0
- package/dist/backend/codex.js +58 -0
- package/dist/backend/codex.js.map +1 -0
- package/dist/backend/factory.d.ts +2 -0
- package/dist/backend/factory.js +19 -0
- package/dist/backend/factory.js.map +1 -0
- package/dist/backend/gemini-cli.d.ts +10 -0
- package/dist/backend/gemini-cli.js +68 -0
- package/dist/backend/gemini-cli.js.map +1 -0
- package/dist/backend/hook-based-approval.d.ts +20 -0
- package/dist/backend/hook-based-approval.js +41 -0
- package/dist/backend/hook-based-approval.js.map +1 -0
- package/dist/backend/index.d.ts +6 -0
- package/dist/backend/index.js +6 -0
- package/dist/backend/index.js.map +1 -0
- package/dist/backend/opencode.d.ts +10 -0
- package/dist/backend/opencode.js +63 -0
- package/dist/backend/opencode.js.map +1 -0
- package/dist/backend/types.d.ts +26 -0
- package/dist/backend/types.js +2 -0
- package/dist/backend/types.js.map +1 -0
- package/dist/channel/access-manager.d.ts +18 -0
- package/dist/channel/access-manager.js +149 -0
- package/dist/channel/access-manager.js.map +1 -0
- package/dist/channel/adapters/discord.d.ts +45 -0
- package/dist/channel/adapters/discord.js +366 -0
- package/dist/channel/adapters/discord.js.map +1 -0
- package/dist/channel/adapters/telegram.d.ts +58 -0
- package/dist/channel/adapters/telegram.js +569 -0
- package/dist/channel/adapters/telegram.js.map +1 -0
- package/dist/channel/attachment-handler.d.ts +15 -0
- package/dist/channel/attachment-handler.js +55 -0
- package/dist/channel/attachment-handler.js.map +1 -0
- package/dist/channel/factory.d.ts +12 -0
- package/dist/channel/factory.js +38 -0
- package/dist/channel/factory.js.map +1 -0
- package/dist/channel/ipc-bridge.d.ts +26 -0
- package/dist/channel/ipc-bridge.js +170 -0
- package/dist/channel/ipc-bridge.js.map +1 -0
- package/dist/channel/mcp-server.d.ts +10 -0
- package/dist/channel/mcp-server.js +196 -0
- package/dist/channel/mcp-server.js.map +1 -0
- package/dist/channel/mcp-tools.d.ts +909 -0
- package/dist/channel/mcp-tools.js +346 -0
- package/dist/channel/mcp-tools.js.map +1 -0
- package/dist/channel/message-bus.d.ts +17 -0
- package/dist/channel/message-bus.js +86 -0
- package/dist/channel/message-bus.js.map +1 -0
- package/dist/channel/message-queue.d.ts +39 -0
- package/dist/channel/message-queue.js +248 -0
- package/dist/channel/message-queue.js.map +1 -0
- package/dist/channel/tool-router.d.ts +6 -0
- package/dist/channel/tool-router.js +69 -0
- package/dist/channel/tool-router.js.map +1 -0
- package/dist/channel/tool-tracker.d.ts +13 -0
- package/dist/channel/tool-tracker.js +58 -0
- package/dist/channel/tool-tracker.js.map +1 -0
- package/dist/channel/types.d.ts +116 -0
- package/dist/channel/types.js +2 -0
- package/dist/channel/types.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +782 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +85 -0
- package/dist/config.js.map +1 -0
- package/dist/container-manager.d.ts +24 -0
- package/dist/container-manager.js +148 -0
- package/dist/container-manager.js.map +1 -0
- package/dist/context-guardian.d.ts +29 -0
- package/dist/context-guardian.js +123 -0
- package/dist/context-guardian.js.map +1 -0
- package/dist/cost-guard.d.ts +21 -0
- package/dist/cost-guard.js +113 -0
- package/dist/cost-guard.js.map +1 -0
- package/dist/daemon-entry.d.ts +1 -0
- package/dist/daemon-entry.js +29 -0
- package/dist/daemon-entry.js.map +1 -0
- package/dist/daemon.d.ts +88 -0
- package/dist/daemon.js +820 -0
- package/dist/daemon.js.map +1 -0
- package/dist/daily-summary.d.ts +13 -0
- package/dist/daily-summary.js +55 -0
- package/dist/daily-summary.js.map +1 -0
- package/dist/db.d.ts +10 -0
- package/dist/db.js +43 -0
- package/dist/db.js.map +1 -0
- package/dist/event-log.d.ts +22 -0
- package/dist/event-log.js +66 -0
- package/dist/event-log.js.map +1 -0
- package/dist/export-import.d.ts +2 -0
- package/dist/export-import.js +110 -0
- package/dist/export-import.js.map +1 -0
- package/dist/fleet-context.d.ts +36 -0
- package/dist/fleet-context.js +4 -0
- package/dist/fleet-context.js.map +1 -0
- package/dist/fleet-manager.d.ts +115 -0
- package/dist/fleet-manager.js +1742 -0
- package/dist/fleet-manager.js.map +1 -0
- package/dist/fleet-system-prompt.d.ts +11 -0
- package/dist/fleet-system-prompt.js +60 -0
- package/dist/fleet-system-prompt.js.map +1 -0
- package/dist/hang-detector.d.ts +16 -0
- package/dist/hang-detector.js +53 -0
- package/dist/hang-detector.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/install-recorder.d.ts +30 -0
- package/dist/install-recorder.js +159 -0
- package/dist/install-recorder.js.map +1 -0
- package/dist/logger.d.ts +3 -0
- package/dist/logger.js +63 -0
- package/dist/logger.js.map +1 -0
- package/dist/meeting/orchestrator.d.ts +30 -0
- package/dist/meeting/orchestrator.js +355 -0
- package/dist/meeting/orchestrator.js.map +1 -0
- package/dist/meeting/prompt-builder.d.ts +12 -0
- package/dist/meeting/prompt-builder.js +96 -0
- package/dist/meeting/prompt-builder.js.map +1 -0
- package/dist/meeting/role-assigner.d.ts +2 -0
- package/dist/meeting/role-assigner.js +25 -0
- package/dist/meeting/role-assigner.js.map +1 -0
- package/dist/meeting/types.d.ts +21 -0
- package/dist/meeting/types.js +2 -0
- package/dist/meeting/types.js.map +1 -0
- package/dist/meeting-manager.d.ts +10 -0
- package/dist/meeting-manager.js +38 -0
- package/dist/meeting-manager.js.map +1 -0
- package/dist/memory-layer.d.ts +13 -0
- package/dist/memory-layer.js +44 -0
- package/dist/memory-layer.js.map +1 -0
- package/dist/plugin/agend/.claude-plugin/plugin.json +5 -0
- package/dist/plugin/agend/.mcp.json +9 -0
- package/dist/plugin/ccd-channel/.claude-plugin/plugin.json +5 -0
- package/dist/plugin/ccd-channel/.mcp.json +9 -0
- package/dist/process-manager.d.ts +31 -0
- package/dist/process-manager.js +264 -0
- package/dist/process-manager.js.map +1 -0
- package/dist/scheduler/db.d.ts +16 -0
- package/dist/scheduler/db.js +132 -0
- package/dist/scheduler/db.js.map +1 -0
- package/dist/scheduler/db.test.d.ts +1 -0
- package/dist/scheduler/db.test.js +92 -0
- package/dist/scheduler/db.test.js.map +1 -0
- package/dist/scheduler/index.d.ts +4 -0
- package/dist/scheduler/index.js +4 -0
- package/dist/scheduler/index.js.map +1 -0
- package/dist/scheduler/scheduler.d.ts +25 -0
- package/dist/scheduler/scheduler.js +119 -0
- package/dist/scheduler/scheduler.js.map +1 -0
- package/dist/scheduler/scheduler.test.d.ts +1 -0
- package/dist/scheduler/scheduler.test.js +119 -0
- package/dist/scheduler/scheduler.test.js.map +1 -0
- package/dist/scheduler/types.d.ts +47 -0
- package/dist/scheduler/types.js +7 -0
- package/dist/scheduler/types.js.map +1 -0
- package/dist/service-installer.d.ts +14 -0
- package/dist/service-installer.js +91 -0
- package/dist/service-installer.js.map +1 -0
- package/dist/setup-wizard.d.ts +14 -0
- package/dist/setup-wizard.js +517 -0
- package/dist/setup-wizard.js.map +1 -0
- package/dist/stt.d.ts +10 -0
- package/dist/stt.js +33 -0
- package/dist/stt.js.map +1 -0
- package/dist/tmux-manager.d.ts +22 -0
- package/dist/tmux-manager.js +131 -0
- package/dist/tmux-manager.js.map +1 -0
- package/dist/topic-commands.d.ts +22 -0
- package/dist/topic-commands.js +176 -0
- package/dist/topic-commands.js.map +1 -0
- package/dist/transcript-monitor.d.ts +21 -0
- package/dist/transcript-monitor.js +149 -0
- package/dist/transcript-monitor.js.map +1 -0
- package/dist/types.d.ts +153 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/webhook-emitter.d.ts +15 -0
- package/dist/webhook-emitter.js +41 -0
- package/dist/webhook-emitter.js.map +1 -0
- package/package.json +60 -4
- package/templates/launchd.plist.ejs +29 -0
- package/templates/systemd.service.ejs +15 -0
- 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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"}
|