@inetafrica/open-claudia 3.4.22 → 3.4.23
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 +7 -0
- package/core/handlers.js +26 -6
- package/core/runner.js +38 -8
- package/core/scheduler.js +19 -0
- package/core/state.js +12 -0
- package/package.json +2 -2
- package/test-provider-language.js +7 -1
- package/test-provider-scheduler.js +41 -1
- package/test-run-lock.js +31 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.4.23
|
|
4
|
+
|
|
5
|
+
- Watch, wakeup and cron turns now stream their progress (interim narration, and thinking under `/verbose`) like chat turns. The live preview was gated to chat turns only, so a watch-woken turn showed the bell notice and "thinking…" and then nothing until its final answer, however long it worked.
|
|
6
|
+
- `/stop` no longer cancels the messages you send after it. It released the room the moment it killed the run, before that run had unwound, so a message sent in those seconds started, met the stop flag at its pre-spawn checkpoint and was cancelled "before the provider started" — three messages lost after one `/stop` on 24 Sep. The room now stays busy until the stopped run's own unwind releases it and drains the queue (a 30 s backstop covers a child that never reports its exit), and the stop flag names the run it stops, so it can never cancel another.
|
|
7
|
+
- "Queued." now says what the message is waiting on once the wait passes two minutes, e.g. `Queued behind the watch "Phone alarm" (running 42 min). /stop cancels it.`
|
|
8
|
+
- A watch that has triggered but waited 15 minutes behind a busy room posts one plain line per occurrence, so an alarm is never hidden for hours (24 Sep: 2 h and 7.5 h, with nothing in the chat).
|
|
9
|
+
|
|
3
10
|
## 3.4.22
|
|
4
11
|
|
|
5
12
|
Supersedes 3.4.21, which was tagged but never published: its build failed because `test-upgrade-path.js` was wired into the test chain without being committed. Nothing shipped from that tag.
|
package/core/handlers.js
CHANGED
|
@@ -20,7 +20,7 @@ const {
|
|
|
20
20
|
getProviderSettings, resolveProviderSettings,
|
|
21
21
|
getProjectSessions, getLastProjectSession, userOwnsProviderSession,
|
|
22
22
|
createSessionCallbackToken, linkIdentity,
|
|
23
|
-
roomSettingsFor, setRoomSetting,
|
|
23
|
+
roomSettingsFor, setRoomSetting, cancelFlagFor,
|
|
24
24
|
} = require("./state");
|
|
25
25
|
const { canonicalForChannel, canonicalForTelegram, normalizeCanonicalUserId, channelKey, identities, isConfiguredOwnerChannel, removeIdentityMapping } = require("./identity");
|
|
26
26
|
const { isChatAuthorized, isChatOwner, recordPendingAuthRequest, authRequestLabel, hasOwner, bootstrapOwner } = require("./access");
|
|
@@ -1642,6 +1642,17 @@ register({
|
|
|
1642
1642
|
},
|
|
1643
1643
|
});
|
|
1644
1644
|
|
|
1645
|
+
// How long /stop waits for a stopped child to report its exit before it
|
|
1646
|
+
// releases the room anyway (SIGKILL follows SIGTERM after 3 s).
|
|
1647
|
+
const STOP_RELEASE_MS = 30 * 1000;
|
|
1648
|
+
function releaseStuckStop(state, proc, stopped) {
|
|
1649
|
+
if (state.runningProcess !== proc) return;
|
|
1650
|
+
if (proc.exitCode != null || proc.signalCode != null) return; // exited: its unwind releases the room
|
|
1651
|
+
console.warn(`[stop] child ${proc.pid} never reported its exit; releasing the room`);
|
|
1652
|
+
state.runningProcess = null;
|
|
1653
|
+
if (state.cancelRequested === stopped) state.cancelRequested = false;
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1645
1656
|
register({
|
|
1646
1657
|
name: "stop", description: "Cancel running task",
|
|
1647
1658
|
handler: async (env) => {
|
|
@@ -1655,14 +1666,23 @@ register({
|
|
|
1655
1666
|
return n ? ` ${n} queued message${n === 1 ? "" : "s"} will run next.` : "";
|
|
1656
1667
|
};
|
|
1657
1668
|
if (state.runningProcess) {
|
|
1658
|
-
const
|
|
1669
|
+
const proc = state.runningProcess;
|
|
1670
|
+
const pid = proc.pid;
|
|
1659
1671
|
const { killProcessTree } = require("./process-tree");
|
|
1660
1672
|
// Quiet unwind: deliverForegroundResult consumes this flag to suppress
|
|
1661
|
-
// the "run failed" diagnostic the SIGTERM exit would otherwise emit.
|
|
1662
|
-
|
|
1673
|
+
// the "run failed" diagnostic the SIGTERM exit would otherwise emit. It
|
|
1674
|
+
// names the run it stops, so it can never cancel a different one.
|
|
1675
|
+
const stopped = cancelFlagFor(state);
|
|
1676
|
+
state.cancelRequested = stopped;
|
|
1663
1677
|
killProcessTree(pid, "SIGTERM");
|
|
1664
1678
|
setTimeout(() => killProcessTree(pid, "SIGKILL"), 3000);
|
|
1665
|
-
|
|
1679
|
+
// The room stays busy until the stopped run has unwound: its own
|
|
1680
|
+
// finally releases the lock, clears this flag and drains the queue.
|
|
1681
|
+
// Releasing it here let a message sent in those seconds start, meet the
|
|
1682
|
+
// flag and be cancelled "before the provider started" (24 Sep: three
|
|
1683
|
+
// messages lost after one /stop). Backstop: a child that never reports
|
|
1684
|
+
// its exit must not pin the room.
|
|
1685
|
+
setTimeout(() => releaseStuckStop(state, proc, stopped), STOP_RELEASE_MS).unref?.();
|
|
1666
1686
|
// Stop pending preview flushes but keep statusMessageId/Channel: the
|
|
1667
1687
|
// killed run's delivery unwind edits the preview to drop its footer.
|
|
1668
1688
|
if (state.streamInterval) { clearTimeout(state.streamInterval); state.streamInterval = null; }
|
|
@@ -1675,7 +1695,7 @@ register({
|
|
|
1675
1695
|
} else if (state.preparingRun) {
|
|
1676
1696
|
// Turn is mid-recall/prompt-build — no process to kill yet. Flag it so
|
|
1677
1697
|
// the runner's pre-spawn shouldAbort checkpoint bails before spawning.
|
|
1678
|
-
state.cancelRequested =
|
|
1698
|
+
state.cancelRequested = cancelFlagFor(state);
|
|
1679
1699
|
if (state.typingHeartbeat) { clearInterval(state.typingHeartbeat); state.typingHeartbeat = null; state.thinkingPhase = false; state.typingTick = null; }
|
|
1680
1700
|
try { currentAdapter()?.typingStop?.(currentChannelId()); } catch (_) {}
|
|
1681
1701
|
await sideCancellation;
|
package/core/runner.js
CHANGED
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
} = require("./config");
|
|
14
14
|
const {
|
|
15
15
|
currentState, saveState, recordSession,
|
|
16
|
-
resetSessionUsage, acquireRunLock, getActiveProvider, getProjectKey,
|
|
16
|
+
resetSessionUsage, acquireRunLock, cancelTargets, getActiveProvider, getProjectKey,
|
|
17
17
|
getProviderSession, setProviderSession, clearProviderSession, quarantineProviderSession, getUsageForSession, commitProviderCompaction,
|
|
18
18
|
resolveProviderSettings,
|
|
19
19
|
} = require("./state");
|
|
@@ -1698,9 +1698,10 @@ async function deliverForegroundResult(result, runContext, state = currentState(
|
|
|
1698
1698
|
// /stop unwind: the SIGTERM (or pre-spawn abort) that follows a user cancel
|
|
1699
1699
|
// is not a failure worth announcing — /stop already replied "Cancelled.".
|
|
1700
1700
|
// The flag is consumed here (and re-cleared on the run's unwind) so it can
|
|
1701
|
-
// never suppress a later run's genuine failure.
|
|
1702
|
-
|
|
1703
|
-
|
|
1701
|
+
// never suppress a later run's genuine failure; it names the run it stopped.
|
|
1702
|
+
const stoppedHere = cancelTargets(state.cancelRequested, runContext);
|
|
1703
|
+
if (!result.ok && (stoppedHere || result.error?.code === "RUN_CANCELLED")) {
|
|
1704
|
+
if (stoppedHere) state.cancelRequested = false;
|
|
1704
1705
|
return { ok: true, suppressed: "cancelled" };
|
|
1705
1706
|
}
|
|
1706
1707
|
const diagnostic = providerRunDiagnostic(result, runContext);
|
|
@@ -1871,6 +1872,18 @@ function settleImmediateEffects(result, runContext, { state, store, capture }) {
|
|
|
1871
1872
|
const STREAM_PREVIEW_THROTTLE_MS = 2500;
|
|
1872
1873
|
const STREAM_PREVIEW_FOOTER = "\n\n⏳ working — /stop to cancel";
|
|
1873
1874
|
|
|
1875
|
+
// Runs whose interim narration (and, under /verbose, thinking) streams into
|
|
1876
|
+
// the chat: the person's own turns, and scheduled wake-ups (watches, wakeups,
|
|
1877
|
+
// crons). A scheduled run answers in the same room and settles through
|
|
1878
|
+
// deliverForegroundResult, which cleans the preview up exactly as for a
|
|
1879
|
+
// foreground turn; without it a watch-woken turn showed only "thinking..."
|
|
1880
|
+
// until the final answer. Background runs (dream, review, recall, learn,
|
|
1881
|
+
// compaction, subagents, side chats, silent runs) stay silent.
|
|
1882
|
+
const STREAM_PREVIEW_PURPOSES = new Set(["foreground", "scheduled"]);
|
|
1883
|
+
function streamPreviewAllowed(runContext) {
|
|
1884
|
+
return STREAM_PREVIEW_PURPOSES.has(runContext?.purpose);
|
|
1885
|
+
}
|
|
1886
|
+
|
|
1874
1887
|
// Edited-in-place interim preview: each completed text block the provider
|
|
1875
1888
|
// emits mid-run is appended to one throttled status message, so the user sees
|
|
1876
1889
|
// the narration as it happens instead of silence. finalizeStreamPreview
|
|
@@ -1911,7 +1924,7 @@ function createStreamPreview({ state, store }) {
|
|
|
1911
1924
|
};
|
|
1912
1925
|
|
|
1913
1926
|
const appendChunk = (chunk, runContext) => {
|
|
1914
|
-
if (!enabled || runContext
|
|
1927
|
+
if (!enabled || !streamPreviewAllowed(runContext) || state.cancelRequested) return;
|
|
1915
1928
|
state.streamBuffer = state.streamBuffer ? `${state.streamBuffer}\n\n${chunk}` : chunk;
|
|
1916
1929
|
if (state.streamBuffer.length > 20000) state.streamBuffer = state.streamBuffer.slice(-16000);
|
|
1917
1930
|
if (!state.streamInterval) state.streamInterval = setTimeout(flush, STREAM_PREVIEW_THROTTLE_MS);
|
|
@@ -1983,7 +1996,7 @@ function createForegroundService({ state, store, stopTyping, capture, ownsAdmiss
|
|
|
1983
1996
|
},
|
|
1984
1997
|
shouldAbort(runContext, { capture: isCapture } = {}) {
|
|
1985
1998
|
if (isCapture || !ownsAdmissionLock) return false;
|
|
1986
|
-
if (!state.cancelRequested) return false;
|
|
1999
|
+
if (!cancelTargets(state.cancelRequested, runContext)) return false;
|
|
1987
2000
|
state.cancelRequested = false;
|
|
1988
2001
|
return true;
|
|
1989
2002
|
},
|
|
@@ -2197,7 +2210,7 @@ async function executeAdmittedRun(runContext, state, store, capture, ownsAdmissi
|
|
|
2197
2210
|
// A cancel flag that survived this run (e.g. /stop raced a run that
|
|
2198
2211
|
// finished successfully anyway) must not leak into the next turn, where
|
|
2199
2212
|
// it would falsely abort a queued run or mute a genuine failure.
|
|
2200
|
-
if (ownsAdmissionLock) state.cancelRequested = false;
|
|
2213
|
+
if (ownsAdmissionLock && cancelTargets(state.cancelRequested, runContext)) state.cancelRequested = false;
|
|
2201
2214
|
if (tracksMainRun && state.activeRunContext === runContext) state.activeRunContext = null;
|
|
2202
2215
|
stopTyping();
|
|
2203
2216
|
}
|
|
@@ -2265,6 +2278,21 @@ function admitRunContext(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
2265
2278
|
});
|
|
2266
2279
|
}
|
|
2267
2280
|
|
|
2281
|
+
// What a message queued behind a long run is waiting for. A short wait is just
|
|
2282
|
+
// "Queued."; after two minutes it says what is running and how to stop it, so a
|
|
2283
|
+
// busy room never looks like a dead one (24 Sep: an hour behind a watch run).
|
|
2284
|
+
const QUEUED_DETAIL_AFTER_MS = 2 * 60 * 1000;
|
|
2285
|
+
function queuedNotice(activeRunContext, at = Date.now()) {
|
|
2286
|
+
const since = Number(activeRunContext?.admittedAt);
|
|
2287
|
+
if (!Number.isFinite(since) || at - since < QUEUED_DETAIL_AFTER_MS) return "Queued.";
|
|
2288
|
+
const minutes = Math.floor((at - since) / 60000);
|
|
2289
|
+
const header = String(activeRunContext.userPrompt || "").match(/^\[(Watch triggered|Watch expired|Scheduled cron|Scheduled wakeup) "([^"]*)"/);
|
|
2290
|
+
const kind = header ? { "Watch triggered": "watch", "Watch expired": "watch", "Scheduled cron": "scheduled job", "Scheduled wakeup": "wakeup" }[header[1]] : null;
|
|
2291
|
+
const label = header && header[2].length > 60 ? `${header[2].slice(0, 59).trimEnd()}…` : header?.[2];
|
|
2292
|
+
const what = kind ? `the ${kind} "${label}"` : "an earlier message";
|
|
2293
|
+
return `Queued behind ${what} (running ${minutes} min). /stop cancels it.`;
|
|
2294
|
+
}
|
|
2295
|
+
|
|
2268
2296
|
function runAgent(prompt, cwd, replyToMsgId, opts = {}) {
|
|
2269
2297
|
const state = currentState();
|
|
2270
2298
|
const store = chatContext.getStore() || null;
|
|
@@ -2294,7 +2322,7 @@ function runAgent(prompt, cwd, replyToMsgId, opts = {}) {
|
|
|
2294
2322
|
// task — noise. On a live call, send() is the TTS line: "Queued." would be
|
|
2295
2323
|
// spoken to the caller. Both stay quiet and just deliver the real reply.
|
|
2296
2324
|
if (currentAdapter()?.type !== "spaces" && !statusChatterMuted()) {
|
|
2297
|
-
send(state.isCompacting ? "Compacting context, will pick this up next…" :
|
|
2325
|
+
send(state.isCompacting ? "Compacting context, will pick this up next…" : queuedNotice(state.activeRunContext), { replyTo: replyToMsgId }).catch(() => {});
|
|
2298
2326
|
}
|
|
2299
2327
|
return deferred.promise;
|
|
2300
2328
|
}
|
|
@@ -2389,7 +2417,9 @@ function runAgentSilent(prompt, cwd, label, opts = {}) {
|
|
|
2389
2417
|
}
|
|
2390
2418
|
|
|
2391
2419
|
module.exports = {
|
|
2420
|
+
queuedNotice,
|
|
2392
2421
|
admitRunContext,
|
|
2422
|
+
streamPreviewAllowed,
|
|
2393
2423
|
capturedTranscriptMetadata,
|
|
2394
2424
|
decorateForegroundInvocation,
|
|
2395
2425
|
createRunnerService,
|
package/core/scheduler.js
CHANGED
|
@@ -41,6 +41,11 @@ 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;
|
|
46
51
|
// Every watch carries a hard expiry so none can idle-poll forever: when the
|
|
@@ -110,6 +115,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
110
115
|
);
|
|
111
116
|
const retryDelayMs = Number.isFinite(dependencies.retryDelayMs) ? dependencies.retryDelayMs : RETRY_DELAY_MS;
|
|
112
117
|
const busyDelayMs = Number.isFinite(dependencies.busyDelayMs) ? dependencies.busyDelayMs : BUSY_DELAY_MS;
|
|
118
|
+
const busyNoticeAfterMs = Number.isFinite(dependencies.busyNoticeAfterMs) ? dependencies.busyNoticeAfterMs : BUSY_NOTICE_AFTER_MS;
|
|
113
119
|
const missedGraceMs = Number.isFinite(dependencies.missedGraceMs) ? dependencies.missedGraceMs : MISSED_WAKEUP_GRACE_MS;
|
|
114
120
|
const watchTimeoutMs = Number.isFinite(dependencies.watchTimeoutMs) ? dependencies.watchTimeoutMs : DEFAULT_WATCH_TIMEOUT_MS;
|
|
115
121
|
const occurrenceIdFactory = dependencies.occurrenceIdFactory || (() => uniqueId("occ"));
|
|
@@ -345,14 +351,27 @@ function createSchedulerService(dependencies = {}) {
|
|
|
345
351
|
function markBusy(job) {
|
|
346
352
|
const latest = jobsStore.get(job.id) || job;
|
|
347
353
|
const busyDeferralCount = (latest.busyDeferralCount || 0) + 1;
|
|
354
|
+
const waited = now() - Number(latest.watchTriggeredAt);
|
|
355
|
+
const noticeDue = !!latest.check
|
|
356
|
+
&& latest.busyNoticeOccurrenceId !== latest.occurrenceId
|
|
357
|
+
&& Number.isFinite(waited) && waited >= busyNoticeAfterMs;
|
|
348
358
|
const patch = {
|
|
349
359
|
status: "deferred",
|
|
350
360
|
disabled: false,
|
|
351
361
|
busyDeferralCount,
|
|
352
362
|
nextAttemptAt: now() + busyDelayMs,
|
|
363
|
+
// At most once per occurrence: the marker is persisted with the deferral, before the send.
|
|
364
|
+
...(noticeDue ? { busyNoticeOccurrenceId: latest.occurrenceId } : {}),
|
|
353
365
|
};
|
|
354
366
|
const updated = jobsStore.update(job.id, patch) || { ...latest, ...patch };
|
|
355
367
|
armRetry(updated, busyDelayMs);
|
|
368
|
+
if (noticeDue) {
|
|
369
|
+
const minutes = Math.floor(waited / 60000);
|
|
370
|
+
const what = updated.watchExpired ? "timed out" : "triggered";
|
|
371
|
+
try {
|
|
372
|
+
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(() => {});
|
|
373
|
+
} catch (_) { /* a notice only */ }
|
|
374
|
+
}
|
|
356
375
|
return { ok: false, deferred: true, status: updated.status, job: updated };
|
|
357
376
|
}
|
|
358
377
|
|
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.23",
|
|
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",
|
|
@@ -494,7 +494,13 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
494
494
|
// removing those would push recall, pack review and the enforcer onto opus.
|
|
495
495
|
// Removal is cosmetic by design: /model accepts a typed name without checking
|
|
496
496
|
// the list, so `/model claude-opus-4-8` remains a working rollback.
|
|
497
|
-
|
|
497
|
+
// 3.4.23 approved by Sumeet 2026-09-24 ("push", answering "Shall I install
|
|
498
|
+
// 3.4.23 once that is done?" after "can you fix this in open claudia"): watch and
|
|
499
|
+
// wakeup turns stream like chat turns; /stop keeps the room busy until the
|
|
500
|
+
// stopped run unwinds and its flag names that run, so it no longer cancels the
|
|
501
|
+
// next messages; "Queued." names what it waits on; a watch alarm stuck 15 min
|
|
502
|
+
// behind a busy room posts one line.
|
|
503
|
+
assert.strictEqual(pkg.version, "3.4.23", "release version must remain unchanged without explicit approval");
|
|
498
504
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
499
505
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
500
506
|
}
|
|
@@ -633,6 +633,43 @@ async function busyRestartAndMissedProbe(createSchedulerService) {
|
|
|
633
633
|
assert.strictEqual(missedTimers.length, 1, "a missed wakeup gets exactly one startup attempt");
|
|
634
634
|
}
|
|
635
635
|
|
|
636
|
+
async function busyWatchNoticeProbe(createSchedulerService) {
|
|
637
|
+
// A watch alarm waiting behind a busy room: silent for 15 minutes, then one
|
|
638
|
+
// plain line, never a second for the same occurrence; the next occurrence
|
|
639
|
+
// may say so again.
|
|
640
|
+
let clock = 1_720_000_000_000;
|
|
641
|
+
let checks = 0;
|
|
642
|
+
const h = schedulerHarness(createSchedulerService, [baseJob({
|
|
643
|
+
id: "alarm", kind: "cron", schedule: "*/15 * * * *", check: "fixture-check", repeat: true,
|
|
644
|
+
label: "Phone alarm", expiresAt: clock + 24 * 60 * 60 * 1000,
|
|
645
|
+
})], { now: () => clock, runWatchCheck: async () => { checks++; return { code: 0 }; } });
|
|
646
|
+
h.state.runningProcess = { pid: 1 };
|
|
647
|
+
await h.service.fireJob("alarm"); // triggers and latches
|
|
648
|
+
assert.strictEqual(checks, 1);
|
|
649
|
+
for (const minutes of [5, 10, 14]) {
|
|
650
|
+
clock = 1_720_000_000_000 + minutes * 60_000;
|
|
651
|
+
assert.strictEqual((await h.service.fireJob("alarm")).deferred, true);
|
|
652
|
+
}
|
|
653
|
+
assert.strictEqual(h.sends.length, 0, "no notice before 15 minutes");
|
|
654
|
+
clock = 1_720_000_000_000 + 16 * 60_000;
|
|
655
|
+
await h.service.fireJob("alarm");
|
|
656
|
+
assert.deepStrictEqual(h.sends, ['⏳ Watch "Phone alarm" triggered 16 min ago and is waiting for the current run to end. /stop ends that run so the watch can go.']);
|
|
657
|
+
for (const minutes of [20, 45, 120]) {
|
|
658
|
+
clock = 1_720_000_000_000 + minutes * 60_000;
|
|
659
|
+
await h.service.fireJob("alarm");
|
|
660
|
+
}
|
|
661
|
+
assert.strictEqual(h.sends.length, 1, "one notice per occurrence");
|
|
662
|
+
assert.strictEqual(h.jobsStore.get("alarm").busyNoticeOccurrenceId, "occ-wake-1", "the marker is persisted");
|
|
663
|
+
assert.strictEqual(checks, 1, "a latched alarm is not re-checked while it waits");
|
|
664
|
+
|
|
665
|
+
// A wakeup waiting behind the same run stays quiet.
|
|
666
|
+
const w = schedulerHarness(createSchedulerService, [baseJob({ id: "wake" })], { now: () => clock });
|
|
667
|
+
w.state.runningProcess = { pid: 1 };
|
|
668
|
+
clock += 60 * 60_000;
|
|
669
|
+
await w.service.fireJob("wake");
|
|
670
|
+
assert.strictEqual(w.sends.length, 0);
|
|
671
|
+
}
|
|
672
|
+
|
|
636
673
|
async function durablePendingProbe(createSchedulerService, jobsApi) {
|
|
637
674
|
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "oc-pending-restart-"));
|
|
638
675
|
let clock = 1_720_000_000_500;
|
|
@@ -659,7 +696,9 @@ async function durablePendingProbe(createSchedulerService, jobsApi) {
|
|
|
659
696
|
assert.strictEqual(store.get(kind).attemptCount, 0);
|
|
660
697
|
assert.strictEqual(store.get(kind).disabled, false);
|
|
661
698
|
assert.strictEqual(checks, kind === "wakeup" ? 0 : 1);
|
|
662
|
-
|
|
699
|
+
// A watch that waits says so once; a wakeup stays quiet.
|
|
700
|
+
assert.strictEqual(h.sends.length, kind === "wakeup" ? 0 : 1);
|
|
701
|
+
if (kind !== "wakeup") assert.match(h.sends[0], /^⏳ Watch "fixture wakeup" (triggered|timed out) 60 min ago/);
|
|
663
702
|
h.service.stopAll();
|
|
664
703
|
|
|
665
704
|
// Reopen the real on-disk store and construct a new scheduler, as boot does.
|
|
@@ -1043,6 +1082,7 @@ async function main() {
|
|
|
1043
1082
|
await detachedHistoricalBranchProbe(schedulerApi.createSchedulerService);
|
|
1044
1083
|
await busyRestartAndMissedProbe(schedulerApi.createSchedulerService);
|
|
1045
1084
|
await durablePendingProbe(schedulerApi.createSchedulerService, jobsApi);
|
|
1085
|
+
await busyWatchNoticeProbe(schedulerApi.createSchedulerService);
|
|
1046
1086
|
await unavailableAndFallbackProbe(schedulerApi.createSchedulerService);
|
|
1047
1087
|
await cronOccurrenceProbe(schedulerApi.createSchedulerService);
|
|
1048
1088
|
await watchGateProbe(schedulerApi.createSchedulerService);
|
package/test-run-lock.js
CHANGED
|
@@ -60,4 +60,35 @@ const { acquireRunLock } = require("./core/state");
|
|
|
60
60
|
assert.strictEqual(acquireRunLock(shared), true, "lock is re-acquirable once the turn fully releases");
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
// A /stop flag names the run it stops. Only that run (or a legacy bare `true`)
|
|
64
|
+
// may act on it: a stop that outlived its run cancelled the next message
|
|
65
|
+
// "before the provider started" (24 Sep, three messages lost after one /stop).
|
|
66
|
+
{
|
|
67
|
+
const { cancelFlagFor, cancelTargets } = require("./core/state");
|
|
68
|
+
assert.strictEqual(cancelFlagFor({ activeRunContext: { runId: "run-a" } }), "run-a", "the flag names the active run");
|
|
69
|
+
assert.strictEqual(cancelFlagFor({ activeRunContext: null }), true, "no known run: a bare flag");
|
|
70
|
+
assert.strictEqual(cancelTargets("run-a", { runId: "run-a" }), true, "the stopped run sees its stop");
|
|
71
|
+
assert.strictEqual(cancelTargets("run-a", { runId: "run-b" }), false, "another run never does");
|
|
72
|
+
assert.strictEqual(cancelTargets(true, { runId: "run-b" }), true, "a bare flag still stops whatever is preparing");
|
|
73
|
+
assert.strictEqual(cancelTargets(false, { runId: "run-a" }), false, "no stop, no cancel");
|
|
74
|
+
assert.strictEqual(cancelTargets("run-a", null), false, "no run context, no match");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// "Queued." says what the room is busy with once the wait is long enough to
|
|
78
|
+
// look like a dead bot.
|
|
79
|
+
{
|
|
80
|
+
const { queuedNotice } = require("./core/runner");
|
|
81
|
+
const t0 = 1_720_000_000_000;
|
|
82
|
+
assert.strictEqual(queuedNotice(null, t0), "Queued.");
|
|
83
|
+
assert.strictEqual(queuedNotice({ admittedAt: t0, userPrompt: "hi" }, t0 + 60_000), "Queued.", "a short wait stays terse");
|
|
84
|
+
assert.strictEqual(queuedNotice({ admittedAt: t0, userPrompt: "hi" }, t0 + 5 * 60_000),
|
|
85
|
+
"Queued behind an earlier message (running 5 min). /stop cancels it.");
|
|
86
|
+
assert.strictEqual(queuedNotice({ admittedAt: t0, userPrompt: '[Watch triggered "Phone alarm" fired at x; occurrence y]\nbody' }, t0 + 42 * 60_000),
|
|
87
|
+
'Queued behind the watch "Phone alarm" (running 42 min). /stop cancels it.');
|
|
88
|
+
assert.strictEqual(queuedNotice({ admittedAt: t0, userPrompt: '[Scheduled wakeup "Check CI" fired at x]' }, t0 + 3 * 60_000),
|
|
89
|
+
'Queued behind the wakeup "Check CI" (running 3 min). /stop cancels it.');
|
|
90
|
+
const long = queuedNotice({ admittedAt: t0, userPrompt: `[Watch triggered "${"x".repeat(90)}" fired at x]` }, t0 + 3 * 60_000);
|
|
91
|
+
assert.ok(long.includes(`"${"x".repeat(59)}…"`), "a long label is shortened to 60 characters");
|
|
92
|
+
}
|
|
93
|
+
|
|
63
94
|
console.log("run-lock OK");
|