@songsid/agend 2.0.8-beta.19 → 2.0.8-beta.20
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/fleet-manager.d.ts +6 -0
- package/dist/fleet-manager.js +91 -39
- package/dist/fleet-manager.js.map +1 -1
- package/dist/outbound-schemas.d.ts +1 -1
- package/dist/topic-commands.d.ts +13 -0
- package/dist/topic-commands.js +51 -0
- package/dist/topic-commands.js.map +1 -1
- package/package.json +1 -1
package/dist/fleet-manager.d.ts
CHANGED
|
@@ -163,6 +163,12 @@ export declare class FleetManager implements FleetContext, LifecycleContext, Arc
|
|
|
163
163
|
toggleFleetCollab(instanceName: string): boolean;
|
|
164
164
|
notifyInstanceTopic(instanceName: string, text: string): void;
|
|
165
165
|
/** Send a "🛑 Cancel" button to the instance's topic/channel after delivery. */
|
|
166
|
+
/**
|
|
167
|
+
* Handle the DC `/save` slash command for both classic AND fleet-topic targets.
|
|
168
|
+
* Picks the backend-appropriate command (kiro → /chat save, claude → /export);
|
|
169
|
+
* unsupported backends get a clear error. Routes via classic paste or fleet IPC.
|
|
170
|
+
*/
|
|
171
|
+
private handleSlashSave;
|
|
166
172
|
sendCancelButton(instanceName: string): Promise<void>;
|
|
167
173
|
/**
|
|
168
174
|
* Poll the instance's idle state and retire its cancel button once the work is
|
package/dist/fleet-manager.js
CHANGED
|
@@ -22,7 +22,7 @@ import { processAttachments } from "./channel/attachment-handler.js";
|
|
|
22
22
|
import { routeToolCall } from "./channel/tool-router.js";
|
|
23
23
|
import { Scheduler } from "./scheduler/index.js";
|
|
24
24
|
import { DEFAULT_SCHEDULER_CONFIG } from "./scheduler/index.js";
|
|
25
|
-
import { TopicCommands, sanitizeInstanceName } from "./topic-commands.js";
|
|
25
|
+
import { TopicCommands, sanitizeInstanceName, saveCommandForBackend, parseSaveFilename, SAVE_FILENAME_RE, SAVE_UNSUPPORTED_MSG } from "./topic-commands.js";
|
|
26
26
|
import { DailySummary } from "./daily-summary.js";
|
|
27
27
|
import { WebhookEmitter } from "./webhook-emitter.js";
|
|
28
28
|
import { TmuxControlClient } from "./tmux-control.js";
|
|
@@ -794,7 +794,11 @@ export class FleetManager {
|
|
|
794
794
|
timestamp: new Date(),
|
|
795
795
|
});
|
|
796
796
|
}
|
|
797
|
-
else if (data.command === "save"
|
|
797
|
+
else if (data.command === "save") {
|
|
798
|
+
await this.handleSlashSave(data);
|
|
799
|
+
}
|
|
800
|
+
else if (data.command === "load") {
|
|
801
|
+
// load is kiro-cli/classic only — no claude-code equivalent.
|
|
798
802
|
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
799
803
|
await data.respond("⛔ This command requires admin access.");
|
|
800
804
|
return;
|
|
@@ -804,25 +808,13 @@ export class FleetManager {
|
|
|
804
808
|
await data.respond("No active agent in this channel. Use `/start` first.");
|
|
805
809
|
return;
|
|
806
810
|
}
|
|
807
|
-
|
|
808
|
-
if (
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
812
|
-
return;
|
|
813
|
-
}
|
|
814
|
-
rawCmd = data.options?.force ? `/chat save ${filename} -f` : `/chat save ${filename}`;
|
|
815
|
-
}
|
|
816
|
-
else {
|
|
817
|
-
const filename = data.options?.filename;
|
|
818
|
-
if (!/^[\w.-]+$/.test(filename)) {
|
|
819
|
-
await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
820
|
-
return;
|
|
821
|
-
}
|
|
822
|
-
rawCmd = `/chat load ${filename}`;
|
|
811
|
+
const filename = data.options?.filename;
|
|
812
|
+
if (!SAVE_FILENAME_RE.test(filename ?? "")) {
|
|
813
|
+
await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
814
|
+
return;
|
|
823
815
|
}
|
|
824
|
-
this.pasteRawToClassicInstance(target.name,
|
|
825
|
-
await data.respond(`✅ Sent
|
|
816
|
+
this.pasteRawToClassicInstance(target.name, `/chat load ${filename}`);
|
|
817
|
+
await data.respond(`✅ Sent \`/chat load ${filename}\` to ${target.name}`);
|
|
826
818
|
}
|
|
827
819
|
else if (data.command === "compact") {
|
|
828
820
|
const target = this.routing.resolve(data.channelId);
|
|
@@ -1064,7 +1056,11 @@ export class FleetManager {
|
|
|
1064
1056
|
timestamp: new Date(),
|
|
1065
1057
|
});
|
|
1066
1058
|
}
|
|
1067
|
-
else if (data.command === "save"
|
|
1059
|
+
else if (data.command === "save") {
|
|
1060
|
+
await this.handleSlashSave(data);
|
|
1061
|
+
}
|
|
1062
|
+
else if (data.command === "load") {
|
|
1063
|
+
// load is kiro-cli/classic only — no claude-code equivalent.
|
|
1068
1064
|
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
1069
1065
|
await data.respond("⛔ This command requires admin access.");
|
|
1070
1066
|
return;
|
|
@@ -1074,25 +1070,13 @@ export class FleetManager {
|
|
|
1074
1070
|
await data.respond("No active agent in this channel. Use `/start` first.");
|
|
1075
1071
|
return;
|
|
1076
1072
|
}
|
|
1077
|
-
|
|
1078
|
-
if (
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
1082
|
-
return;
|
|
1083
|
-
}
|
|
1084
|
-
rawCmd = data.options?.force ? `/chat save ${filename} -f` : `/chat save ${filename}`;
|
|
1085
|
-
}
|
|
1086
|
-
else {
|
|
1087
|
-
const filename = data.options?.filename;
|
|
1088
|
-
if (!/^[\w.-]+$/.test(filename)) {
|
|
1089
|
-
await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
1090
|
-
return;
|
|
1091
|
-
}
|
|
1092
|
-
rawCmd = `/chat load ${filename}`;
|
|
1073
|
+
const filename = data.options?.filename;
|
|
1074
|
+
if (!SAVE_FILENAME_RE.test(filename ?? "")) {
|
|
1075
|
+
await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
1076
|
+
return;
|
|
1093
1077
|
}
|
|
1094
|
-
this.pasteRawToClassicInstance(target.name,
|
|
1095
|
-
await data.respond(`✅ Sent
|
|
1078
|
+
this.pasteRawToClassicInstance(target.name, `/chat load ${filename}`);
|
|
1079
|
+
await data.respond(`✅ Sent \`/chat load ${filename}\` to ${target.name}`);
|
|
1096
1080
|
}
|
|
1097
1081
|
else if (data.command === "compact") {
|
|
1098
1082
|
const target = this.routing.resolve(data.channelId);
|
|
@@ -1578,6 +1562,36 @@ export class FleetManager {
|
|
|
1578
1562
|
await msgAdapter?.sendText(chatId, reply);
|
|
1579
1563
|
return;
|
|
1580
1564
|
}
|
|
1565
|
+
// Handle /save command (admin only)
|
|
1566
|
+
if (text === "/save" || text.startsWith("/save ") || text.startsWith("/save@")) {
|
|
1567
|
+
if (!this.classicChannels.isAdmin(msg.userId)) {
|
|
1568
|
+
await msgAdapter?.sendText(chatId, "⛔ /save requires admin access.");
|
|
1569
|
+
return;
|
|
1570
|
+
}
|
|
1571
|
+
const saveTarget = this.routing.resolve(chatId);
|
|
1572
|
+
if (!saveTarget || saveTarget.kind !== "classic") {
|
|
1573
|
+
await msgAdapter?.sendText(chatId, "No active agent. Use /start first.");
|
|
1574
|
+
return;
|
|
1575
|
+
}
|
|
1576
|
+
const filename = parseSaveFilename(text);
|
|
1577
|
+
if (!filename) {
|
|
1578
|
+
await msgAdapter?.sendText(chatId, "Usage: /save <filename>");
|
|
1579
|
+
return;
|
|
1580
|
+
}
|
|
1581
|
+
if (!SAVE_FILENAME_RE.test(filename)) {
|
|
1582
|
+
await msgAdapter?.sendText(chatId, "⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
const backend = this.classicChannels.getBackendByInstance(saveTarget.name, this.fleetConfig?.defaults?.backend);
|
|
1586
|
+
const cmd = saveCommandForBackend(backend, filename);
|
|
1587
|
+
if (!cmd) {
|
|
1588
|
+
await msgAdapter?.sendText(chatId, SAVE_UNSUPPORTED_MSG);
|
|
1589
|
+
return;
|
|
1590
|
+
}
|
|
1591
|
+
this.pasteRawToClassicInstance(saveTarget.name, cmd);
|
|
1592
|
+
await msgAdapter?.sendText(chatId, `✅ Sent \`${cmd}\` to ${saveTarget.name}`);
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1581
1595
|
// Route to classic channel if registered
|
|
1582
1596
|
const target = this.routing.resolve(chatId);
|
|
1583
1597
|
if (target?.kind === "classic") {
|
|
@@ -2525,6 +2539,44 @@ export class FleetManager {
|
|
|
2525
2539
|
// Sent after delivering a user message to an instance; clicking it (or
|
|
2526
2540
|
// /cancel) sends Escape to the instance's pane to interrupt generation.
|
|
2527
2541
|
/** Send a "🛑 Cancel" button to the instance's topic/channel after delivery. */
|
|
2542
|
+
/**
|
|
2543
|
+
* Handle the DC `/save` slash command for both classic AND fleet-topic targets.
|
|
2544
|
+
* Picks the backend-appropriate command (kiro → /chat save, claude → /export);
|
|
2545
|
+
* unsupported backends get a clear error. Routes via classic paste or fleet IPC.
|
|
2546
|
+
*/
|
|
2547
|
+
async handleSlashSave(data) {
|
|
2548
|
+
if (!this.classicChannels?.isAdmin(data.userId)) {
|
|
2549
|
+
await data.respond("⛔ This command requires admin access.");
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
const target = this.routing.resolve(data.channelId);
|
|
2553
|
+
if (!target) {
|
|
2554
|
+
await data.respond("No active agent in this channel. Use `/start` first.");
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
const filename = data.options?.filename ?? "";
|
|
2558
|
+
if (!SAVE_FILENAME_RE.test(filename)) {
|
|
2559
|
+
await data.respond("⛔ Invalid filename — only letters, numbers, dots, hyphens, underscores allowed.");
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
const backend = target.kind === "classic"
|
|
2563
|
+
? this.classicChannels.getBackendByInstance(target.name, this.fleetConfig?.defaults?.backend)
|
|
2564
|
+
: (this.fleetConfig?.instances[target.name]?.backend ?? this.fleetConfig?.defaults?.backend ?? "claude-code");
|
|
2565
|
+
// force (-f) is only meaningful for kiro/classic /chat save.
|
|
2566
|
+
const force = target.kind === "classic" && !!data.options?.force;
|
|
2567
|
+
const cmd = saveCommandForBackend(backend, filename, force);
|
|
2568
|
+
if (!cmd) {
|
|
2569
|
+
await data.respond(SAVE_UNSUPPORTED_MSG);
|
|
2570
|
+
return;
|
|
2571
|
+
}
|
|
2572
|
+
if (target.kind === "classic") {
|
|
2573
|
+
this.pasteRawToClassicInstance(target.name, cmd);
|
|
2574
|
+
}
|
|
2575
|
+
else {
|
|
2576
|
+
this.instanceIpcClients.get(target.name)?.send({ type: "raw_paste", content: cmd });
|
|
2577
|
+
}
|
|
2578
|
+
await data.respond(`✅ Sent \`${cmd}\` to ${target.name}`);
|
|
2579
|
+
}
|
|
2528
2580
|
async sendCancelButton(instanceName) {
|
|
2529
2581
|
// Replace any stale button for this instance first.
|
|
2530
2582
|
this.clearCancelButton(instanceName);
|