mirror of
https://github.com/affaan-m/everything-claude-code.git
synced 2026-04-30 22:13:28 +08:00
feat: add loop-status watch mode
This commit is contained in:
committed by
Affaan Mustafa
parent
b1456bd954
commit
38f4265a1c
@@ -40,10 +40,15 @@ tool calls that have no matching `tool_result`.
|
||||
directly.
|
||||
- `ecc loop-status --bash-timeout-seconds 1800` adjusts the stale Bash
|
||||
threshold.
|
||||
- `ecc loop-status --watch` refreshes status until interrupted.
|
||||
- `ecc loop-status --watch --watch-count 3` emits a bounded watch stream for
|
||||
scripts and handoffs.
|
||||
|
||||
## Watch Mode
|
||||
|
||||
When `--watch` is present, refresh status periodically and surface state changes.
|
||||
When `--watch` is present, refresh status periodically. With `--json`, each
|
||||
refresh is emitted as one JSON object per line so another terminal or script can
|
||||
consume the stream.
|
||||
|
||||
## Arguments
|
||||
|
||||
|
||||
@@ -8,12 +8,13 @@ const path = require('path');
|
||||
const DEFAULT_BASH_TIMEOUT_SECONDS = 30 * 60;
|
||||
const DEFAULT_LIMIT = 10;
|
||||
const DEFAULT_WAKE_GRACE_MULTIPLIER = 2;
|
||||
const DEFAULT_WATCH_INTERVAL_SECONDS = 5;
|
||||
|
||||
function usage() {
|
||||
console.log([
|
||||
'Usage:',
|
||||
' node scripts/loop-status.js [--json] [--home <dir>] [--limit <n>]',
|
||||
' node scripts/loop-status.js --transcript <session.jsonl> [--json]',
|
||||
' node scripts/loop-status.js [--json] [--home <dir>] [--limit <n>] [--watch]',
|
||||
' node scripts/loop-status.js --transcript <session.jsonl> [--json] [--watch]',
|
||||
'',
|
||||
'Options:',
|
||||
' --json Emit machine-readable status JSON',
|
||||
@@ -23,6 +24,9 @@ function usage() {
|
||||
' --bash-timeout-seconds <n> Age before a pending Bash call is stale (default: 1800)',
|
||||
' --wake-grace-multiplier <n> ScheduleWakeup grace multiplier (default: 2)',
|
||||
' --now <time> Override current time (ISO, epoch ms, or "now")',
|
||||
' --watch Refresh status until interrupted',
|
||||
' --watch-count <n> Stop after n watch refreshes',
|
||||
' --watch-interval-seconds <n> Seconds between watch refreshes (default: 5)',
|
||||
'',
|
||||
'Examples:',
|
||||
' node scripts/loop-status.js --json',
|
||||
@@ -64,7 +68,10 @@ function parseArgs(argv) {
|
||||
now: null,
|
||||
showHelp: false,
|
||||
transcriptPaths: [],
|
||||
watch: false,
|
||||
watchCount: null,
|
||||
wakeGraceMultiplier: DEFAULT_WAKE_GRACE_MULTIPLIER,
|
||||
watchIntervalSeconds: DEFAULT_WATCH_INTERVAL_SECONDS,
|
||||
};
|
||||
|
||||
for (let index = 0; index < args.length; index += 1) {
|
||||
@@ -92,6 +99,14 @@ function parseArgs(argv) {
|
||||
} else if (arg === '--now') {
|
||||
options.now = readValue(args, index, arg);
|
||||
index += 1;
|
||||
} else if (arg === '--watch') {
|
||||
options.watch = true;
|
||||
} else if (arg === '--watch-count') {
|
||||
options.watchCount = readPositiveInteger(readValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else if (arg === '--watch-interval-seconds') {
|
||||
options.watchIntervalSeconds = readPositiveNumber(readValue(args, index, arg), arg);
|
||||
index += 1;
|
||||
} else {
|
||||
throw new Error(`Unknown option: ${arg}`);
|
||||
}
|
||||
@@ -106,7 +121,10 @@ function normalizeOptions(options = {}) {
|
||||
bashTimeoutSeconds: options.bashTimeoutSeconds ?? DEFAULT_BASH_TIMEOUT_SECONDS,
|
||||
limit: options.limit ?? DEFAULT_LIMIT,
|
||||
transcriptPaths: options.transcriptPaths || [],
|
||||
watch: Boolean(options.watch),
|
||||
watchCount: options.watchCount ?? null,
|
||||
wakeGraceMultiplier: options.wakeGraceMultiplier ?? DEFAULT_WAKE_GRACE_MULTIPLIER,
|
||||
watchIntervalSeconds: options.watchIntervalSeconds ?? DEFAULT_WATCH_INTERVAL_SECONDS,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -576,28 +594,57 @@ function formatText(payload) {
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function main() {
|
||||
function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function writeStatus(payload, options) {
|
||||
if (options.json) {
|
||||
console.log(options.watch ? JSON.stringify(payload) : JSON.stringify(payload, null, 2));
|
||||
} else {
|
||||
console.log(formatText(payload));
|
||||
}
|
||||
}
|
||||
|
||||
async function runWatch(options) {
|
||||
const normalizedOptions = normalizeOptions(options);
|
||||
let iteration = 0;
|
||||
|
||||
while (normalizedOptions.watchCount === null || iteration < normalizedOptions.watchCount) {
|
||||
if (iteration > 0 && !normalizedOptions.json) {
|
||||
console.log('');
|
||||
}
|
||||
writeStatus(buildStatus(normalizedOptions), normalizedOptions);
|
||||
iteration += 1;
|
||||
|
||||
if (normalizedOptions.watchCount !== null && iteration >= normalizedOptions.watchCount) {
|
||||
break;
|
||||
}
|
||||
|
||||
await sleep(normalizedOptions.watchIntervalSeconds * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseArgs(process.argv);
|
||||
if (options.showHelp) {
|
||||
usage();
|
||||
return;
|
||||
}
|
||||
|
||||
const payload = buildStatus(options);
|
||||
if (options.json) {
|
||||
console.log(JSON.stringify(payload, null, 2));
|
||||
} else {
|
||||
console.log(formatText(payload));
|
||||
if (options.watch) {
|
||||
await runWatch(options);
|
||||
return;
|
||||
}
|
||||
|
||||
writeStatus(buildStatus(options), options);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
try {
|
||||
main();
|
||||
} catch (error) {
|
||||
main().catch(error => {
|
||||
console.error(`[loop-status] ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
@@ -606,4 +653,5 @@ module.exports = {
|
||||
extractToolResultIds,
|
||||
extractToolUses,
|
||||
parseArgs,
|
||||
runWatch,
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@ const path = require('path');
|
||||
const { execFileSync } = require('child_process');
|
||||
|
||||
const SCRIPT = path.join(__dirname, '..', '..', 'scripts', 'loop-status.js');
|
||||
const { analyzeTranscript, buildStatus } = require('../../scripts/loop-status');
|
||||
const { analyzeTranscript, buildStatus, parseArgs } = require('../../scripts/loop-status');
|
||||
const NOW = '2026-04-30T10:00:00.000Z';
|
||||
|
||||
function run(args = [], options = {}) {
|
||||
@@ -396,6 +396,58 @@ function runTests() {
|
||||
assert.match(result.stderr, /--limit must be a positive integer/);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('parses watch mode controls', () => {
|
||||
const options = parseArgs([
|
||||
'node',
|
||||
'scripts/loop-status.js',
|
||||
'--watch',
|
||||
'--watch-count',
|
||||
'2',
|
||||
'--watch-interval-seconds',
|
||||
'0.01',
|
||||
]);
|
||||
|
||||
assert.strictEqual(options.watch, true);
|
||||
assert.strictEqual(options.watchCount, 2);
|
||||
assert.strictEqual(options.watchIntervalSeconds, 0.01);
|
||||
})) passed++; else failed++;
|
||||
|
||||
if (test('watch mode emits repeated JSON status frames', () => {
|
||||
const homeDir = createTempHome();
|
||||
|
||||
try {
|
||||
writeTranscript(homeDir, '-Users-affoon-project-watch', 'session-watch.jsonl', [
|
||||
toolUse('2026-04-30T09:00:00.000Z', 'session-watch', 'toolu_watch', 'ScheduleWakeup', {
|
||||
delaySeconds: 300,
|
||||
reason: 'Loop checkpoint',
|
||||
}),
|
||||
]);
|
||||
|
||||
const result = run([
|
||||
'--home',
|
||||
homeDir,
|
||||
'--now',
|
||||
NOW,
|
||||
'--json',
|
||||
'--watch',
|
||||
'--watch-count',
|
||||
'2',
|
||||
'--watch-interval-seconds',
|
||||
'0.01',
|
||||
]);
|
||||
|
||||
assert.strictEqual(result.code, 0, result.stderr);
|
||||
const frames = result.stdout.trim().split(/\r?\n/).map(line => JSON.parse(line));
|
||||
assert.strictEqual(frames.length, 2);
|
||||
assert.strictEqual(frames[0].schemaVersion, 'ecc.loop-status.v1');
|
||||
assert.strictEqual(frames[1].schemaVersion, 'ecc.loop-status.v1');
|
||||
assert.strictEqual(frames[0].sessions[0].sessionId, 'session-watch');
|
||||
assert.strictEqual(frames[1].sessions[0].sessionId, 'session-watch');
|
||||
} finally {
|
||||
fs.rmSync(homeDir, { recursive: true, force: true });
|
||||
}
|
||||
})) passed++; else failed++;
|
||||
|
||||
console.log(`\nResults: Passed: ${passed}, Failed: ${failed}`);
|
||||
process.exit(failed > 0 ? 1 : 0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user