@sema-agent/core 5.39.0 → 5.41.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +150 -11
  2. package/dist/core/auto-mode-prompt.js +1 -1
  3. package/dist/core/checkpoint-store.d.ts +12 -0
  4. package/dist/core/governance-codes.js +2 -0
  5. package/dist/core/hooks.js +9 -1
  6. package/dist/core/memory-engine/engine.d.ts +27 -0
  7. package/dist/core/memory-engine/engine.js +103 -1
  8. package/dist/core/memory-engine/export-bundle.d.ts +192 -0
  9. package/dist/core/memory-engine/export-bundle.js +306 -0
  10. package/dist/core/memory-engine/file-backend.d.ts +178 -1
  11. package/dist/core/memory-engine/file-backend.js +648 -6
  12. package/dist/core/memory-engine/index.d.ts +2 -1
  13. package/dist/core/memory-engine/index.js +1 -0
  14. package/dist/core/memory-engine/layout.d.ts +99 -1
  15. package/dist/core/memory-engine/layout.js +143 -7
  16. package/dist/core/memory-engine/memory-backend-contract.d.ts +1 -1
  17. package/dist/core/memory-engine/memory-backend-contract.js +52 -0
  18. package/dist/core/memory-engine/tools.js +8 -1
  19. package/dist/core/permission-rule-consent.js +14 -2
  20. package/dist/core/runner/prepare-config-doors.js +20 -0
  21. package/dist/core/runner/prepare-task.js +13 -0
  22. package/dist/core/runner/runtask.js +14 -1
  23. package/dist/core/runner/synthetic-tools.js +3 -1
  24. package/dist/core/runner/tool-disclosure.js +2 -1
  25. package/dist/core/types.d.ts +7 -0
  26. package/dist/core/write-protect.d.ts +0 -20
  27. package/dist/core/write-protect.js +4 -3
  28. package/dist/engine/harness/agent-harness.d.ts +17 -0
  29. package/dist/engine/harness/agent-harness.js +19 -1
  30. package/dist/engine/harness/types.d.ts +5 -0
  31. package/dist/index.d.ts +1 -1
  32. package/dist/index.js +1 -1
  33. package/dist/tools/fs/safety.d.ts +1 -1
  34. package/dist/tools/fs/safety.js +1 -1
  35. package/dist/tools/fs/search.d.ts +33 -0
  36. package/dist/tools/fs/search.js +72 -0
  37. package/package.json +1 -1
  38. package/test/export-surface.snapshot.json +9 -1
