agent-coord-mcp 0.3.7 → 0.3.8
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/package.json +1 -1
- package/scripts/coord-chat.mjs +73 -6
package/package.json
CHANGED
package/scripts/coord-chat.mjs
CHANGED
|
@@ -80,16 +80,65 @@ function agentColor(id) {
|
|
|
80
80
|
|
|
81
81
|
await register();
|
|
82
82
|
|
|
83
|
+
// Visual separator above the prompt — delimits "typing area starts here."
|
|
84
|
+
// Embedded in the prompt string itself so readline owns the redraw; no
|
|
85
|
+
// scroll-region gymnastics needed (those don't survive tmux cleanly).
|
|
86
|
+
const TTY = !!process.stdout.isTTY;
|
|
87
|
+
let COLS = process.stdout.columns || 80;
|
|
88
|
+
const sepLine = () => A.dim("─".repeat(Math.max(10, COLS)));
|
|
89
|
+
|
|
90
|
+
// inputAreaReady flips to true after the banner + initial drain finish and
|
|
91
|
+
// we lay down the first separator. From that point say() treats the line
|
|
92
|
+
// above the prompt as a separator slot it owns.
|
|
93
|
+
let inputAreaReady = false;
|
|
94
|
+
|
|
95
|
+
if (TTY) {
|
|
96
|
+
process.stdout.on("resize", () => {
|
|
97
|
+
COLS = process.stdout.columns || 80;
|
|
98
|
+
if (typeof rl !== "undefined") {
|
|
99
|
+
rl.setPrompt(makePrompt());
|
|
100
|
+
rl.prompt(true);
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const SLASH_COMMANDS = [
|
|
106
|
+
"/dm", "/list", "/who", "/whoami", "/last", "/clear", "/cls",
|
|
107
|
+
"/me", "/help", "/?", "/quit", "/exit",
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
function completer(line) {
|
|
111
|
+
// Tab-complete slash commands and DM targets.
|
|
112
|
+
if (line.startsWith("/dm ")) {
|
|
113
|
+
const partial = line.slice(4);
|
|
114
|
+
const reg = readJsonSafe(AGENTS_FILE, {});
|
|
115
|
+
const ids = Object.keys(reg).filter((id) => id !== ID && id.startsWith(partial));
|
|
116
|
+
const hits = ids.map((id) => `/dm ${id} `);
|
|
117
|
+
return [hits, line];
|
|
118
|
+
}
|
|
119
|
+
if (line.startsWith("/")) {
|
|
120
|
+
const hits = SLASH_COMMANDS.filter((c) => c.startsWith(line));
|
|
121
|
+
return [hits, line];
|
|
122
|
+
}
|
|
123
|
+
return [[], line];
|
|
124
|
+
}
|
|
125
|
+
|
|
83
126
|
const rl = readline.createInterface({
|
|
84
127
|
input: process.stdin,
|
|
85
128
|
output: process.stdout,
|
|
86
129
|
prompt: makePrompt(),
|
|
130
|
+
completer,
|
|
87
131
|
});
|
|
88
132
|
|
|
89
133
|
// Banner — printed once on launch. Keep it tight; this is a CLI, not a poster.
|
|
90
134
|
printBanner();
|
|
91
135
|
await drainAndPrint();
|
|
92
136
|
|
|
137
|
+
// Lay down the first separator, then activate input-area mode so subsequent
|
|
138
|
+
// say() calls maintain a sep line directly above the prompt.
|
|
139
|
+
process.stdout.write(sepLine() + "\n");
|
|
140
|
+
inputAreaReady = true;
|
|
141
|
+
|
|
93
142
|
try { watch(INBOX_FILE, () => void drainAndPrint()); } catch {}
|
|
94
143
|
try { watch(ROOM_FILE, () => void drainAndPrint()); } catch {}
|
|
95
144
|
try { watch(AGENTS_FILE, () => refreshPrompt()); } catch {}
|
|
@@ -104,7 +153,8 @@ rl.on("line", async (line) => {
|
|
|
104
153
|
try {
|
|
105
154
|
if (text === "/quit" || text === "/exit") {
|
|
106
155
|
await unregister();
|
|
107
|
-
|
|
156
|
+
teardownFooter();
|
|
157
|
+
process.stdout.write(A.dim("bye.\n"));
|
|
108
158
|
process.exit(0);
|
|
109
159
|
} else if (text === "/help" || text === "/?") {
|
|
110
160
|
printHelp();
|
|
@@ -135,16 +185,24 @@ rl.on("line", async (line) => {
|
|
|
135
185
|
} catch (e) {
|
|
136
186
|
say(A.red(`error: ${e?.message ?? e}`));
|
|
137
187
|
}
|
|
188
|
+
// After Enter, terminal advanced to a new line. Print a fresh separator
|
|
189
|
+
// there so the next prompt sits below a sep line, maintaining the layout.
|
|
190
|
+
process.stdout.write(sepLine() + "\n");
|
|
138
191
|
rl.prompt();
|
|
139
192
|
});
|
|
140
193
|
|
|
141
194
|
process.on("SIGINT", async () => {
|
|
142
195
|
try { await unregister(); } catch {}
|
|
143
|
-
|
|
144
|
-
|
|
196
|
+
teardownFooter();
|
|
197
|
+
process.stdout.write("\n" + A.dim("bye.\n"));
|
|
145
198
|
process.exit(0);
|
|
146
199
|
});
|
|
147
200
|
|
|
201
|
+
process.on("exit", () => {
|
|
202
|
+
// Final safety net — restore terminal state if we exit via any path.
|
|
203
|
+
teardownFooter();
|
|
204
|
+
});
|
|
205
|
+
|
|
148
206
|
// ---------- helpers ----------
|
|
149
207
|
|
|
150
208
|
function parseArgs(argv) {
|
|
@@ -166,11 +224,17 @@ function sanitize(s) {
|
|
|
166
224
|
return s.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
167
225
|
}
|
|
168
226
|
|
|
169
|
-
// Write output without clobbering whatever the user is typing.
|
|
170
227
|
function say(line) {
|
|
171
|
-
|
|
172
|
-
|
|
228
|
+
if (!inputAreaReady) {
|
|
229
|
+
process.stdout.write(line + "\n");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
// We're on the prompt line. The line above is the current separator.
|
|
233
|
+
// Move up to it, clear it, drop our message there, then a fresh separator,
|
|
234
|
+
// then re-render the prompt on the next line.
|
|
235
|
+
process.stdout.write("\x1b[1A\r\x1b[2K");
|
|
173
236
|
process.stdout.write(line + "\n");
|
|
237
|
+
process.stdout.write(sepLine() + "\n");
|
|
174
238
|
if (typeof rl !== "undefined") rl.prompt(true);
|
|
175
239
|
}
|
|
176
240
|
|
|
@@ -190,6 +254,9 @@ function makePrompt() {
|
|
|
190
254
|
return `${agentColor(ID)(ID)} ${A.dim(`(${peers})`)}${A.dim(">")} `;
|
|
191
255
|
}
|
|
192
256
|
|
|
257
|
+
// No-op stubs kept so the exit paths don't reference deleted functions.
|
|
258
|
+
function teardownFooter() {}
|
|
259
|
+
|
|
193
260
|
function refreshPrompt() {
|
|
194
261
|
if (typeof rl === "undefined") return;
|
|
195
262
|
rl.setPrompt(makePrompt());
|