@sema-agent/core 5.43.0 → 5.45.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/CHANGELOG.md +108 -6
- package/dist/agents/subagent.d.ts +3 -1
- package/dist/brain/reasoning.d.ts +50 -4
- package/dist/brain/reasoning.js +28 -7
- package/dist/brain/request-params.d.ts +0 -12
- package/dist/brain/request-params.js +1 -1
- package/dist/core/governance-codes.js +3 -0
- package/dist/core/memory-engine/content-origin.d.ts +6 -3
- package/dist/core/memory-engine/delegation-provenance.d.ts +12 -7
- package/dist/core/memory-engine/engine.d.ts +94 -2
- package/dist/core/memory-engine/engine.js +46 -4
- package/dist/core/memory-engine/layout.d.ts +11 -4
- package/dist/core/memory-engine/layout.js +3 -3
- package/dist/core/memory-engine/tools.d.ts +12 -0
- package/dist/core/memory-engine/tools.js +30 -11
- package/dist/core/runner/assemble-result.d.ts +5 -0
- package/dist/core/runner/assemble-result.js +1 -1
- package/dist/core/runner/prepare-config-doors.d.ts +6 -0
- package/dist/core/runner/prepare-config-doors.js +17 -0
- package/dist/core/runner/prepare-memory.js +24 -3
- package/dist/core/runner/prepare-task.d.ts +3 -1
- package/dist/core/runner/prepare-task.js +25 -3
- package/dist/core/runner/runtask.js +20 -17
- package/dist/core/trace.d.ts +17 -2
- package/dist/core/types.d.ts +100 -1
- package/dist/tools/fs/fs-search-tools.d.ts +4 -3
- package/dist/tools/fs/search.d.ts +43 -7
- package/dist/tools/fs/search.js +75 -31
- package/package.json +3 -2
|
@@ -66,6 +66,18 @@ export interface MemoryEngineToolsOptions {
|
|
|
66
66
|
planes: ReadonlyArray<MemoryEnginePlane>;
|
|
67
67
|
/** Clock (tests); default Date.now. Drives the natural-language age rendering only. */
|
|
68
68
|
now?: () => number;
|
|
69
|
+
/**
|
|
70
|
+
* design/178 §3 (#324b) — the session's pollution READ face: the record when this session is marked
|
|
71
|
+
* polluted, `undefined` when it is clean. Read-only and side-effect-free (the mark is one-way and
|
|
72
|
+
* lives at the runner's mark seat); consulted ONLY on the empty outcomes, where a session whose
|
|
73
|
+
* writes are being withheld deserves to hear it instead of being told to search again.
|
|
74
|
+
*
|
|
75
|
+
* Absent ⇒ no pollution face is wired (engine-direct hosts, tests): the empty-outcome wording is
|
|
76
|
+
* then exactly the clean-session wording — nothing is inferred from the seat's absence.
|
|
77
|
+
*/
|
|
78
|
+
sessionPollution?: () => {
|
|
79
|
+
reason: string;
|
|
80
|
+
} | undefined;
|
|
69
81
|
}
|
|
70
82
|
export interface MemorySearchHit {
|
|
71
83
|
id: string;
|
|
@@ -91,6 +91,26 @@ export function skipBytes(text, startBytes) {
|
|
|
91
91
|
export function createMemoryEngineTools(opts) {
|
|
92
92
|
const { planes } = opts;
|
|
93
93
|
const now = opts.now ?? Date.now;
|
|
94
|
+
const pollutionSentence = () => {
|
|
95
|
+
let reason;
|
|
96
|
+
try {
|
|
97
|
+
reason = opts.sessionPollution?.()?.reason;
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return "";
|
|
101
|
+
}
|
|
102
|
+
if (reason === undefined)
|
|
103
|
+
return "";
|
|
104
|
+
return ` This session is marked polluted (${inlineUntrusted(reason, 200)}): the memory it writes is not admitted to the library at harvest, so writing this down now would not make it retrievable later either.`;
|
|
105
|
+
};
|
|
106
|
+
const noMatch = (query) => {
|
|
107
|
+
const details = { outcome: "ok", hits: [] };
|
|
108
|
+
return {
|
|
109
|
+
content: `No memory entries matched ${JSON.stringify(inlineUntrusted(query, 120))}. If the user expected you to know this, say that you checked memory and found nothing.` +
|
|
110
|
+
pollutionSentence(),
|
|
111
|
+
details,
|
|
112
|
+
};
|
|
113
|
+
};
|
|
94
114
|
const getWithinScopes = async (plane, ids) => {
|
|
95
115
|
const scopeSet = new Set(plane.scopes);
|
|
96
116
|
return (await plane.backend.getByIds(ids)).filter((e) => scopeSet.has(e.scope) && e.frontmatter.deleted !== true);
|
|
@@ -102,7 +122,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
102
122
|
defer: true,
|
|
103
123
|
offload: false,
|
|
104
124
|
contentOrigin: "local",
|
|
105
|
-
contract: { contractId: "core.memory_search@1", implementationRevision: "
|
|
125
|
+
contract: { contractId: "core.memory_search@1", implementationRevision: "2" },
|
|
106
126
|
parameters: Type.Object({
|
|
107
127
|
query: Type.String({ description: "Keywords to look for (lexical match against entry names, descriptions and bodies)." }),
|
|
108
128
|
limit: Type.Optional(Type.Number({ description: `Maximum hits to return (default ${MEMORY_SEARCH_DEFAULT_LIMIT}, max ${MEMORY_SEARCH_MAX_LIMIT}).` })),
|
|
@@ -144,10 +164,8 @@ export function createMemoryEngineTools(opts) {
|
|
|
144
164
|
}
|
|
145
165
|
merged.sort(contractOrder);
|
|
146
166
|
const top = merged.slice(0, limit);
|
|
147
|
-
if (top.length === 0)
|
|
148
|
-
|
|
149
|
-
return { content: `No memory entries matched ${JSON.stringify(inlineUntrusted(query, 120))}. If the user expected you to know this, say that you checked memory and found nothing.`, details };
|
|
150
|
-
}
|
|
167
|
+
if (top.length === 0)
|
|
168
|
+
return noMatch(query);
|
|
151
169
|
const bodyById = new Map();
|
|
152
170
|
try {
|
|
153
171
|
for (let i = 0; i < planes.length; i++) {
|
|
@@ -171,10 +189,8 @@ export function createMemoryEngineTools(opts) {
|
|
|
171
189
|
return refusedSearch("challenge_ledger_unavailable", "Memory search is unavailable: the challenge ledger for a mounted memory plane cannot be read (fail-closed). Report this to the operator.", "failed");
|
|
172
190
|
}
|
|
173
191
|
const live = top.filter((h) => bodyById.has(h.id) && terminalExclusions[h.planeIndex]?.has(h.id) !== true);
|
|
174
|
-
if (live.length === 0)
|
|
175
|
-
|
|
176
|
-
return { content: `No memory entries matched ${JSON.stringify(inlineUntrusted(query, 120))}. If the user expected you to know this, say that you checked memory and found nothing.`, details };
|
|
177
|
-
}
|
|
192
|
+
if (live.length === 0)
|
|
193
|
+
return noMatch(query);
|
|
178
194
|
for (let i = 0; i < planes.length; i++) {
|
|
179
195
|
const ids = live.filter((h) => h.planeIndex === i).map((h) => h.id);
|
|
180
196
|
if (ids.length === 0)
|
|
@@ -219,7 +235,7 @@ export function createMemoryEngineTools(opts) {
|
|
|
219
235
|
defer: true,
|
|
220
236
|
offload: false,
|
|
221
237
|
contentOrigin: "local",
|
|
222
|
-
contract: { contractId: "core.memory_get@1", implementationRevision: "
|
|
238
|
+
contract: { contractId: "core.memory_get@1", implementationRevision: "3" },
|
|
223
239
|
parameters: Type.Object({
|
|
224
240
|
id: Type.Optional(Type.String({ description: "Entry id (exact lookup). Pass either id or slug, not both." })),
|
|
225
241
|
slug: Type.Optional(Type.String({ description: "Entry slug (its file path without .md). Ambiguous across scopes unless scope is also passed." })),
|
|
@@ -275,7 +291,10 @@ export function createMemoryEngineTools(opts) {
|
|
|
275
291
|
}
|
|
276
292
|
if (matches.length === 0) {
|
|
277
293
|
const where = scope !== undefined ? ` in scope ${JSON.stringify(inlineUntrusted(scope, 80))}` : "";
|
|
278
|
-
return refusedGet("not_found", `No memory entry with slug ${JSON.stringify(inlineUntrusted(slug, 160))}${where} is mounted in this session.
|
|
294
|
+
return refusedGet("not_found", `No memory entry with slug ${JSON.stringify(inlineUntrusted(slug, 160))}${where} is mounted in this session. ` +
|
|
295
|
+
`Only entries carrying a committed entry id are served here, and a memory file written into the memory root during ` +
|
|
296
|
+
`this session gets its id when the session-boundary harvest admits it — a file written in this session is not served here before then.` +
|
|
297
|
+
pollutionSentence(), "not_found", { slug: slug, ...(scope !== undefined ? { scope } : {}) });
|
|
279
298
|
}
|
|
280
299
|
if (matches.length > 1) {
|
|
281
300
|
const candidates = matches.map((m) => ({ scope: m.header.scope, slug: m.header.slug, id: m.header.id }));
|
|
@@ -133,6 +133,11 @@ export interface ResultFlags {
|
|
|
133
133
|
* read-posture seats above: present iff prepare completed (the memory-less states are their own
|
|
134
134
|
* values — absence means only "prepare never ran to completion"). */
|
|
135
135
|
effectiveMemoryScopes?: TaskResult["effectiveMemoryScopes"];
|
|
136
|
+
/** #327 — the leg's effective reasoning resolution, echoed on `TaskResult.effectiveReasoning`.
|
|
137
|
+
* Pure pass-through on every terminal (same law as the read-posture seats): the SAME object the
|
|
138
|
+
* `reasoning.resolved` trace frame carried (runtask resolves once per leg — two faces, one mint);
|
|
139
|
+
* absent when thinking was off/unset for the leg, so the key is omitted. */
|
|
140
|
+
effectiveReasoning?: TaskResult["effectiveReasoning"];
|
|
136
141
|
/** ruled 2026-08-04 — the usage-governance wait hint carried by the platform terminal the run adopted
|
|
137
142
|
* (`undefined` for every other cause: an expiring environment has no return time to give, and a store
|
|
138
143
|
* failure is not a window). Passed as data rather than read back off `threw` so the seat has a typed
|
|
@@ -165,5 +165,5 @@ export function assembleResult(spec, sessionId, final, stats, flags) {
|
|
|
165
165
|
void _internalCompaction;
|
|
166
166
|
if (flags.unpricedSpend)
|
|
167
167
|
delete publicStats.costMicroUsd;
|
|
168
|
-
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, ...(checkpointId !== undefined ? { checkpointId } : {}), checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), stats: publicStats };
|
|
168
|
+
return { taskId, sessionId, status, ...(flags.model !== undefined ? { model: flags.model } : {}), result: result.trim(), salvagedOutput, blockedReason, errorMessage, errorCode, ...(retryAfterMs !== undefined ? { retryAfterMs } : {}), checkpointToken, ...(checkpointId !== undefined ? { checkpointId } : {}), checkpointGate, ...(workspaceRestoreMode !== undefined ? { workspaceRestoreMode } : {}), ...(flags.rewindNotes !== undefined && flags.rewindNotes.length > 0 ? { rewindNotes: flags.rewindNotes } : {}), ...(flags.remoteEnvFailures !== undefined && flags.remoteEnvFailures.length > 0 ? { remoteEnvFailures: flags.remoteEnvFailures } : {}), ...(flags.strandedHumanAnswers !== undefined && flags.strandedHumanAnswers.length > 0 ? { strandedHumanAnswers: flags.strandedHumanAnswers } : {}), ...(flags.effectiveReadFace !== undefined ? { effectiveReadFace: flags.effectiveReadFace } : {}), ...(flags.effectiveReadDenyPatterns !== undefined && flags.effectiveReadDenyPatterns.length > 0 ? { effectiveReadDenyPatterns: flags.effectiveReadDenyPatterns } : {}), ...(flags.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: flags.effectiveMemoryScopes } : {}), ...(flags.effectiveReasoning !== undefined ? { effectiveReasoning: flags.effectiveReasoning } : {}), stats: publicStats };
|
|
169
169
|
}
|
|
@@ -144,6 +144,12 @@ export interface PrepareConfigDoorsResult {
|
|
|
144
144
|
* material; the in-force arm never lands here — it throws at the door). */
|
|
145
145
|
discardedEnvRaw: string | undefined;
|
|
146
146
|
};
|
|
147
|
+
/** owned — the NORMALIZED per-leg delegation-evidence standard (design/324): the deps seat read
|
|
148
|
+
* ONCE, in this synchronous pre-await stretch, screened, and absent folded to the "static-face"
|
|
149
|
+
* default. The delegation wrap consumes THIS value, never the live deps object — a deps seat
|
|
150
|
+
* mutated (or getter-backed) after the door must not present an unscreened value to the mark
|
|
151
|
+
* branch (the #245 5.33 read-once posture). */
|
|
152
|
+
memoryDelegationEvidence: "static-face" | "attested-only";
|
|
147
153
|
/** owned — validated deployment governance windows (undefined = ungoverned). */
|
|
148
154
|
usageWindows: readonly UsageWindow[] | undefined;
|
|
149
155
|
/** owned, out-param cell — created EMPTY here; the brain-call wiring later installs into
|
|
@@ -132,6 +132,22 @@ export function prepareConfigDoors(input) {
|
|
|
132
132
|
}
|
|
133
133
|
assertReadFaceValue(spec.readFace, "TaskSpec.readFace");
|
|
134
134
|
assertReadFaceValue(deps.readFace, "readFace (deployment seat)");
|
|
135
|
+
const memoryDelegationEvidenceRaw = deps.memoryDelegationEvidence;
|
|
136
|
+
if (memoryDelegationEvidenceRaw !== undefined && memoryDelegationEvidenceRaw !== "static-face" && memoryDelegationEvidenceRaw !== "attested-only") {
|
|
137
|
+
const evidence = memoryDelegationEvidenceRaw;
|
|
138
|
+
const got = typeof evidence === "string"
|
|
139
|
+
? JSON.stringify(evidence.length > 64 ? `${evidence.slice(0, 64)}…` : evidence)
|
|
140
|
+
: evidence === null
|
|
141
|
+
? "null"
|
|
142
|
+
: Array.isArray(evidence)
|
|
143
|
+
? "an array"
|
|
144
|
+
: typeof evidence;
|
|
145
|
+
const e = new Error(`RunnerDeps.memoryDelegationEvidence must be "static-face" or "attested-only" when present (got ${got}) — ` +
|
|
146
|
+
`an unevaluable evidence standard is refused loudly, never folded to either standard.`);
|
|
147
|
+
e.code = "config.memory_delegation_evidence";
|
|
148
|
+
throw e;
|
|
149
|
+
}
|
|
150
|
+
const memoryDelegationEvidence = memoryDelegationEvidenceRaw === "attested-only" ? "attested-only" : "static-face";
|
|
135
151
|
if (spec.resumeAtMode !== undefined) {
|
|
136
152
|
if (spec.resumeAt === undefined) {
|
|
137
153
|
const e = new Error(`resumeAtMode "${spec.resumeAtMode}" requires resumeAt (there is no branch target to position against)`);
|
|
@@ -294,6 +310,7 @@ export function prepareConfigDoors(input) {
|
|
|
294
310
|
compModel,
|
|
295
311
|
fableMitigations,
|
|
296
312
|
modelGate,
|
|
313
|
+
memoryDelegationEvidence,
|
|
297
314
|
usageWindows,
|
|
298
315
|
brainCallGuardrailRef,
|
|
299
316
|
brainCallGuardrailMs,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { sep } from "node:path";
|
|
2
|
+
import { deliverEngineNotice } from "../types.js";
|
|
2
3
|
import { admitMemoryScopes } from "../memory-admission.js";
|
|
3
4
|
import { adoptLegacyRepoDirs, canonicalize, deriveRepoControlPlaneDir, deriveProjectControlDir, deriveProjectMemoryDir, deriveRepoMemoryDir, drainMemoryAnnouncements, enqueueMemoryAnnouncement, isContainedIn, lookupProjectIdHint, recordProjectIdHint, resolveMemoryEngineRoot } from "../memory-engine/layout.js";
|
|
4
5
|
import { classifyScopePlanes, derivePersonalControlDir, derivePersonalMemoryDir, mergeHarvestReports, mergeInjections, needsDualRoots, parsedProjectPlane } from "../memory-engine/dual-root.js";
|
|
5
6
|
import { normalizeMemorySpec } from "../memory.js";
|
|
6
|
-
import { MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_RECALL_DISCIPLINE, MemoryEngine } from "../memory-engine/engine.js";
|
|
7
|
+
import { MEMORY_ANNOUNCEMENT_READONLY_CODA, MEMORY_PREFERENCE_DISCIPLINE, MEMORY_READONLY_NOTICE, MEMORY_RECALL_DISCIPLINE, MemoryEngine, memoryHarvestQuarantinedNotice, memorySessionPollutedNotice, pollutionContainmentCounts, } from "../memory-engine/engine.js";
|
|
7
8
|
import { createMemoryEngineTools } from "../memory-engine/tools.js";
|
|
8
9
|
import { assertScopeContractPlacement, parseScopeKey, resolveProjectId } from "../memory-engine/scope-contract.js";
|
|
9
10
|
import { FileMemoryEngineBackend } from "../memory-engine/file-backend.js";
|
|
@@ -298,12 +299,26 @@ export async function prepareMemory(input) {
|
|
|
298
299
|
}
|
|
299
300
|
return writeEngine.gateWrite(writeHandle, w.key, w.content);
|
|
300
301
|
};
|
|
302
|
+
let pollutionAnnounced = false;
|
|
303
|
+
const announceHarvestContainment = (report) => {
|
|
304
|
+
const { count, moved, escalated } = pollutionContainmentCounts(report);
|
|
305
|
+
if (count === 0)
|
|
306
|
+
return;
|
|
307
|
+
let reason;
|
|
308
|
+
try {
|
|
309
|
+
reason = writeEngine.sessionPollution(sessionId)?.reason;
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
}
|
|
313
|
+
deliverEngineNotice(deps.onNotice, memoryHarvestQuarantinedNotice({ count, moved, escalated, ...(reason !== undefined ? { reason } : {}), sessionId }));
|
|
314
|
+
};
|
|
301
315
|
const harvestSafe = async (phase = "terminal") => {
|
|
302
316
|
try {
|
|
303
317
|
const report = await harvestBoth();
|
|
304
318
|
if (!report.ok && report.incident) {
|
|
305
319
|
deps.onError?.(new Error(`memory harvest refused (${report.incident.kind}): ${report.incident.detail}`), { phase: "memory", sessionId });
|
|
306
320
|
}
|
|
321
|
+
announceHarvestContainment(report);
|
|
307
322
|
try {
|
|
308
323
|
deps.onMemoryHarvestReport?.(report, { sessionId, phase });
|
|
309
324
|
}
|
|
@@ -327,7 +342,13 @@ export async function prepareMemory(input) {
|
|
|
327
342
|
harvest: harvestSafe,
|
|
328
343
|
pollution: {
|
|
329
344
|
polluted: () => writeEngine.sessionPollution(sessionId),
|
|
330
|
-
markPolluted: (reason) =>
|
|
345
|
+
markPolluted: (reason) => {
|
|
346
|
+
const outcome = writeEngine.markSessionPolluted(sessionId, reason);
|
|
347
|
+
if (outcome === "existed" || (outcome === "unpersisted" && pollutionAnnounced))
|
|
348
|
+
return;
|
|
349
|
+
pollutionAnnounced = true;
|
|
350
|
+
deliverEngineNotice(deps.onNotice, memorySessionPollutedNotice({ reason, sessionId }));
|
|
351
|
+
},
|
|
331
352
|
},
|
|
332
353
|
contentSafety: {
|
|
333
354
|
trustedTools: new Set(memorySpec.trustedTools ?? []),
|
|
@@ -341,7 +362,7 @@ export async function prepareMemory(input) {
|
|
|
341
362
|
writeScope: memorySpec.writeScope,
|
|
342
363
|
};
|
|
343
364
|
if (input.memorySearchToolsPlanned)
|
|
344
|
-
memoryTools = createMemoryEngineTools({ planes: toolPlanes });
|
|
365
|
+
memoryTools = createMemoryEngineTools({ planes: toolPlanes, sessionPollution: () => writeEngine.sessionPollution(sessionId) });
|
|
345
366
|
}
|
|
346
367
|
catch (err) {
|
|
347
368
|
if (typeof err.code === "string" &&
|
|
@@ -1092,7 +1092,9 @@ export interface RunInternals {
|
|
|
1092
1092
|
* it); `contentSafety` is the chain's FROZEN classification snapshot — the child may narrow it
|
|
1093
1093
|
* with its own config, never widen (design/180 A-2). Trusted internals chain only, same posture as
|
|
1094
1094
|
* {@link inheritedGate}. Absent ⇒ the child records nothing (its deliveries then read `unknown`,
|
|
1095
|
-
* and every judgment falls back to the static floor — fail-closed by construction
|
|
1095
|
+
* and every judgment falls back to the static floor — fail-closed by construction; whether the
|
|
1096
|
+
* floor's verdict MARKS the judging session follows that run's deployment evidence standard,
|
|
1097
|
+
* {@link RunnerDeps.memoryDelegationEvidence}).
|
|
1096
1098
|
*/
|
|
1097
1099
|
delegationProvenance?: {
|
|
1098
1100
|
ref: {
|
|
@@ -333,7 +333,7 @@ function cwdConflictsRestoreError(requestedCwd) {
|
|
|
333
333
|
export async function prepareTask(spec, deps, sessions, resume, internals, runnerSelf) {
|
|
334
334
|
const doors = prepareConfigDoors({ spec, deps, sessions, resume, internals });
|
|
335
335
|
spec = doors.spec;
|
|
336
|
-
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
336
|
+
const { toolFaceSnapshot, promptProfile, lockedPreflight, resolvedInteractionPosture, resolvedRole, model, thinking, compModel, fableMitigations, memoryDelegationEvidence, usageWindows, brainCallGuardrailRef, brainCallGuardrailMs } = doors;
|
|
337
337
|
announceToolModelGate(deps.onNotice, model.id, doors.modelGate);
|
|
338
338
|
const { toolEffects, egressTools, irreversibleTools, irreversibilityTier, axisExplicitNegatives, reversibilityProbes, ownToolNames } = prepareSafetyScan({ spec, deps });
|
|
339
339
|
let shellGatedBash = false;
|
|
@@ -1855,6 +1855,8 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1855
1855
|
const delegationProvenanceChannel = internals?.delegationProvenance;
|
|
1856
1856
|
if (memoryEngineSession !== undefined || delegationProvenanceChannel !== undefined) {
|
|
1857
1857
|
const pollution = memoryEngineSession?.pollution;
|
|
1858
|
+
const delegationEvidenceAttestedOnly = memoryDelegationEvidence === "attested-only";
|
|
1859
|
+
let staticMarkWaiverAnnounced = false;
|
|
1858
1860
|
const effectiveSafety = narrowContentSafety(delegationProvenanceChannel?.contentSafety, memoryEngineSession?.contentSafety);
|
|
1859
1861
|
const trustedTools = effectiveSafety.trustedTools;
|
|
1860
1862
|
const execIsExternalContent = effectiveSafety.execIsExternalContent;
|
|
@@ -1934,8 +1936,28 @@ export async function prepareTask(spec, deps, sessions, resume, internals, runne
|
|
|
1934
1936
|
}
|
|
1935
1937
|
else if (att !== "clean") {
|
|
1936
1938
|
if (external) {
|
|
1937
|
-
|
|
1938
|
-
(typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)")
|
|
1939
|
+
const staticFaceReason = `tool "${t.name}" returned the output of a delegated agent whose tool face can reach external content` +
|
|
1940
|
+
(typeof requested === "string" ? ` (subagent_type "${requested}")` : " (no subagent_type named — classified fail-closed)");
|
|
1941
|
+
if (!delegationEvidenceAttestedOnly) {
|
|
1942
|
+
mark(staticFaceReason);
|
|
1943
|
+
}
|
|
1944
|
+
else if (pollution !== undefined && !staticMarkWaiverAnnounced) {
|
|
1945
|
+
staticMarkWaiverAnnounced = true;
|
|
1946
|
+
const waivedReason = inlineUntrusted(staticFaceReason, 200);
|
|
1947
|
+
deliverEngineNotice(deps.onNotice, {
|
|
1948
|
+
code: "memory.delegation_static_mark_waived",
|
|
1949
|
+
message: `Delegation static-face pollution mark waived: ${waivedReason}. The deployment configured ` +
|
|
1950
|
+
`memoryDelegationEvidence="attested-only", so a static tool-face verdict alone does not mark this ` +
|
|
1951
|
+
`session's memory polluted; a delivered "external" attestation and a direct polluting-class ` +
|
|
1952
|
+
`invocation still mark, and on a provenance-armed chain this delegation still records as ` +
|
|
1953
|
+
`incomplete. At most one notice per prepared task leg.`,
|
|
1954
|
+
detail: {
|
|
1955
|
+
reason: waivedReason,
|
|
1956
|
+
...(typeof requested === "string" ? { subagentType: inlineUntrusted(requested, 120) } : {}),
|
|
1957
|
+
sessionId,
|
|
1958
|
+
},
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1939
1961
|
}
|
|
1940
1962
|
recordIncomplete();
|
|
1941
1963
|
}
|
|
@@ -1661,6 +1661,7 @@ export class Runner {
|
|
|
1661
1661
|
errorCode: code,
|
|
1662
1662
|
...(remoteEnvFailure !== undefined ? { remoteEnvFailures: remoteEnvFailure } : {}),
|
|
1663
1663
|
...(taskIdRef.effectiveMemoryScopes !== undefined ? { effectiveMemoryScopes: taskIdRef.effectiveMemoryScopes } : {}),
|
|
1664
|
+
...(taskIdRef.effectiveReasoning !== undefined ? { effectiveReasoning: taskIdRef.effectiveReasoning } : {}),
|
|
1664
1665
|
...(() => {
|
|
1665
1666
|
const hinted = err.retryAfterMs;
|
|
1666
1667
|
return code === "memory.admission_required" && typeof hinted === "number" && Number.isFinite(hinted) && hinted > 0
|
|
@@ -2468,23 +2469,24 @@ export class Runner {
|
|
|
2468
2469
|
ts: Date.now(),
|
|
2469
2470
|
}));
|
|
2470
2471
|
}
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
}
|
|
2487
|
-
|
|
2472
|
+
const reasoningResolution = prepared.thinking && prepared.thinking !== "off" ? resolveReasoning(prepared.thinking, prepared.model) : undefined;
|
|
2473
|
+
if (taskIdRef && reasoningResolution !== undefined)
|
|
2474
|
+
taskIdRef.effectiveReasoning = reasoningResolution;
|
|
2475
|
+
if (reasoningResolution !== undefined) {
|
|
2476
|
+
emitTrace(rs.telemetry.tracer, () => ({
|
|
2477
|
+
kind: "reasoning.resolved",
|
|
2478
|
+
version: 1,
|
|
2479
|
+
taskId: rs.telemetry.taskId,
|
|
2480
|
+
model: prepared.model.id,
|
|
2481
|
+
requested: reasoningResolution.requested,
|
|
2482
|
+
effective: reasoningResolution.effective,
|
|
2483
|
+
graded: reasoningResolution.graded,
|
|
2484
|
+
clamped: reasoningResolution.clamped,
|
|
2485
|
+
format: reasoningResolution.format,
|
|
2486
|
+
endpoint: reasoningResolution.endpoint,
|
|
2487
|
+
...(reasoningResolution.dropped === true ? { dropped: true } : {}),
|
|
2488
|
+
ts: Date.now(),
|
|
2489
|
+
}));
|
|
2488
2490
|
}
|
|
2489
2491
|
const effectiveTimeoutMs = spec.limits?.maxWalltimeMs;
|
|
2490
2492
|
const walltimeMonotonicDeadline = effectiveTimeoutMs !== undefined ? rs.telemetry.taskStartMonotonic + effectiveTimeoutMs : undefined;
|
|
@@ -3458,6 +3460,7 @@ export class Runner {
|
|
|
3458
3460
|
effectiveReadFace: prepared.effectiveReadFace,
|
|
3459
3461
|
effectiveReadDenyPatterns: prepared.effectiveReadDenyPatterns,
|
|
3460
3462
|
effectiveMemoryScopes: prepared.effectiveMemoryScopes,
|
|
3463
|
+
effectiveReasoning: reasoningResolution,
|
|
3461
3464
|
retryAfterMs: rs.limits.platformTerminal?.retryAfterMs,
|
|
3462
3465
|
abortedForTimeout: timeout.fired,
|
|
3463
3466
|
abortedForTurns: rs.limits.turnsExceeded,
|
package/dist/core/trace.d.ts
CHANGED
|
@@ -143,8 +143,13 @@ export type TraceEvent = {
|
|
|
143
143
|
/**
|
|
144
144
|
* How a task's requested reasoning intensity RESOLVED against the model's real capability (design/96 S6).
|
|
145
145
|
* Emitted once at task start when thinking is on, so a deployment can SEE — not silently swallow (§E
|
|
146
|
-
* honesty red-line) — that a binary provider ignored the tier (`graded:false`)
|
|
147
|
-
* clamped it down (`clamped:true`)
|
|
146
|
+
* honesty red-line) — that a binary provider ignored the tier (`graded:false`), that an effort endpoint
|
|
147
|
+
* clamped it down (`clamped:true`), or that a NON-reasoning model dropped the request entirely
|
|
148
|
+
* (`dropped:true` — the frame fires for that model too; it used to be the one arm with no report).
|
|
149
|
+
* Metadata-only (tiers + format + endpoint, never prompt content). The resolution is against the
|
|
150
|
+
* task's PRIMARY serving model at leg entry (same law as `TaskResult.model`): a mid-run
|
|
151
|
+
* degradation does not re-emit this frame — the switch is observed on its own seats
|
|
152
|
+
* (`TaskResult.degraded`).
|
|
148
153
|
*/
|
|
149
154
|
kind: "reasoning.resolved";
|
|
150
155
|
version: 1;
|
|
@@ -163,6 +168,16 @@ export type TraceEvent = {
|
|
|
163
168
|
format: string;
|
|
164
169
|
/** Coarse endpoint label (`model.api`, e.g. `openai-completions` / `anthropic-messages`). */
|
|
165
170
|
endpoint: string;
|
|
171
|
+
/**
|
|
172
|
+
* Present (true) only when the model declares NO reasoning capability (`Model.reasoning` falsy): the
|
|
173
|
+
* brains send no thinking parameter at all, so the requested tier was DROPPED — not clamped.
|
|
174
|
+
* `effective:"off"` states the ENGINE side (nothing was requested), not a measured gateway state:
|
|
175
|
+
* on the binary enable-only formats (qwen/zai/qwen-chat-template) parameter absence is
|
|
176
|
+
* provider-default and a default-on gateway may still reason at its own tier — read `format` to
|
|
177
|
+
* know which family applies. `format`/`endpoint` report the family that WOULD have carried the
|
|
178
|
+
* tier. Absent on every reasoning-capable resolution (old consumers see identical frames).
|
|
179
|
+
*/
|
|
180
|
+
dropped?: true;
|
|
166
181
|
/** design/148 S3 (additive, §10.2): the nine-element cache identity — digests only. */
|
|
167
182
|
snapshot?: {
|
|
168
183
|
cacheIdentity: string;
|
package/dist/core/types.d.ts
CHANGED
|
@@ -846,7 +846,9 @@ export interface ToolExecuteContext {
|
|
|
846
846
|
* content-safety snapshot — the tool then mints the child's recorder ref and threads both into
|
|
847
847
|
* the child's trusted `RunInternals.delegationProvenance`. Undefined return / absent field ⇒ the
|
|
848
848
|
* child spawns without a recorder (its deliveries read `unknown` and every judgment stays on the
|
|
849
|
-
* static floor —
|
|
849
|
+
* static floor — whose MARK action follows the judging run's deployment evidence standard,
|
|
850
|
+
* {@link RunnerDeps.memoryDelegationEvidence}; under the `"static-face"` default this is v1
|
|
851
|
+
* behavior byte-identical). Same trust posture as
|
|
850
852
|
* {@link inheritedGateForChildren}: never a model/tool argument, never a TaskSpec field.
|
|
851
853
|
*/
|
|
852
854
|
delegationProvenanceForChildren?: () => import("./memory-engine/delegation-provenance.js").DelegationContentSafety | undefined;
|
|
@@ -2839,6 +2841,29 @@ export interface TaskResult {
|
|
|
2839
2841
|
* `TaskSpec.model` ref (which may be a role / name / `@mention`). Lets a UI echo "served by X" instead of the
|
|
2840
2842
|
* requested ref. A mid-run degradation is observed separately (see the degraded-model fields). */
|
|
2841
2843
|
model?: string;
|
|
2844
|
+
/**
|
|
2845
|
+
* #327 — the leg's effective REASONING resolution: the result-face twin of {@link model} for the thinking
|
|
2846
|
+
* knob. How the requested tier resolved against the serving model's real capability
|
|
2847
|
+
* (`requested`/`effective`/`graded`/`clamped`/`format`/`endpoint`, plus `dropped:true` when a
|
|
2848
|
+
* non-reasoning model dropped the request entirely — field semantics on
|
|
2849
|
+
* {@link import("../brain/reasoning.js").ResolvedReasoning}). It is the SAME resolver output the
|
|
2850
|
+
* `reasoning.resolved` trace frame carries, computed once per leg — the two faces cannot tell different
|
|
2851
|
+
* stories; this seat serves consumers without a tracer (the trace frame is the deployment-observability
|
|
2852
|
+
* face, this is the caller face).
|
|
2853
|
+
*
|
|
2854
|
+
* **In-presence condition** — mirrors the trace frame exactly: present on every terminal of a leg that ran
|
|
2855
|
+
* with a REQUESTED thinking tier other than off/unset (the `spec > role > model.defaultThinking` chain);
|
|
2856
|
+
* absent when thinking was off/unset for the leg, and on prepare failures (the resolution is minted after
|
|
2857
|
+
* prepare). On a resumed task each leg re-resolves against the leg's own serving model.
|
|
2858
|
+
*
|
|
2859
|
+
* **Degradation law** — same as {@link model}, whose resolution this is: the seat describes the leg's
|
|
2860
|
+
* PRIMARY serving model at leg entry. A mid-run degradation (reactive fallback / near-budget switch)
|
|
2861
|
+
* changes the serving model WITHOUT re-minting this seat or its trace twin — read {@link degraded} to see
|
|
2862
|
+
* the switch; the fallback's own reasoning capability is deliberately NOT re-reported here (re-resolving
|
|
2863
|
+
* one face would desync it from the task-start `reasoning.resolved` frame). A consumer needing the
|
|
2864
|
+
* fallback's reasoning posture resolves `degraded.to` itself (`resolveReasoning` is exported).
|
|
2865
|
+
*/
|
|
2866
|
+
effectiveReasoning?: import("../brain/reasoning.js").ResolvedReasoning;
|
|
2842
2867
|
/** Final assistant text. */
|
|
2843
2868
|
result: string;
|
|
2844
2869
|
/**
|
|
@@ -4683,6 +4708,50 @@ export interface EngineNotice {
|
|
|
4683
4708
|
* `detail: { steer, taskId? }` / `{ followUp, taskId? }`. Per-run, at most once per family
|
|
4684
4709
|
* (the terminal sweep is a single site).
|
|
4685
4710
|
*
|
|
4711
|
+
* - `"memory.session_polluted"` (design/178 §3, #324a) — this session's memory crossed into the
|
|
4712
|
+
* one-way POLLUTED state (a tool classified as an external content source was invoked, directly
|
|
4713
|
+
* or through a delegated child): its memory writes are no longer eligible for the long-term
|
|
4714
|
+
* library. The notice states that VERDICT plus what a harvest does when it collects — it does
|
|
4715
|
+
* not promise quarantine, because a `writeScope`-null layering and a declared-unavailable
|
|
4716
|
+
* session both reach the zero-admission harvest arm (nothing collected ⇒ nothing captured), and
|
|
4717
|
+
* a mark landing after the pre-commit pollution read leaves already-committed rows to the
|
|
4718
|
+
* challenge sweep; what a harvest actually contained is the other code below. The challenge
|
|
4719
|
+
* clause is a policy statement, not a receipt — a sweep that cannot run announces
|
|
4720
|
+
* `"memory.challenge_sweep_failed"` on the engine's incident seat. Announced ONCE PER
|
|
4721
|
+
* SESSION, at the mark seat: a repeat mark inside the process and a resumed session whose
|
|
4722
|
+
* durable marker already exists stay quiet (the state is one-way, so a second line would carry
|
|
4723
|
+
* no new fact); a pollution state that cannot be read announces rather than assuming it was
|
|
4724
|
+
* already said. `detail: { reason, sessionId? }` — `reason` names the invoked tool and is
|
|
4725
|
+
* neutralized/length-bounded (the name comes from the host/protocol roster).
|
|
4726
|
+
* - `"memory.harvest_quarantined"` (design/178 §3, #324a) — a polluted session's harvest ran its
|
|
4727
|
+
* containment: `count` ENTRY files written or changed in the session were withheld from the
|
|
4728
|
+
* library, `moved` of them were physically moved into the control-plane quarantine directory,
|
|
4729
|
+
* and `escalated` of them carry a `HarvestReport.quarantineFailures` row (a failed capture, a
|
|
4730
|
+
* failed removal, or a file tombstoned in place — which counts as moved AND escalated, so the
|
|
4731
|
+
* two numbers are read off the report's rows, never subtracted from each other). One notice PER
|
|
4732
|
+
* HARVEST that withheld at least one entry file (a checkpoint harvest and the terminal harvest
|
|
4733
|
+
* are distinct facts), never minted for a clean session; `detail: { count, moved, escalated,
|
|
4734
|
+
* reason?, sessionId? }` (`reason` absent ⇔ the pollution marker could not be re-read at report
|
|
4735
|
+
* time — the withheld count stays true either way). Registered gap: the derived index (`MEMORY.md`) is
|
|
4736
|
+
* contained on a path that mints no rejection row, so an index-ONLY containment produces no
|
|
4737
|
+
* notice and is disclosed by the harvest report's warnings alone.
|
|
4738
|
+
* - `"memory.delegation_static_mark_waived"` (design/324, #324 ruling ①) — the deployment set
|
|
4739
|
+
* {@link RunnerDeps.memoryDelegationEvidence} to `"attested-only"` and a delegation call whose
|
|
4740
|
+
* STATIC tool-face verdict would have marked this session's memory polluted (attestation
|
|
4741
|
+
* missing/unknown + face can reach external content) was not marked: the waiver is announced —
|
|
4742
|
+
* the explicit dual of `"memory.session_polluted"` for this arm, so the arm has a voice where a
|
|
4743
|
+
* mark used to land. MINT-side guarantee only: delivery rides the shared guarded form
|
|
4744
|
+
* ({@link deliverEngineNotice} — a wired sink that throws owns that loss, exactly as at every
|
|
4745
|
+
* de-duplicating station), and the leg latch is consumed at mint. Minted only when a pollution
|
|
4746
|
+
* face is mounted (a
|
|
4747
|
+
* recorder-only child's mark is a no-op — nothing is waived there), at most ONCE PER PREPARED
|
|
4748
|
+
* TASK LEG (a resume leg may announce again — a new leg's audit stream is a new fact;
|
|
4749
|
+
* deliberately no durable once-per-session state), never on the `"static-face"` default, and
|
|
4750
|
+
* never for a delivered `"external"` attestation (that mark still lands);
|
|
4751
|
+
* `detail: { reason, subagentType?, sessionId? }` — `reason` is the same sentence the waived
|
|
4752
|
+
* mark would have carried, neutralized/length-bounded (tool and agent-type names are
|
|
4753
|
+
* host/model-controlled inputs).
|
|
4754
|
+
*
|
|
4686
4755
|
* Deliberately NOT a notice family: brain retry/reconnect liveness (a rate limit, a 5xx, a
|
|
4687
4756
|
* transient network failure being retried). Those are per-attempt liveness frames with their own
|
|
4688
4757
|
* frequency semantics and ride the wire `status` channel ({@link BrainStatus}), whose sink the
|
|
@@ -4830,6 +4899,36 @@ export interface RunnerDeps {
|
|
|
4830
4899
|
* bypasses the gate as always — the human already adjudicated it).
|
|
4831
4900
|
*/
|
|
4832
4901
|
writeProtectedPaths?: readonly import("./write-protect.js").WriteProtectedEntry[];
|
|
4902
|
+
/**
|
|
4903
|
+
* design/324 (#324 ruling ① containment) — the EVIDENCE STANDARD the delegation arm of the
|
|
4904
|
+
* content-origin wrap applies when deciding whether a delegation call marks THIS session's memory
|
|
4905
|
+
* polluted (design/178 §3 / design/180 half A):
|
|
4906
|
+
* - `"static-face"` (absent ≡ this; the default) — today's behavior, byte-identical: a delegation
|
|
4907
|
+
* whose delivered attestation is missing/unknown and whose static tool face can reach external
|
|
4908
|
+
* content marks the session (the capability over-approximation: possibility counts as exposure).
|
|
4909
|
+
* - `"attested-only"` — exactly that ONE static-face mark is waived, and each prepared leg
|
|
4910
|
+
* announces the first waiver (`"memory.delegation_static_mark_waived"`). Everything else is
|
|
4911
|
+
* unchanged: a delivered `"external"` attestation still marks, the chain's `incomplete`
|
|
4912
|
+
* recording still happens, a non-delegation polluting-class tool still marks, and a delegation
|
|
4913
|
+
* tool that is ITSELF classified polluting still marks pre-call.
|
|
4914
|
+
* ACCEPTED COST (the deployment's to own, stated as mechanism, not as absence of risk): under
|
|
4915
|
+
* `"attested-only"` a BACKGROUND child's real external contact does not mark this session — its
|
|
4916
|
+
* content re-enters through the TaskOutput result, the task-notification injection, or the
|
|
4917
|
+
* AgentTranscript step summaries, none of which carries an attestation — and a foreground child
|
|
4918
|
+
* that ended abnormally (crash/salvage) is likewise not marked on its face alone (the chain still
|
|
4919
|
+
* records `incomplete`). Already-marked sessions are never retroactively cleaned; the key only
|
|
4920
|
+
* governs NEW marks.
|
|
4921
|
+
* DEPLOYMENT seat ONLY (same posture as {@link readDenyBuiltinTiers}): deliberately no TaskSpec
|
|
4922
|
+
* twin and not in the governed workflow whitelist — a task author or governed script gets no
|
|
4923
|
+
* channel to loosen the evidence standard below its deployment. Not frozen into checkpoints: a
|
|
4924
|
+
* resumed leg follows the CURRENT deployment configuration. Each run reads the deps of the Runner
|
|
4925
|
+
* that PREPARES it — a multi-runner assembly should configure every runner with the same value
|
|
4926
|
+
* (drift is the deployment's own configuration hazard; recorder/attestation semantics are
|
|
4927
|
+
* value-independent, so the chain's evidence quality never varies with this key). Any other value
|
|
4928
|
+
* refuses loudly at prepare (`config.memory_delegation_evidence`, #123 — exact spellings only,
|
|
4929
|
+
* never truthiness).
|
|
4930
|
+
*/
|
|
4931
|
+
memoryDelegationEvidence?: "static-face" | "attested-only";
|
|
4833
4932
|
/**
|
|
4834
4933
|
* design/199 件A — the DEPLOYMENT's read-face declaration
|
|
4835
4934
|
* ({@link import("../tools/fs/read-face.js").ReadFace}; see {@link TaskSpec.readFace} for the
|
|
@@ -5,9 +5,10 @@ import type { ReadFace } from "./read-face.js";
|
|
|
5
5
|
/**
|
|
6
6
|
* The Grep card's details assembly, PURE over the engine text ({mode, offset} from the request) —
|
|
7
7
|
* exported so the text→structured mapping is pinnable with synthetic texts (the byte-truncation and
|
|
8
|
-
* fenced-partial shapes are impractical to construct through a live tool call).
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* fenced-partial shapes are impractical to construct through a live tool call). #313 moved PATH
|
|
9
|
+
* IDENTITY off the text and onto the engines' served rows (both legs supply them now); the counts
|
|
10
|
+
* and totals below still read the engine's own honesty markers out of the text, which is what keeps
|
|
11
|
+
* this seam the honesty boundary.
|
|
11
12
|
*/
|
|
12
13
|
export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number, structuredRows?: readonly import("./search.js").GrepRow[]): Record<string, unknown>;
|
|
13
14
|
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
|
|
@@ -190,7 +190,27 @@ export interface ReadDenyJudge {
|
|
|
190
190
|
/** JS-fallback grep: ignore-aware walk + per-line scan, honoring output_mode / context / head_limit.
|
|
191
191
|
* design/199 件B: `deny` prunes the walk (note appended to every output shape); `denyOut` is the
|
|
192
192
|
* structured twin — a sink because this function's dozen error returns predate the facts (the sink
|
|
193
|
-
* is written the moment the walk lands, whatever the scan then returns).
|
|
193
|
+
* is written the moment the walk lands, whatever the scan then returns).
|
|
194
|
+
*
|
|
195
|
+
* backlog #313 — the SERVED rows ride back next to the text. This scanner knows every result's
|
|
196
|
+
* identity natively (the walk hands it whole paths), so a row's `path` is that walked identity,
|
|
197
|
+
* never a cut of the rendered line — the mis-split family the text re-parse carries cannot exist
|
|
198
|
+
* here. The rows ARE the window `text` shows: post offset, post row cap, post byte ceiling, in the
|
|
199
|
+
* same order. The unit is the RECORD, which is the unit the cap has always counted: one record is
|
|
200
|
+
* one rendered line EXCEPT where the file's own name carries a newline, and then the one record
|
|
201
|
+
* spans two physical lines — the same relation {@link formatRgRecords} states for the rg leg, and
|
|
202
|
+
* precisely the case where a row's identity beats anything the text can be split into.
|
|
203
|
+
* Every row carries a path (this leg emits no group-separator row — the one pathless shape
|
|
204
|
+
* ripgrep's output can carry), and a no-match run's row set is `[]`, not absent. A typed
|
|
205
|
+
* `Error (grep): …` return carries no rows at all: a refusal is not a row set. */
|
|
206
|
+
export declare function jsGrepDetailed(env: ExecutionEnv, root: string, p: GrepParams, signal?: AbortSignal, guards?: JsGrepGuards, deny?: ReadDenyJudge, denyOut?: {
|
|
207
|
+
withheld?: ReadDenyWithheld;
|
|
208
|
+
}): Promise<{
|
|
209
|
+
text: string;
|
|
210
|
+
rows?: readonly GrepRow[];
|
|
211
|
+
}>;
|
|
212
|
+
/** Text-only wrapper of {@link jsGrepDetailed} (the shape callers that never look at the served rows
|
|
213
|
+
* want — the same relation {@link runGrep} has to {@link runGrepDetailed}). */
|
|
194
214
|
export declare function jsGrep(env: ExecutionEnv, root: string, p: GrepParams, signal?: AbortSignal, guards?: JsGrepGuards, deny?: ReadDenyJudge, denyOut?: {
|
|
195
215
|
withheld?: ReadDenyWithheld;
|
|
196
216
|
}): Promise<string>;
|
|
@@ -210,18 +230,34 @@ export type GrepDegradation = {
|
|
|
210
230
|
};
|
|
211
231
|
/** Structured grep result: the model-facing text plus the degradation facts, so the tool layer can
|
|
212
232
|
* ship them on the structured frame instead of leaving them prose-only. */
|
|
213
|
-
/** #313
|
|
214
|
-
*
|
|
215
|
-
*
|
|
216
|
-
*
|
|
217
|
-
*
|
|
233
|
+
/** #313 — one SERVED result RECORD with its path taken from the engine's own knowledge, exactly the
|
|
234
|
+
* window `text` shows (post cap/offset): ripgrep's `--null` path field ({@link parseRgRecords}) on
|
|
235
|
+
* the rg legs, the walked file identity ({@link jsGrepDetailed}) on the JS-scanner legs. A record is
|
|
236
|
+
* one rendered line except where a path carries a newline (see {@link formatRgRecords}), which is
|
|
237
|
+
* also the case the text can no longer be split into records at all. The tool layer prefers these
|
|
238
|
+
* over re-parsing `text` (whose `path:line:text` split mis-cuts a path that
|
|
239
|
+
* itself contains `:digits:`).
|
|
240
|
+
*
|
|
241
|
+
* KNOWN EXCEPTION to "exactly the window `text` shows", stated because it is measurable: on the two
|
|
242
|
+
* ripgrep PARTIAL legs (timeout / error-exit with output) the delivered text is fenced by
|
|
243
|
+
* `delimitUntrusted`, which NEUTRALIZES fence markers inside the body, while the rows carry the
|
|
244
|
+
* record bytes as ripgrep emitted them. A row's `text` there is therefore the un-neutralized form —
|
|
245
|
+
* identity (`path`) is unaffected. Consume `text` for anything rendered; the rows' contract is
|
|
246
|
+
* identity. Reconciling the two (defuse per record, or withhold rows where the text is fenced) is a
|
|
247
|
+
* contract choice recorded for the backlog, not settled here.
|
|
248
|
+
*
|
|
249
|
+
* `path` is absent only where the record genuinely carries none —
|
|
250
|
+
* ripgrep's `--` group separator and the unaccountable shapes listed on {@link RgRecord} — and a
|
|
251
|
+
* row set carrying such a row sends the tool layer back to the text parse for identity. */
|
|
218
252
|
export interface GrepRow {
|
|
219
253
|
path?: string;
|
|
220
254
|
text: string;
|
|
221
255
|
}
|
|
222
256
|
export interface GrepRunResult {
|
|
223
257
|
text: string;
|
|
224
|
-
/** Served rows (#313): present on
|
|
258
|
+
/** Served rows (#313): present on BOTH engine legs — ripgrep's and the JS scanner's, including the
|
|
259
|
+
* rescan legs. Absent only when the JS scanner refused the search outright (a typed
|
|
260
|
+
* `Error (grep): …` text): a refusal has no row set, truthfully, rather than an empty one. */
|
|
225
261
|
rows?: readonly GrepRow[];
|
|
226
262
|
degraded?: GrepDegradation;
|
|
227
263
|
/** design/199 件B — deny-list withholding facts (see {@link ReadDenyWithheld}); absent = nothing
|