@agentmeshhq/agent 0.1.10 → 0.1.12
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/dist/__tests__/injector.test.d.ts +1 -0
- package/dist/__tests__/injector.test.js +26 -0
- package/dist/__tests__/injector.test.js.map +1 -0
- package/dist/cli/build.d.ts +6 -0
- package/dist/cli/build.js +111 -0
- package/dist/cli/build.js.map +1 -0
- package/dist/cli/deploy.d.ts +9 -0
- package/dist/cli/deploy.js +130 -0
- package/dist/cli/deploy.js.map +1 -0
- package/dist/cli/index.js +155 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/list.js +18 -6
- package/dist/cli/list.js.map +1 -1
- package/dist/cli/local.d.ts +9 -0
- package/dist/cli/local.js +139 -0
- package/dist/cli/local.js.map +1 -0
- package/dist/cli/migrate.d.ts +8 -0
- package/dist/cli/migrate.js +167 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli/slack.d.ts +3 -0
- package/dist/cli/slack.js +57 -0
- package/dist/cli/slack.js.map +1 -0
- package/dist/cli/start.d.ts +4 -0
- package/dist/cli/start.js +5 -0
- package/dist/cli/start.js.map +1 -1
- package/dist/cli/test.d.ts +8 -0
- package/dist/cli/test.js +110 -0
- package/dist/cli/test.js.map +1 -0
- package/dist/config/schema.d.ts +2 -0
- package/dist/core/daemon.d.ts +13 -0
- package/dist/core/daemon.js +100 -27
- package/dist/core/daemon.js.map +1 -1
- package/dist/core/injector.d.ts +6 -1
- package/dist/core/injector.js +64 -1
- package/dist/core/injector.js.map +1 -1
- package/dist/core/tmux.js +11 -13
- package/dist/core/tmux.js.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/injector.test.ts +29 -0
- package/src/cli/build.ts +137 -0
- package/src/cli/deploy.ts +153 -0
- package/src/cli/index.ts +156 -0
- package/src/cli/list.ts +18 -6
- package/src/cli/local.ts +174 -0
- package/src/cli/migrate.ts +210 -0
- package/src/cli/slack.ts +69 -0
- package/src/cli/start.ts +8 -0
- package/src/cli/test.ts +141 -0
- package/src/config/schema.ts +2 -0
- package/src/core/daemon.ts +123 -35
- package/src/core/injector.ts +98 -1
- package/src/core/tmux.ts +12 -14
package/src/core/daemon.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { execSync } from "node:child_process";
|
|
1
|
+
import { type ChildProcess, execSync, spawn } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
@@ -40,6 +40,10 @@ export interface DaemonOptions {
|
|
|
40
40
|
restoreContext?: boolean;
|
|
41
41
|
/** Auto-clone repository for project assignments */
|
|
42
42
|
autoSetup?: boolean;
|
|
43
|
+
/** Run opencode serve instead of tmux TUI (for Integration Service) */
|
|
44
|
+
serve?: boolean;
|
|
45
|
+
/** Port for opencode serve (default: 3001) */
|
|
46
|
+
servePort?: number;
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
export class AgentDaemon {
|
|
@@ -52,8 +56,12 @@ export class AgentDaemon {
|
|
|
52
56
|
private token: string | null = null;
|
|
53
57
|
private agentId: string | null = null;
|
|
54
58
|
private isRunning = false;
|
|
59
|
+
private assignedProject: string | undefined;
|
|
55
60
|
private shouldRestoreContext: boolean;
|
|
56
61
|
private autoSetup: boolean;
|
|
62
|
+
private serveMode: boolean;
|
|
63
|
+
private servePort: number;
|
|
64
|
+
private serveProcess: ChildProcess | null = null;
|
|
57
65
|
|
|
58
66
|
constructor(options: DaemonOptions) {
|
|
59
67
|
const config = loadConfig();
|
|
@@ -84,6 +92,8 @@ export class AgentDaemon {
|
|
|
84
92
|
if (options.model) agentConfig.model = options.model;
|
|
85
93
|
|
|
86
94
|
this.agentConfig = agentConfig;
|
|
95
|
+
this.serveMode = options.serve === true;
|
|
96
|
+
this.servePort = options.servePort || 3001;
|
|
87
97
|
|
|
88
98
|
// Build runner configuration with model resolution
|
|
89
99
|
this.runnerConfig = buildRunnerConfig({
|
|
@@ -109,6 +119,7 @@ export class AgentDaemon {
|
|
|
109
119
|
// Register with hub first (needed for assignment check)
|
|
110
120
|
console.log("Registering with AgentMesh hub...");
|
|
111
121
|
const existingState = getAgentState(this.agentName);
|
|
122
|
+
console.log(`Existing state: ${existingState ? `agentId=${existingState.agentId}` : "none"}`);
|
|
112
123
|
|
|
113
124
|
const registration = await registerAgent({
|
|
114
125
|
url: this.config.hubUrl,
|
|
@@ -127,39 +138,45 @@ export class AgentDaemon {
|
|
|
127
138
|
// Check assignments and auto-setup workdir if needed (before creating tmux session)
|
|
128
139
|
await this.checkAssignments();
|
|
129
140
|
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
// Create tmux session if it doesn't exist
|
|
135
|
-
if (!sessionAlreadyExists) {
|
|
136
|
-
console.log(`Creating tmux session: ${sessionName}`);
|
|
137
|
-
|
|
138
|
-
// Include runner env vars (e.g., OPENCODE_MODEL) at session creation
|
|
139
|
-
const created = createSession(
|
|
140
|
-
this.agentName,
|
|
141
|
-
this.agentConfig.command,
|
|
142
|
-
this.agentConfig.workdir,
|
|
143
|
-
this.runnerConfig.env, // Apply model env at process start
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
if (!created) {
|
|
147
|
-
throw new Error("Failed to create tmux session");
|
|
148
|
-
}
|
|
141
|
+
// Serve mode: start opencode serve instead of tmux
|
|
142
|
+
if (this.serveMode) {
|
|
143
|
+
await this.startServeMode();
|
|
149
144
|
} else {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
// Check if session already exists
|
|
146
|
+
const sessionName = getSessionName(this.agentName);
|
|
147
|
+
const sessionAlreadyExists = sessionExists(sessionName);
|
|
148
|
+
|
|
149
|
+
// Create tmux session if it doesn't exist
|
|
150
|
+
if (!sessionAlreadyExists) {
|
|
151
|
+
console.log(`Creating tmux session: ${sessionName}`);
|
|
152
|
+
|
|
153
|
+
// Include runner env vars (e.g., OPENCODE_MODEL) at session creation
|
|
154
|
+
const created = createSession(
|
|
155
|
+
this.agentName,
|
|
156
|
+
this.agentConfig.command,
|
|
157
|
+
this.agentConfig.workdir,
|
|
158
|
+
this.runnerConfig.env, // Apply model env at process start
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (!created) {
|
|
162
|
+
throw new Error("Failed to create tmux session");
|
|
163
|
+
}
|
|
164
|
+
} else {
|
|
165
|
+
console.log(`Reconnecting to existing session: ${sessionName}`);
|
|
166
|
+
// Update environment for existing session
|
|
167
|
+
updateSessionEnvironment(this.agentName, this.runnerConfig.env);
|
|
168
|
+
}
|
|
154
169
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
170
|
+
// Inject environment variables into tmux session
|
|
171
|
+
console.log("Injecting environment variables...");
|
|
172
|
+
updateSessionEnvironment(this.agentName, {
|
|
173
|
+
AGENT_TOKEN: this.token,
|
|
174
|
+
AGENTMESH_AGENT_ID: this.agentId,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
161
177
|
|
|
162
178
|
// Save state including runtime model info
|
|
179
|
+
const sessionName = this.serveMode ? `serve:${this.servePort}` : getSessionName(this.agentName);
|
|
163
180
|
addAgentToState({
|
|
164
181
|
name: this.agentName,
|
|
165
182
|
agentId: this.agentId,
|
|
@@ -167,6 +184,8 @@ export class AgentDaemon {
|
|
|
167
184
|
tmuxSession: sessionName,
|
|
168
185
|
startedAt: new Date().toISOString(),
|
|
169
186
|
token: this.token,
|
|
187
|
+
workdir: this.agentConfig.workdir,
|
|
188
|
+
assignedProject: this.assignedProject,
|
|
170
189
|
runtimeModel: this.runnerConfig.model,
|
|
171
190
|
runnerType: this.runnerConfig.type,
|
|
172
191
|
});
|
|
@@ -208,6 +227,7 @@ export class AgentDaemon {
|
|
|
208
227
|
url: `${wsUrl}/ws/v1`,
|
|
209
228
|
token: newToken,
|
|
210
229
|
onMessage: (event) => {
|
|
230
|
+
console.log(`[WS] Received event: ${event.type}`);
|
|
211
231
|
handleWebSocketEvent(this.agentName, event);
|
|
212
232
|
},
|
|
213
233
|
onConnect: () => {
|
|
@@ -234,7 +254,11 @@ export class AgentDaemon {
|
|
|
234
254
|
url: `${wsUrl}/ws/v1`,
|
|
235
255
|
token: this.token,
|
|
236
256
|
onMessage: (event) => {
|
|
237
|
-
|
|
257
|
+
console.log(`[WS] Received event: ${event.type}`);
|
|
258
|
+
handleWebSocketEvent(this.agentName, event, {
|
|
259
|
+
hubUrl: this.config.hubUrl,
|
|
260
|
+
token: this.token ?? undefined,
|
|
261
|
+
});
|
|
238
262
|
},
|
|
239
263
|
onConnect: () => {
|
|
240
264
|
console.log("WebSocket connected");
|
|
@@ -313,16 +337,78 @@ Nudge agent:
|
|
|
313
337
|
this.ws = null;
|
|
314
338
|
}
|
|
315
339
|
|
|
316
|
-
//
|
|
317
|
-
|
|
340
|
+
// Stop serve process or destroy tmux session
|
|
341
|
+
if (this.serveMode && this.serveProcess) {
|
|
342
|
+
console.log("Stopping opencode serve...");
|
|
343
|
+
this.serveProcess.kill("SIGTERM");
|
|
344
|
+
this.serveProcess = null;
|
|
345
|
+
} else {
|
|
346
|
+
destroySession(this.agentName);
|
|
347
|
+
}
|
|
318
348
|
|
|
319
|
-
//
|
|
320
|
-
|
|
349
|
+
// Update state to mark as stopped but preserve agentId for next restart
|
|
350
|
+
updateAgentInState(this.agentName, {
|
|
351
|
+
pid: 0,
|
|
352
|
+
tmuxSession: "",
|
|
353
|
+
startedAt: "",
|
|
354
|
+
token: undefined,
|
|
355
|
+
});
|
|
321
356
|
|
|
322
357
|
console.log("Agent stopped.");
|
|
323
358
|
process.exit(0);
|
|
324
359
|
}
|
|
325
360
|
|
|
361
|
+
/**
|
|
362
|
+
* Starts opencode serve mode (for Integration Service)
|
|
363
|
+
* Replaces tmux with a direct HTTP server
|
|
364
|
+
*/
|
|
365
|
+
private async startServeMode(): Promise<void> {
|
|
366
|
+
console.log(`Starting opencode serve mode on port ${this.servePort}...`);
|
|
367
|
+
|
|
368
|
+
const workdir = this.agentConfig.workdir || process.cwd();
|
|
369
|
+
|
|
370
|
+
// Build environment for opencode serve
|
|
371
|
+
const env: Record<string, string> = {
|
|
372
|
+
...process.env,
|
|
373
|
+
...this.runnerConfig.env,
|
|
374
|
+
AGENT_TOKEN: this.token!,
|
|
375
|
+
AGENTMESH_AGENT_ID: this.agentId!,
|
|
376
|
+
} as Record<string, string>;
|
|
377
|
+
|
|
378
|
+
// Spawn opencode serve as a child process
|
|
379
|
+
this.serveProcess = spawn(
|
|
380
|
+
"opencode",
|
|
381
|
+
["serve", "--port", String(this.servePort), "--hostname", "0.0.0.0"],
|
|
382
|
+
{
|
|
383
|
+
cwd: workdir,
|
|
384
|
+
env,
|
|
385
|
+
stdio: ["ignore", "inherit", "inherit"],
|
|
386
|
+
},
|
|
387
|
+
);
|
|
388
|
+
|
|
389
|
+
// Handle process exit
|
|
390
|
+
this.serveProcess.on("exit", (code, signal) => {
|
|
391
|
+
console.error(`opencode serve exited with code ${code}, signal ${signal}`);
|
|
392
|
+
if (this.isRunning) {
|
|
393
|
+
console.log("Restarting opencode serve in 5 seconds...");
|
|
394
|
+
setTimeout(() => {
|
|
395
|
+
if (this.isRunning) {
|
|
396
|
+
this.startServeMode().catch(console.error);
|
|
397
|
+
}
|
|
398
|
+
}, 5000);
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
this.serveProcess.on("error", (error) => {
|
|
403
|
+
console.error("Failed to start opencode serve:", error);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Wait a moment for the server to start
|
|
407
|
+
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
408
|
+
|
|
409
|
+
console.log(`opencode serve started on http://0.0.0.0:${this.servePort}`);
|
|
410
|
+
}
|
|
411
|
+
|
|
326
412
|
/**
|
|
327
413
|
* Saves the current agent context to disk
|
|
328
414
|
*/
|
|
@@ -375,6 +461,8 @@ Nudge agent:
|
|
|
375
461
|
console.log(` - ${assignment.project.name} (${assignment.role})${repoInfo}${workdirInfo}`);
|
|
376
462
|
}
|
|
377
463
|
|
|
464
|
+
this.assignedProject = assignments[0]?.project.name;
|
|
465
|
+
|
|
378
466
|
// If no CLI workdir specified, try to use project.workdir from HQ
|
|
379
467
|
if (!this.agentConfig.workdir) {
|
|
380
468
|
const assignmentWithWorkdir = assignments.find((a) => a.project.workdir);
|
package/src/core/injector.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
1
3
|
import { formatHandoffContextSummary, parseHandoffContext } from "../context/handoff.js";
|
|
2
4
|
import type { AgentContext } from "../context/schema.js";
|
|
3
5
|
import type { InboxItem } from "./registry.js";
|
|
@@ -65,6 +67,85 @@ You can now proceed with your work.`;
|
|
|
65
67
|
sendKeys(agentName, message);
|
|
66
68
|
}
|
|
67
69
|
|
|
70
|
+
export interface EventContext {
|
|
71
|
+
hubUrl?: string;
|
|
72
|
+
token?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface SlackFile {
|
|
76
|
+
name: string;
|
|
77
|
+
type: string;
|
|
78
|
+
url: string;
|
|
79
|
+
permalink: string;
|
|
80
|
+
base64?: string;
|
|
81
|
+
mediaType?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function injectSlackMessage(
|
|
85
|
+
agentName: string,
|
|
86
|
+
event: WebSocketEvent,
|
|
87
|
+
context?: EventContext,
|
|
88
|
+
): void {
|
|
89
|
+
const user = (event.user as string) || "unknown";
|
|
90
|
+
const channel = (event.channel as string) || "unknown";
|
|
91
|
+
const text = (event.text as string) || "";
|
|
92
|
+
const files = (event.files as SlackFile[] | undefined) || [];
|
|
93
|
+
|
|
94
|
+
// Build message with file info
|
|
95
|
+
let message = `[Slack from ${user} in ${channel}] ${text}`;
|
|
96
|
+
|
|
97
|
+
if (files.length > 0) {
|
|
98
|
+
const savedImages: string[] = [];
|
|
99
|
+
const otherFiles: string[] = [];
|
|
100
|
+
|
|
101
|
+
// Save images with base64 data to disk
|
|
102
|
+
const attachmentsDir = join(process.env.HOME || "/tmp", ".agentmesh", "attachments");
|
|
103
|
+
if (!existsSync(attachmentsDir)) {
|
|
104
|
+
mkdirSync(attachmentsDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
for (const f of files) {
|
|
108
|
+
console.log(
|
|
109
|
+
`[Injector] File: ${f.name}, type: ${f.type}, mediaType: ${f.mediaType}, has base64: ${!!f.base64}`,
|
|
110
|
+
);
|
|
111
|
+
if (f.base64 && (f.mediaType?.startsWith("image/") || f.type?.startsWith("image/"))) {
|
|
112
|
+
// Save image to disk
|
|
113
|
+
const timestamp = Date.now();
|
|
114
|
+
const safeName = f.name.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
115
|
+
const filePath = join(attachmentsDir, `${timestamp}-${safeName}`);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
writeFileSync(filePath, Buffer.from(f.base64, "base64"));
|
|
119
|
+
savedImages.push(filePath);
|
|
120
|
+
console.log(`[Injector] Saved image to: ${filePath}`);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error(`[Injector] Failed to save image:`, err);
|
|
123
|
+
otherFiles.push(`${f.name} (failed to save)`);
|
|
124
|
+
}
|
|
125
|
+
} else {
|
|
126
|
+
// Just reference the URL for non-image files
|
|
127
|
+
otherFiles.push(`${f.name}: ${f.permalink}`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Add instruction to view images - OpenCode needs explicit instruction
|
|
132
|
+
if (savedImages.length > 0) {
|
|
133
|
+
message += `\n\nUser attached ${savedImages.length} image(s). Use the Read tool to view them:`;
|
|
134
|
+
for (const img of savedImages) {
|
|
135
|
+
message += `\n- ${img}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (otherFiles.length > 0) {
|
|
140
|
+
message += ` [Other files: ${otherFiles.join(" | ")}]`;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
console.log(`[Injector] Sending to tmux: ${message}`);
|
|
145
|
+
const result = sendKeys(agentName, message);
|
|
146
|
+
console.log(`[Injector] sendKeys result: ${result}`);
|
|
147
|
+
}
|
|
148
|
+
|
|
68
149
|
export function injectInboxItems(agentName: string, items: InboxItem[]): void {
|
|
69
150
|
if (items.length === 0) {
|
|
70
151
|
sendKeys(agentName, "[AgentMesh] Your inbox is empty.");
|
|
@@ -85,7 +166,11 @@ export function injectInboxItems(agentName: string, items: InboxItem[]): void {
|
|
|
85
166
|
sendKeys(agentName, message);
|
|
86
167
|
}
|
|
87
168
|
|
|
88
|
-
export function handleWebSocketEvent(
|
|
169
|
+
export function handleWebSocketEvent(
|
|
170
|
+
agentName: string,
|
|
171
|
+
event: WebSocketEvent,
|
|
172
|
+
context?: EventContext,
|
|
173
|
+
): void {
|
|
89
174
|
switch (event.type) {
|
|
90
175
|
case "handoff_received":
|
|
91
176
|
case "handoff.received":
|
|
@@ -102,6 +187,18 @@ export function handleWebSocketEvent(agentName: string, event: WebSocketEvent):
|
|
|
102
187
|
injectBlockerResolved(agentName, event);
|
|
103
188
|
break;
|
|
104
189
|
|
|
190
|
+
case "slack.message":
|
|
191
|
+
console.log(`[Injector] Handling slack.message for ${agentName}`);
|
|
192
|
+
console.log(`[Injector] Event keys: ${Object.keys(event).join(", ")}`);
|
|
193
|
+
if (event.files) {
|
|
194
|
+
const files = event.files as Array<{ name: string; base64?: string }>;
|
|
195
|
+
console.log(
|
|
196
|
+
`[Injector] Files count: ${files.length}, has base64: ${files.some((f) => !!f.base64)}`,
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
injectSlackMessage(agentName, event, context);
|
|
200
|
+
break;
|
|
201
|
+
|
|
105
202
|
default:
|
|
106
203
|
// Unknown event type, ignore
|
|
107
204
|
break;
|
package/src/core/tmux.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ChildProcess, execSync, spawn } from "node:child_process";
|
|
1
|
+
import { type ChildProcess, execFileSync, execSync, spawn } from "node:child_process";
|
|
2
2
|
|
|
3
3
|
const SESSION_PREFIX = "agentmesh-";
|
|
4
4
|
|
|
@@ -56,7 +56,8 @@ export function createSession(
|
|
|
56
56
|
|
|
57
57
|
const fullCommand = `${envPrefix}${command}`;
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
// Set reasonable terminal size for TUI applications
|
|
60
|
+
const args = ["new-session", "-d", "-s", sessionName, "-x", "200", "-y", "50"];
|
|
60
61
|
|
|
61
62
|
if (workdir) {
|
|
62
63
|
args.push("-c", workdir);
|
|
@@ -64,7 +65,7 @@ export function createSession(
|
|
|
64
65
|
|
|
65
66
|
args.push(fullCommand);
|
|
66
67
|
|
|
67
|
-
|
|
68
|
+
execFileSync("tmux", args);
|
|
68
69
|
|
|
69
70
|
// Also set session environment for any subsequent processes/refreshes
|
|
70
71
|
if (env) {
|
|
@@ -82,7 +83,7 @@ export function setSessionEnvironment(sessionName: string, env: SessionEnv): boo
|
|
|
82
83
|
try {
|
|
83
84
|
for (const [key, value] of Object.entries(env)) {
|
|
84
85
|
if (value !== undefined && value !== "") {
|
|
85
|
-
|
|
86
|
+
execFileSync("tmux", ["set-environment", "-t", sessionName, key, value]);
|
|
86
87
|
}
|
|
87
88
|
}
|
|
88
89
|
return true;
|
|
@@ -128,16 +129,13 @@ export function sendKeys(agentName: string, message: string): boolean {
|
|
|
128
129
|
}
|
|
129
130
|
|
|
130
131
|
try {
|
|
131
|
-
//
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
// Send the message, then press Enter separately to submit to the AI
|
|
139
|
-
execSync(`tmux send-keys -t "${sessionName}" "${escapedMessage}"`);
|
|
140
|
-
execSync(`tmux send-keys -t "${sessionName}" Enter`);
|
|
132
|
+
// Replace newlines with " | " to keep message on single line (newlines would act as Enter)
|
|
133
|
+
const cleanMessage = message.replace(/\n/g, " | ");
|
|
134
|
+
|
|
135
|
+
// Use execFileSync with array args to avoid shell escaping issues
|
|
136
|
+
// The -l flag sends keys literally
|
|
137
|
+
execFileSync("tmux", ["send-keys", "-t", sessionName, "-l", cleanMessage]);
|
|
138
|
+
execFileSync("tmux", ["send-keys", "-t", sessionName, "Enter"]);
|
|
141
139
|
return true;
|
|
142
140
|
} catch (error) {
|
|
143
141
|
console.error(`Failed to send keys: ${error}`);
|