@@ -4669,6 +4669,13 @@ export interface EngineNotice {
4669
4669
  * value IS in force the prepare refuses with the same code as `TaskResult.errorCode` (one
4670
4670
  * fact, one code, two loudness dialects); `detail: { raw }`.
4671
4671
  *
4672
+ * - `"task.user_steer_undrained"` (#259) — user steers/follow-ups whose receipts said "queued"
4673
+ * were still in the queues at agent_end: the run ended before any turn could drain them. They
4674
+ * are NOT redelivered (a steer aimed at a finished run must not fire at the next one — unlike
4675
+ * ENGINE notes, which pend per session); the notice is the loud half of the #257 contract's
4676
+ * "accepted = enqueued, not consumed" sentence; `detail: { steer, followUp, taskId? }`.
4677
+ * Per-run, at most once (the terminal sweep is a single site).
4678
+ *
4672
4679
  * Deliberately NOT a notice family: brain retry/reconnect liveness (a rate limit, a 5xx, a
4673
4680
  * transient network failure being retried). Those are per-attempt liveness frames with their own
4674
4681
  * frequency semantics and ride the wire `status` channel ({@link BrainStatus}), whose sink the
@@ -25,26 +25,6 @@ export interface WriteProtectedHit {
25
25
  readonly name: string;
26
26
  readonly kind: WriteProtectedKind;
27
27
  }
28
- /**
29
- * WRITE_PROTECTED_DEFAULT_TABLE — the default-active table (the material basis of the deployment
30
- * admin face: what an unconfigured deployment demotes to `ask`; visible here, deletable by
31
- * replacing the seat with a filtered copy). CC 2.1.233 triple VERBATIM first, then the sema rows,
32
- * each with its argument.
33
- *
34
- * NOT listed, deliberately (each a ruled-out candidate, recorded so the next reader does not
35
- * re-litigate silently):
36
- * · `.env` / `.env.*` — CC's own table excludes them too (only `.envrc`, the direnv AUTO-EXECUTION
37
- * vector, is in): a plain `.env` is application config and routine workspace material for the
38
- * tasks this engine runs (the read deny set rules it out on the same grounds); the opt-in write
39
- * DENY (`RECOMMENDED_SENSITIVE_PATTERNS`) covers deployments that want it guarded.
40
- * · key-material FILE patterns (`id_rsa*`, `*.pem`, …) — they are glob-shaped, and this table
41
- * speaks literals; the opt-in deny policy owns that vocabulary.
42
- * · cloud credential dirs (`.aws`, `.kube`, `.azure`, `.config/gcloud`) — writing cloud config IS
43
- * the routine "configure this environment" action tasks are asked to perform, so a default ask
44
- * on every such write is recurring friction without CC precedent; the opt-in deny policy covers
45
- * them, and the READ side already default-refuses them (reading credentials exfiltrates; writing
46
- * a fresh config file does not).
47
- */
48
28
  export declare const WRITE_PROTECTED_DEFAULT_TABLE: readonly WriteProtectedRow[];
49
29
  /**
50
30
  * The ONE case fold of this module, applied to BOTH sides of every comparison (table names at
@@ -1,6 +1,7 @@
1
1
  import { writeTargetPath } from "../tools/fs/safety.js";
2
2
  import { PATH_CONFINABLE_WRITE_TOOLS } from "./runner/session-rule-policy.js";
3
- export const WRITE_PROTECTED_DEFAULT_TABLE = [
3
+ const freezeTable = (rows) => Object.freeze(rows.map((r) => Object.freeze(r)));
4
+ export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
4
5
  { name: ".gitconfig", kind: "basename" },
5
6
  { name: ".gitmodules", kind: "basename" },
6
7
  { name: ".bashrc", kind: "basename" },
@@ -50,7 +51,7 @@ export const WRITE_PROTECTED_DEFAULT_TABLE = [
50
51
  { name: ".config/git", kind: "segment-run" },
51
52
  { name: ".ssh", kind: "segment" },
52
53
  { name: ".gnupg", kind: "segment" },
53
- ];
54
+ ]);
54
55
  export function foldWriteProtectCase(s) {
55
56
  return s.toLowerCase().replace(/ı/g, "i").replace(/ſ/g, "s");
56
57
  }
@@ -107,7 +108,7 @@ export function resolveWriteProtectedTable(entries) {
107
108
  seen.add(key);
108
109
  out.push(row);
109
110
  }
110
- return out;
111
+ return freezeTable(out);
111
112
  }
112
113
  export function compileWriteProtection(entries) {
113
114
  const rows = resolveWriteProtectedTable(entries);
@@ -80,6 +80,16 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
80
80
  private thinkingLevel;
81
81
  /** RB-30 terminal fix — runner-set sink for engine-note payloads left undrained at agent_end. */
82
82
  onUndrainedEngineNotes?: (payloads: unknown[]) => void;
83
+ /** backlog #259 — USER-authored queue remnants at agent_end: steers/follow-ups whose receipts said
84
+ * "queued" but that no turn will ever drain (the run ended first). The engine-note sweep above
85
+ * hands ENGINE payloads back for redelivery; user inputs have no redelivery semantics (a steer
86
+ * aimed at a finished run must not silently fire at the next one), so their loss is ANNOUNCED
87
+ * instead — the runner surfaces it as an operator notice, closing the "accepted then silently
88
+ * dropped" window the #257 contract could only document. */
89
+ onUndrainedUserInputs?: (counts: {
90
+ steer: number;
91
+ followUp: number;
92
+ }) => void;
83
93
  /** design/176 — runner-set sink fired at the CONSUMPTION boundary, once per engine-note payload,
84
94
  * in consumption order (steer/followUp drain and the turn-open nextTurn splice — the two points
85
95
  * where a queued frame actually enters the model's input). The runner uses it to record the
@@ -99,6 +109,13 @@ export declare class AgentHarness<TSkill extends Skill = Skill, TPromptTemplate
99
109
  * so a double call is a no-op. */
100
110
  recoverUndrainedEngineNotes(): void;
101
111
  private sweepUndrainedEngineNotes;
112
+ /** backlog #259 — what remains in the steer/follow-up queues AFTER the engine-note sweep is USER
113
+ * input that was accepted ("queued") and will never be consumed: the run reached agent_end first.
114
+ * User inputs have no redelivery semantics (unlike engine notes — a steer aimed at a finished run
115
+ * must not fire at the next one), so the loss is ANNOUNCED, never silent. The window is a narrow
116
+ * race (an injection landing after the loop's final queue check), which is exactly why it needs a
117
+ * loud terminal account rather than an e2e reproduction. Returns the counts for the settled frame. */
118
+ private announceUndrainedUserInputs;
102
119
  private systemPrompt;
103
120
  /** S4: physical system blocks (static per leg, additive — see AgentHarnessOptions.systemBlocks). */
104
121
  private systemBlocks;
@@ -158,6 +158,7 @@ export class AgentHarness {
158
158
  model;
159
159
  thinkingLevel;
160
160
  onUndrainedEngineNotes;
161
+ onUndrainedUserInputs;
161
162
  onEngineNoteConsumed;
162
163
  recoverUndrainedEngineNotes() {
163
164
  this.sweepUndrainedEngineNotes([this.nextTurnQueue, this.steerQueue, this.followUpQueue]);
@@ -182,6 +183,17 @@ export class AgentHarness {
182
183
  }
183
184
  }
184
185
  }
186
+ announceUndrainedUserInputs() {
187
+ const counts = { steer: this.steerQueue.length, followUp: this.followUpQueue.length };
188
+ if ((counts.steer > 0 || counts.followUp > 0) && this.onUndrainedUserInputs) {
189
+ try {
190
+ this.onUndrainedUserInputs(counts);
191
+ }
192
+ catch {
193
+ }
194
+ }
195
+ return counts;
196
+ }
185
197
  systemPrompt;
186
198
  systemBlocks;
187
199
  streamOptions;
@@ -620,9 +632,15 @@ export class AgentHarness {
620
632
  if (event.type === "agent_end") {
621
633
  await this.flushPendingSessionWrites();
622
634
  this.sweepUndrainedEngineNotes([this.nextTurnQueue, this.steerQueue, this.followUpQueue]);
635
+ const undrainedUser = this.announceUndrainedUserInputs();
623
636
  this.phase = "idle";
624
637
  await this.emitAny(event, signal);
625
- await this.emitOwn({ type: "settled", nextTurnCount: this.nextTurnQueue.length }, signal);
638
+ await this.emitOwn({
639
+ type: "settled",
640
+ nextTurnCount: this.nextTurnQueue.length,
641
+ ...(undrainedUser.steer > 0 ? { undrainedSteerCount: undrainedUser.steer } : {}),
642
+ ...(undrainedUser.followUp > 0 ? { undrainedFollowUpCount: undrainedUser.followUp } : {}),
643
+ }, signal);
626
644
  return;
627
645
  }
628
646
  await this.emitAny(event, signal);
@@ -815,6 +815,11 @@ export interface AbortEvent {
815
815
  export interface SettledEvent {
816
816
  type: "settled";
817
817
  nextTurnCount: number;
818
+ /** backlog #259 (additive) — USER steers accepted ("queued") but never drained before agent_end.
819
+ * Present only when > 0; their loss is announced through `onUndrainedUserInputs` too. */
820
+ undrainedSteerCount?: number;
821
+ /** backlog #259 (additive) — same for the follow-up queue. */
822
+ undrainedFollowUpCount?: number;
818
823
  }
819
824
  export interface BeforeAgentStartEvent<TSkill extends Skill = Skill, TPromptTemplate extends PromptTemplate = PromptTemplate> {
820
825
  type: "before_agent_start";
package/dist/index.d.ts CHANGED
@@ -166,7 +166,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
166
166
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, type AdoptionStatus, type AdoptLocalDataRootOptions, type AdoptLocalDataRootResult, type AdoptionCarriageLeg, type AdoptionCarriageLegContext, type AdoptionConfigWitnessReceipt, } from "./stores/file/adoption/adopt.js";
167
167
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, type PersistedRuleHit, type PersistedRuleUnreadable, type PersistedRuleAnswer, normalizePersistedRuleHit, type Hooks, type HookToolContext, type HookInvocationIdentity, type UserPromptSubmitContext, type PostToolBatchContext, type HookEnvCapabilities, type HookToolOutput, type PreToolUseResult, type PostToolUseResult, type UserPromptSubmitResult, type HookToolFailure, type PostToolUseFailureResult, type PostToolBatchCall, type PostToolBatchResult, type PreCompactContext, type PreCompactResult, type PostCompactContext, type StopFailureContext, type PermissionDeniedPayload, type PermissionDeniedSource, } from "./core/hooks.js";
168
168
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, type NormalizedMemorySpec, type MemorySpecInput, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, type Embedder, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, type UtilityGate, type MemoryStore, type MemoryVectorMode, type ScoredMemory, type MemoryNoteHeader, type MemoryNoteRecord, type MemoryNoteType, type StructuredNoteInput, } from "./core/memory.js";
169
- export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
169
+ export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, type MemoryBackendContractHooks, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, type V2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, type ControlPlaneRebuildReceipt, type StrictControlPlaneLedger, type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, type MemorySearchDetails, type MemorySearchHit, type MemoryGetDetails, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, type MemoryAnnouncement, type ScanFinding, type MemoryEngineOptions, type MemoryInjection, type EntryProvenanceAccount, type TransferEvidence, type CommittedBinding, type CommittedEntrySnapshot, type CommittedScopeSnapshots, type EntryCustodyReport, erasureSelectHash, type EraseMemoryEntriesInput, type ErasureSelect, type ErasedBinding, type MemoryErasureAttestation, computeMemoryBundleHash, type MemoryExportBundle, type MemoryImportReport, type MemoryExportSnapshot, type MemoryBundleImportPlan, type BundleChallengeRow, type BundleLineageRow, type BundlePollutedSession, type MemoryBackend, type MemoryEntry, type MemoryEntryFrontmatter, type MemoryEntryHeader, type ScoredMemoryEntry, type NotePatch, type PatchReport, type MaterializedFile, type MemorySessionHandle, type HarvestReport, type HarvestRejection, type HarvestRejectionCode, type ParsedEntryFile, type ScannedEntryFile, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, type ParsedScopeKey, migrateScope, type MigrateScopeReport, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, type RemoteMemoryFile, type RemoteMemoryBaseline, type RemoteMaterialization, type RemoteHarvestResult, type InboundEntryFinding, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, type MemorySyncCursor, type MemorySyncConflict, type MemorySyncPlan, type RecallSource, syncMemoryScope, type MemorySyncTransport, type MemorySyncRequestBody, type MemorySyncResponseBody, type MemorySyncClientConflict, type SyncMemoryScopeOptions, type MemorySyncClientResult, } from "./core/memory-engine/index.js";
170
170
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, type SharedMemoryStoreProvider, type SharedMemoryStoreReader, type SharedMemoryPagedList, type SharedMemoryStoreInfo, type SharedMemoryDocumentEntry, type SharedMemorySnapshot, type SharedMemoryRequestContext, type MemoryListDetails, type MemoryReadDetails, } from "./core/shared-memory/types.js";
171
171
  export { sharedMemoryStoreContract, type SharedMemoryFixture, type SharedMemoryStoreContractHooks, } from "./core/shared-memory/contract.js";
