@inetafrica/open-claudia 3.4.22 → 3.4.24
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/CHANGELOG.md +17 -0
- package/bin/schedule.js +45 -11
- package/core/handlers.js +26 -6
- package/core/loopback.js +13 -2
- package/core/runner.js +38 -8
- package/core/scheduler.js +215 -34
- package/core/state.js +12 -0
- package/package.json +2 -2
- package/test-provider-compaction.js +93 -0
- package/test-provider-language.js +13 -1
- package/test-provider-pack-review.js +25 -2
- package/test-provider-parity-e2e.js +5 -3
- package/test-provider-scheduler.js +531 -4
- package/test-run-lock.js +33 -0
package/core/scheduler.js
CHANGED
|
@@ -41,8 +41,25 @@ const { redactSensitive } = require("./redact");
|
|
|
41
41
|
const MISSED_WAKEUP_GRACE_MS = 60 * 60 * 1000;
|
|
42
42
|
const RETRY_DELAY_MS = 30 * 1000;
|
|
43
43
|
const BUSY_DELAY_MS = 30 * 1000;
|
|
44
|
+
// A watch alarm that has waited this long behind a busy room says so in the
|
|
45
|
+
// chat, once per occurrence, so a busy room never hides an alarm for hours
|
|
46
|
+
// (24 Sep: the Phone alarm waited 2 h through 244 deferrals, the event-bus
|
|
47
|
+
// alarm 7.5 h through 902).
|
|
48
|
+
const BUSY_NOTICE_AFTER_MS = 15 * 60 * 1000;
|
|
44
49
|
// A watch's `check` command is capped so a hung poll can't wedge the tick.
|
|
45
50
|
const WATCH_CHECK_TIMEOUT_MS = 30 * 1000;
|
|
51
|
+
// A hanging check is itself a signal (24 Sep: the Phone health check timed out
|
|
52
|
+
// twice and was only logged idle). This many timeouts in a row wake the agent
|
|
53
|
+
// once, marked "check timing out"; the watch keeps polling, and any completed
|
|
54
|
+
// check resets the count.
|
|
55
|
+
const WATCH_TIMEOUT_ALARM_AFTER = 3;
|
|
56
|
+
// setTimeout holds at most 2^31-1 ms (~24.8 days); a longer delay overflows
|
|
57
|
+
// and fires at once (17 Sep: a 2028 reminder fired 61 s after it was set).
|
|
58
|
+
// Far wakeups sleep in chunks of this size and re-plan until due.
|
|
59
|
+
const MAX_TIMER_DELAY_MS = 2 ** 31 - 1 - 1000;
|
|
60
|
+
// schedule-wakeup refuses a fire time further out than this: past it the
|
|
61
|
+
// <when> is a typo or a mis-parse (one wakeup was set 1273 years out).
|
|
62
|
+
const MAX_WAKEUP_HORIZON_MS = 10 * 365.25 * 24 * 60 * 60 * 1000;
|
|
46
63
|
// Every watch carries a hard expiry so none can idle-poll forever: when the
|
|
47
64
|
// deadline lapses with the check still failing, the next poll tick fires the
|
|
48
65
|
// wake anyway (marked expired) instead of skipping silently.
|
|
@@ -70,9 +87,25 @@ function terminalRunSucceeded(result) {
|
|
|
70
87
|
|
|
71
88
|
// A one-shot fires exactly once then is removed after a successful run: a
|
|
72
89
|
// wakeup, or a watch (a cron carrying a shell `check`) created without repeat
|
|
73
|
-
// polling. A repeat watch stays pending and keeps polling like any cron
|
|
90
|
+
// polling. A repeat watch stays pending and keeps polling like any cron, and
|
|
91
|
+
// so does any watch whose fire only reported a hanging check.
|
|
74
92
|
function isOneShot(job) {
|
|
75
|
-
return job.kind === "wakeup" || (!!job.check && !job.repeat);
|
|
93
|
+
return job.kind === "wakeup" || (!!job.check && !job.repeat && !job.watchCheckTimingOut);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// A repeating watch keeps polling on its schedule while a triggered delivery
|
|
97
|
+
// waits for a busy room, waits for a retry after a failed or /stop-killed run,
|
|
98
|
+
// or runs. New hits fold into that one delivery. Everything else parks on its
|
|
99
|
+
// retry timer alone (24 Sep: the event-bus watch went unpolled for 7.5 h).
|
|
100
|
+
function pollsWhilePending(job) {
|
|
101
|
+
return !!job?.check && !!job.repeat;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// The watch's trigger is latched and its delivery has not completed.
|
|
105
|
+
function deliveryPending(job) {
|
|
106
|
+
return pollsWhilePending(job)
|
|
107
|
+
&& job.watchTriggeredOccurrenceId === job.occurrenceId
|
|
108
|
+
&& ["deferred", "failed", "running"].includes(job.status);
|
|
76
109
|
}
|
|
77
110
|
|
|
78
111
|
function providerIsRunnable(status) {
|
|
@@ -110,6 +143,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
110
143
|
);
|
|
111
144
|
const retryDelayMs = Number.isFinite(dependencies.retryDelayMs) ? dependencies.retryDelayMs : RETRY_DELAY_MS;
|
|
112
145
|
const busyDelayMs = Number.isFinite(dependencies.busyDelayMs) ? dependencies.busyDelayMs : BUSY_DELAY_MS;
|
|
146
|
+
const busyNoticeAfterMs = Number.isFinite(dependencies.busyNoticeAfterMs) ? dependencies.busyNoticeAfterMs : BUSY_NOTICE_AFTER_MS;
|
|
113
147
|
const missedGraceMs = Number.isFinite(dependencies.missedGraceMs) ? dependencies.missedGraceMs : MISSED_WAKEUP_GRACE_MS;
|
|
114
148
|
const watchTimeoutMs = Number.isFinite(dependencies.watchTimeoutMs) ? dependencies.watchTimeoutMs : DEFAULT_WATCH_TIMEOUT_MS;
|
|
115
149
|
const occurrenceIdFactory = dependencies.occurrenceIdFactory || (() => uniqueId("occ"));
|
|
@@ -282,16 +316,37 @@ function createSchedulerService(dependencies = {}) {
|
|
|
282
316
|
return null;
|
|
283
317
|
}
|
|
284
318
|
|
|
319
|
+
// What a repeating watch's polls saw while its delivery waited.
|
|
320
|
+
function pendingPollNote(job) {
|
|
321
|
+
const hits = Number(job.watchExtraHits) || 0;
|
|
322
|
+
const lastHitAt = Number(job.watchLastHitAt) || 0;
|
|
323
|
+
const lastClearAt = Number(job.watchLastClearAt) || 0;
|
|
324
|
+
if (!hits && !lastClearAt) return null;
|
|
325
|
+
const at = (ms) => new Date(ms).toISOString();
|
|
326
|
+
const seen = [];
|
|
327
|
+
if (hits) seen.push(`the check passed ${hits} more time${hits === 1 ? "" : "s"}, the latest at ${at(lastHitAt)}`);
|
|
328
|
+
seen.push(lastClearAt > lastHitAt
|
|
329
|
+
? `its latest run, at ${at(lastClearAt)}, did not pass, so the condition may have cleared`
|
|
330
|
+
: "the condition was still true at the latest check");
|
|
331
|
+
return `[This watch kept polling while this delivery waited (queued at ${at(Number(job.watchTriggeredAt) || now())}): ${seen.join("; ")}.]`;
|
|
332
|
+
}
|
|
333
|
+
|
|
285
334
|
function wrappedPrompt(job, target) {
|
|
286
335
|
const firedAt = new Date(now()).toISOString();
|
|
287
336
|
const expired = !!(job.check && job.watchExpired);
|
|
337
|
+
const timingOut = !!(job.check && !expired && job.watchCheckTimingOut);
|
|
288
338
|
const header = job.check
|
|
289
|
-
? (expired ? "Watch expired" : "Watch triggered")
|
|
339
|
+
? (expired ? "Watch expired" : timingOut ? "Watch check timing out" : "Watch triggered")
|
|
290
340
|
: job.kind === "cron" ? "Scheduled cron" : "Scheduled wakeup";
|
|
291
341
|
const lines = [`[${header} "${job.label}" fired at ${firedAt}; occurrence ${job.occurrenceId}]`];
|
|
292
342
|
if (expired) {
|
|
293
343
|
lines.push("[This watch reached its timeout without its check ever passing — the watched condition did NOT happen. This is a deadline wake-up, not a trigger: investigate why the condition never became true, and arm a new watch if it is still worth waiting for.]");
|
|
294
344
|
}
|
|
345
|
+
if (timingOut) {
|
|
346
|
+
lines.push(`[This watch's check timed out ${WATCH_TIMEOUT_ALARM_AFTER} times in a row (each run is capped at ${WATCH_CHECK_TIMEOUT_MS / 1000} s), so the watched condition could not be read. That is itself a signal: find out why the check hangs. This is not a trigger, and the watch keeps polling.]`);
|
|
347
|
+
}
|
|
348
|
+
const polled = job.check ? pendingPollNote(job) : null;
|
|
349
|
+
if (polled) lines.push(polled);
|
|
295
350
|
if (target.fallbackFrom) {
|
|
296
351
|
lines.push(`[Archived provider-neutral brief: the originating ${target.fallbackFrom} provider is unavailable. Its native session is not being resumed; this explicitly configured fallback starts fresh.]`);
|
|
297
352
|
} else if (target.revertedFrom) {
|
|
@@ -311,14 +366,36 @@ function createSchedulerService(dependencies = {}) {
|
|
|
311
366
|
timers.delete(id);
|
|
312
367
|
}
|
|
313
368
|
|
|
369
|
+
// Drop only a job's delivery (retry) timer; a watch's poll schedule stays.
|
|
370
|
+
function clearDeliveryTimer(id) {
|
|
371
|
+
const timer = timers.get(id);
|
|
372
|
+
if (!timer?.timeout) return;
|
|
373
|
+
try { clearTimer(timer.timeout); } catch (_) {}
|
|
374
|
+
if (timer.stop) timers.set(id, { stop: timer.stop });
|
|
375
|
+
else timers.delete(id);
|
|
376
|
+
}
|
|
377
|
+
|
|
314
378
|
function armRetry(job, delay = null) {
|
|
315
|
-
unscheduleJob(job.id);
|
|
316
|
-
if (job
|
|
379
|
+
if (job.disabled || job.status === "dead-letter" || job.status === "succeeded") return unscheduleJob(job.id);
|
|
380
|
+
if (pollsWhilePending(job)) clearDeliveryTimer(job.id);
|
|
381
|
+
else unscheduleJob(job.id);
|
|
317
382
|
const wait = delay === null
|
|
318
383
|
? Math.max(0, Number(job.nextAttemptAt || now()) - now())
|
|
319
384
|
: Math.max(0, delay);
|
|
320
385
|
const timeout = setTimer(() => fireJob(job.id, { reason: "retry" }), wait);
|
|
321
|
-
timers.set(job.id, { timeout });
|
|
386
|
+
timers.set(job.id, { ...timers.get(job.id), timeout });
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// A jobs-store write that throws (a full disk, a torn journal) must not
|
|
390
|
+
// strand a queued delivery: the retry stays armed in memory and the next
|
|
391
|
+
// attempt re-reads the store. Such a throw used to return with no timer,
|
|
392
|
+
// so the delivery was never tried again (21 and 23 Sep: watch alarms that
|
|
393
|
+
// triggered during long turns were never delivered).
|
|
394
|
+
function keepRetrying(job, error, delay = retryDelayMs) {
|
|
395
|
+
const failure = stableFailure(error);
|
|
396
|
+
try { armRetry(job, delay); } catch (_) {}
|
|
397
|
+
if (job.check) logWatch(job, `delivery bookkeeping failed (${failure.code}) — retrying in ${Math.round(delay / 1000)} s`);
|
|
398
|
+
return { ok: false, status: "failed", error: failure };
|
|
322
399
|
}
|
|
323
400
|
|
|
324
401
|
function markFailure(job, failure, options = {}) {
|
|
@@ -345,14 +422,27 @@ function createSchedulerService(dependencies = {}) {
|
|
|
345
422
|
function markBusy(job) {
|
|
346
423
|
const latest = jobsStore.get(job.id) || job;
|
|
347
424
|
const busyDeferralCount = (latest.busyDeferralCount || 0) + 1;
|
|
425
|
+
const waited = now() - Number(latest.watchTriggeredAt);
|
|
426
|
+
const noticeDue = !!latest.check
|
|
427
|
+
&& latest.busyNoticeOccurrenceId !== latest.occurrenceId
|
|
428
|
+
&& Number.isFinite(waited) && waited >= busyNoticeAfterMs;
|
|
348
429
|
const patch = {
|
|
349
430
|
status: "deferred",
|
|
350
431
|
disabled: false,
|
|
351
432
|
busyDeferralCount,
|
|
352
433
|
nextAttemptAt: now() + busyDelayMs,
|
|
434
|
+
// At most once per occurrence: the marker is persisted with the deferral, before the send.
|
|
435
|
+
...(noticeDue ? { busyNoticeOccurrenceId: latest.occurrenceId } : {}),
|
|
353
436
|
};
|
|
354
437
|
const updated = jobsStore.update(job.id, patch) || { ...latest, ...patch };
|
|
355
438
|
armRetry(updated, busyDelayMs);
|
|
439
|
+
if (noticeDue) {
|
|
440
|
+
const minutes = Math.floor(waited / 60000);
|
|
441
|
+
const what = updated.watchExpired ? "timed out" : updated.watchCheckTimingOut ? "raised a check-timeout alarm" : "triggered";
|
|
442
|
+
try {
|
|
443
|
+
Promise.resolve(send(`⏳ Watch "${updated.label}" ${what} ${minutes} min ago and is waiting for the current run to end. /stop ends that run so the watch can go.`)).catch(() => {});
|
|
444
|
+
} catch (_) { /* a notice only */ }
|
|
445
|
+
}
|
|
356
446
|
return { ok: false, deferred: true, status: updated.status, job: updated };
|
|
357
447
|
}
|
|
358
448
|
|
|
@@ -397,6 +487,10 @@ function createSchedulerService(dependencies = {}) {
|
|
|
397
487
|
watchTriggeredOccurrenceId: null,
|
|
398
488
|
watchTriggeredAt: null,
|
|
399
489
|
watchExpired: false,
|
|
490
|
+
watchCheckTimingOut: false,
|
|
491
|
+
watchExtraHits: 0,
|
|
492
|
+
watchLastHitAt: null,
|
|
493
|
+
watchLastClearAt: null,
|
|
400
494
|
advisoryOccurrenceId: null,
|
|
401
495
|
});
|
|
402
496
|
if (reset && timers.get(job.id)?.timeout) scheduleJob(reset);
|
|
@@ -421,6 +515,10 @@ function createSchedulerService(dependencies = {}) {
|
|
|
421
515
|
return { ok: false, status: job.status, skipped: true };
|
|
422
516
|
}
|
|
423
517
|
if (job.status === "running" && Number(job.nextAttemptAt || 0) > now()) {
|
|
518
|
+
// fireJob already turned away a fire in flight here, so this lease was
|
|
519
|
+
// left by an earlier process: look again when it lapses. Returning with
|
|
520
|
+
// no timer stranded the job until the next restart.
|
|
521
|
+
armRetry(job);
|
|
424
522
|
return { ok: false, status: "running", skipped: true, inFlight: true };
|
|
425
523
|
}
|
|
426
524
|
if (job.status === "succeeded" && isOneShot(job)) {
|
|
@@ -450,15 +548,28 @@ function createSchedulerService(dependencies = {}) {
|
|
|
450
548
|
// persisted before expiry existed (no expiresAt) keep polling forever.
|
|
451
549
|
const expiresAt = Number(job.expiresAt || 0);
|
|
452
550
|
if (!(expiresAt > 0 && now() >= expiresAt)) {
|
|
453
|
-
|
|
454
|
-
|
|
551
|
+
// Timeouts in a row; a completed check resets the count. Only the
|
|
552
|
+
// WATCH_TIMEOUT_ALARM_AFTER-th wakes the agent, so a check that stays
|
|
553
|
+
// hung alarms once rather than on every later poll.
|
|
554
|
+
const timedOut = !!check?.timedOut;
|
|
555
|
+
const previous = Number(job.watchTimeoutStreak) || 0;
|
|
556
|
+
const streak = timedOut ? previous + 1 : 0;
|
|
557
|
+
if (streak !== WATCH_TIMEOUT_ALARM_AFTER) {
|
|
558
|
+
if (streak !== previous) {
|
|
559
|
+
try { jobsStore.update(job.id, { watchTimeoutStreak: streak }); } catch (_) { /* best effort */ }
|
|
560
|
+
}
|
|
561
|
+
logWatch(job, timedOut ? `idle (check timed out, ${streak} in a row)` : `idle (check exit ${check ? check.code : "?"})`);
|
|
562
|
+
return { ok: false, status: "watch-idle", skipped: true };
|
|
563
|
+
}
|
|
564
|
+
job = { ...job, watchExpired: false, watchCheckTimingOut: true, watchTimeoutStreak: streak };
|
|
565
|
+
} else {
|
|
566
|
+
// Expiry ends the watch's window: one-shot removal even for --repeat
|
|
567
|
+
// (the woken agent re-arms if still wanted). Persisted so a retry of a
|
|
568
|
+
// failed expiry fire keeps the same semantics across restarts.
|
|
569
|
+
job = { ...job, repeat: false, watchExpired: true, watchCheckTimingOut: false };
|
|
455
570
|
}
|
|
456
|
-
// Expiry ends the watch's window: one-shot removal even for --repeat
|
|
457
|
-
// (the woken agent re-arms if still wanted). Persisted so a retry of a
|
|
458
|
-
// failed expiry fire keeps the same semantics across restarts.
|
|
459
|
-
job = { ...job, repeat: false, watchExpired: true };
|
|
460
571
|
} else {
|
|
461
|
-
job = { ...job, watchExpired: false };
|
|
572
|
+
job = { ...job, watchExpired: false, watchCheckTimingOut: false, watchTimeoutStreak: 0 };
|
|
462
573
|
}
|
|
463
574
|
// Latch the condition before admission or notification. Delivery retries
|
|
464
575
|
// and restarts must not run the check again or change an expiry's meaning.
|
|
@@ -468,6 +579,8 @@ function createSchedulerService(dependencies = {}) {
|
|
|
468
579
|
nextAttemptAt: now(),
|
|
469
580
|
repeat: job.repeat,
|
|
470
581
|
watchExpired: job.watchExpired,
|
|
582
|
+
watchCheckTimingOut: job.watchCheckTimingOut,
|
|
583
|
+
watchTimeoutStreak: Number(job.watchTimeoutStreak) || 0,
|
|
471
584
|
watchTriggeredOccurrenceId: job.occurrenceId,
|
|
472
585
|
watchTriggeredAt: now(),
|
|
473
586
|
});
|
|
@@ -476,13 +589,15 @@ function createSchedulerService(dependencies = {}) {
|
|
|
476
589
|
} catch (error) {
|
|
477
590
|
return { ok: false, status: "failed", error: stableFailure(error) };
|
|
478
591
|
}
|
|
479
|
-
logWatch(job, job.watchExpired ? "expired without firing — delivery queued"
|
|
592
|
+
logWatch(job, job.watchExpired ? "expired without firing — delivery queued"
|
|
593
|
+
: job.watchCheckTimingOut ? `check timed out ${WATCH_TIMEOUT_ALARM_AFTER} times in a row — delivery queued`
|
|
594
|
+
: "triggered (check passed) — delivery queued");
|
|
480
595
|
}
|
|
481
596
|
|
|
482
597
|
const adapter = resolveAdapter(job);
|
|
483
598
|
if (!adapter) {
|
|
484
599
|
try { return markFailure(job, { code: "SCHEDULED_ADAPTER_UNAVAILABLE", message: "Saved channel adapter is unavailable" }); }
|
|
485
|
-
catch (error) { return
|
|
600
|
+
catch (error) { return keepRetrying(job, error); }
|
|
486
601
|
}
|
|
487
602
|
const canonicalUserId = job.canonicalUserId || canonicalForChannel(adapter.type, job.channelId);
|
|
488
603
|
const userId = String(job.userId || (adapter.type === "telegram" ? job.channelId : canonicalUserId || job.channelId || ""));
|
|
@@ -512,7 +627,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
512
627
|
const state = stateForUser(canonicalUserId);
|
|
513
628
|
if (state.runningProcess || state.preparingRun || state.isCompacting) {
|
|
514
629
|
try { return markBusy(job); }
|
|
515
|
-
catch (error) { return
|
|
630
|
+
catch (error) { return keepRetrying(job, error, busyDelayMs); }
|
|
516
631
|
}
|
|
517
632
|
|
|
518
633
|
const target = executionTarget(job, state);
|
|
@@ -522,7 +637,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
522
637
|
code: "SCHEDULED_PROVIDER_UNAVAILABLE",
|
|
523
638
|
message: `Saved provider ${job.provider} is unavailable and no explicit fallback is configured`,
|
|
524
639
|
});
|
|
525
|
-
} catch (error) { return
|
|
640
|
+
} catch (error) { return keepRetrying(job, error); }
|
|
526
641
|
}
|
|
527
642
|
|
|
528
643
|
const project = projectDescriptor(job);
|
|
@@ -540,8 +655,14 @@ function createSchedulerService(dependencies = {}) {
|
|
|
540
655
|
scheduledJobId: job.id,
|
|
541
656
|
originRunId: job.originRunId || null,
|
|
542
657
|
};
|
|
543
|
-
// Admission is synchronous and precedes all notification delivery.
|
|
544
|
-
|
|
658
|
+
// Admission is synchronous and precedes all notification delivery. A
|
|
659
|
+
// refusal is a failed attempt, never a rejection that drops the job.
|
|
660
|
+
let runContext;
|
|
661
|
+
try { runContext = admit(prompt, project.dir, null, runOptions); }
|
|
662
|
+
catch (error) {
|
|
663
|
+
try { return markFailure(job, error); }
|
|
664
|
+
catch (persistError) { return keepRetrying(job, persistError); }
|
|
665
|
+
}
|
|
545
666
|
|
|
546
667
|
let running;
|
|
547
668
|
try {
|
|
@@ -556,11 +677,13 @@ function createSchedulerService(dependencies = {}) {
|
|
|
556
677
|
});
|
|
557
678
|
if (!running) throw new Error("scheduled attempt could not be persisted");
|
|
558
679
|
} catch (error) {
|
|
559
|
-
return
|
|
680
|
+
return keepRetrying(job, error);
|
|
560
681
|
}
|
|
561
682
|
|
|
562
683
|
const advisory = job.check
|
|
563
|
-
? (job.watchExpired ? `⏰ Watch "${job.label}" timed out without triggering — waking anyway`
|
|
684
|
+
? (job.watchExpired ? `⏰ Watch "${job.label}" timed out without triggering — waking anyway`
|
|
685
|
+
: job.watchCheckTimingOut ? `⚠️ Watch "${job.label}": its check timed out ${WATCH_TIMEOUT_ALARM_AFTER} times in a row`
|
|
686
|
+
: `🔔 Watch "${job.label}" triggered`)
|
|
564
687
|
: `Wakeup: ${job.label}`;
|
|
565
688
|
// Say so when the run is NOT on the provider this room is set to: an
|
|
566
689
|
// unexplained error from a provider the room never selected is worse than
|
|
@@ -582,16 +705,70 @@ function createSchedulerService(dependencies = {}) {
|
|
|
582
705
|
try { result = await runInProvider(prompt, runContext.cwd, null, { ...runOptions, runContext }); }
|
|
583
706
|
catch (error) {
|
|
584
707
|
try { return markFailure(running, error, { attemptAlreadyCounted: true }); }
|
|
585
|
-
catch (persistError) { return
|
|
708
|
+
catch (persistError) { return keepRetrying(running, persistError); }
|
|
586
709
|
}
|
|
587
710
|
if (!terminalRunSucceeded(result)) {
|
|
588
711
|
try { return markFailure(running, result, { attemptAlreadyCounted: true }); }
|
|
589
|
-
catch (error) { return {
|
|
712
|
+
catch (error) { return { ...keepRetrying(running, error), result }; }
|
|
590
713
|
}
|
|
591
714
|
return settleSuccess(running, result, target);
|
|
592
715
|
});
|
|
593
716
|
}
|
|
594
717
|
|
|
718
|
+
// Poll a repeating watch whose delivery is still pending. Nothing new is
|
|
719
|
+
// queued: a hit is counted into that delivery (its prompt says so), a
|
|
720
|
+
// completed miss is noted as a possible clear, a timeout is only logged.
|
|
721
|
+
async function pollPendingWatch(job) {
|
|
722
|
+
if (!watchesEnabled()) {
|
|
723
|
+
logWatch(job, "skipped (watches disabled globally)");
|
|
724
|
+
return { ok: false, status: "watch-disabled", skipped: true };
|
|
725
|
+
}
|
|
726
|
+
if (checksInFlight.has(job.id)) return { ok: false, status: "watch-checking", skipped: true };
|
|
727
|
+
checksInFlight.add(job.id);
|
|
728
|
+
let check;
|
|
729
|
+
try { check = await runWatchCheck(job.check, job); }
|
|
730
|
+
catch (_) { check = { code: 1, timedOut: false }; }
|
|
731
|
+
finally { checksInFlight.delete(job.id); }
|
|
732
|
+
try {
|
|
733
|
+
const latest = jobsStore.get(job.id);
|
|
734
|
+
// The delivery may have finished while the check ran; the result then
|
|
735
|
+
// belongs to no pending occurrence.
|
|
736
|
+
if (!latest || latest.occurrenceId !== job.occurrenceId || !deliveryPending(latest)) {
|
|
737
|
+
return { ok: false, status: "watch-idle", skipped: true };
|
|
738
|
+
}
|
|
739
|
+
if (check?.timedOut) {
|
|
740
|
+
logWatch(latest, "idle (check timed out) — delivery still pending");
|
|
741
|
+
return { ok: false, status: "watch-idle", skipped: true };
|
|
742
|
+
}
|
|
743
|
+
const hit = check?.code === 0;
|
|
744
|
+
const extraHits = (Number(latest.watchExtraHits) || 0) + (hit ? 1 : 0);
|
|
745
|
+
jobsStore.update(job.id, {
|
|
746
|
+
...(hit ? { watchExtraHits: extraHits, watchLastHitAt: now() } : { watchLastClearAt: now() }),
|
|
747
|
+
...(latest.watchTimeoutStreak ? { watchTimeoutStreak: 0 } : {}),
|
|
748
|
+
});
|
|
749
|
+
logWatch(latest, hit
|
|
750
|
+
? `triggered again (check passed) — folded into the pending delivery (${extraHits} more)`
|
|
751
|
+
: `idle (check exit ${check ? check.code : "?"}) — delivery still pending`);
|
|
752
|
+
return { ok: false, status: hit ? "watch-folded" : "watch-idle", skipped: true, extraHits };
|
|
753
|
+
} catch (error) {
|
|
754
|
+
return { ok: false, status: "failed", error: stableFailure(error) };
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
// A cron's schedule. A pending job fires on each tick; a repeating watch
|
|
759
|
+
// whose delivery is still pending is only polled.
|
|
760
|
+
function armCron(job) {
|
|
761
|
+
if (!validateCron(job.schedule)) return false;
|
|
762
|
+
const task = scheduleCron(job.schedule, () => {
|
|
763
|
+
const current = jobsStore.get(job.id);
|
|
764
|
+
if (!current || current.disabled) return;
|
|
765
|
+
if (current.status === "pending") fireJob(current.id, { reason: "cron" });
|
|
766
|
+
else if (deliveryPending(current)) pollPendingWatch(current);
|
|
767
|
+
});
|
|
768
|
+
timers.set(job.id, { stop: () => task.stop?.() });
|
|
769
|
+
return true;
|
|
770
|
+
}
|
|
771
|
+
|
|
595
772
|
function scheduleJob(job) {
|
|
596
773
|
unscheduleJob(job.id);
|
|
597
774
|
if (!job || job.disabled || ["archived", "dead-letter"].includes(job.status)) return false;
|
|
@@ -600,6 +777,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
600
777
|
return false;
|
|
601
778
|
}
|
|
602
779
|
if (["failed", "deferred", "running"].includes(job.status)) {
|
|
780
|
+
if (pollsWhilePending(job)) armCron(job);
|
|
603
781
|
armRetry(job, Math.max(0, Number(job.nextAttemptAt || now()) - now()));
|
|
604
782
|
return true;
|
|
605
783
|
}
|
|
@@ -617,20 +795,20 @@ function createSchedulerService(dependencies = {}) {
|
|
|
617
795
|
} catch (_) {}
|
|
618
796
|
return false;
|
|
619
797
|
}
|
|
620
|
-
const
|
|
798
|
+
const wait = Math.max(0, fireAt - now());
|
|
799
|
+
// Too far out for one timer: sleep a chunk, then re-plan from the store.
|
|
800
|
+
const timeout = wait > MAX_TIMER_DELAY_MS
|
|
801
|
+
? setTimer(() => {
|
|
802
|
+
try {
|
|
803
|
+
const current = jobsStore.get(job.id);
|
|
804
|
+
if (current) scheduleJob(current);
|
|
805
|
+
} catch (_) { /* init() re-plans on the next boot */ }
|
|
806
|
+
}, MAX_TIMER_DELAY_MS)
|
|
807
|
+
: setTimer(() => fireJob(job.id, { reason: fireAt < now() ? "missed" : "timer" }), wait);
|
|
621
808
|
timers.set(job.id, { timeout });
|
|
622
809
|
return true;
|
|
623
810
|
}
|
|
624
|
-
if (job.kind === "cron")
|
|
625
|
-
if (!validateCron(job.schedule)) return false;
|
|
626
|
-
const task = scheduleCron(job.schedule, () => {
|
|
627
|
-
const current = jobsStore.get(job.id);
|
|
628
|
-
if (!current || current.disabled || current.status !== "pending") return;
|
|
629
|
-
fireJob(current.id, { reason: "cron" });
|
|
630
|
-
});
|
|
631
|
-
timers.set(job.id, { stop: () => task.stop?.() });
|
|
632
|
-
return true;
|
|
633
|
-
}
|
|
811
|
+
if (job.kind === "cron") return armCron(job);
|
|
634
812
|
return false;
|
|
635
813
|
}
|
|
636
814
|
|
|
@@ -790,6 +968,9 @@ module.exports = {
|
|
|
790
968
|
RETRY_DELAY_MS,
|
|
791
969
|
BUSY_DELAY_MS,
|
|
792
970
|
DEFAULT_WATCH_TIMEOUT_MS,
|
|
971
|
+
WATCH_TIMEOUT_ALARM_AFTER,
|
|
972
|
+
MAX_TIMER_DELAY_MS,
|
|
973
|
+
MAX_WAKEUP_HORIZON_MS,
|
|
793
974
|
createSchedulerService,
|
|
794
975
|
parseFallbacks,
|
|
795
976
|
terminalRunSucceeded,
|
package/core/state.js
CHANGED
|
@@ -796,6 +796,16 @@ function acquireRunLock(state = currentState()) {
|
|
|
796
796
|
return true;
|
|
797
797
|
}
|
|
798
798
|
|
|
799
|
+
// /stop arms state.cancelRequested with the id of the run it stops (true only
|
|
800
|
+
// when no run context is known), and a run acts on the flag only when it names
|
|
801
|
+
// that run. A bare boolean let a stop outlive its run and cancel the next one.
|
|
802
|
+
function cancelFlagFor(state = currentState()) {
|
|
803
|
+
return state.activeRunContext?.runId || true;
|
|
804
|
+
}
|
|
805
|
+
function cancelTargets(flag, runContext) {
|
|
806
|
+
return flag === true || (!!flag && flag === runContext?.runId);
|
|
807
|
+
}
|
|
808
|
+
|
|
799
809
|
// True when ANY user has a turn running, admitted-and-preparing, compacting, or
|
|
800
810
|
// waiting in its queue. Same busy predicate as acquireRunLock, but across every
|
|
801
811
|
// user state rather than one — the Telegram poll-wedge backstop consults this
|
|
@@ -1535,6 +1545,8 @@ module.exports = {
|
|
|
1535
1545
|
resetSessionUsage,
|
|
1536
1546
|
resetSettings,
|
|
1537
1547
|
acquireRunLock,
|
|
1548
|
+
cancelFlagFor,
|
|
1549
|
+
cancelTargets,
|
|
1538
1550
|
anyTurnActive,
|
|
1539
1551
|
saveState,
|
|
1540
1552
|
loadSessions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.4.
|
|
3
|
+
"version": "3.4.24",
|
|
4
4
|
"description": "An always-on, provider-agnostic coding-agent harness for Claude Code and OpenAI Codex via chat",
|
|
5
5
|
"main": "bot.js",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"setup": "node setup.js",
|
|
12
12
|
"start": "node bot.js",
|
|
13
13
|
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-turn-observer.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-refusal-recovery.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-upgrade-path.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-kazee-media-caption.js && node test-kazee-context-media.js && node test-kazee-typing-join.js && node test-kazee-inbound-dedup.js && node test-typing-phase-push.js && node test-socket-reconnect.js && node test-speech-loopback.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js && node test-boot-health.js && node test-lexical-floor.js && node test-safe-mode.js && node test-logout.js && node test-kazee-call-audio.js && node test-kazee-call-janus.js && node test-kazee-call-webrtc.js && node test-kazee-call-speech.js && node test-kazee-call-manager.js && node test-call-speech.js && node test-kazee-call-session.js",
|
|
14
|
-
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js"
|
|
14
|
+
"test": "OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node -e \"require('./vault'); console.log('OK')\" && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-runner-watchdog-static.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-usage-accounting.js && node test-codex-rollout.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-engine.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-discoverer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-query.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-mandate-hygiene.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-external-rule-scopes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-cards.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-identity-prune.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-message-id.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-kazee-group-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-people-owner-seed.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-delivery-contract.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-run-lock.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-typing-heartbeat.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stream-preview-purpose.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-say-loopback.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-union.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-subst-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-section-budgets.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-dedup-adjudicator.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-prospective.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-regressions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cost-report.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-context.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-mining.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-certification.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-apprenticeship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-attest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-verify-contact.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-loopback-owner-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-intro-adoption.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-grants.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-room-provider-settings.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-store-durability.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
|
@@ -289,6 +289,98 @@ async function codexRestrictionProbe() {
|
|
|
289
289
|
assert.deepStrictEqual(claudeInvocation.args.slice(claudeInvocation.args.indexOf("--permission-mode"), claudeInvocation.args.indexOf("--permission-mode") + 2), ["--permission-mode", "plan"]);
|
|
290
290
|
}
|
|
291
291
|
|
|
292
|
+
// 21 Sep (task 76ecr7): a watch passed while a long turn ran, the turn ended in
|
|
293
|
+
// a compaction, and the alarm looked lost. The queued delivery waits out the
|
|
294
|
+
// turn and the compaction, then runs once at the next idle moment on the
|
|
295
|
+
// compacted session; after a later session switch it still runs, on its own
|
|
296
|
+
// branch, without taking over the live session.
|
|
297
|
+
async function deferredWatchAcrossCompactionProbe() {
|
|
298
|
+
const { createSchedulerService } = require("./core/scheduler");
|
|
299
|
+
for (const switchAfter of [false, true]) {
|
|
300
|
+
resetFixture("claude");
|
|
301
|
+
const records = new Map([["watch-long-turn", {
|
|
302
|
+
id: "watch-long-turn",
|
|
303
|
+
kind: "cron",
|
|
304
|
+
schedule: "* * * * *",
|
|
305
|
+
check: "fixture-check",
|
|
306
|
+
repeat: false,
|
|
307
|
+
expiresAt: Date.now() + 60 * 60 * 1000,
|
|
308
|
+
adapter: "telegram-fixture",
|
|
309
|
+
adapterType: "telegram",
|
|
310
|
+
channelId: "fixture",
|
|
311
|
+
canonicalUserId: state.userId,
|
|
312
|
+
provider: "claude",
|
|
313
|
+
providerSettings: {},
|
|
314
|
+
project: "alpha",
|
|
315
|
+
projectDir: alphaDir,
|
|
316
|
+
sessionId: "old-claude",
|
|
317
|
+
prompt: "the SDK tag pipeline finished",
|
|
318
|
+
label: "SDK pipeline",
|
|
319
|
+
occurrenceId: "occ-long-turn",
|
|
320
|
+
status: "pending",
|
|
321
|
+
attemptCount: 0,
|
|
322
|
+
maxAttempts: 3,
|
|
323
|
+
disabled: false,
|
|
324
|
+
}]]);
|
|
325
|
+
const jobsStore = {
|
|
326
|
+
get: (id) => (records.has(id) ? structuredClone(records.get(id)) : null),
|
|
327
|
+
update(id, patch) {
|
|
328
|
+
if (!records.has(id)) return null;
|
|
329
|
+
records.set(id, { ...records.get(id), ...structuredClone(patch) });
|
|
330
|
+
return structuredClone(records.get(id));
|
|
331
|
+
},
|
|
332
|
+
remove(id) { const value = records.get(id) || null; records.delete(id); return value; },
|
|
333
|
+
listAll: () => [...records.values()].map((job) => structuredClone(job)),
|
|
334
|
+
};
|
|
335
|
+
const timers = [];
|
|
336
|
+
const runs = [];
|
|
337
|
+
const scheduler = createSchedulerService({
|
|
338
|
+
jobsStore,
|
|
339
|
+
adapters: [{ id: "telegram-fixture", type: "telegram" }],
|
|
340
|
+
workspace,
|
|
341
|
+
getUserState: () => state,
|
|
342
|
+
runInChat: async (_chat, fn) => fn(),
|
|
343
|
+
send: async () => ({ ok: true }),
|
|
344
|
+
setTimer: (fn, delay) => { timers.push({ fn, delay }); return timers.length; },
|
|
345
|
+
clearTimer: () => {},
|
|
346
|
+
scheduleCron: () => ({ stop() {} }),
|
|
347
|
+
providerStatus: () => ({ availability: { state: "available" }, auth: { state: "authenticated" } }),
|
|
348
|
+
fallbackProviders: [],
|
|
349
|
+
runWatchCheck: async () => ({ code: 0 }),
|
|
350
|
+
admitRunContext: (_prompt, cwd, _replyTo, opts) => ({ cwd, ...opts }),
|
|
351
|
+
runAgent: async (_prompt, _cwd, _replyTo, opts) => {
|
|
352
|
+
runs.push(opts);
|
|
353
|
+
return { ok: true, status: "succeeded", persistence: { transcript: { ok: true } }, delivery: { ok: true } };
|
|
354
|
+
},
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
state.runningProcess = { pid: 4242 };
|
|
358
|
+
assert.strictEqual((await scheduler.fireJob("watch-long-turn")).deferred, true, "a watch that passes mid-turn is queued");
|
|
359
|
+
for (let i = 0; i < 3; i++) await timers.at(-1).fn();
|
|
360
|
+
state.runningProcess = null;
|
|
361
|
+
let calls = 0;
|
|
362
|
+
const compaction = serviceWith(async () => {
|
|
363
|
+
calls += 1;
|
|
364
|
+
if (calls === 1) {
|
|
365
|
+
assert.strictEqual(state.isCompacting, true);
|
|
366
|
+
assert.strictEqual((await timers.at(-1).fn()).deferred, true, "the delivery waits out the compaction");
|
|
367
|
+
return result({ sessionId: "old-claude" });
|
|
368
|
+
}
|
|
369
|
+
return result({ sessionId: "compacted-claude" });
|
|
370
|
+
});
|
|
371
|
+
assert.strictEqual((await compaction.compact(alphaDir, { operationId: `compact-watch-${switchAfter}` })).compacted, true);
|
|
372
|
+
assert.strictEqual(runs.length, 0);
|
|
373
|
+
assert.ok(records.get("watch-long-turn"), "the queued delivery survives the compaction");
|
|
374
|
+
if (switchAfter) stateApi.setProviderSession(state, "claude", "alpha", "fresh-claude-after-new");
|
|
375
|
+
|
|
376
|
+
assert.strictEqual((await timers.at(-1).fn()).ok, true, "the next idle moment delivers it");
|
|
377
|
+
assert.strictEqual(runs.length, 1, "exactly once");
|
|
378
|
+
assert.strictEqual(runs[0].resumeSessionId, "compacted-claude", "it resumes the compacted session, not the pre-compaction one");
|
|
379
|
+
assert.strictEqual(runs[0].attachSession, !switchAfter, "it never takes over a session the user switched to");
|
|
380
|
+
assert.strictEqual(records.has("watch-long-turn"), false, "a delivered one-shot watch is consumed, not left listed");
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
292
384
|
function duplicateCommitProbe() {
|
|
293
385
|
resetFixture("claude");
|
|
294
386
|
const transaction = {
|
|
@@ -364,6 +456,7 @@ function restartRecoveryProbe() {
|
|
|
364
456
|
await immutableSuccessProbe();
|
|
365
457
|
await failureProbe();
|
|
366
458
|
await codexRestrictionProbe();
|
|
459
|
+
await deferredWatchAcrossCompactionProbe();
|
|
367
460
|
duplicateCommitProbe();
|
|
368
461
|
restartRecoveryProbe();
|
|
369
462
|
console.log("provider compaction OK");
|