@chibitek/chibi 0.1.158 → 0.1.160
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/dist/assets/{abnfDiagram-O67JEVCF-B8-8zzMS.js → abnfDiagram-O67JEVCF-CpuRvzQ3.js} +1 -1
- package/dist/assets/{arc-CSHQ9Dx3.js → arc-ko1e0R6h.js} +1 -1
- package/dist/assets/{architectureDiagram-NJMV4G6O-BCHM9rXW.js → architectureDiagram-NJMV4G6O-OZ3SsOOa.js} +1 -1
- package/dist/assets/{blockDiagram-BEXU5L5S-B-5uxPFN.js → blockDiagram-BEXU5L5S-DLRkBkjs.js} +1 -1
- package/dist/assets/{c4Diagram-YGBWAQC7-CuKNM_MR.js → c4Diagram-YGBWAQC7-e8Q3qMnw.js} +1 -1
- package/dist/assets/channel-Lp7erhx-.js +1 -0
- package/dist/assets/{chunk-2Q5K7J3B-BNF02GM4.js → chunk-2Q5K7J3B-hCDqaM4Q.js} +1 -1
- package/dist/assets/{chunk-3FUC2YCW-uEWgBd4M.js → chunk-3FUC2YCW-Dy3Pahu-.js} +2 -2
- package/dist/assets/{chunk-5VM5RSS4-eSZuXctY.js → chunk-5VM5RSS4-QMK3iFBF.js} +1 -1
- package/dist/assets/{chunk-GTNCS2PH-CslS8nRS.js → chunk-GTNCS2PH-6fyp9ERp.js} +1 -1
- package/dist/assets/{chunk-GWA4HPMP-w71-MeKj.js → chunk-GWA4HPMP-DWvOhTaO.js} +1 -1
- package/dist/assets/{chunk-JWPE2WC7-Bjju7WGh.js → chunk-JWPE2WC7-DgohMW2S.js} +1 -1
- package/dist/assets/{chunk-NETBCI7D-HxrroD4M.js → chunk-NETBCI7D-sRGeD2Y7.js} +1 -1
- package/dist/assets/{chunk-WEXAMYUT-D7Rz470x.js → chunk-WEXAMYUT-CZslD_VF.js} +1 -1
- package/dist/assets/{chunk-XXDRQBXY-BWKnAeyM.js → chunk-XXDRQBXY-D7ocO3VN.js} +1 -1
- package/dist/assets/{classDiagram-v2-NBCMYWYE-CeBDRHmP.js → classDiagram-v2-NBCMYWYE-T1lLqHHW.js} +1 -1
- package/dist/assets/{cose-bilkent-JH36ORCC-BBbRKlvr.js → cose-bilkent-JH36ORCC-BdeLiJr3.js} +1 -1
- package/dist/assets/{cynefin-OW5HDTMX-W02cnJ0B.js → cynefin-OW5HDTMX-BydCA0pv.js} +1 -1
- package/dist/assets/{cynefinDiagram-VND7K2PF-Cs47eaxd.js → cynefinDiagram-VND7K2PF-DBxckjwQ.js} +1 -1
- package/dist/assets/{dagre-6A5THRUB-8mG18hzT.js → dagre-6A5THRUB-CAsvq6Zk.js} +1 -1
- package/dist/assets/{diagram-22UHCM2B-DF4Qrw67.js → diagram-22UHCM2B-DTwH3rA_.js} +1 -1
- package/dist/assets/{diagram-3UASUU5V-3BAVoed1.js → diagram-3UASUU5V-BNYj1bey.js} +1 -1
- package/dist/assets/{diagram-ATOU4E4O-CKO3pECE.js → diagram-ATOU4E4O-CihwYFxb.js} +1 -1
- package/dist/assets/{diagram-CDSNMT55-CZhrGvur.js → diagram-CDSNMT55-nouwk3s2.js} +1 -1
- package/dist/assets/{diagram-MLGK6HIB-C07l9pdQ.js → diagram-MLGK6HIB-BoKewJeA.js} +1 -1
- package/dist/assets/{diagram-MPIPVDR6-D3or4lkZ.js → diagram-MPIPVDR6-CrE1m9DQ.js} +1 -1
- package/dist/assets/{ebnfDiagram-ZINNZB2B-DV2epXp_.js → ebnfDiagram-ZINNZB2B-DRj0aU4Y.js} +1 -1
- package/dist/assets/{elk-276RUBZZ-DlvEn4-1.js → elk-276RUBZZ-Do0JzCG5.js} +1 -1
- package/dist/assets/{erDiagram-OPXOYQCR-CB6X7Wt5.js → erDiagram-OPXOYQCR-CRsI9pXb.js} +1 -1
- package/dist/assets/{flowDiagram-KWPJA3E3-C3bqz4lC.js → flowDiagram-KWPJA3E3-dtD6wm3G.js} +1 -1
- package/dist/assets/{ganttDiagram-FUAMR5RP-yo_Evhab.js → ganttDiagram-FUAMR5RP-Cn7KyLCo.js} +1 -1
- package/dist/assets/{gitGraphDiagram-X574FWY7-Cq2oUlap.js → gitGraphDiagram-X574FWY7-CGH_VWzq.js} +1 -1
- package/dist/assets/index-B2fJMh8N.css +1 -0
- package/dist/assets/{index-CMcr5k3Z.js → index-BIcziktH.js} +246 -246
- package/dist/assets/{index-CG4AFPb4.js → index-Bk8iAodB.js} +1 -1
- package/dist/assets/{infoDiagram-VRGFBTTK-Y3ZrY7qL.js → infoDiagram-VRGFBTTK-CQKsYTCL.js} +1 -1
- package/dist/assets/{ishikawaDiagram-OU5B5YK6-BpOtSfyi.js → ishikawaDiagram-OU5B5YK6-D5catDe0.js} +1 -1
- package/dist/assets/{journeyDiagram-ZHPQQLJL-UQM727le.js → journeyDiagram-ZHPQQLJL-KjY8mki9.js} +1 -1
- package/dist/assets/{kanban-definition-PNTS6WVX-C3s5oHpY.js → kanban-definition-PNTS6WVX-BKCyJtge.js} +1 -1
- package/dist/assets/{linear-hX6nFOQr.js → linear-B-_obUkq.js} +1 -1
- package/dist/assets/{mermaid.core-DZObGLN7.js → mermaid.core-DlSzuDKt.js} +5 -5
- package/dist/assets/{mindmap-definition-NLK3R4M7-Br-i2E3P.js → mindmap-definition-NLK3R4M7-BFQSaYKh.js} +1 -1
- package/dist/assets/{pegDiagram-GJSIUBJH-D5SERB-N.js → pegDiagram-GJSIUBJH-l51izlev.js} +1 -1
- package/dist/assets/{pieDiagram-5QR66LMP-BPNf4J3D.js → pieDiagram-5QR66LMP-6NEXRoZX.js} +1 -1
- package/dist/assets/{quadrantDiagram-O4NWA36T-Bm_Wh7Jr.js → quadrantDiagram-O4NWA36T-BCC9XlOe.js} +1 -1
- package/dist/assets/{railroadDiagram-XR7U4H2S-D6bo0abD.js → railroadDiagram-XR7U4H2S-Dd4TmJ86.js} +1 -1
- package/dist/assets/{requirementDiagram-PLB6GJNP--1n5rkV7.js → requirementDiagram-PLB6GJNP-C1E5DaDb.js} +1 -1
- package/dist/assets/{sankeyDiagram-IPEJSGJF-Ct4tP2kK.js → sankeyDiagram-IPEJSGJF-Ci6r7abb.js} +1 -1
- package/dist/assets/{sequenceDiagram-PO4LG4MO-CCG7O5fR.js → sequenceDiagram-PO4LG4MO-BJ-I6WOh.js} +1 -1
- package/dist/assets/{sizeCapture-INFHLROL-CJJJra9c.js → sizeCapture-INFHLROL-Cs5QbLOP.js} +1 -1
- package/dist/assets/{stateDiagram-v2-GCMORJYK-BMdRvVEX.js → stateDiagram-v2-GCMORJYK-Bw--rdvq.js} +1 -1
- package/dist/assets/{swimlanes-2SLR337P-B1wnogip.js → swimlanes-2SLR337P-B1O9IEz5.js} +1 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-C6wGgYKP.js +8 -0
- package/dist/assets/{timeline-definition-EJHVYXUP-l-3yhXUP.js → timeline-definition-EJHVYXUP-CTZ7DXaw.js} +1 -1
- package/dist/assets/{usecaseDiagram-POWQR4AR-DtwtCOoJ.js → usecaseDiagram-POWQR4AR-DjwgNG0H.js} +1 -1
- package/dist/assets/{vennDiagram-UO4OBE2U-BZAEce1B.js → vennDiagram-UO4OBE2U-DrdJG55h.js} +1 -1
- package/dist/assets/{wardleyDiagram-VNRHLVJA-Na_BCMMO.js → wardleyDiagram-VNRHLVJA-HVq776nz.js} +1 -1
- package/dist/assets/{xychartDiagram-PMCCYNJV-BW9dlUfE.js → xychartDiagram-PMCCYNJV-DPf9VEua.js} +1 -1
- package/dist/index.html +2 -2
- package/dist-server/container-mcp.js +1 -1
- package/dist-server/index.js +3585 -3076
- package/dist-server/openmausbot.js +346 -159
- package/dist-server/pair-cli.js +346 -159
- package/dist-server/server/attachment-mimes.js +58 -0
- package/dist-server/server/attachments.js +28 -67
- package/dist-server/server/avatar-image.js +1 -0
- package/dist-server/server/avatar-subscription.js +24 -1
- package/dist-server/server/drivers/claude-message.js +12 -0
- package/dist-server/server/drivers/claude.js +9 -12
- package/dist-server/server/drivers/codex-image.js +141 -0
- package/dist-server/server/drivers/codex.js +6 -9
- package/dist-server/server/drivers/openai-chat.js +13 -2
- package/dist-server/server/engine-failover.js +40 -0
- package/dist-server/server/engine-failure.js +2 -2
- package/dist-server/server/harness-import/history.js +5 -5
- package/dist-server/server/harness-import/test-fixtures.js +1 -1
- package/dist-server/server/index.js +35 -20
- package/dist-server/server/message-reactions.js +11 -0
- package/dist-server/server/model-evidence-context.js +18 -0
- package/dist-server/server/model-evidence.js +145 -0
- package/dist-server/shared/emoji-catalog.json +1 -0
- package/dist-server/shared/emoji.js +27 -0
- package/dist-server/shared/mascot-bodies.js +9 -1
- package/dist-server/shared/model-evidence.js +25 -0
- package/dist-server/vps-container-mcp.js +1 -1
- package/package.json +1 -1
- package/dist/assets/channel-bPN03jIt.js +0 -1
- package/dist/assets/index-CZjRcUdn.css +0 -1
- package/dist/assets/swimlanesDiagram-TC7HE7FX-DWyjdGGR.js +0 -8
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createModelExpectation, ModelEvidenceLedger } from "./model-evidence-context.js";
|
|
2
|
+
import { reactToMessage } from "./message-reactions.js";
|
|
1
3
|
import { hasUnboundGoalGuard, goalGuardForEvent, addGoalGuard, removeGoalGuard, runPersonalGoalTurn, updatePersonalGoalReceipt } from "./personal-goal-turn.js";
|
|
2
4
|
import { PersonalGoalRuntime, receiveGoalCommand } from "./personal-goal-runtime.js";
|
|
3
5
|
import { personalGoalInstructions } from "./personal-goals.js";
|
|
@@ -46,7 +48,8 @@ import { validateBotCwd } from "./bot-cwd.js";
|
|
|
46
48
|
import { ATTACHMENTS_DIR, attachmentExists, cleanupStaleAttachmentPartials, deleteAttachment, extensionForMime, FILE_MAX_BYTES, IMAGE_MAX_BYTES, readAttachment, saveFile, saveImage, saveImageUpload, validateAttachmentUploadId, } from "./attachments.js";
|
|
47
49
|
import { companyCheck, companyContext } from "./company-document-check.js";
|
|
48
50
|
import { messageFileDisposition, messageFileDownloadName, messageAttachmentName, messageFileRoots, messageImageTargetAt, messageReferencesFile, openMessageFile, } from "./message-file.js";
|
|
49
|
-
import { avatarGenerationRequestSchema, avatarGenerationStateMatches,
|
|
51
|
+
import { avatarGenerationRequestSchema, avatarGenerationStateMatches, avatarImageStatus, snapshotAvatarGenerationState } from "./avatar-image.js";
|
|
52
|
+
import { generateRoutedAvatar, installAvatarSubscriptionRoute } from "./avatar-subscription.js";
|
|
50
53
|
import { fitsOnOneLine, parseBotProfilePatch } from "./bot-profile.js";
|
|
51
54
|
import { groupTurnCwd } from "./room-cwd.js";
|
|
52
55
|
import { RepoRegistry } from "./repos.js";
|
|
@@ -304,7 +307,7 @@ import { createNexusMemoryRoutes } from "./routes/nexus-memory.js";
|
|
|
304
307
|
import { createBoardRoutes } from "./routes/board.js";
|
|
305
308
|
import { readBoardFromNexus } from "./board/nexus-board.js";
|
|
306
309
|
import { LocalModelInventory, localModePlacement, machineMemory } from "./local-models-mode.js";
|
|
307
|
-
import { FailoverGate, MAX_ENGINES_PER_TURN, continuationNote, restingLine } from "./engine-failover.js";
|
|
310
|
+
import { eventBase, restTime, FailoverGate, MAX_ENGINES_PER_TURN, continuationNote, restingLine } from "./engine-failover.js";
|
|
308
311
|
import { enterSessionFallback, onSessionFallback, ownOrFallback, sessionFallbackSpentLine, withoutSubscription } from "./session-fallback.js";
|
|
309
312
|
import { TURN_ERROR_CATEGORIES } from "../shared/runtime-events.js";
|
|
310
313
|
import { EngineProbes, PROBE_PROMPT, isProbeThread, probeThreadId } from "./engine-probe.js";
|
|
@@ -487,6 +490,7 @@ function customDomainStatus() {
|
|
|
487
490
|
};
|
|
488
491
|
}
|
|
489
492
|
const registry = new ProviderRegistry(BUILT_IN_DRIVERS);
|
|
493
|
+
installAvatarSubscriptionRoute(() => registry.instances());
|
|
490
494
|
// Engines installed from Settings live under the data directory and win over
|
|
491
495
|
// any other copy on PATH.
|
|
492
496
|
registerEnginesBinDir();
|
|
@@ -3564,6 +3568,9 @@ const roomFailoverTurns = new Map();
|
|
|
3564
3568
|
* default (a resting engine's stand-in, a failover): the ledger books it. */
|
|
3565
3569
|
const roomTurnSelections = new Map();
|
|
3566
3570
|
const turnEngineOverrides = new Map();
|
|
3571
|
+
const modelEvidence = new ModelEvidenceLedger(DATA_DIR);
|
|
3572
|
+
const modelExpectation = createModelExpectation({ get store() { return store; }, get registry() { return registry; }, turnEngineOverrides, roomTurnSelections,
|
|
3573
|
+
speakerBot: threadId => groupSpeakers.get(threadId)?.botId, enginePlan, botClassChoices });
|
|
3567
3574
|
const failoverGate = new FailoverGate((turn, completion) => engineFailover(turn, completion));
|
|
3568
3575
|
/** An approval shows a shell command as the model meant it: the RTK add-on's
|
|
3569
3576
|
* launcher, which engines that take RTK as instructions put in front of
|
|
@@ -3582,9 +3589,16 @@ bus.setGate((published) => {
|
|
|
3582
3589
|
return [];
|
|
3583
3590
|
}
|
|
3584
3591
|
const event = withoutRtkInSummary(published);
|
|
3592
|
+
if (!shouldIgnoreProviderEvent(event))
|
|
3593
|
+
modelEvidence.observe(event, () => modelExpectation(event.threadId, event.providerInstanceId, event.provider));
|
|
3585
3594
|
// [jev-verifier] note a failover turn that finished, before the gate forgets it.
|
|
3586
3595
|
const jevCandidate = jevAnswerCheckCandidate(event);
|
|
3587
3596
|
const delivered = failoverGate.filter(event);
|
|
3597
|
+
// Gate-generated switches never re-enter publish; preserve their evidence too.
|
|
3598
|
+
for (const out of delivered)
|
|
3599
|
+
if (out.type === "turn.engine_failover" && out !== event) {
|
|
3600
|
+
modelEvidence.observe(out, () => modelExpectation(out.threadId, out.providerInstanceId, out.provider));
|
|
3601
|
+
}
|
|
3588
3602
|
if (jevCandidate)
|
|
3589
3603
|
setImmediate(() => void jevAnswerCheck(jevCandidate));
|
|
3590
3604
|
for (const out of delivered)
|
|
@@ -3603,12 +3617,6 @@ function noteEngineBack(event) {
|
|
|
3603
3617
|
const model = selection?.instanceId === instanceId ? selection.model : undefined;
|
|
3604
3618
|
engineRests.noteBack(instanceId, candidate ? engineAccount(candidate) : undefined, candidate ? restScope(candidate, model) : undefined);
|
|
3605
3619
|
}
|
|
3606
|
-
/** A new harness event about the same provider turn as `event`. */
|
|
3607
|
-
const eventBase = (event) => ({
|
|
3608
|
-
eventId: newEventId(), provider: event.provider, threadId: event.threadId, createdAt: new Date().toISOString(),
|
|
3609
|
-
...(event.providerInstanceId ? { providerInstanceId: event.providerInstanceId } : {}), ...(event.turnId ? { turnId: event.turnId } : {}),
|
|
3610
|
-
});
|
|
3611
|
-
const restTime = (at) => new Date(at).toLocaleTimeString("en-GB", { hour: "2-digit", minute: "2-digit" });
|
|
3612
3620
|
/** Rest a refused engine and continue on an eligible engine, or show an actionable line. */
|
|
3613
3621
|
function engineFailover(turn, completion) {
|
|
3614
3622
|
const failure = turn.failure;
|
|
@@ -9825,6 +9833,7 @@ async function startTurn(botId, text, opts) {
|
|
|
9825
9833
|
// thread, so this is the one moment the ledger's "who asked" may change.
|
|
9826
9834
|
if (opts?.trigger)
|
|
9827
9835
|
turnTriggers.set(threadId, opts.trigger);
|
|
9836
|
+
const expectedModel = modelExpectation(threadId);
|
|
9828
9837
|
// The engine the ladder chose becomes the thread's model, as a person's
|
|
9829
9838
|
// switch would; the thread stays tier-managed for its next turn.
|
|
9830
9839
|
if (tierSelection && JSON.stringify(tierSelection) !== JSON.stringify(task.modelSelection)) {
|
|
@@ -9849,6 +9858,8 @@ async function startTurn(botId, text, opts) {
|
|
|
9849
9858
|
// busy flips immediately so the composer locks; the dispatch itself runs
|
|
9850
9859
|
// in the background — box provisioning can take ~90s and must never
|
|
9851
9860
|
// hang the HTTP request
|
|
9861
|
+
const evidenceAttemptId = expectedModel ? modelEvidence.begin({ ...expectedModel, requestedModel: model,
|
|
9862
|
+
instanceId: instance.instanceId, provider: instance.driverKind }, Boolean(failover)) : undefined;
|
|
9852
9863
|
const dispatchClaimId = randomUUID();
|
|
9853
9864
|
const resourceOwner = { threadId, generation: dispatchClaimId };
|
|
9854
9865
|
turnResourceOwners.set(threadId, resourceOwner);
|
|
@@ -10889,6 +10900,8 @@ async function startTurn(botId, text, opts) {
|
|
|
10889
10900
|
}
|
|
10890
10901
|
}
|
|
10891
10902
|
catch (e) {
|
|
10903
|
+
if (evidenceAttemptId)
|
|
10904
|
+
modelEvidence.finishDispatch(threadId, evidenceAttemptId);
|
|
10892
10905
|
handoffs.abandon(threadId, dispatchClaimId);
|
|
10893
10906
|
failoverGate.untrack(threadId, dispatchClaimId);
|
|
10894
10907
|
if (failoverTurns.get(threadId)?.generation === dispatchClaimId)
|
|
@@ -20478,14 +20491,7 @@ const handleRequest = async (req, res) => {
|
|
|
20478
20491
|
// emoji reactions — works on any thread (1:1 or room)
|
|
20479
20492
|
m = path.match(/^\/api\/threads\/([\w-]+)\/messages\/([\w-]+)\/reactions$/);
|
|
20480
20493
|
if (m && method === "POST") {
|
|
20481
|
-
|
|
20482
|
-
const emoji = String(body.emoji ?? "").slice(0, 8);
|
|
20483
|
-
if (!emoji)
|
|
20484
|
-
return json(res, 400, { error: "emoji required" });
|
|
20485
|
-
const patched = store.toggleReaction(m[1], m[2], emoji, typeof body.by === "string" ? body.by : "user");
|
|
20486
|
-
if (!patched)
|
|
20487
|
-
return json(res, 404, { error: "no such message" });
|
|
20488
|
-
return json(res, 200, { message: patched });
|
|
20494
|
+
return reactToMessage(store, req, res, m[1], m[2]);
|
|
20489
20495
|
}
|
|
20490
20496
|
if (path === "/api/sidebar-sections" && method === "GET") {
|
|
20491
20497
|
return json(res, 200, { sections: store.sections });
|
|
@@ -20598,13 +20604,13 @@ const handleRequest = async (req, res) => {
|
|
|
20598
20604
|
const profile = parseBotProfilePatch(body.profile, true);
|
|
20599
20605
|
if (!profile.ok)
|
|
20600
20606
|
return json(res, 400, { error: profile.error });
|
|
20601
|
-
const checked = avatarGenerationRequestSchema.safeParse({ prompt: body.prompt });
|
|
20607
|
+
const checked = avatarGenerationRequestSchema.safeParse({ prompt: body.prompt, source: body.source });
|
|
20602
20608
|
if (!checked.success)
|
|
20603
20609
|
return json(res, 400, { error: checked.error.message });
|
|
20604
20610
|
const { prompt } = checked.data;
|
|
20605
|
-
const generated = await
|
|
20611
|
+
const generated = await generateRoutedAvatar(cfg, registry.instances(), {
|
|
20606
20612
|
name: profile.patch.name ?? "", title: profile.patch.title ?? "", description: profile.patch.description ?? "",
|
|
20607
|
-
}, prompt);
|
|
20613
|
+
}, prompt, checked.data.source);
|
|
20608
20614
|
// A preview belongs to the draft. Upload it through the existing image
|
|
20609
20615
|
// endpoint only when the user commits the new bot or its defaults.
|
|
20610
20616
|
return json(res, 200, { dataUrl: `data:${generated.mime};base64,${generated.bytes.toString("base64")}` });
|
|
@@ -20855,7 +20861,7 @@ const handleRequest = async (req, res) => {
|
|
|
20855
20861
|
if (!parsed.success) {
|
|
20856
20862
|
return json(res, 400, { error: `prompt must be at most 400 characters` });
|
|
20857
20863
|
}
|
|
20858
|
-
const generated = await
|
|
20864
|
+
const generated = await generateRoutedAvatar(cfg, registry.instances(), existing, parsed.data.prompt, parsed.data.source);
|
|
20859
20865
|
const current = store.bot(existing.id);
|
|
20860
20866
|
if (!current)
|
|
20861
20867
|
return json(res, 404, { error: "no such bot" });
|
|
@@ -21836,6 +21842,15 @@ const handleRequest = async (req, res) => {
|
|
|
21836
21842
|
return json(res, 404, { error: "no such bot" });
|
|
21837
21843
|
return json(res, 200, previewSystemPrompt(bot, { privateOwner: privateOwnerFor(auth) }));
|
|
21838
21844
|
}
|
|
21845
|
+
m = path.match(/^\/api\/bots\/([\w-]+)\/model-evidence$/);
|
|
21846
|
+
if (m && method === "GET") {
|
|
21847
|
+
const thread = requestedTaskBot(m[1], url.searchParams.get("threadId") ?? undefined);
|
|
21848
|
+
const range = parseUsageRange(url.searchParams.get("from"), url.searchParams.get("to"));
|
|
21849
|
+
if (!range)
|
|
21850
|
+
return json(res, 400, { error: "Provide a date range of at most one year" });
|
|
21851
|
+
res.setHeader("cache-control", "no-store");
|
|
21852
|
+
return json(res, 200, modelEvidence.query(m[1], thread.threadId, range.from, range.to));
|
|
21853
|
+
}
|
|
21839
21854
|
m = path.match(/^\/api\/bots\/([\w-]+)\/model-classes$/);
|
|
21840
21855
|
if (m && method === "GET") {
|
|
21841
21856
|
const thread = requestedTaskBot(m[1], url.searchParams.get("threadId") ?? undefined);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { isReactionEmoji } from "../shared/emoji.js";
|
|
2
|
+
import { json, readBody } from "./harness/http.js";
|
|
3
|
+
export async function reactToMessage(store, req, res, threadId, messageId) {
|
|
4
|
+
const body = await readBody(req);
|
|
5
|
+
if (!isReactionEmoji(body.emoji))
|
|
6
|
+
return json(res, 400, { error: "Choose one emoji" });
|
|
7
|
+
const message = store.toggleReaction(threadId, messageId, body.emoji, "user");
|
|
8
|
+
if (!message)
|
|
9
|
+
return json(res, 404, { error: "no such message" });
|
|
10
|
+
return json(res, 200, { message });
|
|
11
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { ModelEvidenceLedger } from "./model-evidence.js";
|
|
2
|
+
// Snapshot the same class choices the model picker displays before routing mutates the task.
|
|
3
|
+
import { threadEngineMode } from "../shared/engine-tiers.js";
|
|
4
|
+
export function createModelExpectation(context) {
|
|
5
|
+
return (threadId, instanceId, provider) => {
|
|
6
|
+
const profile = context.store.botByThread(threadId) ?? context.store.bot(context.speakerBot(threadId) ?? "");
|
|
7
|
+
if (!profile)
|
|
8
|
+
return null;
|
|
9
|
+
const task = context.store.taskByThread(profile.id, threadId);
|
|
10
|
+
const selection = context.turnEngineOverrides.get(threadId) ?? context.roomTurnSelections.get(threadId) ?? task?.modelSelection ?? profile.modelSelection;
|
|
11
|
+
const modelClass = context.enginePlan(profile, threadId).modelClass;
|
|
12
|
+
const mode = threadEngineMode(profile, task ?? {});
|
|
13
|
+
const preview = mode.kind === "pinned" ? undefined : context.botClassChoices(profile, threadId).options.find(option => option.modelClass === modelClass);
|
|
14
|
+
return { botId: profile.id, threadId, label: `${mode.kind === "tier" ? "Automatic: " : ""}${modelClass} · ${preview?.label ?? selection.model}`, modelClass,
|
|
15
|
+
model: preview?.selection?.model ?? selection.model, requestedModel: selection.model,
|
|
16
|
+
instanceId: instanceId ?? selection.instanceId, provider: provider ?? context.registry.get(selection.instanceId)?.driverKind ?? "unknown" };
|
|
17
|
+
};
|
|
18
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// Name: model-evidence; author: Atomic Engineering; date: 2026-10-27.
|
|
2
|
+
// Append-only identity history beside the existing usage ledger, without prompts.
|
|
3
|
+
import { randomUUID } from "node:crypto";
|
|
4
|
+
import { mkdirSync, appendFileSync, readdirSync, readFileSync } from "node:fs";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { modelEvidenceMetrics } from "../shared/model-evidence.js";
|
|
7
|
+
export { modelEvidenceMetrics };
|
|
8
|
+
const safe = (value) => typeof value === "string" && /^[\w .:/@+()[\]-]{1,200}$/u.test(value) ? value : undefined;
|
|
9
|
+
export class ModelEvidenceLedger {
|
|
10
|
+
active = new Map();
|
|
11
|
+
byTurn = new Map();
|
|
12
|
+
failures = 0;
|
|
13
|
+
dataDir;
|
|
14
|
+
constructor(dataDir) { this.dataDir = dataDir; }
|
|
15
|
+
begin(expected, continuation = false) {
|
|
16
|
+
const prior = this.active.get(expected.threadId);
|
|
17
|
+
const attempt = { expected: { ...expected, ...(continuation && prior ? { label: prior.expected.label, modelClass: prior.expected.modelClass, model: prior.expected.model } : {}) }, logicalTurnId: continuation && prior ? prior.logicalTurnId : randomUUID(), attemptId: randomUUID() };
|
|
18
|
+
this.active.set(expected.threadId, attempt);
|
|
19
|
+
this.write(attempt, { kind: "dispatch", level: expected.requestedModel || expected.model ? "requested-only" : "unknown" });
|
|
20
|
+
return attempt.attemptId;
|
|
21
|
+
}
|
|
22
|
+
finishDispatch(threadId, attemptId) {
|
|
23
|
+
const attempt = this.active.get(threadId);
|
|
24
|
+
if (attempt?.attemptId === attemptId)
|
|
25
|
+
this.write(attempt, { kind: "completed", level: "unknown", ok: false, reason: "dispatch ended before provider completion" });
|
|
26
|
+
}
|
|
27
|
+
write(attempt, fields) {
|
|
28
|
+
const row = { ...attempt.expected, version: 1, id: randomUUID(), at: new Date().toISOString(),
|
|
29
|
+
logicalTurnId: attempt.logicalTurnId, attemptId: attempt.attemptId, ...(attempt.turnId ? { turnId: attempt.turnId } : {}), ...fields };
|
|
30
|
+
try {
|
|
31
|
+
const directory = join(this.dataDir, "model-evidence");
|
|
32
|
+
mkdirSync(directory, { recursive: true, mode: 0o700 });
|
|
33
|
+
appendFileSync(join(directory, `${row.at.slice(0, 7)}.jsonl`), JSON.stringify(row) + "\n", { mode: 0o600 });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
this.failures++;
|
|
37
|
+
if (this.failures === 1)
|
|
38
|
+
console.error("[model-evidence] Cannot persist model history; queries will report incomplete evidence.");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
observe(event, expectation) {
|
|
42
|
+
if (!["turn.started", "session.started", "model.evidence", "model.call", "turn.retrying", "turn.engine_failover", "session.model-fallback", "turn.completed", "session.exited"].includes(event.type))
|
|
43
|
+
return;
|
|
44
|
+
const turnKey = event.turnId ? JSON.stringify([event.threadId, event.providerInstanceId ?? event.provider, event.turnId]) : undefined;
|
|
45
|
+
let attempt = turnKey ? this.byTurn.get(turnKey) : undefined;
|
|
46
|
+
if (!attempt) {
|
|
47
|
+
attempt = this.active.get(event.threadId);
|
|
48
|
+
if (!attempt || (event.type === "turn.started" && attempt.turnId && attempt.turnId !== event.turnId)) {
|
|
49
|
+
const expected = expectation();
|
|
50
|
+
if (!expected)
|
|
51
|
+
return;
|
|
52
|
+
this.begin(expected);
|
|
53
|
+
attempt = this.active.get(event.threadId);
|
|
54
|
+
}
|
|
55
|
+
if (event.turnId) {
|
|
56
|
+
attempt.turnId = event.turnId;
|
|
57
|
+
this.byTurn.set(turnKey, attempt);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const base = { provider: event.provider, instanceId: event.providerInstanceId ?? attempt.expected.instanceId };
|
|
61
|
+
switch (event.type) {
|
|
62
|
+
case "turn.started":
|
|
63
|
+
this.write(attempt, { ...base, kind: "start", level: "requested-only" });
|
|
64
|
+
break;
|
|
65
|
+
case "model.call":
|
|
66
|
+
this.write(attempt, { ...base, kind: "call", level: "unknown", callId: event.callId });
|
|
67
|
+
break;
|
|
68
|
+
case "model.evidence":
|
|
69
|
+
this.write(attempt, { ...base, kind: "evidence", level: safe(event.model) ? event.level : "unknown",
|
|
70
|
+
...(safe(event.model) ? { reportedModel: safe(event.model) } : {}), callId: event.callId, responseId: safe(event.responseId) });
|
|
71
|
+
break;
|
|
72
|
+
case "session.started":
|
|
73
|
+
this.write(attempt, { ...base, kind: "evidence", level: safe(event.model) ? event.modelEvidenceLevel ?? "requested-only" : "unknown", reportedModel: safe(event.model) });
|
|
74
|
+
break;
|
|
75
|
+
case "turn.retrying": {
|
|
76
|
+
this.write(attempt, { ...base, kind: "retry", level: "unknown", reason: safe(event.reason) });
|
|
77
|
+
// CLI retries reuse their native turn id, but are distinct attempts.
|
|
78
|
+
this.begin(attempt.expected, true);
|
|
79
|
+
const retry = this.active.get(event.threadId);
|
|
80
|
+
retry.turnId = event.turnId;
|
|
81
|
+
if (turnKey)
|
|
82
|
+
this.byTurn.set(turnKey, retry);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case "turn.engine_failover":
|
|
86
|
+
case "session.model-fallback":
|
|
87
|
+
this.write(attempt, { ...base, kind: "fallback", level: "unknown", reason: safe(event.reason) ?? "provider fallback" });
|
|
88
|
+
break;
|
|
89
|
+
case "turn.completed":
|
|
90
|
+
this.write(attempt, { ...base, kind: "completed", level: "unknown", ok: event.ok });
|
|
91
|
+
break;
|
|
92
|
+
case "session.exited":
|
|
93
|
+
this.write(attempt, { ...base, kind: "exited", level: "unknown" });
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
// ponytail: bounded correlation cache; durable history remains on disk.
|
|
97
|
+
if (this.byTurn.size > 10000)
|
|
98
|
+
this.byTurn.delete(this.byTurn.keys().next().value);
|
|
99
|
+
if (this.active.size > 10000)
|
|
100
|
+
this.active.delete(this.active.keys().next().value);
|
|
101
|
+
}
|
|
102
|
+
query(botId, threadId, from, to) {
|
|
103
|
+
const directory = join(this.dataDir, "model-evidence");
|
|
104
|
+
const rows = [];
|
|
105
|
+
let incomplete = this.failures > 0;
|
|
106
|
+
let files = [];
|
|
107
|
+
try {
|
|
108
|
+
files = readdirSync(directory).filter(file => /^\d{4}-\d{2}\.jsonl$/.test(file)).sort();
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (error.code !== "ENOENT")
|
|
112
|
+
incomplete = true;
|
|
113
|
+
}
|
|
114
|
+
for (const file of files) {
|
|
115
|
+
if (file.slice(0, 7) < from.toISOString().slice(0, 7) || file.slice(0, 7) > to.toISOString().slice(0, 7))
|
|
116
|
+
continue;
|
|
117
|
+
try {
|
|
118
|
+
for (const line of readFileSync(join(directory, file), "utf8").split("\n").filter(Boolean)) {
|
|
119
|
+
try {
|
|
120
|
+
const row = JSON.parse(line);
|
|
121
|
+
if (row.version !== 1 || !row.logicalTurnId || !row.attemptId) {
|
|
122
|
+
incomplete = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (row.botId === botId && row.threadId === threadId && row.at >= from.toISOString() && row.at <= to.toISOString())
|
|
126
|
+
rows.push(row);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
incomplete = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
incomplete = true;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const latestDispatch = rows.findLast(row => row.kind === "dispatch");
|
|
138
|
+
const latest = latestDispatch && rows.findLast(row => row.attemptId === latestDispatch.attemptId);
|
|
139
|
+
// Late exit/events from an older attempt must not settle a newer attempt.
|
|
140
|
+
const running = latest && !rows.some(row => row.attemptId === latest.attemptId && ["completed", "exited"].includes(row.kind));
|
|
141
|
+
return { rows, metrics: modelEvidenceMetrics(rows), incomplete, writeFailures: this.failures,
|
|
142
|
+
current: running && this.active.has(threadId) ? latest.logicalTurnId : null,
|
|
143
|
+
interrupted: Boolean(running && !this.active.has(threadId)), last: latest?.logicalTurnId ?? null };
|
|
144
|
+
}
|
|
145
|
+
}
|