@eventmodelers/cli 0.0.18 → 0.0.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +27 -3
- package/package.json +1 -1
- package/shared/build-kit/lib/ralph.js +27 -2
package/cli.js
CHANGED
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import { execSync, spawn } from 'child_process';
|
|
18
18
|
import { createInterface, emitKeypressEvents, moveCursor, clearScreenDown } from 'readline';
|
|
19
19
|
import { homedir } from 'os';
|
|
20
|
+
import { randomUUID } from 'crypto';
|
|
20
21
|
|
|
21
22
|
const __filename = fileURLToPath(import.meta.url);
|
|
22
23
|
const __dirname = dirname(__filename);
|
|
@@ -437,6 +438,23 @@ function readJsonSafe(path) {
|
|
|
437
438
|
}
|
|
438
439
|
}
|
|
439
440
|
|
|
441
|
+
// Distinguishes this agent process from any other agent pinging the same
|
|
442
|
+
// token/board — e.g. a build-kit and a modeling-kit install in the same project
|
|
443
|
+
// share one root config.json, and without a per-agent id both would upsert the
|
|
444
|
+
// same alive row and race each other. Written to the kit's OWN config.json
|
|
445
|
+
// (inside kitDir, not the shared root one credentials live in) so a build agent
|
|
446
|
+
// and a modeling agent never end up with the same id, and persisted so restarts
|
|
447
|
+
// of this same kit keep reporting under the same identity.
|
|
448
|
+
function ensureAgentId(kitDir) {
|
|
449
|
+
const kitConfigPath = join(kitDir, '.eventmodelers', 'config.json');
|
|
450
|
+
const existing = readJsonSafe(kitConfigPath);
|
|
451
|
+
if (existing.agentId) return existing.agentId;
|
|
452
|
+
const agentId = randomUUID();
|
|
453
|
+
mkdirSync(dirname(kitConfigPath), { recursive: true });
|
|
454
|
+
writeFileSync(kitConfigPath, JSON.stringify({ ...existing, agentId }, null, 2));
|
|
455
|
+
return agentId;
|
|
456
|
+
}
|
|
457
|
+
|
|
440
458
|
// Hierarchical resolution: a shared config higher up the directory tree (e.g. the
|
|
441
459
|
// project root's own .eventmodelers/config.json, or ~/.eventmodelers/config.json for
|
|
442
460
|
// defaults shared across every project) provides the base values — this is where
|
|
@@ -828,6 +846,7 @@ async function runModeling(kitDir, projectDir) {
|
|
|
828
846
|
const { createClient } = await import('@supabase/supabase-js');
|
|
829
847
|
|
|
830
848
|
const local = loadLocalConfig(kitDir);
|
|
849
|
+
local.agentId = local.agentId ?? ensureAgentId(kitDir);
|
|
831
850
|
if (!local.token || !local.organizationId) {
|
|
832
851
|
console.error('❌ --modeling needs platform credentials in .eventmodelers/config.json (token + organizationId) — run `/connect` once or paste your config first.');
|
|
833
852
|
process.exit(1);
|
|
@@ -1020,10 +1039,10 @@ async function runModeling(kitDir, projectDir) {
|
|
|
1020
1039
|
const res = await fetch(`${cfg.baseUrl}/api/agent-alive`, {
|
|
1021
1040
|
method: 'POST',
|
|
1022
1041
|
headers: { Authorization: `Bearer ${realtimeToken}`, 'Content-Type': 'application/json' },
|
|
1023
|
-
body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'MODELING' }),
|
|
1042
|
+
body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'MODELING', agent_id: cfg.agentId }),
|
|
1024
1043
|
signal: AbortSignal.timeout(10_000),
|
|
1025
1044
|
});
|
|
1026
|
-
if (!res.ok) log(`ping failed: ${res.status}`);
|
|
1045
|
+
if (!res.ok) log(`ping failed: ${res.status} ${await res.text().catch(() => '')}`);
|
|
1027
1046
|
} catch (err) {
|
|
1028
1047
|
log(`ping error: ${err.message}`);
|
|
1029
1048
|
}
|
|
@@ -1215,7 +1234,12 @@ program
|
|
|
1215
1234
|
console.error(`❌ --modeling only supports a modeling-kit install (${MODELING_KIT.kitDirName}/) — it subscribes to the org-wide prompt queue, which build-kit stacks don't have. Use \`eventmodelers run\` (optionally with --ollama/--bash) for build-kit's slice-status loop instead.`);
|
|
1216
1235
|
process.exit(1);
|
|
1217
1236
|
}
|
|
1218
|
-
|
|
1237
|
+
// Writes to a stdout pipe are asynchronous on POSIX — without waiting for this
|
|
1238
|
+
// write's own flush callback, the heavier synchronous/async work runModeling()
|
|
1239
|
+
// does right after (dynamic imports, config reads) can eat the event-loop tick
|
|
1240
|
+
// this write needed to drain, so a piped watcher sees the ping arrive after
|
|
1241
|
+
// runModeling's own [modeling] log lines instead of before them.
|
|
1242
|
+
await new Promise((res) => process.stdout.write(`▶ Starting modeling loop (warm Claude process) for ${relative(cwd, kitDir)}...\n\n`, res));
|
|
1219
1243
|
try {
|
|
1220
1244
|
await runModeling(kitDir, resolve(kitDir, '..'));
|
|
1221
1245
|
} catch (err) {
|
package/package.json
CHANGED
|
@@ -108,6 +108,30 @@ function hasCredentials(cfg) {
|
|
|
108
108
|
return !!(cfg.token && cfg.organizationId && cfg.boardId && cfg.baseUrl);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// Distinguishes this agent process from any other agent pinging the same
|
|
112
|
+
// token/board — e.g. a build-kit and a modeling-kit install in the same project
|
|
113
|
+
// share one root config.json, and without a per-agent id both would upsert the
|
|
114
|
+
// same alive row and race each other. Written to the kit's OWN config.json
|
|
115
|
+
// (inside kitDir, not the shared root one credentials live in) so a build agent
|
|
116
|
+
// and a modeling agent never end up with the same id, and persisted so restarts
|
|
117
|
+
// of this same kit keep reporting under the same identity.
|
|
118
|
+
function ensureAgentId(kitDir) {
|
|
119
|
+
const kitConfigPath = join(kitDir, '.eventmodelers', 'config.json');
|
|
120
|
+
let existing = {};
|
|
121
|
+
if (existsSync(kitConfigPath)) {
|
|
122
|
+
try {
|
|
123
|
+
existing = JSON.parse(readFileSync(kitConfigPath, 'utf-8'));
|
|
124
|
+
} catch {
|
|
125
|
+
console.warn(`[ralph] Skipping invalid config at ${kitConfigPath}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (existing.agentId) return existing.agentId;
|
|
129
|
+
const agentId = randomUUID();
|
|
130
|
+
mkdirSync(dirname(kitConfigPath), { recursive: true });
|
|
131
|
+
writeFileSync(kitConfigPath, JSON.stringify({ ...existing, agentId }, null, 2));
|
|
132
|
+
return agentId;
|
|
133
|
+
}
|
|
134
|
+
|
|
111
135
|
async function fetchPlatformConfig(local) {
|
|
112
136
|
const remote = await fetchJSON(`${local.baseUrl}/api/config`, {
|
|
113
137
|
headers: { 'x-token': local.token },
|
|
@@ -252,10 +276,10 @@ async function startRealtimeAgent(cfg, kitDir) {
|
|
|
252
276
|
const res = await fetch(`${cfg.baseUrl}/api/agent-alive`, {
|
|
253
277
|
method: 'POST',
|
|
254
278
|
headers: { Authorization: `Bearer ${realtimeToken}`, 'Content-Type': 'application/json' },
|
|
255
|
-
body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'BUILD' }),
|
|
279
|
+
body: JSON.stringify({ token: cfg.token, board_id: cfg.boardId, agent_type: 'BUILD', agent_id: cfg.agentId }),
|
|
256
280
|
signal: AbortSignal.timeout(10_000),
|
|
257
281
|
});
|
|
258
|
-
if (!res.ok) console.error(`[agent] Ping failed: ${res.status}`);
|
|
282
|
+
if (!res.ok) console.error(`[agent] Ping failed: ${res.status} ${await res.text().catch(() => '')}`);
|
|
259
283
|
} catch (err) {
|
|
260
284
|
console.error('[agent] Ping error:', err);
|
|
261
285
|
}
|
|
@@ -357,6 +381,7 @@ export { loadLocalConfig, fetchPlatformConfig, retryOn401, startRealtimeAgent };
|
|
|
357
381
|
|
|
358
382
|
export async function startRalph({ kitDir, projectDir, onTask, onPlannedSlice }) {
|
|
359
383
|
const local = loadLocalConfig(kitDir);
|
|
384
|
+
local.agentId = local.agentId ?? ensureAgentId(kitDir);
|
|
360
385
|
|
|
361
386
|
console.log(`Ralph — kit: ${kitDir}`);
|
|
362
387
|
console.log(` project: ${projectDir}`);
|