@remnic/core 9.3.749 → 9.3.751
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/access-boundary.d.ts +8 -8
- package/dist/access-boundary.js +15 -15
- package/dist/access-cli.js +39 -39
- package/dist/access-http.d.ts +8 -8
- package/dist/access-http.js +20 -20
- package/dist/access-mcp.d.ts +8 -8
- package/dist/access-mcp.js +19 -19
- package/dist/access-operations-batch.js +16 -16
- package/dist/access-operations.d.ts +8 -8
- package/dist/access-operations.js +18 -18
- package/dist/access-schema.d.ts +2 -2
- package/dist/access-schema.js +5 -5
- package/dist/{access-service-5-EVpt3v.d.ts → access-service-DmAEOJdJ.d.ts} +11 -3
- package/dist/access-service.d.ts +5 -5
- package/dist/access-service.js +14 -14
- package/dist/access-surface-catalog.d.ts +8 -8
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +8 -1
- package/dist/active-recall.js.map +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +7 -7
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-D_ZDxu8Y.d.ts → catalog-BpOcwHdE.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +8 -8
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-C5WN74HH.js → chunk-2HAG7JOI.js} +8 -2
- package/dist/chunk-2HAG7JOI.js.map +1 -0
- package/dist/{chunk-GKRLPHPU.js → chunk-2KK5WNQB.js} +2 -2
- package/dist/{chunk-JRDPT6DA.js → chunk-4NWIGAIC.js} +2 -2
- package/dist/{chunk-OBJUKYDO.js → chunk-4XL3CVII.js} +2 -2
- package/dist/{chunk-WWVXXSLF.js → chunk-5PZCDUJ6.js} +4 -4
- package/dist/{chunk-URQLO7BX.js → chunk-6FNZ3NMS.js} +2 -2
- package/dist/{chunk-DRUDC2UX.js → chunk-6MMONPHM.js} +2 -2
- package/dist/{chunk-RB6A7OUN.js → chunk-6SXNQFHB.js} +2 -2
- package/dist/{chunk-ZGZNLRKF.js → chunk-7TWA7DKP.js} +5 -5
- package/dist/{chunk-KMNYXLPL.js → chunk-B35L45HZ.js} +2 -2
- package/dist/{chunk-SIV3JZPP.js → chunk-CGZWGVQF.js} +5 -5
- package/dist/{chunk-CKFLLAIS.js → chunk-DANV2HTH.js} +3 -3
- package/dist/{chunk-NVYF5OB6.js → chunk-DBRVU5PR.js} +3 -3
- package/dist/{chunk-LMFS64GY.js → chunk-E4PFDD3N.js} +4 -4
- package/dist/{chunk-DIHCFJVV.js → chunk-HYI2GP3F.js} +4 -4
- package/dist/{chunk-UIKZZ242.js → chunk-IKHKHADN.js} +2 -2
- package/dist/{chunk-PUR2CKX2.js → chunk-JS2HKKOK.js} +1 -1
- package/dist/{chunk-PUR2CKX2.js.map → chunk-JS2HKKOK.js.map} +1 -1
- package/dist/{chunk-R2OAN6IP.js → chunk-K2JM4DZZ.js} +2 -2
- package/dist/{chunk-3IJGF6K2.js → chunk-K2R3DEEH.js} +2 -2
- package/dist/{chunk-HIHCB2S6.js → chunk-QRRBXD24.js} +2 -2
- package/dist/{chunk-PNUOQYRJ.js → chunk-RWZB6MAY.js} +1953 -2172
- package/dist/chunk-RWZB6MAY.js.map +1 -0
- package/dist/{chunk-ZPZ4VVLT.js → chunk-SD34EK4Z.js} +2 -2
- package/dist/{chunk-WK3NK6SP.js → chunk-SNEXVX3U.js} +6 -6
- package/dist/{chunk-FVZ2ISIX.js → chunk-TPLE6SNY.js} +7 -7
- package/dist/{chunk-24YDB5QP.js → chunk-UETBEHBA.js} +44 -24
- package/dist/chunk-UETBEHBA.js.map +1 -0
- package/dist/{chunk-5IN26V6W.js → chunk-UU32EAIY.js} +5 -5
- package/dist/{chunk-ZGAJB74V.js → chunk-W3BFT5OJ.js} +2 -2
- package/dist/{chunk-L4N7SVNN.js → chunk-WA7FECSG.js} +11 -11
- package/dist/{chunk-Q3X7GUAQ.js → chunk-WOX2UW4K.js} +2 -2
- package/dist/{chunk-XTEWVF3C.js → chunk-WWSXVOGY.js} +2 -2
- package/dist/{chunk-PWWWLD7D.js → chunk-X64YLC24.js} +153 -27
- package/dist/chunk-X64YLC24.js.map +1 -0
- package/dist/{chunk-P4FW3ZU5.js → chunk-YGGXUNS4.js} +20 -20
- package/dist/{chunk-JHQUIH3W.js → chunk-ZBVQ4ZM4.js} +19 -19
- package/dist/{cli-DuWWjaKT.d.ts → cli-lmf5RGyQ.d.ts} +3 -3
- package/dist/cli.d.ts +9 -9
- package/dist/cli.js +33 -33
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +7 -7
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +8 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +7 -7
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +1 -1
- package/dist/entity-retrieval.js +7 -7
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +7 -7
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +10 -10
- package/dist/index.js +76 -72
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/mcp-memory-inspector-app.d.ts +5 -5
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +2 -2
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/search.js +1 -1
- package/dist/namespaces/storage.d.ts +2 -2
- package/dist/namespaces/storage.js +7 -7
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync.d.ts +108 -1
- package/dist/offline-sync.js +7 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +15 -12
- package/dist/orchestration/compression-guideline-coordinator.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts +2 -2
- package/dist/orchestration/maintenance.js +9 -9
- package/dist/{orchestrator-CZI6hO_R.d.ts → orchestrator-D7bb8ZGp.d.ts} +193 -201
- package/dist/orchestrator.d.ts +5 -5
- package/dist/orchestrator.js +51 -41
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +9 -2
- package/dist/retrieval-agents.d.ts +1 -1
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/schemas.d.ts +22 -22
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-D3aSiu9t.d.ts → semantic-consolidation-CTfbVwCA.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/storage.d.ts +1 -1
- package/dist/storage.js +6 -6
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +1 -1
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/transfer/types.d.ts +12 -12
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CZm6rx9-.d.ts → types-D4NYDtXI.d.ts} +7 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.js +7 -7
- package/package.json +2 -2
- package/src/access-service.ts +22 -0
- package/src/config.ts +5 -0
- package/src/index.ts +3 -1
- package/src/offline-sync.test.ts +354 -52
- package/src/offline-sync.ts +332 -24
- package/src/orchestration/extraction-run.ts +4 -34
- package/src/orchestration/namespace-read-fanout.ts +484 -0
- package/src/orchestration/orchestrator-helpers.ts +1130 -0
- package/src/orchestration/self-deps.ts +50 -0
- package/src/orchestration/workspace-ops.ts +789 -0
- package/src/orchestrator.ts +177 -2296
- package/src/types.ts +9 -0
- package/dist/chunk-24YDB5QP.js.map +0 -1
- package/dist/chunk-C5WN74HH.js.map +0 -1
- package/dist/chunk-PNUOQYRJ.js.map +0 -1
- package/dist/chunk-PWWWLD7D.js.map +0 -1
- /package/dist/{chunk-GKRLPHPU.js.map → chunk-2KK5WNQB.js.map} +0 -0
- /package/dist/{chunk-JRDPT6DA.js.map → chunk-4NWIGAIC.js.map} +0 -0
- /package/dist/{chunk-OBJUKYDO.js.map → chunk-4XL3CVII.js.map} +0 -0
- /package/dist/{chunk-WWVXXSLF.js.map → chunk-5PZCDUJ6.js.map} +0 -0
- /package/dist/{chunk-URQLO7BX.js.map → chunk-6FNZ3NMS.js.map} +0 -0
- /package/dist/{chunk-DRUDC2UX.js.map → chunk-6MMONPHM.js.map} +0 -0
- /package/dist/{chunk-RB6A7OUN.js.map → chunk-6SXNQFHB.js.map} +0 -0
- /package/dist/{chunk-ZGZNLRKF.js.map → chunk-7TWA7DKP.js.map} +0 -0
- /package/dist/{chunk-KMNYXLPL.js.map → chunk-B35L45HZ.js.map} +0 -0
- /package/dist/{chunk-SIV3JZPP.js.map → chunk-CGZWGVQF.js.map} +0 -0
- /package/dist/{chunk-CKFLLAIS.js.map → chunk-DANV2HTH.js.map} +0 -0
- /package/dist/{chunk-NVYF5OB6.js.map → chunk-DBRVU5PR.js.map} +0 -0
- /package/dist/{chunk-LMFS64GY.js.map → chunk-E4PFDD3N.js.map} +0 -0
- /package/dist/{chunk-DIHCFJVV.js.map → chunk-HYI2GP3F.js.map} +0 -0
- /package/dist/{chunk-UIKZZ242.js.map → chunk-IKHKHADN.js.map} +0 -0
- /package/dist/{chunk-R2OAN6IP.js.map → chunk-K2JM4DZZ.js.map} +0 -0
- /package/dist/{chunk-3IJGF6K2.js.map → chunk-K2R3DEEH.js.map} +0 -0
- /package/dist/{chunk-HIHCB2S6.js.map → chunk-QRRBXD24.js.map} +0 -0
- /package/dist/{chunk-ZPZ4VVLT.js.map → chunk-SD34EK4Z.js.map} +0 -0
- /package/dist/{chunk-WK3NK6SP.js.map → chunk-SNEXVX3U.js.map} +0 -0
- /package/dist/{chunk-FVZ2ISIX.js.map → chunk-TPLE6SNY.js.map} +0 -0
- /package/dist/{chunk-5IN26V6W.js.map → chunk-UU32EAIY.js.map} +0 -0
- /package/dist/{chunk-ZGAJB74V.js.map → chunk-W3BFT5OJ.js.map} +0 -0
- /package/dist/{chunk-L4N7SVNN.js.map → chunk-WA7FECSG.js.map} +0 -0
- /package/dist/{chunk-Q3X7GUAQ.js.map → chunk-WOX2UW4K.js.map} +0 -0
- /package/dist/{chunk-XTEWVF3C.js.map → chunk-WWSXVOGY.js.map} +0 -0
- /package/dist/{chunk-P4FW3ZU5.js.map → chunk-YGGXUNS4.js.map} +0 -0
- /package/dist/{chunk-JHQUIH3W.js.map → chunk-ZBVQ4ZM4.js.map} +0 -0
package/src/offline-sync.ts
CHANGED
|
@@ -198,6 +198,73 @@ const EXCLUDED_FILE_PREFIXES = [
|
|
|
198
198
|
".remnic-sync.",
|
|
199
199
|
".remnic-sync-state.",
|
|
200
200
|
];
|
|
201
|
+
// Issue #1786: node-local runtime state that each node rebuilds from synced
|
|
202
|
+
// records. Pushing these wastes bandwidth, corrupts the remote's live state
|
|
203
|
+
// dir, and trips the large-file push retry loop on a live SQLite database.
|
|
204
|
+
const DEFAULT_OFFLINE_SYNC_EXCLUDE_GLOBS: readonly string[] = [
|
|
205
|
+
// Leading `**/` matches zero or more segments, so each pattern covers both
|
|
206
|
+
// the root `state/` dir AND per-namespace `namespaces/<ns>/state/` dirs
|
|
207
|
+
// (Cursor review on PR #1793: multi-namespace deployments previously kept
|
|
208
|
+
// pushing their namespaced live sqlite files).
|
|
209
|
+
"**/state/*.sqlite",
|
|
210
|
+
"**/state/*.sqlite-*",
|
|
211
|
+
"**/state/index_tags.json",
|
|
212
|
+
"**/state/entity-mention-index.json",
|
|
213
|
+
"**/state/memory-governance/runs/**",
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Convert a tiny subset of glob syntax (`*`, `**`, `?`, literal text) into a
|
|
218
|
+
* regular expression anchored at the start and end of the input. The matcher
|
|
219
|
+
* is intentionally narrow — it is only used for operator-supplied
|
|
220
|
+
* `offlineSyncExcludes` entries, not for full shell-style globbing.
|
|
221
|
+
* `*` and `?` match within a single path segment. `**` is cross-segment
|
|
222
|
+
* wherever it appears: a `star-star-slash` prefix (leading or embedded)
|
|
223
|
+
* matches zero or more whole segments, and a trailing `dir/star-star`
|
|
224
|
+
* matches everything under `dir/` at any depth.
|
|
225
|
+
*/
|
|
226
|
+
export function globToRegExp(glob: string): RegExp {
|
|
227
|
+
if (typeof glob !== "string" || glob.length === 0) {
|
|
228
|
+
throw new Error("offlineSyncExcludes entry must be a non-empty string");
|
|
229
|
+
}
|
|
230
|
+
if (glob.includes("\0")) {
|
|
231
|
+
throw new Error("offlineSyncExcludes entry must not contain NUL bytes");
|
|
232
|
+
}
|
|
233
|
+
let source = "";
|
|
234
|
+
for (let i = 0; i < glob.length; i += 1) {
|
|
235
|
+
const ch = glob[i];
|
|
236
|
+
if (ch === "*") {
|
|
237
|
+
if (glob[i + 1] === "*") {
|
|
238
|
+
// `**` is cross-segment wherever it appears:
|
|
239
|
+
// leading `**/` -> zero or more whole segments
|
|
240
|
+
// `/**` at end -> everything under the directory
|
|
241
|
+
// `a/**/b` -> any depth between segments
|
|
242
|
+
// (Cursor review on PR #1793: trailing `scratch/**` must match
|
|
243
|
+
// nested `scratch/a/b.md`, matching the offline-mode guide.)
|
|
244
|
+
if (glob[i + 2] === "/") {
|
|
245
|
+
source += "(?:.*/)?";
|
|
246
|
+
i += 2;
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
source += ".*";
|
|
250
|
+
i += 1;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
source += "[^/]*";
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
if (ch === "?") {
|
|
257
|
+
source += "[^/]";
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
if (ch === "/") {
|
|
261
|
+
source += "/";
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
source += ch.replace(/[\\^$.+()|{}\[\]]/g, "\\$&");
|
|
265
|
+
}
|
|
266
|
+
return new RegExp(`^${source}$`);
|
|
267
|
+
}
|
|
201
268
|
|
|
202
269
|
function hashText(value: string): string {
|
|
203
270
|
return createHash("sha256").update(value).digest("hex");
|
|
@@ -354,7 +421,9 @@ export function normalizeOfflineSyncSnapshot(
|
|
|
354
421
|
};
|
|
355
422
|
}
|
|
356
423
|
|
|
357
|
-
export function normalizeOfflineSyncChangeset(
|
|
424
|
+
export function normalizeOfflineSyncChangeset(
|
|
425
|
+
input: unknown,
|
|
426
|
+
): OfflineSyncChangeset {
|
|
358
427
|
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
359
428
|
throw new Error("offline sync changeset must be an object");
|
|
360
429
|
}
|
|
@@ -467,7 +536,95 @@ function shouldExcludeRelPath(relPosix: string, includeTranscripts: boolean): bo
|
|
|
467
536
|
const basename = parts[parts.length - 1] ?? "";
|
|
468
537
|
if (isCanonicalRuntimeStatePath(parts) && basename.includes(".tmp-")) return true;
|
|
469
538
|
if (EXCLUDED_FILE_NAMES.has(basename)) return true;
|
|
470
|
-
|
|
539
|
+
if (EXCLUDED_FILE_PREFIXES.some((prefix) => basename.startsWith(prefix))) return true;
|
|
540
|
+
return false;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Push-side variant of `shouldExcludeRelPath` for issue #1786. Combines the
|
|
545
|
+
* legacy structural exclude (private dirs, sync internal, tmp runtime state,
|
|
546
|
+
* excluded file names/prefixes) with the new push-only exclusion layers:
|
|
547
|
+
* - the `state/*.sqlite`, `state/index_tags.json`, etc. defaults that
|
|
548
|
+
* describe node-local runtime state each node rebuilds from synced
|
|
549
|
+
* records, and
|
|
550
|
+
* - operator-supplied `offlineSyncExcludes` regexps.
|
|
551
|
+
*
|
|
552
|
+
* Used ONLY by push-side enumeration/validation paths. Apply-side callers
|
|
553
|
+
* must use the legacy `shouldExcludeRelPath` so a remote snapshot containing
|
|
554
|
+
* `state/lcm.sqlite` can still be accepted on first sync -- the remote's
|
|
555
|
+
* view is authoritative for that file until the local LCM is bootstrapped.
|
|
556
|
+
*/
|
|
557
|
+
function shouldExcludePushRelPath(
|
|
558
|
+
relPosix: string,
|
|
559
|
+
includeTranscripts: boolean,
|
|
560
|
+
userExcludeRegexps?: readonly RegExp[],
|
|
561
|
+
): boolean {
|
|
562
|
+
if (shouldExcludeRelPath(relPosix, includeTranscripts)) return true;
|
|
563
|
+
// Issue #1786: node-local runtime state (live sqlite, derived indexes,
|
|
564
|
+
// per-node governance run logs) is rebuilt by each node, never synced.
|
|
565
|
+
if (matchesOfflineSyncDefaultExclude(relPosix)) return true;
|
|
566
|
+
if (userExcludeRegexps && userExcludeRegexps.some((re) => re.test(relPosix))) return true;
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Precompiled once at module load — this check sits on the hot
|
|
571
|
+
// enumeration path for every walked file (Kilo review, PR #1793).
|
|
572
|
+
const DEFAULT_OFFLINE_SYNC_EXCLUDE_REGEXPS: readonly RegExp[] =
|
|
573
|
+
DEFAULT_OFFLINE_SYNC_EXCLUDE_GLOBS.map((glob) => globToRegExp(glob));
|
|
574
|
+
|
|
575
|
+
function matchesOfflineSyncDefaultExclude(relPosix: string): boolean {
|
|
576
|
+
for (const regexp of DEFAULT_OFFLINE_SYNC_EXCLUDE_REGEXPS) {
|
|
577
|
+
if (regexp.test(relPosix)) return true;
|
|
578
|
+
}
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Compile operator-supplied `offlineSyncExcludes` glob strings into a
|
|
584
|
+
* pre-validated array of regular expressions. Empty input is allowed and
|
|
585
|
+
* returns an empty list. Throws on any invalid entry — callers should treat
|
|
586
|
+
* a thrown error as a fatal configuration mistake and refuse to start the
|
|
587
|
+
* sync run rather than silently dropping the bad entry.
|
|
588
|
+
*/
|
|
589
|
+
export function compileOfflineSyncExcludeGlobs(
|
|
590
|
+
globs: readonly unknown[],
|
|
591
|
+
): RegExp[] {
|
|
592
|
+
const out: RegExp[] = [];
|
|
593
|
+
for (const entry of globs) {
|
|
594
|
+
if (typeof entry !== "string" || entry.length === 0) {
|
|
595
|
+
throw new Error("offlineSyncExcludes must contain only non-empty strings");
|
|
596
|
+
}
|
|
597
|
+
out.push(globToRegExp(entry));
|
|
598
|
+
}
|
|
599
|
+
return out;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/**
|
|
603
|
+
* Validate the operator-supplied offline-sync exclude list (#1786).
|
|
604
|
+
* Rejects loudly instead of silently defaulting (CLAUDE.md rule 39):
|
|
605
|
+
* a misspelled key value must fail config parse, not be ignored.
|
|
606
|
+
* Lives next to the glob compiler so config.ts only carries the call.
|
|
607
|
+
*/
|
|
608
|
+
export function parseOfflineSyncExcludes(raw: unknown): string[] {
|
|
609
|
+
if (raw === undefined || raw === null) return [];
|
|
610
|
+
if (!Array.isArray(raw)) {
|
|
611
|
+
throw new Error(
|
|
612
|
+
`offlineSyncExcludes must be an array of non-empty glob strings; got ${typeof raw}`,
|
|
613
|
+
);
|
|
614
|
+
}
|
|
615
|
+
for (const entry of raw) {
|
|
616
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
617
|
+
throw new Error(
|
|
618
|
+
"offlineSyncExcludes must contain only non-empty glob strings",
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
const globs = raw.map((entry) => (entry as string).trim());
|
|
623
|
+
// Compile-check every glob now so a bad pattern fails at parse time
|
|
624
|
+
// rather than mid-sync. compileOfflineSyncExcludeGlobs throws with a
|
|
625
|
+
// per-entry message.
|
|
626
|
+
compileOfflineSyncExcludeGlobs(globs);
|
|
627
|
+
return globs;
|
|
471
628
|
}
|
|
472
629
|
|
|
473
630
|
function shouldIgnoreIncomingRuntimePath(relPosix: string): boolean {
|
|
@@ -629,6 +786,15 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
|
|
|
629
786
|
includeTranscripts?: boolean;
|
|
630
787
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
631
788
|
readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
|
|
789
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
790
|
+
/**
|
|
791
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
792
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
793
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
794
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
795
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
796
|
+
*/
|
|
797
|
+
excludeNodeLocalState?: boolean;
|
|
632
798
|
signal?: AbortSignal;
|
|
633
799
|
}): AsyncIterable<OfflineSyncFileRecord> {
|
|
634
800
|
throwIfOfflineSyncAborted(options.signal);
|
|
@@ -641,10 +807,13 @@ export async function* iterateOfflineSyncSnapshotFileRecords(options: {
|
|
|
641
807
|
let entries = await readdir(dirAbs, { withFileTypes: true });
|
|
642
808
|
entries = entries.sort((left, right) => left.name.localeCompare(right.name));
|
|
643
809
|
for (const entry of entries) {
|
|
644
|
-
throwIfOfflineSyncAborted(options.signal);
|
|
645
810
|
const abs = path.join(dirAbs, entry.name);
|
|
646
811
|
const relPosix = path.relative(root.abs, abs).split(path.sep).join("/");
|
|
647
|
-
if (
|
|
812
|
+
if (
|
|
813
|
+
options.excludeNodeLocalState === false
|
|
814
|
+
? shouldExcludeRelPath(relPosix, includeTranscripts)
|
|
815
|
+
: shouldExcludePushRelPath(relPosix, includeTranscripts, options.userExcludeRegexps)
|
|
816
|
+
) continue;
|
|
648
817
|
if (entry.isSymbolicLink()) continue;
|
|
649
818
|
if (entry.isDirectory()) {
|
|
650
819
|
yield* walk(abs);
|
|
@@ -674,6 +843,15 @@ export async function buildOfflineSyncSnapshot(options: {
|
|
|
674
843
|
now?: Date;
|
|
675
844
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
676
845
|
readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
|
|
846
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
847
|
+
/**
|
|
848
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
849
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
850
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
851
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
852
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
853
|
+
*/
|
|
854
|
+
excludeNodeLocalState?: boolean;
|
|
677
855
|
signal?: AbortSignal;
|
|
678
856
|
}): Promise<OfflineSyncSnapshot> {
|
|
679
857
|
throwIfOfflineSyncAborted(options.signal);
|
|
@@ -702,6 +880,15 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
|
|
|
702
880
|
now?: Date;
|
|
703
881
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
704
882
|
readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
|
|
883
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
884
|
+
/**
|
|
885
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
886
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
887
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
888
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
889
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
890
|
+
*/
|
|
891
|
+
excludeNodeLocalState?: boolean;
|
|
705
892
|
signal?: AbortSignal;
|
|
706
893
|
}): Promise<OfflineSyncSnapshot> {
|
|
707
894
|
throwIfOfflineSyncAborted(options.signal);
|
|
@@ -726,7 +913,11 @@ export async function buildOfflineSyncSnapshotFromBase(options: {
|
|
|
726
913
|
throwIfOfflineSyncAborted(options.signal);
|
|
727
914
|
const abs = path.join(dirAbs, entry.name);
|
|
728
915
|
const relPosix = path.relative(root.abs, abs).split(path.sep).join("/");
|
|
729
|
-
if (
|
|
916
|
+
if (
|
|
917
|
+
options.excludeNodeLocalState === false
|
|
918
|
+
? shouldExcludeRelPath(relPosix, includeTranscripts)
|
|
919
|
+
: shouldExcludePushRelPath(relPosix, includeTranscripts, options.userExcludeRegexps)
|
|
920
|
+
) continue;
|
|
730
921
|
if (entry.isSymbolicLink()) continue;
|
|
731
922
|
if (entry.isDirectory()) {
|
|
732
923
|
await walk(abs);
|
|
@@ -778,6 +969,15 @@ export async function buildOfflineSyncSnapshotForPaths(options: {
|
|
|
778
969
|
now?: Date;
|
|
779
970
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
780
971
|
readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
|
|
972
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
973
|
+
/**
|
|
974
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
975
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
976
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
977
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
978
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
979
|
+
*/
|
|
980
|
+
excludeNodeLocalState?: boolean;
|
|
781
981
|
signal?: AbortSignal;
|
|
782
982
|
}): Promise<OfflineSyncSnapshot> {
|
|
783
983
|
throwIfOfflineSyncAborted(options.signal);
|
|
@@ -792,7 +992,11 @@ export async function buildOfflineSyncSnapshotForPaths(options: {
|
|
|
792
992
|
const relPath = normalizeRelativePath(rawPath, "paths[]");
|
|
793
993
|
if (seen.has(relPath)) continue;
|
|
794
994
|
seen.add(relPath);
|
|
795
|
-
if (
|
|
995
|
+
if (
|
|
996
|
+
options.excludeNodeLocalState === false
|
|
997
|
+
? shouldExcludeRelPath(relPath, includeTranscripts)
|
|
998
|
+
: shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps)
|
|
999
|
+
) {
|
|
796
1000
|
throw new Error(`offline sync snapshot path is excluded: ${relPath}`);
|
|
797
1001
|
}
|
|
798
1002
|
const filePath = await resolveSafeArchiveTarget(root, relPath);
|
|
@@ -830,12 +1034,25 @@ export async function readOfflineSyncFileContentChunk(options: {
|
|
|
830
1034
|
length?: number;
|
|
831
1035
|
includeTranscripts?: boolean;
|
|
832
1036
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
1037
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
1038
|
+
/**
|
|
1039
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
1040
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
1041
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
1042
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
1043
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
1044
|
+
*/
|
|
1045
|
+
excludeNodeLocalState?: boolean;
|
|
833
1046
|
}): Promise<OfflineSyncFileContentChunk> {
|
|
834
1047
|
const rootAbs = path.resolve(options.root);
|
|
835
1048
|
const root = await prepareSafeArchiveRoot(rootAbs, "readOfflineSyncFileContentChunk", "root");
|
|
836
1049
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
837
1050
|
const relPath = normalizeRelativePath(options.path, "path");
|
|
838
|
-
if (
|
|
1051
|
+
if (
|
|
1052
|
+
options.excludeNodeLocalState === false
|
|
1053
|
+
? shouldExcludeRelPath(relPath, includeTranscripts)
|
|
1054
|
+
: shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps)
|
|
1055
|
+
) {
|
|
839
1056
|
throw new Error(`offline sync file content path is excluded: ${relPath}`);
|
|
840
1057
|
}
|
|
841
1058
|
const offset = options.offset === undefined
|
|
@@ -906,6 +1123,15 @@ export async function buildOfflineSyncChangeset(options: {
|
|
|
906
1123
|
excludePaths?: readonly string[];
|
|
907
1124
|
includeTranscripts?: boolean;
|
|
908
1125
|
now?: Date;
|
|
1126
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
1127
|
+
/**
|
|
1128
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
1129
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
1130
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
1131
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
1132
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
1133
|
+
*/
|
|
1134
|
+
excludeNodeLocalState?: boolean;
|
|
909
1135
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
910
1136
|
readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
|
|
911
1137
|
}): Promise<OfflineSyncChangeset> {
|
|
@@ -918,6 +1144,7 @@ export async function buildOfflineSyncChangeset(options: {
|
|
|
918
1144
|
includeContent: false,
|
|
919
1145
|
includeTranscripts,
|
|
920
1146
|
now: options.now,
|
|
1147
|
+
userExcludeRegexps: options.userExcludeRegexps,
|
|
921
1148
|
readFile: options.readFile,
|
|
922
1149
|
readFileDigest: options.readFileDigest,
|
|
923
1150
|
});
|
|
@@ -929,6 +1156,7 @@ export async function buildOfflineSyncChangeset(options: {
|
|
|
929
1156
|
excludePaths: options.excludePaths,
|
|
930
1157
|
includeTranscripts,
|
|
931
1158
|
now: options.now,
|
|
1159
|
+
userExcludeRegexps: options.userExcludeRegexps,
|
|
932
1160
|
readFile: options.readFile,
|
|
933
1161
|
});
|
|
934
1162
|
}
|
|
@@ -941,6 +1169,15 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
|
|
|
941
1169
|
excludePaths?: readonly string[];
|
|
942
1170
|
includeTranscripts?: boolean;
|
|
943
1171
|
now?: Date;
|
|
1172
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
1173
|
+
/**
|
|
1174
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
1175
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
1176
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
1177
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
1178
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
1179
|
+
*/
|
|
1180
|
+
excludeNodeLocalState?: boolean;
|
|
944
1181
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
945
1182
|
}): Promise<OfflineSyncChangeset> {
|
|
946
1183
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
@@ -959,6 +1196,9 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
|
|
|
959
1196
|
|
|
960
1197
|
for (const relPath of unionPaths(base, currentMap)) {
|
|
961
1198
|
if (excludedPaths.has(relPath)) continue;
|
|
1199
|
+
// Issue #1786: node-local runtime state must never appear in a push-side
|
|
1200
|
+
// changeset — each node rebuilds it from synced records.
|
|
1201
|
+
if (shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps)) continue;
|
|
962
1202
|
// Runtime state is remote-authoritative in offline sync: local edits and
|
|
963
1203
|
// deletes are not pushed; the pull phase restores or removes these files
|
|
964
1204
|
// from the remote snapshot.
|
|
@@ -973,6 +1213,7 @@ export async function buildOfflineSyncChangesetFromSnapshot(options: {
|
|
|
973
1213
|
includeContent: true,
|
|
974
1214
|
includeTranscripts,
|
|
975
1215
|
now: options.now,
|
|
1216
|
+
userExcludeRegexps: options.userExcludeRegexps,
|
|
976
1217
|
readFile: options.readFile,
|
|
977
1218
|
});
|
|
978
1219
|
const record = file.files[0];
|
|
@@ -1025,6 +1266,15 @@ export async function summarizeOfflineSyncPendingChanges(options: {
|
|
|
1025
1266
|
baseCapturedAt?: Date;
|
|
1026
1267
|
includeTranscripts?: boolean;
|
|
1027
1268
|
now?: Date;
|
|
1269
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
1270
|
+
/**
|
|
1271
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
1272
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
1273
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
1274
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
1275
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
1276
|
+
*/
|
|
1277
|
+
excludeNodeLocalState?: boolean;
|
|
1028
1278
|
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
1029
1279
|
readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
|
|
1030
1280
|
}): Promise<OfflineSyncChangesetSummary> {
|
|
@@ -1037,6 +1287,7 @@ export async function summarizeOfflineSyncPendingChanges(options: {
|
|
|
1037
1287
|
includeContent: false,
|
|
1038
1288
|
includeTranscripts,
|
|
1039
1289
|
now: options.now,
|
|
1290
|
+
userExcludeRegexps: options.userExcludeRegexps,
|
|
1040
1291
|
readFile: options.readFile,
|
|
1041
1292
|
readFileDigest: options.readFileDigest,
|
|
1042
1293
|
});
|
|
@@ -1044,6 +1295,7 @@ export async function summarizeOfflineSyncPendingChanges(options: {
|
|
|
1044
1295
|
baseFiles: options.baseFiles,
|
|
1045
1296
|
currentFiles: current.files,
|
|
1046
1297
|
includeTranscripts,
|
|
1298
|
+
userExcludeRegexps: options.userExcludeRegexps,
|
|
1047
1299
|
});
|
|
1048
1300
|
}
|
|
1049
1301
|
|
|
@@ -1051,6 +1303,15 @@ export function summarizeOfflineSyncPendingFiles(options: {
|
|
|
1051
1303
|
baseFiles?: readonly OfflineSyncFileState[];
|
|
1052
1304
|
currentFiles: readonly OfflineSyncFileState[];
|
|
1053
1305
|
includeTranscripts?: boolean;
|
|
1306
|
+
userExcludeRegexps?: readonly RegExp[];
|
|
1307
|
+
/**
|
|
1308
|
+
* When false, enumeration uses only the legacy structural excludes —
|
|
1309
|
+
* the apply/pull-side view of local files. Push-side callers keep the
|
|
1310
|
+
* default (true): built-in node-local state excludes + user excludes.
|
|
1311
|
+
* (Cursor review on PR #1793: apply-side local enumeration must see
|
|
1312
|
+
* files like state/lcm.sqlite or merges misclassify them.)
|
|
1313
|
+
*/
|
|
1314
|
+
excludeNodeLocalState?: boolean;
|
|
1054
1315
|
}): OfflineSyncChangesetSummary {
|
|
1055
1316
|
const includeTranscripts = options.includeTranscripts !== false;
|
|
1056
1317
|
const base = byPath(filterBaseFilesForMode(
|
|
@@ -1063,9 +1324,9 @@ export function summarizeOfflineSyncPendingFiles(options: {
|
|
|
1063
1324
|
));
|
|
1064
1325
|
let upserts = 0;
|
|
1065
1326
|
let deletes = 0;
|
|
1066
|
-
|
|
1067
1327
|
for (const relPath of unionPaths(base, currentMap)) {
|
|
1068
1328
|
if (shouldPreferIncomingOfflineRuntimeFile(relPath)) continue;
|
|
1329
|
+
if (shouldExcludePushRelPath(relPath, includeTranscripts, options.userExcludeRegexps)) continue;
|
|
1069
1330
|
const baseEntry = base.get(relPath);
|
|
1070
1331
|
const currentEntry = currentMap.get(relPath);
|
|
1071
1332
|
if (currentEntry && currentEntry.sha256 !== baseEntry?.sha256) {
|
|
@@ -1116,6 +1377,7 @@ export async function applyOfflineSyncSnapshot(options: {
|
|
|
1116
1377
|
includeTranscripts: snapshot.includeTranscripts,
|
|
1117
1378
|
readFile: options.readFile,
|
|
1118
1379
|
readFileDigest: options.readFileDigest,
|
|
1380
|
+
excludeNodeLocalState: false,
|
|
1119
1381
|
})).files;
|
|
1120
1382
|
const currentMap = byPath(currentFiles);
|
|
1121
1383
|
const deferredPaths = new Set(options.deferredPaths ?? []);
|
|
@@ -1222,6 +1484,16 @@ export async function applyOfflineSyncSnapshot(options: {
|
|
|
1222
1484
|
continue;
|
|
1223
1485
|
}
|
|
1224
1486
|
|
|
1487
|
+
// Node-local runtime state (#1786): a push-filtered remote snapshot
|
|
1488
|
+
// never carries these paths, so their absence is NOT a delete
|
|
1489
|
+
// instruction. Drop any stale base entry (pre-#1786 state files still
|
|
1490
|
+
// carry them) and leave the local file untouched — deleting a live
|
|
1491
|
+
// sqlite WAL/SHM here would corrupt the local LCM store.
|
|
1492
|
+
if (matchesOfflineSyncDefaultExclude(relPath)) {
|
|
1493
|
+
nextBase.delete(relPath);
|
|
1494
|
+
skipped += 1;
|
|
1495
|
+
continue;
|
|
1496
|
+
}
|
|
1225
1497
|
if (!currentEntry) {
|
|
1226
1498
|
nextBase.delete(relPath);
|
|
1227
1499
|
skipped += 1;
|
|
@@ -1308,6 +1580,7 @@ export async function applyOfflineSyncChangeset(options: {
|
|
|
1308
1580
|
includeTranscripts: changeset.includeTranscripts,
|
|
1309
1581
|
readFile: options.readFile,
|
|
1310
1582
|
readFileDigest: options.readFileDigest,
|
|
1583
|
+
excludeNodeLocalState: false,
|
|
1311
1584
|
})).files;
|
|
1312
1585
|
const currentMap = byPath(currentFiles);
|
|
1313
1586
|
const conflicts: OfflineSyncConflict[] = [];
|
|
@@ -1401,6 +1674,7 @@ export async function applyOfflineSyncChangeset(options: {
|
|
|
1401
1674
|
includeTranscripts: changeset.includeTranscripts,
|
|
1402
1675
|
readFile: options.readFile,
|
|
1403
1676
|
readFileDigest: options.readFileDigest,
|
|
1677
|
+
excludeNodeLocalState: false,
|
|
1404
1678
|
})).files,
|
|
1405
1679
|
...(options.returnCurrentFiles === false ? { currentFilesComplete: false } : {}),
|
|
1406
1680
|
};
|
|
@@ -1527,6 +1801,48 @@ async function setSafeFileMtime(
|
|
|
1527
1801
|
return true;
|
|
1528
1802
|
}
|
|
1529
1803
|
|
|
1804
|
+
/**
|
|
1805
|
+
* Read the local file digest for a single relative path without applying
|
|
1806
|
+
* push-side exclude rules. Used by apply-side helpers that need to check
|
|
1807
|
+
* the current local state of a path that the push-side exclude would
|
|
1808
|
+
* reject (e.g. `state/lcm.sqlite`). Returns `null` if the file does not
|
|
1809
|
+
* exist, is not a regular file, or is a symlink.
|
|
1810
|
+
*/
|
|
1811
|
+
async function readLocalFileState(options: {
|
|
1812
|
+
rootAbs: string;
|
|
1813
|
+
relPath: string;
|
|
1814
|
+
readFile?: (target: OfflineSyncFileTarget) => Promise<Buffer>;
|
|
1815
|
+
readFileDigest?: (target: OfflineSyncFileTarget) => Promise<OfflineSyncFileDigest>;
|
|
1816
|
+
signal?: AbortSignal;
|
|
1817
|
+
}): Promise<OfflineSyncFileState | null> {
|
|
1818
|
+
const filePath = await resolveSafeArchiveTarget(
|
|
1819
|
+
await prepareSafeArchiveRoot(options.rootAbs, "readLocalFileState", "rootAbs"),
|
|
1820
|
+
options.relPath,
|
|
1821
|
+
);
|
|
1822
|
+
const st = await lstat(filePath).catch((error: unknown) => {
|
|
1823
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
1824
|
+
throw error;
|
|
1825
|
+
});
|
|
1826
|
+
if (!st || st.isSymbolicLink() || !st.isFile()) return null;
|
|
1827
|
+
throwIfOfflineSyncAborted(options.signal);
|
|
1828
|
+
let digest: OfflineSyncFileDigest;
|
|
1829
|
+
if (options.readFileDigest) {
|
|
1830
|
+
digest = await options.readFileDigest({ root: options.rootAbs, path: options.relPath, filePath });
|
|
1831
|
+
} else if (options.readFile) {
|
|
1832
|
+
const content = await options.readFile({ root: options.rootAbs, path: options.relPath, filePath });
|
|
1833
|
+
digest = sha256Buffer(content);
|
|
1834
|
+
} else {
|
|
1835
|
+
digest = await sha256File(filePath, options.signal);
|
|
1836
|
+
}
|
|
1837
|
+
throwIfOfflineSyncAborted(options.signal);
|
|
1838
|
+
return {
|
|
1839
|
+
path: options.relPath,
|
|
1840
|
+
sha256: digest.sha256,
|
|
1841
|
+
bytes: digest.bytes,
|
|
1842
|
+
mtimeMs: st.mtimeMs,
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1530
1846
|
export async function applyOfflineSyncFileContentChunk(options: {
|
|
1531
1847
|
root: string;
|
|
1532
1848
|
sourceId: string;
|
|
@@ -1630,17 +1946,13 @@ export async function applyOfflineSyncFileContentChunk(options: {
|
|
|
1630
1946
|
};
|
|
1631
1947
|
if (offset === 0) {
|
|
1632
1948
|
await pruneOfflineUploadStaging(root);
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
paths: [relPath],
|
|
1637
|
-
includeContent: false,
|
|
1638
|
-
includeTranscripts,
|
|
1949
|
+
const currentFile = await readLocalFileState({
|
|
1950
|
+
rootAbs: root.abs,
|
|
1951
|
+
relPath,
|
|
1639
1952
|
readFile: options.readFile,
|
|
1640
1953
|
readFileDigest: options.readFileDigest,
|
|
1641
1954
|
});
|
|
1642
|
-
|
|
1643
|
-
if (currentFile?.sha256 === sha256) {
|
|
1955
|
+
if (currentFile && currentFile.sha256 === sha256) {
|
|
1644
1956
|
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1645
1957
|
return {
|
|
1646
1958
|
...baseResult,
|
|
@@ -1695,16 +2007,12 @@ export async function applyOfflineSyncFileContentChunk(options: {
|
|
|
1695
2007
|
throw new Error(`offline sync upload checksum mismatch for ${relPath}`);
|
|
1696
2008
|
}
|
|
1697
2009
|
|
|
1698
|
-
const
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
paths: [relPath],
|
|
1702
|
-
includeContent: false,
|
|
1703
|
-
includeTranscripts,
|
|
2010
|
+
const currentFile = await readLocalFileState({
|
|
2011
|
+
rootAbs: root.abs,
|
|
2012
|
+
relPath,
|
|
1704
2013
|
readFile: options.readFile,
|
|
1705
2014
|
readFileDigest: options.readFileDigest,
|
|
1706
2015
|
});
|
|
1707
|
-
const currentFile = currentSnapshot.files[0];
|
|
1708
2016
|
const uploadedState: OfflineSyncFileState = {
|
|
1709
2017
|
path: relPath,
|
|
1710
2018
|
sha256,
|
|
@@ -1713,7 +2021,7 @@ export async function applyOfflineSyncFileContentChunk(options: {
|
|
|
1713
2021
|
};
|
|
1714
2022
|
|
|
1715
2023
|
try {
|
|
1716
|
-
if (currentFile
|
|
2024
|
+
if (currentFile && currentFile.sha256 === sha256) {
|
|
1717
2025
|
await setSafeFileMtime(root, relPath, mtimeMs);
|
|
1718
2026
|
return {
|
|
1719
2027
|
...baseResult,
|
|
@@ -44,7 +44,6 @@ import {
|
|
|
44
44
|
type GraphConstructionCapabilitySet,
|
|
45
45
|
type MemoryLifecycleCapabilitySet,
|
|
46
46
|
} from "../capabilities.js";
|
|
47
|
-
import { throwIfAborted as sharedThrowIfAborted, abortError as sharedAbortError } from "../abort-error.js";
|
|
48
47
|
import { parseFlexibleIsoTimestamp } from "../utils/iso-timestamp.js";
|
|
49
48
|
import { log } from "../logger.js";
|
|
50
49
|
import type {
|
|
@@ -122,39 +121,10 @@ export interface ExtractionRunCoordinatorDeps {
|
|
|
122
121
|
) => Promise<void>;
|
|
123
122
|
}
|
|
124
123
|
|
|
125
|
-
// Recall/extraction
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
function throwIfRecallAborted(
|
|
130
|
-
signal?: AbortSignal,
|
|
131
|
-
message = "recall aborted",
|
|
132
|
-
): void {
|
|
133
|
-
sharedThrowIfAborted(signal, message);
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
async function raceRecallAbort<T>(
|
|
137
|
-
promise: Promise<T>,
|
|
138
|
-
signal?: AbortSignal,
|
|
139
|
-
message = "recall aborted",
|
|
140
|
-
): Promise<T> {
|
|
141
|
-
throwIfRecallAborted(signal, message);
|
|
142
|
-
if (!signal) return promise;
|
|
143
|
-
|
|
144
|
-
let onAbort: (() => void) | null = null;
|
|
145
|
-
const abortPromise = new Promise<T>((_resolve, reject) => {
|
|
146
|
-
onAbort = () => reject(abortRecallError(message));
|
|
147
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
return await Promise.race([promise, abortPromise]);
|
|
152
|
-
} finally {
|
|
153
|
-
if (onAbort) {
|
|
154
|
-
signal.removeEventListener("abort", onAbort);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
124
|
+
// Recall/extraction abort helpers live in orchestrator-helpers.ts since
|
|
125
|
+
// #1526 seam 25; this file previously carried duplicate copies
|
|
126
|
+
// (AGENTS.md rule 8 — no duplicated helpers).
|
|
127
|
+
import { raceRecallAbort, throwIfRecallAborted } from "./orchestrator-helpers.js";
|
|
158
128
|
|
|
159
129
|
export function deriveTopicsFromExtraction(result: ExtractionResult): string[] {
|
|
160
130
|
const topics = new Set<string>();
|