@sema-agent/core 5.40.0 → 5.42.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 +101 -3
- package/dist/core/auto-mode-prompt.js +1 -1
- package/dist/core/checkpoint-store.d.ts +15 -0
- package/dist/core/checkpoint-store.js +5 -1
- package/dist/core/memory-engine/file-backend.js +13 -2
- package/dist/core/memory-engine/layout.d.ts +15 -0
- package/dist/core/memory-engine/layout.js +17 -8
- package/dist/core/park-selfcheck.js +3 -1
- package/dist/core/runner/prepare-config-doors.js +20 -0
- package/dist/core/runner/prepare-hands-readface.d.ts +192 -0
- package/dist/core/runner/prepare-hands-readface.js +283 -0
- package/dist/core/runner/prepare-task.d.ts +1 -5
- package/dist/core/runner/prepare-task.js +16 -229
- package/dist/core/runner/runtask.js +6 -1
- package/dist/core/types.d.ts +20 -0
- package/dist/core/types.js +20 -0
- package/dist/engine/harness/agent-harness.d.ts +17 -0
- package/dist/engine/harness/agent-harness.js +19 -1
- package/dist/engine/harness/types.d.ts +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/tools/fs/fs-search-tools.d.ts +8 -0
- package/dist/tools/fs/fs-search-tools.js +67 -58
- package/dist/tools/fs/search.d.ts +31 -9
- package/dist/tools/fs/search.js +110 -73
- package/package.json +1 -1
- package/test/export-surface.snapshot.json +2 -1
package/dist/core/types.d.ts
CHANGED
|
@@ -4669,6 +4669,16 @@ 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"` / `"task.user_followup_undrained"` (#259) — user steers /
|
|
4673
|
+
* follow-ups whose receipts said "queued" were still in their queue at agent_end: the run
|
|
4674
|
+
* ended before any turn could drain them. One notice PER family (a consumer routing on `code`
|
|
4675
|
+
* alone must not mistake a stranded follow-up for a stranded steer — same split as the settled
|
|
4676
|
+
* frame's two count keys). They are NOT redelivered (a steer aimed at a finished run must not
|
|
4677
|
+
* fire at the next one — unlike ENGINE notes, which pend per session); the loud half of the
|
|
4678
|
+
* #257 contract's "accepted = enqueued, not consumed" sentence;
|
|
4679
|
+
* `detail: { steer, taskId? }` / `{ followUp, taskId? }`. Per-run, at most once per family
|
|
4680
|
+
* (the terminal sweep is a single site).
|
|
4681
|
+
*
|
|
4672
4682
|
* Deliberately NOT a notice family: brain retry/reconnect liveness (a rate limit, a 5xx, a
|
|
4673
4683
|
* transient network failure being retried). Those are per-attempt liveness frames with their own
|
|
4674
4684
|
* frequency semantics and ride the wire `status` channel ({@link BrainStatus}), whose sink the
|
|
@@ -4702,6 +4712,16 @@ export declare function __resetMalformedNoticeSeatAnnouncement(): void;
|
|
|
4702
4712
|
* · an ABSENT seat prints the historic `console.warn` line verbatim (byte-compat loudness).
|
|
4703
4713
|
*/
|
|
4704
4714
|
export declare function deliverEngineNotice(onNotice: ((notice: EngineNotice) => void) | undefined, notice: EngineNotice): void;
|
|
4715
|
+
/**
|
|
4716
|
+
* #259 loud half, one notice PER FAMILY (`task.user_steer_undrained` / `task.user_followup_undrained`):
|
|
4717
|
+
* a consumer routing on `code` alone must never mistake a stranded follow-up for a stranded steer, so
|
|
4718
|
+
* the code carries exactly the semantics its name claims — the same two-key split the settled frame
|
|
4719
|
+
* uses. Pure (the terminal sweep race window is not constructible deterministically; this seam is).
|
|
4720
|
+
*/
|
|
4721
|
+
export declare function undrainedUserInputNotices(counts: {
|
|
4722
|
+
steer: number;
|
|
4723
|
+
followUp: number;
|
|
4724
|
+
}, taskId?: string): EngineNotice[];
|
|
4705
4725
|
/** Runtime dependencies shared across tasks. */
|
|
4706
4726
|
export interface RunnerDeps {
|
|
4707
4727
|
brain: Brain;
|
package/dist/core/types.js
CHANGED
|
@@ -49,3 +49,23 @@ export function deliverEngineNotice(onNotice, notice) {
|
|
|
49
49
|
}
|
|
50
50
|
console.warn(notice.message);
|
|
51
51
|
}
|
|
52
|
+
export function undrainedUserInputNotices(counts, taskId) {
|
|
53
|
+
const tid = taskId !== undefined ? { taskId } : {};
|
|
54
|
+
const tail = `accepted as "queued" were never consumed — the run ended first. They are NOT redelivered; re-send against a live run if still wanted.`;
|
|
55
|
+
const out = [];
|
|
56
|
+
if (counts.steer > 0) {
|
|
57
|
+
out.push({
|
|
58
|
+
code: "task.user_steer_undrained",
|
|
59
|
+
message: `${counts.steer} user steer(s) ${tail}`,
|
|
60
|
+
detail: { steer: counts.steer, ...tid },
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
if (counts.followUp > 0) {
|
|
64
|
+
out.push({
|
|
65
|
+
code: "task.user_followup_undrained",
|
|
66
|
+
message: `${counts.followUp} user follow-up(s) ${tail}`,
|
|
67
|
+
detail: { followUp: counts.followUp, ...tid },
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
@@ -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({
|
|
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
|
@@ -93,7 +93,7 @@ export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY
|
|
|
93
93
|
export { deploymentReadFaceClampNotice, resolveReadFace, type ReadFace, type ReadFaceInputs } from "./tools/fs/index.js";
|
|
94
94
|
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, } from "./core/write-protect.js";
|
|
95
95
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
|
|
96
|
-
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
|
|
96
|
+
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
|
|
97
97
|
export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
|
|
98
98
|
export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
|
|
99
99
|
export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./core/runner/prepare-task.js";
|
package/dist/index.js
CHANGED
|
@@ -73,7 +73,7 @@ export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY
|
|
|
73
73
|
export { deploymentReadFaceClampNotice, resolveReadFace } from "./tools/fs/index.js";
|
|
74
74
|
export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, } from "./core/write-protect.js";
|
|
75
75
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, } from "./core/tool-result-store.js";
|
|
76
|
-
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, } from "./core/checkpoint-store.js";
|
|
76
|
+
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, LEGACY_PENDING_STEER_INPUT_ID, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, } from "./core/checkpoint-store.js";
|
|
77
77
|
export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, } from "./core/usage-window-store.js";
|
|
78
78
|
export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
|
|
79
79
|
export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./core/runner/prepare-task.js";
|
|
@@ -2,6 +2,14 @@ import type { AgentTool, ExecutionEnv } from "../../internal/harness-types.js";
|
|
|
2
2
|
import type { ToolEffect } from "../../core/types.js";
|
|
3
3
|
import type { ReadDenyMatcher } from "./read-deny.js";
|
|
4
4
|
import type { ReadFace } from "./read-face.js";
|
|
5
|
+
/**
|
|
6
|
+
* The Grep card's details assembly, PURE over the engine text ({mode, offset} from the request) —
|
|
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). Follow-up #313 will
|
|
9
|
+
* replace the text parsing with structured rows from runGrep; until then this seam is the honesty
|
|
10
|
+
* boundary.
|
|
11
|
+
*/
|
|
12
|
+
export declare function grepDetailFields(text: string, mode: "files_with_matches" | "content" | "count", offset?: number): Record<string, unknown>;
|
|
5
13
|
export declare function createGrepTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
|
|
6
14
|
export declare function createGlobTool(env: ExecutionEnv, rootCanonical: string, additionalRoots?: readonly string[], readDeny?: ReadDenyMatcher, readFace?: ReadFace): AgentTool;
|
|
7
15
|
/** Static side-effect class of every hand tool, by name (design/44 §3). Used by prepare-task to (a) feed
|
|
@@ -2,6 +2,72 @@ import { Type } from "typebox";
|
|
|
2
2
|
import { defineTool, errorResult } from "../../core/tools.js";
|
|
3
3
|
import { resolveKey, violationText, violationDetails } from "./safety.js";
|
|
4
4
|
import { runGrepDetailed, runGlobDetailed, splitAbsoluteGlobPattern, invalidGlobTokens } from "./search.js";
|
|
5
|
+
export function grepDetailFields(text, mode, offset) {
|
|
6
|
+
const rows = text.startsWith("No matches.")
|
|
7
|
+
? []
|
|
8
|
+
: text
|
|
9
|
+
.split("\n")
|
|
10
|
+
.filter((l) => l.length > 0 &&
|
|
11
|
+
l !== "No matches." &&
|
|
12
|
+
!l.startsWith("…[") &&
|
|
13
|
+
!l.startsWith("[offset") &&
|
|
14
|
+
!l.startsWith("[note") &&
|
|
15
|
+
!l.startsWith("[!]") &&
|
|
16
|
+
!l.startsWith("<<<UNTRUSTED ") &&
|
|
17
|
+
!l.startsWith("<<<END UNTRUSTED "));
|
|
18
|
+
const contentPathOf = (l) => {
|
|
19
|
+
const m = /^(.*?):(\d+):/.exec(l);
|
|
20
|
+
return m ? m[1] : l;
|
|
21
|
+
};
|
|
22
|
+
const byteTruncated = text.includes("…[output truncated at ");
|
|
23
|
+
const truncatedFlag = byteTruncated ? { truncated: true } : {};
|
|
24
|
+
const capMarker = /^…\[capped at (\d+) of (\d+)\]$/m.exec(text);
|
|
25
|
+
const cappedAt = capMarker ? Number(capMarker[1]) : undefined;
|
|
26
|
+
const capTotal = capMarker ? Number(capMarker[2]) : undefined;
|
|
27
|
+
const appliedOffset = typeof offset === "number" && offset > 0 ? { appliedOffset: offset } : {};
|
|
28
|
+
const appliedLimit = cappedAt !== undefined ? { appliedLimit: cappedAt } : {};
|
|
29
|
+
let detailFields;
|
|
30
|
+
if (mode === "files_with_matches") {
|
|
31
|
+
detailFields = { filenames: rows, numFiles: rows.length, totalFiles: capTotal ?? rows.length, ...appliedLimit, ...appliedOffset };
|
|
32
|
+
}
|
|
33
|
+
else if (mode === "count") {
|
|
34
|
+
const filenames = [...new Set(rows.map((l) => (l.includes(":") ? l.slice(0, l.lastIndexOf(":")) : l)))];
|
|
35
|
+
let numMatches = 0;
|
|
36
|
+
let malformed = false;
|
|
37
|
+
for (const l of rows) {
|
|
38
|
+
const m = /:(\d+)$/.exec(l);
|
|
39
|
+
if (m)
|
|
40
|
+
numMatches += Number(m[1]);
|
|
41
|
+
else
|
|
42
|
+
malformed = true;
|
|
43
|
+
}
|
|
44
|
+
detailFields = {
|
|
45
|
+
filenames,
|
|
46
|
+
numFiles: filenames.length,
|
|
47
|
+
content: rows.join("\n"),
|
|
48
|
+
...(malformed || byteTruncated ? {} : { numMatches }),
|
|
49
|
+
...truncatedFlag,
|
|
50
|
+
...appliedLimit,
|
|
51
|
+
...appliedOffset,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const filenames = [...new Set(rows.map(contentPathOf))];
|
|
56
|
+
const joined = rows.join("\n");
|
|
57
|
+
const GREP_CONTENT_PREVIEW_CHARS = 16_000;
|
|
58
|
+
detailFields = {
|
|
59
|
+
filenames,
|
|
60
|
+
numFiles: filenames.length,
|
|
61
|
+
content: joined.length > GREP_CONTENT_PREVIEW_CHARS ? `${joined.slice(0, GREP_CONTENT_PREVIEW_CHARS)}\n…[truncated — full text in the tool output]` : joined,
|
|
62
|
+
numLines: rows.length,
|
|
63
|
+
...(byteTruncated ? {} : { totalLines: capTotal ?? rows.length }),
|
|
64
|
+
...truncatedFlag,
|
|
65
|
+
...appliedLimit,
|
|
66
|
+
...appliedOffset,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
return detailFields;
|
|
70
|
+
}
|
|
5
71
|
export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, readFace) {
|
|
6
72
|
return defineTool({
|
|
7
73
|
name: "Grep",
|
|
@@ -95,64 +161,7 @@ export function createGrepTool(env, rootCanonical, additionalRoots, readDeny, re
|
|
|
95
161
|
if (text.startsWith("Error (grep)") || text.startsWith("Error (Grep)"))
|
|
96
162
|
return errorResult(text);
|
|
97
163
|
const mode = a.output_mode ?? "files_with_matches";
|
|
98
|
-
const
|
|
99
|
-
? []
|
|
100
|
-
: text
|
|
101
|
-
.split("\n")
|
|
102
|
-
.filter((l) => l.length > 0 &&
|
|
103
|
-
!l.startsWith("…[") &&
|
|
104
|
-
!l.startsWith("[offset") &&
|
|
105
|
-
!l.startsWith("[note") &&
|
|
106
|
-
!l.startsWith("[!]") &&
|
|
107
|
-
!l.startsWith("<<<UNTRUSTED ") &&
|
|
108
|
-
!l.startsWith("<<<END UNTRUSTED "));
|
|
109
|
-
const contentPathOf = (l) => {
|
|
110
|
-
const m = /^(.*?):(\d+):/.exec(l);
|
|
111
|
-
return m ? m[1] : l;
|
|
112
|
-
};
|
|
113
|
-
const capMarker = /^…\[capped at (\d+) of (\d+)\]$/m.exec(text);
|
|
114
|
-
const cappedAt = capMarker ? Number(capMarker[1]) : undefined;
|
|
115
|
-
const capTotal = capMarker ? Number(capMarker[2]) : undefined;
|
|
116
|
-
const appliedOffset = typeof a.offset === "number" && a.offset > 0 ? { appliedOffset: a.offset } : {};
|
|
117
|
-
const appliedLimit = cappedAt !== undefined ? { appliedLimit: cappedAt } : {};
|
|
118
|
-
let detailFields;
|
|
119
|
-
if (mode === "files_with_matches") {
|
|
120
|
-
detailFields = { filenames: rows, numFiles: rows.length, totalFiles: capTotal ?? rows.length, ...appliedLimit, ...appliedOffset };
|
|
121
|
-
}
|
|
122
|
-
else if (mode === "count") {
|
|
123
|
-
const filenames = [...new Set(rows.map((l) => (l.includes(":") ? l.slice(0, l.lastIndexOf(":")) : l)))];
|
|
124
|
-
let numMatches = 0;
|
|
125
|
-
let malformed = false;
|
|
126
|
-
for (const l of rows) {
|
|
127
|
-
const m = /:(\d+)$/.exec(l);
|
|
128
|
-
if (m)
|
|
129
|
-
numMatches += Number(m[1]);
|
|
130
|
-
else
|
|
131
|
-
malformed = true;
|
|
132
|
-
}
|
|
133
|
-
detailFields = {
|
|
134
|
-
filenames,
|
|
135
|
-
numFiles: filenames.length,
|
|
136
|
-
content: rows.join("\n"),
|
|
137
|
-
...(malformed ? {} : { numMatches }),
|
|
138
|
-
...appliedLimit,
|
|
139
|
-
...appliedOffset,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
const filenames = [...new Set(rows.map(contentPathOf))];
|
|
144
|
-
const joined = rows.join("\n");
|
|
145
|
-
const GREP_CONTENT_PREVIEW_CHARS = 16_000;
|
|
146
|
-
detailFields = {
|
|
147
|
-
filenames,
|
|
148
|
-
numFiles: filenames.length,
|
|
149
|
-
content: joined.length > GREP_CONTENT_PREVIEW_CHARS ? `${joined.slice(0, GREP_CONTENT_PREVIEW_CHARS)}\n…[truncated — full text in the tool output]` : joined,
|
|
150
|
-
numLines: rows.length,
|
|
151
|
-
totalLines: capTotal ?? rows.length,
|
|
152
|
-
...appliedLimit,
|
|
153
|
-
...appliedOffset,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
164
|
+
const detailFields = grepDetailFields(text, mode, a.offset);
|
|
156
165
|
return {
|
|
157
166
|
content: text,
|
|
158
167
|
details: { type: "grep", mode, ...detailFields, ...(grepRun.degraded ?? {}), ...(grepRun.withheld !== undefined ? { withheld: grepRun.withheld } : {}) },
|
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import type { ExecutionEnv } from "../../internal/harness-types.js";
|
|
2
|
+
/** backlog #310 — the JS scanner's own output ceiling, in bytes of emitted content rows. Pinned to the
|
|
3
|
+
* ripgrep leg's figure ({@link MAX_EXEC_OUTPUT_BYTES}, the exec pipe's rolling-tail bound) so the two
|
|
4
|
+
* legs cannot disagree about how much one search may return; see the collection site in {@link jsGrep}
|
|
5
|
+
* for why this leg bounds the HEAD where the pipe bounds the tail. */
|
|
6
|
+
export declare const JS_GREP_OUTPUT_MAX_BYTES: number;
|
|
2
7
|
/** Directories never worth crawling (design/64 §10.3) — dependency/build/cache trees, PLUS the VCS
|
|
3
8
|
* metadata directories CC names explicitly (RB-200 F1, 220 @368402: `Ok_ = [".git",".svn",".hg",
|
|
4
9
|
* ".bzr",".jj",".sl"]` — git/svn/mercurial/bazaar/jujutsu/sapling). `.bzr`/`.jj`/`.sl` are additive here
|
|
@@ -153,6 +158,10 @@ export interface JsGrepGuards {
|
|
|
153
158
|
budgetMs?: number;
|
|
154
159
|
/** Longest line/content (chars) a gray-zone pattern may be matched against. */
|
|
155
160
|
longLineLimit?: number;
|
|
161
|
+
/** backlog #310 — bytes of content rows this scan may accumulate before it stops collecting and
|
|
162
|
+
* says so (default {@link JS_GREP_OUTPUT_MAX_BYTES}). A seam for the same reason the two above
|
|
163
|
+
* are: the boundary is pinnable at a testable size instead of only at an 8MB harness. */
|
|
164
|
+
outputMaxBytes?: number;
|
|
156
165
|
}
|
|
157
166
|
/** design/199 件B — what a traversal withheld under the sensitive-path read deny list, structured
|
|
158
167
|
* (the prose note is the model-facing twin). Three shapes (§3.1): `pruned_count` = the JS walker
|
|
@@ -208,6 +217,8 @@ export interface GrepRunResult {
|
|
|
208
217
|
* withheld / no deny judge in play. */
|
|
209
218
|
withheld?: ReadDenyWithheld;
|
|
210
219
|
}
|
|
220
|
+
/** The output modes the rg leg and {@link jsGrep} share. */
|
|
221
|
+
type GrepOutputMode = "content" | "files_with_matches" | "count";
|
|
211
222
|
/**
|
|
212
223
|
* backlog #303 (rescan-hardened form) — the rg leg's deny TRIPWIRE. rg's exclusion globs are spelled
|
|
213
224
|
* from the pattern text and cannot express the win32 component-alias family (`.aws.` / `.aws ` —
|
|
@@ -220,21 +231,31 @@ export interface GrepRunResult {
|
|
|
220
231
|
* ripgrep prints filenames verbatim, so a path component containing a NEWLINE splits one record
|
|
221
232
|
* across physical lines whose fragments carry no `:line:` boundary — the filter kept them and the
|
|
222
233
|
* 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.
|
|
224
|
-
*
|
|
225
|
-
* longer edits anything: it JUDGES. Any deny hit — or any line the format cannot account for —
|
|
234
|
+
* of a partial run fabricated a `No matches.` row inside the structured card. So this function no
|
|
235
|
+
* longer edits anything: it JUDGES. Any deny hit — or any record the format cannot account for —
|
|
226
236
|
* trips, and the caller abandons the rg run for the JS scanner, whose walk prunes with the
|
|
227
237
|
* authoritative judge and needs no path parsing at all. rg stays the fast path for the common case
|
|
228
238
|
* (no guarded entries in the result); the moment a guarded spelling is involved, the engine that
|
|
229
239
|
* cannot mis-parse it owns the answer.
|
|
230
240
|
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
241
|
+
* backlog #306 — what it judges is now ripgrep's OWN path field ({@link parseRgRecords}, `--null`),
|
|
242
|
+
* not a prefix cut at a guessed separator. Trip conditions, all three modes alike: a record whose
|
|
243
|
+
* path the deny judge names trips as `deny-hit`; a record with no path field at all (rg's binary-file
|
|
244
|
+
* notice, a no-filename row, an unterminated tail, output from an env that ignored `--null`) is
|
|
245
|
+
* unaccountable and trips as `ambiguous-record` — same abandonment, same reason code as before.
|
|
246
|
+
* `dropIncompleteTail` mirrors {@link parseRgRecords}: on the partial legs the cut tail is dropped
|
|
247
|
+
* BEFORE judging, because judging a record the caller will never receive can only cost an
|
|
248
|
+
* unnecessary rescan — every record that does reach the output is still judged, which is what the
|
|
249
|
+
* rule is for. (A killed stream ending mid-record is an EXPECTED shape with a rule of its own; the
|
|
250
|
+
* unaccountable arm is for records that survive that rule.)
|
|
251
|
+
* `--` group separators and empty lines carry no path by construction and pass. The `:digits:` /
|
|
252
|
+
* `-digits-` boundary family this used to reason about is gone with the guessing: both of #303's
|
|
253
|
+
* declared residuals (over-tripping on a cluster inside match TEXT, and the dedup key folding two
|
|
254
|
+
* denied files whose own names carry a cluster) were artifacts of that decision, not of the judge.
|
|
236
255
|
*/
|
|
237
|
-
export declare function rgOutputDenyTripwire(stdout: string, mode:
|
|
256
|
+
export declare function rgOutputDenyTripwire(stdout: string, mode: GrepOutputMode, judge: Pick<ReadDenyJudge, "matchPath">, opts?: {
|
|
257
|
+
dropIncompleteTail?: boolean;
|
|
258
|
+
}): {
|
|
238
259
|
trip: false;
|
|
239
260
|
} | {
|
|
240
261
|
trip: true;
|
|
@@ -285,3 +306,4 @@ export declare function runGlobDetailed(env: ExecutionEnv, root: string, pattern
|
|
|
285
306
|
error?: string;
|
|
286
307
|
withheld?: ReadDenyWithheld;
|
|
287
308
|
}>;
|
|
309
|
+
export {};
|