@trops/dash-core 0.1.627 → 0.1.629
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/electron/index.js +151 -2
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +118 -7
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +118 -7
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -73814,11 +73814,25 @@ function validateBotDefinition$1(def) {
|
|
|
73814
73814
|
if (!WHILE_PAUSED.has(def.whilePaused)) {
|
|
73815
73815
|
errors.push(`whilePaused must be one of ${[...WHILE_PAUSED].join(", ")}`);
|
|
73816
73816
|
}
|
|
73817
|
-
for (const field of [
|
|
73818
|
-
|
|
73817
|
+
for (const field of [
|
|
73818
|
+
"mcpServers",
|
|
73819
|
+
"allowedTools",
|
|
73820
|
+
"schedules",
|
|
73821
|
+
"subscriptions",
|
|
73822
|
+
]) {
|
|
73823
|
+
if (def[field] !== undefined && !Array.isArray(def[field])) {
|
|
73819
73824
|
errors.push(`${field} must be an array`);
|
|
73820
73825
|
}
|
|
73821
73826
|
}
|
|
73827
|
+
// Each subscription must name an eventType (PRD FR-009).
|
|
73828
|
+
if (Array.isArray(def.subscriptions)) {
|
|
73829
|
+
for (const sub of def.subscriptions) {
|
|
73830
|
+
if (!sub || typeof sub.eventType !== "string" || !sub.eventType.trim()) {
|
|
73831
|
+
errors.push("each subscription must have a non-empty eventType");
|
|
73832
|
+
break;
|
|
73833
|
+
}
|
|
73834
|
+
}
|
|
73835
|
+
}
|
|
73822
73836
|
return { valid: errors.length === 0, errors };
|
|
73823
73837
|
}
|
|
73824
73838
|
|
|
@@ -75740,6 +75754,92 @@ function handleMemoryTool$1(memory, ctx, toolName, args = {}) {
|
|
|
75740
75754
|
|
|
75741
75755
|
var memoryTools = { MEMORY_SERVER: MEMORY_SERVER$1, MEMORY_TOOLS: MEMORY_TOOLS$1, handleMemoryTool: handleMemoryTool$1 };
|
|
75742
75756
|
|
|
75757
|
+
/**
|
|
75758
|
+
* eventMatcher.js
|
|
75759
|
+
*
|
|
75760
|
+
* Given a fired event and the set of bots, returns the bots that subscribe to
|
|
75761
|
+
* it (PRD FR-009 / US-010). A bot subscribes via `subscriptions: [{ eventType }]`.
|
|
75762
|
+
*
|
|
75763
|
+
* Workspace scoping is designed in but not yet exercised: when an event carries
|
|
75764
|
+
* a `workspaceId`, a bot only matches if it's unscoped (`workspaceId` falsy) or
|
|
75765
|
+
* its `workspaceId` equals the event's. Events aren't stamped with a workspace
|
|
75766
|
+
* yet (a follow-up slice), so today every subscribed bot matches — but the
|
|
75767
|
+
* scoping "just works" once stamping lands, with no matcher change.
|
|
75768
|
+
*
|
|
75769
|
+
* Pure (NFR-006): no Electron, no I/O.
|
|
75770
|
+
*/
|
|
75771
|
+
|
|
75772
|
+
/**
|
|
75773
|
+
* @param {Array<object>} bots bot definitions (each may have `subscriptions`)
|
|
75774
|
+
* @param {{ eventType: string, workspaceId?: string }} event
|
|
75775
|
+
* @param {{ excludeBotId?: string }} [opts] loop guard — never match this bot
|
|
75776
|
+
* @returns {Array<object>} the subscribing bots
|
|
75777
|
+
*/
|
|
75778
|
+
function matchSubscribedBots$1(bots, event, opts = {}) {
|
|
75779
|
+
if (!Array.isArray(bots) || !event || !event.eventType) return [];
|
|
75780
|
+
const excludeBotId = opts.excludeBotId || null;
|
|
75781
|
+
const eventWorkspace = event.workspaceId || null;
|
|
75782
|
+
|
|
75783
|
+
return bots.filter((bot) => {
|
|
75784
|
+
if (!bot || bot.id === excludeBotId) return false;
|
|
75785
|
+
const subs = Array.isArray(bot.subscriptions) ? bot.subscriptions : [];
|
|
75786
|
+
const subscribed = subs.some((s) => s && s.eventType === event.eventType);
|
|
75787
|
+
if (!subscribed) return false;
|
|
75788
|
+
|
|
75789
|
+
// Workspace scoping (forward-compatible): only enforced once events carry a
|
|
75790
|
+
// workspaceId. An unscoped bot (no workspaceId) listens everywhere.
|
|
75791
|
+
if (eventWorkspace && bot.workspaceId) {
|
|
75792
|
+
return bot.workspaceId === eventWorkspace;
|
|
75793
|
+
}
|
|
75794
|
+
return true;
|
|
75795
|
+
});
|
|
75796
|
+
}
|
|
75797
|
+
|
|
75798
|
+
var eventMatcher = { matchSubscribedBots: matchSubscribedBots$1 };
|
|
75799
|
+
|
|
75800
|
+
/**
|
|
75801
|
+
* EventDispatcher.js
|
|
75802
|
+
*
|
|
75803
|
+
* Rate guard for event-triggered bot runs (PRD FR-009): a chatty event must not
|
|
75804
|
+
* spawn a flood of runs. Enforces a per-bot cooldown — a given bot won't be
|
|
75805
|
+
* event-triggered more often than once per `cooldownMs`. The dispatch decision
|
|
75806
|
+
* (pause, already-running) lives in botController; this owns only the timing.
|
|
75807
|
+
*
|
|
75808
|
+
* Pure (NFR-006): the clock is injected, so it's deterministic under test and
|
|
75809
|
+
* Electron-free.
|
|
75810
|
+
*/
|
|
75811
|
+
|
|
75812
|
+
const DEFAULT_COOLDOWN_MS = 10_000;
|
|
75813
|
+
|
|
75814
|
+
let EventDispatcher$1 = class EventDispatcher {
|
|
75815
|
+
/**
|
|
75816
|
+
* @param {{ cooldownMs?: number, clock?: { nowMs: () => number } }} [opts]
|
|
75817
|
+
*/
|
|
75818
|
+
constructor(opts = {}) {
|
|
75819
|
+
this._cooldownMs =
|
|
75820
|
+
typeof opts.cooldownMs === "number"
|
|
75821
|
+
? opts.cooldownMs
|
|
75822
|
+
: DEFAULT_COOLDOWN_MS;
|
|
75823
|
+
this._nowMs = (opts.clock && opts.clock.nowMs) || (() => Date.now());
|
|
75824
|
+
/** @type {Map<string, number>} botId → last dispatch time (ms) */
|
|
75825
|
+
this._last = new Map();
|
|
75826
|
+
}
|
|
75827
|
+
|
|
75828
|
+
/** @returns {boolean} true if the bot is allowed to run for an event now. */
|
|
75829
|
+
shouldDispatch(botId) {
|
|
75830
|
+
const last = this._last.get(botId);
|
|
75831
|
+
if (last === undefined) return true;
|
|
75832
|
+
return this._nowMs() - last >= this._cooldownMs;
|
|
75833
|
+
}
|
|
75834
|
+
|
|
75835
|
+
/** Record that the bot was just event-dispatched (starts its cooldown). */
|
|
75836
|
+
note(botId) {
|
|
75837
|
+
this._last.set(botId, this._nowMs());
|
|
75838
|
+
}
|
|
75839
|
+
};
|
|
75840
|
+
|
|
75841
|
+
var EventDispatcher_1 = { EventDispatcher: EventDispatcher$1, DEFAULT_COOLDOWN_MS };
|
|
75842
|
+
|
|
75743
75843
|
/**
|
|
75744
75844
|
* mcpResult.js
|
|
75745
75845
|
*
|
|
@@ -75812,6 +75912,8 @@ const {
|
|
|
75812
75912
|
MEMORY_TOOLS,
|
|
75813
75913
|
handleMemoryTool,
|
|
75814
75914
|
} = memoryTools;
|
|
75915
|
+
const { matchSubscribedBots } = eventMatcher;
|
|
75916
|
+
const { EventDispatcher } = EventDispatcher_1;
|
|
75815
75917
|
const { normalizeMcpResult } = mcpResult;
|
|
75816
75918
|
const {
|
|
75817
75919
|
getProvider: getProvider$1,
|
|
@@ -75833,6 +75935,25 @@ function pricingFor(providerId, model) {
|
|
|
75833
75935
|
return (m && m.pricing) || null;
|
|
75834
75936
|
}
|
|
75835
75937
|
|
|
75938
|
+
/** Build the run prompt for an event-triggered bot (P1: FR-009). */
|
|
75939
|
+
function composeEventPrompt(event) {
|
|
75940
|
+
let payload;
|
|
75941
|
+
try {
|
|
75942
|
+
payload =
|
|
75943
|
+
event.content === undefined
|
|
75944
|
+
? "(no payload)"
|
|
75945
|
+
: JSON.stringify(event.content);
|
|
75946
|
+
} catch (_e) {
|
|
75947
|
+
payload = "(unserializable payload)";
|
|
75948
|
+
}
|
|
75949
|
+
return (
|
|
75950
|
+
`An event you subscribe to just fired.\n\n` +
|
|
75951
|
+
`Event: ${event.eventType}\n` +
|
|
75952
|
+
`Payload: ${payload}\n\n` +
|
|
75953
|
+
`Follow your instructions to handle it.`
|
|
75954
|
+
);
|
|
75955
|
+
}
|
|
75956
|
+
|
|
75836
75957
|
const botController$1 = {
|
|
75837
75958
|
_ready: false,
|
|
75838
75959
|
|
|
@@ -75892,6 +76013,10 @@ const botController$1 = {
|
|
|
75892
76013
|
// tools that every bot can use without a consent prompt.
|
|
75893
76014
|
this._memory = new BotMemory({ persistence: host.memoryPersistence });
|
|
75894
76015
|
|
|
76016
|
+
// Event-bus bridge (P1: FR-009): per-bot cooldown so a chatty event can't
|
|
76017
|
+
// flood the runner with event-triggered runs.
|
|
76018
|
+
this._dispatcher = new EventDispatcher();
|
|
76019
|
+
|
|
75895
76020
|
this._runner = new BotRunner({
|
|
75896
76021
|
engines,
|
|
75897
76022
|
store: this._store,
|
|
@@ -75947,6 +76072,30 @@ const botController$1 = {
|
|
|
75947
76072
|
}
|
|
75948
76073
|
},
|
|
75949
76074
|
|
|
76075
|
+
/**
|
|
76076
|
+
* Dispatch a fired event to every subscribed bot (P1: FR-009 / US-010).
|
|
76077
|
+
* Called by dash-electron's widget-event relay tap. Guards: never re-trigger
|
|
76078
|
+
* the origin bot (loop safety), skip paused bots and bots in cooldown; a bot
|
|
76079
|
+
* already running is skipped by the runner itself.
|
|
76080
|
+
* @param {{ eventType: string, content?: any, workspaceId?: string,
|
|
76081
|
+
* originBotId?: string }} event
|
|
76082
|
+
*/
|
|
76083
|
+
handleEvent(event) {
|
|
76084
|
+
if (!this._ready || !event || !event.eventType) return;
|
|
76085
|
+
const bots = matchSubscribedBots(this._store.list(), event, {
|
|
76086
|
+
excludeBotId: event.originBotId,
|
|
76087
|
+
});
|
|
76088
|
+
for (const bot of bots) {
|
|
76089
|
+
if (this._pause.isPaused(bot.id)) continue;
|
|
76090
|
+
if (!this._dispatcher.shouldDispatch(bot.id)) continue;
|
|
76091
|
+
this._dispatcher.note(bot.id);
|
|
76092
|
+
this._run(bot.id, {
|
|
76093
|
+
prompt: composeEventPrompt(event),
|
|
76094
|
+
trigger: "event",
|
|
76095
|
+
});
|
|
76096
|
+
}
|
|
76097
|
+
},
|
|
76098
|
+
|
|
75950
76099
|
// ---- IPC-facing operations ---------------------------------------------
|
|
75951
76100
|
|
|
75952
76101
|
list() {
|