172
172
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
package/dist/index.js CHANGED
@@ -128,7 +128,7 @@ export { AdoptionError, assertAdoptionBootGate, readRootAdoptionFile, writeRootA
128
128
  export { adoptLocalDataRoot, ackAdoptionConfig, witnessAdoptionConfig, listAdoptionQuarantine, readAdoptionStatus, } from "./stores/file/adoption/adopt.js";
129
129
  export { formatHookFeedback, runToolGate, createHookEnvCapabilities, createPreToolUseConstraintPolicy, normalizePersistedRuleHit, } from "./core/hooks.js";
130
130
  export { InMemoryMemoryStore, composeMemoryBlock, composeLayeredMemoryBlock, normalizeMemorySpec, supportsConsolidation, supportsPeriodicConsolidation, firstSentence, expandLexicalTerms, lexicalSearchMatch, classifyPromotable, enforcePromotableWriteGate, guardedMemoryStore, MemoryGateError, detectSecret, enforceSecretWriteGate, enforceStructuredNoteSecretGate, } from "./core/memory.js";
131
- export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, erasureSelectHash, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
131
+ export { MemoryEngine, FileMemoryEngineBackend, memoryBackendContract, assertMemoryBackendSearchEquivalence, buildMemoryInstruction, truncateIndex, scanEntryFiles, deriveRepoKey, deriveRepoMemoryDir, deriveControlPlaneDir, deriveRepoControlPlaneDir, resolveMemoryEngineRoot, scopeDirName, ControlPlaneCorruptError, parseEntryFile, serializeEntryFile, computeEntryRev, entryFromFile, MEMORY_INSTRUCTION_TEMPLATE, MEMORY_RECALL_DISCIPLINE, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, readV2HeaderHints, LINEAGE_FILE, CHALLENGES_FILE, CHALLENGED_HISTORY_FILE, CHALLENGE_LEDGER_MAX_EVENTS, rebuildStrictControlPlaneLedger, MEMORY_INDEX_FILENAME, MEMORY_INDEX_MAX_LINES, MEMORY_INDEX_MAX_BYTES, MEMORY_SEARCH_TOOL_NAME, MEMORY_GET_TOOL_NAME, MEMORY_ENGINE_TOOL_NAMES, STUB_ARCHIVED_LINE, DEFAULT_MAX_MEMORY_FILES, DEFAULT_MAX_ENTRY_DEPTH, DEFAULT_HARVEST_DEADLINE_MS, DEFAULT_HARVEST_FILE_BUDGET, MASS_DELETION_FUSE_RATIO, scanMemoryWrite, scanMemoryFileName, scanRemediation, MEMORY_FILENAME_SEGMENT_RE, renderAnnouncements, enqueueMemoryAnnouncement, drainMemoryAnnouncements, peekMemoryAnnouncements, bumpScanFuse, scanFuseCount, clearScanFuse, ANNOUNCEMENTS_FILE, MEMORY_ANNOUNCEMENTS_MAX, SCAN_FUSE_FILE, SCAN_FUSE_THRESHOLD, erasureSelectHash, computeMemoryBundleHash, SCOPE_SEGMENT_MAX_ENCODED, PROJECT_MARKER_PATH, encodeScopeSegment, decodeScopeSegment, parseScopeKey, formatUserScope, formatOrgScope, formatProjScope, formatUserProjScope, isPersonalScope, assertScopeContractPlacement, formatProjectMarker, parseProjectMarker, resolveProjectId, PROJECT_ID_REGEX, migrateScope, materializeEntriesToFiles, harvestFilesToPatches, projectionsToWriteBack, screenInboundEntries, REMOTE_HARVEST_PER_FILE_BYTES, REMOTE_MASS_DELETION_FUSE_RATIO, deriveProjectMemoryDir, deriveProjectControlDir, recordProjectIdHint, lookupProjectIdHint, PROJECT_ID_HINTS_FILE, reconcileMemoryEntries, nextSyncBaseline, mergeRecallHits, syncMemoryScope, } from "./core/memory-engine/index.js";
132
132
  export { SHARED_MEMORY_READ_CAP_BYTES, SHARED_MEMORY_LIST_PAGE_SIZE, SharedMemoryStoreError, } from "./core/shared-memory/types.js";
133
133
  export { sharedMemoryStoreContract, } from "./core/shared-memory/contract.js";
134
134
  export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.js";
@@ -344,7 +344,7 @@ export declare function withinAnyRoot(rootsCanonical: readonly string[], p: stri
344
344
  * just let the model discover it by trial), but it is a statement, not a recommendation, and it
345
345
  * comes last. Every shell call still passes the deployment's approval policy.
346
346
  */
347
- export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
347
+ export declare const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories \u2014 or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
348
348
  /** inv 1 (read-before-edit): a file must have been read this task before it can be edited/overwritten.
349
349
  * Message is CC 2.1.198 live-verbatim (all-tools-live-probe 2026-07-08 §2.1/§3.1/§5.1 — one message for
350
350
  * Edit/Write/NotebookEdit: "before writing to it", not the old sema "before editing").
@@ -493,7 +493,7 @@ export function violationDetails(v) {
493
493
  export function withinAnyRoot(rootsCanonical, p) {
494
494
  return rootsCanonical.some((r) => within(r, p));
495
495
  }
496
- export const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories — or additionalReadDirectories for read-only access. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
496
+ export const PATH_NOT_IN_ROOT_ESCAPE_HINT = "(This boundary applies to the structured file tools. If you genuinely need content outside the allowed root(s), ask for the directory to be added to the deployment's additionalDirectories — or additionalReadDirectories for read-only access; a deployment that wants reads open everywhere can declare readFace: \"open\" instead of listing directories. For completeness: the Bash tool is not confined by this fence, and every Bash call remains subject to the deployment's approval policy.)";
497
497
  export function requireRead(state, key) {
498
498
  const entry = state.get(key);
499
499
  if (entry === undefined || entry.isPartialView) {
@@ -208,6 +208,39 @@ export interface GrepRunResult {
208
208
  * withheld / no deny judge in play. */
209
209
  withheld?: ReadDenyWithheld;
210
210
  }
