@eventmodelers/cli 1.0.71 → 1.0.72
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/README.md
CHANGED
|
@@ -272,9 +272,12 @@ the turn's own instructions rather than being enforced from outside — the `cla
|
|
|
272
272
|
what spawns the agents — so it's a budget the agent is told to keep, not a hard ceiling.
|
|
273
273
|
|
|
274
274
|
Every event that arrives is remembered until a turn carries it — including events that land
|
|
275
|
-
while a turn is running. Its own writes come back on that same channel and
|
|
276
|
-
|
|
277
|
-
|
|
275
|
+
while a turn is running. Its own writes come back on that same channel, and each event says who
|
|
276
|
+
made it (`agent_id` from the writer's `x-agent-id` header, `user_id` for a browser), so the
|
|
277
|
+
agent's own echo is recognized exactly: a burst that is nothing but its own writes is dropped
|
|
278
|
+
without spending a turn, and one that is mixed lists its own lines marked as such. Only a write
|
|
279
|
+
that reached the platform with neither id falls back to the echo window's guess and is labelled
|
|
280
|
+
"possibly your own". The lane is damped on timing on top of that: it waits for a quiet period (but not forever), waits
|
|
278
281
|
out the echo window of its last turn, never fires twice in quick succession, and widens that
|
|
279
282
|
floor each time it answers `NOOP`, so a finished board goes quiet by itself. Override the
|
|
280
283
|
windows if the defaults don't suit your board:
|
|
@@ -283,7 +286,7 @@ windows if the defaults don't suit your board:
|
|
|
283
286
|
|---|---|---|
|
|
284
287
|
| `EVENTMODELERS_STANDALONE_DEBOUNCE_MS` | `8000` | quiet period before buffered board changes turn into a turn |
|
|
285
288
|
| `EVENTMODELERS_STANDALONE_MAX_WAIT_MS` | `90000` | cap on that quiet period, so a board being edited continuously still gets a turn |
|
|
286
|
-
| `EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS` | `20000` | after a turn, how long incoming
|
|
289
|
+
| `EVENTMODELERS_STANDALONE_ECHO_WINDOW_MS` | `20000` | after a turn, how long an *unattributed* incoming change is labelled as probably the agent's own echo (attributed ones are identified outright) |
|
|
287
290
|
| `EVENTMODELERS_STANDALONE_MIN_INTERVAL_MS` | `60000` | floor between two self-directed turns |
|
|
288
291
|
| `EVENTMODELERS_STANDALONE_BACKOFF_CAP_MS` | `900000` | ceiling that floor doubles up to while turns keep answering `NOOP` |
|
|
289
292
|
| `EVENTMODELERS_STANDALONE_IDLE_MS` | `900000` | with nothing happening at all, how long before the agent reviews the model anyway (`0` disables it) |
|
package/cli.js
CHANGED
|
@@ -1310,7 +1310,15 @@ function configureHooks({ hooksSrc, targetDir }) {
|
|
|
1310
1310
|
// per task, or a long-lived warm process) must call this first — a `.mcp.json`
|
|
1311
1311
|
// written mid-session by the process itself is too late for that same process.
|
|
1312
1312
|
// The token itself is never written to disk here — `${EVENTMODELERS_TOKEN}` is
|
|
1313
|
-
// resolved by `claude` from its own process env, which the caller must set.
|
|
1313
|
+
// resolved by `claude` from its own process env, which the caller must set. Same for
|
|
1314
|
+
// `${EVENTMODELERS_AGENT_ID}`: without it on the transport, every MCP write this agent makes
|
|
1315
|
+
// reaches the platform unattributed (board_events.agent_id null), and the board shows one
|
|
1316
|
+
// anonymous robot for it instead of this agent's name. It belongs here rather than only in the
|
|
1317
|
+
// skill's Step 3.5 because this entry is rewritten on every run — it would otherwise overwrite
|
|
1318
|
+
// the header the skill just added — and because a `.mcp.json` fixed mid-session comes too late
|
|
1319
|
+
// for the `claude` process already running. An unset var arrives at the server as the literal
|
|
1320
|
+
// `${EVENTMODELERS_AGENT_ID}` text, which it drops (it only accepts a uuid), so the entry is the
|
|
1321
|
+
// same one for a human's session and an agent's.
|
|
1314
1322
|
function ensureMcpRegistered(projectDir, baseUrl) {
|
|
1315
1323
|
const mcpConfigPath = join(projectDir, '.mcp.json');
|
|
1316
1324
|
const mcpConfig = readJsonSafe(mcpConfigPath);
|
|
@@ -1318,7 +1326,7 @@ function ensureMcpRegistered(projectDir, baseUrl) {
|
|
|
1318
1326
|
mcpConfig.mcpServers.eventmodelers = {
|
|
1319
1327
|
type: 'http',
|
|
1320
1328
|
url: `${baseUrl}/mcp`,
|
|
1321
|
-
headers: { 'x-token': '${EVENTMODELERS_TOKEN}' },
|
|
1329
|
+
headers: { 'x-token': '${EVENTMODELERS_TOKEN}', 'x-agent-id': '${EVENTMODELERS_AGENT_ID}' },
|
|
1322
1330
|
};
|
|
1323
1331
|
writeFileSync(mcpConfigPath, JSON.stringify(mcpConfig, null, 2));
|
|
1324
1332
|
}
|
|
@@ -2044,20 +2052,22 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2044
2052
|
// later adds to these payloads lands here without a client change.
|
|
2045
2053
|
const BOARD_CHANGE_EVENTS = ['node:created', 'node:changed', 'node:deleted', 'edge:added', 'edge:removed', 'board:cleared'];
|
|
2046
2054
|
|
|
2047
|
-
// Four knobs
|
|
2048
|
-
//
|
|
2049
|
-
// agent's own edits are indistinguishable from the human's. They all govern *when* a
|
|
2050
|
-
// self-directed turn fires — never whether an event is remembered. Everything that
|
|
2051
|
-
// arrives is buffered (see onBoardEvent): an event seen while a turn runs, or inside the
|
|
2052
|
-
// echo window, is only *marked* as possibly the agent's own write, so a burst that
|
|
2055
|
+
// Four knobs. They govern *when* a self-directed turn fires — never whether an event is
|
|
2056
|
+
// remembered: everything that arrives is buffered (see onBoardEvent), so a burst that
|
|
2053
2057
|
// straddles a turn boundary still reaches the next turn instead of being thrown away and
|
|
2054
2058
|
// leaving the agent looking at whichever single event happened to land last.
|
|
2059
|
+
// Who wrote an event is *read off the event*, not inferred from these windows: the payload
|
|
2060
|
+
// carries `agent_id` (stamped from the writer's `x-agent-id` header) and `user_id` (a
|
|
2061
|
+
// browser session), so this agent's own echo is identified exactly and dropped without
|
|
2062
|
+
// costing a turn. The windows below only cover the one case attribution can't: a write that
|
|
2063
|
+
// carries neither id.
|
|
2055
2064
|
// DEBOUNCE — one gesture (place a node, drag a column) fans out into several
|
|
2056
2065
|
// events; wait for the board to fall quiet, then send a single turn.
|
|
2057
2066
|
// MAX_WAIT — cap on that quiet period: a board someone keeps editing never falls
|
|
2058
2067
|
// quiet, and the debounce alone would slide forever.
|
|
2059
|
-
// ECHO_WINDOW — how long after a turn its own writes are expected back;
|
|
2060
|
-
// that window
|
|
2068
|
+
// ECHO_WINDOW — how long after a turn its own writes are expected back; an
|
|
2069
|
+
// *unattributed* change in that window is labelled a possible echo, and
|
|
2070
|
+
// the next turn waits it out so a write and its echo don't each get one.
|
|
2061
2071
|
// MIN_INTERVAL — a floor between self-directed turns, so a mistake upstream can't
|
|
2062
2072
|
// become a self-feeding loop burning tokens unattended. Doubles per
|
|
2063
2073
|
// consecutive NOOP up to BACKOFF_CAP, and resets as soon as a turn
|
|
@@ -2077,10 +2087,11 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2077
2087
|
const STANDALONE_BACKOFF_CAP_MS = envMs('EVENTMODELERS_STANDALONE_BACKOFF_CAP_MS', 15 * 60_000);
|
|
2078
2088
|
const STANDALONE_IDLE_MS = envMs('EVENTMODELERS_STANDALONE_IDLE_MS', 15 * 60_000);
|
|
2079
2089
|
|
|
2080
|
-
// node_id (or '(board)') -> { types: Set<string>, count
|
|
2081
|
-
//
|
|
2082
|
-
//
|
|
2083
|
-
//
|
|
2090
|
+
// node_id (or '(board)') -> { types: Set<string>, count, own, other, maybe } for
|
|
2091
|
+
// everything seen since the last self-directed turn, each event counted into exactly one
|
|
2092
|
+
// origin bucket: `own` = attributed to this agent's own id, `other` = attributed to a human
|
|
2093
|
+
// or another agent, `maybe` = carries no attribution at all and landed inside the echo
|
|
2094
|
+
// window, so it *might* be this agent's. Only `maybe` is a guess.
|
|
2084
2095
|
const observed = new Map();
|
|
2085
2096
|
let observedCount = 0;
|
|
2086
2097
|
let seqLo = null;
|
|
@@ -2097,6 +2108,16 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2097
2108
|
return Math.min(STANDALONE_MIN_INTERVAL_MS * 2 ** noopStreak, STANDALONE_BACKOFF_CAP_MS);
|
|
2098
2109
|
}
|
|
2099
2110
|
|
|
2111
|
+
// Empties the buffer — every field of it, which is why it is one function and not five
|
|
2112
|
+
// lines repeated at each place a burst stops being pending.
|
|
2113
|
+
function resetObserved() {
|
|
2114
|
+
observed.clear();
|
|
2115
|
+
observedCount = 0;
|
|
2116
|
+
seqLo = null;
|
|
2117
|
+
seqHi = null;
|
|
2118
|
+
firstObservedAt = 0;
|
|
2119
|
+
}
|
|
2120
|
+
|
|
2100
2121
|
function onBoardEvent(type, payload) {
|
|
2101
2122
|
if (!standalone) {
|
|
2102
2123
|
if (verbose) log(`board event ${type} dropped — not running with --standalone`);
|
|
@@ -2107,12 +2128,26 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2107
2128
|
// The warm-up turn is read-only, so a change that lands while it runs is somebody
|
|
2108
2129
|
// else's — labelling it "possibly your own write" would only teach the agent to
|
|
2109
2130
|
// discount the very edits it just came up to work on.
|
|
2110
|
-
const
|
|
2131
|
+
const guessOwn = (!!pending && !warmingUp) || draining || inEchoWindow;
|
|
2132
|
+
// Attribution beats the clock in both directions. `agent_id === ours` is this agent's own
|
|
2133
|
+
// write, certainly, whenever it comes back. Any *other* id — a person's user_id, another
|
|
2134
|
+
// agent's — is certainly not ours, which is the half the timer used to get wrong: a human
|
|
2135
|
+
// editing while this agent worked had their change written off as an echo of it.
|
|
2136
|
+
const writerAgent = payload?.agent_id || null;
|
|
2137
|
+
const writerUser = payload?.user_id || null;
|
|
2138
|
+
const origin =
|
|
2139
|
+
writerAgent && cfg.agentId && writerAgent === cfg.agentId
|
|
2140
|
+
? 'own'
|
|
2141
|
+
: writerAgent || writerUser
|
|
2142
|
+
? 'other'
|
|
2143
|
+
: guessOwn
|
|
2144
|
+
? 'maybe'
|
|
2145
|
+
: 'other';
|
|
2111
2146
|
const nodeId = payload?.node_id ?? '(board)';
|
|
2112
|
-
const entry = observed.get(nodeId) ?? { types: new Set(), count: 0,
|
|
2147
|
+
const entry = observed.get(nodeId) ?? { types: new Set(), count: 0, own: 0, other: 0, maybe: 0 };
|
|
2113
2148
|
entry.types.add(type);
|
|
2114
2149
|
entry.count += 1;
|
|
2115
|
-
|
|
2150
|
+
entry[origin] += 1;
|
|
2116
2151
|
observed.set(nodeId, entry);
|
|
2117
2152
|
observedCount += 1;
|
|
2118
2153
|
if (!firstObservedAt) firstObservedAt = Date.now();
|
|
@@ -2121,7 +2156,17 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2121
2156
|
if (seqLo === null || seq < seqLo) seqLo = seq;
|
|
2122
2157
|
if (seqHi === null || seq > seqHi) seqHi = seq;
|
|
2123
2158
|
}
|
|
2124
|
-
|
|
2159
|
+
const writtenBy =
|
|
2160
|
+
origin === 'own'
|
|
2161
|
+
? ' — own write'
|
|
2162
|
+
: origin === 'maybe'
|
|
2163
|
+
? ' — unattributed, maybe own write'
|
|
2164
|
+
: writerUser
|
|
2165
|
+
? ' — by a person'
|
|
2166
|
+
: writerAgent
|
|
2167
|
+
? ` — by agent ${writerAgent.slice(0, 8)}`
|
|
2168
|
+
: '';
|
|
2169
|
+
log(`board change: ${type} node=${nodeId}${Number.isFinite(seq) ? ` seq=${seq}` : ''}${writtenBy}`);
|
|
2125
2170
|
armStandaloneTurn(nextDelayMs());
|
|
2126
2171
|
}
|
|
2127
2172
|
|
|
@@ -2186,10 +2231,17 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2186
2231
|
'nothing, change nothing and reply <promise>NOOP</promise>.';
|
|
2187
2232
|
|
|
2188
2233
|
function buildStandaloneTurn() {
|
|
2189
|
-
const lines = [...observed.entries()].map(
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2234
|
+
const lines = [...observed.entries()].map(([nodeId, entry]) => {
|
|
2235
|
+
const origin =
|
|
2236
|
+
entry.own === entry.count
|
|
2237
|
+
? ' — YOUR OWN earlier write, echoed back'
|
|
2238
|
+
: entry.own
|
|
2239
|
+
? ` — ${entry.own} of ${entry.count} are YOUR OWN earlier writes, the rest are not`
|
|
2240
|
+
: entry.maybe === entry.count
|
|
2241
|
+
? ' — unattributed, possibly your own earlier write'
|
|
2242
|
+
: '';
|
|
2243
|
+
return `- ${nodeId}: ${[...entry.types].join(', ')} (${entry.count}×)${origin}`;
|
|
2244
|
+
});
|
|
2193
2245
|
const header = [
|
|
2194
2246
|
'BOARD_CHANGE',
|
|
2195
2247
|
`board_id=${cfg.boardId}`,
|
|
@@ -2238,6 +2290,15 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2238
2290
|
|
|
2239
2291
|
async function dispatchStandaloneTurn({ idle = false } = {}) {
|
|
2240
2292
|
if (!idle && !observed.size) return;
|
|
2293
|
+
// Every buffered event is provably this agent's own write coming back — attribution says
|
|
2294
|
+
// so, not a timer. There is nothing new on the board, so it costs no turn; if the board
|
|
2295
|
+
// then stays quiet the idle review still comes around.
|
|
2296
|
+
if (!idle && [...observed.values()].every((entry) => entry.own === entry.count)) {
|
|
2297
|
+
log(`standalone turn skipped: ${observedCount} board event(s) on ${observed.size} node(s), all own writes`);
|
|
2298
|
+
resetObserved();
|
|
2299
|
+
armIdleReview();
|
|
2300
|
+
return;
|
|
2301
|
+
}
|
|
2241
2302
|
// A direct message always outranks the agent's own initiative — re-arm instead of
|
|
2242
2303
|
// queueing behind the prompt lane, so the buffer just keeps collecting meanwhile.
|
|
2243
2304
|
if (pending || draining) {
|
|
@@ -2255,17 +2316,20 @@ async function runModeling(kitDir, projectDir, { verbose = false, standalone = f
|
|
|
2255
2316
|
if (idle) {
|
|
2256
2317
|
log(`standalone review turn: board quiet for ${Math.round(STANDALONE_IDLE_MS / 1000)}s`);
|
|
2257
2318
|
} else {
|
|
2258
|
-
const
|
|
2319
|
+
const totals = [...observed.values()].reduce(
|
|
2320
|
+
(acc, entry) => ({ own: acc.own + entry.own, maybe: acc.maybe + entry.maybe }),
|
|
2321
|
+
{ own: 0, maybe: 0 },
|
|
2322
|
+
);
|
|
2323
|
+
const breakdown = [
|
|
2324
|
+
totals.own ? `${totals.own} own` : null,
|
|
2325
|
+
totals.maybe ? `${totals.maybe} unattributed` : null,
|
|
2326
|
+
].filter(Boolean);
|
|
2259
2327
|
log(
|
|
2260
2328
|
`standalone turn: ${observedCount} board event(s) on ${observed.size} node(s)` +
|
|
2261
|
-
`${
|
|
2329
|
+
`${breakdown.length ? ` (${breakdown.join(', ')})` : ''}`,
|
|
2262
2330
|
);
|
|
2263
2331
|
}
|
|
2264
|
-
|
|
2265
|
-
observedCount = 0;
|
|
2266
|
-
seqLo = null;
|
|
2267
|
-
seqHi = null;
|
|
2268
|
-
firstObservedAt = 0;
|
|
2332
|
+
resetObserved();
|
|
2269
2333
|
lastStandaloneAt = Date.now();
|
|
2270
2334
|
try {
|
|
2271
2335
|
const result = await runClaudeWarm(text);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.72",
|
|
4
4
|
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, Cratis, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -881,7 +881,7 @@ Set a prompt's status, optionally attaching a progress comment. Auth: `x-token`
|
|
|
881
881
|
```
|
|
882
882
|
|
|
883
883
|
**Response**: `200` — the updated row
|
|
884
|
-
**Errors**: `400` invalid/missing `status` · `403` token not for this prompt's org · `404` prompt not found
|
|
884
|
+
**Errors**: `400` invalid/missing `status` · `403` token not for this prompt's org · `404` prompt not found · `409` (`PROMPT_STATUS_ALREADY_SET`) the prompt already carries that status — another agent already made this transition, so don't retry it
|
|
885
885
|
|
|
886
886
|
---
|
|
887
887
|
|
|
@@ -52,6 +52,7 @@ Omit `comment` entirely when there isn't one — don't pass an empty string.
|
|
|
52
52
|
|----------|---------|--------|
|
|
53
53
|
| `400` | `status` missing or not a valid value | Fix the value and retry — do not retry with the same bad value. |
|
|
54
54
|
| `404` | Prompt not found | The prompt may have been deleted by its author while you were working. Report this and move on — do not treat it as a failure of your actual task work. |
|
|
55
|
+
| `409` | The prompt already carries the status you asked for (`PROMPT_STATUS_ALREADY_SET`) | Somebody else — usually another agent working the same queue — already made this transition. Do not retry it; treat the transition as done and carry on. |
|
|
55
56
|
| `401`/`403` | Token invalid or wrong organization | Re-run `connect` to refresh credentials, then retry once. |
|
|
56
57
|
|
|
57
58
|
---
|
|
@@ -199,6 +199,9 @@ Steps:
|
|
|
199
199
|
|
|
200
200
|
Keep these turns finished within the turn: wait for the subagents you dispatched, don't leave
|
|
201
201
|
work trailing. Everything you and they write to the board comes back on this same channel as
|
|
202
|
-
another change
|
|
203
|
-
|
|
204
|
-
|
|
202
|
+
another change. A burst that is *only* your own writes never becomes a turn at all, so a
|
|
203
|
+
change list you are handed always contains something that isn't yours — but it may still list
|
|
204
|
+
yours alongside it, marked `YOUR OWN earlier write`. Take that mark literally: those lines are
|
|
205
|
+
there for context, not to be reworked. A line marked `unattributed, possibly your own earlier
|
|
206
|
+
write` is the one uncertain case (a write that reached the platform without an agent id);
|
|
207
|
+
anything unmarked was written by someone else and is real work to look at.
|