@commonlyai/cli 0.1.57 → 0.1.60
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/package.json +1 -1
- package/src/commands/daemon.js +19 -6
- package/src/lib/environment.js +41 -0
package/package.json
CHANGED
package/src/commands/daemon.js
CHANGED
|
@@ -34,6 +34,24 @@ import {
|
|
|
34
34
|
import { daemonLogsDir, daemonSeatLogPath, readLogTail } from '../lib/daemon-logs.js';
|
|
35
35
|
import { loadDaemonState, saveDaemonState } from '../lib/daemon-state.js';
|
|
36
36
|
|
|
37
|
+
// One supervised seat, as `commonly daemon status --verbose` names it. Two of these
|
|
38
|
+
// fields are worth reading closely: the seat's `instanceId` (the identity the
|
|
39
|
+
// platform routes by, so a seat attached to the wrong instance is otherwise
|
|
40
|
+
// invisible) and the supervisor's `restarts` flap counter — which is written,
|
|
41
|
+
// persisted and served, and was shown by no surface at all, so a seat that had
|
|
42
|
+
// been crash-looping looked exactly like a healthy one (TASK-072, 2026-09-19).
|
|
43
|
+
//
|
|
44
|
+
// `restarts` distinguishes 0 from absent on purpose: a state file written by an
|
|
45
|
+
// older daemon has no such field, and printing 0 for it would claim a reading the
|
|
46
|
+
// file does not carry.
|
|
47
|
+
export const formatSeatLine = (seat = {}) => {
|
|
48
|
+
const model = seat.model || 'default model';
|
|
49
|
+
const effort = seat.effort ? `/${seat.effort}` : '';
|
|
50
|
+
const error = seat.lastError ? ` error=${seat.lastError}` : '';
|
|
51
|
+
const restarts = seat.restarts === undefined || seat.restarts === null ? '-' : seat.restarts;
|
|
52
|
+
return ` ${seat.agentName}: ${seat.state} instance=${seat.instanceId || '-'} adapter=${seat.adapter || 'unknown'} model=${model}${effort} pid=${seat.pid || '-'} restarts=${restarts} lastTurn=${seat.lastTurnAt || 'never'}${error}`;
|
|
53
|
+
};
|
|
54
|
+
|
|
37
55
|
const requireDaemonRecord = () => {
|
|
38
56
|
const record = loadDaemonRecord();
|
|
39
57
|
if (!record) {
|
|
@@ -397,12 +415,7 @@ Examples:
|
|
|
397
415
|
console.log('Local supervisor state: no supervised seats.');
|
|
398
416
|
} else {
|
|
399
417
|
console.log('Local supervised seats:');
|
|
400
|
-
for (const seat of local.seats)
|
|
401
|
-
const model = seat.model || 'default model';
|
|
402
|
-
const effort = seat.effort ? `/${seat.effort}` : '';
|
|
403
|
-
const error = seat.lastError ? ` error=${seat.lastError}` : '';
|
|
404
|
-
console.log(` ${seat.agentName}: ${seat.state} adapter=${seat.adapter || 'unknown'} model=${model}${effort} pid=${seat.pid || '-'} lastTurn=${seat.lastTurnAt || 'never'}${error}`);
|
|
405
|
-
}
|
|
418
|
+
for (const seat of local.seats) console.log(formatSeatLine(seat));
|
|
406
419
|
}
|
|
407
420
|
}
|
|
408
421
|
} catch (error) {
|
package/src/lib/environment.js
CHANGED
|
@@ -246,6 +246,22 @@ export const validateEnvironmentSpec = (spec) => {
|
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
248
|
|
|
249
|
+
// A TRANSPORT DECIDES THE ENTRY, so the fields must agree with it (TASK-071,
|
|
250
|
+
// Vera's ruling 2026-09-19). This block is MIRRORED, not shared: the other
|
|
251
|
+
// writer-side check is the backend's, in
|
|
252
|
+
// backend/utils/environmentSpecValidation.ts, called from the
|
|
253
|
+
// `PATCH /api/registry/pods/:podId/agents/:name` handler that stores
|
|
254
|
+
// `config.environment` for the owner's daemon. The two cannot share code at
|
|
255
|
+
// runtime — this is a published ESM package, that is the CJS backend — so
|
|
256
|
+
// the wording here and there is kept parallel deliberately and the two must
|
|
257
|
+
// move together. This one refuses a hand-written `--environment <file>`; the
|
|
258
|
+
// backend's refuses a stored row.
|
|
259
|
+
//
|
|
260
|
+
// The shape rule is not decoration: every adapter branches on it
|
|
261
|
+
// (isStdioServer/isHttpServer in adapters/pi-mcp-client.mjs), and `command`
|
|
262
|
+
// is an argv ARRAY — `connectStdioMcp` destructures `const [cmd, ...args] =
|
|
263
|
+
// command`. An entry whose fields contradict its transport is a record whose
|
|
264
|
+
// reader has to pick a winner, and the readers do not agree on which.
|
|
249
265
|
if (spec.mcp !== undefined) {
|
|
250
266
|
if (!Array.isArray(spec.mcp)) {
|
|
251
267
|
errors.push('mcp must be an array');
|
|
@@ -261,6 +277,31 @@ export const validateEnvironmentSpec = (spec) => {
|
|
|
261
277
|
if (server.transport !== undefined
|
|
262
278
|
&& !['http', 'stdio', 'sse'].includes(server.transport)) {
|
|
263
279
|
errors.push(`mcp[${i}].transport must be one of: http, stdio, sse`);
|
|
280
|
+
// The agreement rule below is defined in terms of a transport this
|
|
281
|
+
// entry does not have; judging it here would mean inventing the
|
|
282
|
+
// second definition that rule exists to avoid.
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
const argv = Array.isArray(server.command) && server.command.length > 0
|
|
286
|
+
&& server.command.every((part) => typeof part === 'string' && part.trim().length > 0);
|
|
287
|
+
const hasUrl = typeof server.url === 'string' && server.url.trim().length > 0;
|
|
288
|
+
if (server.transport === 'http' || server.transport === 'sse') {
|
|
289
|
+
if (!hasUrl) {
|
|
290
|
+
errors.push(`mcp[${i}].url is required when transport is ${server.transport}: a ${server.transport} server is reached by URL, and this entry declares no URL to reach`);
|
|
291
|
+
}
|
|
292
|
+
if (server.command !== undefined) {
|
|
293
|
+
errors.push(`mcp[${i}].command must not be set when transport is ${server.transport}: with a url and a command in one entry each reader picks a different winner, so the record does not say what runs`);
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
// stdio, or absent transport — the historical default.
|
|
297
|
+
if (!argv) {
|
|
298
|
+
errors.push(server.command === undefined
|
|
299
|
+
? `mcp[${i}].command is required for a stdio entry (and for an entry that declares no transport): with no command and no url there is nothing to run`
|
|
300
|
+
: `mcp[${i}].command must be a non-empty array of strings (argv, e.g. ["npx", "-y", "@commonlyai/mcp@latest"]); a string command is not a command line any reader in this repo executes`);
|
|
301
|
+
}
|
|
302
|
+
if (hasUrl) {
|
|
303
|
+
errors.push(`mcp[${i}].url must not be set for a stdio entry (and for an entry that declares no transport): a url here is a second, contradictory way to reach the server`);
|
|
304
|
+
}
|
|
264
305
|
}
|
|
265
306
|
});
|
|
266
307
|
}
|