codemem 0.47.0-beta.3 → 0.47.0-beta.4
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.js +228 -63
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { ACCOUNT_CONNECTION_MIGRATION_STATEMENTS, ACCOUNT_UNLINK_MIGRATION_STATEMENTS, AUTH_LINK_AUTHORITY_CUTOVER_SQL, ContextOnlyReplayError, CoordinatorAccountLinkError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, FILE_CONTEXT_TRANSPORT_MAX_BYTES, FILE_CONTEXT_TRANSPORT_PROFILE, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, OwnerProfileEnrollmentError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, RemoteCoordinatorRequestError, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, atomicReplaceConfigFile, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildTieredObserverSelection, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorAuthControllerReviewAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantIdentityGroupAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorQualifyOwnerAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewInviteAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeDeviceAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, createFileContextRunner, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, describePiObserverStatus, detectInstallKind, dispatchFileContextTransport, draftDistillRule, enrollOwnerProfile, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, expandUserPath, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getCodememConfigPath, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, importPiSessions, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isCoordinatorControllerAttestationReference, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, linkCoordinatorAccount, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadRuntimeSigningPublicKey, loadSqliteVec, mdnsEnabled, mutateCodememConfigFile, normalizeCoordinatorAccountLinkOrigin, normalizePromptTransportProtocolRange, observerBaseUrlForProviderOverride, parseFileContextTransportRequest, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readAccountLinkDevice, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorAccountLinkStatus, readCoordinatorOwnerReviewLocalEvidence, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveComparablePath, resolveDbPath, resolveHookProject, resolveKeyPaths, resolvePiAgentDir, resolvePiObserverConfig, resolveProject, resolveProjectRoot, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, searchPiSessions, setPeerProjectFilter, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, upgradeCoordinatorAccountSchemaFile, upgradeCoordinatorAuthSchemaFile, vacuumDatabase, viewerUrl, writeCodememConfigFile } from "@codemem/core";
|
|
2
|
+
import { ACCOUNT_CONNECTION_MIGRATION_STATEMENTS, ACCOUNT_UNLINK_MIGRATION_STATEMENTS, AUTH_LINK_AUTHORITY_CUTOVER_SQL, ContextOnlyReplayError, CoordinatorAccountLinkError, DEDUP_KEY_BACKFILL_JOB, DEFAULT_COORDINATOR_DB_PATH, DedupKeyBackfillRunner, DeviceIdentityError, EXTRACTION_BENCHMARK_QUALITY_WEIGHTS, FILE_CONTEXT_TRANSPORT_MAX_BYTES, FILE_CONTEXT_TRANSPORT_PROFILE, LEGACY_OWNER_RESOLUTION_SCHEMA_STATEMENTS, MUTATING_TOOL_NAMES, MemoryStore, ObserverClient, ObserverOutputError, ObserverOutputTransportError, OwnerProfileEnrollmentError, PROMPT_TRANSPORT_PROTOCOL_RANGE, REF_BACKFILL_JOB, RawEventIngestValidationError, RawEventSweeper, RefBackfillRunner, RemoteCoordinatorRequestError, SCOPE_BACKFILL_JOB, SESSION_CONTEXT_BACKFILL_JOB, SUMMARY_DEDUP_BACKFILL_JOB, ScopeBackfillRunner, SessionContextBackfillRunner, SummaryDedupBackfillRunner, SyncRetentionRunner, TRUSTED_HOOK_MAPPER_OPTIONS, VERSION, VectorModelMigrationRunner, aiBackfillStructuredContent, applyBootstrapSnapshot, applyDistillRule, arePromptTransportProtocolRangesCompatible, atomicReplaceConfigFile, backfillMemoryDedupKeys, backfillNarrativeFromBody, backfillTagsText, backfillVectors, buildBaseUrl, buildDirectPeerAuthHeaders, buildDistillReport, buildPiFlushSignalFromEvent, buildRawEventEnvelopeFromHook, buildRawEventEnvelopeFromPiEvent, buildTieredObserverSelection, buildViewerIdentityTarget, classifyPromptTransportFailure, clonePromptPackAttempt, collectOperationalStatus, compareMemoryRoleReports, connect, connectReadOnly, coordinatorAuthControllerReviewAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnrollDeviceAction, coordinatorGrantIdentityGroupAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorQualifyOwnerAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorReviewInviteAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeDeviceAction, coordinatorRevokeScopeMembershipAction, coordinatorUpdateScopeAction, createBetterSqliteCoordinatorApp, createFileContextRunner, deactivateLowSignalMemories, deactivateLowSignalObservations, dedupNearDuplicateMemories, describePiObserverStatus, detectInstallKind, dispatchFileContextTransport, draftDistillRule, enrollOwnerProfile, ensureDeviceIdentity, ensureSchemaBootstrapped, estimateExtractionModelCost, expandUserPath, exportMemories, extractApplyPatchPaths, extractionReplayObserverIdentity, fetchAllSnapshotPages, fingerprintPublicKey, flushRawEvents, formatHostPort, getAttributionDiagnostics, getCodememConfigPath, getExtractionBenchmarkProfile, getExtractionModelPricing, getInjectionEvalScenarioPack, getInjectionEvalScenarioPrompts, getMaintenanceJob, getMemoryArtifactReport, getMemoryRoleReport, getRawEventRelinkPlan, getRawEventRelinkReport, getRawEventStatus, getSemanticIndexDiagnostics, getSessionExtractionEval, getSessionExtractionEvalScenario, getUpdateStatus, getWorkspaceCodememConfigPath, hasPendingDedupKeyBackfill, hasPendingRefBackfill, hasPendingScopeBackfill, hasPendingSessionContextBackfill, hasPendingSummaryDedupBackfill, hasUnsyncedSharedMemoryChanges, importMemories, importPiSessions, ingestRawEvents, initDatabase, isAutomaticRecallMeasurement, isCoordinatorControllerAttestationReference, isEmbeddingDisabled, isExplicitUpdateInstallEligible, isReleaseVersionForChannel, judgeDistillReport, linkCoordinatorAccount, listMaintenanceJobs, listPerPeerScopeSyncState, listRetentionScopeIds, loadObserverConfig, loadPublicKey, loadRuntimeSigningPublicKey, loadSqliteVec, mdnsEnabled, mutateCodememConfigFile, normalizeCoordinatorAccountLinkOrigin, normalizePromptTransportProtocolRange, observerBaseUrlForProviderOverride, parseControllerHistoryEvidence, parseFileContextTransportRequest, planReplicationOpsAgePrune, probeCodememViewerLiveness, probeCodememViewerLiveness as probeCodememViewerLiveness$1, probeRequiredNativeRuntime, projectMatchesFilter, promptPackArtifactFingerprint, pruneReplicationOpsUntilCaughtUp, rawEventsGate, readAccountLinkDevice, readCodememConfigFile, readCodememConfigFileAtPath, readCoordinatorAccountLinkStatus, readCoordinatorOwnerReviewLocalEvidence, readCoordinatorSyncConfig, readImportPayload, recordAutomaticRecall, recordPromptPackArtifacts, recordPromptPackTerminal, renderUnifiedDiff, replayBatchExtraction, replayBatchExtractionWithTierRouting, requestJson, resolveCodememConfigPath, resolveComparablePath, resolveDbPath, resolveHookProject, resolveKeyPaths, resolvePiAgentDir, resolvePiObserverConfig, resolveProject, resolveProjectRoot, retryRawEventFailures, runSyncDaemon, runSyncPass, scanSecretsRetroactive, schema, scoreExtractionBenchmarkOutput, searchPiSessions, setPeerProjectFilter, syncPassPreflight, tryUpdateRetrievalDelivery, updatePeerAddresses, upgradeCoordinatorAccountSchemaFile, upgradeCoordinatorAuthSchemaFile, upgradeCoordinatorOwnerResolutionSchemaFile, vacuumDatabase, viewerUrl, writeCodememConfigFile } from "@codemem/core";
|
|
3
3
|
import { Command, Option } from "commander";
|
|
4
4
|
import omelette from "omelette";
|
|
5
5
|
import { appendFileSync, copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
@@ -1596,7 +1596,7 @@ async function buildLocalPack$2(context, project, dbPath, workingSetPaths = [])
|
|
|
1596
1596
|
packTokens: Number.isFinite(Number(pack.metrics?.pack_tokens)) ? Number(pack.metrics.pack_tokens) : 0
|
|
1597
1597
|
};
|
|
1598
1598
|
} finally {
|
|
1599
|
-
store.
|
|
1599
|
+
await store.closeAsync();
|
|
1600
1600
|
}
|
|
1601
1601
|
}
|
|
1602
1602
|
async function buildClaudeHookInjection(payload, opts, deps = {}) {
|
|
@@ -2237,7 +2237,7 @@ async function buildLocalPack$1(context, project, dbPath) {
|
|
|
2237
2237
|
packTokens: Number.isFinite(Number(pack.metrics?.pack_tokens)) ? Number(pack.metrics?.pack_tokens) : 0
|
|
2238
2238
|
};
|
|
2239
2239
|
} finally {
|
|
2240
|
-
store.
|
|
2240
|
+
await store.closeAsync();
|
|
2241
2241
|
}
|
|
2242
2242
|
}
|
|
2243
2243
|
async function prepareCodexHookInjection(payload, opts, deps = {}) {
|
|
@@ -2499,21 +2499,34 @@ function hasDatabaseSelection(options) {
|
|
|
2499
2499
|
return options.dbPath !== void 0 || options.db !== void 0;
|
|
2500
2500
|
}
|
|
2501
2501
|
function exportAccountSchema(options) {
|
|
2502
|
-
|
|
2503
|
-
|
|
2502
|
+
let statements = options.deviceUnlink ? ACCOUNT_UNLINK_MIGRATION_STATEMENTS : ACCOUNT_CONNECTION_MIGRATION_STATEMENTS;
|
|
2503
|
+
let prerequisites = accountPrerequisites;
|
|
2504
|
+
if (options.ownerResolution) {
|
|
2505
|
+
statements = LEGACY_OWNER_RESOLUTION_SCHEMA_STATEMENTS;
|
|
2506
|
+
prerequisites += " Requires the current account-management schema first; installs inert receipt storage only, not ownership or access.";
|
|
2507
|
+
}
|
|
2508
|
+
const text = `-- ${prerequisites}\n-- Execute in ONE atomic D1 batch; do not split into autocommitted requests. Nothing has been applied.\n${statements.map((s) => `${s.trim().replace(/;$/u, "")};`).join("\n")}\n`;
|
|
2504
2509
|
process.stdout.write(options.json ? `${JSON.stringify({
|
|
2505
2510
|
statements,
|
|
2506
2511
|
applied: false
|
|
2507
2512
|
})}\n` : text);
|
|
2508
2513
|
}
|
|
2514
|
+
function invalidAccountSelection(options, path) {
|
|
2515
|
+
if (options.exportSql) return hasDatabaseSelection(options);
|
|
2516
|
+
if (!path) return true;
|
|
2517
|
+
return Boolean(options.ownerResolution && !hasDatabaseSelection(options));
|
|
2518
|
+
}
|
|
2509
2519
|
async function upgradeAccount(options) {
|
|
2520
|
+
if (options.ownerResolution && options.deviceUnlink) {
|
|
2521
|
+
failure$4(options, "Use --owner-resolution without --device-unlink.", 2, "account_schema_upgrade_failed");
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2510
2524
|
if (options.deviceUnlink && !options.exportSql) {
|
|
2511
2525
|
failure$4(options, "Use --device-unlink only with --export-sql; local upgrades detect the installed schema.", 2, "account_schema_upgrade_failed");
|
|
2512
2526
|
return;
|
|
2513
2527
|
}
|
|
2514
2528
|
const path = resolveDbOpt(options);
|
|
2515
|
-
|
|
2516
|
-
if (options.exportSql && selected || !options.exportSql && !path) {
|
|
2529
|
+
if (invalidAccountSelection(options, path)) {
|
|
2517
2530
|
failure$4(options, options.exportSql ? "Use --export-sql without --db-path; export opens no database." : `Select an existing coordinator database with --db-path. ${accountPrerequisites}`, 2, "account_schema_upgrade_failed");
|
|
2518
2531
|
return;
|
|
2519
2532
|
}
|
|
@@ -2524,14 +2537,23 @@ async function upgradeAccount(options) {
|
|
|
2524
2537
|
}
|
|
2525
2538
|
if (!path) return;
|
|
2526
2539
|
if (!options.json) process.stderr.write(`${accountPrerequisites}\n`);
|
|
2527
|
-
await
|
|
2528
|
-
|
|
2540
|
+
if (options.ownerResolution) await upgradeCoordinatorOwnerResolutionSchemaFile(path);
|
|
2541
|
+
else await upgradeCoordinatorAccountSchemaFile(path);
|
|
2542
|
+
process.stdout.write(options.json ? `${JSON.stringify({ upgraded: true })}\n` : accountUpgradeSuccess(options));
|
|
2529
2543
|
} catch (error) {
|
|
2530
|
-
failure$4(options, `Account schema upgrade stopped. Inspect the selected database before retrying. ${
|
|
2544
|
+
failure$4(options, `Account schema upgrade stopped. Inspect the selected database before retrying. ${accountUpgradeFailure(options, error)}`, 1, "account_schema_upgrade_failed");
|
|
2531
2545
|
}
|
|
2532
2546
|
}
|
|
2547
|
+
function accountUpgradeSuccess(options) {
|
|
2548
|
+
if (options.ownerResolution) return "Coordinator owner-resolution receipt storage ready; ownership and access unchanged.\n";
|
|
2549
|
+
return "Coordinator account schema upgraded.\n";
|
|
2550
|
+
}
|
|
2551
|
+
function accountUpgradeFailure(options, error) {
|
|
2552
|
+
if (options.ownerResolution) return "Owner-resolution storage requires the current account schema: first use upgrade-account-schema without --owner-resolution. Partial, unsupported or colliding receipt schemas cannot be repaired by this command. Check file access and writer locks.";
|
|
2553
|
+
return error instanceof Error ? error.message : "Coordinator account schema upgrade failed.";
|
|
2554
|
+
}
|
|
2533
2555
|
function buildCoordinatorAccountSchemaCommand() {
|
|
2534
|
-
const command = new Command("upgrade-account-schema").configureHelp(helpStyle).description("Explicitly install optional coordinator account management on an existing V2 database").option("--device-unlink", "export only the delta for the prior account-management schema (requires --export-sql)").option("-e, --export-sql", "export statement boundaries for one atomic D1 batch without opening a database").addHelpText("after", `\n${accountPrerequisites}\nD1 export does not apply SQL or verify remote execution limits.\n`);
|
|
2556
|
+
const command = new Command("upgrade-account-schema").configureHelp(helpStyle).description("Explicitly install optional coordinator account management on an existing V2 database").option("-o, --owner-resolution", "install inert owner-resolution receipt storage (requires current account schema)").option("--device-unlink", "export only the delta for the prior account-management schema (requires --export-sql)").option("-e, --export-sql", "export statement boundaries for one atomic D1 batch without opening a database").addHelpText("after", `\n${accountPrerequisites}\n--owner-resolution installs storage only; it does not resolve an owner or change access. It requires the current account schema and an explicit --db-path, or --export-sql without a database. It cannot be combined with --device-unlink.\nD1 export does not apply SQL or verify remote execution limits.\n`);
|
|
2535
2557
|
addDbOption(command);
|
|
2536
2558
|
addJsonOption(command);
|
|
2537
2559
|
command.action(upgradeAccount);
|
|
@@ -3064,7 +3086,8 @@ var REVIEW_REASONS = [
|
|
|
3064
3086
|
"key_mismatch",
|
|
3065
3087
|
"enrollment_identity_mismatch",
|
|
3066
3088
|
"invite_identity_mismatch",
|
|
3067
|
-
"invite_evidence_invalid"
|
|
3089
|
+
"invite_evidence_invalid",
|
|
3090
|
+
"legacy_history_invalid"
|
|
3068
3091
|
];
|
|
3069
3092
|
function validId$1(value) {
|
|
3070
3093
|
return typeof value === "string" && value.length > 0 && value.length <= 256 && value.trim() === value && !/[\p{Cc}\p{Cf}\p{Cs}]/u.test(value);
|
|
@@ -3073,6 +3096,7 @@ function isHash(value) {
|
|
|
3073
3096
|
return typeof value === "string" && value.length === 64 && /^[a-f0-9]{64}$/.test(value);
|
|
3074
3097
|
}
|
|
3075
3098
|
function reviewErrorMessage(error) {
|
|
3099
|
+
if (error instanceof CoordinatorHistoryUnavailableError) return "Update the coordinator before reviewing this device owner: retained history evidence was missing or invalid in its response. Nothing changed.";
|
|
3076
3100
|
if (error instanceof RemoteCoordinatorRequestError) {
|
|
3077
3101
|
if (error.code === "review_stale") return "Evidence changed since the preview. Nothing changed; run the review again.";
|
|
3078
3102
|
if (error.code === "already_reviewed_or_needs_review") return "This device already has a reviewed owner, or needs a separate review. Nothing changed.";
|
|
@@ -3140,9 +3164,27 @@ function validInviteCount(reply, invites) {
|
|
|
3140
3164
|
function validInviteRef(invite) {
|
|
3141
3165
|
return !!invite && validId$1(invite.invite_id) && (invite.kind === "team_member" || invite.kind === "add_device");
|
|
3142
3166
|
}
|
|
3167
|
+
function previewEnrollment(enrollment) {
|
|
3168
|
+
if (!enrollment) return null;
|
|
3169
|
+
return {
|
|
3170
|
+
device_id: enrollment.device_id,
|
|
3171
|
+
fingerprint: enrollment.fingerprint,
|
|
3172
|
+
display_name: null,
|
|
3173
|
+
identity_label: enrollment.identity_label
|
|
3174
|
+
};
|
|
3175
|
+
}
|
|
3176
|
+
var CoordinatorHistoryUnavailableError = class extends Error {};
|
|
3177
|
+
function parsePreviewHistory(value, coordinatorId) {
|
|
3178
|
+
try {
|
|
3179
|
+
return parseControllerHistoryEvidence(value, coordinatorId);
|
|
3180
|
+
} catch {
|
|
3181
|
+
throw new CoordinatorHistoryUnavailableError();
|
|
3182
|
+
}
|
|
3183
|
+
}
|
|
3143
3184
|
function parsePreview(payload, request) {
|
|
3144
3185
|
if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("invalid_preview");
|
|
3145
3186
|
const reply = payload;
|
|
3187
|
+
if (!Array.isArray(reply.legacy_history)) throw new CoordinatorHistoryUnavailableError();
|
|
3146
3188
|
const enrollment = reply.enrollment;
|
|
3147
3189
|
const invites = reply.reviewed_invites;
|
|
3148
3190
|
if (reply.state !== "ready" && reply.state !== "needs_review" || !validId$1(reply.coordinator_id) || !Array.isArray(reply.reasons) || !reply.reasons.every((reason) => typeof reason === "string" && REVIEW_REASONS.includes(reason)) || !Array.isArray(invites) || !validInviteCount(reply, invites) || !invites.every(validInviteRef)) throw new Error("invalid_preview");
|
|
@@ -3151,14 +3193,10 @@ function parsePreview(payload, request) {
|
|
|
3151
3193
|
return {
|
|
3152
3194
|
state: reply.state,
|
|
3153
3195
|
coordinator_id: reply.coordinator_id,
|
|
3196
|
+
legacy_history: parsePreviewHistory(reply.legacy_history, reply.coordinator_id),
|
|
3154
3197
|
reasons: reply.reasons,
|
|
3155
3198
|
evidence_digest: reply.state === "ready" ? reply.evidence_digest : void 0,
|
|
3156
|
-
enrollment: enrollment
|
|
3157
|
-
device_id: enrollment.device_id,
|
|
3158
|
-
fingerprint: enrollment.fingerprint,
|
|
3159
|
-
display_name: null,
|
|
3160
|
-
identity_label: enrollment.identity_label
|
|
3161
|
-
} : null,
|
|
3199
|
+
enrollment: previewEnrollment(enrollment),
|
|
3162
3200
|
reviewed_invites: invites.map((invite) => ({
|
|
3163
3201
|
invite_id: invite.invite_id,
|
|
3164
3202
|
kind: invite.kind
|
|
@@ -3233,6 +3271,86 @@ function emitReviewPreview(evidence, preview) {
|
|
|
3233
3271
|
...stopped
|
|
3234
3272
|
})}\n`);
|
|
3235
3273
|
}
|
|
3274
|
+
function terminalIdentifier(value) {
|
|
3275
|
+
return JSON.stringify(value).replace(/[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}]/gu, (character) => `\\u{${character.codePointAt(0)?.toString(16)}}`);
|
|
3276
|
+
}
|
|
3277
|
+
function historyAssociation(deviceId, fingerprint, subject) {
|
|
3278
|
+
if (deviceId === subject.deviceId) {
|
|
3279
|
+
if (fingerprint === subject.fingerprint) return "Current device; current fingerprint";
|
|
3280
|
+
return "Current device; different recorded key";
|
|
3281
|
+
}
|
|
3282
|
+
if (fingerprint === subject.fingerprint) return "Other device; current fingerprint";
|
|
3283
|
+
return "Other recorded device/key; canonical-key equivalence is not identified in this summary";
|
|
3284
|
+
}
|
|
3285
|
+
function* controllerHistoryRows(preview, subject) {
|
|
3286
|
+
for (const record of preview.legacy_history) {
|
|
3287
|
+
yield {
|
|
3288
|
+
text: `Historical group ${terminalIdentifier(record.group_id)}; Team ${terminalIdentifier(record.manifest.team_id)}; completed ${terminalIdentifier(record.completed_at)}\nCandidate: ${terminalIdentifier(record.candidate_ref)}; manifest version: ${record.manifest_version}; recorded: ${terminalIdentifier(record.created_at)}\nOriginal retained manifest JSON: ${terminalIdentifier(record.manifest_json)}`,
|
|
3289
|
+
records: 1,
|
|
3290
|
+
decisions: 0
|
|
3291
|
+
};
|
|
3292
|
+
for (const decision of record.manifest.device_decisions) {
|
|
3293
|
+
const identity = decision.identity_id === null ? "none" : terminalIdentifier(decision.identity_id);
|
|
3294
|
+
yield {
|
|
3295
|
+
text: ` ${decision.decision}; Identity: ${identity}; enabled: ${decision.enabled ? "yes" : "no"}\n Device: ${terminalIdentifier(decision.device_id)}; fingerprint: ${terminalIdentifier(decision.key_fingerprint)}\n ${historyAssociation(decision.device_id, decision.key_fingerprint, subject)}`,
|
|
3296
|
+
records: 0,
|
|
3297
|
+
decisions: 1
|
|
3298
|
+
};
|
|
3299
|
+
}
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
/** A truncated summary must never authorize the terminal confirmation. */
|
|
3303
|
+
function formatControllerReviewHistory(preview, subject) {
|
|
3304
|
+
const lines = [
|
|
3305
|
+
`Proposed current owner Identity: ${terminalIdentifier(subject.identityId)}`,
|
|
3306
|
+
"Retained legacy sharing assignments (not ownership changes):",
|
|
3307
|
+
"Records may contain other devices. Different fingerprints may represent known key aliases; no row below is assumed to belong to this device unless labeled as an exact device/fingerprint match."
|
|
3308
|
+
];
|
|
3309
|
+
let records = 0;
|
|
3310
|
+
let decisions = 0;
|
|
3311
|
+
let characters = lines.join("\n").length;
|
|
3312
|
+
let complete = true;
|
|
3313
|
+
for (const row of controllerHistoryRows(preview, subject)) {
|
|
3314
|
+
if (records + row.records > 20 || decisions + row.decisions > 40 || characters + row.text.length + 1 > 24e3) {
|
|
3315
|
+
complete = false;
|
|
3316
|
+
break;
|
|
3317
|
+
}
|
|
3318
|
+
lines.push(row.text);
|
|
3319
|
+
records += row.records;
|
|
3320
|
+
decisions += row.decisions;
|
|
3321
|
+
characters += row.text.length + 1;
|
|
3322
|
+
}
|
|
3323
|
+
if (preview.legacy_history.length === 0) lines.push("No relevant retained legacy assignments.");
|
|
3324
|
+
if (!complete) {
|
|
3325
|
+
const totalDecisions = preview.legacy_history.reduce((total, record) => total + record.manifest.device_decisions.length, 0);
|
|
3326
|
+
lines.push(`TRUNCATED: showing ${records} of ${preview.legacy_history.length} records and ${decisions} of ${totalDecisions} device decisions. Terminal confirmation is disabled.`);
|
|
3327
|
+
}
|
|
3328
|
+
lines.push("Previous assignments are retained. This review records current ownership; it does not migrate sharing, move memories, merge Identities, or grant Team/Project access.", "Approval grants the existing controller authority, including account management when the coordinator permits it. Google account linking remains a separate step.");
|
|
3329
|
+
return {
|
|
3330
|
+
text: lines.join("\n"),
|
|
3331
|
+
complete
|
|
3332
|
+
};
|
|
3333
|
+
}
|
|
3334
|
+
function showCoordinatorReview(preview, request, options) {
|
|
3335
|
+
p.log.info(`Coordinator: ${preview.coordinator_id}; reviewed invitations: ${preview.reviewed_invite_count}`, { output: process.stderr });
|
|
3336
|
+
const history = formatControllerReviewHistory(preview, request);
|
|
3337
|
+
p.log.message(history.text, { output: process.stderr });
|
|
3338
|
+
if (history.complete) return true;
|
|
3339
|
+
failure$1(options, "Nothing changed. Run the same command with --json to inspect all retained evidence. Ask the coordinator operator to use the administrator review API for evidence exceeding this terminal's display limit.", 2);
|
|
3340
|
+
return false;
|
|
3341
|
+
}
|
|
3342
|
+
function reportReviewFailure(options, error) {
|
|
3343
|
+
if (options.json && error instanceof CoordinatorHistoryUnavailableError) {
|
|
3344
|
+
process.stdout.write(`${JSON.stringify({
|
|
3345
|
+
error: "owner_review_unavailable",
|
|
3346
|
+
message: reviewErrorMessage(error),
|
|
3347
|
+
unchanged: true
|
|
3348
|
+
})}\n`);
|
|
3349
|
+
process.exitCode = 1;
|
|
3350
|
+
return;
|
|
3351
|
+
}
|
|
3352
|
+
failure$1(options, reviewErrorMessage(error));
|
|
3353
|
+
}
|
|
3236
3354
|
async function reviewDeviceOwner(group, options) {
|
|
3237
3355
|
try {
|
|
3238
3356
|
if (!validId$1(group)) return failure$1(options, "Use a valid coordinator group ID.", 2);
|
|
@@ -3261,10 +3379,10 @@ async function reviewDeviceOwner(group, options) {
|
|
|
3261
3379
|
return;
|
|
3262
3380
|
}
|
|
3263
3381
|
if (preview.state !== "ready") return failure$1(options, `Coordinator review stopped: ${preview.reasons.join(", ")}. Nothing changed.`);
|
|
3264
|
-
|
|
3382
|
+
if (!showCoordinatorReview(preview, request, options)) return;
|
|
3265
3383
|
await confirmReview(group, options, evidence, request, preview);
|
|
3266
3384
|
} catch (error) {
|
|
3267
|
-
|
|
3385
|
+
reportReviewFailure(options, error);
|
|
3268
3386
|
}
|
|
3269
3387
|
}
|
|
3270
3388
|
function buildCoordinatorReviewDeviceOwnerCommand() {
|
|
@@ -5909,7 +6027,7 @@ function createInjectMemoryCommand() {
|
|
|
5909
6027
|
const pack = await store.buildMemoryPackAsync(context, limit, budget, filters);
|
|
5910
6028
|
console.log(pack.pack_text ?? "");
|
|
5911
6029
|
} finally {
|
|
5912
|
-
store.
|
|
6030
|
+
await store.closeAsync();
|
|
5913
6031
|
}
|
|
5914
6032
|
});
|
|
5915
6033
|
return cmd;
|
|
@@ -7076,7 +7194,7 @@ async function withStore(opts, errorCode, run) {
|
|
|
7076
7194
|
p.log.error(message);
|
|
7077
7195
|
process.exitCode = usageError ? 2 : 1;
|
|
7078
7196
|
} finally {
|
|
7079
|
-
store?.
|
|
7197
|
+
await store?.closeAsync();
|
|
7080
7198
|
}
|
|
7081
7199
|
}
|
|
7082
7200
|
function ledgerAutomaticContext(payload) {
|
|
@@ -7769,7 +7887,7 @@ async function buildLocalPack(context, project, dbPath) {
|
|
|
7769
7887
|
packTokens: Number.isFinite(Number(pack.metrics?.pack_tokens)) ? Number(pack.metrics?.pack_tokens) : 0
|
|
7770
7888
|
};
|
|
7771
7889
|
} finally {
|
|
7772
|
-
store.
|
|
7890
|
+
await store.closeAsync();
|
|
7773
7891
|
}
|
|
7774
7892
|
}
|
|
7775
7893
|
function isRecord$1(value) {
|
|
@@ -8658,6 +8776,12 @@ function readViewerPidRecord(dbPath) {
|
|
|
8658
8776
|
};
|
|
8659
8777
|
return null;
|
|
8660
8778
|
}
|
|
8779
|
+
function removeOwnedViewerPidFile(dbPath) {
|
|
8780
|
+
try {
|
|
8781
|
+
if (readViewerPidRecord(dbPath)?.pid !== process.pid) return;
|
|
8782
|
+
rmSync(pidFilePath(dbPath));
|
|
8783
|
+
} catch {}
|
|
8784
|
+
}
|
|
8661
8785
|
function normalizeViewerHost(host) {
|
|
8662
8786
|
const normalized = host.trim().toLowerCase();
|
|
8663
8787
|
if (normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]") return "loopback";
|
|
@@ -9076,6 +9200,10 @@ function createRawEventTargetState(store) {
|
|
|
9076
9200
|
stop
|
|
9077
9201
|
};
|
|
9078
9202
|
}
|
|
9203
|
+
function createOptionalOwnerRecovery(serverModule, options) {
|
|
9204
|
+
if (!("createAccountOwnerRecoveryService" in serverModule) || typeof serverModule.createAccountOwnerRecoveryService !== "function") return void 0;
|
|
9205
|
+
return serverModule.createAccountOwnerRecoveryService(options);
|
|
9206
|
+
}
|
|
9079
9207
|
function createViewerAccountLink(store, invocation, serverModule) {
|
|
9080
9208
|
const configPath = resolveCodememConfigPath(invocation.configPath ?? void 0).resolved.path;
|
|
9081
9209
|
const keysDir = dirname(resolveKeyPaths(process.env.CODEMEM_KEYS_DIR?.trim() || void 0)[0]);
|
|
@@ -9091,15 +9219,31 @@ function createViewerAccountLink(store, invocation, serverModule) {
|
|
|
9091
9219
|
keysDir
|
|
9092
9220
|
});
|
|
9093
9221
|
const registry = serverModule.createAccountLinkRegistry({ readContext });
|
|
9222
|
+
const ownerRecovery = createOptionalOwnerRecovery(serverModule, {
|
|
9223
|
+
readContext,
|
|
9224
|
+
registry,
|
|
9225
|
+
readAdminSecret: () => {
|
|
9226
|
+
const envSecret = process.env.CODEMEM_SYNC_COORDINATOR_ADMIN_SECRET?.trim();
|
|
9227
|
+
if (envSecret) return envSecret;
|
|
9228
|
+
const secret = readCodememConfigFileAtPath(configPath).sync_coordinator_admin_secret;
|
|
9229
|
+
return typeof secret === "string" ? secret.trim() : null;
|
|
9230
|
+
}
|
|
9231
|
+
});
|
|
9094
9232
|
let actualBoundPort = null;
|
|
9095
9233
|
return {
|
|
9096
9234
|
registry,
|
|
9235
|
+
ownerRecovery,
|
|
9236
|
+
close: async () => {
|
|
9237
|
+
if (ownerRecovery) await ownerRecovery.close();
|
|
9238
|
+
await registry.close();
|
|
9239
|
+
},
|
|
9097
9240
|
setBoundPort: (port) => {
|
|
9098
9241
|
actualBoundPort = port;
|
|
9099
9242
|
},
|
|
9100
9243
|
options: {
|
|
9101
9244
|
readContext,
|
|
9102
9245
|
registry,
|
|
9246
|
+
ownerRecovery,
|
|
9103
9247
|
getListenerAddress: () => actualBoundPort === null ? null : {
|
|
9104
9248
|
host: invocation.host,
|
|
9105
9249
|
port: actualBoundPort
|
|
@@ -9121,7 +9265,7 @@ function announceViewerStartup(options) {
|
|
|
9121
9265
|
}
|
|
9122
9266
|
async function startForegroundViewer(invocation) {
|
|
9123
9267
|
const serverModule = await import("@codemem/server");
|
|
9124
|
-
const { createApp, createSyncApp,
|
|
9268
|
+
const { createApp, createSyncApp, closeStoreAsync, createViewerRawEventInbox, getStore } = serverModule;
|
|
9125
9269
|
const createRawEventInbox = createViewerRawEventInbox;
|
|
9126
9270
|
const coordinatorMaintenanceDependencies = createServeCoordinatorMaintenanceDependencies(serverModule);
|
|
9127
9271
|
const { serve } = await import("@hono/node-server");
|
|
@@ -9143,7 +9287,7 @@ async function startForegroundViewer(invocation) {
|
|
|
9143
9287
|
p.log.warn("sqlite-vec failed to load; retrying viewer startup with embeddings disabled");
|
|
9144
9288
|
for (const line of sqliteVecFailureDiagnostics(err, resolveDbPath(invocation.dbPath ?? void 0))) p.log.warn(`sqlite-vec diagnostic: ${line}`);
|
|
9145
9289
|
process.env.CODEMEM_EMBEDDING_DISABLED = "1";
|
|
9146
|
-
|
|
9290
|
+
await closeStoreAsync();
|
|
9147
9291
|
store = getStore();
|
|
9148
9292
|
p.log.warn("Embeddings disabled for this viewer process; raw-event ingestion remains active.");
|
|
9149
9293
|
}
|
|
@@ -9237,51 +9381,72 @@ async function startForegroundViewer(invocation) {
|
|
|
9237
9381
|
server.on("error", (err) => {
|
|
9238
9382
|
if (err.code === "EADDRINUSE") p.log.warn(`Viewer already running at http://${invocation.host}:${invocation.port}`);
|
|
9239
9383
|
else p.log.error(err.message);
|
|
9240
|
-
|
|
9384
|
+
requestShutdown(1);
|
|
9241
9385
|
});
|
|
9242
|
-
|
|
9386
|
+
let shutdownPromise;
|
|
9387
|
+
let shutdownExitCode = 0;
|
|
9388
|
+
const shutdown = (exitCode = 0) => {
|
|
9389
|
+
shutdownExitCode = Math.max(shutdownExitCode, exitCode);
|
|
9390
|
+
shutdownPromise ??= finishShutdown();
|
|
9391
|
+
return shutdownPromise;
|
|
9392
|
+
};
|
|
9393
|
+
const finishShutdown = async () => {
|
|
9243
9394
|
p.outro("shutting down");
|
|
9244
9395
|
syncAbort.abort();
|
|
9245
|
-
await accountLink.registry.close();
|
|
9246
|
-
await stopMaintenanceWorkerProcess(maintenanceWorker, dbPath);
|
|
9247
|
-
await new Promise((resolve) => {
|
|
9248
|
-
let remaining = syncServer ? 2 : 1;
|
|
9249
|
-
const done = () => {
|
|
9250
|
-
if (--remaining === 0) resolve();
|
|
9251
|
-
};
|
|
9252
|
-
syncServer?.close(done);
|
|
9253
|
-
server.close(done);
|
|
9254
|
-
}).catch(() => {});
|
|
9255
|
-
await rawEventProcessing.stop();
|
|
9256
9396
|
try {
|
|
9257
|
-
rmSync(pidPath);
|
|
9258
|
-
} catch {}
|
|
9259
|
-
closeStore();
|
|
9260
|
-
process.exit(0);
|
|
9261
|
-
};
|
|
9262
|
-
const forceShutdown = () => {
|
|
9263
|
-
setTimeout(() => {
|
|
9264
|
-
if (maintenanceWorker?.pid && isProcessRunning(maintenanceWorker.pid)) try {
|
|
9265
|
-
process.kill(maintenanceWorker.pid, "SIGKILL");
|
|
9266
|
-
} catch {}
|
|
9267
9397
|
try {
|
|
9268
|
-
|
|
9269
|
-
|
|
9398
|
+
await accountLink.close();
|
|
9399
|
+
await stopMaintenanceWorkerProcess(maintenanceWorker, dbPath);
|
|
9400
|
+
} finally {
|
|
9401
|
+
await closeViewerListeners(syncServer ? [syncServer, server] : [server]);
|
|
9402
|
+
await rawEventProcessing.stop();
|
|
9403
|
+
}
|
|
9404
|
+
} catch (error) {
|
|
9405
|
+
shutdownExitCode = 1;
|
|
9406
|
+
logViewerShutdownError(error);
|
|
9407
|
+
} finally {
|
|
9408
|
+
removeOwnedViewerPidFile(dbPath);
|
|
9270
9409
|
try {
|
|
9271
|
-
|
|
9272
|
-
} catch {
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9410
|
+
await closeStoreAsync();
|
|
9411
|
+
} catch (error) {
|
|
9412
|
+
shutdownExitCode = 1;
|
|
9413
|
+
logViewerShutdownError(error);
|
|
9414
|
+
}
|
|
9415
|
+
}
|
|
9416
|
+
process.exit(shutdownExitCode);
|
|
9276
9417
|
};
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
shutdown()
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9418
|
+
const requestShutdown = (exitCode = 0) => {
|
|
9419
|
+
if (!shutdownPromise) forceViewerShutdown(maintenanceWorker, dbPath);
|
|
9420
|
+
shutdown(exitCode).catch((error) => {
|
|
9421
|
+
logViewerShutdownError(error);
|
|
9422
|
+
});
|
|
9423
|
+
};
|
|
9424
|
+
process.on("SIGINT", () => requestShutdown());
|
|
9425
|
+
process.on("SIGTERM", () => requestShutdown());
|
|
9426
|
+
}
|
|
9427
|
+
function logViewerShutdownError(error) {
|
|
9428
|
+
p.log.error(error instanceof Error ? error.message : String(error));
|
|
9429
|
+
}
|
|
9430
|
+
function closeViewerListeners(listeners) {
|
|
9431
|
+
return new Promise((resolve) => {
|
|
9432
|
+
let remaining = listeners.length;
|
|
9433
|
+
const done = () => {
|
|
9434
|
+
if (--remaining === 0) resolve();
|
|
9435
|
+
};
|
|
9436
|
+
for (const listener of listeners) listener.close(done);
|
|
9437
|
+
}).catch(() => {});
|
|
9438
|
+
}
|
|
9439
|
+
function forceViewerShutdown(maintenanceWorker, dbPath) {
|
|
9440
|
+
setTimeout(() => {
|
|
9441
|
+
if (maintenanceWorker?.pid && isProcessRunning(maintenanceWorker.pid)) try {
|
|
9442
|
+
process.kill(maintenanceWorker.pid, "SIGKILL");
|
|
9443
|
+
} catch {}
|
|
9444
|
+
removeOwnedViewerPidFile(dbPath);
|
|
9445
|
+
try {
|
|
9446
|
+
rmSync(maintenanceWorkerPidFilePath(dbPath));
|
|
9447
|
+
} catch {}
|
|
9448
|
+
process.exit(1);
|
|
9449
|
+
}, 3e4).unref();
|
|
9285
9450
|
}
|
|
9286
9451
|
async function runServeInvocation(invocation, dependencies = {
|
|
9287
9452
|
nativeRuntimeAllowsServeStart,
|