@inetafrica/open-claudia 3.5.81 → 3.5.82
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 +21 -0
- package/bin/cards.js +41 -10
- package/bin/cli.js +1 -1
- package/core/cards.js +469 -32
- package/core/loopback.js +47 -20
- package/core/router.js +4 -0
- package/core/run-context.js +1 -0
- package/core/runner.js +8 -0
- package/core/scheduler.js +4 -1
- package/core/system-prompt.js +1 -1
- package/package.json +2 -2
- package/test-provider-language.js +3 -1
- package/test-provider-scheduler.js +15 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,26 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.5.82
|
|
4
|
+
|
|
5
|
+
3.5.81 plus the waiting-on-you list (Sumeet 14 Nov 15:18 EAT, card 6af83e0b: "Yes, release it").
|
|
6
|
+
|
|
7
|
+
"Waiting on you" for every thread (Sumeet's 7-day trial in his Mail thread, Nov 2026: asks waiting on him went 11 -> 4; "build it into Maya for every thread"). Built on the owner's decision queue in `core/cards.js`: one shared list per home chat, each item keeping its own thread and origin conversation.
|
|
8
|
+
|
|
9
|
+
- **The card says "Waiting on you · 1 of N"** (N = queued, asked and snoozed in that chat; "Waiting on you" when alone). My pick comes first, marked "(my pick)"; the server recommendation stays.
|
|
10
|
+
- **🗑 Drop** beside 🎧 Tell me more and ⏰ Later: the item is `dropped`, its card closes with "Dropped.", the origin conversation is told with a quiet wake, and the next item goes out at once.
|
|
11
|
+
- **`decide done <id> ["what happened"]`** for what he did himself or answered in words elsewhere: `done`, its card closes with "✅ Done: …", the next goes out. `decide cancel` stays "withdrawn, no longer needed" and now also sends the next. Loopback `decide-done`; node and Rust CLI.
|
|
12
|
+
- **Asked 3 days ago** (`OC_DECISION_STALE_MS`): the old card closes ("Moved to a fresh card.") and a fresh card ("Asked N days ago.") offers my pick plus "Something else" (a decision card needs 2-4 options), with Later and Drop, once per item, 09:00-18:00 local. "Something else" wakes the asking conversation to ask him what he wants instead. If the server refuses it, the fresh card carries every option. The overnight reminder skips an item re-asked that day.
|
|
13
|
+
- **One catch-up after 24 h of him quiet** in a home chat (`OC_DECISION_CATCHUP_MS`, 09:00-21:00 local): "While you were away, N things wait on you:" with up to five items, posted in the chat itself; never twice for one quiet stretch. His messages are noted from `core/router.js` into `decisions-meta.json` (`OC_DECISIONS_META_FILE`).
|
|
14
|
+
- **The morning walk** (`OC_DECISION_WALK_AT`, default 09:45; `OC_DECISION_WALK=off`): once a local day, each origin conversation with items waiting over 20 h gets one quiet wake to tick off (`decide done`), cancel or leave them.
|
|
15
|
+
- **Quiet wake-ups** (`core/scheduler.js`, `core/runner.js`): a job with `quiet: true` posts no "Wakeup:" line, and its reply of exactly `[quiet]` is not posted.
|
|
16
|
+
- **`decide list [--all]`**: every open item in queue order with position, status, age, home thread and title, then "N waiting on you"; `--all` adds the last 20 settled with their outcome.
|
|
17
|
+
- The per-turn open-decisions section names queued items by decision id and says: did it himself or answered in words -> `decide done`; no longer needed -> `decide cancel`; information only -> tell him once, never queue it. Its budget is 700 characters (was 600).
|
|
18
|
+
|
|
19
|
+
Tests: `test-waiting-on-you.js` (new); `test-kazee-cards.js`, `test-decision-upkeep.js`, `test-placeholder-reply.js`, `test-provider-scheduler.js` and `test-rust-cli-parity.js` updated.
|
|
20
|
+
|
|
21
|
+
- `open-claudia decide ... --due YYYY-MM-DD`: an item needed by a date goes ahead of older items once it is 3 days off (soonest first), and from 2 days off it takes the place of a card out without a date; that card closes with "Back on your list: something with a deadline goes first." The card says "Needed by Wed 18 Nov."; `decide list` (Node and Rust) shows "(due Wed 18 Nov)". Found by the Mail thread on 14 Nov: the LINX vote closing Wed 18 Nov sat 23rd in line.
|
|
22
|
+
- A title over 140 or an option label over 80 characters is shortened with "…" and a warning, instead of failing the whole call with a 400.
|
|
23
|
+
|
|
3
24
|
## 3.5.81
|
|
4
25
|
|
|
5
26
|
3.5.80 plus one change (Sumeet 13 Nov 18:03 EAT: personal bots may send Kazee Chat messages as their owner, as long as Chat shows the AI mark).
|
package/bin/cards.js
CHANGED
|
@@ -279,23 +279,53 @@ async function runCard(args) {
|
|
|
279
279
|
}
|
|
280
280
|
|
|
281
281
|
const DECIDE_USAGE = `Usage:
|
|
282
|
-
open-claudia decide "<question>" --option <id>="<label>" --option <id>="<label>" [--pick <id>] [--body "why it matters"] [--tag "Area"]
|
|
283
|
-
open-claudia decide list
|
|
282
|
+
open-claudia decide "<question>" --option <id>="<label>" --option <id>="<label>" [--pick <id>] [--body "why it matters"] [--tag "Area"] [--due YYYY-MM-DD]
|
|
283
|
+
open-claudia decide list [--all]
|
|
284
|
+
open-claudia decide done <id> ["what happened"]
|
|
284
285
|
open-claudia decide cancel <id>
|
|
285
|
-
Owner only. Decisions
|
|
286
|
+
Owner only. Decisions wait on you in your Kazee chat, one card at a time
|
|
287
|
+
("Waiting on you · 1 of N"), until you answer, snooze or drop them; the answer
|
|
286
288
|
wakes the conversation that queued it. "Later" snoozes it for 3 hours, "Tell me
|
|
287
|
-
more" asks this conversation for a short voice explainer
|
|
289
|
+
more" asks this conversation for a short voice explainer, "Drop" settles it
|
|
290
|
+
without doing it. done: you did it yourself or answered in words elsewhere.
|
|
291
|
+
--due: needed by that day; moves up within 3 days, and within 2 takes the
|
|
292
|
+
place of a card out without a date. cancel: withdrawn, no longer needed. list --all adds the last 20 settled.`;
|
|
293
|
+
|
|
294
|
+
/** "2026-11-18" -> "Wed 18 Nov". */
|
|
295
|
+
function dueLabel(ymd) {
|
|
296
|
+
if (!/^\d{4}-\d{2}-\d{2}$/.test(String(ymd || ""))) return "";
|
|
297
|
+
const [y, m, d] = String(ymd).split("-").map(Number);
|
|
298
|
+
if (m < 1 || m > 12) return "";
|
|
299
|
+
const t = new Date(Date.UTC(y, m - 1, d));
|
|
300
|
+
return `${["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][t.getUTCDay()]} ${d} ${["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"][m - 1]}`;
|
|
301
|
+
}
|
|
288
302
|
|
|
289
303
|
async function runDecide(args) {
|
|
290
304
|
const first = args[0];
|
|
291
305
|
try {
|
|
292
306
|
if (first === "list") {
|
|
293
|
-
const
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
307
|
+
const all = args.includes("--all");
|
|
308
|
+
const res = await postJson("decide-list", { all: all || undefined });
|
|
309
|
+
const items = res.decisions;
|
|
310
|
+
if (!Array.isArray(items)) throw new Error("the bot's answer has no list of decisions");
|
|
311
|
+
const lines = [];
|
|
312
|
+
for (const [i, d] of items.entries()) {
|
|
313
|
+
const until = d.status === "snoozed" && d.notBefore ? ` (until ${d.notBefore})` : "";
|
|
314
|
+
const due = typeof d.due === "string" && dueLabel(d.due) ? ` (due ${dueLabel(d.due)})` : "";
|
|
315
|
+
const extra = `${due}${until}`;
|
|
316
|
+
lines.push(`${i + 1}. ${d.id} ${d.status.padEnd(8)} ${d.age.padEnd(10)} ${d.thread.padEnd(8)} ${d.title}${extra}`);
|
|
317
|
+
}
|
|
318
|
+
lines.push(`${items.length} waiting on you`);
|
|
319
|
+
if (all && Array.isArray(res.settled) && res.settled.length) {
|
|
320
|
+
lines.push("Last settled:");
|
|
321
|
+
for (const d of res.settled) lines.push(`${d.id} ${d.status.padEnd(9)} ${d.title}${d.outcome ? ` -> ${d.outcome}` : ""}`);
|
|
298
322
|
}
|
|
323
|
+
for (const line of lines) console.log(line);
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (first === "done") {
|
|
327
|
+
const res = await postJson("decide-done", { id: args[1], note: args[2] });
|
|
328
|
+
console.log(`Done ${res.decision.id}: ${res.decision.title}`);
|
|
299
329
|
return;
|
|
300
330
|
}
|
|
301
331
|
if (first === "cancel") {
|
|
@@ -318,7 +348,8 @@ async function runDecide(args) {
|
|
|
318
348
|
const pick = flag(rest, "--pick");
|
|
319
349
|
const body = flag(rest, "--body");
|
|
320
350
|
const tag = flag(rest, "--tag");
|
|
321
|
-
const
|
|
351
|
+
const due = flag(rest, "--due");
|
|
352
|
+
const res = await postJson("decide-add", { title, options, pick, body, tag, due });
|
|
322
353
|
const where = res.askedNow ? "asked now" : `queued (${res.waiting} open)`;
|
|
323
354
|
console.log(`Decision ${res.decision.id} ${where}: ${res.decision.title}${res.warning ? `\n${res.warning}` : ""}`);
|
|
324
355
|
} catch (e) {
|
package/bin/cli.js
CHANGED
|
@@ -696,7 +696,7 @@ Send tools (only work inside an active bot-spawned task):
|
|
|
696
696
|
open-claudia card send <spec|-> Send a Kazee Card (decision, choice, rating, slider, form, report); answers wake this conversation
|
|
697
697
|
open-claudia card pieces List the pieces a card is built from (table, tick, date, slots, rows, board...) and their examples
|
|
698
698
|
open-claudia card update <id> ... Close/open a card you sent, add a note, or replace its spec (card example <name> for specs)
|
|
699
|
-
open-claudia decide "<q>" --option id="label" ... Queue a decision card for the owner (
|
|
699
|
+
open-claudia decide "<q>" --option id="label" ... Queue a decision card for the owner (waits until answered, snoozed or dropped; decide list|done|cancel)
|
|
700
700
|
open-claudia view send <page.html> --title "<t>" [--handler <file>] Send an interactive web page (Kazee Chat); view push|example
|
|
701
701
|
open-claudia teach list|show|finish|cancel <id> Browser recordings from Teach me; finish deletes the pictures, cancel the whole recording
|
|
702
702
|
|
package/core/cards.js
CHANGED
|
@@ -31,6 +31,12 @@ const { atomicWriteFileSync } = require("./fsutil");
|
|
|
31
31
|
|
|
32
32
|
const CARDS_FILE = () => process.env.OC_CARDS_FILE || path.join(CONFIG_DIR, "cards.json");
|
|
33
33
|
const DECISIONS_FILE = () => process.env.OC_DECISIONS_FILE || path.join(CONFIG_DIR, "decisions.json");
|
|
34
|
+
// Beside decisions.json: per home chat, when the owner last wrote there and
|
|
35
|
+
// when he last got a catch-up; the local day of the last morning walk.
|
|
36
|
+
const DECISIONS_META_FILE = () => process.env.OC_DECISIONS_META_FILE || path.join(path.dirname(DECISIONS_FILE()), "decisions-meta.json");
|
|
37
|
+
// Waiting on him: until he answers, snoozes, drops or does it himself.
|
|
38
|
+
const OPEN_STATUSES = ["queued", "asked", "snoozed"];
|
|
39
|
+
const isOpenDecision = (d) => !!d && OPEN_STATUSES.includes(d.status);
|
|
34
40
|
const CARD_KEEP_MS = 45 * 24 * 60 * 60 * 1000;
|
|
35
41
|
const CARD_KEEP_MAX = 2000;
|
|
36
42
|
const SNOOZE_MS = Number(process.env.OC_DECISION_SNOOZE_MS || 3 * 60 * 60 * 1000);
|
|
@@ -46,7 +52,29 @@ const LATE_ANSWER_MS = 6 * HOUR_MS;
|
|
|
46
52
|
const PROMPT_DECISIONS_MAX = 5;
|
|
47
53
|
const PROMPT_TITLE_MAX = 80;
|
|
48
54
|
const PROMPT_TITLE_MIN = 20;
|
|
49
|
-
|
|
55
|
+
// 700: room for the line that information is told once, never queued.
|
|
56
|
+
const PROMPT_SECTION_MAX = 700;
|
|
57
|
+
// Waiting on you (Sumeet's 7-day trial, Nov 2026): a card asked 3+ days ago is
|
|
58
|
+
// asked again once on a fresh card; one catch-up after 24 h of him quiet; a
|
|
59
|
+
// morning walk re-checks what still applies.
|
|
60
|
+
const STALE_MS = () => Number(process.env.OC_DECISION_STALE_MS) || 3 * DAY_MS;
|
|
61
|
+
const STALE_FIRST_HOUR = 9;
|
|
62
|
+
const STALE_LAST_HOUR = 18;
|
|
63
|
+
const CATCHUP_MS = () => Number(process.env.OC_DECISION_CATCHUP_MS) || DAY_MS;
|
|
64
|
+
const CATCHUP_FIRST_HOUR = 9;
|
|
65
|
+
const CATCHUP_LAST_HOUR = 21;
|
|
66
|
+
const CATCHUP_LINES = 5;
|
|
67
|
+
const WALK_MIN_AGE_MS = 20 * HOUR_MS;
|
|
68
|
+
const WALK_WINDOW_MS = 8 * HOUR_MS;
|
|
69
|
+
const PICK_MARK = " (my pick)";
|
|
70
|
+
const OPTION_LABEL_MAX = 80;
|
|
71
|
+
// An item needed by a date (decide --due YYYY-MM-DD, local day): within
|
|
72
|
+
// DUE_SOON_DAYS it goes ahead of older items; within DUE_URGENT_DAYS it also
|
|
73
|
+
// takes the place of a card out without a date (Mail thread, 14 Nov: the LINX
|
|
74
|
+
// vote closing Wed 18 Nov sat 23rd in line, one card at a time).
|
|
75
|
+
const DUE_SOON_DAYS = 3;
|
|
76
|
+
const DUE_URGENT_DAYS = 2;
|
|
77
|
+
const DUE_RE = /^\d{4}-\d{2}-\d{2}$/;
|
|
50
78
|
const timeZone = () => process.env.OC_TIMEZONE || "Africa/Nairobi";
|
|
51
79
|
|
|
52
80
|
function readJson(file, fallback) {
|
|
@@ -173,7 +201,12 @@ function openDecisions({ adapterId = null, channelId, threadId = null, now = Dat
|
|
|
173
201
|
return Object.values(loadCards())
|
|
174
202
|
.filter((c) => c && c.kind === "decision" && !c.answeredAt && cardIsOpen(c.lastState))
|
|
175
203
|
.filter((c) => now - Date.parse(c.sentAt || 0) <= OPEN_DECISION_MAX_AGE_MS)
|
|
176
|
-
.filter((c) =>
|
|
204
|
+
.filter((c) => {
|
|
205
|
+
if (!c.decisionId || !queue.has(c.decisionId)) return true;
|
|
206
|
+
const d = queue.get(c.decisionId);
|
|
207
|
+
// A decision asked again on a fresh card: only that card is open.
|
|
208
|
+
return isOpenDecision(d) && (!d.askedMessageId || String(d.askedMessageId) === String(c.messageId));
|
|
209
|
+
})
|
|
177
210
|
.filter((c) => {
|
|
178
211
|
if (channelId === undefined || channelId === null) return true;
|
|
179
212
|
const p = placeOf(c);
|
|
@@ -213,10 +246,17 @@ function openDecisionsPromptSection({ adapterId = null, channelId, threadId = nu
|
|
|
213
246
|
const shown = open.slice(0, PROMPT_DECISIONS_MAX);
|
|
214
247
|
const head = "## Open decisions you asked here";
|
|
215
248
|
const more = open.length > shown.length ? [`- and ${open.length - shown.length} more`] : [];
|
|
216
|
-
const
|
|
217
|
-
const
|
|
249
|
+
const queued = shown.some((c) => c.decisionId);
|
|
250
|
+
const loose = shown.some((c) => !c.decisionId);
|
|
251
|
+
const tail = [
|
|
252
|
+
queued ? "A [dec_…] item waits on Sumeet until settled: if he did it himself or answered in words, `open-claudia decide done <id> \"<what happened>\"`; if it is no longer needed, `open-claudia decide cancel <id>`." : "",
|
|
253
|
+
loose ? "If the latest message answers one, or it no longer applies, close it: `open-claudia card update <messageId> --close --note \"<why, in plain words>\"`." : "",
|
|
254
|
+
"Information only, nothing to decide: tell him once, never queue it. Never leave an answered or moot decision open.",
|
|
255
|
+
].filter(Boolean).join(" ");
|
|
256
|
+
const line = (c, title) => `- [${c.decisionId || c.messageId}] "${title}" — asked ${ageWords(now - Date.parse(c.sentAt))} ago`;
|
|
218
257
|
const fixed = [head, ...shown.map((c) => line(c, "")), ...more, tail].join("\n").length;
|
|
219
|
-
const
|
|
258
|
+
const budget = PROMPT_SECTION_MAX + (queued ? 200 : 0);
|
|
259
|
+
const titleMax = Math.max(PROMPT_TITLE_MIN, Math.min(PROMPT_TITLE_MAX, Math.floor((budget - fixed) / shown.length)));
|
|
220
260
|
return [head, ...shown.map((c) => line(c, shortTitle(c.title, titleMax))), ...more, tail].join("\n");
|
|
221
261
|
}
|
|
222
262
|
|
|
@@ -459,7 +499,9 @@ function describeResponse(rec, payload, now = Date.now()) {
|
|
|
459
499
|
|
|
460
500
|
// `source` tags the job for job lists and audits: "card" for card answers,
|
|
461
501
|
// "browser" for the Browser app's hand-backs and Teach-me recordings.
|
|
462
|
-
|
|
502
|
+
// `quiet`: the job posts no "Wakeup:" line and a reply of exactly [quiet] is
|
|
503
|
+
// not posted (core/scheduler.js, core/runner.js).
|
|
504
|
+
function wakeOrigin(origin, prompt, label, { scheduler, delayMs = 1500, source = "card", quiet = false } = {}) {
|
|
463
505
|
if (!origin || !origin.channelId || !origin.provider || !origin.project || !origin.canonicalUserId) {
|
|
464
506
|
throw new Error("the card's origin conversation is unknown");
|
|
465
507
|
}
|
|
@@ -487,6 +529,7 @@ function wakeOrigin(origin, prompt, label, { scheduler, delayMs = 1500, source =
|
|
|
487
529
|
prompt,
|
|
488
530
|
label: String(label).slice(0, 60),
|
|
489
531
|
source,
|
|
532
|
+
...(quiet ? { quiet: true } : {}),
|
|
490
533
|
fireAt: Date.now() + delayMs,
|
|
491
534
|
});
|
|
492
535
|
}
|
|
@@ -530,8 +573,9 @@ function loadDecisions() {
|
|
|
530
573
|
}
|
|
531
574
|
function saveDecisions(list) {
|
|
532
575
|
// Keep every open decision and the last 200 settled ones.
|
|
533
|
-
|
|
534
|
-
const
|
|
576
|
+
// Settled: decided, dropped, done, cancelled.
|
|
577
|
+
const open = list.filter(isOpenDecision);
|
|
578
|
+
const settled = list.filter((d) => !isOpenDecision(d)).slice(-200);
|
|
535
579
|
writeJson(DECISIONS_FILE(), [...open, ...settled].sort((a, b) => a.seq - b.seq));
|
|
536
580
|
}
|
|
537
581
|
|
|
@@ -551,6 +595,7 @@ function addDecision(input) {
|
|
|
551
595
|
options: input.options,
|
|
552
596
|
pick: input.pick || null,
|
|
553
597
|
tag: input.tag || null,
|
|
598
|
+
due: input.due && DUE_RE.test(String(input.due)) ? String(input.due) : null,
|
|
554
599
|
home: input.home, // { adapter, adapterType, channelId, threadId, ownerKazeeUserId }
|
|
555
600
|
origin: input.origin, // run context to wake with the answer
|
|
556
601
|
createdAt: new Date().toISOString(),
|
|
@@ -560,23 +605,122 @@ function addDecision(input) {
|
|
|
560
605
|
return d;
|
|
561
606
|
}
|
|
562
607
|
|
|
563
|
-
function
|
|
608
|
+
function pickLabel(label) {
|
|
609
|
+
const l = String(label || "");
|
|
610
|
+
if (/\bmy pick\b/i.test(l)) return l;
|
|
611
|
+
const room = OPTION_LABEL_MAX - PICK_MARK.length;
|
|
612
|
+
return `${l.length > room ? `${l.slice(0, room - 1)}…` : l}${PICK_MARK}`;
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* The card for one item on the owner's list. `stale`: asked again after
|
|
617
|
+
* days unanswered: "Asked N days ago." on top, my pick + Something else (all options
|
|
618
|
+
* when there is none, or with `allOptions`), Later and Drop.
|
|
619
|
+
*/
|
|
620
|
+
const SOMETHING_ELSE = "something_else";
|
|
621
|
+
const CARD_BODY_MAX = 600;
|
|
622
|
+
|
|
623
|
+
function decisionCard(d, position, total, { stale = false, allOptions = false, now = Date.now() } = {}) {
|
|
624
|
+
const pick = d.pick && d.options.some((o) => o.id === d.pick) ? d.pick : null;
|
|
625
|
+
let options = pick ? [...d.options.filter((o) => o.id === pick), ...d.options.filter((o) => o.id !== pick)] : [...d.options];
|
|
626
|
+
// A decision card needs 2-4 options (chat-central cards.ts), so "only my
|
|
627
|
+
// pick" is my pick plus Something else; Later and Drop stay as extras.
|
|
628
|
+
if (stale && pick && !allOptions && options.length > 1) options = [options[0], { id: SOMETHING_ELSE, label: "Something else" }];
|
|
564
629
|
const card = {
|
|
565
630
|
v: 1,
|
|
566
631
|
kind: "decision",
|
|
567
|
-
kicker: total > 1 ? `
|
|
632
|
+
kicker: total > 1 ? `Waiting on you · ${position} of ${total}` : "Waiting on you",
|
|
568
633
|
title: d.title,
|
|
569
|
-
input: { options:
|
|
570
|
-
extras: [
|
|
634
|
+
input: { options: options.map((o, i) => ({ id: o.id, label: o.id === pick ? pickLabel(o.label) : o.label, style: i === 0 ? "primary" : "secondary" })) },
|
|
635
|
+
extras: [
|
|
636
|
+
...(stale ? [] : [{ id: "more", label: "🎧 Tell me more" }]),
|
|
637
|
+
{ id: "later", label: "⏰ Later" },
|
|
638
|
+
{ id: "drop", label: "🗑 Drop" },
|
|
639
|
+
],
|
|
571
640
|
policy: { responders: [String(d.home.ownerKazeeUserId)], mode: "first" },
|
|
572
641
|
footer: "Only you can answer",
|
|
573
642
|
};
|
|
574
643
|
if (d.tag) card.tag = d.tag;
|
|
575
|
-
|
|
576
|
-
|
|
644
|
+
const since = Date.parse(d.askedAt || d.createdAt || now);
|
|
645
|
+
const needed = DUE_RE.test(String(d.due || "")) ? `Needed by ${dayLabel(d.due).replace(/ \d{4}$/, "")}.` : null;
|
|
646
|
+
const body = [stale ? `Asked ${ageWords(Math.max(0, now - since))} ago.` : null, needed, d.body].filter(Boolean).join("\n\n") || null;
|
|
647
|
+
// chat-central refuses a card body over 600 characters; the "Asked N days
|
|
648
|
+
// ago." line on a fresh card can push a full body past it (14 Nov: the
|
|
649
|
+
// 39-day-old card was refused on every tick).
|
|
650
|
+
if (body) card.body = body.length > CARD_BODY_MAX ? `${body.slice(0, CARD_BODY_MAX - 1).trimEnd()}…` : body;
|
|
651
|
+
if (pick) card.recommendation = { option: pick };
|
|
577
652
|
return card;
|
|
578
653
|
}
|
|
579
654
|
|
|
655
|
+
/** Whole local days from today to an item's due date (negative: overdue); null without one. */
|
|
656
|
+
function daysToDue(d, now = Date.now(), tz = timeZone()) {
|
|
657
|
+
const m = DUE_RE.test(String(d?.due || "")) ? String(d.due).split("-").map(Number) : null;
|
|
658
|
+
if (!m) return null;
|
|
659
|
+
const p = zonedParts(now, tz);
|
|
660
|
+
return Math.round((Date.UTC(m[0], m[1] - 1, m[2]) - Date.UTC(p.year, p.month - 1, p.day)) / DAY_MS);
|
|
661
|
+
}
|
|
662
|
+
const dueSoon = (d, now, tz) => { const n = daysToDue(d, now, tz); return n !== null && n <= DUE_SOON_DAYS; };
|
|
663
|
+
const dueUrgent = (d, now, tz) => { const n = daysToDue(d, now, tz); return n !== null && n <= DUE_URGENT_DAYS; };
|
|
664
|
+
/** Among items waiting to go out: due soon first (soonest date first), then oldest first. */
|
|
665
|
+
function byDueThenSeq(now, tz) {
|
|
666
|
+
return (a, b) => {
|
|
667
|
+
const sa = dueSoon(a, now, tz), sb = dueSoon(b, now, tz);
|
|
668
|
+
if (sa !== sb) return sa ? -1 : 1;
|
|
669
|
+
if (sa && a.due !== b.due) return a.due < b.due ? -1 : 1;
|
|
670
|
+
return (a.seq || 0) - (b.seq || 0);
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/** Close an undated card that a deadline pushed aside; the item waits again. */
|
|
675
|
+
const PUT_BACK_NOTE = "⏰ Back on your list: something with a deadline goes first.";
|
|
676
|
+
async function putBack(d, { updateCard = null } = {}) {
|
|
677
|
+
const fresh = loadDecisions();
|
|
678
|
+
const target = fresh.find((x) => x.id === d.id);
|
|
679
|
+
if (!target || target.status !== "asked") return false;
|
|
680
|
+
const oldId = target.askedMessageId;
|
|
681
|
+
target.status = "queued";
|
|
682
|
+
target.putBackAt = new Date().toISOString();
|
|
683
|
+
for (const k of ["askedMessageId", "askedAt", "staleAskedAt", "oldCardId", "remindedAt", "keptCard", "notBefore"]) delete target[k];
|
|
684
|
+
saveDecisions(fresh);
|
|
685
|
+
if (oldId && updateCard) {
|
|
686
|
+
try {
|
|
687
|
+
const view = await updateCard(oldId, { status: "closed", note: PUT_BACK_NOTE });
|
|
688
|
+
noteCardUpdate(oldId, { status: "closed", note: PUT_BACK_NOTE }, view);
|
|
689
|
+
} catch (e) { console.error(`[cards] closing ${oldId} for a deadline failed:`, e.message); }
|
|
690
|
+
}
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
/** Queue order: what is on a card first, then snoozed, then waiting (due soon first); each oldest first. */
|
|
695
|
+
function queueOrder(ds, now = Date.now(), tz = timeZone()) {
|
|
696
|
+
const rank = { asked: 0, snoozed: 1, queued: 2 };
|
|
697
|
+
const order = byDueThenSeq(now, tz);
|
|
698
|
+
return [...ds].sort((a, b) => (rank[a.status] ?? 3) - (rank[b.status] ?? 3) || order(a, b));
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function homeKeyOf(d) {
|
|
702
|
+
return `${d.home?.adapter}|${d.home?.channelId}`;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** Open items per home chat (adapter|channelId), in queue order. */
|
|
706
|
+
function openByHome(list, homeKey = null, now = Date.now()) {
|
|
707
|
+
const homes = new Map();
|
|
708
|
+
for (const d of list) {
|
|
709
|
+
if (!isOpenDecision(d) || !d.home) continue;
|
|
710
|
+
const key = homeKeyOf(d);
|
|
711
|
+
if (homeKey && key !== homeKey) continue;
|
|
712
|
+
if (!homes.has(key)) homes.set(key, []);
|
|
713
|
+
homes.get(key).push(d);
|
|
714
|
+
}
|
|
715
|
+
for (const [key, ds] of homes) homes.set(key, queueOrder(ds, now));
|
|
716
|
+
return homes;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/** How long an item has waited on him: since it was queued. */
|
|
720
|
+
function waitedWords(d, now) {
|
|
721
|
+
return ageWords(Math.max(0, now - Date.parse(d.createdAt || d.askedAt || now)));
|
|
722
|
+
}
|
|
723
|
+
|
|
580
724
|
/** The one reply that brings an open decision back to the top of the chat. */
|
|
581
725
|
function reminderText(title) {
|
|
582
726
|
return `⏳ Still waiting on you: ${title || "a decision"}`;
|
|
@@ -587,22 +731,18 @@ function reminderText(title) {
|
|
|
587
731
|
* and returns its message id: (home, card) => Promise<string>. `remind`
|
|
588
732
|
* posts a plain reply: (home, text, { threadId, replyTo }) => Promise. A
|
|
589
733
|
* snoozed decision keeps its card open; when its time is up it gets one
|
|
590
|
-
* reply on that card (not a new card) and counts as asked again.
|
|
734
|
+
* reply on that card (not a new card) and counts as asked again. A card
|
|
735
|
+
* asked STALE_MS ago and still unanswered is asked once more on a fresh card
|
|
736
|
+
* (09:00-18:00 local); that is the same item, not another card out.
|
|
591
737
|
*/
|
|
592
|
-
async function pumpDecisions({ send, remind = null, updateCard = null, now = Date.now(), homeKey = null } = {}) {
|
|
593
|
-
const
|
|
594
|
-
const homes = new Map();
|
|
595
|
-
for (const d of list) {
|
|
596
|
-
if (!["queued", "asked", "snoozed"].includes(d.status)) continue;
|
|
597
|
-
const key = `${d.home.adapter}|${d.home.channelId}`;
|
|
598
|
-
if (homeKey && key !== homeKey) continue;
|
|
599
|
-
if (!homes.has(key)) homes.set(key, []);
|
|
600
|
-
homes.get(key).push(d);
|
|
601
|
-
}
|
|
738
|
+
async function pumpDecisions({ send, remind = null, updateCard = null, now = Date.now(), homeKey = null, tz = timeZone() } = {}) {
|
|
739
|
+
const homes = openByHome(loadDecisions(), homeKey, now);
|
|
602
740
|
const asked = [];
|
|
741
|
+
const localHour = zonedParts(now, tz).hour;
|
|
603
742
|
for (const [key, ds] of homes) {
|
|
604
743
|
const isDue = (d) => d.status === "snoozed" && Date.parse(d.notBefore || 0) <= now;
|
|
605
744
|
let remindedHere = false;
|
|
745
|
+
const remindedNow = new Set();
|
|
606
746
|
for (const d of ds.filter((x) => isDue(x) && x.keptCard && x.askedMessageId)) {
|
|
607
747
|
if (!remind) continue;
|
|
608
748
|
try {
|
|
@@ -627,15 +767,34 @@ async function pumpDecisions({ send, remind = null, updateCard = null, now = Dat
|
|
|
627
767
|
}
|
|
628
768
|
d.status = "asked";
|
|
629
769
|
remindedHere = true;
|
|
770
|
+
remindedNow.add(d.id);
|
|
630
771
|
asked.push({ key, decisionId: d.id, messageId: d.askedMessageId, reminded: true });
|
|
631
772
|
}
|
|
773
|
+
if (localHour >= STALE_FIRST_HOUR && localHour < STALE_LAST_HOUR) {
|
|
774
|
+
const stale = ds.filter((d) => d.status === "asked" && d.askedAt && d.askedMessageId && !d.staleAskedAt
|
|
775
|
+
&& !remindedNow.has(d.id) && now - Date.parse(d.askedAt) >= STALE_MS());
|
|
776
|
+
for (const d of stale) {
|
|
777
|
+
const messageId = await askAgain(d, ds.length, { send, updateCard, now });
|
|
778
|
+
if (messageId) asked.push({ key, decisionId: d.id, messageId, stale: true });
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
// Something needed within DUE_URGENT_DAYS takes the place of a card out
|
|
782
|
+
// without a date: that card closes and goes back on the list, keeping
|
|
783
|
+
// its place by age.
|
|
784
|
+
const out = ds.find((d) => d.status === "asked");
|
|
785
|
+
const urgent = ds.find((d) => d.status === "queued" && dueUrgent(d, now, tz));
|
|
786
|
+
if (out && urgent && !remindedHere && !dueSoon(out, now, tz) && !remindedNow.has(out.id)
|
|
787
|
+
&& !asked.some((x) => x.decisionId === out.id)) {
|
|
788
|
+
if (await putBack(out, { updateCard })) out.status = "queued";
|
|
789
|
+
}
|
|
632
790
|
if (remindedHere || ds.some((d) => d.status === "asked")) continue;
|
|
633
791
|
// A decision snoozed before cards stayed open had its card closed: ask it again as a new card.
|
|
634
792
|
const due = ds.filter((d) => d.status === "queued" || (isDue(d) && !d.keptCard));
|
|
635
793
|
if (!due.length) continue;
|
|
636
|
-
const
|
|
637
|
-
const
|
|
638
|
-
|
|
794
|
+
const order = byDueThenSeq(now, tz);
|
|
795
|
+
const next = due.sort((a, b) => (dueUrgent(b, now, tz) - dueUrgent(a, now, tz)) || (a.status === "queued") - (b.status === "queued") || order(a, b))[0];
|
|
796
|
+
// N counts every open item in this home chat: on a card, snoozed or waiting.
|
|
797
|
+
const card = decisionCard(next, 1, ds.length, { now });
|
|
639
798
|
const messageId = await send(next.home, card);
|
|
640
799
|
if (!messageId) continue;
|
|
641
800
|
const fresh = loadDecisions();
|
|
@@ -659,6 +818,50 @@ async function pumpDecisions({ send, remind = null, updateCard = null, now = Dat
|
|
|
659
818
|
return asked;
|
|
660
819
|
}
|
|
661
820
|
|
|
821
|
+
/**
|
|
822
|
+
* Ask a stale item again: post the fresh card (my pick, Something else,
|
|
823
|
+
* Later, Drop; all options if the server refuses that), then close the old card. The
|
|
824
|
+
* item keeps its first askedAt, so its age and a late answer still count
|
|
825
|
+
* from the first ask. Returns the new card's id, or null (tried next tick).
|
|
826
|
+
*/
|
|
827
|
+
async function askAgain(d, total, { send, updateCard, now }) {
|
|
828
|
+
let messageId = null;
|
|
829
|
+
try {
|
|
830
|
+
messageId = await send(d.home, decisionCard(d, 1, total, { stale: true, now }));
|
|
831
|
+
} catch (e) {
|
|
832
|
+
console.error(`[cards] fresh card for stale decision ${d.id} refused (${e.message}); trying with every option`);
|
|
833
|
+
}
|
|
834
|
+
if (!messageId && d.pick && d.options.some((o) => o.id === d.pick)) {
|
|
835
|
+
try { messageId = await send(d.home, decisionCard(d, 1, total, { stale: true, allOptions: true, now })); }
|
|
836
|
+
catch (e) { console.error(`[cards] fresh card for stale decision ${d.id} failed:`, e.message); }
|
|
837
|
+
}
|
|
838
|
+
if (!messageId) return null;
|
|
839
|
+
const fresh = loadDecisions();
|
|
840
|
+
const target = fresh.find((x) => x.id === d.id);
|
|
841
|
+
if (!target) return null;
|
|
842
|
+
const oldCardId = target.askedMessageId;
|
|
843
|
+
target.oldCardId = oldCardId;
|
|
844
|
+
target.askedMessageId = String(messageId);
|
|
845
|
+
target.staleAskedAt = new Date(now).toISOString();
|
|
846
|
+
saveDecisions(fresh);
|
|
847
|
+
recordSentCard({
|
|
848
|
+
messageId,
|
|
849
|
+
decisionId: d.id,
|
|
850
|
+
title: d.title,
|
|
851
|
+
kind: "decision",
|
|
852
|
+
wake: "each",
|
|
853
|
+
origin: d.origin,
|
|
854
|
+
home: d.home,
|
|
855
|
+
staleAsk: true,
|
|
856
|
+
});
|
|
857
|
+
if (oldCardId && updateCard) {
|
|
858
|
+
const patch = { status: "closed", note: "Moved to a fresh card." };
|
|
859
|
+
try { noteCardUpdate(oldCardId, patch, await updateCard(oldCardId, patch)); }
|
|
860
|
+
catch (e) { console.error(`[cards] closing the old card ${oldCardId} failed:`, e.message); }
|
|
861
|
+
}
|
|
862
|
+
return String(messageId);
|
|
863
|
+
}
|
|
864
|
+
|
|
662
865
|
async function handleDecisionResponse(rec, payload, envelope, deps = {}) {
|
|
663
866
|
const list = loadDecisions();
|
|
664
867
|
const d = list.find((x) => x.id === rec.decisionId);
|
|
@@ -667,6 +870,27 @@ async function handleDecisionResponse(rec, payload, envelope, deps = {}) {
|
|
|
667
870
|
const send = deps.sendCard || ((home, card) => envelope.adapter?.sendCard?.(home.channelId, card, { threadId: home.threadId || null }));
|
|
668
871
|
const remind = deps.remind || ((home, text, opts) => envelope.adapter?.send?.(home.channelId, text, opts));
|
|
669
872
|
const pump = () => pumpDecisions({ send, remind, updateCard, homeKey: `${d.home.adapter}|${d.home.channelId}` });
|
|
873
|
+
// Only he can answer these cards: a tap is him being here.
|
|
874
|
+
try { noteOwnerActivity({ adapter: d.home.adapter, channelId: d.home.channelId }); } catch (_) {}
|
|
875
|
+
|
|
876
|
+
if (payload.action === "drop") {
|
|
877
|
+
if (!isOpenDecision(d)) return { handled: true, dropped: false, reason: "already settled" };
|
|
878
|
+
d.status = "dropped";
|
|
879
|
+
d.droppedAt = new Date().toISOString();
|
|
880
|
+
saveDecisions(list);
|
|
881
|
+
const patch = { status: "closed", note: "Dropped." };
|
|
882
|
+
try { noteCardUpdate(rec.messageId, patch, await updateCard(rec.messageId, patch)); } catch (e) { console.error("[cards] closing a dropped card failed:", e.message); }
|
|
883
|
+
try {
|
|
884
|
+
wakeOrigin(
|
|
885
|
+
d.origin,
|
|
886
|
+
`[Decision ${d.id}] Sumeet dropped "${d.title}". Don't raise it again unless something changes. If nothing needs saying, reply exactly [quiet].`,
|
|
887
|
+
`Dropped: ${d.title}`,
|
|
888
|
+
{ ...deps, quiet: true },
|
|
889
|
+
);
|
|
890
|
+
} catch (e) { console.error(`[cards] dropped decision ${d.id}: origin not told:`, e.message); }
|
|
891
|
+
await pump();
|
|
892
|
+
return { handled: true, dropped: true };
|
|
893
|
+
}
|
|
670
894
|
|
|
671
895
|
if (payload.action === "later") {
|
|
672
896
|
// The card stays open where it is (no closing it and posting a new one
|
|
@@ -702,10 +926,13 @@ async function handleDecisionResponse(rec, payload, envelope, deps = {}) {
|
|
|
702
926
|
if (wasSnoozed) {
|
|
703
927
|
try { noteCardUpdate(rec.messageId, { note: "" }, await updateCard(rec.messageId, { note: "" })); } catch (_) { /* only clears the "remind you at" note */ }
|
|
704
928
|
}
|
|
705
|
-
|
|
929
|
+
// From the first ask: a stale item's fresh card is not a fresh question.
|
|
930
|
+
const late = lateAnswerLine([rec.sentAt, d.askedAt].filter(Boolean).sort()[0]);
|
|
706
931
|
wakeOrigin(
|
|
707
932
|
d.origin,
|
|
708
|
-
|
|
933
|
+
payload.answer.option === SOMETHING_ELSE && !d.options.some((o) => o.id === SOMETHING_ELSE)
|
|
934
|
+
? `[Decision ${d.id}] On "${d.title}" Sumeet tapped "Something else": not your pick (${(d.options.find((o) => o.id === d.pick) || {}).label || d.pick}). Ask him in one short voice note what he wants instead, then carry on with his answer.${late ? `\n${late}` : ""}`
|
|
935
|
+
: `[Decision ${d.id}] Sumeet decided "${d.title}": ${d.answerLabel}.\nThis was queued from this conversation. Carry on with the work that was waiting for it, and tell him briefly what happens next.${late ? `\n${late}` : ""}`,
|
|
709
936
|
`Decided: ${d.title}`,
|
|
710
937
|
deps,
|
|
711
938
|
);
|
|
@@ -719,7 +946,7 @@ async function cancelDecision(id, { updateCard } = {}) {
|
|
|
719
946
|
const list = loadDecisions();
|
|
720
947
|
const d = list.find((x) => x.id === id);
|
|
721
948
|
if (!d) return null;
|
|
722
|
-
if (!
|
|
949
|
+
if (!isOpenDecision(d)) return d;
|
|
723
950
|
const cardOpen = d.status === "asked" || (d.status === "snoozed" && d.keptCard);
|
|
724
951
|
d.status = "cancelled";
|
|
725
952
|
d.cancelledAt = new Date().toISOString();
|
|
@@ -731,6 +958,195 @@ async function cancelDecision(id, { updateCard } = {}) {
|
|
|
731
958
|
return d;
|
|
732
959
|
}
|
|
733
960
|
|
|
961
|
+
/**
|
|
962
|
+
* He did it himself, or answered in words elsewhere: tick it off. Its open
|
|
963
|
+
* card is closed with what happened; with `send`, the next item in that
|
|
964
|
+
* home chat goes out at once.
|
|
965
|
+
*/
|
|
966
|
+
async function doneDecision(id, note, { updateCard = null, send = null, remind = null, now = Date.now() } = {}) {
|
|
967
|
+
const list = loadDecisions();
|
|
968
|
+
const d = list.find((x) => x.id === id);
|
|
969
|
+
if (!d) return null;
|
|
970
|
+
if (!isOpenDecision(d)) return d;
|
|
971
|
+
const cardOpen = d.status === "asked" || (d.status === "snoozed" && d.keptCard);
|
|
972
|
+
const what = note == null ? "" : String(note).replace(/\s+/g, " ").trim().slice(0, 300);
|
|
973
|
+
d.status = "done";
|
|
974
|
+
d.doneAt = new Date(now).toISOString();
|
|
975
|
+
d.doneNote = what || null;
|
|
976
|
+
saveDecisions(list);
|
|
977
|
+
if (cardOpen && d.askedMessageId && updateCard) {
|
|
978
|
+
const patch = { status: "closed", note: what ? `✅ Done: ${what}` : "✅ Done" };
|
|
979
|
+
try { noteCardUpdate(d.askedMessageId, patch, await updateCard(d.askedMessageId, patch)); } catch (e) { console.error(`[cards] closing done decision ${d.id}'s card failed:`, e.message); }
|
|
980
|
+
}
|
|
981
|
+
if (send && d.home) {
|
|
982
|
+
try { await pumpDecisions({ send, remind, updateCard, now, homeKey: homeKeyOf(d) }); }
|
|
983
|
+
catch (e) { console.error("[cards] asking the next decision failed:", e.message); }
|
|
984
|
+
}
|
|
985
|
+
return d;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
/**
|
|
989
|
+
* The owner's list for `decide list`: every open item in queue order (home
|
|
990
|
+
* by home) with how long it has waited and its home thread (short), and
|
|
991
|
+
* with `all` the last 20 settled with their outcome.
|
|
992
|
+
*/
|
|
993
|
+
function listDecisions({ all = false, now = Date.now() } = {}) {
|
|
994
|
+
const list = loadDecisions();
|
|
995
|
+
const open = [...openByHome(list, null, now).values()].flat().map((d) => ({
|
|
996
|
+
...d,
|
|
997
|
+
age: waitedWords(d, now),
|
|
998
|
+
thread: d.home?.threadId ? String(d.home.threadId).slice(-6) : "main",
|
|
999
|
+
}));
|
|
1000
|
+
const res = { decisions: open, waiting: open.length };
|
|
1001
|
+
if (all) {
|
|
1002
|
+
const at = (d) => Date.parse(d.decidedAt || d.droppedAt || d.doneAt || d.cancelledAt || d.createdAt || 0) || 0;
|
|
1003
|
+
const outcome = (d) => (d.status === "decided" ? (d.answerLabel || "decided")
|
|
1004
|
+
: d.status === "done" ? (d.doneNote ? `done: ${d.doneNote}` : "done")
|
|
1005
|
+
: d.status === "cancelled" ? "withdrawn" : d.status);
|
|
1006
|
+
res.settled = list.filter((d) => !isOpenDecision(d))
|
|
1007
|
+
.sort((a, b) => at(b) - at(a))
|
|
1008
|
+
.slice(0, 20)
|
|
1009
|
+
.map((d) => ({ ...d, outcome: outcome(d) }));
|
|
1010
|
+
}
|
|
1011
|
+
return res;
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
// ---------------------------------------------------------------------------
|
|
1015
|
+
// When he was last here, per home chat (decisions-meta.json)
|
|
1016
|
+
|
|
1017
|
+
function loadDecisionsMeta() {
|
|
1018
|
+
const m = readJson(DECISIONS_META_FILE(), {});
|
|
1019
|
+
const meta = m && typeof m === "object" && !Array.isArray(m) ? m : {};
|
|
1020
|
+
return { ...meta, homes: meta.homes && typeof meta.homes === "object" && !Array.isArray(meta.homes) ? meta.homes : {} };
|
|
1021
|
+
}
|
|
1022
|
+
function saveDecisionsMeta(meta) {
|
|
1023
|
+
writeJson(DECISIONS_META_FILE(), meta);
|
|
1024
|
+
}
|
|
1025
|
+
|
|
1026
|
+
function updateHomeMeta(key, patch) {
|
|
1027
|
+
const meta = loadDecisionsMeta();
|
|
1028
|
+
meta.homes[key] = { ...(meta.homes[key] || {}), ...patch };
|
|
1029
|
+
saveDecisionsMeta(meta);
|
|
1030
|
+
return meta.homes[key];
|
|
1031
|
+
}
|
|
1032
|
+
|
|
1033
|
+
/** The owner wrote (or tapped a card) in this chat: a new quiet stretch starts from here. */
|
|
1034
|
+
function noteOwnerActivity({ adapter, channelId, at = Date.now() } = {}) {
|
|
1035
|
+
if (!adapter || !channelId) return null;
|
|
1036
|
+
const key = `${adapter}|${channelId}`;
|
|
1037
|
+
const meta = loadDecisionsMeta();
|
|
1038
|
+
const cur = meta.homes[key] || {};
|
|
1039
|
+
const t = typeof at === "number" ? at : Date.parse(at);
|
|
1040
|
+
if (!Number.isFinite(t) || (cur.lastOwnerAt && Date.parse(cur.lastOwnerAt) >= t)) return cur;
|
|
1041
|
+
return updateHomeMeta(key, { lastOwnerAt: new Date(t).toISOString() });
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Every inbound message (core/router.js): when it is the owner writing in a
|
|
1046
|
+
* chat his decisions are asked in, note it. Another bot's message is not him.
|
|
1047
|
+
*/
|
|
1048
|
+
function noteInboundForDecisions(envelope, now = Date.now()) {
|
|
1049
|
+
if (!envelope || envelope.botHandoff) return false;
|
|
1050
|
+
const adapter = envelope.adapter?.id;
|
|
1051
|
+
const channelId = envelope.channelId == null ? "" : String(envelope.channelId);
|
|
1052
|
+
const userId = envelope.userId == null ? "" : String(envelope.userId);
|
|
1053
|
+
if (!adapter || !channelId || !userId) return false;
|
|
1054
|
+
const home = loadDecisions().some((d) => d.home && d.home.adapter === adapter
|
|
1055
|
+
&& String(d.home.channelId) === channelId && String(d.home.ownerKazeeUserId || "") === userId);
|
|
1056
|
+
if (!home) return false;
|
|
1057
|
+
noteOwnerActivity({ adapter, channelId, at: now });
|
|
1058
|
+
return true;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
/**
|
|
1062
|
+
* One catch-up message per quiet stretch: when items wait in a home chat and
|
|
1063
|
+
* he has not written there for CATCHUP_MS, post one plain message in the chat
|
|
1064
|
+
* itself (no thread), 09:00-21:00 local. A chat never seen starts its clock.
|
|
1065
|
+
* `send(home, text, { threadId })`.
|
|
1066
|
+
*/
|
|
1067
|
+
async function catchUpOwner({ send, now = Date.now(), tz = timeZone() } = {}) {
|
|
1068
|
+
const homes = openByHome(loadDecisions());
|
|
1069
|
+
if (!homes.size) return [];
|
|
1070
|
+
const localHour = zonedParts(now, tz).hour;
|
|
1071
|
+
const meta = loadDecisionsMeta();
|
|
1072
|
+
const out = [];
|
|
1073
|
+
for (const [key, ds] of homes) {
|
|
1074
|
+
const h = meta.homes[key] || {};
|
|
1075
|
+
if (!h.lastOwnerAt) {
|
|
1076
|
+
updateHomeMeta(key, { lastOwnerAt: new Date(now).toISOString() });
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
const last = Date.parse(h.lastOwnerAt);
|
|
1080
|
+
if (now - last < CATCHUP_MS()) continue;
|
|
1081
|
+
if (localHour < CATCHUP_FIRST_HOUR || localHour >= CATCHUP_LAST_HOUR) continue;
|
|
1082
|
+
if (h.catchupAt && Date.parse(h.catchupAt) >= last) continue;
|
|
1083
|
+
// Claimed before sending: never twice for one quiet stretch.
|
|
1084
|
+
updateHomeMeta(key, { catchupAt: new Date(now).toISOString() });
|
|
1085
|
+
const n = ds.length;
|
|
1086
|
+
const text = [
|
|
1087
|
+
`While you were away, ${n === 1 ? "1 thing waits" : `${n} things wait`} on you:`,
|
|
1088
|
+
...ds.slice(0, CATCHUP_LINES).map((d, i) => `${i + 1}. ${shortTitle(d.title, 70)} (${waitedWords(d, now)})`),
|
|
1089
|
+
"The first one is on its card.",
|
|
1090
|
+
].join("\n");
|
|
1091
|
+
try {
|
|
1092
|
+
await send({ ...ds[0].home, threadId: null }, text, { threadId: null });
|
|
1093
|
+
out.push(key);
|
|
1094
|
+
} catch (e) {
|
|
1095
|
+
console.error(`[cards] catch-up for ${key} failed:`, e.message);
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
return out;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
function walkAt() {
|
|
1102
|
+
const m = /^(\d{1,2}):(\d{2})$/.exec(String(process.env.OC_DECISION_WALK_AT || "09:45").trim());
|
|
1103
|
+
const h = m ? Number(m[1]) : 9;
|
|
1104
|
+
const min = m ? Number(m[2]) : 45;
|
|
1105
|
+
return h < 24 && min < 60 ? { h, min } : { h: 9, min: 45 };
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
/**
|
|
1109
|
+
* The morning walk: once a local day, from OC_DECISION_WALK_AT (09:45) for
|
|
1110
|
+
* a few hours (a restart at night never walks), each origin conversation
|
|
1111
|
+
* with items waiting over 20 h gets one quiet wake to re-check them: done,
|
|
1112
|
+
* cancel, or leave. The day is recorded first, so restarts never walk twice.
|
|
1113
|
+
*/
|
|
1114
|
+
async function morningWalk({ scheduler = null, now = Date.now(), tz = timeZone() } = {}) {
|
|
1115
|
+
if (String(process.env.OC_DECISION_WALK || "").trim().toLowerCase() === "off") return [];
|
|
1116
|
+
const p = zonedParts(now, tz);
|
|
1117
|
+
const { h, min } = walkAt();
|
|
1118
|
+
const start = zonedTime(p.year, p.month, p.day, h, tz) + min * 60 * 1000;
|
|
1119
|
+
if (now < start || now >= start + WALK_WINDOW_MS) return [];
|
|
1120
|
+
const today = `${p.year}-${String(p.month).padStart(2, "0")}-${String(p.day).padStart(2, "0")}`;
|
|
1121
|
+
const meta = loadDecisionsMeta();
|
|
1122
|
+
if (meta.walkedOn === today) return [];
|
|
1123
|
+
meta.walkedOn = today;
|
|
1124
|
+
saveDecisionsMeta(meta);
|
|
1125
|
+
const groups = new Map();
|
|
1126
|
+
for (const d of [...openByHome(loadDecisions()).values()].flat()) {
|
|
1127
|
+
if (!d.origin || now - Date.parse(d.createdAt || now) <= WALK_MIN_AGE_MS) continue;
|
|
1128
|
+
const key = `${d.origin.project || ""}|${d.origin.threadId || ""}`;
|
|
1129
|
+
if (!groups.has(key)) groups.set(key, []);
|
|
1130
|
+
groups.get(key).push(d);
|
|
1131
|
+
}
|
|
1132
|
+
const woke = [];
|
|
1133
|
+
let i = 0;
|
|
1134
|
+
for (const [key, ds] of groups) {
|
|
1135
|
+
const prompt = [
|
|
1136
|
+
"Morning check of what's waiting on Sumeet from this conversation.",
|
|
1137
|
+
...ds.map((d) => `- ${d.id} · ${shortTitle(d.title, 100)} · asked ${waitedWords(d, now)} ago`),
|
|
1138
|
+
"For each item: if he did it himself or it was settled elsewhere, close it with open-claudia decide done <id> \"<what happened>\"; if it no longer applies, decide cancel <id>; otherwise leave it. Don't message him about this check. If nothing changed, reply exactly [quiet].",
|
|
1139
|
+
].join("\n");
|
|
1140
|
+
try {
|
|
1141
|
+
wakeOrigin(ds[0].origin, prompt, "Morning check: waiting on Sumeet", { scheduler, quiet: true, delayMs: 1500 + 30 * 1000 * i++ });
|
|
1142
|
+
woke.push(key);
|
|
1143
|
+
} catch (e) {
|
|
1144
|
+
console.error(`[cards] morning walk for ${key} failed:`, e.message);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
return woke;
|
|
1148
|
+
}
|
|
1149
|
+
|
|
734
1150
|
// ---------------------------------------------------------------------------
|
|
735
1151
|
// One reminder for a decision left waiting overnight
|
|
736
1152
|
|
|
@@ -754,6 +1170,12 @@ function zonedTime(year, month, day, hour, tz) {
|
|
|
754
1170
|
return guess - offset;
|
|
755
1171
|
}
|
|
756
1172
|
|
|
1173
|
+
function sameLocalDay(a, b, tz) {
|
|
1174
|
+
const x = zonedParts(typeof a === "number" ? a : Date.parse(a), tz);
|
|
1175
|
+
const y = zonedParts(b, tz);
|
|
1176
|
+
return x.year === y.year && x.month === y.month && x.day === y.day;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
757
1179
|
/** The first 09:00 local at least 8 hours after the decision was asked. */
|
|
758
1180
|
function reminderDueAt(sentAt, tz = timeZone()) {
|
|
759
1181
|
const earliest = Date.parse(sentAt) + REMIND_MIN_WAIT_MS;
|
|
@@ -780,7 +1202,9 @@ async function remindOpenDecisions({ send, readState = null, now = Date.now(), t
|
|
|
780
1202
|
&& now - Date.parse(c.sentAt) <= REMIND_MAX_AGE_MS
|
|
781
1203
|
&& now >= reminderDueAt(c.sentAt, tz)
|
|
782
1204
|
// a snoozed decision comes back through the queue at its own time
|
|
783
|
-
&& !(c.decisionId && queue.get(c.decisionId)?.status === "snoozed")
|
|
1205
|
+
&& !(c.decisionId && queue.get(c.decisionId)?.status === "snoozed")
|
|
1206
|
+
// asked again on a fresh card today: that was today's nudge
|
|
1207
|
+
&& !(c.decisionId && queue.get(c.decisionId)?.staleAskedAt && sameLocalDay(queue.get(c.decisionId).staleAskedAt, now, tz)));
|
|
784
1208
|
const reminded = [];
|
|
785
1209
|
for (const c of due) {
|
|
786
1210
|
if (readState) {
|
|
@@ -836,6 +1260,8 @@ function startDecisionPump(registry, intervalMs = 5 * 60 * 1000) {
|
|
|
836
1260
|
const tick = async () => {
|
|
837
1261
|
await pumpDecisions({ send, remind, updateCard }).catch((e) => console.error("[cards] decision pump failed:", e.message));
|
|
838
1262
|
await remindOpenDecisions({ send: remind, readState }).catch((e) => console.error("[cards] decision reminders failed:", e.message));
|
|
1263
|
+
await catchUpOwner({ send: remind }).catch((e) => console.error("[cards] catch-up failed:", e.message));
|
|
1264
|
+
await morningWalk({}).catch((e) => console.error("[cards] morning walk failed:", e.message));
|
|
839
1265
|
};
|
|
840
1266
|
pumpTimer = setInterval(tick, intervalMs);
|
|
841
1267
|
if (pumpTimer.unref) pumpTimer.unref();
|
|
@@ -845,6 +1271,8 @@ function startDecisionPump(registry, intervalMs = 5 * 60 * 1000) {
|
|
|
845
1271
|
}
|
|
846
1272
|
|
|
847
1273
|
module.exports = {
|
|
1274
|
+
queueOrder,
|
|
1275
|
+
daysToDue,
|
|
848
1276
|
CARDS_FILE,
|
|
849
1277
|
startDecisionPump,
|
|
850
1278
|
DECISIONS_FILE,
|
|
@@ -874,4 +1302,13 @@ module.exports = {
|
|
|
874
1302
|
pumpDecisions,
|
|
875
1303
|
handleDecisionResponse,
|
|
876
1304
|
cancelDecision,
|
|
1305
|
+
doneDecision,
|
|
1306
|
+
listDecisions,
|
|
1307
|
+
DECISIONS_META_FILE,
|
|
1308
|
+
loadDecisionsMeta,
|
|
1309
|
+
noteOwnerActivity,
|
|
1310
|
+
noteInboundForDecisions,
|
|
1311
|
+
catchUpOwner,
|
|
1312
|
+
morningWalk,
|
|
1313
|
+
OPEN_STATUSES,
|
|
877
1314
|
};
|
package/core/loopback.js
CHANGED
|
@@ -107,7 +107,7 @@ const JSON_KINDS = new Set([
|
|
|
107
107
|
"approval-request",
|
|
108
108
|
"tool-gate",
|
|
109
109
|
"say", "call-say", "call", "tts", "transcribe",
|
|
110
|
-
"card-send", "card-update", "card-show", "decide-add", "decide-list", "decide-cancel",
|
|
110
|
+
"card-send", "card-update", "card-show", "decide-add", "decide-list", "decide-cancel", "decide-done",
|
|
111
111
|
"owner-setup",
|
|
112
112
|
"view-send", "view-push",
|
|
113
113
|
]);
|
|
@@ -254,14 +254,30 @@ async function handleCardKind(res, kind, payload, { channelId, adapterId, adapte
|
|
|
254
254
|
|
|
255
255
|
// Decisions: the owner's own queue, asked one at a time in this room.
|
|
256
256
|
if (caller.owner !== true) return reply(res, 403, { error: "owner only" });
|
|
257
|
+
const decisionDeps = {
|
|
258
|
+
send: (home, card) => ((registry.findAdapter(home.adapter) || own).sendCard(home.channelId, card, { threadId: home.threadId || null })),
|
|
259
|
+
remind: (home, text, opts) => ((registry.findAdapter(home.adapter) || own).send(home.channelId, text, opts)),
|
|
260
|
+
updateCard: typeof own.updateCard === "function" ? (messageId, patch) => own.updateCard(messageId, patch) : null,
|
|
261
|
+
};
|
|
257
262
|
if (kind === "decide-list") {
|
|
258
|
-
|
|
259
|
-
return reply(res, 200, { ok: true, decisions: all.filter((d) => ["queued", "asked", "snoozed"].includes(d.status) || Date.now() - Date.parse(d.decidedAt || d.cancelledAt || 0) < 7 * 86400000) });
|
|
263
|
+
return reply(res, 200, { ok: true, ...cards.listDecisions({ all: payload.all === true }) });
|
|
260
264
|
}
|
|
261
265
|
if (kind === "decide-cancel") {
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
266
|
+
const d = await cards.cancelDecision(String(payload.id || ""), { updateCard: decisionDeps.updateCard });
|
|
267
|
+
if (!d) return reply(res, 404, { error: "no such decision" });
|
|
268
|
+
// Withdrawn: the next item in that chat goes out at once.
|
|
269
|
+
if (d.home && typeof own.sendCard === "function") {
|
|
270
|
+
await cards.pumpDecisions({ ...decisionDeps, homeKey: `${d.home.adapter}|${d.home.channelId}` }).catch((e) => console.error("[loopback] asking the next decision failed:", e.message));
|
|
271
|
+
}
|
|
272
|
+
return reply(res, 200, { ok: true, decision: d });
|
|
273
|
+
}
|
|
274
|
+
if (kind === "decide-done") {
|
|
275
|
+
// He did it himself, or answered in words elsewhere.
|
|
276
|
+
const note = payload.note === undefined || payload.note === null ? null : String(payload.note);
|
|
277
|
+
const d = await cards.doneDecision(String(payload.id || ""), note, {
|
|
278
|
+
updateCard: decisionDeps.updateCard,
|
|
279
|
+
send: typeof own.sendCard === "function" ? decisionDeps.send : null,
|
|
280
|
+
remind: decisionDeps.remind,
|
|
265
281
|
});
|
|
266
282
|
if (!d) return reply(res, 404, { error: "no such decision" });
|
|
267
283
|
return reply(res, 200, { ok: true, decision: d });
|
|
@@ -269,20 +285,34 @@ async function handleCardKind(res, kind, payload, { channelId, adapterId, adapte
|
|
|
269
285
|
if (kind === "decide-add") {
|
|
270
286
|
if (typeof own.sendCard !== "function") return reply(res, 501, { error: "decision cards need Kazee Chat" });
|
|
271
287
|
if (!caller.userId) return reply(res, 400, { error: "cannot tell your Kazee user id from this turn" });
|
|
272
|
-
|
|
273
|
-
|
|
288
|
+
// Too long is shortened with a warning, not refused (Mail thread, 14 Nov:
|
|
289
|
+
// an 81-character label failed the whole call).
|
|
290
|
+
const shortened = [];
|
|
291
|
+
const fit = (v, max, what) => {
|
|
292
|
+
const t = String(v || "").trim();
|
|
293
|
+
if (t.length <= max) return t;
|
|
294
|
+
shortened.push(what);
|
|
295
|
+
return `${t.slice(0, max - 1).trimEnd()}…`;
|
|
296
|
+
};
|
|
297
|
+
const title = fit(payload.title, 140, "the title (140 characters)");
|
|
298
|
+
if (!title) return reply(res, 400, { error: "a decision needs a title" });
|
|
274
299
|
const options = Array.isArray(payload.options) ? payload.options : [];
|
|
275
300
|
if (options.length < 2 || options.length > 4) return reply(res, 400, { error: "a decision needs 2-4 options" });
|
|
276
301
|
for (const o of options) {
|
|
277
|
-
if (!o || !DECISION_ID_RE.test(String(o.id || "")) || !String(o.label || "").trim()
|
|
278
|
-
return reply(res, 400, { error: "each option needs an id (letters, digits, - or _) and a label
|
|
302
|
+
if (!o || !DECISION_ID_RE.test(String(o.id || "")) || !String(o.label || "").trim()) {
|
|
303
|
+
return reply(res, 400, { error: "each option needs an id (letters, digits, - or _) and a label" });
|
|
279
304
|
}
|
|
280
305
|
}
|
|
306
|
+
const due = payload.due ? String(payload.due).trim() : null;
|
|
307
|
+
if (due && (!/^\d{4}-\d{2}-\d{2}$/.test(due) || Number.isNaN(Date.parse(`${due}T00:00:00Z`)))) {
|
|
308
|
+
return reply(res, 400, { error: `--due needs a date as YYYY-MM-DD (got ${due})` });
|
|
309
|
+
}
|
|
281
310
|
const body = payload.body ? String(payload.body).slice(0, 600) : null;
|
|
282
311
|
const d = cards.addDecision({
|
|
283
312
|
title,
|
|
284
313
|
body,
|
|
285
|
-
options: options.map((o) => ({ id: String(o.id), label:
|
|
314
|
+
options: options.map((o) => ({ id: String(o.id), label: fit(o.label, 80, `option ${o.id} (80 characters)`) })),
|
|
315
|
+
due,
|
|
286
316
|
pick: payload.pick ? String(payload.pick) : null,
|
|
287
317
|
tag: payload.tag ? String(payload.tag).slice(0, 24) : null,
|
|
288
318
|
// Asked where the conversation is: the thread that queued it (Sumeet,
|
|
@@ -293,17 +323,14 @@ async function handleCardKind(res, kind, payload, { channelId, adapterId, adapte
|
|
|
293
323
|
});
|
|
294
324
|
let asked = [];
|
|
295
325
|
try {
|
|
296
|
-
asked = await cards.pumpDecisions({
|
|
297
|
-
send: (home, card) => ((registry.findAdapter(home.adapter) || own).sendCard(home.channelId, card, { threadId: home.threadId || null })),
|
|
298
|
-
remind: (home, text, opts) => ((registry.findAdapter(home.adapter) || own).send(home.channelId, text, opts)),
|
|
299
|
-
updateCard: (messageId, patch) => (typeof own.updateCard === "function" ? own.updateCard(messageId, patch) : null),
|
|
300
|
-
});
|
|
326
|
+
asked = await cards.pumpDecisions({ ...decisionDeps, updateCard: decisionDeps.updateCard || (() => null) });
|
|
301
327
|
} catch (e) {
|
|
302
|
-
const waiting = cards.loadDecisions().filter((x) =>
|
|
303
|
-
return reply(res, 200, { ok: true, decision: d, askedNow: false, waiting, warning: `queued, but asking failed: ${e.message}` });
|
|
328
|
+
const waiting = cards.loadDecisions().filter((x) => cards.OPEN_STATUSES.includes(x.status)).length;
|
|
329
|
+
return reply(res, 200, { ok: true, decision: d, askedNow: false, waiting, warning: [shortened.length ? `shortened ${shortened.join(", ")}` : null, `queued, but asking failed: ${e.message}`].filter(Boolean).join("; ") });
|
|
304
330
|
}
|
|
305
|
-
const open = cards.loadDecisions().filter((x) =>
|
|
306
|
-
|
|
331
|
+
const open = cards.loadDecisions().filter((x) => cards.OPEN_STATUSES.includes(x.status));
|
|
332
|
+
const warning = shortened.length ? `shortened ${shortened.join(", ")}` : undefined;
|
|
333
|
+
return reply(res, 200, { ok: true, decision: d, askedNow: asked.some((a) => a.decisionId === d.id), waiting: open.length, warning });
|
|
307
334
|
}
|
|
308
335
|
return reply(res, 400, { error: `unknown card command ${kind}` });
|
|
309
336
|
}
|
package/core/router.js
CHANGED
|
@@ -111,6 +111,10 @@ async function onMessage(envelope) {
|
|
|
111
111
|
// owner's canonical bucket on first contact, before any state is read.
|
|
112
112
|
try { autoLinkOwnerChannel(envelope.adapter?.type, envelope.channelId); } catch (e) {}
|
|
113
113
|
|
|
114
|
+
// The owner writing in a chat his decisions wait in: the catch-up after
|
|
115
|
+
// 24 h of him quiet counts from here (core/cards.js).
|
|
116
|
+
try { require("./cards").noteInboundForDecisions(envelope); } catch (e) {}
|
|
117
|
+
|
|
114
118
|
// Safe mode: only the diagnosis/rescue command set runs; everything else
|
|
115
119
|
// gets a short banner pointing at /safemode. Preempts wizards, media and
|
|
116
120
|
// agent turns — nothing may write memory or spawn a provider here.
|
package/core/run-context.js
CHANGED
|
@@ -235,6 +235,7 @@ function captureRunContext({
|
|
|
235
235
|
attachSession: !liveCall && opts.attachSession !== false,
|
|
236
236
|
occurrenceId: opts.occurrenceId || null,
|
|
237
237
|
scheduledJobId: opts.scheduledJobId || null,
|
|
238
|
+
quiet: opts.quiet === true,
|
|
238
239
|
originRunId: opts.originRunId || null,
|
|
239
240
|
// Set on a turn that core/limit-pickup.js re-runs after a usage limit
|
|
240
241
|
// reset: { originalPrompt, firstHitAt, attempts }.
|
package/core/runner.js
CHANGED
|
@@ -2175,6 +2175,9 @@ async function finalizeStreamPreview(lane, result, runContext = null) {
|
|
|
2175
2175
|
// A model that later sees them in its history can answer a wake-up with the same
|
|
2176
2176
|
// words. That is never an answer, so it is never posted.
|
|
2177
2177
|
const PLACEHOLDER_REPLY = /^no response requested\.?$/i;
|
|
2178
|
+
// A quiet scheduled run (core/cards.js: a dropped decision, the morning walk)
|
|
2179
|
+
// says "[quiet]" when there is nothing to tell; that is never posted.
|
|
2180
|
+
const QUIET_REPLY = /^\[quiet\]$/i;
|
|
2178
2181
|
|
|
2179
2182
|
// "⚠️ Claude: 82% of the 5-hour limit is used. It resets at 17:20." goes out
|
|
2180
2183
|
// after the answer that crossed it. Never to an outside contact (plan
|
|
@@ -2249,6 +2252,11 @@ async function deliverForegroundResult(result, runContext, state = currentState(
|
|
|
2249
2252
|
return { ok: true, held: true, suppressed: "placeholder" };
|
|
2250
2253
|
}
|
|
2251
2254
|
|
|
2255
|
+
if (result.ok && runContext.quiet === true && QUIET_REPLY.test(finalText.trim())) {
|
|
2256
|
+
console.log(`[runner] quiet run ${runContext.runId || "?"}: nothing to say, nothing posted`);
|
|
2257
|
+
return { ok: true, held: true, suppressed: "quiet" };
|
|
2258
|
+
}
|
|
2259
|
+
|
|
2252
2260
|
if (result.ok) {
|
|
2253
2261
|
let guarded = false;
|
|
2254
2262
|
try { guarded = require("./relationship").isCurrentSpeakerGuarded(); } catch (error) {
|
package/core/scheduler.js
CHANGED
|
@@ -1017,6 +1017,9 @@ function createSchedulerService(dependencies = {}) {
|
|
|
1017
1017
|
occurrenceId: job.occurrenceId,
|
|
1018
1018
|
scheduledJobId: job.id,
|
|
1019
1019
|
originRunId: job.originRunId || null,
|
|
1020
|
+
// A quiet job (a dropped decision, the morning walk) posts no
|
|
1021
|
+
// "Wakeup:" line, and its reply of exactly [quiet] is not posted.
|
|
1022
|
+
...(job.quiet === true ? { quiet: true } : {}),
|
|
1020
1023
|
// A run that waited for a limit's reset: a new hit says "still used up"
|
|
1021
1024
|
// and counts towards the most waits (limit-pickup MAX_ATTEMPTS).
|
|
1022
1025
|
...(Number(job.limitWaitCount) > 0 ? {
|
|
@@ -1054,7 +1057,7 @@ function createSchedulerService(dependencies = {}) {
|
|
|
1054
1057
|
? (job.watchExpired ? `⏰ Watch "${job.label}" timed out without triggering — waking anyway`
|
|
1055
1058
|
: job.watchCheckTimingOut ? `⚠️ Watch "${job.label}": its check timed out ${WATCH_TIMEOUT_ALARM_AFTER} times in a row`
|
|
1056
1059
|
: `🔔 Watch "${job.label}" triggered`)
|
|
1057
|
-
: job.handoff ? "" : `Wakeup: ${job.label}`;
|
|
1060
|
+
: job.handoff || job.quiet === true ? "" : `Wakeup: ${job.label}`;
|
|
1058
1061
|
// Say so when the run is NOT on the provider this room is set to: an
|
|
1059
1062
|
// unexplained error from a provider the room never selected is worse than
|
|
1060
1063
|
// the outage itself.
|
package/core/system-prompt.js
CHANGED
|
@@ -320,7 +320,7 @@ Reply normally in your final answer. To send a file, image, or voice clip back t
|
|
|
320
320
|
- \`open-claudia say "<text>"\` — synthesize the text and send it as a voice note (use this when asked for a voice reply)
|
|
321
321
|
- \`open-claudia card send <spec.json|->\` — (Kazee Chat) a decision, choice (poll), rating, slider, form or report card; answers wake this conversation. Cards are built from pieces: blocks (text, stats, bars, facts, table) and form questions (choice, rating, text, tick, date, slots, rows with a tick/date/option per row, board). When a reply would list items for someone to tick, date, pick, approve or move, or asks one person 2+ questions, send ONE card built from pieces instead of a text list plus a separate question; \`open-claudia card pieces\` lists them, \`card example <name>\` prints a spec, \`card update <id> --close | --note "..."\` changes one you sent.
|
|
322
322
|
- \`open-claudia view send <page.html> --title "<title>" [--handler <file>]\` — (Kazee Chat) an interactive web page, sent as a card with an Open button; it opens in a walled-off panel. The page can load libraries from cdn.jsdelivr.net, unpkg.com, cdnjs.cloudflare.com or esm.sh but cannot fetch data from the internet, so put the data in the page. It talks to you with \`kazee.send(name, data)\` (wakes this conversation, or a --handler script answers fast) and \`kazee.on(fn)\` (gets what you \`view push\`); \`open-claudia view example\` shows a page and a handler.
|
|
323
|
-
- \`open-claudia decide "<question>" --option yes="..." --option no="..." [--pick yes] [--body "why"]\` — (owner, Kazee Chat) queue a decision for the owner as a card: one is out at a time, his answer wakes this conversation. Prefer it to asking a yes/no question in text when work waits on his decision.
|
|
323
|
+
- \`open-claudia decide "<question>" --option yes="..." --option no="..." [--pick yes] [--body "why"] [--due YYYY-MM-DD]\` — (owner, Kazee Chat) queue a decision for the owner as a card: one is out at a time, his answer wakes this conversation; give --due when it is needed by a date (a vote, a deadline) so it moves up as the date nears; items wait until he answers, snoozes or drops them; tick off what he did himself with \`open-claudia decide done <id>\`. Information with nothing to decide is told once, never queued. Prefer it to asking a yes/no question in text when work waits on his decision.
|
|
324
324
|
Never print or embed bot tokens in prompts, commands, logs, or messages.
|
|
325
325
|
${hasVoice || hasEars ? `
|
|
326
326
|
## Speech services (TTS / STT)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.5.
|
|
3
|
+
"version": "3.5.82",
|
|
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-codex-output-schema.js && node test-codex-device-login.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-effort-reset.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-group-status-notes.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 && node test-call-flags.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-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-test-run-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-read-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-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-owner-cards-dm.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stop-queue.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-quiet-defaults.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-handoff.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-cost-by-part.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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 && node test-kazee-cards.js && node test-card-blocks.js && node test-decision-upkeep.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-kazee-bot-handoffs.js && node test-placeholder-reply.js && node test-media-speech.js && node test-speech-voices.js && node test-tts-voice.js && node test-passive-replies-router.js && node test-loopback-cards.js && node test-task-injection.js && node test-speaker-extras.js && node test-token-diet.js && node test-dream-tasks.js && node test-task-archive.js && node test-cron-tolerance.js && node test-dream-schedule.js && node test-job-failures.js && node test-scheduled-placeholder.js && node test-lessons-since.js && node test-pack-reinject.js && node test-turn-journal.js && node test-kazee-catchup.js && node test-usage-limits.js && node test-limit-waits.js && node test-signin-race-retry.js && node test-auth-recheck.js && node test-signin-pickup.js && node test-limit-pickup.js && node test-browser-view.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-view.js && node test-browser-address.js && node test-browser-control.js && node test-browser-drive.js && node test-browser-teach.js && node test-kazee-bot-apps.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-app.js && node test-safe-filename.js && node test-media-filename-paths.js && node test-router-document-guard.js && OPEN_CLAUDIA_TEST=1 node test-web-dashboard-escape.js && OPEN_CLAUDIA_TEST=1 node test-web-bad-requests.js && OPEN_CLAUDIA_TEST=1 node test-kazee-download-limit.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-auth-routes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-otp-link-preview.js && node test-call-quick-voice.js && node test-call-run-stop.js && node test-call-stopped-transcript.js && node test-call-greeting-context.js && node test-call-camera.js && node test-switchboard-calls.js && node test-switchboard-supervisor.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-topic-lessons.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-env-pins.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-session-core.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-related.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-decisions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-external.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-introspection-retry.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-watch-every-cron.js && node test-rust-cli-parity.js && OPEN_CLAUDIA_TEST=1 node test-bot-acts-for-owner.js && node test-walker-idle-stop.js && node test-usage-history-lazy.js && node test-node-flags-entrypoint.js && node test-recall-engine-env.js && node test-review-queue.js && node test-lazy-heavy-libs.js && node test-lessons-meta-keep.js && node test-views.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-transcript-room-scope.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-test-run-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-read-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-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-owner-cards-dm.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-kazee-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-thread-lanes.js && node test-thread-titles.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-stop.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-stop-queue.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-quiet-defaults.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-loopback-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-thread-handoff.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-threads.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-cost-by-part.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-run-attest.js && node test-sandbox-probe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-bot-endpoint.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-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-cli-owner-gates.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-watch-check-env.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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 && node test-kazee-cards.js && node test-card-blocks.js && node test-decision-upkeep.js && node test-waiting-on-you.js && node test-passive-replies.js && node test-kazee-bot-loops.js && node test-kazee-bot-handoffs.js && node test-placeholder-reply.js && node test-media-speech.js && node test-speech-voices.js && node test-tts-voice.js && node test-passive-replies-router.js && node test-loopback-cards.js && node test-task-injection.js && node test-speaker-extras.js && node test-token-diet.js && node test-dream-tasks.js && node test-task-archive.js && node test-cron-tolerance.js && node test-dream-schedule.js && node test-job-failures.js && node test-scheduled-placeholder.js && node test-lessons-since.js && node test-pack-reinject.js && node test-turn-journal.js && node test-kazee-catchup.js && node test-usage-limits.js && node test-limit-waits.js && node test-signin-race-retry.js && node test-auth-recheck.js && node test-signin-pickup.js && node test-limit-pickup.js && node test-browser-view.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-view.js && node test-browser-address.js && node test-browser-control.js && node test-browser-drive.js && node test-browser-teach.js && node test-kazee-bot-apps.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-browser-app.js && node test-safe-filename.js && node test-media-filename-paths.js && node test-router-document-guard.js && OPEN_CLAUDIA_TEST=1 node test-web-dashboard-escape.js && OPEN_CLAUDIA_TEST=1 node test-web-bad-requests.js && OPEN_CLAUDIA_TEST=1 node test-kazee-download-limit.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-auth-routes.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test node test-web-otp-link-preview.js && node test-call-quick-voice.js && node test-call-run-stop.js && node test-call-stopped-transcript.js && node test-call-greeting-context.js && node test-call-camera.js && node test-switchboard-calls.js && node test-switchboard-supervisor.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-topic-lessons.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-env-pins.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-session-core.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-transcript-related.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-decisions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-external.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-introspection-retry.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-watch-every-cron.js && node test-rust-cli-parity.js && OPEN_CLAUDIA_TEST=1 node test-bot-acts-for-owner.js && node test-walker-idle-stop.js && node test-usage-history-lazy.js && node test-node-flags-entrypoint.js && node test-recall-engine-env.js && node test-review-queue.js && node test-lazy-heavy-libs.js && node test-lessons-meta-keep.js && node test-views.js"
|
|
15
15
|
},
|
|
16
16
|
"files": [
|
|
17
17
|
"bot.js",
|
|
@@ -671,6 +671,8 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
671
671
|
// (Sumeet 12 Nov 07:53 EAT, card 6af54622: "Build 3.5.77 with the 3 pieces, test on one bot, roll on your word").
|
|
672
672
|
// 3.5.80 = 3.5.79 + the owner switch-on card lands in the thread that asked for it
|
|
673
673
|
// (Sumeet 13 Nov 14:05 EAT: "why was that on the main chat not on the thread? Is that a bug?").
|
|
674
|
+
// 3.5.82 = 3.5.81 + "Waiting on you" for every thread: Drop, decide done, fresh card after 3 days, catch-up, morning walk, decide list, --due
|
|
675
|
+
// (Sumeet 14 Nov 15:18 EAT, card 6af83e0b: "Yes, release it").
|
|
674
676
|
// 3.5.81 = 3.5.80 + a personal bot may send a Kazee Chat message as its owner when the owner asks (Chat shows "Owner via Bot" with the AI mark)
|
|
675
677
|
// 3.5.79 = 3.5.78 + a reply refused as signed out is tried again every minute, quietly,
|
|
676
678
|
// up to 30 times (Sumeet 13 Nov 13:17 EAT, during Anthropic's sign-in incident:
|
|
@@ -684,7 +686,7 @@ assert.match(pkg.description, /provider-agnostic coding-agent harness/i);
|
|
|
684
686
|
// 3.5.72 = 3.5.71 + the Mac memory fixes of 22-28 Oct:
|
|
685
687
|
// topic lessons ride with their pack, .env recall pins, prompt fixes, past
|
|
686
688
|
// chats reach recall, settled decisions, /engine x|mem0, nightly retry.
|
|
687
|
-
assert.strictEqual(pkg.version, "3.5.
|
|
689
|
+
assert.strictEqual(pkg.version, "3.5.82", "release version must remain unchanged without explicit approval");
|
|
688
690
|
for (const keyword of ["claude", "codex", "coding-agent", "provider-agnostic"]) {
|
|
689
691
|
assert.ok(pkg.keywords.includes(keyword), `package keyword missing: ${keyword}`);
|
|
690
692
|
}
|
|
@@ -516,6 +516,20 @@ async function firetimeSelectionProbe(createSchedulerService) {
|
|
|
516
516
|
assert.match(switched.runCalls[0].prompt, /claude/i);
|
|
517
517
|
}
|
|
518
518
|
|
|
519
|
+
// A quiet job (core/cards.js: a dropped decision, the morning walk) posts no
|
|
520
|
+
// "Wakeup:" line and runs as a quiet run (its [quiet] reply is not posted).
|
|
521
|
+
async function quietJobProbe(createSchedulerService) {
|
|
522
|
+
const ok = { ok: true, status: "succeeded", persistence: { transcript: { ok: true }, usage: { ok: true }, session: { ok: true } }, delivery: { ok: true } };
|
|
523
|
+
const quiet = schedulerHarness(createSchedulerService, [baseJob({ id: "quiet-wake", quiet: true })], { outcomes: [ok] });
|
|
524
|
+
assert.strictEqual((await quiet.service.fireJob("quiet-wake")).ok, true);
|
|
525
|
+
assert.deepStrictEqual(quiet.sends, [], "no Wakeup: line");
|
|
526
|
+
assert.strictEqual(quiet.admissions[0].opts.quiet, true);
|
|
527
|
+
const loud = schedulerHarness(createSchedulerService, [baseJob({ id: "loud-wake" })], { outcomes: [ok] });
|
|
528
|
+
await loud.service.fireJob("loud-wake");
|
|
529
|
+
assert.deepStrictEqual(loud.sends, ["Wakeup: fixture wakeup"]);
|
|
530
|
+
assert.strictEqual(loud.admissions[0].opts.quiet, undefined);
|
|
531
|
+
}
|
|
532
|
+
|
|
519
533
|
async function duplicateFireProbe(createSchedulerService) {
|
|
520
534
|
const terminal = deferred();
|
|
521
535
|
const h = schedulerHarness(createSchedulerService, [baseJob({ id: "single-flight" })], {
|
|
@@ -1758,6 +1772,7 @@ async function main() {
|
|
|
1758
1772
|
migrationIntegrationProbe(jobsApi, migrationApi);
|
|
1759
1773
|
await firetimeSelectionProbe(schedulerApi.createSchedulerService);
|
|
1760
1774
|
await duplicateFireProbe(schedulerApi.createSchedulerService);
|
|
1775
|
+
await quietJobProbe(schedulerApi.createSchedulerService);
|
|
1761
1776
|
await failureAndRetryProbe(schedulerApi.createSchedulerService);
|
|
1762
1777
|
await detachedHistoricalBranchProbe(schedulerApi.createSchedulerService);
|
|
1763
1778
|
await historyLineageProbe(schedulerApi.createSchedulerService);
|