@codemem/server 0.44.0-alpha.1 → 0.44.0
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/index.d.ts.map +1 -1
- package/dist/index.js +640 -53
- package/dist/index.js.map +1 -1
- package/dist/routes/diagnostics.d.ts +31 -0
- package/dist/routes/diagnostics.d.ts.map +1 -0
- package/dist/routes/memory.d.ts.map +1 -1
- package/dist/routes/pack.d.ts.map +1 -1
- package/dist/routes/stats.d.ts.map +1 -1
- package/dist/routes/team-setup-view.d.ts +2 -1
- package/dist/routes/team-setup-view.d.ts.map +1 -1
- package/dist/routes/team-setup.d.ts +20 -1
- package/dist/routes/team-setup.d.ts.map +1 -1
- package/package.json +2 -2
- package/static/app.js +41 -41
- package/static/app.js.br +0 -0
- package/static/app.js.gz +0 -0
- package/static/index.html +184 -21
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { dirname, isAbsolute, join, posix, resolve, win32 } from "node:path";
|
|
3
|
-
import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, MemoryStore, PROMPT_TRANSPORT_PROTOCOL_RANGE, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, containLegacyTeamSetupCompletionConflict, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateLegacyTeamCompletionAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGetLegacyTeamCompletionAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListLegacyTeamCompletionsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, summarizeInboundScopeRejections, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
|
|
3
|
+
import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, CoordinatorReciprocalApprovalRequestChangedError, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, LOCAL_SYNC_FEATURES, LegacyTeamSetupAdditiveConvergenceError, LegacyTeamSetupRosterCapacityError, MemoryStore, PROMPT_TRANSPORT_PROTOCOL_RANGE, RawEventIngestValidationError, RecipientPolicyEdgeRequestError, RecipientPolicyTeamRenameError, RemoteCoordinatorRequestError, SCOPE_MEMBERSHIP_REVOCATION_LIMITATION, SYNC_AUTHORIZATION_REFRESH_HEADER, SYNC_CAPABILITY_HEADER, SYNC_FEATURES_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, VIEWER_IDENTITY_TARGET_KEYS, VIEWER_SERVICE_DISCRIMINATOR, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyLegacyTeamSetupCompletionManifest, applyLegacyTeamSetupCompletionManifestAndReturnActivation, applyReplicationOps, areLegacyTeamSetupCompletionPolicyFactsAdditivelyCompatible, automaticRecallHealth, buildAuthHeaders, buildBaseUrl, buildDirectPeerAuthHeaders, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, buildViewerIdentityTarget, canonicalCoordinatorLegacyTeamCompletionManifestJson, canonicalMemoryKind, canonicalWorkspaceIdentity, claimRecipientPolicyActorMutations, claimRecipientPolicyPublicationMutation, cleanupNonces, clearLegacyTeamSetupDeviceDecision, clonePromptPackAttempt, coerceObserverCommand, commitDeviceIdentityBindings, commitRecipientPolicyEdges, containLegacyTeamSetupCompletionConflict, coordinatorArchiveGroupAction, coordinatorCreateAddDeviceInviteAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateLegacyTeamCompletionAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGetLegacyTeamCompletionAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListConsumedTeamInvitesAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListLegacyTeamCompletionsAction, coordinatorListReviewedRecipientInviteEvidenceAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, countShareableProjectMemories, createCoordinatorReciprocalApproval, decodeInvitePayload, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, deriveLegacyTeamSetupCompletionManifest, detectInstallKind, deterministicPolicyTeamId, diagnoseStalePeerReceivedRows, discoverLegacyTeamCandidates, ensureDeviceIdentity, executeShareProvisioning, extractInvitePayload, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, friendlyDeviceName, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorEnrollmentReconciliationIssueSummary, getCoordinatorGroupPreference, getLegacyTeamSetupDraft, getRecipientPolicyAuthorityState, getSemanticIndexDiagnostics, getSyncResetState, getUpdateStatus, ingestRawEvents, inspectFreshLegacyTeamSetupActivation, inspectLegacyTeamSetupActivation, inviteTokenDigest, isAutomaticRecallMeasurement, isLegacyTeamCandidateSelectable, isLegacyTeamSetupProjectMappingIdentity, isPeerTrustBindingCompatible, isProjectSyncEnablementError, isScopedSyncCapability, legacyTeamCandidateId, legacyTeamCandidateProjectInventory, legacyTeamCanonicalProjectRef, legacyTeamDeviceRef, legacyTeamResolvedProjectRef, legacyTeamSetupApiErrorCode, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listDeviceIdentityInventory, listInboundScopeRejections, listLegacyRecipientPolicyProjections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listRecipientPolicyIntent, listRecipientPolicyReview, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, migrateRecipientPolicyIntent, negotiateSyncCapability, normalizeAddress, normalizeHumanPresentationName, normalizeSyncCapability, normalizeSyncFeatures, normalizeTeammateName, parseAcceptedProjectIntent, parsePositiveMemoryId, parseReassignScopePayload, parseRecipientPolicyEdgeCommitRequest, parseStrictInteger, parseSyncScopeRequest, persistShareOperation, personalScopeGrantStatusForPeer, planShareOperation, previewDeviceIdentityBindings, previewLegacyTeamSetupActivation, previewRecipientPolicyEdges, previewRecipientPolicyOnboarding, previewRecipientPolicyOnboardingFromReviewedIntent, probeAvailableCredentials, projectShareLifecycle, promptPackArtifactFingerprint, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recipientInviteAuthoritativeIdentityId, recipientPolicyDigest, recipientReviewedIntentDigest, reconcileCoordinatorEnrollmentSnapshot, reconcileRecipientPolicyProject, reconcileShareOperationAcceptance, reconstructLegacyTeamSetupCompletionManifest, recordAutomaticRecall, recordHighestObservedDirectSignatureVersion, recordNonce, recordPromptPackArtifacts, recordPromptPackTerminal, refreshAuthorizedCoordinatorPeerTrust, refreshConfiguredScopeMembershipCache, refreshLegacyTeamCandidate, rejectInboundScopeFailures, renameRecipientPolicyTeam, replayLegacyTeamSetupActivation, requestJson, requireLegacyTeamSetupAccessDeltaWithinLimit, resolveDbPath, resolveProject, resolveRecipientPolicyReview, resolveRecipientPolicyReviewBulk, runSyncPass, schema, serializeRecipientPolicyCoordinatorGroupMutation, serializeRecipientPolicyPublicationMutation, serializeRecipientPolicyTeamMutation, setLegacyTeamSetupDeviceAssignment, setLegacyTeamSetupDeviceDecision, setLegacyTeamSetupProjectMapping, summarizeInboundScopeRejections, summaryLikeSqlPredicate, supportsSyncFeature, syncScopeResetRequiredPayload, tryUpdateRetrievalDelivery, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, validateLegacyTeamSetupCompletionManifest, validateLegacyTeamSetupCompletionManifestBinding, verifyDirectPeerSignature, verifyRecipientReviewedIntent, verifySignature, writeCodememConfigFile } from "@codemem/core";
|
|
4
4
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
5
5
|
import { Hono } from "hono";
|
|
6
6
|
import { createMiddleware } from "hono/factory";
|
|
7
7
|
import { RateLimiter } from "limiter";
|
|
8
|
+
import { createCipheriv, createDecipheriv, createHash, randomBytes, randomUUID } from "node:crypto";
|
|
8
9
|
import { and, count, desc, eq, inArray, max, ne } from "drizzle-orm";
|
|
9
10
|
import { drizzle } from "drizzle-orm/better-sqlite3";
|
|
10
11
|
import { homedir, hostname, networkInterfaces } from "node:os";
|
|
11
|
-
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
//#region src/middleware.ts
|
|
13
13
|
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set([
|
|
14
14
|
"127.0.0.1",
|
|
@@ -513,6 +513,363 @@ function configRoutes(opts = {}) {
|
|
|
513
513
|
return app;
|
|
514
514
|
}
|
|
515
515
|
//#endregion
|
|
516
|
+
//#region src/routes/diagnostics.ts
|
|
517
|
+
var DEFAULT_LIMIT = 50;
|
|
518
|
+
var MAX_LIMIT = 100;
|
|
519
|
+
var CURSOR_KEY = randomBytes(32);
|
|
520
|
+
var DIAGNOSTIC_SEVERITIES = [
|
|
521
|
+
"info",
|
|
522
|
+
"warning",
|
|
523
|
+
"error"
|
|
524
|
+
];
|
|
525
|
+
var DIAGNOSTIC_SUBSYSTEMS = [
|
|
526
|
+
"viewer",
|
|
527
|
+
"observer",
|
|
528
|
+
"capture",
|
|
529
|
+
"sync",
|
|
530
|
+
"storage",
|
|
531
|
+
"maintenance"
|
|
532
|
+
];
|
|
533
|
+
function opaqueId(kind, sourceId) {
|
|
534
|
+
return createHash("sha256").update(`${kind}\0${String(sourceId)}`).digest("base64url").slice(0, 22);
|
|
535
|
+
}
|
|
536
|
+
function encodeCursor(event) {
|
|
537
|
+
const iv = randomBytes(12);
|
|
538
|
+
const cipher = createCipheriv("aes-256-gcm", CURSOR_KEY, iv);
|
|
539
|
+
const plaintext = JSON.stringify({
|
|
540
|
+
occurredAt: event.occurred_at,
|
|
541
|
+
orderKey: event.orderKey
|
|
542
|
+
});
|
|
543
|
+
const encrypted = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
|
|
544
|
+
return Buffer.concat([
|
|
545
|
+
iv,
|
|
546
|
+
cipher.getAuthTag(),
|
|
547
|
+
encrypted
|
|
548
|
+
]).toString("base64url");
|
|
549
|
+
}
|
|
550
|
+
function parseCursor(raw) {
|
|
551
|
+
if (!raw) return null;
|
|
552
|
+
try {
|
|
553
|
+
const encoded = Buffer.from(raw, "base64url");
|
|
554
|
+
if (encoded.length < 29) return "invalid";
|
|
555
|
+
const decipher = createDecipheriv("aes-256-gcm", CURSOR_KEY, encoded.subarray(0, 12));
|
|
556
|
+
decipher.setAuthTag(encoded.subarray(12, 28));
|
|
557
|
+
const plaintext = Buffer.concat([decipher.update(encoded.subarray(28)), decipher.final()]).toString("utf8");
|
|
558
|
+
const parsed = JSON.parse(plaintext);
|
|
559
|
+
if (typeof parsed.occurredAt !== "string" || !/^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:.+-]+Z$/.test(parsed.occurredAt) || !Number.isFinite(Date.parse(parsed.occurredAt))) return "invalid";
|
|
560
|
+
if (typeof parsed.orderKey !== "string" || parsed.orderKey.length < 3 || parsed.orderKey.length > 2048) return "invalid";
|
|
561
|
+
return {
|
|
562
|
+
occurredAt: parsed.occurredAt,
|
|
563
|
+
orderKey: parsed.orderKey
|
|
564
|
+
};
|
|
565
|
+
} catch {
|
|
566
|
+
return "invalid";
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
function parseLimit(raw) {
|
|
570
|
+
if (raw == null || raw === "") return DEFAULT_LIMIT;
|
|
571
|
+
if (!/^\d+$/.test(raw)) return "invalid";
|
|
572
|
+
const parsed = Number(raw);
|
|
573
|
+
if (!Number.isSafeInteger(parsed) || parsed <= 0) return "invalid";
|
|
574
|
+
return Math.min(parsed, MAX_LIMIT);
|
|
575
|
+
}
|
|
576
|
+
function parseFilter(raw, allowed) {
|
|
577
|
+
if (raw == null) return new Set(allowed);
|
|
578
|
+
const values = raw.split(",").map((value) => value.trim()).filter(Boolean);
|
|
579
|
+
if (!values.length || values.some((value) => !allowed.includes(value))) return "invalid";
|
|
580
|
+
return new Set(values);
|
|
581
|
+
}
|
|
582
|
+
function parseTechnical(raw) {
|
|
583
|
+
if (raw == null || raw === "0") return false;
|
|
584
|
+
if (raw === "1") return true;
|
|
585
|
+
return "invalid";
|
|
586
|
+
}
|
|
587
|
+
function technicalDetail(available, text, include) {
|
|
588
|
+
if (!available) return { available: false };
|
|
589
|
+
return include ? {
|
|
590
|
+
available: true,
|
|
591
|
+
text: text.slice(0, 2e3)
|
|
592
|
+
} : { available: true };
|
|
593
|
+
}
|
|
594
|
+
var SYNC_OCCURRED_AT_SQL = "CASE WHEN finished_at IS NULL THEN started_at ELSE finished_at END";
|
|
595
|
+
function syncSelect(options) {
|
|
596
|
+
if (!options.subsystems.has("sync")) return null;
|
|
597
|
+
const predicates = [];
|
|
598
|
+
if (!options.severities.has("info")) predicates.push("ok = 0");
|
|
599
|
+
if (!options.severities.has("error")) predicates.push("ok <> 0");
|
|
600
|
+
if (!options.severities.has("info") && !options.severities.has("error")) return null;
|
|
601
|
+
if (options.cursor) predicates.push(`(${SYNC_OCCURRED_AT_SQL} < @cursorOccurredAt OR
|
|
602
|
+
(${SYNC_OCCURRED_AT_SQL} = @cursorOccurredAt AND
|
|
603
|
+
's:' || printf('%020d', id) < @cursorOrderKey))`);
|
|
604
|
+
return `SELECT 'sync' AS source_type, id AS source_id,
|
|
605
|
+
's:' || printf('%020d', id) AS order_key,
|
|
606
|
+
${SYNC_OCCURRED_AT_SQL} AS occurred_at,
|
|
607
|
+
CASE WHEN ok <> 0 THEN 'succeeded' ELSE 'failed' END AS status,
|
|
608
|
+
ops_in AS metric_a, ops_out AS metric_b,
|
|
609
|
+
CASE
|
|
610
|
+
WHEN lower(error) LIKE '%auth%' OR lower(error) LIKE '%unauthorized%' THEN 'authentication'
|
|
611
|
+
WHEN lower(error) LIKE '%timeout%' OR lower(error) LIKE '%timed out%' THEN 'timeout'
|
|
612
|
+
WHEN lower(error) LIKE '%version%' OR lower(error) LIKE '%capability%' THEN 'compatibility'
|
|
613
|
+
WHEN lower(error) LIKE '%connect%' OR lower(error) LIKE '%network%'
|
|
614
|
+
OR lower(error) LIKE '%dns%' THEN 'connectivity'
|
|
615
|
+
ELSE 'unspecified'
|
|
616
|
+
END AS category
|
|
617
|
+
FROM sync_attempts ${predicates.length ? `WHERE ${predicates.join(" AND ")}` : ""}
|
|
618
|
+
ORDER BY ${SYNC_OCCURRED_AT_SQL} DESC, id DESC
|
|
619
|
+
LIMIT @sourceLimit`;
|
|
620
|
+
}
|
|
621
|
+
function observerSelect(options) {
|
|
622
|
+
if (!options.severities.has("error")) return null;
|
|
623
|
+
const observer = options.subsystems.has("observer");
|
|
624
|
+
const capture = options.subsystems.has("capture");
|
|
625
|
+
if (!observer && !capture) return null;
|
|
626
|
+
let providerFilter = "";
|
|
627
|
+
if (observer && !capture) providerFilter = "AND observer_provider IS NOT NULL";
|
|
628
|
+
if (capture && !observer) providerFilter = "AND observer_provider IS NULL";
|
|
629
|
+
const cursorFilter = options.cursor ? `AND (updated_at < @cursorOccurredAt OR
|
|
630
|
+
(updated_at = @cursorOccurredAt AND
|
|
631
|
+
'o:' || printf('%020d', id) < @cursorOrderKey))` : "";
|
|
632
|
+
return [
|
|
633
|
+
"error",
|
|
634
|
+
"failed",
|
|
635
|
+
"gave_up"
|
|
636
|
+
].map((status) => `SELECT * FROM (
|
|
637
|
+
SELECT
|
|
638
|
+
CASE WHEN observer_provider IS NULL THEN 'capture_failure' ELSE 'observer' END AS source_type,
|
|
639
|
+
id AS source_id, 'o:' || printf('%020d', id) AS order_key,
|
|
640
|
+
updated_at AS occurred_at, status, attempt_count AS metric_a,
|
|
641
|
+
NULL AS metric_b,
|
|
642
|
+
CASE
|
|
643
|
+
WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%auth%' THEN 'authentication'
|
|
644
|
+
WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%rate%'
|
|
645
|
+
OR lower(COALESCE(observer_error_code, error_type)) LIKE '%limit%' THEN 'rate limit'
|
|
646
|
+
WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%timeout%' THEN 'timeout'
|
|
647
|
+
WHEN lower(COALESCE(observer_error_code, error_type)) LIKE '%schema%'
|
|
648
|
+
OR lower(COALESCE(observer_error_code, error_type)) LIKE '%parse%' THEN 'response format'
|
|
649
|
+
ELSE 'unspecified'
|
|
650
|
+
END AS category
|
|
651
|
+
FROM raw_event_flush_batches
|
|
652
|
+
WHERE status = '${status}' ${providerFilter} ${cursorFilter}
|
|
653
|
+
ORDER BY updated_at DESC, id DESC
|
|
654
|
+
LIMIT @sourceLimit
|
|
655
|
+
)`).join(" UNION ALL ");
|
|
656
|
+
}
|
|
657
|
+
function maintenanceSelect(options) {
|
|
658
|
+
if (!options.subsystems.has("maintenance")) return null;
|
|
659
|
+
const statuses = [];
|
|
660
|
+
if (options.severities.has("info")) statuses.push("'pending'", "'running'");
|
|
661
|
+
if (options.severities.has("warning")) statuses.push("'cancelled'");
|
|
662
|
+
if (options.severities.has("error")) statuses.push("'failed'");
|
|
663
|
+
if (!statuses.length) return null;
|
|
664
|
+
return `SELECT 'maintenance' AS source_type, kind AS source_id,
|
|
665
|
+
'm:' || hex(kind) AS order_key, updated_at AS occurred_at,
|
|
666
|
+
status, progress_current AS metric_a, progress_total AS metric_b,
|
|
667
|
+
NULL AS category
|
|
668
|
+
FROM maintenance_jobs WHERE status IN (${statuses.join(", ")})`;
|
|
669
|
+
}
|
|
670
|
+
function backlogSelect(options) {
|
|
671
|
+
if (!options.subsystems.has("capture")) return null;
|
|
672
|
+
const warning = options.severities.has("warning");
|
|
673
|
+
const error = options.severities.has("error");
|
|
674
|
+
if (!warning && !error) return null;
|
|
675
|
+
let having = "pending >= 200";
|
|
676
|
+
if (!warning) having = "pending >= 1000";
|
|
677
|
+
if (!error) having = "pending >= 200 AND pending < 1000";
|
|
678
|
+
return `WITH candidate_sessions AS MATERIALIZED (
|
|
679
|
+
SELECT source, stream_id, last_flushed_event_seq, updated_at
|
|
680
|
+
FROM raw_event_sessions
|
|
681
|
+
WHERE last_received_event_seq > last_flushed_event_seq
|
|
682
|
+
ORDER BY updated_at DESC
|
|
683
|
+
LIMIT 1001
|
|
684
|
+
), bounded_sessions AS MATERIALIZED (
|
|
685
|
+
SELECT source, stream_id, last_flushed_event_seq, updated_at
|
|
686
|
+
FROM candidate_sessions
|
|
687
|
+
ORDER BY updated_at DESC
|
|
688
|
+
LIMIT 1000
|
|
689
|
+
), retained_sessions AS MATERIALIZED (
|
|
690
|
+
SELECT updated_at,
|
|
691
|
+
(SELECT COUNT(*) FROM raw_events AS events
|
|
692
|
+
WHERE events.source = sessions.source
|
|
693
|
+
AND events.stream_id = sessions.stream_id
|
|
694
|
+
AND events.event_seq > sessions.last_flushed_event_seq) AS pending
|
|
695
|
+
FROM bounded_sessions AS sessions
|
|
696
|
+
)
|
|
697
|
+
SELECT 'backlog' AS source_type, 0 AS source_id,
|
|
698
|
+
'b:' || printf('%020d', 0) AS order_key, occurred_at,
|
|
699
|
+
CASE WHEN pending >= 1000 THEN 'error' ELSE 'warning' END AS status,
|
|
700
|
+
pending AS metric_a, sessions AS metric_b,
|
|
701
|
+
CASE WHEN candidate_count > 1000 THEN 'capped' ELSE NULL END AS category
|
|
702
|
+
FROM (
|
|
703
|
+
SELECT MAX(updated_at) AS occurred_at,
|
|
704
|
+
SUM(pending) AS pending,
|
|
705
|
+
COUNT(*) FILTER (WHERE pending > 0) AS sessions,
|
|
706
|
+
(SELECT COUNT(*) FROM candidate_sessions) AS candidate_count
|
|
707
|
+
FROM retained_sessions
|
|
708
|
+
WHERE pending > 0
|
|
709
|
+
) WHERE ${having}`;
|
|
710
|
+
}
|
|
711
|
+
function sourceSelects(options) {
|
|
712
|
+
return [
|
|
713
|
+
syncSelect(options),
|
|
714
|
+
observerSelect(options),
|
|
715
|
+
maintenanceSelect(options),
|
|
716
|
+
backlogSelect(options)
|
|
717
|
+
].filter((select) => select != null);
|
|
718
|
+
}
|
|
719
|
+
function boundedSourceSelect(select, hasCursor) {
|
|
720
|
+
return `SELECT * FROM (
|
|
721
|
+
SELECT * FROM (${select})
|
|
722
|
+
${hasCursor ? "WHERE occurred_at < @cursorOccurredAt OR (occurred_at = @cursorOccurredAt AND order_key < @cursorOrderKey)" : ""}
|
|
723
|
+
ORDER BY occurred_at DESC, order_key DESC
|
|
724
|
+
LIMIT @sourceLimit
|
|
725
|
+
)`;
|
|
726
|
+
}
|
|
727
|
+
function loadSourceRows(store, options) {
|
|
728
|
+
const selects = sourceSelects(options);
|
|
729
|
+
if (!selects.length) return [];
|
|
730
|
+
const boundedSelects = selects.map((select) => boundedSourceSelect(select, options.cursor != null));
|
|
731
|
+
const params = {
|
|
732
|
+
resultLimit: options.limit + 1,
|
|
733
|
+
sourceLimit: options.limit + 1
|
|
734
|
+
};
|
|
735
|
+
if (options.cursor) {
|
|
736
|
+
params.cursorOccurredAt = options.cursor.occurredAt;
|
|
737
|
+
params.cursorOrderKey = options.cursor.orderKey;
|
|
738
|
+
}
|
|
739
|
+
return store.db.prepare(`SELECT * FROM (${boundedSelects.join(" UNION ALL ")})
|
|
740
|
+
ORDER BY occurred_at DESC, order_key DESC
|
|
741
|
+
LIMIT @resultLimit`).all(params);
|
|
742
|
+
}
|
|
743
|
+
function syncEvent(row, includeTechnical) {
|
|
744
|
+
const succeeded = row.status === "succeeded";
|
|
745
|
+
const opsIn = Number(row.metric_a ?? 0);
|
|
746
|
+
const opsOut = Number(row.metric_b ?? 0);
|
|
747
|
+
const category = row.category ?? "unspecified";
|
|
748
|
+
return {
|
|
749
|
+
id: opaqueId("sync-attempt", row.source_id),
|
|
750
|
+
orderKey: row.order_key,
|
|
751
|
+
occurred_at: row.occurred_at,
|
|
752
|
+
severity: succeeded ? "info" : "error",
|
|
753
|
+
subsystem: "sync",
|
|
754
|
+
code: succeeded ? "sync_attempt_succeeded" : "sync_attempt_failed",
|
|
755
|
+
message: succeeded ? "A sync attempt completed." : "A sync attempt failed before all work completed.",
|
|
756
|
+
recovery: succeeded ? void 0 : {
|
|
757
|
+
label: "Open advanced sync diagnostics",
|
|
758
|
+
href: "#advanced/sync/diagnostics"
|
|
759
|
+
},
|
|
760
|
+
technical_detail: technicalDetail(true, succeeded ? `${opsIn} inbound and ${opsOut} outbound operations.` : `Failure category: ${category}. ${opsIn} inbound and ${opsOut} outbound operations.`, includeTechnical)
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
function failureEvent(row, includeTechnical) {
|
|
764
|
+
const subsystem = row.source_type === "observer" ? "observer" : "capture";
|
|
765
|
+
const attempts = Math.max(0, Number(row.metric_a ?? 0));
|
|
766
|
+
const gaveUp = row.status === "gave_up";
|
|
767
|
+
return {
|
|
768
|
+
id: opaqueId("processing-failure", row.source_id),
|
|
769
|
+
orderKey: row.order_key,
|
|
770
|
+
occurred_at: row.occurred_at,
|
|
771
|
+
severity: "error",
|
|
772
|
+
subsystem,
|
|
773
|
+
code: `${subsystem}_flush_${gaveUp ? "gave_up" : "failed"}`,
|
|
774
|
+
message: gaveUp ? "Processing stopped after all retry attempts were exhausted." : "Processing failed and queued events are waiting for retry.",
|
|
775
|
+
recovery: {
|
|
776
|
+
label: "Open Health",
|
|
777
|
+
href: "#health"
|
|
778
|
+
},
|
|
779
|
+
technical_detail: technicalDetail(true, `Failure category: ${row.category ?? "unspecified"}. Attempts: ${attempts}.`, includeTechnical)
|
|
780
|
+
};
|
|
781
|
+
}
|
|
782
|
+
function maintenanceEvent(row, includeTechnical) {
|
|
783
|
+
let severity = "info";
|
|
784
|
+
let code = "maintenance_job_active";
|
|
785
|
+
let message = "A background maintenance task is active.";
|
|
786
|
+
if (row.status === "failed") {
|
|
787
|
+
severity = "error";
|
|
788
|
+
code = "maintenance_job_failed";
|
|
789
|
+
message = "A background maintenance task failed.";
|
|
790
|
+
} else if (row.status === "cancelled") {
|
|
791
|
+
severity = "warning";
|
|
792
|
+
code = "maintenance_job_cancelled";
|
|
793
|
+
message = "A background maintenance task was cancelled.";
|
|
794
|
+
}
|
|
795
|
+
const total = row.metric_b == null ? "unknown" : String(row.metric_b);
|
|
796
|
+
return {
|
|
797
|
+
id: opaqueId("maintenance", row.source_id),
|
|
798
|
+
orderKey: row.order_key,
|
|
799
|
+
occurred_at: row.occurred_at,
|
|
800
|
+
severity,
|
|
801
|
+
subsystem: "maintenance",
|
|
802
|
+
code,
|
|
803
|
+
message,
|
|
804
|
+
recovery: severity === "error" ? {
|
|
805
|
+
label: "Open Health",
|
|
806
|
+
href: "#health"
|
|
807
|
+
} : void 0,
|
|
808
|
+
technical_detail: technicalDetail(true, `Progress: ${Number(row.metric_a ?? 0)} of ${total}.`, includeTechnical)
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function backlogEvent(row, includeTechnical) {
|
|
812
|
+
const severity = row.status === "error" ? "error" : "warning";
|
|
813
|
+
const pending = Number(row.metric_a ?? 0);
|
|
814
|
+
const sessions = Number(row.metric_b ?? 0);
|
|
815
|
+
const countPrefix = row.category === "capped" ? "At least " : "";
|
|
816
|
+
const sessionLabel = sessions === 1 ? "session" : "sessions";
|
|
817
|
+
return {
|
|
818
|
+
id: opaqueId("capture-backlog", 0),
|
|
819
|
+
orderKey: row.order_key,
|
|
820
|
+
occurred_at: row.occurred_at,
|
|
821
|
+
severity,
|
|
822
|
+
subsystem: "capture",
|
|
823
|
+
code: severity === "error" ? "capture_backlog_high" : "capture_backlog_growing",
|
|
824
|
+
message: severity === "error" ? "The capture queue has a high pending backlog." : "The capture queue is growing and may need attention.",
|
|
825
|
+
recovery: {
|
|
826
|
+
label: "Open Health",
|
|
827
|
+
href: "#health"
|
|
828
|
+
},
|
|
829
|
+
technical_detail: technicalDetail(true, `${countPrefix}${pending} pending events across ${sessions} ${sessionLabel}.`, includeTechnical)
|
|
830
|
+
};
|
|
831
|
+
}
|
|
832
|
+
function collectEvents(store, options) {
|
|
833
|
+
return loadSourceRows(store, options).map((row) => {
|
|
834
|
+
if (row.source_type === "sync") return syncEvent(row, options.includeTechnical);
|
|
835
|
+
if (row.source_type === "maintenance") return maintenanceEvent(row, options.includeTechnical);
|
|
836
|
+
if (row.source_type === "backlog") return backlogEvent(row, options.includeTechnical);
|
|
837
|
+
return failureEvent(row, options.includeTechnical);
|
|
838
|
+
});
|
|
839
|
+
}
|
|
840
|
+
function diagnosticsRoutes(getStore) {
|
|
841
|
+
const app = new Hono();
|
|
842
|
+
app.get("/api/diagnostics/events", (c) => {
|
|
843
|
+
const limit = parseLimit(c.req.query("limit"));
|
|
844
|
+
const cursor = parseCursor(c.req.query("cursor"));
|
|
845
|
+
const severities = parseFilter(c.req.query("severity"), DIAGNOSTIC_SEVERITIES);
|
|
846
|
+
const subsystems = parseFilter(c.req.query("subsystem"), DIAGNOSTIC_SUBSYSTEMS);
|
|
847
|
+
const includeTechnical = parseTechnical(c.req.query("includeTechnical"));
|
|
848
|
+
if (limit === "invalid" || cursor === "invalid" || severities === "invalid" || subsystems === "invalid" || includeTechnical === "invalid") return c.json({ error: "invalid_diagnostics_query" }, 400);
|
|
849
|
+
const generatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
850
|
+
const events = collectEvents(getStore(), {
|
|
851
|
+
cursor,
|
|
852
|
+
includeTechnical,
|
|
853
|
+
limit,
|
|
854
|
+
severities,
|
|
855
|
+
subsystems
|
|
856
|
+
});
|
|
857
|
+
const orderedItems = events.slice(0, limit);
|
|
858
|
+
const items = orderedItems.map(({ orderKey: _orderKey, ...event }) => event);
|
|
859
|
+
const lastItem = orderedItems.at(-1);
|
|
860
|
+
const nextCursor = events.length > limit && lastItem ? encodeCursor(lastItem) : null;
|
|
861
|
+
c.header("Cache-Control", "no-store");
|
|
862
|
+
return c.json({
|
|
863
|
+
contract_version: 1,
|
|
864
|
+
items,
|
|
865
|
+
next_cursor: nextCursor,
|
|
866
|
+
redacted: true,
|
|
867
|
+
generated_at: generatedAt
|
|
868
|
+
});
|
|
869
|
+
});
|
|
870
|
+
return app;
|
|
871
|
+
}
|
|
872
|
+
//#endregion
|
|
516
873
|
//#region src/routes/health.ts
|
|
517
874
|
function databaseReachable(getStore) {
|
|
518
875
|
try {
|
|
@@ -801,18 +1158,6 @@ function countVisibleMemoryRows(store, filters) {
|
|
|
801
1158
|
const row = store.db.prepare(`SELECT COUNT(*) AS total FROM ${from} WHERE ${clauses.join(" AND ")}`).get(...filterResult.params);
|
|
802
1159
|
return Number(row?.total ?? 0);
|
|
803
1160
|
}
|
|
804
|
-
function summaryLikeSqlPredicate() {
|
|
805
|
-
return `(
|
|
806
|
-
LOWER(TRIM(COALESCE(memory_items.kind, ''))) = 'session_summary'
|
|
807
|
-
OR (
|
|
808
|
-
json_valid(COALESCE(memory_items.metadata_json, ''))
|
|
809
|
-
AND (
|
|
810
|
-
COALESCE(json_type(memory_items.metadata_json, '$.is_summary') = 'true', 0)
|
|
811
|
-
OR LOWER(TRIM(COALESCE(json_extract(memory_items.metadata_json, '$.source'), ''))) = 'observer_summary'
|
|
812
|
-
)
|
|
813
|
-
)
|
|
814
|
-
)`;
|
|
815
|
-
}
|
|
816
1161
|
function memorySearchSql(query) {
|
|
817
1162
|
const textClause = `INSTR(codemem_casefold(
|
|
818
1163
|
COALESCE(memory_items.title, '') || CHAR(10) ||
|
|
@@ -1351,9 +1696,12 @@ var PACK_KEYS = /* @__PURE__ */ new Set([
|
|
|
1351
1696
|
"compact_detail_count",
|
|
1352
1697
|
"db_path",
|
|
1353
1698
|
"identity_target",
|
|
1354
|
-
"attempt"
|
|
1699
|
+
"attempt",
|
|
1700
|
+
"automatic_context"
|
|
1355
1701
|
]);
|
|
1356
1702
|
var LEDGER_KEYS = /* @__PURE__ */ new Set([
|
|
1703
|
+
"automatic_recall",
|
|
1704
|
+
"evaluation_key",
|
|
1357
1705
|
"action",
|
|
1358
1706
|
"attempt_id",
|
|
1359
1707
|
"started_at",
|
|
@@ -1459,6 +1807,48 @@ function attemptMetadata(payload) {
|
|
|
1459
1807
|
requestId: payload.request_id ?? null
|
|
1460
1808
|
};
|
|
1461
1809
|
}
|
|
1810
|
+
function validateAutomaticContext(value) {
|
|
1811
|
+
if (value === null) return null;
|
|
1812
|
+
if (!isRecord(value)) return "automatic_context must be an object or null";
|
|
1813
|
+
for (const key of Object.keys(value)) if (key !== "source" && key !== "host_session_id") return `automatic_context contains unsupported field: ${key}`;
|
|
1814
|
+
const source = value.source;
|
|
1815
|
+
const hostSessionId = value.host_session_id;
|
|
1816
|
+
if (typeof source !== "string" || !source.trim() || source.length > 64) return "automatic_context source is invalid";
|
|
1817
|
+
if (typeof hostSessionId !== "string" || !hostSessionId.trim() || hostSessionId.length > MAX_METADATA_FIELD_CHARS || isAbsolutePath(hostSessionId)) return "automatic_context host_session_id is invalid";
|
|
1818
|
+
return {
|
|
1819
|
+
source: source.trim(),
|
|
1820
|
+
hostSessionId: hostSessionId.trim()
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
function resolveRequestAutomaticContext(value, attempt) {
|
|
1824
|
+
if (Object.hasOwn(value, "automatic_context")) return validateAutomaticContext(value.automatic_context);
|
|
1825
|
+
if (!attempt) return void 0;
|
|
1826
|
+
const source = typeof attempt.source === "string" ? attempt.source.trim().toLowerCase() : "";
|
|
1827
|
+
if (source && source !== "opencode") return void 0;
|
|
1828
|
+
const hostSessionId = attempt.source_session_id;
|
|
1829
|
+
if (source === "opencode" && typeof hostSessionId === "string" && hostSessionId.trim()) return {
|
|
1830
|
+
source,
|
|
1831
|
+
hostSessionId: hostSessionId.trim()
|
|
1832
|
+
};
|
|
1833
|
+
return null;
|
|
1834
|
+
}
|
|
1835
|
+
function packRenderOptions(value) {
|
|
1836
|
+
if (value.compact == null && value.compact_detail_count == null) return void 0;
|
|
1837
|
+
return {
|
|
1838
|
+
compact: value.compact === true || value.compact_detail_count != null,
|
|
1839
|
+
...value.compact_detail_count != null ? { compactDetailCount: value.compact_detail_count } : {}
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
function packRequestArgs(request) {
|
|
1843
|
+
return [
|
|
1844
|
+
request.context,
|
|
1845
|
+
request.limit,
|
|
1846
|
+
request.tokenBudget,
|
|
1847
|
+
request.filters,
|
|
1848
|
+
request.renderOptions,
|
|
1849
|
+
request.automaticContext
|
|
1850
|
+
];
|
|
1851
|
+
}
|
|
1462
1852
|
function validatePackRequest(value) {
|
|
1463
1853
|
if (!isRecord(value)) return "request body must be an object";
|
|
1464
1854
|
for (const key of Object.keys(value)) if (!PACK_KEYS.has(key)) return `request body contains unsupported field: ${key}`;
|
|
@@ -1490,6 +1880,8 @@ function validatePackRequest(value) {
|
|
|
1490
1880
|
if (attemptError) return attemptError;
|
|
1491
1881
|
attempt = value.attempt;
|
|
1492
1882
|
}
|
|
1883
|
+
const automaticContext = resolveRequestAutomaticContext(value, attempt);
|
|
1884
|
+
if (typeof automaticContext === "string") return automaticContext;
|
|
1493
1885
|
const filters = {};
|
|
1494
1886
|
if (value.all_projects !== true) {
|
|
1495
1887
|
const envProject = process.env.CODEMEM_PROJECT?.trim() || null;
|
|
@@ -1499,18 +1891,14 @@ function validatePackRequest(value) {
|
|
|
1499
1891
|
if (project) filters.project = project;
|
|
1500
1892
|
}
|
|
1501
1893
|
if (workingSetFiles.length > 0) filters.working_set_paths = workingSetFiles;
|
|
1502
|
-
let renderOptions;
|
|
1503
|
-
if (value.compact != null || value.compact_detail_count != null) renderOptions = {
|
|
1504
|
-
compact: value.compact === true || value.compact_detail_count != null,
|
|
1505
|
-
...value.compact_detail_count != null ? { compactDetailCount: value.compact_detail_count } : {}
|
|
1506
|
-
};
|
|
1507
1894
|
return {
|
|
1508
1895
|
context,
|
|
1509
1896
|
limit,
|
|
1510
1897
|
tokenBudget,
|
|
1511
1898
|
filters,
|
|
1512
|
-
renderOptions,
|
|
1513
|
-
attempt
|
|
1899
|
+
renderOptions: packRenderOptions(value),
|
|
1900
|
+
attempt,
|
|
1901
|
+
automaticContext
|
|
1514
1902
|
};
|
|
1515
1903
|
}
|
|
1516
1904
|
function ledgerFailureStatus(reason) {
|
|
@@ -1519,19 +1907,37 @@ function ledgerFailureStatus(reason) {
|
|
|
1519
1907
|
if (reason === "storage_unavailable") return 503;
|
|
1520
1908
|
return 400;
|
|
1521
1909
|
}
|
|
1910
|
+
function dispatchRecallMeasurement(store, payload) {
|
|
1911
|
+
const action = payload.action;
|
|
1912
|
+
if (payload.automatic_recall === void 0 && action !== "recall") return null;
|
|
1913
|
+
const isValidMeasurement = isAutomaticRecallMeasurement(payload.automatic_recall) && typeof payload.evaluation_key === "string" && /^[a-f0-9]{64}$/.test(payload.evaluation_key);
|
|
1914
|
+
if (action === "delivery") {
|
|
1915
|
+
if (!isValidMeasurement) return null;
|
|
1916
|
+
try {
|
|
1917
|
+
recordAutomaticRecall(store.db, payload.attempt_id, payload.evaluation_key, payload.automatic_recall);
|
|
1918
|
+
} catch {}
|
|
1919
|
+
return null;
|
|
1920
|
+
}
|
|
1921
|
+
if (action !== "recall" || !isValidMeasurement) return "invalid automatic recall measurement";
|
|
1922
|
+
return recordAutomaticRecall(store.db, payload.attempt_id, payload.evaluation_key, payload.automatic_recall);
|
|
1923
|
+
}
|
|
1522
1924
|
function dispatchLedger(store, payload) {
|
|
1523
1925
|
const action = payload.action;
|
|
1524
|
-
if (action !== "record" && action !== "delivery" && action !== "cache_reuse") return "ledger action is invalid";
|
|
1525
1926
|
const metadata = attemptMetadata(payload);
|
|
1526
1927
|
if (action === "delivery") {
|
|
1527
1928
|
const status = payload.delivery_status;
|
|
1528
1929
|
if (status !== "handed_off" && status !== "failed" && status !== "unknown") return "delivery action requires a valid delivery_status";
|
|
1529
|
-
|
|
1930
|
+
const outcome = tryUpdateRetrievalDelivery(store.db, metadata.attemptId, status);
|
|
1931
|
+
if (outcome.ok) dispatchRecallMeasurement(store, payload);
|
|
1932
|
+
return outcome;
|
|
1530
1933
|
}
|
|
1934
|
+
const measurement = dispatchRecallMeasurement(store, payload);
|
|
1935
|
+
if (measurement !== null) return measurement;
|
|
1531
1936
|
if (action === "cache_reuse") {
|
|
1532
1937
|
if (typeof payload.original_attempt_id !== "string" || !payload.original_attempt_id) return "cache_reuse action requires original_attempt_id";
|
|
1533
1938
|
return clonePromptPackAttempt(store.db, payload.original_attempt_id, metadata);
|
|
1534
1939
|
}
|
|
1940
|
+
if (action !== "record") return "ledger action is invalid";
|
|
1535
1941
|
if (payload.retrieval_status !== "skipped" && payload.retrieval_status !== "failed" || typeof payload.failure_code !== "string" || !payload.failure_code || typeof payload.failure_stage !== "string" || !payload.failure_stage) return "record action requires retrieval_status, failure_code, and failure_stage";
|
|
1536
1942
|
return recordPromptPackTerminal(store.db, metadata, payload.retrieval_status, payload.failure_code, payload.failure_stage);
|
|
1537
1943
|
}
|
|
@@ -1568,10 +1974,10 @@ function packTransportRoutes(getStore) {
|
|
|
1568
1974
|
const target = validateViewerTarget(store, parsed, { requireCurrentIdentity: true });
|
|
1569
1975
|
if (!target.ok) return c.json(target.body, target.status);
|
|
1570
1976
|
if (!request.attempt) {
|
|
1571
|
-
const pack = await store.buildMemoryPackAsync(request
|
|
1977
|
+
const pack = await store.buildMemoryPackAsync(...packRequestArgs(request));
|
|
1572
1978
|
return c.json(pack);
|
|
1573
1979
|
}
|
|
1574
|
-
const artifacts = await store.buildMemoryPackWithTraceAsync(request
|
|
1980
|
+
const artifacts = await store.buildMemoryPackWithTraceAsync(...packRequestArgs(request));
|
|
1575
1981
|
let ledgerArtifactFingerprint;
|
|
1576
1982
|
try {
|
|
1577
1983
|
ledgerArtifactFingerprint = promptPackArtifactFingerprint(store.db, request.context, request.filters, artifacts);
|
|
@@ -2128,7 +2534,7 @@ function statsRoutes(getStore) {
|
|
|
2128
2534
|
error: job.error
|
|
2129
2535
|
}));
|
|
2130
2536
|
return c.json({
|
|
2131
|
-
...store
|
|
2537
|
+
...healthStats(store),
|
|
2132
2538
|
viewer_pid: process.pid,
|
|
2133
2539
|
maintenance_jobs: surfacedJobs
|
|
2134
2540
|
});
|
|
@@ -2193,6 +2599,15 @@ function statsRoutes(getStore) {
|
|
|
2193
2599
|
});
|
|
2194
2600
|
return app;
|
|
2195
2601
|
}
|
|
2602
|
+
function healthStats(store) {
|
|
2603
|
+
return {
|
|
2604
|
+
...store.stats(),
|
|
2605
|
+
automatic_recall: automaticRecallHealth(store.db, {
|
|
2606
|
+
actorId: store.actorId,
|
|
2607
|
+
deviceId: store.deviceId
|
|
2608
|
+
})
|
|
2609
|
+
};
|
|
2610
|
+
}
|
|
2196
2611
|
//#endregion
|
|
2197
2612
|
//#region src/routes/team-setup-view.ts
|
|
2198
2613
|
var enabled = {
|
|
@@ -2922,10 +3337,10 @@ function projectMappingChoices(store) {
|
|
|
2922
3337
|
excludePeerReceived: true
|
|
2923
3338
|
}).filter((candidate) => !candidate.read_only && isLegacyTeamSetupProjectMappingIdentity(candidate.workspace_identity, store.db));
|
|
2924
3339
|
} catch (error) {
|
|
2925
|
-
if (error instanceof Error && (error.message === "project_scope_candidate_scan_too_large" || error.message === "project_scope_candidate_metadata_too_large")) throw new
|
|
3340
|
+
if (error instanceof Error && (error.message === "project_scope_candidate_scan_too_large" || error.message === "project_scope_candidate_metadata_too_large")) throw new LocalProjectScanBudgetError(error);
|
|
2926
3341
|
throw error;
|
|
2927
3342
|
}
|
|
2928
|
-
if (candidates.length > MAX_PROJECT_MAPPING_CHOICES) throw new
|
|
3343
|
+
if (candidates.length > MAX_PROJECT_MAPPING_CHOICES) throw new LocalProjectScanBudgetError();
|
|
2929
3344
|
return candidates.map((candidate) => ({
|
|
2930
3345
|
projectIdentity: candidate.workspace_identity,
|
|
2931
3346
|
sourceDisplayName: candidate.display_project
|
|
@@ -3001,6 +3416,156 @@ function completionApiError(error) {
|
|
|
3001
3416
|
const code = legacyTeamSetupApiErrorCode(error);
|
|
3002
3417
|
return code === "team_setup_failed" ? "team_setup_completion_unavailable" : code;
|
|
3003
3418
|
}
|
|
3419
|
+
var TeamSetupFinishError = class extends Error {
|
|
3420
|
+
reason;
|
|
3421
|
+
alreadyLogged;
|
|
3422
|
+
constructor(code, reason, alreadyLogged, cause) {
|
|
3423
|
+
super(code, cause === void 0 ? void 0 : { cause });
|
|
3424
|
+
this.reason = reason;
|
|
3425
|
+
this.alreadyLogged = alreadyLogged;
|
|
3426
|
+
this.name = "TeamSetupFinishError";
|
|
3427
|
+
}
|
|
3428
|
+
};
|
|
3429
|
+
/**
|
|
3430
|
+
* The generic `legacy_team_setup_roster_too_large` string is thrown by ten
|
|
3431
|
+
* sites for unrelated limits (coordinator roster size, draft device count,
|
|
3432
|
+
* local project choices). Only the local project-candidate scan is a
|
|
3433
|
+
* "this device's history is too large" condition, so it carries a distinct
|
|
3434
|
+
* type. The message is kept identical so the frozen API-code mapping and every
|
|
3435
|
+
* existing caller that matches on it are unchanged.
|
|
3436
|
+
*/
|
|
3437
|
+
var LocalProjectScanBudgetError = class extends Error {
|
|
3438
|
+
constructor(cause) {
|
|
3439
|
+
super("legacy_team_setup_roster_too_large", cause === void 0 ? void 0 : { cause });
|
|
3440
|
+
this.name = "LocalProjectScanBudgetError";
|
|
3441
|
+
}
|
|
3442
|
+
};
|
|
3443
|
+
/**
|
|
3444
|
+
* Remote error codes the finish path can legitimately receive from the
|
|
3445
|
+
* coordinator. Anything else — including identifier-shaped values, which a
|
|
3446
|
+
* hostile or misconfigured upstream could use to smuggle a credential through
|
|
3447
|
+
* a JSON `error` field — is reduced to a fixed classification before logging.
|
|
3448
|
+
* A shape check is not an allowlist; only this set is echoed verbatim.
|
|
3449
|
+
*/
|
|
3450
|
+
var LOGGABLE_REMOTE_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
3451
|
+
"body_too_large",
|
|
3452
|
+
"completion_manifest_invalid",
|
|
3453
|
+
"completion_manifest_unavailable",
|
|
3454
|
+
"completion_not_found",
|
|
3455
|
+
"completion_query_invalid",
|
|
3456
|
+
"group_archived",
|
|
3457
|
+
"group_id_and_candidate_ref_required",
|
|
3458
|
+
"group_not_found",
|
|
3459
|
+
"invalid_json",
|
|
3460
|
+
"missing_admin_header",
|
|
3461
|
+
"not_found",
|
|
3462
|
+
"rate_limited",
|
|
3463
|
+
"response_too_large",
|
|
3464
|
+
"unauthorized"
|
|
3465
|
+
]);
|
|
3466
|
+
function loggableRemoteCode(code) {
|
|
3467
|
+
if (LOGGABLE_REMOTE_ERROR_CODES.has(code)) return code;
|
|
3468
|
+
if (code.startsWith("non_json_response")) return "non_json_response";
|
|
3469
|
+
return "unclassified_remote_error";
|
|
3470
|
+
}
|
|
3471
|
+
/**
|
|
3472
|
+
* Local causes the finish path can produce. Same allowlist discipline as
|
|
3473
|
+
* remote codes: everything else, including transport messages that embed the
|
|
3474
|
+
* target URL, is classified rather than echoed.
|
|
3475
|
+
*/
|
|
3476
|
+
var LOGGABLE_LOCAL_CAUSES = /* @__PURE__ */ new Set([
|
|
3477
|
+
"completion_conflict",
|
|
3478
|
+
"legacy_team_setup_roster_too_large",
|
|
3479
|
+
"project_scope_candidate_metadata_too_large",
|
|
3480
|
+
"project_scope_candidate_scan_too_large",
|
|
3481
|
+
"team_setup_completion_conflict",
|
|
3482
|
+
"team_setup_completion_invalid",
|
|
3483
|
+
"team_setup_completion_unavailable",
|
|
3484
|
+
"team_setup_confirmation_stale",
|
|
3485
|
+
"team_setup_roster_unavailable"
|
|
3486
|
+
]);
|
|
3487
|
+
function loggableLocalCause(error) {
|
|
3488
|
+
if (LOGGABLE_LOCAL_CAUSES.has(error.message)) return error.message;
|
|
3489
|
+
if (error.name === "AbortError" || error.name === "TimeoutError") return error.name;
|
|
3490
|
+
if (/fetch failed/iu.test(error.message)) return "fetch_failed";
|
|
3491
|
+
if (/timed out|timeout/iu.test(error.message)) return "timed_out";
|
|
3492
|
+
return `unclassified_error:${error.name || "Error"}`;
|
|
3493
|
+
}
|
|
3494
|
+
function errorChain(error) {
|
|
3495
|
+
const chain = [];
|
|
3496
|
+
let current = error;
|
|
3497
|
+
while (current && !chain.includes(current)) {
|
|
3498
|
+
chain.push(current);
|
|
3499
|
+
current = current instanceof Error ? current.cause : void 0;
|
|
3500
|
+
}
|
|
3501
|
+
return chain;
|
|
3502
|
+
}
|
|
3503
|
+
function remoteFailureReason(remote, fallback) {
|
|
3504
|
+
if (remote.status === 404 && (remote.code === "not_found" || remote.code.startsWith("non_json"))) return "coordinator_route_missing";
|
|
3505
|
+
if (remote.status === 400 && remote.code === "completion_manifest_invalid") return "coordinator_rejected_manifest";
|
|
3506
|
+
return fallback;
|
|
3507
|
+
}
|
|
3508
|
+
function teamSetupFinishFailureDetails(error) {
|
|
3509
|
+
const chain = errorChain(error);
|
|
3510
|
+
const wrapper = chain.find((item) => item instanceof TeamSetupFinishError);
|
|
3511
|
+
const remote = chain.find((item) => item instanceof RemoteCoordinatorRequestError);
|
|
3512
|
+
if (remote) {
|
|
3513
|
+
const reason = remoteFailureReason(remote, wrapper?.reason);
|
|
3514
|
+
return {
|
|
3515
|
+
...reason ? { reason } : {},
|
|
3516
|
+
cause: "remote coordinator request failed",
|
|
3517
|
+
status: remote.status,
|
|
3518
|
+
code: loggableRemoteCode(remote.code)
|
|
3519
|
+
};
|
|
3520
|
+
}
|
|
3521
|
+
if (chain.some((item) => item instanceof LegacyTeamSetupRosterCapacityError || item instanceof LocalProjectScanBudgetError)) return {
|
|
3522
|
+
reason: "local_candidate_scan_budget_exceeded",
|
|
3523
|
+
cause: "local project candidate scan exceeded budget"
|
|
3524
|
+
};
|
|
3525
|
+
const errorCause = chain.find((item) => item instanceof Error && !(item instanceof TeamSetupFinishError));
|
|
3526
|
+
const cause = errorCause ? loggableLocalCause(errorCause) : "unclassified_non_error";
|
|
3527
|
+
if (wrapper?.reason) return {
|
|
3528
|
+
reason: wrapper.reason,
|
|
3529
|
+
cause
|
|
3530
|
+
};
|
|
3531
|
+
if (errorCause && (errorCause.name === "AbortError" || errorCause.name === "TimeoutError" || /fetch failed|timed out|timeout/iu.test(errorCause.message))) return {
|
|
3532
|
+
reason: "coordinator_unreachable",
|
|
3533
|
+
cause
|
|
3534
|
+
};
|
|
3535
|
+
return { cause };
|
|
3536
|
+
}
|
|
3537
|
+
function warnTeamSetupFinishFailure(error, details, context) {
|
|
3538
|
+
if (errorChain(error).some((item) => item instanceof TeamSetupFinishError && item.alreadyLogged)) return;
|
|
3539
|
+
const safe = (value) => {
|
|
3540
|
+
let sanitized = String(value).replace(/[\r\n\t]+/gu, " ");
|
|
3541
|
+
for (const secret of context.redactValues ?? []) if (secret) sanitized = sanitized.replaceAll(secret, "[redacted]");
|
|
3542
|
+
return sanitized.replace(/https?:\/\/[^\s]+/giu, (value) => {
|
|
3543
|
+
try {
|
|
3544
|
+
const url = new URL(value);
|
|
3545
|
+
return `${url.protocol}//${url.host}`;
|
|
3546
|
+
} catch {
|
|
3547
|
+
return "[redacted-url]";
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
};
|
|
3551
|
+
const fields = [
|
|
3552
|
+
`candidateRef=${safe(context.candidateRef)}`,
|
|
3553
|
+
`attemptId=${safe(context.attemptId)}`,
|
|
3554
|
+
`apiCode=${safe(context.apiCode)}`,
|
|
3555
|
+
`cause=${safe(details.cause)}`,
|
|
3556
|
+
...details.reason ? [`reason=${safe(details.reason)}`] : [],
|
|
3557
|
+
...details.status == null ? [] : [`status=${safe(details.status)}`],
|
|
3558
|
+
...details.code == null ? [] : [`code=${safe(details.code)}`]
|
|
3559
|
+
];
|
|
3560
|
+
console.warn(`[team-setup finish] ${fields.join(" ")}`);
|
|
3561
|
+
}
|
|
3562
|
+
function finishErrorResponse(code, details) {
|
|
3563
|
+
const reason = details.reason;
|
|
3564
|
+
return reason ? {
|
|
3565
|
+
error: code,
|
|
3566
|
+
reason
|
|
3567
|
+
} : { error: code };
|
|
3568
|
+
}
|
|
3004
3569
|
async function parseBoundedJsonObject(c) {
|
|
3005
3570
|
if (c.req.header("content-type")?.split(";", 1)[0]?.trim().toLowerCase() !== "application/json") return { ok: false };
|
|
3006
3571
|
const lengthHeader = c.req.header("content-length");
|
|
@@ -3741,6 +4306,7 @@ function teamSetupRoutes(options) {
|
|
|
3741
4306
|
let releaseMutation;
|
|
3742
4307
|
let releasePublicationMutation;
|
|
3743
4308
|
let releaseActorMutations;
|
|
4309
|
+
let finishLogRedactValues = [];
|
|
3744
4310
|
try {
|
|
3745
4311
|
const store = options.getStore();
|
|
3746
4312
|
const draft = getLegacyTeamSetupDraft(store.db, candidateRef);
|
|
@@ -3803,8 +4369,9 @@ function teamSetupRoutes(options) {
|
|
|
3803
4369
|
if (!completionDependencies || !authorization) throw new Error("team_setup_completion_unavailable");
|
|
3804
4370
|
if (!authorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
|
|
3805
4371
|
const { config, remoteUrl } = authorization;
|
|
4372
|
+
finishLogRedactValues = [config.syncCoordinatorAdminSecret];
|
|
3806
4373
|
const freshGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
|
|
3807
|
-
if (!freshGroup) throw new
|
|
4374
|
+
if (!freshGroup) throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, /* @__PURE__ */ new Error("coordinator roster unavailable"));
|
|
3808
4375
|
if (!stillAuthorizesGroup(authorization, group)) throw new Error("team_setup_completion_invalid");
|
|
3809
4376
|
const projectInventory = legacyTeamCandidateProjectInventory(store.db, projectionOptions(store), candidateRef);
|
|
3810
4377
|
const freshPreview = inspectFreshLegacyTeamSetupActivation(store.db, {
|
|
@@ -3845,8 +4412,17 @@ function teamSetupRoutes(options) {
|
|
|
3845
4412
|
if (canonicalCoordinatorLegacyTeamCompletionManifestJson(canonicalManifest) !== canonicalCoordinatorLegacyTeamCompletionManifestJson(proposedManifest)) throw new Error("team_setup_completion_conflict");
|
|
3846
4413
|
} catch (error) {
|
|
3847
4414
|
const code = completionApiError(error);
|
|
4415
|
+
const details = teamSetupFinishFailureDetails(error);
|
|
3848
4416
|
const getCompletion = completionDependencies.get;
|
|
3849
|
-
if (code !== "team_setup_completion_conflict" || !getCompletion)
|
|
4417
|
+
if (code !== "team_setup_completion_conflict" || !getCompletion) {
|
|
4418
|
+
warnTeamSetupFinishFailure(error, details, {
|
|
4419
|
+
candidateRef,
|
|
4420
|
+
attemptId,
|
|
4421
|
+
apiCode: code,
|
|
4422
|
+
redactValues: finishLogRedactValues
|
|
4423
|
+
});
|
|
4424
|
+
throw new TeamSetupFinishError(code, details.reason, true, error);
|
|
4425
|
+
}
|
|
3850
4426
|
try {
|
|
3851
4427
|
const existing = await retryTransientCoordinatorRead((attemptTimeoutS) => getCompletion({
|
|
3852
4428
|
groupId: group.groupId,
|
|
@@ -3862,17 +4438,17 @@ function teamSetupRoutes(options) {
|
|
|
3862
4438
|
});
|
|
3863
4439
|
if (canonicalManifest.finish_digest !== proposedManifest.finish_digest) throw new Error("team_setup_completion_conflict");
|
|
3864
4440
|
} catch (recoveryError) {
|
|
3865
|
-
throw new
|
|
4441
|
+
throw new TeamSetupFinishError(completionApiError(recoveryError), teamSetupFinishFailureDetails(recoveryError).reason, false, recoveryError);
|
|
3866
4442
|
}
|
|
3867
4443
|
}
|
|
3868
4444
|
let postPublicationGroup;
|
|
3869
4445
|
try {
|
|
3870
4446
|
postPublicationGroup = await loadFreshGroupSnapshot(group, remoteUrl, publicationDeadlineMs);
|
|
3871
4447
|
} catch (error) {
|
|
3872
|
-
if (legacyTeamSetupApiErrorCode(error) === "team_setup_completion_invalid") throw new
|
|
4448
|
+
if (legacyTeamSetupApiErrorCode(error) === "team_setup_completion_invalid") throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, error);
|
|
3873
4449
|
throw error;
|
|
3874
4450
|
}
|
|
3875
|
-
if (!postPublicationGroup) throw new
|
|
4451
|
+
if (!postPublicationGroup) throw new TeamSetupFinishError("team_setup_roster_unavailable", "coordinator_roster_unavailable", false, /* @__PURE__ */ new Error("coordinator roster unavailable"));
|
|
3876
4452
|
const result = await applyLegacyTeamSetupCompletionManifestAndReturnActivation(store.db, {
|
|
3877
4453
|
coordinatorId: group.coordinatorId,
|
|
3878
4454
|
groupId: group.groupId,
|
|
@@ -3889,7 +4465,14 @@ function teamSetupRoutes(options) {
|
|
|
3889
4465
|
}));
|
|
3890
4466
|
} catch (error) {
|
|
3891
4467
|
const code = apiErrorCode(error);
|
|
3892
|
-
|
|
4468
|
+
const details = teamSetupFinishFailureDetails(error);
|
|
4469
|
+
warnTeamSetupFinishFailure(error, details, {
|
|
4470
|
+
candidateRef,
|
|
4471
|
+
attemptId,
|
|
4472
|
+
apiCode: code,
|
|
4473
|
+
redactValues: finishLogRedactValues
|
|
4474
|
+
});
|
|
4475
|
+
return c.json(finishErrorResponse(code, details), errorStatus$1(code));
|
|
3893
4476
|
} finally {
|
|
3894
4477
|
releaseActorMutations?.();
|
|
3895
4478
|
releasePublicationMutation?.();
|
|
@@ -10033,6 +10616,26 @@ function closeStore() {
|
|
|
10033
10616
|
sharedStore?.close();
|
|
10034
10617
|
sharedStore = null;
|
|
10035
10618
|
}
|
|
10619
|
+
function registerStaticRoutes(app) {
|
|
10620
|
+
const staticRoot = process.env.CODEMEM_VIEWER_STATIC_DIR ?? join(import.meta.dirname ?? ".", "../static");
|
|
10621
|
+
app.use("/assets/*", async (c, next) => {
|
|
10622
|
+
c.header("Cache-Control", "no-cache");
|
|
10623
|
+
await next();
|
|
10624
|
+
});
|
|
10625
|
+
app.use("/assets/*", serveStatic({
|
|
10626
|
+
root: staticRoot,
|
|
10627
|
+
rewriteRequestPath: (path) => path.replace(/^\/assets/, ""),
|
|
10628
|
+
precompressed: true
|
|
10629
|
+
}));
|
|
10630
|
+
const indexPath = join(staticRoot, "index.html");
|
|
10631
|
+
if (!existsSync(indexPath)) throw new Error(`Viewer assets missing at ${indexPath}. Run \`pnpm build\` from the repo root before starting the viewer.`);
|
|
10632
|
+
const indexHtml = readFileSync(indexPath, "utf-8");
|
|
10633
|
+
app.get("*", (c) => {
|
|
10634
|
+
if (c.req.path.startsWith("/api/")) return c.json({ error: "not found" }, 404);
|
|
10635
|
+
c.header("Cache-Control", "no-store");
|
|
10636
|
+
return c.html(indexHtml);
|
|
10637
|
+
});
|
|
10638
|
+
}
|
|
10036
10639
|
function createApp(opts) {
|
|
10037
10640
|
const storeFactory = opts?.storeFactory ?? getStore;
|
|
10038
10641
|
const sweeper = opts?.sweeper ?? null;
|
|
@@ -10043,6 +10646,7 @@ function createApp(opts) {
|
|
|
10043
10646
|
app.use("*", preflightHandler());
|
|
10044
10647
|
app.use("*", originGuard({ unsafeGetPathPrefixes: [TEAM_SETUP_ROUTE_PREFIX] }));
|
|
10045
10648
|
app.route("/", healthRoutes(storeFactory));
|
|
10649
|
+
app.route("/", diagnosticsRoutes(storeFactory));
|
|
10046
10650
|
app.route("/", statsRoutes(storeFactory));
|
|
10047
10651
|
app.route("/", memoryRoutes(storeFactory));
|
|
10048
10652
|
app.route("/", packTransportRoutes(storeFactory));
|
|
@@ -10069,24 +10673,7 @@ function createApp(opts) {
|
|
|
10069
10673
|
}
|
|
10070
10674
|
}));
|
|
10071
10675
|
app.route("/", updateStatusRoutes({ getUpdateStatus: opts?.getUpdateStatus }));
|
|
10072
|
-
|
|
10073
|
-
app.use("/assets/*", async (c, next) => {
|
|
10074
|
-
c.header("Cache-Control", "no-cache");
|
|
10075
|
-
await next();
|
|
10076
|
-
});
|
|
10077
|
-
app.use("/assets/*", serveStatic({
|
|
10078
|
-
root: staticRoot,
|
|
10079
|
-
rewriteRequestPath: (path) => path.replace(/^\/assets/, ""),
|
|
10080
|
-
precompressed: true
|
|
10081
|
-
}));
|
|
10082
|
-
const indexPath = join(staticRoot, "index.html");
|
|
10083
|
-
if (!existsSync(indexPath)) throw new Error(`Viewer assets missing at ${indexPath}. Run \`pnpm build\` from the repo root before starting the viewer.`);
|
|
10084
|
-
const indexHtml = readFileSync(indexPath, "utf-8");
|
|
10085
|
-
app.get("*", (c) => {
|
|
10086
|
-
if (c.req.path.startsWith("/api/")) return c.json({ error: "not found" }, 404);
|
|
10087
|
-
c.header("Cache-Control", "no-store");
|
|
10088
|
-
return c.html(indexHtml);
|
|
10089
|
-
});
|
|
10676
|
+
registerStaticRoutes(app);
|
|
10090
10677
|
return app;
|
|
10091
10678
|
}
|
|
10092
10679
|
/**
|