runspec-node 0.11.1 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/loader.js +6 -0
- package/dist/loader.js.map +1 -1
- package/dist/logging_setup.d.ts +20 -2
- package/dist/logging_setup.d.ts.map +1 -1
- package/dist/logging_setup.js +209 -11
- package/dist/logging_setup.js.map +1 -1
- package/dist/models.d.ts +1 -0
- package/dist/models.d.ts.map +1 -1
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +26 -0
- package/dist/parser.js.map +1 -1
- package/dist/serve.js +18 -2
- package/dist/serve.js.map +1 -1
- package/package.json +4 -4
- package/src/loader.ts +6 -0
- package/src/logging_setup.ts +239 -11
- package/src/models.ts +1 -0
- package/src/parser.ts +27 -0
- package/src/serve.ts +19 -2
- package/tests/test_loader.test.ts +18 -4
- package/tests/test_logging_setup.test.ts +10 -3
- package/tests/test_run_summary.test.ts +204 -0
package/dist/serve.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runspec-node",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Node/TypeScript language pack for runspec",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"node": ">=18.0.0"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/jest": "^
|
|
28
|
-
"@types/node": "^
|
|
29
|
-
"jest": "^
|
|
27
|
+
"@types/jest": "^30.0.0",
|
|
28
|
+
"@types/node": "^25.9.1",
|
|
29
|
+
"jest": "^30.4.2",
|
|
30
30
|
"ts-jest": "^29.0.0",
|
|
31
31
|
"typescript": "^5.0.0"
|
|
32
32
|
},
|
package/src/loader.ts
CHANGED
|
@@ -40,10 +40,16 @@ function normaliseLogging(raw: Record<string, unknown> | undefined): LoggingConf
|
|
|
40
40
|
// silencing INFO would break agent responses (stdout is the MCP tool
|
|
41
41
|
// response body), and verbosity for debugging is handled by the `--debug`
|
|
42
42
|
// flag injected at parse time.
|
|
43
|
+
//
|
|
44
|
+
// `summary` (default true) writes one record per run to the audit log and
|
|
45
|
+
// one human-readable line to stderr at process exit: duration, exit code,
|
|
46
|
+
// log-event counts by level. Suppress per-invocation with `--no-summary`
|
|
47
|
+
// or `RUNSPEC_NO_SUMMARY=1`.
|
|
43
48
|
if (raw === undefined) return undefined;
|
|
44
49
|
return {
|
|
45
50
|
rotate: String(raw['rotate'] ?? 'midnight'),
|
|
46
51
|
keep: Number(raw['keep'] ?? 7),
|
|
52
|
+
summary: raw['summary'] !== undefined ? Boolean(raw['summary']) : true,
|
|
47
53
|
};
|
|
48
54
|
}
|
|
49
55
|
|
package/src/logging_setup.ts
CHANGED
|
@@ -15,6 +15,29 @@ let _configured = false;
|
|
|
15
15
|
const _loggers = new Map<string, Logger>();
|
|
16
16
|
const _handlers: Handler[] = [];
|
|
17
17
|
|
|
18
|
+
const RUN_SUMMARY_LOGGER = 'runspec.runsummary';
|
|
19
|
+
|
|
20
|
+
interface CapturedException {
|
|
21
|
+
type: string;
|
|
22
|
+
message: string;
|
|
23
|
+
traceback: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface SummaryState {
|
|
27
|
+
counter: RunSummaryCounter;
|
|
28
|
+
start: bigint;
|
|
29
|
+
runnable: string;
|
|
30
|
+
autonomy: string | undefined;
|
|
31
|
+
agent: boolean;
|
|
32
|
+
commandPath: string[];
|
|
33
|
+
exception: CapturedException | null;
|
|
34
|
+
exitCode: number;
|
|
35
|
+
emitted: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let _summaryState: SummaryState | null = null;
|
|
39
|
+
let _exitHooksInstalled = false;
|
|
40
|
+
|
|
18
41
|
// ── level map ─────────────────────────────────────────────────────────────────
|
|
19
42
|
|
|
20
43
|
const LEVEL_NUM: Record<string, number> = {
|
|
@@ -149,12 +172,16 @@ function formatConsole(record: LogRecord, showTracebacks: boolean): string {
|
|
|
149
172
|
* Routes DEBUG/INFO records (i.e. below WARNING) to stdout.
|
|
150
173
|
* Treated as the runnable's primary output — captured as the response body
|
|
151
174
|
* when `runspec serve` invokes the runnable as a subprocess.
|
|
175
|
+
*
|
|
176
|
+
* Drops `runspec.runsummary` records — those are file-only; the human form
|
|
177
|
+
* of the summary is written directly to stderr by the exit hook.
|
|
152
178
|
*/
|
|
153
179
|
class StdoutHandler implements Handler {
|
|
154
180
|
constructor(public readonly level: number, private readonly showTracebacks: boolean) {}
|
|
155
181
|
|
|
156
182
|
emit(record: LogRecord): void {
|
|
157
183
|
if (record.levelNum >= 30) return; // WARNING+ belongs on stderr
|
|
184
|
+
if (record.loggerName === RUN_SUMMARY_LOGGER) return;
|
|
158
185
|
try {
|
|
159
186
|
process.stdout.write(formatConsole(record, this.showTracebacks) + '\n');
|
|
160
187
|
} catch {
|
|
@@ -175,6 +202,7 @@ class StderrHandler implements Handler {
|
|
|
175
202
|
|
|
176
203
|
emit(record: LogRecord): void {
|
|
177
204
|
if (record.levelNum < 30) return;
|
|
205
|
+
if (record.loggerName === RUN_SUMMARY_LOGGER) return;
|
|
178
206
|
try {
|
|
179
207
|
process.stderr.write(formatConsole(record, this.showTracebacks) + '\n');
|
|
180
208
|
} catch {
|
|
@@ -183,6 +211,28 @@ class StderrHandler implements Handler {
|
|
|
183
211
|
}
|
|
184
212
|
}
|
|
185
213
|
|
|
214
|
+
// ── run summary counter ───────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Counts records by level. Emits nothing — read at process exit by the
|
|
218
|
+
* summary hook. Always attached at level=DEBUG so every record is counted.
|
|
219
|
+
*/
|
|
220
|
+
class RunSummaryCounter implements Handler {
|
|
221
|
+
readonly level = 10;
|
|
222
|
+
readonly counts: Record<string, number> = {
|
|
223
|
+
DEBUG: 0, INFO: 0, WARNING: 0, ERROR: 0, CRITICAL: 0,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
emit(record: LogRecord): void {
|
|
227
|
+
// Don't count the summary record itself.
|
|
228
|
+
if (record.loggerName === RUN_SUMMARY_LOGGER) return;
|
|
229
|
+
const label = LEVEL_LABEL[record.levelNum];
|
|
230
|
+
if (label && label in this.counts) {
|
|
231
|
+
this.counts[label]++;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
186
236
|
// ── rotating file handlers ────────────────────────────────────────────────────
|
|
187
237
|
|
|
188
238
|
function doRotate(logPath: string, keep: number): void {
|
|
@@ -289,21 +339,160 @@ function makeFileHandler(logPath: string, rotate: string, keep: number, level: n
|
|
|
289
339
|
|
|
290
340
|
// ── log dir resolution ────────────────────────────────────────────────────────
|
|
291
341
|
|
|
342
|
+
/**
|
|
343
|
+
* Walk up from `start` looking for a `package.json` that is NOT inside a
|
|
344
|
+
* `node_modules` directory — that's the project root. Returns null if we
|
|
345
|
+
* reach the filesystem root without finding one.
|
|
346
|
+
*
|
|
347
|
+
* Skipping node_modules is intentional: dependency packages bundle their
|
|
348
|
+
* own package.json, but they're not the project root the user owns.
|
|
349
|
+
*/
|
|
350
|
+
function findProjectRoot(start: string): string | null {
|
|
351
|
+
let dir = path.resolve(start);
|
|
352
|
+
while (true) {
|
|
353
|
+
if (!dir.split(path.sep).includes('node_modules')) {
|
|
354
|
+
if (fs.existsSync(path.join(dir, 'package.json'))) return dir;
|
|
355
|
+
}
|
|
356
|
+
const parent = path.dirname(dir);
|
|
357
|
+
if (parent === dir) return null; // hit filesystem root
|
|
358
|
+
dir = parent;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Resolve the log directory.
|
|
364
|
+
*
|
|
365
|
+
* Mirror of Python's `sys.prefix / "logs"`: pick the project's installation
|
|
366
|
+
* root — the nearest ancestor `package.json` of the runnable's runspec.toml,
|
|
367
|
+
* skipping anything under `node_modules`. Logs land at `{project_root}/logs/`,
|
|
368
|
+
* so one logs directory per project, surviving reinstalls.
|
|
369
|
+
*
|
|
370
|
+
* Falls back to `~/logs/` when no project root is found or the chosen
|
|
371
|
+
* directory is not writable (e.g. read-only volumes, system installs).
|
|
372
|
+
*/
|
|
292
373
|
function resolveLogDir(configPath: string): string {
|
|
293
|
-
const
|
|
374
|
+
const projectRoot = findProjectRoot(path.dirname(configPath));
|
|
375
|
+
if (projectRoot) {
|
|
376
|
+
const candidate = path.join(projectRoot, 'logs');
|
|
377
|
+
try {
|
|
378
|
+
fs.mkdirSync(candidate, { recursive: true });
|
|
379
|
+
const probe = path.join(candidate, '.wtest');
|
|
380
|
+
fs.writeFileSync(probe, '');
|
|
381
|
+
fs.unlinkSync(probe);
|
|
382
|
+
return candidate;
|
|
383
|
+
} catch {
|
|
384
|
+
// fall through to home
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
const fallback = path.join(os.homedir(), 'logs');
|
|
388
|
+
fs.mkdirSync(fallback, { recursive: true });
|
|
389
|
+
return fallback;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── run summary ──────────────────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
function formatSummaryLine(state: SummaryState, durationMs: number, exitCode: number): string {
|
|
395
|
+
const counts = state.counter.counts;
|
|
396
|
+
const total = (counts.DEBUG ?? 0) + (counts.INFO ?? 0) + (counts.WARNING ?? 0) + (counts.ERROR ?? 0) + (counts.CRITICAL ?? 0);
|
|
397
|
+
const warnings = counts.WARNING ?? 0;
|
|
398
|
+
const errors = (counts.ERROR ?? 0) + (counts.CRITICAL ?? 0);
|
|
399
|
+
const secs = (durationMs / 1000).toFixed(2);
|
|
400
|
+
const runnable = state.runnable;
|
|
401
|
+
const wSuffix = warnings === 1 ? '' : 's';
|
|
402
|
+
const eSuffix = errors === 1 ? '' : 's';
|
|
403
|
+
if (state.exception || exitCode !== 0) {
|
|
404
|
+
const excPart = state.exception ? `, ${state.exception.type}` : '';
|
|
405
|
+
return `runspec: ${runnable} failed in ${secs}s — exit ${exitCode}${excPart} — ${total} events (${warnings} warning${wSuffix}, ${errors} error${eSuffix})`;
|
|
406
|
+
}
|
|
407
|
+
return `runspec: ${runnable} completed in ${secs}s — ${total} events (${warnings} warning${wSuffix}, ${errors} error${eSuffix})`;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Emit one summary record to the file (via the standard logger pipeline —
|
|
412
|
+
* picked up by the file handler, dropped by the console handlers) and one
|
|
413
|
+
* formatted line directly to stderr. Idempotent — safe to call repeatedly.
|
|
414
|
+
*/
|
|
415
|
+
export function emitRunSummary(): void {
|
|
416
|
+
const state = _summaryState;
|
|
417
|
+
if (state === null || state.emitted) return;
|
|
418
|
+
state.emitted = true;
|
|
419
|
+
|
|
420
|
+
const durationMs = Number((process.hrtime.bigint() - state.start) / 1_000_000n);
|
|
421
|
+
// Exit code: explicit capture (set by uncaughtException hook) or
|
|
422
|
+
// process.exitCode if the user set it. Defaults to 0.
|
|
423
|
+
const exitCode = state.exitCode !== 0 ? state.exitCode : (state.exception ? 1 : 0);
|
|
424
|
+
|
|
294
425
|
try {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
426
|
+
getLogger(RUN_SUMMARY_LOGGER).info('run completed', {
|
|
427
|
+
event: 'run_summary',
|
|
428
|
+
runnable: state.runnable,
|
|
429
|
+
command_path: state.commandPath,
|
|
430
|
+
duration_ms: durationMs,
|
|
431
|
+
exit_code: exitCode,
|
|
432
|
+
agent: state.agent,
|
|
433
|
+
autonomy: state.autonomy,
|
|
434
|
+
exception: state.exception,
|
|
435
|
+
events: { ...state.counter.counts },
|
|
436
|
+
});
|
|
300
437
|
} catch {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
438
|
+
// never disrupt shutdown
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
try {
|
|
442
|
+
process.stderr.write(formatSummaryLine(state, durationMs, exitCode) + '\n');
|
|
443
|
+
} catch {
|
|
444
|
+
// never disrupt shutdown
|
|
304
445
|
}
|
|
305
446
|
}
|
|
306
447
|
|
|
448
|
+
function installExitHooks(): void {
|
|
449
|
+
if (_exitHooksInstalled) return;
|
|
450
|
+
_exitHooksInstalled = true;
|
|
451
|
+
|
|
452
|
+
process.on('exit', (code) => {
|
|
453
|
+
if (_summaryState && !_summaryState.emitted) {
|
|
454
|
+
// process.exitCode wins over the explicit exception capture only if
|
|
455
|
+
// it's non-zero — uncaughtException already set state.exitCode=1.
|
|
456
|
+
if (code !== 0 && _summaryState.exitCode === 0) _summaryState.exitCode = code;
|
|
457
|
+
emitRunSummary();
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// Skip the crash-handlers under jest — they call process.exit(1), which
|
|
462
|
+
// would tear down the test runner if any test ever produced an unhandled
|
|
463
|
+
// rejection. The 'exit' hook above is harmless and still runs.
|
|
464
|
+
if (process.env['JEST_WORKER_ID'] !== undefined) return;
|
|
465
|
+
|
|
466
|
+
process.on('uncaughtException', (err: Error) => {
|
|
467
|
+
if (_summaryState) {
|
|
468
|
+
_summaryState.exception = {
|
|
469
|
+
type: err.name || 'Error',
|
|
470
|
+
message: err.message || String(err),
|
|
471
|
+
traceback: err.stack ?? '',
|
|
472
|
+
};
|
|
473
|
+
_summaryState.exitCode = 1;
|
|
474
|
+
}
|
|
475
|
+
// Preserve default Node behaviour: print and exit non-zero. The 'exit'
|
|
476
|
+
// hook above will fire and run emitRunSummary().
|
|
477
|
+
process.stderr.write((err.stack ?? String(err)) + '\n');
|
|
478
|
+
process.exit(1);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
process.on('unhandledRejection', (reason: unknown) => {
|
|
482
|
+
if (_summaryState) {
|
|
483
|
+
const err = reason instanceof Error ? reason : new Error(String(reason));
|
|
484
|
+
_summaryState.exception = {
|
|
485
|
+
type: err.name || 'Error',
|
|
486
|
+
message: err.message || String(reason),
|
|
487
|
+
traceback: err.stack ?? '',
|
|
488
|
+
};
|
|
489
|
+
_summaryState.exitCode = 1;
|
|
490
|
+
}
|
|
491
|
+
process.stderr.write(`Unhandled rejection: ${reason instanceof Error ? (reason.stack ?? reason.message) : String(reason)}\n`);
|
|
492
|
+
process.exit(1);
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
|
|
307
496
|
// ── public: configureLogging ──────────────────────────────────────────────────
|
|
308
497
|
|
|
309
498
|
export interface ConfigureLoggingOptions {
|
|
@@ -311,6 +500,10 @@ export interface ConfigureLoggingOptions {
|
|
|
311
500
|
runnableName: string;
|
|
312
501
|
configPath: string;
|
|
313
502
|
debug?: boolean;
|
|
503
|
+
noSummary?: boolean;
|
|
504
|
+
autonomy?: string;
|
|
505
|
+
agent?: boolean;
|
|
506
|
+
commandPath?: string[];
|
|
314
507
|
}
|
|
315
508
|
|
|
316
509
|
/**
|
|
@@ -331,7 +524,14 @@ export interface ConfigureLoggingOptions {
|
|
|
331
524
|
* WARNING regardless.
|
|
332
525
|
*
|
|
333
526
|
* File handler is always JSON; level follows the same `--debug` toggle as
|
|
334
|
-
* stdout (defaults to INFO — keeps third-party DEBUG noise out of the audit
|
|
527
|
+
* stdout (defaults to INFO — keeps third-party DEBUG noise out of the audit
|
|
528
|
+
* log). Log files land under `{project_root}/logs/` — the nearest ancestor
|
|
529
|
+
* `package.json` skipping `node_modules`, mirroring Python's venv-root
|
|
530
|
+
* convention. Falls back to `~/logs/` when no project root is found.
|
|
531
|
+
*
|
|
532
|
+
* Run summary (when `logCfg.summary` is true and `noSummary` is false)
|
|
533
|
+
* counts log events by level and emits a single record at process exit
|
|
534
|
+
* with duration, exit code, exception class, and per-level counts.
|
|
335
535
|
*/
|
|
336
536
|
export function configureLogging(opts: ConfigureLoggingOptions): void {
|
|
337
537
|
if (!opts.logCfg || _configured) return;
|
|
@@ -346,6 +546,31 @@ export function configureLogging(opts: ConfigureLoggingOptions): void {
|
|
|
346
546
|
const logPath = path.join(logDir, `${opts.runnableName}.log`);
|
|
347
547
|
_handlers.push(makeFileHandler(logPath, opts.logCfg.rotate, opts.logCfg.keep, floor));
|
|
348
548
|
|
|
549
|
+
// Always attach the counter — cost is one dict increment per log call.
|
|
550
|
+
// Only the exit hook + state population are conditional on summary mode.
|
|
551
|
+
const counter = new RunSummaryCounter();
|
|
552
|
+
_handlers.push(counter);
|
|
553
|
+
|
|
554
|
+
const summaryEnabled =
|
|
555
|
+
opts.logCfg.summary !== false &&
|
|
556
|
+
!opts.noSummary &&
|
|
557
|
+
!['1', 'true', 'yes'].includes((process.env['RUNSPEC_NO_SUMMARY'] ?? '').toLowerCase());
|
|
558
|
+
|
|
559
|
+
if (summaryEnabled) {
|
|
560
|
+
_summaryState = {
|
|
561
|
+
counter,
|
|
562
|
+
start: process.hrtime.bigint(),
|
|
563
|
+
runnable: opts.runnableName,
|
|
564
|
+
autonomy: opts.autonomy,
|
|
565
|
+
agent: opts.agent ?? false,
|
|
566
|
+
commandPath: opts.commandPath ?? [],
|
|
567
|
+
exception: null,
|
|
568
|
+
exitCode: 0,
|
|
569
|
+
emitted: false,
|
|
570
|
+
};
|
|
571
|
+
installExitHooks();
|
|
572
|
+
}
|
|
573
|
+
|
|
349
574
|
_configured = true;
|
|
350
575
|
}
|
|
351
576
|
|
|
@@ -355,6 +580,9 @@ export function _resetForTest(): void {
|
|
|
355
580
|
_configured = false;
|
|
356
581
|
_loggers.clear();
|
|
357
582
|
_handlers.length = 0;
|
|
583
|
+
_summaryState = null;
|
|
584
|
+
// Note: process event listeners installed by installExitHooks() stay —
|
|
585
|
+
// they no-op when _summaryState is null, which is the test-time state.
|
|
358
586
|
}
|
|
359
587
|
|
|
360
|
-
export { _periodForDate };
|
|
588
|
+
export { _periodForDate, RUN_SUMMARY_LOGGER };
|
package/src/models.ts
CHANGED
package/src/parser.ts
CHANGED
|
@@ -54,6 +54,26 @@ export function parse(opts: ParseOptions = {}): ParsedArgs {
|
|
|
54
54
|
};
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Auto-inject --no-summary when [config.logging] is present. Suppresses
|
|
58
|
+
// the per-run summary record and stderr line for that one invocation.
|
|
59
|
+
if (config.logging && !('no-summary' in rawScript.args)) {
|
|
60
|
+
rawScript = {
|
|
61
|
+
...rawScript,
|
|
62
|
+
args: {
|
|
63
|
+
...rawScript.args,
|
|
64
|
+
'no-summary': {
|
|
65
|
+
name: 'no-summary',
|
|
66
|
+
type: 'flag',
|
|
67
|
+
default: false,
|
|
68
|
+
required: false,
|
|
69
|
+
description: 'Suppress the per-run summary record and stderr line.',
|
|
70
|
+
multiple: false,
|
|
71
|
+
env: 'RUNSPEC_NO_SUMMARY',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
let argv = argvOverride ?? process.argv.slice(2);
|
|
58
78
|
let activeScript = rawScript;
|
|
59
79
|
let commandPath: string[] = [];
|
|
@@ -90,6 +110,9 @@ export function parse(opts: ParseOptions = {}): ParsedArgs {
|
|
|
90
110
|
const debug = config.logging
|
|
91
111
|
? Boolean(coercedValues['debug'] ?? false)
|
|
92
112
|
: false;
|
|
113
|
+
const noSummary = config.logging
|
|
114
|
+
? Boolean(coercedValues['no_summary'] ?? false)
|
|
115
|
+
: false;
|
|
93
116
|
|
|
94
117
|
try {
|
|
95
118
|
configureLogging({
|
|
@@ -97,6 +120,10 @@ export function parse(opts: ParseOptions = {}): ParsedArgs {
|
|
|
97
120
|
runnableName: name,
|
|
98
121
|
configPath,
|
|
99
122
|
debug,
|
|
123
|
+
noSummary,
|
|
124
|
+
autonomy,
|
|
125
|
+
agent,
|
|
126
|
+
commandPath,
|
|
100
127
|
});
|
|
101
128
|
} catch (e) {
|
|
102
129
|
throw new RunSpecError((e as Error).message);
|
package/src/serve.ts
CHANGED
|
@@ -139,13 +139,26 @@ function handleToolsCall(
|
|
|
139
139
|
const runspecEnv = argsToRunspecEnv(args, toolArgSpecs);
|
|
140
140
|
const env = { ...process.env, RUNSPEC_AGENT: '1', ...runspecEnv };
|
|
141
141
|
|
|
142
|
+
const start = process.hrtime.bigint();
|
|
142
143
|
const result = spawnSync(cmd, argv, { encoding: 'utf-8', env });
|
|
144
|
+
const durationMs = Number((process.hrtime.bigint() - start) / 1_000_000n);
|
|
145
|
+
|
|
146
|
+
// _meta is the MCP-standard extension point; clients that don't understand
|
|
147
|
+
// it ignore the block. Same envelope on success and failure so callers can
|
|
148
|
+
// rely on it being present.
|
|
149
|
+
const meta = {
|
|
150
|
+
runspec: { tool: name, duration_ms: durationMs, exit_code: result.status ?? null },
|
|
151
|
+
};
|
|
143
152
|
|
|
144
153
|
if (result.status === 0) {
|
|
145
154
|
return {
|
|
146
155
|
jsonrpc: '2.0',
|
|
147
156
|
id: reqId,
|
|
148
|
-
result: {
|
|
157
|
+
result: {
|
|
158
|
+
content: [{ type: 'text', text: result.stdout ?? '' }],
|
|
159
|
+
isError: false,
|
|
160
|
+
_meta: meta,
|
|
161
|
+
},
|
|
149
162
|
};
|
|
150
163
|
}
|
|
151
164
|
|
|
@@ -156,7 +169,11 @@ function handleToolsCall(
|
|
|
156
169
|
return {
|
|
157
170
|
jsonrpc: '2.0',
|
|
158
171
|
id: reqId,
|
|
159
|
-
result: {
|
|
172
|
+
result: {
|
|
173
|
+
content: [{ type: 'text', text: parts.join('\n') }],
|
|
174
|
+
isError: true,
|
|
175
|
+
_meta: meta,
|
|
176
|
+
},
|
|
160
177
|
};
|
|
161
178
|
}
|
|
162
179
|
|
|
@@ -145,7 +145,7 @@ test('normalises [config.logging] with defaults', () => {
|
|
|
145
145
|
description = "hi"
|
|
146
146
|
`);
|
|
147
147
|
const raw = loadRaw(file);
|
|
148
|
-
expect(raw.config.logging).toEqual({ rotate: 'midnight', keep: 7 });
|
|
148
|
+
expect(raw.config.logging).toEqual({ rotate: 'midnight', keep: 7, summary: true });
|
|
149
149
|
});
|
|
150
150
|
|
|
151
151
|
test('normalises [config.logging] all fields', () => {
|
|
@@ -153,14 +153,28 @@ test('normalises [config.logging] all fields', () => {
|
|
|
153
153
|
const file = path.join(dir, 'runspec.toml');
|
|
154
154
|
fs.writeFileSync(file, `
|
|
155
155
|
[config.logging]
|
|
156
|
-
rotate
|
|
157
|
-
keep
|
|
156
|
+
rotate = "10 MB"
|
|
157
|
+
keep = 3
|
|
158
|
+
summary = false
|
|
158
159
|
|
|
159
160
|
[greet]
|
|
160
161
|
description = "hi"
|
|
161
162
|
`);
|
|
162
163
|
const raw = loadRaw(file);
|
|
163
|
-
expect(raw.config.logging).toEqual({ rotate: '10 MB', keep: 3 });
|
|
164
|
+
expect(raw.config.logging).toEqual({ rotate: '10 MB', keep: 3, summary: false });
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test('[config.logging] summary defaults to true when omitted', () => {
|
|
168
|
+
const dir = tmpDir();
|
|
169
|
+
const file = path.join(dir, 'runspec.toml');
|
|
170
|
+
fs.writeFileSync(file, `
|
|
171
|
+
[config.logging]
|
|
172
|
+
rotate = "midnight"
|
|
173
|
+
|
|
174
|
+
[greet]
|
|
175
|
+
description = "hi"
|
|
176
|
+
`);
|
|
177
|
+
expect(loadRaw(file).config.logging?.summary).toBe(true);
|
|
164
178
|
});
|
|
165
179
|
|
|
166
180
|
test('logging is undefined when section absent', () => {
|
|
@@ -10,13 +10,20 @@ import {
|
|
|
10
10
|
} from '../src/logging_setup';
|
|
11
11
|
|
|
12
12
|
function tmpDir(): string {
|
|
13
|
-
|
|
13
|
+
// Each test dir is its own project root so resolveLogDir lands inside it.
|
|
14
|
+
// Without a package.json the walk-up would escape to a real project root
|
|
15
|
+
// and write logs to a shared location — drop a marker file to anchor it.
|
|
16
|
+
const d = fs.mkdtempSync(path.join(os.tmpdir(), 'runspec-log-test-'));
|
|
17
|
+
fs.writeFileSync(path.join(d, 'package.json'), '{"name":"test","version":"0.0.0"}');
|
|
18
|
+
return d;
|
|
14
19
|
}
|
|
15
20
|
|
|
16
21
|
function makeCfg(dir: string, overrides: Record<string, unknown> = {}): Parameters<typeof configureLogging>[0] {
|
|
17
22
|
const { debug, ...logOverrides } = overrides as { debug?: boolean } & Record<string, unknown>;
|
|
18
23
|
return {
|
|
19
|
-
|
|
24
|
+
// summary defaults off in tests so we don't litter atexit emissions across
|
|
25
|
+
// unrelated assertions; the run-summary suite enables it explicitly.
|
|
26
|
+
logCfg: { rotate: 'midnight', keep: 7, summary: false, ...logOverrides },
|
|
20
27
|
runnableName: 'myscript',
|
|
21
28
|
configPath: path.join(dir, 'runspec.toml'),
|
|
22
29
|
debug,
|
|
@@ -365,7 +372,7 @@ test('_periodForDate weekly: adjacent weeks differ', () => {
|
|
|
365
372
|
|
|
366
373
|
// ── log dir fallback ──────────────────────────────────────────────────────────
|
|
367
374
|
|
|
368
|
-
test('falls back to ~/logs when
|
|
375
|
+
test('falls back to ~/logs when the project root is not writable', () => {
|
|
369
376
|
const dir = tmpDir();
|
|
370
377
|
const logsDir = path.join(dir, 'logs');
|
|
371
378
|
// Create logs dir as unwritable
|