codemem 0.47.0-beta.3 → 0.47.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.js +248 -64
- 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,87 @@ 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
|
+
let identity = decision.identity_id === null ? "none" : terminalIdentifier(decision.identity_id);
|
|
3294
|
+
if (decision.identity_evidence === "label") identity += " (guessed by another device)";
|
|
3295
|
+
yield {
|
|
3296
|
+
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)}`,
|
|
3297
|
+
records: 0,
|
|
3298
|
+
decisions: 1
|
|
3299
|
+
};
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
}
|
|
3303
|
+
/** A truncated summary must never authorize the terminal confirmation. */
|
|
3304
|
+
function formatControllerReviewHistory(preview, subject) {
|
|
3305
|
+
const lines = [
|
|
3306
|
+
`Proposed current owner Identity: ${terminalIdentifier(subject.identityId)}`,
|
|
3307
|
+
"Retained legacy sharing assignments (not ownership changes):",
|
|
3308
|
+
"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."
|
|
3309
|
+
];
|
|
3310
|
+
let records = 0;
|
|
3311
|
+
let decisions = 0;
|
|
3312
|
+
let characters = lines.join("\n").length;
|
|
3313
|
+
let complete = true;
|
|
3314
|
+
for (const row of controllerHistoryRows(preview, subject)) {
|
|
3315
|
+
if (records + row.records > 20 || decisions + row.decisions > 40 || characters + row.text.length + 1 > 24e3) {
|
|
3316
|
+
complete = false;
|
|
3317
|
+
break;
|
|
3318
|
+
}
|
|
3319
|
+
lines.push(row.text);
|
|
3320
|
+
records += row.records;
|
|
3321
|
+
decisions += row.decisions;
|
|
3322
|
+
characters += row.text.length + 1;
|
|
3323
|
+
}
|
|
3324
|
+
if (preview.legacy_history.length === 0) lines.push("No relevant retained legacy assignments.");
|
|
3325
|
+
if (!complete) {
|
|
3326
|
+
const totalDecisions = preview.legacy_history.reduce((total, record) => total + record.manifest.device_decisions.length, 0);
|
|
3327
|
+
lines.push(`TRUNCATED: showing ${records} of ${preview.legacy_history.length} records and ${decisions} of ${totalDecisions} device decisions. Terminal confirmation is disabled.`);
|
|
3328
|
+
}
|
|
3329
|
+
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.");
|
|
3330
|
+
return {
|
|
3331
|
+
text: lines.join("\n"),
|
|
3332
|
+
complete
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
function showCoordinatorReview(preview, request, options) {
|
|
3336
|
+
p.log.info(`Coordinator: ${preview.coordinator_id}; reviewed invitations: ${preview.reviewed_invite_count}`, { output: process.stderr });
|
|
3337
|
+
const history = formatControllerReviewHistory(preview, request);
|
|
3338
|
+
p.log.message(history.text, { output: process.stderr });
|
|
3339
|
+
if (history.complete) return true;
|
|
3340
|
+
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);
|
|
3341
|
+
return false;
|
|
3342
|
+
}
|
|
3343
|
+
function reportReviewFailure(options, error) {
|
|
3344
|
+
if (options.json && error instanceof CoordinatorHistoryUnavailableError) {
|
|
3345
|
+
process.stdout.write(`${JSON.stringify({
|
|
3346
|
+
error: "owner_review_unavailable",
|
|
3347
|
+
message: reviewErrorMessage(error),
|
|
3348
|
+
unchanged: true
|
|
3349
|
+
})}\n`);
|
|
3350
|
+
process.exitCode = 1;
|
|
3351
|
+
return;
|
|
3352
|
+
}
|
|
3353
|
+
failure$1(options, reviewErrorMessage(error));
|
|
3354
|
+
}
|
|
3236
3355
|
async function reviewDeviceOwner(group, options) {
|
|
3237
3356
|
try {
|
|
3238
3357
|
if (!validId$1(group)) return failure$1(options, "Use a valid coordinator group ID.", 2);
|
|
@@ -3261,10 +3380,10 @@ async function reviewDeviceOwner(group, options) {
|
|
|
3261
3380
|
return;
|
|
3262
3381
|
}
|
|
3263
3382
|
if (preview.state !== "ready") return failure$1(options, `Coordinator review stopped: ${preview.reasons.join(", ")}. Nothing changed.`);
|
|
3264
|
-
|
|
3383
|
+
if (!showCoordinatorReview(preview, request, options)) return;
|
|
3265
3384
|
await confirmReview(group, options, evidence, request, preview);
|
|
3266
3385
|
} catch (error) {
|
|
3267
|
-
|
|
3386
|
+
reportReviewFailure(options, error);
|
|
3268
3387
|
}
|
|
3269
3388
|
}
|
|
3270
3389
|
function buildCoordinatorReviewDeviceOwnerCommand() {
|
|
@@ -5909,7 +6028,7 @@ function createInjectMemoryCommand() {
|
|
|
5909
6028
|
const pack = await store.buildMemoryPackAsync(context, limit, budget, filters);
|
|
5910
6029
|
console.log(pack.pack_text ?? "");
|
|
5911
6030
|
} finally {
|
|
5912
|
-
store.
|
|
6031
|
+
await store.closeAsync();
|
|
5913
6032
|
}
|
|
5914
6033
|
});
|
|
5915
6034
|
return cmd;
|
|
@@ -7076,7 +7195,7 @@ async function withStore(opts, errorCode, run) {
|
|
|
7076
7195
|
p.log.error(message);
|
|
7077
7196
|
process.exitCode = usageError ? 2 : 1;
|
|
7078
7197
|
} finally {
|
|
7079
|
-
store?.
|
|
7198
|
+
await store?.closeAsync();
|
|
7080
7199
|
}
|
|
7081
7200
|
}
|
|
7082
7201
|
function ledgerAutomaticContext(payload) {
|
|
@@ -7769,7 +7888,7 @@ async function buildLocalPack(context, project, dbPath) {
|
|
|
7769
7888
|
packTokens: Number.isFinite(Number(pack.metrics?.pack_tokens)) ? Number(pack.metrics?.pack_tokens) : 0
|
|
7770
7889
|
};
|
|
7771
7890
|
} finally {
|
|
7772
|
-
store.
|
|
7891
|
+
await store.closeAsync();
|
|
7773
7892
|
}
|
|
7774
7893
|
}
|
|
7775
7894
|
function isRecord$1(value) {
|
|
@@ -8658,6 +8777,12 @@ function readViewerPidRecord(dbPath) {
|
|
|
8658
8777
|
};
|
|
8659
8778
|
return null;
|
|
8660
8779
|
}
|
|
8780
|
+
function removeOwnedViewerPidFile(dbPath) {
|
|
8781
|
+
try {
|
|
8782
|
+
if (readViewerPidRecord(dbPath)?.pid !== process.pid) return;
|
|
8783
|
+
rmSync(pidFilePath(dbPath));
|
|
8784
|
+
} catch {}
|
|
8785
|
+
}
|
|
8661
8786
|
function normalizeViewerHost(host) {
|
|
8662
8787
|
const normalized = host.trim().toLowerCase();
|
|
8663
8788
|
if (normalized === "localhost" || normalized === "127.0.0.1" || normalized === "::1" || normalized === "[::1]") return "loopback";
|
|
@@ -9076,9 +9201,23 @@ function createRawEventTargetState(store) {
|
|
|
9076
9201
|
stop
|
|
9077
9202
|
};
|
|
9078
9203
|
}
|
|
9204
|
+
function createOptionalOwnerRecovery(serverModule, options) {
|
|
9205
|
+
if (!("createAccountOwnerRecoveryService" in serverModule) || typeof serverModule.createAccountOwnerRecoveryService !== "function") return void 0;
|
|
9206
|
+
return serverModule.createAccountOwnerRecoveryService(options);
|
|
9207
|
+
}
|
|
9208
|
+
/** The People name Sharing shows for an Identity; absent when the local store has none. */
|
|
9209
|
+
function localIdentityName(store, identityId) {
|
|
9210
|
+
try {
|
|
9211
|
+
const row = store.db.prepare("SELECT display_name FROM actors WHERE actor_id = ? AND status <> 'deactivated'").get(identityId);
|
|
9212
|
+
return (typeof row?.display_name === "string" ? row.display_name.trim() : "") || void 0;
|
|
9213
|
+
} catch {
|
|
9214
|
+
return;
|
|
9215
|
+
}
|
|
9216
|
+
}
|
|
9079
9217
|
function createViewerAccountLink(store, invocation, serverModule) {
|
|
9080
9218
|
const configPath = resolveCodememConfigPath(invocation.configPath ?? void 0).resolved.path;
|
|
9081
9219
|
const keysDir = dirname(resolveKeyPaths(process.env.CODEMEM_KEYS_DIR?.trim() || void 0)[0]);
|
|
9220
|
+
const identityName = (identityId) => localIdentityName(store, identityId);
|
|
9082
9221
|
const readContext = serverModule.createAccountContextReader({
|
|
9083
9222
|
getProfile: () => ({
|
|
9084
9223
|
dbPath: store.dbPath,
|
|
@@ -9090,16 +9229,40 @@ function createViewerAccountLink(store, invocation, serverModule) {
|
|
|
9090
9229
|
configPath,
|
|
9091
9230
|
keysDir
|
|
9092
9231
|
});
|
|
9093
|
-
const registry = serverModule.createAccountLinkRegistry({
|
|
9232
|
+
const registry = serverModule.createAccountLinkRegistry({
|
|
9233
|
+
readContext,
|
|
9234
|
+
rebind: serverModule.createAccountRebindRunner(store)
|
|
9235
|
+
});
|
|
9236
|
+
serverModule.resumeAccountRebind(store, {
|
|
9237
|
+
keysDir,
|
|
9238
|
+
configPath
|
|
9239
|
+
}).catch(() => {});
|
|
9240
|
+
const ownerRecovery = createOptionalOwnerRecovery(serverModule, {
|
|
9241
|
+
readContext,
|
|
9242
|
+
registry,
|
|
9243
|
+
readAdminSecret: () => {
|
|
9244
|
+
const envSecret = process.env.CODEMEM_SYNC_COORDINATOR_ADMIN_SECRET?.trim();
|
|
9245
|
+
if (envSecret) return envSecret;
|
|
9246
|
+
const secret = readCodememConfigFileAtPath(configPath).sync_coordinator_admin_secret;
|
|
9247
|
+
return typeof secret === "string" ? secret.trim() : null;
|
|
9248
|
+
}
|
|
9249
|
+
});
|
|
9094
9250
|
let actualBoundPort = null;
|
|
9095
9251
|
return {
|
|
9096
9252
|
registry,
|
|
9253
|
+
ownerRecovery,
|
|
9254
|
+
close: async () => {
|
|
9255
|
+
if (ownerRecovery) await ownerRecovery.close();
|
|
9256
|
+
await registry.close();
|
|
9257
|
+
},
|
|
9097
9258
|
setBoundPort: (port) => {
|
|
9098
9259
|
actualBoundPort = port;
|
|
9099
9260
|
},
|
|
9100
9261
|
options: {
|
|
9101
9262
|
readContext,
|
|
9102
9263
|
registry,
|
|
9264
|
+
ownerRecovery,
|
|
9265
|
+
identityName,
|
|
9103
9266
|
getListenerAddress: () => actualBoundPort === null ? null : {
|
|
9104
9267
|
host: invocation.host,
|
|
9105
9268
|
port: actualBoundPort
|
|
@@ -9121,7 +9284,7 @@ function announceViewerStartup(options) {
|
|
|
9121
9284
|
}
|
|
9122
9285
|
async function startForegroundViewer(invocation) {
|
|
9123
9286
|
const serverModule = await import("@codemem/server");
|
|
9124
|
-
const { createApp, createSyncApp,
|
|
9287
|
+
const { createApp, createSyncApp, closeStoreAsync, createViewerRawEventInbox, getStore } = serverModule;
|
|
9125
9288
|
const createRawEventInbox = createViewerRawEventInbox;
|
|
9126
9289
|
const coordinatorMaintenanceDependencies = createServeCoordinatorMaintenanceDependencies(serverModule);
|
|
9127
9290
|
const { serve } = await import("@hono/node-server");
|
|
@@ -9143,7 +9306,7 @@ async function startForegroundViewer(invocation) {
|
|
|
9143
9306
|
p.log.warn("sqlite-vec failed to load; retrying viewer startup with embeddings disabled");
|
|
9144
9307
|
for (const line of sqliteVecFailureDiagnostics(err, resolveDbPath(invocation.dbPath ?? void 0))) p.log.warn(`sqlite-vec diagnostic: ${line}`);
|
|
9145
9308
|
process.env.CODEMEM_EMBEDDING_DISABLED = "1";
|
|
9146
|
-
|
|
9309
|
+
await closeStoreAsync();
|
|
9147
9310
|
store = getStore();
|
|
9148
9311
|
p.log.warn("Embeddings disabled for this viewer process; raw-event ingestion remains active.");
|
|
9149
9312
|
}
|
|
@@ -9237,51 +9400,72 @@ async function startForegroundViewer(invocation) {
|
|
|
9237
9400
|
server.on("error", (err) => {
|
|
9238
9401
|
if (err.code === "EADDRINUSE") p.log.warn(`Viewer already running at http://${invocation.host}:${invocation.port}`);
|
|
9239
9402
|
else p.log.error(err.message);
|
|
9240
|
-
|
|
9403
|
+
requestShutdown(1);
|
|
9241
9404
|
});
|
|
9242
|
-
|
|
9405
|
+
let shutdownPromise;
|
|
9406
|
+
let shutdownExitCode = 0;
|
|
9407
|
+
const shutdown = (exitCode = 0) => {
|
|
9408
|
+
shutdownExitCode = Math.max(shutdownExitCode, exitCode);
|
|
9409
|
+
shutdownPromise ??= finishShutdown();
|
|
9410
|
+
return shutdownPromise;
|
|
9411
|
+
};
|
|
9412
|
+
const finishShutdown = async () => {
|
|
9243
9413
|
p.outro("shutting down");
|
|
9244
9414
|
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
9415
|
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
9416
|
try {
|
|
9268
|
-
|
|
9269
|
-
|
|
9417
|
+
await accountLink.close();
|
|
9418
|
+
await stopMaintenanceWorkerProcess(maintenanceWorker, dbPath);
|
|
9419
|
+
} finally {
|
|
9420
|
+
await closeViewerListeners(syncServer ? [syncServer, server] : [server]);
|
|
9421
|
+
await rawEventProcessing.stop();
|
|
9422
|
+
}
|
|
9423
|
+
} catch (error) {
|
|
9424
|
+
shutdownExitCode = 1;
|
|
9425
|
+
logViewerShutdownError(error);
|
|
9426
|
+
} finally {
|
|
9427
|
+
removeOwnedViewerPidFile(dbPath);
|
|
9270
9428
|
try {
|
|
9271
|
-
|
|
9272
|
-
} catch {
|
|
9273
|
-
|
|
9274
|
-
|
|
9275
|
-
|
|
9429
|
+
await closeStoreAsync();
|
|
9430
|
+
} catch (error) {
|
|
9431
|
+
shutdownExitCode = 1;
|
|
9432
|
+
logViewerShutdownError(error);
|
|
9433
|
+
}
|
|
9434
|
+
}
|
|
9435
|
+
process.exit(shutdownExitCode);
|
|
9276
9436
|
};
|
|
9277
|
-
|
|
9278
|
-
|
|
9279
|
-
shutdown()
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9437
|
+
const requestShutdown = (exitCode = 0) => {
|
|
9438
|
+
if (!shutdownPromise) forceViewerShutdown(maintenanceWorker, dbPath);
|
|
9439
|
+
shutdown(exitCode).catch((error) => {
|
|
9440
|
+
logViewerShutdownError(error);
|
|
9441
|
+
});
|
|
9442
|
+
};
|
|
9443
|
+
process.on("SIGINT", () => requestShutdown());
|
|
9444
|
+
process.on("SIGTERM", () => requestShutdown());
|
|
9445
|
+
}
|
|
9446
|
+
function logViewerShutdownError(error) {
|
|
9447
|
+
p.log.error(error instanceof Error ? error.message : String(error));
|
|
9448
|
+
}
|
|
9449
|
+
function closeViewerListeners(listeners) {
|
|
9450
|
+
return new Promise((resolve) => {
|
|
9451
|
+
let remaining = listeners.length;
|
|
9452
|
+
const done = () => {
|
|
9453
|
+
if (--remaining === 0) resolve();
|
|
9454
|
+
};
|
|
9455
|
+
for (const listener of listeners) listener.close(done);
|
|
9456
|
+
}).catch(() => {});
|
|
9457
|
+
}
|
|
9458
|
+
function forceViewerShutdown(maintenanceWorker, dbPath) {
|
|
9459
|
+
setTimeout(() => {
|
|
9460
|
+
if (maintenanceWorker?.pid && isProcessRunning(maintenanceWorker.pid)) try {
|
|
9461
|
+
process.kill(maintenanceWorker.pid, "SIGKILL");
|
|
9462
|
+
} catch {}
|
|
9463
|
+
removeOwnedViewerPidFile(dbPath);
|
|
9464
|
+
try {
|
|
9465
|
+
rmSync(maintenanceWorkerPidFilePath(dbPath));
|
|
9466
|
+
} catch {}
|
|
9467
|
+
process.exit(1);
|
|
9468
|
+
}, 3e4).unref();
|
|
9285
9469
|
}
|
|
9286
9470
|
async function runServeInvocation(invocation, dependencies = {
|
|
9287
9471
|
nativeRuntimeAllowsServeStart,
|