211
+ /**
212
+ * backlog #303 (rescan-hardened form) — the rg leg's deny TRIPWIRE. rg's exclusion globs are spelled
213
+ * from the pattern text and cannot express the win32 component-alias family (`.aws.` / `.aws ` —
214
+ * trailing dots/spaces the win32 namespace strips), so a directory planted under an alias spelling
215
+ * slips past the globs while the shared `matchPath` (two-view, read-deny.ts) correctly names it —
216
+ * the JS leg pruned it, the rg leg returned its contents, and the same tree answered differently
217
+ * depending on whether ripgrep was installed.
218
+ *
219
+ * The first fix FILTERED rg's text output line-by-line. The rescan killed that shape twice over:
220
+ * ripgrep prints filenames verbatim, so a path component containing a NEWLINE splits one record
221
+ * across physical lines whose fragments carry no `:line:` boundary — the filter kept them and the
222
+ * deny-listed content passed (a protection hole, not a precision residual); and pruning every line
223
+ * of a partial run fabricated a `No matches.` row inside the structured card. Text-splitting rg
224
+ * records is unsalvageable without structured output (`--json`, backlog #306), so this function no
225
+ * longer edits anything: it JUDGES. Any deny hit — or any line the format cannot account for —
226
+ * trips, and the caller abandons the rg run for the JS scanner, whose walk prunes with the
227
+ * authoritative judge and needs no path parsing at all. rg stays the fast path for the common case
228
+ * (no guarded entries in the result); the moment a guarded spelling is involved, the engine that
229
+ * cannot mis-parse it owns the answer.
230
+ *
231
+ * Trip conditions by mode — `files_with_matches`: every line IS a path, judged whole; `count`:
232
+ * `path:N` (a line without the numeric tail is a split record — ambiguous, trip); `content`:
233
+ * judged at EVERY `:digits:` boundary and every `-digits-` boundary (a candidate cut inside match
234
+ * text can over-trip — safe: the fallback re-derives the exact answer), and a non-separator line
235
+ * with NO boundary at all is a split record — trip. `--` separators and empty lines pass.
236
+ */
237
+ export declare function rgOutputDenyTripwire(stdout: string, mode: "content" | "files_with_matches" | "count", judge: Pick<ReadDenyJudge, "matchPath">): {
238
+ trip: false;
239
+ } | {
240
+ trip: true;
241
+ reason: "deny-hit" | "ambiguous-record";
242
+ pattern?: string;
243
+ };
211
244
  /** ripgrep grep: build flags from params, run, normalize to the same output as {@link jsGrep}.
212
245
  * Result-fidelity contract: output ripgrep DID produce is never silently replaced by a fallback
213
246
  * rescan — partial results ship with a caveat; only a zero-output failure degrades to
@@ -1043,6 +1043,63 @@ function formatRgStdout(stdout, p, caveat = "") {
1043
1043
  (off > 0 ? `\n[offset ${off}]` : "") +
1044
1044
  caveat);
1045
1045
  }
1046
+ export function rgOutputDenyTripwire(stdout, mode, judge) {
1047
+ if (stdout.length === 0)
1048
+ return { trip: false };
1049
+ const judgeBoundaries = (line, boundary) => {
1050
+ let sawBoundary = false;
1051
+ for (let m = boundary.exec(line); m !== null; m = boundary.exec(line)) {
1052
+ sawBoundary = true;
1053
+ const prefix = line.slice(0, m.index);
1054
+ if (prefix.length === 0)
1055
+ continue;
1056
+ const hit = judge.matchPath(prefix);
1057
+ if (hit !== null)
1058
+ return hit.pattern;
1059
+ }
1060
+ return sawBoundary ? null : "";
1061
+ };
1062
+ for (const line of stdout.split("\n")) {
1063
+ if (line.length === 0 || line === "--")
1064
+ continue;
1065
+ if (mode === "files_with_matches") {
1066
+ const h = judge.matchPath(line);
1067
+ if (h !== null)
1068
+ return { trip: true, reason: "deny-hit", pattern: h.pattern };
1069
+ }
1070
+ else if (mode === "count") {
1071
+ const m = /^(.*):\d+$/.exec(line);
1072
+ if (m === null)
1073
+ return { trip: true, reason: "ambiguous-record" };
1074
+ const h = judge.matchPath(m[1]);
1075
+ if (h !== null)
1076
+ return { trip: true, reason: "deny-hit", pattern: h.pattern };
1077
+ }
1078
+ else {
1079
+ const colon = judgeBoundaries(line, /:\d+:/g);
1080
+ if (colon !== null && colon !== "")
1081
+ return { trip: true, reason: "deny-hit", pattern: colon };
1082
+ const dash = judgeBoundaries(line, /-\d+-/g);
1083
+ if (dash !== null && dash !== "")
1084
+ return { trip: true, reason: "deny-hit", pattern: dash };
1085
+ if (colon === "" && dash === "")
1086
+ return { trip: true, reason: "ambiguous-record" };
1087
+ }
1088
+ }
1089
+ return { trip: false };
1090
+ }
1091
+ async function jsGrepDenyTripFallback(env, root, p, signal, trip, deny) {
1092
+ const denyOut = {};
1093
+ const text = await jsGrep(env, root, p, signal, undefined, deny, denyOut);
1094
+ const why = trip.reason === "deny-hit" ? "its output involved sensitive-path deny-listed entries the exclusion globs cannot express" : "its output contained a record the line format cannot account for";
1095
+ return {
1096
+ text: text.startsWith("Error (grep)")
1097
+ ? text
1098
+ : `${text}\n[note: the ripgrep pass was abandoned (${why}); results are from the fallback scanner, which prunes with the authoritative deny judge]`,
1099
+ degraded: { fallback: "js-scan", reason: `ripgrep output tripped the deny tripwire (${trip.reason})` },
1100
+ ...(denyOut.withheld !== undefined ? { withheld: denyOut.withheld } : {}),
1101
+ };
1102
+ }
1046
1103
  async function jsGrepFallback(env, root, p, signal, reason, deny) {
1047
1104
  const denyOut = {};
1048
1105
  const text = await jsGrep(env, root, p, signal, undefined, deny, denyOut);
@@ -1097,6 +1154,11 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
1097
1154
  if (err.code === "timeout") {
1098
1155
  const partial = err.partialStdout ?? "";
1099
1156
  if (partial.trim().length > 0) {
1157
+ if (deny !== undefined) {
1158
+ const trip = rgOutputDenyTripwire(partial, mode, deny);
1159
+ if (trip.trip)
1160
+ return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
1161
+ }
1100
1162
  return {
1101
1163
  text: `${delimitUntrusted("ripgrep partial output", formatRgStdout(partial, p))}\n…[ripgrep timed out after producing partial output — results may be incomplete]`,
1102
1164
  degraded: { partial: true, reason: "ripgrep timed out" },
@@ -1124,6 +1186,11 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
1124
1186
  }
1125
1187
  if (exitCode >= 2) {
1126
1188
  if (stdout.trim().length > 0) {
1189
+ if (deny !== undefined) {
1190
+ const trip = rgOutputDenyTripwire(stdout, mode, deny);
1191
+ if (trip.trip)
1192
+ return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
1193
+ }
1127
1194
  return {
1128
1195
  text: `${delimitUntrusted("ripgrep partial output", formatRgStdout(stdout, p))}\n…[ripgrep exited with an error after producing partial output — results may be incomplete]`,
1129
1196
  degraded: { partial: true, reason: `ripgrep exited with code ${exitCode}` },
@@ -1131,6 +1198,11 @@ export async function rgGrepDetailed(env, root, p, signal, deny) {
1131
1198
  }
1132
1199
  return jsGrepFallback(env, root, p, signal, `exited with code ${exitCode} and produced no output`, deny);
1133
1200
  }
1201
+ if (deny !== undefined) {
1202
+ const trip = rgOutputDenyTripwire(stdout, mode, deny);
1203
+ if (trip.trip)
1204
+ return jsGrepDenyTripFallback(env, root, p, signal, trip, deny);
1205
+ }
1134
1206
  const orderedStdout = mode === "files_with_matches" ? await sortRgFilesByMtime(env, root, stdout, signal) : stdout;
1135
1207
  const d = await denyDisclosure();
1136
1208
  return { text: formatRgStdout(orderedStdout, p) + d.note, ...(d.withheld !== undefined ? { withheld: d.withheld } : {}) };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "5.39.0",
3
+ "version": "5.41.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
3
- "count": 1597,
3
+ "count": 1605,
4
4
  "exports": {
5
5
  "A2ATaskState": "type",
6
6
  "A2ATaskStateReversal": "type",
@@ -119,6 +119,9 @@
119
119
  "BudgetAxis": "type",
120
120
  "BuildAutoModePromptOptions": "interface",
121
121
  "BuiltinWorkflowDefinition": "interface",
122
+ "BundleChallengeRow": "interface",
123
+ "BundleLineageRow": "interface",
124
+ "BundlePollutedSession": "interface",
122
125
  "CC_MODEL_TIER_ALIASES": "variable",
123
126
  "CHALLENGED_HISTORY_FILE": "variable",
124
127
  "CHALLENGES_FILE": "variable",
@@ -467,14 +470,18 @@
467
470
  "MemoryAnnouncement": "interface",
468
471
  "MemoryBackend": "interface",
469
472
  "MemoryBackendContractHooks": "interface",
473
+ "MemoryBundleImportPlan": "interface",
470
474
  "MemoryEngine": "class",
471
475
  "MemoryEngineOptions": "interface",
472
476
  "MemoryEntry": "interface",
473
477
  "MemoryEntryFrontmatter": "interface",
474
478
  "MemoryEntryHeader": "interface",
475
479
  "MemoryErasureAttestation": "interface",
480
+ "MemoryExportBundle": "interface",
481
+ "MemoryExportSnapshot": "interface",
476
482
  "MemoryGateError": "class",
477
483
  "MemoryGetDetails": "interface",
484
+ "MemoryImportReport": "interface",
478
485
  "MemoryInjection": "interface",
479
486
  "MemoryListDetails": "type",
480
487
  "MemoryNoteHeader": "interface",
@@ -1180,6 +1187,7 @@
1180
1187
  "computeCatalogDigest": "function",
1181
1188
  "computeCostMicroUsd": "function",
1182
1189
  "computeEntryRev": "function",
1190
+ "computeMemoryBundleHash": "function",
1183
1191
  "computeShapeDigest": "function",
1184
1192
  "confirmRuleApproval": "function",
1185
1193
  "consolidateScope": "function",