@eventmodelers/cli 1.0.79 → 1.0.81
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 +54 -3
- package/RELEASE_NOTES.md +3 -1
- package/cli.js +22 -8
- package/lib/modeling-local-ai.js +19 -4
- package/package.json +1 -1
- package/shared/skills/connect/SKILL.md +6 -4
- package/shared/skills/learn-eventmodelers-api/SKILL.md +11 -10
- package/shared/skills/request-feedback/SKILL.md +0 -2
- package/shared/skills/update-slice-status/SKILL.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/attributes/references/api-fallback.md +1 -3
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +0 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/api-fallback.md +0 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/SKILL.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/api-fallback.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +1 -3
- package/stacks/modeling-kit/templates/.claude/skills/examples/references/api-fallback.md +1 -3
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/SKILL.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +3 -5
- package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md +4 -8
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +0 -4
- package/stacks/modeling-kit/templates/.claude/skills/timeline/references/api-fallback.md +1 -7
- package/stacks/modeling-kit/templates/kit/AGENTS.md +2 -1
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +1 -1
package/README.md
CHANGED
|
@@ -335,7 +335,20 @@ SGLang all serve. Everything above the transport is identical, which is why anyt
|
|
|
335
335
|
OpenAI-compatible works by pointing `LOCAL_AI_URL` at it. The `LOCAL_AI_*` vars are the same
|
|
336
336
|
ones a build kit's local runner reads (`LOCAL_AI_TARGET`, `LOCAL_AI_URL`, `LOCAL_AI_API`,
|
|
337
337
|
`LOCAL_AI_MODEL`, `LOCAL_AI_API_KEY`, `LOCAL_AI_NUM_CTX`), or set them once as `localAi` in
|
|
338
|
-
`.eventmodelers/config.json
|
|
338
|
+
`.eventmodelers/config.json` — env wins over the file, the file over the preset:
|
|
339
|
+
|
|
340
|
+
```json
|
|
341
|
+
{
|
|
342
|
+
"boardId": "...",
|
|
343
|
+
"token": "...",
|
|
344
|
+
"localAi": {
|
|
345
|
+
"target": "ollama",
|
|
346
|
+
"url": "http://localhost:11434",
|
|
347
|
+
"model": "qwen3.5:9b",
|
|
348
|
+
"numCtx": 49152
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
```
|
|
339
352
|
|
|
340
353
|
**What you get and what you don't.** The loop around the turn is unchanged — the prompt queue,
|
|
341
354
|
the standalone board-change lane with all its damping, the idle review, the alive-ping, and the
|
|
@@ -349,9 +362,9 @@ than read from `.agent-modeling-kit/CLAUDE.md`. `--max-agents` has nothing to ca
|
|
|
349
362
|
|
|
350
363
|
Give it room: the platform's MCP tool schemas alone run to ~16k tokens, so Ollama's default
|
|
351
364
|
`num_ctx` of 4096 would silently truncate the tool block and leave the model inventing tool
|
|
352
|
-
names. The runner raises it to
|
|
365
|
+
names. The runner raises it to 49152 by default (`LOCAL_AI_NUM_CTX`) and warns when the schemas
|
|
353
366
|
still fill more than 60% of it. On an OpenAI-compatible server the context is fixed at launch
|
|
354
|
-
instead, so start it accordingly (vLLM: `--max-model-len
|
|
367
|
+
instead, so start it accordingly (vLLM: `--max-model-len 49152`, llama.cpp: `-c 49152`) —
|
|
355
368
|
overflow there surfaces as an HTTP 400, which the runner reports with that advice attached.
|
|
356
369
|
A turn is capped at 24 tool iterations, which ends a model that has lost the plot without
|
|
357
370
|
ending the session.
|
|
@@ -361,6 +374,44 @@ different thing, and `anthropicBaseUrl` below is that path — but note that it
|
|
|
361
374
|
speaking Anthropic's own `/v1/messages`, which Ollama does not serve. Pointing it straight at
|
|
362
375
|
`localhost:11434` gets you a 404; a translating proxy has to sit in between.
|
|
363
376
|
|
|
377
|
+
### Running headless, in a container
|
|
378
|
+
|
|
379
|
+
`run` normally confirms which board it is about to drive and where that board's credentials
|
|
380
|
+
come from. Those questions are worth one keystroke at a terminal and fatal anywhere else, so
|
|
381
|
+
`--non-interactive` turns them off: the board and credentials that resolve from flags,
|
|
382
|
+
`EVENTMODELERS_*` env vars and the config files are taken as final, and an incomplete set fails
|
|
383
|
+
the run with the reason instead of being interviewed for.
|
|
384
|
+
|
|
385
|
+
A missing TTY already implied this, which covers CI and most process supervisors — but not a
|
|
386
|
+
container started with `-it`, or a loop started from a terminal: stdin is a TTY nobody is
|
|
387
|
+
watching, and the run stops on a question forever. Pass the flag rather than relying on how
|
|
388
|
+
stdin happens to be wired.
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
docker run --rm \
|
|
392
|
+
-e EVENTMODELERS_TOKEN=<token> \
|
|
393
|
+
-e EVENTMODELERS_ORGANIZATION_ID=<uuid> \
|
|
394
|
+
-e EVENTMODELERS_BOARD_ID=<uuid> \
|
|
395
|
+
-e LOCAL_AI_URL=http://host.docker.internal:11434 \
|
|
396
|
+
node:20 npx -y @eventmodelers/cli run --standalone --local-ai --non-interactive
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Two things a container needs that a laptop already had:
|
|
400
|
+
|
|
401
|
+
- **A writable `HOME`.** With no kit in the working directory the agent installs one under
|
|
402
|
+
`~/.eventmodelers/kit` and keeps per-board credentials in `~/.eventmodelers/boards/` (`0600`,
|
|
403
|
+
in a `0700` directory). Run as a user whose home exists and is writable, or mount a volume
|
|
404
|
+
there to keep the kit across restarts instead of reinstalling it on every boot.
|
|
405
|
+
- **A reachable model server.** `localhost` inside the container is the container, not the host
|
|
406
|
+
— point `LOCAL_AI_URL` at `host.docker.internal` (Docker Desktop), the host's LAN address, or
|
|
407
|
+
the service name if Ollama/vLLM is a sibling container.
|
|
408
|
+
|
|
409
|
+
`EVENTMODELERS_TOKEN` now also answers the credentials question on its own, the way
|
|
410
|
+
`EVENTMODELERS_BOARD_ID` always answered the board question — an env var that wins over every
|
|
411
|
+
config file anyway makes asking where the credentials come from moot. So a fully env-driven run
|
|
412
|
+
is silent even without the flag; pass it anyway, so a half-set environment fails loudly instead
|
|
413
|
+
of waiting for an answer.
|
|
414
|
+
|
|
364
415
|
### Installing skills globally
|
|
365
416
|
|
|
366
417
|
By default, skills are copied into the project's own `.claude/skills/`. Pass `--global` to `init` or `init-modeling` to install them into `~/.claude/skills/` instead — available in every project without re-running the installer each time:
|
package/RELEASE_NOTES.md
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
## Unreleased
|
|
2
2
|
|
|
3
3
|
### Features
|
|
4
|
-
- `run --modeling`/`run --standalone` accept `--local-ai`, so the modeling agent can be driven by a local or self-hosted model instead of Claude. It was previously rejected as a build-kit-only flag, which conflated two different things: `--bash`/`--exec` select a *queue* the modeling loop has no equivalent of, while `--local-ai` selects a *model*. Both wire dialects are supported — `--local-ai` (bare) or `--local-ai ollama` for Ollama's native `POST /api/chat`, and `--local-ai vllm`/`lmstudio`/`llamacpp` for the OpenAI-compatible `POST /v1/chat/completions` that vLLM, LM Studio, llama.cpp-server, TGI and SGLang serve; anything else OpenAI-compatible works via `LOCAL_AI_URL`. The loop itself is untouched: the prompt queue, the standalone board-change lane and its damping, the idle review and the platform's full MCP tool set all behave as before, because everything Claude-specific already sat behind a single `runTurn(text)` seam. What a local model cannot bring along is the part that is not a wire format — the skills (`/place-element`, `/timeline`, `eventmodeling-*`) and the subagent fan-out are Claude Code features — so a self-directed turn does the most valuable piece of work itself, inline, and the board rules it needs come from the new runner's system prompt (`lib/modeling-local-ai.js`) instead of `.agent-modeling-kit/CLAUDE.md`. `--max-agents` is ignored in this mode. Ollama's `num_ctx` is raised to
|
|
4
|
+
- `run --modeling`/`run --standalone` accept `--local-ai`, so the modeling agent can be driven by a local or self-hosted model instead of Claude. It was previously rejected as a build-kit-only flag, which conflated two different things: `--bash`/`--exec` select a *queue* the modeling loop has no equivalent of, while `--local-ai` selects a *model*. Both wire dialects are supported — `--local-ai` (bare) or `--local-ai ollama` for Ollama's native `POST /api/chat`, and `--local-ai vllm`/`lmstudio`/`llamacpp` for the OpenAI-compatible `POST /v1/chat/completions` that vLLM, LM Studio, llama.cpp-server, TGI and SGLang serve; anything else OpenAI-compatible works via `LOCAL_AI_URL`. The loop itself is untouched: the prompt queue, the standalone board-change lane and its damping, the idle review and the platform's full MCP tool set all behave as before, because everything Claude-specific already sat behind a single `runTurn(text)` seam. What a local model cannot bring along is the part that is not a wire format — the skills (`/place-element`, `/timeline`, `eventmodeling-*`) and the subagent fan-out are Claude Code features — so a self-directed turn does the most valuable piece of work itself, inline, and the board rules it needs come from the new runner's system prompt (`lib/modeling-local-ai.js`) instead of `.agent-modeling-kit/CLAUDE.md`. `--max-agents` is ignored in this mode. Ollama's `num_ctx` is raised to 49152 by default (the MCP tool schemas alone are ~16k tokens, well past Ollama's 4096 default, which would silently truncate the tool block), with a warning when the schemas still fill >60% of it and a turn cap of 24 tool iterations.
|
|
5
|
+
|
|
6
|
+
- `run --non-interactive` never asks anything: the board and credentials that resolve from flags, `EVENTMODELERS_*` env vars and the config files are final, and an incomplete set fails the run with the reason instead of being interviewed for. A missing TTY already implied this, which covered CI and most supervisors, but not a container started with `-it` or a loop started from a terminal — stdin is a TTY nobody is watching, and the run stopped on the board confirmation or the credentials question forever. `EVENTMODELERS_TOKEN` also now answers the credentials question on its own, the way `EVENTMODELERS_BOARD_ID` always answered the board question: an env var that outranks every config file makes asking where the credentials come from moot. The README gains a container recipe, including the two things a container needs that a laptop already had — a writable HOME for the global kit and per-board credentials, and a `LOCAL_AI_URL` that isn't `localhost`.
|
|
5
7
|
|
|
6
8
|
### Docs
|
|
7
9
|
- The `anthropicBaseUrl` option no longer implies Ollama works behind it. That route keeps Claude Code and swaps only the endpoint, so the server has to speak Anthropic's own `/v1/messages`; Ollama does not, and pointing it at `localhost:11434` returns a 404. For Ollama, `--local-ai` is the supported route, and the README now says which of the two to reach for.
|
package/cli.js
CHANGED
|
@@ -1568,10 +1568,22 @@ async function fetchDefaultBoardId(baseUrl, token) {
|
|
|
1568
1568
|
// .eventmodelers/config.json up the tree beat ~/.eventmodelers/config.json. So
|
|
1569
1569
|
// `run --standalone --board-id <uuid>` is enough for a board used before, and any run can
|
|
1570
1570
|
// be pointed somewhere else entirely with --token/--organization-id.
|
|
1571
|
-
async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print) {
|
|
1571
|
+
async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print, nonInteractive = false) {
|
|
1572
1572
|
const walked = loadEffectiveConfig(cwd, null, explicitConfigPath).config;
|
|
1573
1573
|
const explicit = Object.fromEntries(Object.entries(flags ?? {}).filter(([, v]) => v));
|
|
1574
1574
|
|
|
1575
|
+
// Whether there is anyone to ask, decided once. A TTY was the only signal before, which is
|
|
1576
|
+
// right for a supervisor or CI (no stdin, so nothing to ask) but wrong for a loop started
|
|
1577
|
+
// from a terminal: stdin is a TTY nobody is watching, and the run stops on a question.
|
|
1578
|
+
// --non-interactive says so explicitly; --print never asks either.
|
|
1579
|
+
const interactive = !print && !nonInteractive && process.stdin.isTTY;
|
|
1580
|
+
|
|
1581
|
+
// An EVENTMODELERS_TOKEN set for this run answers the credentials question below as
|
|
1582
|
+
// squarely as --token does — applyEnvOverrides makes it win over every file anyway, so
|
|
1583
|
+
// asking where this board's credentials come from could not change the outcome. The board
|
|
1584
|
+
// question has always treated its env var this way; this is the same rule for the token.
|
|
1585
|
+
const credentialsNamed = !!(explicit.token || process.env.EVENTMODELERS_TOKEN);
|
|
1586
|
+
|
|
1575
1587
|
// Which board comes first — everything else is stored per board, so there is nothing to
|
|
1576
1588
|
// look up until we know which board this run is for.
|
|
1577
1589
|
let boardId = explicit.boardId || process.env.EVENTMODELERS_BOARD_ID || walked.boardId || null;
|
|
@@ -1583,7 +1595,7 @@ async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print)
|
|
|
1583
1595
|
// non-interactive stdin such as CI or a process supervisor), where the resolved value
|
|
1584
1596
|
// stands on its own exactly as before.
|
|
1585
1597
|
let boardChosen = !!(explicit.boardId || process.env.EVENTMODELERS_BOARD_ID);
|
|
1586
|
-
if (!boardChosen &&
|
|
1598
|
+
if (!boardChosen && interactive) {
|
|
1587
1599
|
const answer = await prompt(boardId ? `\n Board ID [${boardId}]: ` : '\n Board ID: ');
|
|
1588
1600
|
if (answer) {
|
|
1589
1601
|
boardId = answer;
|
|
@@ -1613,7 +1625,7 @@ async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print)
|
|
|
1613
1625
|
// implied — explicit credentials on the command line — or when there is no one to ask:
|
|
1614
1626
|
// --print, or a non-interactive stdin such as CI or a supervisor that would otherwise
|
|
1615
1627
|
// hang here forever (those keep using whatever is on file, silently).
|
|
1616
|
-
if (
|
|
1628
|
+
if (interactive && !credentialsNamed) {
|
|
1617
1629
|
const hasAccountWide = !!(walked.token && walked.organizationId);
|
|
1618
1630
|
|
|
1619
1631
|
// What "keep" would keep. A pointer entry is deliberately not offered: it says the
|
|
@@ -1678,9 +1690,10 @@ async function resolveModelingCredentials(cwd, flags, explicitConfigPath, print)
|
|
|
1678
1690
|
: { ...applyEnvOverrides({ ...walked, ...stored }), ...explicit };
|
|
1679
1691
|
if (boardId) config.boardId = boardId;
|
|
1680
1692
|
|
|
1681
|
-
if (!config.token || !config.organizationId) {
|
|
1693
|
+
if ((!config.token || !config.organizationId) && interactive) {
|
|
1682
1694
|
// Nothing anywhere — ask once, and save it account-wide rather than into this
|
|
1683
|
-
// directory, so every later run from anywhere is silent.
|
|
1695
|
+
// directory, so every later run from anywhere is silent. Headless, there is nobody to
|
|
1696
|
+
// interview, so this is skipped and the check below fails the run with the reason.
|
|
1684
1697
|
console.log('🔐 No Eventmodelers credentials found — configuring them once, account-wide.\n');
|
|
1685
1698
|
config = await configureCredentials({
|
|
1686
1699
|
config,
|
|
@@ -3107,6 +3120,7 @@ credentialFlags(program
|
|
|
3107
3120
|
.option('--exec [command]', 'Hand each prompt to an external agent command instead of the default Claude runner, via ralph-exec.js (build-kit stacks only) — for agentic harnesses that bring their own tool loop, e.g. "codex exec --full-auto" or "opencode run". The prompt is appended as a quoted argument and also written to the file named by RALPH_PROMPT_FILE. Bare --exec uses localAi.exec from .eventmodelers/config.json. Claude remains the default when this flag is absent.')
|
|
3108
3121
|
.option('--bash', 'Use the bash-only ralph.sh loop (build-kit stacks only, no realtime)')
|
|
3109
3122
|
.option('--modeling', 'Keep one Claude process warm across prompts instead of spawning a fresh one per task, for low-latency voice/live use. Runs from a modeling-kit install in this directory, or from the global install (~/.eventmodelers/kit) when there is none. Built into the CLI, not a per-project file.')
|
|
3123
|
+
.option('--non-interactive', 'Never ask anything: take the board and credentials that resolve from flags, EVENTMODELERS_* env vars and the config files, and fail with the reason if they are incomplete instead of interviewing for them. A TTY was previously the only signal — right for CI or a supervisor, wrong for a loop started from a terminal, where stdin is a TTY nobody is watching and the run stops on a question. Only affects the modeling loop (--modeling/--standalone/--global).')
|
|
3110
3124
|
.option('--standalone', 'Let the modeling agent work the board in the background, on its own initiative: on top of direct prompts it subscribes to the board\'s change channel (like the build agents do) and, whenever the board goes quiet after an edit — or has simply been idle for a while — it takes a turn nobody asked for. Changed nodes are a notification, not the task: it judges the model as a whole and fans the work out over parallel subagents, one per changed area (examples on a new node, specs for a new command or read model, a missing attribute along a chain, a screen, a question comment). Filling that detail in while the human keeps modeling is the point — it does not wait for the board to be finished. Implies --modeling.')
|
|
3111
3125
|
.option('--max-agents <n>', 'Cap how many subagents a self-directed --standalone turn may dispatch at once, to bound what an unattended agent can spend per turn. The agent merges work that shares a slice or chain first, then takes the most valuable pieces up to this many and leaves the rest for a later turn. 1 makes it do the single most valuable piece itself, without spawning anything. Default 5. Ignored without --standalone — prompt turns are one piece of work by definition.', '5')
|
|
3112
3126
|
.option('--exclusive', 'Work only the prompts addressed to this agent\'s id — the board\'s "preferred agent" (the star in the prompts panel) — and hand every untargeted prompt straight back to the queue for another agent to take. Without it an agent also works everything nobody addressed to anyone, which is what you want for a single agent and exactly what you do not want for a dedicated one (a board with a general agent plus a specialist, or an agent a supervisor drives by id). Pair it with --id so the same agent is addressable across restarts — --global/--standalone otherwise mint a fresh id per run, and prompts addressed to the previous run\'s id are never claimed. Leaves --standalone alone: a self-directed turn is nobody\'s prompt, so an exclusive standalone agent still works the board on its own initiative.')
|
|
@@ -3201,7 +3215,7 @@ credentialFlags(program
|
|
|
3201
3215
|
...(opts.credentials ? parseCredentialsArg(opts.credentials) : {}),
|
|
3202
3216
|
...Object.fromEntries(Object.entries(credentialOverridesFromOpts(opts)).filter(([, v]) => v)),
|
|
3203
3217
|
};
|
|
3204
|
-
const config = await resolveModelingCredentials(cwd, flags, globalOpts.config, globalOpts.print);
|
|
3218
|
+
const config = await resolveModelingCredentials(cwd, flags, globalOpts.config, globalOpts.print, !!opts.nonInteractive);
|
|
3205
3219
|
projectDir = await ensureGlobalKit(config.baseUrl);
|
|
3206
3220
|
kitDir = join(projectDir, MODELING_KIT.kitDirName);
|
|
3207
3221
|
overrides = config;
|
|
@@ -3581,11 +3595,11 @@ program
|
|
|
3581
3595
|
...agentHeaders(cfg),
|
|
3582
3596
|
},
|
|
3583
3597
|
body: JSON.stringify([{
|
|
3598
|
+
// The event id is the caller's and required; boardId/timestamp are the server's —
|
|
3599
|
+
// it derives both from the request and ignores anything sent here.
|
|
3584
3600
|
id: randomUUID(),
|
|
3585
3601
|
eventType: 'node:changed',
|
|
3586
3602
|
nodeId: slice.id,
|
|
3587
|
-
boardId: cfg.boardId,
|
|
3588
|
-
timestamp: Date.now(),
|
|
3589
3603
|
changedAttributes: ['sliceStatus'],
|
|
3590
3604
|
meta: { sliceStatus: newStatus },
|
|
3591
3605
|
}]),
|
package/lib/modeling-local-ai.js
CHANGED
|
@@ -47,7 +47,7 @@ const PRESETS = {
|
|
|
47
47
|
};
|
|
48
48
|
|
|
49
49
|
const DEFAULT_MODEL = 'qwen3.5:9b';
|
|
50
|
-
const DEFAULT_NUM_CTX =
|
|
50
|
+
const DEFAULT_NUM_CTX = 49152;
|
|
51
51
|
// A modeling turn is read-then-write (get_nodes, then a placement or a field change),
|
|
52
52
|
// so it needs more round trips than the build kit's 12 — but a local model that has
|
|
53
53
|
// lost the plot loops on one tool forever, and this is what ends that turn instead of
|
|
@@ -77,7 +77,22 @@ export function resolveLocalAiTarget({ target, localAi = {} } = {}) {
|
|
|
77
77
|
throw new Error(`Unknown local-AI dialect "${dialect}" — one of: ${Object.keys(DIALECTS).join(', ')}`);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
// Configured the same way every other knob here is: env wins over config.json's `localAi`,
|
|
81
|
+
// and the default stands when neither says otherwise. Validated rather than coerced, because
|
|
82
|
+
// Number('32k') is NaN, which JSON.stringify turns into `num_ctx: null` — a request Ollama
|
|
83
|
+
// accepts and silently answers with its own 4096 default, which is the exact failure the
|
|
84
|
+
// default below exists to prevent.
|
|
85
|
+
const rawCtx = process.env.LOCAL_AI_NUM_CTX ?? localAi.numCtx;
|
|
86
|
+
let numCtx = DEFAULT_NUM_CTX;
|
|
87
|
+
if (rawCtx !== undefined && rawCtx !== null && String(rawCtx).trim() !== '') {
|
|
88
|
+
numCtx = Number(rawCtx);
|
|
89
|
+
if (!Number.isInteger(numCtx) || numCtx <= 0) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Invalid context size "${rawCtx}" (LOCAL_AI_NUM_CTX or localAi.numCtx) — a positive whole number of tokens, e.g. ${DEFAULT_NUM_CTX}`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
81
96
|
return {
|
|
82
97
|
url,
|
|
83
98
|
dialect,
|
|
@@ -87,7 +102,7 @@ export function resolveLocalAiTarget({ target, localAi = {} } = {}) {
|
|
|
87
102
|
// num_ctx is per-request in Ollama and its default (4096) is far below what the
|
|
88
103
|
// MCP tool schemas alone need; on an OpenAI-compatible server the context is fixed
|
|
89
104
|
// at launch, so there is nothing to send and overflow surfaces as an HTTP 400.
|
|
90
|
-
numCtx: dialect === 'ollama' ?
|
|
105
|
+
numCtx: dialect === 'ollama' ? numCtx : null,
|
|
91
106
|
};
|
|
92
107
|
}
|
|
93
108
|
|
|
@@ -200,7 +215,7 @@ export function createModelingLocalAiRunner({ cfg, target, log, verbose = false,
|
|
|
200
215
|
throw new Error(
|
|
201
216
|
`${t.dialect} HTTP 400 — the request exceeds the server's context window. The MCP tool schemas alone ` +
|
|
202
217
|
`are ~${approxTokens(tools)} tokens; restart the server with a larger context ` +
|
|
203
|
-
`(vLLM: --max-model-len
|
|
218
|
+
`(vLLM: --max-model-len 49152, llama.cpp: -c 49152).\n${text.slice(0, 300)}`,
|
|
204
219
|
);
|
|
205
220
|
}
|
|
206
221
|
throw new Error(`${t.dialect} HTTP ${res.status}: ${text.slice(0, 300)}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.81",
|
|
4
4
|
"description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -283,15 +283,17 @@ The same discipline applies to writes: `submit_node_events` takes `events[]`, so
|
|
|
283
283
|
|
|
284
284
|
Where per-node calls genuinely can't be avoided, issue them together in one message so they run concurrently rather than in sequence.
|
|
285
285
|
|
|
286
|
-
### Ids
|
|
286
|
+
### Ids
|
|
287
287
|
|
|
288
|
-
Elements you create carry client-side ids
|
|
288
|
+
Elements you create carry client-side ids — one `nodeId` per new node — and **every event carries its own `id`**, a fresh uuid per event, never the node's. Mint them **once per turn, in a single call**, and take from that pool as you assemble the event array:
|
|
289
289
|
|
|
290
290
|
```bash
|
|
291
|
-
for i in $(seq
|
|
291
|
+
for i in $(seq 8); do uuidgen; done
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
Nothing in that depends on anything you're about to read, so splitting it across
|
|
294
|
+
Nothing in that depends on anything you're about to read, so splitting it across several shells is round trips bought for nothing.
|
|
295
|
+
|
|
296
|
+
The event `id` is **required** — an event without one is rejected as an invalid shape. It is what keys the returned `hashes` map, and what makes a resubmit idempotent: the same event sent twice under the same id writes one board event, not two. An event's `boardId` and `timestamp`, by contrast, are **not** yours to send — the server takes the board from the call and stamps the time itself, and ignores both if you send them.
|
|
295
297
|
|
|
296
298
|
---
|
|
297
299
|
|
|
@@ -20,12 +20,12 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
|
|
|
20
20
|
| Tool | Args | Purpose | REST equivalent |
|
|
21
21
|
|---|---|---|---|
|
|
22
22
|
| `list_boards` | — | List boards for the org | §1 `GET /api/boards` (org-scoped) |
|
|
23
|
-
| `get_nodes` | `boardId`, `type?`, `name?`, `chapterId?`, `nodeIds?`, `projection?` (`"line"`) | List nodes, optionally by type and/or a partial case-insensitive title match. `chapterId` scopes to one timeline — prefer this over an unscoped board-wide call whenever the step is working within one chapter (the common case); `nodeIds` fetches a known, scattered subset in one call (e.g. re-verifying exactly the nodes just touched by a batch write) instead of a full `type` refetch; `projection: "line"` maps each match to `{id, type, title}` only | §3 `GET .../nodes` |
|
|
24
|
-
| `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node. `projection: "cells"` (CHAPTER nodes only) returns just `{rows, columns, cells}` instead of the full `timelineData` — use whenever only the grid/occupancy is needed, not the whole chapter; `projection: "edges"` returns just that node's inbound/outbound connections instead of `findNodeById`'s full record. Both are opt-in — omitting `projection` is the unchanged full response | §3 `GET .../nodes/:nodeId` |
|
|
23
|
+
| `get_nodes` | `boardId`, `type?`, `name?`, `chapterId?`, `nodeIds?`, `projection?` (`"line"`) | List nodes, optionally by type and/or a partial case-insensitive title match. `chapterId` scopes to one timeline — prefer this over an unscoped board-wide call whenever the step is working within one chapter (the common case); `nodeIds` fetches a known, scattered subset in one call (e.g. re-verifying exactly the nodes just touched by a batch write) instead of a full `type` refetch; `projection: "line"` maps each match to `{id, type, title}` only. Every node carries `cellName`, its cell address in the chapter it is placed in (absent when unplaced) — read the cell names the cell-addressed tools need from here, never derive or invent one | §3 `GET .../nodes` |
|
|
24
|
+
| `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node — the full response carries `cellName`, its cell address in the chapter it is placed in. `projection: "cells"` (CHAPTER nodes only) returns just `{rows, columns, cells}` instead of the full `timelineData` — use whenever only the grid/occupancy is needed, not the whole chapter; `projection: "edges"` returns just that node's inbound/outbound connections instead of `findNodeById`'s full record. Both are opt-in — omitting `projection` is the unchanged full response | §3 `GET .../nodes/:nodeId` |
|
|
25
25
|
| `get_node_comments` | `boardId`, `nodeId` | List comments on a node | §1 `GET .../nodes/:nodeId/comments` |
|
|
26
26
|
| `get_board_events` | `boardId` | All board events, in sequence | §1 `GET .../events` |
|
|
27
27
|
| `search_board_events` | `boardId`, `name` | Search events by node name | §1 `GET .../events/search` |
|
|
28
|
-
| `submit_node_events` | `boardId`, `events[]`, `autoConnect?`, `compact?` | Create/update nodes (raw `NodeChangeEvent`/edge events). Every event property is described on the tool's own `events[]` schema — read that rather than this skill when all you need is the event shape. `autoConnect: false` places freshly-created nodes without wiring them to their own/previous-column neighbors (avoids a stray nearest-left edge); `compact: true` returns `{persisted: <count>}` instead of the per-node hash map | §3 `POST .../nodes/events` |
|
|
28
|
+
| `submit_node_events` | `boardId`, `events[]`, `autoConnect?`, `compact?` | Create/update nodes (raw `NodeChangeEvent`/edge events). Every event property is described on the tool's own `events[]` schema — read that rather than this skill when all you need is the event shape. An event needs its own `id` (required — a fresh uuid per event, never the node id) and its `eventType`, plus the keys of its kind (`nodeId`/`meta`/`node`, or `edgeId`/`source`/`target`). The `id` keys the returned `hashes` map and makes a resubmit idempotent — the same event sent twice under one id writes a single board event. Don't send `boardId` or `timestamp` per event — the server takes the board from the call and stamps the time itself, and ignores both if sent. `autoConnect: false` places freshly-created nodes without wiring them to their own/previous-column neighbors (avoids a stray nearest-left edge); `compact: true` returns `{persisted: <count>}` instead of the per-node hash map | §3 `POST .../nodes/events` |
|
|
29
29
|
| `delete_node` | `boardId`, `nodeId` | Delete a node. Deleting a chapter (timeline) cascades — every node placed in one of its cells, plus any node parented to it (e.g. SLICE_BORDER), is deleted too, along with all their edges | (via `node:deleted` event, §3) |
|
|
30
30
|
| `create_drawing` / `create_drawings` | `boardId`, `kind`, `x`, `y`, `width`, `height`, ... (plural: `drawings[]`) | Freehand canvas annotation (path/rect/text/sticky) — never placed in a cell. Use the plural form whenever an annotation is more than one stroke (a loop plus its arrows and label is one annotation, not three calls) | — (REST `POST .../drawing/draw` accepts a single drawing or an array) |
|
|
31
31
|
| `find_nodes_in_drawing` | `boardId`, `drawingId` | Nodes fully contained inside a drawing's bounding box | — (no REST equivalent; MCP-only) |
|
|
@@ -48,7 +48,7 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
|
|
|
48
48
|
| `update_slice_status` | `boardId`, `newStatus`, plus exactly one of `sliceId` / `sliceTitle` / `columnId` | Change a SLICE_BORDER's `sliceStatus`. With a title or column id there is no need to call `list_slices` first; an ambiguous title comes back with its candidates. A slice being created takes its status from `create_slice`/`create_slice_definition` instead | — (via `node:changed` event, §3) |
|
|
49
49
|
| `get_slice_data` | `boardId`, `contextName?`, `contextId?`, `sliceId?` | Full element graph for slices in a context | §8 `GET /slicedata` |
|
|
50
50
|
| `get_spec_info` | `boardId`, `timelineId`, `elementTypes?` | EVENT/COMMAND/READMODEL nodes valid in GWT steps. Pass `elementTypes` (subset of `EVENT`/`COMMAND`/`READMODEL`) to avoid pulling the full element list when only one or two types are needed — filtered server-side, not just after a full fetch | §6 `GET .../spec-info` |
|
|
51
|
-
| `get_board_outline` | `boardId`, `chapterId` | One chapter's structure, compact: per-column node lists (`{id, type, title, lane}`) + a flat edge list, no HTML pages / field bodies / meta. The cheap "what is where and how is it wired" read — prefer over `get_nodes` (no projection) for orientation checks | — (MCP-only convenience) |
|
|
51
|
+
| `get_board_outline` | `boardId`, `chapterId` | One chapter's structure, compact: per-column node lists (`{id, type, title, lane, cellName}`) + a flat edge list, no HTML pages / field bodies / meta. `cellName` is each node's real cell address (column letter + row number) — the one read that gives you every address in a chapter at once. The cheap "what is where and how is it wired" read — prefer over `get_nodes` (no projection) for orientation checks | — (MCP-only convenience) |
|
|
52
52
|
| `get_connected_nodes` | `boardId`, `nodeId`, `chapterId?`, `direction?` (`inbound`/`outbound`/`both`), `depth?`, `types?`, `includeFields?` | Neighbours of **one** node — what feeds it and what it feeds. Answers from a single anchor, unlike `get_attribute_chain` (which needs both ends of the chain as cell names up front). `depth` follows a whole chain; `types` filters the result only, never the traversal. Each neighbour carries `via`: `"edge"` for a real connection, `"layout"` when the node has none in that direction and the neighbour was inferred from the grid using auto-connect's own window (own column + adjacent one, forward-only pairs). Real edges always win. The `layout` fallback is what makes hand-built/imported chapters — which routinely carry **zero** edges — readable instead of falsely empty | — (MCP-only convenience) |
|
|
53
53
|
| `validate_model` | `boardId`, `chapterId`, `checks?[]` | Server-side Event Modeling structural checklist over one chapter — compact `findings` only. Checks: unplaced nodes, backward arrows (with the todo-list `EVENT→READMODEL` exception), zero/multi-issuer commands, sourceless read models, two-screens-in-a-column, missing scenarios. Replaces the manual per-type `get_nodes` + `get_node projection=edges` validation pass | — (MCP-only convenience) |
|
|
54
54
|
| `add_scenario` | `boardId`, `timelineId`, `columnId`, `scenarios[]`, `compact?` | Append GWT scenario(s) to a column's spec node — created automatically, and a scenario `id` is generated when omitted. A given/when/then step may be addressed by `{title, type}` instead of a node id, resolved against that timeline, so no `get_spec_info` call is needed first (an ambiguous title is reported with its candidates). `compact: true` returns `{specNodeId, added, scenarioCount, isNewNode}` instead of echoing every scenario back | §6 `POST .../scenarios` |
|
|
@@ -61,7 +61,7 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
|
|
|
61
61
|
| `create_screen` | `boardId`, `contentType` (`'image'\|'sketch'\|'html'`), `nodeId?`, `chapterId`, `cellId?`/`cellName?`, plus content fields (`imageBase64`/`mimeType`, `elements[]`, or `pages[]`/`backgroundColor`), `title?`, `description?`, `fields?`, `autoConnect?` | Create + place a new screen node (SCREEN or HTML_SCREEN) atomically, in one call. `title` names the node (`meta.title`) in the same call — no follow-up `node:changed` just to label the screen; `create_screens` takes it per entry. Batch form `create_screens` takes `screens[]` (HTML only) + `autoConnect?`. `autoConnect: false` places without wiring to timeline neighbors | §4 `POST .../images/:id/sketch` + `image-nodes` |
|
|
62
62
|
| `render_screen` | `boardId`, `nodeId`, `elements[]?` (SCREEN) or `pages[]?`+`backgroundColor?` (HTML_SCREEN), `description?` | Update an existing screen's content — exactly one of `elements`/`pages` | §4 `POST .../images/:id/sketch` + `image-nodes` |
|
|
63
63
|
| `add_field_examples` | `boardId`, `nodeId?`, `name?`, `cellName?`, `timelineId?` | Fill empty field examples using linked-node context | — (MCP-only convenience) |
|
|
64
|
-
| `get_attribute_chain` | `boardId`, `timelineId`, `targetCellName`, `sourceCellName` | Resolve every node between two cells, ordered target→source | — (MCP-only convenience) |
|
|
64
|
+
| `get_attribute_chain` | `boardId`, `timelineId`, `targetCellName`, `sourceCellName` | Resolve every node between two cells, ordered target→source. Both cell names must come from a node's reported `cellName` (`get_board_outline`, `get_nodes`, `get_node`) — a guessed address that happens to exist silently resolves the wrong chain | — (MCP-only convenience) |
|
|
65
65
|
| `verify_screen` | `boardId`, `nodeId` | Check a screen node exists and has rendered content — works for both SCREEN and HTML_SCREEN, dispatching on the node's actual type | — (MCP-only convenience) |
|
|
66
66
|
| `get_image_snapshot_description` | `boardId`, `nodeId` | Load the `{elements:[...]}` sketch description from storage | — (reads what §4 sketch endpoints write) |
|
|
67
67
|
| `validate_slice_data` | `sliceData` | Offline validation of a `SliceDataOutput` payload — no board access | — (MCP-only, pure function) |
|
|
@@ -337,8 +337,6 @@ Place a free-text markdown note in that lane the same way any other node is plac
|
|
|
337
337
|
"id": "<event-uuid>",
|
|
338
338
|
"eventType": "node:created",
|
|
339
339
|
"nodeId": "<node-uuid>",
|
|
340
|
-
"boardId": "<boardId>",
|
|
341
|
-
"timestamp": 1234567890,
|
|
342
340
|
"chapterId": "<chapterId>",
|
|
343
341
|
"cellId": "<feedbackRowId>-<columnId>",
|
|
344
342
|
"meta": {
|
|
@@ -370,13 +368,16 @@ A `node:deleted` event cascades: if the deleted node is a chapter (timeline), ev
|
|
|
370
368
|
|
|
371
369
|
**Request body**: `NodeChangeEvent[]`
|
|
372
370
|
|
|
371
|
+
Every event carries its own `id` — **required**, a fresh uuid per event and never the node id. An event without one is rejected (`400`, invalid event shape). It keys the response's `hashes` map, and it is what makes a resubmit safe: the board-event insert ignores a conflicting id, so the same batch sent twice writes one history row per event rather than two.
|
|
372
|
+
|
|
373
|
+
Don't send `boardId` or `timestamp` per event: the board is the path's (the one this request was authorized for) and the timestamp is the server's. Both are ignored if sent — older payloads that include them still work.
|
|
374
|
+
|
|
373
375
|
```typescript
|
|
374
376
|
interface NodeChangeEvent {
|
|
375
|
-
id: string // uuid
|
|
377
|
+
id: string // required — fresh uuid per event, NOT the node id; keys the response's hashes map
|
|
376
378
|
eventType: 'node:created' | 'node:changed' | 'node:deleted'
|
|
377
379
|
nodeId: string
|
|
378
|
-
boardId:
|
|
379
|
-
timestamp: number // unix ms
|
|
380
|
+
// boardId / timestamp: don't send them — server-derived, ignored if present
|
|
380
381
|
userId?: string
|
|
381
382
|
hash?: string // content hash
|
|
382
383
|
changedAttributes?: string[] // dot-paths e.g. 'meta.title'
|
|
@@ -128,8 +128,6 @@ curl -s -X POST "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/events" \
|
|
|
128
128
|
"id": "<new-random-uuid>",
|
|
129
129
|
"eventType": "node:changed",
|
|
130
130
|
"nodeId": "<SLICE_NODE_ID>",
|
|
131
|
-
"boardId": "<BOARD_ID>",
|
|
132
|
-
"timestamp": <Date.now()>,
|
|
133
131
|
"changedAttributes": ["sliceStatus"],
|
|
134
132
|
"meta": {
|
|
135
133
|
"sliceStatus": "Blocked"
|
|
@@ -89,8 +89,6 @@ curl -s -X POST "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/events" \
|
|
|
89
89
|
"id": "<new-random-uuid>",
|
|
90
90
|
"eventType": "node:changed",
|
|
91
91
|
"nodeId": "<SLICE_NODE_ID>",
|
|
92
|
-
"boardId": "<BOARD_ID>",
|
|
93
|
-
"timestamp": <Date.now()>,
|
|
94
92
|
"changedAttributes": ["sliceStatus"],
|
|
95
93
|
"meta": {
|
|
96
94
|
"sliceStatus": "<newStatus>"
|
|
@@ -42,6 +42,8 @@ mcp__eventmodelers__get_attribute_chain {
|
|
|
42
42
|
|
|
43
43
|
The result gives you the ordered chain directly — save it as the chain used in Step 4, and skip the manual walk in 2a–3c below. Continue with the fallback only if MCP isn't connected.
|
|
44
44
|
|
|
45
|
+
**Both cell names must be real.** When the task named a node (an id, a title, a comment on it) instead of a cell, read that node's `cellName` off `get_board_outline` (every node in the chapter, with its address), `get_nodes`, or `get_node` — all three report it. Never compute an address from where a node appeared in a list, and never hand `get_attribute_chain` an address you have not read: a cell that exists but holds a different node resolves a wrong chain without any error.
|
|
46
|
+
|
|
45
47
|
### Fallback (no MCP) — resolve both cells to nodes
|
|
46
48
|
|
|
47
49
|
For each cell (target and source), resolve it to a node using the exact same cell-resolution steps as the `examples` skill's "2c — Cell name" section (fetch chapters, fetch the chapter fresh to decode the grid, decode the cell name into a `CELL_ID`, then always fetch the cell live — `get_nodes` has no `cellId` filter) — see there for the full mechanics, substituting `x-user-id: attributes-skill`.
|
|
@@ -149,8 +151,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
149
151
|
"id": "<uuid>",
|
|
150
152
|
"eventType": "node:changed",
|
|
151
153
|
"nodeId": "<NODE_ID_1>",
|
|
152
|
-
"boardId": "$BOARD_ID",
|
|
153
|
-
"timestamp": <epoch-ms>,
|
|
154
154
|
"changedAttributes": ["meta.fields"],
|
|
155
155
|
"meta": { "fields": "<updated_fields_array_1>" }
|
|
156
156
|
},
|
|
@@ -15,13 +15,11 @@ Build the payload with Python to avoid JSON escaping issues, then POST it:
|
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
python3 - <<EOF > /tmp/attributes_payload.json
|
|
18
|
-
import json,
|
|
18
|
+
import json, uuid
|
|
19
19
|
payload = [{
|
|
20
20
|
"id": str(uuid.uuid4()),
|
|
21
21
|
"eventType": "node:changed",
|
|
22
22
|
"nodeId": "<NODE_ID>",
|
|
23
|
-
"boardId": "<BOARD_ID>",
|
|
24
|
-
"timestamp": int(time.time() * 1000),
|
|
25
23
|
"changedAttributes": ["meta.fields"],
|
|
26
24
|
"meta": {
|
|
27
25
|
"fields": <updated_fields_as_python_list>
|
|
@@ -232,8 +232,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
232
232
|
"id": "<uuid>",
|
|
233
233
|
"eventType": "node:changed",
|
|
234
234
|
"nodeId": "<CHAPTER_ID>",
|
|
235
|
-
"boardId": "<BOARD_ID>",
|
|
236
|
-
"timestamp": <NOW_MS>,
|
|
237
235
|
"changedAttributes": ["meta.title"],
|
|
238
236
|
"meta": { "type": "CHAPTER", "title": "<flow name>" },
|
|
239
237
|
"node": { "id": "<CHAPTER_ID>", "data": {} }
|
package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md
CHANGED
|
@@ -22,8 +22,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
22
22
|
"id": "<uuid>",
|
|
23
23
|
"eventType": "node:changed",
|
|
24
24
|
"nodeId": "<CHAPTER_ID>",
|
|
25
|
-
"boardId": "<BOARD_ID>",
|
|
26
|
-
"timestamp": <NOW_MS>,
|
|
27
25
|
"changedAttributes": ["meta.title"],
|
|
28
26
|
"meta": { "type": "CHAPTER", "title": "<flow name>" },
|
|
29
27
|
"node": { "id": "<CHAPTER_ID>", "data": {} }
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md
CHANGED
|
@@ -107,8 +107,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
107
107
|
"id": "<uuid>",
|
|
108
108
|
"eventType": "node:changed",
|
|
109
109
|
"nodeId": "<chapterId>",
|
|
110
|
-
"boardId": "<BOARD_ID>",
|
|
111
|
-
"timestamp": 1234567890,
|
|
112
110
|
"meta": {"type": "CHAPTER", "title": "Reservation & Lending"}
|
|
113
111
|
}]
|
|
114
112
|
}
|
|
@@ -200,8 +198,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
200
198
|
"id": "<event-uuid>",
|
|
201
199
|
"eventType": "node:created",
|
|
202
200
|
"nodeId": "<node-uuid>",
|
|
203
|
-
"boardId": "<BOARD_ID>",
|
|
204
|
-
"timestamp": 1234567890,
|
|
205
201
|
"chapterId": "<chapterId>",
|
|
206
202
|
"cellId": "<swimlaneRowId>-<columnId>",
|
|
207
203
|
"meta": {
|
|
@@ -35,8 +35,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
35
35
|
"id": "<uuid>",
|
|
36
36
|
"eventType": "node:changed",
|
|
37
37
|
"nodeId": "<chapterId>",
|
|
38
|
-
"boardId": "<boardId>",
|
|
39
|
-
"timestamp": 1234567890,
|
|
40
38
|
"meta": {"type": "CHAPTER", "title": "Reservation & Lending"}
|
|
41
39
|
}]'
|
|
42
40
|
```
|
|
@@ -78,8 +76,6 @@ Same body via `POST .../nodes/events`:
|
|
|
78
76
|
"id": "<event-uuid>",
|
|
79
77
|
"eventType": "node:created",
|
|
80
78
|
"nodeId": "<node-uuid>",
|
|
81
|
-
"boardId": "<boardId>",
|
|
82
|
-
"timestamp": 1234567890,
|
|
83
79
|
"chapterId": "<chapterId>",
|
|
84
80
|
"cellId": "<swimlaneRowId>-<columnId>",
|
|
85
81
|
"meta": {
|
|
@@ -95,7 +95,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
95
95
|
"boardId": "<BOARD_ID>",
|
|
96
96
|
"events": [{
|
|
97
97
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<returned-node-id>",
|
|
98
|
-
"boardId": "<BOARD_ID>", "timestamp": 1234567890,
|
|
99
98
|
"meta": {"type": "READMODEL", "title": "NotificationsToSend", "fields": [...], "listElement": true}
|
|
100
99
|
}]
|
|
101
100
|
}
|
|
@@ -14,7 +14,6 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
|
|
|
14
14
|
-H "Content-Type: application/json" \
|
|
15
15
|
-d '[{
|
|
16
16
|
"id":"<event-uuid>","eventType":"node:created","nodeId":"<node-uuid>",
|
|
17
|
-
"boardId":"<BOARD_ID>","timestamp":1234567890,
|
|
18
17
|
"chapterId":"<CHAPTER_ID>","cellId":"<interactionRowId>-<columnId>",
|
|
19
18
|
"meta":{"type":"READMODEL","title":"NotificationsToSend","fields":[...]}
|
|
20
19
|
}]'
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md
CHANGED
|
@@ -160,8 +160,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
160
160
|
"id": "<event-uuid>",
|
|
161
161
|
"eventType": "node:changed",
|
|
162
162
|
"nodeId": "<returned-node-id>",
|
|
163
|
-
"boardId": "<BOARD_ID>",
|
|
164
|
-
"timestamp": 1234567890,
|
|
165
163
|
"meta": {
|
|
166
164
|
"type": "COMMAND",
|
|
167
165
|
"title": "ReserveBike",
|
|
@@ -34,8 +34,6 @@ curl -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
34
34
|
"id": "<event-uuid>",
|
|
35
35
|
"eventType": "node:created",
|
|
36
36
|
"nodeId": "<node-uuid>",
|
|
37
|
-
"boardId": "<boardId>",
|
|
38
|
-
"timestamp": 1234567890,
|
|
39
37
|
"chapterId": "<chapterId>",
|
|
40
38
|
"cellId": "<interactionRowId>-<columnId>",
|
|
41
39
|
"meta": {
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
CHANGED
|
@@ -267,7 +267,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
267
267
|
"boardId": "<BOARD_ID>",
|
|
268
268
|
"events": [{
|
|
269
269
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<returned-node-id>",
|
|
270
|
-
"boardId": "<BOARD_ID>", "timestamp": 1234567890,
|
|
271
270
|
"meta": {"type": "READMODEL", "title": "ActiveReservationView", "fields": [...]}
|
|
272
271
|
}]
|
|
273
272
|
}
|
|
@@ -26,8 +26,6 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
|
|
|
26
26
|
"id": "<event-uuid>",
|
|
27
27
|
"eventType": "node:created",
|
|
28
28
|
"nodeId": "<node-uuid>",
|
|
29
|
-
"boardId": "<BOARD_ID>",
|
|
30
|
-
"timestamp": 1234567890,
|
|
31
29
|
"chapterId": "<CHAPTER_ID>",
|
|
32
30
|
"cellId": "<interactionRowId>-<columnId>",
|
|
33
31
|
"meta": {"type": "READMODEL", "title": "ActiveReservationView", "fields": [...]}
|
|
@@ -431,7 +431,6 @@ Not delegated to a separate skill — performed directly by this orchestrating s
|
|
|
431
431
|
"boardId": "$BOARD_ID",
|
|
432
432
|
"events": [{
|
|
433
433
|
"id": "<event-uuid>", "eventType": "node:created", "nodeId": "<node-uuid>",
|
|
434
|
-
"boardId": "$BOARD_ID", "timestamp": 1234567890,
|
|
435
434
|
"chapterId": "$CHAPTER_ID", "cellId": "<feedbackLaneId>-<firstColumnId>",
|
|
436
435
|
"meta": { "type": "MARKDOWN", "title": "Modeling Reasoning — <Chapter Name>", "description": "<full markdown body>" }
|
|
437
436
|
}]
|
|
@@ -17,8 +17,8 @@ done
|
|
|
17
17
|
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
18
18
|
-H "x-token: $TOKEN" -H "x-user-id: orchestrator" \
|
|
19
19
|
-H "Content-Type: application/json" \
|
|
20
|
-
-d '[{"id":"<uuid>","eventType":"node:changed","nodeId":"<nodeId>",
|
|
21
|
-
"
|
|
20
|
+
-d '[{"id":"<event-uuid>","eventType":"node:changed","nodeId":"<nodeId>",
|
|
21
|
+
"chapterId":"<chapterId>","cellId":"<rowId>-<colId>",
|
|
22
22
|
"meta":{"type":"<TYPE>","title":"<title>"}}]'
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -45,7 +45,7 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/timelines/$CHAPTER_I
|
|
|
45
45
|
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
46
46
|
-H "x-token: $TOKEN" -H "x-user-id: orchestrator" \
|
|
47
47
|
-H "Content-Type: application/json" \
|
|
48
|
-
-d '[{"id":"<event-uuid>","eventType":"node:created","nodeId":"<node-uuid>",
|
|
49
|
-
"
|
|
48
|
+
-d '[{"id":"<event-uuid>","eventType":"node:created","nodeId":"<node-uuid>",
|
|
49
|
+
"chapterId":"<CHAPTER_ID>","cellId":"<feedbackLaneId>-<firstColumnId>",
|
|
50
50
|
"meta":{"type":"MARKDOWN","title":"Modeling Reasoning — <Chapter Name>","description":"<full markdown body>"}}]'
|
|
51
51
|
```
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md
CHANGED
|
@@ -256,8 +256,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
256
256
|
"id": "<event-uuid>",
|
|
257
257
|
"eventType": "node:created",
|
|
258
258
|
"nodeId": "<node-uuid>",
|
|
259
|
-
"boardId": "<BOARD_ID>",
|
|
260
|
-
"timestamp": 1234567890,
|
|
261
259
|
"chapterId": "<CHAPTER_ID>",
|
|
262
260
|
"cellId": "<actorRowId>-<columnId>",
|
|
263
261
|
"meta": {"type": "SCREEN", "title": "<Screen Title>", "fields": [...]}
|
|
@@ -52,8 +52,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
52
52
|
"id": "<event-uuid>",
|
|
53
53
|
"eventType": "node:created",
|
|
54
54
|
"nodeId": "<node-uuid>",
|
|
55
|
-
"boardId": "<BOARD_ID>",
|
|
56
|
-
"timestamp": 1234567890,
|
|
57
55
|
"chapterId": "<CHAPTER_ID>",
|
|
58
56
|
"cellId": "<actorRowId>-<columnId>",
|
|
59
57
|
"meta": {"type": "SCREEN", "title": "<Screen Title>", "fields": [...]}
|
|
@@ -43,7 +43,7 @@ A run that opens each element with its own `get_node` to "read existing examples
|
|
|
43
43
|
|
|
44
44
|
- `target` is a UUID → pass `nodeId`
|
|
45
45
|
- `target` is a name → pass `name`
|
|
46
|
-
- `target` is a cell name (e.g. `B3`) → pass `cellName` + `timelineId` (the chapter id — if multiple chapters exist on the board, resolve which one first using 2c-fallback's chapter lookup, or `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER" }`, and ask the user if ambiguous)
|
|
46
|
+
- `target` is a cell name (e.g. `B3`) → pass `cellName` + `timelineId` (the chapter id — if multiple chapters exist on the board, resolve which one first using 2c-fallback's chapter lookup, or `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER" }`, and ask the user if ambiguous). Only pass a cell name you were given or read back as a node's `cellName` (`get_board_outline`/`get_nodes`/`get_node` all report it) — if all you have is a node id or title, pass that instead rather than constructing an address.
|
|
47
47
|
|
|
48
48
|
```
|
|
49
49
|
mcp__eventmodelers__add_field_examples { "boardId": "$BOARD_ID", "nodeId": "<target, if a UUID>" }
|
|
@@ -105,8 +105,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
105
105
|
"id": "<uuid>",
|
|
106
106
|
"eventType": "node:changed",
|
|
107
107
|
"nodeId": "<TARGET_NODE.id>",
|
|
108
|
-
"boardId": "$BOARD_ID",
|
|
109
|
-
"timestamp": <epoch-ms>,
|
|
110
108
|
"changedAttributes": ["meta.fields"],
|
|
111
109
|
"meta": { "fields": "<updated-fields-array>" }
|
|
112
110
|
}]
|
|
@@ -94,13 +94,11 @@ Build the payload with Python to avoid shell JSON-escaping issues, then POST it:
|
|
|
94
94
|
|
|
95
95
|
```bash
|
|
96
96
|
python3 - <<EOF > /tmp/examples_payload.json
|
|
97
|
-
import json,
|
|
97
|
+
import json, uuid
|
|
98
98
|
payload = [{
|
|
99
99
|
"id": str(uuid.uuid4()),
|
|
100
100
|
"eventType": "node:changed",
|
|
101
101
|
"nodeId": "<TARGET_NODE.id>",
|
|
102
|
-
"boardId": "<BOARD_ID>",
|
|
103
|
-
"timestamp": int(time.time() * 1000),
|
|
104
102
|
"changedAttributes": ["meta.fields"],
|
|
105
103
|
"meta": {
|
|
106
104
|
"fields": <updated-fields-array as Python list>
|
|
@@ -97,7 +97,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
97
97
|
"boardId": "<BOARD_ID>",
|
|
98
98
|
"events": [{
|
|
99
99
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
100
|
-
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
101
100
|
"meta": {
|
|
102
101
|
"type": "HTML_SCREEN",
|
|
103
102
|
"title": "<Screen Title>",
|
|
@@ -174,7 +173,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
174
173
|
"boardId": "<BOARD_ID>",
|
|
175
174
|
"events": [{
|
|
176
175
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
177
|
-
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
178
176
|
"changedAttributes": ["meta.fields"],
|
|
179
177
|
"meta": { "type": "HTML_SCREEN", "fields": [
|
|
180
178
|
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
|
@@ -39,7 +39,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
39
39
|
-H "Content-Type: application/json" \
|
|
40
40
|
-d '[{
|
|
41
41
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
42
|
-
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
43
42
|
"changedAttributes": ["meta.fields"],
|
|
44
43
|
"meta": { "type": "HTML_SCREEN", "fields": [
|
|
45
44
|
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
|
@@ -321,10 +321,9 @@ This step applies to `SCREEN` (view/output conflict case), `AUTOMATION`, `SCENAR
|
|
|
321
321
|
mcp__eventmodelers__submit_node_events {
|
|
322
322
|
"boardId": "<BOARD_ID>",
|
|
323
323
|
"events": [{
|
|
324
|
+
"id": "<event-uuid>",
|
|
324
325
|
"eventType": "node:created",
|
|
325
326
|
"nodeId": "<node-uuid>",
|
|
326
|
-
"boardId": "<BOARD_ID>",
|
|
327
|
-
"timestamp": <Date.now()>,
|
|
328
327
|
"chapterId": "<TIMELINE_ID>",
|
|
329
328
|
"cellId": "<CELL_ID>",
|
|
330
329
|
"meta": { "type": "<ELEMENT_TYPE>", "title": "<title>" },
|
|
@@ -343,10 +342,9 @@ mcp__eventmodelers__submit_node_events {
|
|
|
343
342
|
mcp__eventmodelers__submit_node_events {
|
|
344
343
|
"boardId": "<BOARD_ID>",
|
|
345
344
|
"events": [{
|
|
345
|
+
"id": "<event-uuid>",
|
|
346
346
|
"eventType": "node:created",
|
|
347
347
|
"nodeId": "<node-uuid>",
|
|
348
|
-
"boardId": "<BOARD_ID>",
|
|
349
|
-
"timestamp": <Date.now()>,
|
|
350
348
|
"chapterId": "<TIMELINE_ID>",
|
|
351
349
|
"cellName": "<CELL_NAME>",
|
|
352
350
|
"meta": { "type": "<ELEMENT_TYPE>", "title": "<title>" },
|
|
@@ -357,7 +355,7 @@ mcp__eventmodelers__submit_node_events {
|
|
|
357
355
|
|
|
358
356
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 7b — Create any other node type (fast path, `cellName`)".
|
|
359
357
|
|
|
360
|
-
Response: `{ "hashes": { "<event-
|
|
358
|
+
Response: `{ "hashes": { "<event-id>": "<hash>" } }` — keyed by the `id` you sent on each event, which is how you match a hash back to the event that produced it.
|
|
361
359
|
|
|
362
360
|
> **`node:created` with `cellId`/`cellName` IS the placement** — do NOT also call the `drop` endpoint afterwards. The `drop` endpoint adds a second cell reference without removing the first, causing the node to appear in two columns simultaneously. Use `node:created` with `cellId` or `cellName` for all initial placements.
|
|
363
361
|
|
package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md
CHANGED
|
@@ -124,10 +124,9 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
124
124
|
-H "x-user-id: agent" \
|
|
125
125
|
-H "Content-Type: application/json" \
|
|
126
126
|
-d '[{
|
|
127
|
+
"id": "<event-uuid>",
|
|
127
128
|
"eventType": "node:created",
|
|
128
129
|
"nodeId": "<node-uuid>",
|
|
129
|
-
"boardId": "<BOARD_ID>",
|
|
130
|
-
"timestamp": <Date.now()>,
|
|
131
130
|
"chapterId": "<TIMELINE_ID>",
|
|
132
131
|
"cellId": "<CELL_ID>",
|
|
133
132
|
"meta": {
|
|
@@ -148,10 +147,9 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
148
147
|
-H "x-user-id: agent" \
|
|
149
148
|
-H "Content-Type: application/json" \
|
|
150
149
|
-d '[{
|
|
150
|
+
"id": "<event-uuid>",
|
|
151
151
|
"eventType": "node:created",
|
|
152
152
|
"nodeId": "<node-uuid>",
|
|
153
|
-
"boardId": "<BOARD_ID>",
|
|
154
|
-
"timestamp": <Date.now()>,
|
|
155
153
|
"chapterId": "<TIMELINE_ID>",
|
|
156
154
|
"cellName": "<CELL_NAME>",
|
|
157
155
|
"meta": {
|
|
@@ -162,7 +160,7 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
162
160
|
}]'
|
|
163
161
|
```
|
|
164
162
|
|
|
165
|
-
Response: `{ "hashes": { "<event-
|
|
163
|
+
Response: `{ "hashes": { "<event-id>": "<hash>" } }` — keyed by the `id` you sent on each event, which is how you match a hash back to the event that produced it.
|
|
166
164
|
|
|
167
165
|
## Full worked example — place an EVENT via curl, start to finish
|
|
168
166
|
|
|
@@ -188,8 +186,6 @@ curl -s -X POST "http://localhost:3000/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/
|
|
|
188
186
|
"id": "<event-uuid>",
|
|
189
187
|
"eventType": "node:created",
|
|
190
188
|
"nodeId": "<node-uuid>",
|
|
191
|
-
"boardId": "<BOARD_ID>",
|
|
192
|
-
"timestamp": 1714900000000,
|
|
193
189
|
"chapterId": "<TIMELINE_ID>",
|
|
194
190
|
"cellId": "<CELL_ID>",
|
|
195
191
|
"meta": { "type": "EVENT", "title": "Order Placed" },
|
|
@@ -197,4 +193,4 @@ curl -s -X POST "http://localhost:3000/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/
|
|
|
197
193
|
}]'
|
|
198
194
|
```
|
|
199
195
|
|
|
200
|
-
Replace `<TIMELINE_ID>`, `<BOARD_ID>`, `<CELL_ID>`, `<event-uuid
|
|
196
|
+
Replace `<TIMELINE_ID>`, `<BOARD_ID>`, `<CELL_ID>`, `<event-uuid>` and `<node-uuid>` with real values — you mint two uuids here: the node's and the event's own `id`, which is required. `boardId`/`timestamp` are omitted on purpose: the server derives both from the request and ignores them if sent.
|
|
@@ -224,7 +224,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
224
224
|
"boardId": "<BOARD_ID>",
|
|
225
225
|
"events": [{
|
|
226
226
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<SCREEN_NODE_ID>",
|
|
227
|
-
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
228
227
|
"changedAttributes": ["meta.fields"],
|
|
229
228
|
"meta": { "type": "HTML_SCREEN", "fields": [ /* planned fields */ ] }
|
|
230
229
|
}]
|
|
@@ -67,7 +67,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
67
67
|
-H "Content-Type: application/json" \
|
|
68
68
|
-d '[{
|
|
69
69
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<SCREEN_NODE_ID>",
|
|
70
|
-
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
71
70
|
"changedAttributes": ["meta.fields"],
|
|
72
71
|
"meta": { "type": "HTML_SCREEN", "fields": [ /* planned fields */ ] }
|
|
73
72
|
}]'
|
|
@@ -122,7 +122,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
122
122
|
"boardId": "<BOARD_ID>",
|
|
123
123
|
"events": [{
|
|
124
124
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
125
|
-
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
126
125
|
"changedAttributes": ["meta.fields"],
|
|
127
126
|
"meta": { "type": "SCREEN", "fields": [
|
|
128
127
|
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md
CHANGED
|
@@ -33,7 +33,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
33
33
|
-H "Content-Type: application/json" \
|
|
34
34
|
-d '[{
|
|
35
35
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<NODE_ID>",
|
|
36
|
-
"boardId": "<BOARD_ID>", "timestamp": <NOW_MS>,
|
|
37
36
|
"changedAttributes": ["meta.fields"],
|
|
38
37
|
"meta": { "type": "SCREEN", "fields": [
|
|
39
38
|
{"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
|
|
@@ -246,8 +246,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
246
246
|
"id": "<event-uuid>",
|
|
247
247
|
"eventType": "node:created",
|
|
248
248
|
"nodeId": "<node-uuid>",
|
|
249
|
-
"boardId": "<BOARD_ID>",
|
|
250
|
-
"timestamp": <Date.now()>,
|
|
251
249
|
"chapterId": "<CHAPTER_ID>",
|
|
252
250
|
"cellId": "<CELL_ID>",
|
|
253
251
|
"meta": { "type": "EVENT", "title": "<EventName>" },
|
|
@@ -302,8 +300,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
302
300
|
"id": "<new-uuid>",
|
|
303
301
|
"eventType": "node:changed",
|
|
304
302
|
"nodeId": "<eventNodeId>",
|
|
305
|
-
"boardId": "<BOARD_ID>",
|
|
306
|
-
"timestamp": <Date.now()>,
|
|
307
303
|
"changedAttributes": ["meta.title"],
|
|
308
304
|
"meta": { "type": "EVENT", "title": "<NewTitle>" },
|
|
309
305
|
"node": { "id": "<eventNodeId>", "data": {} }
|
|
@@ -48,8 +48,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
48
48
|
"id": "<event-uuid>",
|
|
49
49
|
"eventType": "node:created",
|
|
50
50
|
"nodeId": "<node-uuid>",
|
|
51
|
-
"boardId": "<BOARD_ID>",
|
|
52
|
-
"timestamp": <Date.now()>,
|
|
53
51
|
"chapterId": "<CHAPTER_ID>",
|
|
54
52
|
"cellId": "<CELL_ID>",
|
|
55
53
|
"meta": { "type": "EVENT", "title": "<EventName>" },
|
|
@@ -67,8 +65,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
67
65
|
"id": "<new-uuid>",
|
|
68
66
|
"eventType": "node:changed",
|
|
69
67
|
"nodeId": "<eventNodeId>",
|
|
70
|
-
"boardId": "<BOARD_ID>",
|
|
71
|
-
"timestamp": <Date.now()>,
|
|
72
68
|
"changedAttributes": ["meta.title"],
|
|
73
69
|
"meta": { "type": "EVENT", "title": "<NewTitle>" },
|
|
74
70
|
"node": { "id": "<eventNodeId>", "data": {} }
|
|
@@ -84,8 +80,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
84
80
|
-d '[{
|
|
85
81
|
"id": "<new-uuid>",
|
|
86
82
|
"eventType": "node:deleted",
|
|
87
|
-
"nodeId": "<eventNodeId>"
|
|
88
|
-
"boardId": "<BOARD_ID>",
|
|
89
|
-
"timestamp": <Date.now()>
|
|
83
|
+
"nodeId": "<eventNodeId>"
|
|
90
84
|
}]'
|
|
91
85
|
```
|
|
@@ -14,9 +14,10 @@ ones in a compressed, reusable form; only add if not already covered here.
|
|
|
14
14
|
- `/update-slice-status` rejects moving a slice into a status it's already in — this is a concurrency guard so two agents can't both claim the same slice. Treat this as `ALREADY_IN_STATUS`, not a task failure: drop the prompt, move on to the next task, and do not retry the same update.
|
|
15
15
|
- macOS/BSD `date` silently ignores GNU-only format specifiers like `%N`/`%3N` (sub-second precision) instead of erroring — it prints the literal characters, producing a malformed timestamp that only fails downstream. Don't shell out to `date` for sub-second precision; use `$(( $(date +%s) * 1000 ))` for whole-second-in-ms, or a runtime call (`Date.now()`, `process.hrtime()`) instead.
|
|
16
16
|
- Before retrying a failed shell command a second time, diagnose why it failed (e.g. a GNU/BSD flag mismatch) rather than re-running it unchanged — repeating the same command produces the same failure and just burns retries.
|
|
17
|
+
- A cell address (`B2`, `AE6`) is always read, never computed: `get_board_outline`, `get_nodes` and `get_node` each report a node's `cellName`. An address inferred from a column letter plus a node's place in a list is a guess, and a guess that happens to name an occupied cell is accepted silently by every cell-addressed tool.
|
|
17
18
|
- Orientation and working set are two different reads, in that order: `get_board_outline` (or `get_nodes` with `projection: "line"`) answers *where is the work*, and only then does one full-`meta` `get_nodes`, scoped by `chapterId`/`nodeIds`, cover the nodes actually being touched. Opening with an unscoped full-`meta` read pulls every field body and rendered HTML page on the board to answer the cheapest question of the turn.
|
|
18
19
|
- Both reads are once per turn. A chapter's outline and grid don't move unless something writes to them, so re-read only after a structural write. Three `get_board_outline` calls in one turn means the first two were thrown away.
|
|
19
|
-
- Mint ids
|
|
20
|
+
- Mint ids once per turn, in one shell: `for i in $(seq <n>); do uuidgen; done` — one per new node, plus one per event, since every event needs its own fresh `id` (required; not the node id). Nothing in it depends on anything being read, so splitting it across several calls buys round trips for nothing. Never mint a per-event `boardId` or `timestamp` — both are server-derived and ignored if sent.
|
|
20
21
|
- A subagent is a fresh session: it gets `token=`/`org=`/`baseUrl=`/`board=` inline as already-resolved values (which satisfies `connect` at its Step 0 — tell it not to invoke `/connect`) plus the board state already read for it, inline. Handed bare node ids instead, it has exactly one way to recover the rest — re-reading the whole board, once per agent.
|
|
21
22
|
- Dispatch executor agents with `model:` set to the session header's `subagent_model` (default `sonnet`). The judging happened before the dispatch, on this session's model; what's left is execution against a written brief. Keep an agent on the session model only where its piece re-derives modeling structure (a chain's shape, a slice boundary).
|
|
22
23
|
- Only slices in status `Created` may be modified. `get_nodes` returns `sliceStatus` per node and `get_board_outline` per column, so the board read at `/connect` Step 5 already answers it — absent means the element is in no slice (writable), not locked. Never spend a `list_slices`/`get_slice_data` call just to check whether you may write.
|
|
@@ -96,7 +96,7 @@ stale. Then a single full-`meta` `get_nodes`, scoped by
|
|
|
96
96
|
`chapterId` or `nodeIds`, covering the nodes you concluded you will touch. Both tiers are once per turn: keep what
|
|
97
97
|
came back and answer later questions from it instead of re-fetching a chapter you already hold. `/connect` Step 5
|
|
98
98
|
carries the full discipline — the two tiers, the one-call `submit_node_events` rule for writes, and the per-turn
|
|
99
|
-
pool for the ids
|
|
99
|
+
pool for the ids a `node:created` needs (one for the node, one for the event itself). Whatever you hand a subagent comes out of that same read,
|
|
100
100
|
never out of a second one it pays for itself (step 2).
|
|
101
101
|
|
|
102
102
|
**Every prompt gets exactly two `/update-prompt-status` calls per turn — never zero, never one.** `IN_PROGRESS` before you start the work (step 4), `DONE` after you finish it (step 6). This holds even for a prompt that turns out to be trivial or a no-op — the board UI has no other way to know the agent picked it up and finished it.
|