@inetafrica/open-claudia 3.0.35 → 3.0.40
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/cli.js +24 -0
- package/bin/pack.js +19 -2
- package/bin/schedule.js +71 -2
- package/core/actions.js +2 -2
- package/core/auth-flow.js +5 -3
- package/core/credential-detect.js +55 -0
- package/core/handlers.js +73 -0
- package/core/loopback.js +52 -0
- package/core/packs.js +51 -1
- package/core/recall/discoverer.js +123 -20
- package/core/recall/kpi.js +23 -2
- package/core/recall/tuning.js +18 -0
- package/core/router.js +53 -9
- package/core/scheduler.js +105 -5
- package/core/system-prompt.js +71 -27
- package/package.json +9 -3
- package/test-cokept-warming.js +117 -0
- package/test-credential-capture.js +109 -0
- package/test-extractive-injection.js +70 -0
- package/test-pack-search.js +79 -0
- package/test-provider-language.js +28 -9
- package/test-provider-scheduler.js +90 -0
- package/test-recall-evolution.js +9 -1
- package/test-score-gate.js +104 -0
- package/test-walker-score.js +69 -0
package/core/system-prompt.js
CHANGED
|
@@ -241,6 +241,12 @@ Wake-ups and crons (real schedulers, not hallucinations — use them instead of
|
|
|
241
241
|
- \`open-claudia cron-list\` — list all wakeups + crons on this channel.
|
|
242
242
|
- \`open-claudia cron-remove <id>\` — cancel one.
|
|
243
243
|
|
|
244
|
+
Watches (a silent background monitor — poll a condition WITHOUT waking every tick; wake only when it actually happens). Use this instead of a minute-by-minute cron whenever you'd otherwise fire a turn just to check "is it done yet?" and stay quiet most times:
|
|
245
|
+
- \`open-claudia watch-add "<check-cmd>" "<wake-prompt>"\` — the scheduler polls \`<check-cmd>\` as a shell command (default every minute). The poll is SILENT: nothing reaches the chat while the check keeps failing. The instant \`<check-cmd>\` exits 0, you wake in this same conversation with \`<wake-prompt>\`, exactly like a wakeup. Non-zero exit = still waiting = nothing happens. Default is ONE-SHOT (fires once, then removes itself); add \`--repeat\` to keep polling after each hit. \`--every "<5-field cron>"\` changes the poll cadence.
|
|
246
|
+
- \`open-claudia watch-list\` / \`open-claudia watch-remove <id>\` — inspect/cancel watches on this channel.
|
|
247
|
+
- The owner has a global master switch: \`/watch off\` pauses ALL watch polling (and \`/watch on\` resumes). If \`watch-list\` shows the switch OFF, watches won't fire until it's back on.
|
|
248
|
+
- Example: after kicking off a long external job, \`open-claudia watch-add "test -f /tmp/build-done || exit 1" "the build finished — verify and report"\` beats a wakeup guess, because it fires the moment it's actually done, not on a fixed timer. (Never use a watch to poll harness-tracked work you started — that notifies you automatically. Watches are for EXTERNAL state the harness can't see.)
|
|
249
|
+
|
|
244
250
|
The selected coding-agent provider may expose native scheduling tools. Do NOT use those for Open Claudia wakeups or crons: a provider runs as a per-turn subprocess, so its timers do not belong to the long-running bot. Only the \`open-claudia\` CLI commands above are authoritative and survive.
|
|
245
251
|
|
|
246
252
|
Persistent todo list with plans + subtasks (per channel; survives compaction and restart).
|
|
@@ -465,6 +471,7 @@ function buildDynamicContextBlock(opts = {}) {
|
|
|
465
471
|
const packsInjectedFor = new Map(); // `${adapterId}:${channelId}:${dir}` -> `${sessionId}:${updated}`
|
|
466
472
|
const PACK_INJECT_MAX_CHARS = 4000;
|
|
467
473
|
const DEFAULT_MEMORY_RECALL_MAX_CHARS = 9000;
|
|
474
|
+
const EXTRACTIVE_LINES_PER_SECTION = 4;
|
|
468
475
|
|
|
469
476
|
// Progressive disclosure: when on, a matched pack is injected as a cheap
|
|
470
477
|
// teaser (Stance + State + a pointer) rather than its full body. Heavy
|
|
@@ -533,6 +540,7 @@ function consumeLastInjected() {
|
|
|
533
540
|
}
|
|
534
541
|
|
|
535
542
|
function formatPackForContext(pack, packsLib, opts = {}) {
|
|
543
|
+
const extractive = !!opts.extractive;
|
|
536
544
|
const headline = !!opts.headline;
|
|
537
545
|
const progressive = !!opts.progressive;
|
|
538
546
|
const clip = (s, n) => (s.length > n ? s.slice(0, n) + "\n…[truncated — read the full pack file]" : s);
|
|
@@ -547,30 +555,61 @@ function formatPackForContext(pack, packsLib, opts = {}) {
|
|
|
547
555
|
const childLines = children.map((c) => `- ${c.dir}: ${c.description || c.name}`).join("\n");
|
|
548
556
|
parts.push(`Sub-packs (dig deeper with \`open-claudia pack show <dir>\`):\n${childLines}`);
|
|
549
557
|
}
|
|
550
|
-
//
|
|
551
|
-
//
|
|
552
|
-
//
|
|
553
|
-
//
|
|
554
|
-
//
|
|
555
|
-
//
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
const
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
558
|
+
// Extractive mode (Defect-A fix): the old headline showed Stance only, so a
|
|
559
|
+
// fact living in State/Procedure/Journal never loaded even when it was the
|
|
560
|
+
// whole reason the pack matched. Here we surface Stance verbatim (it carries
|
|
561
|
+
// the always-on rules/prefs) PLUS the actual lines from anywhere in the pack
|
|
562
|
+
// that fuzzy-matched this message, tagged by section — verbatim, never a
|
|
563
|
+
// paraphrase. Progressive/headline/full below are the pre-extractive modes,
|
|
564
|
+
// still used by the classic engine and as the fallback when no lines matched.
|
|
565
|
+
if (extractive) {
|
|
566
|
+
const stance = (pack.sections.Stance || "").trim();
|
|
567
|
+
if (stance) parts.push(`#### Stance\n${stance}`);
|
|
568
|
+
const stanceLow = stance.toLowerCase();
|
|
569
|
+
const bySection = new Map();
|
|
570
|
+
for (const h of (Array.isArray(opts.matchedLines) ? opts.matchedLines : [])) {
|
|
571
|
+
if (!h || h.section === "Stance") continue; // Stance already shown in full
|
|
572
|
+
const text = String(h.text || "").trim();
|
|
573
|
+
if (!text) continue;
|
|
574
|
+
if (stanceLow && stanceLow.includes(text.toLowerCase())) continue; // dedupe vs Stance
|
|
575
|
+
if (!bySection.has(h.section)) bySection.set(h.section, []);
|
|
576
|
+
const arr = bySection.get(h.section);
|
|
577
|
+
if (arr.length < EXTRACTIVE_LINES_PER_SECTION && !arr.includes(text)) arr.push(text);
|
|
578
|
+
}
|
|
579
|
+
let matchedAny = false;
|
|
580
|
+
for (const section of ["Procedure", "State", "Journal"]) {
|
|
581
|
+
const arr = bySection.get(section);
|
|
582
|
+
if (!arr || !arr.length) continue;
|
|
583
|
+
matchedAny = true;
|
|
584
|
+
parts.push(`#### ${section} (matched)\n${arr.map((t) => (/^\s*[-*]/.test(t) ? t : `- ${t}`)).join("\n")}`);
|
|
585
|
+
}
|
|
586
|
+
parts.push(`#### More\n${matchedAny ? "Showing Stance + the lines matched to this message." : "Showing Stance."} Full pack (State/Procedure/Journal): \`open-claudia pack show ${pack.dir}\`.`);
|
|
571
587
|
} else {
|
|
572
|
-
|
|
573
|
-
|
|
588
|
+
// Progressive mode injects only the live sections (Stance = how to think,
|
|
589
|
+
// State = where we are) plus a pointer; full mode also inlines Procedure
|
|
590
|
+
// and the recent Journal.
|
|
591
|
+
// Headline = description + Stance only (Stance carries hard rules/prefs that
|
|
592
|
+
// must always apply when the topic comes up). Everything else is pulled on
|
|
593
|
+
// demand. Progressive = Stance + State. Full = Stance + Procedure + State.
|
|
594
|
+
const sections = headline
|
|
595
|
+
? ["Stance"]
|
|
596
|
+
: progressive
|
|
597
|
+
? ["Stance", "State"]
|
|
598
|
+
: ["Stance", "Procedure", "State"];
|
|
599
|
+
for (const section of sections) {
|
|
600
|
+
const body = (pack.sections[section] || "").trim();
|
|
601
|
+
if (body) parts.push(`#### ${section}\n${body}`);
|
|
602
|
+
}
|
|
603
|
+
if (headline) {
|
|
604
|
+
const hidden = ["State", "Procedure", "Journal"].filter((s) => (pack.sections[s] || "").trim());
|
|
605
|
+
if (hidden.length) parts.push(`#### More\n${hidden.join(" + ")} not shown — run \`open-claudia pack show ${pack.dir}\` to read them.`);
|
|
606
|
+
} else if (progressive) {
|
|
607
|
+
const hasMore = (pack.sections.Procedure || "").trim() || (pack.sections.Journal || "").trim();
|
|
608
|
+
if (hasMore) parts.push(`#### More\nProcedure + Journal not shown — run \`open-claudia pack show ${pack.dir}\` to read them.`);
|
|
609
|
+
} else {
|
|
610
|
+
const journal = (pack.sections.Journal || "").trim().split("\n").filter(Boolean).slice(-10).join("\n");
|
|
611
|
+
if (journal) parts.push(`#### Journal (recent)\n${journal}`);
|
|
612
|
+
}
|
|
574
613
|
}
|
|
575
614
|
// Pack↔tool usages: concrete reusable-tool invocations recorded while this
|
|
576
615
|
// pack was active, so the agent sees HOW a tool was used in this topic (not
|
|
@@ -597,7 +636,9 @@ function buildPackBlock(matches, budget, opts = {}) {
|
|
|
597
636
|
const channelId = currentChannelId();
|
|
598
637
|
const runIdentity = promptDedupeIdentity(opts);
|
|
599
638
|
const progressive = packProgressive();
|
|
639
|
+
const extractive = opts.extractive === true;
|
|
600
640
|
const headline = recallHeadline();
|
|
641
|
+
const matchedByDir = opts.matchedLines instanceof Map ? opts.matchedLines : new Map();
|
|
601
642
|
const collector = opts.recallCollector || lastInjected;
|
|
602
643
|
const blocks = [];
|
|
603
644
|
const used = [];
|
|
@@ -613,7 +654,7 @@ function buildPackBlock(matches, budget, opts = {}) {
|
|
|
613
654
|
// as the task tree), so the pack survives compaction without paying
|
|
614
655
|
// to re-stamp an anchor on every intervening turn.
|
|
615
656
|
if (packsInjectedFor.get(key) === stamp) continue;
|
|
616
|
-
const block = formatPackForContext(pack, packsLib, { progressive, headline });
|
|
657
|
+
const block = formatPackForContext(pack, packsLib, { progressive, headline, extractive, matchedLines: extractive ? (matchedByDir.get(m.dir) || []) : undefined });
|
|
617
658
|
if (!tryUseRecallBudget(budget, block)) continue;
|
|
618
659
|
packsInjectedFor.set(key, stamp);
|
|
619
660
|
collector.packs.push(pack.name || m.dir);
|
|
@@ -621,7 +662,9 @@ function buildPackBlock(matches, budget, opts = {}) {
|
|
|
621
662
|
}
|
|
622
663
|
if (used.length > 0) setImmediate(() => { try { packsLib.touchUsed(used); } catch (e) {} });
|
|
623
664
|
if (blocks.length === 0) return "";
|
|
624
|
-
const intro =
|
|
665
|
+
const intro = extractive
|
|
666
|
+
? `Long-term topic context auto-matched to this message. Each pack shows its Stance (always-on rules/prefs) plus the specific lines that matched — verbatim, not a summary. These are facts already recalled for you; pull the full State/Procedure/Journal with \`open-claudia pack show <dir>\` when you need more. If the user asked for a skill by name, \`pack show\` the matching pack to load its Procedure. Source files live under ${packsLib.PACKS_DIR}/<dir>/PACK.md — read or edit them directly when deeper context or a correction is needed.`
|
|
667
|
+
: headline
|
|
625
668
|
? `Long-term topic context auto-matched to this message — these are *headlines* (description + Stance only), not full packs. Decide which are actually relevant and pull the full State/Procedure/Journal on demand with \`open-claudia pack show <dir>\`. If the user asked for a skill by name, \`pack show\` the matching pack to load its Procedure. Source files live under ${packsLib.PACKS_DIR}/<dir>/PACK.md — read or edit them directly when deeper context or a correction is needed.`
|
|
626
669
|
: `Long-term topic context auto-matched to this message. If the user asked for a skill by name, treat the matching pack's Procedure section as that Open Claudia skill. Source files live under ${packsLib.PACKS_DIR}/<dir>/PACK.md — read or edit them directly when deeper context or a correction is needed.`;
|
|
627
670
|
return `\n\n## Active Open Claudia skills / context packs\n${intro}\n\n${blocks.join("\n\n---\n\n")}`;
|
|
@@ -968,8 +1011,8 @@ async function buildOptionalRecallContext(prompt, opts = {}) {
|
|
|
968
1011
|
{ provider: recallProvider },
|
|
969
1012
|
),
|
|
970
1013
|
logRecall,
|
|
971
|
-
buildPackBlock: (matches, recallBudget) => buildPackBlock(matches, recallBudget, recallOpts),
|
|
972
|
-
buildEntityBlock: (matches, recallBudget) => buildEntityBlock(matches, recallBudget, recallOpts),
|
|
1014
|
+
buildPackBlock: (matches, recallBudget, engineOpts) => buildPackBlock(matches, recallBudget, { ...recallOpts, ...(engineOpts || {}) }),
|
|
1015
|
+
buildEntityBlock: (matches, recallBudget, engineOpts) => buildEntityBlock(matches, recallBudget, { ...recallOpts, ...(engineOpts || {}) }),
|
|
973
1016
|
};
|
|
974
1017
|
const result = await engine.run({
|
|
975
1018
|
userText, contextText, fullContext, packLimit, budget, helpers, provider: recallProvider,
|
|
@@ -1123,6 +1166,7 @@ module.exports = {
|
|
|
1123
1166
|
PromptConstructionError,
|
|
1124
1167
|
buildCoreInstructions,
|
|
1125
1168
|
buildOptionalRecallContext,
|
|
1169
|
+
formatPackForContext,
|
|
1126
1170
|
buildDynamicContextBlock,
|
|
1127
1171
|
buildExternalModeBlock,
|
|
1128
1172
|
buildSpacesTaskModeBlock,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@inetafrica/open-claudia",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.40",
|
|
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": {
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
"scripts": {
|
|
10
10
|
"setup": "node setup.js",
|
|
11
11
|
"start": "node bot.js",
|
|
12
|
-
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-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-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-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js",
|
|
13
|
-
"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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-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-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-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js"
|
|
12
|
+
"pretest": "node test-provider-fixture.js && node test-provider-core-prompt.js && node test-provider-prompt-parity.js && node test-provider-env-isolation.js && node test-provider-resume-parity.js && node test-provider-bootstrap.js && node test-provider-registry.js && node test-provider-stream-decoder.js && node test-provider-events.js && node test-provider-claude.js && node test-provider-codex.js && node test-provider-runner.js && node test-provider-run-lifecycle.js && node test-provider-gateway.js && node test-provider-side-chat.js && node test-single-runtime.js && node test-provider-migration-backup.js && node test-provider-state-migration.js && node test-provider-session-history.js && node test-provider-session-commands.js && node test-provider-compaction.js && node test-provider-scheduler.js && node test-credential-capture.js && node test-cursor-removal.js && node test-utility-provider-policy.js && node test-provider-subagent.js && node test-provider-recall.js && node test-provider-pack-review.js && node test-memory-mutation-queue.js && node test-provider-dream.js && node test-provider-enforcer.js && node test-provider-tool-hooks.js && node test-provider-boot-matrix.js && node test-provider-capabilities.js && node test-provider-language.js && node test-provider-coupling-audit.js && node test-provider-parity-e2e.js && node test-kazee-channel-health.js && node test-prompt-budget.js && node test-agency-ledger.js && node test-agency-deliberate.js && node test-guardrail.js && node test-spaces-comment-content.js",
|
|
13
|
+
"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 && OPEN_CLAUDIA_TEST=1 WORKSPACE=/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-walker-score.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-score-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-cokept-warming.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-project-transcripts-smoke.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-abilities.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-extraction.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-couse.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-transfer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-tiers.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-ability-merge-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-learning-e2e.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-nesting.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-pack-search.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-extractive-injection.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-journal-dedupe.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-read-signal.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tools.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-graph.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tooling-mode.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-tool-manifest.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-evolution.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-approval-async.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-unified-identity.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-queue-routing.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-packs.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-speaker-persona.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-persona-pipeline.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-recall-relationship-gate.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-relationship.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-enforcer.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-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-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-web-sessions.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-poll-recovery.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-409-grace.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-telegram-conflict-heal-guard.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-single-instance.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-streaming-split.js && OPEN_CLAUDIA_TEST=1 WORKSPACE=/tmp/open-claudia-test CLAUDE_PATH=./test-fixtures/fake-agent-cli.js FAKE_AGENT_KIND=claude AGENT_ENV_PASSTHROUGH=FAKE_AGENT_KIND node test-spaces-autoenable.js"
|
|
14
14
|
},
|
|
15
15
|
"files": [
|
|
16
16
|
"bot.js",
|
|
@@ -38,6 +38,9 @@
|
|
|
38
38
|
"test-recall-engine.js",
|
|
39
39
|
"test-recall-graph.js",
|
|
40
40
|
"test-recall-discoverer.js",
|
|
41
|
+
"test-walker-score.js",
|
|
42
|
+
"test-score-gate.js",
|
|
43
|
+
"test-cokept-warming.js",
|
|
41
44
|
"test-project-transcripts-smoke.js",
|
|
42
45
|
"test-abilities.js",
|
|
43
46
|
"test-ability-extraction.js",
|
|
@@ -47,6 +50,8 @@
|
|
|
47
50
|
"test-ability-merge-guard.js",
|
|
48
51
|
"test-learning-e2e.js",
|
|
49
52
|
"test-pack-nesting.js",
|
|
53
|
+
"test-pack-search.js",
|
|
54
|
+
"test-extractive-injection.js",
|
|
50
55
|
"test-read-signal.js",
|
|
51
56
|
"test-tools.js",
|
|
52
57
|
"test-tool-graph.js",
|
|
@@ -95,6 +100,7 @@
|
|
|
95
100
|
"test-provider-session-commands.js",
|
|
96
101
|
"test-provider-compaction.js",
|
|
97
102
|
"test-provider-scheduler.js",
|
|
103
|
+
"test-credential-capture.js",
|
|
98
104
|
"test-cursor-removal.js",
|
|
99
105
|
"test-utility-provider-policy.js",
|
|
100
106
|
"test-provider-subagent.js",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// WS5: asymmetric co-kept warming. Co-OPEN warming (the model actually opened
|
|
2
|
+
// the nodes together) fires at full strength elsewhere; co-KEPT (the walker
|
|
3
|
+
// co-injected them) is a weaker, noisier signal, so it warms by a small amount
|
|
4
|
+
// and only under guards — both endpoints high-confidence (score ≥ warmMinScore),
|
|
5
|
+
// ≥1 message-grounded (user-origin) endpoint, and only the top N pairs. The
|
|
6
|
+
// feature is dark-launched OFF (it writes persistent graph state, so it can't be
|
|
7
|
+
// validated inside one test session). This test covers the pure pair-selection
|
|
8
|
+
// (cokeptPairs) and the end-to-end warming write through disc.run() when ON.
|
|
9
|
+
const assert = require("assert");
|
|
10
|
+
const fs = require("fs");
|
|
11
|
+
const os = require("os");
|
|
12
|
+
const path = require("path");
|
|
13
|
+
|
|
14
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "recall-warm-"));
|
|
15
|
+
process.env.RECALL_GRAPH_DB = path.join(tmp, "graph.db");
|
|
16
|
+
process.env.RECALL_DISCOVERER_WALKER = "on";
|
|
17
|
+
process.env.RECALL_METRICS = "off";
|
|
18
|
+
process.env.TOOLS_DIR = path.join(tmp, "tools");
|
|
19
|
+
process.env.RECALL_CASCADE = "off";
|
|
20
|
+
delete process.env.RECALL_MIN_RELEVANCE_SCORE; // default gate (55) — 80/70 both survive
|
|
21
|
+
delete process.env.RECALL_COKEPT_WARMING; // default OFF unless a case flips it
|
|
22
|
+
|
|
23
|
+
const disc = require("./core/recall/discoverer");
|
|
24
|
+
const graph = require("./core/recall/graph");
|
|
25
|
+
|
|
26
|
+
// ── pure: cokeptPairs guards + ranking ───────────────────────────────────────
|
|
27
|
+
const S = (obj) => new Map(Object.entries(obj));
|
|
28
|
+
|
|
29
|
+
// basic: a high-score grounded pair is warmed
|
|
30
|
+
assert.deepStrictEqual(
|
|
31
|
+
disc.cokeptPairs(new Set(["pack:a", "pack:b"]), S({ "pack:a": 80, "pack:b": 70 }), new Set(["pack:a"]), { minScore: 60, maxPairs: 3 }),
|
|
32
|
+
[["pack:a", "pack:b"]], "grounded high-score pair warms");
|
|
33
|
+
|
|
34
|
+
// grounding guard: neither endpoint is a message-grounded seed → no warming
|
|
35
|
+
assert.deepStrictEqual(
|
|
36
|
+
disc.cokeptPairs(new Set(["pack:a", "pack:b"]), S({ "pack:a": 90, "pack:b": 90 }), new Set(), { minScore: 60, maxPairs: 3 }),
|
|
37
|
+
[], "two pure graph hops never reinforce each other (no grounded endpoint)");
|
|
38
|
+
|
|
39
|
+
// score guard: an endpoint below the floor is dropped, killing the only pair
|
|
40
|
+
assert.deepStrictEqual(
|
|
41
|
+
disc.cokeptPairs(new Set(["pack:a", "pack:b"]), S({ "pack:a": 80, "pack:b": 50 }), new Set(["pack:a"]), { minScore: 60, maxPairs: 3 }),
|
|
42
|
+
[], "a below-floor endpoint removes the pair");
|
|
43
|
+
|
|
44
|
+
// node-type guard: only pack/entity graph nodes count — tools/episodes are not graph nodes
|
|
45
|
+
assert.deepStrictEqual(
|
|
46
|
+
disc.cokeptPairs(new Set(["pack:a", "tool:t", "episode:e"]), S({ "pack:a": 90, "tool:t": 90, "episode:e": 90 }), new Set(["pack:a"]), { minScore: 60, maxPairs: 3 }),
|
|
47
|
+
[], "tools and episodes are excluded from co-kept warming");
|
|
48
|
+
|
|
49
|
+
// entity nodes are eligible too
|
|
50
|
+
assert.deepStrictEqual(
|
|
51
|
+
disc.cokeptPairs(new Set(["entity:e1", "pack:a"]), S({ "entity:e1": 70, "pack:a": 65 }), new Set(["entity:e1"]), { minScore: 60, maxPairs: 3 }),
|
|
52
|
+
[["entity:e1", "pack:a"]], "entity↔pack pair warms when grounded + high-score");
|
|
53
|
+
|
|
54
|
+
// cap + ranking: strongest pairs first, capped at maxPairs
|
|
55
|
+
const three = disc.cokeptPairs(
|
|
56
|
+
new Set(["pack:a", "pack:b", "pack:c"]),
|
|
57
|
+
S({ "pack:a": 90, "pack:b": 80, "pack:c": 70 }),
|
|
58
|
+
new Set(["pack:a", "pack:b", "pack:c"]), { minScore: 60, maxPairs: 2 });
|
|
59
|
+
assert.deepStrictEqual(three, [["pack:a", "pack:b"], ["pack:a", "pack:c"]], "top-2 strongest pairs by combined score");
|
|
60
|
+
const one = disc.cokeptPairs(
|
|
61
|
+
new Set(["pack:a", "pack:b", "pack:c"]),
|
|
62
|
+
S({ "pack:a": 90, "pack:b": 80, "pack:c": 70 }),
|
|
63
|
+
new Set(["pack:a"]), { minScore: 60, maxPairs: 1 });
|
|
64
|
+
assert.deepStrictEqual(one, [["pack:a", "pack:b"]], "maxPairs=1 keeps only the single strongest pair");
|
|
65
|
+
|
|
66
|
+
// ── integration: warming ON writes a reinforced edge; OFF writes none ─────────
|
|
67
|
+
const packs = {
|
|
68
|
+
alpha: { dir: "alpha", name: "Alpha", description: "alpha pack", parent: null, tags: [], sections: { Stance: "alpha stance", Procedure: "", State: "", Journal: "" } },
|
|
69
|
+
beta: { dir: "beta", name: "Beta", description: "beta pack", parent: null, tags: [], sections: { Stance: "beta stance", Procedure: "", State: "", Journal: "" } },
|
|
70
|
+
};
|
|
71
|
+
const packsLib = {
|
|
72
|
+
matchPacks: (text) => /widget/.test(text) ? [{ dir: "alpha", name: "Alpha", score: 5 }, { dir: "beta", name: "Beta", score: 4 }] : [],
|
|
73
|
+
readPack: (dir) => packs[dir] || null,
|
|
74
|
+
listPacks: () => Object.values(packs),
|
|
75
|
+
searchLines: () => [],
|
|
76
|
+
PACKS_DIR: tmp,
|
|
77
|
+
};
|
|
78
|
+
const entitiesLib = { matchEntities: () => [], readEntity: () => null, listEntities: () => [], ENTITIES_DIR: tmp };
|
|
79
|
+
const mergeMatches = (primary, secondary, keyOf) => {
|
|
80
|
+
const out = primary.map((m) => ({ ...m, origin: "user" }));
|
|
81
|
+
const seen = new Set(primary.map(keyOf));
|
|
82
|
+
for (const m of secondary) if (!seen.has(keyOf(m))) out.push({ ...m, origin: "context" });
|
|
83
|
+
return out;
|
|
84
|
+
};
|
|
85
|
+
const buildPackBlock = (matches) => (matches.length ? "PACKBLOCK" : "");
|
|
86
|
+
const buildEntityBlock = (matches) => (matches.length ? "ENTITYBLOCK" : "");
|
|
87
|
+
const helpers = { packsLib, entitiesLib, mergeMatches, buildPackBlock, buildEntityBlock };
|
|
88
|
+
const walkerReturning = (text) => ({
|
|
89
|
+
warmWalker: { isEnabled: () => false },
|
|
90
|
+
resolveUtilityProvider: () => ({ provider: {}, providerId: "fake", model: "fake-model" }),
|
|
91
|
+
spawnUtilityAgent: async () => ({ text, provider: "fake", model: "fake-model" }),
|
|
92
|
+
});
|
|
93
|
+
const QUERY = "tell me about the widget deployment process in detail";
|
|
94
|
+
const KEEP_BOTH = '[{"id":"pack:alpha","why":"relevant","score":80},{"id":"pack:beta","why":"related","score":70}]';
|
|
95
|
+
const reinforcedBetween = (a, b) => graph.allEdges().some((e) =>
|
|
96
|
+
((e.src === a && e.dst === b) || (e.src === b && e.dst === a)) && e.type === "related" && e.last_reinforced);
|
|
97
|
+
|
|
98
|
+
(async () => {
|
|
99
|
+
// The integration path writes/reads the sqlite-backed graph. CI runs node:20
|
|
100
|
+
// (no node:sqlite), where graph.reinforce()/allEdges() are no-ops — so, like
|
|
101
|
+
// test-recall-graph.js, skip the graph-dependent assertions when the graph is
|
|
102
|
+
// unavailable. The pure cokeptPairs() unit tests above already ran and cover
|
|
103
|
+
// the selection logic on every node version.
|
|
104
|
+
if (!graph.available()) { console.log("cokept warming OK (integration skipped — no node:sqlite)"); return; }
|
|
105
|
+
// OFF (default): both packs kept, but NO reinforced edge is written
|
|
106
|
+
const off = await disc.run({ userText: QUERY, contextText: "", fullContext: QUERY, packLimit: 6, budget: {}, helpers, walkerOptions: walkerReturning(KEEP_BOTH) });
|
|
107
|
+
assert.ok(off.packMatches.some((m) => m.dir === "alpha") && off.packMatches.some((m) => m.dir === "beta"), "both packs kept");
|
|
108
|
+
assert.strictEqual(reinforcedBetween("pack:alpha", "pack:beta"), false, "warming OFF (default) writes no reinforced edge");
|
|
109
|
+
|
|
110
|
+
// ON: the co-kept, high-confidence, grounded pair warms its related edge
|
|
111
|
+
process.env.RECALL_COKEPT_WARMING = "on";
|
|
112
|
+
await disc.run({ userText: QUERY, contextText: "", fullContext: QUERY, packLimit: 6, budget: {}, helpers, walkerOptions: walkerReturning(KEEP_BOTH) });
|
|
113
|
+
assert.strictEqual(reinforcedBetween("pack:alpha", "pack:beta"), true, "warming ON reinforces the co-kept pair's related edge");
|
|
114
|
+
delete process.env.RECALL_COKEPT_WARMING;
|
|
115
|
+
|
|
116
|
+
console.log("cokept warming OK");
|
|
117
|
+
})().catch((e) => { console.error(e); process.exit(1); });
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
"use strict";
|
|
4
|
+
|
|
5
|
+
// Loose credential capture: a pasted secret must be stored out-of-band and the
|
|
6
|
+
// turn stopped BEFORE the message reaches the model — regardless of which
|
|
7
|
+
// provider is active. This is the Codex complaint: Codex refuses secrets in its
|
|
8
|
+
// prompt, so the harness has to intercept the paste itself rather than forward
|
|
9
|
+
// it. Two layers are covered here:
|
|
10
|
+
// 1. the pure classifier (core/credential-detect.js) — the decision logic
|
|
11
|
+
// 2. the router wiring (core/router.js) — capture precedes admitRunContext and
|
|
12
|
+
// each disposition routes to the right sink, provider-agnostically.
|
|
13
|
+
|
|
14
|
+
const assert = require("assert");
|
|
15
|
+
const fs = require("fs");
|
|
16
|
+
const path = require("path");
|
|
17
|
+
|
|
18
|
+
const root = __dirname;
|
|
19
|
+
const { classifyPastedCredential } = require("./core/credential-detect");
|
|
20
|
+
|
|
21
|
+
function classifierProbe() {
|
|
22
|
+
const kind = (text) => classifyPastedCredential(text).kind;
|
|
23
|
+
|
|
24
|
+
// Claude OAuth: the sk-ant- prefix and the long opaque no-sk shape.
|
|
25
|
+
assert.strictEqual(kind("sk-ant-oat01-" + "A".repeat(40)), "claude");
|
|
26
|
+
assert.strictEqual(kind("x".repeat(80)), "claude", "long opaque token with no sk- prefix reads as a Claude token");
|
|
27
|
+
assert.strictEqual(kind("A1b2C3d4_" + "e".repeat(80)), "claude");
|
|
28
|
+
|
|
29
|
+
// OpenAI API keys go to the Codex CLI — the whole point of the fix.
|
|
30
|
+
assert.strictEqual(kind("sk-" + "a".repeat(40)), "openai");
|
|
31
|
+
assert.strictEqual(kind("sk-proj-" + "b".repeat(40)), "openai");
|
|
32
|
+
|
|
33
|
+
// Recognised service prefixes are claimed before the generic Claude heuristic.
|
|
34
|
+
const keyring = (text) => {
|
|
35
|
+
const d = classifyPastedCredential(text);
|
|
36
|
+
return `${d.kind}:${d.name}`;
|
|
37
|
+
};
|
|
38
|
+
assert.strictEqual(keyring("glpat-" + "x".repeat(20)), "keyring:gitlab_token");
|
|
39
|
+
assert.strictEqual(keyring("ghp_" + "x".repeat(36)), "keyring:github_token");
|
|
40
|
+
assert.strictEqual(keyring("github_pat_" + "x".repeat(40)), "keyring:github_token");
|
|
41
|
+
assert.strictEqual(keyring("xoxb-" + "1".repeat(20)), "keyring:slack_token");
|
|
42
|
+
assert.strictEqual(keyring("AKIA" + "ABCDEFGH1234"), "keyring:aws_access_key_id");
|
|
43
|
+
assert.strictEqual(keyring("AIza" + "a".repeat(30)), "keyring:google_api_key");
|
|
44
|
+
|
|
45
|
+
// A long sk-ant- token must win over the generic long-opaque Claude branch,
|
|
46
|
+
// and over the OpenAI sk- shape.
|
|
47
|
+
assert.strictEqual(kind("sk-ant-" + "z".repeat(90)), "claude");
|
|
48
|
+
|
|
49
|
+
// Labelled single-token secrets land in the keyring with a derived name.
|
|
50
|
+
const labelled = classifyPastedCredential("token: " + "s".repeat(24));
|
|
51
|
+
assert.strictEqual(labelled.kind, "keyring");
|
|
52
|
+
assert.strictEqual(labelled.name, "token");
|
|
53
|
+
assert.strictEqual(labelled.value, "s".repeat(24));
|
|
54
|
+
assert.strictEqual(classifyPastedCredential("Bearer " + "t".repeat(24)).name, "bearer_token");
|
|
55
|
+
|
|
56
|
+
// A label followed by prose is ordinary text, not a swallowed secret.
|
|
57
|
+
assert.strictEqual(kind("key: the answer is 42"), "none");
|
|
58
|
+
|
|
59
|
+
// Ambiguous medium-length single token → scrub from chat, but let the turn run.
|
|
60
|
+
assert.strictEqual(kind("A1b2C3d4E5f6G7h8I9j0"), "scrub");
|
|
61
|
+
|
|
62
|
+
// Ordinary conversation and empties are left completely alone.
|
|
63
|
+
assert.strictEqual(kind("hey can you check the deploy status"), "none");
|
|
64
|
+
assert.strictEqual(kind("what colour are the fish?"), "none");
|
|
65
|
+
assert.strictEqual(kind(""), "none");
|
|
66
|
+
assert.strictEqual(kind(" "), "none");
|
|
67
|
+
assert.strictEqual(kind("short"), "none");
|
|
68
|
+
assert.strictEqual(kind(null), "none");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function routerWiringProbe() {
|
|
72
|
+
const source = fs.readFileSync(path.join(root, "core", "router.js"), "utf8");
|
|
73
|
+
|
|
74
|
+
// The classifier is imported and driven through a single capture funnel.
|
|
75
|
+
assert.match(source, /classifyPastedCredential/, "router must use the pure classifier");
|
|
76
|
+
assert.match(source, /async function captureLooseCredential/, "router must define the capture funnel");
|
|
77
|
+
|
|
78
|
+
// Capture must run — and short-circuit the turn — BEFORE the model is admitted,
|
|
79
|
+
// so a pasted secret never becomes part of a provider prompt.
|
|
80
|
+
const captureIdx = source.indexOf("if (await captureLooseCredential(");
|
|
81
|
+
const admitIdx = source.indexOf("admitRunContext(text,");
|
|
82
|
+
assert.ok(captureIdx > 0, "handleText must call captureLooseCredential");
|
|
83
|
+
assert.ok(admitIdx > 0, "handleText must still admit ordinary turns");
|
|
84
|
+
assert.ok(captureIdx < admitIdx, "credential capture must precede admitRunContext so secrets never reach the model");
|
|
85
|
+
|
|
86
|
+
// Capture is provider-agnostic: it must not branch on the active backend/provider.
|
|
87
|
+
const fnStart = source.indexOf("async function captureLooseCredential");
|
|
88
|
+
const fnEnd = source.indexOf("\nasync function handleText", fnStart);
|
|
89
|
+
const fnBody = source.slice(fnStart, fnEnd);
|
|
90
|
+
assert.ok(fnEnd > fnStart, "captureLooseCredential body must be locatable");
|
|
91
|
+
assert.ok(!/settings\.backend|state\.settings|activeProvider|currentProvider/.test(fnBody),
|
|
92
|
+
"credential capture must not depend on which provider is active");
|
|
93
|
+
|
|
94
|
+
// Each disposition routes to its sink, and every stored secret is redaction-
|
|
95
|
+
// registered and deleted from the chat.
|
|
96
|
+
assert.match(fnBody, /saveClaudeOAuthToken\(/, "claude paste → save Claude OAuth token");
|
|
97
|
+
assert.match(fnBody, /saveCodexApiKeyWithCli\(/, "openai paste → hand to Codex CLI");
|
|
98
|
+
assert.match(fnBody, /keyring\.set\(/, "recognised secret → operational keyring");
|
|
99
|
+
assert.match(fnBody, /registerSecrets\(/, "captured secrets are registered for redaction");
|
|
100
|
+
assert.match(fnBody, /deleteMessage\(/, "captured secrets are deleted from the chat");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function main() {
|
|
104
|
+
classifierProbe();
|
|
105
|
+
routerWiringProbe();
|
|
106
|
+
console.log("credential capture OK");
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
main();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// WS3: extractive on-open injection. The old headline surfaced Stance only, so
|
|
2
|
+
// a fact living in State/Procedure/Journal never loaded even when it was the
|
|
3
|
+
// reason the pack matched (Defect A — the felt regression). Extractive mode
|
|
4
|
+
// renders Stance verbatim PLUS the exact lines that matched this message, from
|
|
5
|
+
// anywhere in the pack, tagged by section and never paraphrased. Covers: cross-
|
|
6
|
+
// section surfacing, verbatim text, section tags, Stance dedupe, per-section
|
|
7
|
+
// cap, Stance-only fallback, and the unchanged classic headline path.
|
|
8
|
+
const assert = require("assert");
|
|
9
|
+
const { formatPackForContext } = require("./core/system-prompt");
|
|
10
|
+
|
|
11
|
+
const pack = {
|
|
12
|
+
dir: "deploy-notes",
|
|
13
|
+
name: "Deploy Notes",
|
|
14
|
+
description: "how we ship",
|
|
15
|
+
parent: null,
|
|
16
|
+
tags: [],
|
|
17
|
+
sections: {
|
|
18
|
+
Stance: "Prefer GitOps for what runs in the cluster.\nNever force-push to main.",
|
|
19
|
+
Procedure: "Run the smoke test before every deploy.\nTag the release then push via git-release.",
|
|
20
|
+
State: "The ArgoCD sync for payments is currently paused.\nWaiting on Emmanuel's review.",
|
|
21
|
+
Journal: "2026-07-01 shipped v3.\n2026-07-02 rolled back v3 then re-shipped.\n2026-06-30 unrelated note about pizza.",
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
const packsLib = { readPack: () => null, listPacks: () => [] };
|
|
25
|
+
|
|
26
|
+
// ── extractive: Stance verbatim + the matched State line, tagged, not paraphrased ──
|
|
27
|
+
const matched = [
|
|
28
|
+
{ section: "State", lineno: 1, text: "The ArgoCD sync for payments is currently paused.", score: 30 },
|
|
29
|
+
{ section: "Procedure", lineno: 2, text: "Tag the release then push via git-release.", score: 20 },
|
|
30
|
+
];
|
|
31
|
+
const out = formatPackForContext(pack, packsLib, { extractive: true, matchedLines: matched });
|
|
32
|
+
|
|
33
|
+
assert.ok(/#### Stance\nPrefer GitOps for what runs in the cluster\./.test(out), "Stance shown verbatim");
|
|
34
|
+
assert.ok(/Never force-push to main\./.test(out), "full Stance body preserved");
|
|
35
|
+
assert.ok(/#### State \(matched\)/.test(out), "matched State section is tagged");
|
|
36
|
+
assert.ok(out.includes("The ArgoCD sync for payments is currently paused."), "the matched State fact is surfaced verbatim (Defect-A fix)");
|
|
37
|
+
assert.ok(/#### Procedure \(matched\)/.test(out), "matched Procedure section is tagged");
|
|
38
|
+
assert.ok(out.includes("Tag the release then push via git-release."), "matched Procedure line surfaced verbatim");
|
|
39
|
+
// Un-matched noise must NOT be dumped: the pizza journal line never matched.
|
|
40
|
+
assert.ok(!out.includes("pizza"), "unmatched Journal noise is not injected");
|
|
41
|
+
assert.ok(!out.includes("Waiting on Emmanuel"), "unmatched State line is not injected");
|
|
42
|
+
assert.ok(/open-claudia pack show deploy-notes/.test(out), "points to the full pack for more");
|
|
43
|
+
|
|
44
|
+
// ── Stance dedupe: a matched line that IS a Stance line is not repeated ──
|
|
45
|
+
const dupMatched = [{ section: "Stance", text: "Never force-push to main.", score: 20 },
|
|
46
|
+
{ section: "State", text: "Prefer GitOps for what runs in the cluster.", score: 20 }]; // same text as a Stance line
|
|
47
|
+
const outDup = formatPackForContext(pack, packsLib, { extractive: true, matchedLines: dupMatched });
|
|
48
|
+
// "Prefer GitOps..." appears once (in Stance), not again under State (matched).
|
|
49
|
+
const gitopsCount = (outDup.match(/Prefer GitOps for what runs in the cluster\./g) || []).length;
|
|
50
|
+
assert.strictEqual(gitopsCount, 1, "a matched line equal to a Stance line is not duplicated");
|
|
51
|
+
|
|
52
|
+
// ── per-section cap: at most EXTRACTIVE_LINES_PER_SECTION (4) lines per section ──
|
|
53
|
+
const many = Array.from({ length: 9 }, (_, i) => ({ section: "Journal", text: `2026-07-${10 + i} note number ${i}`, score: 10 }));
|
|
54
|
+
const outMany = formatPackForContext(pack, packsLib, { extractive: true, matchedLines: many });
|
|
55
|
+
const journalBullets = (outMany.match(/note number \d/g) || []).length;
|
|
56
|
+
assert.ok(journalBullets <= 4, `per-section cap holds (got ${journalBullets})`);
|
|
57
|
+
|
|
58
|
+
// ── Stance-only fallback: extractive with no matched lines → Stance + pointer, no (matched) blocks ──
|
|
59
|
+
const outNone = formatPackForContext(pack, packsLib, { extractive: true, matchedLines: [] });
|
|
60
|
+
assert.ok(/#### Stance/.test(outNone), "Stance still shown when nothing matched");
|
|
61
|
+
assert.ok(!/\(matched\)/.test(outNone), "no (matched) sections when there were no line hits");
|
|
62
|
+
assert.ok(/Showing Stance\./.test(outNone), "fallback pointer explains only Stance is shown");
|
|
63
|
+
|
|
64
|
+
// ── classic headline path unchanged: Stance only, heavier sections hidden ──
|
|
65
|
+
const outHeadline = formatPackForContext(pack, packsLib, { headline: true });
|
|
66
|
+
assert.ok(/#### Stance/.test(outHeadline), "headline still shows Stance");
|
|
67
|
+
assert.ok(!outHeadline.includes("The ArgoCD sync for payments"), "headline does NOT inline State (unchanged legacy behavior)");
|
|
68
|
+
assert.ok(/not shown/.test(outHeadline), "headline still points to hidden sections");
|
|
69
|
+
|
|
70
|
+
console.log("extractive injection OK");
|