crawd 0.8.3 → 0.8.5
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/backend/index.js +25 -10
- package/dist/plugin.js +52 -10
- package/package.json +1 -1
- package/src/backend/coordinator.ts +29 -9
- package/src/plugin.ts +30 -0
package/dist/backend/index.js
CHANGED
|
@@ -11205,13 +11205,14 @@ var YouTubeChatClient = class extends BaseChatClient {
|
|
|
11205
11205
|
// src/backend/coordinator.ts
|
|
11206
11206
|
import { randomUUID } from "crypto";
|
|
11207
11207
|
import WebSocket from "ws";
|
|
11208
|
-
var
|
|
11208
|
+
var DEFAULT_BATCH_WINDOW_MS = 2e4;
|
|
11209
11209
|
var SESSION_KEY = process.env.CRAWD_CHANNEL_ID || "agent:main:crawd:live";
|
|
11210
11210
|
var DEFAULT_CONFIG = {
|
|
11211
11211
|
vibeEnabled: true,
|
|
11212
11212
|
vibeIntervalMs: 3e4,
|
|
11213
11213
|
idleAfterMs: 18e4,
|
|
11214
11214
|
sleepAfterIdleMs: 18e4,
|
|
11215
|
+
chatBatchWindowMs: DEFAULT_BATCH_WINDOW_MS,
|
|
11215
11216
|
vibePrompt: `[CRAWD:VIBE] You are on a livestream. Make sure the crawd skill is loaded. Do one thing on the internet or ask the chat something. Respond with LIVESTREAM_REPLIED after using a tool, or NO_REPLY if you have nothing to say.`
|
|
11216
11217
|
};
|
|
11217
11218
|
var realClock = {
|
|
@@ -11443,7 +11444,7 @@ var GatewayClient = class _GatewayClient {
|
|
|
11443
11444
|
};
|
|
11444
11445
|
var STARTUP_GRACE_MS = 3e4;
|
|
11445
11446
|
var SLEEP_CHECK_INTERVAL_MS = 1e4;
|
|
11446
|
-
var Coordinator = class {
|
|
11447
|
+
var Coordinator = class _Coordinator {
|
|
11447
11448
|
buffer = [];
|
|
11448
11449
|
timer = null;
|
|
11449
11450
|
triggerFn;
|
|
@@ -11482,6 +11483,7 @@ var Coordinator = class {
|
|
|
11482
11483
|
this.config = { ...this.config, ...config2 };
|
|
11483
11484
|
this.logger.log("[Coordinator] Config updated:", {
|
|
11484
11485
|
vibeIntervalMs: this.config.vibeIntervalMs,
|
|
11486
|
+
chatBatchWindowMs: this.config.chatBatchWindowMs,
|
|
11485
11487
|
idleAfterMs: this.config.idleAfterMs,
|
|
11486
11488
|
sleepAfterIdleMs: this.config.sleepAfterIdleMs
|
|
11487
11489
|
});
|
|
@@ -11648,14 +11650,18 @@ var Coordinator = class {
|
|
|
11648
11650
|
this._busy = true;
|
|
11649
11651
|
try {
|
|
11650
11652
|
const replies = await this.triggerFn(this.config.vibePrompt);
|
|
11651
|
-
|
|
11653
|
+
const agentReplies = replies.filter((r) => !this.isApiError(r));
|
|
11654
|
+
if (agentReplies.some((r) => r.trim().toUpperCase() === "NO_REPLY")) {
|
|
11652
11655
|
noReply = true;
|
|
11653
|
-
} else if (!this.isCompliantReply(
|
|
11654
|
-
misaligned =
|
|
11656
|
+
} else if (!this.isCompliantReply(agentReplies)) {
|
|
11657
|
+
misaligned = agentReplies.filter((r) => {
|
|
11655
11658
|
const t = r.trim().toUpperCase();
|
|
11656
11659
|
return t !== "NO_REPLY" && t !== "LIVESTREAM_REPLIED";
|
|
11657
11660
|
});
|
|
11658
11661
|
}
|
|
11662
|
+
if (replies.length > agentReplies.length) {
|
|
11663
|
+
this.logger.warn(`[Coordinator] Filtered ${replies.length - agentReplies.length} API error(s) from vibe response`);
|
|
11664
|
+
}
|
|
11659
11665
|
} catch (err) {
|
|
11660
11666
|
this.logger.error("[Coordinator] Vibe failed:", err);
|
|
11661
11667
|
} finally {
|
|
@@ -11701,26 +11707,31 @@ var Coordinator = class {
|
|
|
11701
11707
|
}
|
|
11702
11708
|
if (!this.timer) {
|
|
11703
11709
|
this.flush();
|
|
11704
|
-
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(),
|
|
11710
|
+
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(), this.config.chatBatchWindowMs);
|
|
11705
11711
|
}
|
|
11706
11712
|
}
|
|
11707
11713
|
onCooldownEnd() {
|
|
11708
11714
|
this.timer = null;
|
|
11709
11715
|
if (this.buffer.length > 0) {
|
|
11710
11716
|
this.flush();
|
|
11711
|
-
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(),
|
|
11717
|
+
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(), this.config.chatBatchWindowMs);
|
|
11712
11718
|
}
|
|
11713
11719
|
}
|
|
11714
11720
|
/** Whether the coordinator is busy processing a flush or talk */
|
|
11715
11721
|
get busy() {
|
|
11716
11722
|
return this._busy;
|
|
11717
11723
|
}
|
|
11724
|
+
/** Detect API/gateway errors surfaced as reply strings (e.g. rate limits) */
|
|
11725
|
+
static API_ERROR_RE = /^\d{3}\s+(status\s+code|error)|^rate\s*limit|^too\s+many\s+requests|^overloaded|^server\s+error/i;
|
|
11726
|
+
isApiError(reply) {
|
|
11727
|
+
return _Coordinator.API_ERROR_RE.test(reply.trim());
|
|
11728
|
+
}
|
|
11718
11729
|
/** Check if agent replies are compliant (NO_REPLY or LIVESTREAM_REPLIED) */
|
|
11719
11730
|
isCompliantReply(replies) {
|
|
11720
11731
|
if (replies.length === 0) return true;
|
|
11721
11732
|
return replies.every((r) => {
|
|
11722
11733
|
const t = r.trim().toUpperCase();
|
|
11723
|
-
return t === "NO_REPLY" || t === "LIVESTREAM_REPLIED";
|
|
11734
|
+
return t === "NO_REPLY" || t === "LIVESTREAM_REPLIED" || this.isApiError(r);
|
|
11724
11735
|
});
|
|
11725
11736
|
}
|
|
11726
11737
|
/** Send misalignment correction when agent responds with plaintext */
|
|
@@ -11746,8 +11757,12 @@ var Coordinator = class {
|
|
|
11746
11757
|
this._busy = true;
|
|
11747
11758
|
try {
|
|
11748
11759
|
const replies = await this.triggerFn(batchText);
|
|
11749
|
-
|
|
11750
|
-
|
|
11760
|
+
const agentReplies = replies.filter((r) => !this.isApiError(r));
|
|
11761
|
+
if (replies.length > agentReplies.length) {
|
|
11762
|
+
this.logger.warn(`[Coordinator] Filtered ${replies.length - agentReplies.length} API error(s) from chat response`);
|
|
11763
|
+
}
|
|
11764
|
+
if (!this.isCompliantReply(agentReplies)) {
|
|
11765
|
+
await this.sendMisalignment(agentReplies.filter((r) => {
|
|
11751
11766
|
const t = r.trim().toUpperCase();
|
|
11752
11767
|
return t !== "NO_REPLY" && t !== "LIVESTREAM_REPLIED";
|
|
11753
11768
|
}));
|
package/dist/plugin.js
CHANGED
|
@@ -11206,13 +11206,14 @@ var YouTubeChatClient = class extends BaseChatClient {
|
|
|
11206
11206
|
// src/backend/coordinator.ts
|
|
11207
11207
|
import { randomUUID } from "crypto";
|
|
11208
11208
|
import WebSocket from "ws";
|
|
11209
|
-
var
|
|
11209
|
+
var DEFAULT_BATCH_WINDOW_MS = 2e4;
|
|
11210
11210
|
var SESSION_KEY = process.env.CRAWD_CHANNEL_ID || "agent:main:crawd:live";
|
|
11211
11211
|
var DEFAULT_CONFIG = {
|
|
11212
11212
|
vibeEnabled: true,
|
|
11213
11213
|
vibeIntervalMs: 3e4,
|
|
11214
11214
|
idleAfterMs: 18e4,
|
|
11215
11215
|
sleepAfterIdleMs: 18e4,
|
|
11216
|
+
chatBatchWindowMs: DEFAULT_BATCH_WINDOW_MS,
|
|
11216
11217
|
vibePrompt: `[CRAWD:VIBE] You are on a livestream. Make sure the crawd skill is loaded. Do one thing on the internet or ask the chat something. Respond with LIVESTREAM_REPLIED after using a tool, or NO_REPLY if you have nothing to say.`
|
|
11217
11218
|
};
|
|
11218
11219
|
var realClock = {
|
|
@@ -11321,7 +11322,7 @@ var OneShotGateway = class {
|
|
|
11321
11322
|
};
|
|
11322
11323
|
var STARTUP_GRACE_MS = 3e4;
|
|
11323
11324
|
var SLEEP_CHECK_INTERVAL_MS = 1e4;
|
|
11324
|
-
var Coordinator = class {
|
|
11325
|
+
var Coordinator = class _Coordinator {
|
|
11325
11326
|
buffer = [];
|
|
11326
11327
|
timer = null;
|
|
11327
11328
|
triggerFn;
|
|
@@ -11360,6 +11361,7 @@ var Coordinator = class {
|
|
|
11360
11361
|
this.config = { ...this.config, ...config2 };
|
|
11361
11362
|
this.logger.log("[Coordinator] Config updated:", {
|
|
11362
11363
|
vibeIntervalMs: this.config.vibeIntervalMs,
|
|
11364
|
+
chatBatchWindowMs: this.config.chatBatchWindowMs,
|
|
11363
11365
|
idleAfterMs: this.config.idleAfterMs,
|
|
11364
11366
|
sleepAfterIdleMs: this.config.sleepAfterIdleMs
|
|
11365
11367
|
});
|
|
@@ -11526,14 +11528,18 @@ var Coordinator = class {
|
|
|
11526
11528
|
this._busy = true;
|
|
11527
11529
|
try {
|
|
11528
11530
|
const replies = await this.triggerFn(this.config.vibePrompt);
|
|
11529
|
-
|
|
11531
|
+
const agentReplies = replies.filter((r) => !this.isApiError(r));
|
|
11532
|
+
if (agentReplies.some((r) => r.trim().toUpperCase() === "NO_REPLY")) {
|
|
11530
11533
|
noReply = true;
|
|
11531
|
-
} else if (!this.isCompliantReply(
|
|
11532
|
-
misaligned =
|
|
11534
|
+
} else if (!this.isCompliantReply(agentReplies)) {
|
|
11535
|
+
misaligned = agentReplies.filter((r) => {
|
|
11533
11536
|
const t = r.trim().toUpperCase();
|
|
11534
11537
|
return t !== "NO_REPLY" && t !== "LIVESTREAM_REPLIED";
|
|
11535
11538
|
});
|
|
11536
11539
|
}
|
|
11540
|
+
if (replies.length > agentReplies.length) {
|
|
11541
|
+
this.logger.warn(`[Coordinator] Filtered ${replies.length - agentReplies.length} API error(s) from vibe response`);
|
|
11542
|
+
}
|
|
11537
11543
|
} catch (err) {
|
|
11538
11544
|
this.logger.error("[Coordinator] Vibe failed:", err);
|
|
11539
11545
|
} finally {
|
|
@@ -11579,26 +11585,31 @@ var Coordinator = class {
|
|
|
11579
11585
|
}
|
|
11580
11586
|
if (!this.timer) {
|
|
11581
11587
|
this.flush();
|
|
11582
|
-
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(),
|
|
11588
|
+
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(), this.config.chatBatchWindowMs);
|
|
11583
11589
|
}
|
|
11584
11590
|
}
|
|
11585
11591
|
onCooldownEnd() {
|
|
11586
11592
|
this.timer = null;
|
|
11587
11593
|
if (this.buffer.length > 0) {
|
|
11588
11594
|
this.flush();
|
|
11589
|
-
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(),
|
|
11595
|
+
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(), this.config.chatBatchWindowMs);
|
|
11590
11596
|
}
|
|
11591
11597
|
}
|
|
11592
11598
|
/** Whether the coordinator is busy processing a flush or talk */
|
|
11593
11599
|
get busy() {
|
|
11594
11600
|
return this._busy;
|
|
11595
11601
|
}
|
|
11602
|
+
/** Detect API/gateway errors surfaced as reply strings (e.g. rate limits) */
|
|
11603
|
+
static API_ERROR_RE = /^\d{3}\s+(status\s+code|error)|^rate\s*limit|^too\s+many\s+requests|^overloaded|^server\s+error/i;
|
|
11604
|
+
isApiError(reply) {
|
|
11605
|
+
return _Coordinator.API_ERROR_RE.test(reply.trim());
|
|
11606
|
+
}
|
|
11596
11607
|
/** Check if agent replies are compliant (NO_REPLY or LIVESTREAM_REPLIED) */
|
|
11597
11608
|
isCompliantReply(replies) {
|
|
11598
11609
|
if (replies.length === 0) return true;
|
|
11599
11610
|
return replies.every((r) => {
|
|
11600
11611
|
const t = r.trim().toUpperCase();
|
|
11601
|
-
return t === "NO_REPLY" || t === "LIVESTREAM_REPLIED";
|
|
11612
|
+
return t === "NO_REPLY" || t === "LIVESTREAM_REPLIED" || this.isApiError(r);
|
|
11602
11613
|
});
|
|
11603
11614
|
}
|
|
11604
11615
|
/** Send misalignment correction when agent responds with plaintext */
|
|
@@ -11624,8 +11635,12 @@ var Coordinator = class {
|
|
|
11624
11635
|
this._busy = true;
|
|
11625
11636
|
try {
|
|
11626
11637
|
const replies = await this.triggerFn(batchText);
|
|
11627
|
-
|
|
11628
|
-
|
|
11638
|
+
const agentReplies = replies.filter((r) => !this.isApiError(r));
|
|
11639
|
+
if (replies.length > agentReplies.length) {
|
|
11640
|
+
this.logger.warn(`[Coordinator] Filtered ${replies.length - agentReplies.length} API error(s) from chat response`);
|
|
11641
|
+
}
|
|
11642
|
+
if (!this.isCompliantReply(agentReplies)) {
|
|
11643
|
+
await this.sendMisalignment(agentReplies.filter((r) => {
|
|
11629
11644
|
const t = r.trim().toUpperCase();
|
|
11630
11645
|
return t !== "NO_REPLY" && t !== "LIVESTREAM_REPLIED";
|
|
11631
11646
|
}));
|
|
@@ -12309,6 +12324,33 @@ var plugin = {
|
|
|
12309
12324
|
},
|
|
12310
12325
|
{ name: "livestream_reply" }
|
|
12311
12326
|
);
|
|
12327
|
+
api.registerTool(
|
|
12328
|
+
{
|
|
12329
|
+
name: "livestream_config",
|
|
12330
|
+
label: "Livestream Config",
|
|
12331
|
+
description: "Update livestream coordinator settings at runtime. Use when asked to change vibe speed/frequency, chat throttle/batch window, idle timeout, or sleep timeout.",
|
|
12332
|
+
parameters: Type.Object({
|
|
12333
|
+
vibeIntervalMs: Type.Optional(Type.Number({ description: "Milliseconds between autonomous vibe prompts (lower = more frequent vibes)" })),
|
|
12334
|
+
chatBatchWindowMs: Type.Optional(Type.Number({ description: "Milliseconds to batch chat messages before sending to agent (lower = faster chat response, higher = more messages per batch)" })),
|
|
12335
|
+
idleAfterMs: Type.Optional(Type.Number({ description: "Milliseconds of inactivity before transitioning to idle state" })),
|
|
12336
|
+
sleepAfterIdleMs: Type.Optional(Type.Number({ description: "Milliseconds in idle before transitioning to sleep state" })),
|
|
12337
|
+
vibeEnabled: Type.Optional(Type.Boolean({ description: "Enable or disable autonomous vibe prompts" }))
|
|
12338
|
+
}),
|
|
12339
|
+
async execute(_toolCallId, params) {
|
|
12340
|
+
const b = await ensureBackend();
|
|
12341
|
+
if (!b.coordinator) {
|
|
12342
|
+
return { content: [{ type: "text", text: "Coordinator not running" }] };
|
|
12343
|
+
}
|
|
12344
|
+
const p = params;
|
|
12345
|
+
b.coordinator.updateConfig(p);
|
|
12346
|
+
const { state, config: cfg } = b.coordinator.getState();
|
|
12347
|
+
return {
|
|
12348
|
+
content: [{ type: "text", text: `Config updated. state=${state}, vibeInterval=${cfg.vibeIntervalMs}ms, chatBatch=${cfg.chatBatchWindowMs}ms, idleAfter=${cfg.idleAfterMs}ms, sleepAfter=${cfg.sleepAfterIdleMs}ms, vibes=${cfg.vibeEnabled}` }]
|
|
12349
|
+
};
|
|
12350
|
+
}
|
|
12351
|
+
},
|
|
12352
|
+
{ name: "livestream_config" }
|
|
12353
|
+
);
|
|
12312
12354
|
api.registerService({
|
|
12313
12355
|
id: "crawd",
|
|
12314
12356
|
start: async () => {
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@ import { randomUUID } from 'crypto'
|
|
|
2
2
|
import WebSocket from 'ws'
|
|
3
3
|
import type { ChatMessage } from '../lib/chat/types'
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const DEFAULT_BATCH_WINDOW_MS = 20_000
|
|
6
6
|
const SESSION_KEY = process.env.CRAWD_CHANNEL_ID || 'agent:main:crawd:live'
|
|
7
7
|
|
|
8
8
|
/** Coordinator configuration */
|
|
@@ -15,6 +15,8 @@ export type CoordinatorConfig = {
|
|
|
15
15
|
idleAfterMs: number
|
|
16
16
|
/** Go sleep after this much inactivity while idle (ms). Default: 60000 (1 min) */
|
|
17
17
|
sleepAfterIdleMs: number
|
|
18
|
+
/** Chat message batching window (ms). Default: 20000 (20 sec) */
|
|
19
|
+
chatBatchWindowMs: number
|
|
18
20
|
/** The autonomous "vibe" prompt sent periodically */
|
|
19
21
|
vibePrompt: string
|
|
20
22
|
}
|
|
@@ -24,6 +26,7 @@ export const DEFAULT_CONFIG: CoordinatorConfig = {
|
|
|
24
26
|
vibeIntervalMs: 30_000,
|
|
25
27
|
idleAfterMs: 180_000,
|
|
26
28
|
sleepAfterIdleMs: 180_000,
|
|
29
|
+
chatBatchWindowMs: DEFAULT_BATCH_WINDOW_MS,
|
|
27
30
|
vibePrompt: `[CRAWD:VIBE] You are on a livestream. Make sure the crawd skill is loaded. Do one thing on the internet or ask the chat something. Respond with LIVESTREAM_REPLIED after using a tool, or NO_REPLY if you have nothing to say.`,
|
|
28
31
|
}
|
|
29
32
|
|
|
@@ -554,6 +557,7 @@ export class Coordinator {
|
|
|
554
557
|
this.config = { ...this.config, ...config }
|
|
555
558
|
this.logger.log('[Coordinator] Config updated:', {
|
|
556
559
|
vibeIntervalMs: this.config.vibeIntervalMs,
|
|
560
|
+
chatBatchWindowMs: this.config.chatBatchWindowMs,
|
|
557
561
|
idleAfterMs: this.config.idleAfterMs,
|
|
558
562
|
sleepAfterIdleMs: this.config.sleepAfterIdleMs,
|
|
559
563
|
})
|
|
@@ -765,14 +769,19 @@ export class Coordinator {
|
|
|
765
769
|
this._busy = true
|
|
766
770
|
try {
|
|
767
771
|
const replies = await this.triggerFn(this.config.vibePrompt)
|
|
768
|
-
|
|
772
|
+
// Filter out API errors (429s, rate limits) — not agent responses
|
|
773
|
+
const agentReplies = replies.filter(r => !this.isApiError(r))
|
|
774
|
+
if (agentReplies.some(r => r.trim().toUpperCase() === 'NO_REPLY')) {
|
|
769
775
|
noReply = true
|
|
770
|
-
} else if (!this.isCompliantReply(
|
|
771
|
-
misaligned =
|
|
776
|
+
} else if (!this.isCompliantReply(agentReplies)) {
|
|
777
|
+
misaligned = agentReplies.filter(r => {
|
|
772
778
|
const t = r.trim().toUpperCase()
|
|
773
779
|
return t !== 'NO_REPLY' && t !== 'LIVESTREAM_REPLIED'
|
|
774
780
|
})
|
|
775
781
|
}
|
|
782
|
+
if (replies.length > agentReplies.length) {
|
|
783
|
+
this.logger.warn(`[Coordinator] Filtered ${replies.length - agentReplies.length} API error(s) from vibe response`)
|
|
784
|
+
}
|
|
776
785
|
} catch (err) {
|
|
777
786
|
this.logger.error('[Coordinator] Vibe failed:', err)
|
|
778
787
|
} finally {
|
|
@@ -831,7 +840,7 @@ export class Coordinator {
|
|
|
831
840
|
// Leading edge: if no timer running, flush immediately and start cooldown
|
|
832
841
|
if (!this.timer) {
|
|
833
842
|
this.flush()
|
|
834
|
-
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(),
|
|
843
|
+
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(), this.config.chatBatchWindowMs)
|
|
835
844
|
}
|
|
836
845
|
// Otherwise, message is buffered and will be flushed when cooldown ends
|
|
837
846
|
}
|
|
@@ -842,19 +851,26 @@ export class Coordinator {
|
|
|
842
851
|
// If messages accumulated during cooldown, flush them and restart cooldown
|
|
843
852
|
if (this.buffer.length > 0) {
|
|
844
853
|
this.flush()
|
|
845
|
-
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(),
|
|
854
|
+
this.timer = this.clock.setTimeout(() => this.onCooldownEnd(), this.config.chatBatchWindowMs)
|
|
846
855
|
}
|
|
847
856
|
}
|
|
848
857
|
|
|
849
858
|
/** Whether the coordinator is busy processing a flush or talk */
|
|
850
859
|
get busy(): boolean { return this._busy }
|
|
851
860
|
|
|
861
|
+
/** Detect API/gateway errors surfaced as reply strings (e.g. rate limits) */
|
|
862
|
+
private static readonly API_ERROR_RE = /^\d{3}\s+(status\s+code|error)|^rate\s*limit|^too\s+many\s+requests|^overloaded|^server\s+error/i
|
|
863
|
+
|
|
864
|
+
private isApiError(reply: string): boolean {
|
|
865
|
+
return Coordinator.API_ERROR_RE.test(reply.trim())
|
|
866
|
+
}
|
|
867
|
+
|
|
852
868
|
/** Check if agent replies are compliant (NO_REPLY or LIVESTREAM_REPLIED) */
|
|
853
869
|
private isCompliantReply(replies: string[]): boolean {
|
|
854
870
|
if (replies.length === 0) return true
|
|
855
871
|
return replies.every(r => {
|
|
856
872
|
const t = r.trim().toUpperCase()
|
|
857
|
-
return t === 'NO_REPLY' || t === 'LIVESTREAM_REPLIED'
|
|
873
|
+
return t === 'NO_REPLY' || t === 'LIVESTREAM_REPLIED' || this.isApiError(r)
|
|
858
874
|
})
|
|
859
875
|
}
|
|
860
876
|
|
|
@@ -889,8 +905,12 @@ export class Coordinator {
|
|
|
889
905
|
this._busy = true
|
|
890
906
|
try {
|
|
891
907
|
const replies = await this.triggerFn(batchText)
|
|
892
|
-
|
|
893
|
-
|
|
908
|
+
const agentReplies = replies.filter(r => !this.isApiError(r))
|
|
909
|
+
if (replies.length > agentReplies.length) {
|
|
910
|
+
this.logger.warn(`[Coordinator] Filtered ${replies.length - agentReplies.length} API error(s) from chat response`)
|
|
911
|
+
}
|
|
912
|
+
if (!this.isCompliantReply(agentReplies)) {
|
|
913
|
+
await this.sendMisalignment(agentReplies.filter(r => {
|
|
894
914
|
const t = r.trim().toUpperCase()
|
|
895
915
|
return t !== 'NO_REPLY' && t !== 'LIVESTREAM_REPLIED'
|
|
896
916
|
}))
|
package/src/plugin.ts
CHANGED
|
@@ -249,6 +249,36 @@ const plugin: PluginDefinition = {
|
|
|
249
249
|
{ name: 'livestream_reply' },
|
|
250
250
|
)
|
|
251
251
|
|
|
252
|
+
// livestream_config — runtime coordinator tuning
|
|
253
|
+
api.registerTool(
|
|
254
|
+
{
|
|
255
|
+
name: 'livestream_config',
|
|
256
|
+
label: 'Livestream Config',
|
|
257
|
+
description:
|
|
258
|
+
'Update livestream coordinator settings at runtime. Use when asked to change vibe speed/frequency, chat throttle/batch window, idle timeout, or sleep timeout.',
|
|
259
|
+
parameters: Type.Object({
|
|
260
|
+
vibeIntervalMs: Type.Optional(Type.Number({ description: 'Milliseconds between autonomous vibe prompts (lower = more frequent vibes)' })),
|
|
261
|
+
chatBatchWindowMs: Type.Optional(Type.Number({ description: 'Milliseconds to batch chat messages before sending to agent (lower = faster chat response, higher = more messages per batch)' })),
|
|
262
|
+
idleAfterMs: Type.Optional(Type.Number({ description: 'Milliseconds of inactivity before transitioning to idle state' })),
|
|
263
|
+
sleepAfterIdleMs: Type.Optional(Type.Number({ description: 'Milliseconds in idle before transitioning to sleep state' })),
|
|
264
|
+
vibeEnabled: Type.Optional(Type.Boolean({ description: 'Enable or disable autonomous vibe prompts' })),
|
|
265
|
+
}),
|
|
266
|
+
async execute(_toolCallId: string, params: unknown) {
|
|
267
|
+
const b = await ensureBackend()
|
|
268
|
+
if (!b.coordinator) {
|
|
269
|
+
return { content: [{ type: 'text', text: 'Coordinator not running' }] }
|
|
270
|
+
}
|
|
271
|
+
const p = params as Record<string, unknown>
|
|
272
|
+
b.coordinator.updateConfig(p)
|
|
273
|
+
const { state, config: cfg } = b.coordinator.getState()
|
|
274
|
+
return {
|
|
275
|
+
content: [{ type: 'text', text: `Config updated. state=${state}, vibeInterval=${cfg.vibeIntervalMs}ms, chatBatch=${cfg.chatBatchWindowMs}ms, idleAfter=${cfg.idleAfterMs}ms, sleepAfter=${cfg.sleepAfterIdleMs}ms, vibes=${cfg.vibeEnabled}` }],
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{ name: 'livestream_config' },
|
|
280
|
+
)
|
|
281
|
+
|
|
252
282
|
// Service lifecycle
|
|
253
283
|
api.registerService({
|
|
254
284
|
id: 'crawd',
|